Let's Create a Particle System - Part 1

We're creating a particle system from scratch in JavaScript using HTML Canvas and springs.

Keywords: Newtonian forces, numerical methods, maths, physical simulations

By Carmen Cincotti Β 

This week, I wanted to continue working through the course outlined in the (famous) Pixar Physics Simulation Animation Course, specifically creating the example that is in Particle System Dynamics under β€˜User Interaction’. We’re asked to create a particle system in order simulate a mass-spring system. I was thinking about this project during the weekend (when I was camping) and I decided to do a project in HTML Canvas. The reasons why I wanted to do it in HTML Canvas are:

  • It’s easy to prototype it without worrying about compiling
  • It will work in the browser, which is available to everyone
  • I am more comfortable in JavaScript

This decision may be confusing considering I’ve been learning low level languages over the past month. On the other hand, I think if I create this project in C, I would be learning too many things at once, which seems too stressful given the casualness of this blog… OK, let’s go!

The project

Here’s a demo of the finished project The project will be a system of particles that are influenced by forces found in mass-spring interactions between the particles themselves that are within the system, as well as other forces such as gravity.

What is a particle system?

A particle system is a set of particles where we can solve the displacement, velocity, and acceleration over time of each in the system. Each particle is represented in space by two generalized coordinates (x, v) where x is a position vector and v is a velocity vector. The constructed vector of these two coordinates, when we append them one after the other in a vector that has a size of 2*n elements (where n is the amount of dimensions represented in our system), is called phase space:

pn=(xxxyxzvxvyvz)p_n = \begin{pmatrix} x_x \\ x_y \\ x_z \\ v_x \\ v_y \\ v_z \\ \end{pmatrix}

For a particle represented by the phase space, we calculate the acceleration via the calculation of the force accumulated on itself:

a=vΛ™=βˆ‘Fm \bf{a} = \bf{\dot{v}} = \frac{\bf{\sum F}}{m}

The derivative of a particle in phase space is:

f(t,pn)=(vxvyvzβˆ‘Fxmβˆ‘Fymβˆ‘Fzm)f(t, p_n) = \begin{pmatrix} v_x \\ v_y \\ v_z \\ \frac{\bf{\sum F_x}}{m} \\ \frac{\bf{\sum F_y}}{m} \\ \frac{\bf{\sum F_z}}{m} \\ \end{pmatrix}

where a is the acceleration, F is the accumulated force acting on the particle, and m is the mass. Thus, we calculate the new position and the new speed for each time step (with the Euler method in this example) like this:

vn=vo+aΞ”t\bf{v_n} = \bf{v_o} + \bf{a}\Delta{t}\\
xn=xo+vnΞ”t\bf{x_n} = \bf{x_o} + \bf{v_n}\Delta{t}\\

The JavaScript code

The code can be found here

The whole process of calculating the next position of each particle is summarized as follows:

loop { for particle in the system { - advance forward by a time step (deltaTs) - add the forces acting on each particle - calculate the acceleration of the summed force (F/m) - use a numerical method (a solver) to solve the next position of a particle - update the Canvas (redraw it) } }

Lets start with the method run:

// Calculate new positions, then draw frame const run = currentElapsedTs => { unimportantCanvasDrawStuff(ctx) // Store deltaTs, as that acts as our step time deltaTs = currentElapsedTs - lastElapsedTs lastElapsedTs = currentElapsedTs // Solve the system, then draw it. ctx.save() particleSystem.solve(deltaTs) particleSystem.draw(ctx) ctx.restore() // Loop back requestAnimationFrame(run) } requestAnimationFrame(run)

We call requestAnimationFrame in order to notify the browser that we want to run an animation. This method takes a callback as an argument, which is called each time through the loop. Luckily the callback has a very useful argument which I call elapsedTime which is the time in milliseconds since the start of the loop. We calculate the deltaTs time step by subtracting currentElapsedTs and lastElapsedTs.

Next, we come to the most important class method, solve, which is responsible for updating the positions of every particle in the system:

/** * Solve the particle system, which updates all the particles's position * @param {number} deltaTs */ solve(deltaTs) { // Clear force accumulators on all particles for (const particle of this.particles) { particle.clearForceAccumulator(); } // Apply all the forces on all the particles this.forces.forEach((f) => { f.applyTo(this); }); // Update particles velocity and positions given that now we know the acceleration // by way of force / mass: a = F / m this.particles.forEach((p) => { EulerStep(p, deltaTs); }); }

A particular particle has an accumulator of forces which keeps a running total of the summation of forces acting on itself in each direction (our project is two-dimensional (x and y). The first step is to clear its values by resetting it to zero.

for (const particle of this.particles) { particle.clearForceAccumulator() }

After that, we accumulate the forces on each particle via the f.applyTo(this) call. Right now, our system has only one gravitational force, defined as Fg=mgF_g = mg where g is the gravitational constant (we use 9.81ms2 9.81 \frac{m}{s ^2 }). In the next part, we will see other forces.

Then we calculate the next position of each particle in our system using a solver that implements Euler’s method.

function EulerStep(p, deltaTs) { // Calculate acceleration step const accelerationStep = new Vec2(0, 0) p.f.divideScalar(p.mass, accelerationStep) // a = F / m accelerationStep.multiplyScalar(deltaTs, accelerationStep) // a*Ξ”t p.velocity.add(accelerationStep, p.velocity) // vn = vo + a*Ξ”t // Calculate velocity step const velocityStep = p.velocity.clone() velocityStep.multiplyScalar(deltaTs, velocityStep) // vn*Ξ”t p.position.add(velocityStep, p.position) // xn = x0 + vn*Ξ”t }

To be clear, we solve for the value of the acceleration by dividing the force by the mass. Then, we approximate the next velocity and the next position by using Euler’s Method and we proceed to update these values of the properties on each particle.

Finally, we update the Canvas:

particleSystem.draw(ctx)

and we call requestAnimationFrame to do the loop again!

The result

Code result

Here’s a demo of the finished project

I added a handler for the click event to create our particles (in red).

Next time

I do a ton of refactoring, and we’ll continue this project by adding spring forces between the particles! Be sure to check out the next part to see a fully working prototype!

Resources


Comments for Let's Create a Particle System - Part 1



Written by Carmen Cincotti, computer graphics enthusiast, language learner, and improv actor currently living in San Francisco, CA. Β Follow @CarmenCincotti

Contribute

Interested in contributing to Carmen's Graphics Blog? Click here for details!