Refactor new editor scene viewport tools
This commit is contained in:
@@ -1,14 +1,29 @@
|
||||
#include "Scene/EditorSceneRuntime.h"
|
||||
|
||||
#include "State/EditorSelectionStamp.h"
|
||||
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
#include <XCEngine/Components/Component.h>
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Components/TransformComponent.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
|
||||
#include <charconv>
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::Components::GameObject;
|
||||
using ::XCEngine::Components::Scene;
|
||||
using ::XCEngine::Components::TransformComponent;
|
||||
using ::XCEngine::Components::Component;
|
||||
using ::XCEngine::Math::Quaternion;
|
||||
using ::XCEngine::Math::Vector3;
|
||||
|
||||
constexpr char kComponentIdSeparator = '#';
|
||||
|
||||
std::string ResolveGameObjectDisplayName(const GameObject& gameObject) {
|
||||
return gameObject.GetName().empty()
|
||||
@@ -16,19 +31,164 @@ std::string ResolveGameObjectDisplayName(const GameObject& 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
EditorSceneComponentDescriptor BuildComponentDescriptor(
|
||||
const Component& component,
|
||||
std::size_t ordinal) {
|
||||
EditorSceneComponentDescriptor descriptor = {};
|
||||
descriptor.typeName = component.GetName();
|
||||
descriptor.componentId =
|
||||
BuildEditorComponentId(descriptor.typeName, ordinal);
|
||||
descriptor.component = &component;
|
||||
descriptor.removable =
|
||||
component.GetGameObject() != nullptr &&
|
||||
component.GetGameObject()->GetTransform() != &component;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string_view GetSceneToolModeName(SceneToolMode mode) {
|
||||
switch (mode) {
|
||||
case SceneToolMode::View:
|
||||
return "View";
|
||||
case SceneToolMode::Translate:
|
||||
return "Translate";
|
||||
case SceneToolMode::Rotate:
|
||||
return "Rotate";
|
||||
case SceneToolMode::Scale:
|
||||
return "Scale";
|
||||
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::Toolbar:
|
||||
return "Toolbar";
|
||||
case SceneToolInteractionLock::TransformDrag:
|
||||
return "TransformDrag";
|
||||
case SceneToolInteractionLock::None:
|
||||
default:
|
||||
return "None";
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::Initialize(const std::filesystem::path& projectRoot) {
|
||||
m_projectRoot = projectRoot;
|
||||
m_startupSceneResult = EnsureEditorStartupScene(projectRoot);
|
||||
EnsureSceneViewCamera();
|
||||
ResetTransformEditHistory();
|
||||
m_toolState = {};
|
||||
RefreshScene();
|
||||
return m_startupSceneResult.ready;
|
||||
}
|
||||
|
||||
void EditorSceneRuntime::RefreshScene() {
|
||||
if (!HasValidSelection()) {
|
||||
if (m_selectedGameObjectId.has_value() && !HasValidSelection()) {
|
||||
m_selectedGameObjectId.reset();
|
||||
m_selectionStamp = GenerateEditorSelectionStamp();
|
||||
}
|
||||
|
||||
ClearInvalidToolInteractionState();
|
||||
}
|
||||
|
||||
void EditorSceneRuntime::EnsureSceneSelection() {
|
||||
@@ -47,6 +207,56 @@ Scene* EditorSceneRuntime::GetActiveScene() const {
|
||||
return GetActiveEditorScene();
|
||||
}
|
||||
|
||||
::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() {
|
||||
const GameObject* selectedGameObject = GetSelectedGameObject();
|
||||
if (selectedGameObject == nullptr || !EnsureSceneViewCamera()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto* transform = selectedGameObject->GetTransform();
|
||||
if (transform == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_sceneViewCamera.controller.Focus(transform->GetPosition());
|
||||
ApplySceneViewCameraController();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::HasSceneSelection() const {
|
||||
return HasValidSelection();
|
||||
}
|
||||
@@ -80,6 +290,35 @@ const GameObject* EditorSceneRuntime::GetSelectedGameObject() const {
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
std::vector<EditorSceneComponentDescriptor> EditorSceneRuntime::GetSelectedComponents() const {
|
||||
std::vector<EditorSceneComponentDescriptor> descriptors = {};
|
||||
const GameObject* gameObject = GetSelectedGameObject();
|
||||
if (gameObject == nullptr) {
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
const std::vector<const Component*> components =
|
||||
gameObject->GetComponents<Component>();
|
||||
descriptors.reserve(components.size());
|
||||
std::unordered_map<std::string, std::size_t> 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::uint64_t EditorSceneRuntime::GetSelectionStamp() const {
|
||||
return m_selectionStamp;
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::SetSelection(std::string_view itemId) {
|
||||
const std::optional<GameObject::ID> gameObjectId =
|
||||
ParseEditorGameObjectItemId(itemId);
|
||||
@@ -103,12 +342,41 @@ bool EditorSceneRuntime::SetSelection(GameObject::ID id) {
|
||||
const bool changed =
|
||||
!m_selectedGameObjectId.has_value() ||
|
||||
m_selectedGameObjectId.value() != id;
|
||||
if (changed) {
|
||||
ResetToolInteractionState();
|
||||
m_selectionStamp = GenerateEditorSelectionStamp();
|
||||
}
|
||||
m_selectedGameObjectId = id;
|
||||
ClearInvalidToolInteractionState();
|
||||
return changed;
|
||||
}
|
||||
|
||||
void EditorSceneRuntime::ClearSelection() {
|
||||
ResetToolInteractionState();
|
||||
m_selectedGameObjectId.reset();
|
||||
m_selectionStamp = GenerateEditorSelectionStamp();
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::OpenSceneAsset(const std::filesystem::path& scenePath) {
|
||||
if (!OpenEditorSceneAsset(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();
|
||||
RefreshScene();
|
||||
EnsureSceneSelection();
|
||||
return true;
|
||||
}
|
||||
|
||||
GameObject* EditorSceneRuntime::FindGameObject(std::string_view itemId) const {
|
||||
@@ -124,6 +392,7 @@ bool EditorSceneRuntime::RenameGameObject(
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::DeleteGameObject(std::string_view itemId) {
|
||||
ResetTransformEditHistory();
|
||||
const bool deleted = DeleteEditorGameObject(itemId);
|
||||
RefreshScene();
|
||||
EnsureSceneSelection();
|
||||
@@ -131,6 +400,7 @@ bool EditorSceneRuntime::DeleteGameObject(std::string_view itemId) {
|
||||
}
|
||||
|
||||
std::string EditorSceneRuntime::DuplicateGameObject(std::string_view itemId) {
|
||||
ResetTransformEditHistory();
|
||||
const std::string duplicatedItemId = DuplicateEditorGameObject(itemId);
|
||||
if (!duplicatedItemId.empty()) {
|
||||
SetSelection(duplicatedItemId);
|
||||
@@ -143,6 +413,7 @@ std::string EditorSceneRuntime::DuplicateGameObject(std::string_view itemId) {
|
||||
bool EditorSceneRuntime::ReparentGameObject(
|
||||
std::string_view itemId,
|
||||
std::string_view parentItemId) {
|
||||
ResetTransformEditHistory();
|
||||
const bool reparented =
|
||||
ReparentEditorGameObject(itemId, parentItemId);
|
||||
RefreshScene();
|
||||
@@ -150,19 +421,476 @@ bool EditorSceneRuntime::ReparentGameObject(
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::MoveGameObjectToRoot(std::string_view itemId) {
|
||||
ResetTransformEditHistory();
|
||||
const bool moved = MoveEditorGameObjectToRoot(itemId);
|
||||
RefreshScene();
|
||||
return moved;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const std::optional<GameObject::ID> selectedId = GetSelectedGameObjectId();
|
||||
Scene* scene = GetActiveScene();
|
||||
if (!selectedId.has_value() || scene == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GameObject* gameObject = scene->FindByID(selectedId.value());
|
||||
Component* component =
|
||||
const_cast<Component*>(descriptor.component);
|
||||
if (gameObject == nullptr || component == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetTransformEditHistory();
|
||||
const bool removed = gameObject->RemoveComponent(component);
|
||||
RefreshScene();
|
||||
return removed;
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::SetSelectedTransformLocalPosition(
|
||||
std::string_view componentId,
|
||||
const Vector3& position) {
|
||||
const EditorSceneComponentDescriptor descriptor =
|
||||
ResolveSelectedComponentDescriptor(componentId);
|
||||
auto* transform =
|
||||
const_cast<TransformComponent*>(
|
||||
dynamic_cast<const TransformComponent*>(descriptor.component));
|
||||
if (transform == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SceneTransformSnapshot beforeSnapshot = {};
|
||||
if (!CaptureSelectedTransformSnapshot(beforeSnapshot)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
transform->SetLocalPosition(position);
|
||||
|
||||
SceneTransformSnapshot afterSnapshot = {};
|
||||
CaptureSelectedTransformSnapshot(afterSnapshot);
|
||||
RecordTransformEdit(beforeSnapshot, afterSnapshot);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::SetSelectedTransformLocalEulerAngles(
|
||||
std::string_view componentId,
|
||||
const Vector3& eulerAngles) {
|
||||
const EditorSceneComponentDescriptor descriptor =
|
||||
ResolveSelectedComponentDescriptor(componentId);
|
||||
auto* transform =
|
||||
const_cast<TransformComponent*>(
|
||||
dynamic_cast<const TransformComponent*>(descriptor.component));
|
||||
if (transform == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SceneTransformSnapshot beforeSnapshot = {};
|
||||
if (!CaptureSelectedTransformSnapshot(beforeSnapshot)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
transform->SetLocalEulerAngles(eulerAngles);
|
||||
|
||||
SceneTransformSnapshot afterSnapshot = {};
|
||||
CaptureSelectedTransformSnapshot(afterSnapshot);
|
||||
RecordTransformEdit(beforeSnapshot, afterSnapshot);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::SetSelectedTransformLocalScale(
|
||||
std::string_view componentId,
|
||||
const Vector3& scale) {
|
||||
const EditorSceneComponentDescriptor descriptor =
|
||||
ResolveSelectedComponentDescriptor(componentId);
|
||||
auto* transform =
|
||||
const_cast<TransformComponent*>(
|
||||
dynamic_cast<const TransformComponent*>(descriptor.component));
|
||||
if (transform == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SceneTransformSnapshot beforeSnapshot = {};
|
||||
if (!CaptureSelectedTransformSnapshot(beforeSnapshot)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
transform->SetLocalScale(scale);
|
||||
|
||||
SceneTransformSnapshot afterSnapshot = {};
|
||||
CaptureSelectedTransformSnapshot(afterSnapshot);
|
||||
RecordTransformEdit(beforeSnapshot, afterSnapshot);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::ApplySelectedComponentMutation(
|
||||
std::string_view componentId,
|
||||
const std::function<bool(Component&)>& mutation) {
|
||||
if (!mutation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const EditorSceneComponentDescriptor descriptor =
|
||||
ResolveSelectedComponentDescriptor(componentId);
|
||||
Component* component = const_cast<Component*>(descriptor.component);
|
||||
if (!descriptor.IsValid() || component == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mutation(*component);
|
||||
}
|
||||
|
||||
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::SetToolbarHoveredIndex(std::size_t index) {
|
||||
m_toolState.toolbarHoveredIndex = index;
|
||||
}
|
||||
|
||||
void EditorSceneRuntime::SetToolbarActiveIndex(std::size_t index) {
|
||||
m_toolState.toolbarActiveIndex = index;
|
||||
}
|
||||
|
||||
void EditorSceneRuntime::ClearToolbarInteraction() {
|
||||
m_toolState.toolbarHoveredIndex = kSceneToolInvalidToolbarIndex;
|
||||
m_toolState.toolbarActiveIndex = kSceneToolInvalidToolbarIndex;
|
||||
if (m_toolState.interactionLock == SceneToolInteractionLock::Toolbar) {
|
||||
ClearToolInteractionLock();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneRuntime::ResetToolInteractionState() {
|
||||
CancelTransformToolDrag();
|
||||
ResetToolInteractionTransientState();
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::CaptureSelectedTransformSnapshot(
|
||||
SceneTransformSnapshot& outSnapshot) const {
|
||||
const GameObject* selectedGameObject = GetSelectedGameObject();
|
||||
if (selectedGameObject == nullptr) {
|
||||
outSnapshot = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
const TransformComponent* transform = selectedGameObject->GetTransform();
|
||||
if (transform == nullptr) {
|
||||
outSnapshot = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
outSnapshot = {};
|
||||
outSnapshot.targetId = selectedGameObject->GetID();
|
||||
outSnapshot.position = transform->GetPosition();
|
||||
outSnapshot.rotation = transform->GetRotation();
|
||||
outSnapshot.scale = transform->GetScale();
|
||||
outSnapshot.valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::ApplyTransformSnapshot(
|
||||
const SceneTransformSnapshot& snapshot) {
|
||||
if (!snapshot.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Scene* scene = GetActiveScene();
|
||||
if (scene == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GameObject* gameObject = scene->FindByID(snapshot.targetId);
|
||||
if (gameObject == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TransformComponent* transform = gameObject->GetTransform();
|
||||
if (transform == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
transform->SetPosition(snapshot.position);
|
||||
transform->SetRotation(snapshot.rotation);
|
||||
transform->SetScale(snapshot.scale);
|
||||
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::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);
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::HasValidSelection() const {
|
||||
return GetSelectedGameObject() != nullptr;
|
||||
}
|
||||
|
||||
EditorSceneComponentDescriptor EditorSceneRuntime::ResolveSelectedComponentDescriptor(
|
||||
std::string_view componentId) const {
|
||||
std::string typeName = {};
|
||||
std::size_t ordinal = 0u;
|
||||
if (!ParseEditorComponentId(componentId, typeName, ordinal)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const GameObject* gameObject = GetSelectedGameObject();
|
||||
if (gameObject == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t currentOrdinal = 0u;
|
||||
for (const Component* component : gameObject->GetComponents<Component>()) {
|
||||
if (component == nullptr || component->GetName() != typeName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentOrdinal == ordinal) {
|
||||
return BuildComponentDescriptor(*component, currentOrdinal);
|
||||
}
|
||||
|
||||
++currentOrdinal;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool EditorSceneRuntime::SelectFirstAvailableGameObject() {
|
||||
Scene* scene = GetActiveScene();
|
||||
if (scene == nullptr) {
|
||||
m_selectedGameObjectId.reset();
|
||||
if (m_selectedGameObjectId.has_value()) {
|
||||
ClearSelection();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -171,13 +899,45 @@ bool EditorSceneRuntime::SelectFirstAvailableGameObject() {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_selectedGameObjectId = root->GetID();
|
||||
return true;
|
||||
return SetSelection(root->GetID());
|
||||
}
|
||||
|
||||
m_selectedGameObjectId.reset();
|
||||
if (m_selectedGameObjectId.has_value()) {
|
||||
ClearSelection();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
void EditorSceneRuntime::ResetTransformEditHistory() {
|
||||
m_transformUndoStack.clear();
|
||||
m_transformRedoStack.clear();
|
||||
}
|
||||
|
||||
void EditorSceneRuntime::ResetToolInteractionTransientState() {
|
||||
m_toolState.hoveredHandle = SceneToolHandle::None;
|
||||
m_toolState.activeHandle = SceneToolHandle::None;
|
||||
ClearToolbarInteraction();
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user