I've had a pen from some time ago featured in the Spark email, which got me inspired to put together a post. In this post I will try to explain the concept of building a simple particle setup that leads to a natural-looking motion.

The basis of this type of motion is what's often referred to as Brownian Motion. Every frame a particle will pick a random point within a square constraint, and move closer to it

  // update position with vector
this.x += (Math.random() * 2 - 1) * this.speed;
this.y += (Math.random() * 2 - 1) * this.speed;

though the code looks slightly different in this pen, the particles there operate on that principle. The red line shows the direction which the random point was picked, and can be hidden using the showVector setting.

Example 1 : move towards random point

The problem with this motion is that it looks too rigid, and not at all fluid. To solve this issue, I apply an easing to the vector..

Example 2 : ease towards random point

The basis of the easing is vector = vector * a + randomDestination * (1 - a), where a is like an easing amount, as a gets larger the more of the original vector remains the same, and less of the random number gets added ((1 - a)), the changes are smaller. As a gets smaller, the vector uses more of the random point and less of the vector, and the movement gets more erradic.

The code now looks like this:

  // pick random destination
this.dx = (Math.random()*2-1) * settings.particleSpeed;
this.dy = (Math.random()*2-1) * settings.particleSpeed;

// update vector
this.vx = this.vx * settings.a + this.dx * (1 - settings.a);
this.vy = this.vy * settings.a + this.dy * (1 - settings.a);

// update position with vector
this.x += this.vx
this.y += this.vy

You should be able to see a more natural movement emerge with a between 0.9 and 1. This laso depends on speed and frame rate, but I've found that a value close to 0.98 tends to work well.

Another variation is to split a; istead of using (1 - a) for the random multipler, to use a separate number (b). This way a + b don't need to add up to 1

Example 3: ease towards random point (with a and b)

If the numbers a and b add up to less than 1 (and b is lager than 0), then they'll have a tendency to not travel far, and hover around the same spot, as the cumulative vector is constantly deminishing, and the random component is making shake in it's place. If the combined number (a + b) is larger than 1, they'll have a tendency to travel more because the cumulative vector is growing, and the random component is making the particle change with small increments.

So that concludes the basics of how I use use the vector of a particle and a new random position every frame to generate a somewhat fluid motion. With these basics in mind, it's time to do some experiments.

Experiment 1 : sine × b

I've added a sine wave multiplier to to the b component. If b is larger (positive or negative), it moves faster, if it's closer to 0, then the movement slows down. The relative speed is controlled by b, and how frequently it slows down and speeds up again is controlled using the cycle.

Below is the update function code (the initial step is set to random during setup)

  // incerement step and reset to 0 once it reaches cycle
this.step = (this.step + 1) % settings.cycle;

// convert step to a number between 0 to 1
const phase = this.step / settings.cycle;
// map ramp value to a sine wave value
const p1 = Math.sin(phase * 3.14 * 2)

// set random destination (unchanged)
this.dx = (Math.random()*2-1) * settings.particleSpeed;
this.dy = (Math.random()*2-1) * settings.particleSpeed;

// update vector (added p1 to b)
this.vx = this.vx * settings.a + this.dx * settings.b * p1;
this.vy = this.vy * settings.a + this.dy * settings.b * p1;

// update position with vector
this.x += this.vx
this.y += this.vy

Experiment 2 : sin & cos on a

Instead of multiplying sin × b, I calculate the sine and the cosine, and compute x and y components using sine and cosine; vx uses a × sin and vy uses a × cos. This makes it so that the x and y components move faster and slower in an inverse relation. This creates a little more structured looking result. I've reduced the particle size and reduced the speed of fade to show how this motion ends up looking over time.

Other possibilities include maping the speed or other particle values to the amplitude of a frequency range taken from an audio source, like I've done here. Or a much smaller variation on example 2 here. Even with just a few parameters, there's lots to experiment with.

Related: In my previous Post, Draw with loops, I show how I use trigonometry to affect change on a particle.


5,037 1 61