Building a particle constellation effect

Written by radarboy3000 | Published 2017/01/13
Tech Story Tags: javascript | creative-coding | media-art | learning-to-code | generative-art

TLDRvia the TL;DR App

Checking two arrays against each other

If you’ve been following along, do a new git pull to get the latest code.

If you’re new to the series start here: Part1, Part 2, Part 3, Part 4 and Part 5

All the code and libraries for these tutorials can be found here: https://github.com/GeorgeGally/creative_coding

I want to introduce you to two functions I use all the time. The first is dist() that enables you to calculate the distance between two objects:

// returns distance between to objectsdist(x1, y1, x2, y2);

So we could either write it like this:

var my_distance = dist(x1, y1, x2, y2);

Or use it in an if statement like this:

if (dist(x1, y1, x2, y2) > 100) {

// do something

}

The dist() function is handy for not just for checking the distance between to objects, but for collision detection and obstacle avoidance. The inner workings of the function, which you don’t really need to know if you’re using my library, look like this:

function dist(x1, y1, x2, y2) {x2-=x1; y2-=y1;return Math.sqrt((x2*x2) + (y2*y2));}

The other function I want to show you is posNeg() which returns a random value of either 1 or -1. You might have noticed in some of your explorations that when you ask for a random speed for example of random(-10, 10) your value can end up as zero or close to that, which is often not what you want. So we can use posNeg() to circumvent that by doing something like this:

var speed_x = posNeg * random(5,10);

OK, enough theory. Let’s recreate the constellation network effect in the image above. First let’s get some balls flying around… Nothing new here really, except the use of posNeg() and me dropping the brackets on the if statement, which you can do if there’s only one line of code inside it:

var ctx = createCanvas("canvas1");var number_of_balls = 200;var balls = [];var radius = 200;ctx.lineWidth = 1;for (var i = 0; i < number_of_balls; i++) {addBall(i);}

function addBall(_i){var ball = {x: random(w),y: random(h),speed_x: posNeg() * random(0.2, 1),speed_y: posNeg() * random(0.2, 1),size: 5,colour: rgb(255),angle: i * 360/number_of_balls}balls.push(ball);}

function draw(){

ctx.background(0);

moveBall();drawBall();

}

function moveBall(){

for (var i = 0; i < balls.length; i++) {

var b = balls\[i\];  
b.x += b.speed\_x;  
b.y += b.speed\_y;

**if (bounce(b.x, 0, w)) b.speed\_x \*=-1;  
if (bounce(b.y, 0, h)) b.speed\_y \*=-1;**

}

}

function drawBall(){

for (var i = 0; i < balls.length; i++) {

 var b = balls\[i\];  
 ctx.fillStyle = b.colour;  
 ctx.fillEllipse(b.x, b.y, b.size);

}

}

Now let’s make use of the dist() function, and say: if a particle is near another particle draw a line between them. In code we would say:

if (dist(b1.x, b1.y, b2.x, b2.y) <= 50) {ctx.line(b1.x, b1.y, b2.x, b2.y);}

We need to loop through all the particles to do this check, so we could create a function like so, which we’ll then call in our moveBall() function:

function moveBall(){

for (var i = 0; i < balls.length; i++) {

var b = balls\[i\];  
b.x += b.speed\_x;  
b.y += b.speed\_y;

drawConnections(i);

if (bounce(b.x, 0, w)) b.speed\_x \*=-1;  
if (bounce(b.y, 0, h)) b.speed\_y \*=-1;

}

}

// pass in the particle number we want to check against

function drawConnections(_particle_number) {

// loop through all the particles againfor (var j = 0; j < balls.length; j++) {

b1 = balls\[\_particle\_number\];  
b2 = balls\[j\];  
  
_// check the distances between the two particles  
// and draw a line between them   
//if they're less than a certain distance_

if (dist(b1.x, b1.y, b2.x, b2.y) <= 50) {  
    ctx.strokeStyle = rgb(255);  
    ctx.line(b1.x, b1.y, b2.x, b2.y);  
}

}

}

But there’s something inefficient with doing it that way.

Firstly, at some stage j would equal our _particle_number, ie. if would be checking against itself, and obviously it’s always less than 50. We could fix that by doing something like this, by using the != (not equals) sign:

