/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "dusktest.h" #include "util/memory.h" #include "physics/physicstest.h" #include "physics/physicsshapemesh.h" // A flat 10x10 ground quad on the XZ plane at y=0, centered at the origin // (two triangles, tightly packed vec3 vertices -- no indices). static vec3 FLAT_GROUND_VERTICES[6] = { { -5.0f, 0.0f, -5.0f }, { 5.0f, 0.0f, -5.0f }, { 5.0f, 0.0f, 5.0f }, { -5.0f, 0.0f, -5.0f }, { 5.0f, 0.0f, 5.0f }, { -5.0f, 0.0f, 5.0f }, }; static physicsshapemesh_t flatGroundMesh(void) { return (physicsshapemesh_t){ .vertices = (const uint8_t *)FLAT_GROUND_VERTICES, .stride = sizeof(vec3), .triangleCount = 2 }; } static void test_getVertexReadsRawPositions(void **state) { physicsshapemesh_t mesh = flatGroundMesh(); vec3 v0, v3; physicsShapeMeshGetVertex(&mesh, 0, v0); physicsShapeMeshGetVertex(&mesh, 3, v3); assert_float_equal(v0[0], -5.0f, 0.0001f); assert_float_equal(v0[2], -5.0f, 0.0001f); assert_float_equal(v3[0], -5.0f, 0.0001f); assert_float_equal(v3[2], -5.0f, 0.0001f); assert_int_equal(memoryGetAllocatedCount(), 0); } // Vertex layout matching a typical interleaved render vertex (uv then // pos), to prove the stride-based reader can point straight at a render // buffer instead of a dedicated physics copy. typedef struct { float_t uv[2]; float_t pos[3]; } testInterleavedVertex_t; static void test_supportsInterleavedRenderVertexLayout(void **state) { testInterleavedVertex_t vertices[3] = { { .uv = { 0, 0 }, .pos = { -5.0f, 0.0f, -5.0f } }, { .uv = { 1, 0 }, .pos = { 5.0f, 0.0f, -5.0f } }, { .uv = { 0, 1 }, .pos = { -5.0f, 0.0f, 5.0f } }, }; physicsshapemesh_t mesh = { .vertices = (const uint8_t *)vertices[0].pos, .stride = sizeof(testInterleavedVertex_t), .triangleCount = 1 }; vec3 v1; physicsShapeMeshGetVertex(&mesh, 1, v1); assert_float_equal(v1[0], 5.0f, 0.0001f); assert_float_equal(v1[2], -5.0f, 0.0001f); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_sphereRestingOnMesh(void **state) { physicsshapemesh_t mesh = flatGroundMesh(); vec3 meshPos = { 0.0f, 0.0f, 0.0f }; // Sphere overlapping the ground by 0.2. vec3 sphereCenter = { 0.0f, 0.3f, 0.0f }; vec3 normal; float_t depth; assert_true(physicsShapeMeshTestSphere( &mesh, meshPos, sphereCenter, 0.5f, normal, &depth )); assert_float_equal(depth, 0.2f, 0.0001f); assert_float_equal(normal[1], 1.0f, 0.0001f); // pushes sphere up // Far above: no overlap. vec3 sphereFar = { 0.0f, 10.0f, 0.0f }; assert_false(physicsShapeMeshTestSphere( &mesh, meshPos, sphereFar, 0.5f, normal, &depth )); // Offsetting the mesh's own position shifts the ground with it: raising // both the ground and the sphere together reproduces the same overlap. vec3 meshPosRaised = { 0.0f, 1.0f, 0.0f }; vec3 sphereCenterRaised = { 0.0f, 1.3f, 0.0f }; assert_true(physicsShapeMeshTestSphere( &mesh, meshPosRaised, sphereCenterRaised, 0.5f, normal, &depth )); assert_float_equal(depth, 0.2f, 0.0001f); // But the sphere no longer overlaps the ground at its old (un-raised) // height, since the ground moved out from under it. assert_false(physicsShapeMeshTestSphere( &mesh, meshPosRaised, sphereCenter, 0.5f, normal, &depth )); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_capsuleRestingOnMesh(void **state) { physicsshapemesh_t mesh = flatGroundMesh(); vec3 meshPos = { 0.0f, 0.0f, 0.0f }; // Standing capsule (half-height 1) whose bottom cap dips 0.1 into the // ground: center at y = radius + halfHeight - 0.1. const float_t radius = 0.5f; const float_t halfHeight = 1.0f; vec3 capsuleCenter = { 0.0f, radius + halfHeight - 0.1f, 0.0f }; vec3 normal; float_t depth; assert_true(physicsShapeMeshTestCapsule( &mesh, meshPos, capsuleCenter, radius, halfHeight, normal, &depth )); assert_float_equal(depth, 0.1f, 0.001f); assert_float_equal(normal[1], 1.0f, 0.0001f); // Lifted well clear of the ground: no overlap. vec3 capsuleHigh = { 0.0f, 10.0f, 0.0f }; assert_false(physicsShapeMeshTestCapsule( &mesh, meshPos, capsuleHigh, radius, halfHeight, normal, &depth )); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_cubeIsUnsupported(void **state) { physicsshapemesh_t mesh = flatGroundMesh(); vec3 meshPos = { 0.0f, 0.0f, 0.0f }; physicsshape_t cube = { .type = PHYSICS_SHAPE_CUBE, .data.cube.halfExtents = { 0.5f, 0.5f, 0.5f } }; vec3 cubePos = { 0.0f, 0.3f, 0.0f }; vec3 normal; float_t depth; assert_false(physicsShapeMeshTest( meshPos, &mesh, cubePos, &cube, normal, &depth )); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_physicsShapeMeshCreateIntegratesWithDispatch(void **state) { physicsshapemesh_t mesh = flatGroundMesh(); physicsshape_t landscape = physicsShapeMeshCreate(&mesh); assert_int_equal(landscape.type, PHYSICS_SHAPE_CUSTOM); physicsshape_t sphere = { .type = PHYSICS_SHAPE_SPHERE, .data.sphere.radius = 0.5f }; vec3 meshPos = { 0.0f, 0.0f, 0.0f }; vec3 sphereCenter = { 0.0f, 0.3f, 0.0f }; // Landscape as B, matching physicsWorldStep's dynamic(A)-vs-static(B). vec3 normal; float_t depth; assert_true(physicsTestShapeVsShape( sphereCenter, &sphere, meshPos, &landscape, normal, &depth )); assert_float_equal(depth, 0.2f, 0.0001f); assert_float_equal(normal[1], 1.0f, 0.0001f); assert_int_equal(memoryGetAllocatedCount(), 0); } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_getVertexReadsRawPositions), cmocka_unit_test(test_supportsInterleavedRenderVertexLayout), cmocka_unit_test(test_sphereRestingOnMesh), cmocka_unit_test(test_capsuleRestingOnMesh), cmocka_unit_test(test_cubeIsUnsupported), cmocka_unit_test(test_physicsShapeMeshCreateIntegratesWithDispatch), }; return cmocka_run_group_tests(tests, NULL, NULL); }