mooncollider

2D game physics and collision detection library for MoonBit

physics
collision
game
2d
gamedev
moon add uiwcvb/mooncollider@0.2.1
Download zip
Author
Version
0.2.1
License
Apache-2.0
Last updated
last month
Downloads
14
README

#MoonCollider

Website Live Demo License MoonBit Tests

A 2D game physics and collision detection library for MoonBit.

MoonCollider provides vector math, shape geometry, narrowphase collision detection (SAT + GJK/EPA), raycasting, four broadphase acceleration structures, a sequential-impulse rigid-body solver with friction and positional correction, joint constraints (distance/revolute/weld), and continuous collision detection (CCD). It is pure logic — no rendering — so it runs on every MoonBit target (wasm-gc, js, native).

#Features

  • Vector mathVec2 with add/sub/scale/dot/cross/length/normalize/ rotate/perp.
  • ShapesAABB, Circle, convex Polygon (with convex_hull, regular, box builders), and a Shape enum for generic dispatch.
  • Narrowphase — pairwise collision with 2-point contact manifolds:
    • All 9 shape pairs (AABB/Circle/Polygon) via Separating Axis Theorem
    • collide_gjk(a, b) — GJK + EPA for arbitrary convex shapes via support functions
  • Raycast — ray vs AABB / Circle / Polygon, returning parametric t, hit point, and surface normal.
  • Broadphase — four options:
    • GridHash — uniform spatial hash grid
    • QuadTree — loose quadtree
    • SweepAndPrune — single-axis sort and sweep
    • AABBTree — dynamic AABB tree with insert/remove/update
  • Rigid bodyRigidBody with mass, inertia, restitution, friction, damping, static/dynamic types; World::step integrates forces, runs broadphase + narrowphase, and resolves collisions with sequential impulses, Coulomb friction, and Baumgarte positional correction.
  • JointsDistance, Revolute, and Weld constraints with soft Baumgarte bias.
  • CCD — continuous collision detection via sweep tests (Circle/AABB) to prevent tunneling at high velocities.
  • Body removalWorld::remove_body(id) marks bodies inactive; inactive bodies are skipped in all simulation phases.

#Installation

moon add uiwcvb/mooncollider

#Quick start

///|
fn main {
let world = @mooncollider.World::new()
// A dynamic circle (the ball).
let _ = world.add_body(
@mooncollider.BodyDef::dynamic(
@mooncollider.Vec2::new(0.0, 10.0),
@mooncollider.Shape::from_circle(
@mooncollider.Circle::new(@mooncollider.Vec2::zero(), 0.5),
),
1.0, // mass
0.8, // restitution (bounciness)
),
)
// A static floor.
let _ = world.add_body(
@mooncollider.BodyDef::static_(
@mooncollider.Vec2::new(0.0, 0.0),
@mooncollider.Shape::from_aabb(
@mooncollider.AABB::from_min_max(
@mooncollider.Vec2::new(-10.0, -1.0),
@mooncollider.Vec2::new(10.0, 0.0),
),
),
),
)
// Step the simulation.
for i = 0; i < 200; i = i + 1 {
world.step(0.016)
}
}

#Using individual pieces

#Collision detection without a world

let a = @mooncollider.Shape::from_aabb(
@mooncollider.AABB::from_min_max(
@mooncollider.Vec2::new(0.0, 0.0),
@mooncollider.Vec2::new(2.0, 2.0),
),
)
let b = @mooncollider.Shape::from_circle(
@mooncollider.Circle::new(@mooncollider.Vec2::new(3.0, 1.0), 1.5),
)
let m = @mooncollider.collide(a, b)
if m.colliding() {
// m.normal points from a to b; m.depth is the penetration.
}

#Raycasting

let ray = @mooncollider.Ray::from_to(
@mooncollider.Vec2::new(-5.0, 0.0),
@mooncollider.Vec2::new(5.0, 0.0),
)
let hit = @mooncollider.raycast(
ray,
@mooncollider.Shape::from_circle(
@mooncollider.Circle::new(@mooncollider.Vec2::zero(), 1.0),
),
)
if hit.hit() {
// hit.point, hit.normal, hit.t
}

#Broadphase

