251 lines
10 KiB
C++
251 lines
10 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <XCEngine/Core/Math/Quaternion.h>
|
|
|
|
#include "Core/EditorContext.h"
|
|
#include "Managers/SceneManager.h"
|
|
#include "Viewport/SceneViewportMath.h"
|
|
#include "Viewport/SceneViewportScaleGizmo.h"
|
|
|
|
namespace XCEngine::Editor {
|
|
namespace {
|
|
|
|
float HandleLength(const SceneViewportScaleGizmoAxisHandleDrawData& handle) {
|
|
return (handle.end - handle.start).Magnitude();
|
|
}
|
|
|
|
Math::Vector2 HandleDirection(const SceneViewportScaleGizmoAxisHandleDrawData& handle) {
|
|
return (handle.end - handle.start).Normalized();
|
|
}
|
|
|
|
const SceneViewportScaleGizmoAxisHandleDrawData* FindAxisHandle(
|
|
const SceneViewportScaleGizmoDrawData& drawData,
|
|
SceneViewportScaleGizmoHandle handleKind) {
|
|
for (const SceneViewportScaleGizmoAxisHandleDrawData& handle : drawData.axisHandles) {
|
|
if (handle.handle == handleKind) {
|
|
return &handle;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
class SceneViewportScaleGizmoTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
m_context.GetSceneManager().NewScene("Scale 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 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 SceneViewportScaleGizmoContext MakeContext(
|
|
Components::GameObject* selectedObject,
|
|
const Math::Vector2& mousePosition) {
|
|
SceneViewportScaleGizmoContext context = {};
|
|
context.overlay = MakeOverlay();
|
|
context.viewportSize = Math::Vector2(800.0f, 600.0f);
|
|
context.mousePosition = mousePosition;
|
|
context.selectedObject = selectedObject;
|
|
return context;
|
|
}
|
|
|
|
static SceneViewportScaleGizmoContext MakeContext(
|
|
Components::GameObject* selectedObject,
|
|
const Math::Vector2& mousePosition,
|
|
const SceneViewportOverlayData& overlay) {
|
|
SceneViewportScaleGizmoContext 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(SceneViewportScaleGizmoTest, UpdateHighlightsXAxisWhenMouseIsNearXAxisHandle) {
|
|
Components::GameObject* target = GetSceneManager().CreateEntity("Target");
|
|
ASSERT_NE(target, nullptr);
|
|
|
|
SceneViewportScaleGizmo gizmo;
|
|
gizmo.Update(MakeContext(target, Math::Vector2(400.0f, 300.0f)));
|
|
|
|
ASSERT_TRUE(gizmo.GetDrawData().visible);
|
|
const SceneViewportScaleGizmoAxisHandleDrawData* xHandle =
|
|
FindAxisHandle(gizmo.GetDrawData(), SceneViewportScaleGizmoHandle::X);
|
|
ASSERT_NE(xHandle, nullptr);
|
|
ASSERT_TRUE(xHandle->visible);
|
|
|
|
gizmo.Update(MakeContext(target, xHandle->capCenter));
|
|
|
|
EXPECT_TRUE(gizmo.IsHoveringHandle());
|
|
EXPECT_TRUE(FindAxisHandle(gizmo.GetDrawData(), SceneViewportScaleGizmoHandle::X)->hovered);
|
|
}
|
|
|
|
TEST_F(SceneViewportScaleGizmoTest, DraggingXAxisOnChildWithScaledParentChangesOnlyLocalXScale) {
|
|
Components::GameObject* parent = GetSceneManager().CreateEntity("Parent");
|
|
ASSERT_NE(parent, nullptr);
|
|
parent->GetTransform()->SetLocalScale(Math::Vector3(0.5f, 2.0f, 1.5f));
|
|
parent->GetTransform()->SetLocalRotation(Math::Quaternion::FromAxisAngle(Math::Vector3::Up(), Math::PI * 0.25f));
|
|
|
|
Components::GameObject* target = GetSceneManager().CreateEntity("Target", parent);
|
|
ASSERT_NE(target, nullptr);
|
|
target->GetTransform()->SetLocalScale(Math::Vector3(1.0f, 2.0f, 3.0f));
|
|
|
|
const SceneViewportOverlayData overlay = MakeIsometricOverlay();
|
|
SceneViewportScaleGizmo gizmo;
|
|
gizmo.Update(MakeContext(target, Math::Vector2(400.0f, 300.0f), overlay));
|
|
|
|
const SceneViewportScaleGizmoAxisHandleDrawData* xHandle =
|
|
FindAxisHandle(gizmo.GetDrawData(), SceneViewportScaleGizmoHandle::X);
|
|
ASSERT_NE(xHandle, nullptr);
|
|
ASSERT_TRUE(xHandle->visible);
|
|
|
|
const Math::Vector2 startMouse = xHandle->capCenter;
|
|
const Math::Vector2 dragMouse = startMouse + HandleDirection(*xHandle) * 48.0f;
|
|
const auto startContext = MakeContext(target, startMouse, overlay);
|
|
gizmo.Update(startContext);
|
|
ASSERT_TRUE(gizmo.TryBeginDrag(startContext, m_context.GetUndoManager()));
|
|
|
|
const auto dragContext = MakeContext(target, dragMouse, overlay);
|
|
gizmo.Update(dragContext);
|
|
gizmo.UpdateDrag(dragContext);
|
|
gizmo.EndDrag(m_context.GetUndoManager());
|
|
|
|
const Math::Vector3 localScale = target->GetTransform()->GetLocalScale();
|
|
EXPECT_GT(localScale.x, 1.1f);
|
|
EXPECT_NEAR(localScale.y, 2.0f, 1e-4f);
|
|
EXPECT_NEAR(localScale.z, 3.0f, 1e-4f);
|
|
}
|
|
|
|
TEST_F(SceneViewportScaleGizmoTest, DraggingXAxisTemporarilyChangesHandleLengthAndResetsAfterRelease) {
|
|
Components::GameObject* target = GetSceneManager().CreateEntity("Target");
|
|
ASSERT_NE(target, nullptr);
|
|
|
|
const SceneViewportOverlayData overlay = MakeIsometricOverlay();
|
|
SceneViewportScaleGizmo gizmo;
|
|
gizmo.Update(MakeContext(target, Math::Vector2(400.0f, 300.0f), overlay));
|
|
|
|
const SceneViewportScaleGizmoAxisHandleDrawData* xInitial =
|
|
FindAxisHandle(gizmo.GetDrawData(), SceneViewportScaleGizmoHandle::X);
|
|
ASSERT_NE(xInitial, nullptr);
|
|
ASSERT_TRUE(xInitial->visible);
|
|
const float initialLength = HandleLength(*xInitial);
|
|
|
|
const Math::Vector2 startMouse = xInitial->capCenter;
|
|
const Math::Vector2 dragMouse = startMouse + HandleDirection(*xInitial) * 48.0f;
|
|
const auto startContext = MakeContext(target, startMouse, overlay);
|
|
gizmo.Update(startContext);
|
|
ASSERT_TRUE(gizmo.TryBeginDrag(startContext, m_context.GetUndoManager()));
|
|
|
|
const auto dragContext = MakeContext(target, dragMouse, overlay);
|
|
gizmo.Update(dragContext);
|
|
gizmo.UpdateDrag(dragContext);
|
|
gizmo.Update(dragContext);
|
|
|
|
const SceneViewportScaleGizmoAxisHandleDrawData* xDuring =
|
|
FindAxisHandle(gizmo.GetDrawData(), SceneViewportScaleGizmoHandle::X);
|
|
ASSERT_NE(xDuring, nullptr);
|
|
EXPECT_GT(HandleLength(*xDuring), initialLength + 6.0f);
|
|
|
|
gizmo.EndDrag(m_context.GetUndoManager());
|
|
gizmo.Update(dragContext);
|
|
|
|
const SceneViewportScaleGizmoAxisHandleDrawData* xAfter =
|
|
FindAxisHandle(gizmo.GetDrawData(), SceneViewportScaleGizmoHandle::X);
|
|
ASSERT_NE(xAfter, nullptr);
|
|
EXPECT_NEAR(HandleLength(*xAfter), initialLength, 1.0f);
|
|
}
|
|
|
|
TEST_F(SceneViewportScaleGizmoTest, DraggingCenterHandleScalesUniformlyAndCreatesUndoStep) {
|
|
Components::GameObject* target = GetSceneManager().CreateEntity("Target");
|
|
ASSERT_NE(target, nullptr);
|
|
const uint64_t targetId = target->GetID();
|
|
|
|
SceneViewportScaleGizmo gizmo;
|
|
const SceneViewportOverlayData overlay = MakeIsometricOverlay();
|
|
gizmo.Update(MakeContext(target, Math::Vector2(400.0f, 300.0f), overlay));
|
|
|
|
ASSERT_TRUE(gizmo.GetDrawData().centerHandle.visible);
|
|
const Math::Vector2 startMouse = gizmo.GetDrawData().centerHandle.center;
|
|
const auto startContext = MakeContext(target, startMouse, overlay);
|
|
gizmo.Update(startContext);
|
|
ASSERT_TRUE(gizmo.TryBeginDrag(startContext, m_context.GetUndoManager()));
|
|
|
|
const auto dragContext = MakeContext(target, startMouse + Math::Vector2(28.0f, -28.0f), overlay);
|
|
gizmo.Update(dragContext);
|
|
gizmo.UpdateDrag(dragContext);
|
|
gizmo.EndDrag(m_context.GetUndoManager());
|
|
|
|
const Math::Vector3 localScale = target->GetTransform()->GetLocalScale();
|
|
EXPECT_GT(localScale.x, 1.1f);
|
|
EXPECT_NEAR(localScale.x, localScale.y, 1e-4f);
|
|
EXPECT_NEAR(localScale.y, localScale.z, 1e-4f);
|
|
EXPECT_TRUE(m_context.GetUndoManager().CanUndo());
|
|
|
|
m_context.GetUndoManager().Undo();
|
|
Components::GameObject* restoredTarget = GetSceneManager().GetEntity(targetId);
|
|
ASSERT_NE(restoredTarget, nullptr);
|
|
EXPECT_NEAR(restoredTarget->GetTransform()->GetLocalScale().x, 1.0f, 1e-4f);
|
|
EXPECT_NEAR(restoredTarget->GetTransform()->GetLocalScale().y, 1.0f, 1e-4f);
|
|
EXPECT_NEAR(restoredTarget->GetTransform()->GetLocalScale().z, 1.0f, 1e-4f);
|
|
}
|
|
|
|
TEST_F(SceneViewportScaleGizmoTest, RotatedObjectPlacesXAxisHandleAlongProjectedLocalRight) {
|
|
Components::GameObject* target = GetSceneManager().CreateEntity("Target");
|
|
ASSERT_NE(target, nullptr);
|
|
target->GetTransform()->SetLocalRotation(Math::Quaternion::FromAxisAngle(Math::Vector3::Up(), Math::PI * 0.33f));
|
|
|
|
const SceneViewportOverlayData overlay = MakeIsometricOverlay();
|
|
SceneViewportScaleGizmo gizmo;
|
|
gizmo.Update(MakeContext(target, Math::Vector2(400.0f, 300.0f), overlay));
|
|
|
|
const SceneViewportScaleGizmoAxisHandleDrawData* xHandle =
|
|
FindAxisHandle(gizmo.GetDrawData(), SceneViewportScaleGizmoHandle::X);
|
|
ASSERT_NE(xHandle, nullptr);
|
|
ASSERT_TRUE(xHandle->visible);
|
|
|
|
Math::Vector2 expectedDirection = Math::Vector2::Zero();
|
|
ASSERT_TRUE(ProjectSceneViewportAxisDirectionAtPoint(
|
|
overlay,
|
|
800.0f,
|
|
600.0f,
|
|
target->GetTransform()->GetPosition(),
|
|
target->GetTransform()->GetRight(),
|
|
expectedDirection));
|
|
|
|
EXPECT_GT(Math::Vector2::Dot(HandleDirection(*xHandle), expectedDirection), 0.99f);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace XCEngine::Editor
|