# box2d

> Simulate rigid bodies falling, colliding and coming to rest with Box2D, and read their positions back as numbers.

This is the Markdown version of https://osl-lang.org/packages/box2d. Every page of the OSL docs has one: add .md to its path. https://osl-lang.org/llms.txt lists them all.

```osl
import "std:box2d"
```

`std:box2d` is a 2D physics engine. You build a world, put bodies in it, give each body a shape, and call `advance` once per frame. The engine moves the bodies, resolves collisions and tells you where everything ended up. Use it for games, toys and anything where things should stack, bounce and slide.

The engine draws nothing. It works in plain numbers, so it runs, and can be tested, without a window. Pair it with [window](/packages/window.md) or [raylib](/packages/raylib.md) to show the bodies.

```osl
import "std:box2d"
```

## A world and a body

`box2d.new()` makes an empty world. By default gravity is `{x: 0, y: 9.8}`, and y points down, like a screen. `createBody` adds a body and `addCircle` gives it a shape:

```osl
import "std:box2d"

auto world = box2d.new()
log world.gravity()

auto ball = world.createBody({type: "dynamic", position: {x: 0, y: 0}})
ball.addCircle(0.5).unwrap()

log world.bodyCount()
log ball.bodyType()
log ball.position()
```

Output:

```text
{"x":0,"y":9.8}
1
dynamic
{"x":0,"y":0}
```

Positions are in world units, not pixels. Pick a scale (say one unit is one metre, or 50 pixels) and convert when you draw. Shape functions return a `result`, so call `.unwrap()` or handle the failure.

## Body types

A body is one of three types:

- `"static"` (the default) never moves: floors, walls, platforms.
- `"dynamic"` is pushed around by gravity, forces and collisions.
- `"kinematic"` moves at the velocity you give it and ignores forces: moving platforms.

Shapes are `addBox(width, height)`, `addCircle(radius)` and `addPolygon(points)`, a convex polygon of 3 to 8 points. A body with no shape has no mass and collides with nothing.

## Advancing time

`world.advance(ms)` runs the simulation forward by that many milliseconds. Call it once per frame with the frame's duration. Here a ball falls onto a floor, 60 frames a second for two seconds:

```osl
import "std:box2d"

auto world = box2d.new()
auto floor = world.createBody({position: {x: 0, y: 10}})
floor.addBox(40, 1).unwrap()

auto ball = world.createBody({type: "dynamic"})
ball.addCircle(0.5, {restitution: 0.3}).unwrap()

for frame in 1 to 120 (
  world.advance(1000 / 60).unwrap()
  if frame % 30 == 0 (
    log "frame " ++ frame ++ ": y = " ++ round(ball.position().y, 2)
  )
)
```

Output:

```text
frame 30: y = 1.27
frame 60: y = 4.98
frame 90: y = 8.51
frame 120: y = 8.53
```

The floor is a box 1 unit tall centred on y = 10, so its top is at 9.5, and the ball touches it when its centre is at about 9. It lands a little after frame 80, bounces up a short way (restitution 0.3) and settles back. Use a fixed step like this, not the real elapsed time, if you want the same result on every run.

## Shape options

Every shape takes the same options:

- `density` (default 1) decides the body's mass together with the shape's area.
- `friction` (default 0.2) is how much surfaces grip.
- `restitution` is bounciness. 0 does not bounce, 1 keeps all its speed.
- `sensor: true` detects touching without colliding.

```osl
import "std:box2d"

auto world = box2d.new()
auto light = world.createBody({type: "dynamic"})
light.addBox(1, 1, {density: 1}).unwrap()
auto heavy = world.createBody({type: "dynamic"})
heavy.addBox(1, 1, {density: 5}).unwrap()

log light.mass()
log heavy.mass()
```

Output:

```text
1
5
```

## Resting and sleeping

A body that stops moving falls asleep, so the engine spends no time on it. `isAwake()` tells you, and `contactCount()` says how many shape pairs are touching:

```osl
import "std:box2d"

auto world = box2d.new({gravity: box2d.vec(0, 10)})
auto floor = world.createBody({position: {x: 0, y: 10}})
floor.addBox(40, 1).unwrap()
auto crate = world.createBody({type: "dynamic"})
crate.addBox(1, 1, {friction: 0.5}).unwrap()

for _ in 1 to 240 ( world.advance(1000 / 60).unwrap() )
log "y:" round(crate.position().y, 2)
log "awake:" crate.isAwake()
log "contacts:" world.contactCount()
```

Output:

```text
y: 8.99
awake: false
contacts: 1
```

The crate came to rest with its centre at about 9: the floor's top is at 9.5 and the crate is 0.5 from its centre to its bottom. `box2d.vec(x, y)` builds the `{x, y}` vector that gravity, forces and velocities use. Pass `allowSleep: false` to `createBody` to keep a body awake.

