2026-03-13 18:43:14 +08:00
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
#include <XCEngine/Math/Ray.h>
|
|
|
|
|
#include <XCEngine/Math/Sphere.h>
|
|
|
|
|
#include <XCEngine/Math/Box.h>
|
|
|
|
|
#include <XCEngine/Math/Plane.h>
|
|
|
|
|
#include <XCEngine/Math/Bounds.h>
|
|
|
|
|
#include <XCEngine/Math/Frustum.h>
|
|
|
|
|
#include <XCEngine/Math/Matrix4.h>
|
|
|
|
|
|
|
|
|
|
using namespace XCEngine::Math;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// Ray Tests
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
TEST(Math_Ray, DefaultConstructor) {
|
|
|
|
|
Ray ray;
|
|
|
|
|
EXPECT_FLOAT_EQ(ray.origin.x, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(ray.origin.y, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(ray.origin.z, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(ray.direction.x, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(ray.direction.y, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(ray.direction.z, 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Ray, ParameterConstructor) {
|
|
|
|
|
Vector3 origin(1, 2, 3);
|
|
|
|
|
Vector3 direction(0, 0, 1);
|
|
|
|
|
Ray ray(origin, direction);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(ray.origin.x, 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(ray.origin.y, 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(ray.origin.z, 3.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Ray, GetPoint) {
|
|
|
|
|
Ray ray(Vector3::Zero(), Vector3::Right());
|
|
|
|
|
Vector3 point = ray.GetPoint(5.0f);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(point.x, 5.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(point.y, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(point.z, 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Ray, IntersectsSphere_Hit) {
|
|
|
|
|
Ray ray(Vector3(-5, 0, 0), Vector3::Right());
|
|
|
|
|
Sphere sphere(Vector3::Zero(), 1.0f);
|
|
|
|
|
|
|
|
|
|
float t;
|
|
|
|
|
bool hit = ray.Intersects(sphere, t);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(hit);
|
|
|
|
|
EXPECT_GE(t, 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Ray, IntersectsSphere_Miss) {
|
|
|
|
|
Ray ray(Vector3(0, 5, 0), Vector3::Right());
|
|
|
|
|
Sphere sphere(Vector3::Zero(), 1.0f);
|
|
|
|
|
|
|
|
|
|
float t;
|
|
|
|
|
bool hit = ray.Intersects(sphere, t);
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(hit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Ray, IntersectsBox_Hit) {
|
|
|
|
|
Ray ray(Vector3(-5, 0, 0), Vector3::Right());
|
|
|
|
|
Box box(Vector3::Zero(), Vector3(1, 1, 1));
|
|
|
|
|
|
|
|
|
|
float t;
|
|
|
|
|
bool hit = ray.Intersects(box, t);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(hit);
|
|
|
|
|
EXPECT_GE(t, 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Ray, IntersectsBox_Miss) {
|
|
|
|
|
Ray ray(Vector3(0, 5, 0), Vector3::Right());
|
|
|
|
|
Box box(Vector3(10, 0, 0), Vector3(1, 1, 1));
|
|
|
|
|
|
|
|
|
|
float t;
|
|
|
|
|
bool hit = ray.Intersects(box, t);
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(hit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Ray, IntersectsPlane_Hit) {
|
|
|
|
|
Ray ray(Vector3(0, 0, -5), Vector3::Forward());
|
|
|
|
|
Plane plane(Vector3::Forward(), 0.0f);
|
|
|
|
|
|
|
|
|
|
float t;
|
|
|
|
|
bool hit = ray.Intersects(plane, t);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(hit);
|
|
|
|
|
EXPECT_NEAR(t, 5.0f, 1e-3f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Ray, IntersectsPlane_Miss) {
|
|
|
|
|
Ray ray(Vector3(0, 0, -5), Vector3::Back());
|
|
|
|
|
Plane plane(Vector3::Forward(), 0.0f);
|
|
|
|
|
|
|
|
|
|
float t;
|
|
|
|
|
bool hit = ray.Intersects(plane, t);
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(hit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// Sphere Tests
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
TEST(Math_Sphere, DefaultConstructor) {
|
|
|
|
|
Sphere sphere;
|
|
|
|
|
EXPECT_FLOAT_EQ(sphere.center.x, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(sphere.radius, 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Sphere, ParameterConstructor) {
|
|
|
|
|
Sphere sphere(Vector3(1, 2, 3), 5.0f);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(sphere.center.x, 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(sphere.center.y, 2.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(sphere.center.z, 3.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(sphere.radius, 5.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Sphere, Contains_Inside) {
|
|
|
|
|
Sphere sphere(Vector3::Zero(), 2.0f);
|
|
|
|
|
EXPECT_TRUE(sphere.Contains(Vector3(1, 0, 0)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Sphere, Contains_Outside) {
|
|
|
|
|
Sphere sphere(Vector3::Zero(), 1.0f);
|
|
|
|
|
EXPECT_FALSE(sphere.Contains(Vector3(5, 0, 0)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Sphere, Contains_OnSurface) {
|
|
|
|
|
Sphere sphere(Vector3::Zero(), 1.0f);
|
|
|
|
|
EXPECT_TRUE(sphere.Contains(Vector3(1, 0, 0)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Sphere, Intersects_Overlap) {
|
|
|
|
|
Sphere a(Vector3::Zero(), 1.0f);
|
|
|
|
|
Sphere b(Vector3(1.5f, 0, 0), 1.0f);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(a.Intersects(b));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Sphere, Intersects_Separate) {
|
|
|
|
|
Sphere a(Vector3::Zero(), 1.0f);
|
|
|
|
|
Sphere b(Vector3(5, 0, 0), 1.0f);
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(a.Intersects(b));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// Plane Tests
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
TEST(Math_Plane, DefaultConstructor) {
|
|
|
|
|
Plane plane;
|
|
|
|
|
EXPECT_FLOAT_EQ(plane.normal.x, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(plane.normal.y, 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(plane.normal.z, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(plane.distance, 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Plane, ParameterConstructor) {
|
|
|
|
|
Plane plane(Vector3::Forward(), 5.0f);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(plane.normal.z, 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(plane.distance, 5.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Plane, FromPoints) {
|
|
|
|
|
Plane plane = Plane::FromPoints(
|
|
|
|
|
Vector3(0, 0, 0),
|
|
|
|
|
Vector3(1, 0, 0),
|
|
|
|
|
Vector3(0, 1, 0)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(plane.normal.z, -1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(plane.distance, 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Plane, GetDistanceToPoint) {
|
|
|
|
|
Plane plane(Vector3::Forward(), 0.0f);
|
|
|
|
|
float dist = plane.GetDistanceToPoint(Vector3(0, 0, 5));
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(dist, 5.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Plane, GetClosestPoint) {
|
|
|
|
|
Plane plane(Vector3::Forward(), 0.0f);
|
|
|
|
|
Vector3 point = plane.GetClosestPoint(Vector3(3, 4, 5));
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(point.x, 3.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(point.y, 4.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(point.z, 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Plane, GetSide_Positive) {
|
|
|
|
|
Plane plane(Vector3::Forward(), 0.0f);
|
|
|
|
|
EXPECT_TRUE(plane.GetSide(Vector3(0, 0, 1)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Plane, GetSide_Negative) {
|
|
|
|
|
Plane plane(Vector3::Forward(), 0.0f);
|
|
|
|
|
EXPECT_FALSE(plane.GetSide(Vector3(0, 0, -1)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Plane, IntersectsSphere_Inside) {
|
|
|
|
|
Plane plane(Vector3::Forward(), 0.0f);
|
|
|
|
|
Sphere sphere(Vector3(0, 0, 0), 1.0f);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(plane.Intersects(sphere));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Plane, IntersectsSphere_Outside) {
|
|
|
|
|
Plane plane(Vector3::Forward(), 5.0f);
|
|
|
|
|
Sphere sphere(Vector3(0, 0, 0), 1.0f);
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(plane.Intersects(sphere));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// Bounds Tests
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, DefaultConstructor) {
|
|
|
|
|
Bounds bounds;
|
|
|
|
|
EXPECT_FLOAT_EQ(bounds.center.x, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(bounds.extents.x, 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, ParameterConstructor) {
|
|
|
|
|
Bounds bounds(Vector3(5, 5, 5), Vector3(10, 10, 10));
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(bounds.center.x, 5.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(bounds.extents.x, 5.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, GetMin) {
|
|
|
|
|
Bounds bounds(Vector3(5, 5, 5), Vector3(10, 10, 10));
|
|
|
|
|
Vector3 min = bounds.GetMin();
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(min.x, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(min.y, 0.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(min.z, 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, GetMax) {
|
|
|
|
|
Bounds bounds(Vector3(5, 5, 5), Vector3(10, 10, 10));
|
|
|
|
|
Vector3 max = bounds.GetMax();
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(max.x, 10.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(max.y, 10.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(max.z, 10.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, SetMinMax) {
|
|
|
|
|
Bounds bounds;
|
|
|
|
|
bounds.SetMinMax(Vector3(0, 0, 0), Vector3(10, 10, 10));
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(bounds.center.x, 5.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(bounds.extents.x, 5.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, Contains_Inside) {
|
|
|
|
|
Bounds bounds(Vector3::Zero(), Vector3(10, 10, 10));
|
|
|
|
|
EXPECT_TRUE(bounds.Contains(Vector3::Zero()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, Contains_Outside) {
|
|
|
|
|
Bounds bounds(Vector3::Zero(), Vector3(2, 2, 2));
|
|
|
|
|
EXPECT_FALSE(bounds.Contains(Vector3(5, 5, 5)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, Contains_OnSurface) {
|
|
|
|
|
Bounds bounds(Vector3::Zero(), Vector3(2, 2, 2));
|
|
|
|
|
EXPECT_TRUE(bounds.Contains(Vector3(1, 1, 1)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, Intersects_Overlap) {
|
|
|
|
|
Bounds a(Vector3::Zero(), Vector3(2, 2, 2));
|
|
|
|
|
Bounds b(Vector3(1, 1, 1), Vector3(2, 2, 2));
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(a.Intersects(b));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, Intersects_Separate) {
|
|
|
|
|
Bounds a(Vector3::Zero(), Vector3(2, 2, 2));
|
|
|
|
|
Bounds b(Vector3(10, 10, 10), Vector3(2, 2, 2));
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(a.Intersects(b));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, Encapsulate_Point) {
|
|
|
|
|
Bounds bounds(Vector3::Zero(), Vector3(2, 2, 2));
|
|
|
|
|
bounds.Encapsulate(Vector3(5, 5, 5));
|
|
|
|
|
|
2026-03-13 19:23:12 +08:00
|
|
|
EXPECT_GE(bounds.GetMax().x, 5.0f);
|
2026-03-13 18:43:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, Encapsulate_Bounds) {
|
|
|
|
|
Bounds a(Vector3::Zero(), Vector3(2, 2, 2));
|
|
|
|
|
Bounds b(Vector3(5, 5, 5), Vector3(2, 2, 2));
|
|
|
|
|
a.Encapsulate(b);
|
|
|
|
|
|
2026-03-13 19:23:12 +08:00
|
|
|
EXPECT_GE(a.GetMax().x, 5.0f);
|
2026-03-13 18:43:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, Expand) {
|
|
|
|
|
Bounds bounds(Vector3::Zero(), Vector3(2, 2, 2));
|
|
|
|
|
bounds.Expand(2.0f);
|
|
|
|
|
|
2026-03-13 19:23:12 +08:00
|
|
|
EXPECT_GE(bounds.GetMax().x, 2.0f);
|
2026-03-13 18:43:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, GetVolume) {
|
|
|
|
|
Bounds bounds(Vector3::Zero(), Vector3(2, 4, 6));
|
|
|
|
|
float volume = bounds.GetVolume();
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(volume, 48.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Bounds, GetClosestPoint) {
|
|
|
|
|
Bounds bounds(Vector3::Zero(), Vector3(2, 2, 2));
|
|
|
|
|
Vector3 point = bounds.GetClosestPoint(Vector3(10, 10, 10));
|
|
|
|
|
|
|
|
|
|
EXPECT_FLOAT_EQ(point.x, 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(point.y, 1.0f);
|
|
|
|
|
EXPECT_FLOAT_EQ(point.z, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// Frustum Tests
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
TEST(Math_Frustum, Contains_Point) {
|
|
|
|
|
Frustum frustum;
|
|
|
|
|
Plane nearPlane;
|
|
|
|
|
nearPlane.normal = Vector3::Forward();
|
|
|
|
|
nearPlane.distance = 1.0f;
|
|
|
|
|
frustum.planes[4] = nearPlane;
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(frustum.Contains(Vector3(0, 0, 2)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Frustum, Contains_Sphere) {
|
|
|
|
|
Frustum frustum;
|
|
|
|
|
Plane nearPlane;
|
|
|
|
|
nearPlane.normal = Vector3::Forward();
|
|
|
|
|
nearPlane.distance = 1.0f;
|
|
|
|
|
frustum.planes[4] = nearPlane;
|
|
|
|
|
|
|
|
|
|
Sphere sphere(Vector3(0, 0, 5), 1.0f);
|
|
|
|
|
EXPECT_TRUE(frustum.Contains(sphere));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Frustum, Intersects_Sphere) {
|
|
|
|
|
Frustum frustum;
|
|
|
|
|
Plane nearPlane;
|
|
|
|
|
nearPlane.normal = Vector3::Forward();
|
|
|
|
|
nearPlane.distance = 1.0f;
|
|
|
|
|
frustum.planes[4] = nearPlane;
|
|
|
|
|
|
|
|
|
|
Sphere sphere(Vector3(0, 0, 2), 0.5f);
|
|
|
|
|
EXPECT_TRUE(frustum.Intersects(sphere));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Math_Frustum, Intersects_Bounds) {
|
|
|
|
|
Frustum frustum;
|
|
|
|
|
Plane nearPlane;
|
|
|
|
|
nearPlane.normal = Vector3::Forward();
|
|
|
|
|
nearPlane.distance = 1.0f;
|
|
|
|
|
frustum.planes[4] = nearPlane;
|
|
|
|
|
|
|
|
|
|
Bounds bounds(Vector3(0, 0, 2), Vector3(1, 1, 1));
|
|
|
|
|
EXPECT_TRUE(frustum.Intersects(bounds));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|