Files
dusk/.claude/physics.md
T
2026-06-16 10:15:59 -05:00

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)

  1. Integrate dynamics -- apply gravity scaled by gravityScale, advance velocity, update position.
  2. Dynamic vs static/kinematic -- resolve penetration and cancel the normal velocity component.
  3. Dynamic vs dynamic -- split penetration 50/50; exchange relative normal velocity.
  4. 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.