Unify editor scene edit history

This commit is contained in:
2026-04-29 17:30:44 +08:00
parent 2e50c90167
commit 4a125cbe7f
8 changed files with 708 additions and 225 deletions

View File

@@ -451,7 +451,129 @@ TEST(SceneViewportRuntimeTests, TransformSetterApisWriteLocalValuesOnSelectedTra
EXPECT_FLOAT_EQ(scale.x, 2.0f);
EXPECT_FLOAT_EQ(scale.y, 3.0f);
EXPECT_FLOAT_EQ(scale.z, 4.0f);
EXPECT_TRUE(runtime.CanUndoTransformEdit());
EXPECT_TRUE(runtime.CanUndoSceneEdit());
ASSERT_TRUE(runtime.UndoSceneEdit());
scene = GetLoadedActiveScene();
ASSERT_NE(scene, nullptr);
target = scene->Find("Target");
ASSERT_NE(target, nullptr);
transform = target->GetTransform();
ASSERT_NE(transform, nullptr);
const Math::Vector3 undonePosition = transform->GetLocalPosition();
const Math::Quaternion undoneRotation = transform->GetLocalRotation();
const Math::Vector3 undoneScale = transform->GetLocalScale();
EXPECT_FLOAT_EQ(undonePosition.x, 8.0f);
EXPECT_FLOAT_EQ(undonePosition.y, 9.0f);
EXPECT_FLOAT_EQ(undonePosition.z, 10.0f);
EXPECT_GT(std::abs(undoneRotation.Dot(expectedRotation)), 0.9999f);
EXPECT_FLOAT_EQ(undoneScale.x, 1.0f);
EXPECT_FLOAT_EQ(undoneScale.y, 1.0f);
EXPECT_FLOAT_EQ(undoneScale.z, 1.0f);
EXPECT_TRUE(runtime.CanRedoSceneEdit());
ASSERT_TRUE(runtime.RedoSceneEdit());
scene = GetLoadedActiveScene();
ASSERT_NE(scene, nullptr);
target = scene->Find("Target");
ASSERT_NE(target, nullptr);
transform = target->GetTransform();
ASSERT_NE(transform, nullptr);
const Math::Vector3 redoneScale = transform->GetLocalScale();
EXPECT_FLOAT_EQ(redoneScale.x, 2.0f);
EXPECT_FLOAT_EQ(redoneScale.y, 3.0f);
EXPECT_FLOAT_EQ(redoneScale.z, 4.0f);
}
TEST(SceneViewportRuntimeTests, DeleteGameObjectUndoRedoRestoresSceneAndSelection) {
ScopedSceneManagerReset reset = {};
TemporaryProjectRoot projectRoot = {};
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
Scene* scene = GetLoadedActiveScene();
ASSERT_NE(scene, nullptr);
GameObject* secondary = scene->CreateGameObject("Secondary");
ASSERT_NE(secondary, nullptr);
ASSERT_TRUE(runtime.SetSelection(secondary->GetID()));
const std::string secondaryItemId =
MakeEditorGameObjectItemId(secondary->GetID());
ASSERT_TRUE(runtime.DeleteGameObject(secondaryItemId));
EXPECT_EQ(scene->Find("Secondary"), nullptr);
EXPECT_EQ(runtime.GetSelectedDisplayName(), "Target");
EXPECT_TRUE(runtime.CanUndoSceneEdit());
ASSERT_TRUE(runtime.UndoSceneEdit());
scene = GetLoadedActiveScene();
ASSERT_NE(scene, nullptr);
GameObject* restoredSecondary = scene->Find("Secondary");
ASSERT_NE(restoredSecondary, nullptr);
ASSERT_TRUE(runtime.GetSelectedObjectId().has_value());
EXPECT_EQ(runtime.GetSelectedObjectId().value(), restoredSecondary->GetID());
EXPECT_EQ(runtime.GetSelectedDisplayName(), "Secondary");
EXPECT_TRUE(runtime.CanRedoSceneEdit());
ASSERT_TRUE(runtime.RedoSceneEdit());
scene = GetLoadedActiveScene();
ASSERT_NE(scene, nullptr);
EXPECT_EQ(scene->Find("Secondary"), nullptr);
EXPECT_EQ(runtime.GetSelectedDisplayName(), "Target");
}
TEST(SceneViewportRuntimeTests, RemoveSelectedComponentUndoRedoRestoresComponentDescriptors) {
ScopedSceneManagerReset reset = {};
TemporaryProjectRoot projectRoot = {};
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
Scene* scene = GetLoadedActiveScene();
ASSERT_NE(scene, nullptr);
GameObject* target = scene->Find("Target");
ASSERT_NE(target, nullptr);
ASSERT_NE(target->AddComponent<::XCEngine::Components::CameraComponent>(), nullptr);
ASSERT_TRUE(runtime.SetSelection(target->GetID()));
ASSERT_TRUE(runtime.RemoveSelectedComponent("Camera#0"));
std::vector<EditorSceneComponentDescriptor> descriptors =
runtime.GetSelectedComponents();
ASSERT_EQ(descriptors.size(), 1u);
EXPECT_EQ(descriptors[0].typeName, "Transform");
EXPECT_TRUE(runtime.CanUndoSceneEdit());
ASSERT_TRUE(runtime.UndoSceneEdit());
scene = GetLoadedActiveScene();
ASSERT_NE(scene, nullptr);
target = scene->Find("Target");
ASSERT_NE(target, nullptr);
ASSERT_TRUE(runtime.SetSelection(target->GetID()) ||
(runtime.GetSelectedObjectId().has_value() &&
runtime.GetSelectedObjectId().value() == target->GetID()));
descriptors = runtime.GetSelectedComponents();
ASSERT_EQ(descriptors.size(), 2u);
EXPECT_NE(FindComponentDescriptor(descriptors, "Camera"), nullptr);
EXPECT_NE(target->GetComponent<::XCEngine::Components::CameraComponent>(), nullptr);
EXPECT_TRUE(runtime.CanRedoSceneEdit());
ASSERT_TRUE(runtime.RedoSceneEdit());
scene = GetLoadedActiveScene();
ASSERT_NE(scene, nullptr);
target = scene->Find("Target");
ASSERT_NE(target, nullptr);
ASSERT_TRUE(runtime.SetSelection(target->GetID()) ||
(runtime.GetSelectedObjectId().has_value() &&
runtime.GetSelectedObjectId().value() == target->GetID()));
descriptors = runtime.GetSelectedComponents();
ASSERT_EQ(descriptors.size(), 1u);
EXPECT_EQ(target->GetComponent<::XCEngine::Components::CameraComponent>(), nullptr);
}
TEST(SceneViewportRuntimeTests, SelectionStampAdvancesOnSceneSelectionChanges) {
@@ -1027,27 +1149,31 @@ TEST(SceneViewportRuntimeTests, TranslateGizmoDragAppliesPreviewToSelectedObject
auto* transform = target->GetTransform();
ASSERT_NE(transform, nullptr);
SceneViewportTransformGizmo gizmo = {};
const UIRect viewportRect(0.0f, 0.0f, 640.0f, 360.0f);
const std::optional<UIPoint> hoverPoint =
FindHoveredTransformGizmoPoint(gizmo, runtime, session, viewportRect);
ASSERT_TRUE(hoverPoint.has_value());
const std::array<UIPoint, 6u> dragTargets = {
UIPoint(hoverPoint->x + 40.0f, hoverPoint->y),
UIPoint(hoverPoint->x - 40.0f, hoverPoint->y),
UIPoint(hoverPoint->x, hoverPoint->y + 40.0f),
UIPoint(hoverPoint->x, hoverPoint->y - 40.0f),
UIPoint(hoverPoint->x + 32.0f, hoverPoint->y + 32.0f),
UIPoint(hoverPoint->x - 32.0f, hoverPoint->y - 32.0f)
const std::array<UIPoint, 6u> dragOffsets = {
UIPoint(40.0f, 0.0f),
UIPoint(-40.0f, 0.0f),
UIPoint(0.0f, 40.0f),
UIPoint(0.0f, -40.0f),
UIPoint(32.0f, 32.0f),
UIPoint(-32.0f, -32.0f)
};
bool previewApplied = false;
for (const UIPoint& dragPoint : dragTargets) {
for (const UIPoint& dragOffset : dragOffsets) {
SceneViewportTransformGizmo gizmo = {};
transform->SetPosition(Math::Vector3(0.0f, 0.0f, 0.0f));
const std::optional<UIPoint> hoverPoint =
FindHoveredTransformGizmoPoint(gizmo, runtime, session, viewportRect);
ASSERT_TRUE(hoverPoint.has_value());
const UIPoint dragPoint(
hoverPoint->x + dragOffset.x,
hoverPoint->y + dragOffset.y);
gizmo.Refresh(runtime, session, viewportRect, hoverPoint.value(), true);
ASSERT_TRUE(gizmo.IsHoveringHandle());
ASSERT_TRUE(gizmo.TryBeginDrag(runtime, session));
EXPECT_TRUE(runtime.HasPendingSceneEditTransaction());
gizmo.Refresh(runtime, session, viewportRect, dragPoint, true);
ASSERT_TRUE(gizmo.UpdateDrag(runtime));
@@ -1058,15 +1184,127 @@ TEST(SceneViewportRuntimeTests, TranslateGizmoDragAppliesPreviewToSelectedObject
std::abs(previewPosition.z) > 0.0001f) {
previewApplied = true;
gizmo.CancelDrag(runtime);
EXPECT_FALSE(runtime.HasPendingSceneEditTransaction());
scene = GetLoadedActiveScene();
ASSERT_NE(scene, nullptr);
target = scene->Find("Target");
ASSERT_NE(target, nullptr);
transform = target->GetTransform();
ASSERT_NE(transform, nullptr);
ASSERT_TRUE(runtime.SetSelection(target->GetID()) ||
(runtime.GetSelectedObjectId().has_value() &&
runtime.GetSelectedObjectId().value() == target->GetID()));
const Math::Vector3 canceledPosition = transform->GetPosition();
EXPECT_FLOAT_EQ(canceledPosition.x, 0.0f);
EXPECT_FLOAT_EQ(canceledPosition.y, 0.0f);
EXPECT_FLOAT_EQ(canceledPosition.z, 0.0f);
EXPECT_FALSE(runtime.CanUndoSceneEdit());
break;
}
gizmo.CancelDrag(runtime);
EXPECT_FALSE(runtime.HasPendingSceneEditTransaction());
scene = GetLoadedActiveScene();
ASSERT_NE(scene, nullptr);
target = scene->Find("Target");
ASSERT_NE(target, nullptr);
transform = target->GetTransform();
ASSERT_NE(transform, nullptr);
ASSERT_TRUE(runtime.SetSelection(target->GetID()) ||
(runtime.GetSelectedObjectId().has_value() &&
runtime.GetSelectedObjectId().value() == target->GetID()));
}
EXPECT_TRUE(previewApplied);
}
TEST(SceneViewportRuntimeTests, TranslateGizmoEndDragRecordsUnifiedSceneEditHistory) {
ScopedSceneManagerReset reset = {};
TemporaryProjectRoot projectRoot = {};
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
SceneViewportSession session = {};
session.SetToolMode(SceneToolMode::Translate);
runtime.EnsureSceneSelection();
Scene* scene = GetLoadedActiveScene();
ASSERT_NE(scene, nullptr);
GameObject* target = scene->Find("Target");
ASSERT_NE(target, nullptr);
auto* transform = target->GetTransform();
ASSERT_NE(transform, nullptr);
const UIRect viewportRect(0.0f, 0.0f, 640.0f, 360.0f);
const std::array<UIPoint, 6u> dragOffsets = {
UIPoint(40.0f, 0.0f),
UIPoint(-40.0f, 0.0f),
UIPoint(0.0f, 40.0f),
UIPoint(0.0f, -40.0f),
UIPoint(32.0f, 32.0f),
UIPoint(-32.0f, -32.0f)
};
bool committed = false;
for (const UIPoint& dragOffset : dragOffsets) {
SceneViewportTransformGizmo gizmo = {};
transform->SetPosition(Math::Vector3(0.0f, 0.0f, 0.0f));
const std::optional<UIPoint> hoverPoint =
FindHoveredTransformGizmoPoint(gizmo, runtime, session, viewportRect);
ASSERT_TRUE(hoverPoint.has_value());
const UIPoint dragPoint(
hoverPoint->x + dragOffset.x,
hoverPoint->y + dragOffset.y);
gizmo.Refresh(runtime, session, viewportRect, hoverPoint.value(), true);
ASSERT_TRUE(gizmo.IsHoveringHandle());
ASSERT_TRUE(gizmo.TryBeginDrag(runtime, session));
gizmo.Refresh(runtime, session, viewportRect, dragPoint, true);
ASSERT_TRUE(gizmo.UpdateDrag(runtime));
const Math::Vector3 previewPosition = transform->GetPosition();
if (std::abs(previewPosition.x) <= 0.0001f &&
std::abs(previewPosition.y) <= 0.0001f &&
std::abs(previewPosition.z) <= 0.0001f) {
gizmo.CancelDrag(runtime);
scene = GetLoadedActiveScene();
ASSERT_NE(scene, nullptr);
target = scene->Find("Target");
ASSERT_NE(target, nullptr);
transform = target->GetTransform();
ASSERT_NE(transform, nullptr);
ASSERT_TRUE(runtime.SetSelection(target->GetID()) ||
(runtime.GetSelectedObjectId().has_value() &&
runtime.GetSelectedObjectId().value() == target->GetID()));
continue;
}
ASSERT_TRUE(gizmo.EndDrag(runtime));
committed = true;
break;
}
ASSERT_TRUE(committed);
EXPECT_FALSE(runtime.HasPendingSceneEditTransaction());
EXPECT_TRUE(runtime.CanUndoSceneEdit());
ASSERT_TRUE(runtime.UndoSceneEdit());
scene = GetLoadedActiveScene();
ASSERT_NE(scene, nullptr);
target = scene->Find("Target");
ASSERT_NE(target, nullptr);
transform = target->GetTransform();
ASSERT_NE(transform, nullptr);
const Math::Vector3 undonePosition = transform->GetPosition();
EXPECT_FLOAT_EQ(undonePosition.x, 0.0f);
EXPECT_FLOAT_EQ(undonePosition.y, 0.0f);
EXPECT_FLOAT_EQ(undonePosition.z, 0.0f);
EXPECT_TRUE(runtime.CanRedoSceneEdit());
}
TEST(SceneViewportRuntimeTests, SceneViewportRendererDeclaresExplicitAuxiliaryResourceRequirements) {
const ViewportResourceRequirements requirements =
SceneViewportRenderService::GetViewportResourceRequirements();