Pendulum Party
This file contains 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
license: gpl-3.0 |
This file contains 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
<!DOCTYPE html> | |
<html> | |
<head> | |
<style> | |
body { | |
margin: 0; | |
} | |
</style> | |
</head> | |
<body> | |
<canvas></canvas> | |
<script> | |
const vecmath = { | |
// Add vector w to vector v | |
add: (v, w) => { | |
let out = []; | |
for (let i = 0; i < v.length; i++){ | |
out[i] = v[i] + w[i]; | |
} | |
return out; | |
}, | |
// Translate position of vector v by an angle in radians and a distance in pixels | |
trans: (v, ang, dist) => [v[0] + dist * Math.cos(ang), v[1] + dist * Math.sin(ang)] | |
}; | |
// The Pendulum class | |
class Pendulum { | |
constructor(){ | |
this._acceleration = 0; | |
this._angle = Math.PI / 4; | |
this._damping = 1.0; | |
this._gravity = 0.4; | |
this._length = 200; | |
this._origin = [innerWidth / 2, 0]; | |
this._velocity = 0; | |
this._position = vecmath.trans(this._origin, this._angle, this._length); | |
return this; | |
} | |
acceleration(num){ | |
return num ? (this._acceleration = num, this) : this._acceleration; | |
} | |
angle(num){ | |
return num ? (this._angle = num, this) : this._angle; | |
} | |
damping(num){ | |
return num ? (this._damping = num, this) : this._damping; | |
} | |
gravity(num){ | |
return num ? (this._gravity = num, this) : this._gravity; | |
} | |
length(num){ | |
return num ? (this._length = num, this) : this._length; | |
} | |
origin(arr){ | |
return arr ? (this._origin = arr, this) : this._origin; | |
} | |
position(arr){ | |
return arr ? (this._position = arr, this) : this._position; | |
} | |
velocity(num){ | |
return num ? (this._velocity = num, this) : this._velocity; | |
} | |
tick(){ | |
return this | |
.acceleration((-1 * this.gravity() / this.length()) * Math.sin(this.angle())) | |
.velocity((this.velocity() + this.acceleration()) * this.damping()) | |
.angle(this.angle() + this.velocity()) | |
.position( | |
vecmath.add( | |
[this.length() * Math.sin(this.angle()), this.length() * Math.cos(this.angle())], | |
this.origin() | |
) | |
); | |
} | |
} | |
// Create the pendula | |
const pendula = []; | |
const rows = 8; | |
const cols = 10; | |
let i = 1; | |
for (let row = 0; row < rows; row++){ | |
const evenRow = row % 2 === 0; | |
for (let col = 0; col < cols; col++){ | |
if (evenRow && col === cols - 1) continue; | |
const px = innerWidth / cols; | |
const x = px / 2 + col * px + (evenRow ? px / 2 : 0); | |
const y = 10 + row * innerHeight / rows; | |
pendula.push( | |
new Pendulum() | |
.gravity(.15) | |
.length(-10 + px / 2) | |
.origin([x, y]) | |
.angle(Math.PI / 2 * (i % 2 === 0 ? 1 : -1)) | |
); | |
i++; | |
} | |
} | |
// Draw to Canvas | |
const canvas = document.querySelector("canvas"); | |
canvas.width = innerWidth; | |
canvas.height = innerHeight; | |
const ctx = canvas.getContext("2d"); | |
ctx.strokeStyle = "#aaa"; | |
function tick(){ | |
requestAnimationFrame(tick); | |
ctx.clearRect(0, 0, innerWidth, innerHeight); | |
pendula.forEach((p, i, e) => { | |
draw(p, `rgb(200, 40, ${(i + 1) / e.length * 255})`); | |
}); | |
} | |
tick(); | |
function draw(p, fill){ | |
p.tick(); | |
ctx.fillStyle = fill; | |
ctx.beginPath(); | |
ctx.moveTo(...p.origin()); | |
ctx.lineTo(...p.position()); | |
ctx.stroke(); | |
ctx.beginPath(); | |
ctx.arc(...p.origin(), 3, 0, Math.PI * 2) | |
ctx.fill(); | |
ctx.beginPath(); | |
ctx.arc(...p.position(), 10, 0, Math.PI * 2) | |
ctx.fill(); | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment