Al principio de esta sección, vimos cómo modelar movimiento armónico simple al mapear la onda senoidal a un rango de pixeles, y modelaste una masa en un resorte usando esa onda de seno. Utilizar la función sin() es una forma rápida y sucia, con una línea de código, de echar a andar algo, que no funcionará si lo que queremos es tener una masa colgada de un resorte en un espacio bidimensional que responda a otras fuerzas en el ambiente (viento, gravedad, etc.). Para lograr una simulación como esta (una que sea idéntica al ejemplo del péndulo, solo que ahora el brazo sea una conexión elástica), tenemos que modelar las fuerzas de un resorte al usar PVector.
La fuerza de un resorte se calcula conforme a la ley de Hooke, llamada así por Robert Hooke, un físico británico que desarrolló la fórmula en 1660. Hooke originalmente expresó la ley en latín: "Ut tensio, sic vis", o “Así como la extensión, la fuerza”. Vamos a pensarlo de esta manera:
<div class="callout">
La fuerza del resorte es directamente proporcional a la extensión del resorte.
</div>
En otras palabras, si jalas mucho de la masa, la fuerza será intensa; si jalas poco de la masa, la fuerza será débil. Matemáticamente, la ley se expresa como sigue:
F, start subscript, r, e, s, o, r, t, e, end subscript, equals, minus, k, times, x
  • k es constante y su valor en última instancia escalará la fuerza. ¿El resorte es altamente elástico o muy rígido?
  • x se refiere al desplazamiento del resorte, es decir, la diferencia entre la longitud actual y la longitud en reposo. La longitud en reposo se define como la longitud del resorte en un estado de equilibrio.
Ahora recuerda, la fuerza es un vector, así que necesitamos calcular tanto la magnitud como la dirección. Veamos un diagrama más del resorte y vamos a etiquetar todos los datos que podríamos tener en un programa.
Vamos a establecer las siguientes tres variables iniciales como se muestran en el diagrama de arriba, con algunos valores razonables.
var anchor = new PVector(100, 10);
var bob = new PVector(110, 100);
var restLength = 20;
Primero, vamos a usar la ley de Hooke para calcular la magnitud de la fuerza. Necesitamos conocer k y x. k es fácil; solo es una constante, así que vamos a inventar algo.
var k = 0.1;
x es quizá un poco más difícil. Necesitamos conocer la “diferencia entre la longitud actual y la longitud en reposo”. La longitud en reposo está definida como la variable restLength. ¿Cuál es la longitud actual? La distancia entre el anclaje (anchor) y la masa. ¿Y cómo podemos calcular esa distancia? ¿Qué tal la magnitud de un vector que apunta desde el anclaje hasta la masa? (Observa que esto es exactamente el mismo proceso que empleamos cuando calculamos la distancia en la sección de Atracción Gravitacional).
var dir = PVector.sub(bob, anchor);
var currentLength = dir.mag();
var x = restLength - currentLength;
Ahora que ya ordenamos los elementos necesarios para la magnitud de la fuerza (-1 * k * x), necesitamos averiguar la dirección, un vector unitario que apunta en la dirección de la fuerza. La buena noticia es que ya tenemos este vector. ¿Verdad? Hace un momento pensamos: “¿cómo podemos calcular esa distancia? ¿Qué tal la magnitud de un vector que apunta desde el anclaje hasta la masa?” Bueno, ¡ese mismo vector es la dirección de la fuerza!
En el diagrama anterior, podemos ver que si estiramos el resorte más allá de su longitud en reposo, debería existir una fuerza que lo jale de regreso hacia el anclaje. Y si se contrae por debajo de su longitud en reposo, la fuerza debe empujarlo para alejarlo del anclaje. Este cambio de dirección se toma en cuenta en la fórmula con el -1. ¡Así que todo lo que necesitamos hacer es normalizar el PVector que utilizamos para el cálculo de la distancia! Echemos un vistazo al código y renombremos esa variable PVector como “force” (fuerza).
var k = 0.01;
var force = PVector.sub(bob, anchor);
var currentLength = force.mag();
var x = restLength - currentLength;
// Dirección de la fuerza del resorte, un vector unitario
force.normalize();
// Juntándolo todo: ¡dirección y magnitud!
force.mult(-1 * k * x);
Ahora que tenemos trabajado el algoritmo para calcular el vector de fuerza del resorte, la pregunta sigue siendo: ¿qué estructura de programación orientada a objetos debemos utilizar? Esta, de nuevo, es una de esas situaciones en las que no hay una respuesta “correcta”. Hay varias posibilidades; la que escojamos depende de los objetivos del programa y de nuestro estilo personal de codificación. Sin embargo, como hemos estado trabajando todo el tiempo con un objeto Mover, vamos a seguir con este mismo marco de trabajo. Vamos a pensar en nuestro objeto Mover como la “masa” del resorte. La masa necesita vectores de posición, velocidad y aceleración para moverse sobre la pantalla. Perfecto, ¡eso ya lo tenemos! Y tal vez la masa experimenta una fuerza de gravedad mediante el método applyForce(). Un paso más. Tenemos que aplicar la fuerza del resorte:
var bob = new Bob();

