Skip to content

Physics

The Box2D world, physics nodes and joints, contact and area events, queries, materials, collision traced from images and debug drawing.

Files: physics.odin, physics_nodes.odin, physics_events.odin, physics_joints.odin, physics_debug.odin, physics_material.odin, collision_trace.odin.

The physics world: Box2D 3.1 (the box2d/ package), one world per scene, created when the scene’s first physics node is. Physics nodes are in physics_nodes.odin.

Every scene update (Scene_Update) runs Physics_Update:

  1. Bodies whose nodes entered the scene get their Box2D bodies and shapes; bodies whose shapes changed get them rebuilt.
  2. The world steps at a fixed PHYSICS_TIMESTEP (as many steps as the frame’s time covers, at most PHYSICS_MAX_STEPS). Before each step, nodes the game moved move their bodies; after it, dynamic bodies move their nodes, collision events are sent and RayCasts update. Then the nodes and scripts update, so they see this frame’s physics.

Units: Box2D works in meters; the engine API in pixels. PhysicsSettings.pixelsPerMeter (100 by default) converts, so a 50 px crate is half a meter, which Box2D handles best (0.1 to 10 m). Gravity is in px/s² (980 = Earth’s, at 100 px per meter). Rotation maps directly: Box2D angles in the engine’s y-down space turn clockwise, like Node.rotation (converted to radians).

Physics nodes. A body node (StaticBody, DynamicBody, CharacterMover, Area) gets its shape from the CollisionShape nodes under it:

World
DynamicBody "Crate" falls, bounces, is pushed
Sprite what it looks like
CollisionShape Rectangle what it collides as
StaticBody "Ground"
CollisionShape Rectangle
Area "Coin" detects bodies entering and leaving; nothing collides with it
CollisionShape Circle

A shape belongs to its nearest body ancestor; the shape’s position, rotation and scale relative to that body are baked into the Box2D shape when the body is created (or its shapes are rebuilt with PhysicsBody_RebuildShapes). Bodies are created in the physics world at the start of the scene’s first update after they entered it (physics.odin), and destroyed with their node.

Units are the engine’s: pixels, degrees (clockwise), pixels per second. Collision layers are bit sets of layers 1-64 (PhysicsLayer, PHYSICS_ALL_LAYERS): a body is on its collisionLayer layers and collides with bodies whose layers are in its collisionMask (both ways).

Collisions come as events (event.odin) on the body: onCollisionEnter when two bodies start touching (for an Area: when a body enters it), onCollisionExit when they stop. The event data is a ^Collision (Collision_FromEvent). Scripts on body nodes can use the collisionEnter and collisionExit hooks instead (script.odin).

Example

crate := e.CreateDynamicBody(scene)
crate.position = {400, 100}
shape := e.CreateCollisionShape(scene, .Rectangle)
shape.size = {48, 48}
e.Node_AddChild(crate, shape)
crate.onCollisionEnter->subscribe(proc(_: ^e.EventSubject, _: rawptr, data: e.EventData, _: rawptr) {
hit := e.Collision_FromEvent(data)
log.infof("%s hit %s", hit.body.name, hit.other.name)
}, nil)

Collision and area events: Box2D’s contact and sensor events turned into node events, per body pair.

Box2D reports a begin and an end per pair of shapes. Bodies with several shapes would hear about the same body several times, so each body counts its contacts per other body (PhysicsBody.contacts, and areas for the Areas it’s in): onCollisionEnter when the first shape pair starts touching, onCollisionExit when the last one stops. For an Area, its contacts are the bodies inside it. A body hears about Areas with onAreaEnter / onAreaExit, and can ask at any time (PhysicsBody_GetAreas, PhysicsBody_IsInArea: a list read, no world query).

When a body is removed, everything it touched (and every Area it was in, and every body inside it, for an Area) gets its exit right away (physicsForgetBody). Box2D’s own end events for destroyed shapes come a step later with stale ids: the world’s shapes table still knows which body a destroyed shape belonged to (for shapes rebuilt on a living body) or has forgotten it (a removed body: already handled).

Joints: a Joint node connects two bodies, at the node’s position (its anchor).

  • Body A is the body the joint is under (its nearest body ancestor) unless bodyA names one; with neither, it’s the world itself (pin a body to a point in the world).
  • Body B is named by bodyB: a node name or a path (“World/Crate”), found with Scene_FindNode.
  • The joint is made when both bodies exist (the first physics sync after they enter the scene), from where the bodies are then: anchors, axes and angles are taken from that pose.

Kinds (JointKind), and what their settings mean:

Revolute a hinge or pin at the anchor. Limit: lower/upper angle (degrees). Motor: speed in
degrees per second, maxMotor torque in kg·px²/s². Spring: pulls back to the start angle
Distance keeps the anchor and `offset` (relative to the joint, on body B) apart: a rod, or
with limits a rope (lower/upper length in px), with a spring a spring.
Motor: speed in px/s along the line, maxMotor force in kg·px/s²
Prismatic body B slides along the joint's X axis (rotate the joint to aim it), without
turning. Limit: lower/upper translation (px). Motor: px/s and force
Weld glues the bodies together. A spring (enableSpring, hertz > 0) makes it bendy
Wheel body B slides along the joint's X axis on a spring (suspension) and turns freely:
the motor spins it (degrees per second, torque)

Springs: hertz is how stiff (cycles per second; 0 = off), dampingRatio how quickly it settles (0 = bounces forever, 1 = no bounce).

When either body is removed, Box2D removes the joint with it.

Example

hinge := e.CreateJoint(scene, .Revolute)
hinge.position = {300, 100} // the pin, in the world (no body above it: body A is the world)
hinge.bodyB = "Door"
hinge.enableLimit, hinge.lower, hinge.upper = true, -90, 0

Smoothing and debug drawing.

Smoothing (Box2D’s recommendation: a fixed time step, see “Fix Your Timestep!”): the world steps at PHYSICS_TIMESTEP whatever the frame rate, so at 144 frames per second a body would move on some frames and not others. Instead, dynamic bodies are drawn between their pose before the last step and after it, PhysicsWorld.alpha of the way (the share of a step the frame’s time has accumulated toward the next one). The simulation itself is untouched: node.position is the body’s real position; only drawing (Node_GetRenderWorldTransform: the scene’s draw order, the camera, the debug shapes) uses the in-between pose. It’s one step behind at most, which is what the technique costs. PhysicsSettings.smoothing turns it off. Bodies moved by code (CharacterMover, Areas, static bodies, teleports) are drawn where they are.

