From b07d4630e9d713138f54307be079c55e5ae90b98 Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Sat, 18 Mar 2023 13:00:41 +0000 Subject: [PATCH 01/16] Initial skeleton for sclera --- CMakeLists.txt | 5 +++++ tools/CMakeLists.txt | 1 + tools/sclera/CMakeLists.txt | 3 +++ tools/sclera/main.cpp | 14 ++++++++++++++ 4 files changed, 23 insertions(+) create mode 100644 tools/CMakeLists.txt create mode 100644 tools/sclera/CMakeLists.txt create mode 100644 tools/sclera/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 17bfc9e2..fec8af86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ include(GenerateExportHeader) # set options for library option(IRIS_BUILD_UNIT_TESTS "whether to build unit tests" ON) +option(IRIS_BUILD_TOOLS "whether to build tools" ON) set(ASM_OPTIONS "-x assembler-with-cpp") @@ -146,6 +147,10 @@ add_subdirectory("shaders") add_subdirectory("src") add_subdirectory("samples") +if(IRIS_BUILD_TOOLS) + add_subdirectory("tools") +endif() + if(IRIS_BUILD_UNIT_TESTS) enable_testing() include(CTest) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 00000000..1ecfe76f --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory("sclera") diff --git a/tools/sclera/CMakeLists.txt b/tools/sclera/CMakeLists.txt new file mode 100644 index 00000000..e5a3a54b --- /dev/null +++ b/tools/sclera/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(sclera + main.cpp +) \ No newline at end of file diff --git a/tools/sclera/main.cpp b/tools/sclera/main.cpp new file mode 100644 index 00000000..904cd554 --- /dev/null +++ b/tools/sclera/main.cpp @@ -0,0 +1,14 @@ +//////////////////////////////////////////////////////////////////////////////// +// Distributed under the Boost Software License, Version 1.0. // +// (See accompanying file LICENSE or copy at // +// https://www.boost.org/LICENSE_1_0.txt) // +//////////////////////////////////////////////////////////////////////////////// + +#include + +int main() +{ + std::cout << "hello world\n"; + + return 0; +} From 5b5853d7bfefae2efc29bee67625a55a309f6f48 Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Sat, 18 Mar 2023 13:40:52 +0000 Subject: [PATCH 02/16] Basic rendering window --- CMakeLists.txt | 2 +- tools/sclera/CMakeLists.txt | 8 ++++- tools/sclera/main.cpp | 70 +++++++++++++++++++++++++++++++++++-- 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fec8af86..bfc05823 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.18) project( iris - VERSION "4.0.1" + VERSION "4.1.0" DESCRIPTION "Cross-platform game engine" LANGUAGES C CXX) diff --git a/tools/sclera/CMakeLists.txt b/tools/sclera/CMakeLists.txt index e5a3a54b..7049aaef 100644 --- a/tools/sclera/CMakeLists.txt +++ b/tools/sclera/CMakeLists.txt @@ -1,3 +1,9 @@ add_executable(sclera main.cpp -) \ No newline at end of file +) + +target_link_libraries(sclera iris) + +if(IRIS_PLATFORM MATCHES "WIN32") + set_target_properties(sclera PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreadedDebug") +endif() \ No newline at end of file diff --git a/tools/sclera/main.cpp b/tools/sclera/main.cpp index 904cd554..6ab813f8 100644 --- a/tools/sclera/main.cpp +++ b/tools/sclera/main.cpp @@ -5,10 +5,76 @@ //////////////////////////////////////////////////////////////////////////////// #include +#include +#include -int main() +#include "core/camera.h" +#include "core/colour.h" +#include "core/context.h" +#include "core/looper.h" +#include "core/start.h" +#include "events/event.h" +#include "events/keyboard_event.h" +#include "graphics/post_processing_description.h" +#include "graphics/render_pipeline.h" +#include "graphics/scene.h" +#include "graphics/texture_manager.h" +#include "graphics/window.h" +#include "graphics/window_manager.h" +#include "log/log.h" + +using namespace std::chrono_literals; + +void go(iris::Context ctx) { - std::cout << "hello world\n"; + LOG_INFO("sclera", "hello sclera"); + + auto *window = ctx.window_manager().create_window(1920, 1080); + iris::Camera camera{iris::CameraType::ORTHOGRAPHIC, window->width(), window->height()}; + + auto render_pipeline = std::make_unique( + ctx.material_manager(), ctx.mesh_manager(), ctx.render_target_manager(), window->width(), window->height()); + auto *scene = render_pipeline->create_scene(); + + ctx.texture_manager().blank_texture(); + auto sky_box = + ctx.texture_manager().create(iris::Colour{0.275f, 0.51f, 0.796f}, iris::Colour{0.5f, 0.5f, 0.5f}, 2048u, 2048u); + + auto *pass = render_pipeline->create_render_pass(scene); + pass->camera = &camera; + pass->sky_box = sky_box; + + window->set_render_pipeline(std::move(render_pipeline)); + + iris::Looper looper{ + 0ms, + 30ms, + [](auto, auto) { return true; }, + [window](auto, auto) + { + auto running = true; + auto event = window->pump_event(); + while (event) + { + if (event->is_quit() || event->is_key(iris::Key::ESCAPE)) + { + running = false; + } + + event = window->pump_event(); + } + + window->render(); + + return running; + }}; + + looper.run(); +} + +int main(int argc, char **argv) +{ + iris::start(argc, argv, go); return 0; } From 37b43f8208974e171f2432d7a05f9ee2917f2179 Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Sat, 18 Mar 2023 21:47:50 +0000 Subject: [PATCH 03/16] Add new methods to Window --- include/iris/graphics/window.h | 18 ++++++++++++++++++ src/graphics/window.cpp | 10 ++++++++++ 2 files changed, 28 insertions(+) diff --git a/include/iris/graphics/window.h b/include/iris/graphics/window.h index 98a12511..da42ab85 100644 --- a/include/iris/graphics/window.h +++ b/include/iris/graphics/window.h @@ -92,6 +92,24 @@ class Window */ void set_render_pipeline(std::unique_ptr render_pipeline); + /** + * Get the renderer object for the window. + * + * @returns + * Window renderer. + */ + Renderer *renderer() const; + + /** + * Set the renderer for the window. + * + * This method is really meant for setting the renderer before any rendering has happened. + * + * @param renderer + * New renderer. + */ + void set_renderer(std::unique_ptr renderer); + /** * Elapsed time since set_render_pipeline was called. This is also the value that is passed to shaders via TimeNode. * diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index eb3a0800..e29f0e2e 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -46,6 +46,16 @@ void Window::set_render_pipeline(std::unique_ptr render_pipeline renderer_->set_render_pipeline(std::move(render_pipeline)); } +Renderer *Window::renderer() const +{ + return renderer_.get(); +} + +void Window::set_renderer(std::unique_ptr renderer) +{ + renderer_ = std::move(renderer); +} + std::chrono::milliseconds Window::time() const { return renderer_->time(); From 1c9756a1001dd31c746603b96f78b2d21a273b77 Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Sat, 18 Mar 2023 21:48:14 +0000 Subject: [PATCH 04/16] Make MetalRender members protected --- include/iris/graphics/metal/metal_renderer.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/iris/graphics/metal/metal_renderer.h b/include/iris/graphics/metal/metal_renderer.h index af0de437..81ae62af 100644 --- a/include/iris/graphics/metal/metal_renderer.h +++ b/include/iris/graphics/metal/metal_renderer.h @@ -87,7 +87,6 @@ class MetalRenderer : public Renderer void execute_present(RenderCommand &command) override; void post_render() override; - private: /** * Internal struct encapsulating data needed for a frame. */ From 390cebc75c56e70352e7ce869b6609c2448ac693 Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Sat, 18 Mar 2023 21:54:51 +0000 Subject: [PATCH 05/16] wip --- tools/sclera/CMakeLists.txt | 22 ++- tools/sclera/main.cpp | 12 +- tools/sclera/metal_gui_renderer.h | 49 ++++++ tools/sclera/metal_gui_renderer.mm | 251 +++++++++++++++++++++++++++++ 4 files changed, 331 insertions(+), 3 deletions(-) create mode 100644 tools/sclera/metal_gui_renderer.h create mode 100644 tools/sclera/metal_gui_renderer.mm diff --git a/tools/sclera/CMakeLists.txt b/tools/sclera/CMakeLists.txt index 7049aaef..b073e66e 100644 --- a/tools/sclera/CMakeLists.txt +++ b/tools/sclera/CMakeLists.txt @@ -1,9 +1,29 @@ +FetchContent_Declare( + imgui + GIT_REPOSITORY https://github.com/ocornut/imgui + GIT_TAG 5a2b1e84828f192d30c91cdd210db41de8ad3236 + CONFIGURE_COMMAND "" BUILD_COMMAND "") +FetchContent_MakeAvailable(imgui) + add_executable(sclera + ${imgui_SOURCE_DIR}/backends/imgui_impl_metal.mm + ${imgui_SOURCE_DIR}/imgui.cpp + ${imgui_SOURCE_DIR}/imgui_demo.cpp + ${imgui_SOURCE_DIR}/imgui_draw.cpp + ${imgui_SOURCE_DIR}/imgui_tables.cpp + ${imgui_SOURCE_DIR}/imgui_widgets.cpp main.cpp + metal_gui_renderer.mm ) +target_include_directories(sclera SYSTEM + PRIVATE ${imgui_SOURCE_DIR}) + target_link_libraries(sclera iris) -if(IRIS_PLATFORM MATCHES "WIN32") +if(IRIS_PLATFORM MATCHES "MACOS") + target_compile_options(sclera PRIVATE -Wall -Werror -pedantic -glldb -fobjc-arc) +elseif(IRIS_PLATFORM MATCHES "WIN32") set_target_properties(sclera PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreadedDebug") + target_compile_options(sclera PRIVATE /W4 /WX) endif() \ No newline at end of file diff --git a/tools/sclera/main.cpp b/tools/sclera/main.cpp index 6ab813f8..dae24e3a 100644 --- a/tools/sclera/main.cpp +++ b/tools/sclera/main.cpp @@ -4,6 +4,7 @@ // https://www.boost.org/LICENSE_1_0.txt) // //////////////////////////////////////////////////////////////////////////////// +#include #include #include #include @@ -23,6 +24,8 @@ #include "graphics/window_manager.h" #include "log/log.h" +#include "metal_gui_renderer.h" + using namespace std::chrono_literals; void go(iris::Context ctx) @@ -30,6 +33,9 @@ void go(iris::Context ctx) LOG_INFO("sclera", "hello sclera"); auto *window = ctx.window_manager().create_window(1920, 1080); + window->set_renderer(std::make_unique( + ctx.texture_manager(), ctx.material_manager(), window->width(), window->height())); + iris::Camera camera{iris::CameraType::ORTHOGRAPHIC, window->width(), window->height()}; auto render_pipeline = std::make_unique( @@ -50,7 +56,7 @@ void go(iris::Context ctx) 0ms, 30ms, [](auto, auto) { return true; }, - [window](auto, auto) + [&](auto, auto) { auto running = true; @@ -62,6 +68,8 @@ void go(iris::Context ctx) running = false; } + static_cast(window->renderer())->handle_input(*event); + event = window->pump_event(); } @@ -75,6 +83,6 @@ void go(iris::Context ctx) int main(int argc, char **argv) { - iris::start(argc, argv, go); + iris::start(argc, argv, go, true); return 0; } diff --git a/tools/sclera/metal_gui_renderer.h b/tools/sclera/metal_gui_renderer.h new file mode 100644 index 00000000..4e499181 --- /dev/null +++ b/tools/sclera/metal_gui_renderer.h @@ -0,0 +1,49 @@ +//////////////////////////////////////////////////////////////////////////////// +// Distributed under the Boost Software License, Version 1.0. // +// (See accompanying file LICENSE or copy at // +// https://www.boost.org/LICENSE_1_0.txt) // +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include + +#include "events/event.h" +#include "graphics/material_manager.h" +#include "graphics/render_command.h" +#include "graphics/renderer.h" +#include "graphics/texture_manager.h" + +class MetalGuiRenderer : public iris::Renderer +{ + public: + MetalGuiRenderer( + iris::TextureManager &texture_manager, + iris::MaterialManager &material_manager, + std::uint32_t width, + std::uint32_t height); + ~MetalGuiRenderer(); + MetalGuiRenderer(const MetalGuiRenderer &) = delete; + MetalGuiRenderer &operator=(const MetalGuiRenderer &) = delete; + + // handlers for the supported RenderCommandTypes + + void pre_render() override; + void execute_pass_start(iris::RenderCommand &command) override; + void execute_draw(iris::RenderCommand &command) override; + void execute_pass_end(iris::RenderCommand &command) override; + void execute_present(iris::RenderCommand &command) override; + void post_render() override; + + void handle_input(iris::Event event); + + private: + void do_set_render_pipeline(std::function build_queue) override; + + struct implementation; + std::unique_ptr impl_; + + std::uint32_t width_; + std::uint32_t height_; +}; \ No newline at end of file diff --git a/tools/sclera/metal_gui_renderer.mm b/tools/sclera/metal_gui_renderer.mm new file mode 100644 index 00000000..b9227cd2 --- /dev/null +++ b/tools/sclera/metal_gui_renderer.mm @@ -0,0 +1,251 @@ +//////////////////////////////////////////////////////////////////////////////// +// Distributed under the Boost Software License, Version 1.0. // +// (See accompanying file LICENSE or copy at // +// https://www.boost.org/LICENSE_1_0.txt) // +//////////////////////////////////////////////////////////////////////////////// + +#include "metal_gui_renderer.h" + +#include +#include +#include +#include + +#include +#include + +#include "backends/imgui_impl_metal.h" +#include "core/macos/macos_ios_utility.h" +#include "events/mouse_button_event.h" +#include "graphics/metal/metal_renderer.h" +#include "graphics/render_command.h" +#include "graphics/renderer.h" +#include "graphics/texture_manager.h" +#include "imgui.h" +#include "log/log.h" + +namespace +{ +class ActualMetalGuiRenderer : public iris::MetalRenderer +{ + public: + ActualMetalGuiRenderer( + iris::TextureManager &texture_manager, + iris::MaterialManager &material_manager, + std::uint32_t width, + std::uint32_t height, + std::function execute_pass_end_hook) + : iris::MetalRenderer(texture_manager, material_manager, width, height) + , execute_pass_end_hook_(execute_pass_end_hook) + { + } + + ~ActualMetalGuiRenderer() + { + } + + void pre_render() override + { + iris::MetalRenderer::pre_render(); + } + + void execute_pass_start(iris::RenderCommand &command) override + { + iris::MetalRenderer::execute_pass_start(command); + } + + void execute_draw(iris::RenderCommand &command) override + { + iris::MetalRenderer::execute_draw(command); + } + + void execute_pass_end(iris::RenderCommand &command) override + { + execute_pass_end_hook_(); + iris::MetalRenderer::execute_pass_end(command); + } + + void execute_present(iris::RenderCommand &command) override + { + iris::MetalRenderer::execute_present(command); + } + + void post_render() override + { + iris::MetalRenderer::post_render(); + } + + void do_set_render_pipeline(std::function build_queue) override + { + iris::MetalRenderer::do_set_render_pipeline(build_queue); + } + + MTLRenderPassDescriptor *single_pass_descriptor() const + { + return single_pass_descriptor_; + } + + id command_buffer() const + { + return command_buffer_; + } + + id render_encoder() const + { + return render_encoder_; + } + + private: + std::function execute_pass_end_hook_; +}; + +} + +struct MetalGuiRenderer::implementation +{ + implementation() + { + IMGUI_CHECKVERSION(); + + ::ImGui::CreateContext(); + imgui_io = std::make_unique>(::ImGui::GetIO()); + } + + ImGuiIO &io() + { + return imgui_io->get(); + } + + std::unique_ptr> imgui_io; + id command_queue; + MTLRenderPassDescriptor *pass_descriptor; + std::unique_ptr renderer; +}; + +MetalGuiRenderer::MetalGuiRenderer( + iris::TextureManager &texture_manager, + iris::MaterialManager &material_manager, + std::uint32_t width, + std::uint32_t height) + : iris::Renderer(material_manager) + , impl_(std::make_unique()) + , width_(width) + , height_(height) +{ + impl_->io().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + impl_->io().ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + impl_->io().ConfigFlags |= ImGuiConfigFlags_NavEnableSetMousePos; + impl_->io().MouseDrawCursor = true; + impl_->io().DisplaySize = ImVec2(width_ * 2.0f, height * 2.0f); + impl_->io().DisplayFramebufferScale = ImVec2(2.0f, 2.0f); + + ::ImGui::StyleColorsDark(); + + unsigned char *tex_pixels = nullptr; + int tex_w, tex_h; + impl_->io().Fonts->GetTexDataAsRGBA32(&tex_pixels, &tex_w, &tex_h); + + auto device = iris::core::utility::metal_device(); + ::ImGui_ImplMetal_Init(device); + + impl_->command_queue = [device newCommandQueue]; + impl_->pass_descriptor = [MTLRenderPassDescriptor new]; + impl_->renderer = std::make_unique( + texture_manager, + material_manager, + width, + height, + [&] + { + impl_->io().DisplaySize = ImVec2(width_, height_); + impl_->io().DeltaTime = 1.0f / 30.0f; + + ::ImGui_ImplMetal_NewFrame(impl_->renderer->single_pass_descriptor()); + ::ImGui::NewFrame(); + + ::ImGui::ShowDemoWindow(nullptr); + + ::ImGui::Render(); + ::ImGui_ImplMetal_RenderDrawData( + ::ImGui::GetDrawData(), impl_->renderer->command_buffer(), impl_->renderer->render_encoder()); + }); +} + +MetalGuiRenderer::~MetalGuiRenderer() +{ + ::ImGui_ImplMetal_Shutdown(); + ::ImGui::DestroyContext(); +} + +void MetalGuiRenderer::pre_render() +{ + impl_->renderer->pre_render(); +} + +void MetalGuiRenderer::execute_pass_start(iris::RenderCommand &command) +{ + impl_->renderer->execute_pass_start(command); +} + +void MetalGuiRenderer::execute_draw(iris::RenderCommand &command) +{ + impl_->renderer->execute_draw(command); +} + +void MetalGuiRenderer::execute_pass_end(iris::RenderCommand &command) +{ + impl_->renderer->execute_pass_end(command); +} + +void MetalGuiRenderer::execute_present(iris::RenderCommand &command) +{ + impl_->renderer->execute_present(command); +} + +void MetalGuiRenderer::post_render() +{ + impl_->renderer->post_render(); +} + +void MetalGuiRenderer::handle_input(iris::Event event) +{ + static auto x = width_ / 2.0f; + static auto y = height_ / 2.0f; + + if (event.is_mouse()) + { + const auto mouse_event = event.mouse(); + x += mouse_event.delta_x; + y += mouse_event.delta_y; + + impl_->io().AddMousePosEvent(x, y); + } + else if (event.is_mouse_button()) + { + const auto mouse_button = event.mouse_button(); + std::optional imgui_button; + std::optional imgui_state; + + switch (mouse_button.button) + { + case iris::MouseButton::LEFT: imgui_button = 0; break; + case iris::MouseButton::RIGHT: imgui_button = 1; break; + } + + switch (mouse_button.state) + { + case iris::MouseButtonState::UP: imgui_state = false; break; + case iris::MouseButtonState::DOWN: imgui_state = true; break; + } + + if (imgui_button && imgui_state) + { + impl_->io().AddMouseButtonEvent(*imgui_button, *imgui_state); + } + } +} + +void MetalGuiRenderer::do_set_render_pipeline(std::function build_queue) +{ + impl_->renderer->do_set_render_pipeline(build_queue); +} From 1ecee956bc7ce2dd47d9411826ec56c282feebf6 Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Sun, 19 Mar 2023 21:30:59 +0000 Subject: [PATCH 06/16] wip --- include/iris/core/quaternion.h | 26 ++++++ tools/sclera/main.cpp | 112 +++++++++++++++++++++-- tools/sclera/metal_gui_renderer.h | 11 +-- tools/sclera/metal_gui_renderer.mm | 142 +++++++++++++++++++++++++++-- 4 files changed, 267 insertions(+), 24 deletions(-) diff --git a/include/iris/core/quaternion.h b/include/iris/core/quaternion.h index 550b1bff..22a45593 100644 --- a/include/iris/core/quaternion.h +++ b/include/iris/core/quaternion.h @@ -8,6 +8,8 @@ #include #include +#include +#include #include "core/utils.h" #include "core/vector3.h" @@ -406,6 +408,30 @@ class Quaternion return *this; } + std::tuple to_euler_angles() + { + float x_angle = 0.0f; + float y_angle = 0.0f; + float z_angle = 0.0f; + + // roll (x-axis rotation) + double sinr_cosp = 2 * (w * x + y * z); + double cosr_cosp = 1 - 2 * (x * x + y * y); + z_angle = std::atan2(sinr_cosp, cosr_cosp); + + // pitch (y-axis rotation) + double sinp = std::sqrt(1 + 2 * (w * y - x * z)); + double cosp = std::sqrt(1 - 2 * (w * y - x * z)); + y_angle = 2 * std::atan2(sinp, cosp) - std::numbers::pi_v / 2; + + //// yaw (z-axis rotation) + double siny_cosp = 2 * (w * z + x * y); + double cosy_cosp = 1 - 2 * (y * y + z * z); + x_angle = std::atan2(siny_cosp, cosy_cosp); + + return {x_angle, y_angle, z_angle}; + } + /** Angle of rotation. */ float w; diff --git a/tools/sclera/main.cpp b/tools/sclera/main.cpp index dae24e3a..b1f3565d 100644 --- a/tools/sclera/main.cpp +++ b/tools/sclera/main.cpp @@ -8,14 +8,18 @@ #include #include #include +#include #include "core/camera.h" #include "core/colour.h" #include "core/context.h" #include "core/looper.h" #include "core/start.h" +#include "core/vector3.h" #include "events/event.h" #include "events/keyboard_event.h" +#include "events/mouse_button_event.h" +#include "graphics/lights/directional_light.h" #include "graphics/post_processing_description.h" #include "graphics/render_pipeline.h" #include "graphics/scene.h" @@ -28,34 +32,105 @@ using namespace std::chrono_literals; +namespace +{ + +/** + * Helper function to update camera based on user input. + * + * @param camera + * Camera to update. + * + * @param key_map + * Map of user pressed keys. + */ +void update_camera(iris::Camera &camera, const std::unordered_map &key_map) +{ + static auto speed = 2.0f; + iris::Vector3 velocity; + + if (key_map.at(iris::Key::W) == iris::KeyState::DOWN) + { + velocity += camera.direction() * speed; + } + + if (key_map.at(iris::Key::S) == iris::KeyState::DOWN) + { + velocity -= camera.direction() * speed; + } + + if (key_map.at(iris::Key::A) == iris::KeyState::DOWN) + { + velocity -= camera.right() * speed; + } + + if (key_map.at(iris::Key::D) == iris::KeyState::DOWN) + { + velocity += camera.right() * speed; + } + + if (key_map.at(iris::Key::Q) == iris::KeyState::DOWN) + { + velocity += camera.right().cross(camera.direction()) * speed; + } + + if (key_map.at(iris::Key::E) == iris::KeyState::DOWN) + { + velocity -= camera.right().cross(camera.direction()) * speed; + } + + camera.translate(velocity); +} + void go(iris::Context ctx) { LOG_INFO("sclera", "hello sclera"); - auto *window = ctx.window_manager().create_window(1920, 1080); - window->set_renderer(std::make_unique( - ctx.texture_manager(), ctx.material_manager(), window->width(), window->height())); - - iris::Camera camera{iris::CameraType::ORTHOGRAPHIC, window->width(), window->height()}; + static constexpr auto width = 1920u; + static constexpr auto height = 1080u; auto render_pipeline = std::make_unique( - ctx.material_manager(), ctx.mesh_manager(), ctx.render_target_manager(), window->width(), window->height()); + ctx.material_manager(), ctx.mesh_manager(), ctx.render_target_manager(), width, height); auto *scene = render_pipeline->create_scene(); + scene->create_light(iris::Vector3{-1.0f}); + + auto *window = ctx.window_manager().create_window(1920, 1080); + window->set_renderer(std::make_unique(ctx, width, height, scene)); + + iris::Camera camera{iris::CameraType::PERSPECTIVE, width, height}; ctx.texture_manager().blank_texture(); auto sky_box = ctx.texture_manager().create(iris::Colour{0.275f, 0.51f, 0.796f}, iris::Colour{0.5f, 0.5f, 0.5f}, 2048u, 2048u); + iris::PostProcessingDescription post_processing_description{.colour_adjust = {iris::ColourAdjustDescription{}}}; + auto *pass = render_pipeline->create_render_pass(scene); pass->camera = &camera; pass->sky_box = sky_box; + pass->post_processing_description = post_processing_description; window->set_render_pipeline(std::move(render_pipeline)); + std::unordered_map key_map = { + {iris::Key::W, iris::KeyState::UP}, + {iris::Key::A, iris::KeyState::UP}, + {iris::Key::S, iris::KeyState::UP}, + {iris::Key::D, iris::KeyState::UP}, + {iris::Key::Q, iris::KeyState::UP}, + {iris::Key::E, iris::KeyState::UP}, + }; + + auto right_mouse_down = false; + iris::Looper looper{ 0ms, 30ms, - [](auto, auto) { return true; }, + [&](auto, auto) + { + update_camera(camera, key_map); + return true; + }, [&](auto, auto) { auto running = true; @@ -67,6 +142,27 @@ void go(iris::Context ctx) { running = false; } + else if (event->is_key()) + { + const auto keyboard = event->key(); + key_map[keyboard.key] = keyboard.state; + } + else if (event->is_mouse()) + { + static const auto sensitivity = 0.0025f; + const auto mouse = event->mouse(); + + if (right_mouse_down) + { + camera.adjust_yaw(mouse.delta_x * sensitivity); + camera.adjust_pitch(-mouse.delta_y * sensitivity); + } + } + else if (event->is_mouse_button((iris::MouseButton::RIGHT))) + { + const auto mouse_button = event->mouse_button(); + right_mouse_down = mouse_button.state == iris::MouseButtonState::DOWN; + } static_cast(window->renderer())->handle_input(*event); @@ -81,6 +177,8 @@ void go(iris::Context ctx) looper.run(); } +} + int main(int argc, char **argv) { iris::start(argc, argv, go, true); diff --git a/tools/sclera/metal_gui_renderer.h b/tools/sclera/metal_gui_renderer.h index 4e499181..99784408 100644 --- a/tools/sclera/metal_gui_renderer.h +++ b/tools/sclera/metal_gui_renderer.h @@ -9,20 +9,16 @@ #include #include +#include "core/context.h" #include "events/event.h" -#include "graphics/material_manager.h" #include "graphics/render_command.h" #include "graphics/renderer.h" -#include "graphics/texture_manager.h" +#include "graphics/scene.h" class MetalGuiRenderer : public iris::Renderer { public: - MetalGuiRenderer( - iris::TextureManager &texture_manager, - iris::MaterialManager &material_manager, - std::uint32_t width, - std::uint32_t height); + MetalGuiRenderer(iris::Context &ctx, std::uint32_t width, std::uint32_t height, iris::Scene *scene); ~MetalGuiRenderer(); MetalGuiRenderer(const MetalGuiRenderer &) = delete; MetalGuiRenderer &operator=(const MetalGuiRenderer &) = delete; @@ -46,4 +42,5 @@ class MetalGuiRenderer : public iris::Renderer std::uint32_t width_; std::uint32_t height_; + bool show_demo_; }; \ No newline at end of file diff --git a/tools/sclera/metal_gui_renderer.mm b/tools/sclera/metal_gui_renderer.mm index b9227cd2..f09d9e90 100644 --- a/tools/sclera/metal_gui_renderer.mm +++ b/tools/sclera/metal_gui_renderer.mm @@ -8,18 +8,28 @@ #include #include +#include #include #include #include #include +#include #include "backends/imgui_impl_metal.h" +#include "core/context.h" #include "core/macos/macos_ios_utility.h" +#include "core/quaternion.h" +#include "core/transform.h" +#include "core/vector3.h" +#include "events/keyboard_event.h" #include "events/mouse_button_event.h" #include "graphics/metal/metal_renderer.h" #include "graphics/render_command.h" +#include "graphics/render_entity_type.h" #include "graphics/renderer.h" +#include "graphics/scene.h" +#include "graphics/single_entity.h" #include "graphics/texture_manager.h" #include "imgui.h" #include "log/log.h" @@ -99,6 +109,11 @@ void do_set_render_pipeline(std::function build_queue) override std::function execute_pass_end_hook_; }; +std::string label_name(std::string_view label, std::uint32_t id) +{ + return std::string{label} + std::to_string(id); +} + } struct MetalGuiRenderer::implementation @@ -122,15 +137,12 @@ void do_set_render_pipeline(std::function build_queue) override std::unique_ptr renderer; }; -MetalGuiRenderer::MetalGuiRenderer( - iris::TextureManager &texture_manager, - iris::MaterialManager &material_manager, - std::uint32_t width, - std::uint32_t height) - : iris::Renderer(material_manager) +MetalGuiRenderer::MetalGuiRenderer(iris::Context &ctx, std::uint32_t width, std::uint32_t height, iris::Scene *scene) + : iris::Renderer(ctx.material_manager()) , impl_(std::make_unique()) , width_(width) , height_(height) + , show_demo_(false) { impl_->io().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; impl_->io().ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; @@ -151,19 +163,125 @@ void do_set_render_pipeline(std::function build_queue) override impl_->command_queue = [device newCommandQueue]; impl_->pass_descriptor = [MTLRenderPassDescriptor new]; impl_->renderer = std::make_unique( - texture_manager, - material_manager, + ctx.texture_manager(), + ctx.material_manager(), width, height, - [&] + [&, scene] { + static std::vector entities; + impl_->io().DisplaySize = ImVec2(width_, height_); impl_->io().DeltaTime = 1.0f / 30.0f; ::ImGui_ImplMetal_NewFrame(impl_->renderer->single_pass_descriptor()); ::ImGui::NewFrame(); - ::ImGui::ShowDemoWindow(nullptr); + if (show_demo_) + { + ::ImGui::ShowDemoWindow(nullptr); + } + + ::ImGui::Begin("Object creator", nullptr, ImGuiWindowFlags_None); + if (::ImGui::Button("Add Box")) + { + entities.push_back(scene->create_entity( + nullptr, ctx.mesh_manager().cube(iris::Colour{1.0f, 1.0f, 1.0f}), iris::Transform{{}, {}, {1.0f}})); + } + + auto counter = 0u; + for (auto *entity : entities) + { + if (::ImGui::TreeNode(label_name("Object", counter).c_str())) + { + auto position = entity->position(); + float pos_x = position.x; + float pos_y = position.y; + float pos_z = position.z; + + ::ImGui::LabelText("", "Position"); + { + ::ImGui::Text("X:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##px", counter).c_str(), &pos_x); + } + { + ::ImGui::Text("Y:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##py", counter).c_str(), &pos_y); + } + { + ::ImGui::Text("Z:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##pz", counter).c_str(), &pos_z); + } + + const iris::Vector3 new_position{pos_x, pos_y, pos_z}; + if (new_position != position) + { + entity->set_position(new_position); + } + + auto rotation = entity->orientation(); + auto [rot_x, rot_y, rot_z] = rotation.to_euler_angles(); + + ::ImGui::LabelText("", "Rotation"); + { + ::ImGui::Text("X:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##rx", counter).c_str(), &rot_x, 0.5f); + } + { + ::ImGui::Text("Y:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##ry", counter).c_str(), &rot_y, 0.5f); + } + { + ::ImGui::Text("Z:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##rz", counter).c_str(), &rot_z, 0.5f); + } + + const iris::Quaternion new_rotation{rot_x, rot_y, rot_z}; + if (new_rotation != rotation) + { + entity->set_orientation(new_rotation); + } + + auto scale = entity->scale(); + float scale_x = scale.x; + float scale_y = scale.y; + float scale_z = scale.z; + + ::ImGui::LabelText("", "Scale"); + { + ::ImGui::Text("X:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##sx", counter).c_str(), &scale_x); + } + { + ::ImGui::Text("Y:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##sy", counter).c_str(), &scale_y); + } + { + ::ImGui::Text("Z:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##sz", counter).c_str(), &scale_z); + } + + const iris::Vector3 new_scale{scale_x, scale_y, scale_z}; + if (new_scale != scale) + { + entity->set_scale(new_scale); + } + ::ImGui::TreePop(); + } + + ++counter; + } + + ::ImGui::End(); ::ImGui::Render(); ::ImGui_ImplMetal_RenderDrawData( @@ -243,6 +361,10 @@ void do_set_render_pipeline(std::function build_queue) override impl_->io().AddMouseButtonEvent(*imgui_button, *imgui_state); } } + else if (event.is_key(iris::Key::I, iris::KeyState::DOWN)) + { + show_demo_ = !show_demo_; + } } void MetalGuiRenderer::do_set_render_pipeline(std::function build_queue) From b3d8a8b666033f65c83d6e31ba10ba89f71755d1 Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Sat, 25 Mar 2023 21:21:15 +0000 Subject: [PATCH 07/16] working gizmo --- include/iris/core/camera.h | 2 +- include/iris/core/matrix4.h | 3 +- tools/sclera/CMakeLists.txt | 11 +++++- tools/sclera/main.cpp | 6 ++-- tools/sclera/metal_gui_renderer.h | 9 ++++- tools/sclera/metal_gui_renderer.mm | 56 ++++++++++++++++++++++++++++-- 6 files changed, 78 insertions(+), 9 deletions(-) diff --git a/include/iris/core/camera.h b/include/iris/core/camera.h index bb492ae3..195ae35c 100644 --- a/include/iris/core/camera.h +++ b/include/iris/core/camera.h @@ -45,7 +45,7 @@ class Camera * @param translate * Amount to translate. */ - void translate(const Vector3 &translat); + void translate(const Vector3 &translate); /** * Set the view matrix for the camera. diff --git a/include/iris/core/matrix4.h b/include/iris/core/matrix4.h index 20c50a36..e771ab3a 100644 --- a/include/iris/core/matrix4.h +++ b/include/iris/core/matrix4.h @@ -380,7 +380,8 @@ class Matrix4 { const auto e = elements_; - const auto calculate_cell = [&e, &matrix](std::size_t row_num, std::size_t col_num) { + const auto calculate_cell = [&e, &matrix](std::size_t row_num, std::size_t col_num) + { return (e[row_num + 0u] * matrix[col_num + 0u]) + (e[row_num + 1u] * matrix[col_num + 4u]) + (e[row_num + 2u] * matrix[col_num + 8u]) + (e[row_num + 3u] * matrix[col_num + 12u]); }; diff --git a/tools/sclera/CMakeLists.txt b/tools/sclera/CMakeLists.txt index b073e66e..2b81ed0e 100644 --- a/tools/sclera/CMakeLists.txt +++ b/tools/sclera/CMakeLists.txt @@ -5,6 +5,13 @@ FetchContent_Declare( CONFIGURE_COMMAND "" BUILD_COMMAND "") FetchContent_MakeAvailable(imgui) +FetchContent_Declare( + imgui_gizmo + GIT_REPOSITORY https://github.com/CedricGuillemet/ImGuizmo + GIT_TAG 1.83 + CONFIGURE_COMMAND "" BUILD_COMMAND "") +FetchContent_MakeAvailable(imgui_gizmo) + add_executable(sclera ${imgui_SOURCE_DIR}/backends/imgui_impl_metal.mm ${imgui_SOURCE_DIR}/imgui.cpp @@ -12,14 +19,16 @@ add_executable(sclera ${imgui_SOURCE_DIR}/imgui_draw.cpp ${imgui_SOURCE_DIR}/imgui_tables.cpp ${imgui_SOURCE_DIR}/imgui_widgets.cpp + ${imgui_gizmo_SOURCE_DIR}/ImGuizmo.cpp main.cpp metal_gui_renderer.mm ) target_include_directories(sclera SYSTEM - PRIVATE ${imgui_SOURCE_DIR}) + PRIVATE ${imgui_SOURCE_DIR} ${imgui_gizmo_SOURCE_DIR}) target_link_libraries(sclera iris) +target_compile_definitions(sclera PRIVATE "IMGUI_DEFINE_MATH_OPERATORS=1") if(IRIS_PLATFORM MATCHES "MACOS") target_compile_options(sclera PRIVATE -Wall -Werror -pedantic -glldb -fobjc-arc) diff --git a/tools/sclera/main.cpp b/tools/sclera/main.cpp index b1f3565d..c7803ad0 100644 --- a/tools/sclera/main.cpp +++ b/tools/sclera/main.cpp @@ -94,11 +94,11 @@ void go(iris::Context ctx) auto *scene = render_pipeline->create_scene(); scene->create_light(iris::Vector3{-1.0f}); - auto *window = ctx.window_manager().create_window(1920, 1080); - window->set_renderer(std::make_unique(ctx, width, height, scene)); - iris::Camera camera{iris::CameraType::PERSPECTIVE, width, height}; + auto *window = ctx.window_manager().create_window(1920, 1080); + window->set_renderer(std::make_unique(ctx, width, height, scene, camera)); + ctx.texture_manager().blank_texture(); auto sky_box = ctx.texture_manager().create(iris::Colour{0.275f, 0.51f, 0.796f}, iris::Colour{0.5f, 0.5f, 0.5f}, 2048u, 2048u); diff --git a/tools/sclera/metal_gui_renderer.h b/tools/sclera/metal_gui_renderer.h index 99784408..4a8b99ef 100644 --- a/tools/sclera/metal_gui_renderer.h +++ b/tools/sclera/metal_gui_renderer.h @@ -9,6 +9,7 @@ #include #include +#include "core/camera.h" #include "core/context.h" #include "events/event.h" #include "graphics/render_command.h" @@ -18,7 +19,12 @@ class MetalGuiRenderer : public iris::Renderer { public: - MetalGuiRenderer(iris::Context &ctx, std::uint32_t width, std::uint32_t height, iris::Scene *scene); + MetalGuiRenderer( + iris::Context &ctx, + std::uint32_t width, + std::uint32_t height, + iris::Scene *scene, + iris::Camera &camera); ~MetalGuiRenderer(); MetalGuiRenderer(const MetalGuiRenderer &) = delete; MetalGuiRenderer &operator=(const MetalGuiRenderer &) = delete; @@ -43,4 +49,5 @@ class MetalGuiRenderer : public iris::Renderer std::uint32_t width_; std::uint32_t height_; bool show_demo_; + iris::Camera &camera_; }; \ No newline at end of file diff --git a/tools/sclera/metal_gui_renderer.mm b/tools/sclera/metal_gui_renderer.mm index f09d9e90..5c347ac3 100644 --- a/tools/sclera/metal_gui_renderer.mm +++ b/tools/sclera/metal_gui_renderer.mm @@ -16,6 +16,9 @@ #include #include +#include "imgui.h" + +#include "ImGuizmo.h" #include "backends/imgui_impl_metal.h" #include "core/context.h" #include "core/macos/macos_ios_utility.h" @@ -31,7 +34,6 @@ #include "graphics/scene.h" #include "graphics/single_entity.h" #include "graphics/texture_manager.h" -#include "imgui.h" #include "log/log.h" namespace @@ -137,12 +139,18 @@ void do_set_render_pipeline(std::function build_queue) override std::unique_ptr renderer; }; -MetalGuiRenderer::MetalGuiRenderer(iris::Context &ctx, std::uint32_t width, std::uint32_t height, iris::Scene *scene) +MetalGuiRenderer::MetalGuiRenderer( + iris::Context &ctx, + std::uint32_t width, + std::uint32_t height, + iris::Scene *scene, + iris::Camera &camera) : iris::Renderer(ctx.material_manager()) , impl_(std::make_unique()) , width_(width) , height_(height) , show_demo_(false) + , camera_(camera) { impl_->io().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; impl_->io().ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; @@ -177,6 +185,9 @@ void do_set_render_pipeline(std::function build_queue) override ::ImGui_ImplMetal_NewFrame(impl_->renderer->single_pass_descriptor()); ::ImGui::NewFrame(); + ::ImGuizmo::SetOrthographic(false); + ::ImGuizmo::BeginFrame(); + if (show_demo_) { ::ImGui::ShowDemoWindow(nullptr); @@ -281,6 +292,47 @@ void do_set_render_pipeline(std::function build_queue) override ++counter; } + ::ImGuizmo::Enable(true); + ::ImGuizmo::SetRect(0, 0, impl_->io().DisplaySize.x, impl_->io().DisplaySize.y); + + static const float identityMatrix[16] = { + 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f}; + auto inv_view = iris::Matrix4::transpose(camera_.view()); + const auto inv_proj = iris::Matrix4::transpose(camera_.projection()); + ::ImGuizmo::DrawGrid(inv_view.data(), inv_proj.data(), identityMatrix, 1000.f); + + const iris::Transform transform{{}, {}, {1.0f}}; + ::ImGuizmo::DrawCubes(inv_view.data(), inv_proj.data(), transform.matrix().data(), 1); + if (!entities.empty()) + { + static float bounds[] = {-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f}; + auto transform = iris::Matrix4::transpose(entities[0]->transform()); + auto *transform_ptr = transform.data(); + ::ImGuizmo::Manipulate( + inv_view.data(), + inv_proj.data(), + ::ImGuizmo::TRANSLATE, + ::ImGuizmo::WORLD, + const_cast(transform_ptr), + nullptr, + nullptr, + bounds, + nullptr); + + entities[0]->set_transform(iris::Matrix4::transpose(transform)); + } + + // const auto viewManipulateRight = impl_->io().DisplaySize.x; + // const auto viewManipulateTop = 0.0f; + //::ImGuizmo::ViewManipulate( + // inv_view.data(), + // 200.0f, + // ImVec2(viewManipulateRight - 128, viewManipulateTop), + // ImVec2(128, 128), + // 0x10101010); + + // camera_.set_view(iris::Matrix4::invert(inv_view)); + ::ImGui::End(); ::ImGui::Render(); From ddcd8ab30ad69be1ac68e49228c9927f80d5c1a0 Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Sun, 26 Mar 2023 08:46:03 +0100 Subject: [PATCH 08/16] wip --- include/iris/core/camera.h | 2 ++ src/core/camera.cpp | 5 +++++ tools/sclera/main.cpp | 2 ++ tools/sclera/metal_gui_renderer.mm | 18 ++---------------- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/include/iris/core/camera.h b/include/iris/core/camera.h index 195ae35c..74e43099 100644 --- a/include/iris/core/camera.h +++ b/include/iris/core/camera.h @@ -39,6 +39,8 @@ class Camera */ Camera(CameraType type, std::uint32_t width, std::uint32_t height, std::uint32_t depth = 1000u); + void look_at(const Vector3 &target); + /** * Translate the camera. * diff --git a/src/core/camera.cpp b/src/core/camera.cpp index 565ff4f7..a7225b73 100644 --- a/src/core/camera.cpp +++ b/src/core/camera.cpp @@ -77,6 +77,11 @@ Camera::Camera(CameraType type, std::uint32_t width, std::uint32_t height, std:: LOG_ENGINE_INFO("camera", "constructed"); } +void Camera::look_at(const Vector3 &target) +{ + view_ = Matrix4::make_look_at(position_, target, up_); +} + void Camera::translate(const Vector3 &translate) { position_ += translate; diff --git a/tools/sclera/main.cpp b/tools/sclera/main.cpp index c7803ad0..c1e5fc70 100644 --- a/tools/sclera/main.cpp +++ b/tools/sclera/main.cpp @@ -95,6 +95,8 @@ void go(iris::Context ctx) scene->create_light(iris::Vector3{-1.0f}); iris::Camera camera{iris::CameraType::PERSPECTIVE, width, height}; + camera.translate({0.0f, 10.0f, 0.0f}); + camera.look_at({}); auto *window = ctx.window_manager().create_window(1920, 1080); window->set_renderer(std::make_unique(ctx, width, height, scene, camera)); diff --git a/tools/sclera/metal_gui_renderer.mm b/tools/sclera/metal_gui_renderer.mm index 5c347ac3..02146da1 100644 --- a/tools/sclera/metal_gui_renderer.mm +++ b/tools/sclera/metal_gui_renderer.mm @@ -299,13 +299,10 @@ void do_set_render_pipeline(std::function build_queue) override 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f}; auto inv_view = iris::Matrix4::transpose(camera_.view()); const auto inv_proj = iris::Matrix4::transpose(camera_.projection()); - ::ImGuizmo::DrawGrid(inv_view.data(), inv_proj.data(), identityMatrix, 1000.f); + ::ImGuizmo::DrawGrid(inv_view.data(), inv_proj.data(), identityMatrix, 100.f); - const iris::Transform transform{{}, {}, {1.0f}}; - ::ImGuizmo::DrawCubes(inv_view.data(), inv_proj.data(), transform.matrix().data(), 1); if (!entities.empty()) { - static float bounds[] = {-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f}; auto transform = iris::Matrix4::transpose(entities[0]->transform()); auto *transform_ptr = transform.data(); ::ImGuizmo::Manipulate( @@ -316,23 +313,12 @@ void do_set_render_pipeline(std::function build_queue) override const_cast(transform_ptr), nullptr, nullptr, - bounds, + nullptr, nullptr); entities[0]->set_transform(iris::Matrix4::transpose(transform)); } - // const auto viewManipulateRight = impl_->io().DisplaySize.x; - // const auto viewManipulateTop = 0.0f; - //::ImGuizmo::ViewManipulate( - // inv_view.data(), - // 200.0f, - // ImVec2(viewManipulateRight - 128, viewManipulateTop), - // ImVec2(128, 128), - // 0x10101010); - - // camera_.set_view(iris::Matrix4::invert(inv_view)); - ::ImGui::End(); ::ImGui::Render(); From 5505e81413075b52d94c011bb511c29db81ff55a Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Sun, 26 Mar 2023 11:24:54 +0100 Subject: [PATCH 09/16] initial gui class --- tools/sclera/CMakeLists.txt | 2 + tools/sclera/gui.cpp | 101 +++++++ tools/sclera/gui.h | 30 ++ tools/sclera/main.cpp | 2 +- tools/sclera/metal_gui.h | 36 +++ tools/sclera/metal_gui.mm | 49 +++ tools/sclera/metal_gui_renderer.h | 16 +- tools/sclera/metal_gui_renderer.mm | 470 +++++++++++++++-------------- 8 files changed, 464 insertions(+), 242 deletions(-) create mode 100644 tools/sclera/gui.cpp create mode 100644 tools/sclera/gui.h create mode 100644 tools/sclera/metal_gui.h create mode 100644 tools/sclera/metal_gui.mm diff --git a/tools/sclera/CMakeLists.txt b/tools/sclera/CMakeLists.txt index 2b81ed0e..fb75c7fd 100644 --- a/tools/sclera/CMakeLists.txt +++ b/tools/sclera/CMakeLists.txt @@ -20,7 +20,9 @@ add_executable(sclera ${imgui_SOURCE_DIR}/imgui_tables.cpp ${imgui_SOURCE_DIR}/imgui_widgets.cpp ${imgui_gizmo_SOURCE_DIR}/ImGuizmo.cpp + gui.cpp main.cpp + metal_gui.mm metal_gui_renderer.mm ) diff --git a/tools/sclera/gui.cpp b/tools/sclera/gui.cpp new file mode 100644 index 00000000..69f7c8a2 --- /dev/null +++ b/tools/sclera/gui.cpp @@ -0,0 +1,101 @@ +//////////////////////////////////////////////////////////////////////////////// +// Distributed under the Boost Software License, Version 1.0. // +// (See accompanying file LICENSE or copy at // +// https://www.boost.org/LICENSE_1_0.txt) // +//////////////////////////////////////////////////////////////////////////////// + +#include "gui.h" + +#include "core/auto_release.h" +#include "graphics/window.h" +#include "imgui.h" + +namespace +{ + +auto create_imgui_context() +{ + IMGUI_CHECKVERSION(); + + return iris::AutoRelease<::ImGuiContext *, nullptr>{::ImGui::CreateContext(), ::ImGui::DestroyContext}; +} + +} + +Gui::Gui(const iris::Window *window) + : ctx_(create_imgui_context()) + , io_(::ImGui::GetIO()) + , window_(window) +{ + const auto scale = window_->screen_scale(); + + io_.ConfigFlags |= ::ImGuiConfigFlags_NavEnableKeyboard; + io_.ConfigFlags |= ::ImGuiConfigFlags_NavEnableGamepad; + io_.ConfigFlags |= ::ImGuiConfigFlags_NavEnableSetMousePos; + io_.MouseDrawCursor = true; + io_.DisplaySize = + ::ImVec2(static_cast(window_->width() * scale), static_cast(window_->height() * scale)); + io_.DisplayFramebufferScale = ::ImVec2(static_cast(scale), static_cast(scale)); + io_.DeltaTime = 1.0f / 30.0f; + + ::ImGui::StyleColorsDark(); + + unsigned char *tex_pixels = nullptr; + auto tex_w = 0; + auto tex_h = 0; + io_.Fonts->GetTexDataAsRGBA32(&tex_pixels, &tex_w, &tex_h); +} + +void Gui::render() +{ + pre_render(); + ::ImGui::NewFrame(); + + ::ImGui::ShowDemoWindow(nullptr); + + //::ImGui::End(); + ::ImGui::Render(); + post_render(); +} + +void Gui::handle_input(const iris::Event &event) +{ + static auto x = window_->width() / 2.0f; + static auto y = window_->height() / 2.0f; + + if (event.is_mouse()) + { + const auto mouse_event = event.mouse(); + x += mouse_event.delta_x; + y += mouse_event.delta_y; + + io_.AddMousePosEvent(x, y); + } + else if (event.is_mouse_button()) + { + const auto mouse_button = event.mouse_button(); + std::optional imgui_button; + std::optional imgui_state; + + switch (mouse_button.button) + { + case iris::MouseButton::LEFT: imgui_button = 0; break; + case iris::MouseButton::RIGHT: imgui_button = 1; break; + } + + switch (mouse_button.state) + { + case iris::MouseButtonState::UP: imgui_state = false; break; + case iris::MouseButtonState::DOWN: imgui_state = true; break; + } + + if (imgui_button && imgui_state) + { + io_.AddMouseButtonEvent(*imgui_button, *imgui_state); + } + } + else if (event.is_key(iris::Key::I, iris::KeyState::DOWN)) + { + // show_demo_ = !show_demo_; + } +} diff --git a/tools/sclera/gui.h b/tools/sclera/gui.h new file mode 100644 index 00000000..38059fd3 --- /dev/null +++ b/tools/sclera/gui.h @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// Distributed under the Boost Software License, Version 1.0. // +// (See accompanying file LICENSE or copy at // +// https://www.boost.org/LICENSE_1_0.txt) // +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "core/auto_release.h" +#include "events/event.h" +#include "graphics/window.h" +#include "imgui.h" + +class Gui +{ + public: + Gui(const iris::Window *window); + virtual ~Gui() = default; + void render(); + + void handle_input(const iris::Event &event); + + protected: + virtual void pre_render() = 0; + virtual void post_render() = 0; + + iris::AutoRelease<::ImGuiContext *, nullptr> ctx_; + ::ImGuiIO &io_; + const iris::Window *window_; +}; diff --git a/tools/sclera/main.cpp b/tools/sclera/main.cpp index c1e5fc70..47692aaa 100644 --- a/tools/sclera/main.cpp +++ b/tools/sclera/main.cpp @@ -99,7 +99,7 @@ void go(iris::Context ctx) camera.look_at({}); auto *window = ctx.window_manager().create_window(1920, 1080); - window->set_renderer(std::make_unique(ctx, width, height, scene, camera)); + window->set_renderer(std::make_unique(ctx, window, scene, camera)); ctx.texture_manager().blank_texture(); auto sky_box = diff --git a/tools/sclera/metal_gui.h b/tools/sclera/metal_gui.h new file mode 100644 index 00000000..63f3643c --- /dev/null +++ b/tools/sclera/metal_gui.h @@ -0,0 +1,36 @@ +//////////////////////////////////////////////////////////////////////////////// +// Distributed under the Boost Software License, Version 1.0. // +// (See accompanying file LICENSE or copy at // +// https://www.boost.org/LICENSE_1_0.txt) // +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "gui.h" + +#include + +#include + +#include "graphics/window.h" + +class MetalGui : public Gui +{ + public: + MetalGui( + const iris::Window *window, + MTLRenderPassDescriptor *pass_descriptor, + std::function()> get_command_buffer, + std::function()> get_render_encoder); + ~MetalGui() override; + + protected: + void pre_render() override; + void post_render() override; + + private: + id command_queue_; + MTLRenderPassDescriptor *pass_descriptor_; + std::function()> get_command_buffer_; + std::function()> get_render_encoder_; +}; diff --git a/tools/sclera/metal_gui.mm b/tools/sclera/metal_gui.mm new file mode 100644 index 00000000..50fdd5b1 --- /dev/null +++ b/tools/sclera/metal_gui.mm @@ -0,0 +1,49 @@ +//////////////////////////////////////////////////////////////////////////////// +// Distributed under the Boost Software License, Version 1.0. // +// (See accompanying file LICENSE or copy at // +// https://www.boost.org/LICENSE_1_0.txt) // +//////////////////////////////////////////////////////////////////////////////// + +#include "metal_gui.h" + +#include + +#include + +#include "imgui.h" + +#include "backends/imgui_impl_metal.h" +#include "core/macos/macos_ios_utility.h" +#include "graphics/window.h" + +MetalGui::MetalGui( + const iris::Window *window, + MTLRenderPassDescriptor *pass_descriptor, + std::function()> get_command_buffer, + std::function()> get_render_encoder) + : Gui(window) + , command_queue_(nullptr) + , pass_descriptor_(pass_descriptor) + , get_command_buffer_(get_command_buffer) + , get_render_encoder_(get_render_encoder) +{ + auto device = iris::core::utility::metal_device(); + ::ImGui_ImplMetal_Init(device); + + command_queue_ = [device newCommandQueue]; +} + +MetalGui::~MetalGui() +{ + ::ImGui_ImplMetal_Shutdown(); +} + +void MetalGui::pre_render() +{ + ::ImGui_ImplMetal_NewFrame(pass_descriptor_); +} + +void MetalGui::post_render() +{ + ::ImGui_ImplMetal_RenderDrawData(::ImGui::GetDrawData(), get_command_buffer_(), get_render_encoder_()); +} \ No newline at end of file diff --git a/tools/sclera/metal_gui_renderer.h b/tools/sclera/metal_gui_renderer.h index 4a8b99ef..cd12086d 100644 --- a/tools/sclera/metal_gui_renderer.h +++ b/tools/sclera/metal_gui_renderer.h @@ -15,16 +15,12 @@ #include "graphics/render_command.h" #include "graphics/renderer.h" #include "graphics/scene.h" +#include "graphics/window.h" class MetalGuiRenderer : public iris::Renderer { public: - MetalGuiRenderer( - iris::Context &ctx, - std::uint32_t width, - std::uint32_t height, - iris::Scene *scene, - iris::Camera &camera); + MetalGuiRenderer(iris::Context &ctx, const iris::Window *window, iris::Scene *scene, iris::Camera &camera); ~MetalGuiRenderer(); MetalGuiRenderer(const MetalGuiRenderer &) = delete; MetalGuiRenderer &operator=(const MetalGuiRenderer &) = delete; @@ -38,7 +34,7 @@ class MetalGuiRenderer : public iris::Renderer void execute_present(iris::RenderCommand &command) override; void post_render() override; - void handle_input(iris::Event event); + void handle_input(const iris::Event &event); private: void do_set_render_pipeline(std::function build_queue) override; @@ -46,8 +42,6 @@ class MetalGuiRenderer : public iris::Renderer struct implementation; std::unique_ptr impl_; - std::uint32_t width_; - std::uint32_t height_; - bool show_demo_; - iris::Camera &camera_; + const iris::Window *window_; + [[maybe_unused]] iris::Camera &camera_; }; \ No newline at end of file diff --git a/tools/sclera/metal_gui_renderer.mm b/tools/sclera/metal_gui_renderer.mm index 02146da1..c59bc40d 100644 --- a/tools/sclera/metal_gui_renderer.mm +++ b/tools/sclera/metal_gui_renderer.mm @@ -34,8 +34,11 @@ #include "graphics/scene.h" #include "graphics/single_entity.h" #include "graphics/texture_manager.h" +#include "graphics/window.h" #include "log/log.h" +#include "metal_gui.h" + namespace { class ActualMetalGuiRenderer : public iris::MetalRenderer @@ -111,226 +114,232 @@ void do_set_render_pipeline(std::function build_queue) override std::function execute_pass_end_hook_; }; -std::string label_name(std::string_view label, std::uint32_t id) -{ - return std::string{label} + std::to_string(id); -} +// std::string label_name(std::string_view label, std::uint32_t id) +//{ +// return std::string{label} + std::to_string(id); +// } } struct MetalGuiRenderer::implementation { - implementation() - { - IMGUI_CHECKVERSION(); - - ::ImGui::CreateContext(); - imgui_io = std::make_unique>(::ImGui::GetIO()); - } - - ImGuiIO &io() - { - return imgui_io->get(); - } - - std::unique_ptr> imgui_io; - id command_queue; - MTLRenderPassDescriptor *pass_descriptor; + // implementation() + //{ + // IMGUI_CHECKVERSION(); + + // ::ImGui::CreateContext(); + // imgui_io = std::make_unique>(::ImGui::GetIO()); + //} + + // ImGuiIO &io() + //{ + // return imgui_io->get(); + // } + + // std::unique_ptr> imgui_io; + // id command_queue; + // MTLRenderPassDescriptor *pass_descriptor; std::unique_ptr renderer; + std::unique_ptr gui; }; MetalGuiRenderer::MetalGuiRenderer( iris::Context &ctx, - std::uint32_t width, - std::uint32_t height, + const iris::Window *window, iris::Scene *scene, iris::Camera &camera) : iris::Renderer(ctx.material_manager()) , impl_(std::make_unique()) - , width_(width) - , height_(height) - , show_demo_(false) + , window_(window) , camera_(camera) { - impl_->io().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - impl_->io().ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; - impl_->io().ConfigFlags |= ImGuiConfigFlags_NavEnableSetMousePos; - impl_->io().MouseDrawCursor = true; - impl_->io().DisplaySize = ImVec2(width_ * 2.0f, height * 2.0f); - impl_->io().DisplayFramebufferScale = ImVec2(2.0f, 2.0f); + // impl_->io().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + // impl_->io().ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + // impl_->io().ConfigFlags |= ImGuiConfigFlags_NavEnableSetMousePos; + // impl_->io().MouseDrawCursor = true; + // impl_->io().DisplaySize = ImVec2(width_ * 2.0f, height * 2.0f); + // impl_->io().DisplayFramebufferScale = ImVec2(2.0f, 2.0f); - ::ImGui::StyleColorsDark(); + //::ImGui::StyleColorsDark(); - unsigned char *tex_pixels = nullptr; - int tex_w, tex_h; - impl_->io().Fonts->GetTexDataAsRGBA32(&tex_pixels, &tex_w, &tex_h); + // unsigned char *tex_pixels = nullptr; + // int tex_w, tex_h; + // impl_->io().Fonts->GetTexDataAsRGBA32(&tex_pixels, &tex_w, &tex_h); - auto device = iris::core::utility::metal_device(); - ::ImGui_ImplMetal_Init(device); + // auto device = iris::core::utility::metal_device(); + //::ImGui_ImplMetal_Init(device); - impl_->command_queue = [device newCommandQueue]; - impl_->pass_descriptor = [MTLRenderPassDescriptor new]; + // impl_->command_queue = [device newCommandQueue]; + // impl_->pass_descriptor = [MTLRenderPassDescriptor new]; impl_->renderer = std::make_unique( ctx.texture_manager(), ctx.material_manager(), - width, - height, - [&, scene] + window_->width(), + window_->height(), + [this] { - static std::vector entities; - - impl_->io().DisplaySize = ImVec2(width_, height_); - impl_->io().DeltaTime = 1.0f / 30.0f; - - ::ImGui_ImplMetal_NewFrame(impl_->renderer->single_pass_descriptor()); - ::ImGui::NewFrame(); - - ::ImGuizmo::SetOrthographic(false); - ::ImGuizmo::BeginFrame(); - - if (show_demo_) - { - ::ImGui::ShowDemoWindow(nullptr); - } - - ::ImGui::Begin("Object creator", nullptr, ImGuiWindowFlags_None); - if (::ImGui::Button("Add Box")) - { - entities.push_back(scene->create_entity( - nullptr, ctx.mesh_manager().cube(iris::Colour{1.0f, 1.0f, 1.0f}), iris::Transform{{}, {}, {1.0f}})); - } - - auto counter = 0u; - for (auto *entity : entities) - { - if (::ImGui::TreeNode(label_name("Object", counter).c_str())) - { - auto position = entity->position(); - float pos_x = position.x; - float pos_y = position.y; - float pos_z = position.z; - - ::ImGui::LabelText("", "Position"); - { - ::ImGui::Text("X:"); - ::ImGui::SameLine(); - ::ImGui::DragFloat(label_name("##px", counter).c_str(), &pos_x); - } - { - ::ImGui::Text("Y:"); - ::ImGui::SameLine(); - ::ImGui::DragFloat(label_name("##py", counter).c_str(), &pos_y); - } - { - ::ImGui::Text("Z:"); - ::ImGui::SameLine(); - ::ImGui::DragFloat(label_name("##pz", counter).c_str(), &pos_z); - } - - const iris::Vector3 new_position{pos_x, pos_y, pos_z}; - if (new_position != position) - { - entity->set_position(new_position); - } - - auto rotation = entity->orientation(); - auto [rot_x, rot_y, rot_z] = rotation.to_euler_angles(); - - ::ImGui::LabelText("", "Rotation"); - { - ::ImGui::Text("X:"); - ::ImGui::SameLine(); - ::ImGui::DragFloat(label_name("##rx", counter).c_str(), &rot_x, 0.5f); - } - { - ::ImGui::Text("Y:"); - ::ImGui::SameLine(); - ::ImGui::DragFloat(label_name("##ry", counter).c_str(), &rot_y, 0.5f); - } - { - ::ImGui::Text("Z:"); - ::ImGui::SameLine(); - ::ImGui::DragFloat(label_name("##rz", counter).c_str(), &rot_z, 0.5f); - } - - const iris::Quaternion new_rotation{rot_x, rot_y, rot_z}; - if (new_rotation != rotation) - { - entity->set_orientation(new_rotation); - } - - auto scale = entity->scale(); - float scale_x = scale.x; - float scale_y = scale.y; - float scale_z = scale.z; - - ::ImGui::LabelText("", "Scale"); - { - ::ImGui::Text("X:"); - ::ImGui::SameLine(); - ::ImGui::DragFloat(label_name("##sx", counter).c_str(), &scale_x); - } - { - ::ImGui::Text("Y:"); - ::ImGui::SameLine(); - ::ImGui::DragFloat(label_name("##sy", counter).c_str(), &scale_y); - } - { - ::ImGui::Text("Z:"); - ::ImGui::SameLine(); - ::ImGui::DragFloat(label_name("##sz", counter).c_str(), &scale_z); - } - - const iris::Vector3 new_scale{scale_x, scale_y, scale_z}; - if (new_scale != scale) - { - entity->set_scale(new_scale); - } - ::ImGui::TreePop(); - } - - ++counter; - } - - ::ImGuizmo::Enable(true); - ::ImGuizmo::SetRect(0, 0, impl_->io().DisplaySize.x, impl_->io().DisplaySize.y); - - static const float identityMatrix[16] = { - 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f}; - auto inv_view = iris::Matrix4::transpose(camera_.view()); - const auto inv_proj = iris::Matrix4::transpose(camera_.projection()); - ::ImGuizmo::DrawGrid(inv_view.data(), inv_proj.data(), identityMatrix, 100.f); - - if (!entities.empty()) - { - auto transform = iris::Matrix4::transpose(entities[0]->transform()); - auto *transform_ptr = transform.data(); - ::ImGuizmo::Manipulate( - inv_view.data(), - inv_proj.data(), - ::ImGuizmo::TRANSLATE, - ::ImGuizmo::WORLD, - const_cast(transform_ptr), - nullptr, - nullptr, - nullptr, - nullptr); - - entities[0]->set_transform(iris::Matrix4::transpose(transform)); - } - - ::ImGui::End(); - - ::ImGui::Render(); - ::ImGui_ImplMetal_RenderDrawData( - ::ImGui::GetDrawData(), impl_->renderer->command_buffer(), impl_->renderer->render_encoder()); + impl_->gui->render(); + // static std::vector entities; + + // impl_->io().DisplaySize = ImVec2(width_, height_); + // impl_->io().DeltaTime = 1.0f / 30.0f; + + //::ImGui_ImplMetal_NewFrame(impl_->renderer->single_pass_descriptor()); + //::ImGui::NewFrame(); + + // if (show_demo_) + //{ + // ::ImGui::ShowDemoWindow(nullptr); + // } + + //::ImGui::Begin("Object creator", nullptr, ImGuiWindowFlags_None); + // if (::ImGui::Button("Add Box")) + //{ + // entities.push_back(scene->create_entity( + // nullptr, ctx.mesh_manager().cube(iris::Colour{1.0f, 1.0f, 1.0f}), iris::Transform{{}, {}, + // {1.0f}})); + // } + + // auto counter = 0u; + // for (auto *entity : entities) + //{ + // if (::ImGui::TreeNode(label_name("Object", counter).c_str())) + // { + // auto position = entity->position(); + // float pos_x = position.x; + // float pos_y = position.y; + // float pos_z = position.z; + + // ::ImGui::LabelText("", "Position"); + // { + // ::ImGui::Text("X:"); + // ::ImGui::SameLine(); + // ::ImGui::DragFloat(label_name("##px", counter).c_str(), &pos_x); + // } + // { + // ::ImGui::Text("Y:"); + // ::ImGui::SameLine(); + // ::ImGui::DragFloat(label_name("##py", counter).c_str(), &pos_y); + // } + // { + // ::ImGui::Text("Z:"); + // ::ImGui::SameLine(); + // ::ImGui::DragFloat(label_name("##pz", counter).c_str(), &pos_z); + // } + + // const iris::Vector3 new_position{pos_x, pos_y, pos_z}; + // if (new_position != position) + // { + // entity->set_position(new_position); + // } + + // auto rotation = entity->orientation(); + // auto [rot_x, rot_y, rot_z] = rotation.to_euler_angles(); + + // ::ImGui::LabelText("", "Rotation"); + // { + // ::ImGui::Text("X:"); + // ::ImGui::SameLine(); + // ::ImGui::DragFloat(label_name("##rx", counter).c_str(), &rot_x, 0.5f); + // } + // { + // ::ImGui::Text("Y:"); + // ::ImGui::SameLine(); + // ::ImGui::DragFloat(label_name("##ry", counter).c_str(), &rot_y, 0.5f); + // } + // { + // ::ImGui::Text("Z:"); + // ::ImGui::SameLine(); + // ::ImGui::DragFloat(label_name("##rz", counter).c_str(), &rot_z, 0.5f); + // } + + // const iris::Quaternion new_rotation{rot_x, rot_y, rot_z}; + // if (new_rotation != rotation) + // { + // entity->set_orientation(new_rotation); + // } + + // auto scale = entity->scale(); + // float scale_x = scale.x; + // float scale_y = scale.y; + // float scale_z = scale.z; + + // ::ImGui::LabelText("", "Scale"); + // { + // ::ImGui::Text("X:"); + // ::ImGui::SameLine(); + // ::ImGui::DragFloat(label_name("##sx", counter).c_str(), &scale_x); + // } + // { + // ::ImGui::Text("Y:"); + // ::ImGui::SameLine(); + // ::ImGui::DragFloat(label_name("##sy", counter).c_str(), &scale_y); + // } + // { + // ::ImGui::Text("Z:"); + // ::ImGui::SameLine(); + // ::ImGui::DragFloat(label_name("##sz", counter).c_str(), &scale_z); + // } + + // const iris::Vector3 new_scale{scale_x, scale_y, scale_z}; + // if (new_scale != scale) + // { + // entity->set_scale(new_scale); + // } + // ::ImGui::TreePop(); + // } + + // ++counter; + //} + + //::ImGuizmo::SetOrthographic(false); + //::ImGuizmo::BeginFrame(); + + //::ImGuizmo::Enable(true); + //::ImGuizmo::SetRect(0, 0, impl_->io().DisplaySize.x, impl_->io().DisplaySize.y); + + // static const float identityMatrix[16] = { + // 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f}; + // auto inv_view = iris::Matrix4::transpose(camera_.view()); + // const auto inv_proj = iris::Matrix4::transpose(camera_.projection()); + //::ImGuizmo::DrawGrid(inv_view.data(), inv_proj.data(), identityMatrix, 100.f); + + // if (!entities.empty()) + //{ + // auto transform = iris::Matrix4::transpose(entities[0]->transform()); + // auto *transform_ptr = transform.data(); + // ::ImGuizmo::Manipulate( + // inv_view.data(), + // inv_proj.data(), + // ::ImGuizmo::TRANSLATE, + // ::ImGuizmo::WORLD, + // const_cast(transform_ptr), + // nullptr, + // nullptr, + // nullptr, + // nullptr); + + // entities[0]->set_transform(iris::Matrix4::transpose(transform)); + //} + + //::ImGui::End(); + + //::ImGui::Render(); + //::ImGui_ImplMetal_RenderDrawData( + // ::ImGui::GetDrawData(), impl_->renderer->command_buffer(), impl_->renderer->render_encoder()); }); + + impl_->gui = std::make_unique( + window_, + impl_->renderer->single_pass_descriptor(), + [this] { return impl_->renderer->command_buffer(); }, + [this] { return impl_->renderer->render_encoder(); }); } MetalGuiRenderer::~MetalGuiRenderer() { - ::ImGui_ImplMetal_Shutdown(); - ::ImGui::DestroyContext(); + //::ImGui_ImplMetal_Shutdown(); + //::ImGui::DestroyContext(); } void MetalGuiRenderer::pre_render() @@ -363,46 +372,47 @@ void do_set_render_pipeline(std::function build_queue) override impl_->renderer->post_render(); } -void MetalGuiRenderer::handle_input(iris::Event event) +void MetalGuiRenderer::handle_input(const iris::Event &event) { - static auto x = width_ / 2.0f; - static auto y = height_ / 2.0f; - - if (event.is_mouse()) - { - const auto mouse_event = event.mouse(); - x += mouse_event.delta_x; - y += mouse_event.delta_y; - - impl_->io().AddMousePosEvent(x, y); - } - else if (event.is_mouse_button()) - { - const auto mouse_button = event.mouse_button(); - std::optional imgui_button; - std::optional imgui_state; - - switch (mouse_button.button) - { - case iris::MouseButton::LEFT: imgui_button = 0; break; - case iris::MouseButton::RIGHT: imgui_button = 1; break; - } - - switch (mouse_button.state) - { - case iris::MouseButtonState::UP: imgui_state = false; break; - case iris::MouseButtonState::DOWN: imgui_state = true; break; - } - - if (imgui_button && imgui_state) - { - impl_->io().AddMouseButtonEvent(*imgui_button, *imgui_state); - } - } - else if (event.is_key(iris::Key::I, iris::KeyState::DOWN)) - { - show_demo_ = !show_demo_; - } + impl_->gui->handle_input(event); + // static auto x = width_ / 2.0f; + // static auto y = height_ / 2.0f; + + // if (event.is_mouse()) + //{ + // const auto mouse_event = event.mouse(); + // x += mouse_event.delta_x; + // y += mouse_event.delta_y; + + // impl_->io().AddMousePosEvent(x, y); + //} + // else if (event.is_mouse_button()) + //{ + // const auto mouse_button = event.mouse_button(); + // std::optional imgui_button; + // std::optional imgui_state; + + // switch (mouse_button.button) + // { + // case iris::MouseButton::LEFT: imgui_button = 0; break; + // case iris::MouseButton::RIGHT: imgui_button = 1; break; + // } + + // switch (mouse_button.state) + // { + // case iris::MouseButtonState::UP: imgui_state = false; break; + // case iris::MouseButtonState::DOWN: imgui_state = true; break; + // } + + // if (imgui_button && imgui_state) + // { + // impl_->io().AddMouseButtonEvent(*imgui_button, *imgui_state); + // } + //} + // else if (event.is_key(iris::Key::I, iris::KeyState::DOWN)) + //{ + // show_demo_ = !show_demo_; + //} } void MetalGuiRenderer::do_set_render_pipeline(std::function build_queue) From 63de023923738feac268c24050e7c069bcf52def Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Tue, 28 Mar 2023 19:31:25 +0100 Subject: [PATCH 10/16] wip --- tools/sclera/gui.cpp | 314 ++++++++++++++++++++++++++++- tools/sclera/gui.h | 15 +- tools/sclera/main.cpp | 4 +- tools/sclera/metal_gui.h | 7 + tools/sclera/metal_gui.mm | 8 +- tools/sclera/metal_gui_renderer.h | 2 +- tools/sclera/metal_gui_renderer.mm | 3 + 7 files changed, 339 insertions(+), 14 deletions(-) diff --git a/tools/sclera/gui.cpp b/tools/sclera/gui.cpp index 69f7c8a2..b0370d50 100644 --- a/tools/sclera/gui.cpp +++ b/tools/sclera/gui.cpp @@ -6,9 +6,17 @@ #include "gui.h" +#include +#include + +#include "imgui.h" + +#include "ImGuizmo.h" #include "core/auto_release.h" +#include "core/context.h" +#include "graphics/scene.h" +#include "graphics/single_entity.h" #include "graphics/window.h" -#include "imgui.h" namespace { @@ -20,12 +28,276 @@ auto create_imgui_context() return iris::AutoRelease<::ImGuiContext *, nullptr>{::ImGui::CreateContext(), ::ImGui::DestroyContext}; } +std::optional iris_to_imgui_key(iris::Key key) +{ + switch (key) + { + using enum iris::Key; + + case TAB: return ImGuiKey_Tab; + case LEFT_ARROW: return ImGuiKey_LeftArrow; + case RIGHT_ARROW: return ImGuiKey_RightArrow; + case UP_ARROW: return ImGuiKey_UpArrow; + case DOWN_ARROW: return ImGuiKey_DownArrow; + case PAGE_UP: return ImGuiKey_PageUp; + case PAGE_DOWN: return ImGuiKey_PageDown; + case HOME: return ImGuiKey_Home; + case END: return ImGuiKey_End; + case SPACE: return ImGuiKey_Space; + case ESCAPE: return ImGuiKey_Escape; + case CONTROL: return ImGuiKey_LeftCtrl; + case SHIFT: return ImGuiKey_LeftShift; + case NUM_0: return ImGuiKey_0; + case NUM_1: return ImGuiKey_1; + case NUM_2: return ImGuiKey_2; + case NUM_3: return ImGuiKey_3; + case NUM_4: return ImGuiKey_4; + case NUM_5: return ImGuiKey_5; + case NUM_6: return ImGuiKey_6; + case NUM_7: return ImGuiKey_7; + case NUM_8: return ImGuiKey_8; + case NUM_9: return ImGuiKey_9; + case A: return ImGuiKey_A; + case B: return ImGuiKey_B; + case C: return ImGuiKey_C; + case D: return ImGuiKey_D; + case E: return ImGuiKey_E; + case F: return ImGuiKey_F; + case G: return ImGuiKey_G; + case H: return ImGuiKey_H; + case I: return ImGuiKey_I; + case J: return ImGuiKey_J; + case K: return ImGuiKey_K; + case L: return ImGuiKey_L; + case M: return ImGuiKey_M; + case N: return ImGuiKey_N; + case O: return ImGuiKey_O; + case P: return ImGuiKey_P; + case Q: return ImGuiKey_Q; + case R: return ImGuiKey_R; + case S: return ImGuiKey_S; + case T: return ImGuiKey_T; + case U: return ImGuiKey_U; + case V: return ImGuiKey_V; + case W: return ImGuiKey_W; + case X: return ImGuiKey_X; + case Y: return ImGuiKey_Y; + case Z: return ImGuiKey_Z; + case F1: return ImGuiKey_F1; + case F2: return ImGuiKey_F2; + case F3: return ImGuiKey_F3; + case F4: return ImGuiKey_F4; + case F5: return ImGuiKey_F5; + case F6: return ImGuiKey_F6; + case F7: return ImGuiKey_F7; + case F8: return ImGuiKey_F8; + case F9: return ImGuiKey_F9; + case F10: return ImGuiKey_F10; + case F11: return ImGuiKey_F11; + case F12: return ImGuiKey_F12; + case COMMA: return ImGuiKey_Comma; + case MINUS: return ImGuiKey_Minus; + case PERIOD: return ImGuiKey_Period; + case SLASH: return ImGuiKey_Slash; + case SEMI_COLON: return ImGuiKey_Semicolon; + case EQUAL: return ImGuiKey_Equal; + case LEFT_BRACKET: return ImGuiKey_LeftBracket; + case BACKSLASH: return ImGuiKey_Backslash; + case RIGHT_BRACKET: return ImGuiKey_RightBracket; + case CAPS_LOCK: return ImGuiKey_CapsLock; + case KEYPAD_0: return ImGuiKey_Keypad0; + case KEYPAD_1: return ImGuiKey_Keypad1; + case KEYPAD_2: return ImGuiKey_Keypad2; + case KEYPAD_3: return ImGuiKey_Keypad3; + case KEYPAD_4: return ImGuiKey_Keypad4; + case KEYPAD_5: return ImGuiKey_Keypad5; + case KEYPAD_6: return ImGuiKey_Keypad6; + case KEYPAD_7: return ImGuiKey_Keypad7; + case KEYPAD_8: return ImGuiKey_Keypad8; + case KEYPAD_9: return ImGuiKey_Keypad9; + case KEYPAD_DECIMAL: return ImGuiKey_KeypadDecimal; + case KEYPAD_DIVIDE: return ImGuiKey_KeypadDivide; + case KEYPAD_MULTIPLY: return ImGuiKey_KeypadMultiply; + case KEYPAD_MINUS: return ImGuiKey_KeypadSubtract; + case KEYPAD_PLUS: return ImGuiKey_KeypadAdd; + case KEYPAD_ENTER: return ImGuiKey_KeypadEnter; + default: return std::nullopt; + } +} + +struct AutoBegin +{ + template + AutoBegin(Args &&...args) + { + ::ImGui::Begin(std::forward(args)...); + } + + ~AutoBegin() + { + ::ImGui::End(); + } +}; + +std::string label_name(std::string_view label, std::uint32_t id) +{ + return std::string{label} + std::to_string(id); +} + +void object_creator_ui(iris::Context &ctx, std::vector &entities, iris::Scene *scene) +{ + if (::ImGui::Button("Add Box")) + { + entities.push_back(scene->create_entity( + nullptr, ctx.mesh_manager().cube(iris::Colour{1.0f, 1.0f, 1.0f}), iris::Transform{{}, {}, {1.0f}})); + } +} + +void object_editor_tree_ui(std::vector &entities) +{ + auto counter = 0u; + for (auto *entity : entities) + { + if (::ImGui::TreeNode(label_name("Object", counter).c_str())) + { + auto position = entity->position(); + float pos_x = position.x; + float pos_y = position.y; + float pos_z = position.z; + + ::ImGui::LabelText("", "Position"); + { + ::ImGui::Text("X:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##px", counter).c_str(), &pos_x); + } + { + ::ImGui::Text("Y:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##py", counter).c_str(), &pos_y); + } + { + ::ImGui::Text("Z:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##pz", counter).c_str(), &pos_z); + } + + const iris::Vector3 new_position{pos_x, pos_y, pos_z}; + if (new_position != position) + { + entity->set_position(new_position); + } + + auto rotation = entity->orientation(); + auto [rot_x, rot_y, rot_z] = rotation.to_euler_angles(); + + ::ImGui::LabelText("", "Rotation"); + { + ::ImGui::Text("X:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##rx", counter).c_str(), &rot_x, 0.5f); + } + { + ::ImGui::Text("Y:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##ry", counter).c_str(), &rot_y, 0.5f); + } + { + ::ImGui::Text("Z:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##rz", counter).c_str(), &rot_z, 0.5f); + } + + const iris::Quaternion new_rotation{rot_x, rot_y, rot_z}; + if (new_rotation != rotation) + { + entity->set_orientation(new_rotation); + } + + auto scale = entity->scale(); + float scale_x = scale.x; + float scale_y = scale.y; + float scale_z = scale.z; + + ::ImGui::LabelText("", "Scale"); + { + ::ImGui::Text("X:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##sx", counter).c_str(), &scale_x); + } + { + ::ImGui::Text("Y:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##sy", counter).c_str(), &scale_y); + } + { + ::ImGui::Text("Z:"); + ::ImGui::SameLine(); + ::ImGui::DragFloat(label_name("##sz", counter).c_str(), &scale_z); + } + + const iris::Vector3 new_scale{scale_x, scale_y, scale_z}; + if (new_scale != scale) + { + entity->set_scale(new_scale); + } + ::ImGui::TreePop(); + } + + ++counter; + } +} + +void selected_object_gizmo_ui( + ::ImGuiIO &io, + const std::vector &entities, + const iris::Camera &camera) +{ + ::ImGuizmo::SetOrthographic(false); + ::ImGuizmo::BeginFrame(); + + ::ImGuizmo::Enable(true); + ::ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + + static const float identityMatrix[16] = { + 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f}; + auto inv_view = iris::Matrix4::transpose(camera.view()); + const auto inv_proj = iris::Matrix4::transpose(camera.projection()); + ::ImGuizmo::DrawGrid(inv_view.data(), inv_proj.data(), identityMatrix, 100.f); + + const auto m = iris::Matrix4::transpose(iris::Transform({}, {}, {1.0f}).matrix()); + ::ImGuizmo::DrawCubes(inv_view.data(), inv_proj.data(), m.data(), 1); + + if (!entities.empty()) + { + auto transform = iris::Matrix4::transpose(entities[0]->transform()); + auto *transform_ptr = transform.data(); + ::ImGuizmo::Manipulate( + inv_view.data(), + inv_proj.data(), + ::ImGuizmo::TRANSLATE, + ::ImGuizmo::WORLD, + const_cast(transform_ptr), + nullptr, + nullptr, + nullptr, + nullptr); + + entities[0]->set_transform(iris::Matrix4::transpose(transform)); + } } -Gui::Gui(const iris::Window *window) - : ctx_(create_imgui_context()) +} + +Gui::Gui(iris::Context &ctx, const iris::Window *window, iris::Scene *scene, iris::Camera &camera) + : iris_ctx_(ctx) + , imgui_ctx_(create_imgui_context()) , io_(::ImGui::GetIO()) , window_(window) + , scene_(scene) + , camera_(camera) + , entities_() + , show_demo_(false) { const auto scale = window_->screen_scale(); @@ -33,8 +305,7 @@ Gui::Gui(const iris::Window *window) io_.ConfigFlags |= ::ImGuiConfigFlags_NavEnableGamepad; io_.ConfigFlags |= ::ImGuiConfigFlags_NavEnableSetMousePos; io_.MouseDrawCursor = true; - io_.DisplaySize = - ::ImVec2(static_cast(window_->width() * scale), static_cast(window_->height() * scale)); + io_.DisplaySize = ::ImVec2(static_cast(window_->width()), static_cast(window_->height())); io_.DisplayFramebufferScale = ::ImVec2(static_cast(scale), static_cast(scale)); io_.DeltaTime = 1.0f / 30.0f; @@ -51,9 +322,19 @@ void Gui::render() pre_render(); ::ImGui::NewFrame(); - ::ImGui::ShowDemoWindow(nullptr); + if (show_demo_) + { + ::ImGui::ShowDemoWindow(nullptr); + } + + { + AutoBegin begin{"Object creator", nullptr, ImGuiWindowFlags_None}; + + object_creator_ui(iris_ctx_, entities_, scene_); + object_editor_tree_ui(entities_); + selected_object_gizmo_ui(io_, entities_, camera_); + } - //::ImGui::End(); ::ImGui::Render(); post_render(); } @@ -96,6 +377,23 @@ void Gui::handle_input(const iris::Event &event) } else if (event.is_key(iris::Key::I, iris::KeyState::DOWN)) { - // show_demo_ = !show_demo_; + } + else if (event.is_key()) + { + const auto key = event.key(); + if ((key.key == iris::Key::TAB) && (key.state == iris::KeyState::DOWN)) + { + show_demo_ = !show_demo_; + } + + if (const auto imgui_key = iris_to_imgui_key(key.key); imgui_key) + { + io_.AddKeyEvent(*imgui_key, key.state == iris::KeyState::DOWN); + } + } + else if (event.is_scroll_wheel()) + { + const auto scroll = event.scroll_wheel(); + io_.AddMouseWheelEvent(0.0f, scroll.delta_y); } } diff --git a/tools/sclera/gui.h b/tools/sclera/gui.h index 38059fd3..1e319386 100644 --- a/tools/sclera/gui.h +++ b/tools/sclera/gui.h @@ -6,15 +6,21 @@ #pragma once +#include + #include "core/auto_release.h" +#include "core/camera.h" +#include "core/context.h" #include "events/event.h" +#include "graphics/scene.h" +#include "graphics/single_entity.h" #include "graphics/window.h" #include "imgui.h" class Gui { public: - Gui(const iris::Window *window); + Gui(iris::Context &ctx, const iris::Window *window, iris::Scene *scene, iris::Camera &camera); virtual ~Gui() = default; void render(); @@ -24,7 +30,12 @@ class Gui virtual void pre_render() = 0; virtual void post_render() = 0; - iris::AutoRelease<::ImGuiContext *, nullptr> ctx_; + iris::Context &iris_ctx_; + iris::AutoRelease<::ImGuiContext *, nullptr> imgui_ctx_; ::ImGuiIO &io_; const iris::Window *window_; + iris::Scene *scene_; + iris::Camera &camera_; + std::vector entities_; + bool show_demo_; }; diff --git a/tools/sclera/main.cpp b/tools/sclera/main.cpp index 47692aaa..3717168b 100644 --- a/tools/sclera/main.cpp +++ b/tools/sclera/main.cpp @@ -95,8 +95,8 @@ void go(iris::Context ctx) scene->create_light(iris::Vector3{-1.0f}); iris::Camera camera{iris::CameraType::PERSPECTIVE, width, height}; - camera.translate({0.0f, 10.0f, 0.0f}); - camera.look_at({}); + // camera.translate({0.0f, 10.0f, 0.0f}); + // camera.look_at({}); auto *window = ctx.window_manager().create_window(1920, 1080); window->set_renderer(std::make_unique(ctx, window, scene, camera)); diff --git a/tools/sclera/metal_gui.h b/tools/sclera/metal_gui.h index 63f3643c..c9fbf392 100644 --- a/tools/sclera/metal_gui.h +++ b/tools/sclera/metal_gui.h @@ -12,13 +12,20 @@ #include +#include "core/camera.h" +#include "core/context.h" +#include "graphics/scene.h" +#include "graphics/single_entity.h" #include "graphics/window.h" class MetalGui : public Gui { public: MetalGui( + iris::Context &ctx, const iris::Window *window, + iris::Scene *scene, + iris::Camera &camera, MTLRenderPassDescriptor *pass_descriptor, std::function()> get_command_buffer, std::function()> get_render_encoder); diff --git a/tools/sclera/metal_gui.mm b/tools/sclera/metal_gui.mm index 50fdd5b1..8f9dc509 100644 --- a/tools/sclera/metal_gui.mm +++ b/tools/sclera/metal_gui.mm @@ -13,15 +13,21 @@ #include "imgui.h" #include "backends/imgui_impl_metal.h" +#include "core/context.h" #include "core/macos/macos_ios_utility.h" +#include "graphics/scene.h" +#include "graphics/single_entity.h" #include "graphics/window.h" MetalGui::MetalGui( + iris::Context &ctx, const iris::Window *window, + iris::Scene *scene, + iris::Camera &camera, MTLRenderPassDescriptor *pass_descriptor, std::function()> get_command_buffer, std::function()> get_render_encoder) - : Gui(window) + : Gui(ctx, window, scene, camera) , command_queue_(nullptr) , pass_descriptor_(pass_descriptor) , get_command_buffer_(get_command_buffer) diff --git a/tools/sclera/metal_gui_renderer.h b/tools/sclera/metal_gui_renderer.h index cd12086d..6073a133 100644 --- a/tools/sclera/metal_gui_renderer.h +++ b/tools/sclera/metal_gui_renderer.h @@ -43,5 +43,5 @@ class MetalGuiRenderer : public iris::Renderer std::unique_ptr impl_; const iris::Window *window_; - [[maybe_unused]] iris::Camera &camera_; + iris::Camera &camera_; }; \ No newline at end of file diff --git a/tools/sclera/metal_gui_renderer.mm b/tools/sclera/metal_gui_renderer.mm index c59bc40d..35ac6e54 100644 --- a/tools/sclera/metal_gui_renderer.mm +++ b/tools/sclera/metal_gui_renderer.mm @@ -330,7 +330,10 @@ void do_set_render_pipeline(std::function build_queue) override }); impl_->gui = std::make_unique( + ctx, window_, + scene, + camera_, impl_->renderer->single_pass_descriptor(), [this] { return impl_->renderer->command_buffer(); }, [this] { return impl_->renderer->render_encoder(); }); From 819c6c08332a1b9262ce1bfc8ac9003bfda0c208 Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Tue, 28 Mar 2023 20:38:42 +0100 Subject: [PATCH 11/16] wip --- include/iris/events/event.h | 2 ++ tools/sclera/gui.cpp | 23 +++++++++++++++++------ tools/sclera/gui.h | 5 ++++- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/include/iris/events/event.h b/include/iris/events/event.h index 927a9b54..8a153124 100644 --- a/include/iris/events/event.h +++ b/include/iris/events/event.h @@ -6,7 +6,9 @@ #pragma once +#include #include +#include #include #include "core/exception.h" diff --git a/tools/sclera/gui.cpp b/tools/sclera/gui.cpp index b0370d50..f8053460 100644 --- a/tools/sclera/gui.cpp +++ b/tools/sclera/gui.cpp @@ -251,7 +251,8 @@ void object_editor_tree_ui(std::vector &entities) void selected_object_gizmo_ui( ::ImGuiIO &io, const std::vector &entities, - const iris::Camera &camera) + const iris::Camera &camera, + ::ImGuizmo::OPERATION transform_operation) { ::ImGuizmo::SetOrthographic(false); ::ImGuizmo::BeginFrame(); @@ -275,7 +276,7 @@ void selected_object_gizmo_ui( ::ImGuizmo::Manipulate( inv_view.data(), inv_proj.data(), - ::ImGuizmo::TRANSLATE, + transform_operation, ::ImGuizmo::WORLD, const_cast(transform_ptr), nullptr, @@ -298,6 +299,7 @@ Gui::Gui(iris::Context &ctx, const iris::Window *window, iris::Scene *scene, iri , camera_(camera) , entities_() , show_demo_(false) + , transform_operation_(::ImGuizmo::TRANSLATE) { const auto scale = window_->screen_scale(); @@ -332,7 +334,7 @@ void Gui::render() object_creator_ui(iris_ctx_, entities_, scene_); object_editor_tree_ui(entities_); - selected_object_gizmo_ui(io_, entities_, camera_); + selected_object_gizmo_ui(io_, entities_, camera_, transform_operation_); } ::ImGui::Render(); @@ -375,12 +377,21 @@ void Gui::handle_input(const iris::Event &event) io_.AddMouseButtonEvent(*imgui_button, *imgui_state); } } - else if (event.is_key(iris::Key::I, iris::KeyState::DOWN)) - { - } else if (event.is_key()) { const auto key = event.key(); + if (key.state == iris::KeyState::DOWN) + { + switch (key.key) + { + using enum iris::Key; + case TAB: show_demo_ = !show_demo_; break; + case W: transform_operation_ = ::ImGuizmo::TRANSLATE; break; + case E: transform_operation_ = ::ImGuizmo::ROTATE; break; + case R: transform_operation_ = ::ImGuizmo::SCALE; break; + default: break; + } + } if ((key.key == iris::Key::TAB) && (key.state == iris::KeyState::DOWN)) { show_demo_ = !show_demo_; diff --git a/tools/sclera/gui.h b/tools/sclera/gui.h index 1e319386..39cbe92c 100644 --- a/tools/sclera/gui.h +++ b/tools/sclera/gui.h @@ -8,6 +8,9 @@ #include +#include "imgui.h" + +#include "ImGuizmo.h" #include "core/auto_release.h" #include "core/camera.h" #include "core/context.h" @@ -15,7 +18,6 @@ #include "graphics/scene.h" #include "graphics/single_entity.h" #include "graphics/window.h" -#include "imgui.h" class Gui { @@ -38,4 +40,5 @@ class Gui iris::Camera &camera_; std::vector entities_; bool show_demo_; + ::ImGuizmo::OPERATION transform_operation_; }; From 122477079a781c824b222ecfd84726f89f542c4b Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Tue, 28 Mar 2023 22:14:31 +0100 Subject: [PATCH 12/16] wip --- src/graphics/macos/macos_window.mm | 71 +++++++++++++++++++++++++----- tools/sclera/gui.cpp | 5 +++ tools/sclera/gui.h | 2 + tools/sclera/main.cpp | 67 +++++++++++++++++++--------- tools/sclera/metal_gui_renderer.h | 2 + tools/sclera/metal_gui_renderer.mm | 5 +++ 6 files changed, 122 insertions(+), 30 deletions(-) diff --git a/src/graphics/macos/macos_window.mm b/src/graphics/macos/macos_window.mm index 297cb1fc..3265a0e3 100644 --- a/src/graphics/macos/macos_window.mm +++ b/src/graphics/macos/macos_window.mm @@ -7,6 +7,8 @@ #include "graphics/macos/macos_window.h" #include +#include +#include #import #import @@ -159,13 +161,25 @@ return key; } +iris::Key macos_modifier_key_to_engine_Key(NSEventModifierFlags modifier) +{ + auto key = iris::Key::UNKNOWN; + + if (modifier & NSEventModifierFlagOption) + { + key = iris::Key::OPTION; + } + + return key; +} + /** * Helper method to handle native keyboard events. * * @param event * Native Event object. */ -iris::KeyboardEvent handle_keyboard_event(NSEvent *event) +iris::KeyboardEvent handle_keyboard_event(NSEvent *event, bool modifier_only) { // extract the Key code from the event const std::uint16_t key_code = [event keyCode]; @@ -174,7 +188,8 @@ const auto type = ([event type] == NSEventTypeKeyDown) ? iris::KeyState::DOWN : iris::KeyState::UP; // convert Key code and dispatch - const auto key = macos_key_to_engine_Key(key_code); + const auto key = + modifier_only ? macos_modifier_key_to_engine_Key([event modifierFlags]) : macos_key_to_engine_Key(key_code); return {key, type}; } @@ -244,7 +259,9 @@ std::optional MacosWindow::pump_event() { - std::optional evt{}; + static std::queue event_queue{}; + static std::unordered_map modifier_key_state{ + {Key::OPTION, KeyState::DOWN}, {Key::COMMAND, KeyState::DOWN}}; NSEvent *event = nil; @@ -263,25 +280,59 @@ case NSEventTypeKeyUp: if (!event.ARepeat) { - evt = handle_keyboard_event(event); + event_queue.push(handle_keyboard_event(event, false)); } break; case NSEventTypeLeftMouseDragged: [[fallthrough]]; case NSEventTypeRightMouseDragged: [[fallthrough]]; - case NSEventTypeMouseMoved: evt = handle_mouse_event(event); break; - case NSEventTypeLeftMouseDown: evt = MouseButtonEvent{MouseButton::LEFT, MouseButtonState::DOWN}; break; - case NSEventTypeLeftMouseUp: evt = MouseButtonEvent{MouseButton::LEFT, MouseButtonState::UP}; break; - case NSEventTypeRightMouseDown: evt = MouseButtonEvent{MouseButton::RIGHT, MouseButtonState::DOWN}; break; - case NSEventTypeRightMouseUp: evt = MouseButtonEvent{MouseButton::RIGHT, MouseButtonState::UP}; break; - case NSEventTypeScrollWheel: evt = ScrollWheelEvent{static_cast([event scrollingDeltaY])}; break; + case NSEventTypeMouseMoved: event_queue.push(handle_mouse_event(event)); break; + case NSEventTypeLeftMouseDown: + event_queue.push(MouseButtonEvent{MouseButton::LEFT, MouseButtonState::DOWN}); + break; + case NSEventTypeLeftMouseUp: + event_queue.push(MouseButtonEvent{MouseButton::LEFT, MouseButtonState::UP}); + break; + case NSEventTypeRightMouseDown: + event_queue.push(MouseButtonEvent{MouseButton::RIGHT, MouseButtonState::DOWN}); + break; + case NSEventTypeRightMouseUp: + event_queue.push(MouseButtonEvent{MouseButton::RIGHT, MouseButtonState::UP}); + break; + case NSEventTypeScrollWheel: + event_queue.push(ScrollWheelEvent{static_cast([event scrollingDeltaY])}); + break; default: break; } + const auto current_option_key_state = + ([NSEvent modifierFlags] & NSEventModifierFlagOption) == NSEventModifierFlagOption ? KeyState::DOWN + : KeyState::UP; + if (std::exchange(modifier_key_state[Key::OPTION], current_option_key_state) != current_option_key_state) + { + event_queue.push(KeyboardEvent{Key::OPTION, current_option_key_state}); + } + + const auto current_command_key_state = + ([NSEvent modifierFlags] & NSEventModifierFlagCommand) == NSEventModifierFlagCommand ? KeyState::DOWN + : KeyState::UP; + if (std::exchange(modifier_key_state[Key::COMMAND], current_command_key_state) != current_command_key_state) + { + event_queue.push(KeyboardEvent{Key::COMMAND, current_command_key_state}); + } + // dispatch the Event to other objects, this stops us swallowing // all events and preventing anything else from receiving them [NSApp sendEvent:event]; } + std::optional evt{}; + + if (!event_queue.empty()) + { + evt = event_queue.front(); + event_queue.pop(); + } + return evt; } diff --git a/tools/sclera/gui.cpp b/tools/sclera/gui.cpp index f8053460..b7eeb3a4 100644 --- a/tools/sclera/gui.cpp +++ b/tools/sclera/gui.cpp @@ -408,3 +408,8 @@ void Gui::handle_input(const iris::Event &event) io_.AddMouseWheelEvent(0.0f, scroll.delta_y); } } + +bool Gui::is_mouse_captured() const +{ + return io_.WantCaptureMouse; +} diff --git a/tools/sclera/gui.h b/tools/sclera/gui.h index 39cbe92c..5c32965c 100644 --- a/tools/sclera/gui.h +++ b/tools/sclera/gui.h @@ -28,6 +28,8 @@ class Gui void handle_input(const iris::Event &event); + bool is_mouse_captured() const; + protected: virtual void pre_render() = 0; virtual void post_render() = 0; diff --git a/tools/sclera/main.cpp b/tools/sclera/main.cpp index 3717168b..672cd9c7 100644 --- a/tools/sclera/main.cpp +++ b/tools/sclera/main.cpp @@ -44,37 +44,37 @@ namespace * @param key_map * Map of user pressed keys. */ -void update_camera(iris::Camera &camera, const std::unordered_map &key_map) +void update_camera(iris::Camera &camera, bool track_up, bool track_down, bool track_left, bool track_right) { static auto speed = 2.0f; iris::Vector3 velocity; - if (key_map.at(iris::Key::W) == iris::KeyState::DOWN) - { - velocity += camera.direction() * speed; - } + // if (key_map.at(iris::Key::W) == iris::KeyState::DOWN) + //{ + // velocity += camera.direction() * speed; + // } - if (key_map.at(iris::Key::S) == iris::KeyState::DOWN) - { - velocity -= camera.direction() * speed; - } + // if (key_map.at(iris::Key::S) == iris::KeyState::DOWN) + //{ + // velocity -= camera.direction() * speed; + // } - if (key_map.at(iris::Key::A) == iris::KeyState::DOWN) + if (track_left) { velocity -= camera.right() * speed; } - if (key_map.at(iris::Key::D) == iris::KeyState::DOWN) + if (track_right) { velocity += camera.right() * speed; } - if (key_map.at(iris::Key::Q) == iris::KeyState::DOWN) + if (track_up) { velocity += camera.right().cross(camera.direction()) * speed; } - if (key_map.at(iris::Key::E) == iris::KeyState::DOWN) + if (track_down) { velocity -= camera.right().cross(camera.direction()) * speed; } @@ -123,14 +123,18 @@ void go(iris::Context ctx) {iris::Key::E, iris::KeyState::UP}, }; - auto right_mouse_down = false; + auto left_mouse_down = false; + auto track_up = false; + auto track_down = false; + auto track_left = false; + auto track_right = false; iris::Looper looper{ 0ms, 30ms, [&](auto, auto) { - update_camera(camera, key_map); + update_camera(camera, track_up, track_down, track_left, track_right); return true; }, [&](auto, auto) @@ -140,6 +144,9 @@ void go(iris::Context ctx) auto event = window->pump_event(); while (event) { + auto *metal_renderer = static_cast(window->renderer()); + metal_renderer->handle_input(*event); + if (event->is_quit() || event->is_key(iris::Key::ESCAPE)) { running = false; @@ -154,19 +161,39 @@ void go(iris::Context ctx) static const auto sensitivity = 0.0025f; const auto mouse = event->mouse(); - if (right_mouse_down) + if (left_mouse_down && (key_map[iris::Key::OPTION] == iris::KeyState::DOWN) && + (key_map[iris::Key::COMMAND] == iris::KeyState::DOWN)) + { + static constexpr auto threshold = 0.5f; + + track_up = mouse.delta_y > threshold; + track_down = mouse.delta_y < -threshold; + track_left = mouse.delta_x > threshold; + track_right = mouse.delta_x < -threshold; + } + else if (left_mouse_down && (key_map[iris::Key::OPTION] == iris::KeyState::DOWN)) { camera.adjust_yaw(mouse.delta_x * sensitivity); camera.adjust_pitch(-mouse.delta_y * sensitivity); } + else + { + track_up = false; + track_down = false; + track_left = false; + track_right = false; + } } - else if (event->is_mouse_button((iris::MouseButton::RIGHT))) + else if (event->is_mouse_button((iris::MouseButton::LEFT))) { const auto mouse_button = event->mouse_button(); - right_mouse_down = mouse_button.state == iris::MouseButtonState::DOWN; + left_mouse_down = mouse_button.state == iris::MouseButtonState::DOWN; + } + else if (event->is_scroll_wheel() && !metal_renderer->is_mouse_captured()) + { + const auto scroll = event->scroll_wheel(); + camera.translate(camera.direction() * scroll.delta_y); } - - static_cast(window->renderer())->handle_input(*event); event = window->pump_event(); } diff --git a/tools/sclera/metal_gui_renderer.h b/tools/sclera/metal_gui_renderer.h index 6073a133..2bc76759 100644 --- a/tools/sclera/metal_gui_renderer.h +++ b/tools/sclera/metal_gui_renderer.h @@ -36,6 +36,8 @@ class MetalGuiRenderer : public iris::Renderer void handle_input(const iris::Event &event); + bool is_mouse_captured() const; + private: void do_set_render_pipeline(std::function build_queue) override; diff --git a/tools/sclera/metal_gui_renderer.mm b/tools/sclera/metal_gui_renderer.mm index 35ac6e54..fc345924 100644 --- a/tools/sclera/metal_gui_renderer.mm +++ b/tools/sclera/metal_gui_renderer.mm @@ -418,6 +418,11 @@ void do_set_render_pipeline(std::function build_queue) override //} } +bool MetalGuiRenderer::is_mouse_captured() const +{ + return impl_->gui->is_mouse_captured(); +} + void MetalGuiRenderer::do_set_render_pipeline(std::function build_queue) { impl_->renderer->do_set_render_pipeline(build_queue); From 277409dec0e39db79dd9f0422b415a69dda4f628 Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Wed, 29 Mar 2023 22:01:50 +0100 Subject: [PATCH 13/16] wip --- include/iris/core/matrix4.h | 12 ++++- include/iris/core/resource_manager.h | 2 + include/iris/core/vector3.h | 1 + include/iris/core/vector4.h | 66 ++++++++++++++++++++++++++++ src/core/resource_manager.cpp | 15 +++++++ tools/sclera/gui.cpp | 53 ++++++++++++++++++---- tools/sclera/gui.h | 1 + 7 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 include/iris/core/vector4.h diff --git a/include/iris/core/matrix4.h b/include/iris/core/matrix4.h index e771ab3a..17693b0f 100644 --- a/include/iris/core/matrix4.h +++ b/include/iris/core/matrix4.h @@ -13,6 +13,7 @@ #include "core/quaternion.h" #include "core/utils.h" #include "core/vector3.h" +#include "core/vector4.h" namespace iris { @@ -439,13 +440,20 @@ class Matrix4 { return { vector.x * elements_[0] + vector.y * elements_[1] + vector.z * elements_[2] + elements_[3], - vector.x * elements_[4] + vector.y * elements_[5] + vector.z * elements_[6] + elements_[7], - vector.x * elements_[8] + vector.y * elements_[9] + vector.z * elements_[10] + elements_[11], }; } + constexpr Vector4 operator*(const Vector4 &vector) const + { + return { + vector.x * elements_[0] + vector.y * elements_[1] + vector.z * elements_[2] + vector.w * elements_[3], + vector.x * elements_[4] + vector.y * elements_[5] + vector.z * elements_[6] + vector.w * elements_[7], + vector.x * elements_[8] + vector.y * elements_[9] + vector.z * elements_[10] + vector.w * elements_[11], + vector.x * elements_[12] + vector.y * elements_[13] + vector.z * elements_[14] + vector.w * elements_[15]}; + } + /** * Get a reference to the element at the supplied index. * diff --git a/include/iris/core/resource_manager.h b/include/iris/core/resource_manager.h index 4c6dad78..c9d2d70c 100644 --- a/include/iris/core/resource_manager.h +++ b/include/iris/core/resource_manager.h @@ -48,6 +48,8 @@ class ResourceManager */ void set_root_directory(const std::filesystem::path &root); + std::vector available_resources() const; + protected: /** * Implementations should override this to perform their specific data loading logic. diff --git a/include/iris/core/vector3.h b/include/iris/core/vector3.h index 6002dd70..84089cef 100644 --- a/include/iris/core/vector3.h +++ b/include/iris/core/vector3.h @@ -98,6 +98,7 @@ class Vector3 { return Vector3(*this) *= scale; } + /** * Component wise add a Vector3 to this vector3. * diff --git a/include/iris/core/vector4.h b/include/iris/core/vector4.h new file mode 100644 index 00000000..9fce6b07 --- /dev/null +++ b/include/iris/core/vector4.h @@ -0,0 +1,66 @@ +//////////////////////////////////////////////////////////////////////////////// +// Distributed under the Boost Software License, Version 1.0. // +// (See accompanying file LICENSE or copy at // +// https://www.boost.org/LICENSE_1_0.txt) // +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "vector3.h" + +namespace iris +{ + +class Vector4 +{ + public: + constexpr Vector4() + : Vector4(0.0f) + { + } + + constexpr Vector4(float xyzw) + : Vector4(xyzw, xyzw, xyzw, xyzw) + { + } + + constexpr Vector4(float x, float y, float z, float w) + : x(x) + , y(y) + , z(z) + , w(w) + { + } + + constexpr Vector4(const Vector3 &v, float w = 0.0f) + : Vector4(v.x, v.y, v.z, w) + { + } + + constexpr Vector4 &operator/=(float scale) + { + x /= scale; + y /= scale; + z /= scale; + w /= scale; + + return *this; + } + + constexpr Vector4 operator/(float scale) const + { + return Vector4(*this) /= scale; + } + + constexpr Vector3 xyz() const + { + return {x, y, z}; + } + + float x; + float y; + float z; + float w; +}; + +} \ No newline at end of file diff --git a/src/core/resource_manager.cpp b/src/core/resource_manager.cpp index 07f96df9..2bdd0c87 100644 --- a/src/core/resource_manager.cpp +++ b/src/core/resource_manager.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "core/error_handling.h" @@ -41,4 +42,18 @@ void ResourceManager::set_root_directory(const std::filesystem::path &root) root_ = root; } +std::vector ResourceManager::available_resources() const +{ + std::vector paths{}; + const auto iter = std::filesystem::directory_iterator{root_}; + + std::transform( + std::filesystem::begin(iter), + std::filesystem::end(iter), + std::back_inserter(paths), + [](const auto &path) { return path.path().filename().native(); }); + + return paths; +} + } diff --git a/tools/sclera/gui.cpp b/tools/sclera/gui.cpp index b7eeb3a4..17b98be2 100644 --- a/tools/sclera/gui.cpp +++ b/tools/sclera/gui.cpp @@ -17,6 +17,7 @@ #include "graphics/scene.h" #include "graphics/single_entity.h" #include "graphics/window.h" +#include "log/log.h" namespace { @@ -250,7 +251,7 @@ void object_editor_tree_ui(std::vector &entities) void selected_object_gizmo_ui( ::ImGuiIO &io, - const std::vector &entities, + iris::SingleEntity *selected, const iris::Camera &camera, ::ImGuizmo::OPERATION transform_operation) { @@ -269,9 +270,9 @@ void selected_object_gizmo_ui( const auto m = iris::Matrix4::transpose(iris::Transform({}, {}, {1.0f}).matrix()); ::ImGuizmo::DrawCubes(inv_view.data(), inv_proj.data(), m.data(), 1); - if (!entities.empty()) + if (selected != nullptr) { - auto transform = iris::Matrix4::transpose(entities[0]->transform()); + auto transform = iris::Matrix4::transpose(selected->transform()); auto *transform_ptr = transform.data(); ::ImGuizmo::Manipulate( inv_view.data(), @@ -284,8 +285,10 @@ void selected_object_gizmo_ui( nullptr, nullptr); - entities[0]->set_transform(iris::Matrix4::transpose(transform)); + selected->set_transform(iris::Matrix4::transpose(transform)); } + + LOG_DEBUG("gui", "{} {}", ::ImGuizmo::IsOver(), ::ImGuizmo::IsUsing()); } } @@ -302,6 +305,7 @@ Gui::Gui(iris::Context &ctx, const iris::Window *window, iris::Scene *scene, iri , transform_operation_(::ImGuizmo::TRANSLATE) { const auto scale = window_->screen_scale(); + const auto resources = ctx.resource_manager().available_resources(); io_.ConfigFlags |= ::ImGuiConfigFlags_NavEnableKeyboard; io_.ConfigFlags |= ::ImGuiConfigFlags_NavEnableGamepad; @@ -334,7 +338,7 @@ void Gui::render() object_creator_ui(iris_ctx_, entities_, scene_); object_editor_tree_ui(entities_); - selected_object_gizmo_ui(io_, entities_, camera_, transform_operation_); + selected_object_gizmo_ui(io_, selected_, camera_, transform_operation_); } ::ImGui::Render(); @@ -376,6 +380,41 @@ void Gui::handle_input(const iris::Event &event) { io_.AddMouseButtonEvent(*imgui_button, *imgui_state); } + + if (event.is_mouse_button(iris::MouseButton::LEFT, iris::MouseButtonState::DOWN) && !is_mouse_captured()) + { + const iris::Vector3 mouse_coord{ + (2.0f * x) / window_->width() - 1.0f, 1.0f - (2.0f * y) / window_->height(), 1.0f}; + + const auto to_world = iris::Matrix4::invert(camera_.projection() * camera_.view()); + + auto from = to_world * iris::Vector4(mouse_coord.x, mouse_coord.y, -1.0f, 1.0f); + auto to = to_world * iris::Vector4(mouse_coord.x, mouse_coord.y, 1.0f, 1.0f); + from /= from.w; + to /= to.w; + + const auto origin = from.xyz(); + const auto direction = iris::Vector3::normalise(to.xyz() - from.xyz()); + const auto radius = std::numbers::sqrt2_v; + + selected_ = nullptr; + + for (auto *entity : entities_) + { + const auto centre = entity->position(); + + const auto b = direction.dot(origin - centre); + const auto c = (origin - centre).dot(origin - centre) - std::pow(radius, 2.0f); + + const auto t = (std::pow(b, 2.0f) - c); + + if (t >= 0.0f) + { + selected_ = entity; + break; + } + } + } } else if (event.is_key()) { @@ -392,10 +431,6 @@ void Gui::handle_input(const iris::Event &event) default: break; } } - if ((key.key == iris::Key::TAB) && (key.state == iris::KeyState::DOWN)) - { - show_demo_ = !show_demo_; - } if (const auto imgui_key = iris_to_imgui_key(key.key); imgui_key) { diff --git a/tools/sclera/gui.h b/tools/sclera/gui.h index 5c32965c..e2501495 100644 --- a/tools/sclera/gui.h +++ b/tools/sclera/gui.h @@ -43,4 +43,5 @@ class Gui std::vector entities_; bool show_demo_; ::ImGuizmo::OPERATION transform_operation_; + iris::SingleEntity *selected_; }; From bd0bbaf03f71d1b4f342f33e7ea75434acdab8bd Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Sat, 1 Apr 2023 19:42:31 +0100 Subject: [PATCH 14/16] wip --- include/iris/core/transform.h | 7 +++ include/iris/core/vector3.h | 34 +++++++++++- src/core/resource_manager.cpp | 3 + src/core/transform.cpp | 22 ++++++-- tools/sclera/CMakeLists.txt | 1 + tools/sclera/entity.cpp | 101 ++++++++++++++++++++++++++++++++++ tools/sclera/entity.h | 40 ++++++++++++++ tools/sclera/gui.cpp | 96 +++++++++++++++++++++++--------- tools/sclera/gui.h | 7 ++- 9 files changed, 276 insertions(+), 35 deletions(-) create mode 100644 tools/sclera/entity.cpp create mode 100644 tools/sclera/entity.h diff --git a/include/iris/core/transform.h b/include/iris/core/transform.h index fd0ef1f8..47417efd 100644 --- a/include/iris/core/transform.h +++ b/include/iris/core/transform.h @@ -6,6 +6,9 @@ #pragma once +#include +#include + #include "core/matrix4.h" #include "core/quaternion.h" #include "core/vector3.h" @@ -124,6 +127,8 @@ class Transform */ void set_scale(const Vector3 &scale); + std::tuple decompose() const; + /** * Equality operator. * @@ -195,6 +200,8 @@ class Transform */ Transform &operator*=(const Matrix4 &other); + friend std::ostream &operator<<(std::ostream &out, const Transform &t); + private: /** Translation component. */ Vector3 translation_; diff --git a/include/iris/core/vector3.h b/include/iris/core/vector3.h index 84089cef..70ac98a8 100644 --- a/include/iris/core/vector3.h +++ b/include/iris/core/vector3.h @@ -65,7 +65,7 @@ class Vector3 { } - /**d221G + /** * Multiply each component by a scalar value. * * @param scale @@ -99,6 +99,38 @@ class Vector3 return Vector3(*this) *= scale; } + /** + * Divide each component by a scalar value. + * + * @param scale + * scalar value. + * + * @return + * Reference to this vector3. + */ + constexpr Vector3 &operator/=(float scale) + { + x /= scale; + y /= scale; + z /= scale; + + return *this; + } + + /** + * Create a new Vector3 which is this Vector3 with each component divided by a scalar value. + * + * @param scale + * scalar value. + * + * @return + * Copy of this Vector3 with each component divided by a scalar value. + */ + constexpr Vector3 operator/(float scale) const + { + return Vector3(*this) /= scale; + } + /** * Component wise add a Vector3 to this vector3. * diff --git a/src/core/resource_manager.cpp b/src/core/resource_manager.cpp index 2bdd0c87..9d229f99 100644 --- a/src/core/resource_manager.cpp +++ b/src/core/resource_manager.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -53,6 +54,8 @@ std::vector ResourceManager::available_resources() const std::back_inserter(paths), [](const auto &path) { return path.path().filename().native(); }); + std::ranges::sort(paths); + return paths; } diff --git a/src/core/transform.cpp b/src/core/transform.cpp index 632247d0..6839c620 100644 --- a/src/core/transform.cpp +++ b/src/core/transform.cpp @@ -7,6 +7,7 @@ #include "core/transform.h" #include +#include #include "core/matrix4.h" #include "core/quaternion.h" @@ -22,7 +23,7 @@ * @returns * Tuple of */ -std::tuple decompose(iris::Matrix4 matrix) +std::tuple decompose_matrix(iris::Matrix4 matrix) { // extract translation const iris::Vector3 translation = matrix.column(3u); @@ -48,7 +49,7 @@ std::tuple decompose(iris::Matri iris::Quaternion rotation{}; - // the following code is cribbed from OgreQuaternion.cpp FromRotatinMatrix + // the following code is cribbed from OgreQuaternion.cpp FromRotationMatrix // commit: e1c3732c51f9099bed10d36805b738015adc8f47 // which in turn is based on: // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes @@ -109,7 +110,7 @@ Transform::Transform() Transform::Transform(const Matrix4 &matrix) : Transform({0.0f}, {}, {0.0f}) { - const auto [translation, rotation, scale] = decompose(matrix); + const auto [translation, rotation, scale] = decompose_matrix(matrix); translation_ = translation; rotation_ = rotation; @@ -130,7 +131,7 @@ Matrix4 Transform::matrix() const void Transform::set_matrix(const Matrix4 &matrix) { - const auto [translation, rotation, scale] = decompose(matrix); + const auto [translation, rotation, scale] = decompose_matrix(matrix); translation_ = translation; rotation_ = rotation; @@ -174,6 +175,11 @@ void Transform::set_scale(const Vector3 &scale) scale_ = scale; } +std::tuple Transform::decompose() const +{ + return {translation_, rotation_, scale_}; +} + bool Transform::operator==(const Transform &other) const { return (translation_ == other.translation_) && (rotation_ == other.rotation_) && (scale_ == other.scale_); @@ -203,7 +209,7 @@ Transform &Transform::operator*=(const Matrix4 &other) { auto new_matrix = matrix() * other; - const auto [translation, rotation, scale] = decompose(new_matrix); + const auto [translation, rotation, scale] = decompose_matrix(new_matrix); translation_ = translation; rotation_ = rotation; @@ -212,4 +218,10 @@ Transform &Transform::operator*=(const Matrix4 &other) return *this; } +std::ostream &operator<<(std::ostream &out, const Transform &t) +{ + out << "translation: " << t.translation_ << " rotation: " << t.rotation_ << " scale: " << t.scale_; + return out; +} + } diff --git a/tools/sclera/CMakeLists.txt b/tools/sclera/CMakeLists.txt index fb75c7fd..03caf251 100644 --- a/tools/sclera/CMakeLists.txt +++ b/tools/sclera/CMakeLists.txt @@ -20,6 +20,7 @@ add_executable(sclera ${imgui_SOURCE_DIR}/imgui_tables.cpp ${imgui_SOURCE_DIR}/imgui_widgets.cpp ${imgui_gizmo_SOURCE_DIR}/ImGuizmo.cpp + entity.cpp gui.cpp main.cpp metal_gui.mm diff --git a/tools/sclera/entity.cpp b/tools/sclera/entity.cpp new file mode 100644 index 00000000..b8145e2d --- /dev/null +++ b/tools/sclera/entity.cpp @@ -0,0 +1,101 @@ +//////////////////////////////////////////////////////////////////////////////// +// Distributed under the Boost Software License, Version 1.0. // +// (See accompanying file LICENSE or copy at // +// https://www.boost.org/LICENSE_1_0.txt) // +//////////////////////////////////////////////////////////////////////////////// + +#include "entity.h" + +#include +#include +#include + +#include "core/error_handling.h" +#include "core/transform.h" +#include "core/vector3.h" +#include "graphics/mesh.h" +#include "graphics/mesh_manager.h" +#include "graphics/single_entity.h" + +namespace +{ + +iris::Vector3 calculate_centre(const std::vector &entities) +{ + iris::ensure(!entities.empty(), "vector cannot be empty"); + + return std::reduce( + std::cbegin(entities), + std::cend(entities), + iris::Vector3{}, + [](const auto &total, const iris::SingleEntity *e2) -> iris::Vector3 + { return total + e2->position(); }) / + static_cast(entities.size()); +} + +} + +Entity::Entity(const std::vector &entities) + : Entity(entities, calculate_centre(entities)) +{ +} + +Entity::Entity(const std::vector &entities, const iris::Vector3 ¢re) + : entities_(entities) + , centre_(centre) + , transform_() +{ +} + +iris::Vector3 Entity::centre() const +{ + return centre_; +} + +iris::Transform Entity::transform() const +{ + return transform_; +} + +void Entity::set_transform(const iris::Transform &transform) +{ + for (auto *entity : entities_) + { + entity->set_transform(transform.matrix()); + } + transform_ = transform; +} + +void Entity::apply_transform(const iris::Transform &transform) +{ + for (auto *entity : entities_) + { + entity->set_transform(entity->transform() * transform.matrix()); + } + + transform_ *= transform; +} + +bool Entity::intersects(const iris::Vector3 &origin, const iris::Vector3 &direction) +{ + const auto radius = std::numbers::sqrt2_v; + auto intersects = false; + + for (auto *entity : entities_) + { + const auto centre = entity->position(); + + const auto b = direction.dot(origin - centre); + const auto c = (origin - centre).dot(origin - centre) - std::pow(radius, 2.0f); + + const auto t = (std::pow(b, 2.0f) - c); + + if (t >= 0.0f) + { + intersects = true; + break; + } + } + + return intersects; +} diff --git a/tools/sclera/entity.h b/tools/sclera/entity.h new file mode 100644 index 00000000..bfa7b70f --- /dev/null +++ b/tools/sclera/entity.h @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////////////////////////// +// Distributed under the Boost Software License, Version 1.0. // +// (See accompanying file LICENSE or copy at // +// https://www.boost.org/LICENSE_1_0.txt) // +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +#include "core/transform.h" +#include "core/vector3.h" +#include "graphics/mesh.h" +#include "graphics/mesh_manager.h" +#include "graphics/single_entity.h" + +class Entity +{ + public: + Entity(const std::vector &entities); + + Entity(const std::vector &entities, const iris::Vector3 ¢re); + + iris::Vector3 centre() const; + + iris::Transform transform() const; + + void set_transform(const iris::Transform &transform); + + void apply_transform(const iris::Transform &transform); + + bool intersects(const iris::Vector3 &origin, const iris::Vector3 &direction); + + private: + std::vector entities_; + + iris::Vector3 centre_; + + iris::Transform transform_; +}; diff --git a/tools/sclera/gui.cpp b/tools/sclera/gui.cpp index 17b98be2..1fdd8023 100644 --- a/tools/sclera/gui.cpp +++ b/tools/sclera/gui.cpp @@ -6,7 +6,9 @@ #include "gui.h" +#include #include +#include #include #include "imgui.h" @@ -14,6 +16,7 @@ #include "ImGuizmo.h" #include "core/auto_release.h" #include "core/context.h" +#include "entity.h" #include "graphics/scene.h" #include "graphics/single_entity.h" #include "graphics/window.h" @@ -145,23 +148,62 @@ std::string label_name(std::string_view label, std::uint32_t id) return std::string{label} + std::to_string(id); } -void object_creator_ui(iris::Context &ctx, std::vector &entities, iris::Scene *scene) +void object_creator_ui(iris::Context &ctx, std::deque &entities, iris::Scene *scene, Entity **selected_entity) { if (::ImGui::Button("Add Box")) { - entities.push_back(scene->create_entity( - nullptr, ctx.mesh_manager().cube(iris::Colour{1.0f, 1.0f, 1.0f}), iris::Transform{{}, {}, {1.0f}})); + auto *entity = scene->create_entity( + nullptr, ctx.mesh_manager().cube(iris::Colour{1.0f, 1.0f, 1.0f}), iris::Transform{{}, {}, {1.0f}}); + + auto &new_entity = entities.emplace_back(std::vector{entity}, iris::Vector3{}); + *selected_entity = std::addressof(new_entity); + } + + ::ImGui::SameLine(); + + if (::ImGui::Button("Add model")) + { + ImGui::OpenPopup("model_select_popup"); + } + + if (ImGui::BeginPopup("model_select_popup")) + { + const auto is_fbx = [](const auto &str) { return str.ends_with(".fbx"); }; + + for (const auto &model : ctx.resource_manager().available_resources() | std::ranges::views::filter(is_fbx)) + { + if (ImGui::Selectable(model.c_str())) + { + const auto mesh_parts = ctx.mesh_manager().load_mesh(model); + std::vector engine_entities{}; + + for (const auto &mesh : mesh_parts.mesh_data) + { + engine_entities.push_back( + scene->create_entity(nullptr, mesh.mesh, iris::Transform{{}, {}, {1.0f}})); + } + + auto &new_entity = entities.emplace_back(engine_entities); + *selected_entity = std::addressof(new_entity); + (*selected_entity) + ->set_transform( + iris::Transform{{}, {{1.0f, 0.0f, 0.0f}, -std::numbers::pi_v / 2.0f}, {1.0f}}); + } + } + ImGui::EndPopup(); } } -void object_editor_tree_ui(std::vector &entities) +void object_editor_tree_ui(std::deque &entities) { auto counter = 0u; - for (auto *entity : entities) + for (auto &entity : entities) { + auto [position, rotation, scale] = entity.transform().decompose(); + auto dirty = false; + if (::ImGui::TreeNode(label_name("Object", counter).c_str())) { - auto position = entity->position(); float pos_x = position.x; float pos_y = position.y; float pos_z = position.z; @@ -186,10 +228,10 @@ void object_editor_tree_ui(std::vector &entities) const iris::Vector3 new_position{pos_x, pos_y, pos_z}; if (new_position != position) { - entity->set_position(new_position); + position = new_position; + dirty = true; } - auto rotation = entity->orientation(); auto [rot_x, rot_y, rot_z] = rotation.to_euler_angles(); ::ImGui::LabelText("", "Rotation"); @@ -212,10 +254,10 @@ void object_editor_tree_ui(std::vector &entities) const iris::Quaternion new_rotation{rot_x, rot_y, rot_z}; if (new_rotation != rotation) { - entity->set_orientation(new_rotation); + rotation = new_rotation; + dirty = true; } - auto scale = entity->scale(); float scale_x = scale.x; float scale_y = scale.y; float scale_z = scale.z; @@ -240,18 +282,24 @@ void object_editor_tree_ui(std::vector &entities) const iris::Vector3 new_scale{scale_x, scale_y, scale_z}; if (new_scale != scale) { - entity->set_scale(new_scale); + scale = new_scale; + dirty = true; } ::ImGui::TreePop(); } + if (dirty) + { + entity.set_transform({position, rotation, scale}); + } + ++counter; } } void selected_object_gizmo_ui( ::ImGuiIO &io, - iris::SingleEntity *selected, + Entity *selected, const iris::Camera &camera, ::ImGuizmo::OPERATION transform_operation) { @@ -261,19 +309,17 @@ void selected_object_gizmo_ui( ::ImGuizmo::Enable(true); ::ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); - static const float identityMatrix[16] = { - 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f}; + static const std::array identity_matrix = { + {1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f}}; auto inv_view = iris::Matrix4::transpose(camera.view()); const auto inv_proj = iris::Matrix4::transpose(camera.projection()); - ::ImGuizmo::DrawGrid(inv_view.data(), inv_proj.data(), identityMatrix, 100.f); - - const auto m = iris::Matrix4::transpose(iris::Transform({}, {}, {1.0f}).matrix()); - ::ImGuizmo::DrawCubes(inv_view.data(), inv_proj.data(), m.data(), 1); + ::ImGuizmo::DrawGrid(inv_view.data(), inv_proj.data(), identity_matrix.data(), 100.f); if (selected != nullptr) { - auto transform = iris::Matrix4::transpose(selected->transform()); + auto transform = iris::Matrix4::transpose(selected->transform().matrix()); auto *transform_ptr = transform.data(); + ::ImGuizmo::Manipulate( inv_view.data(), inv_proj.data(), @@ -285,10 +331,8 @@ void selected_object_gizmo_ui( nullptr, nullptr); - selected->set_transform(iris::Matrix4::transpose(transform)); + selected->set_transform(iris::Transform{iris::Matrix4::transpose(transform)}); } - - LOG_DEBUG("gui", "{} {}", ::ImGuizmo::IsOver(), ::ImGuizmo::IsUsing()); } } @@ -336,7 +380,7 @@ void Gui::render() { AutoBegin begin{"Object creator", nullptr, ImGuiWindowFlags_None}; - object_creator_ui(iris_ctx_, entities_, scene_); + object_creator_ui(iris_ctx_, entities_, scene_, &selected_); object_editor_tree_ui(entities_); selected_object_gizmo_ui(io_, selected_, camera_, transform_operation_); } @@ -399,9 +443,9 @@ void Gui::handle_input(const iris::Event &event) selected_ = nullptr; - for (auto *entity : entities_) + for (auto &entity : entities_) { - const auto centre = entity->position(); + const auto centre = entity.transform().translation(); const auto b = direction.dot(origin - centre); const auto c = (origin - centre).dot(origin - centre) - std::pow(radius, 2.0f); @@ -410,7 +454,7 @@ void Gui::handle_input(const iris::Event &event) if (t >= 0.0f) { - selected_ = entity; + selected_ = std::addressof(entity); break; } } diff --git a/tools/sclera/gui.h b/tools/sclera/gui.h index e2501495..c3de32e5 100644 --- a/tools/sclera/gui.h +++ b/tools/sclera/gui.h @@ -6,7 +6,7 @@ #pragma once -#include +#include #include "imgui.h" @@ -14,6 +14,7 @@ #include "core/auto_release.h" #include "core/camera.h" #include "core/context.h" +#include "entity.h" #include "events/event.h" #include "graphics/scene.h" #include "graphics/single_entity.h" @@ -40,8 +41,8 @@ class Gui const iris::Window *window_; iris::Scene *scene_; iris::Camera &camera_; - std::vector entities_; + std::deque entities_; bool show_demo_; ::ImGuizmo::OPERATION transform_operation_; - iris::SingleEntity *selected_; + Entity *selected_; }; From 8d678d9147cc514cb6ccb080d94e6cd460bb0905 Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Sat, 1 Apr 2023 21:58:38 +0100 Subject: [PATCH 15/16] wip --- tools/sclera/entity.cpp | 5 +++ tools/sclera/entity.h | 2 + tools/sclera/gui.cpp | 88 +++++++++++++++++++++++++++++++---------- tools/sclera/gui.h | 3 ++ 4 files changed, 78 insertions(+), 20 deletions(-) diff --git a/tools/sclera/entity.cpp b/tools/sclera/entity.cpp index b8145e2d..36283d44 100644 --- a/tools/sclera/entity.cpp +++ b/tools/sclera/entity.cpp @@ -99,3 +99,8 @@ bool Entity::intersects(const iris::Vector3 &origin, const iris::Vector3 &direct return intersects; } + +std::vector Entity::entities() const +{ + return entities_; +} diff --git a/tools/sclera/entity.h b/tools/sclera/entity.h index bfa7b70f..b61e73ab 100644 --- a/tools/sclera/entity.h +++ b/tools/sclera/entity.h @@ -31,6 +31,8 @@ class Entity bool intersects(const iris::Vector3 &origin, const iris::Vector3 &direction); + std::vector entities() const; + private: std::vector entities_; diff --git a/tools/sclera/gui.cpp b/tools/sclera/gui.cpp index 1fdd8023..a3c0544c 100644 --- a/tools/sclera/gui.cpp +++ b/tools/sclera/gui.cpp @@ -148,15 +148,26 @@ std::string label_name(std::string_view label, std::uint32_t id) return std::string{label} + std::to_string(id); } -void object_creator_ui(iris::Context &ctx, std::deque &entities, iris::Scene *scene, Entity **selected_entity) +void object_creator_ui( + iris::Context &ctx, + std::deque &entities, + std::unordered_map> &entity_creators, + iris::Scene *scene, + Entity **selected_entity) { if (::ImGui::Button("Add Box")) { - auto *entity = scene->create_entity( - nullptr, ctx.mesh_manager().cube(iris::Colour{1.0f, 1.0f, 1.0f}), iris::Transform{{}, {}, {1.0f}}); + const auto creator = [&] + { + auto *entity = scene->create_entity( + nullptr, ctx.mesh_manager().cube(iris::Colour{1.0f, 1.0f, 1.0f}), iris::Transform{{}, {}, {1.0f}}); + + return Entity{std::vector{entity}, iris::Vector3{}}; + }; - auto &new_entity = entities.emplace_back(std::vector{entity}, iris::Vector3{}); + auto &new_entity = entities.emplace_back(creator()); *selected_entity = std::addressof(new_entity); + entity_creators[*selected_entity] = creator; } ::ImGui::SameLine(); @@ -170,24 +181,30 @@ void object_creator_ui(iris::Context &ctx, std::deque &entities, iris::S { const auto is_fbx = [](const auto &str) { return str.ends_with(".fbx"); }; - for (const auto &model : ctx.resource_manager().available_resources() | std::ranges::views::filter(is_fbx)) + for (auto model : ctx.resource_manager().available_resources() | std::ranges::views::filter(is_fbx)) { if (ImGui::Selectable(model.c_str())) { - const auto mesh_parts = ctx.mesh_manager().load_mesh(model); - std::vector engine_entities{}; - - for (const auto &mesh : mesh_parts.mesh_data) + const auto creator = [model, scene, selected_entity, &ctx, &entities] { - engine_entities.push_back( - scene->create_entity(nullptr, mesh.mesh, iris::Transform{{}, {}, {1.0f}})); - } - - auto &new_entity = entities.emplace_back(engine_entities); - *selected_entity = std::addressof(new_entity); - (*selected_entity) - ->set_transform( - iris::Transform{{}, {{1.0f, 0.0f, 0.0f}, -std::numbers::pi_v / 2.0f}, {1.0f}}); + const auto mesh_parts = ctx.mesh_manager().load_mesh(model); + std::vector engine_entities{}; + + for (const auto &mesh : mesh_parts.mesh_data) + { + engine_entities.push_back(scene->create_entity( + nullptr, mesh.mesh, iris::Transform{{}, {}, {1.0f}})); + } + + auto &new_entity = entities.emplace_back(engine_entities); + *selected_entity = std::addressof(new_entity); + (*selected_entity) + ->set_transform( + iris::Transform{{}, {{1.0f, 0.0f, 0.0f}, -std::numbers::pi_v / 2.0f}, {1.0f}}); + }; + + creator(); + entity_creators[*selected_entity] = creator; } } ImGui::EndPopup(); @@ -320,6 +337,9 @@ void selected_object_gizmo_ui( auto transform = iris::Matrix4::transpose(selected->transform().matrix()); auto *transform_ptr = transform.data(); + const auto snap_value = transform_operation == ::ImGuizmo::ROTATE ? 45.0f : 1.0f; + std::array snap = {snap_value, snap_value, snap_value}; + ::ImGuizmo::Manipulate( inv_view.data(), inv_proj.data(), @@ -327,7 +347,7 @@ void selected_object_gizmo_ui( ::ImGuizmo::WORLD, const_cast(transform_ptr), nullptr, - nullptr, + snap.data(), nullptr, nullptr); @@ -380,7 +400,7 @@ void Gui::render() { AutoBegin begin{"Object creator", nullptr, ImGuiWindowFlags_None}; - object_creator_ui(iris_ctx_, entities_, scene_, &selected_); + object_creator_ui(iris_ctx_, entities_, entity_creators_, scene_, &selected_); object_editor_tree_ui(entities_); selected_object_gizmo_ui(io_, selected_, camera_, transform_operation_); } @@ -393,6 +413,7 @@ void Gui::handle_input(const iris::Event &event) { static auto x = window_->width() / 2.0f; static auto y = window_->height() / 2.0f; + static auto control = false; if (event.is_mouse()) { @@ -472,6 +493,33 @@ void Gui::handle_input(const iris::Event &event) case W: transform_operation_ = ::ImGuizmo::TRANSLATE; break; case E: transform_operation_ = ::ImGuizmo::ROTATE; break; case R: transform_operation_ = ::ImGuizmo::SCALE; break; + case D: + { + if ((selected_ != nullptr) && control) + { + const auto creator = entity_creators_[selected_]; + creator(); + entity_creators_[selected_] = creator; + } + break; + } + case FORWARD_DELETE: + { + if (selected_ != nullptr) + { + for (auto *entity : selected_->entities()) + { + scene_->remove(entity); + } + + std::erase_if(entities_, [this](const Entity &e) { return std::addressof(e) == selected_; }); + selected_ = nullptr; + } + + break; + } + case CONTROL: [[fallthrough]]; + case COMMAND: control = true; break; default: break; } } diff --git a/tools/sclera/gui.h b/tools/sclera/gui.h index c3de32e5..7c65a230 100644 --- a/tools/sclera/gui.h +++ b/tools/sclera/gui.h @@ -7,6 +7,8 @@ #pragma once #include +#include +#include #include "imgui.h" @@ -45,4 +47,5 @@ class Gui bool show_demo_; ::ImGuizmo::OPERATION transform_operation_; Entity *selected_; + std::unordered_map> entity_creators_; }; From 3ddc0ce80a02e2d022b26dec4b589b95efeb5e21 Mon Sep 17 00:00:00 2001 From: iris-engine-dev Date: Wed, 5 Apr 2023 20:50:17 +0100 Subject: [PATCH 16/16] wip --- CMakeLists.txt | 15 +++ include/iris/core/default_resource_manager.h | 5 + include/iris/core/resource_manager.h | 6 + include/iris/graphics/render_pipeline.h | 4 + include/iris/graphics/scene_loader.h | 31 +++++ include/iris/graphics/yaml_scene_loader.h | 39 ++++++ src/CMakeLists.txt | 2 +- src/core/default_resource_manager.cpp | 11 ++ src/core/resource_manager.cpp | 6 + src/graphics/CMakeLists.txt | 2 + src/graphics/render_pipeline.cpp | 100 ++++++++------ src/graphics/yaml_scene_loader.cpp | 135 +++++++++++++++++++ tests/mocks/mock_resource_manager.h | 4 + tools/sclera/entity.cpp | 39 ++---- tools/sclera/entity.h | 14 +- tools/sclera/gui.cpp | 109 ++++++++++++++- 16 files changed, 443 insertions(+), 79 deletions(-) create mode 100644 include/iris/graphics/scene_loader.h create mode 100644 include/iris/graphics/yaml_scene_loader.h create mode 100644 src/graphics/yaml_scene_loader.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bfc05823..3d6268f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,10 @@ set(ASSIMP_NO_EXPORT ON CACHE BOOL "" FORCE) set(INJA_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(BUILD_BENCHMARK OFF CACHE BOOL "" FORCE) set(COVERALLS OFF CACHE BOOL "" FORCE) +set(YAML_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(YAML_CPP_FORMAT_SOURCE OFF CACHE BOOL "" FORCE) +set(YAML_CPP_BUILD_CONTRIB OFF CACHE BOOL "" FORCE) +set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "" FORCE) # fetch third party libraries # note that in most cases we manually populate and add, this alloes us to use @@ -130,6 +134,17 @@ if(NOT inja_POPULATED) add_subdirectory(${inja_SOURCE_DIR} ${inja_BINARY_DIR} EXCLUDE_FROM_ALL) endif() +FetchContent_Declare( + yamlcpp + GIT_REPOSITORY https://github.com/jbeder/yaml-cpp/ + GIT_TAG yaml-cpp-0.7.0) +FetchContent_GetProperties(yamlcpp) + +if(NOT yamlcpp_POPULATED) + FetchContent_Populate(yamlcpp) + add_subdirectory(${yamlcpp_SOURCE_DIR} ${yamlcpp_BINARY_DIR} EXCLUDE_FROM_ALL) +endif() + if(IRIS_PLATFORM MATCHES "WIN32") FetchContent_Declare( directx-headers diff --git a/include/iris/core/default_resource_manager.h b/include/iris/core/default_resource_manager.h index 531014a9..ab5ac693 100644 --- a/include/iris/core/default_resource_manager.h +++ b/include/iris/core/default_resource_manager.h @@ -19,6 +19,9 @@ namespace iris */ class DefaultResourceManager : public ResourceManager { + public: + bool exists(std::string_view resource) const override; + protected: /** * Load data frm disk. @@ -30,6 +33,8 @@ class DefaultResourceManager : public ResourceManager * Loaded data. */ DataBuffer do_load(std::string_view resource) override; + + void do_save(std::string_view resource, const DataBuffer &data) override; }; } diff --git a/include/iris/core/resource_manager.h b/include/iris/core/resource_manager.h index c9d2d70c..1d5e5eb1 100644 --- a/include/iris/core/resource_manager.h +++ b/include/iris/core/resource_manager.h @@ -40,6 +40,8 @@ class ResourceManager */ const DataBuffer &load(std::string_view resource); + void save(std::string_view resource, const DataBuffer &data); + /** * Set root resource location. Note that implementations may choose to ignore this. * @@ -50,6 +52,8 @@ class ResourceManager std::vector available_resources() const; + virtual bool exists(std::string_view resource) const = 0; + protected: /** * Implementations should override this to perform their specific data loading logic. @@ -62,6 +66,8 @@ class ResourceManager */ virtual DataBuffer do_load(std::string_view resource) = 0; + virtual void do_save(std::string_view resource, const DataBuffer &data) = 0; + /** Resource root. */ std::filesystem::path root_; diff --git a/include/iris/graphics/render_pipeline.h b/include/iris/graphics/render_pipeline.h index 44596926..34fd22f8 100644 --- a/include/iris/graphics/render_pipeline.h +++ b/include/iris/graphics/render_pipeline.h @@ -24,6 +24,8 @@ namespace iris { +class SceneLoader; + /** * This class encapsulates all the logic and machinery of rendering. It creates and manages the primitives a user needs * to build their desired rendered output. @@ -77,6 +79,8 @@ class RenderPipeline */ Scene *create_scene(); + Scene *create_scene(const SceneLoader &loader); + /** * Create a RenderGraph for use with this pipeline. * diff --git a/include/iris/graphics/scene_loader.h b/include/iris/graphics/scene_loader.h new file mode 100644 index 00000000..bc48c19d --- /dev/null +++ b/include/iris/graphics/scene_loader.h @@ -0,0 +1,31 @@ +//////////////////////////////////////////////////////////////////////////////// +// Distributed under the Boost Software License, Version 1.0. // +// (See accompanying file LICENSE or copy at // +// https://www.boost.org/LICENSE_1_0.txt) // +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include + +namespace iris +{ + +class Scene; +class SingleEntity; + +class SceneLoader +{ + public: + virtual ~SceneLoader() = default; + + virtual void load( + Scene *scene, + std::function &, std::string_view file_name)> entity_callback = + nullptr) const = 0; + + private: +}; + +} diff --git a/include/iris/graphics/yaml_scene_loader.h b/include/iris/graphics/yaml_scene_loader.h new file mode 100644 index 00000000..256f22cd --- /dev/null +++ b/include/iris/graphics/yaml_scene_loader.h @@ -0,0 +1,39 @@ +//////////////////////////////////////////////////////////////////////////////// +// Distributed under the Boost Software License, Version 1.0. // +// (See accompanying file LICENSE or copy at // +// https://www.boost.org/LICENSE_1_0.txt) // +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include + +#include "core/context.h" +#include "graphics/scene_loader.h" + +namespace iris +{ + +class Scene; +class SingleEntity; + +class YamlSceneLoader : public SceneLoader +{ + public: + YamlSceneLoader(Context &ctx, std::string_view file_name); + ~YamlSceneLoader() override; + + void load( + Scene *scene, + std::function &, std::string_view file_name)> entity_callback = + nullptr) const override; + + private: + Context &ctx_; + struct implementation; + std::unique_ptr impl_; +}; +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 62a9834e..38469438 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -47,7 +47,7 @@ elseif(IRIS_PLATFORM MATCHES "IOS") endif() # default link options (maybe extended by platform below) -set(IRIS_LINKED_LIBS IrrXML zlibstatic BulletDynamics BulletCollision LinearMath assimp lua) +set(IRIS_LINKED_LIBS IrrXML zlibstatic BulletDynamics BulletCollision LinearMath assimp lua yaml-cpp) set(IRIS_LINKED_LIBS_PRIVATE) # handle platform specific setup including setting default graphics apis diff --git a/src/core/default_resource_manager.cpp b/src/core/default_resource_manager.cpp index c488f709..3b756bcb 100644 --- a/src/core/default_resource_manager.cpp +++ b/src/core/default_resource_manager.cpp @@ -16,6 +16,11 @@ namespace iris { +bool DefaultResourceManager::exists(std::string_view resource) const +{ + return std::filesystem::exists(root_ / resource); +} + DataBuffer DefaultResourceManager::do_load(std::string_view resource) { std::stringstream strm{}; @@ -31,4 +36,10 @@ DataBuffer DefaultResourceManager::do_load(std::string_view resource) return {str_ptr, str_ptr + str.length()}; } +void DefaultResourceManager::do_save(std::string_view resource, const DataBuffer &data) +{ + std::fstream f(root_ / resource, std::ios::out | std::ios::binary); + f.write(reinterpret_cast(data.data()), data.size()); +} + } diff --git a/src/core/resource_manager.cpp b/src/core/resource_manager.cpp index 9d229f99..105cb0e5 100644 --- a/src/core/resource_manager.cpp +++ b/src/core/resource_manager.cpp @@ -38,6 +38,12 @@ const DataBuffer &ResourceManager::load(std::string_view resource) return loaded_resource->second; } +void ResourceManager::save(std::string_view resource, const DataBuffer &data) +{ + resources_[std::string{resource}] = data; + do_save(resource, data); +} + void ResourceManager::set_root_directory(const std::filesystem::path &root) { root_ = root; diff --git a/src/graphics/CMakeLists.txt b/src/graphics/CMakeLists.txt index 87f57f57..d4f6ea8e 100644 --- a/src/graphics/CMakeLists.txt +++ b/src/graphics/CMakeLists.txt @@ -56,6 +56,7 @@ target_sources(iris PRIVATE ${INCLUDE_ROOT}/weight.h ${INCLUDE_ROOT}/window.h ${INCLUDE_ROOT}/window_manager.h + ${INCLUDE_ROOT}/yaml_scene_loader.h bone.cpp cube_map.cpp instanced_entity.cpp @@ -78,4 +79,5 @@ target_sources(iris PRIVATE utils.cpp vertex_attributes.cpp window.cpp + yaml_scene_loader.cpp ) diff --git a/src/graphics/render_pipeline.cpp b/src/graphics/render_pipeline.cpp index 2a84955b..e9073a34 100644 --- a/src/graphics/render_pipeline.cpp +++ b/src/graphics/render_pipeline.cpp @@ -31,6 +31,7 @@ #include "graphics/render_target_manager.h" #include "graphics/renderer.h" #include "graphics/scene.h" +#include "graphics/scene_loader.h" #include "graphics/single_entity.h" #include "log/log.h" @@ -173,6 +174,14 @@ Scene *RenderPipeline::create_scene() return scenes_.back().get(); } +Scene *RenderPipeline::create_scene(const SceneLoader &loader) +{ + auto *scene = create_scene(); + loader.load(scene); + + return scene; +} + RenderGraph *RenderPipeline::create_render_graph() { // using new to access private ctor @@ -247,13 +256,16 @@ std::vector RenderPipeline::build() const auto prev_camera = prev->camera; // add a pass to calculate ssao (combined with the ambient light pass) - auto *ao_target = add_pass(pre_process_passes, [prev, ssao](RenderGraph *rg, const RenderTarget *target) { - rg->set_render_node( - rg->create(target->colour_texture()), - rg->create(prev->normal_target->colour_texture()), - rg->create(prev->position_target->colour_texture()), - *ssao); - }); + auto *ao_target = add_pass( + pre_process_passes, + [prev, ssao](RenderGraph *rg, const RenderTarget *target) + { + rg->set_render_node( + rg->create(target->colour_texture()), + rg->create(prev->normal_target->colour_texture()), + rg->create(prev->position_target->colour_texture()), + *ssao); + }); // ensure we render with the perspective camera not the orthographic camera that will be created for the // new pass @@ -445,51 +457,63 @@ void RenderPipeline::add_post_processing_passes() if (const auto bloom = description.bloom; bloom) { - const auto *null_target = add_pass(render_passes, [](RenderGraph *rg, const RenderTarget *target) { - rg->render_node()->set_colour_input(rg->create(target->colour_texture())); - }); - - add_pass(render_passes, [&bloom](RenderGraph *rg, const RenderTarget *target) { - rg->render_node()->set_colour_input(rg->create( - rg->create( + const auto *null_target = add_pass( + render_passes, + [](RenderGraph *rg, const RenderTarget *target) + { rg->render_node()->set_colour_input(rg->create(target->colour_texture())); }); + + add_pass( + render_passes, + [&bloom](RenderGraph *rg, const RenderTarget *target) + { + rg->render_node()->set_colour_input(rg->create( + rg->create( + rg->create(target->colour_texture()), + rg->create>(Colour{0.2126f, 0.7152f, 0.0722f, 0.0f}), + BinaryOperator::DOT), + rg->create>(bloom->threshold), rg->create(target->colour_texture()), - rg->create>(Colour{0.2126f, 0.7152f, 0.0722f, 0.0f}), - BinaryOperator::DOT), - rg->create>(bloom->threshold), - rg->create(target->colour_texture()), - rg->create>(Colour{0.0f, 0.0f, 0.0f, 1.0f}), - ConditionalOperator::GREATER)); - }); + rg->create>(Colour{0.0f, 0.0f, 0.0f, 1.0f}), + ConditionalOperator::GREATER)); + }); for (auto i = 0u; i < bloom->iterations; ++i) { - add_pass(render_passes, [](RenderGraph *rg, const RenderTarget *target) { - rg->render_node()->set_colour_input( - rg->create(rg->create(target->colour_texture()))); - }); + add_pass( + render_passes, + [](RenderGraph *rg, const RenderTarget *target) { + rg->render_node()->set_colour_input( + rg->create(rg->create(target->colour_texture()))); + }); } - add_pass(render_passes, [null_target](RenderGraph *rg, const RenderTarget *target) { - rg->render_node()->set_colour_input(rg->create( - rg->create(null_target->colour_texture()), - rg->create(target->colour_texture()), - BinaryOperator::ADD)); - }); + add_pass( + render_passes, + [null_target](RenderGraph *rg, const RenderTarget *target) + { + rg->render_node()->set_colour_input(rg->create( + rg->create(null_target->colour_texture()), + rg->create(target->colour_texture()), + BinaryOperator::ADD)); + }); } if (const auto colour_adjust = description.colour_adjust; colour_adjust) { - add_pass(render_passes, [&colour_adjust](RenderGraph *rg, const RenderTarget *target) { - rg->set_render_node( - rg->create(target->colour_texture()), *colour_adjust); - }); + add_pass( + render_passes, + [&colour_adjust](RenderGraph *rg, const RenderTarget *target) { + rg->set_render_node( + rg->create(target->colour_texture()), *colour_adjust); + }); } if (description.anti_aliasing) { - add_pass(render_passes, [](RenderGraph *rg, const RenderTarget *target) { - rg->set_render_node(rg->create(target->colour_texture())); - }); + add_pass( + render_passes, + [](RenderGraph *rg, const RenderTarget *target) + { rg->set_render_node(rg->create(target->colour_texture())); }); } render_passes.back()->colour_target = input_target; diff --git a/src/graphics/yaml_scene_loader.cpp b/src/graphics/yaml_scene_loader.cpp new file mode 100644 index 00000000..eba1fddb --- /dev/null +++ b/src/graphics/yaml_scene_loader.cpp @@ -0,0 +1,135 @@ +//////////////////////////////////////////////////////////////////////////////// +// Distributed under the Boost Software License, Version 1.0. // +// (See accompanying file LICENSE or copy at // +// https://www.boost.org/LICENSE_1_0.txt) // +//////////////////////////////////////////////////////////////////////////////// + +#include "graphics/yaml_scene_loader.h" + +#include +#include +#include +#include + +#include "core/quaternion.h" +#include "core/resource_manager.h" +#include "core/vector3.h" +#include "graphics/mesh_manager.h" +#include "graphics/scene.h" +#include "graphics/single_entity.h" + +#include "yaml-cpp/yaml.h" + +namespace YAML +{ + +template <> +struct convert +{ + static Node encode(const iris::Vector3 &rhs) + { + Node node; + node.push_back(rhs.x); + node.push_back(rhs.y); + node.push_back(rhs.z); + return node; + } + + static bool decode(const Node &node, iris::Vector3 &rhs) + { + if (!node.IsSequence() || node.size() != 3) + { + return false; + } + + rhs.x = node[0].as(); + rhs.y = node[1].as(); + rhs.z = node[2].as(); + return true; + } +}; + +template <> +struct convert +{ + static Node encode(const iris::Quaternion &rhs) + { + Node node; + node.push_back(rhs.x); + node.push_back(rhs.y); + node.push_back(rhs.z); + node.push_back(rhs.w); + return node; + } + + static bool decode(const Node &node, iris::Quaternion &rhs) + { + if (!node.IsSequence() || node.size() != 4) + { + return false; + } + + rhs.x = node[0].as(); + rhs.y = node[1].as(); + rhs.z = node[2].as(); + rhs.w = node[3].as(); + return true; + } +}; + +} + +namespace iris +{ + +struct YamlSceneLoader::implementation +{ + ::YAML::Node config; +}; + +YamlSceneLoader::YamlSceneLoader(Context &ctx, std::string_view file_name) + : ctx_(ctx) + , impl_(std::make_unique()) +{ + const auto file_contents = ctx_.resource_manager().load(file_name); + std::string file_as_str(file_contents.size(), '\0'); + std::transform( + std::cbegin(file_contents), + std::cend(file_contents), + std::begin(file_as_str), + [](auto byte) { return static_cast(byte); }); + + impl_->config = ::YAML::Load(file_as_str); +} + +YamlSceneLoader::~YamlSceneLoader() = default; + +void YamlSceneLoader::load( + Scene *scene, + std::function &, std::string_view file_name)> entity_callback) const +{ + for (const auto &entity : impl_->config["models"]) + { + const auto file_name = entity["file_name"].as(); + const auto position = entity["position"].as(); + const auto rotation = entity["rotation"].as(); + const auto scale = entity["scale"].as(); + + const auto loaded_mesh = ctx_.mesh_manager().load_mesh(file_name); + + std::vector entities{}; + + for (const auto &mesh : loaded_mesh.mesh_data) + { + entities.push_back(scene->create_entity( + nullptr, mesh.mesh, iris::Transform{position, rotation, scale})); + } + + if (entity_callback) + { + entity_callback(entities, file_name); + } + } +} + +} diff --git a/tests/mocks/mock_resource_manager.h b/tests/mocks/mock_resource_manager.h index 85919ab1..a7fa52dc 100644 --- a/tests/mocks/mock_resource_manager.h +++ b/tests/mocks/mock_resource_manager.h @@ -15,6 +15,10 @@ class MockResourceManager : public iris::ResourceManager { + public: + MOCK_METHOD(bool, exists, (std::string_view), (const override)); + protected: MOCK_METHOD(iris::DataBuffer, do_load, (std::string_view), (override)); + MOCK_METHOD(void, do_save, (std::string_view, const iris::DataBuffer &), (override)); }; diff --git a/tools/sclera/entity.cpp b/tools/sclera/entity.cpp index 36283d44..ab61517a 100644 --- a/tools/sclera/entity.cpp +++ b/tools/sclera/entity.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include "core/error_handling.h" @@ -17,41 +18,13 @@ #include "graphics/mesh_manager.h" #include "graphics/single_entity.h" -namespace -{ - -iris::Vector3 calculate_centre(const std::vector &entities) -{ - iris::ensure(!entities.empty(), "vector cannot be empty"); - - return std::reduce( - std::cbegin(entities), - std::cend(entities), - iris::Vector3{}, - [](const auto &total, const iris::SingleEntity *e2) -> iris::Vector3 - { return total + e2->position(); }) / - static_cast(entities.size()); -} - -} - -Entity::Entity(const std::vector &entities) - : Entity(entities, calculate_centre(entities)) -{ -} - -Entity::Entity(const std::vector &entities, const iris::Vector3 ¢re) +Entity::Entity(const std::vector &entities, std::string_view file_name) : entities_(entities) - , centre_(centre) , transform_() + , file_name_(file_name) { } -iris::Vector3 Entity::centre() const -{ - return centre_; -} - iris::Transform Entity::transform() const { return transform_; @@ -63,6 +36,7 @@ void Entity::set_transform(const iris::Transform &transform) { entity->set_transform(transform.matrix()); } + transform_ = transform; } @@ -104,3 +78,8 @@ std::vector Entity::entities() const { return entities_; } + +std::string Entity::file_name() const +{ + return file_name_; +} diff --git a/tools/sclera/entity.h b/tools/sclera/entity.h index b61e73ab..4142dfe1 100644 --- a/tools/sclera/entity.h +++ b/tools/sclera/entity.h @@ -6,6 +6,8 @@ #pragma once +#include +#include #include #include "core/transform.h" @@ -17,11 +19,7 @@ class Entity { public: - Entity(const std::vector &entities); - - Entity(const std::vector &entities, const iris::Vector3 ¢re); - - iris::Vector3 centre() const; + Entity(const std::vector &entities, std::string_view file_name); iris::Transform transform() const; @@ -33,10 +31,12 @@ class Entity std::vector entities() const; + std::string file_name() const; + private: std::vector entities_; - iris::Vector3 centre_; - iris::Transform transform_; + + std::string file_name_; }; diff --git a/tools/sclera/gui.cpp b/tools/sclera/gui.cpp index a3c0544c..3e768393 100644 --- a/tools/sclera/gui.cpp +++ b/tools/sclera/gui.cpp @@ -20,7 +20,9 @@ #include "graphics/scene.h" #include "graphics/single_entity.h" #include "graphics/window.h" +#include "graphics/yaml_scene_loader.h" #include "log/log.h" +#include "yaml-cpp/yaml.h" namespace { @@ -143,6 +145,65 @@ struct AutoBegin } }; +struct AutoSequence +{ + AutoSequence(::YAML::Emitter &out) + : out_(out) + { + out_ << ::YAML::BeginSeq; + } + + ~AutoSequence() + { + out_ << ::YAML::EndSeq; + } + + ::YAML::Emitter &out_; +}; + +struct AutoMap +{ + AutoMap(::YAML::Emitter &out) + : out_(out) + { + out_ << ::YAML::BeginMap; + } + + ~AutoMap() + { + out_ << ::YAML::EndMap; + } + + ::YAML::Emitter &out_; +}; + +YAML::Emitter &operator<<(YAML::Emitter &out, const iris::Vector3 &v) +{ + out << YAML::Flow; + AutoSequence seq{out}; + out << v.x << v.y << v.z; + + return out; +} + +YAML::Emitter &operator<<(YAML::Emitter &out, const iris::Quaternion &q) +{ + out << YAML::Flow; + AutoSequence seq{out}; + out << q.x << q.y << q.z << q.w; + + return out; +} + +template +void serialise_yaml_key_value(::YAML::Emitter &out, const std::string &key, const T &value) +{ + out << ::YAML::Key; + out << key; + out << ::YAML::Value; + out << value; +} + std::string label_name(std::string_view label, std::uint32_t id) { return std::string{label} + std::to_string(id); @@ -162,7 +223,7 @@ void object_creator_ui( auto *entity = scene->create_entity( nullptr, ctx.mesh_manager().cube(iris::Colour{1.0f, 1.0f, 1.0f}), iris::Transform{{}, {}, {1.0f}}); - return Entity{std::vector{entity}, iris::Vector3{}}; + return Entity{std::vector{entity}, ""}; }; auto &new_entity = entities.emplace_back(creator()); @@ -196,7 +257,7 @@ void object_creator_ui( nullptr, mesh.mesh, iris::Transform{{}, {}, {1.0f}})); } - auto &new_entity = entities.emplace_back(engine_entities); + auto &new_entity = entities.emplace_back(engine_entities, model); *selected_entity = std::addressof(new_entity); (*selected_entity) ->set_transform( @@ -355,6 +416,34 @@ void selected_object_gizmo_ui( } } +void save_scene(iris::Context &ctx, std::deque &entities) +{ + ::YAML::Emitter out; + + AutoMap models{out}; + out << ::YAML::Key << "models"; + out << ::YAML::Value; + + AutoSequence seq{out}; + for (const auto &entity : entities) + { + AutoMap map{out}; + + const auto [position, rotation, scale] = entity.transform().decompose(); + + serialise_yaml_key_value(out, "file_name", entity.file_name()); + serialise_yaml_key_value(out, "position", position); + serialise_yaml_key_value(out, "rotation", rotation); + serialise_yaml_key_value(out, "scale", scale); + } + + const auto *string_ptr = out.c_str(); + iris::DataBuffer data(std::strlen(string_ptr)); + std::memcpy(data.data(), string_ptr, std::strlen(string_ptr)); + + ctx.resource_manager().save("scene.yml", data); +} + } Gui::Gui(iris::Context &ctx, const iris::Window *window, iris::Scene *scene, iris::Camera &camera) @@ -368,8 +457,14 @@ Gui::Gui(iris::Context &ctx, const iris::Window *window, iris::Scene *scene, iri , show_demo_(false) , transform_operation_(::ImGuizmo::TRANSLATE) { + if (ctx.resource_manager().exists("scene.yml")) + { + iris::YamlSceneLoader loader{iris_ctx_, "scene.yml"}; + loader.load( + scene, [this](const auto &entities, auto file_name) { entities_.emplace_back(entities, file_name); }); + } + const auto scale = window_->screen_scale(); - const auto resources = ctx.resource_manager().available_resources(); io_.ConfigFlags |= ::ImGuiConfigFlags_NavEnableKeyboard; io_.ConfigFlags |= ::ImGuiConfigFlags_NavEnableGamepad; @@ -503,6 +598,14 @@ void Gui::handle_input(const iris::Event &event) } break; } + case S: + { + if (control) + { + save_scene(iris_ctx_, entities_); + } + break; + } case FORWARD_DELETE: { if (selected_ != nullptr)