#include "Scene/EditorSceneRuntime.h" #include namespace XCEngine::UI::Editor::App { namespace { using ::XCEngine::Math::Quaternion; using ::XCEngine::Math::Vector3; bool NearlyEqual(float lhs, float rhs, float epsilon = 0.0001f) { return std::abs(lhs - rhs) <= epsilon; } bool NearlyEqual(const Vector3& lhs, const Vector3& rhs, float epsilon = 0.0001f) { return NearlyEqual(lhs.x, rhs.x, epsilon) && NearlyEqual(lhs.y, rhs.y, epsilon) && NearlyEqual(lhs.z, rhs.z, epsilon); } bool NearlyEqual(const Quaternion& lhs, const Quaternion& rhs, float epsilon = 0.0001f) { return std::abs(lhs.Dot(rhs)) >= 1.0f - epsilon; } bool TransformSnapshotsMatch( const SceneTransformSnapshot& lhs, const SceneTransformSnapshot& rhs) { return lhs.IsValid() && rhs.IsValid() && lhs.targetId == rhs.targetId && NearlyEqual(lhs.position, rhs.position) && NearlyEqual(lhs.rotation, rhs.rotation) && NearlyEqual(lhs.scale, rhs.scale); } std::uint64_t CombineSceneViewportRequestRevisions( std::uint64_t sceneContentRevision, std::uint64_t sceneViewCameraRevision) { constexpr std::uint64_t kRevisionMixConstant = 0x9e3779b97f4a7c15ull; return sceneContentRevision ^ (sceneViewCameraRevision + kRevisionMixConstant + (sceneContentRevision << 6u) + (sceneContentRevision >> 2u)); } } // namespace std::string_view GetSceneToolModeName(SceneToolMode mode) { switch (mode) { case SceneToolMode::View: return "View"; case SceneToolMode::Translate: return "Move"; case SceneToolMode::Rotate: return "Rotate"; case SceneToolMode::Scale: return "Scale"; case SceneToolMode::Transform: return "Transform"; default: return "Unknown"; } } std::string_view GetSceneToolSpaceModeName(SceneToolSpaceMode mode) { switch (mode) { case SceneToolSpaceMode::World: return "World"; case SceneToolSpaceMode::Local: return "Local"; default: return "Unknown"; } } std::string_view GetSceneToolPivotModeName(SceneToolPivotMode mode) { switch (mode) { case SceneToolPivotMode::Pivot: return "Pivot"; case SceneToolPivotMode::Center: return "Center"; default: return "Unknown"; } } std::string_view GetSceneToolHandleName(SceneToolHandle handle) { switch (handle) { case SceneToolHandle::AxisX: return "AxisX"; case SceneToolHandle::AxisY: return "AxisY"; case SceneToolHandle::AxisZ: return "AxisZ"; case SceneToolHandle::None: default: return "None"; } } std::string_view GetSceneToolInteractionLockName(SceneToolInteractionLock lock) { switch (lock) { case SceneToolInteractionLock::TransformDrag: return "TransformDrag"; case SceneToolInteractionLock::None: default: return "None"; } } void EditorSceneRuntime::Reset() { m_projectRoot.clear(); m_startupSceneResult = {}; m_ownedSelectionService.ClearSelection(); m_selectionService = &m_ownedSelectionService; m_sceneViewCameraController.Reset(); m_toolState = {}; ResetTransformEditHistory(); m_inspectorRevision = 0u; m_sceneContentRevision = 0u; m_sceneViewCameraRevision = 0u; } void EditorSceneRuntime::SetBackend(std::unique_ptr backend) { m_backend = std::move(backend); Reset(); } bool EditorSceneRuntime::Initialize(const std::filesystem::path& projectRoot) { Reset(); if (m_backend == nullptr) { return false; } m_projectRoot = projectRoot; m_startupSceneResult = m_backend->EnsureStartupScene(projectRoot); RefreshScene(); return m_startupSceneResult.ready; } void EditorSceneRuntime::BindSelectionService( EditorSelectionService* selectionService) { m_selectionService = selectionService != nullptr ? selectionService : &m_ownedSelectionService; } void EditorSceneRuntime::RefreshScene() { if (HasHierarchySelection()) { if (!HasValidSelection()) { ClearSelection(); } else { RevalidateSelection(); } } else if (m_toolState.dragState.active) { ResetToolInteractionState(); } ClearInvalidToolInteractionState(); } void EditorSceneRuntime::EnsureSceneSelection() { if (HasValidSelection() || SelectionService().HasSelectionKind(EditorSelectionKind::ProjectItem)) { return; } SelectFirstAvailableGameObject(); } const EditorStartupSceneResult& EditorSceneRuntime::GetStartupResult() const { return m_startupSceneResult; } EditorSceneHierarchySnapshot EditorSceneRuntime::BuildHierarchySnapshot() const { return m_backend != nullptr ? m_backend->BuildHierarchySnapshot() : EditorSceneHierarchySnapshot{}; } float EditorSceneRuntime::GetSceneViewOrbitDistance() const { return m_sceneViewCameraController.GetDistance(); } SceneViewportRenderRequest EditorSceneRuntime::BuildSceneViewportRenderRequest() { SceneViewportRenderRequest request = {}; request.camera = BuildSceneViewCameraSnapshot(); request.requestRevision = BuildSceneViewportRequestRevision(); if (const std::optional selectedId = GetSelectedObjectId(); selectedId.has_value()) { request.selectedObjectIds.push_back(selectedId.value()); if (m_backend != nullptr) { request.selectedHelpers = m_backend->BuildViewportHelperSnapshots(selectedId.value()); } } return request; } EditorSceneCameraSnapshot EditorSceneRuntime::BuildSceneViewCameraSnapshot() const { EditorSceneCameraSnapshot snapshot = {}; snapshot.valid = true; snapshot.position = m_sceneViewCameraController.GetPosition(); snapshot.forward = m_sceneViewCameraController.GetForward(); snapshot.right = m_sceneViewCameraController.GetRight(); snapshot.up = m_sceneViewCameraController.GetUp(); snapshot.verticalFovDegrees = 60.0f; snapshot.nearClipPlane = 0.03f; snapshot.farClipPlane = 2000.0f; snapshot.orbitDistance = GetSceneViewOrbitDistance(); return snapshot; } std::vector EditorSceneRuntime::BuildSceneViewportIconSnapshots() const { return m_backend != nullptr ? m_backend->BuildViewportIconSnapshots() : std::vector{}; } std::optional EditorSceneRuntime::BuildSceneViewportSelectionSnapshot() const { const std::optional selectedId = GetSelectedObjectId(); if (!selectedId.has_value() || m_backend == nullptr) { return std::nullopt; } return m_backend->BuildViewportSelectionSnapshot(selectedId.value()); } void EditorSceneRuntime::ApplySceneViewportCameraInput( const SceneViewportCameraInputState& input) { m_sceneViewCameraController.ApplyInput(input); IncrementSceneViewCameraRevision(); } bool EditorSceneRuntime::FocusSceneSelection() { SceneTransformSnapshot snapshot = {}; if (!CaptureSelectedTransformSnapshot(snapshot)) { return false; } m_sceneViewCameraController.Focus(snapshot.position); IncrementSceneViewCameraRevision(); return true; } bool EditorSceneRuntime::HasSceneSelection() const { return HasValidSelection(); } std::optional EditorSceneRuntime::GetSelectedObjectId() const { if (!HasHierarchySelection()) { return std::nullopt; } return ParseEditorGameObjectItemId(SelectionService().GetSelection().itemId); } std::string EditorSceneRuntime::GetSelectedItemId() const { const std::optional selectedId = GetSelectedObjectId(); return selectedId.has_value() ? MakeEditorGameObjectItemId(selectedId.value()) : std::string(); } std::string EditorSceneRuntime::GetSelectedDisplayName() const { const std::optional snapshot = GetSelectedObjectSnapshot(); return snapshot.has_value() ? snapshot->displayName : std::string(); } std::optional EditorSceneRuntime::GetSelectedObjectSnapshot() const { const std::string itemId = GetSelectedItemId(); return itemId.empty() ? std::nullopt : GetObjectSnapshot(itemId); } std::vector EditorSceneRuntime::GetSelectedComponents() const { const std::string selectedItemId = GetSelectedItemId(); return m_backend != nullptr && !selectedItemId.empty() ? m_backend->GetComponents(selectedItemId) : std::vector{}; } std::uint64_t EditorSceneRuntime::GetSelectionStamp() const { return SelectionService().GetStamp(); } std::uint64_t EditorSceneRuntime::GetInspectorRevision() const { return m_inspectorRevision; } bool EditorSceneRuntime::SetSelection(std::string_view itemId) { const std::optional gameObjectId = ParseEditorGameObjectItemId(itemId); if (!gameObjectId.has_value()) { return false; } return SetSelection(gameObjectId.value()); } bool EditorSceneRuntime::SetSelection(EditorSceneObjectId id) { if (id == kInvalidEditorSceneObjectId) { return false; } const std::optional snapshot = GetObjectSnapshot(MakeEditorGameObjectItemId(id)); if (!snapshot.has_value()) { return false; } const std::optional previousId = GetSelectedObjectId(); const bool changed = !previousId.has_value() || previousId.value() != id || !HasHierarchySelection(); if (changed) { ResetToolInteractionState(); } SelectionService().SetHierarchySelection( MakeEditorGameObjectItemId(id), snapshot->displayName); ClearInvalidToolInteractionState(); return changed; } void EditorSceneRuntime::ClearSelection() { ResetToolInteractionState(); SelectionService().ClearSelection(); } bool EditorSceneRuntime::OpenSceneAsset(const std::filesystem::path& scenePath) { if (m_backend == nullptr || !m_backend->OpenSceneAsset(scenePath)) { return false; } m_startupSceneResult.ready = true; m_startupSceneResult.loadedFromDisk = true; m_startupSceneResult.scenePath = scenePath; m_startupSceneResult.sceneName = scenePath.stem().string(); ResetTransformEditHistory(); ResetToolInteractionState(); SelectionService().ClearSelection(); IncrementInspectorRevision(); IncrementSceneContentRevision(); RefreshScene(); EnsureSceneSelection(); return true; } bool EditorSceneRuntime::RenameGameObject( std::string_view itemId, std::string_view newName) { const bool renamed = m_backend != nullptr && m_backend->RenameGameObject(itemId, newName); if (renamed) { IncrementInspectorRevision(); IncrementSceneContentRevision(); } RefreshScene(); return renamed; } bool EditorSceneRuntime::DeleteGameObject(std::string_view itemId) { ResetTransformEditHistory(); const bool deleted = m_backend != nullptr && m_backend->DeleteGameObject(itemId); if (deleted) { IncrementInspectorRevision(); IncrementSceneContentRevision(); } RefreshScene(); EnsureSceneSelection(); return deleted; } std::string EditorSceneRuntime::DuplicateGameObject(std::string_view itemId) { ResetTransformEditHistory(); const std::string duplicatedItemId = m_backend != nullptr ? m_backend->DuplicateGameObject(itemId) : std::string(); if (!duplicatedItemId.empty()) { IncrementInspectorRevision(); IncrementSceneContentRevision(); SetSelection(duplicatedItemId); } else { RefreshScene(); } return duplicatedItemId; } bool EditorSceneRuntime::ReparentGameObject( std::string_view itemId, std::string_view parentItemId) { ResetTransformEditHistory(); const bool reparented = m_backend != nullptr && m_backend->ReparentGameObject(itemId, parentItemId); if (reparented) { IncrementInspectorRevision(); IncrementSceneContentRevision(); } RefreshScene(); return reparented; } bool EditorSceneRuntime::MoveGameObjectBefore( std::string_view itemId, std::string_view targetItemId) { ResetTransformEditHistory(); const bool moved = m_backend != nullptr && m_backend->MoveGameObjectBefore(itemId, targetItemId); if (moved) { IncrementInspectorRevision(); IncrementSceneContentRevision(); } RefreshScene(); return moved; } bool EditorSceneRuntime::MoveGameObjectAfter( std::string_view itemId, std::string_view targetItemId) { ResetTransformEditHistory(); const bool moved = m_backend != nullptr && m_backend->MoveGameObjectAfter(itemId, targetItemId); if (moved) { IncrementInspectorRevision(); IncrementSceneContentRevision(); } RefreshScene(); return moved; } bool EditorSceneRuntime::MoveGameObjectToRoot(std::string_view itemId) { ResetTransformEditHistory(); const bool moved = m_backend != nullptr && m_backend->MoveGameObjectToRoot(itemId); if (moved) { IncrementInspectorRevision(); IncrementSceneContentRevision(); } RefreshScene(); return moved; } bool EditorSceneRuntime::AddComponentToSelectedGameObject( std::string_view componentTypeName) { if (componentTypeName.empty()) { return false; } const std::string selectedItemId = GetSelectedItemId(); if (selectedItemId.empty() || m_backend == nullptr || !m_backend->AddComponent(selectedItemId, componentTypeName)) { return false; } IncrementInspectorRevision(); IncrementSceneContentRevision(); RefreshScene(); return true; } bool EditorSceneRuntime::CanRemoveSelectedComponent( std::string_view componentId) const { const EditorSceneComponentDescriptor descriptor = ResolveSelectedComponentDescriptor(componentId); return descriptor.IsValid() && descriptor.removable; } bool EditorSceneRuntime::RemoveSelectedComponent(std::string_view componentId) { const EditorSceneComponentDescriptor descriptor = ResolveSelectedComponentDescriptor(componentId); if (!descriptor.IsValid() || !descriptor.removable) { return false; } ResetTransformEditHistory(); const bool removed = m_backend != nullptr && m_backend->RemoveComponent(GetSelectedItemId(), componentId); if (removed) { IncrementInspectorRevision(); IncrementSceneContentRevision(); } RefreshScene(); return removed; } bool EditorSceneRuntime::SetSelectedTransformLocalPosition( std::string_view componentId, const Vector3& position) { const EditorSceneComponentDescriptor descriptor = ResolveSelectedComponentDescriptor(componentId); if (m_backend == nullptr || descriptor.typeName != "Transform") { return false; } SceneTransformSnapshot beforeSnapshot = {}; if (!CaptureSelectedTransformSnapshot(beforeSnapshot)) { return false; } if (!m_backend->SetTransformLocalPosition( GetSelectedItemId(), componentId, position)) { return false; } SceneTransformSnapshot afterSnapshot = {}; CaptureSelectedTransformSnapshot(afterSnapshot); if (!TransformSnapshotsMatch(beforeSnapshot, afterSnapshot)) { RecordTransformEdit(beforeSnapshot, afterSnapshot); IncrementInspectorRevision(); IncrementSceneContentRevision(); } return true; } bool EditorSceneRuntime::SetSelectedTransformLocalEulerAngles( std::string_view componentId, const Vector3& eulerAngles) { const EditorSceneComponentDescriptor descriptor = ResolveSelectedComponentDescriptor(componentId); if (m_backend == nullptr || descriptor.typeName != "Transform") { return false; } SceneTransformSnapshot beforeSnapshot = {}; if (!CaptureSelectedTransformSnapshot(beforeSnapshot)) { return false; } if (!m_backend->SetTransformLocalEulerAngles( GetSelectedItemId(), componentId, eulerAngles)) { return false; } SceneTransformSnapshot afterSnapshot = {}; CaptureSelectedTransformSnapshot(afterSnapshot); if (!TransformSnapshotsMatch(beforeSnapshot, afterSnapshot)) { RecordTransformEdit(beforeSnapshot, afterSnapshot); IncrementInspectorRevision(); IncrementSceneContentRevision(); } return true; } bool EditorSceneRuntime::SetSelectedTransformLocalScale( std::string_view componentId, const Vector3& scale) { const EditorSceneComponentDescriptor descriptor = ResolveSelectedComponentDescriptor(componentId); if (m_backend == nullptr || descriptor.typeName != "Transform") { return false; } SceneTransformSnapshot beforeSnapshot = {}; if (!CaptureSelectedTransformSnapshot(beforeSnapshot)) { return false; } if (!m_backend->SetTransformLocalScale( GetSelectedItemId(), componentId, scale)) { return false; } SceneTransformSnapshot afterSnapshot = {}; CaptureSelectedTransformSnapshot(afterSnapshot); if (!TransformSnapshotsMatch(beforeSnapshot, afterSnapshot)) { RecordTransformEdit(beforeSnapshot, afterSnapshot); IncrementInspectorRevision(); IncrementSceneContentRevision(); } return true; } bool EditorSceneRuntime::ApplySelectedComponentMutation( const EditorSceneComponentMutation& mutation) { if (m_backend == nullptr || !mutation.IsValid() || !ResolveSelectedComponentDescriptor(mutation.componentId).IsValid()) { return false; } if (!m_backend->ApplyComponentMutation( GetSelectedItemId(), mutation)) { return false; } IncrementInspectorRevision(); IncrementSceneContentRevision(); return true; } const SceneToolState& EditorSceneRuntime::GetToolState() const { return m_toolState; } SceneToolMode EditorSceneRuntime::GetToolMode() const { return m_toolState.mode; } SceneToolSpaceMode EditorSceneRuntime::GetToolSpaceMode() const { return m_toolState.spaceMode; } SceneToolPivotMode EditorSceneRuntime::GetToolPivotMode() const { return m_toolState.pivotMode; } void EditorSceneRuntime::SetToolMode(SceneToolMode mode) { if (m_toolState.mode == mode) { return; } ResetToolInteractionState(); m_toolState.mode = mode; } void EditorSceneRuntime::SetToolSpaceMode(SceneToolSpaceMode mode) { if (m_toolState.spaceMode == mode) { return; } ResetToolInteractionState(); m_toolState.spaceMode = mode; } void EditorSceneRuntime::SetToolPivotMode(SceneToolPivotMode mode) { if (m_toolState.pivotMode == mode) { return; } ResetToolInteractionState(); m_toolState.pivotMode = mode; } void EditorSceneRuntime::SetHoveredToolHandle(SceneToolHandle handle) { if (m_toolState.dragState.active) { return; } m_toolState.hoveredHandle = handle; } void EditorSceneRuntime::SetToolInteractionLock(SceneToolInteractionLock lock) { m_toolState.interactionLock = lock; } void EditorSceneRuntime::ClearToolInteractionLock() { m_toolState.interactionLock = SceneToolInteractionLock::None; } void EditorSceneRuntime::ResetToolInteractionState() { CancelTransformToolDrag(); ResetToolInteractionTransientState(); } bool EditorSceneRuntime::CaptureSelectedTransformSnapshot( SceneTransformSnapshot& outSnapshot) const { const std::optional selectedId = GetSelectedObjectId(); if (!selectedId.has_value() || m_backend == nullptr) { outSnapshot = {}; return false; } outSnapshot = {}; outSnapshot.targetId = selectedId.value(); if (!m_backend->QueryWorldTransform( selectedId.value(), outSnapshot.position, outSnapshot.rotation, outSnapshot.scale)) { outSnapshot = {}; return false; } outSnapshot.valid = true; return true; } bool EditorSceneRuntime::ApplyTransformSnapshot( const SceneTransformSnapshot& snapshot) { if (!snapshot.IsValid()) { return false; } if (m_backend == nullptr || !m_backend->SetWorldTransform( snapshot.targetId, snapshot.position, snapshot.rotation, snapshot.scale)) { return false; } IncrementInspectorRevision(); IncrementSceneContentRevision(); return true; } bool EditorSceneRuntime::RecordTransformEdit( const SceneTransformSnapshot& before, const SceneTransformSnapshot& after) { if (!before.IsValid() || !after.IsValid() || before.targetId != after.targetId || TransformSnapshotsMatch(before, after)) { return false; } m_transformUndoStack.push_back({ before, after }); m_transformRedoStack.clear(); return true; } bool EditorSceneRuntime::BeginTransformToolDrag( SceneToolHandle handle, const ::XCEngine::UI::UIPoint& startPointerPosition) { if (handle == SceneToolHandle::None || m_toolState.mode == SceneToolMode::View) { return false; } SceneTransformSnapshot snapshot = {}; if (!CaptureSelectedTransformSnapshot(snapshot)) { return false; } CancelTransformToolDrag(); m_toolState.dragState = {}; m_toolState.dragState.active = true; m_toolState.dragState.mode = m_toolState.mode; m_toolState.dragState.handle = handle; m_toolState.dragState.startPointerPosition = startPointerPosition; m_toolState.dragState.initialTransform = snapshot; m_toolState.hoveredHandle = handle; m_toolState.activeHandle = handle; SetToolInteractionLock(SceneToolInteractionLock::TransformDrag); return true; } bool EditorSceneRuntime::HasActiveTransformToolDrag() const { return m_toolState.dragState.active; } const SceneToolDragState* EditorSceneRuntime::GetActiveTransformToolDrag() const { return m_toolState.dragState.active ? &m_toolState.dragState : nullptr; } bool EditorSceneRuntime::ApplyTransformToolPreview( const SceneTransformSnapshot& snapshot) { if (!m_toolState.dragState.active || !snapshot.IsValid() || snapshot.targetId != m_toolState.dragState.initialTransform.targetId) { return false; } return ApplyTransformSnapshot(snapshot); } bool EditorSceneRuntime::ApplyTransformToolWorldPreview( EditorSceneObjectId targetId, const Vector3& position, const Quaternion& rotation) { 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; } if (m_backend == nullptr || !m_backend->SetWorldPositionRotation(targetId, position, rotation)) { return false; } IncrementInspectorRevision(); IncrementSceneContentRevision(); return true; } bool EditorSceneRuntime::ApplyTransformToolLocalScalePreview( EditorSceneObjectId targetId, const Vector3& localScale) { 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; } if (m_backend == nullptr || !m_backend->SetObjectLocalScale(targetId, localScale)) { return false; } IncrementInspectorRevision(); IncrementSceneContentRevision(); return true; } bool EditorSceneRuntime::CommitTransformToolDrag() { if (!m_toolState.dragState.active) { return false; } SceneTransformSnapshot afterSnapshot = {}; const bool captured = CaptureSelectedTransformSnapshot(afterSnapshot); const SceneTransformSnapshot beforeSnapshot = m_toolState.dragState.initialTransform; ResetToolInteractionTransientState(); if (!captured || !afterSnapshot.IsValid() || !beforeSnapshot.IsValid()) { return false; } return RecordTransformEdit(beforeSnapshot, afterSnapshot); } void EditorSceneRuntime::CancelTransformToolDrag() { if (!m_toolState.dragState.active) { return; } const SceneTransformSnapshot snapshot = m_toolState.dragState.initialTransform; if (snapshot.IsValid()) { ApplyTransformSnapshot(snapshot); } ResetToolInteractionTransientState(); } bool EditorSceneRuntime::CanUndoTransformEdit() const { return !m_transformUndoStack.empty(); } bool EditorSceneRuntime::CanRedoTransformEdit() const { return !m_transformRedoStack.empty(); } bool EditorSceneRuntime::UndoTransformEdit() { if (m_transformUndoStack.empty()) { return false; } const TransformEditTransaction transaction = m_transformUndoStack.back(); if (!ApplyTransformSnapshot(transaction.before)) { return false; } m_transformUndoStack.pop_back(); m_transformRedoStack.push_back(transaction); SetSelection(transaction.before.targetId); return true; } bool EditorSceneRuntime::RedoTransformEdit() { if (m_transformRedoStack.empty()) { return false; } const TransformEditTransaction transaction = m_transformRedoStack.back(); if (!ApplyTransformSnapshot(transaction.after)) { return false; } m_transformRedoStack.pop_back(); m_transformUndoStack.push_back(transaction); SetSelection(transaction.after.targetId); return true; } EditorSelectionService& EditorSceneRuntime::SelectionService() { return *m_selectionService; } const EditorSelectionService& EditorSceneRuntime::SelectionService() const { return *m_selectionService; } bool EditorSceneRuntime::HasHierarchySelection() const { return SelectionService().HasSelectionKind(EditorSelectionKind::HierarchyNode); } void EditorSceneRuntime::RevalidateSelection() { const std::optional snapshot = GetSelectedObjectSnapshot(); if (!snapshot.has_value()) { return; } SelectionService().SetHierarchySelection( snapshot->itemId, snapshot->displayName); } bool EditorSceneRuntime::HasValidSelection() const { return GetSelectedObjectSnapshot().has_value(); } std::optional EditorSceneRuntime::GetObjectSnapshot( std::string_view itemId) const { return m_backend != nullptr ? m_backend->GetObjectSnapshot(itemId) : std::nullopt; } EditorSceneComponentDescriptor EditorSceneRuntime::ResolveSelectedComponentDescriptor( std::string_view componentId) const { for (const EditorSceneComponentDescriptor& descriptor : GetSelectedComponents()) { if (descriptor.componentId == componentId) { return descriptor; } } return {}; } bool EditorSceneRuntime::SelectFirstAvailableGameObject() { const EditorSceneHierarchySnapshot snapshot = BuildHierarchySnapshot(); for (const EditorSceneHierarchyNode& root : snapshot.roots) { if (root.itemId.empty()) { continue; } return SetSelection(root.itemId); } if (HasHierarchySelection()) { ClearSelection(); } return false; } void EditorSceneRuntime::ResetTransformEditHistory() { m_transformUndoStack.clear(); m_transformRedoStack.clear(); } void EditorSceneRuntime::IncrementInspectorRevision() { ++m_inspectorRevision; } void EditorSceneRuntime::IncrementSceneContentRevision() { ++m_sceneContentRevision; } void EditorSceneRuntime::IncrementSceneViewCameraRevision() { ++m_sceneViewCameraRevision; } std::uint64_t EditorSceneRuntime::BuildSceneViewportRequestRevision() const { return CombineSceneViewportRequestRevisions( m_sceneContentRevision, m_sceneViewCameraRevision); } void EditorSceneRuntime::ResetToolInteractionTransientState() { m_toolState.hoveredHandle = SceneToolHandle::None; m_toolState.activeHandle = SceneToolHandle::None; ClearToolInteractionLock(); m_toolState.dragState = {}; } void EditorSceneRuntime::ClearInvalidToolInteractionState() { if (!HasSceneSelection()) { ResetToolInteractionTransientState(); return; } if (!m_toolState.dragState.active) { return; } const SceneTransformSnapshot snapshot = m_toolState.dragState.initialTransform; if (!snapshot.IsValid()) { ResetToolInteractionTransientState(); return; } const std::optional selectionSnapshot = m_backend != nullptr ? m_backend->BuildViewportSelectionSnapshot(snapshot.targetId) : std::nullopt; if (!selectionSnapshot.has_value() || !selectionSnapshot->IsValid()) { ResetToolInteractionTransientState(); } } } // namespace XCEngine::UI::Editor::App