1013 lines
28 KiB
C++
1013 lines
28 KiB
C++
#include "Scene/EditorSceneRuntime.h"
|
|
|
|
#include <XCEngine/Components/CameraComponent.h>
|
|
#include <XCEngine/Components/Component.h>
|
|
#include <XCEngine/Components/GameObject.h>
|
|
#include <XCEngine/Scene/Scene.h>
|
|
|
|
#include <cmath>
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
|
|
namespace {
|
|
|
|
using ::XCEngine::Components::GameObject;
|
|
using ::XCEngine::Components::Scene;
|
|
using ::XCEngine::Components::Component;
|
|
using ::XCEngine::Math::Quaternion;
|
|
using ::XCEngine::Math::Vector3;
|
|
|
|
std::string ResolveGameObjectDisplayName(const GameObject& gameObject) {
|
|
return gameObject.GetName().empty()
|
|
? std::string("GameObject")
|
|
: gameObject.GetName();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
} // 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_sceneViewCamera = {};
|
|
m_toolState = {};
|
|
ResetTransformEditHistory();
|
|
m_inspectorRevision = 0u;
|
|
}
|
|
|
|
void EditorSceneRuntime::SetBackend(std::unique_ptr<EditorSceneBackend> 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);
|
|
EnsureSceneViewCamera();
|
|
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;
|
|
}
|
|
|
|
Scene* EditorSceneRuntime::GetActiveScene() const {
|
|
return m_backend != nullptr ? m_backend->GetActiveScene() : nullptr;
|
|
}
|
|
|
|
EditorSceneHierarchySnapshot EditorSceneRuntime::BuildHierarchySnapshot() const {
|
|
return m_backend != nullptr
|
|
? m_backend->BuildHierarchySnapshot()
|
|
: EditorSceneHierarchySnapshot{};
|
|
}
|
|
|
|
::XCEngine::Components::CameraComponent* EditorSceneRuntime::GetSceneViewCamera() {
|
|
return EnsureSceneViewCamera() ? m_sceneViewCamera.camera : nullptr;
|
|
}
|
|
|
|
const ::XCEngine::Components::CameraComponent* EditorSceneRuntime::GetSceneViewCamera() const {
|
|
return m_sceneViewCamera.camera;
|
|
}
|
|
|
|
float EditorSceneRuntime::GetSceneViewOrbitDistance() const {
|
|
return m_sceneViewCamera.controller.GetDistance();
|
|
}
|
|
|
|
SceneViewportRenderRequest EditorSceneRuntime::BuildSceneViewportRenderRequest() {
|
|
SceneViewportRenderRequest request = {};
|
|
request.scene = GetActiveScene();
|
|
request.camera = GetSceneViewCamera();
|
|
request.orbitDistance = GetSceneViewOrbitDistance();
|
|
if (const std::optional<GameObject::ID> selectedId = GetSelectedGameObjectId();
|
|
selectedId.has_value()) {
|
|
request.selectedObjectIds.push_back(selectedId.value());
|
|
}
|
|
return request;
|
|
}
|
|
|
|
void EditorSceneRuntime::ApplySceneViewportCameraInput(
|
|
const SceneViewportCameraInputState& input) {
|
|
if (!EnsureSceneViewCamera()) {
|
|
return;
|
|
}
|
|
|
|
m_sceneViewCamera.controller.ApplyInput(input);
|
|
ApplySceneViewCameraController();
|
|
}
|
|
|
|
bool EditorSceneRuntime::FocusSceneSelection() {
|
|
SceneTransformSnapshot snapshot = {};
|
|
if (!CaptureSelectedTransformSnapshot(snapshot) || !EnsureSceneViewCamera()) {
|
|
return false;
|
|
}
|
|
|
|
m_sceneViewCamera.controller.Focus(snapshot.position);
|
|
ApplySceneViewCameraController();
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::HasSceneSelection() const {
|
|
return HasValidSelection();
|
|
}
|
|
|
|
std::optional<GameObject::ID> EditorSceneRuntime::GetSelectedGameObjectId() const {
|
|
if (!HasHierarchySelection()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return ParseEditorGameObjectItemId(SelectionService().GetSelection().itemId);
|
|
}
|
|
|
|
std::string EditorSceneRuntime::GetSelectedItemId() const {
|
|
const std::optional<GameObject::ID> selectedId = GetSelectedGameObjectId();
|
|
return selectedId.has_value()
|
|
? MakeEditorGameObjectItemId(selectedId.value())
|
|
: std::string();
|
|
}
|
|
|
|
std::string EditorSceneRuntime::GetSelectedDisplayName() const {
|
|
const GameObject* gameObject = GetSelectedGameObject();
|
|
return gameObject != nullptr
|
|
? ResolveGameObjectDisplayName(*gameObject)
|
|
: std::string();
|
|
}
|
|
|
|
const GameObject* EditorSceneRuntime::GetSelectedGameObject() const {
|
|
const std::optional<GameObject::ID> selectedId = GetSelectedGameObjectId();
|
|
if (!selectedId.has_value()) {
|
|
return nullptr;
|
|
}
|
|
|
|
Scene* scene = GetActiveScene();
|
|
return scene != nullptr ? scene->FindByID(selectedId.value()) : nullptr;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool EditorSceneRuntime::SetSelection(std::string_view itemId) {
|
|
const std::optional<GameObject::ID> gameObjectId =
|
|
ParseEditorGameObjectItemId(itemId);
|
|
if (!gameObjectId.has_value()) {
|
|
return false;
|
|
}
|
|
|
|
return SetSelection(gameObjectId.value());
|
|
}
|
|
|
|
bool EditorSceneRuntime::SetSelection(GameObject::ID id) {
|
|
if (id == GameObject::INVALID_ID) {
|
|
return false;
|
|
}
|
|
|
|
Scene* scene = GetActiveScene();
|
|
GameObject* gameObject = scene != nullptr ? scene->FindByID(id) : nullptr;
|
|
if (gameObject == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
const std::optional<GameObject::ID> previousId = GetSelectedGameObjectId();
|
|
const bool changed =
|
|
!previousId.has_value() ||
|
|
previousId.value() != id ||
|
|
!HasHierarchySelection();
|
|
if (changed) {
|
|
ResetToolInteractionState();
|
|
}
|
|
SelectionService().SetHierarchySelection(
|
|
MakeEditorGameObjectItemId(id),
|
|
ResolveGameObjectDisplayName(*gameObject));
|
|
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;
|
|
if (Scene* activeScene = GetActiveScene();
|
|
activeScene != nullptr) {
|
|
m_startupSceneResult.sceneName = activeScene->GetName();
|
|
} else {
|
|
m_startupSceneResult.sceneName = scenePath.stem().string();
|
|
}
|
|
|
|
ResetTransformEditHistory();
|
|
ResetToolInteractionState();
|
|
SelectionService().ClearSelection();
|
|
IncrementInspectorRevision();
|
|
RefreshScene();
|
|
EnsureSceneSelection();
|
|
return true;
|
|
}
|
|
|
|
GameObject* EditorSceneRuntime::FindGameObject(std::string_view itemId) const {
|
|
return m_backend != nullptr ? m_backend->FindGameObject(itemId) : nullptr;
|
|
}
|
|
|
|
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();
|
|
}
|
|
RefreshScene();
|
|
return renamed;
|
|
}
|
|
|
|
bool EditorSceneRuntime::DeleteGameObject(std::string_view itemId) {
|
|
ResetTransformEditHistory();
|
|
const bool deleted =
|
|
m_backend != nullptr &&
|
|
m_backend->DeleteGameObject(itemId);
|
|
if (deleted) {
|
|
IncrementInspectorRevision();
|
|
}
|
|
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();
|
|
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();
|
|
}
|
|
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();
|
|
}
|
|
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();
|
|
}
|
|
RefreshScene();
|
|
return moved;
|
|
}
|
|
|
|
bool EditorSceneRuntime::MoveGameObjectToRoot(std::string_view itemId) {
|
|
ResetTransformEditHistory();
|
|
const bool moved =
|
|
m_backend != nullptr &&
|
|
m_backend->MoveGameObjectToRoot(itemId);
|
|
if (moved) {
|
|
IncrementInspectorRevision();
|
|
}
|
|
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();
|
|
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();
|
|
}
|
|
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();
|
|
}
|
|
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();
|
|
}
|
|
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();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::ApplySelectedComponentMutation(
|
|
std::string_view componentId,
|
|
const std::function<bool(Component&)>& mutation) {
|
|
if (!mutation) {
|
|
return false;
|
|
}
|
|
|
|
if (m_backend == nullptr ||
|
|
!ResolveSelectedComponentDescriptor(componentId).IsValid()) {
|
|
return false;
|
|
}
|
|
|
|
if (!m_backend->MutateComponent(
|
|
GetSelectedItemId(),
|
|
componentId,
|
|
mutation)) {
|
|
return false;
|
|
}
|
|
|
|
IncrementInspectorRevision();
|
|
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<GameObject::ID> selectedId = GetSelectedGameObjectId();
|
|
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();
|
|
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 (!m_toolState.dragState.active ||
|
|
targetId == kInvalidEditorSceneObjectId ||
|
|
targetId != m_toolState.dragState.initialTransform.targetId) {
|
|
return false;
|
|
}
|
|
|
|
if (m_backend == nullptr ||
|
|
!m_backend->SetWorldPositionRotation(targetId, position, rotation)) {
|
|
return false;
|
|
}
|
|
|
|
IncrementInspectorRevision();
|
|
return true;
|
|
}
|
|
|
|
bool EditorSceneRuntime::ApplyTransformToolLocalScalePreview(
|
|
EditorSceneObjectId targetId,
|
|
const Vector3& localScale) {
|
|
if (!m_toolState.dragState.active ||
|
|
targetId == kInvalidEditorSceneObjectId ||
|
|
targetId != m_toolState.dragState.initialTransform.targetId) {
|
|
return false;
|
|
}
|
|
|
|
if (m_backend == nullptr ||
|
|
!m_backend->SetObjectLocalScale(targetId, localScale)) {
|
|
return false;
|
|
}
|
|
|
|
IncrementInspectorRevision();
|
|
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;
|
|
}
|
|
|
|
bool EditorSceneRuntime::EnsureSceneViewCamera() {
|
|
if (m_sceneViewCamera.gameObject != nullptr &&
|
|
m_sceneViewCamera.camera != nullptr) {
|
|
return true;
|
|
}
|
|
|
|
m_sceneViewCamera = {};
|
|
m_sceneViewCamera.gameObject =
|
|
std::make_unique<GameObject>("EditorSceneCamera");
|
|
m_sceneViewCamera.camera =
|
|
m_sceneViewCamera.gameObject->AddComponent<
|
|
::XCEngine::Components::CameraComponent>();
|
|
if (m_sceneViewCamera.camera == nullptr) {
|
|
m_sceneViewCamera.gameObject.reset();
|
|
return false;
|
|
}
|
|
|
|
m_sceneViewCamera.camera->SetPrimary(false);
|
|
m_sceneViewCamera.camera->SetProjectionType(
|
|
::XCEngine::Components::CameraProjectionType::Perspective);
|
|
m_sceneViewCamera.camera->SetFieldOfView(60.0f);
|
|
m_sceneViewCamera.camera->SetNearClipPlane(0.03f);
|
|
m_sceneViewCamera.camera->SetFarClipPlane(2000.0f);
|
|
m_sceneViewCamera.controller.Reset();
|
|
ApplySceneViewCameraController();
|
|
return true;
|
|
}
|
|
|
|
void EditorSceneRuntime::ApplySceneViewCameraController() {
|
|
if (m_sceneViewCamera.gameObject == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (auto* transform = m_sceneViewCamera.gameObject->GetTransform();
|
|
transform != nullptr) {
|
|
m_sceneViewCamera.controller.ApplyTo(*transform);
|
|
}
|
|
}
|
|
|
|
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 GameObject* gameObject = GetSelectedGameObject();
|
|
if (gameObject == nullptr) {
|
|
return;
|
|
}
|
|
|
|
SelectionService().SetHierarchySelection(
|
|
MakeEditorGameObjectItemId(gameObject->GetID()),
|
|
ResolveGameObjectDisplayName(*gameObject));
|
|
}
|
|
|
|
bool EditorSceneRuntime::HasValidSelection() const {
|
|
return GetSelectedGameObject() != nullptr;
|
|
}
|
|
|
|
EditorSceneComponentDescriptor EditorSceneRuntime::ResolveSelectedComponentDescriptor(
|
|
std::string_view componentId) const {
|
|
for (const EditorSceneComponentDescriptor& descriptor :
|
|
GetSelectedComponents()) {
|
|
if (descriptor.componentId == componentId) {
|
|
return descriptor;
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
bool EditorSceneRuntime::SelectFirstAvailableGameObject() {
|
|
Scene* scene = GetActiveScene();
|
|
if (scene == nullptr) {
|
|
if (HasHierarchySelection()) {
|
|
ClearSelection();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
for (GameObject* root : scene->GetRootGameObjects()) {
|
|
if (root == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
return SetSelection(root->GetID());
|
|
}
|
|
|
|
if (HasHierarchySelection()) {
|
|
ClearSelection();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void EditorSceneRuntime::ResetTransformEditHistory() {
|
|
m_transformUndoStack.clear();
|
|
m_transformRedoStack.clear();
|
|
}
|
|
|
|
void EditorSceneRuntime::IncrementInspectorRevision() {
|
|
++m_inspectorRevision;
|
|
}
|
|
|
|
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;
|
|
Scene* scene = GetActiveScene();
|
|
if (scene == nullptr ||
|
|
!snapshot.IsValid() ||
|
|
scene->FindByID(snapshot.targetId) == nullptr) {
|
|
ResetToolInteractionTransientState();
|
|
}
|
|
}
|
|
|
|
} // namespace XCEngine::UI::Editor::App
|