#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