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:
For a particle represented by the phase space, we calculate the acceleration via the calculation of the force accumulated on itself:
The derivative of a particle in phase space is:
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:
The JavaScript code
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 where g is the gravitational constant (we use ). 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
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!