Box3D-0.1.0.0: README.md
# Box3D
Low-level Haskell bindings to [Box3D](https://box2d.org/documentation3d/), Erin
Catto's 3D rigid-body physics engine.
The engine's C sources (MIT licensed) are bundled and compiled with the package.
You need a C compiler, but no system library.
This package builds commit `2c94ad4` of
[dpwiz/box3d](https://github.com/dpwiz/box3d), which is upstream `v0.1.0` plus 24
commits and reports engine version 0.2.0. Box3D is a young engine, so expect its
API to keep moving between releases.
The bindings stay close to the C API: `b3World_Step` is `Box3D.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](https://gitlab.com/dpwiz/box-nd).
## Example
```haskell
{-# LANGUAGE PatternSynonyms #-}
module Main (main) where
import Control.Monad (replicateM_)
import Box3D.Body (BodyDef (..), defaultBodyDef, pattern DynamicBody)
import Box3D.Body qualified as Body
import Box3D.Geometry (Sphere (..))
import Box3D.MathTypes (Pos (..), Vec3 (..), vec3Zero)
import Box3D.Shape (defaultShapeDef)
import Box3D.Shape qualified as Shape
import Box3D.World (WorldDef (..), defaultWorldDef)
import Box3D.World qualified as World
main :: IO ()
main = do
worldDef <- defaultWorldDef
world <- World.create worldDef{gravity = Vec3 0 (-10) 0}
bodyDef <- defaultBodyDef
ball <- Body.create world bodyDef{type_ = DynamicBody, position = Pos 0 10 0}
shapeDef <- defaultShapeDef
_ <- Shape.createSphere ball shapeDef (Sphere vec3Zero 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`, `Contact`, `Events` |
| Joints | `Joint`, `DistanceJoint`, `FilterJoint`, `MotorJoint`, `ParallelJoint`, `PrismaticJoint`, `RevoluteJoint`, `SphericalJoint`, `WeldJoint`, `WheelJoint` |
| Geometry | `Geometry`, `Hull`, `Mesh`, `HeightField`, `Compound`, `Authoring` |
| Shape builders | `BoxMesh`, `Cone`, `Cylinder`, `Grid`, `GridMesh`, `HollowBoxMesh`, `PlatformMesh`, `Rock`, `TorusMesh`, `Wave`, `WaveMesh` |
| Queries | `Query`, `Collision`, `DynamicTree`, `Mover` |
| Callbacks and drawing | `Callbacks`, `DebugDraw`, `DebugShape` |
| Recording | `Recording`, `RecPlayer`, `Player` |
| Foundation | `Id`, `MathTypes`, `Math`, `MathFunctions`, `Base`, `UserData`, `Extra`, `Tags` |
`Box3D.Authoring` builds meshes, height fields and compounds from Haskell vectors.
`Box3D.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 `Vec3`. `MathTypes.toPos`, `toVec3`, `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 `Box3D.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`. `withDebugShapes` callbacks are trapped too: call
`Callbacks.rethrowDebugShapes` after drawing, or the first exception only
surfaces when `withDebugShapes` returns. An exception escaping any other
callback stops the process, because it cannot unwind through C frames.
- **Events are copied.** `Box3D.Events` copies each event buffer into a vector
that is safe to keep. The raw buffers from `Box3D.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.setAssertFcn` ever fire. Turn it
on while developing, for example with `stack build --flag Box3D: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 Box3D` 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.