feat: 添加Math库和Google Test测试框架

- 新增Math库: Vector2/3/4, Matrix3/4, Quaternion, Transform, Color等
- 新增测试框架: Google Test (gtest)
- 新增140个单元测试,覆盖Vector, Matrix, Quaternion, Geometry
- VolumeRenderer支持vcpkg的NanoVDB
- 添加TESTING.md测试文档
This commit is contained in:
2026-03-13 18:43:14 +08:00
parent 5efa171050
commit 7c54a62f9e
41 changed files with 8530 additions and 1845 deletions

View File

@@ -0,0 +1,385 @@
#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));
EXPECT_GT(bounds.GetMax().x, 5.0f);
}
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);
EXPECT_GT(a.GetMax().x, 5.0f);
}
TEST(Math_Bounds, Expand) {
Bounds bounds(Vector3::Zero(), Vector3(2, 2, 2));
bounds.Expand(2.0f);
EXPECT_GT(bounds.GetMax().x, 3.0f);
}
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