4.2 KiB
Physics System
Source: src/dusk/physics/, entity component at
src/dusk/entity/component/physics/entityphysics.h/.c
Overview
Dusk uses a lightweight, custom 3D physics simulation with no external
library dependency. It is integrated with the ECS: only entities that
have both a COMPONENT_TYPE_PHYSICS and a COMPONENT_TYPE_POSITION
component participate in the simulation.
Shapes
typedef enum {
PHYSICS_SHAPE_CUBE, // Axis-aligned bounding box (AABB)
PHYSICS_SHAPE_SPHERE,
PHYSICS_SHAPE_CAPSULE, // Y-axis aligned; radius + halfHeight
PHYSICS_SHAPE_PLANE, // Infinite plane; normal + distance
} physicshapetype_t;
All shape pairs are supported by the collision dispatch
(physicsTestShapeVsShape). See physicstest.h for the individual
test functions.
Body types
typedef enum {
PHYSICS_BODY_STATIC, // Never moves; immovable collision surface
PHYSICS_BODY_DYNAMIC, // Driven by gravity, velocity, collisions
PHYSICS_BODY_KINEMATIC, // Moved programmatically; collides but not
// driven by the simulation (e.g. player)
} physicsbodytype_t;
World and gravity
extern physicsworld_t PHYSICS_WORLD;
// PHYSICS_WORLD.gravity -- default {0, -9.81, 0}
The simulation step is driven by physicsManagerUpdate(), which is
called each fixed-timestep game loop tick. It skips dynamic-timestep
sub-steps (DUSK_TIME_DYNAMIC).
Simulation phases (each step)
- Integrate dynamics -- apply gravity scaled by
gravityScale, advance velocity, update position. - Dynamic vs static/kinematic -- resolve penetration and cancel the normal velocity component.
- Dynamic vs dynamic -- split penetration 50/50; exchange relative normal velocity.
- Rebuild transforms -- call
entityPositionRebuild()for all affected dynamic bodies.
PHYSICS_GROUND_THRESHOLD = 0.707f -- a collision normal with a Y
component above this value sets onGround = true on the dynamic body.
Entity component (entityphysics_t)
typedef struct {
physicsbodytype_t type;
physicsshape_t shape;
vec3 velocity;
float_t gravityScale; // default 1.0
bool_t onGround; // set by the solver each step
} entityphysics_t;
Default on init: DYNAMIC body, 0.5m half-extents AABB cube,
gravityScale = 1.0f.
Component API
entityphysics_t *entityPhysicsGet(entityid_t, componentid_t);
void entityPhysicsSetShape(entityid_t, componentid_t, physicsshape_t);
physicsshape_t entityPhysicsGetShape(entityid_t, componentid_t);
void entityPhysicsSetVelocity(entityid_t, componentid_t, vec3);
void entityPhysicsGetVelocity(entityid_t, componentid_t, vec3 dest);
void entityPhysicsApplyImpulse(entityid_t, componentid_t, vec3);
// No-op on STATIC bodies.
bool_t entityPhysicsIsOnGround(entityid_t, componentid_t);
void entityPhysicsSetBodyType(entityid_t, componentid_t, physicsbodytype_t);
physicsbodytype_t entityPhysicsGetBodyType(entityid_t, componentid_t);
Collision detection primitives (physicstest.h)
Each function returns true if overlapping and writes the push-out
normal (pointing from B toward A) and penetration depth.
| Function | Shapes |
|---|---|
physicsTestAabbVsAabb |
CUBE vs CUBE |
physicsTestSphereVsSphere |
SPHERE vs SPHERE |
physicsTestSphereVsAabb |
SPHERE vs CUBE |
physicsTestSphereVsPlane |
SPHERE vs PLANE |
physicsTestAabbVsPlane |
CUBE vs PLANE |
physicsTestCapsuleVsSphere |
CAPSULE vs SPHERE |
physicsTestCapsuleVsAabb |
CAPSULE vs CUBE |
physicsTestCapsuleVsPlane |
CAPSULE vs PLANE |
physicsTestCapsuleVsCapsule |
CAPSULE vs CAPSULE |
physicsTestShapeVsShape |
Any pair via dispatch |
Capsules are always Y-axis aligned. Planes are infinite (not half-spaces).
Limitations and known gaps
- No rotation simulation -- bodies do not rotate from collisions.
- No friction or damping model yet.
- No sleeping / deactivation for resting bodies.
- No broad-phase culling: the solver is O(n^2) per phase. This is acceptable up to the ECS entity limit (64 entities) but must be revisited if the entity count grows.
- Capsule vs plane uses the bottom/top hemisphere centers as a degenerate approximation -- accurate for large planes but not for thin surfaces.