Files
XCEngine/tests/editor/test_scene_viewport_move_gizmo.cpp

226 lines
8.9 KiB
C++

#include <gtest/gtest.h>
#include <cmath>
#include "Core/EditorContext.h"
#include "Managers/SceneManager.h"
#include "Viewport/SceneViewportMoveGizmo.h"
namespace XCEngine::Editor {
namespace {
float HandleLength(const SceneViewportMoveGizmoHandleDrawData& handle) {
return (handle.end - handle.start).Magnitude();
}
Math::Vector2 QuadCenter(const SceneViewportMoveGizmoPlaneDrawData& plane) {
Math::Vector2 center = Math::Vector2::Zero();
for (const Math::Vector2& corner : plane.corners) {
center += corner;
}
return center / 4.0f;
}
class SceneViewportMoveGizmoTest : public ::testing::Test {
protected:
void SetUp() override {
m_context.GetSceneManager().NewScene("Move Gizmo Test Scene");
}
static SceneViewportOverlayData MakeOverlay() {
SceneViewportOverlayData overlay = {};
overlay.valid = true;
overlay.cameraPosition = Math::Vector3(0.0f, 0.0f, -5.0f);
overlay.cameraForward = Math::Vector3::Forward();
overlay.cameraRight = Math::Vector3::Right();
overlay.cameraUp = Math::Vector3::Up();
overlay.verticalFovDegrees = 60.0f;
overlay.nearClipPlane = 0.03f;
overlay.farClipPlane = 2000.0f;
return overlay;
}
static SceneViewportOverlayData MakeDownwardTiltedOverlay() {
SceneViewportOverlayData overlay = {};
overlay.valid = true;
overlay.cameraPosition = Math::Vector3(0.0f, 10.0f, -1.0f);
overlay.cameraForward = (Math::Vector3::Zero() - overlay.cameraPosition).Normalized();
overlay.cameraRight = Math::Vector3::Cross(Math::Vector3::Up(), overlay.cameraForward).Normalized();
overlay.cameraUp = Math::Vector3::Cross(overlay.cameraForward, overlay.cameraRight).Normalized();
overlay.verticalFovDegrees = 60.0f;
overlay.nearClipPlane = 0.03f;
overlay.farClipPlane = 2000.0f;
return overlay;
}
static SceneViewportOverlayData MakeIsometricOverlay() {
SceneViewportOverlayData overlay = {};
overlay.valid = true;
overlay.cameraPosition = Math::Vector3(-5.0f, 5.0f, -5.0f);
overlay.cameraForward = (Math::Vector3::Zero() - overlay.cameraPosition).Normalized();
overlay.cameraRight = Math::Vector3::Cross(Math::Vector3::Up(), overlay.cameraForward).Normalized();
overlay.cameraUp = Math::Vector3::Cross(overlay.cameraForward, overlay.cameraRight).Normalized();
overlay.verticalFovDegrees = 60.0f;
overlay.nearClipPlane = 0.03f;
overlay.farClipPlane = 2000.0f;
return overlay;
}
static SceneViewportMoveGizmoContext MakeContext(
Components::GameObject* selectedObject,
const Math::Vector2& mousePosition) {
SceneViewportMoveGizmoContext context = {};
context.overlay = MakeOverlay();
context.viewportSize = Math::Vector2(800.0f, 600.0f);
context.mousePosition = mousePosition;
context.selectedObject = selectedObject;
return context;
}
static SceneViewportMoveGizmoContext MakeContext(
Components::GameObject* selectedObject,
const Math::Vector2& mousePosition,
const SceneViewportOverlayData& overlay) {
SceneViewportMoveGizmoContext context = {};
context.overlay = overlay;
context.viewportSize = Math::Vector2(800.0f, 600.0f);
context.mousePosition = mousePosition;
context.selectedObject = selectedObject;
return context;
}
SceneManager& GetSceneManager() {
return dynamic_cast<SceneManager&>(m_context.GetSceneManager());
}
EditorContext m_context;
};
TEST_F(SceneViewportMoveGizmoTest, UpdateHighlightsXAxisWhenMouseIsNearXAxisHandle) {
Components::GameObject* target = GetSceneManager().CreateEntity("Target");
ASSERT_NE(target, nullptr);
m_context.GetSelectionManager().SetSelectedEntity(target->GetID());
SceneViewportMoveGizmo gizmo;
gizmo.Update(MakeContext(target, Math::Vector2(436.0f, 300.0f)));
ASSERT_TRUE(gizmo.GetDrawData().visible);
EXPECT_TRUE(gizmo.IsHoveringHandle());
EXPECT_TRUE(gizmo.GetDrawData().handles[0].hovered);
EXPECT_FALSE(gizmo.GetDrawData().handles[1].hovered);
EXPECT_FALSE(gizmo.GetDrawData().handles[2].hovered);
}
TEST_F(SceneViewportMoveGizmoTest, DraggingXAxisOnlyChangesWorldXAndCreatesUndoStep) {
Components::GameObject* target = GetSceneManager().CreateEntity("Target");
ASSERT_NE(target, nullptr);
const uint64_t targetId = target->GetID();
m_context.GetSelectionManager().SetSelectedEntity(target->GetID());
SceneViewportMoveGizmo gizmo;
const auto startContext = MakeContext(target, Math::Vector2(436.0f, 300.0f));
gizmo.Update(startContext);
ASSERT_TRUE(gizmo.TryBeginDrag(startContext, m_context.GetUndoManager()));
ASSERT_TRUE(gizmo.IsActive());
const auto dragContext = MakeContext(target, Math::Vector2(500.0f, 300.0f));
gizmo.Update(dragContext);
gizmo.UpdateDrag(dragContext);
gizmo.EndDrag(m_context.GetUndoManager());
const Math::Vector3 movedPosition = target->GetTransform()->GetPosition();
EXPECT_GT(movedPosition.x, 0.05f);
EXPECT_NEAR(movedPosition.y, 0.0f, 1e-4f);
EXPECT_NEAR(movedPosition.z, 0.0f, 1e-4f);
EXPECT_TRUE(m_context.GetUndoManager().CanUndo());
m_context.GetUndoManager().Undo();
Components::GameObject* restoredTarget = GetSceneManager().GetEntity(targetId);
ASSERT_NE(restoredTarget, nullptr);
const Math::Vector3 restoredPosition = restoredTarget->GetTransform()->GetPosition();
EXPECT_NEAR(restoredPosition.x, 0.0f, 1e-4f);
EXPECT_NEAR(restoredPosition.y, 0.0f, 1e-4f);
EXPECT_NEAR(restoredPosition.z, 0.0f, 1e-4f);
}
TEST_F(SceneViewportMoveGizmoTest, AxisNearlyFacingCameraShrinksInsteadOfKeepingFullLength) {
Components::GameObject* target = GetSceneManager().CreateEntity("Target");
ASSERT_NE(target, nullptr);
SceneViewportMoveGizmo gizmo;
gizmo.Update(MakeContext(target, Math::Vector2(400.0f, 300.0f), MakeDownwardTiltedOverlay()));
ASSERT_TRUE(gizmo.GetDrawData().visible);
const float xLength = HandleLength(gizmo.GetDrawData().handles[0]);
const float yLength = HandleLength(gizmo.GetDrawData().handles[1]);
const float zLength = HandleLength(gizmo.GetDrawData().handles[2]);
EXPECT_GT(xLength, 60.0f);
EXPECT_GT(zLength, 60.0f);
EXPECT_LT(yLength, xLength * 0.5f);
EXPECT_LT(yLength, zLength * 0.5f);
}
TEST_F(SceneViewportMoveGizmoTest, IsometricViewShowsPlaneHandleAndCanHoverIt) {
Components::GameObject* target = GetSceneManager().CreateEntity("Target");
ASSERT_NE(target, nullptr);
SceneViewportMoveGizmo gizmo;
const SceneViewportOverlayData overlay = MakeIsometricOverlay();
gizmo.Update(MakeContext(target, Math::Vector2(400.0f, 300.0f), overlay));
ASSERT_TRUE(gizmo.GetDrawData().visible);
const SceneViewportMoveGizmoPlaneDrawData& xyPlane = gizmo.GetDrawData().planes[0];
ASSERT_TRUE(xyPlane.visible);
gizmo.Update(MakeContext(target, QuadCenter(xyPlane), overlay));
EXPECT_TRUE(gizmo.IsHoveringHandle());
EXPECT_TRUE(gizmo.GetDrawData().planes[0].hovered);
EXPECT_FALSE(gizmo.GetDrawData().planes[1].hovered);
EXPECT_FALSE(gizmo.GetDrawData().planes[2].hovered);
}
TEST_F(SceneViewportMoveGizmoTest, DraggingXYPlaneChangesWorldXAndYButKeepsZ) {
Components::GameObject* target = GetSceneManager().CreateEntity("Target");
ASSERT_NE(target, nullptr);
const uint64_t targetId = target->GetID();
SceneViewportMoveGizmo gizmo;
const SceneViewportOverlayData overlay = MakeIsometricOverlay();
gizmo.Update(MakeContext(target, Math::Vector2(400.0f, 300.0f), overlay));
const SceneViewportMoveGizmoPlaneDrawData& xyPlane = gizmo.GetDrawData().planes[0];
ASSERT_TRUE(xyPlane.visible);
const Math::Vector2 startMouse = QuadCenter(xyPlane);
const auto startContext = MakeContext(target, startMouse, overlay);
gizmo.Update(startContext);
ASSERT_TRUE(gizmo.TryBeginDrag(startContext, m_context.GetUndoManager()));
ASSERT_TRUE(gizmo.IsActive());
const auto dragContext = MakeContext(target, startMouse + Math::Vector2(48.0f, -24.0f), overlay);
gizmo.Update(dragContext);
gizmo.UpdateDrag(dragContext);
gizmo.EndDrag(m_context.GetUndoManager());
const Math::Vector3 movedPosition = target->GetTransform()->GetPosition();
EXPECT_GT(std::abs(movedPosition.x), 0.05f);
EXPECT_GT(std::abs(movedPosition.y), 0.05f);
EXPECT_NEAR(movedPosition.z, 0.0f, 1e-4f);
EXPECT_TRUE(m_context.GetUndoManager().CanUndo());
m_context.GetUndoManager().Undo();
Components::GameObject* restoredTarget = GetSceneManager().GetEntity(targetId);
ASSERT_NE(restoredTarget, nullptr);
const Math::Vector3 restoredPosition = restoredTarget->GetTransform()->GetPosition();
EXPECT_NEAR(restoredPosition.x, 0.0f, 1e-4f);
EXPECT_NEAR(restoredPosition.y, 0.0f, 1e-4f);
EXPECT_NEAR(restoredPosition.z, 0.0f, 1e-4f);
}
} // namespace
} // namespace XCEngine::Editor