let grid = @mooncollider.GridHash::new(2.0)
grid.insert(0, aabb_a)
grid.insert(1, aabb_b)
let pairs = grid.pairs() // Array[(Int, Int)], each (a, b) with a < b

#Run the demos

moon run cmd/main # bouncing ball under gravity moon run cmd/stacking # column of boxes settling into a stack moon run cmd/raycast # rays cast at a scene of shapes moon run cmd/pendulum # distance-joint pendulum moon run cmd/ccd_stress # CCD tunneling comparison moon run cmd/perf # performance benchmarks moon run examples/pairs # batch collision queries without a world

#Tests

moon test

The suite covers vector math, all shape operations, every narrowphase pair, GJK+EPA, raycasting, all four broadphase structures, the rigid-body world (gravity, bounce, friction, stacking), joints, CCD, plus adversarial fuzz testing (random shape storms, degenerate geometry, NaN/Inf detection, energy divergence checks) and cross-platform determinism snapshots.

#Website

An interactive physics sandbox powered by the real MoonCollider engine (compiled to JavaScript) is at https://uiwcvb.github.io/mooncollider/. Click to add balls/boxes, drag to throw, toggle gravity.

#Verification

The project passes the MoonBit competition hard requirements:

moon check moon test moon info && git diff --exit-code

And the soft bonus:

moon fmt && git diff --exit-code

#Project layout

mooncollider/ moon.mod / moon.pkg module + package config vec2.mbt 2D vector math shape.mbt AABB, Circle, Polygon, Shape narrowphase.mbt collision detection + Manifold gjk.mbt GJK + EPA generic convex collision raycast.mbt ray vs shape broadphase.mbt GridHash + QuadTree broadphase_sap.mbt SweepAndPrune + AABBTree body.mbt RigidBody + BodyDef world.mbt World::step + collision resolution joints.mbt Distance / Revolute / Weld joints ccd.mbt continuous collision detection mooncollider_test.mbt blackbox tests (public API) mooncollider_wbtest.mbt whitebox tests (internal helpers) mooncollider_fuzz_wbtest.mbt adversarial fuzz testing cmd/main/ bounce demo cmd/stacking/ stacking demo cmd/raycast/ raycast demo cmd/pendulum/ pendulum demo cmd/ccd_stress/ CCD tunneling comparison cmd/perf/ performance benchmarks cmd/web/ JS bindings for the website sandbox examples/pairs/ batch collision query example docs/ website + design notes

#License

Apache-2.0.

#References

This project implements well-known physics algorithms from scratch in MoonBit. No source code was ported from the following references; they were used as algorithmic guidance only:

  • Box2D by Erin Catto — sequential-impulse solver, Baumgarte position correction, contact manifold generation, joint constraints.

  • Real-Time Collision Detection by Christer Ericson (Morgan Kaufmann, 2004) — SAT, GJK, EPA, closest-point algorithms, sweeping.
    • Source: Book (ISBN 978-1558607323)
    • License: Published book (proprietary text; algorithms are public domain mathematics)
    • Reference scope: narrowphase algorithms (SAT, GJK, EPA), CCD sweep theory

#
AABB

pub(all) struct AABB {
center : Vec2
half : Vec2
} derive(Eq,
Debug
)

Axis-aligned bounding box.

#
AABB::area

fn AABB::area(self : AABB) -> Double

Area.

#
AABB::contains

fn AABB::contains(self : AABB, p : Vec2) -> Bool

Does this AABB contain a point?

#
AABB::expand_point

fn AABB::expand_point(self : AABB, p : Vec2) -> AABB

Expand this AABB to include a point. Returns a new AABB.

#
AABB::from_min_max

fn AABB::from_min_max(min : Vec2, max : Vec2) -> AABB

Construct an AABB from min/max corners (inclusive).

#
AABB::height

fn AABB::height(self : AABB) -> Double

Height (full).

#
AABB::max

fn AABB::max(self : AABB) -> Vec2

Max corner.

#
AABB::min

fn AABB::min(self : AABB) -> Vec2

Min corner.

#
AABB::new

fn AABB::new(center : Vec2, half : Vec2) -> AABB

Construct an AABB from center and half-extents.

#
AABB::overlaps

fn AABB::overlaps(self : AABB, other : AABB) -> Bool

Does this AABB overlap another (touching counts as overlap)?

#
AABB::perimeter

fn AABB::perimeter(self : AABB) -> Double

Perimeter.

#
AABB::surface_area

fn AABB::surface_area(self : AABB) -> Double

Surface area.

#
AABB::union

fn AABB::union(self : AABB, other : AABB) -> AABB

Union of two AABBs.

#
AABB::width

fn AABB::width(self : AABB) -> Double

Width (full).

#
AABBTree

pub(all) struct AABBTree {
root : Int
nodes : Array[TreeNode]
free_list : Array[Int]
body_ids : Array[Int]
}

Dynamic AABB tree broadphase.

A binary tree of AABBs where each leaf is a body's AABB and each internal node holds the union of its children. Insertion chooses the sibling that minimizes the resulting union volume; removal rebalances lazily. Supports incremental updates (move a body's AABB without rebuilding the whole tree), making it well-suited for worlds with many moving bodies of varying size.

#
AABBTree::insert

fn AABBTree::insert(self : AABBTree, body_id : Int, box : AABB) -> Int

Insert a body's AABB as a leaf. Returns the leaf node id.

#
AABBTree::new

fn AABBTree::new() -> AABBTree

Construct an empty AABB tree.

#
AABBTree::pairs

fn AABBTree::pairs(self : AABBTree) -> Array[(Int, Int)]

Query candidate pairs by intersecting all leaf AABBs.

#
AABBTree::remove

fn AABBTree::remove(self : AABBTree, leaf : Int) -> Unit

Remove a leaf node by its node id (returned from insert). The leaf is detached from the tree and its parent collapsed. The node id is added to the free list for reuse.

#
AABBTree::size

fn AABBTree::size(self : AABBTree) -> Int

Number of nodes (leaves + internal).

#
AABBTree::update

fn AABBTree::update(self : AABBTree, leaf : Int, box : AABB) -> Unit

Update a leaf's AABB. If the new box is still contained in the leaf's current node box, just updates the leaf; otherwise removes and re-inserts for proper rebalancing.

#
BodyDef

pub(all) struct BodyDef {
body_type : BodyType
position : Vec2
angle : Double
shape : Shape
mass : Double
restitution : Double
friction : Double
linear_damping : Double
angular_damping : Double
gravity_scale : Double
}

Body construction parameters.

#
BodyDef::dynamic

fn BodyDef::dynamic(position : Vec2, shape : Shape, mass : Double, restitution : Double) -> BodyDef

Construct a dynamic body definition.

#
BodyDef::static_

fn BodyDef::static_(position : Vec2, shape : Shape) -> BodyDef

Construct a static body definition.

#
BodyType

pub enum BodyType {
Static
Dynamic
}

Rigid body type. Static bodies have infinite mass and don't move.

#
CCDSweep

pub(all) struct CCDSweep {
t : Double
point : Vec2
normal : Vec2
hit : Bool
}

