186 lines
6.0 KiB
C
186 lines
6.0 KiB
C
/**
|
|
* 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);
|
|
}
|