type Id = Ball | Floor
bodies : List ( Id, Body )
bodies =
[ ( Ball
, Physics.sphere
(Sphere3d.atOrigin (Length.meters 0.5))
Material.rubber
|> Physics.moveTo (Point3d.meters 0 0 5)
)
, ( Floor, Physics.plane Plane3d.xy Material.wood )
]
step model =
let
( newBodies, newContacts ) =
Physics.simulate
{ onEarth | contacts = model.contacts }
model.bodies
in
{ model | bodies = newBodies, contacts = newContacts }- Pure —
simulateis a function, not a stateful world; deterministic, replayable, time-travel friendly. - Your list is the world — bodies live in
List ( id, Body ); add with(::), remove withList.filter. - Type-safe coordinates and units — built on elm-geometry and elm-units; phantom types keep
WorldCoordinatesandBodyCoordinatesapart, and forces, masses, velocities, and durations all carry units. - Shapes — block, sphere, cylinder, capsule, and arbitrary convex polyhedra.
- Compound bodies — combine shapes with
Shape.plus/minus/sumand per-shape materials; mass, center of mass, and the full inertia tensor are derived for you. - Declarative constraints and collisions — pass
constrainandcollideas functions tosimulate; the engine asks per body-pair what applies, so there's no constraint registry and no filter-group API. - Sequential-impulse solver — a Projected Gauss-Seidel velocity solver with Spook soft-constraint stabilization for resting contacts; warm-start it by feeding last frame’s contacts back into
simulate. - Simulation islands — bodies connected by contacts and constraints are grouped into islands and solved independently, so unrelated parts of a large scene don’t slow each other down.
Inspired by Cannon.js and Bullet — this project is an experiment in what a purely functional 3D physics engine can look like.