Very broken rubgu.berb machine (I can't spell)

This sketch creates an interactive Rube Goldberg machine using the Matter.js physics engine. Clicking the canvas releases a ball that cascades through eleven stages including ramps, seesaws, dominoes, pendulums, and funnels, demonstrating realistic physics interactions and chain reactions across an extended canvas.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make everything bounce higher — Increase restitution (bounciness) so balls bounce with more energy and the chain reaction is more vigorous
  2. Triple the domino cascade — Create more dominoes so the cascade is longer and more dramatic
  3. Weaken gravity for slow-motion effect — Reduce gravity so everything falls more slowly, giving you time to watch each stage carefully
  4. Paint the machine in color — Change all shapes from gray to a vibrant color—cyan, magenta, or whatever you prefer
  5. Make the starting ball huge — A bigger ball carries more momentum and crashes through obstacles more dramatically
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a fully functional Rube Goldberg machine—a complex chain-reaction device that demonstrates real physics in action. When you click, a ball drops and triggers eleven connected stages: ramps that guide momentum, seesaws that pivot and launch, dominoes that topple in sequence, pendulums that swing and collide, and funnels that redirect motion. What makes this sketch visually captivating is how each stage's output becomes the next stage's input, creating a mesmerizing cascade of cause-and-effect.

The code integrates three major p5.js techniques: Matter.js physics simulation for realistic body dynamics and gravity, constraint systems to create pivoting joints and swinging pendulums, and composite bodies to build complex structures like buckets and funnels from multiple pieces. By studying this sketch, you will learn how to set up a physics engine, create both static (fixed) and dynamic (moveable) bodies, link bodies together with constraints, and orchestrate a multi-stage interactive experience.

⚙️ How It Works

  1. When the sketch loads, setup() initializes the Matter.js engine with gravity pointing downward, then creates boundary walls and the starting ball as a static (immobile) body that waits at the top left.
  2. Clicking the canvas triggers mousePressed(), which converts the static start ball into a dynamic body that falls under gravity.
  3. The falling ball rolls down a ramp (Stage 2), gaining speed and colliding with a seesaw (Stage 3) that pivots on a constraint and launches a weight.
  4. That weight falls into dominoes (Stage 4), knocking them down in sequence. The final domino rolls into a lever (Stage 5) that catapults a ball toward a bucket (Stage 6).
  5. The ball lands in the bucket, triggering a second seesaw (Stage 7) that launches another ball into a funnel (Stage 8). The funnel funnels the ball down a slide that feeds into a pendulum (Stage 9).
  6. The pendulum swings and strikes a trigger block (Stage 10), which topples a second domino cascade (Stage 10), culminating when the last domino hits a final target block (Stage 11).
  7. Every frame, Engine.update() calculates new positions and velocities for all bodies based on physics laws, and drawBody() renders each body at its current position and rotation.

🎓 Concepts You'll Learn

Matter.js physics engineStatic vs. dynamic bodiesConstraint joints and pivotsCollision detectionChain reactionsComposite bodiesGravity and frictionRestitution (bounciness)

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It initializes the Matter.js physics engine, creates all the static and dynamic bodies that make up the machine, and connects bodies with constraints to create moveable joints. Understanding each Bodies.rectangle() and Bodies.circle() call teaches you how to build complex physical structures by combining simple shapes.

🔬 These four bodies form the containing box. What happens if you comment out the ceiling line (add // before let ceiling)? How does gravity behave?

  let ground = Bodies.rectangle(width / 2, height - 10, width, 20, { isStatic: true });
  let leftWall = Bodies.rectangle(10, height / 2, 20, height, { isStatic: true });
  let rightWall = Bodies.rectangle(width - 10, height / 2, 20, height, { isStatic: true });
  let ceiling = Bodies.rectangle(width / 2, 10, width, 20, { isStatic: true });

🔬 This loop creates 5 dominoes. What happens if you change the 5 to 10? To 2? How does the cascade length change?

  for (let i = 0; i < 5; i++) {
    let domino = Bodies.rectangle(startX1 + i * dominoSpacing, startY1, dominoWidth, dominoHeight, { friction: 0.5, restitution: 0.1 });
    dominoes1.push(domino);
  }
function setup() {
  createCanvas(windowWidth, windowHeight * 1.8);
  frameRate(30);

  engine = Engine.create();
  world = engine.world;
  world.gravity.y = 1;

  let ground = Bodies.rectangle(width / 2, height - 10, width, 20, { isStatic: true });
  let leftWall = Bodies.rectangle(10, height / 2, 20, height, { isStatic: true });
  let rightWall = Bodies.rectangle(width - 10, height / 2, 20, height, { isStatic: true });
  let ceiling = Bodies.rectangle(width / 2, 10, width, 20, { isStatic: true });
  World.add(world, [ground, leftWall, rightWall, ceiling]);
  bodies.push(ground, leftWall, rightWall, ceiling);

  startBall = Bodies.circle(width * 0.2, 50, 15, { friction: 0.1, restitution: 0.8, isStatic: true });
  World.add(world, startBall);
  bodies.push(startBall);

  let ramp1 = Bodies.rectangle(width * 0.35, height * 0.1, 200, 20, { isStatic: true, angle: PI / 6 });
  World.add(world, ramp1);
  bodies.push(ramp1);

  let seesawBlock = Bodies.rectangle(width * 0.6, height * 0.2, 250, 10);
  let seesawPivot = Bodies.circle(width * 0.6, height * 0.2, 5, { isStatic: true });
  let seesawConstraint = Constraint.create({
    bodyA: seesawBlock,
    pointB: { x: seesawBlock.position.x, y: seesawBlock.position.y },
    pointA: { x: 0, y: 0 },
    stiffness: 0.05
  });
  World.add(world, [seesawBlock, seesawPivot, seesawConstraint]);
  bodies.push(seesawBlock, seesawPivot);

  let seesawWeight = Bodies.rectangle(width * 0.75, height * 0.15, 30, 30, { friction: 0.1, restitution: 0.8 });
  World.add(world, seesawWeight);
  bodies.push(seesawWeight);

  let dominoes1 = [];
  let dominoWidth = 10;
  let dominoHeight = 50;
  let dominoSpacing = 20;
  let startX1 = width * 0.2;
  let startY1 = height * 0.3;
  for (let i = 0; i < 5; i++) {
    let domino = Bodies.rectangle(startX1 + i * dominoSpacing, startY1, dominoWidth, dominoHeight, { friction: 0.5, restitution: 0.1 });
    dominoes1.push(domino);
  }
  World.add(world, dominoes1);
  bodies.push(...dominoes1);

  let leverBlock = Bodies.rectangle(width * 0.45, height * 0.4, 150, 10, { isStatic: true, angle: -PI / 8 });
  World.add(world, leverBlock);
  bodies.push(leverBlock);

  let finalBall1 = Bodies.circle(width * 0.55, height * 0.35, 15, { friction: 0.1, restitution: 0.8 });
  World.add(world, finalBall1);
  bodies.push(finalBall1);

  let bucketWidth = 100;
  let bucketHeight = 70;
  let bucketX = width * 0.8;
  let bucketY = height * 0.5;

  let bucketBottom = Bodies.rectangle(bucketX, bucketY + bucketHeight / 2, bucketWidth, 10, { isStatic: true });
  let bucketLeft = Bodies.rectangle(bucketX - bucketWidth / 2 + 5, bucketY, 10, bucketHeight, { isStatic: true });
  let bucketRight = Bodies.rectangle(bucketX + bucketWidth / 2 - 5, bucketY, 10, bucketHeight, { isStatic: true });

  let bucket = Composite.create({
    bodies: [bucketBottom, bucketLeft, bucketRight]
  });
  World.add(world, bucket);
  bodies.push(bucketBottom, bucketLeft, bucketRight);

  let seesawLaunchPivot = Bodies.circle(width * 0.7, height * 0.65, 5, { isStatic: true });
  let seesawLaunchBlock = Bodies.rectangle(width * 0.7, height * 0.65, 200, 10);
  let seesawLaunchConstraint = Constraint.create({
    bodyA: seesawLaunchBlock,
    pointB: { x: seesawLaunchBlock.position.x, y: seesawLaunchBlock.position.y },
    pointA: { x: 0, y: 0 },
    stiffness: 0.05
  });
  World.add(world, [seesawLaunchPivot, seesawLaunchBlock, seesawLaunchConstraint]);
  bodies.push(seesawLaunchPivot, seesawLaunchBlock);

  let ballToLaunch = Bodies.circle(width * 0.85, height * 0.6, 15, { friction: 0.1, restitution: 0.8 });
  World.add(world, ballToLaunch);
  bodies.push(ballToLaunch);

  let funnelLeft = Bodies.rectangle(width * 0.6, height * 0.8, 10, 80, { isStatic: true, angle: -PI / 8 });
  let funnelRight = Bodies.rectangle(width * 0.7, height * 0.8, 10, 80, { isStatic: true, angle: PI / 8 });
  World.add(world, [funnelLeft, funnelRight]);
  bodies.push(funnelLeft, funnelRight);

  let slide2 = Bodies.rectangle(width * 0.45, height * 0.85, 250, 10, { isStatic: true, angle: PI / 12 });
  World.add(world, slide2);
  bodies.push(slide2);

  let pendulumPivot = Bodies.circle(width * 0.2, height * 0.9, 5, { isStatic: true });
  let pendulumBob = Bodies.circle(width * 0.2, height * 1.05, 20, { friction: 0.1, restitution: 0.8 });
  let pendulumConstraint = Constraint.create({
    bodyA: pendulumPivot,
    pointB: { x: pendulumBob.position.x, y: pendulumBob.position.y },
    pointA: { x: 0, y: -100 },
    stiffness: 0.005,
    damping: 0.01
  });
  World.add(world, [pendulumPivot, pendulumBob, pendulumConstraint]);
  bodies.push(pendulumPivot, pendulumBob);

  let pendulumTriggerBlock = Bodies.rectangle(width * 0.3, height * 1.05, 40, 40, { friction: 0.1, restitution: 0.8 });
  World.add(world, pendulumTriggerBlock);
  bodies.push(pendulumTriggerBlock);

  let dominoes2 = [];
  let startX2 = width * 0.4;
  let startY2 = height * 1.1;
  for (let i = 0; i < 7; i++) {
    let domino = Bodies.rectangle(startX2 + i * dominoSpacing, startY2, dominoWidth, dominoHeight, { friction: 0.5, restitution: 0.1 });
    dominoes2.push(domino);
  }
  World.add(world, dominoes2);
  bodies.push(...dominoes2);

  let finalTarget = Bodies.rectangle(width * 0.7, height * 1.25, 80, 80, { isStatic: true, friction: 0.1, restitution: 0.8 });
  World.add(world, finalTarget);
  bodies.push(finalTarget);

  for (let i = 0; i < 2; i++) {
    let x = random(width * 0.1, width * 0.9);
    let y = random(height * 0.1, height * 0.9);
    let size = random(10, 40);
    let body;
    if (random() > 0.5) {
      body = Bodies.circle(x, y, size / 2, { friction: 0.1, restitution: 0.8 });
    } else {
      body = Bodies.rectangle(x, y, size, size, { friction: 0.1, restitution: 0.8 });
    }
    World.add(world, body);
    bodies.push(body);
  }
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

initialization Physics Engine Creation engine = Engine.create(); world = engine.world; world.gravity.y = 1;

Creates the Matter.js physics engine and sets gravity to pull all objects downward

loop Static Boundary Walls let ground = Bodies.rectangle(width / 2, height - 10, width, 20, { isStatic: true });

Creates immovable walls and ground that contain all dynamic bodies within the canvas

initialization Start Ball (Static) startBall = Bodies.circle(width * 0.2, 50, 15, { friction: 0.1, restitution: 0.8, isStatic: true });

Creates the initial ball at top-left, marked as static so it waits for a click to become dynamic

initialization First Ramp let ramp1 = Bodies.rectangle(width * 0.35, height * 0.1, 200, 20, { isStatic: true, angle: PI / 6 });

Creates a tilted static surface to guide the ball downward and accelerate it

initialization First Seesaw with Constraint let seesawConstraint = Constraint.create({ bodyA: seesawBlock, pointB: { x: seesawBlock.position.x, y: seesawBlock.position.y }, pointA: { x: 0, y: 0 }, stiffness: 0.05 });

Connects the seesaw block to a pivot point with a constraint, allowing it to rotate like a real seesaw

for-loop First Domino Cascade for (let i = 0; i < 5; i++) { let domino = Bodies.rectangle(startX1 + i * dominoSpacing, startY1, dominoWidth, dominoHeight, { friction: 0.5, restitution: 0.1 }); dominoes1.push(domino); }

Creates a row of 5 upright rectangles spaced evenly so they topple in sequence when struck

initialization Bucket Container let bucket = Composite.create({ bodies: [bucketBottom, bucketLeft, bucketRight] });

Groups three static rectangles into one composite to act as a container that catches falling balls

initialization Pendulum with Swing Constraint let pendulumConstraint = Constraint.create({ bodyA: pendulumPivot, pointB: { x: pendulumBob.position.x, y: pendulumBob.position.y }, pointA: { x: 0, y: -100 }, stiffness: 0.005, damping: 0.01 });

Creates a swinging pendulum bob connected to a fixed pivot with low stiffness for realistic swing motion

createCanvas(windowWidth, windowHeight * 1.8);
Creates a canvas 80% taller than the window height to accommodate the extended 11-stage machine vertically
frameRate(30);
Locks the animation to 30 frames per second for smoother physics simulation and consistent behavior
engine = Engine.create();
Initializes the Matter.js physics engine—the core system that calculates motion, collisions, and forces
world = engine.world;
Retrieves the world object from the engine, which is where all bodies and constraints live
world.gravity.y = 1;
Sets the gravitational acceleration downward to 1 pixel/frame/frame, pulling all objects toward the bottom
let ground = Bodies.rectangle(width / 2, height - 10, width, 20, { isStatic: true });
Creates a thin static rectangle at the bottom of the canvas to act as immovable ground
World.add(world, [ground, leftWall, rightWall, ceiling]);
Adds all boundary bodies to the physics world so they interact with dynamic bodies
bodies.push(ground, leftWall, rightWall, ceiling);
Stores references to all boundary bodies in the global bodies array so draw() can render them each frame
startBall = Bodies.circle(width * 0.2, 50, 15, { friction: 0.1, restitution: 0.8, isStatic: true });
Creates the trigger ball as a circle with radius 15 at the top-left, marked static so it won't fall until clicked
for (let i = 0; i < 5; i++) {
Loops 5 times to create a row of dominoes at different horizontal positions
let domino = Bodies.rectangle(startX1 + i * dominoSpacing, startY1, dominoWidth, dominoHeight, { friction: 0.5, restitution: 0.1 });
Creates each domino as a thin (10px wide) tall rectangle spaced 20px apart horizontally
let seesawConstraint = Constraint.create({ bodyA: seesawBlock, pointB: { x: seesawBlock.position.x, y: seesawBlock.position.y }, pointA: { x: 0, y: 0 }, stiffness: 0.05 });
Creates a spring-like joint that connects the seesaw block to an invisible pivot point, allowing it to rotate and wobble

draw()

draw() is the heartbeat of every p5.js sketch—it runs 60 times per second (or whatever frameRate you set) and is responsible for updating physics and redrawing everything. The order matters: background() clears the old frame, Engine.update() advances physics, and drawBody() renders the new positions. If you reversed this order, you'd see strange visual artifacts.

🔬 This loop draws every single body each frame. What happens if you add a condition like `if (body.circleRadius)` to only draw circles? How does the visualization change?

  for (let body of bodies) {
    drawBody(body);
  }

🔬 This displays instructions while waiting. What happens if you change the text message to something else? Or move it to a different location like (100, 100)?

  if (startBall.isStatic) {
    fill(0);
    textAlign(CENTER, CENTER);
    textSize(24);
    text("Click to start the Rube Goldberg Machine!", width / 2, height / 2);
  }
function draw() {
  background(220);

  // Update Matter.js engine
  Engine.update(engine);

  // Draw all Matter.js bodies
  for (let body of bodies) {
    drawBody(body);
  }

  // Display frame rate
  fill(0);
  noStroke();
  textSize(12);
  text("FPS: " + frameRate().toFixed(2), 10, 20);

  // Add instructions for starting the machine
  if (startBall.isStatic) {
    fill(0);
    textAlign(CENTER, CENTER);
    textSize(24);
    text("Click to start the Rube Goldberg Machine!", width / 2, height / 2);
  }
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Physics Engine Update Engine.update(engine);

Advances the physics simulation by one frame, calculating new velocities and positions for all bodies

for-loop Body Rendering Loop for (let body of bodies) { drawBody(body); }

Iterates through all bodies and renders each one using the drawBody helper function

display Frame Rate Counter text("FPS: " + frameRate().toFixed(2), 10, 20);

Shows the current frames-per-second in the top-left corner for performance monitoring

conditional Start Instruction Check if (startBall.isStatic) { fill(0); textAlign(CENTER, CENTER); textSize(24); text("Click to start the Rube Goldberg Machine!", width / 2, height / 2); }

Displays instructions centered on screen while the start ball is static; disappears once clicked

background(220);
Fills the entire canvas with light gray each frame, erasing the previous frame so you see smooth animation instead of trails
Engine.update(engine);
Tells Matter.js to calculate one frame of physics—all collisions, gravity, and constraint forces are applied here
for (let body of bodies) {
Loops through every single body stored in the bodies array—ground, walls, balls, dominoes, seesaws, everything
drawBody(body);
Calls the drawBody helper function to render each body at its current position and rotation
fill(0);
Sets the text color to black for the upcoming text displays
text("FPS: " + frameRate().toFixed(2), 10, 20);
Displays the current frame rate (frames per second) at coordinates (10, 20) to monitor performance
if (startBall.isStatic) {
Checks if the start ball is still static (hasn't been clicked yet); if true, displays instructions
text("Click to start the Rube Goldberg Machine!", width / 2, height / 2);
Displays the instruction message centered on the canvas in large text

drawBody(body)

drawBody() is the bridge between Matter.js physics bodies and p5.js drawings. Every body in the physics world must be rendered using this function. The key insight is using push()/pop() to isolate transformations and using translate()/rotate() to position and angle each shape exactly where the physics engine says it should be. Studying this function teaches you how physics engines and graphics rendering need to stay perfectly synchronized.

🔬 This if-else statement decides how to draw based on the body's shape. What happens if you add an extra condition like `if (body.restitution > 0.5)` to change the color of bouncy objects?

  if (body.circleRadius) { // It's a circle
    circle(0, 0, body.circleRadius * 2);
  } else if (body.vertices) { // It's a polygon (or rectangle)
    beginShape();
    for (let vert of body.vertices) {
      vertex(vert.x - pos.x, vert.y - pos.y);
    }
    endShape(CLOSE);
  }
function drawBody(body) {
  let pos = body.position;
  let angle = body.angle;

  push();
  translate(pos.x, pos.y);
  rotate(angle);
  rectMode(CENTER);
  stroke(0);
  fill(127);

  if (body.circleRadius) { // It's a circle
    circle(0, 0, body.circleRadius * 2);
  } else if (body.vertices) { // It's a polygon (or rectangle)
    beginShape();
    for (let vert of body.vertices) {
      vertex(vert.x - pos.x, vert.y - pos.y);
    }
    endShape(CLOSE);
  }
  pop();
}
Line-by-line explanation (14 lines)

🔧 Subcomponents:

calculation Extract Position and Angle let pos = body.position; let angle = body.angle;

Retrieves the body's current x/y coordinates and rotation angle from the physics engine

transformation Coordinate Transform push(); translate(pos.x, pos.y); rotate(angle); rectMode(CENTER);

Moves the drawing origin to the body's position and rotates the canvas to match the body's angle

conditional Circle Drawing if (body.circleRadius) { circle(0, 0, body.circleRadius * 2); }

Renders circles using the body's circleRadius property (doubled because p5 uses diameter)

conditional Polygon/Rectangle Drawing else if (body.vertices) { beginShape(); for (let vert of body.vertices) { vertex(vert.x - pos.x, vert.y - pos.y); } endShape(CLOSE); }

Renders rectangles and complex shapes by connecting their vertices to form closed polygons

let pos = body.position;
Stores the body's x and y coordinates from the physics engine
let angle = body.angle;
Stores the body's rotation angle in radians, which will rotate the shape when drawn
push();
Saves the current drawing state (position, rotation, colors) so changes don't affect future draws
translate(pos.x, pos.y);
Moves the drawing origin (0, 0) to the body's position so shapes draw at the correct location
rotate(angle);
Rotates all future drawings by the body's angle, so rectangles appear tilted and seesaws appear at an angle
rectMode(CENTER);
Tells p5.js that rectangle coordinates should be measured from the center, not top-left
stroke(0);
Sets the outline color to black for all shapes
fill(127);
Sets the fill color to medium gray for all shapes
if (body.circleRadius) {
Checks if the body has a circleRadius property—this is true for all circle bodies
circle(0, 0, body.circleRadius * 2);
Draws a circle at the origin (0, 0) with diameter = 2 × radius (p5 circles use diameter, not radius)
for (let vert of body.vertices) {
Loops through every corner point of a polygon or rectangle body
vertex(vert.x - pos.x, vert.y - pos.y);
Converts each vertex's absolute position to a relative position (since translate moved the origin)
endShape(CLOSE);
Finishes drawing the polygon and automatically connects the last vertex back to the first to close it
pop();
Restores the previous drawing state so future draws are unaffected by translate/rotate/fill changes

mousePressed()

mousePressed() is a p5.js callback function that runs automatically whenever the mouse is clicked anywhere on the canvas. This sketch uses it to convert the static start ball into a dynamic body, triggering the entire chain reaction. This is a simple but powerful pattern for user interaction in physics simulations: click to activate.

function mousePressed() {
  if (startBall.isStatic) { // Check if the ball is currently static
    Matter.Body.setStatic(startBall, false); // Make it dynamic so it falls
  }
}
Line-by-line explanation (2 lines)

🔧 Subcomponents:

conditional Static Body Check if (startBall.isStatic) {

Verifies the start ball hasn't already been released before allowing another click

state-change Activate Start Ball Matter.Body.setStatic(startBall, false);

Converts the ball from a static (immobile) body to a dynamic body subject to gravity

if (startBall.isStatic) {
Checks if the startBall property isStatic is true—only proceeds if the ball hasn't been released yet
Matter.Body.setStatic(startBall, false);
Uses Matter.js to change the ball from static (fixed) to dynamic (moveable), allowing gravity to pull it down immediately

windowResized()

windowResized() is a p5.js callback that fires automatically whenever the browser window is resized. This sketch uses it to keep the canvas responsive to window size changes. Note: it does NOT rebuild the Rube Goldberg machine positions—bodies remain at their original coordinates, which means the machine may go off-screen on very small displays.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight * 1.8);
}
Line-by-line explanation (1 lines)

🔧 Subcomponents:

function-call Canvas Resize resizeCanvas(windowWidth, windowHeight * 1.8);

Updates the canvas dimensions to match the new window size (maintaining 1.8x height ratio)

resizeCanvas(windowWidth, windowHeight * 1.8);
Stretches or shrinks the canvas to match the window size while keeping it 80% taller than the window height

📦 Key Variables

engine Matter.Engine object

The core Matter.js physics engine that calculates all motion, forces, and collisions every frame

let engine; // Declared globally, initialized in setup()
world Matter.World object

The physics world object that contains all bodies, constraints, and gravity settings

let world = engine.world;
bodies array

Stores references to every Matter.js body (ground, walls, balls, dominoes, etc.) so draw() can render them

let bodies = []; // Starts empty, filled in setup()
startBall Matter.Body (circle)

The initial ball that waits at the top-left and starts the chain reaction when clicked

let startBall; // Declared globally, created in setup()

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

PERFORMANCE draw() and drawBody()

Every body is drawn every frame using complex vertex loops for rectangles. With many bodies this becomes expensive. Adding a visual debug overlay (FPS counter) is good, but rendering could optimize with display lists or batching.

💡 For very large machines (50+ bodies), consider caching shapes as p5.Graphics objects or using custom geometry instead of iterating vertices every frame.

BUG setup() boundary walls

The walls are only 20px thick but they're positioned at the exact edge (x=10 for left wall). If bodies penetrate slightly during fast collisions, they can escape the bounds.

💡 Increase wall thickness to 30-40px or position them slightly inward: let leftWall = Bodies.rectangle(-10, height / 2, 20, height, { isStatic: true });

STYLE setup()

Stage coordinates are scattered throughout setup() with magic numbers like width * 0.2, height * 0.3, etc. This makes it hard to visualize the machine layout or adjust stages globally.

💡 Define stage positions as named constants at the top: const STAGE_1_X = width * 0.2; const STAGE_1_Y = 50; Then use these variables, making the code easier to read and modify.

FEATURE mousePressed()

Once the start ball is released, there's no way to reset it without reloading the sketch. Complex Rube Goldbergs often get stuck or need replaying.

💡 Add a reset button or key handler: function keyPressed() { if (key === 'r') { Matter.Body.setStatic(startBall, true); Matter.Body.setPosition(startBall, {x: width*0.2, y: 50}); } }

BUG windowResized()

Resizing the window stretches the canvas but does NOT move any physics bodies. The machine's layout becomes misaligned—dominoes that should be at 30% of the width are now at the wrong position.

💡 Store initial machine configuration and recalculate all body positions in windowResized(), or lock the machine to a fixed viewport that doesn't stretch.

STYLE drawBody()

All bodies are drawn in the same gray color (127). There's no visual distinction between different stages—dominoes, balls, and seesaws are all indistinguishable.

💡 Add color coding by body type: if (body.label === 'domino') fill(200, 50, 50); or vary colors based on body properties like mass or type.

🔄 Code Flow

Code flow showing setup, draw, drawbody, mousepressed, windowresized

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> physics-setup[Physics Engine Creation] physics-setup --> boundary-creation[Static Boundary Walls] boundary-creation --> start-ball-setup[Start Ball (Static)] start-ball-setup --> ramp-setup[First Ramp] ramp-setup --> seesaw-setup[First Seesaw with Constraint] seesaw-setup --> dominoes-loop[First Domino Cascade] dominoes-loop --> bucket-composite[Bucket Container] bucket-composite --> pendulum-setup[Pendulum with Swing Constraint] setup --> draw[draw loop] draw --> fps-display[Frame Rate Counter] draw --> instruction-display[Start Instruction Check] instruction-display -->|Static| draw instruction-display --> static-check[Static Body Check] static-check -->|Not Released| dynamic-conversion[Activate Start Ball] dynamic-conversion --> draw draw --> physics-update[Physics Engine Update] physics-update --> body-render-loop[Body Rendering Loop] body-render-loop --> position-extraction[Extract Position and Angle] position-extraction --> transform-setup[Coordinate Transform] transform-setup --> circle-render[Circle Drawing] transform-setup --> polygon-render[Polygon/Rectangle Drawing] body-render-loop --> draw draw -->|Loop| draw click setup href "#fn-setup" click draw href "#fn-draw" click physics-setup href "#sub-physics-setup" click boundary-creation href "#sub-boundary-creation" click start-ball-setup href "#sub-start-ball-setup" click ramp-setup href "#sub-ramp-setup" click seesaw-setup href "#sub-seesaw-setup" click dominoes-loop href "#sub-dominoes-loop" click bucket-composite href "#sub-bucket-composite" click pendulum-setup href "#sub-pendulum-setup" click fps-display href "#sub-fps-display" click instruction-display href "#sub-instruction-display" click static-check href "#sub-static-check" click dynamic-conversion href "#sub-dynamic-conversion" click physics-update href "#sub-physics-update" click body-render-loop href "#sub-body-render-loop" click position-extraction href "#sub-position-extraction" click transform-setup href "#sub-transform-setup" click circle-render href "#sub-circle-render" click polygon-render href "#sub-polygon-render"

❓ Frequently Asked Questions

What visual elements can I expect to see in the Very broken rubgu.berb machine sketch?

This sketch creates a dynamic and interactive scene featuring a Rube Goldberg machine, with elements like ramps and falling balls that respond to gravity.

How can I interact with the Very broken rubgu.berb machine sketch?

Users can click on the initial static ball to activate it, allowing it to roll down the ramp and trigger subsequent movements in the machine.

What creative coding concepts are showcased in the Very broken rubgu.berb machine sketch?

The sketch demonstrates the use of physics simulation with Matter.js to create realistic interactions and animations in a playful Rube Goldberg-style setup.

Preview

Very broken rubgu.berb machine (I can't spell) - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Very broken rubgu.berb machine (I can't spell) - Code flow showing setup, draw, drawbody, mousepressed, windowresized
Code Flow Diagram