2026-03-13 18:43:14 +08:00
|
|
|
#include <gtest/gtest.h>
|
2026-03-24 16:14:05 +08:00
|
|
|
#include <XCEngine/Core/Math/Matrix4.h>
|
|
|
|
|
#include <XCEngine/Core/Math/Vector3.h>
|
|
|
|
|
#include <XCEngine/Core/Math/Quaternion.h>
|
2026-03-13 18:43:14 +08:00
|
|
|
|
|
|
|
|
using namespace XCEngine::Math;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, DefaultConstructor_InitializesToZero) {
|
|
|
|
|
Matrix4x4 m;
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
for (int j = 0; j < 4; j++) {
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[i][j], 0.0f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Identity_IsCorrect) {
|
|
|
|
|
Matrix4x4 id = Matrix4x4::Identity();
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(id.m[0][0], 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(id.m[1][1], 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(id.m[2][2], 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(id.m[3][3], 1.0f);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(id.m[0][1], 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(id.m[1][2], 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Zero_IsCorrect) {
|
|
|
|
|
Matrix4x4 zero = Matrix4x4::Zero();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
for (int j = 0; j < 4; j++) {
|
|
|
|
|
EXPECT_FLOAT_EQ(zero.m[i][j], 0.0f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Translation) {
|
|
|
|
|
Vector3 translation(1.0f, 2.0f, 3.0f);
|
|
|
|
|
Matrix4x4 m = Matrix4x4::Translation(translation);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[0][3], 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[1][3], 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[2][3], 3.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[3][3], 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Translation_TransformsPoint) {
|
|
|
|
|
Matrix4x4 m = Matrix4x4::Translation(Vector3(1, 2, 3));
|
|
|
|
|
Vector3 point(0, 0, 0);
|
|
|
|
|
|
|
|
|
|
Vector3 result = m.MultiplyPoint(point);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.x, 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.y, 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.z, 3.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Scale) {
|
|
|
|
|
Vector3 scale(2.0f, 3.0f, 4.0f);
|
|
|
|
|
Matrix4x4 m = Matrix4x4::Scale(scale);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[0][0], 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[1][1], 3.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[2][2], 4.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[3][3], 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Scale_TransformsPoint) {
|
|
|
|
|
Matrix4x4 m = Matrix4x4::Scale(Vector3(2, 3, 4));
|
|
|
|
|
Vector3 point(1, 1, 1);
|
|
|
|
|
|
|
|
|
|
Vector3 result = m.MultiplyPoint(point);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.x, 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.y, 3.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.z, 4.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, RotationX) {
|
|
|
|
|
Matrix4x4 m = Matrix4x4::RotationX(PI * 0.5f);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(m.m[1][1], 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(m.m[1][2], -1.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(m.m[2][1], 1.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(m.m[2][2], 0.0f, 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, RotationY) {
|
|
|
|
|
Matrix4x4 m = Matrix4x4::RotationY(PI * 0.5f);
|
|
|
|
|
|
|
|
|
|
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_Matrix4, RotationZ) {
|
|
|
|
|
Matrix4x4 m = Matrix4x4::RotationZ(PI * 0.5f);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(m.m[0][0], 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(m.m[0][1], -1.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(m.m[1][0], 1.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(m.m[1][1], 0.0f, 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, TRS) {
|
|
|
|
|
Vector3 translation(1, 2, 3);
|
|
|
|
|
Quaternion rotation = Quaternion::Identity();
|
|
|
|
|
Vector3 scale(2, 2, 2);
|
|
|
|
|
|
|
|
|
|
Matrix4x4 m = Matrix4x4::TRS(translation, rotation, scale);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[0][3], 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[1][3], 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[2][3], 3.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, LookAt) {
|
|
|
|
|
Vector3 eye(0, 0, 5);
|
|
|
|
|
Vector3 target(0, 0, 0);
|
|
|
|
|
Vector3 up(0, 1, 0);
|
|
|
|
|
|
|
|
|
|
Matrix4x4 m = Matrix4x4::LookAt(eye, target, up);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[0][3], 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[1][3], 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[2][3], -5.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Perspective) {
|
|
|
|
|
float fov = PI * 0.25f;
|
|
|
|
|
float aspect = 16.0f / 9.0f;
|
|
|
|
|
float nearPlane = 0.1f;
|
|
|
|
|
float farPlane = 100.0f;
|
|
|
|
|
|
|
|
|
|
Matrix4x4 m = Matrix4x4::Perspective(fov, aspect, nearPlane, farPlane);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[0][0], 1.0f / (aspect * std::tan(fov * 0.5f)));
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[1][1], 1.0f / std::tan(fov * 0.5f));
|
2026-03-22 20:08:36 +08:00
|
|
|
EXPECT_FLOAT_EQ(m.m[2][2], farPlane / (farPlane - nearPlane));
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[2][3], -farPlane * nearPlane / (farPlane - nearPlane));
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[3][2], 1.0f);
|
2026-03-13 18:43:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Orthographic) {
|
|
|
|
|
float left = -10.0f, right = 10.0f;
|
|
|
|
|
float bottom = -10.0f, top = 10.0f;
|
|
|
|
|
float nearPlane = 0.1f, farPlane = 100.0f;
|
|
|
|
|
|
|
|
|
|
Matrix4x4 m = Matrix4x4::Orthographic(left, right, bottom, top, nearPlane, farPlane);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[0][0], 0.1f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[1][1], 0.1f);
|
2026-03-22 20:08:36 +08:00
|
|
|
EXPECT_FLOAT_EQ(m.m[2][2], 1.0f / (farPlane - nearPlane));
|
2026-03-13 18:43:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Multiply_MatrixWithMatrix) {
|
|
|
|
|
Matrix4x4 a = Matrix4x4::Identity();
|
|
|
|
|
Matrix4x4 b = Matrix4x4::Translation(Vector3(1, 2, 3));
|
|
|
|
|
|
|
|
|
|
Matrix4x4 result = a * b;
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(result.m[0][3], 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.m[1][3], 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.m[2][3], 3.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Multiply_MatrixWithVector4) {
|
|
|
|
|
Matrix4x4 m = Matrix4x4::Translation(Vector3(1, 2, 3));
|
|
|
|
|
Vector4 v(1, 1, 1, 1);
|
|
|
|
|
|
|
|
|
|
Vector4 result = m * v;
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(result.x, 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.y, 3.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.z, 4.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, MultiplyPoint) {
|
|
|
|
|
Matrix4x4 m = Matrix4x4::Translation(Vector3(1, 2, 3));
|
|
|
|
|
Vector3 p(0, 0, 0);
|
|
|
|
|
|
|
|
|
|
Vector3 result = m.MultiplyPoint(p);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(result.x, 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.y, 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.z, 3.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, MultiplyVector) {
|
|
|
|
|
Matrix4x4 m = Matrix4x4::Scale(Vector3(2, 2, 2));
|
|
|
|
|
Vector3 v(1, 1, 1);
|
|
|
|
|
|
|
|
|
|
Vector3 result = m.MultiplyVector(v);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(result.x, 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.y, 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.z, 2.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Transpose) {
|
|
|
|
|
Matrix4x4 m;
|
|
|
|
|
m.m[0][1] = 1.0f;
|
|
|
|
|
m.m[1][2] = 2.0f;
|
|
|
|
|
m.m[2][3] = 3.0f;
|
|
|
|
|
|
|
|
|
|
Matrix4x4 t = m.Transpose();
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(t.m[1][0], 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(t.m[2][1], 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(t.m[3][2], 3.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Inverse_Identity_ReturnsIdentity) {
|
|
|
|
|
Matrix4x4 id = Matrix4x4::Identity();
|
|
|
|
|
Matrix4x4 inv = id.Inverse();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
for (int j = 0; j < 4; j++) {
|
|
|
|
|
EXPECT_NEAR(inv.m[i][j], id.m[i][j], 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Inverse_Translation) {
|
|
|
|
|
Matrix4x4 m = Matrix4x4::Translation(Vector3(1, 2, 3));
|
|
|
|
|
Matrix4x4 inv = m.Inverse();
|
|
|
|
|
|
|
|
|
|
Vector3 p(0, 0, 0);
|
|
|
|
|
Vector3 transformed = m.MultiplyPoint(p);
|
|
|
|
|
Vector3 recovered = inv.MultiplyPoint(transformed);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(recovered.x, 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(recovered.y, 0.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(recovered.z, 0.0f, 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Inverse_OfInverse_EqualsOriginal) {
|
|
|
|
|
Matrix4x4 original = Matrix4x4::TRS(
|
|
|
|
|
Vector3(1, 2, 3),
|
|
|
|
|
Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f),
|
|
|
|
|
Vector3(2, 2, 2)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Matrix4x4 inverted = original.Inverse();
|
|
|
|
|
Matrix4x4 recovered = inverted.Inverse();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
for (int j = 0; j < 4; j++) {
|
|
|
|
|
EXPECT_NEAR(original.m[i][j], recovered.m[i][j], 1e-4f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Determinant_Identity_EqualsOne) {
|
|
|
|
|
Matrix4x4 id = Matrix4x4::Identity();
|
|
|
|
|
EXPECT_FLOAT_EQ(id.Determinant(), 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, GetTranslation) {
|
|
|
|
|
Vector3 translation(1, 2, 3);
|
|
|
|
|
Matrix4x4 m = Matrix4x4::Translation(translation);
|
|
|
|
|
|
|
|
|
|
Vector3 result = m.GetTranslation();
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(result.x, 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.y, 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(result.z, 3.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, GetScale) {
|
|
|
|
|
Vector3 scale(2, 3, 4);
|
|
|
|
|
Matrix4x4 m = Matrix4x4::Scale(scale);
|
|
|
|
|
|
|
|
|
|
Vector3 result = m.GetScale();
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(result.x, 2.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(result.y, 3.0f, 1e-5f);
|
|
|
|
|
EXPECT_NEAR(result.z, 4.0f, 1e-5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, Decompose_TRS) {
|
|
|
|
|
Vector3 originalPos(1, 2, 3);
|
|
|
|
|
Quaternion originalRot = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f);
|
|
|
|
|
Vector3 originalScale(2, 2, 2);
|
|
|
|
|
|
|
|
|
|
Matrix4x4 m = Matrix4x4::TRS(originalPos, originalRot, originalScale);
|
|
|
|
|
|
|
|
|
|
Vector3 pos, scale;
|
|
|
|
|
Quaternion rot;
|
|
|
|
|
m.Decompose(pos, rot, scale);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(pos.x, originalPos.x, 1e-4f);
|
|
|
|
|
EXPECT_NEAR(pos.y, originalPos.y, 1e-4f);
|
|
|
|
|
EXPECT_NEAR(pos.z, originalPos.z, 1e-4f);
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(scale.x, originalScale.x, 1e-4f);
|
|
|
|
|
EXPECT_NEAR(scale.y, originalScale.y, 1e-4f);
|
|
|
|
|
EXPECT_NEAR(scale.z, originalScale.z, 1e-4f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Matrix4, IndexOperator) {
|
|
|
|
|
Matrix4x4 m;
|
|
|
|
|
m[0][0] = 1.0f;
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(m[0][0], 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(m.m[0][0], 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|