Debug drawing (settings.debugDraw, the project’s physicsDebug, --physics-debug for the player, Physics_SetDebugDraw at run time): every shape filled and outlined in its body’s color (static green, dynamic blue, sleeping dynamic grey-blue, movers orange, Areas purple), joints (anchors and the lines between them), and rays.

Physics materials: a body’s surface (friction, bounce) and density in one shared asset, so every ice block, rubber ball or crate in a game feels the same and changes in one place.

A material is an asset of kind “PhysicsMaterial”, a .material.xml file:

<material formatVersion="13" friction="0.05" restitution="0" density="0.9"/>

A body names one with material in its scene file (&lt;node kind="DynamicBody" material= "materials/ice.material.xml"/>): the material’s values replace the body’s own friction, restitution and density. Bodies without one keep theirs. Games read each file once (Game_LoadPhysicsMaterial, Game.physicsMaterials); editors put their unsaved materials there before building. From code: PhysicsBody_SetMaterial.

Collision from images: the outline of an image’s opaque pixels, traced and cut into the convex pieces Box2D takes (at most COLLISION_POLYGON_MAX_POINTS points each), so a rock, a ship or a platform drawn in a sprite gets a body that fits it instead of a box.

  1. Image_TraceOutlines follows the edges of the pixels whose alpha is at least threshold (4-connected: pixels touching only at a corner are apart), keeping each region’s outer outline (holes are ignored), simplified so no point strays more than tolerance pixels from the traced edge (Ramer-Douglas-Peucker).
  2. Polygon_ConvexPieces cuts an outline into triangles (ear clipping) and merges neighbours while they stay convex and small enough (Hertel-Mehlhorn).
  3. Image_CollisionPolygons does both, CollisionShapes_FromTexture makes the CollisionShape nodes for a texture under a body. Editors (the Crossfire Editor’s “Collision from sprite”) make the same shapes as ordinary CollisionShape nodes in the scene, so they can be edited.

Points are in pixels with y down. Results are temp allocated unless an allocator is given.

PhysicsSettings :: struct {
// Pixels per meter of the simulation. 0 means the defaults (DEFAULT_PHYSICS)
pixelsPerMeter: f32,
// px/s²; y points down
gravity: [2]f32,
// Draw every body's shapes over the scene (`Physics_DrawDebug`)
debugDraw: bool,
// Draw dynamic bodies between their last two physics steps (physics_debug.odin)
smoothing: bool,
}

How the physics behaves; a project’s comes from project.xml (ProjectData.physics)

struct. Source: physics.odin:30

PhysicsWorld :: struct {
// The Box2D world
id: b2.WorldId,
// The settings it was made with (gravity and debugDraw change at run time)
settings: PhysicsSettings,
// Time not yet simulated
accumulator: f64,
// How far between the last two steps this frame is drawn (0-1), for smoothing
alpha: f32,
// Every body node made in the scene, in the scene or not yet
bodies: [dynamic]^PhysicsBody,
// Every RayCast node, cast after each step
rayCasts: [dynamic]^RayCast,
// Every Joint node, created in Box2D once both its bodies exist
joints: [dynamic]^Joint,
// Every Box2D shape's body and node (physics_events.odin)
shapes: map[b2.ShapeId]ShapeOwner,
// The body joints attach to when they have no body A: the world itself (static, at the origin)
ground: b2.BodyId,
}

A scene’s physics world (internal state; use the procedures)

struct. Source: physics.odin:54

RayHit :: struct {
// Whether it hit anything (the other fields are zero when not)
hit: bool,
// Where it hit, in world pixels
point: [2]f32,
// The surface's normal there (unit length, pointing out of the surface hit)
normal: [2]f32,
// The body hit
body: ^PhysicsBody,
// Which of its CollisionShapes was hit
shape: ^CollisionShape,
// How far along the ray, 0-1
fraction: f32,
}

What a ray (or Physics_RayCast) hit

struct. Source: physics.odin:191

PhysicsBodyKind :: enum {
// Doesn't move by itself; other bodies collide with it. Moving its node moves it (teleports)
Static,
// Moved by gravity, forces and collisions; its node follows it
Dynamic,
// Moved by code (`CharacterMover_Move`), sliding along what it hits
Character,
// A sensor region: reports bodies entering and leaving, collides with nothing
Area,
}

The kinds of physics body

enum. Source: physics_nodes.odin:47

PhysicsLayer :: distinct int

A collision layer, 1 to 64

distinct type. Source: physics_nodes.odin:59

PhysicsBody :: struct {
// The node part: transform, hierarchy, hooks
using node: Node,
// Which body type it is (set by its Create procedure)
kind: PhysicsBodyKind,
// Layers this body is on, one bit per layer (layer 1 is bit 0)
collisionLayer: u64,
// Layers it collides with (and, for an Area, detects)
collisionMask: u64,
// Surface friction: 0 = ice, 1 = rubber (default 0.6)
friction: f32,
// Bounciness, 0 (none, the default) to 1 (bounces back as high)
restitution: f32,
// kg per square meter (100 x 100 px). Only dynamic bodies use it
density: f32,
// A body (or Area) started touching this one
onCollisionEnter: EventSubject `event:"A body started touching it (an Area: a body came in)" data:"collision := e.Collision_FromEvent(data)"`,
// They stopped touching
onCollisionExit: EventSubject `event:"A body stopped touching it (an Area: a body went out)" data:"collision := e.Collision_FromEvent(data)"`,
// This body entered an Area (`Collision.other` is the Area)
onAreaEnter: EventSubject `event:"It entered an Area (collision.other is the Area)" data:"collision := e.Collision_FromEvent(data)"`,
// This body left an Area (`Collision.other` is the Area)
onAreaExit: EventSubject `event:"It left an Area (collision.other is the Area)" data:"collision := e.Collision_FromEvent(data)"`,
// Bodies touching it (for an Area: inside it), counted per shape pair (internal: use
// `PhysicsBody_GetContacts`, physics_events.odin)
contacts: [dynamic]BodyContact,
// The Areas it's in, counted per shape pair (internal: use `PhysicsBody_GetAreas`)
areas: [dynamic]BodyContact,
// Its local position before the last step, for smoothing (physics_debug.odin)
previousPosition: [2]f32,
// Its local rotation before the last step, in degrees
previousRotation: f32,
// The Box2D body (internal; valid once the body is in the world)
bodyId: b2.BodyId,
// The node's world position when the physics world last saw it, to notice code moving the node
syncedPosition: [2]f32,
// The node's world rotation then, in degrees
syncedRotation: f32,
// Rebuild the Box2D shapes before the next step
shapesDirty: bool,
}

What every physics body node has. The body types embed it first (using body: PhysicsBody), so any of them can be used as a ^PhysicsBody.

struct. Source: physics_nodes.odin:68

StaticBody :: struct {
// The body part: node, layers, surface, collision events
using body: PhysicsBody,
}

A body that doesn’t move by itself: walls, floors, platforms

struct. Source: physics_nodes.odin:112

DynamicBody :: struct {
// The body part: node, layers, surface, collision events
using body: PhysicsBody,
// Multiplies the world's gravity (0 = floats)
gravityScale: f32,
// Slows its movement down over time (0 = no damping)
linearDamping: f32,
// Slows its spin down over time (0 = no damping)
angularDamping: f32,
// It never rotates (characters, upright crates)
fixedRotation: bool,
// Continuous collision against other dynamic bodies, for fast small things like bullets
bullet: bool,
// Velocity it starts with, pixels per second (used when the body is created)
initialVelocity: [2]f32,
}

A body moved by the simulation: gravity, collisions, forces, impulses

struct. Source: physics_nodes.odin:118

CharacterMover :: struct {
// The body part: node, layers, surface, collision events
using body: PhysicsBody,
// Pixels per second. `CharacterMover_Move` moves by it and removes the part that ran into
// something (landing stops the fall, a wall stops the run)
velocity: [2]f32,
// Steepest slope that counts as floor, in degrees (default 45)
floorMaxAngle: f32,
// The last move ended standing on a floor (a surface no steeper than floorMaxAngle)
onFloor: bool,
// The last move ended touching a wall
onWall: bool,
// The last move ended touching a ceiling
onCeiling: bool,
// The floor's normal (pointing up, away from the floor) while onFloor
floorNormal: [2]f32,
// One-way platforms don't stop it while set (hold it to drop through one)
dropThrough: bool,
// Bodies touched by the last move (internal; for collision events)
touching: [dynamic]^PhysicsBody,
// A move ended on a floor after one that didn't (`CharacterMover_Move`)
onLanded: EventSubject `event:"It landed on a floor (a move ended on one after one that didn't)"`,
// A move ended off the floor after one on it: it jumped, fell or walked off an edge
onLeftFloor: EventSubject `event:"It left the floor (jumped, fell or walked off an edge)"`,
// A move ended touching a wall after one that didn't
onWallHit: EventSubject `event:"It ran into a wall"`,
// A move ended touching a ceiling after one that didn't
onCeilingHit: EventSubject `event:"It hit a ceiling"`,
}

A body moved by code that slides along whatever it hits, like characters in platformers and top-down games. Set velocity and call CharacterMover_Move every frame (gravity is up to the game: add it to the velocity). It’s a kinematic body for the rest of the world: it pushes dynamic bodies and Areas detect it, but forces don’t move it.

Its shape is its first Capsule or Circle CollisionShape (Box2D movers are capsules).

struct. Source: physics_nodes.odin:143

Area :: struct {
// The body part: node, layers, collision events
using body: PhysicsBody,
}

A region that reports bodies entering (onCollisionEnter) and leaving (onCollisionExit)

struct. Source: physics_nodes.odin:174

CollisionShapeKind :: enum {
// `size` wide and tall, centered on the node
Rectangle,
// `radius`
Circle,
// Vertical: `height` tall overall (caps included), `radius` wide on each side
Capsule,
// A convex polygon through `points` (up to 8; concave outlines are made convex)
Polygon,
// A line from points[0] to points[1]: thin walls, ground outlines
Segment,
}

The shapes a CollisionShape can be

enum. Source: physics_nodes.odin:180

CollisionShape :: struct {
// The node part: transform, hierarchy, hooks
using node: Node,
// Which shape it is; the fields below say which ones each uses
shape: CollisionShapeKind,
// Rectangle: width and height
size: [2]f32,
// Circle, Capsule
radius: f32,
// Capsule: total height
height: f32,
// Polygon, Segment: points in the node's space
points: [dynamic][2]f32,
// Left out of its body
disabled: bool,
// A one-way platform: only its top surface stops bodies and movers coming down onto it; from
// below or the side they pass through (`CharacterMover.dropThrough` falls through it).
// Change it in code with `PhysicsBody_RebuildShapes`
oneWay: bool,
// An Aseprite slice of its sprite it takes its rectangle from, frame by frame ("" for none:
// aseprite_nodes.odin)
slice: string,
// The Box2D shape (internal)
shapeId: b2.ShapeId,
}

The shape of the body it’s under (its nearest body ancestor). In the node’s local space: it moves, rotates and scales with the node. After changing a shape in code, call PhysicsBody_RebuildShapes on its body.

struct. Source: physics_nodes.odin:201

RayCast :: struct {
// The node part: transform, hierarchy, hooks
using node: Node,
// End of the ray, relative to the node
target: [2]f32,
// Layers it hits
collisionMask: u64,
// Casts after every step while set (the results keep their last values when not)
enabled: bool,
// Ignore the body the ray is under (a character's own body)
excludeParent: bool,
// The last cast hit something
colliding: bool,
// Where, in world pixels
point: [2]f32,
// The surface normal there
normal: [2]f32,
// The body hit (nil when not colliding)
collider: ^PhysicsBody,
// Which of its shapes was hit
colliderShape: ^CollisionShape,
// It started hitting something, or hits another body than before (data: the body)
onHitEnter: EventSubject `event:"It started hitting a body, or hits another one (data: the body)" data:"body := cast(^e.PhysicsBody)data.(rawptr)"`,
// It stopped hitting the body it hit (data: that body)
onHitExit: EventSubject `event:"It stopped hitting the body it hit (data: that body)" data:"body := cast(^e.PhysicsBody)data.(rawptr)"`,
}

Casts a ray from the node toward target (in the node’s space) after every physics step and reports the first thing it hits: colliding, point, normal, collider.

struct. Source: physics_nodes.odin:231

Collision :: struct {
// The body the event is for
body: ^PhysicsBody,
// What it touched (for an Area: the body that entered or left)
other: ^PhysicsBody,
// `body`'s shape that touched (nil for a CharacterMover's contacts and for exits caused by a
// removed body)
shape: ^CollisionShape,
// `other`'s shape that touched (nil in the same cases)
otherShape: ^CollisionShape,
// Where they touch, in world pixels. Zero for exits, Areas and CharacterMover contacts
point: [2]f32,
// The contact normal, from `body` toward `other`. Zero like `point`
normal: [2]f32,
}

What a collision event reports (onCollisionEnter, onCollisionExit, the script hooks). Valid during the event only.

struct. Source: physics_nodes.odin:262

BodyContact :: struct {
// The other body
body: ^PhysicsBody,
// Shape pairs touching now (it's removed from the list at 0)
count: int,
}

One body’s contacts with another, counted per shape pair (internal)

struct. Source: physics_events.odin:24

JointKind :: enum {
// A hinge or pin at the anchor
Revolute,
// Keeps two points a distance apart: a rod, a rope (with limits) or a spring
Distance,
// Body B slides along the joint's X axis without turning
Prismatic,
// Glues the bodies together
Weld,
// Body B slides along the joint's X axis on a spring and turns freely: a wheel's suspension
Wheel,
}

What a Joint does (see the top of physics_joints.odin for each kind’s settings)

enum. Source: physics_joints.odin:44

Joint :: struct {
// The node part: transform (the anchor), hierarchy, hooks
using node: Node,
// What it does (set by `CreateJoint`)
kind: JointKind,
// Name or path of body A ("": the body the joint is under, or the world)
bodyA: string,
// Name or path of body B (required)
bodyB: string,
// Distance: where body B's anchor is, relative to the joint (its length is the distance)
offset: [2]f32,
// Whether the two bodies still collide with each other
collideConnected: bool,
// Use `lower` and `upper`
enableLimit: bool,
// The low limit. Revolute: degrees; Distance: a length in px; Prismatic, Wheel: a translation
// in px
lower: f32,
// The high limit, in the same units (the two are swapped if upper < lower)
upper: f32,
// Drive it at `motorSpeed`, up to `maxMotor`
enableMotor: bool,
// Revolute, Wheel: degrees per second; Distance, Prismatic: px/s
motorSpeed: f32,
// Revolute, Wheel: max torque (kg·px²/s²); Distance, Prismatic: max force (kg·px/s²)
maxMotor: f32,
// Use the spring (`hertz`, `dampingRatio`)
enableSpring: bool,
// How stiff the spring is, in cycles per second (0 = off)
hertz: f32,
// How quickly the spring settles: 0 bounces forever, 1 doesn't bounce (default 0.5)
dampingRatio: f32,
// The Box2D joint (internal)
jointId: b2.JointId,
// Body A it connected (nil: the world)
connectedA: ^PhysicsBody,
// Body B it connected
connectedB: ^PhysicsBody,
// Its bodies couldn't be found (reported once; not retried)
failed: bool,
}

A node that connects two bodies at its position (see the top of physics_joints.odin). The settings are read when the Box2D joint is made; after that only Joint_SetMotorSpeed changes one.

struct. Source: physics_joints.odin:62

PhysicsMaterial :: struct {
// Surface friction: 0 = ice, 1 = rubber
friction: f32 `min:"0" max:"2"`,
// Bounciness: 0 none, 1 bounces back as high
restitution: f32 `min:"0" max:"1"`,
// kg per square meter (100 x 100 px), for dynamic bodies' mass
density: f32 `min:"0" max:"100"`,
}

A body’s surface and density, shared by the bodies that name it

struct. Source: physics_material.odin:26

Scene_GetPhysics :: proc(scene: ^Scene) -> ^PhysicsWorld

The scene’s physics world, created the first time it’s needed, with the game’s settings (Game.physics; DEFAULT_PHYSICS when the game has none). Creating any physics node calls it. The scene owns the world and frees it with itself.

Inputs

  • scene: The scene

Returns

  • The world (internal state: use the Physics_* procedures on the scene)

procedure. Source: physics.odin:91

Physics_SetGravity :: proc(scene: ^Scene, gravity: [2]f32)

Changes the scene’s gravity (creating its world if it has none).

Inputs

  • scene: The scene
  • gravity: px/s², y down ({0, 980} is Earth’s at 100 px per meter)

Example

e.Physics_SetGravity(scene, {0, 490}) // a low-gravity level

procedure. Source: physics.odin:120

Physics_RayCast :: proc(scene: ^Scene, from, to: [2]f32, mask: u64 = PHYSICS_ALL_LAYERS, exclude: ^PhysicsBody = nil) -> RayHit

Casts a ray through the scene’s physics world and returns the first body it hits. Areas (sensors) are ignored. For a ray cast after every step, use a RayCast node.

Inputs

  • scene: The scene
  • from, to: Where the ray starts and ends, in world pixels
  • mask: Layers to hit (PhysicsLayers; every layer by default)
  • exclude: A body to ignore (the caster’s own)

Returns

  • The hit: hit is false when nothing is in the way

Example

// Given: hero: ^e.CharacterMover
// Is there ground within 40 px below the hero?
ground := e.Physics_RayCast(scene, hero.position, hero.position + {0, 40}, exclude = hero)
if ground.hit do log.infof("ground at %v", ground.point)

procedure. Source: physics.odin:225

Physics_QueryPoint :: proc(scene: ^Scene, point: [2]f32, mask: u64 = PHYSICS_ALL_LAYERS, includeAreas := false, allocator := context.temp_allocator) -> []^PhysicsBody

The bodies with a shape under a point.

Inputs

  • scene: The scene
  • point: World pixels
  • mask: Layers to find
  • includeAreas: Areas count too (they’re left out by default)
  • allocator: For the result (the frame’s temp allocator by default)

Returns

  • Each body once, in no particular order

Example

