Time fixes
This commit is contained in:
@@ -8,3 +8,4 @@ include(dusktest)
|
||||
# Tests
|
||||
dusktest(test_physicstest.c)
|
||||
dusktest(test_physicsworld.c)
|
||||
dusktest(test_physicsshapemesh.c)
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
+44
-5
@@ -12,6 +12,7 @@
|
||||
#include "entity/entitymanager.h"
|
||||
|
||||
static void countingUpdateCallback(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
@@ -58,10 +59,9 @@ static void test_sceneEntitiesAreIsolatedPerScene(void **state) {
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_sceneUpdateOnlyTicksActiveScene(void **state) {
|
||||
static void test_sceneFixedUpdateOnlyTicksActiveScene(void **state) {
|
||||
sceneInit();
|
||||
timeInit();
|
||||
TIME.dynamicUpdate = true;
|
||||
|
||||
sceneid_t sceneA = sceneCreate();
|
||||
sceneid_t sceneB = sceneCreate();
|
||||
@@ -84,13 +84,13 @@ static void test_sceneUpdateOnlyTicksActiveScene(void **state) {
|
||||
);
|
||||
|
||||
sceneSetActive(sceneA);
|
||||
sceneUpdate();
|
||||
sceneFixedUpdate();
|
||||
|
||||
assert_int_equal(counterA, 1);
|
||||
assert_int_equal(counterB, 0);
|
||||
|
||||
sceneSetActive(sceneB);
|
||||
sceneUpdate();
|
||||
sceneFixedUpdate();
|
||||
|
||||
assert_int_equal(counterA, 1);
|
||||
assert_int_equal(counterB, 1);
|
||||
@@ -99,6 +99,44 @@ static void test_sceneUpdateOnlyTicksActiveScene(void **state) {
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_sceneUpdateOnlyRunsFixedUpdateOnNonDynamicFrames(void **state) {
|
||||
sceneInit();
|
||||
timeInit();
|
||||
|
||||
sceneid_t scene = sceneCreate();
|
||||
sceneSetActive(scene);
|
||||
|
||||
uint32_t counter = 0;
|
||||
entitymanager_t *entities = sceneGetEntities(scene);
|
||||
entityid_t entity = entityManagerAdd(entities);
|
||||
entityUpdateAdd(
|
||||
entities, entity, countingUpdateCallback, COMPONENT_ID_INVALID, &counter
|
||||
);
|
||||
|
||||
#if DUSK_TIME_DYNAMIC
|
||||
// Still accumulating toward the next fixed timestep: sceneUpdate must
|
||||
// not run sceneFixedUpdate yet.
|
||||
TIME.dynamicUpdate = true;
|
||||
sceneUpdate();
|
||||
assert_int_equal(counter, 0);
|
||||
|
||||
// A fixed timestep boundary was just crossed: sceneUpdate must run
|
||||
// sceneFixedUpdate exactly once.
|
||||
TIME.dynamicUpdate = false;
|
||||
sceneUpdate();
|
||||
assert_int_equal(counter, 1);
|
||||
#else
|
||||
// Non-dynamic-time platforms: every frame is a fixed timestep.
|
||||
sceneUpdate();
|
||||
assert_int_equal(counter, 1);
|
||||
sceneUpdate();
|
||||
assert_int_equal(counter, 2);
|
||||
#endif
|
||||
|
||||
sceneDispose();
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_sceneDestroyClearsActive(void **state) {
|
||||
sceneInit();
|
||||
|
||||
@@ -117,7 +155,8 @@ int main(int argc, char **argv) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_sceneCreateDestroy),
|
||||
cmocka_unit_test(test_sceneEntitiesAreIsolatedPerScene),
|
||||
cmocka_unit_test(test_sceneUpdateOnlyTicksActiveScene),
|
||||
cmocka_unit_test(test_sceneFixedUpdateOnlyTicksActiveScene),
|
||||
cmocka_unit_test(test_sceneUpdateOnlyRunsFixedUpdateOnNonDynamicFrames),
|
||||
cmocka_unit_test(test_sceneDestroyClearsActive),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user