Code Steps

p5.js Particle Workshop 3/3

December 03, 2019

Add multiple particles. Make them bounce from each other, and from the mouse.

This post continues Part 2.

let particle1 = {
x: 150,
y: 50,
dy: 1,
}
let particle2 = {
x: 250,
y: 50,
dy: 1,
}
export function setup() {
createCanvas(windowWidth, windowHeight)
}
export function draw() {
fill("darkblue")
rect(0, 0, width, height)
processParticle(particle1)
processParticle(particle2)
}
function processParticle(particle) {
particle.y += particle.dy
particle.dy += 0.01
fill("white")
rect(particle.x, particle.y, 10, 10)
}
}
createCanvas(windowWidth, windowHeight)
}
createCanvas(windowWidth, windowHeight)
}
createCanvas(windowWidth, windowHeight)
}
createCanvas(windowWidth, windowHeight)
}
createCanvas(windowWidth, windowHeight)
}
createCanvas(windowWidth, windowHeight)
}
let particles = [makeParticle(), makeParticle()];
createCanvas(windowWidth, windowHeight)
}
let particles = Array(2).fill().map(makeParticle)
let particles = [makeParticle(), makeParticle()];
}
let particles = Array(100).fill().map(makeParticle)
let particles = Array(2).fill().map(makeParticle)
}
let particles = Array(100).fill().map(makeParticle)
particles = Array(100).fill().map(makeParticle)
}
particles = Array(100).fill().map(makeParticle)
}
particles = Array(100).fill().map(makeParticle)
}
particles = Array(100).fill().map(makeParticle)
}
particles = Array(100).fill().map(makeParticle)
}
particles = Array(100).fill().map(makeParticle)
let distance = sqrt(dx ** 2 + dy ** 2)
}
let distance = sqrt(dx ** 2 + dy ** 2)
}
let distance = sqrt(dx ** 2 + dy ** 2)
}
let particles = Array(100).fill().map(makeParticle)
let distance = sqrt(dx ** 2 + dy ** 2)
}
let particles = Array(100).fill().map(makeParticle)
let objects = [{x: mouseX, y: mouseY, dx: 0, dy: 0}, ...particles]
}
let objects = [{x: mouseX, y: mouseY, dx: 0, dy: 0}, ...particles]
}
let objects = [{x: mouseX, y: mouseY, dx: 0, dy: 0}, ...particles]
}
let objects = [{x: mouseX, y: mouseY, dx: 0, dy: 0}, ...particles]

Here’s where we left off in the previous post.

Draw circles insead of squares.

Put the two particles in an array.

The draw functions read the particles from the array, instead of from the variables particle1 and particle2.

The two calls to processParticle can be replaced by a call to forEach(processParticle). This applies processParticle to each of the items in the array.

Now draw will work with an array of any length.

Give the particles a random initial position.

Math.random() returns a floating point number (basically, a real number or decimal, although it is not exactly the same as either of these) between 0.0 (inclusive) and 1.0 (exclusive).

Multiplying such as number by windowWidth yields a number between 0.0 and windowWidth.

The code that creates the initial value for particle1 is now identical to the code that creates the initial value for particle2.

Extract the code that computes an initial value for particle1 into a new function makeParticle, and use this function to create the initial value for particle1.

Use this same function to create the initial value for particle2.

We’re only using the particle1 and particle2 variables to store the return value from makeParticle long enough to put it in the particles array. Put these values directly into the array, instead. This allows us to remove the particle1 and particle2 variables.

(We’re gradually getting rid of the code that’s specific to their being exactly two particles.)

Here’s a trick for making an array of length 2, filled by calling makeParticle twice.

Array(2) makes an array with two elements. (They both have the special value undefined..) map calls its argument makeParticle once for each element in the array, and makes a new array that contains the return values from all these calls to makeParticle. That new array becomes the value of particles.

The advantage of using this trick to create the array is that we can as easily make 100 particles instead of just two.

If we create the array inside of setup, then we can use the p5.js random function. random(windowWidth) returns a floating-point number between 0.0 and windowWidth. It’s simpler than the code we were using before.

The p5.js functions are only available inside the setup() and draw() functions (and the event handler functions such as keyPressed() and mouseClicked()), and the functions that they call. This is why we had to move the initialization of particles inside of setup, in order to use p5.js’s random.

Give the particles a small initial random x and y velocity. They’ll each start out moving at a randomm speed (between 0 and 1.41 pixels per second), in a random direction.

The p5.js function random(a, b), with two arguments, returns a value between a and b. It’s equivalent to the JavaScript expression a + (b - a) * Math.random().

Make the particles bounce them when they hit the “floor”.

Bounce them off the walls too. (But not — yet — the ceiling.)

I prefer separate functions for moving and drawing the particles.

The particles bounce when they get close to each other.

This is implemented by looping over each pair of particles p1 and p2, testing whether their centers are close enough, and, if they are, applying a force that moves them away from each other.

The nested forEach’s loop over pairs of particles. The outer forEach loops over each particle, the inner forEach then loops over each particle, and the if statement keeps the code beneath it from comparing a particle to itself.

This code actually examines each pair twice: At one point it sets p1 to particles[0] and p2 to particles[1], and later p1 == particles[1] and p2 == particles[0].

This is mostly harmless. It means that the forces are applied twice, and it means that the bounceParticles takes twice as long as it needs to. We’ll address this at the end of this article.

Turn off gravity, so we can see the particle interactions more easily.

With gravity off, the particles should bounce off the ceiling as well.

Add viscosity (the particles slow down over time).

Bounce the particles off the mouse position as well as each other. This lets you move the mouse around the screen to scatter particles from the mouse path.

We handle this with minimal modifications considering the mouse to be just another object, and running the bounce code over all the objects including the mouse, instead of just the particles.

This updatees the mouse “object“‘s dx and dy properties, which we then ignore.

Advanced Stuff

As promised, here’s the change to only consider each pair of particles once (to process objects[0] and objects[1], but not also to separately process objects[1] and objects[0]).

map and forEach call the supplied function with two arguments, the array element and its index within the array. Use this to only consider pairs where the p1 precedes p2.

Wouldn’t it be cool if there a function like forEach, that called our function on each pair of elements from the array, taking care not to call our function with both (array[0], array[1]) and (array[1], array[0])?

There can be! Now that we’ve written the logic that handles this, we can extract it tto a new function forEachPair. Now it’s easier to read what bounceParticles is doing.