# Physics lab

![Physics lab](physics-lab.png)

Source: [`examples/physics-lab`](https://github.com/matjam/bunyip/tree/main/examples/physics-lab) (main.go)

This example puts the 3D physics package through most of what it can do
in one scene. Forty-eight bodies with five different shapes tumble onto
a heightfield mesh collider, a chain of seven hinge joints hangs from a
point in the sky with a heavy ball on the end, a paddle wheel turns
under a hinge motor, a ragdoll drops in and lands in a heap, and a
character controller walks back and forth over a staircase. Every
collider is drawn as a wire outline over the solid meshes, sleeping
bodies turn grey, and this frame's contacts draw their normals.

The packages are [phys](../pkg/phys.md) for bodies, shapes, joints,
the ragdoll and the character controller, [ecs](../pkg/ecs.md) because
physics components live on entities and the solver is a system,
[gfx](../pkg/gfx.md) for the meshes and the debug lines, and
[ui](../pkg/ui.md) for the readout. The guide is
[Physics](../guides/physics.md).

Run it with:

```bash
go run ./examples/physics-lab -seconds 3 -shot out.png
```

`-ragdoll=false` leaves the ragdoll out. Drag to orbit, the scroll wheel
zooms, R drops the bodies again, Escape quits. The backquote key opens
the debug console and F4 its panels, which is where the world can be
inspected, paused and poked at while it runs.

## The terrain

`terrainHeight` is the ground's shape, and `heightfield` samples it into
three things at once: render vertices with normals and texture
coordinates, the plain positions the physics mesh shape needs, and the
shared index buffer. Building both from one loop is what guarantees that
what is drawn and what is collided against are the same surface.

The normals are computed from finite differences: the height a small
distance either side in each axis gives the slope, and `lin.V3(nx, 2*d,
nz).Norm()` turns the two slopes into a normal.

```go
// terrainSize is the half extent of the heightfield; terrainCells its
// resolution along each side.
const (
	terrainSize  = 20
	terrainCells = 40
)

// terrainHeight is the rolling ground the bodies land on.
func terrainHeight(x, z float32) float32 {
	return 1.2*float32(math.Sin(float64(x)*0.35)*math.Cos(float64(z)*0.3)) + 0.4*float32(math.Sin(float64(x+z)*0.9))
}

// heightfield builds the terrain as render vertices plus the positions
// and indices the physics mesh shares.
func heightfield() ([]gfx.Vertex, []lin.Vec3, []uint32) {
	n := terrainCells + 1
	verts := make([]gfx.Vertex, 0, n*n)
	pts := make([]lin.Vec3, 0, n*n)
	for j := range n {
		for i := range n {
			x := -terrainSize + 2*terrainSize*float32(i)/terrainCells
			z := -terrainSize + 2*terrainSize*float32(j)/terrainCells
			y := terrainHeight(x, z)
			const d = 0.1
			nx := terrainHeight(x-d, z) - terrainHeight(x+d, z)
			nz := terrainHeight(x, z-d) - terrainHeight(x, z+d)
			normal := lin.V3(nx, 2*d, nz).Norm()
			p := lin.V3(x, y, z)
			verts = append(verts, gfx.Vertex{Pos: p, Normal: normal, UV: lin.V2(float32(i)/4, float32(j)/4)})
			pts = append(pts, p)
		}
	}
	var idx []uint32
	for j := range terrainCells {
		for i := range terrainCells {
			a := uint32(j*n + i)
			b := a + uint32(n)
			idx = append(idx, a, b, a+1, a+1, b, b+1)
		}
	}
	return verts, pts, idx
}
```

## Convex hulls and the marker components

A `phys.ConvexHull` is a set of points; the collision code builds what it
needs from them, so a hull is described rather than constructed. The
octahedron is six points on the axes and the wedge is six points making
a doorstop. Convex pairs are resolved with GJK and EPA, while boxes keep
a dedicated path.

The three marker types are empty structs used to tell groups of entities
apart in `Draw`. `debris` carries a colour, which is the general case: a
marker component can hold whatever the game needs.

```go
// octahedron is a convex hull with six points.
func octahedron(r float32) phys.ConvexHull {
	return phys.ConvexHull{Points: []lin.Vec3{{X: r}, {X: -r}, {Y: r}, {Y: -r}, {Z: r}, {Z: -r}}}
}

// wedge is a convex hull shaped like a doorstop.
func wedge(w, h, d float32) phys.ConvexHull {
	return phys.ConvexHull{Points: []lin.Vec3{
		{X: -w, Y: -h, Z: -d}, {X: w, Y: -h, Z: -d}, {X: w, Y: -h, Z: d}, {X: -w, Y: -h, Z: d},
		{X: -w, Y: h, Z: -d}, {X: -w, Y: h, Z: d},
	}}
}

// debris marks a body dropped by the lab, with its colour.
type debris struct{ Color gfx.Color }

// link marks one link of the hinge chain.
type link struct{}

// paddle marks the motorised wheel.
type paddle struct{}
```

## The game type

Two cached queries: everything with a transform, a body and a collider,
and everything marked as debris. The rest is the meshes used to draw the
shapes, the ragdoll, the character controller and its state, this
frame's contacts, and the camera.

```go
type game struct {
	seconds float64
	shot    string
	ragdoll bool

	font      *gfx.Font
	ui        *ui.Context
	world     *ecs.World
	bodies    *ecs.Query3[gfx.Transform, phys.Body3, phys.Collider3]
	debris    *ecs.Query2[gfx.Transform, debris]
	terrain   *gfx.Mesh
	cube      *gfx.Mesh
	sphere    *gfx.Mesh
	cylinder  *gfx.Mesh
	doll      *phys.Ragdoll3
	wheel     ecs.Entity
	random    *rng.Rand
	hero      ecs.Entity
	ctrl      phys.CharacterController3
	heroDir   float32
	heroTimer float64
	torque    float32
	contacts  []phys.Collision3
	yaw       float32
	pitch     float32
	dist      float32
	lastX     float32
	lastY     float32
	dragging  bool
	shotDone  bool
}
```

## Init: the static world, the chain and the wheel

`phys.Settings3` is a resource: gravity, the substep count, the solver
iterations and how long a body must be still before it sleeps. Raising
`Substeps` and `Iterations` makes stacks steadier at the cost of time.

A collider with no body is static. The terrain is one such entity with
`phys.NewMeshShape(pts, idx)`, sharing the arrays the render mesh was
built from. The staircase is a slab plus five boxes of increasing half
height, which is a simple way to get steps whose tops rise evenly.

The chain is seven boxes, each joined to the previous with a
`phys.HingeJoint3`. A joint is itself an entity with a joint component,
naming the two bodies, an anchor on each in that body's local space, and
an axis on each. The first link's `A` is `ecs.None`, which means the
world, and its `AnchorA` is a world position. After the first, the
anchor moves to the end of the previous link. Damping is set on the
bodies so the chain settles rather than swinging forever.

The links use `phys.Layers{Layer: 2, Mask: 1}`, so they collide with
layer 1 (the terrain and the debris) but not with each other, which
keeps a chain of touching boxes from fighting itself.

The paddle wheel is one body with a `phys.Compound3` shape of two
crossed boxes, on a hinge whose `A` is the world and which has
`MotorSpeed` and `MaxMotorTorque` set, so the joint drives it.

The last four calls wire the scene into the
[debug console](../guides/console.md), which `main` turns on with
`Config.Console`. `Console.Attach("lab", g.world)` hands it the world:
from then on the console's entity panel lists the bodies with their
components, and its physics panel shows the solver's settings, the
contact, body and joint counts, and a pause. `Console.Float` binds
`wheel.torque` to the field the next section reads, so the motor's
strength can be changed while the wheel is turning, and
`Console.Register` adds a `drop` command that does what R does. Every
console method is safe on a nil console, so none of this needs guarding
when `Config.Console` is off.

```go
func (g *game) Init(ctx *engine.Context) error {
	var err error
	if g.font, err = ctx.Gfx.NewFont(goregular.TTF, 15, gfx.FontOptions{}); err != nil {
		return err
	}
	g.ui = ui.New(ctx.Gfx, ui.DarkTheme(g.font))
	cv, ci := gfx.CubeMesh()
	if g.cube, err = ctx.Gfx.NewMesh(cv, ci); err != nil {
		return err
	}
	sv, si := gfx.SphereMesh(12, 18)
	if g.sphere, err = ctx.Gfx.NewMesh(sv, si); err != nil {
		return err
	}
	yv, yi := gfx.CylinderMesh(18)
	if g.cylinder, err = ctx.Gfx.NewMesh(yv, yi); err != nil {
		return err
	}
	tv, pts, idx := heightfield()
	if g.terrain, err = ctx.Gfx.NewMesh(tv, idx); err != nil {
		return err
	}
	g.random = rng.New(5)
	g.yaw, g.pitch, g.dist = 0.6, 0.45, 34
	g.heroDir = 1

	w := ecs.NewWorld()
	g.world = w
	g.bodies = w.Query3[gfx.Transform, phys.Body3, phys.Collider3]()
	g.debris = w.Query2[gfx.Transform, debris]()
	w.SetResource(phys.Settings3{Gravity: lin.V3(0, -9.8, 0), Substeps: 4, Iterations: 8, SleepTime: 0.5})
	// The terrain is a static mesh collider sharing the render mesh's data.
	w.SpawnWith(gfx.Transform{}, phys.Collider3{Shape: phys.NewMeshShape(pts, idx)})
	// A staircase for the character, on a flat slab so the steps line up.
	w.SpawnWith(gfx.At(8, 1.5, -8), phys.Collider3{Shape: phys.Box3{Half: lin.V3(8, 0.5, 3)}})
	for i := range 5 {
		w.SpawnWith(gfx.At(6+float32(i)*1.2, 2+0.15+0.3*float32(i), -8), phys.Collider3{Shape: phys.Box3{Half: lin.V3(0.6, 0.15+0.3*float32(i), 3)}})
	}
	g.ctrl = phys.CharacterController3{Radius: 0.35, HalfHeight: 0.45, StepHeight: 0.45, MaxSlope: 50}
	g.hero = w.SpawnWith(gfx.At(1, 3.5, -8))
	// A chain of hinged links hanging from a point in the sky with a ball on the end.
	prev := ecs.None
	anchor := lin.V3(-9, 12, 4)
	for i := range 7 {
		body := phys.Dynamic3(1)
		body.LinearDamping, body.AngularDamping = 0.5, 0.5
		e := w.SpawnWith(gfx.At(-9+0.5+float32(i), 12, 4), body,
			phys.Collider3{Shape: phys.Box3{Half: lin.V3(0.5, 0.12, 0.12)}, Layers: phys.Layers{Layer: 2, Mask: 1}}, link{})
		w.SpawnWith(phys.HingeJoint3{A: prev, AnchorA: anchor, B: e, AnchorB: lin.V3(-0.5, 0, 0), AxisA: lin.V3(0, 0, 1), AxisB: lin.V3(0, 0, 1)})
		anchor = lin.V3(0.5, 0, 0)
		prev = e
	}
	ball := phys.Dynamic3(3)
	ball.LinearDamping = 0.2
	b := w.SpawnWith(gfx.At(-1, 12, 4), ball, phys.Collider3{Shape: phys.Sphere{Radius: 0.6}, Layers: phys.Layers{Layer: 2, Mask: 1}}, link{})
	w.SpawnWith(phys.HingeJoint3{A: prev, AnchorA: lin.V3(0.5, 0, 0), B: b, AnchorB: lin.V3(-0.6, 0, 0), AxisA: lin.V3(0, 0, 1), AxisB: lin.V3(0, 0, 1)})
	// A paddle wheel on a world hinge, turned by the hinge's motor; it
	// bats the debris that lands in it.
	wheelAt := lin.V3(-2, 2.4, 8)
	wheel := phys.Dynamic3(8)
	wheel.AngularDamping = 0.2
	g.wheel = w.SpawnWith(gfx.Transform{Position: wheelAt}, wheel, phys.Collider3{Shape: phys.Compound3{Parts: []phys.Part3{
		{Shape: phys.Box3{Half: lin.V3(1.6, 0.12, 0.5)}},
		{Shape: phys.Box3{Half: lin.V3(0.12, 1.6, 0.5)}},
	}}}, paddle{})
	w.SpawnWith(phys.HingeJoint3{A: ecs.None, AnchorA: wheelAt, B: g.wheel, AxisA: lin.V3(0, 0, 1), AxisB: lin.V3(0, 0, 1), MotorSpeed: 1.5, MaxMotorTorque: 400})
	w.AddSystem("physics", phys.System3)
	g.drop()
	// The console gets the world, so its entity and physics panels can
	// list the bodies, pause the simulation and edit the solver
	// settings, plus a variable and a command of the lab's own.
	g.torque = 400
	ctx.Console.Attach("lab", g.world)
	ctx.Console.Float("wheel.torque", &g.torque, "the paddle wheel's motor torque")
	ctx.Console.Register("drop", "drop: throw the debris in again", func([]string) (string, error) {
		g.drop()
		return "dropped", nil
	})
	return nil
}
```

`phys.Dynamic3(mass)` returns a body with sensible defaults for that
mass, and `gfx.At(x, y, z)` is a transform at a position. `phys.System3`
is the solver, registered like any other system and run by
`world.Update`.

## Dropping the bodies

`drop` clears whatever is there and respawns. The debris query is walked
to despawn the previous batch, which is safe: a query walks its rows
last to first, so the entity being visited may be despawned in the
callback. The ragdoll has its own `Despawn`, since it owns several
entities and the joints between them.

`phys.NewRagdoll3` builds a ragdoll from a `RagdollSpec` of a position,
a rotation and a height. Tipping it 0.7 radians about one axis and 0.4
about another means it lands in a heap rather than on its feet.

The forty-eight bodies cycle through five shapes: a capsule, the
octahedron hull, the wedge hull, a box and a sphere. The spheres get
`CCD = true`, continuous collision detection, because a small fast
sphere is the shape most likely to pass through the terrain in one step.
Each gets a random spin through `AngVel` and a random orientation.

`step` is the only place the world is advanced, and it exists so there
is one. Before each step it copies `g.torque` into the wheel's hinge,
which is what makes the console variable take effect on the running
simulation: a joint is a component like any other, so changing it is a
matter of finding it and writing the field. `Update` calls `step` on the
normal path and again on the path where the console is open, so the
scene keeps simulating while a command is being typed.

```go
// drop respawns the tumbling bodies above the terrain, and a ragdoll
// twice life size in front of the camera, tipped over so it lands in a
// heap.
func (g *game) drop() {
	w := g.world
	g.debris.Each(func(e ecs.Entity, _ *gfx.Transform, _ *debris) { w.Despawn(e) })
	if g.doll != nil {
		g.doll.Despawn(w)
		g.doll = nil
	}
	if g.ragdoll {
		tilt := lin.AxisAngle(lin.V3(1, 0, 0), 0.7).Mul(lin.AxisAngle(lin.V3(0, 0, 1), 0.4))
		g.doll = phys.NewRagdoll3(w, phys.RagdollSpec{Position: lin.V3(5, 6, 10), Rotation: tilt, Height: 3.6})
	}
	palette := []gfx.Color{gfx.RGB(240, 140, 30), gfx.RGB(60, 190, 80), gfx.RGB(50, 110, 240), gfx.RGB(240, 100, 180), gfx.RGB(240, 220, 60)}
	for i := range 48 {
		x := g.random.Between(-12, 4)
		z := g.random.Between(-4, 12)
		y := 6 + float32(i%6)*2 + g.random.Between(0, 1)
		body := phys.Dynamic3(1)
		body.Friction, body.Restitution = 0.6, 0.1
		body.AngVel = lin.V3(g.random.Between(-2, 2), g.random.Between(-2, 2), g.random.Between(-2, 2))
		var shape phys.Shape3
		switch i % 5 {
		case 0:
			shape = phys.Capsule{Radius: 0.3, HalfHeight: 0.45}
		case 1:
			shape = octahedron(0.7)
		case 2:
			shape = wedge(0.6, 0.35, 0.5)
		case 3:
			shape = phys.Box3{Half: lin.V3(0.4, 0.4, 0.4)}
		default:
			shape = phys.Sphere{Radius: 0.4}
			body.CCD = true
		}
		w.SpawnWith(
			gfx.Transform{Position: lin.V3(x, y, z), Rotation: lin.AxisAngle(lin.V3(g.random.Float(), g.random.Float(), g.random.Float()).Norm(), g.random.Float()*3)},
			body, phys.Collider3{Shape: shape}, debris{Color: palette[i%len(palette)]},
		)
	}
}

// step advances the simulation, giving the paddle wheel's motor
// whatever torque the console's wheel.torque variable holds.
func (g *game) step(dt float64) {
	g.world.Each(func(_ ecs.Entity, j *phys.HingeJoint3) {
		if j.B == g.wheel {
			j.MaxMotorTorque = g.torque
		}
	})
	g.world.Update(dt)
}

func (g *game) Shutdown(ctx *engine.Context) {
	g.terrain.Destroy()
	g.cube.Destroy()
	g.sphere.Destroy()
	g.cylinder.Destroy()
	g.font.Destroy()
}
```

## Update: the character and the step

The camera drag is guarded by `g.ui.WantsMouse()` so dragging inside the
panel does not orbit.

The character walks with `ctrl.Move(world, entity, velocity, dt)`. The
velocity has a downward component so the controller stays on the ground
and can walk down steps; the controller resolves the slide, the step up
and the ground contact itself, and reports `Grounded` and
`GroundNormal`. A timer flips the direction every five seconds, so the
character paces the staircase without input.

`g.world.Events[phys.Collision3]()` returns this update's contact
events. They are copied into `g.contacts` with `append(g.contacts[:0],
...)`, retaining a separate snapshot for `Draw`. World events remain
available after an update and are cleared at the start of the next one.

```go
func (g *game) Update(ctx *engine.Context) error {
	in := ctx.Input
	if ctx.Console.Open() {
		// The console has the keyboard and the pointer while it is open;
		// the simulation keeps running behind it.
		g.step(ctx.Delta)
		return nil
	}
	if in.KeyPressed(input.KeyEscape) || (g.seconds > 0 && ctx.Time >= g.seconds) {
		ctx.Quit()
	}
	if g.shot != "" && !g.shotDone && (g.seconds == 0 || ctx.Time >= g.seconds/2) {
		ctx.Screenshot(g.shot)
		g.shotDone = true
	}
	if in.KeyPressed(input.KeyR) {
		g.drop()
	}
	x, y := in.Mouse()
	if in.MousePressed(input.MouseLeft) && !g.ui.WantsMouse() {
		g.dragging = true
	}
	if in.MouseReleased(input.MouseLeft) {
		g.dragging = false
	}
	if g.dragging {
		g.yaw += (x - g.lastX) * 0.01
		g.pitch = lin.Clamp(g.pitch+(y-g.lastY)*0.01, 0.05, 1.5)
	}
	g.lastX, g.lastY = x, y
	_, dy := in.Scroll()
	g.dist = lin.Clamp(g.dist-float32(dy)*2, 8, 120)
	// The character walks the staircase one way, then the other, on a timer.
	g.heroTimer += ctx.Delta
	if g.heroTimer > 5 {
		g.heroTimer, g.heroDir = 0, -g.heroDir
	}
	step := ctx.Profile("physics")
	g.ctrl.Move(g.world, g.hero, lin.V3(2.5*g.heroDir, -6, 0), float32(ctx.Delta))
	g.step(ctx.Delta)
	step.End()
	g.contacts = append(g.contacts[:0], g.world.Events[phys.Collision3]()...)
	return nil
}
```

The profile scope around the character move and the world update is what
the panel reads back as milliseconds per frame.

## Outlining a shape

The lab used to carry its own `wireShape` helper, a type switch that
drew each collider as lines. That work now lives in the physics package
as `phys.DrawShape3`, so the example calls it instead of keeping a copy:
a sphere is drawn as three rings, a box as its edges, a capsule as two
spheres and the lines joining them, a hull as an edge between every pair
of its points, and a compound recurses into its parts, composing each
part's offset and rotation with the body's. A mesh shape is skipped,
since a terrain mesh is drawn as a mesh already.

`phys.DrawColliders3` is the whole-world version of the same thing: it
walks every collider in a world and shades each one by whether its body
is static, awake or asleep, then draws the contact normals. The lab does
not use it, because it colours the debris by its own palette and by
whether the body is touching anything this frame, which the next section
covers.

## Draw: solids, wires and the ragdoll

The main walk is over everything with a transform, a body and a
collider. Its colour is orange, or grey when `b.Asleep()` reports the
body has been still long enough to be taken out of the simulation. The
marker components then decide what solid mesh to draw over the wire: a
debris body draws in its own colour, a chain link draws as metal, and
the paddle's compound parts each draw as a box at their own offset.
Every body gets its collider outlined at the end.

A ragdoll is a set of capsule bodies joined together, so each one is
drawn as a cylinder between two spheres, which is what a capsule looks
like.

```go
func (g *game) Draw(ctx *engine.Context) error {
	gr := ctx.Gfx
	w := g.world
	gr.SetCamera(gfx.OrbitCamera(lin.V3(0, 2, 0), g.yaw, g.pitch, g.dist))
	gr.SetLight(gfx.Light{Direction: lin.V3(-0.5, -1, -0.3), Color: gfx.Color{R: 2.4, G: 2.3, B: 2.1, A: 1},
		Sky: gfx.Sky{Zenith: gfx.Color{R: 0.3, G: 0.35, B: 0.5, A: 1}, Ground: gfx.Color{R: 0.15, G: 0.12, B: 0.1, A: 1}}, Shadows: true, ShadowDistance: 60})
	gr.DrawMesh(g.terrain, gfx.Material{BaseColor: gfx.RGB(110, 140, 90), Roughness: 0.9}, lin.Identity())
	// Solid meshes for the debris, chain and stairs; wire outlines over all colliders.
	g.bodies.Each(func(e ecs.Entity, t *gfx.Transform, b *phys.Body3, c *phys.Collider3) {
		col := gfx.RGB(255, 170, 40)
		if b.Asleep() {
			col = gfx.RGB(150, 150, 160)
		}
		if d, ok := w.Get[debris](e); ok {
			mat := gfx.Material{BaseColor: d.Color, Roughness: 0.6}
			switch sh := c.Shape.(type) {
			case phys.Sphere:
				gr.DrawMesh(g.sphere, mat, lin.TRS(t.Position, t.Rotation, lin.V3(sh.Radius, sh.Radius, sh.Radius)))
			case phys.Box3:
				gr.DrawMesh(g.cube, mat, lin.TRS(t.Position, t.Rotation, sh.Half.Mul(2)))
			}
		}
		if _, ok := w.Get[link](e); ok {
			if sh, ok := c.Shape.(phys.Box3); ok {
				gr.DrawMesh(g.cube, gfx.Material{BaseColor: gfx.RGB(200, 200, 210), Metallic: 1, Roughness: 0.3}, lin.TRS(t.Position, t.Rotation, sh.Half.Mul(2)))
			}
			if sh, ok := c.Shape.(phys.Sphere); ok {
				gr.DrawMesh(g.sphere, gfx.Material{BaseColor: gfx.RGB(220, 60, 60), Roughness: 0.4}, lin.TRS(t.Position, t.Rotation, lin.V3(sh.Radius, sh.Radius, sh.Radius)))
			}
		}
		if _, ok := w.Get[paddle](e); ok {
			if sh, ok := c.Shape.(phys.Compound3); ok {
				for _, p := range sh.Parts {
					if box, ok := p.Shape.(phys.Box3); ok {
						gr.DrawMesh(g.cube, gfx.Material{BaseColor: gfx.RGB(180, 120, 60), Roughness: 0.7}, lin.TRS(t.Position.Add(t.Rotation.Rotate(p.Offset)), t.Rotation, box.Half.Mul(2)))
					}
				}
			}
		}
		phys.DrawShape3(gr, c.Shape, *t, col)
	})
	// The ragdoll's capsules as a cylinder with a sphere at each end.
	if g.doll != nil {
		skin := gfx.Material{BaseColor: gfx.RGB(230, 180, 140), Roughness: 0.8}
		for _, e := range g.doll.Entities() {
			t, ok := w.Get[gfx.Transform](e)
			if !ok {
				continue
			}
			c, ok := w.Get[phys.Collider3](e)
			if !ok {
				continue
			}
			if cap, ok := c.Shape.(phys.Capsule); ok {
				rot := t.Rotation
				if rot == (lin.Quat{}) {
					rot = lin.QuatIdentity()
				}
				r := lin.V3(cap.Radius, cap.Radius, cap.Radius)
				up := rot.Rotate(lin.V3(0, cap.HalfHeight, 0))
				gr.DrawMesh(g.sphere, skin, lin.TRS(t.Position.Add(up), rot, r))
				gr.DrawMesh(g.sphere, skin, lin.TRS(t.Position.Sub(up), rot, r))
				if cap.HalfHeight > 0 {
					gr.DrawMesh(g.cylinder, skin, lin.TRS(t.Position, rot, lin.V3(cap.Radius, cap.HalfHeight, cap.Radius)))
				}
			}
		}
	}
```

## The static bodies, the joints, the character and the contacts

`World.Each2` walks entities with a transform and a collider, and skips
anything that also has a body, which leaves the static geometry: the
staircase. The terrain is not drawn here because its shape is a mesh
rather than a box.

`drawJoint` is a closure used for both joint types. It resolves each
anchor into world space through the body's transform, treating
`ecs.None` as a world-space anchor, and draws a line between them with a
small sphere at the second. `World.Each` is then called once per joint
component type.

The character draws as a green capsule, red when it is not on the
ground, with a line along `GroundNormal` from its feet when it is. The
contacts saved in `Update` draw as short red lines along each contact
normal.

```go
	w.Each2(func(e ecs.Entity, t *gfx.Transform, c *phys.Collider3) {
		if w.Has[phys.Body3](e) {
			return
		}
		if sh, ok := c.Shape.(phys.Box3); ok {
			gr.DrawMesh(g.cube, gfx.Material{BaseColor: gfx.RGB(120, 120, 130), Roughness: 0.9}, lin.TRS(t.Position, t.Rotation, sh.Half.Mul(2)))
			phys.DrawShape3(gr, c.Shape, *t, gfx.RGB(90, 200, 255))
		}
	})
	// Joint anchors as short lines between the bodies they join.
	drawJoint := func(ea, eb ecs.Entity, anchorA, anchorB lin.Vec3) {
		var a lin.Vec3
		if ea == ecs.None {
			a = anchorA
		} else if ta, ok := w.Get[gfx.Transform](ea); ok {
			a = ta.Position.Add(ta.Rotation.Rotate(anchorA))
		}
		if tb, ok := w.Get[gfx.Transform](eb); ok {
			b := tb.Position.Add(tb.Rotation.Rotate(anchorB))
			gr.DrawLine3D(a, b, gfx.RGB(255, 255, 255))
			gr.DrawWireSphere(b, 0.08, gfx.RGB(255, 80, 80))
		}
	}
	w.Each(func(e ecs.Entity, j *phys.HingeJoint3) { drawJoint(j.A, j.B, j.AnchorA, j.AnchorB) })
	w.Each(func(e ecs.Entity, j *phys.BallJoint3) { drawJoint(j.A, j.B, j.AnchorA, j.AnchorB) })
	// The character: a green capsule with its ground normal.
	if ht, ok := w.Get[gfx.Transform](g.hero); ok {
		col := gfx.RGB(80, 255, 120)
		if !g.ctrl.Grounded {
			col = gfx.RGB(255, 80, 80)
		}
		phys.DrawShape3(gr, phys.Capsule{Radius: g.ctrl.Radius, HalfHeight: g.ctrl.HalfHeight}, *ht, col)
		if g.ctrl.Grounded {
			foot := ht.Position.Sub(lin.V3(0, g.ctrl.HalfHeight+g.ctrl.Radius, 0))
			gr.DrawLine3D(foot, foot.Add(g.ctrl.GroundNormal), gfx.RGB(255, 255, 255))
		}
	}
	// Contacts from this frame's collision events.
	for _, c := range g.contacts {
		gr.DrawLine3D(c.Point, c.Point.Add(c.Normal.Mul(0.5)), gfx.RGB(255, 60, 60))
	}
	gr.DrawAxes(lin.Identity(), 2)
```

## The panel

The readout is built inside `u.Begin` and `u.Panel`, both closures. It
reads the first profile scope from `ctx.Stats.Scopes`, which is the
physics scope opened in `Update`, counts the sleeping bodies by walking
the query again, and reads the wheel's angular velocity about the hinge
axis so the motor can be seen working. `u.Button` returns true on the
frame a mouse press is released inside it, or it is activated by keyboard.

```go
	u := g.ui
	u.Begin(ctx.Input, func() {
		u.Panel("physics lab", ui.Rect{X: 12, Y: ctx.Height - 186, W: 380, H: 174}, func() {
			ms := 0.0
			if len(ctx.Stats.Scopes) > 0 {
				ms = ctx.Stats.Scopes[0].MS
			}
			asleep := 0
			g.bodies.Each(func(_ ecs.Entity, _ *gfx.Transform, b *phys.Body3, _ *phys.Collider3) {
				if b.Asleep() {
					asleep++
				}
			})
			var spin float32
			if wb, ok := w.Get[phys.Body3](g.wheel); ok {
				spin = wb.AngVel.Z
			}
			u.Label(fmt.Sprintf("physics %.2f ms/frame, %d bodies asleep; capsules, hulls and spheres on a mesh terrain, a hinge chain, a motorised paddle wheel (%.1f rad/s), a ragdoll, and a character climbing stairs (grounded: %v)", ms, asleep, spin, g.ctrl.Grounded))
			if u.Button("Drop again (R)") {
				g.drop()
			}
			u.Label("` opens the console, F4 the debug panels")
		})
	})
	// The engine draws the console above the lab after this returns.
	return nil
}
```

## main

```go
func main() {
	seconds := flag.Float64("seconds", 0, "exit after this many seconds")
	shot := flag.String("shot", "", "write a screenshot to this PNG")
	ragdoll := flag.Bool("ragdoll", true, "drop a ragdoll with the debris")
	flag.Parse()
	err := engine.Run(engine.Config{Title: "Bunyip physics lab", Width: 1024, Height: 680, Resizable: true, Console: true},
		&game{seconds: *seconds, shot: *shot, ragdoll: *ragdoll})
	if err != nil {
		fmt.Fprintln(os.Stderr, "physics-lab:", err)
		os.Exit(1)
	}
}
```

## What to try

- Lower `Substeps` and `Iterations` in `Init` to 1 and 2 and watch the
  chain stretch and the stack jitter; raise them and watch the frame
  time in the panel.
- Set `SleepTime` in `Init` to 0.05 and see how much sooner the debris
  turns grey, and what that does to a body a paddle is about to hit.
- Give the hinge in `Init` `MotorSpeed: -4` and a larger
  `MaxMotorTorque`, and watch the wheel throw the debris.
- Open the console with the backquote key and type `set wheel.torque 40`,
  then `set wheel.torque 4000`, without restarting. Then run `drop`, and
  open the panels with F4 to watch the body count and the contacts while
  the debris settles.
- Add a `phys.BallJoint3` in `Init` between two of the dropped bodies;
  `drawJoint` in `Draw` already handles that type.
- Steer the character in `Update` from the keyboard instead of the
  timer, passing the direction to `ctrl.Move`, and try walking it up the
  terrain until `MaxSlope` refuses.
