Skateboard simulation

from Red Blob Games
1 Apr 2021

Bret Victor's page http://worrydream.com/SimulationAsAPracticalTool/[1] shows a simulation of a skateboarder on a merry-go-round. The simulations are in Flash, and no longer work in 2021, since Flash has been removed from browsers. I wanted to try implementing this in JavaScript to see how it'd look.

Angle:

There's a paper about Data Theater[2] that reproduces the simulations using a Python-based framework.

from math import radians, sin, cos

disk = [150, 150]
r = 50
rotation = 12
theta = radians(rotation)

x = disk[0] + r * cos(theta)
y = disk[1] - r * sin(theta)

vel_x = -5 * sin(theta)
vel_y = -5 * cos(theta)

xs = [x]
ys = [y]

for _ in range(50):
    next_y = y + vel_y
    if next_y < 0:
        vel_y = -vel_y

    x += vel_x; y += vel_y
    xs.append(x); ys.append(y)
    x, y  #break

Here's the JavaScript equivalent, which of course is a little more noisy because JavaScript has more visible syntax than Python:

function simulate(disk, r, rotation) {
    let theta = Math.PI / 180 * rotation;

    let x = disk[0] + r * Math.cos(theta),
        y = disk[1] - r * Math.sin(theta);
    
    let vel_x = -5 * Math.sin(theta),
        vel_y = -5 * Math.cos(theta);
    
    let history = [[x, y]];

    for (let time = 0; time < 50; time++) {
        let next_y = y + vel_y;
        if (next_y < 0) {
            vel_y = -vel_y;
        }

        x += vel_x; y += vel_y;
        history.push([x, y]);
    }
    
    return {x, y, history};
}

I think other than the language differences, these two pieces of code are comparable.

Here's the data visualization description from the paper:

export default {
  disc: {
    type: 'ellipse',
    x: 'disk[0]',
    y: 'disk[1]',
    rx: 'r',
    ry: 'r',
    text: '"--------------"',
    rotate: '-rotation',
  },
  path: {
    type: 'line',
    xs: 'xs',
    ys: 'ys',
  },
  skateboard: {
    type: 'rect',
    dx: 'x',
    dy: 'y',
    color: '"#ff5858"',
    width: 10,
    height: 10,
  },
}

and here's the SVG equivalent:

<figure>
  <svg viewBox="-100 0 500 200">
    <rect x="-100" width="500" height="2" fill="lightgray" />
    <g :transform="`translate(${disk}) rotate(${-rotation})`">
      <line :x2="r*1.5" fill="none" stroke="blue" stroke-width="2" />
      <circle :r="r" fill="lightgray" stroke="black" stroke-width="2" />
      <line :x1="-r" :x2="r" fill="none" stroke="black" stroke-dasharray="3" />
    </g>
    <polyline :points="sim.history" fill="none" stroke="green" stroke-width="2" />
    <rect :x="sim.x-5" :y="sim.y-5" :width="10" :height="10" fill="#ff5858" />
  </svg>
  Angle: <input v-model="rotation" type="range" min="0" max="360" />
</figure>

If you already know SVG, the SVG version will make more sense, and if you already know Vega Lite, the JSON version will make more sense. I think this is a matter of familiarity, but neither one is better than the other.

Note the :foo="expr" syntax which is a computed value in Vue, like using =expr for formulas in spreadsheets or foo={expr} in React or foo=${expr} in Lit-html.

Here's the glue code where I tell Vue to find the <figure> and calculate the computed values. In Vue, v-model binds the slider to the rotation field, so when you change the slider, the rotation updates, and the diagram is recomputed automatically, like spreadsheet formulas.

new Vue({
    el: "figure",
    data: {
        disk: [150, 150],
        r: 50,
        rotation: 30,
    },
    computed: {
        sim() { return simulate(this.disk, this.r, this.rotation); },
    },
});

I'm a big fan of spreadsheets and wish more programming was like that.

I used a slider in this example because it's already built in but it might be nice to use direct manipulation of the angle. Vue would treat it the same; you can change rotation any way you want and it will figure out the recalculation dependencies like a spreadsheet.


I continue to be frustrated that Flash no longer runs. So much content is now inaccessible. Bret's Flash version is so much flashier. It uses mouseover to set the rotation (instead of a slider). It has nice graphics on everything. It animates the skateboard so you can see it bounce off the wall. It animates the calculation of the plot (second diagram). I recorded a movie:


Here's a second diagram from the paper which I have not yet reproduced:

from math import radians, sin, cos

disk = [150, 150]
r = 50

rotations = []
times = []

for rotation in range(0, 60, 3):
    theta = radians(rotation)
    x = disk[0] + r * cos(theta)
    y = disk[1] - r * sin(theta)
    vel_x = -sin(theta)
    vel_y = -cos(theta)

    time = abs(y / vel_y)
    end_x = x + vel_x * time
    end_y = 0

    rotations.append(rotation)
    times.append(time)
    continue  #break
export default {
  disc: {
    type: 'ellipse',
    dx: 'disk[0]',
    dy: 'disk[1]',
    rx: 'r',
    ry: 'r',
    text: '"--------------"',
    rotate: '-rotation',
  },
  path: {
    type: 'line',
    xs: '[x, end_x]',
    ys: '[y, end_y]',
    color: '"gray"',
  },
  skateboard: {
    type: 'rect',
    dx: 'end_x',
    dy: 'end_y',
    color: '"#ff5858"',
    width: 10,
    height: 10,
  },
  plot: {
    type: 'line',
    x: 100,
    y: 250,
    xs: 'rotations',
    ys: {
      value: 'times',
      scale: {
        type: 'linear',
        domain: [140, 200],
        range: [100, 0],
      },
    },
  },
}

Email me , or tweet @redblobgames, or comment: