You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Framework/Tests/CameraState.cpp

204 lines
8.2 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Math/Transform.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AZTestShared/Math/MathTestHelpers.h>
#include <AzCore/Math/SimdMath.h>
#include <AzCore/Math/Matrix4x4.h>
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<AZStd::tuple<float, float, float>>
{
};
class Rotation
: public CameraStateFixture
, public ::testing::WithParamInterface<AZStd::tuple<float, float, float>>
{
};
class WorldFromViewMatrix
: public CameraStateFixture
, public ::testing::WithParamInterface<AZStd::tuple<AZ::Vector3, AZ::Vector3, AZ::Vector2>>
{
};
class PerspectiveMatrix
: public CameraStateFixture
, public ::testing::WithParamInterface<AZStd::tuple<float, float, float, float>>
{
};
// Taken from Atom::MatrixUtils for testing purposes, this can be removed if MakePerspectiveFovMatrixRH makes it into AZ
static AZ::Matrix4x4 MakePerspectiveMatrixRH(float fovY, float aspectRatio, float nearClip, float farClip)
{
float sinFov, cosFov;
AZ::SinCos(0.5f * fovY, sinFov, cosFov);
float yScale = cosFov / sinFov; //cot(fovY/2)
float xScale = yScale / aspectRatio;
AZ::Matrix4x4 out;
out.SetRow(0, xScale, 0.f, 0.f, 0.f );
out.SetRow(1, 0.f, yScale, 0.f, 0.f );
out.SetRow(2, 0.f, 0.f, farClip / (nearClip - farClip), nearClip*farClip / (nearClip - farClip) );
out.SetRow(3, 0.f, 0.f, -1.f, 0.f );
return out;
}
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 = MakePerspectiveMatrixRH(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