draw = function()  {
  // Nuestra “fuerza de gravedad inventada”
  var gravity = new PVector(0, 1);
  bob.applyForce(gravity);
  // ¡También necesitamos calcular y aplicar una fuerza del resorte!
  var springForce = ¿¿¿¿\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_????
  bob.applyForce(spring);

// Nuestros métodos update() y display() estándares
bob.update();
bob.display();
};
Una opción sería escribir todo el código de la fuerza del resorte en el bucle principal draw(). Pero pensando hacia adelante para cuando puedas tener múltiples masas y múltiples conexiones de resortes, hace mucho sentido escribir un objeto adicional, un objeto Spring (resorte). Como se muestra en el diagrama anterior, el objeto Bob (masa) lleva un registro de los movimientos de la masa; el objeto Spring lleva un registro del anclaje del resorte y su longitud en reposo y calcula la fuerza del resorte sobre la masa.
Esto nos permite escribir este hermoso código para atarlos juntos:
var bob = new Bob();
var spring = new Spring();

draw = function()  {
  // Nuestra “fuerza de gravedad inventada”
  var gravity = new PVector(0, 1);
  bob.applyForce(gravity);
  // Spring.connect se encargará de calcular y aplicar la fuerza del resorte
  spring.connect(bob);

  // Nuestros métodos update() y display() estándares
  bob.update();
  bob.display();
};
Aquí puedes observar que esto es bastante parecido a lo que hicimos en la sección de Gravedad con un atractor. Allí, dijimos algo como:
var force = attractor.calculateAttraction(mover);
mover.applyForce(force);
La situación análoga con un resorte sería:
var force = spring.calculateForce(bob);
bob.applyForce(force);
Sin embargo, en este ejemplo, todo lo que hicimos fue:
spring.connect(bob);
¿Qué pasa? ¿Por qué no necesitamos llamar a applyForce() en la masa? La respuesta es, por supuesto, que sí tenemos que llamar a applyForce() en la masa. Nada más que en lugar de hacerlo en draw(), solo estamos demostrando que una alternativa perfectamente razonable (y a veces preferible) es pedirle al método connect() que maneje internamente el llamar applyForce() en la masa.
Spring.prototype.connect(bob) {
  var force = /* algunos cálculos elegantes */;
  bob.applyForce(force);
};
¿Por qué hacerlo de una forma con el objeto Attractor y de otra forma con el objeto de Spring? Cuando estábamos aprendiendo sobre fuerzas, era un poco más claro mostrar todas las fuerzas siendo aplicadas en el bucle principal draw(), y con suerte, esto te ayudó a aprender sobre la acumulación de fuerzas. Ahora que nos sentimos más cómodos con eso, quizá es más fácil insertar algunos de los detalles dentro de los propios objetos.
Vamos a poner todo junto, en el programa incrustado a continuación. Agregamos algunas cosas: (1) el objeto Bob incluye funciones para la interactividad del ratón para que la masa pueda ser arrastrada por la ventana, y (2) el objeto Spring incluye una función para restringir la longitud de la conexión entre un mínimo y un máximo.

Este curso de "Simulaciones Naturales" es un derivado de "La Naturaleza del Código" por Daniel Shiffman, usado bajo una Licencia Creative Commons Reconocimiento-NoComercial 3.0 Unported.