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/Gems/EMotionFX/Code/Tests/TransformUnitTests.cpp

974 lines
41 KiB
C++

/*
* 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 <gtest/gtest.h>
#include <AzCore/Math/Matrix3x3.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <cmath>
#include <Tests/Printers.h>
#include <Tests/Matchers.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/MathUtils.h>
#include <AzCore/Math/Quaternion.h>
#include <MCore/Source/AzCoreConversions.h>
#include <EMotionFX/Source/PlayBackInfo.h>
#include <EMotionFX/Source/Transform.h>
#if defined(EMFX_SCALE_DISABLED)
#define EMFX_SCALE false
#else
#define EMFX_SCALE true
#endif
namespace EMotionFX
{
static const float sqrt2 = std::sqrt(2.0f);
static const float sqrt2over2 = sqrt2 / 2.0f;
AZ::Matrix3x3 TensorProduct(const AZ::Vector3& u, const AZ::Vector3& v)
{
AZ::Matrix3x3 mat{};
mat.SetElement(0, 0, u.GetX() * v.GetX());
mat.SetElement(0, 1, u.GetX() * v.GetY());
mat.SetElement(0, 2, u.GetX() * v.GetZ());
mat.SetElement(1, 0, u.GetY() * v.GetX());
mat.SetElement(1, 1, u.GetY() * v.GetY());
mat.SetElement(1, 2, u.GetY() * v.GetZ());
mat.SetElement(2, 0, u.GetZ() * v.GetX());
mat.SetElement(2, 1, u.GetZ() * v.GetY());
mat.SetElement(2, 2, u.GetZ() * v.GetZ());
return mat;
}
TEST(TransformFixture, CreateIdentity)
{
const Transform transform = Transform::CreateIdentity();
EXPECT_TRUE(transform.m_position.IsZero());
EXPECT_EQ(transform.m_rotation, AZ::Quaternion::CreateIdentity());
EMFX_SCALECODE
(
EXPECT_EQ(transform.m_scale, AZ::Vector3::CreateOne());
)
}
TEST(TransformFixture, CreateIdentityWithZeroScale)
{
const Transform transform = Transform::CreateIdentityWithZeroScale();
EXPECT_TRUE(transform.m_position.IsZero());
EXPECT_EQ(transform.m_rotation, AZ::Quaternion::CreateIdentity());
EMFX_SCALECODE
(
EXPECT_EQ(transform.m_scale, AZ::Vector3::CreateZero());
)
}
TEST(TransformFixture, CreateZero)
{
const Transform transform = Transform::CreateZero();
EXPECT_TRUE(transform.m_position.IsZero());
EXPECT_EQ(transform.m_rotation, AZ::Quaternion::CreateZero());
EMFX_SCALECODE
(
EXPECT_EQ(transform.m_scale, AZ::Vector3::CreateZero());
)
}
TEST(TransformFixture, ConstructFromVec3Quat)
{
const Transform transform(AZ::Vector3(6.0f, 7.0f, 8.0f), AZ::Quaternion::CreateRotationX(AZ::Constants::HalfPi));
EXPECT_EQ(transform.m_position, AZ::Vector3(6.0f, 7.0f, 8.0f));
EXPECT_THAT(transform.m_rotation, IsClose(AZ::Quaternion(sqrt2over2, 0.0f, 0.0f, sqrt2over2)));
EMFX_SCALECODE
(
EXPECT_EQ(transform.m_scale, AZ::Vector3::CreateOne());
)
}
using TransformConstructFromVec3QuatVec3Params = ::testing::tuple<AZ::Vector3, ::testing::tuple<float, float, float>, AZ::Vector3>;
class TransformConstructFromVec3QuatVec3Fixture
: public ::testing::TestWithParam<TransformConstructFromVec3QuatVec3Params>
{
public:
const AZ::Vector3& ExpectedPosition() const
{
return ::testing::get<0>(GetParam());
}
AZ::Quaternion ExpectedRotation() const
{
return MCore::AzEulerAnglesToAzQuat(
::testing::get<0>(::testing::get<1>(GetParam())),
::testing::get<1>(::testing::get<1>(GetParam())),
::testing::get<2>(::testing::get<1>(GetParam()))
);
}
const AZ::Vector3& ExpectedScale() const
{
return ::testing::get<2>(GetParam());
}
bool HasNonUniformScale() const
{
const AZ::Vector3 scale = ExpectedScale();
return
!AZ::IsClose(scale.GetX(), scale.GetY(), AZ::Constants::Tolerance) ||
!AZ::IsClose(scale.GetX(), scale.GetZ(), AZ::Constants::Tolerance) ||
!AZ::IsClose(scale.GetY(), scale.GetZ(), AZ::Constants::Tolerance);
}
// Returns a transformation matrix where the position is mirrored, the
// rotation axis is mirrored, and the rotation angle is negated
AZ::Matrix4x4 GetMirroredTransform(const AZ::Vector3& axis) const
{
const AZ::Matrix3x3 mirrorMatrix = AZ::Matrix3x3::CreateIdentity() - (2.0f * TensorProduct(axis, axis));
const AZ::Vector3 mirrorPosition = mirrorMatrix * ExpectedPosition();
AZ::Vector3 extractedAxis;
float extractedAngle;
ExpectedRotation().ConvertToAxisAngle(extractedAxis, extractedAngle);
const AZ::Quaternion mirrorRotation = AZ::Quaternion::CreateFromAxisAngle(
mirrorMatrix * AZ::Vector3(extractedAxis.GetX(), extractedAxis.GetY(), extractedAxis.GetZ()),
-extractedAngle
);
return AZ::Matrix4x4::CreateFromQuaternionAndTranslation(mirrorRotation, mirrorPosition)
* AZ::Matrix4x4::CreateScale(ExpectedScale());
}
};
TEST_P(TransformConstructFromVec3QuatVec3Fixture, ConstructFromVec3QuatVec3)
{
const Transform transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale());
EXPECT_THAT(transform.m_position, IsClose(ExpectedPosition()));
EXPECT_THAT(transform.m_rotation, IsClose(ExpectedRotation()));
EMFX_SCALECODE
(
EXPECT_THAT(transform.m_scale, IsClose(ExpectedScale()));
)
}
TEST_P(TransformConstructFromVec3QuatVec3Fixture, SetFromVec3QuatVec3)
{
Transform transform(AZ::Vector3(5.0f, 6.0f, 7.0f), AZ::Quaternion(0.1f, 0.2f, 0.3f, 0.4f), AZ::Vector3(8.0f, 9.0f, 10.0f));
transform.Set(ExpectedPosition(), ExpectedRotation(), ExpectedScale());
EXPECT_THAT(transform.m_position, IsClose(ExpectedPosition()));
EXPECT_THAT(transform.m_rotation, IsClose(ExpectedRotation()));
EMFX_SCALECODE
(
EXPECT_THAT(transform.m_scale, IsClose(ExpectedScale()));
)
}
INSTANTIATE_TEST_CASE_P(Test, TransformConstructFromVec3QuatVec3Fixture,
::testing::Combine(
::testing::Values(
AZ::Vector3::CreateZero(),
AZ::Vector3(6.0f, 7.0f, 8.0f)
),
::testing::Combine(
::testing::Values(0.0f, AZ::Constants::QuarterPi, AZ::Constants::HalfPi),
::testing::Values(0.0f, AZ::Constants::QuarterPi, AZ::Constants::HalfPi),
::testing::Values(0.0f, AZ::Constants::QuarterPi, AZ::Constants::HalfPi)
),
::testing::Values(
AZ::Vector3::CreateOne(),
AZ::Vector3(2.0f, 2.0f, 2.0f),
AZ::Vector3(2.0f, 3.0f, 4.0f)
)
)
);
TEST(TransformFixture, SetFromVec3Quat)
{
Transform transform(AZ::Vector3(5.0f, 6.0f, 7.0f), AZ::Quaternion(0.1f, 0.2f, 0.3f, 0.4f), AZ::Vector3(8.0f, 9.0f, 10.0f));
transform.Set(AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion::CreateRotationX(AZ::Constants::QuarterPi));
EXPECT_EQ(transform.m_position, AZ::Vector3(1.0f, 2.0f, 3.0f));
EXPECT_THAT(transform.m_rotation, IsClose(AZ::Quaternion::CreateRotationX(AZ::Constants::QuarterPi)));
EMFX_SCALECODE
(
EXPECT_EQ(transform.m_scale, AZ::Vector3::CreateOne());
)
}
TEST(TransformFixture, Identity)
{
Transform transform(AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion(0.1f, 0.2f, 0.3f, 0.4f), AZ::Vector3(4.0f, 5.0f, 6.0f));
transform.Identity();
EXPECT_EQ(transform.m_position, AZ::Vector3::CreateZero());
EXPECT_EQ(transform.m_rotation, AZ::Quaternion::CreateIdentity());
EMFX_SCALECODE
(
EXPECT_EQ(transform.m_scale, AZ::Vector3::CreateOne());
)
}
TEST(TransformFixture, Zero)
{
Transform transform(AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion(0.1f, 0.2f, 0.3f, 0.4f), AZ::Vector3(4.0f, 5.0f, 6.0f));
transform.Zero();
EXPECT_EQ(transform.m_position, AZ::Vector3::CreateZero());
EXPECT_EQ(transform.m_rotation, AZ::Quaternion::CreateZero());
EMFX_SCALECODE
(
EXPECT_EQ(transform.m_scale, AZ::Vector3::CreateZero());
)
}
TEST(TransformFixture, IdentityWithZeroScale)
{
Transform transform(AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion(0.1f, 0.2f, 0.3f, 0.4f), AZ::Vector3(4.0f, 5.0f, 6.0f));
transform.IdentityWithZeroScale();
EXPECT_EQ(transform.m_position, AZ::Vector3::CreateZero());
EXPECT_EQ(transform.m_rotation, AZ::Quaternion::CreateIdentity());
EMFX_SCALECODE
(
EXPECT_EQ(transform.m_scale, AZ::Vector3::CreateZero());
)
}
using TransformMultiplyParams = ::testing::tuple<Transform, Transform, Transform, Transform>;
using TransformMultiplyFixture = ::testing::TestWithParam<TransformMultiplyParams>;
TEST_P(TransformMultiplyFixture, Multiply)
{
const Transform& inputA = ::testing::get<0>(GetParam());
const Transform& inputB = ::testing::get<1>(GetParam());
const Transform& expected = ::testing::get<2>(GetParam());
Transform multiply(inputA);
multiply.Multiply(inputB);
EXPECT_THAT(multiply, IsClose(expected));
}
TEST_P(TransformMultiplyFixture, Multiplied)
{
const Transform& inputA = ::testing::get<0>(GetParam());
const Transform& inputB = ::testing::get<1>(GetParam());
const Transform& expected = ::testing::get<2>(GetParam());
EXPECT_THAT(
inputA.Multiplied(inputB),
IsClose(expected)
);
EXPECT_THAT(
inputA.Multiplied(Transform::CreateIdentity()),
IsClose(inputA)
);
}
TEST_P(TransformMultiplyFixture, PreMultiply)
{
Transform inputA = ::testing::get<0>(GetParam());
const Transform& inputB = ::testing::get<1>(GetParam());
const Transform& expected = ::testing::get<3>(GetParam());
EXPECT_THAT(
inputA.PreMultiply(inputB),
IsClose(expected)
);
EXPECT_THAT(
inputA.PreMultiply(Transform::CreateIdentity()),
IsClose(inputA)
);
}
TEST_P(TransformMultiplyFixture, MultiplyWithOutputParam)
{
const Transform& inputA = ::testing::get<0>(GetParam());
const Transform& inputB = ::testing::get<1>(GetParam());
const Transform& expected = ::testing::get<2>(GetParam());
Transform output = Transform::CreateIdentity();
inputA.Multiply(inputB, &output);
EXPECT_THAT(output, IsClose(expected));
}
TEST_P(TransformMultiplyFixture, PreMultiplied)
{
const Transform& inputA = ::testing::get<0>(GetParam());
const Transform& inputB = ::testing::get<1>(GetParam());
const Transform& expected = ::testing::get<3>(GetParam());
EXPECT_THAT(
inputA.PreMultiplied(inputB),
IsClose(expected)
);
EXPECT_THAT(
inputA.PreMultiplied(Transform::CreateIdentity()),
IsClose(inputA)
);
}
TEST_P(TransformMultiplyFixture, PreMultiplyWithOutputParam)
{
const Transform& inputA = ::testing::get<0>(GetParam());
const Transform& inputB = ::testing::get<1>(GetParam());
const Transform& expected = ::testing::get<3>(GetParam());
Transform output;
inputA.PreMultiply(inputB, &output);
EXPECT_THAT(
output,
IsClose(expected)
);
}
TEST_P(TransformMultiplyFixture, operatorMult)
{
const Transform& inputA = ::testing::get<0>(GetParam());
const Transform& inputB = ::testing::get<1>(GetParam());
const Transform& expected = ::testing::get<2>(GetParam());
const Transform& expectedPreMult = ::testing::get<3>(GetParam());
EXPECT_THAT(
inputA * inputB,
IsClose(expected)
);
EXPECT_THAT(
inputB * inputA,
IsClose(expectedPreMult)
);
EXPECT_THAT(
inputA * Transform::CreateIdentity(),
IsClose(inputA)
);
EXPECT_THAT(
inputB * Transform::CreateIdentity(),
IsClose(inputB)
);
}
INSTANTIATE_TEST_CASE_P(Test, TransformMultiplyFixture,
::testing::Values(
TransformMultiplyParams {
/* input a */{Transform::CreateIdentity()},
/* input b */{Transform::CreateIdentity()},
/* a * b = */{Transform::CreateIdentity()},
/* b * a = */{Transform::CreateIdentity()}
},
// symmetric cases (where a*b == b*a) -----------------------------
TransformMultiplyParams {
// just translation
/* input a */{AZ::Vector3::CreateOne(), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()},
/* input b */{AZ::Vector3::CreateOne(), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()},
/* a * b = */{AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()},
/* b * a = */{AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()}
},
TransformMultiplyParams {
// just rotation
/* input a */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3::CreateOne()},
/* input b */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3::CreateOne()},
/* a * b = */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::HalfPi), AZ::Vector3::CreateOne()},
/* b * a = */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::HalfPi), AZ::Vector3::CreateOne()}
},
TransformMultiplyParams {
// just scale
/* input a */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.0f, 2.0f, 2.0f)},
/* input b */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.0f, 2.0f, 2.0f)},
/* a * b = */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateIdentity(), AZ::Vector3(4.0f, 4.0f, 4.0f)},
/* b * a = */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateIdentity(), AZ::Vector3(4.0f, 4.0f, 4.0f)}
},
TransformMultiplyParams {
// translation and rotation
/* input a */{AZ::Vector3::CreateAxisY(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3::CreateOne()},
/* input b */{AZ::Vector3::CreateAxisY(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3::CreateOne()},
/* a * b = */{AZ::Vector3(0.0f, 1.0f + sqrt2over2, sqrt2over2), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::HalfPi), AZ::Vector3::CreateOne()},
/* b * a = */{AZ::Vector3(0.0f, 1.0f + sqrt2over2, sqrt2over2), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::HalfPi), AZ::Vector3::CreateOne()}
},
TransformMultiplyParams {
// rotation and scale
/* input a */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3(2.0f, 2.0f, 2.0f)},
/* input b */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3(2.0f, 2.0f, 2.0f)},
/* a * b = */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::HalfPi), AZ::Vector3(4.0f, 4.0f, 4.0f)},
/* b * a = */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::HalfPi), AZ::Vector3(4.0f, 4.0f, 4.0f)}
},
TransformMultiplyParams {
// translation and scale
/* input a */{AZ::Vector3::CreateOne(), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.0f, 2.0f, 2.0f)},
/* input b */{AZ::Vector3::CreateOne(), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.0f, 2.0f, 2.0f)},
/* a * b = */{AZ::Vector3(3.0f, 3.0f, 3.0f), AZ::Quaternion::CreateIdentity(), AZ::Vector3(4.0f, 4.0f, 4.0f)},
/* b * a = */{AZ::Vector3(3.0f, 3.0f, 3.0f), AZ::Quaternion::CreateIdentity(), AZ::Vector3(4.0f, 4.0f, 4.0f)}
},
TransformMultiplyParams {
// translation, rotation, and scale
/* input a */{AZ::Vector3::CreateOne(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3(2.0f, 2.0f, 2.0f)},
/* input b */{AZ::Vector3::CreateOne(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3(2.0f, 2.0f, 2.0f)},
/* a * b = */{AZ::Vector3(3.0f, 1.0f, 1.0f + 2*sqrt2), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::HalfPi), AZ::Vector3(4.0f, 4.0f, 4.0f)},
/* b * a = */{AZ::Vector3(3.0f, 1.0f, 1.0f + 2*sqrt2), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::HalfPi), AZ::Vector3(4.0f, 4.0f, 4.0f)}
},
// asymmetric cases (where a*b != b*a) -----------------------------
TransformMultiplyParams {
// translation and rotation
/* input a */{AZ::Vector3::CreateOne(), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()},
/* input b */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3::CreateOne()},
// translate then rotate
/* a * b = */{AZ::Vector3(1.0f, 0.0f, sqrt2), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3::CreateOne()},
// rotate then translate
/* b * a = */{AZ::Vector3::CreateOne(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3::CreateOne()}
},
TransformMultiplyParams {
// translation and scale
/* input a */{AZ::Vector3::CreateOne(), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()},
/* input b */{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.0f, 2.0f, 2.0f)},
// translate then scale
/* a * b = */{AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.0f, 2.0f, 2.0f)},
// scale then translate
/* b * a = */{AZ::Vector3::CreateOne(), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.0f, 2.0f, 2.0f)}
},
TransformMultiplyParams {
// rotation and scale
// rotation * scale are only asymmetric when there is a translation involved as well
/* input a */{AZ::Vector3::CreateOne(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3::CreateOne()},
/* input b */{AZ::Vector3::CreateOne(), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.0f, 2.0f, 2.0f)},
// rotate then scale
/* a * b = */{AZ::Vector3(3.0f, 3.0f, 3.0f), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3(2.0f, 2.0f, 2.0f)},
// scale then rotate
/* b * a = */{AZ::Vector3(2.0f, 1.0f, 1.0f + sqrt2), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3(2.0f, 2.0f, 2.0f)},
}
)
);
TEST(TransformFixture, TransformPoint)
{
EXPECT_THAT(
Transform(AZ::Vector3(5.0f, 0.0f, 0.0f), AZ::Quaternion::CreateIdentity())
.TransformPoint(AZ::Vector3::CreateZero()),
IsClose(AZ::Vector3(5.0f, 0.0f, 0.0f))
);
EXPECT_THAT(
Transform(AZ::Vector3(5.0f, 0.0f, 0.0f), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.5f, 1.0f, 1.0f))
.TransformPoint(AZ::Vector3::CreateAxisX()),
IsClose(EMFX_SCALE ? AZ::Vector3(7.5f, 0.0f, 0.0f) : AZ::Vector3(6.0f, 0.0f, 0.0f))
);
EXPECT_THAT(
Transform(AZ::Vector3::CreateZero(), AZ::Quaternion::CreateRotationX(AZ::Constants::QuarterPi), AZ::Vector3::CreateOne())
.TransformPoint(AZ::Vector3(0.0f, 1.0f, 0.0f)),
IsClose(AZ::Vector3(0.0f, sqrt2over2, sqrt2over2))
);
EXPECT_THAT(
Transform(AZ::Vector3::CreateZero(), AZ::Quaternion::CreateRotationX(AZ::Constants::QuarterPi), AZ::Vector3(1.0f, 2.0f, 3.0f))
.TransformPoint(AZ::Vector3::CreateOne()),
IsClose(AZ::Vector3(1.0f, -sqrt2over2, sqrt2over2 * 5.0f))
);
EXPECT_THAT(
Transform(AZ::Vector3(5.0f, 6.0f, 7.0f), AZ::Quaternion::CreateRotationX(AZ::Constants::QuarterPi), AZ::Vector3(1.0f, 2.0f, 3.0f))
.TransformPoint(AZ::Vector3::CreateOne()),
IsClose(AZ::Vector3(6.0f, 6.0f - sqrt2over2, 7.0f + sqrt2over2 * 5.0f))
);
}
TEST(TransformFixture, TransformVector)
{
EXPECT_THAT(
Transform(AZ::Vector3(5.0f, 0.0f, 0.0f), AZ::Quaternion::CreateIdentity())
.TransformVector(AZ::Vector3::CreateZero()),
IsClose(AZ::Vector3::CreateZero())
);
EXPECT_THAT(
Transform(AZ::Vector3(5.0f, 0.0f, 0.0f), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.5f, 1.0f, 1.0f))
.TransformVector(AZ::Vector3::CreateAxisX()),
IsClose(EMFX_SCALE ? AZ::Vector3(2.5f, 0.0f, 0.0f) : AZ::Vector3::CreateAxisX())
);
EXPECT_THAT(
Transform(AZ::Vector3::CreateZero(), AZ::Quaternion::CreateRotationX(AZ::Constants::QuarterPi), AZ::Vector3::CreateOne())
.TransformVector(AZ::Vector3::CreateAxisY()),
IsClose(AZ::Vector3(0.0f, sqrt2over2, sqrt2over2))
);
EXPECT_THAT(
Transform(AZ::Vector3::CreateZero(), AZ::Quaternion::CreateRotationX(AZ::Constants::QuarterPi), AZ::Vector3(1.0f, 2.0f, 3.0f))
.TransformVector(AZ::Vector3::CreateOne()),
IsClose(AZ::Vector3(1.0f, -sqrt2over2, sqrt2over2 * 5.0f))
);
}
TEST(TransformFixture, RotateVector)
{
EXPECT_THAT(
Transform(AZ::Vector3(5.0f, 0.0f, 0.0f), AZ::Quaternion::CreateIdentity())
.RotateVector(AZ::Vector3::CreateZero()),
IsClose(AZ::Vector3::CreateZero())
);
EXPECT_THAT(
Transform(AZ::Vector3(5.0f, 0.0f, 0.0f), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.5f, 1.0f, 1.0f))
.RotateVector(AZ::Vector3::CreateAxisX()),
IsClose(AZ::Vector3::CreateAxisX())
);
EXPECT_THAT(
Transform(AZ::Vector3::CreateZero(), AZ::Quaternion::CreateRotationX(AZ::Constants::QuarterPi), AZ::Vector3::CreateOne())
.RotateVector(AZ::Vector3::CreateAxisY()),
IsClose(AZ::Vector3(0.0f, sqrt2over2, sqrt2over2))
);
}
TEST_P(TransformConstructFromVec3QuatVec3Fixture, Inverse)
{
// Inverse does not work properly when there is non-uniform scale
if (HasNonUniformScale())
{
return;
}
const Transform transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale());
const Transform inverse = Transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale()).Inverse();
const AZ::Vector3 point(1.0f, 2.0f, 3.0f);
EXPECT_THAT(
inverse.TransformPoint(transform.TransformPoint(point)),
IsClose(point)
);
}
TEST_P(TransformConstructFromVec3QuatVec3Fixture, Inversed)
{
if (HasNonUniformScale())
{
return;
}
const Transform transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale());
const Transform inverse = transform.Inversed();
const AZ::Vector3 point(1.0f, 2.0f, 3.0f);
EXPECT_THAT(
inverse.TransformPoint(transform.TransformPoint(point)),
IsClose(point)
);
}
TEST_P(TransformConstructFromVec3QuatVec3Fixture, CalcRelativeToWithOutputParam)
{
const Transform transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale());
const Transform someTransform(
AZ::Vector3(20.0f, 30.0f, 40.0f),
AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(0.2f, 0.4f, 0.7f).GetNormalized(), 0.25f),
AZ::Vector3(2.0f, 3.0f, 4.0f)
);
Transform relative;
transform.CalcRelativeTo(someTransform, &relative);
EXPECT_THAT(
relative * someTransform,
IsClose(transform)
);
}
TEST_P(TransformConstructFromVec3QuatVec3Fixture, CalcRelativeTo)
{
const Transform transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale());
const Transform someTransform(
AZ::Vector3(20.0f, 30.0f, 40.0f),
AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(0.2f, 0.4f, 0.7f).GetNormalized(), 0.25f),
AZ::Vector3(2.0f, 3.0f, 4.0f)
);
const Transform relative = transform.CalcRelativeTo(someTransform);
EXPECT_THAT(
relative * someTransform,
IsClose(transform)
);
}
TEST_P(TransformConstructFromVec3QuatVec3Fixture, InverseWithOutputParam)
{
if (HasNonUniformScale())
{
return;
}
const Transform transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale());
Transform inverse;
transform.Inverse(&inverse);
const AZ::Vector3 point(1.0f, 2.0f, 3.0f);
EXPECT_THAT(
inverse.TransformPoint(transform.TransformPoint(point)),
IsClose(point)
);
}
TEST_P(TransformConstructFromVec3QuatVec3Fixture, Mirror)
{
const AZ::Vector3 axis = AZ::Vector3::CreateAxisX();
const Transform mirrorTransform = Transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale()).Mirror(axis);
const AZ::Matrix4x4 mirrorMatrix = GetMirroredTransform(axis);
const AZ::Vector3 point(3.0f, 4.0f, 5.0f);
EXPECT_THAT(
mirrorTransform.TransformPoint(point),
IsClose(mirrorMatrix * point)
);
}
TEST(TransformFixture, MirrorWithFlags)
{
}
TEST_P(TransformConstructFromVec3QuatVec3Fixture, Mirrored)
{
const AZ::Vector3 axis = AZ::Vector3::CreateAxisX();
const Transform mirrorTransform = Transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale()).Mirrored(axis);
const AZ::Matrix4x4 mirrorMatrix = GetMirroredTransform(axis);
const AZ::Vector3 point(3.0f, 4.0f, 5.0f);
EXPECT_THAT(
mirrorTransform.TransformPoint(point),
IsClose(mirrorMatrix * point)
);
}
TEST_P(TransformConstructFromVec3QuatVec3Fixture, MirrorWithOutputParam)
{
const AZ::Vector3 axis = AZ::Vector3::CreateAxisX();
Transform mirrorTransform;
Transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale()).Mirror(axis, &mirrorTransform);
const AZ::Matrix4x4 mirrorMatrix = GetMirroredTransform(axis);
const AZ::Vector3 point(3.0f, 4.0f, 5.0f);
EXPECT_THAT(
mirrorTransform.TransformPoint(point),
IsClose(mirrorMatrix * point)
);
}
struct ApplyDeltaParams
{
const Transform m_initial;
const Transform m_a;
const Transform m_b;
const Transform m_expected;
const float m_weight;
};
using TransformApplyDeltaFixture = ::testing::TestWithParam<ApplyDeltaParams>;
TEST_P(TransformApplyDeltaFixture, ApplyDelta)
{
if (GetParam().m_weight != 1.0f)
{
return;
}
Transform transform = GetParam().m_initial;
transform.ApplyDelta(GetParam().m_a, GetParam().m_b);
EXPECT_THAT(
transform,
IsClose(GetParam().m_expected)
);
}
TEST_P(TransformApplyDeltaFixture, ApplyDeltaMirrored)
{
if (GetParam().m_weight != 1.0f)
{
return;
}
const AZ::Vector3 mirrorAxis = AZ::Vector3::CreateAxisX();
Transform transform = GetParam().m_initial;
transform.ApplyDeltaMirrored(GetParam().m_a, GetParam().m_b, mirrorAxis);
EXPECT_THAT(
transform,
IsClose(GetParam().m_expected.Mirrored(mirrorAxis))
);
}
TEST_P(TransformApplyDeltaFixture, ApplyDeltaWithWeight)
{
Transform transform = GetParam().m_initial;
transform.ApplyDeltaWithWeight(GetParam().m_a, GetParam().m_b, GetParam().m_weight);
EXPECT_THAT(
transform,
IsClose(GetParam().m_expected)
);
}
INSTANTIATE_TEST_CASE_P(Test, TransformApplyDeltaFixture,
::testing::ValuesIn(std::vector<ApplyDeltaParams>{
{
{Transform::CreateIdentity()},
{AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()},
{AZ::Vector3(2.0f, 3.0f, 4.0f), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()},
{AZ::Vector3(0.5f, 0.5f, 0.5f), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()},
0.5f,
},
{
{Transform::CreateIdentity()},
{AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()},
{AZ::Vector3(2.0f, 3.0f, 4.0f), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()},
{AZ::Vector3(1.0f, 1.0f, 1.0f), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()},
1.0f,
},
{
{Transform::CreateIdentity()},
{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi / 2.0f), AZ::Vector3::CreateOne()},
{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3::CreateOne()},
{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi / 4.0f), AZ::Vector3::CreateOne()},
0.5f,
},
{
{Transform::CreateIdentity()},
{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi / 2.0f), AZ::Vector3::CreateOne()},
{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3::CreateOne()},
{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi / 2.0f), AZ::Vector3::CreateOne()},
1.0f,
},
{
{Transform::CreateIdentity()},
{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()},
{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.0f, 2.0f, 2.0f)},
{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateIdentity(), AZ::Vector3(1.5f, 1.5f, 1.5f)},
0.5f,
},
{
{Transform::CreateIdentity()},
{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateIdentity(), AZ::Vector3::CreateOne()},
{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.0f, 2.0f, 2.0f)},
{AZ::Vector3::CreateZero(), AZ::Quaternion::CreateIdentity(), AZ::Vector3(2.0f, 2.0f, 2.0f)},
1.0f,
},
})
);
TEST_P(TransformConstructFromVec3QuatVec3Fixture, CheckIfHasScale)
{
const Transform transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale());
EXPECT_EQ(transform.CheckIfHasScale(), !ExpectedScale().IsClose(AZ::Vector3::CreateOne()));
}
TEST(TransformFixture, Normalize)
{
const Transform transform = Transform(
AZ::Vector3::CreateOne(),
AZ::Quaternion(2.0f, 0.0f, 0.0f, 2.0f),
AZ::Vector3::CreateOne()
).Normalize();
EXPECT_FLOAT_EQ(transform.m_rotation.GetLength(), 1.0f);
}
TEST(TransformFixture, Normalized)
{
const Transform transform = Transform(
AZ::Vector3::CreateOne(),
AZ::Quaternion(2.0f, 0.0f, 0.0f, 2.0f),
AZ::Vector3::CreateOne()
).Normalized();
EXPECT_FLOAT_EQ(transform.m_rotation.GetLength(), 1.0f);
}
TEST(TransformFixture, BlendAdditive)
{
{
const Transform result =
Transform(AZ::Vector3(5.0f, 6.0f, 7.0f), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3::CreateOne()).BlendAdditive(
/*dest=*/Transform(AZ::Vector3(11.0f, 12.0f, 13.0f), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::HalfPi), AZ::Vector3(2.0f, 2.0f, 2.0f)),
/*orgTransform=*/Transform(AZ::Vector3(8.0f, 10.0f, 12.0f), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi), AZ::Vector3(2.0f, 3.0f, 2.0f)),
0.5f
);
EXPECT_THAT(
result,
IsClose(Transform(AZ::Vector3(6.5f, 7.0f, 7.5f), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::Pi * 3.0f / 8.0f), AZ::Vector3(1.0f, 0.5f, 1.0f)))
);
}
}
class TwoTransformsFixture
: public ::testing::Test
{
protected:
const AZ::Vector3 m_translationA{5.0f, 6.0f, 7.0f};
const AZ::Quaternion m_rotationA = AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi);
const AZ::Vector3 m_scaleA = AZ::Vector3::CreateOne();
const AZ::Vector3 m_translationB{11.0f, 12.0f, 13.0f};
const AZ::Quaternion m_rotationB = AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::HalfPi);
const AZ::Vector3 m_scaleB{3.0f, 4.0f, 5.0f};
};
TEST_F(TwoTransformsFixture, Blend)
{
const Transform transformA(m_translationA, m_rotationA, m_scaleA);
const Transform transformB(m_translationB, m_rotationB, m_scaleB);
EXPECT_THAT(
Transform(m_translationA, m_rotationA, m_scaleA).Blend(transformB, 0.0f),
IsClose(transformA)
);
EXPECT_THAT(
Transform(m_translationA, m_rotationA, m_scaleA).Blend(transformB, 0.25f),
IsClose(Transform(AZ::Vector3(6.5f, 7.5f, 8.5f), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::Pi * 5.0f / 16.0f), AZ::Vector3(1.5f, 1.75f, 2.0f)))
);
EXPECT_THAT(
Transform(m_translationA, m_rotationA, m_scaleA).Blend(transformB, 0.5f),
IsClose(Transform(AZ::Vector3(8.0f, 9.0f, 10.0f), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::Pi * 3.0f / 8.0f), AZ::Vector3(2.0f, 2.5f, 3.0f)))
);
EXPECT_THAT(
Transform(m_translationA, m_rotationA, m_scaleA).Blend(transformB, 0.75f),
IsClose(Transform(AZ::Vector3(9.5f, 10.5f, 11.5f), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::Pi * 7.0f / 16.0f), AZ::Vector3(2.5f, 3.25f, 4.0f)))
);
EXPECT_THAT(
Transform(m_translationA, m_rotationA, m_scaleA).Blend(transformB, 1.0f),
IsClose(transformB)
);
}
TEST_F(TwoTransformsFixture, ApplyAdditiveTransform)
{
EXPECT_THAT(
Transform(m_translationA, m_rotationA, m_scaleA).ApplyAdditive(Transform(m_translationB, m_rotationB, m_scaleB)),
IsClose(Transform(m_translationA + m_translationB, m_rotationA * m_rotationB, m_scaleA * m_scaleB))
);
}
TEST_F(TwoTransformsFixture, ApplyAdditiveTransformFloat)
{
const float factor = 0.5f;
EXPECT_THAT(
Transform(m_translationA, m_rotationA, m_scaleA).ApplyAdditive(Transform(m_translationB, m_rotationB, m_scaleB), factor),
IsClose(Transform(m_translationA + m_translationB * factor, m_rotationA.NLerp(m_rotationA * m_rotationB, factor), m_scaleA * AZ::Vector3::CreateOne().Lerp(m_scaleB, factor)))
);
}
TEST_F(TwoTransformsFixture, AddTransform)
{
EXPECT_THAT(
Transform(m_translationA, m_rotationA, m_scaleA).Add(Transform(m_translationB, m_rotationB, m_scaleB)),
IsClose(Transform(m_translationA + m_translationB, m_rotationA + m_rotationB, m_scaleA + m_scaleB))
);
}
TEST_F(TwoTransformsFixture, AddTransformFloat)
{
const float factor = 0.5f;
EXPECT_THAT(
Transform(m_translationA, m_rotationA, m_scaleA).Add(Transform(m_translationB, m_rotationB, m_scaleB), factor),
IsClose(Transform(m_translationA + m_translationB * factor, m_rotationA + m_rotationB * factor, m_scaleA + m_scaleB * factor))
);
}
TEST_F(TwoTransformsFixture, Subtract)
{
EXPECT_THAT(
Transform(m_translationA, m_rotationA, m_scaleA).Subtract(Transform(m_translationB, m_rotationB, m_scaleB)),
IsClose(Transform(m_translationA - m_translationB, m_rotationA - m_rotationB, m_scaleA - m_scaleB))
);
}
class TransformProjectedToGroundPlaneFixture
: public TransformConstructFromVec3QuatVec3Fixture
{
public:
bool ShouldSkip() const
{
// These tests do not meet the expectation when there is both a
// pitch and a roll value
// This is because the combination of pitch + roll, even when yaw
// is 0, introduces a rotation around z
return ::testing::get<0>(::testing::get<1>(GetParam())) != 0
&& ::testing::get<1>(::testing::get<1>(GetParam())) != 0;
}
void Expect(const Transform& transform, float zValue) const
{
EXPECT_THAT(
transform,
IsClose(Transform(
AZ::Vector3(ExpectedPosition().GetX(), ExpectedPosition().GetY(), zValue),
AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisZ(), ::testing::get<2>(::testing::get<1>(GetParam()))),
ExpectedScale()
))
);
}
};
TEST_P(TransformProjectedToGroundPlaneFixture, ApplyMotionExtractionFlags)
{
if (ShouldSkip())
{
return;
}
Transform transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale());
transform.ApplyMotionExtractionFlags(EMotionExtractionFlags(0));
Expect(transform, 0.0f);
}
TEST_P(TransformProjectedToGroundPlaneFixture, ApplyMotionExtractionFlagsCaptureZ)
{
if (ShouldSkip())
{
return;
}
Transform transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale());
transform.ApplyMotionExtractionFlags(MOTIONEXTRACT_CAPTURE_Z);
Expect(transform, ExpectedPosition().GetZ());
}
TEST_P(TransformProjectedToGroundPlaneFixture, ProjectedToGroundPlane)
{
if (ShouldSkip())
{
return;
}
Expect(Transform(ExpectedPosition(), ExpectedRotation(), ExpectedScale()).ProjectedToGroundPlane(), 0.0f);
}
const auto possiblePitchAndYawValues = ::testing::Values(
-AZ::Constants::HalfPi,
-AZ::Constants::QuarterPi,
0.0f,
AZ::Constants::QuarterPi,
AZ::Constants::HalfPi
);
INSTANTIATE_TEST_CASE_P(Test, TransformProjectedToGroundPlaneFixture,
::testing::Combine(
::testing::Values(
AZ::Vector3::CreateZero(),
AZ::Vector3(6.0f, 7.0f, 8.0f)
),
::testing::Combine(
/* possible pitch values */ possiblePitchAndYawValues,
/* possible roll values */ ::testing::Values(0.0f, AZ::Constants::QuarterPi),
/* possible yaw values */ possiblePitchAndYawValues
),
::testing::Values(
AZ::Vector3::CreateOne()
)
)
);
} // namespace EMotionFX