Lunch-time hack: Day 2 – self organising particles

In today’s hack I wanted to play with a self organising particle system, so I set about writing a loop which worked out the distance between each particle and generated a force based on that. It was a lot to think about so I didn’t quite get it working in 15 minutes and decided to be naughty and finish it off this evening.
One of the best things about it is that it’s a truly chaotic system – the rules are 100% known, but you can’t be sure exactly what will happen when you add a new particle.
Have a play, go on, it’s really cool!
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
var WIDTH = 500, HEIGHT = 500, PARTICLES = [], STARTING_COUNT = 100, MAX_PARTICLES = 200, DAMPING = 0.2, 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 () { // while (STARTING_COUNT--) { // addParticle (); // } _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 = 100-magnitude; if ((actingDistance > 0) && (magnitude > 0)) { 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 (); } |