Sign in

    moonphys

    A deterministic 2D rigid body physics engine in pure MoonBit: SAT collision detection, sequential impulse solver with warm starting, Coulomb friction, revolute and distance joints, sleeping, and SVG rendering.

    physics
    rigid-body
    collision
    simulation
    game
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    1 hour ago
    Downloads
    1

    No dependencies

    #moonphys

    纯 MoonBit 实现的确定性 2D 刚体物理引擎 — 用 SAT 做碰撞检测,用顺序冲量法求解接触,支持摩擦、恢复系数、关节、睡眠,并能把每一帧渲染成 SVG。

    一个 2D 物理引擎是游戏引擎、物理教学和机器人仿真里最基础的一块拼图,MoonBit 生态此前没有:mooncakes 上的 chipmunk、box2d、verlet、aabb、collision 等关键词全部为空。moonphys 填的是这个空位,而不是又一个业务工具。

    let world = @moonphys.World::new()

    // 静态地面
    ignore(
    world.add_body(
    @moonphys.Body::fixed(@moonphys.Shape::box(10.0, 0.1)).at(0.0, -0.1),
    ),
    )

    // 一个自由下落的方块
    ignore(world.add_body(@moonphys.Body::box(0.5, 0.5).at(0.0, 4.0)))

    // 固定步长推进 2 秒
    for _ in 0..<120 {
    world.step(1.0 / 60.0)
    }

    println(world.body(1).position().y()) // 0.5:稳稳落在有厚度的地面上

    #特性

    模块内容
    数学基座Vec2、Transform、向量运算符重载、Show 实现
    形状圆、任意凸多边形(逆时针),自动计算质心与转动惯量(平行轴定理)
    宽相sort-and-sweep:按 AABB 下界插入排序后扫描,稀疏场景近似线性;tie 按 body 下标打破
    窄相SAT + reference/incident edge 裁剪(圆/圆、多边形/圆、多边形/多边形),返回多点接触
    求解器顺序冲量法 + 持久接触(Arbiter 按 feature id 迁移冲量做暖启动)+ Baumgarte 位置偏置
    摩擦库仑锥,切向冲量钳制在 ±μ·Pn
    恢复系数只在法向相对速度超过阈值时生效,避免静止抖动
    关节DistanceJoint(定长)、RevolveJoint(铰链),用 2×2 有效质量矩阵求解
    睡眠线/角速度低于阈值持续 0.5s 后休眠,接触对的动态体互相唤醒
    确定性无随机、无哈希迭代、固定步长 + 稳定排序 → 两次运行逐位一致(有测试)
    渲染render 包把世界快照输出为 SVG,带网格与配色,可逐帧拼成动画

    #安装

    moon add weopqrst/moonphys

    在需要使用的包的 moon.pkg 中:

    import { "weopqrst/moonphys" @moonphys, }

    #演示

    仓库自带一个把物理结果渲染成动画的生成器,输出不依赖任何外部资源的单文件 HTML(每帧一个内联 SVG,浏览器只负责回放):

    moon run cmd/main --target js > docs/index.html

    生成的页面包含三个场景,各 240 帧、4 秒循环:

    1. Pyramid stack — 6 层 21 个方块的金字塔,左上方斜面滚球(摩擦),三颗弹球砸下来。
    2. Five link chain · double pendulum — 5 节链条与双摆,铰链约束在重力下的混沌运动。
    3. Plank bridge — 13 块木板用距离关节连成的桥,两个静态桥墩,中间砸下三颗球。

    在线预览:GitHub Pages 打开 docs/index.html 即可(仓库 Settings → Pages → Source 选 main + /docs)。

    #API 速览

    // 世界
    let world = @moonphys.World::new() // 重力默认 (0, -9.81)
    world.set_gravity(@moonphys.Vec2::new(0.0, -3.0)) // 低重力
    world.set_velocity_iterations(10) // 迭代次数,越大越硬
    world.set_sleep_enabled(true)
    world.step(1.0 / 60.0) // 固定步长
    world.run(120, 1.0 / 60.0) // 等价于循环 step
    world.time() // 累计仿真时间
    world.arbiter_count() // 当前接触对
    world.snapshot() // 渲染/测试用的不可变快照

    // 刚体
    let body = @moonphys.Body::box(0.5, 0.5) // 动态方块
    .at(0.0, 3.0) // 质心位置
    .with_rotation(0.3) // 弧度
    .with_velocity(1.0, 0.0)
    .with_angular_velocity(-0.5)
    .with_friction(0.6)
    .with_restitution(0.2) // 弹性
    .with_density(1.0) // 质量 = 密度 × 面积
    .with_damping(0.05, 0.05) // 线/角阻尼
    .with_allow_sleep(true)
    .with_user_data(3) // 渲染配色用的自定义标记
    ignore(world.add_body(body))

    // 形状
    @moonphys.Shape::box(0.5, 0.5) // 半宽半高
    @moonphys.Shape::circle(0.4)
    @moonphys.Shape::convex(vertices) // 逆时针凸多边形

    // 力与冲量
    body.apply_force(@moonphys.Vec2::new(0.0, 10.0)) // 本步有效
    body.apply_impulse(@moonphys.Vec2::new(2.0, 0.0), body.position())
    body.point_velocity(body.position())

    // 查询
    body.mass(); body.inertia(); body.aabb(); body.contains_point(p)
    body.local_to_world(p); body.world_to_local(p)

    // 关节
    world.add_distance_joint(index_a, index_b) // 保持当前中心距
    world.add_revolve_joint(index_a, index_b, @moonphys.Vec2::new(0.0, 5.0)) // 世界锚点
    world.add_revolve_joint_local(a, b, local_a, local_b) // 各自的局部锚点

    #实现要点

    碰撞检测:多边形用 SAT 求最大分离轴;分离轴落在 A 上时以 A 为参考面,否则交换 A/B(relative_tol = 0.98、absolute_tol = 0.001 做偏置,避免两帧之间反复翻转)。确定参考面后找入射面,把入射线段裁剪到参考面的侧平面,保留 separation < 0 的点,最多两点。

    求解顺序(World::step):

    1. 同步 AABB → 宽相配对
    2. 窄相生成接触点;命中已有 Arbiter 则按 feature id 迁移旧冲量(暖启动),否则新建
    3. 积分重力、外力、阻尼,清除累积力
    4. 每个 Arbiter / 关节计算有效质量与偏置
    5. 迭代 velocity_iterations 次求解速度约束
    6. 积分位置 → 更新睡眠状态 → 同步 AABB

    为什么是顺序冲量法:它只需要存储每条约束的累计冲量,迭代几次就能让堆叠稳定,代码量比 LCP/投影高斯-赛德尔求解器小一个量级,适合"看得懂、改得动"的库。10 次迭代下 6 层方块塔在 4 秒内不塌、不沉陷(有测试盯着)。

    确定性:不引入随机数;宽相排序的所有 tie 都按下标打破;接触点顺序由裁剪顺序决定。因此同样的构造 + 同样的步长 = 逐位相同的轨迹,world_test.mbt 里有逐位比较的回归测试。

    #测试

    moon test

    覆盖:向量/变换数学、质量属性、AABB、圆与多边形的 7 类碰撞情形(含法向翻转)、自由落体解析解对照、落地静止、堆叠稳定性、恢复系数、睡眠、宽相剪枝、冲量、关节长度与锚点误差、演示场景数值健全性、SVG 输出结构。

    #移植来源与许可

    moonphys 是原创的 MoonBit 实现,算法与工程结构参考了以下 MIT 许可的公开实现,未包含其任何源码:

    • Box2D-Lite — Erin Catto,https://github.com/erincatto/box2d-lite (MIT):Arbiter/顺序冲量、裁剪式多边碰撞的经典教学实现
    • Box2D — Erin Catto,https://github.com/erincatto/box2d (MIT):宽相、睡眠、关节的工业级参考
    • Planck.js — Ali Shakiba,https://github.com/shakiba/planck.js (MIT):JavaScript 移植版,本文的 API 风格参考

    本项目以 Apache-2.0 发布,见 LICENSE。

    #边界与后续

    当前明确不做(欢迎 PR):连续碰撞检测(CCD)、凹多边形分解、关节角度限位与马达、传感器、软约束/布料、多线程求解。已规划的方向:CCD、更多关节类型、与 MoonBit GUI/游戏框架对接的适配层、性能基准(对照 Box2D-Lite 的基准场景)。

    #开发方式

    本项目在 AI 编程助手(CodeBuddy / DeepSeek)的协助下完成:数学基底、SAT 裁剪、顺序冲量与关节求解的算法出处均列在上面的"移植来源"一节,代码是逐模块重写而不是复制;每一处物理行为都有对应的测试(38 项)与可复现的数值断言,因此任何一段实现都能被读、被改、被证伪。若你对某段策略有疑问(例如为什么偏置系数是 0.2、为什么恢复系数有速度阈值),可以直接在 issue 里问,代码里的注释和测试会一起回答。

    Aabb

    pub struct Aabb {
    min : Vec2
    max : Vec2
    }

    Axis-aligned bounding box.

    Aabb::center

    fn Aabb::center(self : Aabb) -> Vec2

    Aabb::contains_point

    fn Aabb::contains_point(self : Aabb, p : Vec2) -> Bool

    Aabb::expanded

    fn Aabb::expanded(self : Aabb, margin : Double) -> Aabb

    Grow the box by margin on every side.

    Aabb::height

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

    Aabb::max

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

    Aabb::min

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

    Aabb::new

    fn Aabb::new(min : Vec2, max : Vec2) -> Aabb

    Aabb::overlaps

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

    Aabb::union

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

    Aabb::width

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

    Arbiter

    pub struct Arbiter {
    body_a : Int
    body_b : Int
    contacts : Array[ContactPoint]
    normal_impulses : Array[Double]
    tangent_impulses : Array[Double]
    mass_normal : Array[Double]
    mass_tangent : Array[Double]
    bias : Array[Double]
    restitution_bias : Array[Double]
    friction : Double
    restitution : Double
    }

    Persistent contact constraints between two bodies.

    Impulses are accumulated and clamped, and are carried over to the next frame by matching contact feature ids, which is what makes stacks stable.

    Arbiter::body_a

    fn Arbiter::body_a(self : Arbiter) -> Int

    Arbiter::body_b

    fn Arbiter::body_b(self : Arbiter) -> Int

    Arbiter::contacts

    fn Arbiter::contacts(self : Arbiter) -> Array[ContactPoint]

    Arbiter::is_touching

    fn Arbiter::is_touching(self : Arbiter) -> Bool

    Arbiter::new

    fn Arbiter::new(body_a : Int, body_b : Int, contacts : Array[ContactPoint], friction : Double, restitution : Double) -> Arbiter

    Arbiter::pre_step

    fn Arbiter::pre_step(self : Arbiter, bodies : Array[Body], inv_dt : Double) -> Unit

    Compute effective masses and bias terms, then warm start.

    Arbiter::solve_velocity

    fn Arbiter::solve_velocity(self : Arbiter, bodies : Array[Body]) -> Unit

    Run one sequential impulse iteration.

    Arbiter::total_normal_impulse

    fn Arbiter::total_normal_impulse(self : Arbiter) -> Double

    Total normal impulse, useful to detect resting stability in tests.

    Arbiter::update

    fn Arbiter::update(self : Arbiter, contacts : Array[ContactPoint], friction : Double, restitution : Double) -> Unit

    Refresh the manifold, keeping impulses of surviving points.

    Body

    pub struct Body {
    body_type : BodyType
    shape : Shape
    position : Vec2
    rotation : Double
    velocity : Vec2
    angular_velocity : Double
    force : Vec2
    torque : Double
    mass : Double
    inv_mass : Double
    inertia : Double
    inv_inertia : Double
    friction : Double
    restitution : Double
    linear_damping : Double
    angular_damping : Double
    allow_sleep : Bool
    awake : Bool
    sleep_time : Double
    user_data : Int
    world_aabb : Aabb
    }

    A rigid body: a shape plus its motion state and material parameters.

    For polygons the vertices are re-centered on construction, so position always denotes the center of mass. Circles are centered by definition.

    Body::aabb

    fn Body::aabb(self : Body) -> Aabb

    Body::angular_velocity

    fn Body::angular_velocity(self : Body) -> Double

    Body::apply_angular_impulse

    fn Body::apply_angular_impulse(self : Body, impulse : Double) -> Unit

    Body::apply_force

    fn Body::apply_force(self : Body, force : Vec2) -> Unit

    Body::apply_impulse

    fn Body::apply_impulse(self : Body, impulse : Vec2, point : Vec2) -> Unit

    Apply an impulse at a world-space point.

    Body::apply_torque

    fn Body::apply_torque(self : Body, torque : Double) -> Unit

    Body::at

    fn Body::at(self : Body, x : Double, y : Double) -> Body

    Builder: move the body so its center of mass sits at (x, y).

    Body::awake

    fn Body::awake(self : Body) -> Bool

    Body::ball

    fn Body::ball(radius : Double) -> Body

    A dynamic circle.

    Body::box

    fn Body::box(half_width : Double, half_height : Double) -> Body

    A dynamic box centered at the current position.

    Body::clear_forces

    fn Body::clear_forces(self : Body) -> Unit

    Body::contains_point

    fn Body::contains_point(self : Body, point : Vec2) -> Bool

    Whether a world-space point is inside the body.

    Body::dynamic

    fn Body::dynamic(shape : Shape) -> Body

    A dynamic body, moved by the solver.

    Body::fixed

    fn Body::fixed(shape : Shape) -> Body

    An immovable body, e.g. the ground.

    Body::friction

    fn Body::friction(self : Body) -> Double

    Body::inertia

    fn Body::inertia(self : Body) -> Double

    Body::is_dynamic

    fn Body::is_dynamic(self : Body) -> Bool

    Body::is_static

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

    Body::kinetic_energy

    fn Body::kinetic_energy(self : Body) -> Double

    Translational plus rotational kinetic energy.

    Body::local_to_world

    fn Body::local_to_world(self : Body, point : Vec2) -> Vec2

    Local point to world point.

    Body::mass

    fn Body::mass(self : Body) -> Double

    Body::new

    fn Body::new(shape : Shape, body_type : BodyType) -> Body

    Body::point_velocity

    fn Body::point_velocity(self : Body, point : Vec2) -> Vec2

    Velocity of a world-space point on the body.

    Body::position

    fn Body::position(self : Body) -> Vec2

    Body::restitution

    fn Body::restitution(self : Body) -> Double

    Body::rotation

    fn Body::rotation(self : Body) -> Double

    Body::set_density

    fn Body::set_density(self : Body, density : Double) -> Unit

    Recompute mass and inertia for density.

    Body::shape

    fn Body::shape(self : Body) -> Shape

    Body::sleep

    fn Body::sleep(self : Body) -> Unit

    Body::sync_aabb

    fn Body::sync_aabb(self : Body) -> Unit

    Cached world AABB, refreshed by the world each step.

    Body::transform

    fn Body::transform(self : Body) -> Transform

    Body::user_data

    fn Body::user_data(self : Body) -> Int

    Free integer tag, handy for render colors and test bookkeeping.

    Body::velocity

    fn Body::velocity(self : Body) -> Vec2

    Body::wake

    fn Body::wake(self : Body) -> Unit

    Body::with_allow_sleep

    fn Body::with_allow_sleep(self : Body, allow : Bool) -> Body

    Body::with_angular_velocity

    fn Body::with_angular_velocity(self : Body, w : Double) -> Body

    Body::with_damping

    fn Body::with_damping(self : Body, linear : Double, angular : Double) -> Body

    Body::with_density

    fn Body::with_density(self : Body, density : Double) -> Body

    Body::with_friction

    fn Body::with_friction(self : Body, friction : Double) -> Body

    Body::with_restitution

    fn Body::with_restitution(self : Body, restitution : Double) -> Body

    Body::with_rotation

    fn Body::with_rotation(self : Body, angle : Double) -> Body

    Body::with_user_data

    fn Body::with_user_data(self : Body, data : Int) -> Body

    Body::with_velocity

    fn Body::with_velocity(self : Body, vx : Double, vy : Double) -> Body

    Body::world_to_local

    fn Body::world_to_local(self : Body, point : Vec2) -> Vec2

    World point to local point.

    BodySnapshot

    pub struct BodySnapshot {
    position : Vec2
    rotation : Double
    shape : Shape
    asleep : Bool
    user_data : Int
    }

    Immutable view of a body, used for rendering and for regression tests.

    BodySnapshot::asleep

    fn BodySnapshot::asleep(self : BodySnapshot) -> Bool

    BodySnapshot::position

    fn BodySnapshot::position(self : BodySnapshot) -> Vec2

    BodySnapshot::rotation

    fn BodySnapshot::rotation(self : BodySnapshot) -> Double

    BodySnapshot::shape

    fn BodySnapshot::shape(self : BodySnapshot) -> Shape

    BodySnapshot::user_data

    fn BodySnapshot::user_data(self : BodySnapshot) -> Int

    BodyType

    pub enum BodyType {
    Static
    Dynamic
    }

    Static bodies never move; dynamic bodies are integrated and solved.

    ContactPoint

    pub struct ContactPoint {
    position : Vec2
    normal : Vec2
    separation : Double
    feature_id : Int
    }

    One contact point of a manifold, in world space.

    normal always points from the first shape toward the second one, and separation is negative when the shapes overlap.

    ContactPoint::feature_id

    fn ContactPoint::feature_id(self : ContactPoint) -> Int

    ContactPoint::normal

    fn ContactPoint::normal(self : ContactPoint) -> Vec2

    ContactPoint::position

    fn ContactPoint::position(self : ContactPoint) -> Vec2

    ContactPoint::separation

    fn ContactPoint::separation(self : ContactPoint) -> Double

    DistanceJoint

    pub struct DistanceJoint {
    body_a : Int
    body_b : Int
    local_anchor_a : Vec2
    local_anchor_b : Vec2
    length : Double
    impulse : Double
    mass : Double
    bias : Double
    }

    A rigid rod that keeps two anchor points at a fixed distance.

    DistanceJoint::anchors

    fn DistanceJoint::anchors(self : DistanceJoint, bodies : Array[Body]) -> (Vec2, Vec2)

    World-space anchor points of the joint.

    DistanceJoint::between

    fn DistanceJoint::between(world : World, body_a : Int, body_b : Int) -> DistanceJoint

    Join the centers of two bodies at their current distance.

    DistanceJoint::length

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

    DistanceJoint::new

    fn DistanceJoint::new(body_a : Int, body_b : Int, local_anchor_a : Vec2, local_anchor_b : Vec2, length : Double) -> DistanceJoint

    DistanceJoint::pre_step

    fn DistanceJoint::pre_step(self : DistanceJoint, bodies : Array[Body], inv_dt : Double) -> Unit

    DistanceJoint::solve_velocity

    fn DistanceJoint::solve_velocity(self : DistanceJoint, bodies : Array[Body]) -> Unit

    Joint

    pub enum Joint {
    Distance(DistanceJoint)
    Revolve(RevolveJoint)
    }

    Type-erased joint stored in a world.

    Joint::pre_step

    fn Joint::pre_step(self : Joint, bodies : Array[Body], inv_dt : Double) -> Unit

    Joint::solve_velocity

    fn Joint::solve_velocity(self : Joint, bodies : Array[Body]) -> Unit

    MassData

    pub struct MassData {
    mass : Double
    inertia : Double
    center : Vec2
    }

    Mass properties of a shape with unit density scaled by density.

    MassData::center

    fn MassData::center(self : MassData) -> Vec2

    MassData::inertia

    fn MassData::inertia(self : MassData) -> Double

    MassData::mass

    fn MassData::mass(self : MassData) -> Double

    Mat22

    pub struct Mat22 {
    c1 : Vec2
    c2 : Vec2
    }

    A 2x2 matrix stored as two column vectors.

    Mat22::abs

    fn Mat22::abs(self : Mat22) -> Mat22

    Mat22::from_angle

    fn Mat22::from_angle(angle : Double) -> Mat22

    Mat22::identity

    fn Mat22::identity() -> Mat22

    Mat22::mul_vec

    fn Mat22::mul_vec(self : Mat22, v : Vec2) -> Vec2

    Mat22::transpose

    fn Mat22::transpose(self : Mat22) -> Mat22

    RevolveJoint

    pub struct RevolveJoint {
    body_a : Int
    body_b : Int
    local_anchor_a : Vec2
    local_anchor_b : Vec2
    impulse : Vec2
    k11 : Double
    k12 : Double
    k22 : Double
    bias : Vec2
    }

    A hinge: two anchor points are kept coincident, both bodies may rotate.

    RevolveJoint::at

    fn RevolveJoint::at(world : World, body_a : Int, body_b : Int, anchor : Vec2) -> RevolveJoint

    Revolve joint whose anchors are both at the given world point.

    RevolveJoint::new

    fn RevolveJoint::new(body_a : Int, body_b : Int, local_anchor_a : Vec2, local_anchor_b : Vec2) -> RevolveJoint

    RevolveJoint::pre_step

    fn RevolveJoint::pre_step(self : RevolveJoint, bodies : Array[Body], inv_dt : Double) -> Unit

    RevolveJoint::solve_velocity

    fn RevolveJoint::solve_velocity(self : RevolveJoint, bodies : Array[Body]) -> Unit

    Shape

    pub enum Shape {
    Circle(Double)
    Polygon(Array[Vec2])
    }

    Collision geometry in local space.

    Polygon vertices must form a convex counter-clockwise hull; use Shape::convex to build one safely.

    Shape::aabb

    fn Shape::aabb(self : Shape, transform : Transform) -> Aabb

    World-space AABB of the shape placed by transform.

    Shape::area

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

    Area of the shape (polygon area, or circle area).

    Shape::box

    fn Shape::box(half_width : Double, half_height : Double) -> Shape

    Axis-aligned box centered at the local origin.

    Shape::circle

    fn Shape::circle(radius : Double) -> Shape

    Shape::contains_local_point

    fn Shape::contains_local_point(self : Shape, point : Vec2) -> Bool

    Whether a local-space point lies inside the shape.

    Shape::convex

    fn Shape::convex(vertices : Array[Vec2]) -> Shape

    Build a convex polygon, forcing counter-clockwise winding.

    Shape::edge_normal

    fn Shape::edge_normal(self : Shape, i : Int) -> Vec2

    Outward unit normal of polygon edge i.

    Shape::is_circle

    fn Shape::is_circle(self : Shape) -> Bool

    Shape::mass_data

    fn Shape::mass_data(self : Shape, density : Double) -> MassData

    Compute mass properties for the given density.

    Shape::radius

    fn Shape::radius(self : Shape) -> Double

    Radius of a circle shape, or 0.0 for polygons.

    Shape::vertices

    fn Shape::vertices(self : Shape) -> Array[Vec2]

    Local-space vertices; empty for circles.

    Transform

    pub struct Transform {
    position : Vec2
    rotation : Double
    }

    Rotation of a body: world = rotation * local + position.

    Transform::apply

    fn Transform::apply(self : Transform, point : Vec2) -> Vec2

    Transform a point from local space to world space.

    Transform::apply_inv

    fn Transform::apply_inv(self : Transform, world : Vec2) -> Vec2

    Transform a point from world space to local space.

    Transform::apply_inv_vector

    fn Transform::apply_inv_vector(self : Transform, world : Vec2) -> Vec2

    Rotate a vector from world space to local space.

    Transform::apply_vector

    fn Transform::apply_vector(self : Transform, direction : Vec2) -> Vec2

    Rotate a vector from local space to world space.

    Transform::new

    fn Transform::new(position : Vec2, rotation : Double) -> Transform

    Vec2

    pub struct Vec2 {
    x : Double
    y : Double
    }

    A 2D vector of Double. Immutable: operations return fresh values, which keeps constraint solving free of aliasing and keeps steps reproducible.
    impl Add for Vec2
    impl Eq for Vec2
    impl Neg for Vec2
    impl Show for Vec2
    impl Sub for Vec2

    Vec2::abs

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

    Component-wise absolute value.

    Vec2::cross

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

    Scalar cross product x1 * y2 - y1 * x2.

    Vec2::cross_scalar

    fn Vec2::cross_scalar(self : Vec2, z : Double) -> Vec2

    z x v = (-z * v.y, z * v.x).

    Vec2::dot

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

    Vec2::length

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

    Length.

    Vec2::length_sq

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

    Squared length.

    Vec2::max

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

    Vec2::min

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

    Vec2::new

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

    Vec2::normalize

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

    Unit vector, or zero when the vector is degenerate.

    Vec2::normalize_or

    fn Vec2::normalize_or(self : Vec2, fallback : Vec2) -> Vec2

    Unit vector, or fallback when degenerate.

    Vec2::perp

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

    Counter-clockwise perpendicular (-y, x).

    Vec2::rotate

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

    Counter-clockwise rotation by angle radians.

    Vec2::rotate_by

    fn Vec2::rotate_by(self : Vec2, c : Double, s : Double) -> Vec2

    Roration by an angle given precomputed cosine/sine.

    Vec2::rotate_inv

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

    Inverse rotation by angle radians.

    Vec2::scale

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

    Vec2::splat

    fn Vec2::splat(v : Double) -> Vec2

    Vec2::x

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

    Vec2::y

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

    Vec2::zero

    fn Vec2::zero() -> Vec2

    World

    pub struct World {
    bodies : Array[Body]
    arbiters : Array[Arbiter]
    joints : Array[Joint]
    gravity : Vec2
    velocity_iterations : Int
    time : Double
    sleep_enabled : Bool
    }

    The simulation world: bodies, persistent contacts and joints.

    World::add_body

    fn World::add_body(self : World, body : Body) -> Int

    Add a body and return its index, which joints and arbiters refer to.

    World::add_distance_joint

    fn World::add_distance_joint(self : World, body_a : Int, body_b : Int) -> Unit

    Connect the centers of two bodies with a rigid distance joint.

    World::add_joint

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

    World::add_revolve_joint

    fn World::add_revolve_joint(self : World, body_a : Int, body_b : Int, anchor : Vec2) -> Unit

    Pin two bodies together at a world-space anchor (a hinge).

    World::add_revolve_joint_local

    fn World::add_revolve_joint_local(self : World, body_a : Int, body_b : Int, local_anchor_a : Vec2, local_anchor_b : Vec2) -> Unit

    Pin two bodies together, using one local anchor per body.

    World::arbiter_count

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

    World::body

    fn World::body(self : World, index : Int) -> Body

    World::body_count

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

    World::contact_count

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

    World::gravity

    fn World::gravity(self : World) -> Vec2

    World::new

    fn World::new() -> World

    World::run

    fn World::run(self : World, steps : Int, dt : Double) -> Unit

    Advance by steps fixed-size steps.

    World::set_gravity

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

    World::set_sleep_enabled

    fn World::set_sleep_enabled(self : World, enabled : Bool) -> Unit

    World::set_velocity_iterations

    fn World::set_velocity_iterations(self : World, iterations : Int) -> Unit

    World::snapshot

    fn World::snapshot(self : World) -> Array[BodySnapshot]

    World::step

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

    Advance the simulation by dt seconds.

    World::time

    fn World::time(self : World) -> Double

    World::total_kinetic_energy

    fn World::total_kinetic_energy(self : World) -> Double

    Sum of the kinetic energy of all bodies; the solver must never increase it in a purely dissipative scene.

    World::wake_all

    fn World::wake_all(self : World) -> Unit

    Wake every dynamic body.

    World::with_gravity

    fn World::with_gravity(gx : Double, gy : Double) -> World

    K_EPSILON

    let K_EPSILON : Double

    Numerical tolerance for length comparisons.

    K_PI

    let K_PI : Double

    Pi.

    LINEAR_SLOP

    let LINEAR_SLOP : Double

    Skin width; contacts shallower than this are considered touching.

    clamp

    fn clamp(v : Double, lo : Double, hi : Double) -> Double

    Clamp v into [lo, hi].

    collide

    fn collide(shape_a : Shape, xf_a : Transform, shape_b : Shape, xf_b : Transform) -> Array[ContactPoint]

    Collide two shapes and return 0..2 world-space contact points.

    cross_scalar

    fn cross_scalar(z : Double, v : Vec2) -> Vec2

    z x v = (-z * v.y, z * v.x), scalar on the left.