#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, ApplyToLooksAtControllerFocalPointInViewSpace) { SceneViewportCameraController controller; controller.Reset(); controller.Focus(Vector3(2.0f, 1.0f, -3.0f)); GameObject cameraObject("EditorCamera"); controller.ApplyTo(*cameraObject.GetTransform()); const Vector3 focalPointInViewSpace = cameraObject.GetTransform()->InverseTransformPoint(controller.GetFocalPoint()); EXPECT_NEAR(focalPointInViewSpace.x, 0.0f, 1e-3f); EXPECT_NEAR(focalPointInViewSpace.y, 0.0f, 1e-3f); EXPECT_NEAR(focalPointInViewSpace.z, controller.GetDistance(), 1e-3f); } 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_GT(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_GT(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_LT(Vector3::Dot(panDelta, right), 0.0f); EXPECT_LT(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(Vector3::Dot(positionDelta, forward), 0.0f); EXPECT_GT(Vector3::Dot(positionDelta, right), 0.0f); EXPECT_TRUE(NearlyEqual(controller.GetFocalPoint() - controller.GetPosition(), initialOffset, 1e-3f)); } TEST(SceneViewportCameraController_Test, ZoomDoesNotChangeFlySpeed) { SceneViewportCameraController zoomedController; zoomedController.Reset(); SceneViewportCameraController baselineController; baselineController.Reset(); SceneViewportCameraInputState zoomInput = {}; zoomInput.viewportHeight = 720.0f; zoomInput.zoomDelta = 8.0f; zoomedController.ApplyInput(zoomInput); const Vector3 zoomedPositionBeforeMove = zoomedController.GetPosition(); const Vector3 baselinePositionBeforeMove = baselineController.GetPosition(); SceneViewportCameraInputState moveInput = {}; moveInput.viewportHeight = 720.0f; moveInput.deltaTime = 0.5f; moveInput.moveForward = 1.0f; zoomedController.ApplyInput(moveInput); baselineController.ApplyInput(moveInput); EXPECT_FLOAT_EQ(zoomedController.GetFlySpeed(), baselineController.GetFlySpeed()); const float zoomedTravel = (zoomedController.GetPosition() - zoomedPositionBeforeMove).Magnitude(); const float baselineTravel = (baselineController.GetPosition() - baselinePositionBeforeMove).Magnitude(); EXPECT_NEAR(zoomedTravel, baselineTravel, 1e-3f); } TEST(SceneViewportCameraController_Test, FlySpeedDeltaAdjustsMovementSpeedIndependentlyFromZoom) { SceneViewportCameraController fasterController; fasterController.Reset(); const Vector3 fasterInitialPosition = fasterController.GetPosition(); SceneViewportCameraController baselineController; baselineController.Reset(); const Vector3 baselineInitialPosition = baselineController.GetPosition(); SceneViewportCameraInputState speedInput = {}; speedInput.viewportHeight = 720.0f; speedInput.flySpeedDelta = 4.0f; fasterController.ApplyInput(speedInput); SceneViewportCameraInputState moveInput = {}; moveInput.viewportHeight = 720.0f; moveInput.deltaTime = 0.5f; moveInput.moveForward = 1.0f; fasterController.ApplyInput(moveInput); baselineController.ApplyInput(moveInput); const float fasterTravel = (fasterController.GetPosition() - fasterInitialPosition).Magnitude(); const float baselineTravel = (baselineController.GetPosition() - baselineInitialPosition).Magnitude(); EXPECT_GT(fasterController.GetFlySpeed(), baselineController.GetFlySpeed()); EXPECT_GT(fasterTravel, baselineTravel); } 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); }