2026-03-13 18:43:14 +08:00
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
#include <XCEngine/Math/Quaternion.h>
|
|
|
|
|
#include <XCEngine/Math/Matrix4.h>
|
|
|
|
|
#include <XCEngine/Math/Vector3.h>
|
|
|
|
|
#include <XCEngine/Math/Math.h>
|
|
|
|
|
|
|
|
|
|
using namespace XCEngine::Math;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, DefaultConstructor) {
|
|
|
|
|
Quaternion q;
|
|
|
|
|
EXPECT_FLOAT_EQ(q.x, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.y, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.z, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.w, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, ParameterConstructor) {
|
|
|
|
|
Quaternion q(1.0f, 2.0f, 3.0f, 4.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.x, 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.y, 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.z, 3.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.w, 4.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Identity) {
|
|
|
|
|
Quaternion q = Quaternion::Identity();
|
|
|
|
|
EXPECT_FLOAT_EQ(q.x, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.y, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.z, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.w, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, FromAxisAngle) {
|
|
|
|
|
Quaternion q = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f);
|
|
|
|
|
|
|
|
|
|
float sinHalfAngle = std::sin(PI * 0.25f);
|
|
|
|
|
float cosHalfAngle = std::cos(PI * 0.25f);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(q.x, 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(q.y, sinHalfAngle, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(q.z, 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(q.w, cosHalfAngle, 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, FromAxisAngle_IdentityRotation) {
|
|
|
|
|
Quaternion q = Quaternion::FromAxisAngle(Vector3::Up(), 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.x, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.y, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.z, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.w, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, FromEulerAngles) {
|
|
|
|
|
Quaternion q = Quaternion::FromEulerAngles(PI * 0.5f, 0.0f, 0.0f);
|
|
|
|
|
|
|
|
|
|
float sinHalfPitch = std::sin(PI * 0.25f);
|
|
|
|
|
float cosHalfPitch = std::cos(PI * 0.25f);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(q.x, sinHalfPitch, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(q.w, cosHalfPitch, 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, ToEulerAngles_FromEulerAngles) {
|
2026-03-13 19:23:12 +08:00
|
|
|
Quaternion q = Quaternion::Identity();
|
|
|
|
|
Vector3 euler = q.ToEulerAngles();
|
|
|
|
|
EXPECT_NEAR(euler.x, 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(euler.y, 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(euler.z, 0.0f, 1e-5f);
|
2026-03-13 18:43:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, ToMatrix4x4_Identity) {
|
|
|
|
|
Quaternion q = Quaternion::Identity();
|
|
|
|
|
Matrix4x4 m = q.ToMatrix4x4();
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[0][0], 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[1][1], 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[2][2], 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[3][3], 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, ToMatrix4x4_RotationY) {
|
|
|
|
|
Quaternion q = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f);
|
|
|
|
|
Matrix4x4 m = q.ToMatrix4x4();
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(m.m[0][0], 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(m.m[0][2], 1.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(m.m[2][0], -1.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(m.m[2][2], 0.0f, 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, FromRotationMatrix) {
|
|
|
|
|
Matrix4x4 m = Matrix4x4::RotationY(PI * 0.5f);
|
|
|
|
|
Quaternion q = Quaternion::FromRotationMatrix(m);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(std::abs(q.y), 0.707f, 1e-3f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Slerp_IdentityToIdentity) {
|
|
|
|
|
Quaternion a = Quaternion::Identity();
|
|
|
|
|
Quaternion b = Quaternion::Identity();
|
|
|
|
|
|
|
|
|
|
Quaternion result = Quaternion::Slerp(a, b, 0.5f);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(result.x, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.y, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.z, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.w, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Slerp_QuarterRotation) {
|
|
|
|
|
Quaternion a = Quaternion::Identity();
|
|
|
|
|
Quaternion b = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f);
|
|
|
|
|
|
|
|
|
|
Quaternion result = Quaternion::Slerp(a, b, 0.5f);
|
|
|
|
|
|
|
|
|
|
float expectedAngle = PI * 0.25f;
|
|
|
|
|
Quaternion expected = Quaternion::FromAxisAngle(Vector3::Up(), expectedAngle);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(result.x, expected.x, 1e-4f);
|
|
|
|
|
EXPECT_NEAR(result.y, expected.y, 1e-4f);
|
|
|
|
|
EXPECT_NEAR(result.z, expected.z, 1e-4f);
|
|
|
|
|
EXPECT_NEAR(result.w, expected.w, 1e-4f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Slerp_Start_ReturnsFirst) {
|
|
|
|
|
Quaternion a(0.1f, 0.2f, 0.3f, 0.9f);
|
|
|
|
|
Quaternion b(0.9f, 0.1f, 0.1f, 0.1f);
|
|
|
|
|
|
|
|
|
|
Quaternion result = Quaternion::Slerp(a, b, 0.0f);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(result.x, a.x, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(result.y, a.y, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(result.z, a.z, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(result.w, a.w, 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Slerp_End_ReturnsSecond) {
|
|
|
|
|
Quaternion a(0.1f, 0.2f, 0.3f, 0.9f);
|
|
|
|
|
Quaternion b(0.9f, 0.1f, 0.1f, 0.1f);
|
|
|
|
|
|
|
|
|
|
Quaternion result = Quaternion::Slerp(a, b, 1.0f);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(result.x, b.x, 1e-4f);
|
|
|
|
|
EXPECT_NEAR(result.y, b.y, 1e-4f);
|
|
|
|
|
EXPECT_NEAR(result.z, b.z, 1e-4f);
|
|
|
|
|
EXPECT_NEAR(result.w, b.w, 1e-4f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Slerp_ClampsT) {
|
|
|
|
|
Quaternion a = Quaternion::Identity();
|
|
|
|
|
Quaternion b = Quaternion::FromAxisAngle(Vector3::Up(), PI);
|
|
|
|
|
|
|
|
|
|
Quaternion result1 = Quaternion::Slerp(a, b, -0.5f);
|
|
|
|
|
Quaternion result2 = Quaternion::Slerp(a, b, 1.5f);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(result1.x, a.x, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(result2.x, b.x, 1e-4f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, LookRotation_Forward) {
|
|
|
|
|
Quaternion q = Quaternion::LookRotation(Vector3::Forward());
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(q.x, 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(q.y, 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(q.z, 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(q.w, 1.0f, 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, LookRotation_Up) {
|
|
|
|
|
Quaternion q = Quaternion::LookRotation(Vector3::Up());
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(q.x, 0.707f, 1e-3f);
|
|
|
|
|
EXPECT_NEAR(q.w, 0.707f, 1e-3f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, LookRotation_Right) {
|
|
|
|
|
Quaternion q = Quaternion::LookRotation(Vector3::Right());
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(q.y, -0.707f, 1e-3f);
|
|
|
|
|
EXPECT_NEAR(q.w, 0.707f, 1e-3f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Multiply_ProducesRotation) {
|
|
|
|
|
Quaternion q1 = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f);
|
|
|
|
|
Quaternion q2 = Quaternion::FromAxisAngle(Vector3::Right(), PI * 0.5f);
|
|
|
|
|
|
|
|
|
|
Quaternion result = q1 * q2;
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(result.Magnitude() > 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Multiply_Identity) {
|
|
|
|
|
Quaternion q(0.1f, 0.2f, 0.3f, 0.9f);
|
|
|
|
|
Quaternion identity = Quaternion::Identity();
|
|
|
|
|
|
|
|
|
|
Quaternion result = q * identity;
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(result.x, q.x, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(result.y, q.y, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(result.z, q.z, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(result.w, q.w, 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Multiply_Vector3) {
|
|
|
|
|
Quaternion q = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f);
|
|
|
|
|
Vector3 v = Vector3::Right();
|
|
|
|
|
|
|
|
|
|
Vector3 result = q * v;
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(result.x, 0.0f, 1e-5f);
|
2026-03-13 19:23:12 +08:00
|
|
|
EXPECT_NEAR(result.z, -1.0f, 1e-5f);
|
2026-03-13 18:43:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Inverse_Identity) {
|
|
|
|
|
Quaternion q = Quaternion::Identity();
|
|
|
|
|
Quaternion inv = q.Inverse();
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(inv.x, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(inv.y, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(inv.z, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(inv.w, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Inverse_Rotation) {
|
|
|
|
|
Quaternion q = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f);
|
|
|
|
|
Quaternion inv = q.Inverse();
|
|
|
|
|
|
|
|
|
|
Quaternion product = q * inv;
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(product.x, 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(product.y, 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(product.z, 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(product.w, 1.0f, 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Dot) {
|
|
|
|
|
Quaternion a(1, 0, 0, 0);
|
|
|
|
|
Quaternion b(1, 0, 0, 0);
|
|
|
|
|
|
|
|
|
|
float result = a.Dot(b);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(result, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Dot_Orthogonal) {
|
|
|
|
|
Quaternion a(1, 0, 0, 0);
|
|
|
|
|
Quaternion b(0, 1, 0, 0);
|
|
|
|
|
|
|
|
|
|
float result = a.Dot(b);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(result, 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Magnitude) {
|
|
|
|
|
Quaternion q(1, 0, 0, 0);
|
|
|
|
|
EXPECT_FLOAT_EQ(q.Magnitude(), 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Normalized) {
|
|
|
|
|
Quaternion q(1, 2, 3, 4);
|
|
|
|
|
Quaternion normalized = q.Normalized();
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(normalized.Magnitude(), 1.0f, 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Quaternion, Normalize) {
|
|
|
|
|
Quaternion q(1, 2, 3, 4);
|
|
|
|
|
Quaternion result = Quaternion::Normalize(q);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(result.Magnitude(), 1.0f, 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|