Cette semaine, je voulais continuer à travailler sur le Pixar Physics Simulation Animation Course.
Mon but est de créer l’exemple qui se trouve dans la dynamique du système de particules sous ‘User Interaction’. Cette ressource nous demande de fabriquer un système de particules afin de simuler un système masse-ressort. Moi, je réfléchissais à ce projet pendant le week-end (quand je faisais du camping) et j’ai décidé de faire un projet en HTML Canvas. Les raisons pour lesquelles je voulais le faire en HTML Canvas sont la suite :
- Il est facile de le prototyper sans s’inquiéter de la compilation du code.
- Il fonctionnera dans le navigateur, qui est accessible à tout le monde
- Je suis plus à l’aise en JavaScript
Cette décision est un peu plus déroutante étant donné que je veux apprendre des langues de bas niveau. Mais, je pense que si je crée ce projet en C, ce serait trop stressant, d’autant plus que ce blog est censé me détendre… OK, allons-y !
Le projet
Le projet sera un système de particules qui sont sous l’influence des forces que l’on trouve dans les interactions masse-ressorts entre les particules elles-mêmes qui se trouvent dans le système, ainsi que d’autres forces telles que la force gravitationnelle.
Qu’est-ce qu’un système de particules ?
Un système de particules est un ensemble de particules où l’on peut résoudre le déplacement, la vitesse et l’accélération dans le temps de chacune dans le système. Chaque particule se représente dans l’espace par deux vecteurs de coordonnées généralisées (x, v) où x est un vecteur position et v est un vecteur vitesse. Le vecteur construit de ces deux vecteurs, quand nous les concaténons l’un après l’autre dans un vecteur qui a une taille de 2*n éléments (où n est la quantité de dimensions représentées dans notre système), s’appelle espace des phases :
Pour une particule représentée par l’espace des phases, on calcule l’accélération via le calcul de la force accumulée sur elle-même :
La dérivée d’une particule en espace des phases est :
où a est l’accélération, F est la force accumulée agissant sur la particule, et m est la masse. Ainsi, on calcule la nouvelle position et la nouvelle vitesse pour chaque pas de temps (avec la méthode d’Euler dans cet exemple) comme ceci :
Le code en JavaScript
Le processus entier pour calculer la position suivante de chaque particule se résume comme suit :
loop {
for particule dans le système {
- avancer d'un pas de temps en avance (deltaTs)
- accumulez les forces qui agissent sur chaque particule
- calculez l'accélération de la force sommée (F / m)
- utilisez une méthode numérique (un solveur) pour résoudre la prochaine position d'une particule
- mettez à jour le Canvas (redessinez le)
}
}
Commençons par la méthode 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)
On appelle requestAnimationFrame
afin de notifier au navigateur que nous voudrions exécuter une animation. Cette méthode prend un callback comme argument, ce qui est appelé à chaque tour de boucle. Heureusement, le callback a un seul argument très utile que j’appelle elapsedTime
qui est le temps en millisecondes depuis le commencement de la boucle. On fait le calcul du pas de temps deltaTs
en soustrayant currentElapsedTs
et lastElapsedTs
.
Ensuite, nous arrivons à la méthode de classe la plus importante, solve
, qui est responsable de la mise à jour des positions de chaque particule dans le système :
/**
* 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);
});
}
Une particule particulière a un accumulateur de forces qui conserve la somme des forces agissant sur elle-même dans chaque direction (il faut vous dire que notre projet est bidimensionnel (x et y)). La première étape consiste à effacer ses valeurs en les remettant à zéro.
for (const particle of this.particles) {
particle.clearForceAccumulator()
}
Après avoir fait cela, on accumule les forces sur chaque particule via l’appel f.applyTo(this)
. Pour l’instant, notre système n’a qu’une force de la gravité, définie comme où g est la constante gravitationnelle (on utilise ). Dans le prochain blog post, on verra d’autres forces.
Ensuite, on calcule la prochaine position de chaque particule dans notre système en utilisant un solveur qui implémente la méthode d’Euler.
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
}
On résout la valeur de l’accélération en divisant la force par la masse. Ensuite, on calcule la prochaine vitesse et la prochaine position en utilisant la méthode d’Euler et nous mettons à jour ces valeurs des propriétés sur chaque particule.
Enfin, nous mettons à jour le Canvas :
particleSystem.draw(ctx)
et on rappelle requestAnimationFrame
pour refaire la boucle !
Le résultat
J’ai ajouté un gestionnaire pour l’évènement click
afin de créer nos particules (en rouge).
La suite
On travaille toujours sur ce projet - je fais un peu de refactoring et j’ajoute l’implémentation des forces du ressort !