Files
dusk/test/physics/test_physicstest.c
2026-07-18 20:53:22 -05:00

229 lines
7.5 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"
static void test_aabbVsAabbOverlapAndSeparation(void **state) {
vec3 normal; float_t depth;
// Overlapping on X (smallest penetration axis).
vec3 ac = { 0.6f, 0.0f, 0.0f }, ah = { 0.5f, 0.5f, 0.5f };
vec3 bc = { 0.0f, 0.0f, 0.0f }, bh = { 0.5f, 0.5f, 0.5f };
assert_true(physicsTestAabbVsAabb(ac, ah, bc, bh, normal, &depth));
assert_float_equal(depth, 0.4f, 0.0001f);
assert_float_equal(normal[0], 1.0f, 0.0001f);
assert_float_equal(normal[1], 0.0f, 0.0001f);
assert_float_equal(normal[2], 0.0f, 0.0001f);
// Far apart: no overlap.
vec3 cc = { 10.0f, 0.0f, 0.0f };
assert_false(physicsTestAabbVsAabb(cc, ah, bc, bh, normal, &depth));
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_sphereVsSphere(void **state) {
vec3 normal; float_t depth;
vec3 ac = { 1.0f, 0.0f, 0.0f };
vec3 bc = { 0.0f, 0.0f, 0.0f };
assert_true(physicsTestSphereVsSphere(ac, 0.75f, bc, 0.75f, normal, &depth));
assert_float_equal(depth, 0.5f, 0.0001f);
assert_float_equal(normal[0], 1.0f, 0.0001f);
assert_false(physicsTestSphereVsSphere(ac, 0.25f, bc, 0.25f, normal, &depth));
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_sphereVsAabbOutsideAndInside(void **state) {
vec3 normal; float_t depth;
// Sphere just outside the box corner, overlapping.
vec3 sc = { 1.2f, 0.0f, 0.0f };
vec3 ac = { 0.0f, 0.0f, 0.0f }, ah = { 0.5f, 0.5f, 0.5f };
assert_true(physicsTestSphereVsAabb(sc, 0.75f, ac, ah, normal, &depth));
assert_float_equal(normal[0], 1.0f, 0.0001f);
assert_float_equal(depth, 0.75f - 0.7f, 0.0001f);
// Sphere center inside the box: pushes out the nearest face.
vec3 scInside = { 0.4f, 0.0f, 0.0f };
assert_true(
physicsTestSphereVsAabb(scInside, 0.1f, ac, ah, normal, &depth)
);
assert_float_equal(normal[0], 1.0f, 0.0001f);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_aabbVsPlane(void **state) {
vec3 normal; float_t depth;
vec3 ac = { 0.0f, 0.4f, 0.0f }, ah = { 0.5f, 0.5f, 0.5f };
vec3 pn = { 0.0f, 1.0f, 0.0f };
assert_true(physicsTestAabbVsPlane(ac, ah, pn, 0.0f, normal, &depth));
assert_float_equal(depth, 0.1f, 0.0001f);
assert_float_equal(normal[1], 1.0f, 0.0001f);
vec3 acAbove = { 0.0f, 10.0f, 0.0f };
assert_false(physicsTestAabbVsPlane(acAbove, ah, pn, 0.0f, normal, &depth));
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_dispatchSymmetryAndPlaneRouting(void **state) {
physicsshape_t cubeA = {
.type = PHYSICS_SHAPE_CUBE,
.data.cube.halfExtents = { 0.5f, 0.5f, 0.5f }
};
physicsshape_t sphereB = {
.type = PHYSICS_SHAPE_SPHERE,
.data.sphere.radius = 0.75f
};
vec3 aPos = { 0.6f, 0.0f, 0.0f };
vec3 bPos = { 0.0f, 0.0f, 0.0f };
vec3 normalAB; float_t depthAB;
assert_true(physicsTestShapeVsShape(
aPos, &cubeA, bPos, &sphereB, normalAB, &depthAB
));
vec3 normalBA; float_t depthBA;
assert_true(physicsTestShapeVsShape(
bPos, &sphereB, aPos, &cubeA, normalBA, &depthBA
));
// Swapping A/B should give the same depth and a negated normal.
assert_float_equal(depthAB, depthBA, 0.0001f);
assert_float_equal(normalAB[0], -normalBA[0], 0.0001f);
// Cube vs plane routes to physicsTestAabbVsPlane.
physicsshape_t plane = {
.type = PHYSICS_SHAPE_PLANE,
.data.plane = { .normal = { 0.0f, 1.0f, 0.0f }, .distance = 0.0f }
};
vec3 cubePos = { 0.0f, 0.4f, 0.0f };
vec3 normal; float_t depth;
assert_true(physicsTestShapeVsShape(
cubePos, &cubeA, bPos, &plane, normal, &depth
));
assert_float_equal(depth, 0.1f, 0.0001f);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_capsuleVsSphere(void **state) {
vec3 normal; float_t depth;
// Capsule standing on the Y axis, sphere touching its side.
vec3 cc = { 0.0f, 0.0f, 0.0f };
vec3 sc = { 0.9f, 0.0f, 0.0f };
assert_true(physicsTestCapsuleVsSphere(
cc, 0.5f, 1.0f, sc, 0.5f, normal, &depth
));
assert_float_equal(normal[1], 0.0f, 0.0001f);
assert_true(depth > 0.0f);
vec3 scFar = { 5.0f, 0.0f, 0.0f };
assert_false(physicsTestCapsuleVsSphere(
cc, 0.5f, 1.0f, scFar, 0.5f, normal, &depth
));
assert_int_equal(memoryGetAllocatedCount(), 0);
}
// Example custom shape callback: an infinite flat landscape at a fixed
// height, described by a single float_t (userData). Mirrors how a real
// terrain/heightfield callback would delegate to a primitive test.
static bool_t flatLandscapeTest(
const vec3 selfPos, const void *selfData,
const vec3 otherPos, const physicsshape_t *otherShape,
vec3 outNormal, float_t *outDepth
) {
const float_t *groundHeight = (const float_t *)selfData;
vec3 planeNormal = { 0.0f, 1.0f, 0.0f };
if(otherShape->type != PHYSICS_SHAPE_SPHERE) return false;
return physicsTestSphereVsPlane(
otherPos, otherShape->data.sphere.radius,
planeNormal, selfPos[1] + *groundHeight, outNormal, outDepth
);
}
static void test_customShapeFlatLandscape(void **state) {
float_t groundHeight = 0.0f;
physicsshape_t landscape = {
.type = PHYSICS_SHAPE_CUSTOM,
.data.custom = { .userData = &groundHeight, .test = flatLandscapeTest }
};
physicsshape_t sphere = {
.type = PHYSICS_SHAPE_SPHERE,
.data.sphere.radius = 0.5f
};
vec3 landscapePos = { 0.0f, 0.0f, 0.0f };
// Sphere center at y=0.3, radius 0.5: overlaps the ground by 0.2.
vec3 spherePos = { 0.0f, 0.3f, 0.0f };
// Landscape as B: the ordering physicsWorldStep actually uses (dynamic
// body as A, static/custom body as B).
vec3 normal; float_t depth;
assert_true(physicsTestShapeVsShape(
spherePos, &sphere, landscapePos, &landscape, normal, &depth
));
assert_float_equal(depth, 0.2f, 0.0001f);
assert_float_equal(normal[0], 0.0f, 0.0001f);
assert_float_equal(normal[1], 1.0f, 0.0001f); // pushes the sphere UP
assert_float_equal(normal[2], 0.0f, 0.0001f);
// Landscape as A: same depth, negated normal.
vec3 normalSwapped; float_t depthSwapped;
assert_true(physicsTestShapeVsShape(
landscapePos, &landscape, spherePos, &sphere, normalSwapped, &depthSwapped
));
assert_float_equal(depthSwapped, depth, 0.0001f);
assert_float_equal(normalSwapped[1], -normal[1], 0.0001f);
// Sphere far above the ground: no overlap.
vec3 sphereFar = { 0.0f, 10.0f, 0.0f };
assert_false(physicsTestShapeVsShape(
sphereFar, &sphere, landscapePos, &landscape, normal, &depth
));
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_customVsCustomAsserts(void **state) {
float_t groundHeight = 0.0f;
physicsshape_t landscapeA = {
.type = PHYSICS_SHAPE_CUSTOM,
.data.custom = { .userData = &groundHeight, .test = flatLandscapeTest }
};
physicsshape_t landscapeB = landscapeA;
vec3 pos = { 0.0f, 0.0f, 0.0f };
vec3 normal; float_t depth;
expect_assert_failure(physicsTestShapeVsShape(
pos, &landscapeA, pos, &landscapeB, normal, &depth
));
}
int main(int argc, char **argv) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_aabbVsAabbOverlapAndSeparation),
cmocka_unit_test(test_sphereVsSphere),
cmocka_unit_test(test_sphereVsAabbOutsideAndInside),
cmocka_unit_test(test_aabbVsPlane),
cmocka_unit_test(test_dispatchSymmetryAndPlaneRouting),
cmocka_unit_test(test_capsuleVsSphere),
cmocka_unit_test(test_customShapeFlatLandscape),
cmocka_unit_test(test_customVsCustomAsserts),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}