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,
 PADDING = 10;

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 () {
	addParticle (random(0, WIDTH), random(0, HEIGHT));
	if (PARTICLES.length < STARTING_COUNT) setTimeout(_particleTimer, 0);
}


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


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

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