// What did the player click on? (The mouse is in game coordinates, not the world's)
click := e.Scene_ScreenToWorld(scene, e.Input_GetMousePosition(scene.game))
for body in e.Physics_QueryPoint(scene, click) {
log.infof("clicked %s", body.name)
}

procedure. Source: physics.odin:268

Physics_QueryRect :: proc(scene: ^Scene, rect: sdl.FRect, mask: u64 = PHYSICS_ALL_LAYERS, includeAreas := false, allocator := context.temp_allocator) -> []^PhysicsBody

The bodies with a shape overlapping an axis-aligned rectangle.

Inputs

  • scene: The scene
  • rect: World pixels (x, y the top-left corner)
  • mask: Layers to find
  • includeAreas: Areas count too (they’re left out by default)
  • allocator: For the result (the frame’s temp allocator by default)

Returns

  • Each body once, in no particular order

Example

// Given: blast: [2]f32, hurt: proc(body: ^e.PhysicsBody)
// An explosion's square of damage
for body in e.Physics_QueryRect(scene, {blast.x - 64, blast.y - 64, 128, 128}) do hurt(body)

procedure. Source: physics.odin:292

Physics_QueryCircle :: proc(scene: ^Scene, center: [2]f32, radius: f32, mask: u64 = PHYSICS_ALL_LAYERS, includeAreas := false, allocator := context.temp_allocator) -> []^PhysicsBody

The bodies with a shape overlapping a circle.

Inputs

  • scene: The scene
  • center: World pixels
  • radius: Pixels
  • mask: Layers to find
  • includeAreas: Areas count too (they’re left out by default)
  • allocator: For the result (the frame’s temp allocator by default)

Returns

  • Each body once, in no particular order

Example

// Given: hero: ^e.CharacterMover, ENEMY_LAYER: u64, hit: proc(body: ^e.PhysicsBody)
// Enemies within reach of the hero's sword
for body in e.Physics_QueryCircle(scene, hero.position, 48, ENEMY_LAYER) do hit(body)

procedure. Source: physics.odin:318

Physics_ShapeCast :: proc(scene: ^Scene, radius: f32, from, to: [2]f32, mask: u64 = PHYSICS_ALL_LAYERS, exclude: ^PhysicsBody = nil) -> RayHit

Sweeps a circle from from to to and returns the first body it would hit: a thick ray, for checking whether something of that size fits through (a bullet, a dash). Areas and shapes the circle starts in are ignored.

Inputs

  • scene: The scene
  • radius: The circle’s, in pixels
  • from, to: World pixels (the circle’s center)
  • mask: Layers to hit
  • exclude: A body to ignore (the caster’s own)

Returns

  • The hit: point and normal where the circle touches, fraction of the way along

Example

// Given: hero: ^e.CharacterMover
// How far right can the hero dash?
hit := e.Physics_ShapeCast(scene, 12, hero.position, hero.position + {200, 0}, exclude = hero)
dashTo := hero.position + [2]f32{200, 0} * (hit.hit ? hit.fraction : 1)

procedure. Source: physics.odin:345

CharacterMover_Move :: proc(mover: ^CharacterMover, deltaTime: f64)

Moves a CharacterMover by its velocity for deltaTime seconds, sliding along what it hits, and updates onFloor / onWall / onCeiling and floorNormal. The part of the velocity that ran into something is removed (it stops falling when it lands). Call it from the mover’s update or script every frame.

Uses Box2D’s mover functions: collide the capsule with the world to get contact planes, solve the planes for the move, sweep the capsule along the result (up to 5 times), then clip the velocity against the planes.

Nothing happens until the mover’s body exists (from the scene’s first update after it entered), or without a Capsule or Circle shape (a warning, once).

Inputs

  • mover: The mover
  • deltaTime: Seconds to move for

Example

// Given: hero: ^e.CharacterMover
hero.velocity.y += 980 * f32(deltaTime) // gravity is the game's to add
if hero.onFloor && e.Input_IsKeyJustPressed(.Space) do hero.velocity.y = -500
e.CharacterMover_Move(hero, deltaTime)

procedure. Source: physics.odin:416

RayCast_ForceUpdate :: proc(ray: ^RayCast)

Casts a RayCast now, updating its results (colliding, point, normal, collider, colliderShape). RayCasts cast after every physics step by themselves; call this after moving one to see the result before the next step. It casts even when the ray isn’t enabled.

Inputs

  • ray: The RayCast node

procedure. Source: physics.odin:810

Collision_FromEvent :: proc(data: EventData) -> ^Collision

The Collision a physics event carries, in an onCollisionEnter / onCollisionExit / onAreaEnter / onAreaExit listener.

Inputs

  • data: The event’s data

Returns

  • The collision (valid during the event only), or nil if the data isn’t one

Example

// Given: coin: ^e.Area
coin.onCollisionEnter->subscribe(proc(_: ^e.EventSubject, _: rawptr, data: e.EventData, _: rawptr) {
hit := e.Collision_FromEvent(data)
log.infof("%s picked up %s", hit.other.name, hit.body.name)
}, nil)

procedure. Source: physics_nodes.odin:295

PhysicsLayers :: proc(layers: ..PhysicsLayer) -> u64

A collision layer set from layer numbers (1-64), for collisionLayer and collisionMask. Numbers outside 1-64 are ignored.

Inputs

  • layers: Layer numbers

Returns

  • One bit per layer (layer 1 is bit 0)

Example

// Given: body: ^e.PhysicsBody
body.collisionLayer = e.PhysicsLayers(2)
body.collisionMask = e.PhysicsLayers(1, 3)

procedure. Source: physics_nodes.odin:315

CreateStaticBody :: proc(scene: ^Scene) -> ^StaticBody

Creates a StaticBody (no shape yet: add CollisionShape children). It’s on layer 1 and collides with every layer; its Box2D body is made on the scene’s first update after it’s in the scene.

Inputs

  • scene: The scene

Returns

  • The body (owned by the scene, like every node)

Example

ground := e.CreateStaticBody(scene)
ground.position = {640, 700}
shape := e.CreateCollisionShape(scene, .Rectangle)
shape.size = {1280, 40}
e.Node_AddChild(ground, shape)

procedure. Source: physics_nodes.odin:338

CreateDynamicBody :: proc(scene: ^Scene) -> ^DynamicBody

Creates a DynamicBody (no shape yet: add CollisionShape children; see the example at the top of physics_nodes.odin). Gravity scale 1, density 1, friction 0.6, on layer 1, colliding with every layer.

Inputs

  • scene: The scene

Returns

  • The body

procedure. Source: physics_nodes.odin:355

CreateCharacterMover :: proc(scene: ^Scene) -> ^CharacterMover

Creates a CharacterMover (give it a Capsule or Circle CollisionShape child). floorMaxAngle is 45 degrees. Move it with CharacterMover_Move.

Inputs

  • scene: The scene

Returns

  • The mover

Example

hero := e.CreateCharacterMover(scene)
hero.position = {200, 300}
e.Node_AddChild(hero, e.CreateCollisionShape(scene, .Capsule)) // 64 tall, radius 16

procedure. Source: physics_nodes.odin:377

CreateArea :: proc(scene: ^Scene) -> ^Area

Creates an Area (give it CollisionShape children: the region). It detects bodies on the layers in its collisionMask (all by default).

Inputs

  • scene: The scene

Returns

  • The Area

procedure. Source: physics_nodes.odin:396

CreateCollisionShape :: proc(scene: ^Scene, kind: CollisionShapeKind) -> ^CollisionShape

Creates a CollisionShape with default dimensions (a 32 x 32 rectangle, radius 16, a 64 tall capsule; Polygon and Segment need their points). Make it a child of a body node (or of a node under one): its body includes it from the next physics step after it enters the scene.

Inputs

  • scene: The scene
  • kind: The shape

Returns

  • The shape node

procedure. Source: physics_nodes.odin:414

CreateRayCast :: proc(scene: ^Scene, target: [2]f32 = {0, 50}) -> ^RayCast

Creates a RayCast, enabled, hitting every layer and ignoring the body it’s under. Parent it to the node it casts from.

Inputs

  • scene: The scene
  • target: The ray’s end, relative to the node (pixels; 50 down by default)

Returns

  • The RayCast

Example

// Given: hero: ^e.CharacterMover
feet := e.CreateRayCast(scene, {0, 40})
e.Node_AddChild(hero, feet)
// Later, in the hero's update:
if feet.colliding do log.infof("standing on %s", feet.collider.name)

procedure. Source: physics_nodes.odin:448

Node_IsPhysicsBody :: proc(node: ^Node) -> bool

Whether a node is a physics body of any kind (StaticBody, DynamicBody, CharacterMover, Area).

Inputs

  • node: The node (nil is no body)

Returns

  • true if it can be used as a ^PhysicsBody

procedure. Source: physics_nodes.odin:473

CollisionShape_GetBody :: proc(shape: ^CollisionShape) -> ^PhysicsBody

The body a shape belongs to: its nearest body ancestor.

Inputs

  • shape: The shape

Returns

  • The body, or nil if no body is above it

procedure. Source: physics_nodes.odin:486

PhysicsBody_RebuildShapes :: proc(body: ^PhysicsBody)

Rebuilds a body’s Box2D shapes from its CollisionShape nodes before the next step. Call it after changing a shape’s size, points, position, oneWay or disabled in code, or the body’s friction, restitution or density.

Inputs

  • body: The body

procedure. Source: physics_nodes.odin:501

PhysicsBody_SetLayers :: proc(body: ^PhysicsBody, layer, mask: u64)

Changes the layers a body is on and collides with (its shapes are rebuilt before the next step).

Inputs

  • body: The body
  • layer: Layers it’s on (PhysicsLayers)
  • mask: Layers it collides with (for an Area: detects)

procedure. Source: physics_nodes.odin:513

DynamicBody_GetVelocity :: proc(body: ^DynamicBody) -> [2]f32

A dynamic body’s velocity.

Inputs

  • body: The body

Returns

  • Pixels per second (before its Box2D body exists: initialVelocity)

procedure. Source: physics_nodes.odin:527

DynamicBody_SetVelocity :: proc(body: ^DynamicBody, velocity: [2]f32)

Sets a dynamic body’s velocity (before its Box2D body exists: its initialVelocity).

Inputs

  • body: The body
  • velocity: Pixels per second

Example

// Given: ball: ^e.DynamicBody
e.DynamicBody_SetVelocity(ball, {300, -400})

procedure. Source: physics_nodes.odin:543

DynamicBody_GetAngularVelocity :: proc(body: ^DynamicBody) -> f32

A dynamic body’s spin speed.

Inputs

  • body: The body

Returns

  • Degrees per second, clockwise (0 before its Box2D body exists)

procedure. Source: physics_nodes.odin:560

DynamicBody_SetAngularVelocity :: proc(body: ^DynamicBody, degreesPerSecond: f32)

Sets a dynamic body’s spin speed. Does nothing before its Box2D body exists (the scene’s first update after it entered).

Inputs

  • body: The body
  • degreesPerSecond: Clockwise

procedure. Source: physics_nodes.odin:573

DynamicBody_ApplyImpulse :: proc(body: ^DynamicBody, impulse: [2]f32)

Gives a dynamic body an instant push at its center: its velocity changes by impulse / mass, and it wakes up. Does nothing before its Box2D body exists.

Inputs

  • body: The body
  • impulse: kg·px/s, so an impulse of mass × 300 changes the velocity by 300 px/s whatever the body weighs (DynamicBody_GetMass)

Example

// Given: crate: ^e.DynamicBody
// Knock the crate up and right by 300 px/s
e.DynamicBody_ApplyImpulse(crate, e.DynamicBody_GetMass(crate) * [2]f32{150, -260})

procedure. Source: physics_nodes.odin:591

DynamicBody_ApplyForce :: proc(body: ^DynamicBody, force: [2]f32)

Pushes a dynamic body at its center during the next step, waking it (Box2D clears forces after each step: call it every frame for a steady force). Does nothing before its Box2D body exists.

Inputs

  • body: The body
  • force: kg·px/s² (mass × acceleration in px/s²)

procedure. Source: physics_nodes.odin:603

DynamicBody_GetMass :: proc(body: ^DynamicBody) -> f32

A dynamic body’s mass, from its shapes.

Inputs

  • body: The body

Returns

  • kg: its shapes’ area in square meters × density (0 before its Box2D body exists)

