diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-05 16:40:27 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-05 16:40:27 +0100 |
| commit | f6f3c13fcd287774a458730722854baab8a17366 (patch) | |
| tree | 44420eecdd2e2dd84d68be12cb12641064eb1c5a /src/tests/test_physics.cc | |
| parent | 93a65b43094641b4c188b4fc260b8ed44c883728 (diff) | |
feat(physics): Implement SDF-based physics engine and BVH
Completed Task #49.
- Implemented CPU-side SDF library (sphere, box, torus, plane).
- Implemented Dynamic BVH construction (rebuilt every frame).
- Implemented PhysicsSystem with semi-implicit Euler integration and collision resolution.
- Added visual debugging for BVH nodes.
- Created test_3d_physics interactive test and test_physics unit tests.
- Updated project docs and triaged new tasks.
Diffstat (limited to 'src/tests/test_physics.cc')
| -rw-r--r-- | src/tests/test_physics.cc | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/src/tests/test_physics.cc b/src/tests/test_physics.cc new file mode 100644 index 0000000..a59502c --- /dev/null +++ b/src/tests/test_physics.cc @@ -0,0 +1,151 @@ +// This file is part of the 64k demo project. +// It tests the CPU-side SDF library and BVH for physics and collision. + +#include "3d/bvh.h" +#include "3d/physics.h" +#include "3d/sdf_cpu.h" +#include <cassert> +#include <cmath> +#include <iostream> + +bool near(float a, float b, float e = 0.001f) { + return std::abs(a - b) < e; +} + +void test_sdf_sphere() { + std::cout << "Testing sdSphere..." << std::endl; + float r = 1.0f; + assert(near(sdf::sdSphere({0, 0, 0}, r), -1.0f)); + assert(near(sdf::sdSphere({1, 0, 0}, r), 0.0f)); + assert(near(sdf::sdSphere({2, 0, 0}, r), 1.0f)); +} + +void test_sdf_box() { + std::cout << "Testing sdBox..." << std::endl; + vec3 b(1, 1, 1); + assert(near(sdf::sdBox({0, 0, 0}, b), -1.0f)); + assert(near(sdf::sdBox({1, 1, 1}, b), 0.0f)); + assert(near(sdf::sdBox({2, 0, 0}, b), 1.0f)); +} + +void test_sdf_torus() { + std::cout << "Testing sdTorus..." << std::endl; + vec2 t(1.0f, 0.2f); + // Point on the ring: length(p.xz) = 1.0, p.y = 0 + assert(near(sdf::sdTorus({1, 0, 0}, t), -0.2f)); + assert(near(sdf::sdTorus({1.2f, 0, 0}, t), 0.0f)); +} + +void test_sdf_plane() { + std::cout << "Testing sdPlane..." << std::endl; + vec3 n(0, 1, 0); + float h = 1.0f; // Plane is at y = -1 (dot(p,n) + 1 = 0 => y = -1) + assert(near(sdf::sdPlane({0, 0, 0}, n, h), 1.0f)); + assert(near(sdf::sdPlane({0, -1, 0}, n, h), 0.0f)); +} + +void test_calc_normal() { + std::cout << "Testing calc_normal..." << std::endl; + + // Sphere normal at (1,0,0) should be (1,0,0) + auto sphere_sdf = [](vec3 p) { return sdf::sdSphere(p, 1.0f); }; + vec3 n = sdf::calc_normal({1, 0, 0}, sphere_sdf); + assert(near(n.x, 1.0f) && near(n.y, 0.0f) && near(n.z, 0.0f)); + + // Box normal at side + auto box_sdf = [](vec3 p) { return sdf::sdBox(p, {1, 1, 1}); }; + n = sdf::calc_normal({1, 0, 0}, box_sdf); + assert(near(n.x, 1.0f) && near(n.y, 0.0f) && near(n.z, 0.0f)); + + // Plane normal should be n + vec3 plane_n(0, 1, 0); + auto plane_sdf = [plane_n](vec3 p) { return sdf::sdPlane(p, plane_n, 1.0f); }; + n = sdf::calc_normal({0, 0, 0}, plane_sdf); + assert(near(n.x, plane_n.x) && near(n.y, plane_n.y) && near(n.z, plane_n.z)); +} + +void test_bvh() { + std::cout << "Testing BVH..." << std::endl; + std::vector<Object3D> objects; + + // Object 0: Left side + Object3D obj0(ObjectType::BOX); + obj0.position = {-10, 0, 0}; + objects.push_back(obj0); + + // Object 1: Right side + Object3D obj1(ObjectType::BOX); + obj1.position = {10, 0, 0}; + objects.push_back(obj1); + + BVH bvh; + BVHBuilder::build(bvh, objects); + + assert(bvh.nodes.size() == 3); // 1 root + 2 leaves + + // Query left side + std::vector<int> results; + bvh.query({{-12, -2, -2}, {-8, 2, 2}}, results); + assert(results.size() == 1); + assert(results[0] == 0); + + // Query right side + results.clear(); + bvh.query({{8, -2, -2}, {12, 2, 2}}, results); + assert(results.size() == 1); + assert(results[0] == 1); + + // Query center (should miss both) + results.clear(); + bvh.query({{-2, -2, -2}, {2, 2, 2}}, results); + assert(results.size() == 0); + + // Query both + results.clear(); + bvh.query({{-12, -2, -2}, {12, 2, 2}}, results); + assert(results.size() == 2); + } + + void test_physics_falling() { + std::cout << "Testing Physics falling..." << std::endl; + Scene scene; + + // Plane at y = -1 + Object3D plane(ObjectType::PLANE); + plane.position = {0, -1, 0}; + plane.is_static = true; + scene.add_object(plane); + + // Sphere at y = 5 + Object3D sphere(ObjectType::SPHERE); + sphere.position = {0, 5, 0}; + sphere.velocity = {0, 0, 0}; + sphere.restitution = 0.0f; // No bounce for simple test + scene.add_object(sphere); + + PhysicsSystem physics; + float dt = 0.016f; + for (int i = 0; i < 100; ++i) { + physics.update(scene, dt); + } + + // Sphere should be above or at plane (y >= 0 because sphere radius is 1, + // plane is at -1) + assert(scene.objects[1].position.y >= -0.01f); + // Also should have slowed down + assert(scene.objects[1].velocity.y > -1.0f); + } + + int main() { + test_sdf_sphere(); + test_sdf_box(); + test_sdf_torus(); + test_sdf_plane(); + test_calc_normal(); + test_bvh(); + test_physics_falling(); + + std::cout << "--- ALL PHYSICS TESTS PASSED ---" << std::endl; + return 0; + } +
\ No newline at end of file |
