1015 lines
27 KiB
C++
1015 lines
27 KiB
C++
#include "Scene/EditorSceneRuntime.h"
|
|
|
|
#include <XCEngine/Scene/Scene.h>
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
|
|
namespace {
|
|
|
|
using ::XCEngine::Math::Quaternion;
|
|
using ::XCEngine::Math::Vector3;
|
|
|
|
bool SelectionStatesMatch(
|
|
const EditorSelectionState& lhs,
|
|
const EditorSelectionState& rhs) {
|
|
return lhs.kind == rhs.kind &&
|
|
lhs.itemId == rhs.itemId &&
|
|
lhs.absolutePath == rhs.absolutePath &&
|
|
lhs.directory == rhs.directory;
|
|
}
|
|
|
|
} // 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_ownedSelectionService.ClearSelection();
|
|
m_selectionService = &m_ownedSelectionService;
|
|
ResetSceneEditHistory();
|
|
m_inspectorRevision = 0u;
|
|
m_sceneContentRevision = 0u;
|
|
}
|
|
|
|
void EditorSceneRuntime::SetBackend(std::unique_ptr<EditorSceneBackend> backend) {
|
|
m_backend = std::move(backend);
|
|
Reset();
|
|
}
|
|
|
|
EditorStartupSceneResult EditorSceneRuntime::Initialize(
|
|
const std::filesystem::path& projectRoot) {
|
|
Reset();
|
|
if (m_backend == nullptr) {
|
|
return {};
|
|
}
|
|
m_projectRoot = projectRoot;
|
|
const EditorStartupSceneResult startupScene =
|
|
m_backend->EnsureStartupScene(projectRoot);
|
|
RefreshScene();
|
|
return startupScene;
|
|
}
|
|
|
|
void EditorSceneRuntime::BindSelectionService(
|
|
EditorSelectionService* selectionService) {
|
|
m_selectionService =
|
|
selectionService != nullptr ? selectionService : &m_ownedSelectionService;
|
|
}
|
|
|
|
void EditorSceneRuntime::RefreshScene() {
|
|
if (HasHierarchySelection()) {
|
|
if (!HasValidSelection()) {
|
|
ClearSelection();
|
|
} else {
|
|
RevalidateSelection();
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorSceneRuntime::EnsureSceneSelection() {
|
|
if (HasValidSelection() ||
|
|
SelectionService().HasSelectionKind(EditorSelectionKind::ProjectItem)) {
|
|
return;
|
|
}
|
|
|
|
SelectFirstAvailableGameObject();
|
|
}
|
|
|
|
EditorSceneHierarchySnapshot EditorSceneRuntime::BuildHierarchySnapshot() const {
|
|
return m_backend != nullptr
|
|
? m_backend->BuildHierarchySnapshot()
|
|
: EditorSceneHierarchySnapshot{};
|
|
}
|
|
|
|
std::vector<EditorSceneViewportIconSnapshot>
|
|
EditorSceneRuntime::BuildSceneViewportIconSnapshots() const {
|
|
return m_backend != nullptr
|
|
? m_backend->BuildViewportIconSnapshots()
|
|
: std::vector<EditorSceneViewportIconSnapshot>{};
|
|
}
|
|
|
|
std::optional<EditorSceneViewportSelectionSnapshot>
|
|
EditorSceneRuntime::BuildSceneViewportSelectionSnapshot() const {
|
|
const std::optional<EditorSceneObjectId> selectedId = GetSelectedObjectId();
|
|
if (!selectedId.has_value() || m_backend == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return m_backend->BuildViewportSelectionSnapshot(selectedId.value());
|
|
}
|
|
|
|
std::vector<EditorSceneViewportHelperSnapshot>
|
|
EditorSceneRuntime::BuildSelectedViewportHelperSnapshots() const {
|
|
const std::optional<EditorSceneObjectId> selectedId = GetSelectedObjectId();
|
|
if (!selectedId.has_value() || m_backend == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
return m_backend->BuildViewportHelperSnapshots(selectedId.value());
|
|
}
|
|
|
|
bool EditorSceneRuntime::HasSceneSelection() const {
|
|
return HasValidSelection();
|
|
}
|
|
|
|
std::optional<EditorSceneObjectId> EditorSceneRuntime::GetSelectedObjectId() const {
|
|
if (!HasHierarchySelection()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return ParseEditorGameObjectItemId(SelectionService().GetSelection().itemId);
|
|
}
|
|
|
|
std::string EditorSceneRuntime::GetSelectedItemId() const {
|
|
const std::optional<EditorSceneObjectId> selectedId = GetSelectedObjectId();
|
|
return selectedId.has_value()
|
|
? MakeEditorGameObjectItemId(selectedId.value())
|
|
: std::string();
|
|
}
|
|
|
|
std::string EditorSceneRuntime::GetSelectedDisplayName() const {
|
|
const std::optional<EditorSceneObjectSnapshot> snapshot =
|
|
GetSelectedObjectSnapshot();
|
|
return snapshot.has_value()
|
|
? snapshot->displayName
|
|
: std::string();
|
|
}
|
|
|
|
std::optional<EditorSceneObjectSnapshot>
|
|
EditorSceneRuntime::GetSelectedObjectSnapshot() const {
|
|
const std::string itemId = GetSelectedItemId();
|
|
return itemId.empty()
|
|
? std::nullopt
|
|
: GetObjectSnapshot(itemId);
|
|
}
|
|
|
|
std::vector<EditorSceneComponentDescriptor> EditorSceneRuntime::GetSelectedComponents() const {
|
|
const std::string selectedItemId = GetSelectedItemId();
|
|
return m_backend != nullptr && !selectedItemId.empty()
|
|
? m_backend->GetComponents(selectedItemId)
|
|
: std::vector<EditorSceneComponentDescriptor>{};
|
|
}
|
|
|
|
std::uint64_t EditorSceneRuntime::GetSelectionStamp() const {
|
|
return SelectionService().GetStamp();
|
|
}
|
|
|
|
std::uint64_t EditorSceneRuntime::GetInspectorRevision() const {
|
|
return m_inspectorRevision;
|
|
}
|
|
|
|
std::uint64_t EditorSceneRuntime::GetSceneContentRevision() const {
|
|
return m_sceneContentRevision;
|
|
}
|
|
|
|
bool EditorSceneRuntime::SetSelection(std::string_view itemId) {
|
|
const std::optional<EditorSceneObjectId> 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<EditorSceneObjectSnapshot> snapshot =
|
|
GetObjectSnapshot(MakeEditorGameObjectItemId(id));
|
|
if (!snapshot.has_value()) {
|
|
return false;
|
|
}
|
|
|
|
const std::optional<EditorSceneObjectId> previousId = GetSelectedObjectId();
|
|
const bool changed =
|
|
!previousId.has_value() ||
|
|
previousId.value() != id ||
|
|
!HasHierarchySelection();
|
|
SelectionService().SetHierarchySelection(
|
|
MakeEditorGameObjectItemId(id),
|
|
snapshot->displayName);
|
|
return changed;
|
|
}
|
|
|
|
void EditorSceneRuntime::ClearSelection() {
|
|
SelectionService().ClearSelection();
|
|
}
|
|
|
|
bool EditorSceneRuntime::NewScene(std::string_view sceneName) {
|
|
if (m_backend == nullptr || !m_backend->NewScene(sceneName)) {
|
|
return false;
|
|
}
|
|
|
|
ResetSceneEditHistory();
|
|
SelectionService().ClearSelection();
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
RefreshScene();
|
|
EnsureSceneSelection();
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::OpenSceneAsset(const std::filesystem::path& scenePath) {
|
|
if (m_backend == nullptr || !m_backend->OpenSceneAsset(scenePath)) {
|
|
return false;
|
|
}
|
|
|
|
ResetSceneEditHistory();
|
|
SelectionService().ClearSelection();
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
RefreshScene();
|
|
EnsureSceneSelection();
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::SaveScene(const std::filesystem::path& scenePath) {
|
|
return m_backend != nullptr && m_backend->SaveActiveScene(scenePath);
|
|
}
|
|
|
|
::XCEngine::Components::Scene* EditorSceneRuntime::GetActiveScene() const {
|
|
return m_backend != nullptr ? m_backend->GetActiveScene() : nullptr;
|
|
}
|
|
|
|
std::string EditorSceneRuntime::GetActiveSceneName() const {
|
|
if (const ::XCEngine::Components::Scene* activeScene = GetActiveScene();
|
|
activeScene != nullptr) {
|
|
return activeScene->GetName();
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
std::unique_ptr<EditorScenePlaySession> EditorSceneRuntime::BeginPlaySession() {
|
|
return m_backend != nullptr
|
|
? m_backend->BeginPlaySession()
|
|
: nullptr;
|
|
}
|
|
|
|
bool EditorSceneRuntime::RenameGameObject(
|
|
std::string_view itemId,
|
|
std::string_view newName) {
|
|
if (!BeginSceneEditTransaction("Rename GameObject")) {
|
|
return false;
|
|
}
|
|
|
|
const bool renamed =
|
|
m_backend != nullptr &&
|
|
m_backend->RenameGameObject(itemId, newName);
|
|
RefreshScene();
|
|
if (!renamed) {
|
|
ClearPendingSceneEditTransaction();
|
|
return false;
|
|
}
|
|
|
|
bool changed = false;
|
|
if (!FinalizePendingSceneEditTransaction(changed)) {
|
|
return false;
|
|
}
|
|
if (changed) {
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::DeleteGameObject(std::string_view itemId) {
|
|
if (!BeginSceneEditTransaction("Delete GameObject")) {
|
|
return false;
|
|
}
|
|
|
|
const bool deleted =
|
|
m_backend != nullptr &&
|
|
m_backend->DeleteGameObject(itemId);
|
|
RefreshScene();
|
|
EnsureSceneSelection();
|
|
if (!deleted) {
|
|
ClearPendingSceneEditTransaction();
|
|
return false;
|
|
}
|
|
|
|
bool changed = false;
|
|
if (!FinalizePendingSceneEditTransaction(changed)) {
|
|
return false;
|
|
}
|
|
if (changed) {
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string EditorSceneRuntime::DuplicateGameObject(std::string_view itemId) {
|
|
if (!BeginSceneEditTransaction("Duplicate GameObject")) {
|
|
return {};
|
|
}
|
|
|
|
const std::string duplicatedItemId =
|
|
m_backend != nullptr
|
|
? m_backend->DuplicateGameObject(itemId)
|
|
: std::string();
|
|
if (duplicatedItemId.empty()) {
|
|
ClearPendingSceneEditTransaction();
|
|
RefreshScene();
|
|
return {};
|
|
}
|
|
|
|
SetSelection(duplicatedItemId);
|
|
RefreshScene();
|
|
bool changed = false;
|
|
if (!FinalizePendingSceneEditTransaction(changed)) {
|
|
return {};
|
|
}
|
|
if (changed) {
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
}
|
|
return duplicatedItemId;
|
|
}
|
|
|
|
bool EditorSceneRuntime::ReparentGameObject(
|
|
std::string_view itemId,
|
|
std::string_view parentItemId) {
|
|
if (!BeginSceneEditTransaction("Reparent GameObject")) {
|
|
return false;
|
|
}
|
|
|
|
const bool reparented =
|
|
m_backend != nullptr &&
|
|
m_backend->ReparentGameObject(itemId, parentItemId);
|
|
RefreshScene();
|
|
if (!reparented) {
|
|
ClearPendingSceneEditTransaction();
|
|
return false;
|
|
}
|
|
|
|
bool changed = false;
|
|
if (!FinalizePendingSceneEditTransaction(changed)) {
|
|
return false;
|
|
}
|
|
if (changed) {
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::MoveGameObjectBefore(
|
|
std::string_view itemId,
|
|
std::string_view targetItemId) {
|
|
if (!BeginSceneEditTransaction("Move GameObject Before")) {
|
|
return false;
|
|
}
|
|
|
|
const bool moved =
|
|
m_backend != nullptr &&
|
|
m_backend->MoveGameObjectBefore(itemId, targetItemId);
|
|
RefreshScene();
|
|
if (!moved) {
|
|
ClearPendingSceneEditTransaction();
|
|
return false;
|
|
}
|
|
|
|
bool changed = false;
|
|
if (!FinalizePendingSceneEditTransaction(changed)) {
|
|
return false;
|
|
}
|
|
if (changed) {
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::MoveGameObjectAfter(
|
|
std::string_view itemId,
|
|
std::string_view targetItemId) {
|
|
if (!BeginSceneEditTransaction("Move GameObject After")) {
|
|
return false;
|
|
}
|
|
|
|
const bool moved =
|
|
m_backend != nullptr &&
|
|
m_backend->MoveGameObjectAfter(itemId, targetItemId);
|
|
RefreshScene();
|
|
if (!moved) {
|
|
ClearPendingSceneEditTransaction();
|
|
return false;
|
|
}
|
|
|
|
bool changed = false;
|
|
if (!FinalizePendingSceneEditTransaction(changed)) {
|
|
return false;
|
|
}
|
|
if (changed) {
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::MoveGameObjectToRoot(std::string_view itemId) {
|
|
if (!BeginSceneEditTransaction("Move GameObject To Root")) {
|
|
return false;
|
|
}
|
|
|
|
const bool moved =
|
|
m_backend != nullptr &&
|
|
m_backend->MoveGameObjectToRoot(itemId);
|
|
RefreshScene();
|
|
if (!moved) {
|
|
ClearPendingSceneEditTransaction();
|
|
return false;
|
|
}
|
|
|
|
bool changed = false;
|
|
if (!FinalizePendingSceneEditTransaction(changed)) {
|
|
return false;
|
|
}
|
|
if (changed) {
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::AddComponentToSelectedGameObject(
|
|
std::string_view componentTypeName) {
|
|
if (componentTypeName.empty()) {
|
|
return false;
|
|
}
|
|
|
|
const std::string selectedItemId = GetSelectedItemId();
|
|
if (selectedItemId.empty() ||
|
|
m_backend == nullptr ||
|
|
!BeginSceneEditTransaction("Add Component")) {
|
|
return false;
|
|
}
|
|
|
|
if (!m_backend->AddComponent(selectedItemId, componentTypeName)) {
|
|
ClearPendingSceneEditTransaction();
|
|
return false;
|
|
}
|
|
|
|
RefreshScene();
|
|
bool changed = false;
|
|
if (!FinalizePendingSceneEditTransaction(changed)) {
|
|
return false;
|
|
}
|
|
if (changed) {
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
}
|
|
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;
|
|
}
|
|
|
|
if (!BeginSceneEditTransaction("Remove Component")) {
|
|
return false;
|
|
}
|
|
|
|
const bool removed =
|
|
m_backend != nullptr &&
|
|
m_backend->RemoveComponent(GetSelectedItemId(), componentId);
|
|
RefreshScene();
|
|
if (!removed) {
|
|
ClearPendingSceneEditTransaction();
|
|
return false;
|
|
}
|
|
|
|
bool changed = false;
|
|
if (!FinalizePendingSceneEditTransaction(changed)) {
|
|
return false;
|
|
}
|
|
if (changed) {
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::SetSelectedTransformLocalPosition(
|
|
std::string_view componentId,
|
|
const Vector3& position) {
|
|
const EditorSceneComponentDescriptor descriptor =
|
|
ResolveSelectedComponentDescriptor(componentId);
|
|
if (m_backend == nullptr ||
|
|
descriptor.typeName != "Transform" ||
|
|
!BeginSceneEditTransaction("Set Transform Position")) {
|
|
return false;
|
|
}
|
|
|
|
if (!m_backend->SetTransformLocalPosition(
|
|
GetSelectedItemId(),
|
|
componentId,
|
|
position)) {
|
|
ClearPendingSceneEditTransaction();
|
|
return false;
|
|
}
|
|
|
|
RefreshScene();
|
|
bool changed = false;
|
|
if (!FinalizePendingSceneEditTransaction(changed)) {
|
|
return false;
|
|
}
|
|
if (changed) {
|
|
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" ||
|
|
!BeginSceneEditTransaction("Set Transform Rotation")) {
|
|
return false;
|
|
}
|
|
|
|
if (!m_backend->SetTransformLocalEulerAngles(
|
|
GetSelectedItemId(),
|
|
componentId,
|
|
eulerAngles)) {
|
|
ClearPendingSceneEditTransaction();
|
|
return false;
|
|
}
|
|
|
|
RefreshScene();
|
|
bool changed = false;
|
|
if (!FinalizePendingSceneEditTransaction(changed)) {
|
|
return false;
|
|
}
|
|
if (changed) {
|
|
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" ||
|
|
!BeginSceneEditTransaction("Set Transform Scale")) {
|
|
return false;
|
|
}
|
|
|
|
if (!m_backend->SetTransformLocalScale(
|
|
GetSelectedItemId(),
|
|
componentId,
|
|
scale)) {
|
|
ClearPendingSceneEditTransaction();
|
|
return false;
|
|
}
|
|
|
|
RefreshScene();
|
|
bool changed = false;
|
|
if (!FinalizePendingSceneEditTransaction(changed)) {
|
|
return false;
|
|
}
|
|
if (changed) {
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::ApplySelectedComponentMutation(
|
|
const EditorSceneComponentMutation& mutation) {
|
|
if (m_backend == nullptr ||
|
|
!mutation.IsValid() ||
|
|
!ResolveSelectedComponentDescriptor(mutation.componentId).IsValid() ||
|
|
!BeginSceneEditTransaction("Apply Component Mutation")) {
|
|
return false;
|
|
}
|
|
|
|
if (!m_backend->ApplyComponentMutation(
|
|
GetSelectedItemId(),
|
|
mutation)) {
|
|
ClearPendingSceneEditTransaction();
|
|
return false;
|
|
}
|
|
|
|
RefreshScene();
|
|
bool changed = false;
|
|
if (!FinalizePendingSceneEditTransaction(changed)) {
|
|
return false;
|
|
}
|
|
if (changed) {
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::CaptureSelectedTransformSnapshot(
|
|
SceneTransformSnapshot& outSnapshot) const {
|
|
const std::optional<EditorSceneObjectId> 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::ApplyTransformToolWorldPreview(
|
|
EditorSceneObjectId targetId,
|
|
const Vector3& position,
|
|
const Quaternion& rotation) {
|
|
if (targetId == kInvalidEditorSceneObjectId ||
|
|
!HasPendingSceneEditTransaction()) {
|
|
return false;
|
|
}
|
|
|
|
const EditorSceneObjectId activeTargetId =
|
|
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 ||
|
|
!HasPendingSceneEditTransaction()) {
|
|
return false;
|
|
}
|
|
|
|
const EditorSceneObjectId activeTargetId =
|
|
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::BeginSceneEditTransaction(std::string_view label) {
|
|
if (m_pendingSceneEditTransaction.has_value()) {
|
|
return false;
|
|
}
|
|
|
|
SceneEditStateSnapshot before = {};
|
|
if (!CaptureSceneEditState(before)) {
|
|
return false;
|
|
}
|
|
|
|
PendingSceneEditTransaction pending = {};
|
|
pending.label = std::string(label);
|
|
pending.before = before;
|
|
m_pendingSceneEditTransaction = pending;
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::HasPendingSceneEditTransaction() const {
|
|
return m_pendingSceneEditTransaction.has_value();
|
|
}
|
|
|
|
bool EditorSceneRuntime::CommitSceneEditTransaction() {
|
|
bool changed = false;
|
|
return FinalizePendingSceneEditTransaction(changed);
|
|
}
|
|
|
|
bool EditorSceneRuntime::CancelSceneEditTransaction() {
|
|
if (!m_pendingSceneEditTransaction.has_value()) {
|
|
return false;
|
|
}
|
|
|
|
const SceneEditStateSnapshot before = m_pendingSceneEditTransaction->before;
|
|
ClearPendingSceneEditTransaction();
|
|
if (!RestoreSceneEditState(before)) {
|
|
return false;
|
|
}
|
|
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::CanUndoSceneEdit() const {
|
|
return !m_pendingSceneEditTransaction.has_value() &&
|
|
!m_sceneUndoStack.empty();
|
|
}
|
|
|
|
bool EditorSceneRuntime::CanRedoSceneEdit() const {
|
|
return !m_pendingSceneEditTransaction.has_value() &&
|
|
!m_sceneRedoStack.empty();
|
|
}
|
|
|
|
bool EditorSceneRuntime::UndoSceneEdit() {
|
|
if (!CanUndoSceneEdit()) {
|
|
return false;
|
|
}
|
|
|
|
const SceneEditTransaction transaction = m_sceneUndoStack.back();
|
|
if (!RestoreSceneEditState(transaction.before)) {
|
|
return false;
|
|
}
|
|
|
|
m_sceneUndoStack.pop_back();
|
|
m_sceneRedoStack.push_back(transaction);
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::RedoSceneEdit() {
|
|
if (!CanRedoSceneEdit()) {
|
|
return false;
|
|
}
|
|
|
|
const SceneEditTransaction transaction = m_sceneRedoStack.back();
|
|
if (!RestoreSceneEditState(transaction.after)) {
|
|
return false;
|
|
}
|
|
|
|
m_sceneRedoStack.pop_back();
|
|
m_sceneUndoStack.push_back(transaction);
|
|
IncrementInspectorRevision();
|
|
IncrementSceneContentRevision();
|
|
return true;
|
|
}
|
|
|
|
void EditorSceneRuntime::NotifyExternalInspectorStateChanged() {
|
|
IncrementInspectorRevision();
|
|
}
|
|
|
|
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<EditorSceneObjectSnapshot> snapshot =
|
|
GetSelectedObjectSnapshot();
|
|
if (!snapshot.has_value()) {
|
|
return;
|
|
}
|
|
|
|
SelectionService().SetHierarchySelection(
|
|
snapshot->itemId,
|
|
snapshot->displayName);
|
|
}
|
|
|
|
bool EditorSceneRuntime::HasValidSelection() const {
|
|
return GetSelectedObjectSnapshot().has_value();
|
|
}
|
|
|
|
std::optional<EditorSceneObjectSnapshot> 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;
|
|
}
|
|
|
|
bool EditorSceneRuntime::CaptureSceneEditState(
|
|
SceneEditStateSnapshot& outSnapshot) const {
|
|
if (m_backend == nullptr) {
|
|
outSnapshot = {};
|
|
return false;
|
|
}
|
|
|
|
outSnapshot = {};
|
|
outSnapshot.sceneSnapshot = m_backend->CaptureActiveSceneSnapshot();
|
|
if (outSnapshot.sceneSnapshot.empty()) {
|
|
outSnapshot = {};
|
|
return false;
|
|
}
|
|
|
|
outSnapshot.selection = SelectionService().GetSelection();
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::RestoreSceneEditState(
|
|
const SceneEditStateSnapshot& snapshot) {
|
|
if (m_backend == nullptr ||
|
|
snapshot.sceneSnapshot.empty() ||
|
|
!m_backend->RestoreActiveSceneSnapshot(snapshot.sceneSnapshot)) {
|
|
return false;
|
|
}
|
|
|
|
switch (snapshot.selection.kind) {
|
|
case EditorSelectionKind::HierarchyNode:
|
|
if (snapshot.selection.itemId.empty() ||
|
|
!SetSelection(snapshot.selection.itemId)) {
|
|
ClearSelection();
|
|
}
|
|
break;
|
|
|
|
case EditorSelectionKind::ProjectItem:
|
|
SelectionService().SetProjectSelection(
|
|
snapshot.selection.itemId,
|
|
snapshot.selection.displayName,
|
|
snapshot.selection.absolutePath,
|
|
snapshot.selection.directory);
|
|
break;
|
|
|
|
case EditorSelectionKind::None:
|
|
default:
|
|
ClearSelection();
|
|
break;
|
|
}
|
|
|
|
RefreshScene();
|
|
return true;
|
|
}
|
|
|
|
void EditorSceneRuntime::ResetSceneEditHistory() {
|
|
m_sceneUndoStack.clear();
|
|
m_sceneRedoStack.clear();
|
|
ClearPendingSceneEditTransaction();
|
|
}
|
|
|
|
void EditorSceneRuntime::ClearPendingSceneEditTransaction() {
|
|
m_pendingSceneEditTransaction.reset();
|
|
}
|
|
|
|
bool EditorSceneRuntime::FinalizePendingSceneEditTransaction(bool& outChanged) {
|
|
outChanged = false;
|
|
if (!m_pendingSceneEditTransaction.has_value()) {
|
|
return false;
|
|
}
|
|
|
|
const PendingSceneEditTransaction pending = *m_pendingSceneEditTransaction;
|
|
ClearPendingSceneEditTransaction();
|
|
|
|
SceneEditStateSnapshot after = {};
|
|
if (!CaptureSceneEditState(after)) {
|
|
return false;
|
|
}
|
|
|
|
outChanged =
|
|
pending.before.sceneSnapshot != after.sceneSnapshot ||
|
|
!SelectionStatesMatch(pending.before.selection, after.selection);
|
|
if (!outChanged) {
|
|
return true;
|
|
}
|
|
|
|
SceneEditTransaction transaction = {};
|
|
transaction.label = pending.label;
|
|
transaction.before = pending.before;
|
|
transaction.after = after;
|
|
m_sceneUndoStack.push_back(transaction);
|
|
m_sceneRedoStack.clear();
|
|
return true;
|
|
}
|
|
|
|
void EditorSceneRuntime::IncrementInspectorRevision() {
|
|
++m_inspectorRevision;
|
|
}
|
|
|
|
void EditorSceneRuntime::IncrementSceneContentRevision() {
|
|
++m_sceneContentRevision;
|
|
}
|
|
|
|
} // namespace XCEngine::UI::Editor::App
|