function drawConnections(_particle_number) {

for (var j = 0; j < balls.length; j++) {

_// only check particles which aren't the same_  
**if (j != \_particle\_number) {**

  b1 = balls\[\_particle\_number\];  
  b2 = balls\[j\];

  if (dist(b1.x, b1.y, b2.x, b2.y) <= 50) {  
    ctx.strokeStyle = rgb(255);  
    ctx.line(b1.x, b1.y, b2.x, b2.y);  
  }

**}**

}

}

But still there’s something that’s inefficient (and with particles it’s always good to learn to be as efficient as possible). From our moveBall() and drawConnections() functions, essentially we have two loops, one inside the other, checking against all other balls. So what we’re really doing is this:

for (var i= 0; i< balls.length; j++) {

 // get a particle     
 b1 = balls\[i\];

 // now loop through all particles and check against it  
 for (var j = 0; j < balls.length; j++) {

 b2 = balls\[j\];

if (i!=j) { // check the distance between particles etc.}

}

}

If we were checking say particle 5 against particle 10. We would then later check particles 10 against particle 5. So we’re actually doubling up on our calculations. Not good. So once we’ve checked a particle, no reason to check it again. And the solution is simple and elegant. If checked against the first particle: balls[0], we could just start the second loop from the next particle, which would be p[1]. And continue on like so. Also as the last particle would have already been checked against all the others, no we can shorten the first loop:

for (var i= 0; i< balls.length-1; j++) {

// get a particleb1 = balls[i];

// now loop the next particles  

** for (var j = i+1; j < balls.length; j++) {**

  b2 = balls\[j\];  
 
  _// check the distance between particles etc._

}

}

Here’s the complete code:

var ctx = createCanvas("canvas1");var number_of_balls = 200;var max_distance = 80;var balls = [];

// push a ball and it's values into the arrayfor (var i = 0; i < number_of_balls; i++) {addBall(i);}

function addBall(_i){var ball = {x: random(w),y: random(h),speed_x: posNeg() * random(0.2, 1),speed_y: posNeg() * random(0.2, 1),size: 5,colour: rgb(255),}balls.push(ball);}

function draw(){

ctx.background(0);

moveBall();drawBall();

}

function moveBall(){

for (var i = 0; i < balls.length; i++) {

var b = balls\[i\];  
b.x += b.speed\_x;  
b.y += b.speed\_y;

if (bounce(b.x, 0, w)) b.speed\_x \*=-1;  
if (bounce(b.y, 0, h)) b.speed\_y \*=-1;

}

}

function drawBall(){

for (var i = 0; i < balls.length; i++) {

var b = balls\[i\];  
drawConnections(i);  
ctx.fillStyle = b.colour;  
ctx.fillEllipse(b.x, b.y, b.size);  

}

}

function drawConnections(_i) {

for (var j = _i+1; j < balls.length; j++) {

  b1 = balls\[\_i\];  
  b2 = balls\[j\];

  if (dist(b1.x, b1.y, b2.x, b2.y) <= max\_distance) {  
    ctx.strokeStyle = rgb(255);  
    ctx.line(b1.x, b1.y, b2.x, b2.y);  
  }

}

}

One more thing we could do to make it look even better if to adjust the lineWidth based on the distance:

function drawConnections(_i) {

for (var j = 0; j < balls.length; j++) {

b1 = balls\[\_i\];  
b2 = balls\[j\];  
var distance = dist(b1.x, b1.y, b2.x, b2.y);  
if (j!=i) {  
  if ( distance <= max\_distance) {  
      ctx.strokeStyle = rgb(255);  
      **ctx.lineWidth = 1 - distance/max\_distance;**  
      ctx.line(b1.x, b1.y, b2.x, b2.y);  
  }  
}  

}

}

And boom, we have a simple, clean and elegant particle constellation effect, like in the header image above. Awesome. Hope you enjoyed this tutorial. Happy coding.

If you’re new to the series start here: Part1, Part 2, Part 3, Part 4 and Part 5

All the code and libraries for these tutorials can be found here: https://github.com/GeorgeGally/creative_coding

Follow me on Instagram here: https://www.instagram.com/radarboy3000/

Follow me on Twitter here: https://twitter.com/radarboy_japan

And like my Facebook Page here: https://www.facebook.com/radarboy3000


Published by HackerNoon on 2017/01/13