Clinton Montague

Developer, learner of things, functional programming enthusiast, hacker, and all round inquisitor.

Generative art experiment 5

April 24, 2013

I’ve played with visualising the forces between particles that repel each other before, but I thought I’d have a little play around with how I could use that to create something that actually looked good (to me at least!)

I put it together on codepen (which is my new favourite tool, by the way) so feel free to fork it and see what you can do with it!

It takes a little while to draw, but it’s quite interesting seeing it building up.

The javascript code

```var WIDTH = 800,
HEIGHT = 500,
PARTICLES = [],
STARTING_COUNT = 600,
MAX_PARTICLES = 600,
DAMPING = 0.05,
CURRENT_FRAME = 0,

var canvas = document.getElementsByTagName ('canvas')[0];
canvas.width = WIDTH;
canvas.height = HEIGHT;

var ctx = canvas.getContext ('2d');

function random (min, max) {
return (Math.random() * (max - min) + min);
}

function _particleTimer () {
if (PARTICLES.length < STARTING_COUNT) setTimeout(_particleTimer, 0);
}

function init () {
_particleTimer();
animate();
}

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++;

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 = 255;

var color = green - actingDistance * (green / 2);
if (color < 0) color = 0;
if (color > 255) color = 255;

if (color < 115 && color > 10) {
ctx.save ();
ctx.strokeStyle = 'hsla(' + color + ', 100%, 50%, 0.05)';
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);
}
if (CURRENT_FRAME > 1500) {
document.getElementById("message").innerHTML = 'Done!';
} else {
setTimeout(animate, 0);
}
}

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 - PADDING) {
}
if (position.x < PADDING - 3) {
}
if (position.y > HEIGHT - PADDING) {
}
position.y = random(HEIGHT, HEIGHT - PADDING);
}
}
}

function draw (particle) {
ctx.save ();
ctx.globalCompositeOperation = 'xor';
ctx.translate (particle.position.x, particle.position.y);
ctx.fillStyle = "rgba(255,255,255,0.1)";

ctx.fillRect (0, 0, 1, 1);
ctx.restore ();
}

init();
```