procedure. Source: physics_nodes.odin:616

Area_GetOverlaps :: proc(area: ^Area, allocator := context.temp_allocator) -> []^PhysicsBody

The bodies inside an Area right now (the same as PhysicsBody_GetContacts on it). Areas see bodies whose layers are in their mask. Kept up to date by every physics step (physics_events.odin).

Inputs

  • area: The Area
  • allocator: For the result (the frame’s temp allocator by default)

Returns

  • The bodies, in the order they entered

procedure. Source: physics_nodes.odin:632

PhysicsBody_GetAreas :: proc(body: ^PhysicsBody, allocator := context.temp_allocator) -> []^Area

The Areas a body is inside right now. Updated by every physics step, from Box2D’s sensor events. Areas only see bodies on the layers in their mask.

Inputs

  • body: The body
  • allocator: For the result (the frame’s temp allocator by default)

Returns

  • The Areas, in the order it entered them

Example

// Given: hero: ^e.CharacterMover
for area in e.PhysicsBody_GetAreas(hero) do if area.name == "Water" do hero.velocity *= 0.9

procedure. Source: physics_events.odin:56

PhysicsBody_IsInArea :: proc(body: ^PhysicsBody, area: ^Area) -> bool

Whether a body is inside an Area right now (a list read, no world query).

Inputs

  • body: The body
  • area: The Area

Returns

  • true while any of their shapes overlap

procedure. Source: physics_events.odin:72

PhysicsBody_IsTouching :: proc(body, other: ^PhysicsBody) -> bool

Whether two bodies are touching right now (for an Area: whether other is inside it). A list read, no world query.

Inputs

  • body: One body
  • other: The other

Returns

  • true while any of their shapes touch

procedure. Source: physics_events.odin:88

PhysicsBody_GetContacts :: proc(body: ^PhysicsBody, allocator := context.temp_allocator) -> []^PhysicsBody

The bodies touching a body right now (for an Area: the bodies inside it). Updated by every physics step; a CharacterMover’s by its moves.

Inputs

  • body: The body
  • allocator: For the result (the frame’s temp allocator by default)

Returns

  • The bodies, in the order they started touching

procedure. Source: physics_events.odin:104

CreateJoint :: proc(scene: ^Scene, kind: JointKind) -> ^Joint

Creates a Joint. Set its bodyB and its position (the anchor), and parent it under body A (or set bodyA) before its scene’s next update: the Box2D joint is made from that pose.

Inputs

  • scene: The scene
  • kind: What it does

Returns

  • The joint (offset is {0, 100}, dampingRatio 0.5)

Example

// Given: car: ^e.DynamicBody
wheel := e.CreateJoint(scene, .Wheel)
wheel.position = {-40, 30} // relative to the car, which is body A
wheel.bodyB = "Car/FrontWheel"
wheel.enableSpring, wheel.hertz = true, 4
wheel.enableMotor, wheel.maxMotor = true, 5e6
e.Node_AddChild(car, wheel)

procedure. Source: physics_joints.odin:125

Joint_IsActive :: proc(joint: ^Joint) -> bool

Whether the joint exists in the physics world (both bodies were found and are still there).

Inputs

  • joint: The joint

Returns

  • false before it’s made, when its bodies weren’t found, and after either body is removed

procedure. Source: physics_joints.odin:150

Joint_SetMotorSpeed :: proc(joint: ^Joint, speed: f32)

Changes a joint’s motor speed, turning the motor on, and wakes its bodies. Works before the joint is made too (it starts with it). Weld joints have no motor.

Inputs

  • joint: The joint
  • speed: Degrees per second (clockwise) for Revolute and Wheel, px/s for Distance and Prismatic

Example

// Given: wheel: ^e.Joint
e.Joint_SetMotorSpeed(wheel, e.Input_GetAxis("left", "right") * 720)

procedure. Source: physics_joints.odin:166

Joint_GetAngle :: proc(joint: ^Joint) -> f32

A Revolute joint’s angle: how far body B has turned relative to body A since the joint was made.

Inputs

  • joint: The joint

Returns

  • Degrees, clockwise (0 for other kinds, or before the joint is made)

procedure. Source: physics_joints.odin:197

Joint_GetTranslation :: proc(joint: ^Joint) -> f32

A Prismatic joint’s translation, or a Distance joint’s current length.

Inputs

  • joint: The joint

Returns

  • Pixels: along the axis from where it started, or the length (0 for other kinds, or before the joint is made)

procedure. Source: physics_joints.odin:212

Joint_GetForce :: proc(joint: ^Joint) -> [2]f32

How hard the joint pulls on its bodies right now: compare its length to a limit to make breakable joints (Scene_RemoveNode(scene, joint.id) breaks it).

Inputs

  • joint: The joint

Returns

  • Box2D’s constraint force, kg·px/s² (zero before the joint is made)

Example

// Given: rope: ^e.Joint
if linalg.length(e.Joint_GetForce(rope)) > 50000 do e.Scene_RemoveNode(scene, rope.id)

procedure. Source: physics_joints.odin:236

Joint_GetAnchors :: proc(joint: ^Joint) -> (a, b: [2]f32, ok: bool)

The joint’s anchors in world pixels, for drawing it (a rope’s line, a spring).

Inputs

  • joint: The joint

Returns

  • a: Body A’s anchor (where the joint was)
  • b: Body B’s anchor (it starts at a, except a Distance joint’s: offset away)
  • ok: false before the joint is made

procedure. Source: physics_joints.odin:252

Physics_SetDebugDraw :: proc(game: ^Game, on: bool)

Shows or hides the physics debug shapes (bodies, joints, rays) in every scene of a game, now and in scenes made later (it sets Game.physics.debugDraw).

Inputs

  • game: The game
  • on: Show them

Example

if e.Input_IsKeyJustPressed(.F3) do e.Physics_SetDebugDraw(game, !game.physics.debugDraw)

procedure. Source: physics_debug.odin:38

Node_GetRenderLocalTransform :: proc(node: ^Node) -> Transform2D

A node’s transform relative to its parent as it’s drawn: for a dynamic body with smoothing on (and physics not paused), between its last two physics poses; otherwise its local transform.

Inputs

  • node: The node

Returns

  • The transform (position, rotation and scale relative to the parent)

procedure. Source: physics_debug.odin:54

