///|
/// `moonphys` demo generator.
///
/// Usage:
///   moon run cmd/main --target js > demo.html
///
/// The program simulates three scenes and prints a single self contained HTML
/// page that embeds every frame as inline SVG. The page plays the frames with
/// `requestAnimationFrame`, so the animation is completely deterministic: the
/// browser only replays what the solver produced.
const DT : Double = 1.0 / 60.0

///|
const FRAMES : Int32 = 240

///|
fn add_ground(world : @moonphys.World, half_width : Double) -> Unit {
  let ground = @moonphys.Body::fixed(@moonphys.Shape::box(half_width, 0.1)).at(
    0.0, -0.1,
  )
  ignore(world.add_body(ground.with_user_data(9)))
}

///|
/// A six row box pyramid, a ramp with a rolling ball and a few falling balls.
fn pyramid_scene() -> @moonphys.World {
  let world = @moonphys.World::new()
  add_ground(world, 14.0)
  // Static ramp on the left: a ball rolls down and shows friction.
  ignore(
    world.add_body(
      @moonphys.Body::fixed(@moonphys.Shape::box(2.4, 0.1))
      .at(-5.6, 3.2)
      .with_rotation(-0.4)
      .with_user_data(4),
    ),
  )
  ignore(
    world.add_body(
      @moonphys.Body::ball(0.35)
      .at(-7.4, 4.6)
      .with_friction(0.5)
      .with_user_data(1),
    ),
  )
  // Pyramid on the right.
  let rows = 6
  for row in 0.. @moonphys.World {
  let world = @moonphys.World::new()
  add_ground(world, 14.0)
  // Tilted chain: five links, pinned end to end.
  let anchor = @moonphys.Vec2::new(-3.4, 9.0)
  let tilt = 0.6
  // sin(0.6), cos(0.6)
  let ux = 0.5646
  let uy = -0.8253
  ignore(
    world.add_body(
      @moonphys.Body::fixed(@moonphys.Shape::circle(0.1))
      .at(anchor.x(), anchor.y())
      .with_user_data(9),
    ),
  )
  let mut previous = 0
  for i in 0..<5 {
    let off = 0.5 + i.to_double() * 1.0
    let center = @moonphys.Vec2::new(
      anchor.x() + ux * off,
      anchor.y() + uy * off,
    )
    let link = world.add_body(
      @moonphys.Body::box(0.08, 0.5)
      .at(center.x(), center.y())
      .with_rotation(tilt)
      .with_damping(0.25, 0.25)
      .with_user_data(i),
    )
    let joint = @moonphys.Vec2::new(
      anchor.x() + ux * i.to_double() * 1.0,
      anchor.y() + uy * i.to_double() * 1.0,
    )
    world.add_revolve_joint(previous, link, joint)
    previous = link
  }
  // Double pendulum on the right, released horizontally.
  let pivot = world.add_body(
    @moonphys.Body::fixed(@moonphys.Shape::circle(0.1))
    .at(3.0, 8.6)
    .with_user_data(9),
  )
  let arm_a = world.add_body(
    @moonphys.Body::box(0.9, 0.07)
    .at(3.9, 8.6)
    .with_damping(0.05, 0.05)
    .with_user_data(2),
  )
  world.add_revolve_joint(pivot, arm_a, @moonphys.Vec2::new(3.0, 8.6))
  let arm_b = world.add_body(
    @moonphys.Body::box(0.9, 0.07)
    .at(5.7, 8.6)
    .with_damping(0.05, 0.05)
    .with_user_data(3),
  )
  world.add_revolve_joint(arm_a, arm_b, @moonphys.Vec2::new(4.8, 8.6))
  world
}

///|
/// Machines driven by joints: a linear actuator sweeping crates off the floor
/// and a rotor spun by an angular motor behind a rope of soft distance joints.
fn machine_scene() -> @moonphys.World {
  let world = @moonphys.World::new()
  add_ground(world, 14.0)
  // Linear actuator: a cart on a horizontal rail, driven by a prismatic motor.
  let rail = world.add_body(
    @moonphys.Body::fixed(@moonphys.Shape::circle(0.1))
    .at(0.0, 12.0)
    .with_user_data(9),
  )
  let cart = world.add_body(
    @moonphys.Body::box(0.35, 0.25)
    .at(-5.0, 0.35)
    .with_friction(0.2)
    .with_damping(0.3, 0.3)
    .with_user_data(6),
  )
  let slider = world.add_prismatic_joint(
    rail,
    cart,
    @moonphys.Vec2::new(-5.0, 0.35),
    @moonphys.Vec2::new(1.0, 0.0),
  )
  ignore(slider.with_limits(-8.0, 8.0))
  ignore(slider.with_motor(2.5, 40.0))
  // Crates waiting in the path of the cart.
  for i in 0..<5 {
    ignore(
      world.add_body(
        @moonphys.Body::box(0.3, 0.3)
        .at(-0.6 + i.to_double() * 0.62, 0.32)
        .with_friction(0.5)
        .with_user_data(i),
      ),
    )
  }
  // Rotor: a hub with a long arm spun by an angular motor, plus a hanging rope
  // made of soft distance joints so it whips instead of snapping.
  let hub = world.add_body(
    @moonphys.Body::fixed(@moonphys.Shape::circle(0.12))
    .at(-6.6, 8.4)
    .with_user_data(9),
  )
  let arm = world.add_body(
    @moonphys.Body::box(1.5, 0.09).at(-5.1, 8.4).with_user_data(7),
  )
  world.add_revolve_joint(hub, arm, @moonphys.Vec2::new(-6.6, 8.4))
  ignore(world.add_motor_joint(hub, arm, 3.0, 400.0))
  // Seesaw: gravity swings the bar down onto an angle stop, which holds it.
  let pivot = world.add_body(
    @moonphys.Body::fixed(@moonphys.Shape::circle(0.12))
    .at(5.4, 8.4)
    .with_user_data(9),
  )
  let beam = world.add_body(
    @moonphys.Body::box(1.2, 0.08).at(6.6, 8.4).with_user_data(5),
  )
  world.add_revolve_joint(pivot, beam, @moonphys.Vec2::new(5.4, 8.4))
  ignore(
    world.add_angle_limit_joint_at(
      pivot,
      beam,
      @moonphys.Vec2::new(5.4, 8.4),
      -0.6,
      0.0,
    ),
  )
  let mut previous = world.add_body(
    @moonphys.Body::ball(0.22).at(-3.6, 8.4).with_user_data(2),
  )
  world.add_soft_distance_joint(arm, previous, 3.0, 1.0)
  for i in 0..<4 {
    let next = world.add_body(
      @moonphys.Body::ball(0.18)
      .at(-3.1 + i.to_double() * 0.5, 8.4)
      .with_damping(0.1, 0.1)
      .with_user_data(3),
    )
    world.add_soft_distance_joint(previous, next, 3.0, 1.0)
    previous = next
  }
  world
}

///|
/// Print `FRAMES` frames of one scene as inline SVG inside a `
`. fn emit_scene(world : @moonphys.World, options : @render.SvgOptions) -> Unit { println("
") for _frame in 0..") } ///| const PAGE_HEAD : String = #| #| #| #| #| #|moonphys — 2D rigid body physics in MoonBit #| #| #| #|

moonphys

#|

A deterministic 2D rigid body engine written in MoonBit: #| SAT collision detection, impulse solver with warm starting, Coulomb friction, #| revolute, distance, prismatic, angle limit and motor joints, sleeping, and a #| sort-and-sweep broadphase. #| Every frame below was produced by moon run cmd/main --target js.

///| const PAGE_SCRIPT : String = #| #|
moonphys · Apache-2.0 · concepts ported from Box2D-Lite, Box2D and Planck.js
#| #| ///| fn main { let options = @render.SvgOptions::new() .with_size(720, 480) .with_scale(40.0) .with_center(0.0, 4.0) println(PAGE_HEAD) println("

Pyramid stack · friction ramp · bouncing balls

") emit_scene(pyramid_scene(), options) println("

Five link chain · double pendulum

") emit_scene(pendulum_scene(), options) println("

Linear actuator · angular motor · soft rope

") emit_scene(machine_scene(), options) println(PAGE_SCRIPT) }