Lunch-time hack: Day 3 – Visualising forces between repelling particles
June 9, 2011
See demo →
var WIDTH = 500,
HEIGHT = 500,
PARTICLES = [],
STARTING_COUNT = 100,
MAX_PARTICLES = 200,
DAMPING = 0.05,
CURRENT_FRAME = 0;
var canvas = document.getElementsByTagName ('canvas')[0];
canvas.width = WIDTH;
canvas.height = HEIGHT;
var ctx = canvas.getContext ('2d');
init ();
function random (min, max) {
return (Math.random() * (max - min) + min);
}
function _particleTimer () {
addParticle (random(0, HEIGHT), random(0, HEIGHT));
if (PARTICLES.length < STARTING_COUNT) setTimeout (_particleTimer, 50);
}
function init () {
_particleTimer();
setInterval (animate, 1000 / 50);
canvas.addEventListener ('mousedown', onMouseDown, false);
animate ();
}
function onMouseDown (e) {
addParticle (e.offsetX, e.offsetY);
}
function addParticle (x, y) {
PARTICLES.push ({
position: {
x: x || random(0, WIDTH),
y: y || random (0, HEIGHT)
},
force : {
x: 0,
y: 0
},
vel : {
x: 0,
y: 0
}
});
}
function animate () {
while (PARTICLES.length > MAX_PARTICLES) PARTICLES.shift ();
CURRENT_FRAME++;
ctx.save ();
ctx.fillStyle = "rgba(0,0,0,0.3)";
ctx.fillRect (0, 0, WIDTH, HEIGHT);
ctx.fillStyle = 'rgba(0,0,0,0)';
ctx.lineWidth = 1;
ctx.strokeStyle = '#ffffff';
ctx.beginPath();
ctx.moveTo (20, 20);
ctx.lineTo (20, HEIGHT-20);
ctx.lineTo (WIDTH-20, HEIGHT-20);
ctx.lineTo (WIDTH-20, 20);
ctx.closePath();
ctx.stroke();
ctx.restore();
var force = {x : 0, y: 0};
for (var i = 0; i < PARTICLES.length; i++) {
var p1 = PARTICLES[i];
for (var j = i + 1; j < PARTICLES.length; j++) {
var p2 = PARTICLES[j];
// length vector
force.x = p2.position.x - p1.position.x;
force.y = p2.position.y - p1.position.y;
var magnitude = mag (force);
var actingDistance = 50-magnitude;
if ((actingDistance > 0) && (magnitude > 0)) {
var red = 0;
var green = 120;
var color = green - actingDistance * (green / 2);
if (color < 0) color = 0;
if (color > 120) color = 120;
ctx.save ();
ctx.strokeStyle = 'hsla(' + color + ', 100%, 50%, 1)';
ctx.strokeWidth = 1;
ctx.beginPath ();
ctx.moveTo (p1.position.x, p1.position.y);
ctx.lineTo (p2.position.x, p2.position.y);
ctx.stroke();
ctx.closePath();
ctx.restore ();
force.x *= DAMPING * actingDistance / magnitude;
force.y *= DAMPING * actingDistance / magnitude;
p1.force.x -= force.x;
p1.force.y -= force.y;
p2.force.x += force.x;
p2.force.y += force.y;
}
}
draw (p1);
update (p1);
}
}
function mag (vector) {
return Math.sqrt (vector.x * vector.x + vector.y * vector.y);
}
function update (particle) {
with (particle) {
vel.x += force.x;
vel.y += force.y;
vel.x *= 0.8;
vel.y *= 0.8;
position.x += vel.x;
position.y += vel.y;
force.x = 0;
force.y = 0;
if (position.x > WIDTH - 26) {
position.x = WIDTH - 26;
}
if (position.x < 21) {
position.x = 21;
}
if (position.y > HEIGHT - 26) {
position.y = HEIGHT - 26;
}
if (position.y < 21) {
position.y = 21;
}
}
}
function draw (particle) {
ctx.save ();
ctx.translate (particle.position.x, particle.position.y);
ctx.fillStyle = "hsla("+CURRENT_FRAME%360 +",50%,50%,1)";
ctx.fillRect (0, 0, 5, 5);
ctx.restore ();
}
See demo →