Physics
This commit is contained in:
@@ -10,6 +10,7 @@ add_subdirectory(network)
|
||||
add_subdirectory(thread)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(physics)
|
||||
add_subdirectory(scene)
|
||||
# add_subdirectory(item)
|
||||
add_subdirectory(time)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
include(dusktest)
|
||||
|
||||
# Tests
|
||||
dusktest(test_physicstest.c)
|
||||
dusktest(test_physicsworld.c)
|
||||
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* 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 "entity/entitymanager.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
#include "physics/physicsworld.h"
|
||||
|
||||
static void test_entityPhysicsGettersAndSetters(void **state) {
|
||||
entitymanager_t mgr;
|
||||
entityManagerInit(&mgr);
|
||||
|
||||
entityid_t entity = entityManagerAdd(&mgr);
|
||||
componentid_t phys = entityAddComponent(&mgr, entity, COMPONENT_TYPE_PHYSICS);
|
||||
|
||||
// Defaults: dynamic 1x1x1 cube, zero velocity, not on ground.
|
||||
assert_int_equal(
|
||||
entityPhysicsGetBodyType(&mgr, entity, phys), PHYSICS_BODY_DYNAMIC
|
||||
);
|
||||
assert_false(entityPhysicsIsOnGround(&mgr, entity, phys));
|
||||
|
||||
vec3 velocity;
|
||||
entityPhysicsGetVelocity(&mgr, entity, phys, velocity);
|
||||
assert_float_equal(velocity[0], 0.0f, 0.0001f);
|
||||
assert_float_equal(velocity[1], 0.0f, 0.0001f);
|
||||
assert_float_equal(velocity[2], 0.0f, 0.0001f);
|
||||
|
||||
vec3 setVel = { 1.0f, 2.0f, 3.0f };
|
||||
entityPhysicsSetVelocity(&mgr, entity, phys, setVel);
|
||||
entityPhysicsGetVelocity(&mgr, entity, phys, velocity);
|
||||
assert_float_equal(velocity[0], 1.0f, 0.0001f);
|
||||
assert_float_equal(velocity[1], 2.0f, 0.0001f);
|
||||
assert_float_equal(velocity[2], 3.0f, 0.0001f);
|
||||
|
||||
vec3 impulse = { 1.0f, 0.0f, 0.0f };
|
||||
entityPhysicsApplyImpulse(&mgr, entity, phys, impulse);
|
||||
entityPhysicsGetVelocity(&mgr, entity, phys, velocity);
|
||||
assert_float_equal(velocity[0], 2.0f, 0.0001f);
|
||||
|
||||
// Impulses are a no-op on static bodies.
|
||||
entityPhysicsSetBodyType(&mgr, entity, phys, PHYSICS_BODY_STATIC);
|
||||
entityPhysicsApplyImpulse(&mgr, entity, phys, impulse);
|
||||
entityPhysicsGetVelocity(&mgr, entity, phys, velocity);
|
||||
assert_float_equal(velocity[0], 2.0f, 0.0001f);
|
||||
assert_int_equal(
|
||||
entityPhysicsGetBodyType(&mgr, entity, phys), PHYSICS_BODY_STATIC
|
||||
);
|
||||
|
||||
physicsshape_t sphere = {
|
||||
.type = PHYSICS_SHAPE_SPHERE,
|
||||
.data.sphere.radius = 2.0f
|
||||
};
|
||||
entityPhysicsSetShape(&mgr, entity, phys, sphere);
|
||||
physicsshape_t got = entityPhysicsGetShape(&mgr, entity, phys);
|
||||
assert_int_equal(got.type, PHYSICS_SHAPE_SPHERE);
|
||||
assert_float_equal(got.data.sphere.radius, 2.0f, 0.0001f);
|
||||
|
||||
entityManagerDispose(&mgr);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_physicsWorldGravityIntegratesDynamicBody(void **state) {
|
||||
entitymanager_t mgr;
|
||||
entityManagerInit(&mgr);
|
||||
physicsworld_t world;
|
||||
physicsWorldInit(&world);
|
||||
|
||||
entityid_t entity = entityManagerAdd(&mgr);
|
||||
componentid_t posComp = entityAddComponent(
|
||||
&mgr, entity, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
entityAddComponent(&mgr, entity, COMPONENT_TYPE_PHYSICS);
|
||||
|
||||
const float_t dt = 1.0f / 60.0f;
|
||||
physicsWorldStep(&world, &mgr, dt);
|
||||
|
||||
vec3 pos;
|
||||
entityPositionGetWorldPosition(&mgr, entity, posComp, pos);
|
||||
|
||||
// One step of gravity: velocity.y = gravity.y * dt, position integrates
|
||||
// that same-step velocity (semi-implicit Euler).
|
||||
float_t expectedVelY = world.gravity[1] * dt;
|
||||
float_t expectedPosY = expectedVelY * dt;
|
||||
assert_float_equal(pos[1], expectedPosY, 0.0001f);
|
||||
assert_true(pos[1] < 0.0f);
|
||||
|
||||
entityManagerDispose(&mgr);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_physicsWorldRestsOnStaticFloor(void **state) {
|
||||
entitymanager_t mgr;
|
||||
entityManagerInit(&mgr);
|
||||
physicsworld_t world;
|
||||
physicsWorldInit(&world);
|
||||
|
||||
// Static floor: a wide, thin platform centered at y=0 (top face at 0.5).
|
||||
entityid_t floorEntity = entityManagerAdd(&mgr);
|
||||
componentid_t floorPosComp = entityAddComponent(
|
||||
&mgr, floorEntity, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
componentid_t floorPhysComp = entityAddComponent(
|
||||
&mgr, floorEntity, COMPONENT_TYPE_PHYSICS
|
||||
);
|
||||
entityPhysicsSetBodyType(&mgr, floorEntity, floorPhysComp, PHYSICS_BODY_STATIC);
|
||||
physicsshape_t floorShape = {
|
||||
.type = PHYSICS_SHAPE_CUBE,
|
||||
.data.cube.halfExtents = { 5.0f, 0.5f, 5.0f }
|
||||
};
|
||||
entityPhysicsSetShape(&mgr, floorEntity, floorPhysComp, floorShape);
|
||||
vec3 floorPos = { 0.0f, 0.0f, 0.0f };
|
||||
entityPositionSetLocalPosition(&mgr, floorEntity, floorPosComp, floorPos);
|
||||
|
||||
// Dynamic body (default 1x1x1 cube) starting 4 units above the floor.
|
||||
entityid_t entity = entityManagerAdd(&mgr);
|
||||
componentid_t posComp = entityAddComponent(
|
||||
&mgr, entity, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
componentid_t physComp = entityAddComponent(
|
||||
&mgr, entity, COMPONENT_TYPE_PHYSICS
|
||||
);
|
||||
vec3 startPos = { 0.0f, 4.0f, 0.0f };
|
||||
entityPositionSetLocalPosition(&mgr, entity, posComp, startPos);
|
||||
|
||||
const float_t dt = 1.0f / 60.0f;
|
||||
for(int32_t i = 0; i < 300; i++) {
|
||||
physicsWorldStep(&world, &mgr, dt);
|
||||
}
|
||||
|
||||
vec3 pos;
|
||||
entityPositionGetWorldPosition(&mgr, entity, posComp, pos);
|
||||
// Resting height: floor top (0.5) + body half-extent (0.5).
|
||||
assert_float_equal(pos[1], 1.0f, 0.01f);
|
||||
assert_true(entityPhysicsIsOnGround(&mgr, entity, physComp));
|
||||
|
||||
vec3 velocity;
|
||||
entityPhysicsGetVelocity(&mgr, entity, physComp, velocity);
|
||||
assert_float_equal(velocity[1], 0.0f, 0.01f);
|
||||
|
||||
entityManagerDispose(&mgr);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_physicsWorldDynamicVsDynamicSeparates(void **state) {
|
||||
entitymanager_t mgr;
|
||||
entityManagerInit(&mgr);
|
||||
physicsworld_t world;
|
||||
physicsWorldInit(&world);
|
||||
|
||||
entityid_t entityA = entityManagerAdd(&mgr);
|
||||
componentid_t posA = entityAddComponent(
|
||||
&mgr, entityA, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
componentid_t physA = entityAddComponent(
|
||||
&mgr, entityA, COMPONENT_TYPE_PHYSICS
|
||||
);
|
||||
vec3 posAStart = { 0.0f, 0.0f, 0.3f };
|
||||
entityPositionSetLocalPosition(&mgr, entityA, posA, posAStart);
|
||||
entityPhysicsGet(&mgr, entityA, physA)->gravityScale = 0.0f;
|
||||
|
||||
entityid_t entityB = entityManagerAdd(&mgr);
|
||||
componentid_t posB = entityAddComponent(
|
||||
&mgr, entityB, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
componentid_t physB = entityAddComponent(
|
||||
&mgr, entityB, COMPONENT_TYPE_PHYSICS
|
||||
);
|
||||
vec3 posBStart = { 0.0f, 0.0f, -0.3f };
|
||||
entityPositionSetLocalPosition(&mgr, entityB, posB, posBStart);
|
||||
entityPhysicsGet(&mgr, entityB, physB)->gravityScale = 0.0f;
|
||||
|
||||
// Both default to 1x1x1 cubes (half-extent 0.5), overlapping by 0.4 on Z.
|
||||
physicsWorldStep(&world, &mgr, 1.0f / 60.0f);
|
||||
|
||||
vec3 finalA, finalB;
|
||||
entityPositionGetWorldPosition(&mgr, entityA, posA, finalA);
|
||||
entityPositionGetWorldPosition(&mgr, entityB, posB, finalB);
|
||||
|
||||
assert_float_equal(finalA[2], 0.5f, 0.0001f);
|
||||
assert_float_equal(finalB[2], -0.5f, 0.0001f);
|
||||
// Fully separated: touching exactly, no remaining overlap.
|
||||
assert_float_equal(finalA[2] - finalB[2], 1.0f, 0.0001f);
|
||||
|
||||
entityManagerDispose(&mgr);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_physicsWorldStepNoPhysicsEntitiesIsNoop(void **state) {
|
||||
entitymanager_t mgr;
|
||||
entityManagerInit(&mgr);
|
||||
physicsworld_t world;
|
||||
physicsWorldInit(&world);
|
||||
|
||||
entityManagerAdd(&mgr);
|
||||
physicsWorldStep(&world, &mgr, 1.0f / 60.0f);
|
||||
|
||||
entityManagerDispose(&mgr);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_entityPhysicsGettersAndSetters),
|
||||
cmocka_unit_test(test_physicsWorldGravityIntegratesDynamicBody),
|
||||
cmocka_unit_test(test_physicsWorldRestsOnStaticFloor),
|
||||
cmocka_unit_test(test_physicsWorldDynamicVsDynamicSeparates),
|
||||
cmocka_unit_test(test_physicsWorldStepNoPhysicsEntitiesIsNoop),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
Reference in New Issue
Block a user