Skip to content

Instantly share code, notes, and snippets.

@william-silversmith
Last active October 26, 2021 12:11
Show Gist options
  • Save william-silversmith/cdf9f0a7fd1a3e06040b to your computer and use it in GitHub Desktop.
Save william-silversmith/cdf9f0a7fd1a3e06040b to your computer and use it in GitHub Desktop.
The Bounce Factory
/* bounceFactory
*
* Consult this article: https://medium.com/@willsilversmith/the-bounce-factory-3498de1e5262#.pn5rcjp15
* The variables below are annotate with comments that reference the article.
*
* Simulate a physical bouncing motion based on physics equations of motion.
*
* We assume mass and gravity = 1 as they are immaterial when we normalize both
* the y and t axis to 1. The length of the animation in msec will determine "gravity"
* and the elasticity will determine the number of bounces.
*
* Required:
* [0] bounces: (positive int) how many bounces do you want
*
* Optional:
* [1] threshold (epsilon): [0..1], (default 0.1%) percent of energy remaining
* at which to terminate the animation
*
* Return: f(t), t in 0..1
*/
function bounceFactory (bounces, threshold) {
threshold = threshold || 0.001;
function energy_to_height (energy) {
return energy; // h = E/mg, Eqn. 4
}
function height_to_energy (height) {
return height; // E = mgh, Eqn. 4
}
function bounce_time (height) {
// 2 x the half bounce time measured from the peak
return 2 * Math.sqrt(2 * height); // Modified Eqn. 7
}
function speed (energy) {
// E = 1/2 m v^2, s = |sqrt(2E/m)|
return Math.sqrt(2 * energy); // Eqn. 8
}
var height = 1;
var potential = height_to_energy(height);
var elasticity = Math.pow(threshold, 1 / bounces); // Eqn. 10
// The critical points are the points where the object contacts the "ground"
// Since the object is initially suspended at 1 height, this either creates an
// exception for the following code, or you can use the following trick of placing
// a critical point behind 0 and representing the inital position as halfway though
// that arc.
var critical_points = [{
time: - bounce_time(height) / 2,
energy: potential,
},
{
time: bounce_time(height) / 2,
energy: potential * elasticity,
}];
potential *= elasticity;
height = energy_to_height(potential);
var time = critical_points[1].time;
for (var i = 1; i < bounces; i++) {
time += bounce_time(height);
potential *= elasticity; // Eqn. 2, remove energy after each bounce
critical_points.push({
time: time,
energy: potential,
});
height = energy_to_height(potential);
}
var duration = time; // renaming to emphasize it's the total time now
return function (t) {
t = clamp(t, 0, 1);
var tadj = t * duration;
if (tadj === 0) {
return 0;
}
else if (tadj >= duration) {
return 1;
}
// Find the bounce point we are bouncing from, for very long animations (hours, days),
// an binary search algorithm might be appropriate.
var index;
for (index = 0; index < critical_points.length; index++) {
if (critical_points[index].time > tadj) {
break;
}
}
var bouncept = critical_points[index - 1];
// Bouncing from a bounce point effectively resets time as it is a discontinuity
tadj -= bouncept.time;
var v0 = speed(bouncept.energy);
// Project position of object from bounce point to the current time
var pos = v0 * tadj + -0.5 * tadj * tadj; // Eqn. 1
return 1 - pos;
};
}
function clamp (val, lower, upper) {
return Math.max(Math.min(val, upper), lower);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment