Créons un système de particules — Partie 1

Nous créons un système de particules à partir de zéro en JavaScript en utilisant HTML Canvas et des ressorts.

Keywords: les force newtoniennes, des méthodes numériques, maths, les simulations physiques

By Carmen Cincotti  

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

Voici le projet terminé

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 :

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

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 :

a=v˙=Fm \bf{a} = \bf{\dot{v}} = \frac{\bf{F}}{m}

La dérivée d’une particule en espace des phases est :

f(t,pn)=(vxvyvzFxmFymFzm)f(t, p_n) = \begin{pmatrix} v_x \\ v_y \\ v_z \\ \frac{\bf{F_x}}{m} \\ \frac{\bf{F_y}}{m} \\ \frac{\bf{F_z}}{m} \\ \end{pmatrix}

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 :

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}\\

Le code en JavaScript

Le code se trouve là

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 Fg=mgF_g = mgg est la constante gravitationnelle (on utilise 9.81ms2 9.81 \frac{m}{s^2}). 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

Voici le projet terminé Code result

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 !

Des ressources (en anglais et français)


Comments for Créons un système de particules — Partie 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!