Node_GetRenderWorldTransform :: proc(node: ^Node) -> Transform2D

A node’s world transform as it’s drawn (see Node_GetRenderLocalTransform): use it to draw at a smoothed body, or to follow one with something drawn. It’s Node_GetWorldTransform for nodes with no dynamic body among them and their parents.

Inputs

  • node: The node

Returns

  • The transform, node space to world

Example

// Given: ball: ^e.DynamicBody
// A shadow that follows the ball smoothly
shadowAt := e.Node_GetRenderWorldTransform(ball).origin + {0, 20}

procedure. Source: physics_debug.odin:84

ParsePhysicsMaterial :: proc(text: string) -> (PhysicsMaterial, ProjectError)

Reads a material file.

Returns

  • The material (fields it lacks keep DEFAULT_PHYSICS_MATERIAL’s), and an error for a file that isn’t one or is from a newer format

Example

material, err := e.ParsePhysicsMaterial(`<material friction="0.05"/>`)

procedure. Source: physics_material.odin:48

FormatPhysicsMaterial :: proc(m: PhysicsMaterial, allocator := context.allocator) -> string

Writes a material file.

Returns

  • The file’s text (allocated with allocator)

procedure. Source: physics_material.odin:73

Game_LoadPhysicsMaterial :: proc(game: ^Game, path: string) -> (PhysicsMaterial, bool)

A material asset, read once per game (tools put their unsaved materials in game.physicsMaterials first).

Inputs

  • game: The game
  • path: The .material.xml file, relative to the project

Returns

  • The material, and false when it couldn’t be read

procedure. Source: physics_material.odin:89

PhysicsBody_SetMaterial :: proc(body: ^PhysicsBody, m: PhysicsMaterial)

Gives a body a material’s surface and density, its shapes too when its Box2D body exists (a dynamic body’s mass follows).

Example

// Given: puck: ^e.DynamicBody
e.PhysicsBody_SetMaterial(puck, {friction = 0.02, restitution = 0.9, density = 0.5})

procedure. Source: physics_material.odin:120

Image_TraceOutlines :: proc(pixels: []u8, width, height: int, threshold: u8 = 128, tolerance: f32 = 1.5, allocator := context.temp_allocator) -> [][][2]f32

The outer outlines of an image’s opaque regions.

Inputs

  • pixels: RGBA8 rows, width * height * 4 bytes
  • threshold: The least alpha that counts as solid (1-255)
  • tolerance: How far (pixels) a simplified outline may stray from the traced edge; 0 keeps every corner

Returns

  • One outline per region (points around it, the region on the right going forward in y-down coordinates: a positive shoelace area), in pixel coordinates (0, 0 the image’s top-left)

Example

// Given: pixels: []u8
outlines := e.Image_TraceOutlines(pixels, 64, 32, threshold = 128, tolerance = 1.5)

procedure. Source: collision_trace.odin:43

Polygon_ConvexPieces :: proc(outline: [][2]f32, max_points := COLLISION_POLYGON_MAX_POINTS, allocator := context.temp_allocator) -> [][][2]f32

Cuts an outline into convex pieces of at most max_points points each (Box2D’s polygons).

Inputs

  • outline: A simple polygon (no crossing edges), either way round
  • max_points: The most points a piece may have (3-8)

Returns

  • The pieces, each going the same way as a positive-area outline; slivers under a square pixel are dropped

procedure. Source: collision_trace.odin:118

Image_CollisionPolygons :: proc(pixels: []u8, width, height: int, threshold: u8 = 128, tolerance: f32 = 1.5, offset: [2]f32 = {}, allocator := context.temp_allocator) -> [][][2]f32

The convex pieces of an image’s opaque regions (Image_TraceOutlines, then Polygon_ConvexPieces), moved by offset: -{width, height} / 2 centers them on the image, as a Sprite draws it.

Returns

  • The pieces (each at most COLLISION_POLYGON_MAX_POINTS points)

Example

// Given: pixels: []u8
pieces := e.Image_CollisionPolygons(pixels, 64, 32, offset = {-32, -16})

procedure. Source: collision_trace.odin:196

CollisionShapes_FromTexture :: proc(body: ^Node, texture: string, threshold: u8 = 128, tolerance: f32 = 1.5) -> int

Gives a body CollisionShapes that fit an image’s opaque pixels, centered on its origin as a Sprite of that image draws (scale them with the node if the sprite is scaled).

Inputs

  • body: The body (or any node: the shapes join its nearest body)
  • texture: The image, a path relative to the project (an Aseprite file’s first frame too)
  • threshold: The least alpha that counts as solid
  • tolerance: How far the outline may stray from the pixels (larger: fewer points)

Returns

  • How many shapes were made (0 when the image couldn’t be read or has nothing opaque)

Example

// Given: rock: ^e.StaticBody
e.CollisionShapes_FromTexture(rock, "assets/rock.png")

procedure. Source: collision_trace.odin:224

DEFAULT_PHYSICS :: PhysicsSettings{pixelsPerMeter = 100, gravity = {0, 980}, smoothing = true}

The settings a world uses when the game has none: 100 px per meter, Earth’s gravity, smoothing on

constant. Source: physics.odin:42

PHYSICS_TIMESTEP :: 1.0 / 60.0

Seconds per physics step (60 per second)

constant. Source: physics.odin:45

PHYSICS_SUBSTEPS :: 4

Box2D substeps per step: more is stiffer stacking at more cost

constant. Source: physics.odin:47

PHYSICS_MAX_STEPS :: 5

Steps a frame may run to catch up, so a slow frame doesn’t make the next one slower

constant. Source: physics.odin:49

MOVER_SKIN :: 1.0

How close (pixels) a CharacterMover must be to something to count as touching it

constant. Source: physics.odin:51

PHYSICS_ALL_LAYERS :: max(u64)

Every collision layer: the default collision mask

constant. Source: physics_nodes.odin:62

COLLISION_POLYGON_MAX_POINTS :: 8

The most points a Polygon shape keeps (Box2D’s limit)

constant. Source: physics_nodes.odin:194

DEFAULT_PHYSICS_MATERIAL :: PhysicsMaterial{friction = 0.6, restitution = 0, density = 1}

A material with a body’s defaults

constant. Source: physics_material.odin:36