## Pushing bodies

`applyImpulse` changes a body's velocity at once (an impulse is mass times velocity). `applyForce` pushes for the next `advance`, so call it every frame for a steady push. Both wake a sleeping body. `setLinearVelocity` sets the speed directly:

```osl
import "std:box2d"

auto world = box2d.new({gravity: box2d.vec(0, 0)})
auto puck = world.createBody({type: "dynamic"})
puck.addCircle(0.5).unwrap()

puck.applyImpulse({x: 3, y: 0}).unwrap()
log puck.linearVelocity()

puck.setLinearVelocity({x: 0, y: -2}).unwrap()
for _ in 1 to 60 ( world.advance(1000 / 60).unwrap() )
log round(puck.position().y, 2)
```

Output:

```text
{"x":3.819718634205488,"y":0}
-2
```

With zero gravity and nothing in the way, the puck keeps its velocity: it moved 2 units up in one second. The puck has a mass of about 0.785 (density 1 times the circle's area), which is why an impulse of 3 gave a velocity of about 3.8.

## Placing bodies

Set where a body starts with `position`, `angle`, `velocity` and `angularVelocity` in `createBody`, and move it later with `setTransform(position, angle)`:

```osl
import "std:box2d"

auto world = box2d.new()
auto crate = world.createBody({
  type: "dynamic",
  position: {x: 4, y: 1},
  angle: 0.5
})
crate.addBox(1, 1).unwrap()
log crate.position() round(crate.angle(), 2)

crate.setTransform({x: 0, y: 0}, 0).unwrap()
log crate.position() crate.angle()
```

Output:

```text
{"x":4,"y":1} 0.5
{"x":0,"y":0} 0
```

## Destroying things

`body.destroy()` removes one body, and `world.destroy()` frees the whole world and every body in it. Using a destroyed body is an error, but you can check first:

```osl
import "std:box2d"

auto world = box2d.new()
auto ball = world.createBody({type: "dynamic"})
ball.addCircle(1).unwrap()

world.destroy().unwrap()
log world.isDestroyed() ball.isDestroyed()

auto added = ball.addBox(1, 1)
log added.isErr()
log added.unwrapErr().kind
```

Output:

```text
true true
true
StateError
```

Bad arguments, like a box with no width, are not results: they throw a `ValueError`.

> **Tip**
>
> A good game loop keeps the physics step fixed (1000 / 60 milliseconds) and draws as fast as the screen allows. If the frame took longer, call `advance` more than once.

**Quiz.** A body made with `createBody({type: "static"})` is hit by a falling ball. What does the static body do?

- [ ] It falls too.
- [x] It stays where it is, and the ball bounces off or rests on it.
- [ ] It is destroyed.

Answer: Static bodies never move: floors, walls and platforms.

### Exercise: One second of free fall

Drop a circle from y = 0 with no floor, advance 60 frames of 1000 / 60 milliseconds, and print its y rounded to a whole number. Gravity is the default, 9.8.

Starting code:

```osl
import "std:box2d"

auto world = box2d.new()
auto ball = world.createBody({type: "dynamic"})
ball.addCircle(0.5).unwrap()

log ball.position().y
```

It should print:

```text
5
```

Hint 1: `for _ in 1 to 60 ( world.advance(1000 / 60).unwrap() )` runs the frames.

Hint 2: `round(value)` rounds to a whole number.

A solution:

```osl
import "std:box2d"

auto world = box2d.new()
auto ball = world.createBody({type: "dynamic"})
ball.addCircle(0.5).unwrap()

for _ in 1 to 60 ( world.advance(1000 / 60).unwrap() )
log round(ball.position().y)
```

## API reference

## API reference

Every member of `std:box2d`, generated from the source of OSL.

### Functions

#### `box2d.new(object? options) *box2d.World`

new creates an empty world. Options: gravity ({x, y}, default {x: 0, y:
9.8}, so with y pointing down things fall down the screen).

#### `box2d.newWorld(any gravity) *box2d.World`

**Deprecated.** Deprecated: use box2d.new({gravity: {x, y}}) instead.

newWorld creates a world with the given gravity; anything that isn't a
vector means {x: 0, y: 9.8}.

#### `box2d.vec(number x, number y) {x: number, y: number}`

vec builds a vector {x, y}.

### `*box2d.World`

#### `world.advance(number ms, object? options) result<unit, error>`

advance runs the simulation forward by ms milliseconds; call it once a
frame with the frame's duration. Options: velocityIterations (default 8)
and positionIterations (default 3), the solver's accuracy per step. Fails
with StateError when the world has been destroyed; throws ValueError for a
negative duration.

#### `world.bodyCount() int`

bodyCount returns how many bodies the world holds. Throws StateError when
the world has been destroyed.

#### `world.contactCount() int`

contactCount returns how many shape pairs are touching or close to it.
Throws StateError when the world has been destroyed.

#### `world.createBody(object? options) *box2d.Body`

createBody adds a body to the world and returns it. Options: type
("static" (default), "dynamic" or "kinematic"), position ({x, y}; or x and
y), angle (radians), velocity ({x, y} units per second), angularVelocity
(radians per second), linearDamping, angularDamping, gravityScale
(default 1), allowSleep (default true), awake (default true),
fixedRotation, bullet and active (default true). A body has no shape until
addBox, addCircle or addPolygon gives it one. Throws StateError when the
world has been destroyed.

#### `world.destroy() result<unit, error>`

destroy frees the world and all its bodies. Destroying it again does
nothing; any other use of the world or its bodies afterwards is a
StateError.

#### `world.destroyBody(*box2d.Body body) boolean`

**Deprecated.** Deprecated: use body.destroy() instead.

destroyBody removes a body from the world and returns false when it was
already removed, belongs to another world, or the world is destroyed.

#### `world.gravity() {x: number, y: number}`

gravity returns the world's gravity {x, y}. Throws StateError when the
world has been destroyed.

#### `world.isDestroyed() boolean`

isDestroyed reports whether destroy has been called on the world.

#### `world.setGravity(any gravity) result<unit, error>`

setGravity changes the world's gravity to a vector {x, y}. Fails with
StateError when the world has been destroyed.

#### `world.step(any dt, any ...iterations) boolean`

**Deprecated.** Deprecated: use world.advance(ms, {velocityIterations, positionIterations}) instead; it takes milliseconds.

step runs the simulation forward by dt seconds, with optional velocity and
position iteration counts, and returns false when the world has been
destroyed.

### `*box2d.Body`

#### `body.addBox(number width, number height, object? options) result<unit, error>`

addBox attaches a width by height rectangle centred on the body. Options:
density (default 1), friction (default 0.2), restitution (bounciness,
default 0) and sensor (detects contacts without colliding). Fails with
StateError when the body or its world has been destroyed; throws
ValueError for a size that isn't positive.

#### `body.addCircle(number radius, object? options) result<unit, error>`

addCircle attaches a circle. Options: center ({x, y} relative to the body,
default {x: 0, y: 0}) and the shape options of addBox. Fails with
StateError when the body or its world has been destroyed; throws
ValueError for a radius that isn't positive.

#### `body.addPolygon(array points, object? options) result<unit, error>`

addPolygon attaches a convex polygon with 3 to 8 points {x, y} relative
to the body. Takes the shape options of addBox. Fails with StateError when
the body or its world has been destroyed; throws ValueError for too few or
too many points.

#### `body.angle() number`

angle returns the body's rotation in radians. Throws StateError when the
body or its world has been destroyed.

#### `body.applyForce(any force) result<unit, error>`

applyForce pushes the body's centre with a force {x, y} for the next
advance, waking it. Fails with StateError when the body or its world has
been destroyed.

#### `body.applyImpulse(any impulse) result<unit, error>`

applyImpulse changes the body's velocity at once by an impulse {x, y}
(mass times velocity), waking it. Fails with StateError when the body or
its world has been destroyed.

#### `body.awake() boolean`

**Deprecated.** Deprecated: use body.isAwake() instead.

awake reports whether the body is awake, or false when it was destroyed.

#### `body.bodyType() string`

bodyType returns "static", "dynamic" or "kinematic". Throws StateError when
the body or its world has been destroyed.

#### `body.destroy()`

destroy removes the body from its world. Destroying it again, or after
the world was destroyed, does nothing.

#### `body.isAwake() boolean`

isAwake reports whether the body is being simulated rather than resting
asleep. A destroyed body is not awake.

#### `body.isDestroyed() boolean`

isDestroyed reports whether the body, or the world it belongs to, has been
destroyed.

#### `body.linearVelocity() {x: number, y: number}`

linearVelocity returns the body's velocity {x, y} in units per second.
Throws StateError when the body or its world has been destroyed.

#### `body.mass() number`

mass returns the body's mass, which its shapes' density and area decide.
Throws StateError when the body or its world has been destroyed.

#### `body.position() {x: number, y: number}`

position returns the body's position {x, y} in world units. Throws
StateError when the body or its world has been destroyed.

#### `body.setLinearVelocity(any velocity) result<unit, error>`

setLinearVelocity sets the body's velocity {x, y} in units per second.
Fails with StateError when the body or its world has been destroyed.

#### `body.setTransform(any position, number angle) result<unit, error>`

setTransform moves the body to position {x, y} with rotation angle in
radians. Fails with StateError when the body or its world has been
destroyed.
