/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include namespace UnitTest { class CameraStateFixture : public ::testing::Test { public: void SetUp() override { m_cameraState = AzFramework::CreateDefaultCamera(AZ::Transform::CreateIdentity(), AZ::Vector2(1024, 768)); } AzFramework::CameraState m_cameraState; }; class Translation : public CameraStateFixture , public ::testing::WithParamInterface> { }; class Rotation : public CameraStateFixture , public ::testing::WithParamInterface> { }; class WorldFromViewMatrix : public CameraStateFixture , public ::testing::WithParamInterface> { }; class PerspectiveMatrix : public CameraStateFixture , public ::testing::WithParamInterface> { }; TEST_P(Translation, Permutation) { // Given a position const auto [x, y, z] = GetParam(); const AZ::Vector3 expectedPosition(x, y, z); // Set the camera state's transform to the expected position AzFramework::SetCameraTransform(m_cameraState, AZ::Transform::CreateTranslation(expectedPosition)); // Expect the camera state transform's position to be expected position EXPECT_THAT(m_cameraState.m_position, IsCloseTolerance(expectedPosition, 0.01f)); // Expect the camera state transform's orientation to be identity EXPECT_THAT(m_cameraState.m_forward, IsCloseTolerance(AZ::Vector3::CreateAxisY(), 0.01f)); EXPECT_THAT(m_cameraState.m_up, IsCloseTolerance(AZ::Vector3::CreateAxisZ(), 0.01f)); EXPECT_THAT(m_cameraState.m_side, IsCloseTolerance(AZ::Vector3::CreateAxisX(), 0.01f)); } INSTANTIATE_TEST_CASE_P( CameraState, Translation, testing::Combine( testing::Values(0.0f, 1.0f), testing::Values(0.0f, 1.0f), testing::Values(0.0f, 1.0f)) ); TEST_P(Rotation, Permutation) { int expectedErrors = -1; AZ_TEST_START_TRACE_SUPPRESSION; // Given an orientation derived from the look at points const auto [x, y, z] = GetParam(); const AZ::Vector3 from = AZ::Vector3::CreateZero(); const AZ::Vector3 to = AZ::Vector3(x, y, z); const AZ::Transform expectedTransform = AZ::Transform::CreateLookAt(from, AZ::Vector3(x, y, z)); // Set the camera's rotation to the expected orientation AzFramework::SetCameraTransform(m_cameraState, expectedTransform); // Expect the camera state transform's position to be identity EXPECT_THAT(m_cameraState.m_position, IsCloseTolerance(AZ::Vector3::CreateZero(), 0.01f)); if (from.IsClose(to, 0.001f)) { // Expect one error to be generated by AZ::Transform::CreateLookAt() due to from and to being the same position expectedErrors = 1; // If the look at points yield an invalid orientation, expect identity rotation basis vectors EXPECT_THAT(m_cameraState.m_forward, IsCloseTolerance(AZ::Vector3::CreateAxisY(), 0.01f)); EXPECT_THAT(m_cameraState.m_up, IsCloseTolerance(AZ::Vector3::CreateAxisZ(), 0.01f)); EXPECT_THAT(m_cameraState.m_side, IsCloseTolerance(AZ::Vector3::CreateAxisX(), 0.01f)); } else { // Expect no errors to be generated by AZ::Transform::CreateLookAt() expectedErrors = 0; // If the look at points yield a valid orientation, expect the rotation basis vectors to be that of the orientation EXPECT_THAT(m_cameraState.m_forward, IsCloseTolerance(expectedTransform.GetBasisY(), 0.01f)); EXPECT_THAT(m_cameraState.m_up, IsCloseTolerance(expectedTransform.GetBasisZ(), 0.01f)); EXPECT_THAT(m_cameraState.m_side, IsCloseTolerance(expectedTransform.GetBasisX(), 0.01f)); } AZ_TEST_STOP_TRACE_SUPPRESSION(expectedErrors); } INSTANTIATE_TEST_CASE_P( CameraState, Rotation, testing::Combine( testing::Values(0.0f, 1.0f), testing::Values(0.0f, 1.0f), testing::Values(0.0f, 1.0f)) ); TEST_P(WorldFromViewMatrix, Permutation) { auto [translation, eulerRotation, viewportSize] = GetParam(); // Quaternion::CreateFromEulerAnglesDegrees takes a non-const Vector3& AZ::Vector3 eulerRotationCopy = eulerRotation; AZ::Quaternion rotation = AZ::Quaternion::CreateFromEulerAnglesDegrees(eulerRotationCopy); AZ::Matrix4x4 worldFromView = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(rotation, translation); m_cameraState = AzFramework::CreateCameraFromWorldFromViewMatrix(worldFromView, viewportSize); EXPECT_EQ(m_cameraState.m_viewportSize, viewportSize); EXPECT_THAT(m_cameraState.m_position, IsCloseTolerance(translation, 0.01f)); // Translate back into a quaternion to safely compare rotations auto decomposedCameraStateRotation = AZ::Quaternion::CreateFromBasis(m_cameraState.m_side, m_cameraState.m_forward, m_cameraState.m_up); auto rotationDelta = 1.f - decomposedCameraStateRotation.Dot(decomposedCameraStateRotation); EXPECT_NEAR(rotationDelta, 0.f, 0.01f); } INSTANTIATE_TEST_CASE_P( CameraState, WorldFromViewMatrix, testing::Combine( testing::Values(AZ::Vector3{0.f, 0.f, 0.f}, AZ::Vector3{100.f, 0.f, 0.f}, AZ::Vector3{-5.f,10.f,-1.f}), testing::Values(AZ::Vector3{0.f, 0.f, 0.f}, AZ::Vector3{90.f, 0.f, 0.f}, AZ::Vector3{-45.f, -45.f, -45.f}), testing::Values(AZ::Vector2{100.f, 100.f}) ) ); TEST_P(PerspectiveMatrix, Permutation) { auto [fovY, aspectRatio, nearClip, farClip] = GetParam(); AZ::Matrix4x4 clipFromView; MakePerspectiveFovMatrixRH(clipFromView, fovY, aspectRatio, nearClip, farClip); AzFramework::SetCameraClippingVolumeFromPerspectiveFovMatrixRH(m_cameraState, clipFromView); EXPECT_NEAR(m_cameraState.m_nearClip, nearClip, 0.01f); EXPECT_NEAR(m_cameraState.m_farClip, farClip, 1.f); EXPECT_NEAR(m_cameraState.m_fovOrZoom, fovY, 0.01f); } INSTANTIATE_TEST_CASE_P( CameraState, PerspectiveMatrix, testing::Combine( testing::Values(1.f, 0.5f, 2.f), testing::Values(1.f, 16.f / 9.f), testing::Values(1.f, 50.f), testing::Values(100.f, 10000.f) ) ); } // namespace UnitTest