///|
/// `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 =
#|
#|
#|
#|
///|
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)
}