physics and rendering fixes

This commit is contained in:
2026-07-17 21:06:21 -05:00
parent 5f08337726
commit 8fb1c9fb42
25 changed files with 491 additions and 78 deletions
+1
View File
@@ -7,5 +7,6 @@ include(dusktest)
# Tests
dusktest(test_maparea.c)
dusktest(test_tileshape.c)
# Subdirs
+140
View File
@@ -0,0 +1,140 @@
/**
* 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 "rpg/overworld/tileshape.h"
static void test_tileShapeGetRampHeightGroundIsAlwaysFlat(void **state) {
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_GROUND, 0.0f, 0.0f), 0.0f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_GROUND, 1.0f, 1.0f), 0.0f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_GROUND, 0.5f, 0.5f), 0.0f, 0.0001f
);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_tileShapeGetRampHeightCardinalRamps(void **state) {
// RAMP_NORTH: rises going north (+y) - height equals localY.
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_NORTH, 0.5f, 0.0f), 0.0f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_NORTH, 0.5f, 1.0f), 1.0f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_NORTH, 0.0f, 0.5f), 0.5f, 0.0001f
);
// RAMP_SOUTH: rises going south (-y) - height equals 1 - localY.
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_SOUTH, 0.5f, 0.0f), 1.0f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_SOUTH, 0.5f, 1.0f), 0.0f, 0.0001f
);
// RAMP_EAST: rises going east (+x) - height equals localX.
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_EAST, 0.0f, 0.5f), 0.0f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_EAST, 1.0f, 0.5f), 1.0f, 0.0001f
);
// RAMP_WEST: rises going west (-x) - height equals 1 - localX.
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_WEST, 0.0f, 0.5f), 1.0f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_WEST, 1.0f, 0.5f), 0.0f, 0.0001f
);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_tileShapeGetRampHeightOuterCornerRamps(void **state) {
// RAMP_NORTHEAST: only the NE corner raised - a hip shape, height
// equal to min(localX, localY) since the mesh is split along the
// SW-NE diagonal into two flat triangles.
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_NORTHEAST, 0.0f, 0.0f),
0.0f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_NORTHEAST, 1.0f, 1.0f),
1.0f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_NORTHEAST, 0.3f, 0.7f),
0.3f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_NORTHEAST, 0.7f, 0.3f),
0.3f, 0.0001f
);
// RAMP_SOUTHWEST: only the SW corner raised - height equal to
// min(1 - localX, 1 - localY).
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_SOUTHWEST, 0.0f, 0.0f),
1.0f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_SOUTHWEST, 1.0f, 1.0f),
0.0f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_SOUTHWEST, 1.0f, 0.0f),
0.0f, 0.0001f
);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_tileShapeGetRampHeightInnerCornerRamp(void **state) {
// RAMP_NORTHEAST_INNER: only the opposite (SW) corner lowered, rest of
// the tile raised - height equal to max(localX, localY).
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_NORTHEAST_INNER, 0.0f, 0.0f),
0.0f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_NORTHEAST_INNER, 1.0f, 1.0f),
1.0f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_NORTHEAST_INNER, 0.3f, 0.7f),
0.7f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_NORTHEAST_INNER, 0.7f, 0.3f),
0.7f, 0.0001f
);
assert_float_equal(
tileShapeGetRampHeight(TILE_SHAPE_RAMP_NORTHEAST_INNER, 1.0f, 0.0f),
1.0f, 0.0001f
);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_tileShapeGetRampHeightGroundIsAlwaysFlat),
cmocka_unit_test(test_tileShapeGetRampHeightCardinalRamps),
cmocka_unit_test(test_tileShapeGetRampHeightOuterCornerRamps),
cmocka_unit_test(test_tileShapeGetRampHeightInnerCornerRamp),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}
+80 -4
View File
@@ -224,16 +224,91 @@ static void test_physicsWorldStepMultiColumnFootprint(void **state) {
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_physicsWorldStepRampTreatedAsFlatGround(void **state) {
static void test_physicsWorldStepClimbsRampGoingUp(void **state) {
testMapReset();
testMapSetTile(0, 0, 0, TILE_SHAPE_GROUND);
testMapSetTile(1, 0, 0, TILE_SHAPE_RAMP_EAST);
for(worldunit_t x = 2; x < 6; x++) {
testMapSetTile(x, 0, 1, TILE_SHAPE_GROUND);
}
physicsworld_t world;
physicsWorldInit(&world, 20.0f, 40.0f);
physicsbody_t body;
const vec3 position = { 0.0f, 0.0f, 0.0f };
const vec3 extents = { 1.0f, 1.0f, 1.0f };
physicsBodyInit(&body, position, extents);
body.velocity[0] = 1.0f;
for(uint32_t i = 0; i < 400; i++) {
physicsWorldStep(&world, &body, DUSK_TIME_STEP, NULL, 0);
// Ground height is driven by the body's center (not its corner
// position), so a 1-wide body barely touching a taller neighbouring
// column isn't yanked onto that column's full height - see
// physicsWorldResolveAxisZ. While the center is over the ramp tile,
// height should track how far across it smoothly, not snap flat.
const float_t centerX = body.position[0] + 0.5f;
if(centerX >= 1.0f && centerX < 2.0f) {
const float_t expected = centerX - 1.0f;
assert_float_equal(body.position[2], expected, 0.005f);
}
}
// Fully across, resting on the elevated ground one layer up.
assert_float_equal(body.position[0], 5.0f, 0.001f);
assert_float_equal(body.position[2], 1.0f, 0.005f);
assert_true(body.grounded);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_physicsWorldStepDescendsRampGoingDown(void **state) {
testMapReset();
testMapSetTile(0, 0, 1, TILE_SHAPE_GROUND);
testMapSetTile(1, 0, 0, TILE_SHAPE_RAMP_WEST);
for(worldunit_t x = 2; x < 6; x++) {
testMapSetTile(x, 0, 0, TILE_SHAPE_GROUND);
}
physicsworld_t world;
physicsWorldInit(&world, 0.0f, 40.0f);
physicsWorldInit(&world, 20.0f, 40.0f);
physicsbody_t body;
const vec3 position = { 0.0f, 0.0f, 1.0f };
const vec3 extents = { 1.0f, 1.0f, 1.0f };
physicsBodyInit(&body, position, extents);
body.velocity[0] = 1.0f;
for(uint32_t i = 0; i < 400; i++) {
physicsWorldStep(&world, &body, DUSK_TIME_STEP, NULL, 0);
// Never falls through as a hole while crossing the ramp - descent
// may lag slightly behind the ideal slope (gravity closes the gap
// each step, see physicsWorldStep's documented limitation) but must
// stay close to it, never dropping toward the void below.
assert_true(body.position[2] >= -0.2f);
}
// Fully across, resting on the lower ground.
assert_float_equal(body.position[0], 5.0f, 0.001f);
assert_float_equal(body.position[2], 0.0f, 0.005f);
assert_true(body.grounded);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_physicsWorldStepFlatGroundUnaffectedByRampLogic(
void **state
) {
testMapReset();
for(worldunit_t x = 0; x < 6; x++) {
testMapSetTile(x, 0, 0, TILE_SHAPE_GROUND);
}
physicsworld_t world;
physicsWorldInit(&world, 20.0f, 40.0f);
physicsbody_t body;
const vec3 position = { 0.0f, 0.0f, 0.0f };
@@ -246,7 +321,6 @@ static void test_physicsWorldStepRampTreatedAsFlatGround(void **state) {
assert_float_equal(body.position[2], 0.0f, 0.0001f);
}
assert_float_equal(body.velocity[0], 5.0f, 0.0001f);
assert_true(body.position[0] > 1.5f);
assert_int_equal(memoryGetAllocatedCount(), 0);
@@ -346,7 +420,9 @@ int main(void) {
cmocka_unit_test(test_physicsWorldStepTerminalVelocityClamp),
cmocka_unit_test(test_physicsWorldStepBlockedByCeiling),
cmocka_unit_test(test_physicsWorldStepMultiColumnFootprint),
cmocka_unit_test(test_physicsWorldStepRampTreatedAsFlatGround),
cmocka_unit_test(test_physicsWorldStepClimbsRampGoingUp),
cmocka_unit_test(test_physicsWorldStepDescendsRampGoingDown),
cmocka_unit_test(test_physicsWorldStepFlatGroundUnaffectedByRampLogic),
cmocka_unit_test(test_physicsWorldStepBlockedByOtherBody),
cmocka_unit_test(test_physicsWorldStepBlockedByNearestOfMultipleBodies),
cmocka_unit_test(test_physicsWorldResolveBodyOverlapIgnoresSelfAndNull),