Box2D
Low-level Haskell bindings to Box2D, Erin Catto's 2D
rigid-body physics engine.
The engine's C sources (v3.2.0, MIT licensed) are bundled and compiled with the
package. You need a C compiler, but no system library.
The bindings stay close to the C API: b2World_Step is Box2D.World.step, and
each wrapper's documentation names the function it binds. Most modules are
generated from the engine headers by boxnd-gen, in the
source repository.
Example
{-# LANGUAGE PatternSynonyms #-}
module Main (main) where
import Control.Monad (replicateM_)
import Box2D.Body (BodyDef (..), defaultBodyDef, pattern DynamicBody)
import Box2D.Body qualified as Body
import Box2D.Geometry (Circle (..))
import Box2D.MathTypes (Pos (..), Vec2 (..), vec2Zero)
import Box2D.Shape (defaultShapeDef)
import Box2D.Shape qualified as Shape
import Box2D.World (WorldDef (..), defaultWorldDef)
import Box2D.World qualified as World
main :: IO ()
main = do
worldDef <- defaultWorldDef
world <- World.create worldDef{gravity = Vec2 0 (-10)}
bodyDef <- defaultBodyDef
ball <- Body.create world bodyDef{type_ = DynamicBody, position = Pos 0 10}
shapeDef <- defaultShapeDef
_ <- Shape.createCircle ball shapeDef (Circle vec2Zero 0.5)
replicateM_ 60 (World.step world (1 / 60) 4)
Body.getPosition ball >>= print
World.destroy world
Modules
The modules are designed for qualified import.
| Area |
Modules |
| Simulation |
World, Body, Shape, Chain, Contact, Events |
| Joints |
Joint, DistanceJoint, FilterJoint, MotorJoint, MoverJoint, PogoJoint, PrismaticJoint, RevoluteJoint, WeldJoint, WheelJoint |
| Geometry and queries |
Geometry, Collision, Distance, DynamicTree, Mover |
| Callbacks and drawing |
Callbacks, DebugDraw |
| Recording |
Recording, Replay |
| Foundation |
Id, MathTypes, MathFunctions, Base, UserData, Extra, Tags |
Box2D.Internal and its submodule are exposed, but may change in any release.
Things to know
- Start from the defaults. Create definitions with
default*Def and update
the fields you need, rather than building them from scratch. They carry an
initialization marker, which the engine checks when assertions is on.
- World positions are
Pos. The engine takes and returns Pos for places
in the world and WorldTransform for a full pose, while directions, velocities
and shape geometry stay Vec2. MathTypes.toPos, toVec2, subPos and
offsetPos convert between them.
- Enums are open. They are newtypes over
CInt with pattern synonyms, and the
engine can hand back a value with no name here, so include a wildcard when you
case on one.
- Worlds are not thread-safe. Create, step and destroy them from one thread at
a time, and never create or destroy two worlds at once: the world table is
global.
Callbacks.withThreadPoolTaskSystem runs the engine's internal work in
parallel.
- Callback lifetimes. The
with* functions in Box2D.Callbacks free their
callback when the action ends, so anything holding it, a world included, must
be finished by then. Callbacks may run on the engine's worker threads.
- Exceptions in callbacks.
Draw callbacks are trapped and rethrown by
Callbacks.draw. An exception escaping any other callback stops the process,
because it cannot unwind through C frames.
- Events are copied.
Box2D.Events copies each event buffer into a vector
that is safe to keep. The raw buffers from Box2D.World are only valid until
the next step.
Build flags
assertions (off by default) keeps the engine's internal assertions. They catch
invalid arguments and corrupted state, stopping the process with a trap (SIGILL
on x86-64), and only with this flag does Base.setAssertCallback ever fire. Turn
it on while developing, for example with stack build --flag Box2D:assertions.
double-precision (off by default) builds the engine's large world mode, which
stores world positions as doubles so they stay accurate far from the origin.
Pos then holds Double instead of Float, and everything else stays Float.
Code that builds positions from literals and the MathTypes helpers compiles
either way. Every package in a build shares this setting.
Run stack clean Box2D before changing either flag on an already-built package.
A flag change does not rebuild the C objects or preprocessed modules, so without
a clean the old setting silently stays in effect.
Limits
- x86-64 and AArch64 only: ids cross the FFI as machine words, which relies on
those calling conventions.
- Built and tested on Linux x86-64 with GHC 9.10.3.