Result of a CCD sweep: the time of impact t in [0, 1] along the sweep, the hit point, and the surface normal (pointing from B to A, opposing A's motion).

#
CCDSweep::miss

fn CCDSweep::miss() -> CCDSweep

A miss result.

#
CellKey

pub(all) struct CellKey {
cx : Int
cy : Int
} derive(Eq, Hash)

A 2D integer cell coordinate, used as a hashmap key.

#
Circle

pub(all) struct Circle {
center : Vec2
radius : Double
} derive(Eq,
Debug
)

Circle.

#
Circle::contains

fn Circle::contains(self : Circle, p : Vec2) -> Bool

Does this circle contain a point?

#
Circle::new

fn Circle::new(center : Vec2, radius : Double) -> Circle

Construct a circle.

#
DistanceJoint

pub(all) struct DistanceJoint {
a : Int
b : Int
local_anchor_a : Vec2
local_anchor_b : Vec2
length : Double
stiffness : Double
}

#
DistanceJoint::from_world

fn DistanceJoint::from_world(world : World, a : Int, b : Int, world_anchor_a : Vec2, world_anchor_b : Vec2, length? : Double) -> DistanceJoint

Construct a distance joint between two bodies. The anchors are given in world space and converted to local space automatically.

#
GridHash

pub(all) struct GridHash {
cell_size : Double
cells : Map[CellKey, Array[Int]]
}

Uniform spatial hash grid broadphase.

Maps each shape's AABB to integer cells of a fixed size, recording every pair of IDs that share at least one cell. Best for worlds where objects are roughly uniformly distributed and similar in size.

#
GridHash::clear

fn GridHash::clear(self : GridHash) -> Unit

Clear all entries.

#
GridHash::insert

fn GridHash::insert(self : GridHash, id : Int, box : AABB) -> Unit

Insert a body ID with the given AABB. Adds the ID to every cell the AABB overlaps.

#
GridHash::new

fn GridHash::new(cell_size : Double) -> GridHash

Construct a grid with the given cell size.

#
GridHash::pairs

fn GridHash::pairs(self : GridHash) -> Array[(Int, Int)]

Compute all candidate collision pairs. Each pair (a, b) has a < b and is

#
Joint

pub enum Joint {
Distance(DistanceJoint)
Revolute(RevoluteJoint)
Weld(WeldJoint)
}

#
Joint::distance

fn Joint::distance(j : DistanceJoint) -> Joint

Wrap a distance joint into a Joint.

#
Joint::revolute

fn Joint::revolute(j : RevoluteJoint) -> Joint

Wrap a revolute joint into a Joint.

#
Joint::weld

fn Joint::weld(j : WeldJoint) -> Joint

Wrap a weld joint into a Joint.

#
Manifold

pub(all) struct Manifold {
normal : Vec2
depth : Double
contact : Vec2
contact2 : Vec2
} derive(Eq,
Debug
)

Contact manifold: the result of a narrowphase collision query.

normal points from A to B (the direction B should be pushed along to separate). depth is the penetration depth. contact is a representative contact point (the deepest overlap point), useful for torque computation. contact2 is an optional second contact point for edge/edge collisions (AABB-AABB face contacts), improving stacking stability.

#
Manifold::colliding

fn Manifold::colliding(self : Manifold) -> Bool

Did the shapes collide?

#
Manifold::has_second_contact

fn Manifold::has_second_contact(self : Manifold) -> Bool

Does this manifold have a second contact point?

#
Manifold::new

fn Manifold::new(normal : Vec2, depth : Double, contact : Vec2) -> Manifold

Build a manifold with a single contact point.

#
Manifold::new2

fn Manifold::new2(normal : Vec2, depth : Double, contact : Vec2, contact2 : Vec2) -> Manifold

Build a manifold with two contact points.

#
Manifold::none

fn Manifold::none() -> Manifold

An empty manifold (no collision). normal is zero, depth is 0.

#
Polygon

pub(all) struct Polygon {
vertices : Array[Vec2]
normals : Array[Vec2]
} derive(Eq,
Debug
)

Convex polygon. Vertices are in world space, counter-clockwise.

#
Polygon::aabb

fn Polygon::aabb(self : Polygon) -> AABB

Bounding AABB of this polygon.

#
Polygon::box

fn Polygon::box(min : Vec2, max : Vec2) -> Polygon

Construct a box polygon (axis-aligned) from min/max corners.

#
Polygon::convex_hull

fn Polygon::convex_hull(points : Array[Vec2]) -> Polygon

Build a convex polygon from vertices. The vertices are reduced to their convex hull and ordered counter-clockwise. Fewer than 3 unique points returns an empty polygon (no vertices).

#
Polygon::from_vertices_ccw

fn Polygon::from_vertices_ccw(vertices : Array[Vec2]) -> Polygon

Build a polygon from counter-clockwise vertices, computing normals. Vertices are assumed to already be in CCW order and convex.

#
Polygon::regular

fn Polygon::regular(center : Vec2, radius : Double, n : Int) -> Polygon

Construct a regular polygon (n-gon) centered at center with given radius (distance from center to each vertex).

#
Polygon::size

fn Polygon::size(self : Polygon) -> Int

Number of vertices.

#
QuadTree

pub(all) struct QuadTree {
bounds : AABB
capacity : Int
max_depth : Int
depth : Int
items : Array[(Int, AABB)]
children : Array[QuadTree]?
}

Quadtree broadphase.

A loose quadtree: each node covers a square region and subdivides when it holds more than capacity items. Items are stored at the deepest node whose region fully contains their AABB, falling back to the node itself if none fits (so large objects are always reported).

#
QuadTree::insert

fn QuadTree::insert(self : QuadTree, id : Int, box : AABB) -> Unit

Insert a body ID with the given AABB.

#
QuadTree::new

fn QuadTree::new(bounds : AABB, capacity : Int, max_depth : Int) -> QuadTree

Construct a quadtree covering the given square region.

#
QuadTree::pairs

fn QuadTree::pairs(self : QuadTree) -> Array[(Int, Int)]

Query all candidate pairs. Walks the tree, pairing items whose AABBs overlap. The quadtree has already culled far-apart objects by only returning items whose ancestor nodes overlap the query region.

#
Ray

pub(all) struct Ray {
origin : Vec2
direction : Vec2
} derive(Eq,
Debug
)

A ray: origin + direction. Direction need not be normalized; t results are parametric (distance / |direction|).

#
Ray::from_to

fn Ray::from_to(origin : Vec2, target : Vec2) -> Ray

Construct a ray from origin + a unit direction and a max distance.

#
Ray::new

fn Ray::new(origin : Vec2, direction : Vec2) -> Ray

Construct a ray.

#
RaycastHit

pub(all) struct RaycastHit {
t : Double
point : Vec2
normal : Vec2
} derive(Eq,
Debug
)

Ray hit result. t is the parametric distance along the ray's direction (so the world-space hit point is ray.origin + ray.direction.scale(t)). normal is the surface normal at the hit (unit length, pointing back towards the ray origin).

#
RaycastHit::hit

fn RaycastHit::hit(self : RaycastHit) -> Bool

Did the ray hit?

#
RaycastHit::new

fn RaycastHit::new(t : Double, point : Vec2, normal : Vec2) -> RaycastHit

Build a hit result.

#
RevoluteJoint

pub(all) struct RevoluteJoint {
a : Int
b : Int
local_anchor_a : Vec2
local_anchor_b : Vec2
}

#
RevoluteJoint::from_world

fn RevoluteJoint::from_world(world : World, a : Int, b : Int, world_anchor : Vec2) -> RevoluteJoint

Construct a revolute joint pinning two bodies at a world-space point.

#
RigidBody

pub(all) struct RigidBody {
id : Int
body_type : BodyType
position : Vec2
angle : Double
velocity : Vec2
angular_velocity : Double
force : Vec2
torque : Double
mass : Double
inv_mass : Double
inertia : Double
inv_inertia : Double
restitution : Double
friction : Double
linear_damping : Double
angular_damping : Double
shape : Shape
aabb : AABB
gravity_scale : Double
alive : Bool
}

A 2D rigid body. Stores a shape (in local space centered at the body origin) plus kinematic state. The shape's world position is derived from position each step.

#
RigidBody::apply_force

fn RigidBody::apply_force(self : RigidBody, f : Vec2) -> Unit

Apply a force at the body center (accumulates; cleared each step).

#
RigidBody::apply_linear_impulse

fn RigidBody::apply_linear_impulse(self : RigidBody, impulse : Vec2) -> Unit

Apply an impulse at the center (immediately changes velocity).

#
RigidBody::from_def

fn RigidBody::from_def(id : Int, def : BodyDef) -> RigidBody

Construct a body from a definition.

#
RigidBody::is_static

fn RigidBody::is_static(self : RigidBody) -> Bool

Is this body static (immovable)?

#
RigidBody::update_aabb

fn RigidBody::update_aabb(self : RigidBody) -> Unit

Update the cached world AABB from the current position and orientation.

#
RigidBody::world_shape

fn RigidBody::world_shape(self : RigidBody) -> Shape

Build the shape in world space (applying position and orientation).

#
Shape

pub enum Shape {
AABB(AABB)
Circle(Circle)
Polygon(Polygon)
}

Shape kinds supported by MoonCollider.

A Shape is geometry without a body: it lives in world space and is used directly by the narrowphase and raycast APIs. The rigid-body layer wraps shapes with mass and velocity (see body.mbt).

#
Shape::aabb

fn Shape::aabb(self : Shape) -> AABB

Bounding AABB of a shape.

#
Shape::from_aabb

fn Shape::from_aabb(aabb : AABB) -> Shape

Construct a shape from an AABB.

#
Shape::from_circle

fn Shape::from_circle(c : Circle) -> Shape

Construct a shape from a Circle.

#
Shape::from_polygon

fn Shape::from_polygon(p : Polygon) -> Shape

Construct a shape from a Polygon.

#
Shape::support

fn Shape::support(self : Shape, d : Vec2) -> Vec2

A support function maps a direction d to the farthest point of a convex shape along d. GJK only needs this to reason about a shape.

#
SweepAndPrune

pub(all) struct SweepAndPrune {
entries : Array[(Int, AABB)]
}

Sweep-and-Prune broadphase along a single axis (x by default).

Sorts body AABBs by their min-x coordinate, then for each body walks forward while the next min-x is still below this body's max-x, reporting overlapping pairs. Best for worlds where objects are spread along one axis (side-scrollers, platforms) or where insertion order is already roughly sorted.

#
SweepAndPrune::clear

fn SweepAndPrune::clear(self : SweepAndPrune) -> Unit

Clear all entries.

#
SweepAndPrune::insert

fn SweepAndPrune::insert(self : SweepAndPrune, id : Int, box : AABB) -> Unit

Add a body AABB. Does not keep sorted; call pairs to rebuild.

#
SweepAndPrune::new

Construct an empty SAP structure.

#
SweepAndPrune::pairs

fn SweepAndPrune::pairs(self : SweepAndPrune) -> Array[(Int, Int)]

Compute candidate pairs by sorting on min-x and sweeping.

#
TreeNode

pub(all) struct TreeNode {
box : AABB
parent : Int
left : Int
right : Int
body_id : Int
height : Int
}

A tree node. Leaves have body_id >= 0 and left == right == -1; internal nodes have body_id == -1 and two children.

#
Vec2

pub(all) struct Vec2 {
x : Double
y : Double
} derive(Eq,
Debug
)

2D vector. Core math type for all geometry in MoonCollider.

#
Vec2::add

fn Vec2::add(self : Vec2, other : Vec2) -> Vec2

Vector addition.

#
Vec2::approx_eq

fn Vec2::approx_eq(self : Vec2, other : Vec2, eps : Double) -> Bool

Approximate equality within eps.

#
Vec2::cross

fn Vec2::cross(self : Vec2, other : Vec2) -> Double

2D cross product (scalar): self.x * other.y - self.y * other.x.

#
Vec2::dot

fn Vec2::dot(self : Vec2, other : Vec2) -> Double

Dot product.

#
Vec2::length

fn Vec2::length(self : Vec2) -> Double

Length.

#
Vec2::length_sq

fn Vec2::length_sq(self : Vec2) -> Double

Squared length (cheap, no sqrt).

#
Vec2::neg

fn Vec2::neg(self : Vec2) -> Vec2

Negate.

#
Vec2::new

fn Vec2::new(x : Double, y : Double) -> Vec2

Construct a vector.

#
Vec2::normalize

fn Vec2::normalize(self : Vec2) -> Vec2

Normalize to unit length. Returns zero vector if length is zero.

#
Vec2::perp

fn Vec2::perp(self : Vec2) -> Vec2

Perpendicular vector (rotated +90 degrees).

#
Vec2::rotate

fn Vec2::rotate(self : Vec2, angle : Double) -> Vec2

Rotate by angle radians (counter-clockwise).

#
Vec2::scale

fn Vec2::scale(self : Vec2, s : Double) -> Vec2

Scale by a scalar.

#
Vec2::sub

fn Vec2::sub(self : Vec2, other : Vec2) -> Vec2

Vector subtraction.

#
Vec2::unit_x

fn Vec2::unit_x() -> Vec2

Unit vector along +x.

#
Vec2::unit_y

fn Vec2::unit_y() -> Vec2

Unit vector along +y.

#
Vec2::zero

fn Vec2::zero() -> Vec2

Zero vector.

#
WeldJoint

pub(all) struct WeldJoint {
a : Int
b : Int
local_anchor_a : Vec2
local_anchor_b : Vec2
ref_angle : Double
}

#
WeldJoint::from_world

fn WeldJoint::from_world(world : World, a : Int, b : Int, world_anchor : Vec2) -> WeldJoint

Construct a weld joint between two bodies at a world-space point.

#
World

pub(all) struct World {
gravity : Vec2
bodies : Array[RigidBody]
joints : Array[Joint]
grid_cell_size : Double
velocity_iterations : Int
position_correction : Double
slop : Double
}

The physics world. Owns bodies, applies gravity, integrates, runs broadphase + narrowphase, and resolves collisions.

#
World::add_body

fn World::add_body(self : World, def : BodyDef) -> Int

Add a body to the world. Returns the body id.

#
World::add_joint

fn World::add_joint(self : World, joint : Joint) -> Unit

Add a joint to the world.

#
World::body

fn World::body(self : World, id : Int) -> RigidBody

Get a body by id.

#
World::body_count

fn World::body_count(self : World) -> Int

Number of bodies.

#
World::joint_count

fn World::joint_count(self : World) -> Int

Number of joints.

#
World::new

fn World::new() -> World

Construct an empty world with sensible defaults.

#
World::remove_body

fn World::remove_body(self : World, id : Int) -> Unit

Remove a body by id (marks it as inactive). The body slot is reused for future additions to keep id stability.

#
World::set_gravity

fn World::set_gravity(self : World, g : Vec2) -> Unit

Set the gravity vector.

#
World::set_grid_cell_size

fn World::set_grid_cell_size(self : World, s : Double) -> Unit

Set the broadphase grid cell size.

#
World::step

fn World::step(self : World, dt : Double) -> Unit

Advance the simulation by dt seconds.

#
ccd_sweep_aabb

fn ccd_sweep_aabb(half : Vec2, start_center : Vec2, delta : Vec2, b : Shape) -> CCDSweep

Sweep an AABB from start_center by delta against a static shape b. Uses sub-stepping for all shape types.

#
ccd_sweep_circle

fn ccd_sweep_circle(radius : Double, start : Vec2, delta : Vec2, b : Shape) -> CCDSweep

Sweep a circle from start to start + delta against a static shape b. Returns the earliest time of impact in [0, 1], or a miss.

For circle-vs-circle and circle-vs-AABB this uses a closed-form ray cast on the Minkowski-expanded shape. For polygons it falls back to a sampled sub-step sweep.

#
collide

fn collide(a : Shape, b : Shape) -> Manifold

Dispatch a generic shape-vs-shape collision. Normal points from a to b.

#
collide_aabb_aabb

fn collide_aabb_aabb(a : AABB, b : AABB) -> Manifold

AABB vs AABB. Returns a manifold if they overlap, else none.

#
collide_aabb_circle

fn collide_aabb_circle(a : AABB, b : Circle) -> Manifold

AABB vs Circle. Normal points from AABB (A) to Circle (B).

#
collide_aabb_polygon

fn collide_aabb_polygon(a : AABB, b : Polygon) -> Manifold

AABB vs Polygon. Normal points from AABB (A) to Polygon (B).

#
collide_circle_circle

fn collide_circle_circle(a : Circle, b : Circle) -> Manifold

Circle vs Circle.

#
collide_circle_polygon

fn collide_circle_polygon(a : Circle, b : Polygon) -> Manifold

Circle vs Polygon. Normal points from Circle (A) to Polygon (B).

#
collide_gjk

fn collide_gjk(a : Shape, b : Shape) -> Manifold

GJK collision test + EPA penetration recovery. Returns a Manifold (normal from A to B) if the shapes overlap, else Manifold::none().

#
collide_polygon_polygon

fn collide_polygon_polygon(a : Polygon, b : Polygon) -> Manifold

Polygon vs Polygon using the Separating Axis Theorem. Normals point from A to B.

#
raycast

fn raycast(ray : Ray, shape : Shape) -> RaycastHit

Generic ray vs shape dispatch.

#
raycast_aabb

fn raycast_aabb(ray : Ray, box : AABB) -> RaycastHit

Ray vs AABB. Returns the nearest hit in t in [0, +inf), or a miss (t = -1) if the ray does not hit the box.

#
raycast_circle

fn raycast_circle(ray : Ray, c : Circle) -> RaycastHit

Ray vs Circle. Returns nearest hit or miss.

#
raycast_polygon

fn raycast_polygon(ray : Ray, p : Polygon) -> RaycastHit

Ray vs convex Polygon. Returns nearest hit or miss.