From 313a571e43ddf7bba60f9e8fae782578e97a3aac Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Wed, 29 Apr 2026 03:19:46 +0800 Subject: [PATCH] refactor(editor): isolate engine service boundaries --- editor/AGENTS.md | 114 + editor/CMakeLists.txt | 6 + editor/app/Bootstrap/Application.cpp | 27 +- editor/app/Bootstrap/Application.h | 4 +- editor/app/Composition/EditorContext.cpp | 6 +- editor/app/Composition/EditorContext.h | 4 +- editor/app/Composition/EditorShellRuntime.cpp | 10 +- editor/app/Composition/EditorShellRuntime.h | 4 +- .../app/Core/Engine/EditorEngineLifecycle.h | 13 + .../Core/Engine/EditorSceneBackendFactory.h | 16 + editor/app/Core/Engine/EditorShaderProvider.h | 17 + .../Core/Engine/GameViewportEngineBridge.h | 32 + ...Services.h => SceneViewportEngineBridge.h} | 26 +- .../Core/Product/EditorProductManifest.cpp | 6 +- .../app/Core/Product/EditorProductManifest.h | 2 + .../Viewport/EditorViewportRuntimeServices.h | 8 +- .../Windowing/EditorWorkspaceShellRuntime.h | 8 +- .../Features/EditorWorkspacePanelRegistry.cpp | 51 + .../Features/Game/GameViewportController.cpp | 62 + .../Features/Game/GameViewportController.h | 24 + .../app/Features/Game/GameViewportFeature.cpp | 28 + .../app/Features/Game/GameViewportFeature.h | 27 + .../Inspector/InspectorFieldValueApplier.h | 46 + .../app/Features/Inspector/InspectorPanel.cpp | 27 +- .../EditorViewportRuntimeServicesFactory.cpp | 3 +- .../EditorViewportRuntimeServicesFactory.h | 5 +- .../Viewport/GameViewportRenderService.cpp | 109 + .../Viewport/GameViewportRenderService.h | 25 + .../Viewport/Passes/SceneViewportGridPass.cpp | 18 +- .../Viewport/Passes/SceneViewportGridPass.h | 4 +- .../SceneViewportSelectionOutlinePass.cpp | 18 +- .../SceneViewportSelectionOutlinePass.h | 4 +- .../SceneViewportRenderPassBundle.cpp | 6 +- .../Viewport/SceneViewportRenderPassBundle.h | 4 +- .../Viewport/SceneViewportRenderService.cpp | 24 +- .../Viewport/SceneViewportRenderService.h | 8 +- .../Viewport/ViewportHostService.cpp | 21 +- .../Rendering/Viewport/ViewportHostService.h | 9 +- .../Services/Engine/EngineEditorServices.cpp | 2440 +---------------- .../Services/Engine/EngineEditorServices.h | 29 +- .../Engine/EngineGameViewportBridge.cpp | 66 + .../Engine/EngineGameViewportBridge.h | 23 + .../Engine/EngineSceneViewportBridge.cpp | 187 ++ .../Engine/EngineSceneViewportBridge.h | 43 + .../app/Services/Scene/EditorSceneRuntime.cpp | 26 +- .../Scene/EngineEditorSceneBackend.cpp | 2213 +++++++++++++++ .../Content/EditorWindowContentController.h | 8 +- ...EditorWorkspaceWindowContentController.cpp | 4 +- editor/app/Windowing/EditorWindowManager.cpp | 18 +- editor/app/Windowing/EditorWindowManager.h | 12 +- .../Runtime/EditorWindowRuntimeController.cpp | 12 +- .../Runtime/EditorWindowRuntimeController.h | 12 +- tests/UI/Editor/unit/CMakeLists.txt | 1 + .../test_editor_scene_runtime_backend.cpp | 50 +- .../test_editor_shell_asset_validation.cpp | 6 +- .../unit/test_game_viewport_runtime.cpp | 100 + .../unit/test_hierarchy_scene_binding.cpp | 22 +- .../unit/test_inspector_presentation.cpp | 71 +- .../unit/test_scene_viewport_render_plan.cpp | 41 +- .../unit/test_scene_viewport_runtime.cpp | 205 +- 60 files changed, 3804 insertions(+), 2611 deletions(-) create mode 100644 editor/AGENTS.md create mode 100644 editor/app/Core/Engine/EditorEngineLifecycle.h create mode 100644 editor/app/Core/Engine/EditorSceneBackendFactory.h create mode 100644 editor/app/Core/Engine/EditorShaderProvider.h create mode 100644 editor/app/Core/Engine/GameViewportEngineBridge.h rename editor/app/Core/Engine/{EditorEngineServices.h => SceneViewportEngineBridge.h} (60%) create mode 100644 editor/app/Features/Game/GameViewportController.cpp create mode 100644 editor/app/Features/Game/GameViewportController.h create mode 100644 editor/app/Features/Game/GameViewportFeature.cpp create mode 100644 editor/app/Features/Game/GameViewportFeature.h create mode 100644 editor/app/Features/Inspector/InspectorFieldValueApplier.h create mode 100644 editor/app/Rendering/Viewport/GameViewportRenderService.cpp create mode 100644 editor/app/Rendering/Viewport/GameViewportRenderService.h create mode 100644 editor/app/Services/Engine/EngineGameViewportBridge.cpp create mode 100644 editor/app/Services/Engine/EngineGameViewportBridge.h create mode 100644 editor/app/Services/Engine/EngineSceneViewportBridge.cpp create mode 100644 editor/app/Services/Engine/EngineSceneViewportBridge.h create mode 100644 editor/app/Services/Scene/EngineEditorSceneBackend.cpp create mode 100644 tests/UI/Editor/unit/test_game_viewport_runtime.cpp diff --git a/editor/AGENTS.md b/editor/AGENTS.md new file mode 100644 index 00000000..bdee0405 --- /dev/null +++ b/editor/AGENTS.md @@ -0,0 +1,114 @@ +# Editor Agent Guide + +This file is for agents working in `editor/**` and `tests/UI/Editor/**`. +Treat the current code and tests as source of truth. If this guide drifts from +the checkout, update it in the same change. + +## Current Priority + +The editor is not in a "split more layers for their own sake" phase. + +The primary product line is still runtime/product loop closure: + +- bind real owners for `run.*` +- bind real owners for `scripts.*` +- bind real owners for scene document commands such as + `file.new_scene`, `file.open_scene`, `file.save_scene`, and + `file.save_scene_as` +- keep `Game`, `Scene`, `Inspector`, `Selection`, and `Console` coherent across + runtime transitions + +Do not burn time on broad architectural churn unless it directly unlocks that +product loop or removes an active architectural hazard. + +## Engine Boundary Status + +The old public `EditorEngineServices` god interface is gone. + +The current editor-to-engine boundaries are narrow and must stay narrow: + +- `EditorSceneBackendFactory` +- `SceneViewportEngineBridge` +- `GameViewportEngineBridge` +- `EditorShaderProvider` +- `EditorEngineLifecycle` + +`EngineEditorComposition` in `editor/app/Services/Engine/EngineEditorServices.*` +is an internal composition root that hands out those narrow interfaces. It is +not a new shared facade for features to depend on. + +Rules: + +- do not recreate `EditorEngineServices` +- do not add a replacement god interface under a different name +- do not make new panels, passes, or runtimes depend on a catch-all engine + service locator +- keep render-pass dependencies low-level: passes may depend on + `EditorShaderProvider`, not on scene backend creation or lifecycle code +- composition owns assembly; features and passes must receive explicit + dependencies + +## Product Truths + +- `GameViewportFeature`, `GameViewportRenderService`, and related tests exist. + The game viewport path is real, but that does not mean play-mode ownership is + complete. +- `EditorHostCommandBridge` still exposes honest failure messages for commands + without a bound owner. Do not replace these with fake success. +- `EditorSceneRuntime` already owns real scene editing behavior. Missing work is + usually at the app/document/runtime owner layer, not in the raw scene backend. +- `ProjectPanel` should keep using explicit runtime ownership and honest blocked + behavior for unsupported asset commands. + +## Working Rules + +- prefer explicit owner/coordinator/runtime-service seams over direct panel to + engine hookups +- when changing runtime or document flow, inspect `EditorSession`, + `EditorHostCommandBridge`, the relevant feature, and the matching tests +- if a change affects ownership, state flow, or command routing, add or update + tests in `tests/UI/Editor/unit` +- keep multi-window work behind runtime/document correctness; do not move it + ahead of core ownership closure + +## Do Not Regress + +- no new service-locator style editor-to-engine dependency +- no new low-level render pass dependency on scene backend creation or lifecycle +- no silent fallback where a command should fail honestly +- no panel-local shortcut that bypasses the intended runtime owner + +## Good Entry Points + +- `editor/app/Bootstrap/Application.cpp` +- `editor/app/Composition/EditorContext.cpp` +- `editor/app/Composition/EditorShellRuntime.cpp` +- `editor/app/Rendering/Viewport/ViewportHostService.cpp` +- `editor/app/Services/Engine/EngineEditorServices.h` +- `editor/app/Services/Scene/EditorSceneRuntime.cpp` +- `editor/app/Core/Commands/EditorHostCommandBridge.cpp` +- `docs/plan/editor-next-stage-runtime-plan.md` +- `docs/plan/editor-engine-services-refactor-plan.md` + +## Verification + +Minimum verification for editor app/core changes: + +```powershell +cmake --build build --config Debug --target editor_app_core_tests +cmake --build build --config Debug --target editor_app_feature_tests +``` + +When the executable path is affected, also rebuild: + +```powershell +cmake --build build --config Debug --target XCEditor +``` + +Relevant focused tests include: + +- `test_editor_host_command_bridge.cpp` +- `test_game_viewport_runtime.cpp` +- `test_project_panel.cpp` +- `test_scene_viewport_render_plan.cpp` +- `test_scene_viewport_runtime.cpp` diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 8f02fd75..335d41d8 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -246,6 +246,8 @@ if(XCENGINE_BUILD_XCUI_EDITOR_CORE) app/Features/ColorPicker/ColorPickerPanel.cpp app/Features/EditorUtilityWindowRegistry.cpp app/Features/EditorWorkspacePanelRegistry.cpp + app/Features/Game/GameViewportController.cpp + app/Features/Game/GameViewportFeature.cpp app/Features/Hierarchy/HierarchyModel.cpp app/Features/Hierarchy/HierarchyPanel.cpp app/Features/Inspector/AddComponentPanel.cpp @@ -269,6 +271,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_CORE) app/Rendering/Assets/EditorIconServiceFactory.cpp app/Rendering/Assets/BuiltInIcons.cpp app/Rendering/Viewport/EditorViewportRuntimeServicesFactory.cpp + app/Rendering/Viewport/GameViewportRenderService.cpp app/Rendering/Viewport/Passes/SceneViewportGridPass.cpp app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp app/Rendering/Viewport/Passes/SceneViewportSelectedHelpersPass.cpp @@ -281,6 +284,9 @@ if(XCENGINE_BUILD_XCUI_EDITOR_CORE) set(XCUI_EDITOR_APP_SUPPORT_SOURCES app/Services/Engine/EngineEditorServices.cpp + app/Services/Engine/EngineGameViewportBridge.cpp + app/Services/Engine/EngineSceneViewportBridge.cpp + app/Services/Scene/EngineEditorSceneBackend.cpp app/Services/Scene/EditorSceneRuntime.cpp app/Services/Project/EditorProjectRuntime.cpp app/Services/Project/ProjectBrowserModel.cpp diff --git a/editor/app/Bootstrap/Application.cpp b/editor/app/Bootstrap/Application.cpp index 4501ab76..6421682e 100644 --- a/editor/app/Bootstrap/Application.cpp +++ b/editor/app/Bootstrap/Application.cpp @@ -175,7 +175,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { m_hInstance = hInstance; m_resourceService = std::make_unique(m_hInstance); m_runtimePaths = ResolveRuntimePaths(m_resourceService->GetExecutableDirectory()); - m_engineServices = App::CreateEngineEditorServices(); + m_engineComposition = App::CreateEngineEditorComposition(); EnableDpiAwareness(); const std::filesystem::path logRoot = m_resourceService->GetExecutableDirectory() / "logs"; @@ -186,11 +186,13 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { SetUnhandledExceptionFilter(&Application::HandleUnhandledException); AppendUIEditorRuntimeTrace("app", "initialize begin"); - if (m_engineServices == nullptr) { + if (m_engineComposition == nullptr) { AppendUIEditorRuntimeTrace("app", "engine services initialization failed"); return false; } - if (!m_editorContext->Initialize(m_runtimePaths, *m_engineServices)) { + if (!m_editorContext->Initialize( + m_runtimePaths, + m_engineComposition->GetSceneBackendFactory())) { AppendUIEditorRuntimeTrace( "app", "editor context initialization failed: " + @@ -217,16 +219,17 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { m_runtimePaths); m_renderRuntimeFactory = std::make_unique(); - App::EditorWorkspaceShellRuntimeFactory workspaceShellRuntimeFactory = - [engineServices = m_engineServices.get()]() { + App::EditorWorkspaceShellRuntimeFactory workspaceShellRuntimeFactory = []() { return App::CreateEditorWorkspaceShellRuntime( App::CreateEditorWorkspacePanelRuntimeSet(), App::CreateEditorIconService(), - App::CreateEditorViewportRuntimeServices(engineServices)); + App::CreateEditorViewportRuntimeServices()); }; m_windowManager = std::make_unique( *m_editorContext, - *m_engineServices, + m_engineComposition->GetSceneViewportBridge(), + m_engineComposition->GetGameViewportBridge(), + m_engineComposition->GetShaderProvider(), *m_windowSystem, *m_renderRuntimeFactory, *m_resourceService, @@ -318,9 +321,9 @@ void Application::Shutdown() { } m_systemInteractionHost.reset(); - if (m_engineServices != nullptr) { - m_engineServices->Shutdown(); - m_engineServices.reset(); + if (m_engineComposition != nullptr) { + m_engineComposition->GetLifecycle().Shutdown(); + m_engineComposition.reset(); } if (m_windowClassAtom != 0 && m_hInstance != nullptr) { @@ -394,8 +397,8 @@ int Application::Run(HINSTANCE hInstance, int nCmdShow) { } if (m_windowManager != nullptr) { - if (m_engineServices != nullptr) { - m_engineServices->UpdateAsyncLoads(); + if (m_engineComposition != nullptr) { + m_engineComposition->GetLifecycle().UpdateAsyncLoads(); } m_windowManager->DestroyClosedWindows(); if (!m_windowManager->HasWindows()) { diff --git a/editor/app/Bootstrap/Application.h b/editor/app/Bootstrap/Application.h index 0e5ba8f9..522e0e0b 100644 --- a/editor/app/Bootstrap/Application.h +++ b/editor/app/Bootstrap/Application.h @@ -21,7 +21,7 @@ class EditorWindowSystem; namespace App { class EditorContext; -class EditorEngineServices; +class EngineEditorComposition; class EditorWindowManager; class EditorWindowHostRuntime; } @@ -66,7 +66,7 @@ private: std::chrono::steady_clock::time_point m_smokeTestStartTime = {}; std::chrono::milliseconds m_smokeTestDuration = std::chrono::milliseconds::zero(); std::unique_ptr m_editorContext = {}; - std::unique_ptr m_engineServices = {}; + std::unique_ptr m_engineComposition = {}; std::unique_ptr m_windowSystem = {}; std::unique_ptr m_windowHostRuntime = {}; std::unique_ptr m_renderRuntimeFactory = {}; diff --git a/editor/app/Composition/EditorContext.cpp b/editor/app/Composition/EditorContext.cpp index 868f9d22..5ca7e186 100644 --- a/editor/app/Composition/EditorContext.cpp +++ b/editor/app/Composition/EditorContext.cpp @@ -1,6 +1,6 @@ #include "EditorContext.h" #include "EditorShellAssetBuilder.h" -#include "Engine/EditorEngineServices.h" +#include "Engine/EditorSceneBackendFactory.h" #include "Scene/EditorSceneRuntime.h" #include "Panels/EditorPanelIds.h" #include "Viewport/EditorViewportRuntimeServices.h" @@ -56,7 +56,7 @@ UIEditorWorkspacePanelPresentationModel* FindMutablePresentation( bool EditorContext::Initialize( const EditorRuntimePaths& runtimePaths, - EditorEngineServices& engineServices) { + EditorSceneBackendFactory& sceneBackendFactory) { m_valid = false; m_validationMessage.clear(); AppendUIEditorRuntimeTrace("startup", "EditorContext::Initialize begin"); @@ -82,7 +82,7 @@ bool EditorContext::Initialize( m_projectRuntime.Initialize(runtimePaths.projectRoot); AppendUIEditorRuntimeTrace("startup", "EditorProjectRuntime::Initialize end"); m_projectRuntime.BindSelectionService(&m_selectionService); - m_sceneRuntime.SetBackend(engineServices.CreateSceneBackend()); + m_sceneRuntime.SetBackend(sceneBackendFactory.CreateSceneBackend()); AppendUIEditorRuntimeTrace("startup", "EditorSceneRuntime::Initialize begin"); if (!m_sceneRuntime.Initialize(m_session.projectRoot)) { m_validationMessage = "Editor scene runtime failed to initialize."; diff --git a/editor/app/Composition/EditorContext.h b/editor/app/Composition/EditorContext.h index bf487331..51ea27ab 100644 --- a/editor/app/Composition/EditorContext.h +++ b/editor/app/Composition/EditorContext.h @@ -30,14 +30,14 @@ class SystemInteractionService; namespace XCEngine::UI::Editor::App { -class EditorEngineServices; class EditorEditCommandRoute; +class EditorSceneBackendFactory; class EditorContext : public EditorFrameServices { public: bool Initialize( const EditorRuntimePaths& runtimePaths, - EditorEngineServices& engineServices); + EditorSceneBackendFactory& sceneBackendFactory); void AttachTextMeasurer(const UIEditorTextMeasurer& textMeasurer) override; void SetSystemInteractionHost(System::SystemInteractionService* systemInteractionHost); void BindEditCommandRoutes( diff --git a/editor/app/Composition/EditorShellRuntime.cpp b/editor/app/Composition/EditorShellRuntime.cpp index 61b0d6f5..11c9c959 100644 --- a/editor/app/Composition/EditorShellRuntime.cpp +++ b/editor/app/Composition/EditorShellRuntime.cpp @@ -22,7 +22,9 @@ void EditorShellRuntime::Initialize( Rendering::Host::UiTextureHost& textureHost, Host::EditorHostResourceService& resourceService, UIEditorTextMeasurer& textMeasurer, - EditorEngineServices& engineServices) { + SceneViewportEngineBridge& sceneViewportEngineBridge, + GameViewportEngineBridge& gameViewportEngineBridge, + EditorShaderProvider& shaderProvider) { assert(m_iconService != nullptr); assert(m_viewportRuntimeServices != nullptr); if (m_iconService == nullptr || m_viewportRuntimeServices == nullptr) { @@ -30,7 +32,11 @@ void EditorShellRuntime::Initialize( } m_iconService->Initialize(textureHost, resourceService); - m_viewportRuntimeServices->Initialize(runtimePaths, engineServices); + m_viewportRuntimeServices->Initialize( + runtimePaths, + sceneViewportEngineBridge, + gameViewportEngineBridge, + shaderProvider); m_workspacePanels.Initialize( EditorWorkspacePanelInitializationContext{ .runtimePaths = runtimePaths, diff --git a/editor/app/Composition/EditorShellRuntime.h b/editor/app/Composition/EditorShellRuntime.h index 0881fd02..cc85936b 100644 --- a/editor/app/Composition/EditorShellRuntime.h +++ b/editor/app/Composition/EditorShellRuntime.h @@ -55,7 +55,9 @@ public: Rendering::Host::UiTextureHost& textureHost, Host::EditorHostResourceService& resourceService, UIEditorTextMeasurer& textMeasurer, - EditorEngineServices& engineServices) override; + SceneViewportEngineBridge& sceneViewportEngineBridge, + GameViewportEngineBridge& gameViewportEngineBridge, + EditorShaderProvider& shaderProvider) override; void Shutdown() override; void ResetInteractionState() override; void AttachViewportWindowRenderer(Rendering::Host::ViewportRenderHost& renderer) override; diff --git a/editor/app/Core/Engine/EditorEngineLifecycle.h b/editor/app/Core/Engine/EditorEngineLifecycle.h new file mode 100644 index 00000000..c1241ab7 --- /dev/null +++ b/editor/app/Core/Engine/EditorEngineLifecycle.h @@ -0,0 +1,13 @@ +#pragma once + +namespace XCEngine::UI::Editor::App { + +class EditorEngineLifecycle { +public: + virtual ~EditorEngineLifecycle() = default; + + virtual void UpdateAsyncLoads() = 0; + virtual void Shutdown() = 0; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Core/Engine/EditorSceneBackendFactory.h b/editor/app/Core/Engine/EditorSceneBackendFactory.h new file mode 100644 index 00000000..5e3ccec5 --- /dev/null +++ b/editor/app/Core/Engine/EditorSceneBackendFactory.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Scene/EditorSceneBackend.h" + +#include + +namespace XCEngine::UI::Editor::App { + +class EditorSceneBackendFactory { +public: + virtual ~EditorSceneBackendFactory() = default; + + virtual std::unique_ptr CreateSceneBackend() = 0; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Core/Engine/EditorShaderProvider.h b/editor/app/Core/Engine/EditorShaderProvider.h new file mode 100644 index 00000000..df40397f --- /dev/null +++ b/editor/app/Core/Engine/EditorShaderProvider.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +namespace XCEngine::UI::Editor::App { + +class EditorShaderProvider { +public: + virtual ~EditorShaderProvider() = default; + + virtual ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader> + LoadShader(const ::XCEngine::Containers::String& path) = 0; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Core/Engine/GameViewportEngineBridge.h b/editor/app/Core/Engine/GameViewportEngineBridge.h new file mode 100644 index 00000000..cd8487a0 --- /dev/null +++ b/editor/app/Core/Engine/GameViewportEngineBridge.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +#include +#include + +namespace XCEngine::UI::Editor::App { + +enum class GameViewportFramePlanBuildStatus : std::uint8_t { + Success = 0, + NoActiveScene, + NoRenderableCamera, + Failed +}; + +class GameViewportEngineBridge { +public: + virtual ~GameViewportEngineBridge() = default; + + virtual GameViewportFramePlanBuildStatus BuildGameViewportFramePlans( + const ::XCEngine::Rendering::RenderContext& renderContext, + const ::XCEngine::Rendering::RenderSurface& surface, + std::vector<::XCEngine::Rendering::CameraStackFramePlan>& outFramePlans) = 0; + + virtual bool RenderGameViewportFramePlans( + const std::vector<::XCEngine::Rendering::CameraStackFramePlan>& framePlans) = 0; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Core/Engine/EditorEngineServices.h b/editor/app/Core/Engine/SceneViewportEngineBridge.h similarity index 60% rename from editor/app/Core/Engine/EditorEngineServices.h rename to editor/app/Core/Engine/SceneViewportEngineBridge.h index 77e71bdf..2c8ca1a1 100644 --- a/editor/app/Core/Engine/EditorEngineServices.h +++ b/editor/app/Core/Engine/SceneViewportEngineBridge.h @@ -1,23 +1,13 @@ #pragma once -#include "Scene/EditorSceneBackend.h" #include "Scene/SceneViewportRenderRequest.h" -#include -#include #include #include -#include +#include +#include #include -#include - -namespace XCEngine::Rendering { - -struct RenderContext; -class RenderSurface; - -} // namespace XCEngine::Rendering namespace XCEngine::UI::Editor::App { @@ -28,17 +18,9 @@ enum class SceneViewportFramePlanBuildStatus : std::uint8_t { Failed }; -class EditorEngineServices { +class SceneViewportEngineBridge { public: - virtual ~EditorEngineServices() = default; - - virtual void UpdateAsyncLoads() = 0; - virtual void Shutdown() = 0; - - virtual std::unique_ptr CreateSceneBackend() = 0; - - virtual ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader> - LoadShader(const ::XCEngine::Containers::String& path) = 0; + virtual ~SceneViewportEngineBridge() = default; virtual SceneViewportFramePlanBuildStatus BuildSceneViewportFramePlan( const SceneViewportRenderRequest& request, diff --git a/editor/app/Core/Product/EditorProductManifest.cpp b/editor/app/Core/Product/EditorProductManifest.cpp index d5f8179e..4cb258d3 100644 --- a/editor/app/Core/Product/EditorProductManifest.cpp +++ b/editor/app/Core/Product/EditorProductManifest.cpp @@ -46,9 +46,9 @@ constexpr std::array kEditorProductPanels = { false, false, EditorActionRoute::Game, - EditorProductPanelRuntimeKind::None, - EditorProductViewportRendererKind::Placeholder, - "Game view runtime is not implemented" }, + EditorProductPanelRuntimeKind::Game, + EditorProductViewportRendererKind::Game, + {} }, EditorProductPanelDescriptor{ kInspectorPanelId, kInspectorPanelTitle, diff --git a/editor/app/Core/Product/EditorProductManifest.h b/editor/app/Core/Product/EditorProductManifest.h index 8964318c..2931092a 100644 --- a/editor/app/Core/Product/EditorProductManifest.h +++ b/editor/app/Core/Product/EditorProductManifest.h @@ -15,6 +15,7 @@ enum class EditorProductPanelRuntimeKind : std::uint8_t { None = 0, Console, Hierarchy, + Game, Inspector, Project, Scene @@ -23,6 +24,7 @@ enum class EditorProductPanelRuntimeKind : std::uint8_t { enum class EditorProductViewportRendererKind : std::uint8_t { None = 0, Scene, + Game, Placeholder }; diff --git a/editor/app/Core/Viewport/EditorViewportRuntimeServices.h b/editor/app/Core/Viewport/EditorViewportRuntimeServices.h index ba145c4e..9c4fcf56 100644 --- a/editor/app/Core/Viewport/EditorViewportRuntimeServices.h +++ b/editor/app/Core/Viewport/EditorViewportRuntimeServices.h @@ -20,7 +20,9 @@ class ViewportRenderHost; namespace XCEngine::UI::Editor::App { -class EditorEngineServices; +class EditorShaderProvider; +class GameViewportEngineBridge; +class SceneViewportEngineBridge; class EditorSceneViewportRuntime { public: @@ -36,7 +38,9 @@ public: virtual void Initialize( const EditorRuntimePaths& runtimePaths, - EditorEngineServices& engineServices) = 0; + SceneViewportEngineBridge& sceneViewportEngineBridge, + GameViewportEngineBridge& gameViewportEngineBridge, + EditorShaderProvider& shaderProvider) = 0; virtual void Shutdown() = 0; virtual void AttachWindowRenderer(Rendering::Host::ViewportRenderHost& windowRenderer) = 0; virtual void DetachWindowRenderer() = 0; diff --git a/editor/app/Core/Windowing/EditorWorkspaceShellRuntime.h b/editor/app/Core/Windowing/EditorWorkspaceShellRuntime.h index 26648a7f..68d7db43 100644 --- a/editor/app/Core/Windowing/EditorWorkspaceShellRuntime.h +++ b/editor/app/Core/Windowing/EditorWorkspaceShellRuntime.h @@ -42,7 +42,9 @@ class EditorHostResourceService; namespace XCEngine::UI::Editor::App { -class EditorEngineServices; +class EditorShaderProvider; +class GameViewportEngineBridge; +class SceneViewportEngineBridge; enum class EditorWorkspaceShellCursorKind : std::uint8_t { Arrow = 0, @@ -58,7 +60,9 @@ public: Rendering::Host::UiTextureHost& textureHost, Host::EditorHostResourceService& resourceService, UIEditorTextMeasurer& textMeasurer, - EditorEngineServices& engineServices) = 0; + SceneViewportEngineBridge& sceneViewportEngineBridge, + GameViewportEngineBridge& gameViewportEngineBridge, + EditorShaderProvider& shaderProvider) = 0; virtual void Shutdown() = 0; virtual void ResetInteractionState() = 0; virtual void AttachViewportWindowRenderer(Rendering::Host::ViewportRenderHost& renderer) = 0; diff --git a/editor/app/Features/EditorWorkspacePanelRegistry.cpp b/editor/app/Features/EditorWorkspacePanelRegistry.cpp index 1960929d..710cc2e5 100644 --- a/editor/app/Features/EditorWorkspacePanelRegistry.cpp +++ b/editor/app/Features/EditorWorkspacePanelRegistry.cpp @@ -2,6 +2,7 @@ #include "Panels/EditorPanelIds.h" #include "Console/ConsolePanel.h" +#include "Game/GameViewportFeature.h" #include "Hierarchy/HierarchyPanel.h" #include "Inspector/InspectorPanel.h" #include "Project/ProjectPanel.h" @@ -19,6 +20,7 @@ namespace XCEngine::UI::Editor::App { namespace { constexpr int kSceneUpdatePriority = 0; +constexpr int kGameUpdatePriority = 5; constexpr int kHierarchyUpdatePriority = 10; constexpr int kProjectUpdatePriority = 20; constexpr int kInspectorUpdatePriority = 30; @@ -450,11 +452,60 @@ private: SceneEditCommandRoute m_commandRoute = {}; }; +class GameWorkspacePanel final : public EditorWorkspacePanel { +public: + std::string_view GetPanelId() const override { + return kGamePanelId; + } + + std::string_view GetDrawListId() const override { + return "XCEditorPanel.GameOverlay"; + } + + EditorActionRoute GetActionRoute() const override { + return EditorActionRoute::Game; + } + + int GetUpdatePriority() const override { + return kGameUpdatePriority; + } + + void Shutdown(const EditorWorkspacePanelShutdownContext& context) override { + (void)context; + m_feature.Shutdown(); + } + + void ResetInteractionState() override { + m_feature.ResetInteractionState(); + } + + void PrepareForShellDefinition( + EditorPanelServices& services, + UIEditorWorkspaceController&) override { + m_feature.SetCommandFocusService(&services.commandFocusService); + } + + void Update(const EditorWorkspacePanelUpdateContext& context) override { + m_feature.Update( + context.shellInteractionState.workspaceInteractionState.composeState, + context.shellFrame.workspaceInteractionFrame.composeFrame); + } + + void Append(::XCEngine::UI::UIDrawList& drawList) const override { + m_feature.Append(drawList); + } + +private: + GameViewportFeature m_feature = {}; +}; + std::unique_ptr CreateWorkspacePanelRuntime( const EditorProductPanelDescriptor& panel) { switch (panel.runtimeKind) { case EditorProductPanelRuntimeKind::Console: return std::make_unique(); + case EditorProductPanelRuntimeKind::Game: + return std::make_unique(); case EditorProductPanelRuntimeKind::Hierarchy: return std::make_unique(); case EditorProductPanelRuntimeKind::Inspector: diff --git a/editor/app/Features/Game/GameViewportController.cpp b/editor/app/Features/Game/GameViewportController.cpp new file mode 100644 index 00000000..6ea19ea3 --- /dev/null +++ b/editor/app/Features/Game/GameViewportController.cpp @@ -0,0 +1,62 @@ +#include "Game/GameViewportController.h" + +#include "Panels/EditorPanelIds.h" +#include "State/EditorCommandFocusService.h" + +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +bool ContainsPoint( + const ::XCEngine::UI::UIRect& rect, + const ::XCEngine::UI::UIPoint& point) { + return point.x >= rect.x && + point.x <= rect.x + rect.width && + point.y >= rect.y && + point.y <= rect.y + rect.height; +} + +} // namespace + +void GameViewportController::ResetInteractionState() {} + +void GameViewportController::SetCommandFocusService( + EditorCommandFocusService* commandFocusService) { + m_commandFocusService = commandFocusService; +} + +void GameViewportController::Update( + const UIEditorWorkspaceComposeState& composeState, + const UIEditorWorkspaceComposeFrame& composeFrame) { + const UIEditorWorkspaceViewportComposeFrame* viewportFrame = + FindUIEditorWorkspaceViewportPresentationFrame(composeFrame, kGamePanelId); + const UIEditorWorkspacePanelPresentationState* panelState = + FindUIEditorWorkspacePanelPresentationState(composeState, kGamePanelId); + if (viewportFrame == nullptr || panelState == nullptr) { + return; + } + + const auto& inputFrame = viewportFrame->viewportShellFrame.inputFrame; + const auto& slotLayout = viewportFrame->viewportShellFrame.slotLayout; + if (m_commandFocusService != nullptr && + (inputFrame.focused || + std::any_of( + inputFrame.pointerButtonTransitions.begin(), + inputFrame.pointerButtonTransitions.end(), + [&](const auto& transition) { + return transition.pressed && + ContainsPoint(slotLayout.bounds, transition.screenPosition); + }))) { + m_commandFocusService->ClaimFocus(EditorActionRoute::Game); + } + + (void)panelState; +} + +void GameViewportController::Append(::XCEngine::UI::UIDrawList& drawList) const { + (void)drawList; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Features/Game/GameViewportController.h b/editor/app/Features/Game/GameViewportController.h new file mode 100644 index 00000000..33b4ecfa --- /dev/null +++ b/editor/app/Features/Game/GameViewportController.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include + +namespace XCEngine::UI::Editor::App { + +class EditorCommandFocusService; + +class GameViewportController { +public: + void ResetInteractionState(); + void SetCommandFocusService(EditorCommandFocusService* commandFocusService); + void Update( + const UIEditorWorkspaceComposeState& composeState, + const UIEditorWorkspaceComposeFrame& composeFrame); + void Append(::XCEngine::UI::UIDrawList& drawList) const; + +private: + EditorCommandFocusService* m_commandFocusService = nullptr; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Features/Game/GameViewportFeature.cpp b/editor/app/Features/Game/GameViewportFeature.cpp new file mode 100644 index 00000000..25fa9c2d --- /dev/null +++ b/editor/app/Features/Game/GameViewportFeature.cpp @@ -0,0 +1,28 @@ +#include "Game/GameViewportFeature.h" + +namespace XCEngine::UI::Editor::App { + +void GameViewportFeature::Shutdown() { + m_controller.SetCommandFocusService(nullptr); +} + +void GameViewportFeature::ResetInteractionState() { + m_controller.ResetInteractionState(); +} + +void GameViewportFeature::SetCommandFocusService( + EditorCommandFocusService* commandFocusService) { + m_controller.SetCommandFocusService(commandFocusService); +} + +void GameViewportFeature::Update( + const UIEditorWorkspaceComposeState& composeState, + const UIEditorWorkspaceComposeFrame& composeFrame) { + m_controller.Update(composeState, composeFrame); +} + +void GameViewportFeature::Append(::XCEngine::UI::UIDrawList& drawList) const { + m_controller.Append(drawList); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Features/Game/GameViewportFeature.h b/editor/app/Features/Game/GameViewportFeature.h new file mode 100644 index 00000000..b237e92d --- /dev/null +++ b/editor/app/Features/Game/GameViewportFeature.h @@ -0,0 +1,27 @@ +#pragma once + +#include "Game/GameViewportController.h" + +#include + +#include + +namespace XCEngine::UI::Editor::App { + +class EditorCommandFocusService; + +class GameViewportFeature { +public: + void Shutdown(); + void ResetInteractionState(); + void SetCommandFocusService(EditorCommandFocusService* commandFocusService); + void Update( + const UIEditorWorkspaceComposeState& composeState, + const UIEditorWorkspaceComposeFrame& composeFrame); + void Append(::XCEngine::UI::UIDrawList& drawList) const; + +private: + GameViewportController m_controller = {}; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Features/Inspector/InspectorFieldValueApplier.h b/editor/app/Features/Inspector/InspectorFieldValueApplier.h new file mode 100644 index 00000000..95841523 --- /dev/null +++ b/editor/app/Features/Inspector/InspectorFieldValueApplier.h @@ -0,0 +1,46 @@ +#pragma once + +#include "Inspector/Components/IInspectorComponentEditor.h" +#include "Inspector/Components/InspectorComponentEditorRegistry.h" +#include "Inspector/InspectorPresentationModel.h" +#include "Inspector/InspectorSubject.h" +#include "Scene/EditorSceneRuntime.h" + +namespace XCEngine::UI::Editor::App { + +inline bool ApplyInspectorComponentBoundFieldValue( + EditorSceneRuntime& sceneRuntime, + const InspectorSceneObjectSubject& sceneObject, + const InspectorPresentationComponentBinding& binding, + const Widgets::UIEditorPropertyGridField& field, + const InspectorComponentEditorRegistry& componentEditorRegistry = + InspectorComponentEditorRegistry::Get()) { + const IInspectorComponentEditor* editor = + componentEditorRegistry.FindEditor(binding.typeName); + if (editor == nullptr) { + return false; + } + + const std::vector descriptors = + sceneRuntime.GetSelectedComponents(); + InspectorComponentEditorContext context = {}; + context.gameObject = &sceneObject.object; + context.componentId = binding.componentId; + context.typeName = binding.typeName; + context.displayName = binding.displayName; + context.removable = binding.removable; + for (const EditorSceneComponentDescriptor& descriptor : descriptors) { + if (descriptor.componentId == binding.componentId) { + context.component = descriptor.view.get(); + break; + } + } + + if (context.component == nullptr) { + return false; + } + + return editor->ApplyFieldValue(sceneRuntime, context, field); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Features/Inspector/InspectorPanel.cpp b/editor/app/Features/Inspector/InspectorPanel.cpp index 3a099f82..64f2bef6 100644 --- a/editor/app/Features/Inspector/InspectorPanel.cpp +++ b/editor/app/Features/Inspector/InspectorPanel.cpp @@ -3,6 +3,7 @@ #include "Panels/EditorPanelServices.h" #include "UtilityWindows/EditorUtilityWindowRuntime.h" #include "State/EditorColorPickerToolState.h" +#include "Inspector/InspectorFieldValueApplier.h" #include #include #include @@ -702,27 +703,11 @@ bool InspectorPanel::ApplyChangedField(std::string_view fieldId) { return false; } - const IInspectorComponentEditor* editor = - InspectorComponentEditorRegistry::Get().FindEditor(binding->typeName); - if (editor == nullptr) { - return false; - } - - InspectorComponentEditorContext context = {}; - context.gameObject = &m_subject.sceneObject.object; - context.componentId = binding->componentId; - context.typeName = binding->typeName; - context.displayName = binding->displayName; - context.removable = binding->removable; - for (const EditorSceneComponentDescriptor& descriptor : - m_sceneRuntime->GetSelectedComponents()) { - if (descriptor.componentId == binding->componentId) { - context.component = descriptor.view.get(); - break; - } - } - - return editor->ApplyFieldValue(*m_sceneRuntime, context, field); + return ApplyInspectorComponentBoundFieldValue( + *m_sceneRuntime, + m_subject.sceneObject, + *binding, + field); } void InspectorPanel::Update( diff --git a/editor/app/Rendering/Viewport/EditorViewportRuntimeServicesFactory.cpp b/editor/app/Rendering/Viewport/EditorViewportRuntimeServicesFactory.cpp index f73d3e50..31a9157d 100644 --- a/editor/app/Rendering/Viewport/EditorViewportRuntimeServicesFactory.cpp +++ b/editor/app/Rendering/Viewport/EditorViewportRuntimeServicesFactory.cpp @@ -4,8 +4,7 @@ namespace XCEngine::UI::Editor::App { -std::unique_ptr CreateEditorViewportRuntimeServices( - EditorEngineServices*) { +std::unique_ptr CreateEditorViewportRuntimeServices() { return std::make_unique(); } diff --git a/editor/app/Rendering/Viewport/EditorViewportRuntimeServicesFactory.h b/editor/app/Rendering/Viewport/EditorViewportRuntimeServicesFactory.h index 1723f0f1..11422c84 100644 --- a/editor/app/Rendering/Viewport/EditorViewportRuntimeServicesFactory.h +++ b/editor/app/Rendering/Viewport/EditorViewportRuntimeServicesFactory.h @@ -6,9 +6,6 @@ namespace XCEngine::UI::Editor::App { -class EditorEngineServices; - -std::unique_ptr CreateEditorViewportRuntimeServices( - EditorEngineServices* engineServices); +std::unique_ptr CreateEditorViewportRuntimeServices(); } // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Rendering/Viewport/GameViewportRenderService.cpp b/editor/app/Rendering/Viewport/GameViewportRenderService.cpp new file mode 100644 index 00000000..3882a47f --- /dev/null +++ b/editor/app/Rendering/Viewport/GameViewportRenderService.cpp @@ -0,0 +1,109 @@ +#include "Viewport/GameViewportRenderService.h" + +#include "Engine/GameViewportEngineBridge.h" +#include "Viewport/ViewportRenderTargets.h" + +#include + +#include +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +ViewportRenderResult BuildFallbackResult( + std::string statusText, + float r, + float g, + float b, + float a) { + ViewportRenderResult result = {}; + result.rendered = false; + result.requiresFallbackClear = true; + result.statusText = std::move(statusText); + result.fallbackClearR = r; + result.fallbackClearG = g; + result.fallbackClearB = b; + result.fallbackClearA = a; + return result; +} + +} // namespace + +EditorViewportResourceRequirements +GameViewportRenderService::GetViewportResourceRequirements() { + return {}; +} + +void GameViewportRenderService::Initialize(GameViewportEngineBridge& engineBridge) { + m_engineBridge = &engineBridge; +} + +void GameViewportRenderService::Shutdown() { + m_engineBridge = nullptr; +} + +ViewportRenderResult GameViewportRenderService::Render( + ViewportRenderTargets& targets, + ::XCEngine::RHI::RHIDevice& device, + const ::XCEngine::Rendering::RenderContext& renderContext) { + (void)device; + + if (m_engineBridge == nullptr) { + return BuildFallbackResult( + "Game renderer is unavailable", + 0.18f, + 0.07f, + 0.07f, + 1.0f); + } + + std::vector<::XCEngine::Rendering::CameraStackFramePlan> framePlans = {}; + const GameViewportFramePlanBuildStatus buildStatus = + m_engineBridge->BuildGameViewportFramePlans( + renderContext, + BuildViewportColorSurface(targets), + framePlans); + switch (buildStatus) { + case GameViewportFramePlanBuildStatus::Success: + break; + case GameViewportFramePlanBuildStatus::NoActiveScene: + return BuildFallbackResult( + "No active scene", + 0.07f, + 0.08f, + 0.10f, + 1.0f); + case GameViewportFramePlanBuildStatus::NoRenderableCamera: + return BuildFallbackResult( + "No active game camera", + 0.10f, + 0.10f, + 0.12f, + 1.0f); + case GameViewportFramePlanBuildStatus::Failed: + default: + return BuildFallbackResult( + "Game renderer failed", + 0.18f, + 0.07f, + 0.07f, + 1.0f); + } + + if (!m_engineBridge->RenderGameViewportFramePlans(framePlans)) { + return BuildFallbackResult( + "Game renderer failed", + 0.18f, + 0.07f, + 0.07f, + 1.0f); + } + + ViewportRenderResult result = {}; + result.rendered = true; + return result; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Rendering/Viewport/GameViewportRenderService.h b/editor/app/Rendering/Viewport/GameViewportRenderService.h new file mode 100644 index 00000000..3edf3c52 --- /dev/null +++ b/editor/app/Rendering/Viewport/GameViewportRenderService.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Viewport/ViewportContentRenderer.h" + +namespace XCEngine::UI::Editor::App { + +class GameViewportEngineBridge; + +class GameViewportRenderService final : public IViewportContentRenderer { +public: + static EditorViewportResourceRequirements GetViewportResourceRequirements(); + + void Initialize(GameViewportEngineBridge& engineBridge); + void Shutdown(); + + ViewportRenderResult Render( + ViewportRenderTargets& targets, + ::XCEngine::RHI::RHIDevice& device, + const ::XCEngine::Rendering::RenderContext& renderContext) override; + +private: + GameViewportEngineBridge* m_engineBridge = nullptr; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Rendering/Viewport/Passes/SceneViewportGridPass.cpp b/editor/app/Rendering/Viewport/Passes/SceneViewportGridPass.cpp index bd7e6369..880cbbb2 100644 --- a/editor/app/Rendering/Viewport/Passes/SceneViewportGridPass.cpp +++ b/editor/app/Rendering/Viewport/Passes/SceneViewportGridPass.cpp @@ -1,6 +1,6 @@ #include "Viewport/Passes/SceneViewportGridPass.h" -#include "Engine/EditorEngineServices.h" +#include "Engine/EditorShaderProvider.h" #include #include @@ -286,9 +286,9 @@ public: void SetShaderPath( const ::XCEngine::Containers::String& shaderPath, - EditorEngineServices* engineServices) { + EditorShaderProvider* shaderProvider) { m_shaderPath = shaderPath; - m_engineServices = engineServices; + m_shaderProvider = shaderProvider; DestroyResources(); } @@ -480,16 +480,16 @@ private: return false; } - if (m_shaderPath.Empty() || m_engineServices == nullptr) { + if (m_shaderPath.Empty() || m_shaderProvider == nullptr) { ::XCEngine::Debug::Logger::Get().Error( ::XCEngine::Debug::LogCategory::Rendering, - "SceneViewportGridPassRenderer requires a valid shader path and engine services"); + "SceneViewportGridPassRenderer requires a valid shader path and shader provider"); return false; } m_device = renderContext.device; m_backendType = renderContext.backendType; - m_shader = m_engineServices->LoadShader(m_shaderPath); + m_shader = m_shaderProvider->LoadShader(m_shaderPath); if (!m_shader.IsValid()) { ::XCEngine::Debug::Logger::Get().Error( ::XCEngine::Debug::LogCategory::Rendering, @@ -611,7 +611,7 @@ private: ::XCEngine::RHI::RHIDescriptorPool* m_constantPool = nullptr; ::XCEngine::RHI::RHIDescriptorSet* m_constantSet = nullptr; ::XCEngine::Containers::String m_shaderPath = {}; - EditorEngineServices* m_engineServices = nullptr; + EditorShaderProvider* m_shaderProvider = nullptr; ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader> m_shader = {}; ::XCEngine::RHI::Format m_renderTargetFormat = @@ -633,8 +633,8 @@ void SceneViewportGridPassRenderer::Shutdown() { void SceneViewportGridPassRenderer::SetShaderPath( const ::XCEngine::Containers::String& shaderPath, - EditorEngineServices* engineServices) { - m_impl->SetShaderPath(shaderPath, engineServices); + EditorShaderProvider* shaderProvider) { + m_impl->SetShaderPath(shaderPath, shaderProvider); } bool SceneViewportGridPassRenderer::Render( diff --git a/editor/app/Rendering/Viewport/Passes/SceneViewportGridPass.h b/editor/app/Rendering/Viewport/Passes/SceneViewportGridPass.h index 196307c7..b57c8c8d 100644 --- a/editor/app/Rendering/Viewport/Passes/SceneViewportGridPass.h +++ b/editor/app/Rendering/Viewport/Passes/SceneViewportGridPass.h @@ -11,7 +11,7 @@ namespace XCEngine::UI::Editor::App { -class EditorEngineServices; +class EditorShaderProvider; class SceneViewportGridPassRenderer { public: @@ -25,7 +25,7 @@ public: void Shutdown(); void SetShaderPath( const ::XCEngine::Containers::String& shaderPath, - EditorEngineServices* engineServices); + EditorShaderProvider* shaderProvider); bool Render( const ::XCEngine::Rendering::RenderContext& renderContext, diff --git a/editor/app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp b/editor/app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp index db5d7ff7..1950c4f3 100644 --- a/editor/app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp +++ b/editor/app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp @@ -1,6 +1,6 @@ #include "Viewport/Passes/SceneViewportSelectionOutlinePass.h" -#include "Engine/EditorEngineServices.h" +#include "Engine/EditorShaderProvider.h" #include "Viewport/ViewportRenderTargets.h" #include @@ -275,10 +275,10 @@ public: void SetShaderPaths( const ::XCEngine::Containers::String& selectionMaskShaderPath, const ::XCEngine::Containers::String& selectionOutlineShaderPath, - EditorEngineServices* engineServices) { + EditorShaderProvider* shaderProvider) { m_selectionMaskShaderPath = selectionMaskShaderPath; m_shaderPath = selectionOutlineShaderPath; - m_engineServices = engineServices; + m_shaderProvider = shaderProvider; if (m_selectionMaskPass != nullptr) { m_selectionMaskPass->Shutdown(); } @@ -501,15 +501,15 @@ private: return false; } - if (m_shaderPath.Empty() || m_engineServices == nullptr) { + if (m_shaderPath.Empty() || m_shaderProvider == nullptr) { ::XCEngine::Debug::Logger::Get().Error( ::XCEngine::Debug::LogCategory::Rendering, - "SceneViewportSelectionOutlinePassRenderer requires a valid shader path and engine services"); + "SceneViewportSelectionOutlinePassRenderer requires a valid shader path and shader provider"); return false; } ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader> - shader = m_engineServices->LoadShader(m_shaderPath); + shader = m_shaderProvider->LoadShader(m_shaderPath); if (!shader.IsValid()) { ::XCEngine::Debug::Logger::Get().Error( ::XCEngine::Debug::LogCategory::Rendering, @@ -709,7 +709,7 @@ private: ::XCEngine::RHI::RHIDescriptorPool* m_texturePool = nullptr; ::XCEngine::RHI::RHIDescriptorSet* m_textureSet = nullptr; ::XCEngine::Containers::String m_shaderPath = {}; - EditorEngineServices* m_engineServices = nullptr; + EditorShaderProvider* m_shaderProvider = nullptr; std::optional< ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>> m_shader = {}; @@ -733,11 +733,11 @@ void SceneViewportSelectionOutlinePassRenderer::Shutdown() { void SceneViewportSelectionOutlinePassRenderer::SetShaderPaths( const ::XCEngine::Containers::String& selectionMaskShaderPath, const ::XCEngine::Containers::String& selectionOutlineShaderPath, - EditorEngineServices* engineServices) { + EditorShaderProvider* shaderProvider) { m_impl->SetShaderPaths( selectionMaskShaderPath, selectionOutlineShaderPath, - engineServices); + shaderProvider); } bool SceneViewportSelectionOutlinePassRenderer::Render( diff --git a/editor/app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.h b/editor/app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.h index 6f07381a..b77dc34b 100644 --- a/editor/app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.h +++ b/editor/app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.h @@ -13,7 +13,7 @@ namespace XCEngine::UI::Editor::App { -class EditorEngineServices; +class EditorShaderProvider; struct ViewportRenderTargets; class SceneViewportSelectionOutlinePassRenderer { @@ -33,7 +33,7 @@ public: void SetShaderPaths( const ::XCEngine::Containers::String& selectionMaskShaderPath, const ::XCEngine::Containers::String& selectionOutlineShaderPath, - EditorEngineServices* engineServices); + EditorShaderProvider* shaderProvider); bool Render( const ::XCEngine::Rendering::RenderContext& renderContext, diff --git a/editor/app/Rendering/Viewport/SceneViewportRenderPassBundle.cpp b/editor/app/Rendering/Viewport/SceneViewportRenderPassBundle.cpp index ab013535..05ad6abb 100644 --- a/editor/app/Rendering/Viewport/SceneViewportRenderPassBundle.cpp +++ b/editor/app/Rendering/Viewport/SceneViewportRenderPassBundle.cpp @@ -10,14 +10,14 @@ void SceneViewportRenderPassBundle::Shutdown() { void SceneViewportRenderPassBundle::Initialize( const SceneViewportShaderPaths& shaderPaths, - EditorEngineServices& engineServices) { + EditorShaderProvider& shaderProvider) { m_gridRenderer.SetShaderPath( shaderPaths.infiniteGridShaderPath, - &engineServices); + &shaderProvider); m_selectionOutlineRenderer.SetShaderPaths( shaderPaths.selectionMaskShaderPath, shaderPaths.selectionOutlineShaderPath, - &engineServices); + &shaderProvider); } SceneViewportRenderPlanBuildResult diff --git a/editor/app/Rendering/Viewport/SceneViewportRenderPassBundle.h b/editor/app/Rendering/Viewport/SceneViewportRenderPassBundle.h index 4f95866a..a3e0ae41 100644 --- a/editor/app/Rendering/Viewport/SceneViewportRenderPassBundle.h +++ b/editor/app/Rendering/Viewport/SceneViewportRenderPassBundle.h @@ -8,7 +8,7 @@ namespace XCEngine::UI::Editor::App { -class EditorEngineServices; +class EditorShaderProvider; class SceneViewportRenderPassBundle { public: @@ -17,7 +17,7 @@ public: void Shutdown(); void Initialize( const SceneViewportShaderPaths& shaderPaths, - EditorEngineServices& engineServices); + EditorShaderProvider& shaderProvider); SceneViewportRenderPlanBuildResult BuildRenderPlan( ViewportRenderTargets& targets, diff --git a/editor/app/Rendering/Viewport/SceneViewportRenderService.cpp b/editor/app/Rendering/Viewport/SceneViewportRenderService.cpp index 071c3287..4ff4be93 100644 --- a/editor/app/Rendering/Viewport/SceneViewportRenderService.cpp +++ b/editor/app/Rendering/Viewport/SceneViewportRenderService.cpp @@ -1,6 +1,7 @@ #include "Viewport/SceneViewportRenderService.h" -#include "Engine/EditorEngineServices.h" +#include "Engine/EditorShaderProvider.h" +#include "Engine/SceneViewportEngineBridge.h" #include "Viewport/ViewportObjectIdPicker.h" #include @@ -51,15 +52,16 @@ SceneViewportRenderService::GetViewportResourceRequirements() { void SceneViewportRenderService::Initialize( const SceneViewportShaderPaths& shaderPaths, - EditorEngineServices& engineServices) { - m_engineServices = &engineServices; - m_renderPassBundle.Initialize(shaderPaths, engineServices); + SceneViewportEngineBridge& engineBridge, + EditorShaderProvider& shaderProvider) { + m_engineBridge = &engineBridge; + m_renderPassBundle.Initialize(shaderPaths, shaderProvider); } void SceneViewportRenderService::Shutdown() { m_renderRequest = {}; m_renderPassBundle.Shutdown(); - m_engineServices = nullptr; + m_engineBridge = nullptr; m_device = nullptr; m_lastTargets = nullptr; m_lastRenderContext = {}; @@ -103,8 +105,8 @@ ViewportRenderResult SceneViewportRenderService::Render( BuildViewportColorSurface(targets); ::XCEngine::Rendering::CameraFramePlan framePlan = {}; const SceneViewportFramePlanBuildStatus buildStatus = - m_engineServices != nullptr - ? m_engineServices->BuildSceneViewportFramePlan( + m_engineBridge != nullptr + ? m_engineBridge->BuildSceneViewportFramePlan( m_renderRequest, renderContext, surface, @@ -139,8 +141,8 @@ ViewportRenderResult SceneViewportRenderService::Render( SetStatusIfEmpty(result.statusText, renderPlan.warningStatusText); } - if (m_engineServices == nullptr || - !m_engineServices->RenderSceneViewportFramePlan(framePlan)) { + if (m_engineBridge == nullptr || + !m_engineBridge->RenderSceneViewportFramePlan(framePlan)) { return BuildFallbackResult( "Scene renderer failed", 0.18f, @@ -208,8 +210,8 @@ ViewportObjectIdPickResult SceneViewportRenderService::PickObject( } EditorSceneObjectId runtimeObjectId = kInvalidEditorSceneObjectId; - if (m_engineServices == nullptr || - !m_engineServices->TryResolveActiveSceneRenderObjectId( + if (m_engineBridge == nullptr || + !m_engineBridge->TryResolveActiveSceneRenderObjectId( result.renderObjectId, runtimeObjectId)) { return result; diff --git a/editor/app/Rendering/Viewport/SceneViewportRenderService.h b/editor/app/Rendering/Viewport/SceneViewportRenderService.h index efdbe82f..63fbfebe 100644 --- a/editor/app/Rendering/Viewport/SceneViewportRenderService.h +++ b/editor/app/Rendering/Viewport/SceneViewportRenderService.h @@ -19,7 +19,8 @@ class RHIDevice; namespace XCEngine::UI::Editor::App { -class EditorEngineServices; +class EditorShaderProvider; +class SceneViewportEngineBridge; class SceneViewportRenderService : public IViewportContentRenderer @@ -33,7 +34,8 @@ public: void Initialize( const SceneViewportShaderPaths& shaderPaths, - EditorEngineServices& engineServices); + SceneViewportEngineBridge& engineBridge, + EditorShaderProvider& shaderProvider); void Shutdown(); void SetRenderRequest(SceneViewportRenderRequest request) override; const IViewportObjectPickerService& GetObjectPicker() const override; @@ -51,7 +53,7 @@ private: SceneViewportRenderRequest m_renderRequest = {}; SceneViewportRenderPassBundle m_renderPassBundle = {}; - EditorEngineServices* m_engineServices = nullptr; + SceneViewportEngineBridge* m_engineBridge = nullptr; ::XCEngine::RHI::RHIDevice* m_device = nullptr; ViewportRenderTargets* m_lastTargets = nullptr; ::XCEngine::Rendering::RenderContext m_lastRenderContext = {}; diff --git a/editor/app/Rendering/Viewport/ViewportHostService.cpp b/editor/app/Rendering/Viewport/ViewportHostService.cpp index ecaba7c1..efddbf0c 100644 --- a/editor/app/Rendering/Viewport/ViewportHostService.cpp +++ b/editor/app/Rendering/Viewport/ViewportHostService.cpp @@ -1,6 +1,5 @@ #include "ViewportHostService.h" -#include "Engine/EditorEngineServices.h" #include "Panels/EditorPanelIds.h" #include "Product/EditorProductManifest.h" #include "Viewport/SceneViewportResourcePaths.h" @@ -43,18 +42,20 @@ private: } // namespace -ViewportHostService::ViewportHostService(EditorEngineServices* engineServices) - : m_engineServices(engineServices) {} +ViewportHostService::ViewportHostService() = default; ViewportHostService::~ViewportHostService() = default; void ViewportHostService::Initialize( const EditorRuntimePaths& runtimePaths, - EditorEngineServices& engineServices) { - m_engineServices = &engineServices; + SceneViewportEngineBridge& sceneViewportEngineBridge, + GameViewportEngineBridge& gameViewportEngineBridge, + EditorShaderProvider& shaderProvider) { + m_gameViewportRuntime.Initialize(gameViewportEngineBridge); m_sceneViewportRuntime.Initialize( BuildSceneViewportShaderPaths(runtimePaths), - engineServices); + sceneViewportEngineBridge, + shaderProvider); m_placeholderRenderers.clear(); for (const EditorProductPanelDescriptor& panel : GetEditorProductPanels()) { if (panel.presentationKind != UIEditorPanelPresentationKind::ViewportShell) { @@ -68,6 +69,12 @@ void ViewportHostService::Initialize( &m_sceneViewportRuntime, SceneViewportRenderService::GetViewportResourceRequirements()); break; + case EditorProductViewportRendererKind::Game: + SetContentRenderer( + panel.panelId, + &m_gameViewportRuntime, + GameViewportRenderService::GetViewportResourceRequirements()); + break; case EditorProductViewportRendererKind::Placeholder: { auto placeholder = std::make_unique( @@ -122,6 +129,7 @@ void ViewportHostService::Shutdown() { } } m_placeholderRenderers.clear(); + m_gameViewportRuntime.Shutdown(); m_sceneViewportRuntime.Shutdown(); for (auto& [viewportId, entry] : m_entries) { DestroyViewportEntry(entry); @@ -131,7 +139,6 @@ void ViewportHostService::Shutdown() { m_windowRenderer = nullptr; m_device = nullptr; m_surfacePresentationEnabled = false; - m_engineServices = nullptr; m_entries.clear(); m_retiredTargetsBySlot.clear(); } diff --git a/editor/app/Rendering/Viewport/ViewportHostService.h b/editor/app/Rendering/Viewport/ViewportHostService.h index ba28cb20..75b62ae6 100644 --- a/editor/app/Rendering/Viewport/ViewportHostService.h +++ b/editor/app/Rendering/Viewport/ViewportHostService.h @@ -1,6 +1,7 @@ #pragma once #include "Viewport/EditorViewportRuntimeServices.h" +#include "Viewport/GameViewportRenderService.h" #include "HostFwd.h" #include "Viewport/SceneViewportRenderService.h" #include "Viewport/ViewportContentRenderer.h" @@ -20,12 +21,14 @@ namespace XCEngine::UI::Editor::App { class ViewportHostService final : public EditorViewportRuntimeServices { public: - ViewportHostService(EditorEngineServices* engineServices = nullptr); + ViewportHostService(); ~ViewportHostService(); void Initialize( const EditorRuntimePaths& runtimePaths, - EditorEngineServices& engineServices) override; + SceneViewportEngineBridge& sceneViewportEngineBridge, + GameViewportEngineBridge& gameViewportEngineBridge, + EditorShaderProvider& shaderProvider) override; void Shutdown() override; void AttachWindowRenderer(Rendering::Host::ViewportRenderHost& windowRenderer) override; void DetachWindowRenderer() override; @@ -83,10 +86,10 @@ private: ::XCEngine::RHI::RHIDevice* m_device = nullptr; ViewportRenderTargetManager m_renderTargetManager = {}; bool m_surfacePresentationEnabled = false; - EditorEngineServices* m_engineServices = nullptr; std::unordered_map m_entries = {}; std::vector> m_retiredTargetsBySlot = {}; std::vector> m_placeholderRenderers = {}; + GameViewportRenderService m_gameViewportRuntime = {}; SceneViewportRenderService m_sceneViewportRuntime = {}; }; diff --git a/editor/app/Services/Engine/EngineEditorServices.cpp b/editor/app/Services/Engine/EngineEditorServices.cpp index 65b15ac0..f87e19c9 100644 --- a/editor/app/Services/Engine/EngineEditorServices.cpp +++ b/editor/app/Services/Engine/EngineEditorServices.cpp @@ -1,2233 +1,47 @@ #include "Engine/EngineEditorServices.h" + +#include "Engine/EditorEngineLifecycle.h" +#include "Engine/EditorSceneBackendFactory.h" +#include "Engine/EditorShaderProvider.h" +#include "Engine/EngineGameViewportBridge.h" +#include "Engine/EngineSceneViewportBridge.h" +#include "Engine/GameViewportEngineBridge.h" +#include "Engine/SceneViewportEngineBridge.h" #include "Scene/EngineEditorSceneBackend.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - namespace XCEngine::UI::Editor::App { namespace { -using ::XCEngine::Components::Component; -using ::XCEngine::Components::ComponentFactoryRegistry; -using ::XCEngine::Components::GameObject; -using ::XCEngine::Components::LightComponent; -using ::XCEngine::Components::Scene; -using ::XCEngine::Components::SceneManager; -using ::XCEngine::Components::TransformComponent; -using ::XCEngine::Math::Quaternion; -using ::XCEngine::Math::Vector3; -using ::XCEngine::Rendering::RenderObjectId; -using ::XCEngine::Rendering::RenderObjectIdRegistry; -using ::XCEngine::Rendering::SceneRenderer; -using ::XCEngine::Resources::ResourceManager; -using ::XCEngine::Resources::Shader; - -constexpr char kComponentIdSeparator = '#'; - -void TraceSceneStartup(std::string message) { - ::XCEngine::UI::Editor::AppendUIEditorRuntimeTrace("startup", std::move(message)); -} - -struct ClipboardNode { - std::string name = {}; - std::string transformPayload = {}; - std::vector> components = {}; - std::vector children = {}; -}; - -Scene* ResolvePrimaryScene(SceneManager& sceneManager) { - if (Scene* activeScene = sceneManager.GetActiveScene(); - activeScene != nullptr) { - return activeScene; - } - - const std::vector scenes = sceneManager.GetAllScenes(); - if (!scenes.empty() && scenes.front() != nullptr) { - sceneManager.SetActiveScene(scenes.front()); - return scenes.front(); - } - - return nullptr; -} - -std::pair SerializeComponent( - const Component* component) { - std::ostringstream payload = {}; - component->Serialize(payload); - return { component->GetName(), payload.str() }; -} - -ClipboardNode CopyGameObjectRecursive(const GameObject& gameObject) { - ClipboardNode node = {}; - node.name = gameObject.GetName(); - - if (const TransformComponent* transform = gameObject.GetTransform(); - transform != nullptr) { - std::ostringstream payload = {}; - transform->Serialize(payload); - node.transformPayload = payload.str(); - } - - const auto components = gameObject.GetComponents(); - for (const Component* component : components) { - if (component == nullptr || component == gameObject.GetTransform()) { - continue; - } - - node.components.push_back(SerializeComponent(component)); - } - - for (GameObject* child : gameObject.GetChildren()) { - if (child == nullptr) { - continue; - } - - node.children.push_back(CopyGameObjectRecursive(*child)); - } - - return node; -} - -GameObject* PasteGameObjectRecursive( - Scene& scene, - const ClipboardNode& node, - GameObject* parent) { - GameObject* gameObject = scene.CreateGameObject(node.name, parent); - if (gameObject == nullptr) { - return nullptr; - } - - if (!node.transformPayload.empty()) { - if (TransformComponent* transform = gameObject->GetTransform(); - transform != nullptr) { - std::istringstream transformStream(node.transformPayload); - transform->Deserialize(transformStream); - } - } - - for (const auto& componentData : node.components) { - Component* component = - ComponentFactoryRegistry::Get().CreateComponent( - gameObject, - componentData.first); - if (component == nullptr || componentData.second.empty()) { - continue; - } - - std::istringstream payloadStream(componentData.second); - component->Deserialize(payloadStream); - } - - for (const ClipboardNode& child : node.children) { - PasteGameObjectRecursive(scene, child, gameObject); - } - - return gameObject; -} - -bool WouldCreateCycle( - const GameObject& source, - const GameObject& targetParent) { - const GameObject* current = &targetParent; - while (current != nullptr) { - if (current == &source) { - return true; - } - current = current->GetParent(); - } - - return false; -} - -std::vector ResolveSiblingSequence( - Scene& scene, - GameObject* parent) { - return parent != nullptr - ? parent->GetChildren() - : scene.GetRootGameObjects(); -} - -std::optional FindSiblingIndex( - const std::vector& siblings, - const GameObject* gameObject) { - if (gameObject == nullptr) { - return std::nullopt; - } - - const auto it = - std::find(siblings.begin(), siblings.end(), gameObject); - if (it == siblings.end()) { - return std::nullopt; - } - - return static_cast(std::distance(siblings.begin(), it)); -} - -GameObject* FindGameObjectByItemId( - SceneManager& sceneManager, - std::string_view itemId) { - Scene* scene = ResolvePrimaryScene(sceneManager); - const std::optional gameObjectId = - ParseEditorGameObjectItemId(itemId); - if (scene == nullptr || !gameObjectId.has_value()) { - return nullptr; - } - - return scene->FindByID(gameObjectId.value()); -} - -EditorSceneHierarchyNode BuildHierarchySnapshotNodeRecursive( - const GameObject& gameObject) { - EditorSceneHierarchyNode node = {}; - node.itemId = MakeEditorGameObjectItemId(gameObject.GetID()); - node.displayName = gameObject.GetName().empty() - ? std::string("GameObject") - : gameObject.GetName(); - node.children.reserve(gameObject.GetChildCount()); - for (std::size_t childIndex = 0u; - childIndex < gameObject.GetChildCount(); - ++childIndex) { - const GameObject* child = gameObject.GetChild(childIndex); - if (child == nullptr) { - continue; - } - - node.children.push_back(BuildHierarchySnapshotNodeRecursive(*child)); - } - return node; -} - -std::string BuildEditorComponentId( - std::string_view typeName, - std::size_t ordinal) { - std::string componentId(typeName); - componentId.push_back(kComponentIdSeparator); - componentId += std::to_string(ordinal); - return componentId; -} - -bool ParseEditorComponentId( - std::string_view componentId, - std::string& outTypeName, - std::size_t& outOrdinal) { - const std::size_t separatorIndex = componentId.find(kComponentIdSeparator); - if (separatorIndex == std::string_view::npos || - separatorIndex == 0u || - separatorIndex + 1u >= componentId.size()) { - return false; - } - - outTypeName = std::string(componentId.substr(0u, separatorIndex)); - std::size_t ordinal = 0u; - const std::string_view ordinalText = - componentId.substr(separatorIndex + 1u); - const char* first = ordinalText.data(); - const char* last = ordinalText.data() + ordinalText.size(); - const std::from_chars_result result = - std::from_chars(first, last, ordinal); - if (result.ec != std::errc() || result.ptr != last) { - outTypeName.clear(); - return false; - } - - outOrdinal = ordinal; - return true; -} - -std::string ResolveGameObjectDisplayName(const GameObject& gameObject) { - return gameObject.GetName().empty() - ? std::string("GameObject") - : gameObject.GetName(); -} - -EditorSceneCameraProjectionType ToEditorCameraProjectionType( - ::XCEngine::Components::CameraProjectionType projectionType) { - switch (projectionType) { - case ::XCEngine::Components::CameraProjectionType::Orthographic: - return EditorSceneCameraProjectionType::Orthographic; - case ::XCEngine::Components::CameraProjectionType::Perspective: - default: - return EditorSceneCameraProjectionType::Perspective; - } -} - -::XCEngine::Components::CameraProjectionType ToEngineCameraProjectionType( - EditorSceneCameraProjectionType projectionType) { - switch (projectionType) { - case EditorSceneCameraProjectionType::Orthographic: - return ::XCEngine::Components::CameraProjectionType::Orthographic; - case EditorSceneCameraProjectionType::Perspective: - default: - return ::XCEngine::Components::CameraProjectionType::Perspective; - } -} - -EditorSceneLightType ToEditorLightType(::XCEngine::Components::LightType lightType) { - switch (lightType) { - case ::XCEngine::Components::LightType::Point: - return EditorSceneLightType::Point; - case ::XCEngine::Components::LightType::Spot: - return EditorSceneLightType::Spot; - case ::XCEngine::Components::LightType::Directional: - default: - return EditorSceneLightType::Directional; - } -} - -::XCEngine::Components::LightType ToEngineLightType(EditorSceneLightType lightType) { - switch (lightType) { - case EditorSceneLightType::Point: - return ::XCEngine::Components::LightType::Point; - case EditorSceneLightType::Spot: - return ::XCEngine::Components::LightType::Spot; - case EditorSceneLightType::Directional: - default: - return ::XCEngine::Components::LightType::Directional; - } -} - -EditorSceneColliderAxis ToEditorColliderAxis( - ::XCEngine::Components::ColliderAxis axis) { - switch (axis) { - case ::XCEngine::Components::ColliderAxis::X: - return EditorSceneColliderAxis::X; - case ::XCEngine::Components::ColliderAxis::Z: - return EditorSceneColliderAxis::Z; - case ::XCEngine::Components::ColliderAxis::Y: - default: - return EditorSceneColliderAxis::Y; - } -} - -::XCEngine::Components::ColliderAxis ToEngineColliderAxis( - EditorSceneColliderAxis axis) { - switch (axis) { - case EditorSceneColliderAxis::X: - return ::XCEngine::Components::ColliderAxis::X; - case EditorSceneColliderAxis::Z: - return ::XCEngine::Components::ColliderAxis::Z; - case EditorSceneColliderAxis::Y: - default: - return ::XCEngine::Components::ColliderAxis::Y; - } -} - -EditorScenePhysicsBodyType ToEditorPhysicsBodyType( - ::XCEngine::Physics::PhysicsBodyType bodyType) { - switch (bodyType) { - case ::XCEngine::Physics::PhysicsBodyType::Static: - return EditorScenePhysicsBodyType::Static; - case ::XCEngine::Physics::PhysicsBodyType::Kinematic: - return EditorScenePhysicsBodyType::Kinematic; - case ::XCEngine::Physics::PhysicsBodyType::Dynamic: - default: - return EditorScenePhysicsBodyType::Dynamic; - } -} - -::XCEngine::Physics::PhysicsBodyType ToEnginePhysicsBodyType( - EditorScenePhysicsBodyType bodyType) { - switch (bodyType) { - case EditorScenePhysicsBodyType::Static: - return ::XCEngine::Physics::PhysicsBodyType::Static; - case EditorScenePhysicsBodyType::Kinematic: - return ::XCEngine::Physics::PhysicsBodyType::Kinematic; - case EditorScenePhysicsBodyType::Dynamic: - default: - return ::XCEngine::Physics::PhysicsBodyType::Dynamic; - } -} - -template -class ComponentViewAdapterBase : public TView { -public: - ComponentViewAdapterBase(TComponent& component, const char* typeName) - : m_component(component) - , m_typeName(typeName) {} - - std::string_view GetTypeName() const override { - return m_typeName; - } - -protected: - TComponent& MutableComponent() const { - return m_component; - } - - const TComponent& ComponentRef() const { - return m_component; - } - -private: - TComponent& m_component; - const char* m_typeName = ""; -}; - -class GenericComponentViewAdapter final : public EditorSceneComponentView { -public: - explicit GenericComponentViewAdapter(std::string typeName) - : m_typeName(std::move(typeName)) {} - - std::string_view GetTypeName() const override { - return m_typeName; - } - -private: - std::string m_typeName = {}; -}; - -class TransformComponentViewAdapter final - : public ComponentViewAdapterBase { -public: - explicit TransformComponentViewAdapter(TransformComponent& component) - : ComponentViewAdapterBase(component, "Transform") {} - - Vector3 GetLocalPosition() const override { return ComponentRef().GetLocalPosition(); } - Vector3 GetLocalEulerAngles() const override { return ComponentRef().GetLocalEulerAngles(); } - Vector3 GetLocalScale() const override { return ComponentRef().GetLocalScale(); } -}; - -class CameraComponentViewAdapter final - : public ComponentViewAdapterBase< - EditorSceneCameraComponentView, - ::XCEngine::Components::CameraComponent> { -public: - explicit CameraComponentViewAdapter(::XCEngine::Components::CameraComponent& component) - : ComponentViewAdapterBase(component, "Camera") {} - - EditorSceneCameraProjectionType GetProjectionType() const override { - return ToEditorCameraProjectionType(ComponentRef().GetProjectionType()); - } - - void SetProjectionType(EditorSceneCameraProjectionType projectionType) { - MutableComponent().SetProjectionType(ToEngineCameraProjectionType(projectionType)); - } - - float GetFieldOfView() const override { return ComponentRef().GetFieldOfView(); } - void SetFieldOfView(float fieldOfView) { MutableComponent().SetFieldOfView(fieldOfView); } - float GetOrthographicSize() const override { return ComponentRef().GetOrthographicSize(); } - void SetOrthographicSize(float orthographicSize) { MutableComponent().SetOrthographicSize(orthographicSize); } - float GetNearClipPlane() const override { return ComponentRef().GetNearClipPlane(); } - void SetNearClipPlane(float nearClipPlane) { MutableComponent().SetNearClipPlane(nearClipPlane); } - float GetFarClipPlane() const override { return ComponentRef().GetFarClipPlane(); } - void SetFarClipPlane(float farClipPlane) { MutableComponent().SetFarClipPlane(farClipPlane); } - float GetDepth() const override { return ComponentRef().GetDepth(); } - void SetDepth(float depth) { MutableComponent().SetDepth(depth); } - bool IsPrimary() const override { return ComponentRef().IsPrimary(); } - void SetPrimary(bool primary) { MutableComponent().SetPrimary(primary); } - bool IsSkyboxEnabled() const override { return ComponentRef().IsSkyboxEnabled(); } - void SetSkyboxEnabled(bool enabled) { MutableComponent().SetSkyboxEnabled(enabled); } - std::string GetSkyboxMaterialPath() const override { return ComponentRef().GetSkyboxMaterialPath(); } - void SetSkyboxMaterialPath(std::string_view assetId) { MutableComponent().SetSkyboxMaterialPath(std::string(assetId)); } - ::XCEngine::Math::Color GetSkyboxTopColor() const override { return ComponentRef().GetSkyboxTopColor(); } - void SetSkyboxTopColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetSkyboxTopColor(color); } - ::XCEngine::Math::Color GetSkyboxHorizonColor() const override { return ComponentRef().GetSkyboxHorizonColor(); } - void SetSkyboxHorizonColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetSkyboxHorizonColor(color); } - ::XCEngine::Math::Color GetSkyboxBottomColor() const override { return ComponentRef().GetSkyboxBottomColor(); } - void SetSkyboxBottomColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetSkyboxBottomColor(color); } - ::XCEngine::Math::Color GetClearColor() const override { return ComponentRef().GetClearColor(); } - void SetClearColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetClearColor(color); } -}; - -class LightComponentViewAdapter final - : public ComponentViewAdapterBase { -public: - explicit LightComponentViewAdapter(LightComponent& component) - : ComponentViewAdapterBase(component, "Light") {} - - EditorSceneLightType GetLightType() const override { - return ToEditorLightType(ComponentRef().GetLightType()); - } - - void SetLightType(EditorSceneLightType lightType) { - MutableComponent().SetLightType(ToEngineLightType(lightType)); - } - - ::XCEngine::Math::Color GetColor() const override { return ComponentRef().GetColor(); } - void SetColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetColor(color); } - float GetIntensity() const override { return ComponentRef().GetIntensity(); } - void SetIntensity(float intensity) { MutableComponent().SetIntensity(intensity); } - float GetRange() const override { return ComponentRef().GetRange(); } - void SetRange(float range) { MutableComponent().SetRange(range); } - float GetSpotAngle() const override { return ComponentRef().GetSpotAngle(); } - void SetSpotAngle(float spotAngle) { MutableComponent().SetSpotAngle(spotAngle); } - bool GetCastsShadows() const override { return ComponentRef().GetCastsShadows(); } - void SetCastsShadows(bool castsShadows) { MutableComponent().SetCastsShadows(castsShadows); } - bool GetOverridesDirectionalShadowSettings() const override { return ComponentRef().GetOverridesDirectionalShadowSettings(); } - void SetOverridesDirectionalShadowSettings(bool overrides) { MutableComponent().SetOverridesDirectionalShadowSettings(overrides); } - float GetDirectionalShadowReceiverDepthBias() const override { return ComponentRef().GetDirectionalShadowReceiverDepthBias(); } - void SetDirectionalShadowReceiverDepthBias(float value) { MutableComponent().SetDirectionalShadowReceiverDepthBias(value); } - float GetDirectionalShadowNormalBiasScale() const override { return ComponentRef().GetDirectionalShadowNormalBiasScale(); } - void SetDirectionalShadowNormalBiasScale(float value) { MutableComponent().SetDirectionalShadowNormalBiasScale(value); } - float GetDirectionalShadowStrength() const override { return ComponentRef().GetDirectionalShadowStrength(); } - void SetDirectionalShadowStrength(float value) { MutableComponent().SetDirectionalShadowStrength(value); } - float GetDirectionalShadowDepthBiasFactor() const override { return ComponentRef().GetDirectionalShadowDepthBiasFactor(); } - void SetDirectionalShadowDepthBiasFactor(float value) { MutableComponent().SetDirectionalShadowDepthBiasFactor(value); } - int GetDirectionalShadowDepthBiasUnits() const override { return ComponentRef().GetDirectionalShadowDepthBiasUnits(); } - void SetDirectionalShadowDepthBiasUnits(int value) { MutableComponent().SetDirectionalShadowDepthBiasUnits(value); } -}; - -template -class ColliderComponentViewAdapterBase : public ComponentViewAdapterBase { -public: - ColliderComponentViewAdapterBase(TColliderComponent& component, const char* typeName) - : ComponentViewAdapterBase(component, typeName) {} - - bool IsTrigger() const override { return this->ComponentRef().IsTrigger(); } - void SetTrigger(bool trigger) { this->MutableComponent().SetTrigger(trigger); } - Vector3 GetCenter() const override { return this->ComponentRef().GetCenter(); } - void SetCenter(const Vector3& center) { this->MutableComponent().SetCenter(center); } - float GetStaticFriction() const override { return this->ComponentRef().GetStaticFriction(); } - void SetStaticFriction(float value) { this->MutableComponent().SetStaticFriction(value); } - float GetDynamicFriction() const override { return this->ComponentRef().GetDynamicFriction(); } - void SetDynamicFriction(float value) { this->MutableComponent().SetDynamicFriction(value); } - float GetRestitution() const override { return this->ComponentRef().GetRestitution(); } - void SetRestitution(float value) { this->MutableComponent().SetRestitution(value); } -}; - -class BoxColliderComponentViewAdapter final - : public ColliderComponentViewAdapterBase< - EditorSceneBoxColliderComponentView, - ::XCEngine::Components::BoxColliderComponent> { -public: - explicit BoxColliderComponentViewAdapter(::XCEngine::Components::BoxColliderComponent& component) - : ColliderComponentViewAdapterBase(component, "BoxCollider") {} - - Vector3 GetSize() const override { return this->ComponentRef().GetSize(); } - void SetSize(const Vector3& size) { this->MutableComponent().SetSize(size); } -}; - -class CapsuleColliderComponentViewAdapter final - : public ColliderComponentViewAdapterBase< - EditorSceneCapsuleColliderComponentView, - ::XCEngine::Components::CapsuleColliderComponent> { -public: - explicit CapsuleColliderComponentViewAdapter( - ::XCEngine::Components::CapsuleColliderComponent& component) - : ColliderComponentViewAdapterBase(component, "CapsuleCollider") {} - - float GetRadius() const override { return this->ComponentRef().GetRadius(); } - void SetRadius(float radius) { this->MutableComponent().SetRadius(radius); } - float GetHeight() const override { return this->ComponentRef().GetHeight(); } - void SetHeight(float height) { this->MutableComponent().SetHeight(height); } - EditorSceneColliderAxis GetAxis() const override { - return ToEditorColliderAxis(this->ComponentRef().GetAxis()); - } - void SetAxis(EditorSceneColliderAxis axis) { - this->MutableComponent().SetAxis(ToEngineColliderAxis(axis)); - } -}; - -class SphereColliderComponentViewAdapter final - : public ColliderComponentViewAdapterBase< - EditorSceneSphereColliderComponentView, - ::XCEngine::Components::SphereColliderComponent> { -public: - explicit SphereColliderComponentViewAdapter(::XCEngine::Components::SphereColliderComponent& component) - : ColliderComponentViewAdapterBase(component, "SphereCollider") {} - - float GetRadius() const override { return this->ComponentRef().GetRadius(); } - void SetRadius(float radius) { this->MutableComponent().SetRadius(radius); } -}; - -class AudioSourceComponentViewAdapter final - : public ComponentViewAdapterBase< - EditorSceneAudioSourceComponentView, - ::XCEngine::Components::AudioSourceComponent> { -public: - explicit AudioSourceComponentViewAdapter(::XCEngine::Components::AudioSourceComponent& component) - : ComponentViewAdapterBase(component, "AudioSource") {} - - std::string GetClipPath() const override { return ComponentRef().GetClipPath(); } - void ClearClip() { MutableComponent().ClearClip(); } - void SetClipPath(std::string_view assetId) { MutableComponent().SetClipPath(std::string(assetId)); } - float GetVolume() const override { return ComponentRef().GetVolume(); } - void SetVolume(float volume) { MutableComponent().SetVolume(volume); } - float GetPitch() const override { return ComponentRef().GetPitch(); } - void SetPitch(float pitch) { MutableComponent().SetPitch(pitch); } - float GetPan() const override { return ComponentRef().GetPan(); } - void SetPan(float pan) { MutableComponent().SetPan(pan); } - bool IsLooping() const override { return ComponentRef().IsLooping(); } - void SetLooping(bool looping) { MutableComponent().SetLooping(looping); } - bool IsSpatialize() const override { return ComponentRef().IsSpatialize(); } - void SetSpatialize(bool spatialize) { MutableComponent().SetSpatialize(spatialize); } - ::XCEngine::Audio::Audio3DParams Get3DParams() const override { return ComponentRef().Get3DParams(); } - void Set3DParams(const ::XCEngine::Audio::Audio3DParams& params) { MutableComponent().Set3DParams(params); } - bool IsHRTFEnabled() const override { return ComponentRef().IsHRTFEnabled(); } - void SetHRTFEnabled(bool enabled) { MutableComponent().SetHRTFEnabled(enabled); } - float GetHRTFCrossFeed() const override { return ComponentRef().GetHRTFCrossFeed(); } - void SetHRTFCrossFeed(float value) { MutableComponent().SetHRTFCrossFeed(value); } - std::uint32_t GetHRTFQuality() const override { return ComponentRef().GetHRTFQuality(); } - void SetHRTFQuality(std::uint32_t quality) { MutableComponent().SetHRTFQuality(quality); } -}; - -class AudioListenerComponentViewAdapter final - : public ComponentViewAdapterBase< - EditorSceneAudioListenerComponentView, - ::XCEngine::Components::AudioListenerComponent> { -public: - explicit AudioListenerComponentViewAdapter( - ::XCEngine::Components::AudioListenerComponent& component) - : ComponentViewAdapterBase(component, "AudioListener") {} - - float GetMasterVolume() const override { return ComponentRef().GetMasterVolume(); } - void SetMasterVolume(float value) { MutableComponent().SetMasterVolume(value); } - bool IsMute() const override { return ComponentRef().IsMute(); } - void SetMute(bool mute) { MutableComponent().SetMute(mute); } - float GetDopplerLevel() const override { return ComponentRef().GetDopplerLevel(); } - void SetDopplerLevel(float value) { MutableComponent().SetDopplerLevel(value); } - float GetSpeedOfSound() const override { return ComponentRef().GetSpeedOfSound(); } - void SetSpeedOfSound(float value) { MutableComponent().SetSpeedOfSound(value); } - float GetReverbLevel() const override { return ComponentRef().GetReverbLevel(); } - void SetReverbLevel(float value) { MutableComponent().SetReverbLevel(value); } -}; - -class MeshFilterComponentViewAdapter final - : public ComponentViewAdapterBase< - EditorSceneMeshFilterComponentView, - ::XCEngine::Components::MeshFilterComponent> { -public: - explicit MeshFilterComponentViewAdapter(::XCEngine::Components::MeshFilterComponent& component) - : ComponentViewAdapterBase(component, "MeshFilter") {} - - std::string GetMeshPath() const override { return ComponentRef().GetMeshPath(); } - void ClearMesh() { MutableComponent().ClearMesh(); } - void SetMeshPath(std::string_view assetId) { MutableComponent().SetMeshPath(std::string(assetId)); } -}; - -class MeshRendererComponentViewAdapter final - : public ComponentViewAdapterBase< - EditorSceneMeshRendererComponentView, - ::XCEngine::Components::MeshRendererComponent> { -public: - explicit MeshRendererComponentViewAdapter( - ::XCEngine::Components::MeshRendererComponent& component) - : ComponentViewAdapterBase(component, "MeshRenderer") {} - - bool GetCastShadows() const override { return ComponentRef().GetCastShadows(); } - void SetCastShadows(bool value) { MutableComponent().SetCastShadows(value); } - bool GetReceiveShadows() const override { return ComponentRef().GetReceiveShadows(); } - void SetReceiveShadows(bool value) { MutableComponent().SetReceiveShadows(value); } - std::uint32_t GetRenderLayer() const override { return ComponentRef().GetRenderLayer(); } - void SetRenderLayer(std::uint32_t value) { MutableComponent().SetRenderLayer(value); } - std::size_t GetMaterialCount() const override { return ComponentRef().GetMaterialCount(); } - std::string GetMaterialPath(std::size_t slotIndex) const override { return ComponentRef().GetMaterialPath(slotIndex); } - void SetMaterialPath(std::size_t slotIndex, std::string_view assetId) { - MutableComponent().SetMaterialPath(slotIndex, std::string(assetId)); - } -}; - -class RigidbodyComponentViewAdapter final - : public ComponentViewAdapterBase< - EditorSceneRigidbodyComponentView, - ::XCEngine::Components::RigidbodyComponent> { -public: - explicit RigidbodyComponentViewAdapter(::XCEngine::Components::RigidbodyComponent& component) - : ComponentViewAdapterBase(component, "Rigidbody") {} - - EditorScenePhysicsBodyType GetBodyType() const override { - return ToEditorPhysicsBodyType(ComponentRef().GetBodyType()); - } - void SetBodyType(EditorScenePhysicsBodyType bodyType) { - MutableComponent().SetBodyType(ToEnginePhysicsBodyType(bodyType)); - } - float GetMass() const override { return ComponentRef().GetMass(); } - void SetMass(float mass) { MutableComponent().SetMass(mass); } - float GetLinearDamping() const override { return ComponentRef().GetLinearDamping(); } - void SetLinearDamping(float damping) { MutableComponent().SetLinearDamping(damping); } - float GetAngularDamping() const override { return ComponentRef().GetAngularDamping(); } - void SetAngularDamping(float damping) { MutableComponent().SetAngularDamping(damping); } - bool GetUseGravity() const override { return ComponentRef().GetUseGravity(); } - void SetUseGravity(bool useGravity) { MutableComponent().SetUseGravity(useGravity); } - bool GetEnableCCD() const override { return ComponentRef().GetEnableCCD(); } - void SetEnableCCD(bool enableCCD) { MutableComponent().SetEnableCCD(enableCCD); } -}; - -class VolumeRendererComponentViewAdapter final - : public ComponentViewAdapterBase< - EditorSceneVolumeRendererComponentView, - ::XCEngine::Components::VolumeRendererComponent> { -public: - explicit VolumeRendererComponentViewAdapter( - ::XCEngine::Components::VolumeRendererComponent& component) - : ComponentViewAdapterBase(component, "VolumeRenderer") {} - - std::string GetVolumeFieldPath() const override { return ComponentRef().GetVolumeFieldPath(); } - void ClearVolumeField() { MutableComponent().ClearVolumeField(); } - void SetVolumeFieldPath(std::string_view assetId) { MutableComponent().SetVolumeFieldPath(std::string(assetId)); } - std::string GetMaterialPath() const override { return ComponentRef().GetMaterialPath(); } - void ClearMaterial() { MutableComponent().ClearMaterial(); } - void SetMaterialPath(std::string_view assetId) { MutableComponent().SetMaterialPath(std::string(assetId)); } - bool GetCastShadows() const override { return ComponentRef().GetCastShadows(); } - void SetCastShadows(bool value) { MutableComponent().SetCastShadows(value); } - bool GetReceiveShadows() const override { return ComponentRef().GetReceiveShadows(); } - void SetReceiveShadows(bool value) { MutableComponent().SetReceiveShadows(value); } -}; - -std::shared_ptr CreateComponentView(Component& component) { - if (auto* transform = dynamic_cast(&component); transform != nullptr) { - return std::make_shared(*transform); - } - if (auto* camera = dynamic_cast<::XCEngine::Components::CameraComponent*>(&component); - camera != nullptr) { - return std::make_shared(*camera); - } - if (auto* light = dynamic_cast(&component); light != nullptr) { - return std::make_shared(*light); - } - if (auto* collider = dynamic_cast<::XCEngine::Components::BoxColliderComponent*>(&component); - collider != nullptr) { - return std::make_shared(*collider); - } - if (auto* collider = - dynamic_cast<::XCEngine::Components::CapsuleColliderComponent*>(&component); - collider != nullptr) { - return std::make_shared(*collider); - } - if (auto* collider = - dynamic_cast<::XCEngine::Components::SphereColliderComponent*>(&component); - collider != nullptr) { - return std::make_shared(*collider); - } - if (auto* source = dynamic_cast<::XCEngine::Components::AudioSourceComponent*>(&component); - source != nullptr) { - return std::make_shared(*source); - } - if (auto* listener = - dynamic_cast<::XCEngine::Components::AudioListenerComponent*>(&component); - listener != nullptr) { - return std::make_shared(*listener); - } - if (auto* meshFilter = - dynamic_cast<::XCEngine::Components::MeshFilterComponent*>(&component); - meshFilter != nullptr) { - return std::make_shared(*meshFilter); - } - if (auto* meshRenderer = - dynamic_cast<::XCEngine::Components::MeshRendererComponent*>(&component); - meshRenderer != nullptr) { - return std::make_shared(*meshRenderer); - } - if (auto* rigidbody = - dynamic_cast<::XCEngine::Components::RigidbodyComponent*>(&component); - rigidbody != nullptr) { - return std::make_shared(*rigidbody); - } - if (auto* volumeRenderer = - dynamic_cast<::XCEngine::Components::VolumeRendererComponent*>(&component); - volumeRenderer != nullptr) { - return std::make_shared(*volumeRenderer); - } - - return std::make_shared(component.GetName()); -} - -template -const TValue* TryGetMutationValue(const EditorSceneComponentMutation& mutation) { - return std::get_if(&mutation.value); -} - -template -bool TryApplyMutationValue( - const EditorSceneComponentMutation& mutation, - std::string_view propertyPath, - TApply&& apply) { - if (mutation.propertyPath != propertyPath) { - return false; - } - - const TValue* value = TryGetMutationValue(mutation); - if (value == nullptr) { - return false; - } - - apply(*value); - return true; -} - -bool TryParseMaterialSlotProperty( - std::string_view propertyPath, - std::size_t& outSlotIndex) { - constexpr std::string_view kPrefix = "material_"; - if (propertyPath.size() <= kPrefix.size() || - propertyPath.substr(0u, kPrefix.size()) != kPrefix) { - return false; - } - - const std::string_view slotText = propertyPath.substr(kPrefix.size()); - std::size_t slotIndex = 0u; - const char* first = slotText.data(); - const char* last = slotText.data() + slotText.size(); - const std::from_chars_result result = - std::from_chars(first, last, slotIndex); - if (result.ec != std::errc() || result.ptr != last) { - return false; - } - - outSlotIndex = slotIndex; - return true; -} - -template -bool ApplyColliderBaseMutation( - TColliderViewAdapter& view, - const EditorSceneComponentMutation& mutation) { - if (TryApplyMutationValue( - mutation, - "is_trigger", - [&view](bool value) { view.SetTrigger(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "center", - [&view](const Vector3& value) { view.SetCenter(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "static_friction", - [&view](float value) { view.SetStaticFriction(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "dynamic_friction", - [&view](float value) { view.SetDynamicFriction(value); })) { - return true; - } - return TryApplyMutationValue( - mutation, - "restitution", - [&view](float value) { view.SetRestitution(value); }); -} - -bool ApplyCameraComponentMutation( - ::XCEngine::Components::CameraComponent& component, - const EditorSceneComponentMutation& mutation) { - CameraComponentViewAdapter view(component); - if (TryApplyMutationValue( - mutation, - "projection", - [&view](EditorSceneCameraProjectionType value) { - view.SetProjectionType(value); - })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "field_of_view", - [&view](float value) { view.SetFieldOfView(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "orthographic_size", - [&view](float value) { view.SetOrthographicSize(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "near_clip", - [&view](float value) { view.SetNearClipPlane(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "far_clip", - [&view](float value) { view.SetFarClipPlane(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "depth", - [&view](float value) { view.SetDepth(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "primary", - [&view](bool value) { view.SetPrimary(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "skybox", - [&view](bool value) { view.SetSkyboxEnabled(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "skybox_material", - [&view](const std::string& value) { - view.SetSkyboxMaterialPath(value); - })) { - return true; - } - if (TryApplyMutationValue<::XCEngine::Math::Color>( - mutation, - "skybox_top", - [&view](const ::XCEngine::Math::Color& value) { - view.SetSkyboxTopColor(value); - })) { - return true; - } - if (TryApplyMutationValue<::XCEngine::Math::Color>( - mutation, - "skybox_horizon", - [&view](const ::XCEngine::Math::Color& value) { - view.SetSkyboxHorizonColor(value); - })) { - return true; - } - if (TryApplyMutationValue<::XCEngine::Math::Color>( - mutation, - "skybox_bottom", - [&view](const ::XCEngine::Math::Color& value) { - view.SetSkyboxBottomColor(value); - })) { - return true; - } - return TryApplyMutationValue<::XCEngine::Math::Color>( - mutation, - "clear_color", - [&view](const ::XCEngine::Math::Color& value) { - view.SetClearColor(value); - }); -} - -bool ApplyLightComponentMutation( - LightComponent& component, - const EditorSceneComponentMutation& mutation) { - LightComponentViewAdapter view(component); - if (TryApplyMutationValue( - mutation, - "type", - [&view](EditorSceneLightType value) { view.SetLightType(value); })) { - return true; - } - if (TryApplyMutationValue<::XCEngine::Math::Color>( - mutation, - "color", - [&view](const ::XCEngine::Math::Color& value) { - view.SetColor(value); - })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "intensity", - [&view](float value) { view.SetIntensity(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "range", - [&view](float value) { view.SetRange(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "spot_angle", - [&view](float value) { view.SetSpotAngle(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "cast_shadows", - [&view](bool value) { view.SetCastsShadows(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "override_shadow_params", - [&view](bool value) { - view.SetOverridesDirectionalShadowSettings(value); - })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "receiver_depth_bias", - [&view](float value) { - view.SetDirectionalShadowReceiverDepthBias(value); - })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "normal_bias_scale", - [&view](float value) { - view.SetDirectionalShadowNormalBiasScale(value); - })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "shadow_strength", - [&view](float value) { - view.SetDirectionalShadowStrength(value); - })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "depth_bias_factor", - [&view](float value) { - view.SetDirectionalShadowDepthBiasFactor(value); - })) { - return true; - } - return TryApplyMutationValue( - mutation, - "depth_bias_units", - [&view](std::int32_t value) { - view.SetDirectionalShadowDepthBiasUnits(value); - }); -} - -bool ApplyBoxColliderComponentMutation( - ::XCEngine::Components::BoxColliderComponent& component, - const EditorSceneComponentMutation& mutation) { - BoxColliderComponentViewAdapter view(component); - if (ApplyColliderBaseMutation(view, mutation)) { - return true; - } - return TryApplyMutationValue( - mutation, - "size", - [&view](const Vector3& value) { view.SetSize(value); }); -} - -bool ApplyCapsuleColliderComponentMutation( - ::XCEngine::Components::CapsuleColliderComponent& component, - const EditorSceneComponentMutation& mutation) { - CapsuleColliderComponentViewAdapter view(component); - if (ApplyColliderBaseMutation(view, mutation)) { - return true; - } - if (TryApplyMutationValue( - mutation, - "radius", - [&view](float value) { view.SetRadius(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "height", - [&view](float value) { view.SetHeight(value); })) { - return true; - } - return TryApplyMutationValue( - mutation, - "axis", - [&view](EditorSceneColliderAxis value) { view.SetAxis(value); }); -} - -bool ApplySphereColliderComponentMutation( - ::XCEngine::Components::SphereColliderComponent& component, - const EditorSceneComponentMutation& mutation) { - SphereColliderComponentViewAdapter view(component); - if (ApplyColliderBaseMutation(view, mutation)) { - return true; - } - return TryApplyMutationValue( - mutation, - "radius", - [&view](float value) { view.SetRadius(value); }); -} - -bool TryApplyAudioSource3DParamMutation( - AudioSourceComponentViewAdapter& view, - const EditorSceneComponentMutation& mutation, - std::string_view propertyPath, - float ::XCEngine::Audio::Audio3DParams::* member) { - return TryApplyMutationValue( - mutation, - propertyPath, - [&view, member](float value) { - ::XCEngine::Audio::Audio3DParams params = view.Get3DParams(); - params.*member = value; - view.Set3DParams(params); - }); -} - -bool ApplyAudioSourceComponentMutation( - ::XCEngine::Components::AudioSourceComponent& component, - const EditorSceneComponentMutation& mutation) { - AudioSourceComponentViewAdapter view(component); - if (TryApplyMutationValue( - mutation, - "clip", - [&view](const std::string& value) { - if (value.empty()) { - view.ClearClip(); - } else { - view.SetClipPath(value); - } - })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "volume", - [&view](float value) { view.SetVolume(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "pitch", - [&view](float value) { view.SetPitch(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "pan", - [&view](float value) { view.SetPan(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "looping", - [&view](bool value) { view.SetLooping(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "spatialize", - [&view](bool value) { view.SetSpatialize(value); })) { - return true; - } - if (TryApplyAudioSource3DParamMutation( - view, - mutation, - "min_distance", - &::XCEngine::Audio::Audio3DParams::minDistance)) { - return true; - } - if (TryApplyAudioSource3DParamMutation( - view, - mutation, - "max_distance", - &::XCEngine::Audio::Audio3DParams::maxDistance)) { - return true; - } - if (TryApplyAudioSource3DParamMutation( - view, - mutation, - "pan_level", - &::XCEngine::Audio::Audio3DParams::panLevel)) { - return true; - } - if (TryApplyAudioSource3DParamMutation( - view, - mutation, - "spread", - &::XCEngine::Audio::Audio3DParams::spread)) { - return true; - } - if (TryApplyAudioSource3DParamMutation( - view, - mutation, - "reverb_send", - &::XCEngine::Audio::Audio3DParams::reverbZoneMix)) { - return true; - } - if (TryApplyAudioSource3DParamMutation( - view, - mutation, - "doppler_level", - &::XCEngine::Audio::Audio3DParams::dopplerLevel)) { - return true; - } - if (TryApplyMutationValue( - mutation, - "use_hrtf", - [&view](bool value) { view.SetHRTFEnabled(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "hrtf_crossfeed", - [&view](float value) { view.SetHRTFCrossFeed(value); })) { - return true; - } - return TryApplyMutationValue( - mutation, - "hrtf_quality", - [&view](std::uint32_t value) { view.SetHRTFQuality(value); }); -} - -bool ApplyAudioListenerComponentMutation( - ::XCEngine::Components::AudioListenerComponent& component, - const EditorSceneComponentMutation& mutation) { - AudioListenerComponentViewAdapter view(component); - if (TryApplyMutationValue( - mutation, - "master_volume", - [&view](float value) { view.SetMasterVolume(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "mute", - [&view](bool value) { view.SetMute(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "doppler_level", - [&view](float value) { view.SetDopplerLevel(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "speed_of_sound", - [&view](float value) { view.SetSpeedOfSound(value); })) { - return true; - } - return TryApplyMutationValue( - mutation, - "reverb_level", - [&view](float value) { view.SetReverbLevel(value); }); -} - -bool ApplyMeshFilterComponentMutation( - ::XCEngine::Components::MeshFilterComponent& component, - const EditorSceneComponentMutation& mutation) { - MeshFilterComponentViewAdapter view(component); - return TryApplyMutationValue( - mutation, - "mesh", - [&view](const std::string& value) { - if (value.empty()) { - view.ClearMesh(); - } else { - view.SetMeshPath(value); - } - }); -} - -bool ApplyMeshRendererComponentMutation( - ::XCEngine::Components::MeshRendererComponent& component, - const EditorSceneComponentMutation& mutation) { - MeshRendererComponentViewAdapter view(component); - if (TryApplyMutationValue( - mutation, - "cast_shadows", - [&view](bool value) { view.SetCastShadows(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "receive_shadows", - [&view](bool value) { view.SetReceiveShadows(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "render_layer", - [&view](std::uint32_t value) { view.SetRenderLayer(value); })) { - return true; - } - - std::size_t slotIndex = 0u; - if (!TryParseMaterialSlotProperty(mutation.propertyPath, slotIndex)) { - return false; - } - - const std::string* value = TryGetMutationValue(mutation); - if (value == nullptr) { - return false; - } - - view.SetMaterialPath(slotIndex, *value); - return true; -} - -bool ApplyRigidbodyComponentMutation( - ::XCEngine::Components::RigidbodyComponent& component, - const EditorSceneComponentMutation& mutation) { - RigidbodyComponentViewAdapter view(component); - if (TryApplyMutationValue( - mutation, - "body_type", - [&view](EditorScenePhysicsBodyType value) { - view.SetBodyType(value); - })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "mass", - [&view](float value) { view.SetMass(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "linear_damping", - [&view](float value) { view.SetLinearDamping(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "angular_damping", - [&view](float value) { view.SetAngularDamping(value); })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "use_gravity", - [&view](bool value) { view.SetUseGravity(value); })) { - return true; - } - return TryApplyMutationValue( - mutation, - "enable_ccd", - [&view](bool value) { view.SetEnableCCD(value); }); -} - -bool ApplyVolumeRendererComponentMutation( - ::XCEngine::Components::VolumeRendererComponent& component, - const EditorSceneComponentMutation& mutation) { - VolumeRendererComponentViewAdapter view(component); - if (TryApplyMutationValue( - mutation, - "volume_field", - [&view](const std::string& value) { - if (value.empty()) { - view.ClearVolumeField(); - } else { - view.SetVolumeFieldPath(value); - } - })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "material", - [&view](const std::string& value) { - if (value.empty()) { - view.ClearMaterial(); - } else { - view.SetMaterialPath(value); - } - })) { - return true; - } - if (TryApplyMutationValue( - mutation, - "cast_shadows", - [&view](bool value) { view.SetCastShadows(value); })) { - return true; - } - return TryApplyMutationValue( - mutation, - "receive_shadows", - [&view](bool value) { view.SetReceiveShadows(value); }); -} - -Component* ResolveComponent( - const GameObject& gameObject, - std::string_view componentId) { - std::string typeName = {}; - std::size_t ordinal = 0u; - if (!ParseEditorComponentId(componentId, typeName, ordinal)) { - return nullptr; - } - - std::size_t currentOrdinal = 0u; - for (const Component* component : gameObject.GetComponents()) { - if (component == nullptr || component->GetName() != typeName) { - continue; - } - - if (currentOrdinal == ordinal) { - return const_cast(component); - } - - ++currentOrdinal; - } - - return nullptr; -} - -EditorSceneComponentDescriptor BuildComponentDescriptor( - const Component& component, - std::size_t ordinal) { - EditorSceneComponentDescriptor descriptor = {}; - descriptor.typeName = component.GetName(); - descriptor.componentId = - BuildEditorComponentId(descriptor.typeName, ordinal); - descriptor.view = CreateComponentView(const_cast(component)); - descriptor.removable = - component.GetGameObject() != nullptr && - component.GetGameObject()->GetTransform() != &component; - return descriptor; -} - -EditorSceneComponentDescriptor ResolveComponentDescriptor( - const GameObject& gameObject, - std::string_view componentId) { - Component* component = ResolveComponent(gameObject, componentId); - if (component == nullptr) { - return {}; - } - - std::string typeName = {}; - std::size_t ordinal = 0u; - if (!ParseEditorComponentId(componentId, typeName, ordinal)) { - return {}; - } - - return BuildComponentDescriptor(*component, ordinal); -} - -std::size_t ResolveVisibleMaterialSlotCount(const GameObject& gameObject) { - std::size_t slotCount = 1u; - if (const auto* meshRenderer = - gameObject.GetComponent<::XCEngine::Components::MeshRendererComponent>(); - meshRenderer != nullptr) { - slotCount = (std::max)(slotCount, meshRenderer->GetMaterialCount()); - } - - if (const auto* meshFilter = - gameObject.GetComponent<::XCEngine::Components::MeshFilterComponent>(); - meshFilter != nullptr) { - ::XCEngine::Resources::Mesh* mesh = meshFilter->GetMesh(); - if (mesh != nullptr && mesh->IsValid()) { - slotCount = (std::max)( - slotCount, - static_cast(mesh->GetMaterials().Size())); - const auto& sections = mesh->GetSections(); - for (std::size_t sectionIndex = 0u; - sectionIndex < sections.Size(); - ++sectionIndex) { - slotCount = (std::max)( - slotCount, - static_cast(sections[sectionIndex].materialID) + 1u); - } - } - } - - return slotCount; -} - -EditorSceneObjectSnapshot BuildObjectSnapshot(const GameObject& gameObject) { - EditorSceneObjectSnapshot snapshot = {}; - snapshot.itemId = MakeEditorGameObjectItemId(gameObject.GetID()); - snapshot.objectId = gameObject.GetID(); - snapshot.displayName = ResolveGameObjectDisplayName(gameObject); - snapshot.visibleMaterialSlotCount = ResolveVisibleMaterialSlotCount(gameObject); - - const std::vector components = - gameObject.GetComponents(); - snapshot.componentTypeNames.reserve(components.size()); - for (const Component* component : components) { - if (component == nullptr) { - continue; - } - - snapshot.componentTypeNames.push_back(component->GetName()); - } - - return snapshot; -} - -Vector3 ResolveViewportSelectionCenterWorldPosition(const GameObject& gameObject) { - const TransformComponent* transform = gameObject.GetTransform(); - if (transform == nullptr) { - return Vector3::Zero(); - } - - if (const auto* meshFilter = - gameObject.GetComponent<::XCEngine::Components::MeshFilterComponent>(); - meshFilter != nullptr) { - ::XCEngine::Resources::Mesh* mesh = meshFilter->GetMesh(); - if (mesh != nullptr && mesh->IsValid()) { - return transform->TransformPoint(mesh->GetBounds().center); - } - } - - return transform->GetPosition(); -} - -std::optional BuildViewportSelectionSnapshot( - const GameObject& gameObject) { - const TransformComponent* transform = gameObject.GetTransform(); - if (transform == nullptr) { - return std::nullopt; - } - - EditorSceneViewportSelectionSnapshot snapshot = {}; - snapshot.valid = true; - snapshot.objectId = gameObject.GetID(); - snapshot.itemId = MakeEditorGameObjectItemId(gameObject.GetID()); - snapshot.displayName = ResolveGameObjectDisplayName(gameObject); - snapshot.worldPosition = transform->GetPosition(); - snapshot.worldRotation = transform->GetRotation(); - snapshot.localScale = transform->GetLocalScale(); - snapshot.centerWorldPosition = ResolveViewportSelectionCenterWorldPosition(gameObject); - return snapshot; -} - -bool PopulateViewportHelperTransform( - const GameObject& gameObject, - EditorSceneViewportHelperSnapshot& snapshot) { - const TransformComponent* transform = gameObject.GetTransform(); - if (transform == nullptr || !gameObject.IsActiveInHierarchy()) { - return false; - } - - snapshot.objectId = gameObject.GetID(); - snapshot.worldPosition = transform->GetPosition(); - snapshot.worldForward = transform->GetForward(); - snapshot.worldRight = transform->GetRight(); - snapshot.worldUp = transform->GetUp(); - return true; -} - -std::optional BuildViewportCameraHelperSnapshot( - const GameObject& gameObject, - const ::XCEngine::Components::CameraComponent& camera) { - if (!camera.IsEnabled()) { - return std::nullopt; - } - - EditorSceneViewportHelperSnapshot snapshot = {}; - if (!PopulateViewportHelperTransform(gameObject, snapshot)) { - return std::nullopt; - } - - snapshot.kind = EditorSceneViewportHelperKind::Camera; - snapshot.cameraProjectionType = - ToEditorCameraProjectionType(camera.GetProjectionType()); - snapshot.cameraFieldOfViewDegrees = camera.GetFieldOfView(); - snapshot.cameraOrthographicSize = camera.GetOrthographicSize(); - snapshot.nearClipPlane = camera.GetNearClipPlane(); - snapshot.farClipPlane = camera.GetFarClipPlane(); - return snapshot; -} - -std::optional BuildViewportLightHelperSnapshot( - const GameObject& gameObject, - const LightComponent& light) { - if (!light.IsEnabled()) { - return std::nullopt; - } - - EditorSceneViewportHelperSnapshot snapshot = {}; - if (!PopulateViewportHelperTransform(gameObject, snapshot)) { - return std::nullopt; - } - - switch (light.GetLightType()) { - case ::XCEngine::Components::LightType::Directional: - snapshot.kind = EditorSceneViewportHelperKind::DirectionalLight; - break; - case ::XCEngine::Components::LightType::Point: - snapshot.kind = EditorSceneViewportHelperKind::PointLight; - break; - case ::XCEngine::Components::LightType::Spot: - snapshot.kind = EditorSceneViewportHelperKind::SpotLight; - break; - default: - return std::nullopt; - } - - snapshot.lightRange = light.GetRange(); - snapshot.lightSpotAngle = light.GetSpotAngle(); - return snapshot; -} - -std::vector BuildViewportHelperSnapshotsForGameObject( - const GameObject& gameObject) { - std::vector snapshots = {}; - - if (const auto* camera = - gameObject.GetComponent<::XCEngine::Components::CameraComponent>(); - camera != nullptr) { - if (const std::optional snapshot = - BuildViewportCameraHelperSnapshot(gameObject, *camera); - snapshot.has_value()) { - snapshots.push_back(*snapshot); - } - } - - if (const LightComponent* light = gameObject.GetComponent(); - light != nullptr) { - if (const std::optional snapshot = - BuildViewportLightHelperSnapshot(gameObject, *light); - snapshot.has_value()) { - snapshots.push_back(*snapshot); - } - } - - return snapshots; -} - -bool MoveGameObjectRelativeToTarget( - SceneManager& sceneManager, - std::string_view itemId, - std::string_view targetItemId, - bool placeAfterTarget) { - Scene* scene = ResolvePrimaryScene(sceneManager); - GameObject* gameObject = FindGameObjectByItemId(sceneManager, itemId); - GameObject* target = FindGameObjectByItemId(sceneManager, targetItemId); - if (scene == nullptr || - gameObject == nullptr || - target == nullptr || - gameObject == target) { - return false; - } - - GameObject* targetParent = target->GetParent(); - if (targetParent != nullptr && - WouldCreateCycle(*gameObject, *targetParent)) { - return false; - } - - const std::vector siblings = - ResolveSiblingSequence(*scene, targetParent); - const std::optional targetIndex = - FindSiblingIndex(siblings, target); - if (!targetIndex.has_value()) { - return false; - } - - std::size_t finalIndex = - targetIndex.value() + (placeAfterTarget ? 1u : 0u); - if (gameObject->GetParent() == targetParent) { - const std::optional sourceIndex = - FindSiblingIndex(siblings, gameObject); - if (sourceIndex.has_value()) { - if ((!placeAfterTarget && sourceIndex.value() < targetIndex.value()) || - (placeAfterTarget && sourceIndex.value() <= targetIndex.value())) { - --finalIndex; - } - - if (finalIndex == sourceIndex.value()) { - return false; - } - } - } - - gameObject->SetParent(targetParent, finalIndex, true); - return true; -} - -class EngineEditorSceneBackend final : public EditorSceneBackend { -public: - EngineEditorSceneBackend( - SceneManager& sceneManager, - ResourceManager& resourceManager) - : m_sceneManager(sceneManager) - , m_resourceManager(resourceManager) {} - - EditorStartupSceneResult EnsureStartupScene( - const std::filesystem::path& projectRoot) override { - EditorStartupSceneResult result = {}; - TraceSceneStartup("EnsureStartupScene begin projectRoot=" + projectRoot.string()); - if (projectRoot.empty()) { - return result; - } - - m_resourceManager.SetResourceRoot(projectRoot.string().c_str()); - TraceSceneStartup("ResourceManager::SetResourceRoot complete"); - - if (Scene* activeScene = ResolvePrimaryScene(m_sceneManager); - activeScene != nullptr) { - result.ready = true; - result.sceneName = activeScene->GetName(); - TraceSceneStartup("EnsureStartupScene reused active scene=" + result.sceneName); - return result; - } - - const std::filesystem::path startupScenePath = - (projectRoot / "Assets" / "Scenes" / "Main.xc").lexically_normal(); - if (std::filesystem::exists(startupScenePath) && - std::filesystem::is_regular_file(startupScenePath)) { - TraceSceneStartup( - "SceneManager::LoadScene begin path=" + startupScenePath.string()); - { - ResourceManager::ScopedDeferredSceneLoad deferredSceneLoad( - m_resourceManager); - m_sceneManager.LoadScene(startupScenePath.string()); - } - TraceSceneStartup("SceneManager::LoadScene end"); - Scene* loadedScene = - m_sceneManager.GetScene(startupScenePath.stem().string()); - if (loadedScene == nullptr) { - loadedScene = ResolvePrimaryScene(m_sceneManager); - } else { - m_sceneManager.SetActiveScene(loadedScene); - } - - if (loadedScene != nullptr) { - result.ready = true; - result.loadedFromDisk = true; - result.scenePath = startupScenePath; - result.sceneName = loadedScene->GetName(); - TraceSceneStartup( - "EnsureStartupScene loaded scene=" + result.sceneName); - return result; - } - } - - if (Scene* scene = m_sceneManager.CreateScene("Main"); - scene != nullptr) { - m_sceneManager.SetActiveScene(scene); - result.ready = true; - result.sceneName = scene->GetName(); - TraceSceneStartup( - "EnsureStartupScene created scene=" + result.sceneName); - } - - TraceSceneStartup( - std::string("EnsureStartupScene end ready=") + - (result.ready ? "1" : "0")); - return result; - } - - EditorSceneHierarchySnapshot BuildHierarchySnapshot() const override { - EditorSceneHierarchySnapshot snapshot = {}; - Scene* scene = ResolvePrimaryScene(m_sceneManager); - if (scene == nullptr) { - return snapshot; - } - - const std::vector roots = scene->GetRootGameObjects(); - snapshot.roots.reserve(roots.size()); - for (const GameObject* root : roots) { - if (root == nullptr) { - continue; - } - - snapshot.roots.push_back(BuildHierarchySnapshotNodeRecursive(*root)); - } - return snapshot; - } - - bool OpenSceneAsset(const std::filesystem::path& scenePath) override { - if (scenePath.empty()) { - return false; - } - - std::error_code errorCode = {}; - if (!std::filesystem::exists(scenePath, errorCode) || - errorCode || - !std::filesystem::is_regular_file(scenePath, errorCode)) { - return false; - } - - { - ResourceManager::ScopedDeferredSceneLoad deferredSceneLoad( - m_resourceManager); - m_sceneManager.LoadScene(scenePath.string()); - } - Scene* loadedScene = m_sceneManager.GetScene(scenePath.stem().string()); - if (loadedScene == nullptr) { - loadedScene = ResolvePrimaryScene(m_sceneManager); - } else { - m_sceneManager.SetActiveScene(loadedScene); - } - - return loadedScene != nullptr; - } - - std::optional GetObjectSnapshot( - std::string_view itemId) const override { - const GameObject* gameObject = FindGameObject(itemId); - return gameObject != nullptr - ? std::optional(BuildObjectSnapshot(*gameObject)) - : std::nullopt; - } - - GameObject* FindGameObject(std::string_view itemId) const { - return FindGameObjectByItemId(m_sceneManager, itemId); - } - - bool AddComponent( - std::string_view itemId, - std::string_view componentTypeName) override { - if (componentTypeName.empty()) { - return false; - } - - GameObject* gameObject = FindGameObject(itemId); - if (gameObject == nullptr) { - return false; - } - - Component* addedComponent = - ComponentFactoryRegistry::Get().CreateComponent( - gameObject, - std::string(componentTypeName)); - return addedComponent != nullptr; - } - - std::vector GetComponents( - std::string_view itemId) const override { - std::vector descriptors = {}; - const GameObject* gameObject = FindGameObject(itemId); - if (gameObject == nullptr) { - return descriptors; - } - - const std::vector components = - gameObject->GetComponents(); - descriptors.reserve(components.size()); - std::unordered_map ordinalsByType = {}; - for (const Component* component : components) { - if (component == nullptr) { - continue; - } - - const std::string typeName = component->GetName(); - const std::size_t ordinal = ordinalsByType[typeName]; - descriptors.push_back(BuildComponentDescriptor(*component, ordinal)); - ordinalsByType[typeName] = ordinal + 1u; - } - - return descriptors; - } - - std::vector BuildViewportIconSnapshots() - const override { - std::vector snapshots = {}; - Scene* scene = ResolvePrimaryScene(m_sceneManager); - if (scene == nullptr) { - return snapshots; - } - - for (auto* camera : scene->FindObjectsOfType<::XCEngine::Components::CameraComponent>()) { - if (camera == nullptr || !camera->IsEnabled()) { - continue; - } - - GameObject* gameObject = camera->GetGameObject(); - TransformComponent* transform = - gameObject != nullptr ? gameObject->GetTransform() : nullptr; - if (gameObject == nullptr || - transform == nullptr || - !gameObject->IsActiveInHierarchy()) { - continue; - } - - EditorSceneViewportIconSnapshot snapshot = {}; - snapshot.entityId = gameObject->GetID(); - snapshot.kind = EditorSceneViewportIconKind::Camera; - snapshot.worldPosition = transform->GetPosition(); - snapshots.push_back(std::move(snapshot)); - } - - for (LightComponent* light : scene->FindObjectsOfType()) { - if (light == nullptr || !light->IsEnabled()) { - continue; - } - - GameObject* gameObject = light->GetGameObject(); - TransformComponent* transform = - gameObject != nullptr ? gameObject->GetTransform() : nullptr; - if (gameObject == nullptr || - transform == nullptr || - !gameObject->IsActiveInHierarchy()) { - continue; - } - - EditorSceneViewportIconSnapshot snapshot = {}; - snapshot.entityId = gameObject->GetID(); - snapshot.kind = light->GetLightType() == ::XCEngine::Components::LightType::Point - ? EditorSceneViewportIconKind::PointLight - : light->GetLightType() == ::XCEngine::Components::LightType::Spot - ? EditorSceneViewportIconKind::SpotLight - : EditorSceneViewportIconKind::DirectionalLight; - snapshot.worldPosition = transform->GetPosition(); - snapshots.push_back(std::move(snapshot)); - } - - return snapshots; - } - - std::optional - BuildViewportSelectionSnapshot(EditorSceneObjectId objectId) const override { - Scene* scene = ResolvePrimaryScene(m_sceneManager); - GameObject* gameObject = - scene != nullptr ? scene->FindByID(objectId) : nullptr; - return gameObject != nullptr - ? ::XCEngine::UI::Editor::App::BuildViewportSelectionSnapshot(*gameObject) - : std::nullopt; - } - - std::vector BuildViewportHelperSnapshots( - EditorSceneObjectId objectId) const override { - Scene* scene = ResolvePrimaryScene(m_sceneManager); - GameObject* gameObject = - scene != nullptr ? scene->FindByID(objectId) : nullptr; - return gameObject != nullptr - ? BuildViewportHelperSnapshotsForGameObject(*gameObject) - : std::vector{}; - } - - bool RemoveComponent( - std::string_view itemId, - std::string_view componentId) override { - GameObject* gameObject = FindGameObject(itemId); - if (gameObject == nullptr) { - return false; - } - - Component* component = ResolveComponent(*gameObject, componentId); - const EditorSceneComponentDescriptor descriptor = - ResolveComponentDescriptor(*gameObject, componentId); - if (!descriptor.IsValid() || - !descriptor.removable || - component == nullptr) { - return false; - } - - return gameObject->RemoveComponent(component); - } - - bool SetTransformLocalPosition( - std::string_view itemId, - std::string_view componentId, - const Vector3& position) override { - TransformComponent* transform = - ResolveTransformComponent(itemId, componentId); - if (transform == nullptr) { - return false; - } - - transform->SetLocalPosition(position); - return true; - } - - bool SetTransformLocalEulerAngles( - std::string_view itemId, - std::string_view componentId, - const Vector3& eulerAngles) override { - TransformComponent* transform = - ResolveTransformComponent(itemId, componentId); - if (transform == nullptr) { - return false; - } - - transform->SetLocalEulerAngles(eulerAngles); - return true; - } - - bool SetTransformLocalScale( - std::string_view itemId, - std::string_view componentId, - const Vector3& scale) override { - TransformComponent* transform = - ResolveTransformComponent(itemId, componentId); - if (transform == nullptr) { - return false; - } - - transform->SetLocalScale(scale); - return true; - } - - bool ApplyComponentMutation( - std::string_view itemId, - const EditorSceneComponentMutation& mutation) override { - if (!mutation.IsValid()) { - return false; - } - - GameObject* gameObject = FindGameObject(itemId); - if (gameObject == nullptr) { - return false; - } - - Component* component = ResolveComponent(*gameObject, mutation.componentId); - if (component == nullptr) { - return false; - } - - if (auto* camera = - dynamic_cast<::XCEngine::Components::CameraComponent*>(component); - camera != nullptr) { - return ApplyCameraComponentMutation(*camera, mutation); - } - if (auto* light = dynamic_cast(component); - light != nullptr) { - return ApplyLightComponentMutation(*light, mutation); - } - if (auto* collider = - dynamic_cast<::XCEngine::Components::BoxColliderComponent*>(component); - collider != nullptr) { - return ApplyBoxColliderComponentMutation(*collider, mutation); - } - if (auto* collider = - dynamic_cast<::XCEngine::Components::CapsuleColliderComponent*>(component); - collider != nullptr) { - return ApplyCapsuleColliderComponentMutation(*collider, mutation); - } - if (auto* collider = - dynamic_cast<::XCEngine::Components::SphereColliderComponent*>(component); - collider != nullptr) { - return ApplySphereColliderComponentMutation(*collider, mutation); - } - if (auto* source = - dynamic_cast<::XCEngine::Components::AudioSourceComponent*>(component); - source != nullptr) { - return ApplyAudioSourceComponentMutation(*source, mutation); - } - if (auto* listener = - dynamic_cast<::XCEngine::Components::AudioListenerComponent*>(component); - listener != nullptr) { - return ApplyAudioListenerComponentMutation(*listener, mutation); - } - if (auto* meshFilter = - dynamic_cast<::XCEngine::Components::MeshFilterComponent*>(component); - meshFilter != nullptr) { - return ApplyMeshFilterComponentMutation(*meshFilter, mutation); - } - if (auto* meshRenderer = - dynamic_cast<::XCEngine::Components::MeshRendererComponent*>(component); - meshRenderer != nullptr) { - return ApplyMeshRendererComponentMutation(*meshRenderer, mutation); - } - if (auto* rigidbody = - dynamic_cast<::XCEngine::Components::RigidbodyComponent*>(component); - rigidbody != nullptr) { - return ApplyRigidbodyComponentMutation(*rigidbody, mutation); - } - if (auto* volumeRenderer = - dynamic_cast<::XCEngine::Components::VolumeRendererComponent*>(component); - volumeRenderer != nullptr) { - return ApplyVolumeRendererComponentMutation(*volumeRenderer, mutation); - } - - return false; - } - - bool QueryWorldTransform( - EditorSceneObjectId objectId, - Vector3& outPosition, - Quaternion& outRotation, - Vector3& outScale) const override { - Scene* scene = ResolvePrimaryScene(m_sceneManager); - GameObject* gameObject = - scene != nullptr ? scene->FindByID(objectId) : nullptr; - const TransformComponent* transform = - gameObject != nullptr ? gameObject->GetTransform() : nullptr; - if (transform == nullptr) { - return false; - } - - outPosition = transform->GetPosition(); - outRotation = transform->GetRotation(); - outScale = transform->GetScale(); - return true; - } - - bool SetWorldTransform( - EditorSceneObjectId objectId, - const Vector3& position, - const Quaternion& rotation, - const Vector3& scale) override { - TransformComponent* transform = ResolveTransformComponent(objectId); - if (transform == nullptr) { - return false; - } - - transform->SetPosition(position); - transform->SetRotation(rotation); - transform->SetScale(scale); - return true; - } - - bool SetWorldPositionRotation( - EditorSceneObjectId objectId, - const Vector3& position, - const Quaternion& rotation) override { - TransformComponent* transform = ResolveTransformComponent(objectId); - if (transform == nullptr) { - return false; - } - - transform->SetPosition(position); - transform->SetRotation(rotation); - return true; - } - - bool SetObjectLocalScale( - EditorSceneObjectId objectId, - const Vector3& localScale) override { - TransformComponent* transform = ResolveTransformComponent(objectId); - if (transform == nullptr) { - return false; - } - - transform->SetLocalScale(localScale); - return true; - } - - bool RenameGameObject( - std::string_view itemId, - std::string_view newName) override { - GameObject* gameObject = FindGameObject(itemId); - if (gameObject == nullptr) { - return false; - } - - gameObject->SetName(std::string(newName)); - return true; - } - - bool DeleteGameObject(std::string_view itemId) override { - Scene* scene = ResolvePrimaryScene(m_sceneManager); - GameObject* gameObject = FindGameObject(itemId); - if (scene == nullptr || gameObject == nullptr) { - return false; - } - - scene->DestroyGameObject(gameObject); - return true; - } - - std::string DuplicateGameObject(std::string_view itemId) override { - Scene* scene = ResolvePrimaryScene(m_sceneManager); - GameObject* gameObject = FindGameObject(itemId); - if (scene == nullptr || gameObject == nullptr) { - return {}; - } - - const ClipboardNode clipboard = CopyGameObjectRecursive(*gameObject); - GameObject* duplicate = - PasteGameObjectRecursive(*scene, clipboard, gameObject->GetParent()); - return duplicate != nullptr - ? MakeEditorGameObjectItemId(duplicate->GetID()) - : std::string(); - } - - bool ReparentGameObject( - std::string_view itemId, - std::string_view parentItemId) override { - GameObject* gameObject = FindGameObject(itemId); - GameObject* newParent = FindGameObject(parentItemId); - if (gameObject == nullptr || newParent == nullptr || - gameObject == newParent || - gameObject->GetParent() == newParent || - WouldCreateCycle(*gameObject, *newParent)) { - return false; - } - - gameObject->SetParent(newParent); - return true; - } - - bool MoveGameObjectBefore( - std::string_view itemId, - std::string_view targetItemId) override { - return MoveGameObjectRelativeToTarget( - m_sceneManager, - itemId, - targetItemId, - false); - } - - bool MoveGameObjectAfter( - std::string_view itemId, - std::string_view targetItemId) override { - return MoveGameObjectRelativeToTarget( - m_sceneManager, - itemId, - targetItemId, - true); - } - - bool MoveGameObjectToRoot(std::string_view itemId) override { - GameObject* gameObject = FindGameObject(itemId); - if (gameObject == nullptr || gameObject->GetParent() == nullptr) { - return false; - } - - gameObject->SetParent(nullptr); - return true; - } - -private: - TransformComponent* ResolveTransformComponent(EditorSceneObjectId objectId) const { - Scene* scene = ResolvePrimaryScene(m_sceneManager); - GameObject* gameObject = - scene != nullptr ? scene->FindByID(objectId) : nullptr; - return gameObject != nullptr ? gameObject->GetTransform() : nullptr; - } - - TransformComponent* ResolveTransformComponent( - std::string_view itemId, - std::string_view componentId) const { - GameObject* gameObject = FindGameObject(itemId); - if (gameObject == nullptr) { - return nullptr; - } - - return dynamic_cast( - ResolveComponent(*gameObject, componentId)); - } - - SceneManager& m_sceneManager; - ResourceManager& m_resourceManager; -}; - -class EngineEditorServices final : public EditorEngineServices { +class EngineEditorServices final + : public EditorSceneBackendFactory + , public SceneViewportEngineBridge + , public GameViewportEngineBridge + , public EditorShaderProvider + , public EditorEngineLifecycle { public: void UpdateAsyncLoads() override { - ResourceManager::Get().UpdateAsyncLoads(); + ::XCEngine::Resources::ResourceManager::Get().UpdateAsyncLoads(); } void Shutdown() override { - m_sceneViewportCamera = nullptr; - m_sceneViewportCameraObject.reset(); - ResourceManager::Get().Shutdown(); + m_sceneViewportBridge.Shutdown(); + ::XCEngine::Resources::ResourceManager::Get().Shutdown(); } std::unique_ptr CreateSceneBackend() override { return CreateEngineEditorSceneBackend( - SceneManager::Get(), - ResourceManager::Get()); + ::XCEngine::Components::SceneManager::Get(), + ::XCEngine::Resources::ResourceManager::Get()); } - ::XCEngine::Resources::ResourceHandle LoadShader( - const ::XCEngine::Containers::String& path) override { - return ResourceManager::Get().Load(path); + ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader> + LoadShader(const ::XCEngine::Containers::String& path) override { + return ::XCEngine::Resources::ResourceManager::Get() + .Load<::XCEngine::Resources::Shader>(path); } SceneViewportFramePlanBuildStatus BuildSceneViewportFramePlan( @@ -2235,153 +49,103 @@ public: const ::XCEngine::Rendering::RenderContext& renderContext, const ::XCEngine::Rendering::RenderSurface& surface, ::XCEngine::Rendering::CameraFramePlan& outFramePlan) override { - outFramePlan = {}; - if (!request.IsValid()) { - return SceneViewportFramePlanBuildStatus::InvalidRequest; - } - - Scene* scene = ResolvePrimaryScene(SceneManager::Get()); - if (scene == nullptr) { - return SceneViewportFramePlanBuildStatus::NoActiveScene; - } - - if (!EnsureSceneViewportCamera()) { - return SceneViewportFramePlanBuildStatus::Failed; - } - - ConfigureSceneViewportCamera(request.camera); - const std::vector<::XCEngine::Rendering::CameraFramePlan> framePlans = - m_sceneViewportRenderer.BuildFramePlans( - *scene, - m_sceneViewportCamera, - renderContext, - surface); - if (framePlans.empty() || !framePlans.front().IsValid()) { - return SceneViewportFramePlanBuildStatus::Failed; - } - - outFramePlan = framePlans.front(); - return SceneViewportFramePlanBuildStatus::Success; + return m_sceneViewportBridge.BuildFramePlan( + request, + renderContext, + surface, + outFramePlan); } bool RenderSceneViewportFramePlan( const ::XCEngine::Rendering::CameraFramePlan& framePlan) override { - return framePlan.IsValid() && - m_sceneViewportRenderer.Render(framePlan); + return m_sceneViewportBridge.RenderFramePlan(framePlan); + } + + GameViewportFramePlanBuildStatus BuildGameViewportFramePlans( + const ::XCEngine::Rendering::RenderContext& renderContext, + const ::XCEngine::Rendering::RenderSurface& surface, + std::vector<::XCEngine::Rendering::CameraStackFramePlan>& outFramePlans) + override { + return m_gameViewportBridge.BuildFramePlans( + renderContext, + surface, + outFramePlans); + } + + bool RenderGameViewportFramePlans( + const std::vector<::XCEngine::Rendering::CameraStackFramePlan>& framePlans) + override { + return m_gameViewportBridge.RenderFramePlans(framePlans); } bool TryResolveActiveSceneRenderObjectId( - RenderObjectId renderObjectId, + ::XCEngine::Rendering::RenderObjectId renderObjectId, EditorSceneObjectId& outRuntimeObjectId) const override { - outRuntimeObjectId = kInvalidEditorSceneObjectId; - - std::uint64_t runtimeObjectId = kInvalidEditorSceneObjectId; - if (!RenderObjectIdRegistry::Get().TryResolveRuntimeObjectId( + return m_sceneViewportBridge.TryResolveActiveSceneRenderObjectId( renderObjectId, - runtimeObjectId)) { - return false; - } - - Scene* scene = ResolvePrimaryScene(SceneManager::Get()); - GameObject* gameObject = - scene != nullptr - ? scene->FindByID(static_cast(runtimeObjectId)) - : nullptr; - if (gameObject == nullptr) { - return false; - } - - outRuntimeObjectId = gameObject->GetID(); - return true; + outRuntimeObjectId); } private: - bool EnsureSceneViewportCamera() { - if (m_sceneViewportCameraObject != nullptr && - m_sceneViewportCamera != nullptr) { - return true; - } - - m_sceneViewportCamera = nullptr; - m_sceneViewportCameraObject = - std::make_unique("EditorSceneViewportCamera"); - m_sceneViewportCamera = - m_sceneViewportCameraObject->AddComponent< - ::XCEngine::Components::CameraComponent>(); - if (m_sceneViewportCamera == nullptr) { - m_sceneViewportCameraObject.reset(); - return false; - } - - m_sceneViewportCamera->SetPrimary(false); - m_sceneViewportCamera->SetProjectionType( - ::XCEngine::Components::CameraProjectionType::Perspective); - m_sceneViewportCamera->SetFieldOfView(60.0f); - m_sceneViewportCamera->SetNearClipPlane(0.03f); - m_sceneViewportCamera->SetFarClipPlane(2000.0f); - return true; - } - - void ConfigureSceneViewportCamera(const EditorSceneCameraSnapshot& snapshot) { - if (!EnsureSceneViewportCamera() || - m_sceneViewportCameraObject == nullptr || - m_sceneViewportCamera == nullptr) { - return; - } - - TransformComponent* transform = - m_sceneViewportCameraObject->GetTransform(); - if (transform == nullptr) { - return; - } - - Vector3 forward = snapshot.forward.SqrMagnitude() > - ::XCEngine::Math::EPSILON - ? snapshot.forward.Normalized() - : Vector3::Forward(); - Vector3 up = snapshot.up.SqrMagnitude() > - ::XCEngine::Math::EPSILON - ? snapshot.up.Normalized() - : Vector3::Up(); - if (Vector3::Cross(forward, up).SqrMagnitude() <= - ::XCEngine::Math::EPSILON) { - up = std::abs(Vector3::Dot(forward, Vector3::Up())) < 0.999f - ? Vector3::Up() - : Vector3::Right(); - } - - transform->SetPosition(snapshot.position); - transform->SetRotation(Quaternion::LookRotation(forward, up)); - - const float nearClipPlane = (std::max)(snapshot.nearClipPlane, 0.001f); - const float farClipPlane = - (std::max)(snapshot.farClipPlane, nearClipPlane + 0.001f); - m_sceneViewportCamera->SetPrimary(false); - m_sceneViewportCamera->SetProjectionType( - ::XCEngine::Components::CameraProjectionType::Perspective); - m_sceneViewportCamera->SetFieldOfView( - std::clamp(snapshot.verticalFovDegrees, 1.0f, 179.0f)); - m_sceneViewportCamera->SetNearClipPlane(nearClipPlane); - m_sceneViewportCamera->SetFarClipPlane(farClipPlane); - } - - std::unique_ptr m_sceneViewportCameraObject = {}; - ::XCEngine::Components::CameraComponent* m_sceneViewportCamera = nullptr; - SceneRenderer m_sceneViewportRenderer = {}; + EngineGameViewportBridge m_gameViewportBridge = {}; + EngineSceneViewportBridge m_sceneViewportBridge = {}; }; } // namespace -std::unique_ptr CreateEngineEditorSceneBackend( - SceneManager& sceneManager, - ResourceManager& resourceManager) { - return std::make_unique( - sceneManager, - resourceManager); +class EngineEditorComposition::Impl final { +public: + EditorSceneBackendFactory& GetSceneBackendFactory() { + return m_services; + } + + SceneViewportEngineBridge& GetSceneViewportBridge() { + return m_services; + } + + GameViewportEngineBridge& GetGameViewportBridge() { + return m_services; + } + + EditorShaderProvider& GetShaderProvider() { + return m_services; + } + + EditorEngineLifecycle& GetLifecycle() { + return m_services; + } + +private: + EngineEditorServices m_services = {}; +}; + +EngineEditorComposition::EngineEditorComposition() + : m_impl(std::make_unique()) {} + +EngineEditorComposition::~EngineEditorComposition() = default; + +EditorSceneBackendFactory& EngineEditorComposition::GetSceneBackendFactory() { + return m_impl->GetSceneBackendFactory(); } -std::unique_ptr CreateEngineEditorServices() { - return std::make_unique(); +SceneViewportEngineBridge& EngineEditorComposition::GetSceneViewportBridge() { + return m_impl->GetSceneViewportBridge(); +} + +GameViewportEngineBridge& EngineEditorComposition::GetGameViewportBridge() { + return m_impl->GetGameViewportBridge(); +} + +EditorShaderProvider& EngineEditorComposition::GetShaderProvider() { + return m_impl->GetShaderProvider(); +} + +EditorEngineLifecycle& EngineEditorComposition::GetLifecycle() { + return m_impl->GetLifecycle(); +} + +std::unique_ptr CreateEngineEditorComposition() { + return std::make_unique(); } } // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Services/Engine/EngineEditorServices.h b/editor/app/Services/Engine/EngineEditorServices.h index 4b591c17..77112dff 100644 --- a/editor/app/Services/Engine/EngineEditorServices.h +++ b/editor/app/Services/Engine/EngineEditorServices.h @@ -1,11 +1,36 @@ #pragma once -#include "Engine/EditorEngineServices.h" +#include "Engine/EditorEngineLifecycle.h" +#include "Engine/EditorSceneBackendFactory.h" +#include "Engine/EditorShaderProvider.h" +#include "Engine/GameViewportEngineBridge.h" +#include "Engine/SceneViewportEngineBridge.h" #include namespace XCEngine::UI::Editor::App { -std::unique_ptr CreateEngineEditorServices(); +class EngineEditorComposition { +public: + EngineEditorComposition(); + ~EngineEditorComposition(); + + EngineEditorComposition(const EngineEditorComposition&) = delete; + EngineEditorComposition& operator=(const EngineEditorComposition&) = delete; + EngineEditorComposition(EngineEditorComposition&&) = delete; + EngineEditorComposition& operator=(EngineEditorComposition&&) = delete; + + EditorSceneBackendFactory& GetSceneBackendFactory(); + SceneViewportEngineBridge& GetSceneViewportBridge(); + GameViewportEngineBridge& GetGameViewportBridge(); + EditorShaderProvider& GetShaderProvider(); + EditorEngineLifecycle& GetLifecycle(); + +private: + class Impl; + std::unique_ptr m_impl = {}; +}; + +std::unique_ptr CreateEngineEditorComposition(); } // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Services/Engine/EngineGameViewportBridge.cpp b/editor/app/Services/Engine/EngineGameViewportBridge.cpp new file mode 100644 index 00000000..4aa8dcc7 --- /dev/null +++ b/editor/app/Services/Engine/EngineGameViewportBridge.cpp @@ -0,0 +1,66 @@ +#include "Engine/EngineGameViewportBridge.h" + +#include +#include + +#include +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +using ::XCEngine::Components::Scene; +using ::XCEngine::Components::SceneManager; + +Scene* ResolvePrimaryScene(SceneManager& sceneManager) { + if (Scene* activeScene = sceneManager.GetActiveScene(); + activeScene != nullptr) { + return activeScene; + } + + const std::vector scenes = sceneManager.GetAllScenes(); + if (!scenes.empty() && scenes.front() != nullptr) { + sceneManager.SetActiveScene(scenes.front()); + return scenes.front(); + } + + return nullptr; +} + +} // namespace + +GameViewportFramePlanBuildStatus EngineGameViewportBridge::BuildFramePlans( + const ::XCEngine::Rendering::RenderContext& renderContext, + const ::XCEngine::Rendering::RenderSurface& surface, + std::vector<::XCEngine::Rendering::CameraStackFramePlan>& outFramePlans) { + outFramePlans.clear(); + + Scene* scene = ResolvePrimaryScene(SceneManager::Get()); + if (scene == nullptr) { + return GameViewportFramePlanBuildStatus::NoActiveScene; + } + + outFramePlans = m_gameViewportRenderer.BuildStackFramePlans( + *scene, + nullptr, + renderContext, + surface); + outFramePlans.erase( + std::remove_if( + outFramePlans.begin(), + outFramePlans.end(), + [](const auto& plan) { return !plan.IsValid(); }), + outFramePlans.end()); + return outFramePlans.empty() + ? GameViewportFramePlanBuildStatus::NoRenderableCamera + : GameViewportFramePlanBuildStatus::Success; +} + +bool EngineGameViewportBridge::RenderFramePlans( + const std::vector<::XCEngine::Rendering::CameraStackFramePlan>& framePlans) { + return !framePlans.empty() && + m_gameViewportRenderer.Render(framePlans); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Services/Engine/EngineGameViewportBridge.h b/editor/app/Services/Engine/EngineGameViewportBridge.h new file mode 100644 index 00000000..442154b3 --- /dev/null +++ b/editor/app/Services/Engine/EngineGameViewportBridge.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Engine/GameViewportEngineBridge.h" + +#include + +namespace XCEngine::UI::Editor::App { + +class EngineGameViewportBridge { +public: + GameViewportFramePlanBuildStatus BuildFramePlans( + const ::XCEngine::Rendering::RenderContext& renderContext, + const ::XCEngine::Rendering::RenderSurface& surface, + std::vector<::XCEngine::Rendering::CameraStackFramePlan>& outFramePlans); + + bool RenderFramePlans( + const std::vector<::XCEngine::Rendering::CameraStackFramePlan>& framePlans); + +private: + ::XCEngine::Rendering::SceneRenderer m_gameViewportRenderer = {}; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Services/Engine/EngineSceneViewportBridge.cpp b/editor/app/Services/Engine/EngineSceneViewportBridge.cpp new file mode 100644 index 00000000..957cce58 --- /dev/null +++ b/editor/app/Services/Engine/EngineSceneViewportBridge.cpp @@ -0,0 +1,187 @@ +#include "Engine/EngineSceneViewportBridge.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +using ::XCEngine::Components::GameObject; +using ::XCEngine::Components::Scene; +using ::XCEngine::Components::SceneManager; +using ::XCEngine::Components::TransformComponent; +using ::XCEngine::Math::Quaternion; +using ::XCEngine::Math::Vector3; +using ::XCEngine::Rendering::RenderObjectId; +using ::XCEngine::Rendering::RenderObjectIdRegistry; + +Scene* ResolvePrimaryScene(SceneManager& sceneManager) { + if (Scene* activeScene = sceneManager.GetActiveScene(); + activeScene != nullptr) { + return activeScene; + } + + const std::vector scenes = sceneManager.GetAllScenes(); + if (!scenes.empty() && scenes.front() != nullptr) { + sceneManager.SetActiveScene(scenes.front()); + return scenes.front(); + } + + return nullptr; +} + +} // namespace + +EngineSceneViewportBridge::~EngineSceneViewportBridge() = default; + +void EngineSceneViewportBridge::Shutdown() { + m_sceneViewportCamera = nullptr; + m_sceneViewportCameraObject.reset(); +} + +SceneViewportFramePlanBuildStatus EngineSceneViewportBridge::BuildFramePlan( + const SceneViewportRenderRequest& request, + const ::XCEngine::Rendering::RenderContext& renderContext, + const ::XCEngine::Rendering::RenderSurface& surface, + ::XCEngine::Rendering::CameraFramePlan& outFramePlan) { + outFramePlan = {}; + if (!request.IsValid()) { + return SceneViewportFramePlanBuildStatus::InvalidRequest; + } + + Scene* scene = ResolvePrimaryScene(SceneManager::Get()); + if (scene == nullptr) { + return SceneViewportFramePlanBuildStatus::NoActiveScene; + } + + if (!EnsureSceneViewportCamera()) { + return SceneViewportFramePlanBuildStatus::Failed; + } + + ConfigureSceneViewportCamera(request.camera); + const std::vector<::XCEngine::Rendering::CameraFramePlan> framePlans = + m_sceneViewportRenderer.BuildFramePlans( + *scene, + m_sceneViewportCamera, + renderContext, + surface); + if (framePlans.empty() || !framePlans.front().IsValid()) { + return SceneViewportFramePlanBuildStatus::Failed; + } + + outFramePlan = framePlans.front(); + return SceneViewportFramePlanBuildStatus::Success; +} + +bool EngineSceneViewportBridge::RenderFramePlan( + const ::XCEngine::Rendering::CameraFramePlan& framePlan) { + return framePlan.IsValid() && + m_sceneViewportRenderer.Render(framePlan); +} + +bool EngineSceneViewportBridge::TryResolveActiveSceneRenderObjectId( + RenderObjectId renderObjectId, + EditorSceneObjectId& outRuntimeObjectId) const { + outRuntimeObjectId = kInvalidEditorSceneObjectId; + + std::uint64_t runtimeObjectId = kInvalidEditorSceneObjectId; + if (!RenderObjectIdRegistry::Get().TryResolveRuntimeObjectId( + renderObjectId, + runtimeObjectId)) { + return false; + } + + Scene* scene = ResolvePrimaryScene(SceneManager::Get()); + GameObject* gameObject = + scene != nullptr + ? scene->FindByID(static_cast(runtimeObjectId)) + : nullptr; + if (gameObject == nullptr) { + return false; + } + + outRuntimeObjectId = gameObject->GetID(); + return true; +} + +bool EngineSceneViewportBridge::EnsureSceneViewportCamera() { + if (m_sceneViewportCameraObject != nullptr && + m_sceneViewportCamera != nullptr) { + return true; + } + + m_sceneViewportCamera = nullptr; + m_sceneViewportCameraObject = + std::make_unique("EditorSceneViewportCamera"); + m_sceneViewportCamera = + m_sceneViewportCameraObject->AddComponent< + ::XCEngine::Components::CameraComponent>(); + if (m_sceneViewportCamera == nullptr) { + m_sceneViewportCameraObject.reset(); + return false; + } + + m_sceneViewportCamera->SetPrimary(false); + m_sceneViewportCamera->SetProjectionType( + ::XCEngine::Components::CameraProjectionType::Perspective); + m_sceneViewportCamera->SetFieldOfView(60.0f); + m_sceneViewportCamera->SetNearClipPlane(0.03f); + m_sceneViewportCamera->SetFarClipPlane(2000.0f); + return true; +} + +void EngineSceneViewportBridge::ConfigureSceneViewportCamera( + const EditorSceneCameraSnapshot& snapshot) { + if (!EnsureSceneViewportCamera() || + m_sceneViewportCameraObject == nullptr || + m_sceneViewportCamera == nullptr) { + return; + } + + TransformComponent* transform = + m_sceneViewportCameraObject->GetTransform(); + if (transform == nullptr) { + return; + } + + Vector3 forward = snapshot.forward.SqrMagnitude() > + ::XCEngine::Math::EPSILON + ? snapshot.forward.Normalized() + : Vector3::Forward(); + Vector3 up = snapshot.up.SqrMagnitude() > + ::XCEngine::Math::EPSILON + ? snapshot.up.Normalized() + : Vector3::Up(); + if (Vector3::Cross(forward, up).SqrMagnitude() <= + ::XCEngine::Math::EPSILON) { + up = std::abs(Vector3::Dot(forward, Vector3::Up())) < 0.999f + ? Vector3::Up() + : Vector3::Right(); + } + + transform->SetPosition(snapshot.position); + transform->SetRotation(Quaternion::LookRotation(forward, up)); + + const float nearClipPlane = (std::max)(snapshot.nearClipPlane, 0.001f); + const float farClipPlane = + (std::max)(snapshot.farClipPlane, nearClipPlane + 0.001f); + m_sceneViewportCamera->SetPrimary(false); + m_sceneViewportCamera->SetProjectionType( + ::XCEngine::Components::CameraProjectionType::Perspective); + m_sceneViewportCamera->SetFieldOfView( + std::clamp(snapshot.verticalFovDegrees, 1.0f, 179.0f)); + m_sceneViewportCamera->SetNearClipPlane(nearClipPlane); + m_sceneViewportCamera->SetFarClipPlane(farClipPlane); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Services/Engine/EngineSceneViewportBridge.h b/editor/app/Services/Engine/EngineSceneViewportBridge.h new file mode 100644 index 00000000..76f981dc --- /dev/null +++ b/editor/app/Services/Engine/EngineSceneViewportBridge.h @@ -0,0 +1,43 @@ +#pragma once + +#include "Engine/SceneViewportEngineBridge.h" + +#include + +namespace XCEngine::Components { +class CameraComponent; +class GameObject; +} + +namespace XCEngine::UI::Editor::App { + +class EngineSceneViewportBridge { +public: + ~EngineSceneViewportBridge(); + + void Shutdown(); + + SceneViewportFramePlanBuildStatus BuildFramePlan( + const SceneViewportRenderRequest& request, + const ::XCEngine::Rendering::RenderContext& renderContext, + const ::XCEngine::Rendering::RenderSurface& surface, + ::XCEngine::Rendering::CameraFramePlan& outFramePlan); + + bool RenderFramePlan( + const ::XCEngine::Rendering::CameraFramePlan& framePlan); + + bool TryResolveActiveSceneRenderObjectId( + ::XCEngine::Rendering::RenderObjectId renderObjectId, + EditorSceneObjectId& outRuntimeObjectId) const; + +private: + bool EnsureSceneViewportCamera(); + void ConfigureSceneViewportCamera( + const EditorSceneCameraSnapshot& snapshot); + + std::unique_ptr<::XCEngine::Components::GameObject> m_sceneViewportCameraObject = {}; + ::XCEngine::Components::CameraComponent* m_sceneViewportCamera = nullptr; + ::XCEngine::Rendering::SceneRenderer m_sceneViewportRenderer = {}; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Services/Scene/EditorSceneRuntime.cpp b/editor/app/Services/Scene/EditorSceneRuntime.cpp index c987cddb..12d7fc2b 100644 --- a/editor/app/Services/Scene/EditorSceneRuntime.cpp +++ b/editor/app/Services/Scene/EditorSceneRuntime.cpp @@ -784,9 +784,16 @@ bool EditorSceneRuntime::ApplyTransformToolWorldPreview( EditorSceneObjectId targetId, const Vector3& position, const Quaternion& rotation) { - if (!m_toolState.dragState.active || - targetId == kInvalidEditorSceneObjectId || - targetId != m_toolState.dragState.initialTransform.targetId) { + if (targetId == kInvalidEditorSceneObjectId) { + return false; + } + + const EditorSceneObjectId activeTargetId = + m_toolState.dragState.active + ? m_toolState.dragState.initialTransform.targetId + : GetSelectedObjectId().value_or(kInvalidEditorSceneObjectId); + if (activeTargetId == kInvalidEditorSceneObjectId || + targetId != activeTargetId) { return false; } @@ -803,9 +810,16 @@ bool EditorSceneRuntime::ApplyTransformToolWorldPreview( bool EditorSceneRuntime::ApplyTransformToolLocalScalePreview( EditorSceneObjectId targetId, const Vector3& localScale) { - if (!m_toolState.dragState.active || - targetId == kInvalidEditorSceneObjectId || - targetId != m_toolState.dragState.initialTransform.targetId) { + if (targetId == kInvalidEditorSceneObjectId) { + return false; + } + + const EditorSceneObjectId activeTargetId = + m_toolState.dragState.active + ? m_toolState.dragState.initialTransform.targetId + : GetSelectedObjectId().value_or(kInvalidEditorSceneObjectId); + if (activeTargetId == kInvalidEditorSceneObjectId || + targetId != activeTargetId) { return false; } diff --git a/editor/app/Services/Scene/EngineEditorSceneBackend.cpp b/editor/app/Services/Scene/EngineEditorSceneBackend.cpp new file mode 100644 index 00000000..f8f2aaa0 --- /dev/null +++ b/editor/app/Services/Scene/EngineEditorSceneBackend.cpp @@ -0,0 +1,2213 @@ +#include "Scene/EngineEditorSceneBackend.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +using ::XCEngine::Components::Component; +using ::XCEngine::Components::ComponentFactoryRegistry; +using ::XCEngine::Components::GameObject; +using ::XCEngine::Components::LightComponent; +using ::XCEngine::Components::Scene; +using ::XCEngine::Components::SceneManager; +using ::XCEngine::Components::TransformComponent; +using ::XCEngine::Math::Quaternion; +using ::XCEngine::Math::Vector3; +using ::XCEngine::Resources::ResourceManager; + +constexpr char kComponentIdSeparator = '#'; + +void TraceSceneStartup(std::string message) { + ::XCEngine::UI::Editor::AppendUIEditorRuntimeTrace("startup", std::move(message)); +} + +struct ClipboardNode { + std::string name = {}; + std::string transformPayload = {}; + std::vector> components = {}; + std::vector children = {}; +}; + +Scene* ResolvePrimaryScene(SceneManager& sceneManager) { + if (Scene* activeScene = sceneManager.GetActiveScene(); + activeScene != nullptr) { + return activeScene; + } + + const std::vector scenes = sceneManager.GetAllScenes(); + if (!scenes.empty() && scenes.front() != nullptr) { + sceneManager.SetActiveScene(scenes.front()); + return scenes.front(); + } + + return nullptr; +} + +std::pair SerializeComponent( + const Component* component) { + std::ostringstream payload = {}; + component->Serialize(payload); + return { component->GetName(), payload.str() }; +} + +ClipboardNode CopyGameObjectRecursive(const GameObject& gameObject) { + ClipboardNode node = {}; + node.name = gameObject.GetName(); + + if (const TransformComponent* transform = gameObject.GetTransform(); + transform != nullptr) { + std::ostringstream payload = {}; + transform->Serialize(payload); + node.transformPayload = payload.str(); + } + + const auto components = gameObject.GetComponents(); + for (const Component* component : components) { + if (component == nullptr || component == gameObject.GetTransform()) { + continue; + } + + node.components.push_back(SerializeComponent(component)); + } + + for (GameObject* child : gameObject.GetChildren()) { + if (child == nullptr) { + continue; + } + + node.children.push_back(CopyGameObjectRecursive(*child)); + } + + return node; +} + +GameObject* PasteGameObjectRecursive( + Scene& scene, + const ClipboardNode& node, + GameObject* parent) { + GameObject* gameObject = scene.CreateGameObject(node.name, parent); + if (gameObject == nullptr) { + return nullptr; + } + + if (!node.transformPayload.empty()) { + if (TransformComponent* transform = gameObject->GetTransform(); + transform != nullptr) { + std::istringstream transformStream(node.transformPayload); + transform->Deserialize(transformStream); + } + } + + for (const auto& componentData : node.components) { + Component* component = + ComponentFactoryRegistry::Get().CreateComponent( + gameObject, + componentData.first); + if (component == nullptr || componentData.second.empty()) { + continue; + } + + std::istringstream payloadStream(componentData.second); + component->Deserialize(payloadStream); + } + + for (const ClipboardNode& child : node.children) { + PasteGameObjectRecursive(scene, child, gameObject); + } + + return gameObject; +} + +bool WouldCreateCycle( + const GameObject& source, + const GameObject& targetParent) { + const GameObject* current = &targetParent; + while (current != nullptr) { + if (current == &source) { + return true; + } + current = current->GetParent(); + } + + return false; +} + +std::vector ResolveSiblingSequence( + Scene& scene, + GameObject* parent) { + return parent != nullptr + ? parent->GetChildren() + : scene.GetRootGameObjects(); +} + +std::optional FindSiblingIndex( + const std::vector& siblings, + const GameObject* gameObject) { + if (gameObject == nullptr) { + return std::nullopt; + } + + const auto it = + std::find(siblings.begin(), siblings.end(), gameObject); + if (it == siblings.end()) { + return std::nullopt; + } + + return static_cast(std::distance(siblings.begin(), it)); +} + +GameObject* FindGameObjectByItemId( + SceneManager& sceneManager, + std::string_view itemId) { + Scene* scene = ResolvePrimaryScene(sceneManager); + const std::optional gameObjectId = + ParseEditorGameObjectItemId(itemId); + if (scene == nullptr || !gameObjectId.has_value()) { + return nullptr; + } + + return scene->FindByID(gameObjectId.value()); +} + +EditorSceneHierarchyNode BuildHierarchySnapshotNodeRecursive( + const GameObject& gameObject) { + EditorSceneHierarchyNode node = {}; + node.itemId = MakeEditorGameObjectItemId(gameObject.GetID()); + node.displayName = gameObject.GetName().empty() + ? std::string("GameObject") + : gameObject.GetName(); + node.children.reserve(gameObject.GetChildCount()); + for (std::size_t childIndex = 0u; + childIndex < gameObject.GetChildCount(); + ++childIndex) { + const GameObject* child = gameObject.GetChild(childIndex); + if (child == nullptr) { + continue; + } + + node.children.push_back(BuildHierarchySnapshotNodeRecursive(*child)); + } + return node; +} + +std::string BuildEditorComponentId( + std::string_view typeName, + std::size_t ordinal) { + std::string componentId(typeName); + componentId.push_back(kComponentIdSeparator); + componentId += std::to_string(ordinal); + return componentId; +} + +bool ParseEditorComponentId( + std::string_view componentId, + std::string& outTypeName, + std::size_t& outOrdinal) { + const std::size_t separatorIndex = componentId.find(kComponentIdSeparator); + if (separatorIndex == std::string_view::npos || + separatorIndex == 0u || + separatorIndex + 1u >= componentId.size()) { + return false; + } + + outTypeName = std::string(componentId.substr(0u, separatorIndex)); + std::size_t ordinal = 0u; + const std::string_view ordinalText = + componentId.substr(separatorIndex + 1u); + const char* first = ordinalText.data(); + const char* last = ordinalText.data() + ordinalText.size(); + const std::from_chars_result result = + std::from_chars(first, last, ordinal); + if (result.ec != std::errc() || result.ptr != last) { + outTypeName.clear(); + return false; + } + + outOrdinal = ordinal; + return true; +} + +std::string ResolveGameObjectDisplayName(const GameObject& gameObject) { + return gameObject.GetName().empty() + ? std::string("GameObject") + : gameObject.GetName(); +} + +EditorSceneCameraProjectionType ToEditorCameraProjectionType( + ::XCEngine::Components::CameraProjectionType projectionType) { + switch (projectionType) { + case ::XCEngine::Components::CameraProjectionType::Orthographic: + return EditorSceneCameraProjectionType::Orthographic; + case ::XCEngine::Components::CameraProjectionType::Perspective: + default: + return EditorSceneCameraProjectionType::Perspective; + } +} + +::XCEngine::Components::CameraProjectionType ToEngineCameraProjectionType( + EditorSceneCameraProjectionType projectionType) { + switch (projectionType) { + case EditorSceneCameraProjectionType::Orthographic: + return ::XCEngine::Components::CameraProjectionType::Orthographic; + case EditorSceneCameraProjectionType::Perspective: + default: + return ::XCEngine::Components::CameraProjectionType::Perspective; + } +} + +EditorSceneLightType ToEditorLightType(::XCEngine::Components::LightType lightType) { + switch (lightType) { + case ::XCEngine::Components::LightType::Point: + return EditorSceneLightType::Point; + case ::XCEngine::Components::LightType::Spot: + return EditorSceneLightType::Spot; + case ::XCEngine::Components::LightType::Directional: + default: + return EditorSceneLightType::Directional; + } +} + +::XCEngine::Components::LightType ToEngineLightType(EditorSceneLightType lightType) { + switch (lightType) { + case EditorSceneLightType::Point: + return ::XCEngine::Components::LightType::Point; + case EditorSceneLightType::Spot: + return ::XCEngine::Components::LightType::Spot; + case EditorSceneLightType::Directional: + default: + return ::XCEngine::Components::LightType::Directional; + } +} + +EditorSceneColliderAxis ToEditorColliderAxis( + ::XCEngine::Components::ColliderAxis axis) { + switch (axis) { + case ::XCEngine::Components::ColliderAxis::X: + return EditorSceneColliderAxis::X; + case ::XCEngine::Components::ColliderAxis::Z: + return EditorSceneColliderAxis::Z; + case ::XCEngine::Components::ColliderAxis::Y: + default: + return EditorSceneColliderAxis::Y; + } +} + +::XCEngine::Components::ColliderAxis ToEngineColliderAxis( + EditorSceneColliderAxis axis) { + switch (axis) { + case EditorSceneColliderAxis::X: + return ::XCEngine::Components::ColliderAxis::X; + case EditorSceneColliderAxis::Z: + return ::XCEngine::Components::ColliderAxis::Z; + case EditorSceneColliderAxis::Y: + default: + return ::XCEngine::Components::ColliderAxis::Y; + } +} + +EditorScenePhysicsBodyType ToEditorPhysicsBodyType( + ::XCEngine::Physics::PhysicsBodyType bodyType) { + switch (bodyType) { + case ::XCEngine::Physics::PhysicsBodyType::Static: + return EditorScenePhysicsBodyType::Static; + case ::XCEngine::Physics::PhysicsBodyType::Kinematic: + return EditorScenePhysicsBodyType::Kinematic; + case ::XCEngine::Physics::PhysicsBodyType::Dynamic: + default: + return EditorScenePhysicsBodyType::Dynamic; + } +} + +::XCEngine::Physics::PhysicsBodyType ToEnginePhysicsBodyType( + EditorScenePhysicsBodyType bodyType) { + switch (bodyType) { + case EditorScenePhysicsBodyType::Static: + return ::XCEngine::Physics::PhysicsBodyType::Static; + case EditorScenePhysicsBodyType::Kinematic: + return ::XCEngine::Physics::PhysicsBodyType::Kinematic; + case EditorScenePhysicsBodyType::Dynamic: + default: + return ::XCEngine::Physics::PhysicsBodyType::Dynamic; + } +} + +template +class ComponentViewAdapterBase : public TView { +public: + ComponentViewAdapterBase(TComponent& component, const char* typeName) + : m_component(component) + , m_typeName(typeName) {} + + std::string_view GetTypeName() const override { + return m_typeName; + } + +protected: + TComponent& MutableComponent() const { + return m_component; + } + + const TComponent& ComponentRef() const { + return m_component; + } + +private: + TComponent& m_component; + const char* m_typeName = ""; +}; + +class GenericComponentViewAdapter final : public EditorSceneComponentView { +public: + explicit GenericComponentViewAdapter(std::string typeName) + : m_typeName(std::move(typeName)) {} + + std::string_view GetTypeName() const override { + return m_typeName; + } + +private: + std::string m_typeName = {}; +}; + +class TransformComponentViewAdapter final + : public ComponentViewAdapterBase { +public: + explicit TransformComponentViewAdapter(TransformComponent& component) + : ComponentViewAdapterBase(component, "Transform") {} + + Vector3 GetLocalPosition() const override { return ComponentRef().GetLocalPosition(); } + Vector3 GetLocalEulerAngles() const override { return ComponentRef().GetLocalEulerAngles(); } + Vector3 GetLocalScale() const override { return ComponentRef().GetLocalScale(); } +}; + +class CameraComponentViewAdapter final + : public ComponentViewAdapterBase< + EditorSceneCameraComponentView, + ::XCEngine::Components::CameraComponent> { +public: + explicit CameraComponentViewAdapter(::XCEngine::Components::CameraComponent& component) + : ComponentViewAdapterBase(component, "Camera") {} + + EditorSceneCameraProjectionType GetProjectionType() const override { + return ToEditorCameraProjectionType(ComponentRef().GetProjectionType()); + } + + void SetProjectionType(EditorSceneCameraProjectionType projectionType) { + MutableComponent().SetProjectionType(ToEngineCameraProjectionType(projectionType)); + } + + float GetFieldOfView() const override { return ComponentRef().GetFieldOfView(); } + void SetFieldOfView(float fieldOfView) { MutableComponent().SetFieldOfView(fieldOfView); } + float GetOrthographicSize() const override { return ComponentRef().GetOrthographicSize(); } + void SetOrthographicSize(float orthographicSize) { MutableComponent().SetOrthographicSize(orthographicSize); } + float GetNearClipPlane() const override { return ComponentRef().GetNearClipPlane(); } + void SetNearClipPlane(float nearClipPlane) { MutableComponent().SetNearClipPlane(nearClipPlane); } + float GetFarClipPlane() const override { return ComponentRef().GetFarClipPlane(); } + void SetFarClipPlane(float farClipPlane) { MutableComponent().SetFarClipPlane(farClipPlane); } + float GetDepth() const override { return ComponentRef().GetDepth(); } + void SetDepth(float depth) { MutableComponent().SetDepth(depth); } + bool IsPrimary() const override { return ComponentRef().IsPrimary(); } + void SetPrimary(bool primary) { MutableComponent().SetPrimary(primary); } + bool IsSkyboxEnabled() const override { return ComponentRef().IsSkyboxEnabled(); } + void SetSkyboxEnabled(bool enabled) { MutableComponent().SetSkyboxEnabled(enabled); } + std::string GetSkyboxMaterialPath() const override { return ComponentRef().GetSkyboxMaterialPath(); } + void SetSkyboxMaterialPath(std::string_view assetId) { MutableComponent().SetSkyboxMaterialPath(std::string(assetId)); } + ::XCEngine::Math::Color GetSkyboxTopColor() const override { return ComponentRef().GetSkyboxTopColor(); } + void SetSkyboxTopColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetSkyboxTopColor(color); } + ::XCEngine::Math::Color GetSkyboxHorizonColor() const override { return ComponentRef().GetSkyboxHorizonColor(); } + void SetSkyboxHorizonColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetSkyboxHorizonColor(color); } + ::XCEngine::Math::Color GetSkyboxBottomColor() const override { return ComponentRef().GetSkyboxBottomColor(); } + void SetSkyboxBottomColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetSkyboxBottomColor(color); } + ::XCEngine::Math::Color GetClearColor() const override { return ComponentRef().GetClearColor(); } + void SetClearColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetClearColor(color); } +}; + +class LightComponentViewAdapter final + : public ComponentViewAdapterBase { +public: + explicit LightComponentViewAdapter(LightComponent& component) + : ComponentViewAdapterBase(component, "Light") {} + + EditorSceneLightType GetLightType() const override { + return ToEditorLightType(ComponentRef().GetLightType()); + } + + void SetLightType(EditorSceneLightType lightType) { + MutableComponent().SetLightType(ToEngineLightType(lightType)); + } + + ::XCEngine::Math::Color GetColor() const override { return ComponentRef().GetColor(); } + void SetColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetColor(color); } + float GetIntensity() const override { return ComponentRef().GetIntensity(); } + void SetIntensity(float intensity) { MutableComponent().SetIntensity(intensity); } + float GetRange() const override { return ComponentRef().GetRange(); } + void SetRange(float range) { MutableComponent().SetRange(range); } + float GetSpotAngle() const override { return ComponentRef().GetSpotAngle(); } + void SetSpotAngle(float spotAngle) { MutableComponent().SetSpotAngle(spotAngle); } + bool GetCastsShadows() const override { return ComponentRef().GetCastsShadows(); } + void SetCastsShadows(bool castsShadows) { MutableComponent().SetCastsShadows(castsShadows); } + bool GetOverridesDirectionalShadowSettings() const override { return ComponentRef().GetOverridesDirectionalShadowSettings(); } + void SetOverridesDirectionalShadowSettings(bool overrides) { MutableComponent().SetOverridesDirectionalShadowSettings(overrides); } + float GetDirectionalShadowReceiverDepthBias() const override { return ComponentRef().GetDirectionalShadowReceiverDepthBias(); } + void SetDirectionalShadowReceiverDepthBias(float value) { MutableComponent().SetDirectionalShadowReceiverDepthBias(value); } + float GetDirectionalShadowNormalBiasScale() const override { return ComponentRef().GetDirectionalShadowNormalBiasScale(); } + void SetDirectionalShadowNormalBiasScale(float value) { MutableComponent().SetDirectionalShadowNormalBiasScale(value); } + float GetDirectionalShadowStrength() const override { return ComponentRef().GetDirectionalShadowStrength(); } + void SetDirectionalShadowStrength(float value) { MutableComponent().SetDirectionalShadowStrength(value); } + float GetDirectionalShadowDepthBiasFactor() const override { return ComponentRef().GetDirectionalShadowDepthBiasFactor(); } + void SetDirectionalShadowDepthBiasFactor(float value) { MutableComponent().SetDirectionalShadowDepthBiasFactor(value); } + int GetDirectionalShadowDepthBiasUnits() const override { return ComponentRef().GetDirectionalShadowDepthBiasUnits(); } + void SetDirectionalShadowDepthBiasUnits(int value) { MutableComponent().SetDirectionalShadowDepthBiasUnits(value); } +}; + +template +class ColliderComponentViewAdapterBase : public ComponentViewAdapterBase { +public: + ColliderComponentViewAdapterBase(TColliderComponent& component, const char* typeName) + : ComponentViewAdapterBase(component, typeName) {} + + bool IsTrigger() const override { return this->ComponentRef().IsTrigger(); } + void SetTrigger(bool trigger) { this->MutableComponent().SetTrigger(trigger); } + Vector3 GetCenter() const override { return this->ComponentRef().GetCenter(); } + void SetCenter(const Vector3& center) { this->MutableComponent().SetCenter(center); } + float GetStaticFriction() const override { return this->ComponentRef().GetStaticFriction(); } + void SetStaticFriction(float value) { this->MutableComponent().SetStaticFriction(value); } + float GetDynamicFriction() const override { return this->ComponentRef().GetDynamicFriction(); } + void SetDynamicFriction(float value) { this->MutableComponent().SetDynamicFriction(value); } + float GetRestitution() const override { return this->ComponentRef().GetRestitution(); } + void SetRestitution(float value) { this->MutableComponent().SetRestitution(value); } +}; + +class BoxColliderComponentViewAdapter final + : public ColliderComponentViewAdapterBase< + EditorSceneBoxColliderComponentView, + ::XCEngine::Components::BoxColliderComponent> { +public: + explicit BoxColliderComponentViewAdapter(::XCEngine::Components::BoxColliderComponent& component) + : ColliderComponentViewAdapterBase(component, "BoxCollider") {} + + Vector3 GetSize() const override { return this->ComponentRef().GetSize(); } + void SetSize(const Vector3& size) { this->MutableComponent().SetSize(size); } +}; + +class CapsuleColliderComponentViewAdapter final + : public ColliderComponentViewAdapterBase< + EditorSceneCapsuleColliderComponentView, + ::XCEngine::Components::CapsuleColliderComponent> { +public: + explicit CapsuleColliderComponentViewAdapter( + ::XCEngine::Components::CapsuleColliderComponent& component) + : ColliderComponentViewAdapterBase(component, "CapsuleCollider") {} + + float GetRadius() const override { return this->ComponentRef().GetRadius(); } + void SetRadius(float radius) { this->MutableComponent().SetRadius(radius); } + float GetHeight() const override { return this->ComponentRef().GetHeight(); } + void SetHeight(float height) { this->MutableComponent().SetHeight(height); } + EditorSceneColliderAxis GetAxis() const override { + return ToEditorColliderAxis(this->ComponentRef().GetAxis()); + } + void SetAxis(EditorSceneColliderAxis axis) { + this->MutableComponent().SetAxis(ToEngineColliderAxis(axis)); + } +}; + +class SphereColliderComponentViewAdapter final + : public ColliderComponentViewAdapterBase< + EditorSceneSphereColliderComponentView, + ::XCEngine::Components::SphereColliderComponent> { +public: + explicit SphereColliderComponentViewAdapter(::XCEngine::Components::SphereColliderComponent& component) + : ColliderComponentViewAdapterBase(component, "SphereCollider") {} + + float GetRadius() const override { return this->ComponentRef().GetRadius(); } + void SetRadius(float radius) { this->MutableComponent().SetRadius(radius); } +}; + +class AudioSourceComponentViewAdapter final + : public ComponentViewAdapterBase< + EditorSceneAudioSourceComponentView, + ::XCEngine::Components::AudioSourceComponent> { +public: + explicit AudioSourceComponentViewAdapter(::XCEngine::Components::AudioSourceComponent& component) + : ComponentViewAdapterBase(component, "AudioSource") {} + + std::string GetClipPath() const override { return ComponentRef().GetClipPath(); } + void ClearClip() { MutableComponent().ClearClip(); } + void SetClipPath(std::string_view assetId) { MutableComponent().SetClipPath(std::string(assetId)); } + float GetVolume() const override { return ComponentRef().GetVolume(); } + void SetVolume(float volume) { MutableComponent().SetVolume(volume); } + float GetPitch() const override { return ComponentRef().GetPitch(); } + void SetPitch(float pitch) { MutableComponent().SetPitch(pitch); } + float GetPan() const override { return ComponentRef().GetPan(); } + void SetPan(float pan) { MutableComponent().SetPan(pan); } + bool IsLooping() const override { return ComponentRef().IsLooping(); } + void SetLooping(bool looping) { MutableComponent().SetLooping(looping); } + bool IsSpatialize() const override { return ComponentRef().IsSpatialize(); } + void SetSpatialize(bool spatialize) { MutableComponent().SetSpatialize(spatialize); } + ::XCEngine::Audio::Audio3DParams Get3DParams() const override { return ComponentRef().Get3DParams(); } + void Set3DParams(const ::XCEngine::Audio::Audio3DParams& params) { MutableComponent().Set3DParams(params); } + bool IsHRTFEnabled() const override { return ComponentRef().IsHRTFEnabled(); } + void SetHRTFEnabled(bool enabled) { MutableComponent().SetHRTFEnabled(enabled); } + float GetHRTFCrossFeed() const override { return ComponentRef().GetHRTFCrossFeed(); } + void SetHRTFCrossFeed(float value) { MutableComponent().SetHRTFCrossFeed(value); } + std::uint32_t GetHRTFQuality() const override { return ComponentRef().GetHRTFQuality(); } + void SetHRTFQuality(std::uint32_t quality) { MutableComponent().SetHRTFQuality(quality); } +}; + +class AudioListenerComponentViewAdapter final + : public ComponentViewAdapterBase< + EditorSceneAudioListenerComponentView, + ::XCEngine::Components::AudioListenerComponent> { +public: + explicit AudioListenerComponentViewAdapter( + ::XCEngine::Components::AudioListenerComponent& component) + : ComponentViewAdapterBase(component, "AudioListener") {} + + float GetMasterVolume() const override { return ComponentRef().GetMasterVolume(); } + void SetMasterVolume(float value) { MutableComponent().SetMasterVolume(value); } + bool IsMute() const override { return ComponentRef().IsMute(); } + void SetMute(bool mute) { MutableComponent().SetMute(mute); } + float GetDopplerLevel() const override { return ComponentRef().GetDopplerLevel(); } + void SetDopplerLevel(float value) { MutableComponent().SetDopplerLevel(value); } + float GetSpeedOfSound() const override { return ComponentRef().GetSpeedOfSound(); } + void SetSpeedOfSound(float value) { MutableComponent().SetSpeedOfSound(value); } + float GetReverbLevel() const override { return ComponentRef().GetReverbLevel(); } + void SetReverbLevel(float value) { MutableComponent().SetReverbLevel(value); } +}; + +class MeshFilterComponentViewAdapter final + : public ComponentViewAdapterBase< + EditorSceneMeshFilterComponentView, + ::XCEngine::Components::MeshFilterComponent> { +public: + explicit MeshFilterComponentViewAdapter(::XCEngine::Components::MeshFilterComponent& component) + : ComponentViewAdapterBase(component, "MeshFilter") {} + + std::string GetMeshPath() const override { return ComponentRef().GetMeshPath(); } + void ClearMesh() { MutableComponent().ClearMesh(); } + void SetMeshPath(std::string_view assetId) { MutableComponent().SetMeshPath(std::string(assetId)); } +}; + +class MeshRendererComponentViewAdapter final + : public ComponentViewAdapterBase< + EditorSceneMeshRendererComponentView, + ::XCEngine::Components::MeshRendererComponent> { +public: + explicit MeshRendererComponentViewAdapter( + ::XCEngine::Components::MeshRendererComponent& component) + : ComponentViewAdapterBase(component, "MeshRenderer") {} + + bool GetCastShadows() const override { return ComponentRef().GetCastShadows(); } + void SetCastShadows(bool value) { MutableComponent().SetCastShadows(value); } + bool GetReceiveShadows() const override { return ComponentRef().GetReceiveShadows(); } + void SetReceiveShadows(bool value) { MutableComponent().SetReceiveShadows(value); } + std::uint32_t GetRenderLayer() const override { return ComponentRef().GetRenderLayer(); } + void SetRenderLayer(std::uint32_t value) { MutableComponent().SetRenderLayer(value); } + std::size_t GetMaterialCount() const override { return ComponentRef().GetMaterialCount(); } + std::string GetMaterialPath(std::size_t slotIndex) const override { return ComponentRef().GetMaterialPath(slotIndex); } + void SetMaterialPath(std::size_t slotIndex, std::string_view assetId) { + MutableComponent().SetMaterialPath(slotIndex, std::string(assetId)); + } +}; + +class RigidbodyComponentViewAdapter final + : public ComponentViewAdapterBase< + EditorSceneRigidbodyComponentView, + ::XCEngine::Components::RigidbodyComponent> { +public: + explicit RigidbodyComponentViewAdapter(::XCEngine::Components::RigidbodyComponent& component) + : ComponentViewAdapterBase(component, "Rigidbody") {} + + EditorScenePhysicsBodyType GetBodyType() const override { + return ToEditorPhysicsBodyType(ComponentRef().GetBodyType()); + } + void SetBodyType(EditorScenePhysicsBodyType bodyType) { + MutableComponent().SetBodyType(ToEnginePhysicsBodyType(bodyType)); + } + float GetMass() const override { return ComponentRef().GetMass(); } + void SetMass(float mass) { MutableComponent().SetMass(mass); } + float GetLinearDamping() const override { return ComponentRef().GetLinearDamping(); } + void SetLinearDamping(float damping) { MutableComponent().SetLinearDamping(damping); } + float GetAngularDamping() const override { return ComponentRef().GetAngularDamping(); } + void SetAngularDamping(float damping) { MutableComponent().SetAngularDamping(damping); } + bool GetUseGravity() const override { return ComponentRef().GetUseGravity(); } + void SetUseGravity(bool useGravity) { MutableComponent().SetUseGravity(useGravity); } + bool GetEnableCCD() const override { return ComponentRef().GetEnableCCD(); } + void SetEnableCCD(bool enableCCD) { MutableComponent().SetEnableCCD(enableCCD); } +}; + +class VolumeRendererComponentViewAdapter final + : public ComponentViewAdapterBase< + EditorSceneVolumeRendererComponentView, + ::XCEngine::Components::VolumeRendererComponent> { +public: + explicit VolumeRendererComponentViewAdapter( + ::XCEngine::Components::VolumeRendererComponent& component) + : ComponentViewAdapterBase(component, "VolumeRenderer") {} + + std::string GetVolumeFieldPath() const override { return ComponentRef().GetVolumeFieldPath(); } + void ClearVolumeField() { MutableComponent().ClearVolumeField(); } + void SetVolumeFieldPath(std::string_view assetId) { MutableComponent().SetVolumeFieldPath(std::string(assetId)); } + std::string GetMaterialPath() const override { return ComponentRef().GetMaterialPath(); } + void ClearMaterial() { MutableComponent().ClearMaterial(); } + void SetMaterialPath(std::string_view assetId) { MutableComponent().SetMaterialPath(std::string(assetId)); } + bool GetCastShadows() const override { return ComponentRef().GetCastShadows(); } + void SetCastShadows(bool value) { MutableComponent().SetCastShadows(value); } + bool GetReceiveShadows() const override { return ComponentRef().GetReceiveShadows(); } + void SetReceiveShadows(bool value) { MutableComponent().SetReceiveShadows(value); } +}; + +std::shared_ptr CreateComponentView(Component& component) { + if (auto* transform = dynamic_cast(&component); transform != nullptr) { + return std::make_shared(*transform); + } + if (auto* camera = dynamic_cast<::XCEngine::Components::CameraComponent*>(&component); + camera != nullptr) { + return std::make_shared(*camera); + } + if (auto* light = dynamic_cast(&component); light != nullptr) { + return std::make_shared(*light); + } + if (auto* collider = dynamic_cast<::XCEngine::Components::BoxColliderComponent*>(&component); + collider != nullptr) { + return std::make_shared(*collider); + } + if (auto* collider = + dynamic_cast<::XCEngine::Components::CapsuleColliderComponent*>(&component); + collider != nullptr) { + return std::make_shared(*collider); + } + if (auto* collider = + dynamic_cast<::XCEngine::Components::SphereColliderComponent*>(&component); + collider != nullptr) { + return std::make_shared(*collider); + } + if (auto* source = dynamic_cast<::XCEngine::Components::AudioSourceComponent*>(&component); + source != nullptr) { + return std::make_shared(*source); + } + if (auto* listener = + dynamic_cast<::XCEngine::Components::AudioListenerComponent*>(&component); + listener != nullptr) { + return std::make_shared(*listener); + } + if (auto* meshFilter = + dynamic_cast<::XCEngine::Components::MeshFilterComponent*>(&component); + meshFilter != nullptr) { + return std::make_shared(*meshFilter); + } + if (auto* meshRenderer = + dynamic_cast<::XCEngine::Components::MeshRendererComponent*>(&component); + meshRenderer != nullptr) { + return std::make_shared(*meshRenderer); + } + if (auto* rigidbody = + dynamic_cast<::XCEngine::Components::RigidbodyComponent*>(&component); + rigidbody != nullptr) { + return std::make_shared(*rigidbody); + } + if (auto* volumeRenderer = + dynamic_cast<::XCEngine::Components::VolumeRendererComponent*>(&component); + volumeRenderer != nullptr) { + return std::make_shared(*volumeRenderer); + } + + return std::make_shared(component.GetName()); +} + +template +const TValue* TryGetMutationValue(const EditorSceneComponentMutation& mutation) { + return std::get_if(&mutation.value); +} + +template +bool TryApplyMutationValue( + const EditorSceneComponentMutation& mutation, + std::string_view propertyPath, + TApply&& apply) { + if (mutation.propertyPath != propertyPath) { + return false; + } + + const TValue* value = TryGetMutationValue(mutation); + if (value == nullptr) { + return false; + } + + apply(*value); + return true; +} + +bool TryParseMaterialSlotProperty( + std::string_view propertyPath, + std::size_t& outSlotIndex) { + constexpr std::string_view kPrefix = "material_"; + if (propertyPath.size() <= kPrefix.size() || + propertyPath.substr(0u, kPrefix.size()) != kPrefix) { + return false; + } + + const std::string_view slotText = propertyPath.substr(kPrefix.size()); + std::size_t slotIndex = 0u; + const char* first = slotText.data(); + const char* last = slotText.data() + slotText.size(); + const std::from_chars_result result = + std::from_chars(first, last, slotIndex); + if (result.ec != std::errc() || result.ptr != last) { + return false; + } + + outSlotIndex = slotIndex; + return true; +} + +template +bool ApplyColliderBaseMutation( + TColliderViewAdapter& view, + const EditorSceneComponentMutation& mutation) { + if (TryApplyMutationValue( + mutation, + "is_trigger", + [&view](bool value) { view.SetTrigger(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "center", + [&view](const Vector3& value) { view.SetCenter(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "static_friction", + [&view](float value) { view.SetStaticFriction(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "dynamic_friction", + [&view](float value) { view.SetDynamicFriction(value); })) { + return true; + } + return TryApplyMutationValue( + mutation, + "restitution", + [&view](float value) { view.SetRestitution(value); }); +} + +bool ApplyCameraComponentMutation( + ::XCEngine::Components::CameraComponent& component, + const EditorSceneComponentMutation& mutation) { + CameraComponentViewAdapter view(component); + if (TryApplyMutationValue( + mutation, + "projection", + [&view](EditorSceneCameraProjectionType value) { + view.SetProjectionType(value); + })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "field_of_view", + [&view](float value) { view.SetFieldOfView(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "orthographic_size", + [&view](float value) { view.SetOrthographicSize(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "near_clip", + [&view](float value) { view.SetNearClipPlane(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "far_clip", + [&view](float value) { view.SetFarClipPlane(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "depth", + [&view](float value) { view.SetDepth(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "primary", + [&view](bool value) { view.SetPrimary(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "skybox", + [&view](bool value) { view.SetSkyboxEnabled(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "skybox_material", + [&view](const std::string& value) { + view.SetSkyboxMaterialPath(value); + })) { + return true; + } + if (TryApplyMutationValue<::XCEngine::Math::Color>( + mutation, + "skybox_top", + [&view](const ::XCEngine::Math::Color& value) { + view.SetSkyboxTopColor(value); + })) { + return true; + } + if (TryApplyMutationValue<::XCEngine::Math::Color>( + mutation, + "skybox_horizon", + [&view](const ::XCEngine::Math::Color& value) { + view.SetSkyboxHorizonColor(value); + })) { + return true; + } + if (TryApplyMutationValue<::XCEngine::Math::Color>( + mutation, + "skybox_bottom", + [&view](const ::XCEngine::Math::Color& value) { + view.SetSkyboxBottomColor(value); + })) { + return true; + } + return TryApplyMutationValue<::XCEngine::Math::Color>( + mutation, + "clear_color", + [&view](const ::XCEngine::Math::Color& value) { + view.SetClearColor(value); + }); +} + +bool ApplyLightComponentMutation( + LightComponent& component, + const EditorSceneComponentMutation& mutation) { + LightComponentViewAdapter view(component); + if (TryApplyMutationValue( + mutation, + "type", + [&view](EditorSceneLightType value) { view.SetLightType(value); })) { + return true; + } + if (TryApplyMutationValue<::XCEngine::Math::Color>( + mutation, + "color", + [&view](const ::XCEngine::Math::Color& value) { + view.SetColor(value); + })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "intensity", + [&view](float value) { view.SetIntensity(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "range", + [&view](float value) { view.SetRange(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "spot_angle", + [&view](float value) { view.SetSpotAngle(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "cast_shadows", + [&view](bool value) { view.SetCastsShadows(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "override_shadow_params", + [&view](bool value) { + view.SetOverridesDirectionalShadowSettings(value); + })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "receiver_depth_bias", + [&view](float value) { + view.SetDirectionalShadowReceiverDepthBias(value); + })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "normal_bias_scale", + [&view](float value) { + view.SetDirectionalShadowNormalBiasScale(value); + })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "shadow_strength", + [&view](float value) { + view.SetDirectionalShadowStrength(value); + })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "depth_bias_factor", + [&view](float value) { + view.SetDirectionalShadowDepthBiasFactor(value); + })) { + return true; + } + return TryApplyMutationValue( + mutation, + "depth_bias_units", + [&view](std::int32_t value) { + view.SetDirectionalShadowDepthBiasUnits(value); + }); +} + +bool ApplyBoxColliderComponentMutation( + ::XCEngine::Components::BoxColliderComponent& component, + const EditorSceneComponentMutation& mutation) { + BoxColliderComponentViewAdapter view(component); + if (ApplyColliderBaseMutation(view, mutation)) { + return true; + } + return TryApplyMutationValue( + mutation, + "size", + [&view](const Vector3& value) { view.SetSize(value); }); +} + +bool ApplyCapsuleColliderComponentMutation( + ::XCEngine::Components::CapsuleColliderComponent& component, + const EditorSceneComponentMutation& mutation) { + CapsuleColliderComponentViewAdapter view(component); + if (ApplyColliderBaseMutation(view, mutation)) { + return true; + } + if (TryApplyMutationValue( + mutation, + "radius", + [&view](float value) { view.SetRadius(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "height", + [&view](float value) { view.SetHeight(value); })) { + return true; + } + return TryApplyMutationValue( + mutation, + "axis", + [&view](EditorSceneColliderAxis value) { view.SetAxis(value); }); +} + +bool ApplySphereColliderComponentMutation( + ::XCEngine::Components::SphereColliderComponent& component, + const EditorSceneComponentMutation& mutation) { + SphereColliderComponentViewAdapter view(component); + if (ApplyColliderBaseMutation(view, mutation)) { + return true; + } + return TryApplyMutationValue( + mutation, + "radius", + [&view](float value) { view.SetRadius(value); }); +} + +bool TryApplyAudioSource3DParamMutation( + AudioSourceComponentViewAdapter& view, + const EditorSceneComponentMutation& mutation, + std::string_view propertyPath, + float ::XCEngine::Audio::Audio3DParams::* member) { + return TryApplyMutationValue( + mutation, + propertyPath, + [&view, member](float value) { + ::XCEngine::Audio::Audio3DParams params = view.Get3DParams(); + params.*member = value; + view.Set3DParams(params); + }); +} + +bool ApplyAudioSourceComponentMutation( + ::XCEngine::Components::AudioSourceComponent& component, + const EditorSceneComponentMutation& mutation) { + AudioSourceComponentViewAdapter view(component); + if (TryApplyMutationValue( + mutation, + "clip", + [&view](const std::string& value) { + if (value.empty()) { + view.ClearClip(); + } else { + view.SetClipPath(value); + } + })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "volume", + [&view](float value) { view.SetVolume(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "pitch", + [&view](float value) { view.SetPitch(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "pan", + [&view](float value) { view.SetPan(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "looping", + [&view](bool value) { view.SetLooping(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "spatialize", + [&view](bool value) { view.SetSpatialize(value); })) { + return true; + } + if (TryApplyAudioSource3DParamMutation( + view, + mutation, + "min_distance", + &::XCEngine::Audio::Audio3DParams::minDistance)) { + return true; + } + if (TryApplyAudioSource3DParamMutation( + view, + mutation, + "max_distance", + &::XCEngine::Audio::Audio3DParams::maxDistance)) { + return true; + } + if (TryApplyAudioSource3DParamMutation( + view, + mutation, + "pan_level", + &::XCEngine::Audio::Audio3DParams::panLevel)) { + return true; + } + if (TryApplyAudioSource3DParamMutation( + view, + mutation, + "spread", + &::XCEngine::Audio::Audio3DParams::spread)) { + return true; + } + if (TryApplyAudioSource3DParamMutation( + view, + mutation, + "reverb_send", + &::XCEngine::Audio::Audio3DParams::reverbZoneMix)) { + return true; + } + if (TryApplyAudioSource3DParamMutation( + view, + mutation, + "doppler_level", + &::XCEngine::Audio::Audio3DParams::dopplerLevel)) { + return true; + } + if (TryApplyMutationValue( + mutation, + "use_hrtf", + [&view](bool value) { view.SetHRTFEnabled(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "hrtf_crossfeed", + [&view](float value) { view.SetHRTFCrossFeed(value); })) { + return true; + } + return TryApplyMutationValue( + mutation, + "hrtf_quality", + [&view](std::uint32_t value) { view.SetHRTFQuality(value); }); +} + +bool ApplyAudioListenerComponentMutation( + ::XCEngine::Components::AudioListenerComponent& component, + const EditorSceneComponentMutation& mutation) { + AudioListenerComponentViewAdapter view(component); + if (TryApplyMutationValue( + mutation, + "master_volume", + [&view](float value) { view.SetMasterVolume(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "mute", + [&view](bool value) { view.SetMute(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "doppler_level", + [&view](float value) { view.SetDopplerLevel(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "speed_of_sound", + [&view](float value) { view.SetSpeedOfSound(value); })) { + return true; + } + return TryApplyMutationValue( + mutation, + "reverb_level", + [&view](float value) { view.SetReverbLevel(value); }); +} + +bool ApplyMeshFilterComponentMutation( + ::XCEngine::Components::MeshFilterComponent& component, + const EditorSceneComponentMutation& mutation) { + MeshFilterComponentViewAdapter view(component); + return TryApplyMutationValue( + mutation, + "mesh", + [&view](const std::string& value) { + if (value.empty()) { + view.ClearMesh(); + } else { + view.SetMeshPath(value); + } + }); +} + +bool ApplyMeshRendererComponentMutation( + ::XCEngine::Components::MeshRendererComponent& component, + const EditorSceneComponentMutation& mutation) { + MeshRendererComponentViewAdapter view(component); + if (TryApplyMutationValue( + mutation, + "cast_shadows", + [&view](bool value) { view.SetCastShadows(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "receive_shadows", + [&view](bool value) { view.SetReceiveShadows(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "render_layer", + [&view](std::uint32_t value) { view.SetRenderLayer(value); })) { + return true; + } + + std::size_t slotIndex = 0u; + if (!TryParseMaterialSlotProperty(mutation.propertyPath, slotIndex)) { + return false; + } + + const std::string* value = TryGetMutationValue(mutation); + if (value == nullptr) { + return false; + } + + view.SetMaterialPath(slotIndex, *value); + return true; +} + +bool ApplyRigidbodyComponentMutation( + ::XCEngine::Components::RigidbodyComponent& component, + const EditorSceneComponentMutation& mutation) { + RigidbodyComponentViewAdapter view(component); + if (TryApplyMutationValue( + mutation, + "body_type", + [&view](EditorScenePhysicsBodyType value) { + view.SetBodyType(value); + })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "mass", + [&view](float value) { view.SetMass(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "linear_damping", + [&view](float value) { view.SetLinearDamping(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "angular_damping", + [&view](float value) { view.SetAngularDamping(value); })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "use_gravity", + [&view](bool value) { view.SetUseGravity(value); })) { + return true; + } + return TryApplyMutationValue( + mutation, + "enable_ccd", + [&view](bool value) { view.SetEnableCCD(value); }); +} + +bool ApplyVolumeRendererComponentMutation( + ::XCEngine::Components::VolumeRendererComponent& component, + const EditorSceneComponentMutation& mutation) { + VolumeRendererComponentViewAdapter view(component); + if (TryApplyMutationValue( + mutation, + "volume_field", + [&view](const std::string& value) { + if (value.empty()) { + view.ClearVolumeField(); + } else { + view.SetVolumeFieldPath(value); + } + })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "material", + [&view](const std::string& value) { + if (value.empty()) { + view.ClearMaterial(); + } else { + view.SetMaterialPath(value); + } + })) { + return true; + } + if (TryApplyMutationValue( + mutation, + "cast_shadows", + [&view](bool value) { view.SetCastShadows(value); })) { + return true; + } + return TryApplyMutationValue( + mutation, + "receive_shadows", + [&view](bool value) { view.SetReceiveShadows(value); }); +} + +Component* ResolveComponent( + const GameObject& gameObject, + std::string_view componentId) { + std::string typeName = {}; + std::size_t ordinal = 0u; + if (!ParseEditorComponentId(componentId, typeName, ordinal)) { + return nullptr; + } + + std::size_t currentOrdinal = 0u; + for (const Component* component : gameObject.GetComponents()) { + if (component == nullptr || component->GetName() != typeName) { + continue; + } + + if (currentOrdinal == ordinal) { + return const_cast(component); + } + + ++currentOrdinal; + } + + return nullptr; +} + +EditorSceneComponentDescriptor BuildComponentDescriptor( + const Component& component, + std::size_t ordinal) { + EditorSceneComponentDescriptor descriptor = {}; + descriptor.typeName = component.GetName(); + descriptor.componentId = + BuildEditorComponentId(descriptor.typeName, ordinal); + descriptor.view = CreateComponentView(const_cast(component)); + descriptor.removable = + component.GetGameObject() != nullptr && + component.GetGameObject()->GetTransform() != &component; + return descriptor; +} + +EditorSceneComponentDescriptor ResolveComponentDescriptor( + const GameObject& gameObject, + std::string_view componentId) { + Component* component = ResolveComponent(gameObject, componentId); + if (component == nullptr) { + return {}; + } + + std::string typeName = {}; + std::size_t ordinal = 0u; + if (!ParseEditorComponentId(componentId, typeName, ordinal)) { + return {}; + } + + return BuildComponentDescriptor(*component, ordinal); +} + +std::size_t ResolveVisibleMaterialSlotCount(const GameObject& gameObject) { + std::size_t slotCount = 1u; + if (const auto* meshRenderer = + gameObject.GetComponent<::XCEngine::Components::MeshRendererComponent>(); + meshRenderer != nullptr) { + slotCount = (std::max)(slotCount, meshRenderer->GetMaterialCount()); + } + + if (const auto* meshFilter = + gameObject.GetComponent<::XCEngine::Components::MeshFilterComponent>(); + meshFilter != nullptr) { + ::XCEngine::Resources::Mesh* mesh = meshFilter->GetMesh(); + if (mesh != nullptr && mesh->IsValid()) { + slotCount = (std::max)( + slotCount, + static_cast(mesh->GetMaterials().Size())); + const auto& sections = mesh->GetSections(); + for (std::size_t sectionIndex = 0u; + sectionIndex < sections.Size(); + ++sectionIndex) { + slotCount = (std::max)( + slotCount, + static_cast(sections[sectionIndex].materialID) + 1u); + } + } + } + + return slotCount; +} + +EditorSceneObjectSnapshot BuildObjectSnapshot(const GameObject& gameObject) { + EditorSceneObjectSnapshot snapshot = {}; + snapshot.itemId = MakeEditorGameObjectItemId(gameObject.GetID()); + snapshot.objectId = gameObject.GetID(); + snapshot.displayName = ResolveGameObjectDisplayName(gameObject); + snapshot.visibleMaterialSlotCount = ResolveVisibleMaterialSlotCount(gameObject); + + const std::vector components = + gameObject.GetComponents(); + snapshot.componentTypeNames.reserve(components.size()); + for (const Component* component : components) { + if (component == nullptr) { + continue; + } + + snapshot.componentTypeNames.push_back(component->GetName()); + } + + return snapshot; +} + +Vector3 ResolveViewportSelectionCenterWorldPosition(const GameObject& gameObject) { + const TransformComponent* transform = gameObject.GetTransform(); + if (transform == nullptr) { + return Vector3::Zero(); + } + + if (const auto* meshFilter = + gameObject.GetComponent<::XCEngine::Components::MeshFilterComponent>(); + meshFilter != nullptr) { + ::XCEngine::Resources::Mesh* mesh = meshFilter->GetMesh(); + if (mesh != nullptr && mesh->IsValid()) { + return transform->TransformPoint(mesh->GetBounds().center); + } + } + + return transform->GetPosition(); +} + +std::optional BuildViewportSelectionSnapshot( + const GameObject& gameObject) { + const TransformComponent* transform = gameObject.GetTransform(); + if (transform == nullptr) { + return std::nullopt; + } + + EditorSceneViewportSelectionSnapshot snapshot = {}; + snapshot.valid = true; + snapshot.objectId = gameObject.GetID(); + snapshot.itemId = MakeEditorGameObjectItemId(gameObject.GetID()); + snapshot.displayName = ResolveGameObjectDisplayName(gameObject); + snapshot.worldPosition = transform->GetPosition(); + snapshot.worldRotation = transform->GetRotation(); + snapshot.localScale = transform->GetLocalScale(); + snapshot.centerWorldPosition = ResolveViewportSelectionCenterWorldPosition(gameObject); + return snapshot; +} + +bool PopulateViewportHelperTransform( + const GameObject& gameObject, + EditorSceneViewportHelperSnapshot& snapshot) { + const TransformComponent* transform = gameObject.GetTransform(); + if (transform == nullptr || !gameObject.IsActiveInHierarchy()) { + return false; + } + + snapshot.objectId = gameObject.GetID(); + snapshot.worldPosition = transform->GetPosition(); + snapshot.worldForward = transform->GetForward(); + snapshot.worldRight = transform->GetRight(); + snapshot.worldUp = transform->GetUp(); + return true; +} + +std::optional BuildViewportCameraHelperSnapshot( + const GameObject& gameObject, + const ::XCEngine::Components::CameraComponent& camera) { + if (!camera.IsEnabled()) { + return std::nullopt; + } + + EditorSceneViewportHelperSnapshot snapshot = {}; + if (!PopulateViewportHelperTransform(gameObject, snapshot)) { + return std::nullopt; + } + + snapshot.kind = EditorSceneViewportHelperKind::Camera; + snapshot.cameraProjectionType = + ToEditorCameraProjectionType(camera.GetProjectionType()); + snapshot.cameraFieldOfViewDegrees = camera.GetFieldOfView(); + snapshot.cameraOrthographicSize = camera.GetOrthographicSize(); + snapshot.nearClipPlane = camera.GetNearClipPlane(); + snapshot.farClipPlane = camera.GetFarClipPlane(); + return snapshot; +} + +std::optional BuildViewportLightHelperSnapshot( + const GameObject& gameObject, + const LightComponent& light) { + if (!light.IsEnabled()) { + return std::nullopt; + } + + EditorSceneViewportHelperSnapshot snapshot = {}; + if (!PopulateViewportHelperTransform(gameObject, snapshot)) { + return std::nullopt; + } + + switch (light.GetLightType()) { + case ::XCEngine::Components::LightType::Directional: + snapshot.kind = EditorSceneViewportHelperKind::DirectionalLight; + break; + case ::XCEngine::Components::LightType::Point: + snapshot.kind = EditorSceneViewportHelperKind::PointLight; + break; + case ::XCEngine::Components::LightType::Spot: + snapshot.kind = EditorSceneViewportHelperKind::SpotLight; + break; + default: + return std::nullopt; + } + + snapshot.lightRange = light.GetRange(); + snapshot.lightSpotAngle = light.GetSpotAngle(); + return snapshot; +} + +std::vector BuildViewportHelperSnapshotsForGameObject( + const GameObject& gameObject) { + std::vector snapshots = {}; + + if (const auto* camera = + gameObject.GetComponent<::XCEngine::Components::CameraComponent>(); + camera != nullptr) { + if (const std::optional snapshot = + BuildViewportCameraHelperSnapshot(gameObject, *camera); + snapshot.has_value()) { + snapshots.push_back(*snapshot); + } + } + + if (const LightComponent* light = gameObject.GetComponent(); + light != nullptr) { + if (const std::optional snapshot = + BuildViewportLightHelperSnapshot(gameObject, *light); + snapshot.has_value()) { + snapshots.push_back(*snapshot); + } + } + + return snapshots; +} + +bool MoveGameObjectRelativeToTarget( + SceneManager& sceneManager, + std::string_view itemId, + std::string_view targetItemId, + bool placeAfterTarget) { + Scene* scene = ResolvePrimaryScene(sceneManager); + GameObject* gameObject = FindGameObjectByItemId(sceneManager, itemId); + GameObject* target = FindGameObjectByItemId(sceneManager, targetItemId); + if (scene == nullptr || + gameObject == nullptr || + target == nullptr || + gameObject == target) { + return false; + } + + GameObject* targetParent = target->GetParent(); + if (targetParent != nullptr && + WouldCreateCycle(*gameObject, *targetParent)) { + return false; + } + + const std::vector siblings = + ResolveSiblingSequence(*scene, targetParent); + const std::optional targetIndex = + FindSiblingIndex(siblings, target); + if (!targetIndex.has_value()) { + return false; + } + + std::size_t finalIndex = + targetIndex.value() + (placeAfterTarget ? 1u : 0u); + if (gameObject->GetParent() == targetParent) { + const std::optional sourceIndex = + FindSiblingIndex(siblings, gameObject); + if (sourceIndex.has_value()) { + if ((!placeAfterTarget && sourceIndex.value() < targetIndex.value()) || + (placeAfterTarget && sourceIndex.value() <= targetIndex.value())) { + --finalIndex; + } + + if (finalIndex == sourceIndex.value()) { + return false; + } + } + } + + gameObject->SetParent(targetParent, finalIndex, true); + return true; +} + +class EngineEditorSceneBackend final : public EditorSceneBackend { +public: + EngineEditorSceneBackend( + SceneManager& sceneManager, + ResourceManager& resourceManager) + : m_sceneManager(sceneManager) + , m_resourceManager(resourceManager) {} + + EditorStartupSceneResult EnsureStartupScene( + const std::filesystem::path& projectRoot) override { + EditorStartupSceneResult result = {}; + TraceSceneStartup("EnsureStartupScene begin projectRoot=" + projectRoot.string()); + if (projectRoot.empty()) { + return result; + } + + m_resourceManager.SetResourceRoot(projectRoot.string().c_str()); + TraceSceneStartup("ResourceManager::SetResourceRoot complete"); + + if (Scene* activeScene = ResolvePrimaryScene(m_sceneManager); + activeScene != nullptr) { + result.ready = true; + result.sceneName = activeScene->GetName(); + TraceSceneStartup("EnsureStartupScene reused active scene=" + result.sceneName); + return result; + } + + const std::filesystem::path startupScenePath = + (projectRoot / "Assets" / "Scenes" / "Main.xc").lexically_normal(); + if (std::filesystem::exists(startupScenePath) && + std::filesystem::is_regular_file(startupScenePath)) { + TraceSceneStartup( + "SceneManager::LoadScene begin path=" + startupScenePath.string()); + { + ResourceManager::ScopedDeferredSceneLoad deferredSceneLoad( + m_resourceManager); + m_sceneManager.LoadScene(startupScenePath.string()); + } + TraceSceneStartup("SceneManager::LoadScene end"); + Scene* loadedScene = + m_sceneManager.GetScene(startupScenePath.stem().string()); + if (loadedScene == nullptr) { + loadedScene = ResolvePrimaryScene(m_sceneManager); + } else { + m_sceneManager.SetActiveScene(loadedScene); + } + + if (loadedScene != nullptr) { + result.ready = true; + result.loadedFromDisk = true; + result.scenePath = startupScenePath; + result.sceneName = loadedScene->GetName(); + TraceSceneStartup( + "EnsureStartupScene loaded scene=" + result.sceneName); + return result; + } + } + + if (Scene* scene = m_sceneManager.CreateScene("Main"); + scene != nullptr) { + m_sceneManager.SetActiveScene(scene); + result.ready = true; + result.sceneName = scene->GetName(); + TraceSceneStartup( + "EnsureStartupScene created scene=" + result.sceneName); + } + + TraceSceneStartup( + std::string("EnsureStartupScene end ready=") + + (result.ready ? "1" : "0")); + return result; + } + + EditorSceneHierarchySnapshot BuildHierarchySnapshot() const override { + EditorSceneHierarchySnapshot snapshot = {}; + Scene* scene = ResolvePrimaryScene(m_sceneManager); + if (scene == nullptr) { + return snapshot; + } + + const std::vector roots = scene->GetRootGameObjects(); + snapshot.roots.reserve(roots.size()); + for (const GameObject* root : roots) { + if (root == nullptr) { + continue; + } + + snapshot.roots.push_back(BuildHierarchySnapshotNodeRecursive(*root)); + } + return snapshot; + } + + bool OpenSceneAsset(const std::filesystem::path& scenePath) override { + if (scenePath.empty()) { + return false; + } + + std::error_code errorCode = {}; + if (!std::filesystem::exists(scenePath, errorCode) || + errorCode || + !std::filesystem::is_regular_file(scenePath, errorCode)) { + return false; + } + + { + ResourceManager::ScopedDeferredSceneLoad deferredSceneLoad( + m_resourceManager); + m_sceneManager.LoadScene(scenePath.string()); + } + Scene* loadedScene = m_sceneManager.GetScene(scenePath.stem().string()); + if (loadedScene == nullptr) { + loadedScene = ResolvePrimaryScene(m_sceneManager); + } else { + m_sceneManager.SetActiveScene(loadedScene); + } + + return loadedScene != nullptr; + } + + std::optional GetObjectSnapshot( + std::string_view itemId) const override { + const GameObject* gameObject = FindGameObject(itemId); + return gameObject != nullptr + ? std::optional(BuildObjectSnapshot(*gameObject)) + : std::nullopt; + } + + GameObject* FindGameObject(std::string_view itemId) const { + return FindGameObjectByItemId(m_sceneManager, itemId); + } + + bool AddComponent( + std::string_view itemId, + std::string_view componentTypeName) override { + if (componentTypeName.empty()) { + return false; + } + + GameObject* gameObject = FindGameObject(itemId); + if (gameObject == nullptr) { + return false; + } + + Component* addedComponent = + ComponentFactoryRegistry::Get().CreateComponent( + gameObject, + std::string(componentTypeName)); + return addedComponent != nullptr; + } + + std::vector GetComponents( + std::string_view itemId) const override { + std::vector descriptors = {}; + const GameObject* gameObject = FindGameObject(itemId); + if (gameObject == nullptr) { + return descriptors; + } + + const std::vector components = + gameObject->GetComponents(); + descriptors.reserve(components.size()); + std::unordered_map ordinalsByType = {}; + for (const Component* component : components) { + if (component == nullptr) { + continue; + } + + const std::string typeName = component->GetName(); + const std::size_t ordinal = ordinalsByType[typeName]; + descriptors.push_back(BuildComponentDescriptor(*component, ordinal)); + ordinalsByType[typeName] = ordinal + 1u; + } + + return descriptors; + } + + std::vector BuildViewportIconSnapshots() + const override { + std::vector snapshots = {}; + Scene* scene = ResolvePrimaryScene(m_sceneManager); + if (scene == nullptr) { + return snapshots; + } + + for (auto* camera : scene->FindObjectsOfType<::XCEngine::Components::CameraComponent>()) { + if (camera == nullptr || !camera->IsEnabled()) { + continue; + } + + GameObject* gameObject = camera->GetGameObject(); + TransformComponent* transform = + gameObject != nullptr ? gameObject->GetTransform() : nullptr; + if (gameObject == nullptr || + transform == nullptr || + !gameObject->IsActiveInHierarchy()) { + continue; + } + + EditorSceneViewportIconSnapshot snapshot = {}; + snapshot.entityId = gameObject->GetID(); + snapshot.kind = EditorSceneViewportIconKind::Camera; + snapshot.worldPosition = transform->GetPosition(); + snapshots.push_back(std::move(snapshot)); + } + + for (LightComponent* light : scene->FindObjectsOfType()) { + if (light == nullptr || !light->IsEnabled()) { + continue; + } + + GameObject* gameObject = light->GetGameObject(); + TransformComponent* transform = + gameObject != nullptr ? gameObject->GetTransform() : nullptr; + if (gameObject == nullptr || + transform == nullptr || + !gameObject->IsActiveInHierarchy()) { + continue; + } + + EditorSceneViewportIconSnapshot snapshot = {}; + snapshot.entityId = gameObject->GetID(); + snapshot.kind = light->GetLightType() == ::XCEngine::Components::LightType::Point + ? EditorSceneViewportIconKind::PointLight + : light->GetLightType() == ::XCEngine::Components::LightType::Spot + ? EditorSceneViewportIconKind::SpotLight + : EditorSceneViewportIconKind::DirectionalLight; + snapshot.worldPosition = transform->GetPosition(); + snapshots.push_back(std::move(snapshot)); + } + + return snapshots; + } + + std::optional + BuildViewportSelectionSnapshot(EditorSceneObjectId objectId) const override { + Scene* scene = ResolvePrimaryScene(m_sceneManager); + GameObject* gameObject = + scene != nullptr ? scene->FindByID(objectId) : nullptr; + return gameObject != nullptr + ? ::XCEngine::UI::Editor::App::BuildViewportSelectionSnapshot(*gameObject) + : std::nullopt; + } + + std::vector BuildViewportHelperSnapshots( + EditorSceneObjectId objectId) const override { + Scene* scene = ResolvePrimaryScene(m_sceneManager); + GameObject* gameObject = + scene != nullptr ? scene->FindByID(objectId) : nullptr; + return gameObject != nullptr + ? BuildViewportHelperSnapshotsForGameObject(*gameObject) + : std::vector{}; + } + + bool RemoveComponent( + std::string_view itemId, + std::string_view componentId) override { + GameObject* gameObject = FindGameObject(itemId); + if (gameObject == nullptr) { + return false; + } + + Component* component = ResolveComponent(*gameObject, componentId); + const EditorSceneComponentDescriptor descriptor = + ResolveComponentDescriptor(*gameObject, componentId); + if (!descriptor.IsValid() || + !descriptor.removable || + component == nullptr) { + return false; + } + + return gameObject->RemoveComponent(component); + } + + bool SetTransformLocalPosition( + std::string_view itemId, + std::string_view componentId, + const Vector3& position) override { + TransformComponent* transform = + ResolveTransformComponent(itemId, componentId); + if (transform == nullptr) { + return false; + } + + transform->SetLocalPosition(position); + return true; + } + + bool SetTransformLocalEulerAngles( + std::string_view itemId, + std::string_view componentId, + const Vector3& eulerAngles) override { + TransformComponent* transform = + ResolveTransformComponent(itemId, componentId); + if (transform == nullptr) { + return false; + } + + transform->SetLocalEulerAngles(eulerAngles); + return true; + } + + bool SetTransformLocalScale( + std::string_view itemId, + std::string_view componentId, + const Vector3& scale) override { + TransformComponent* transform = + ResolveTransformComponent(itemId, componentId); + if (transform == nullptr) { + return false; + } + + transform->SetLocalScale(scale); + return true; + } + + bool ApplyComponentMutation( + std::string_view itemId, + const EditorSceneComponentMutation& mutation) override { + if (!mutation.IsValid()) { + return false; + } + + GameObject* gameObject = FindGameObject(itemId); + if (gameObject == nullptr) { + return false; + } + + Component* component = ResolveComponent(*gameObject, mutation.componentId); + if (component == nullptr) { + return false; + } + + if (auto* camera = + dynamic_cast<::XCEngine::Components::CameraComponent*>(component); + camera != nullptr) { + return ApplyCameraComponentMutation(*camera, mutation); + } + if (auto* light = dynamic_cast(component); + light != nullptr) { + return ApplyLightComponentMutation(*light, mutation); + } + if (auto* collider = + dynamic_cast<::XCEngine::Components::BoxColliderComponent*>(component); + collider != nullptr) { + return ApplyBoxColliderComponentMutation(*collider, mutation); + } + if (auto* collider = + dynamic_cast<::XCEngine::Components::CapsuleColliderComponent*>(component); + collider != nullptr) { + return ApplyCapsuleColliderComponentMutation(*collider, mutation); + } + if (auto* collider = + dynamic_cast<::XCEngine::Components::SphereColliderComponent*>(component); + collider != nullptr) { + return ApplySphereColliderComponentMutation(*collider, mutation); + } + if (auto* source = + dynamic_cast<::XCEngine::Components::AudioSourceComponent*>(component); + source != nullptr) { + return ApplyAudioSourceComponentMutation(*source, mutation); + } + if (auto* listener = + dynamic_cast<::XCEngine::Components::AudioListenerComponent*>(component); + listener != nullptr) { + return ApplyAudioListenerComponentMutation(*listener, mutation); + } + if (auto* meshFilter = + dynamic_cast<::XCEngine::Components::MeshFilterComponent*>(component); + meshFilter != nullptr) { + return ApplyMeshFilterComponentMutation(*meshFilter, mutation); + } + if (auto* meshRenderer = + dynamic_cast<::XCEngine::Components::MeshRendererComponent*>(component); + meshRenderer != nullptr) { + return ApplyMeshRendererComponentMutation(*meshRenderer, mutation); + } + if (auto* rigidbody = + dynamic_cast<::XCEngine::Components::RigidbodyComponent*>(component); + rigidbody != nullptr) { + return ApplyRigidbodyComponentMutation(*rigidbody, mutation); + } + if (auto* volumeRenderer = + dynamic_cast<::XCEngine::Components::VolumeRendererComponent*>(component); + volumeRenderer != nullptr) { + return ApplyVolumeRendererComponentMutation(*volumeRenderer, mutation); + } + + return false; + } + + bool QueryWorldTransform( + EditorSceneObjectId objectId, + Vector3& outPosition, + Quaternion& outRotation, + Vector3& outScale) const override { + Scene* scene = ResolvePrimaryScene(m_sceneManager); + GameObject* gameObject = + scene != nullptr ? scene->FindByID(objectId) : nullptr; + const TransformComponent* transform = + gameObject != nullptr ? gameObject->GetTransform() : nullptr; + if (transform == nullptr) { + return false; + } + + outPosition = transform->GetPosition(); + outRotation = transform->GetRotation(); + outScale = transform->GetScale(); + return true; + } + + bool SetWorldTransform( + EditorSceneObjectId objectId, + const Vector3& position, + const Quaternion& rotation, + const Vector3& scale) override { + TransformComponent* transform = ResolveTransformComponent(objectId); + if (transform == nullptr) { + return false; + } + + transform->SetPosition(position); + transform->SetRotation(rotation); + transform->SetScale(scale); + return true; + } + + bool SetWorldPositionRotation( + EditorSceneObjectId objectId, + const Vector3& position, + const Quaternion& rotation) override { + TransformComponent* transform = ResolveTransformComponent(objectId); + if (transform == nullptr) { + return false; + } + + transform->SetPosition(position); + transform->SetRotation(rotation); + return true; + } + + bool SetObjectLocalScale( + EditorSceneObjectId objectId, + const Vector3& localScale) override { + TransformComponent* transform = ResolveTransformComponent(objectId); + if (transform == nullptr) { + return false; + } + + transform->SetLocalScale(localScale); + return true; + } + + bool RenameGameObject( + std::string_view itemId, + std::string_view newName) override { + GameObject* gameObject = FindGameObject(itemId); + if (gameObject == nullptr) { + return false; + } + + gameObject->SetName(std::string(newName)); + return true; + } + + bool DeleteGameObject(std::string_view itemId) override { + Scene* scene = ResolvePrimaryScene(m_sceneManager); + GameObject* gameObject = FindGameObject(itemId); + if (scene == nullptr || gameObject == nullptr) { + return false; + } + + scene->DestroyGameObject(gameObject); + return true; + } + + std::string DuplicateGameObject(std::string_view itemId) override { + Scene* scene = ResolvePrimaryScene(m_sceneManager); + GameObject* gameObject = FindGameObject(itemId); + if (scene == nullptr || gameObject == nullptr) { + return {}; + } + + const ClipboardNode clipboard = CopyGameObjectRecursive(*gameObject); + GameObject* duplicate = + PasteGameObjectRecursive(*scene, clipboard, gameObject->GetParent()); + return duplicate != nullptr + ? MakeEditorGameObjectItemId(duplicate->GetID()) + : std::string(); + } + + bool ReparentGameObject( + std::string_view itemId, + std::string_view parentItemId) override { + GameObject* gameObject = FindGameObject(itemId); + GameObject* newParent = FindGameObject(parentItemId); + if (gameObject == nullptr || newParent == nullptr || + gameObject == newParent || + gameObject->GetParent() == newParent || + WouldCreateCycle(*gameObject, *newParent)) { + return false; + } + + gameObject->SetParent(newParent); + return true; + } + + bool MoveGameObjectBefore( + std::string_view itemId, + std::string_view targetItemId) override { + return MoveGameObjectRelativeToTarget( + m_sceneManager, + itemId, + targetItemId, + false); + } + + bool MoveGameObjectAfter( + std::string_view itemId, + std::string_view targetItemId) override { + return MoveGameObjectRelativeToTarget( + m_sceneManager, + itemId, + targetItemId, + true); + } + + bool MoveGameObjectToRoot(std::string_view itemId) override { + GameObject* gameObject = FindGameObject(itemId); + if (gameObject == nullptr || gameObject->GetParent() == nullptr) { + return false; + } + + gameObject->SetParent(nullptr); + return true; + } + +private: + TransformComponent* ResolveTransformComponent(EditorSceneObjectId objectId) const { + Scene* scene = ResolvePrimaryScene(m_sceneManager); + GameObject* gameObject = + scene != nullptr ? scene->FindByID(objectId) : nullptr; + return gameObject != nullptr ? gameObject->GetTransform() : nullptr; + } + + TransformComponent* ResolveTransformComponent( + std::string_view itemId, + std::string_view componentId) const { + GameObject* gameObject = FindGameObject(itemId); + if (gameObject == nullptr) { + return nullptr; + } + + return dynamic_cast( + ResolveComponent(*gameObject, componentId)); + } + + SceneManager& m_sceneManager; + ResourceManager& m_resourceManager; +}; + +} // namespace + +std::unique_ptr CreateEngineEditorSceneBackend( + SceneManager& sceneManager, + ResourceManager& resourceManager) { + return std::make_unique( + sceneManager, + resourceManager); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Windowing/Content/EditorWindowContentController.h b/editor/app/Windowing/Content/EditorWindowContentController.h index 87e32962..68798a4d 100644 --- a/editor/app/Windowing/Content/EditorWindowContentController.h +++ b/editor/app/Windowing/Content/EditorWindowContentController.h @@ -51,7 +51,9 @@ class EditorHostResourceService; namespace XCEngine::UI::Editor::App { -class EditorEngineServices; +class EditorShaderProvider; +class GameViewportEngineBridge; +class SceneViewportEngineBridge; struct EditorWindowContentCapabilities { bool workspace = false; @@ -69,7 +71,9 @@ struct EditorWindowContentInitializationContext { Host::EditorHostResourceService& resourceService; UIEditorTextMeasurer& textMeasurer; Rendering::Host::ViewportRenderHost& viewportRenderer; - EditorEngineServices& engineServices; + SceneViewportEngineBridge& sceneViewportEngineBridge; + GameViewportEngineBridge& gameViewportEngineBridge; + EditorShaderProvider& shaderProvider; }; struct EditorWindowContentFrameContext { diff --git a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp index 24f88570..dd3521e8 100644 --- a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp +++ b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp @@ -119,7 +119,9 @@ void EditorWorkspaceWindowContentController::Initialize( context.textureHost, context.resourceService, context.textMeasurer, - context.engineServices); + context.sceneViewportEngineBridge, + context.gameViewportEngineBridge, + context.shaderProvider); m_shellRuntime->AttachViewportWindowRenderer(context.viewportRenderer); } diff --git a/editor/app/Windowing/EditorWindowManager.cpp b/editor/app/Windowing/EditorWindowManager.cpp index cd915c56..da15adff 100644 --- a/editor/app/Windowing/EditorWindowManager.cpp +++ b/editor/app/Windowing/EditorWindowManager.cpp @@ -21,15 +21,19 @@ namespace XCEngine::UI::Editor::App { EditorWindowManager::EditorWindowManager( EditorFrameServices& frameServices, - EditorEngineServices& engineServices, + SceneViewportEngineBridge& sceneViewportEngineBridge, + GameViewportEngineBridge& gameViewportEngineBridge, + EditorShaderProvider& shaderProvider, EditorWindowSystem& windowSystem, Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory, Host::EditorHostResourceService& resourceService, EditorWindowHostRuntimeServices& hostRuntime, EditorWorkspaceShellRuntimeFactory workspaceShellRuntimeFactory, - EditorUtilityWindowPanelFactory utilityPanelFactory) + EditorUtilityWindowPanelFactory utilityPanelFactory) : m_frameServices(frameServices) - , m_engineServices(engineServices) + , m_sceneViewportEngineBridge(sceneViewportEngineBridge) + , m_gameViewportEngineBridge(gameViewportEngineBridge) + , m_shaderProvider(shaderProvider) , m_renderRuntimeFactory(renderRuntimeFactory) , m_resourceService(resourceService) , m_hostRuntime(hostRuntime) { @@ -81,7 +85,9 @@ EditorHostWindow* EditorWindowManager::CreateWorkspaceWindow( params.primary, std::make_unique( m_frameServices, - m_engineServices, + m_sceneViewportEngineBridge, + m_gameViewportEngineBridge, + m_shaderProvider, m_resourceService, m_contentFactory->CreateWorkspaceContentController(windowState), m_renderRuntimeFactory.CreateWindowRenderRuntime())); @@ -111,7 +117,9 @@ EditorHostWindow* EditorWindowManager::CreateUtilityWindow( params.primary, std::make_unique( m_frameServices, - m_engineServices, + m_sceneViewportEngineBridge, + m_gameViewportEngineBridge, + m_shaderProvider, m_resourceService, m_contentFactory->CreateUtilityContentController(descriptor), m_renderRuntimeFactory.CreateWindowRenderRuntime())); diff --git a/editor/app/Windowing/EditorWindowManager.h b/editor/app/Windowing/EditorWindowManager.h index b6f046ca..8563f55c 100644 --- a/editor/app/Windowing/EditorWindowManager.h +++ b/editor/app/Windowing/EditorWindowManager.h @@ -35,11 +35,13 @@ class EditorHostResourceService; namespace XCEngine::UI::Editor::App { class EditorWindowContentFactory; -class EditorEngineServices; +class EditorShaderProvider; class EditorWindowHostRuntimeServices; class EditorWindowLifecycleCoordinator; class EditorUtilityWindowCoordinator; class EditorWindowWorkspaceCoordinator; +class GameViewportEngineBridge; +class SceneViewportEngineBridge; struct EditorUtilityWindowDescriptor; struct EditorWindowFrameTransferRequests; @@ -47,7 +49,9 @@ class EditorWindowManager final : public EditorWindowHostCoordinator { public: EditorWindowManager( EditorFrameServices& frameServices, - EditorEngineServices& engineServices, + SceneViewportEngineBridge& sceneViewportEngineBridge, + GameViewportEngineBridge& gameViewportEngineBridge, + EditorShaderProvider& shaderProvider, EditorWindowSystem& windowSystem, Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory, Host::EditorHostResourceService& resourceService, @@ -100,7 +104,9 @@ private: bool requestSkipNextSteadyStateFrame); EditorFrameServices& m_frameServices; - EditorEngineServices& m_engineServices; + SceneViewportEngineBridge& m_sceneViewportEngineBridge; + GameViewportEngineBridge& m_gameViewportEngineBridge; + EditorShaderProvider& m_shaderProvider; Rendering::Host::EditorWindowRenderRuntimeFactory& m_renderRuntimeFactory; Host::EditorHostResourceService& m_resourceService; EditorWindowHostRuntimeServices& m_hostRuntime; diff --git a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp index ad9f65f8..d8d565cb 100644 --- a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp +++ b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp @@ -45,12 +45,16 @@ bool LoadHostPngTexture( EditorWindowRuntimeController::EditorWindowRuntimeController( EditorFrameServices& frameServices, - EditorEngineServices& engineServices, + SceneViewportEngineBridge& sceneViewportEngineBridge, + GameViewportEngineBridge& gameViewportEngineBridge, + EditorShaderProvider& shaderProvider, Host::EditorHostResourceService& resourceService, std::unique_ptr contentController, std::unique_ptr renderRuntime) : m_frameServices(frameServices) - , m_engineServices(engineServices) + , m_sceneViewportEngineBridge(sceneViewportEngineBridge) + , m_gameViewportEngineBridge(gameViewportEngineBridge) + , m_shaderProvider(shaderProvider) , m_resourceService(resourceService) , m_renderRuntime(std::move(renderRuntime)) , m_contentController(std::move(contentController)) {} @@ -174,7 +178,9 @@ bool EditorWindowRuntimeController::Initialize( .resourceService = m_resourceService, .textMeasurer = m_renderRuntime->GetTextMeasurer(), .viewportRenderer = m_renderRuntime->GetViewportRenderHost(), - .engineServices = m_engineServices, + .sceneViewportEngineBridge = m_sceneViewportEngineBridge, + .gameViewportEngineBridge = m_gameViewportEngineBridge, + .shaderProvider = m_shaderProvider, }); m_contentController->SetViewportSurfacePresentationEnabled( initializeResult.hasViewportSurfacePresentation); diff --git a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h index 6d7c4edf..c4ca7331 100644 --- a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h +++ b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h @@ -34,13 +34,17 @@ class EditorHostResourceService; namespace XCEngine::UI::Editor::App { -class EditorEngineServices; +class EditorShaderProvider; +class GameViewportEngineBridge; +class SceneViewportEngineBridge; class EditorWindowRuntimeController final { public: EditorWindowRuntimeController( EditorFrameServices& frameServices, - EditorEngineServices& engineServices, + SceneViewportEngineBridge& sceneViewportEngineBridge, + GameViewportEngineBridge& gameViewportEngineBridge, + EditorShaderProvider& shaderProvider, Host::EditorHostResourceService& resourceService, std::unique_ptr contentController, std::unique_ptr renderRuntime); @@ -105,7 +109,9 @@ private: void RefreshDisplayedFrameStats(); EditorFrameServices& m_frameServices; - EditorEngineServices& m_engineServices; + SceneViewportEngineBridge& m_sceneViewportEngineBridge; + GameViewportEngineBridge& m_gameViewportEngineBridge; + EditorShaderProvider& m_shaderProvider; Host::EditorHostResourceService& m_resourceService; std::unique_ptr m_renderRuntime = {}; EditorWindowScreenshotController m_screenshotController = {}; diff --git a/tests/UI/Editor/unit/CMakeLists.txt b/tests/UI/Editor/unit/CMakeLists.txt index 54d030cb..cbab0f1a 100644 --- a/tests/UI/Editor/unit/CMakeLists.txt +++ b/tests/UI/Editor/unit/CMakeLists.txt @@ -75,6 +75,7 @@ set(EDITOR_APP_CORE_TEST_SOURCES set(EDITOR_APP_FEATURE_TEST_SOURCES test_editor_window_input_routing.cpp + test_game_viewport_runtime.cpp test_project_panel.cpp test_scene_viewport_render_plan.cpp test_scene_viewport_runtime.cpp diff --git a/tests/UI/Editor/unit/test_editor_scene_runtime_backend.cpp b/tests/UI/Editor/unit/test_editor_scene_runtime_backend.cpp index 8cf7362e..60de53d1 100644 --- a/tests/UI/Editor/unit/test_editor_scene_runtime_backend.cpp +++ b/tests/UI/Editor/unit/test_editor_scene_runtime_backend.cpp @@ -1,17 +1,11 @@ #include "Scene/EditorSceneBackend.h" #include "Scene/EditorSceneRuntime.h" -#include -#include - #include namespace XCEngine::UI::Editor::App { namespace { -using ::XCEngine::Components::GameObject; -using ::XCEngine::Components::Scene; - class FakeEditorSceneBackend final : public EditorSceneBackend { public: EditorStartupSceneResult EnsureStartupScene( @@ -21,10 +15,6 @@ public: return startupSceneResult; } - Scene* GetActiveScene() const override { - return activeScene; - } - EditorSceneHierarchySnapshot BuildHierarchySnapshot() const override { return hierarchySnapshot; } @@ -34,9 +24,15 @@ public: return openSceneResult; } - GameObject* FindGameObject(std::string_view itemId) const override { - lastFindItemId = std::string(itemId); - return foundGameObject; + std::optional GetObjectSnapshot( + std::string_view itemId) const override { + lastGetObjectSnapshotItemId = std::string(itemId); + if (objectSnapshot.has_value() && + objectSnapshot->itemId == itemId) { + return objectSnapshot; + } + + return std::nullopt; } bool AddComponent( @@ -85,14 +81,13 @@ public: EditorStartupSceneResult startupSceneResult = {}; EditorSceneHierarchySnapshot hierarchySnapshot = {}; - Scene* activeScene = nullptr; - GameObject* foundGameObject = nullptr; + std::optional objectSnapshot = std::nullopt; bool openSceneResult = false; bool addComponentResult = false; int ensureStartupSceneCallCount = 0; std::filesystem::path lastProjectRoot = {}; std::filesystem::path lastOpenedScenePath = {}; - mutable std::string lastFindItemId = {}; + mutable std::string lastGetObjectSnapshotItemId = {}; std::string lastAddComponentItemId = {}; std::string lastAddComponentTypeName = {}; }; @@ -119,21 +114,30 @@ TEST(EditorSceneRuntimeBackendTests, InitializeUsesBoundBackend) { EXPECT_EQ(runtime.GetStartupResult().sceneName, "Main"); } -TEST(EditorSceneRuntimeBackendTests, FindGameObjectUsesBoundBackend) { +TEST(EditorSceneRuntimeBackendTests, SetSelectionUsesBoundBackendObjectSnapshotLookup) { auto backend = std::make_unique(); - Scene scene("Main"); - GameObject probe("Probe"); backend->startupSceneResult.ready = true; - backend->activeScene = &scene; - backend->foundGameObject = &probe; + backend->objectSnapshot = EditorSceneObjectSnapshot{ + .itemId = "42", + .objectId = 42u, + .displayName = "Probe", + .componentTypeNames = {}, + .visibleMaterialSlotCount = 1u + }; FakeEditorSceneBackend* const backendPtr = backend.get(); EditorSceneRuntime runtime = {}; runtime.SetBackend(std::move(backend)); ASSERT_TRUE(runtime.Initialize("D:/Xuanchi/Main/XCEngine/project")); - EXPECT_EQ(runtime.FindGameObject("42"), &probe); - EXPECT_EQ(backendPtr->lastFindItemId, "42"); + ASSERT_TRUE(runtime.SetSelection("42")); + const std::optional selectedObject = + runtime.GetSelectedObjectSnapshot(); + ASSERT_TRUE(selectedObject.has_value()); + ASSERT_TRUE(runtime.GetSelectedObjectId().has_value()); + EXPECT_EQ(runtime.GetSelectedObjectId().value(), 42u); + EXPECT_EQ(selectedObject->displayName, "Probe"); + EXPECT_EQ(backendPtr->lastGetObjectSnapshotItemId, "42"); } TEST(EditorSceneRuntimeBackendTests, BuildHierarchySnapshotUsesBoundBackend) { diff --git a/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp b/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp index 41c343f9..5daffd00 100644 --- a/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp +++ b/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp @@ -95,11 +95,11 @@ TEST(EditorShellAssetValidationTest, ProductManifestDeclaresPanelRuntimeAndViewp const auto* gamePanel = FindEditorProductPanel(kGamePanelId); ASSERT_NE(gamePanel, nullptr); - EXPECT_EQ(gamePanel->runtimeKind, EditorProductPanelRuntimeKind::None); + EXPECT_EQ(gamePanel->runtimeKind, EditorProductPanelRuntimeKind::Game); EXPECT_EQ( gamePanel->viewportRendererKind, - EditorProductViewportRendererKind::Placeholder); - EXPECT_FALSE(gamePanel->viewportPlaceholderStatus.empty()); + EditorProductViewportRendererKind::Game); + EXPECT_TRUE(gamePanel->viewportPlaceholderStatus.empty()); } TEST(EditorShellAssetValidationTest, ValidationRejectsWorkspacePanelMissingFromRegistry) { diff --git a/tests/UI/Editor/unit/test_game_viewport_runtime.cpp b/tests/UI/Editor/unit/test_game_viewport_runtime.cpp new file mode 100644 index 00000000..8eb20003 --- /dev/null +++ b/tests/UI/Editor/unit/test_game_viewport_runtime.cpp @@ -0,0 +1,100 @@ +#include + +#include "Game/GameViewportFeature.h" +#include "Panels/EditorPanelIds.h" +#include "State/EditorCommandFocusService.h" +#include "Viewport/GameViewportRenderService.h" + +#include + +namespace XCEngine::UI::Editor::App { +namespace { + +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIPointerButton; +using ::XCEngine::UI::UIRect; +using ::XCEngine::UI::UISize; +using ::XCEngine::UI::Editor::UIEditorViewportInputBridgeState; +using ::XCEngine::UI::Editor::UIEditorWorkspaceComposeFrame; +using ::XCEngine::UI::Editor::UIEditorWorkspaceComposeState; +using ::XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationState; +using ::XCEngine::UI::Editor::UIEditorWorkspaceViewportComposeFrame; +using ::XCEngine::UI::Editor::UpdateUIEditorViewportInputBridge; + +UIInputEvent MakePointerEvent( + UIInputEventType type, + float x, + float y, + UIPointerButton button = UIPointerButton::None) { + UIInputEvent event = {}; + event.type = type; + event.position = UIPoint(x, y); + event.pointerButton = button; + return event; +} + +UIEditorWorkspaceComposeState BuildGameComposeState( + const UIEditorViewportInputBridgeState& inputBridgeState) { + UIEditorWorkspaceComposeState composeState = {}; + UIEditorWorkspacePanelPresentationState panelState = {}; + panelState.panelId = std::string(kGamePanelId); + panelState.viewportShellState.inputBridgeState = inputBridgeState; + composeState.panelStates.push_back(std::move(panelState)); + return composeState; +} + +UIEditorWorkspaceComposeFrame BuildGameComposeFrame( + const ::XCEngine::UI::Editor::UIEditorViewportInputBridgeFrame& inputFrame, + const UIRect& inputRect, + const UISize& requestedViewportSize) { + UIEditorWorkspaceComposeFrame composeFrame = {}; + UIEditorWorkspaceViewportComposeFrame viewportFrame = {}; + viewportFrame.panelId = std::string(kGamePanelId); + viewportFrame.viewportShellFrame.inputFrame = inputFrame; + viewportFrame.viewportShellFrame.requestedViewportSize = requestedViewportSize; + viewportFrame.viewportShellFrame.slotLayout.inputRect = inputRect; + viewportFrame.viewportShellFrame.slotLayout.bounds = inputRect; + composeFrame.viewportFrames.push_back(std::move(viewportFrame)); + return composeFrame; +} + +TEST(GameViewportRuntimeTests, PointerDownClaimsGameCommandFocus) { + GameViewportFeature feature = {}; + EditorCommandFocusService commandFocusService = {}; + feature.SetCommandFocusService(&commandFocusService); + + UIEditorViewportInputBridgeState inputBridgeState = {}; + const UIRect inputRect(100.0f, 80.0f, 640.0f, 360.0f); + const UISize viewportSize(640.0f, 360.0f); + const auto frame = UpdateUIEditorViewportInputBridge( + inputBridgeState, + inputRect, + { + MakePointerEvent(UIInputEventType::PointerMove, 220.0f, 180.0f), + MakePointerEvent( + UIInputEventType::PointerButtonDown, + 220.0f, + 180.0f, + UIPointerButton::Left) + }); + + feature.Update( + BuildGameComposeState(inputBridgeState), + BuildGameComposeFrame(frame, inputRect, viewportSize)); + + EXPECT_EQ(commandFocusService.GetExplicitRoute(), EditorActionRoute::Game); +} + +TEST(GameViewportRuntimeTests, GameViewportRendererUsesDefaultViewportResources) { + const ViewportResourceRequirements requirements = + GameViewportRenderService::GetViewportResourceRequirements(); + + EXPECT_FALSE(requirements.requiresDepthSampling); + EXPECT_FALSE(requirements.requiresObjectIdSurface); + EXPECT_FALSE(requirements.requiresSelectionMaskSurface); +} + +} // namespace +} // namespace XCEngine::UI::Editor::App diff --git a/tests/UI/Editor/unit/test_hierarchy_scene_binding.cpp b/tests/UI/Editor/unit/test_hierarchy_scene_binding.cpp index cbf1adf6..254472a4 100644 --- a/tests/UI/Editor/unit/test_hierarchy_scene_binding.cpp +++ b/tests/UI/Editor/unit/test_hierarchy_scene_binding.cpp @@ -80,7 +80,9 @@ TEST(HierarchySceneBindingTests, BuildFromSceneUsesRealGameObjectIds) { GameObject* child = scene->CreateGameObject("Child", root); ASSERT_NE(child, nullptr); - const HierarchyModel model = HierarchyModel::BuildFromScene(scene); + const std::unique_ptr backend = CreateTestSceneBackend(); + const HierarchyModel model = + HierarchyModel::BuildFromSnapshot(backend->BuildHierarchySnapshot()); const HierarchyNode* rootNode = model.FindNode(MakeEditorGameObjectItemId(root->GetID())); ASSERT_NE(rootNode, nullptr); @@ -109,7 +111,8 @@ TEST(HierarchySceneBindingTests, DuplicateGameObjectClonesHierarchyIntoScene) { backend->DuplicateGameObject(MakeEditorGameObjectItemId(root->GetID())); ASSERT_FALSE(duplicateId.empty()); - const HierarchyModel model = HierarchyModel::BuildFromScene(scene); + const HierarchyModel model = + HierarchyModel::BuildFromSnapshot(backend->BuildHierarchySnapshot()); const HierarchyNode* duplicateNode = model.FindNode(duplicateId); ASSERT_NE(duplicateNode, nullptr); EXPECT_EQ(duplicateNode->label, "Root"); @@ -136,7 +139,8 @@ TEST(HierarchySceneBindingTests, RenameGameObjectUpdatesRealSceneAndProjection) ASSERT_TRUE(backend->RenameGameObject(itemId, "PlayerCamera")); EXPECT_EQ(gameObject->GetName(), "PlayerCamera"); - const HierarchyModel model = HierarchyModel::BuildFromScene(scene); + const HierarchyModel model = + HierarchyModel::BuildFromSnapshot(backend->BuildHierarchySnapshot()); const HierarchyNode* node = model.FindNode(itemId); ASSERT_NE(node, nullptr); EXPECT_EQ(node->label, "PlayerCamera"); @@ -162,7 +166,8 @@ TEST(HierarchySceneBindingTests, ReparentAndMoveToRootOperateOnRealScene) { MakeEditorGameObjectItemId(parentB->GetID()))); EXPECT_EQ(child->GetParent(), parentB); - HierarchyModel model = HierarchyModel::BuildFromScene(scene); + HierarchyModel model = + HierarchyModel::BuildFromSnapshot(backend->BuildHierarchySnapshot()); const HierarchyNode* parentBNode = model.FindNode(MakeEditorGameObjectItemId(parentB->GetID())); ASSERT_NE(parentBNode, nullptr); @@ -173,7 +178,7 @@ TEST(HierarchySceneBindingTests, ReparentAndMoveToRootOperateOnRealScene) { MakeEditorGameObjectItemId(child->GetID()))); EXPECT_EQ(child->GetParent(), nullptr); - model = HierarchyModel::BuildFromScene(scene); + model = HierarchyModel::BuildFromSnapshot(backend->BuildHierarchySnapshot()); const auto roots = scene->GetRootGameObjects(); EXPECT_EQ(roots.size(), 3u); } @@ -196,11 +201,12 @@ TEST(HierarchySceneBindingTests, EnsureStartupSceneLoadsMainSceneAndSetsActive) backend->EnsureStartupScene(projectRoot.Root()); EXPECT_TRUE(result.ready); EXPECT_TRUE(result.loadedFromDisk); - ASSERT_NE(backend->GetActiveScene(), nullptr); - EXPECT_EQ(backend->GetActiveScene()->GetName(), "Main"); + Scene* const activeScene = SceneManager::Get().GetActiveScene(); + ASSERT_NE(activeScene, nullptr); + EXPECT_EQ(activeScene->GetName(), "Main"); const HierarchyModel model = - HierarchyModel::BuildFromScene(backend->GetActiveScene()); + HierarchyModel::BuildFromSnapshot(backend->BuildHierarchySnapshot()); EXPECT_FALSE(model.Empty()); SceneManager& sceneManager = SceneManager::Get(); diff --git a/tests/UI/Editor/unit/test_inspector_presentation.cpp b/tests/UI/Editor/unit/test_inspector_presentation.cpp index 3e5c2e62..f9037a05 100644 --- a/tests/UI/Editor/unit/test_inspector_presentation.cpp +++ b/tests/UI/Editor/unit/test_inspector_presentation.cpp @@ -1,5 +1,6 @@ #include "Inspector/Components/IInspectorComponentEditor.h" #include "Inspector/Components/InspectorComponentEditorRegistry.h" +#include "Inspector/InspectorFieldValueApplier.h" #include "Inspector/InspectorPresentationModel.h" #include "Inspector/InspectorSubject.h" #include "Scene/EditorSceneRuntime.h" @@ -118,6 +119,20 @@ const UIEditorPropertyGridField* FindField( return nullptr; } +const UIEditorPropertyGridField* FindFieldById( + const InspectorPresentationModel& model, + std::string_view fieldId) { + for (const UIEditorPropertyGridSection& section : model.sections) { + for (const UIEditorPropertyGridField& field : section.fields) { + if (field.fieldId == fieldId) { + return &field; + } + } + } + + return nullptr; +} + const InspectorPresentationComponentBinding* FindBinding( const InspectorPresentationModel& model, std::string_view typeName) { @@ -201,7 +216,7 @@ TEST(InspectorPresentationModelTests, SceneObjectSubjectBuildsRegisteredComponen EditorSceneRuntime runtime = {}; BindEngineSceneBackend(runtime); ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); - Scene* scene = runtime.GetActiveScene(); + Scene* scene = SceneManager::Get().GetActiveScene(); ASSERT_NE(scene, nullptr); GameObject* parent = scene->Find("Parent"); ASSERT_NE(parent, nullptr); @@ -273,7 +288,7 @@ TEST(InspectorPresentationModelTests, CameraSkyboxMaterialBuildsAssetField) { EditorSceneRuntime runtime = {}; BindEngineSceneBackend(runtime); ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); - Scene* scene = runtime.GetActiveScene(); + Scene* scene = SceneManager::Get().GetActiveScene(); ASSERT_NE(scene, nullptr); GameObject* parent = scene->Find("Parent"); ASSERT_NE(parent, nullptr); @@ -302,5 +317,57 @@ TEST(InspectorPresentationModelTests, CameraSkyboxMaterialBuildsAssetField) { "Skybox.mat"); } +TEST(InspectorPresentationModelTests, BoundFieldValueApplierKeepsComponentViewAliveDuringApply) { + ScopedSceneManagerReset reset = {}; + TemporaryProjectRoot projectRoot = {}; + SaveMainScene(projectRoot); + + EditorSceneRuntime runtime = {}; + BindEngineSceneBackend(runtime); + ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); + Scene* scene = SceneManager::Get().GetActiveScene(); + ASSERT_NE(scene, nullptr); + GameObject* parent = scene->Find("Parent"); + ASSERT_NE(parent, nullptr); + ASSERT_TRUE(runtime.SetSelection(parent->GetID())); + + const InspectorSubject subject = + BuildInspectorSubject(EditorSession{}, runtime); + ASSERT_EQ(subject.kind, InspectorSubjectKind::SceneObject); + + const InspectorPresentationModel model = + BuildInspectorPresentationModel( + subject, + runtime, + InspectorComponentEditorRegistry::Get()); + const auto* transformBinding = FindBinding(model, "Transform"); + ASSERT_NE(transformBinding, nullptr); + ASSERT_FALSE(transformBinding->fieldIds.empty()); + + const UIEditorPropertyGridField* originalField = + FindFieldById(model, transformBinding->fieldIds.front()); + ASSERT_NE(originalField, nullptr); + ASSERT_EQ(originalField->kind, Widgets::UIEditorPropertyGridFieldKind::Vector3); + + UIEditorPropertyGridField editedField = *originalField; + editedField.vector3Value.values = { 8.0, 9.0, 10.0 }; + + bool applied = false; + EXPECT_NO_THROW( + applied = ApplyInspectorComponentBoundFieldValue( + runtime, + subject.sceneObject, + *transformBinding, + editedField)); + ASSERT_TRUE(applied); + + const auto* transform = parent->GetTransform(); + ASSERT_NE(transform, nullptr); + const auto position = transform->GetLocalPosition(); + EXPECT_FLOAT_EQ(position.x, 8.0f); + EXPECT_FLOAT_EQ(position.y, 9.0f); + EXPECT_FLOAT_EQ(position.z, 10.0f); +} + } // namespace } // namespace XCEngine::UI::Editor::App diff --git a/tests/UI/Editor/unit/test_scene_viewport_render_plan.cpp b/tests/UI/Editor/unit/test_scene_viewport_render_plan.cpp index 5ed426bb..1bdd635f 100644 --- a/tests/UI/Editor/unit/test_scene_viewport_render_plan.cpp +++ b/tests/UI/Editor/unit/test_scene_viewport_render_plan.cpp @@ -1,8 +1,5 @@ #include "Viewport/SceneViewportRenderPlan.h" -#include -#include - #include namespace { @@ -72,17 +69,17 @@ public: } }; -SceneViewportRenderRequest CreateValidRequest( - XCEngine::Components::GameObject& cameraObject) { - auto* camera = - cameraObject.AddComponent(); - EXPECT_NE(camera, nullptr); - EXPECT_NE(cameraObject.GetTransform(), nullptr); - cameraObject.GetTransform()->SetPosition( - XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f)); +SceneViewportRenderRequest CreateValidRequest() { SceneViewportRenderRequest request = {}; - request.camera = camera; - request.orbitDistance = 9.0f; + request.camera.valid = true; + request.camera.position = XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f); + request.camera.forward = XCEngine::Math::Vector3::Forward(); + request.camera.right = XCEngine::Math::Vector3::Right(); + request.camera.up = XCEngine::Math::Vector3::Up(); + request.camera.verticalFovDegrees = 60.0f; + request.camera.nearClipPlane = 0.03f; + request.camera.farClipPlane = 2000.0f; + request.camera.orbitDistance = 9.0f; return request; } @@ -98,8 +95,7 @@ TEST(SceneViewportRenderPlanTests, BuildRenderPlanCreatesOutlinePassWhenSelectio targets.selectionMaskView = &selectionMaskView; targets.selectionMaskShaderView = &selectionMaskShaderView; - XCEngine::Components::GameObject cameraObject("SceneCamera"); - SceneViewportRenderRequest request = CreateValidRequest(cameraObject); + SceneViewportRenderRequest request = CreateValidRequest(); request.selectedObjectIds = { 7u, 11u }; std::size_t gridFactoryCallCount = 0u; @@ -136,8 +132,7 @@ TEST(SceneViewportRenderPlanTests, BuildRenderPlanCreatesOutlinePassWhenSelectio TEST(SceneViewportRenderPlanTests, BuildRenderPlanWarnsWhenSelectionResourcesAreUnavailable) { ViewportRenderTargets targets = {}; - XCEngine::Components::GameObject cameraObject("SceneCamera"); - SceneViewportRenderRequest request = CreateValidRequest(cameraObject); + SceneViewportRenderRequest request = CreateValidRequest(); request.selectedObjectIds = { 42u }; std::size_t gridFactoryCallCount = 0u; @@ -167,8 +162,7 @@ TEST(SceneViewportRenderPlanTests, BuildRenderPlanWarnsWhenSelectionResourcesAre } TEST(SceneViewportRenderPlanTests, BuildSceneViewportGridPassDataCopiesCameraTransformAndLens) { - XCEngine::Components::GameObject cameraObject("SceneCamera"); - SceneViewportRenderRequest request = CreateValidRequest(cameraObject); + SceneViewportRenderRequest request = CreateValidRequest(); const SceneViewportGridPassData gridData = BuildSceneViewportGridPassData(request); @@ -177,9 +171,9 @@ TEST(SceneViewportRenderPlanTests, BuildSceneViewportGridPassDataCopiesCameraTra EXPECT_FLOAT_EQ(gridData.cameraPosition.x, 1.0f); EXPECT_FLOAT_EQ(gridData.cameraPosition.y, 2.0f); EXPECT_FLOAT_EQ(gridData.cameraPosition.z, 3.0f); - EXPECT_FLOAT_EQ(gridData.verticalFovDegrees, request.camera->GetFieldOfView()); - EXPECT_FLOAT_EQ(gridData.nearClipPlane, request.camera->GetNearClipPlane()); - EXPECT_FLOAT_EQ(gridData.farClipPlane, request.camera->GetFarClipPlane()); + EXPECT_FLOAT_EQ(gridData.verticalFovDegrees, request.camera.verticalFovDegrees); + EXPECT_FLOAT_EQ(gridData.nearClipPlane, request.camera.nearClipPlane); + EXPECT_FLOAT_EQ(gridData.farClipPlane, request.camera.farClipPlane); EXPECT_FLOAT_EQ(gridData.orbitDistance, 9.0f); } @@ -204,8 +198,7 @@ TEST(SceneViewportRenderPlanTests, ApplyRenderPlanAttachesPassesAndMarksRenderSt targets.objectIdState = ResourceStates::Common; targets.selectionMaskState = ResourceStates::Common; - XCEngine::Components::GameObject cameraObject("SceneCamera"); - SceneViewportRenderRequest request = CreateValidRequest(cameraObject); + SceneViewportRenderRequest request = CreateValidRequest(); request.selectedObjectIds = { 24u }; auto result = BuildSceneViewportRenderPlan( diff --git a/tests/UI/Editor/unit/test_scene_viewport_runtime.cpp b/tests/UI/Editor/unit/test_scene_viewport_runtime.cpp index 9f7b1239..fff653e5 100644 --- a/tests/UI/Editor/unit/test_scene_viewport_runtime.cpp +++ b/tests/UI/Editor/unit/test_scene_viewport_runtime.cpp @@ -23,8 +23,11 @@ #include #include +#include #include #include +#include +#include namespace XCEngine::UI::Editor::App { namespace { @@ -113,6 +116,10 @@ void BindEngineSceneBackend(EditorSceneRuntime& runtime) { ::XCEngine::Resources::ResourceManager::Get())); } +Scene* GetLoadedActiveScene() { + return SceneManager::Get().GetActiveScene(); +} + UIInputEvent MakePointerEvent( UIInputEventType type, float x, @@ -208,6 +215,27 @@ const EditorSceneComponentDescriptor* FindComponentDescriptor( return nullptr; } +std::optional FindHoveredTransformGizmoPoint( + SceneViewportTransformGizmo& gizmo, + EditorSceneRuntime& runtime, + const UIRect& viewportRect) { + for (float y = viewportRect.y + 8.0f; + y < viewportRect.y + viewportRect.height - 8.0f; + y += 6.0f) { + for (float x = viewportRect.x + 8.0f; + x < viewportRect.x + viewportRect.width - 8.0f; + x += 6.0f) { + const UIPoint point(x, y); + gizmo.Refresh(runtime, viewportRect, point, true); + if (gizmo.IsHoveringHandle()) { + return point; + } + } + } + + return std::nullopt; +} + TEST(SceneViewportRuntimeTests, ApplySceneViewportCameraInputUpdatesCameraTransform) { ScopedSceneManagerReset reset = {}; TemporaryProjectRoot projectRoot = {}; @@ -217,22 +245,21 @@ TEST(SceneViewportRuntimeTests, ApplySceneViewportCameraInputUpdatesCameraTransf BindEngineSceneBackend(runtime); ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); - auto* camera = runtime.GetSceneViewCamera(); - ASSERT_NE(camera, nullptr); - auto* transform = camera->GetGameObject()->GetTransform(); - ASSERT_NE(transform, nullptr); - - const Math::Vector3 before = transform->GetPosition(); + const EditorSceneCameraSnapshot before = + runtime.BuildSceneViewCameraSnapshot(); + ASSERT_TRUE(before.valid); SceneViewportCameraInputState input = {}; input.viewportHeight = 720.0f; input.zoomDelta = 1.0f; runtime.ApplySceneViewportCameraInput(input); - const Math::Vector3 after = transform->GetPosition(); - EXPECT_NE(before.x, after.x); - EXPECT_NE(before.y, after.y); - EXPECT_NE(before.z, after.z); + const EditorSceneCameraSnapshot after = + runtime.BuildSceneViewCameraSnapshot(); + ASSERT_TRUE(after.valid); + EXPECT_NE(before.position.x, after.position.x); + EXPECT_NE(before.position.y, after.position.y); + EXPECT_NE(before.position.z, after.position.z); } TEST(SceneViewportRuntimeTests, FocusSceneSelectionRepositionsCameraAroundSelectedObject) { @@ -243,25 +270,25 @@ TEST(SceneViewportRuntimeTests, FocusSceneSelectionRepositionsCameraAroundSelect EditorSceneRuntime runtime = {}; BindEngineSceneBackend(runtime); ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); - Scene* scene = runtime.GetActiveScene(); + Scene* scene = GetLoadedActiveScene(); ASSERT_NE(scene, nullptr); GameObject* target = scene->Find("Target"); ASSERT_NE(target, nullptr); ASSERT_TRUE(runtime.SetSelection(target->GetID())); - auto* camera = runtime.GetSceneViewCamera(); - ASSERT_NE(camera, nullptr); - auto* transform = camera->GetGameObject()->GetTransform(); - ASSERT_NE(transform, nullptr); - const Math::Vector3 before = transform->GetPosition(); + const EditorSceneCameraSnapshot before = + runtime.BuildSceneViewCameraSnapshot(); + ASSERT_TRUE(before.valid); ASSERT_TRUE(runtime.FocusSceneSelection()); - const Math::Vector3 after = transform->GetPosition(); - EXPECT_NE(before.x, after.x); - EXPECT_NE(before.y, after.y); - EXPECT_NE(before.z, after.z); + const EditorSceneCameraSnapshot after = + runtime.BuildSceneViewCameraSnapshot(); + ASSERT_TRUE(after.valid); + EXPECT_NE(before.position.x, after.position.x); + EXPECT_NE(before.position.y, after.position.y); + EXPECT_NE(before.position.z, after.position.z); } TEST(SceneViewportRuntimeTests, BuildSceneViewportRenderRequestIncludesSelectedObjectId) { @@ -272,7 +299,7 @@ TEST(SceneViewportRuntimeTests, BuildSceneViewportRenderRequestIncludesSelectedO EditorSceneRuntime runtime = {}; BindEngineSceneBackend(runtime); ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); - Scene* scene = runtime.GetActiveScene(); + Scene* scene = GetLoadedActiveScene(); ASSERT_NE(scene, nullptr); GameObject* target = scene->Find("Target"); @@ -284,7 +311,7 @@ TEST(SceneViewportRuntimeTests, BuildSceneViewportRenderRequestIncludesSelectedO ASSERT_TRUE(request.IsValid()); ASSERT_EQ(request.selectedObjectIds.size(), 1u); EXPECT_EQ(request.selectedObjectIds.front(), target->GetID()); - EXPECT_GT(request.orbitDistance, 0.0f); + EXPECT_GT(request.camera.orbitDistance, 0.0f); EXPECT_FALSE(request.debugSelectionMask); } @@ -296,7 +323,7 @@ TEST(SceneViewportRuntimeTests, SelectedComponentsExposeTransformAndAttachedCame EditorSceneRuntime runtime = {}; BindEngineSceneBackend(runtime); ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); - Scene* scene = runtime.GetActiveScene(); + Scene* scene = GetLoadedActiveScene(); ASSERT_NE(scene, nullptr); GameObject* target = scene->Find("Target"); ASSERT_NE(target, nullptr); @@ -327,7 +354,7 @@ TEST(SceneViewportRuntimeTests, RemoveSelectedComponentDropsRemovableDescriptorB EditorSceneRuntime runtime = {}; BindEngineSceneBackend(runtime); ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); - Scene* scene = runtime.GetActiveScene(); + Scene* scene = GetLoadedActiveScene(); ASSERT_NE(scene, nullptr); GameObject* target = scene->Find("Target"); ASSERT_NE(target, nullptr); @@ -356,7 +383,9 @@ TEST(SceneViewportRuntimeTests, TransformSetterApisWriteLocalValuesOnSelectedTra ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); runtime.EnsureSceneSelection(); - auto* target = const_cast(runtime.GetSelectedGameObject()); + Scene* scene = GetLoadedActiveScene(); + ASSERT_NE(scene, nullptr); + auto* target = scene->Find("Target"); ASSERT_NE(target, nullptr); auto* transform = target->GetTransform(); ASSERT_NE(transform, nullptr); @@ -400,7 +429,7 @@ TEST(SceneViewportRuntimeTests, SelectionStampAdvancesOnSceneSelectionChanges) { const std::uint64_t initialStamp = runtime.GetSelectionStamp(); EXPECT_GT(initialStamp, 0u); - Scene* scene = runtime.GetActiveScene(); + Scene* scene = GetLoadedActiveScene(); ASSERT_NE(scene, nullptr); GameObject* secondary = scene->CreateGameObject("Secondary"); ASSERT_NE(secondary, nullptr); @@ -438,7 +467,7 @@ TEST(SceneViewportRuntimeTests, InspectorSelectionResolverFollowsUnifiedSelectio BuildInspectorSubject(session, runtime); EXPECT_EQ(sceneSubject.kind, InspectorSubjectKind::SceneObject); EXPECT_EQ(sceneSubject.source, InspectorSelectionSource::Scene); - EXPECT_EQ(sceneSubject.sceneObject.displayName, "Target"); + EXPECT_EQ(sceneSubject.sceneObject.object.displayName, "Target"); selectionService.SetProjectSelection( "asset:scene", @@ -494,11 +523,9 @@ TEST(SceneViewportRuntimeTests, RightMouseDragRotatesSceneCameraThroughViewportC BindEngineSceneBackend(runtime); ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); - auto* camera = runtime.GetSceneViewCamera(); - ASSERT_NE(camera, nullptr); - auto* transform = camera->GetGameObject()->GetTransform(); - ASSERT_NE(transform, nullptr); - const Math::Vector3 beforeForward = transform->GetForward(); + const EditorSceneCameraSnapshot before = + runtime.BuildSceneViewCameraSnapshot(); + ASSERT_TRUE(before.valid); SceneViewportController controller = {}; SceneViewportRenderService sceneViewportRenderService = {}; @@ -540,10 +567,12 @@ TEST(SceneViewportRuntimeTests, RightMouseDragRotatesSceneCameraThroughViewportC BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(dragFrame, inputRect, viewportSize)); - const Math::Vector3 afterForward = transform->GetForward(); - EXPECT_NE(beforeForward.x, afterForward.x); - EXPECT_NE(beforeForward.y, afterForward.y); - EXPECT_NE(beforeForward.z, afterForward.z); + const EditorSceneCameraSnapshot after = + runtime.BuildSceneViewCameraSnapshot(); + ASSERT_TRUE(after.valid); + EXPECT_NE(before.forward.x, after.forward.x); + EXPECT_NE(before.forward.y, after.forward.y); + EXPECT_NE(before.forward.z, after.forward.z); } TEST(SceneViewportRuntimeTests, MoveRightInputMovesSceneCameraTowardPositiveCameraRight) { @@ -555,11 +584,9 @@ TEST(SceneViewportRuntimeTests, MoveRightInputMovesSceneCameraTowardPositiveCame BindEngineSceneBackend(runtime); ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); - auto* camera = runtime.GetSceneViewCamera(); - ASSERT_NE(camera, nullptr); - auto* transform = camera->GetGameObject()->GetTransform(); - ASSERT_NE(transform, nullptr); - const Math::Vector3 before = transform->GetPosition(); + const EditorSceneCameraSnapshot before = + runtime.BuildSceneViewCameraSnapshot(); + ASSERT_TRUE(before.valid); SceneViewportCameraInputState input = {}; input.viewportHeight = 720.0f; @@ -567,8 +594,10 @@ TEST(SceneViewportRuntimeTests, MoveRightInputMovesSceneCameraTowardPositiveCame input.moveRight = 1.0f; runtime.ApplySceneViewportCameraInput(input); - const Math::Vector3 after = transform->GetPosition(); - EXPECT_GT(after.x, before.x); + const EditorSceneCameraSnapshot after = + runtime.BuildSceneViewCameraSnapshot(); + ASSERT_TRUE(after.valid); + EXPECT_GT(after.position.x, before.position.x); } TEST(SceneViewportRuntimeTests, MiddleMouseDragPansSceneCameraWithGrabSemantics) { @@ -580,11 +609,9 @@ TEST(SceneViewportRuntimeTests, MiddleMouseDragPansSceneCameraWithGrabSemantics) BindEngineSceneBackend(runtime); ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); - auto* camera = runtime.GetSceneViewCamera(); - ASSERT_NE(camera, nullptr); - auto* transform = camera->GetGameObject()->GetTransform(); - ASSERT_NE(transform, nullptr); - const Math::Vector3 before = transform->GetPosition(); + const EditorSceneCameraSnapshot before = + runtime.BuildSceneViewCameraSnapshot(); + ASSERT_TRUE(before.valid); SceneViewportController controller = {}; SceneViewportRenderService sceneViewportRenderService = {}; @@ -626,8 +653,10 @@ TEST(SceneViewportRuntimeTests, MiddleMouseDragPansSceneCameraWithGrabSemantics) BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(dragFrame, inputRect, viewportSize)); - const Math::Vector3 after = transform->GetPosition(); - EXPECT_LT(after.x, before.x); + const EditorSceneCameraSnapshot after = + runtime.BuildSceneViewCameraSnapshot(); + ASSERT_TRUE(after.valid); + EXPECT_LT(after.position.x, before.position.x); } TEST(SceneViewportRuntimeTests, ViewToolLeftMouseDragPansSceneCameraWithGrabSemantics) { @@ -640,11 +669,9 @@ TEST(SceneViewportRuntimeTests, ViewToolLeftMouseDragPansSceneCameraWithGrabSema ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); runtime.SetToolMode(SceneToolMode::View); - auto* camera = runtime.GetSceneViewCamera(); - ASSERT_NE(camera, nullptr); - auto* transform = camera->GetGameObject()->GetTransform(); - ASSERT_NE(transform, nullptr); - const Math::Vector3 before = transform->GetPosition(); + const EditorSceneCameraSnapshot before = + runtime.BuildSceneViewCameraSnapshot(); + ASSERT_TRUE(before.valid); SceneViewportController controller = {}; SceneViewportRenderService sceneViewportRenderService = {}; @@ -686,8 +713,10 @@ TEST(SceneViewportRuntimeTests, ViewToolLeftMouseDragPansSceneCameraWithGrabSema BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(dragFrame, inputRect, viewportSize)); - const Math::Vector3 after = transform->GetPosition(); - EXPECT_LT(after.x, before.x); + const EditorSceneCameraSnapshot after = + runtime.BuildSceneViewCameraSnapshot(); + ASSERT_TRUE(after.valid); + EXPECT_LT(after.position.x, before.position.x); } TEST(SceneViewportRuntimeTests, MouseWheelUsesSingleNotchNormalizationForSceneZoom) { @@ -699,7 +728,7 @@ TEST(SceneViewportRuntimeTests, MouseWheelUsesSingleNotchNormalizationForSceneZo BindEngineSceneBackend(runtime); ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); const float beforeDistance = - runtime.BuildSceneViewportRenderRequest().orbitDistance; + runtime.BuildSceneViewportRenderRequest().camera.orbitDistance; SceneViewportController controller = {}; SceneViewportRenderService sceneViewportRenderService = {}; @@ -732,7 +761,7 @@ TEST(SceneViewportRuntimeTests, MouseWheelUsesSingleNotchNormalizationForSceneZo BuildSceneComposeFrame(wheelFrame, inputRect, viewportSize)); const float afterDistance = - runtime.BuildSceneViewportRenderRequest().orbitDistance; + runtime.BuildSceneViewportRenderRequest().camera.orbitDistance; EXPECT_LT(afterDistance, beforeDistance); EXPECT_GT(afterDistance, 4.0f); } @@ -924,6 +953,64 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayHandlesCoalescedClickInSingleFra EXPECT_EQ(runtime.GetToolMode(), SceneToolMode::Rotate); } +TEST(SceneViewportRuntimeTests, TranslateGizmoDragAppliesPreviewToSelectedObject) { + ScopedSceneManagerReset reset = {}; + TemporaryProjectRoot projectRoot = {}; + SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f)); + + EditorSceneRuntime runtime = {}; + BindEngineSceneBackend(runtime); + ASSERT_TRUE(runtime.Initialize(projectRoot.Root())); + runtime.SetToolMode(SceneToolMode::Translate); + runtime.EnsureSceneSelection(); + + Scene* scene = GetLoadedActiveScene(); + ASSERT_NE(scene, nullptr); + GameObject* target = scene->Find("Target"); + ASSERT_NE(target, nullptr); + auto* transform = target->GetTransform(); + ASSERT_NE(transform, nullptr); + + SceneViewportTransformGizmo gizmo = {}; + const UIRect viewportRect(0.0f, 0.0f, 640.0f, 360.0f); + const std::optional hoverPoint = + FindHoveredTransformGizmoPoint(gizmo, runtime, viewportRect); + ASSERT_TRUE(hoverPoint.has_value()); + + const std::array dragTargets = { + UIPoint(hoverPoint->x + 40.0f, hoverPoint->y), + UIPoint(hoverPoint->x - 40.0f, hoverPoint->y), + UIPoint(hoverPoint->x, hoverPoint->y + 40.0f), + UIPoint(hoverPoint->x, hoverPoint->y - 40.0f), + UIPoint(hoverPoint->x + 32.0f, hoverPoint->y + 32.0f), + UIPoint(hoverPoint->x - 32.0f, hoverPoint->y - 32.0f) + }; + + bool previewApplied = false; + for (const UIPoint& dragPoint : dragTargets) { + transform->SetPosition(Math::Vector3(0.0f, 0.0f, 0.0f)); + gizmo.Refresh(runtime, viewportRect, hoverPoint.value(), true); + ASSERT_TRUE(gizmo.IsHoveringHandle()); + ASSERT_TRUE(gizmo.TryBeginDrag(runtime)); + + gizmo.Refresh(runtime, viewportRect, dragPoint, true); + ASSERT_TRUE(gizmo.UpdateDrag(runtime)); + + const Math::Vector3 previewPosition = transform->GetPosition(); + if (std::abs(previewPosition.x) > 0.0001f || + std::abs(previewPosition.y) > 0.0001f || + std::abs(previewPosition.z) > 0.0001f) { + previewApplied = true; + gizmo.CancelDrag(runtime); + break; + } + + gizmo.CancelDrag(runtime); + } + + EXPECT_TRUE(previewApplied); +} + TEST(SceneViewportRuntimeTests, SceneViewportRendererDeclaresExplicitAuxiliaryResourceRequirements) { const ViewportResourceRequirements requirements = SceneViewportRenderService::GetViewportResourceRequirements();