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.
File overviews
Section titled “File overviews”physics.odin
Section titled “physics.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:
- Bodies whose nodes entered the scene get their Box2D bodies and shapes; bodies whose shapes changed get them rebuilt.
- 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.odin
Section titled “physics_nodes.odin”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 CircleA 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)physics_events.odin
Section titled “physics_events.odin”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).
physics_joints.odin
Section titled “physics_joints.odin”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
bodyAnames 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 withScene_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 angleDistance 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 forceWeld glues the bodies together. A spring (enableSpring, hertz > 0) makes it bendyWheel 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, 0physics_debug.odin
Section titled “physics_debug.odin”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_material.odin
Section titled “physics_material.odin”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 (<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_trace.odin
Section titled “collision_trace.odin”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.
Image_TraceOutlinesfollows the edges of the pixels whose alpha is at leastthreshold(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 thantolerancepixels from the traced edge (Ramer-Douglas-Peucker).Polygon_ConvexPiecescuts an outline into triangles (ear clipping) and merges neighbours while they stay convex and small enough (Hertel-Mehlhorn).Image_CollisionPolygonsdoes both,CollisionShapes_FromTexturemakes 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
Section titled “PhysicsSettings”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
Section titled “PhysicsWorld”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
Section titled “RayHit”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
Section titled “PhysicsBodyKind”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
Section titled “PhysicsLayer”PhysicsLayer :: distinct intA collision layer, 1 to 64
distinct type. Source: physics_nodes.odin:59
PhysicsBody
Section titled “PhysicsBody”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
Section titled “StaticBody”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
Section titled “DynamicBody”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
Section titled “CharacterMover”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
Section titled “CollisionShapeKind”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
Section titled “CollisionShape”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
Section titled “RayCast”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
Section titled “Collision”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
Section titled “BodyContact”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
Section titled “JointKind”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
Section titled “PhysicsMaterial”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
Procedures
Section titled “Procedures”Scene_GetPhysics
Section titled “Scene_GetPhysics”Scene_GetPhysics :: proc(scene: ^Scene) -> ^PhysicsWorldThe 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
Section titled “Physics_SetGravity”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 levelprocedure. Source: physics.odin:120
Physics_RayCast
Section titled “Physics_RayCast”Physics_RayCast :: proc(scene: ^Scene, from, to: [2]f32, mask: u64 = PHYSICS_ALL_LAYERS, exclude: ^PhysicsBody = nil) -> RayHitCasts 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:
hitis 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
Section titled “Physics_QueryPoint”Physics_QueryPoint :: proc(scene: ^Scene, point: [2]f32, mask: u64 = PHYSICS_ALL_LAYERS, includeAreas := false, allocator := context.temp_allocator) -> []^PhysicsBodyThe 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
Section titled “Physics_QueryRect”Physics_QueryRect :: proc(scene: ^Scene, rect: sdl.FRect, mask: u64 = PHYSICS_ALL_LAYERS, includeAreas := false, allocator := context.temp_allocator) -> []^PhysicsBodyThe 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 damagefor body in e.Physics_QueryRect(scene, {blast.x - 64, blast.y - 64, 128, 128}) do hurt(body)procedure. Source: physics.odin:292
Physics_QueryCircle
Section titled “Physics_QueryCircle”Physics_QueryCircle :: proc(scene: ^Scene, center: [2]f32, radius: f32, mask: u64 = PHYSICS_ALL_LAYERS, includeAreas := false, allocator := context.temp_allocator) -> []^PhysicsBodyThe 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 swordfor body in e.Physics_QueryCircle(scene, hero.position, 48, ENEMY_LAYER) do hit(body)procedure. Source: physics.odin:318
Physics_ShapeCast
Section titled “Physics_ShapeCast”Physics_ShapeCast :: proc(scene: ^Scene, radius: f32, from, to: [2]f32, mask: u64 = PHYSICS_ALL_LAYERS, exclude: ^PhysicsBody = nil) -> RayHitSweeps 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:
pointandnormalwhere the circle touches,fractionof 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
Section titled “CharacterMover_Move”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.CharacterMoverhero.velocity.y += 980 * f32(deltaTime) // gravity is the game's to addif hero.onFloor && e.Input_IsKeyJustPressed(.Space) do hero.velocity.y = -500e.CharacterMover_Move(hero, deltaTime)procedure. Source: physics.odin:416
RayCast_ForceUpdate
Section titled “RayCast_ForceUpdate”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
Section titled “Collision_FromEvent”Collision_FromEvent :: proc(data: EventData) -> ^CollisionThe 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.Areacoin.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
Section titled “PhysicsLayers”PhysicsLayers :: proc(layers: ..PhysicsLayer) -> u64A 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.PhysicsBodybody.collisionLayer = e.PhysicsLayers(2)body.collisionMask = e.PhysicsLayers(1, 3)procedure. Source: physics_nodes.odin:315
CreateStaticBody
Section titled “CreateStaticBody”CreateStaticBody :: proc(scene: ^Scene) -> ^StaticBodyCreates 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
Section titled “CreateDynamicBody”CreateDynamicBody :: proc(scene: ^Scene) -> ^DynamicBodyCreates 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
Section titled “CreateCharacterMover”CreateCharacterMover :: proc(scene: ^Scene) -> ^CharacterMoverCreates 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 16procedure. Source: physics_nodes.odin:377
CreateArea
Section titled “CreateArea”CreateArea :: proc(scene: ^Scene) -> ^AreaCreates 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
Section titled “CreateCollisionShape”CreateCollisionShape :: proc(scene: ^Scene, kind: CollisionShapeKind) -> ^CollisionShapeCreates 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
Section titled “CreateRayCast”CreateRayCast :: proc(scene: ^Scene, target: [2]f32 = {0, 50}) -> ^RayCastCreates 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.CharacterMoverfeet := 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
Section titled “Node_IsPhysicsBody”Node_IsPhysicsBody :: proc(node: ^Node) -> boolWhether 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
Section titled “CollisionShape_GetBody”CollisionShape_GetBody :: proc(shape: ^CollisionShape) -> ^PhysicsBodyThe 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
Section titled “PhysicsBody_RebuildShapes”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
Section titled “PhysicsBody_SetLayers”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
Section titled “DynamicBody_GetVelocity”DynamicBody_GetVelocity :: proc(body: ^DynamicBody) -> [2]f32A 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
Section titled “DynamicBody_SetVelocity”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.DynamicBodye.DynamicBody_SetVelocity(ball, {300, -400})procedure. Source: physics_nodes.odin:543
DynamicBody_GetAngularVelocity
Section titled “DynamicBody_GetAngularVelocity”DynamicBody_GetAngularVelocity :: proc(body: ^DynamicBody) -> f32A 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
Section titled “DynamicBody_SetAngularVelocity”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
Section titled “DynamicBody_ApplyImpulse”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/se.DynamicBody_ApplyImpulse(crate, e.DynamicBody_GetMass(crate) * [2]f32{150, -260})procedure. Source: physics_nodes.odin:591
DynamicBody_ApplyForce
Section titled “DynamicBody_ApplyForce”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
Section titled “DynamicBody_GetMass”DynamicBody_GetMass :: proc(body: ^DynamicBody) -> f32A 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
Section titled “Area_GetOverlaps”Area_GetOverlaps :: proc(area: ^Area, allocator := context.temp_allocator) -> []^PhysicsBodyThe 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
Section titled “PhysicsBody_GetAreas”PhysicsBody_GetAreas :: proc(body: ^PhysicsBody, allocator := context.temp_allocator) -> []^AreaThe 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.CharacterMoverfor area in e.PhysicsBody_GetAreas(hero) do if area.name == "Water" do hero.velocity *= 0.9procedure. Source: physics_events.odin:56
PhysicsBody_IsInArea
Section titled “PhysicsBody_IsInArea”PhysicsBody_IsInArea :: proc(body: ^PhysicsBody, area: ^Area) -> boolWhether 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
Section titled “PhysicsBody_IsTouching”PhysicsBody_IsTouching :: proc(body, other: ^PhysicsBody) -> boolWhether 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
Section titled “PhysicsBody_GetContacts”PhysicsBody_GetContacts :: proc(body: ^PhysicsBody, allocator := context.temp_allocator) -> []^PhysicsBodyThe 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
Section titled “CreateJoint”CreateJoint :: proc(scene: ^Scene, kind: JointKind) -> ^JointCreates 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 (
offsetis {0, 100},dampingRatio0.5)
Example
// Given: car: ^e.DynamicBodywheel := e.CreateJoint(scene, .Wheel)wheel.position = {-40, 30} // relative to the car, which is body Awheel.bodyB = "Car/FrontWheel"wheel.enableSpring, wheel.hertz = true, 4wheel.enableMotor, wheel.maxMotor = true, 5e6e.Node_AddChild(car, wheel)procedure. Source: physics_joints.odin:125
Joint_IsActive
Section titled “Joint_IsActive”Joint_IsActive :: proc(joint: ^Joint) -> boolWhether 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
Section titled “Joint_SetMotorSpeed”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.Jointe.Joint_SetMotorSpeed(wheel, e.Input_GetAxis("left", "right") * 720)procedure. Source: physics_joints.odin:166
Joint_GetAngle
Section titled “Joint_GetAngle”Joint_GetAngle :: proc(joint: ^Joint) -> f32A 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
Section titled “Joint_GetTranslation”Joint_GetTranslation :: proc(joint: ^Joint) -> f32A 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
Section titled “Joint_GetForce”Joint_GetForce :: proc(joint: ^Joint) -> [2]f32How 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.Jointif linalg.length(e.Joint_GetForce(rope)) > 50000 do e.Scene_RemoveNode(scene, rope.id)procedure. Source: physics_joints.odin:236
Joint_GetAnchors
Section titled “Joint_GetAnchors”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:offsetaway) - ok: false before the joint is made
procedure. Source: physics_joints.odin:252
Physics_SetDebugDraw
Section titled “Physics_SetDebugDraw”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
Section titled “Node_GetRenderLocalTransform”Node_GetRenderLocalTransform :: proc(node: ^Node) -> Transform2DA 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
Section titled “Node_GetRenderWorldTransform”Node_GetRenderWorldTransform :: proc(node: ^Node) -> Transform2DA 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 smoothlyshadowAt := e.Node_GetRenderWorldTransform(ball).origin + {0, 20}procedure. Source: physics_debug.odin:84
ParsePhysicsMaterial
Section titled “ParsePhysicsMaterial”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
Section titled “FormatPhysicsMaterial”FormatPhysicsMaterial :: proc(m: PhysicsMaterial, allocator := context.allocator) -> stringWrites a material file.
Returns
- The file’s text (allocated with
allocator)
procedure. Source: physics_material.odin:73
Game_LoadPhysicsMaterial
Section titled “Game_LoadPhysicsMaterial”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.xmlfile, relative to the project
Returns
- The material, and false when it couldn’t be read
procedure. Source: physics_material.odin:89
PhysicsBody_SetMaterial
Section titled “PhysicsBody_SetMaterial”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.DynamicBodye.PhysicsBody_SetMaterial(puck, {friction = 0.02, restitution = 0.9, density = 0.5})procedure. Source: physics_material.odin:120
Image_TraceOutlines
Section titled “Image_TraceOutlines”Image_TraceOutlines :: proc(pixels: []u8, width, height: int, threshold: u8 = 128, tolerance: f32 = 1.5, allocator := context.temp_allocator) -> [][][2]f32The outer outlines of an image’s opaque regions.
Inputs
- pixels: RGBA8 rows,
width * height * 4bytes - 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: []u8outlines := e.Image_TraceOutlines(pixels, 64, 32, threshold = 128, tolerance = 1.5)procedure. Source: collision_trace.odin:43
Polygon_ConvexPieces
Section titled “Polygon_ConvexPieces”Polygon_ConvexPieces :: proc(outline: [][2]f32, max_points := COLLISION_POLYGON_MAX_POINTS, allocator := context.temp_allocator) -> [][][2]f32Cuts 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
Section titled “Image_CollisionPolygons”Image_CollisionPolygons :: proc(pixels: []u8, width, height: int, threshold: u8 = 128, tolerance: f32 = 1.5, offset: [2]f32 = {}, allocator := context.temp_allocator) -> [][][2]f32The 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: []u8pieces := e.Image_CollisionPolygons(pixels, 64, 32, offset = {-32, -16})procedure. Source: collision_trace.odin:196
CollisionShapes_FromTexture
Section titled “CollisionShapes_FromTexture”CollisionShapes_FromTexture :: proc(body: ^Node, texture: string, threshold: u8 = 128, tolerance: f32 = 1.5) -> intGives 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.StaticBodye.CollisionShapes_FromTexture(rock, "assets/rock.png")procedure. Source: collision_trace.odin:224
Constants and variables
Section titled “Constants and variables”DEFAULT_PHYSICS
Section titled “DEFAULT_PHYSICS”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
Section titled “PHYSICS_TIMESTEP”PHYSICS_TIMESTEP :: 1.0 / 60.0Seconds per physics step (60 per second)
constant. Source: physics.odin:45
PHYSICS_SUBSTEPS
Section titled “PHYSICS_SUBSTEPS”PHYSICS_SUBSTEPS :: 4Box2D substeps per step: more is stiffer stacking at more cost
constant. Source: physics.odin:47
PHYSICS_MAX_STEPS
Section titled “PHYSICS_MAX_STEPS”PHYSICS_MAX_STEPS :: 5Steps 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
Section titled “MOVER_SKIN”MOVER_SKIN :: 1.0How close (pixels) a CharacterMover must be to something to count as touching it
constant. Source: physics.odin:51
PHYSICS_ALL_LAYERS
Section titled “PHYSICS_ALL_LAYERS”PHYSICS_ALL_LAYERS :: max(u64)Every collision layer: the default collision mask
constant. Source: physics_nodes.odin:62
COLLISION_POLYGON_MAX_POINTS
Section titled “COLLISION_POLYGON_MAX_POINTS”COLLISION_POLYGON_MAX_POINTS :: 8The most points a Polygon shape keeps (Box2D’s limit)
constant. Source: physics_nodes.odin:194
DEFAULT_PHYSICS_MATERIAL
Section titled “DEFAULT_PHYSICS_MATERIAL”DEFAULT_PHYSICS_MATERIAL :: PhysicsMaterial{friction = 0.6, restitution = 0, density = 1}A material with a body’s defaults
constant. Source: physics_material.odin:36
