Hasta ahora en esta sección, nos hemos centrado en estructurar nuestro código de una manera orientada a objetos para gestionar una colección de partículas. Tal vez te hayas dado cuenta, o tal vez no, pero durante este proceso involuntariamente tomamos un par de pasos hacia atrás de donde estábamos en las secciones anteriores. Examinemos el constructor de nuestro objeto simple de partículas Particle:
var Particle = function(position) {
  this.acceleration = new PVector(0, 0.05);
  this.velocity = new PVector(random(-1, 1), random(-1, 0));
  this.position = new PVector(position.x, position.y);
  this.timeToLive = 255.0;
};
Y ahora veamos el método update():
Particle.prototype.update = function(){
  this.velocity.add(this.acceleration);
  this.position.add(this.velocity);
  this.timeToLive -= 2;
};
Observa que nuestra aceleración es constante, nunca se le da un valor fuera del constructor. Un marco de trabajo mucho mejor sería seguir la segunda ley de Newton (F, with, vector, on top, equals, M, A, with, vector, on top) e incorporar el algoritmo de acumulación de fuerzas en el cual trabajamos tan duro en la sección de Fuerzas.
El primer paso es agregar un método applyForce(). (Recuerda, tenemos que hacer una copia de PVector antes dividirlo entre la masa).
Particle.prototype.applyForce = function(force) {
  var f = force.get();
  f.div(this.mass);
  this.acceleration.add(f);
};
Una vez que tengamos esto, podemos agregar una línea más de código para limpiar la aceleración al final de update().
Particle.prototype.update = function() {
  this.velocity.add(this.acceleration);
  this.position.add(this.velocity);
  this.acceleration.mult(0);
  this.timeToLive -= 2.0;
};
Y por lo tanto, tenemos un objeto Particle que puede tener fuerza aplicada sobre él. Ahora, ¿dónde deberíamos llamar la función applyForce()? ¿Qué lugar en el código es apropiado aplicarle una fuerza a una partícula? La verdad del asunto es que no hay una respuesta correcta o incorrecta; realmente depende de la funcionalidad exacta y de los objetivos de un programa en particular. Aún así, podemos crear una situación genérica que es probable que se aplique a la mayoría de los casos y hacer un modelo para aplicarle fuerzas a partículas individuales en un sistema.

Agregar viento

Consideremos el siguiente objetivo: aplicarle una fuerza global cada vez a todas las partículas por medio de draw(). Comenzaremos por aplicar una fuerza sencilla como de viento que empuja a las partículas a la derecha:
var wind = new PVector(0.4, 0);
Dijimos que siempre debe aplicarse, es decir, en draw(), así que veamos como está nuestra función draw().
draw = function() {
  background(168, 255, 156);
  particleSystem.addParticle();
  particleSystem.run();
};
Bueno, parece que tenemos un pequeño problema. applyForce() es un método escrito dentro del objeto Particle, pero no tenemos ninguna referencia a las partículas individuales, solo al objeto ParticleSystem: la variable particleSystem.
Sin embargo, como queremos que todas las partículas reciban la fuerza, podemos decidir aplicarle la fuerza al sistema de partículas y dejarlo que gestione la aplicación de la fuerza sobre todas las partículas individuales.
draw = function() {
  background(168, 255, 156);
  particleSystem.applyForce(wind);
  particleSystem.addParticle();
  particleSystem.run();
};
Por supuesto, si llamamos una nueva función en el objeto ParticleSystem en draw(), bueno, tendremos que escribir esa función en el objeto ParticleSystem. Vamos a describir el trabajo que esa función debe realizar: recibir una fuerza como un PVector y aplicarle esa fuerza a todas las partículas.
Ahora en código:
ParticleSystem.prototype.applyForce = function(f){
  for(var i = 0; i < this.particles.length; i++){
    this.particles[i].applyForce(f);
  }
};
Casi parece una tontería escribir esta función. Lo que estamos diciendo es “aplícale una fuerza a un sistema de partículas para que el sistema pueda aplicarle esa fuerza a todas las partículas individuales”. Sin embargo, es bastante razonable. Después de todo, el objeto ParticleSystem está encargado de gestionar a las partículas, así que si queremos hablarle a las partículas, tenemos que hablarles a través de su gestor.
Aquí está, todo junto. Juega con la fuerza del viento y ve cómo afecta el movimiento de las partículas, y observa cómo las partículas de masa diferente responden de manera diferente. Piensa en por qué es eso.

Agregar la gravedad

Ahora apliquemos una fuerza más compleja, la gravedad, que es distinta del viento porque varía con base en la masa de los objetos a los que se les aplica.
Recordemos la ecuación para calcular la fuerza de gravedad entre dos masas:  Fg=Gm1m2r2r^ \vec{F_g} = \frac{Gm_1m_2}{||r||^2} \hat{r}
Recuerda que cuando estamos modelando la fuerza de gravedad en la Tierra, la fuerza ejercida por la Tierra abruma a todas las otras fuerzas gravitacionales, así que la única ecuación con la que estamos tratando es la de calcular la fuerza de gravedad entre la Tierra y el objeto. G y m, start subscript, 1, end subscript son las mismas para cada partícula y r (el radio de la Tierra) es básicamente el mismo (ya que el radio de la Tierra es tan grande en comparación con lo poco que las partículas se alejan), así que típicamente las simplificamos solo como g, la constante de gravedad en la Tierra:
g, equals, start fraction, G, m, start subscript, 1, end subscript, divided by, vertical bar, vertical bar, r, vertical bar, vertical bar, start superscript, 2, end superscript, end fraction
Ahora, la fuerza de gravedad es solo una constante g por la masa de las partículas, multiplicada por un vector unitario en la dirección de la fuerza (que siempre será hacia abajo):
Fg=gm2r^ \vec{F_g} = g m_2 \hat{r}
En código, eso significa que tendremos que aplicarle una fuerza de gravedad diferente a cada partícula con base en su masa. ¿Cómo podemos hacer eso? No podemos reutilizar la función applyForce existente, porque espera la misma fuerza para cada partícula. Podríamos considerar pasarle un parámetro que le dé instrucciones a applyForce de multiplicarla por la masa, pero vamos a dejar sola a esa función y crear una nueva función, applyGravity, que calcule la fuerza con base en un vector constante global:

// A constant down vector, declared at the top
var gravity = new PVector(0, 0.2);
ParticleSystem.prototype.applyGravity = function() {
    for(var i = 0; i < this.particles.length; i++) {
        var particleG = gravity.get();
        particleG.mult(this.particles[i].mass);
        this.particles[i].applyForce(particleG);
    }
};
Ahora, si hicimos esto correctamente, todas nuestras partículas deberían caer a la misma tasa en la siguiente simulación. Eso es porque la fuerza de gravedad está basada en multiplicar la masa, pero la aceleración está basada en dividir entre la masa, así que al final, la masa no tiene un efecto. Puede parecer tonto pasar por todo ese esfuerzo para no tener efecto, pero es importante una vez que empecemos a combinar múltiples fuerzas diferentes.

Agregar repelentes

¿Qué pasa si quisiéramos llevar este ejemplo un paso más lejos y agregar un objeto repelente que empuje a las partículas a medida que se acercan? Sería similar al objeto atractor que creamos anteriormente, solo que empuja en la dirección opuesta. Una vez más, como la gravedad, debemos calcular una fuerza diferente para cada partícula, pero en el caso del repelente, la diferencia es que el cálculo no está basado en la masa, sino en la distancia. Para la gravedad, todas nuestros vectores de fuerza tenían la misma dirección, pero para el repelente, todos los vectores de fuerza tendrán diferentes direcciones:
Fuerza de gravedad: todos los vectores tienen la misma dirección
Fuerza repelente: todos los vectores de dirección son diferentes
Como el cálculo de una fuerza repelente es un poco más complejo que el cálculo de la gravedad (¡y en última instancia podríamos querer muchos repelentes!), resolveremos este problema incorporando un nuevo objeto Repeller (repelente en inglés) en nuestro ejemplo sencillo de un sistema de partículas más gravedad. Vamos a necesitar dos adiciones importantes a nuestro código:
  1. Un objeto Repeller (declarado, inicializado y desplegado).
  2. Una función que pase el objeto Repeller al ParticleSystem para que pueda aplicarle una fuerza a cada partícula.
var particleSystem = new ParticleSystem(new PVector(width/2, 50));
var repeller = new Repeller(width/2-20, height/2);
var gravity = new PVector(0, 0.1);

draw = function() {
  background(214, 255, 171);

  // Apply gravity force to all Particles
  particleSystem.applyForce(gravity);
  particleSystem.applyRepeller(repeller);
  repeller.display();
  particleSystem.addParticle();
  particleSystem.run();
};
Hacer que un objeto Repeller sea visible es fácil; es un duplicado del objeto Attractor que creamos anteriormente:
var Repeller = function(x, y) {
  this.position = new PVector(x, y);
};

Repeller.prototype.display = function() {
  stroke(255);
  strokeWeight(2);
  fill(127);
  ellipse(this.position.x, this.position.y, 32, 32);
};
La pregunta más difícil es, ¿cómo escribimos el método applyRepeller()? En lugar de pasarle un PVector a una función como lo hacemos con applyForce(), vamos a pasarle un objeto Repeller a applyRepeller() y pedirle a esa función que haga el trabajo de calcular la fuerza entre el repelente y todas las partículas:

ParticleSystem.prototype.applyRepeller = function(r) {
  for(var i = 0; i < this.particles.length; i++){
    var p = this.particles[i];
    var force = r.calculateRepelForce(p);
    p.applyForce(force);
  }
};
La gran diferencia aquí es que se calcula una nueva fuerza para cada partícula, porque, como vimos anteriormente, la fuerza es diferente dependiendo de las propiedades de cada partícula en relación con el repelente. Calculamos esa fuerza utilizando la función calculateRepelForce, que es el inverso de la función calculateAttractionForce de nuestros Attractores.
Repeller.prototype.calculateRepelForce = function(p) {
  // Calcular dirección de la fuerza
  var dir = PVector.sub(this.position, p.position); 
  // Distancia entre objetos
  var d = dir.mag();
  // Normalizar vector de dirección (aquí no importa la distancia, solo queremos este vector para la dirección)
  dir.normalize();
  // Mantener la distancia dentro de un rango razonable
  d = constrain(d, 1, 100);    
  // La fuerza repelente es inversamente proporcional a la distancia
  var force = -1 * this.power/ (d * d);     
  // Obtener el vector de fuerza --> magnitud * dirección
  dir.mult(force);                                  
  return dir;
};
Observa cómo a lo largo de todo este proceso de agregar un repelente al medio ambiente nunca consideramos editar el propio objeto Particle. Una partícula en realidad no tiene por qué saber nada acerca de los detalles de su entorno; simplemente tiene que gestionar su ubicación, velocidad y aceleración, así como tener la capacidad para recibir una fuerza externa y actuar en consecuencia.
Así que ahora podemos ver este ejemplo en su totalidad. Intenta cambiar la intensidad de las fuerzas que actúan sobre las partículas (la gravedad y la repelente) y ver cómo eso las cambia: