#include #include "Viewport/SceneViewportCameraController.h" #include #include namespace { bool NearlyEqual(float lhs, float rhs, float epsilon = 1e-4f) { return std::abs(lhs - rhs) <= epsilon; } bool NearlyEqual(const XCEngine::Math::Vector3& lhs, const XCEngine::Math::Vector3& rhs, float epsilon = 1e-4f) { return NearlyEqual(lhs.x, rhs.x, epsilon) && NearlyEqual(lhs.y, rhs.y, epsilon) && NearlyEqual(lhs.z, rhs.z, epsilon); } } // namespace using XCEngine::Components::GameObject; using XCEngine::Editor::SceneViewportCameraController; using XCEngine::Editor::SceneViewportCameraInputState; using XCEngine::Math::Vector3; TEST(SceneViewportCameraController_Test, ApplyToMatchesComputedPositionAndForward) { SceneViewportCameraController controller; controller.Reset(); controller.Focus(Vector3(2.0f, 1.0f, -3.0f)); GameObject cameraObject("EditorCamera"); controller.ApplyTo(*cameraObject.GetTransform()); EXPECT_TRUE(NearlyEqual(cameraObject.GetTransform()->GetPosition(), controller.GetPosition())); EXPECT_TRUE(NearlyEqual( cameraObject.GetTransform()->GetForward().Normalized(), controller.GetForward(), 1e-3f)); EXPECT_GT(Vector3::Dot(cameraObject.GetTransform()->GetUp().Normalized(), Vector3::Up()), 0.0f); } TEST(SceneViewportCameraController_Test, LookInputRotatesCameraInPlaceAndKeepsDistance) { SceneViewportCameraController controller; controller.Reset(); const Vector3 initialPosition = controller.GetPosition(); const Vector3 initialFocus = controller.GetFocalPoint(); const float initialPitch = controller.GetPitchDegrees(); SceneViewportCameraInputState input = {}; input.viewportHeight = 720.0f; input.lookDeltaX = 20.0f; input.lookDeltaY = -15.0f; controller.ApplyInput(input); EXPECT_TRUE(NearlyEqual(controller.GetPosition(), initialPosition)); EXPECT_FALSE(NearlyEqual(controller.GetFocalPoint(), initialFocus)); EXPECT_LT(controller.GetPitchDegrees(), initialPitch); EXPECT_TRUE(NearlyEqual( controller.GetFocalPoint(), controller.GetPosition() + controller.GetForward() * controller.GetDistance(), 1e-3f)); } TEST(SceneViewportCameraController_Test, OrbitInputRotatesAroundFocalPointAndKeepsDistance) { SceneViewportCameraController controller; controller.Reset(); const Vector3 initialPosition = controller.GetPosition(); const Vector3 initialFocus = controller.GetFocalPoint(); const float initialDistance = controller.GetDistance(); const float initialPitch = controller.GetPitchDegrees(); SceneViewportCameraInputState input = {}; input.viewportHeight = 720.0f; input.orbitDeltaX = 20.0f; input.orbitDeltaY = -15.0f; controller.ApplyInput(input); EXPECT_FALSE(NearlyEqual(controller.GetPosition(), initialPosition)); EXPECT_TRUE(NearlyEqual(controller.GetFocalPoint(), initialFocus)); EXPECT_LT(controller.GetPitchDegrees(), initialPitch); EXPECT_NEAR((controller.GetFocalPoint() - controller.GetPosition()).Magnitude(), initialDistance, 1e-3f); } TEST(SceneViewportCameraController_Test, PanAndZoomUpdateCameraStateConsistently) { SceneViewportCameraController controller; controller.Reset(); const Vector3 initialPosition = controller.GetPosition(); const Vector3 initialFocus = controller.GetFocalPoint(); const float initialDistance = controller.GetDistance(); const Vector3 right = Vector3::Normalize(Vector3::Cross(Vector3::Up(), controller.GetForward())); const Vector3 up = Vector3::Normalize(Vector3::Cross(controller.GetForward(), right)); SceneViewportCameraInputState input = {}; input.viewportHeight = 720.0f; input.panDeltaX = 16.0f; input.panDeltaY = -10.0f; input.zoomDelta = 1.0f; controller.ApplyInput(input); EXPECT_FALSE(NearlyEqual(controller.GetPosition(), initialPosition)); EXPECT_FALSE(NearlyEqual(controller.GetFocalPoint(), initialFocus)); EXPECT_LT(controller.GetDistance(), initialDistance); const Vector3 panDelta = controller.GetFocalPoint() - initialFocus; EXPECT_NEAR(Vector3::Dot(panDelta, controller.GetForward()), 0.0f, 1e-3f); EXPECT_GT(std::abs(Vector3::Dot(panDelta, right)), 0.0f); EXPECT_GT(std::abs(Vector3::Dot(panDelta, up)), 0.0f); EXPECT_TRUE(NearlyEqual( controller.GetFocalPoint(), controller.GetPosition() + controller.GetForward() * controller.GetDistance(), 1e-3f)); } TEST(SceneViewportCameraController_Test, FlyInputMovesCameraAndFocalPointTogether) { SceneViewportCameraController controller; controller.Reset(); const Vector3 initialPosition = controller.GetPosition(); const Vector3 initialFocus = controller.GetFocalPoint(); const Vector3 initialOffset = initialFocus - initialPosition; const Vector3 forward = controller.GetForward(); const Vector3 right = Vector3::Normalize(Vector3::Cross(Vector3::Up(), forward)); SceneViewportCameraInputState input = {}; input.viewportHeight = 720.0f; input.deltaTime = 0.5f; input.moveForward = 1.0f; input.moveRight = 1.0f; controller.ApplyInput(input); EXPECT_FALSE(NearlyEqual(controller.GetPosition(), initialPosition)); EXPECT_FALSE(NearlyEqual(controller.GetFocalPoint(), initialFocus)); const Vector3 positionDelta = controller.GetPosition() - initialPosition; EXPECT_GT(std::abs(Vector3::Dot(positionDelta, forward)), 0.0f); EXPECT_GT(std::abs(Vector3::Dot(positionDelta, right)), 0.0f); EXPECT_TRUE(NearlyEqual(controller.GetFocalPoint() - controller.GetPosition(), initialOffset, 1e-3f)); } TEST(SceneViewportCameraController_Test, FocusMovesPivotWithoutChangingDistance) { SceneViewportCameraController controller; controller.Reset(); const float initialDistance = controller.GetDistance(); const Vector3 target(5.0f, -2.0f, 7.5f); controller.Focus(target); EXPECT_TRUE(NearlyEqual(controller.GetFocalPoint(), target)); EXPECT_FLOAT_EQ(controller.GetDistance(), initialDistance); }