Building a particle constellation effect

Checking two arrays against each other

George Galanakis
HackerNoon.com
Published in
6 min readJan 13, 2017

--

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 objects 
dist(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 againstfunction drawConnections(_particle_number) {

// loop through all the particles again
for (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 particle   
b1 = 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 array
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),
}
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

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!

--

--

George Galanakis
HackerNoon.com

Media artist, tinkerer, dreamer. Generative motion, sound and visualizations. Data sculpture and Code Art. https://www.instagram.com/radarboy3000