McRogueFace/tests/unit/math3d_test.cpp

141 lines
5.1 KiB
C++
Raw Normal View History

2026-02-04 13:33:14 -05:00
// math3d_test.cpp - Quick verification of Math3D library
// Compile: g++ -std=c++17 -I../../src math3d_test.cpp -o math3d_test && ./math3d_test
#include "3d/Math3D.h"
#include <iostream>
#include <cmath>
using namespace mcrf;
bool approx(float a, float b, float eps = 0.0001f) {
return std::abs(a - b) < eps;
}
int main() {
int passed = 0, failed = 0;
// vec3 tests
{
vec3 a(1, 2, 3);
vec3 b(4, 5, 6);
vec3 sum = a + b;
if (approx(sum.x, 5) && approx(sum.y, 7) && approx(sum.z, 9)) {
std::cout << "[PASS] vec3 addition\n"; passed++;
} else {
std::cout << "[FAIL] vec3 addition\n"; failed++;
}
float dot = a.dot(b);
if (approx(dot, 32)) { // 1*4 + 2*5 + 3*6 = 32
std::cout << "[PASS] vec3 dot product\n"; passed++;
} else {
std::cout << "[FAIL] vec3 dot product: " << dot << "\n"; failed++;
}
vec3 c(1, 0, 0);
vec3 d(0, 1, 0);
vec3 cross = c.cross(d);
if (approx(cross.x, 0) && approx(cross.y, 0) && approx(cross.z, 1)) {
std::cout << "[PASS] vec3 cross product\n"; passed++;
} else {
std::cout << "[FAIL] vec3 cross product\n"; failed++;
}
vec3 n = vec3(3, 4, 0).normalized();
if (approx(n.length(), 1.0f)) {
std::cout << "[PASS] vec3 normalize\n"; passed++;
} else {
std::cout << "[FAIL] vec3 normalize\n"; failed++;
}
}
// mat4 tests
{
mat4 id = mat4::identity();
vec3 p(1, 2, 3);
vec3 transformed = id.transformPoint(p);
if (approx(transformed.x, 1) && approx(transformed.y, 2) && approx(transformed.z, 3)) {
std::cout << "[PASS] mat4 identity transform\n"; passed++;
} else {
std::cout << "[FAIL] mat4 identity transform\n"; failed++;
}
mat4 trans = mat4::translate(10, 20, 30);
vec3 moved = trans.transformPoint(p);
if (approx(moved.x, 11) && approx(moved.y, 22) && approx(moved.z, 33)) {
std::cout << "[PASS] mat4 translation\n"; passed++;
} else {
std::cout << "[FAIL] mat4 translation\n"; failed++;
}
mat4 scl = mat4::scale(2, 3, 4);
vec3 scaled = scl.transformPoint(p);
if (approx(scaled.x, 2) && approx(scaled.y, 6) && approx(scaled.z, 12)) {
std::cout << "[PASS] mat4 scale\n"; passed++;
} else {
std::cout << "[FAIL] mat4 scale\n"; failed++;
}
// Test rotation: 90 degrees around Y should swap X and Z
mat4 rotY = mat4::rotateY(HALF_PI);
vec3 rotated = rotY.transformPoint(vec3(1, 0, 0));
if (approx(rotated.x, 0) && approx(rotated.z, -1)) {
std::cout << "[PASS] mat4 rotateY\n"; passed++;
} else {
std::cout << "[FAIL] mat4 rotateY: " << rotated.x << "," << rotated.y << "," << rotated.z << "\n"; failed++;
}
}
// Projection matrix test
{
mat4 proj = mat4::perspective(radians(90.0f), 1.0f, 0.1f, 100.0f);
// A point at z=-1 (in front of camera) should project to valid NDC
vec4 p(0, 0, -1, 1);
vec4 clip = proj * p;
vec3 ndc = clip.perspectiveDivide();
if (ndc.z > -1.0f && ndc.z < 1.0f) {
std::cout << "[PASS] mat4 perspective\n"; passed++;
} else {
std::cout << "[FAIL] mat4 perspective\n"; failed++;
}
}
// LookAt matrix test
{
mat4 view = mat4::lookAt(vec3(0, 0, 5), vec3(0, 0, 0), vec3(0, 1, 0));
vec3 origin = view.transformPoint(vec3(0, 0, 0));
// Origin should be at z=-5 in view space (5 units in front)
if (approx(origin.x, 0) && approx(origin.y, 0) && approx(origin.z, -5)) {
std::cout << "[PASS] mat4 lookAt\n"; passed++;
} else {
std::cout << "[FAIL] mat4 lookAt: " << origin.x << "," << origin.y << "," << origin.z << "\n"; failed++;
}
}
// Quaternion tests
{
quat q = quat::fromAxisAngle(vec3(0, 1, 0), HALF_PI);
vec3 rotated = q.rotate(vec3(1, 0, 0));
if (approx(rotated.x, 0) && approx(rotated.z, -1)) {
std::cout << "[PASS] quat rotation\n"; passed++;
} else {
std::cout << "[FAIL] quat rotation: " << rotated.x << "," << rotated.y << "," << rotated.z << "\n"; failed++;
}
quat a = quat::fromAxisAngle(vec3(0, 1, 0), 0);
quat b = quat::fromAxisAngle(vec3(0, 1, 0), PI);
quat mid = quat::slerp(a, b, 0.5f);
vec3 half = mid.rotate(vec3(1, 0, 0));
// At t=0.5 between 0 and PI rotation, we should get 90 degrees
// Result should be perpendicular to input (x near 0, |z| near 1)
if (approx(half.x, 0, 0.01f) && approx(std::abs(half.z), 1, 0.01f)) {
std::cout << "[PASS] quat slerp\n"; passed++;
} else {
std::cout << "[FAIL] quat slerp: " << half.x << "," << half.y << "," << half.z << "\n"; failed++;
}
}
std::cout << "\n=== Results: " << passed << " passed, " << failed << " failed ===\n";
return failed > 0 ? 1 : 0;
}