Extremely fun and rewarding final product! Ran into a major problem when I realized that generating a resultant vector (from adding velocity and gravity together) would make the particle jump from one position to the next instead of animating between them. I decided to revisit interpolation to see if this was finally a good use case for it and it was! I ended up using a formula for LERP that is (1 - alpha)a + alpha * b
where a is the vector we are interpolating from and b is the vector we are interpolating to. We can use this as a useful way to interpolate between values by changing the value of alpha in incremental steps from 0 - 1, where an alpha value of 0 is the a vector and an alpha value of 1 is the b vector. By setting alpha to intermediate values we can make a seemingly smooth transition between two vectors.
Created
June 24, 2015 05:02
-
-
Save joshblack/60680adfd0b9eabb076d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const transform = { | |
translate(x, y) { | |
return transformFunction('translate', x, y); | |
} | |
} | |
function transformFunction(property, ...values) { | |
return `${property}(${values.join(', ')})`; | |
} | |
function projectile(start) { | |
const node = insert(particle(), start); | |
const gravity = [0, 1]; | |
let velocity = vector(100, rad(-75)); | |
return function loop() { | |
const resultant = add(velocity, gravity), | |
pos = position(node), | |
newPos = add(pos, resultant); | |
// Let's prevent the particle from going out of | |
// the screen | |
if (newPos[1] > innerHeight - 50) { | |
newPos[1] = innerHeight - 50; | |
} | |
interpolate(pos, newPos)(node); | |
velocity = resultant; | |
if (newPos[1] < innerHeight - 50) { | |
requestAnimationFrame(loop); | |
} | |
} | |
} | |
function interpolate(a, b) { | |
let alpha = 0; | |
return function loop(node) { | |
if (alpha < 1) { | |
place(node, intermediate(alpha, a, b)); | |
alpha += 0.1; | |
requestAnimationFrame(loop.bind(null, node)); | |
} | |
} | |
} | |
function intermediate(alpha, a, b) { | |
return add(scale(a, 1 - alpha), scale(b, alpha)); | |
} | |
function scale(vector, scalar) { | |
return vector.map((e) => e * scalar); | |
} | |
projectile([0, innerHeight - 50])(); | |
function px(unit) { | |
return `${unit}px`; | |
} | |
function add(a, b) { | |
const result = Array(a.length).fill(0); | |
for (let i = 0; i < a.length; i++) { | |
result[i] = a[i] + b[i]; | |
} | |
return result; | |
} | |
// particle | |
// particle() -> DOM node | |
function particle() { | |
const node = document.createElement('div'); | |
node.style.background = 'black'; | |
node.style.position = 'absolute'; | |
node.style.width = px(50); | |
node.style.height = px(50); | |
node.style.borderRadius = '50%'; | |
node.style.transformOrigin = 'center center'; | |
return node; | |
} | |
// insert(node, position: vector) -> DOM node | |
function insert(node, position) { | |
const { translate } = transform; | |
node.style.transform = | |
translate(...position.map(px)); | |
return document.body.appendChild(node); | |
} | |
function place(node, position) { | |
const { translate } = transform; | |
node.style.transform = | |
translate(...position.map(px)); | |
return node; | |
} | |
// position(node) -> vector | |
function position(node) { | |
const translate = node.style.transform, | |
p = translate.slice(10, translate.length - 1); | |
return p.split(',').map((s) => parseInt(s.trim(), 10)); | |
} | |
// remove(node) -> node | |
function remove(node) { | |
return node.parentNode.removeChild(node); | |
} | |
// shift(node, to: vector) -> node | |
function shift(node, to) { | |
const { translate } = transform; | |
node.style.transform = translate(...to.map(px)); | |
return node; | |
} | |
// move(node, velocity: vector) -> null | |
function move(node, velocity) { | |
const shifted = shift(node, add(position(node), velocity)); | |
requestAnimationFrame( | |
move.bind(null, shifted, velocity)); | |
} | |
function speed([a, b]) { | |
const { pow, sqrt } = Math; | |
return sqrt(pow(a, 2) + pow(b, 2)); | |
} | |
function direction([a, b]) { | |
return deg(Math.atan2(a, b)); | |
} | |
function deg(radian) { | |
return radian * (180 / Math.PI); | |
} | |
function rad(degree) { | |
return degree * (Math.PI / 180); | |
} | |
// magnitude: scalar, direction: rad -> vector | |
function vector(magnitude, direction) { | |
const a = Math.cos(direction) * magnitude, | |
b = Math.sin(direction) * magnitude; | |
return [a, b]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment