A rolling sphere's spin, live¶
The angular velocity of a sphere rolling without slipping, ω = (n × v) / r, n the surface's unit normal, v the sphere's velocity along it and r its radius (lusine.kinematics.rolling_spin): the point touching the surface stands still. A boule rolling on the terrain turns so, and its grooves with it. Below, a boule of 3.7 cm radius is rolled across a level floor at 3 m/s, slowing and curving, a hundredth of a second a step and a hundred steps a second, and at each step the library's law gives its spin on your GPU as you read. Beside it the program sums the angle the spin turns the boule through and the distance it rolls: rolling without slipping, the one times the radius is the other.
This page runs the program on your GPU through WebGPU, so it needs a browser with WebGPU (a recent Chrome, Edge, Safari or Firefox). Without it the program does not run and nothing is drawn below.
What it shows¶
- The strip above the controls is green while every check holds, and turns red for good once one fails.
- The roll: the first chart draws the law's spin and the speed over the radius over the last six seconds, the two one over the other as the boule slows; the second, the angle turned times the radius and the distance rolled against the time since the roll, a point a step, one over the other. Every six seconds the boule is rolled again. Below them, the time, the spin, the speed of the point touching the floor, and the two lengths.
- The checks: no slip, the point touching the floor, at −r n from the centre, moving at v + ω × (−r n), within a millionth of the boule's speed; speed over radius, the spin's size |v| / r within 8 unit roundoffs (2⁻²⁴); axis flat and across, the spin with no part along the floor's normal and none along the motion, within 4 unit roundoffs; turned as rolled, the angle turned times the radius the distance rolled, within 0.001%; a boule at 3 m/s, a boule of 3.7 cm at 3 m/s spinning at 3 / 0.037 = 81.081081 rad/s, about thirteen turns a second, within 4 unit roundoffs. Each is held at every step.
Every number here is computed by the program on the GPU; the page only reads the values back and draws them. The boule's radius, its speed, how fast it slows and curves, the step and how long each roll lasts are the program's settings, which the host may write; this page offers no control to change them yet.
The program¶
The boule, what the panels show and the checks, the panels the page draws, and the one procedure, main, run each step: the boule's velocity at its time, its spin from the law, the angle and the distance summed, and the checks.
// The rolling spin's live page: a boule rolling on a level floor, slowing and curving, its spin each step from the
// library's law, the point touching the floor held still, the spin's size held to the speed over the radius and its
// axis to lying flat and across the motion, the angle it has turned through held to the distance it has rolled over its
// radius, and the law held to a boule at 3 m/s, with the page's checks computed here on the GPU and shown by the panels
// the program declares.
namespace rolling_spin;
lusine.version version = { { number = 1 } };
// The reader's settings: the boule's radius; the speed it is rolled at, and how fast it slows; how fast its heading
// turns; the step; and how long each roll is followed.
host struct {
float<m> radius = 0.037 m; float<m/s> speed = 3.0 m/s; float<m/s2> slowing = 0.4 m/s2; float<s^-1> curving = 0.3 s^-1;
float<s> dt = 0.01 s; float<s> roll = 6.0 s;
} settings;
// The boule: its time since it was rolled, its velocity along the floor, its spin from the law, the angle it has turned
// through (the spin's size summed over the steps) and the distance it has rolled (the speed's).
struct {
float<s> time; vector<float<m/s>, 3> velocity; vector<float<rad/s>, 3> spin; float<rad> turned; float<m> rolled;
} boule;
// What the panels show: the spin's size, the speed over the radius, the touching point's speed, the turned angle times
// the radius and the distance rolled.
struct {
float<rad/s> spin; float<rad/s> speed_over_radius; float<m/s> slip; float<m> arc; float<m> rolled;
float<rad/s> three;
} shown;
// The checks: the point touching the floor, at -r n from the centre, moving at v + w x (-r n) within 0.000001 of the
// speed; the spin's size the speed over the radius within 8 unit roundoffs; its axis flat (no part along the floor's
// normal) and across the motion (w . v within 4 unit roundoffs of |w| |v|); the turned angle times the radius the
// distance rolled within 0.001%; and a boule of 0.037 m at 3 m/s spinning at 81.081081 rad/s, 3 / 0.037, within 4
// unit roundoffs. Each at every step.
struct {
bool no_slip = true; bool size = true; bool axis = true; bool rolled = true; bool three = true;
} checks;
// A strip of lamps the page draws, lit green while every check holds and red once one fails.
struct lamp { vector<float, 4> colour; };
lattice<lamp, 2> lamps(spacing = 1 m, boundary = wall);
vector<float, 4> lamp_colour(bool holds)
{
return holds ? vector<float, 4>(0.15, 0.6, 0.3, 1.0) : vector<float, 4>(0.75, 0.15, 0.15, 1.0);
}
vector<float, 3> up() { return vector<float, 3>(0.0, 1.0, 0.0); }
// The boule's velocity at a time: its speed falling at the slowing, never below a tenth of a metre a second, along a
// heading in the floor that turns, (1 - a^2, 0, 2a) / (1 + a^2) at a = curving t, a unit vector with no trigonometry.
vector<float<m/s>, 3> velocity(float<m/s> speed, float<m/s2> slowing, float<s^-1> curving, float<s> time)
{
let s = speed - slowing * time > 0.1 m/s ? speed - slowing * time : 0.1 m/s;
let a = curving * time;
let q = 1.0 + a * a;
return vector<float<m/s>, 3>(s * ((1.0 - a * a) / q), 0.0 m/s, s * (2.0 * a / q));
}
// The touching point's velocity: the centre's plus the spin's turn of the point at -r n from it.
vector<float<m/s>, 3> touching(vector<float<m/s>, 3> velocity, vector<float<rad/s>, 3> spin, float<m> radius)
{
return velocity + cross(spin, up() * -radius) / 1.0 rad;
}
float<rad/s> three(float<m> radius, float<m/s> speed)
{
return length(lusine.kinematics.rolling_spin(normal = up(),
velocity = vector<float<m/s>, 3>(speed, 0.0 m/s, 0.0 m/s), radius));
}
output table<lusine.widget> panels = {
lusine.series { { panel = "the roll", chart = "spin", quantity = shown.spin, steps = 600, label = "the law's" } },
lusine.series { { panel = "the roll", chart = "spin", quantity = shown.speed_over_radius, steps = 600,
label = "speed / radius" } },
lusine.series { { panel = "the roll", chart = "rolled", quantity = shown.arc, steps = 600, against = boule.time,
label = "angle x radius" } },
lusine.series { { panel = "the roll", chart = "rolled", quantity = shown.rolled, steps = 600, against = boule.time,
label = "rolled" } },
lusine.value { { panel = "the roll", quantity = boule.time, label = "time" } },
lusine.value { { panel = "the roll", quantity = shown.spin, label = "spin" } },
lusine.value { { panel = "the roll", quantity = shown.slip, label = "touching point's speed" } },
lusine.value { { panel = "the roll", quantity = shown.arc, label = "angle times radius" } },
lusine.value { { panel = "the roll", quantity = shown.rolled, label = "distance rolled" } },
lusine.value { { panel = "the checks", quantity = shown.three, label = "at 3 m/s" } },
lusine.value { { panel = "the checks", quantity = checks.no_slip, label = "no slip" } },
lusine.value { { panel = "the checks", quantity = checks.size, label = "speed over radius" } },
lusine.value { { panel = "the checks", quantity = checks.axis, label = "axis flat and across" } },
lusine.value { { panel = "the checks", quantity = checks.rolled, label = "turned as rolled" } },
lusine.value { { panel = "the checks", quantity = checks.three, label = "a boule at 3 m/s" } },
};
void main()
{
// Every roll the boule is rolled again.
boule.time = boule.time >= settings.roll ? 0.0 s : boule.time;
boule.turned = boule.time == 0.0 s ? 0.0 rad : boule.turned;
boule.rolled = boule.time == 0.0 s ? 0.0 m : boule.rolled;
boule.velocity = velocity(settings.speed, settings.slowing, settings.curving, boule.time);
boule.spin = lusine.kinematics.rolling_spin(normal = up(), boule.velocity, settings.radius);
boule.turned = boule.turned + length(boule.spin) * settings.dt;
boule.rolled = boule.rolled + length(boule.velocity) * settings.dt;
boule.time = boule.time + settings.dt;
shown.spin = length(boule.spin);
shown.speed_over_radius = length(boule.velocity) / settings.radius * 1.0 rad;
shown.slip = length(touching(boule.velocity, boule.spin, settings.radius));
shown.arc = boule.turned * settings.radius / 1.0 rad;
shown.rolled = boule.rolled;
shown.three = three(settings.radius, settings.speed);
checks.no_slip = checks.no_slip && shown.slip <= 0.000001 * length(boule.velocity);
checks.size = checks.size
&& abs(shown.spin - shown.speed_over_radius) <= 8.0 / 16777216.0 * shown.speed_over_radius;
checks.axis = checks.axis && dot(boule.spin, up()) == 0.0 rad/s
&& abs(dot(boule.spin, boule.velocity)) <= 4.0 / 16777216.0 * shown.spin * length(boule.velocity);
checks.rolled = checks.rolled && abs(shown.arc - shown.rolled) <= 0.00001 * shown.rolled;
checks.three = checks.three
&& abs <= 4.0 / 16777216.0 * 81.08108108108108 rad/s;
lamps.colour = lamp_colour(holds = checks.no_slip && checks.size && checks.axis && checks.rolled && checks.three);
}
The law's other proof (four hundred rolls at random against a reference, the touching point) is on its proof page.