ENUNCIADO:
Implementar un emisor (efecto de humo).
Cargamos una imagen que utilizaremos como textura de humo, y creamos las partículas con velocidad aleatoria. Recogemos la posición (en la coordenada 'x') del ratón y creamos un vector director de viento, que aplicaremos como fuerza a la partícula; así, el viento seguirá la dirección del ratón. También les daremos un tiempo de vida para poder eliminar la partícula cuando llegue a cero.
Creamos partículas con determinada aceleración y posición, pero velocidad aleatoria, que luego actualizaremos en el 'update'. También les asignamos un tiempo de vida para que sean eliminadas del array de partículas cuando llegue a cero.
Creamos partículas con ángulo y velocidad determinados, y les damos un tiempo de vida. Cuando este llegue a cero, la partícula se eliminará del array de partículas. Integramos con Euler (semi), actualizando la velocidad, posición y aceleración, y aplicamos la gravedad.
ENUNCIADO:
Simular el modelo de péndulo introducido (dinámica angular 2D).
Aparte del 'main', creamos una clase 'Péndulo2' que en el constructor inicializa la posición y el peso (masa * gravedad). Con el método 'calcFuerza', calculamos la fuerza centrípeta del péndulo [ Fcp = m*v*v / r ]. La tensión es la fuerza resultante a aplicar (aparte del peso), que calculamos así:
- Creamos un vector (norm) que será un unitario en la dirección de la cuerda.
- Hacemos la proyección (peso, dirección), y sacamos la tensión.
- tension = PVector.mult(norm, ( -(proyección + fuerza_centrípeta)) )
ENUNCIADO:
Simularemos el acelerador de un coche.
Obtener la gráfica de velocidad y simular el movimiento de un coche con acelerador (trayectoria libre).
Creamos la clase 'Coche' que tiene como parámetros la masa, la velocidad y la energía cinética. Con el método 'AplicarPotencia' actualizamos la energía, es decir, ' Ec += Potencia * ∆t ' cada vez que pulsamos la tecla 'a' de aceleración. Con 'updateVelo' actualizamos la velocidad [ v = sqrt(2*energia/masa) ], calculamos la pérdida de energía [ Perd = -K*vel*vel ] y la aplicamos a la energía cinética [ energia = energia + (Perd*dt) ]. Cuanta más velocidad, más energía pierde, y cuando la energía llega a cero, el coche ya no avanza.
Desde el 'main' creamos un objeto Coche, recogemos los eventos de teclado, actualizamos la posición en función de la velocidad, y dibujamos la gráfica.
ENUNCIADO:
Simular un muelle para interactuar con él con y sin amortiguación.
Con el método 'calcFuerzas' calculamos la fuerza tangencial (masa * gravedad), para luego sumarle la fuerza proporcional al desplazamiento; así obtenemos la fuerza total en 'y' del muelle.
Utilizamos la ley de Hooke para calcular la fuerza proporcional [ Fk = ks * (posicion_actual − longitud_reposo) − kd * velocidad ]. Devolvemos la aceleración, que luego será utilizada en el 'draw' para determinar la velocidad y posición del muelle (utilizando el integrador Euler).
ENUNCIADO:
Simular un escenario de plano inclinado.
El peso se descompone en normal y tangencial al plano de movimiento (en este caso).
En el método 'calcFuerzas' calculamos la fuerza tangencial, la de rozamiento, la fuerza total, y finalmente la aceleración.
Después en el 'draw' actualizamos la posición y la velocidad de la bola.
ENUNCIADO:
Implementar una rutina que simule el movimiento de una partícula a lo largo de la espiral 2D de la figura. La función recibirá el diferencial de tiempo transcurrido desde la llamada anterior y debe moverse a una velocidad w en vueltas/seg.
Parámetros: inicio(1, 0), fin(0, 0)
Comenzamos con ' ellipse( (width/2)+radio*cos(x), (height/2)+radio*sin(x), 30, 30) ', y vamos decrementando el radio (que inicialmente está a 300), hasta que sea 0, entonces la 'x' no variará.
ENUNCIADO:
Hacer un ’bubble-shooter’ sencillo (sólo lanzador de bolas).
En este ejercicio vamos a representar vectores y operar con ellos. Utilizamos 4: 'mouse', donde recogemos los clicks; 'center', con el que situamos el centro del shooter; 'bala', con el que vamos situando la bala una vez disparada; 'mouse_aux', que nos servirá para darle la dirección a la bala, y así esta no seguirá el movimiento del ratón.
ENUNCIADO:
Animar el movimiento de una partícula a velocidad v sobre las 2 funciones osciladoras.
Un oscilador es una función y = f(x) que devuelve una onda a una frecuencia. En esta actividad aplicamos dos funciones distintas a cada una de las esferas.
En la Función 1 tenemos que y=amp*sin(x)*exp(-0.002*x); en la Función 2, y2 = amp*sin(3*x2) + amp*sin(3.5*x2).
ENUNCIADO:
Simular el movimiento lineal de un coche en 2 tramos con pendientes 0 y PI/4 (en este caso descendiente) y velocidades v, v/2 en cada tramo.
En el primer tramo (x < 200), sólo la componente 'x' va incrementando.
En cambio, en el segundo tramo modificamos tanto la 'x' (igual que antes pero reducimos la velocidad) como la 'y' (aquí sumamos la pendiente):
· x+= (vel/2)*incremento
· y+= (PI/4)
Implementar el bucle de simulación y simular la dinámica del sólido rígido con esferas capaces de ser impulsadas (por teclado).
Acumularemos los torques derivados de los impulsos (fuerza, pos, t) y las fuerzas.
Usaremos quaterniones (vector de 4 elementos normalizado).
Integrar: velocidad, posición.
Actualizar la derivada del quaternion y la matriz de inercia (inversa).
Nos proporcionan la clase Quaternion, y tenemos que completar la clase Body y el Main.
- Main: creamos un nuevo Body, y recogemos los eventos del teclado en función de la dirección del impulso que queramos darle a la esfera (hacia delante, detrás, izquierda y derecha).
- SolidoAlumn: formada por las clases Impulse y Body.
En Impulse tenemos el constructor con tres parámetros: la posición del impulso, su fuerza y su duración.
Para completar los métodos de la clase Body nos ayudamos de los apuntes que nos proporcionan en la clase de teoría:
· addImpulse (Impulse i): al pulsar una tecla de dirección, en el main se crea un impulso que se pasa como parámetro a la clase Body. Este método añade dicho impulso al array de impulsos.
· computeFTau: con este método hacemos el cómputo de fuerzas y torques. Si la duración del impulso es menor o igual a cero, eliminamos dicho impulso. Si no, realizamos los siguientes cálculos:
Calculamos el vector torque, de manera que ' Torque = Sumatorio(centro de masa - posición impulso) x Fuerza del impulso '.
Calculamos la rotación y la guardamos en 'totalAngImpulse'
Hallamos la fuerza del impulso.
Trasladamos el centro de masas ('totalLinearImpulse').
Disminuimos la duración del impulso.
· update: hay que calcular el rozamiento antes de la llamada a computar las fuerzas, por lo que:
Calculamos la velocidad de la partícula situada en la base de la esfera: v_ground = v(t0) + w × r
Multiplicamos dicho vector por la constante de rozamiento.
En el método 'ComputeFTau' se sumará este rozamiento a la fuerza [ f.add(rozamiento) ]
Ahora ya llamamos a la función que computa los torques y fuerzas, y después seguimos estas fórmulas:
- Simulación del sólido
- Rotación con quaterniones
Tras ello, actualizamos la posición del impulso y ya pasamos a dibujar la esfera, trasladando con las posiciones que cogemos de Body y rotando con el ángulo y el eje de giro de la clase Quaternion.
·Simular el movimiento de una esfera de manera que se mueva con
una velocidad lineal v(t) y que gire lo necesario para no producir el
clásico efecto de deslizamiento sobre el suelo (que no patine).
·Calcular cuál es la velocidad angular que evita dicho deslizamiento.
Esta restricción se representa mediante la ecuación mostrada, donde
se exige que la velocidad del punto de contacto esfera-suelo sea nula
(deslizamiento nulo).
En esta práctica se nos presentan dos casos: la esfera se desplaza sobre el plano, y la esfera se desplaza sobre el plano situado en la parte superior de la misma. Los hemos unido en la misma actividad, pudiendo cambiar de caso con la tecla 'c'.
Trabajamos en una misma clase, 'CasosConGravedad', y realizamos las operaciones en los distintos métodos:
- Update: resolvemos el sistema de ecuaciones v(t) + w(t) x R = P(t), siendo P(t)=0. Despejando, hallamos la componente 'z' de la velocidad angular: ' w_giro = (v_lineal.x/punto_bola.y) ', y obtenemos que ' velocidad_angular = new PVector(0,0,w_giro) '. La velocidad lineal resulta de multiplicar aceleración por tiempo. Tras ello, añadimos a la posición de la esfera, dicha velocidad lineal. Si la pelota se sale del plano, aplicamos la gravedad con 'applyForce(g)'. Finalmente, se incrementa el angulo de giro, multiplicando la magnitud de la velocidad angular por un incremento de tiempo.
- ApplyForce: le pasamos como parámetro la gravedad, la dividimos entre la masa y la sumamos a la aceleración ( F = m * a).
- Draw: dependiendo del caso seleccionado con el teclado, pintamos el plano encima o debajo de la bola, y rotamos con el ángulo de giro positivo o negativo. La velocidad en el punto de contacto con el plano es cero, ya que el producto vectorial de la velocidad angular con el radio, da negativo, que se anula cuando le sumamos la velocidad lineal.
Se pretende implementar una malla 2D de muelles con el esquema estudiado.
Probar las 3 estructuras (shear, bend, structured)
Introducir el modelo de viento
Simular algún tipo de tela (bandera, cortina, ...)
La deformación de objetos se obtiene al conectar las partículas mediante una red de muelles, es decir, una malla de partículas con estructura basada en muelles, que puede ser Structured, Shear o, entre otras, Bend. En esta práctica simularemos el comportamiento de una cortina con viento.
Contamos con dos clases:
Tela: Aquí crearemos un objeto de tipo Malla, y recogeremos los eventos del teclado para elegir el modelo a aplicar en la cortina.
Malla: En el constructor, creamos una matriz de vértices y matrices de fuerzas, velocidades y aceleraciones. En el método 'update' es donde aplicaremos el viento y actualizaremos la matriz de fuerzas según la estructura elegida (también aplicamos la gravedad); creamos un vector con valores aleatorios de viento, y para que la fuerza de dicho viento sea proporcional al producto escalar entre el vector viento y el vector normal en el vértice considerado, hacemos los siguientes cálculos:
- producto vectorial entre el vértice actual y el vecino [ windNorm = vertA.cross(vertB) ]
- normalizar dicho producto vectorial y multiplicarlo por la magnitud del viento
- producto escalar entre el resultado anterior y el vector viento (director)
- multiplicar 'windNorm' por ese producto escalar
- añadir este vector resultante a la matriz de fuerzas [ forces[x][y].add(windNorm) ]
Cuando actualizamos la matriz de fuerzas, utilizamos el método 'getForce' , al cual le pasamos como parámetros el vértice actual, el vértice vecino (su componente 'x' e 'y'), la distancia entre ellos, y la constante 'k' del muelle. Así, obtenemos la fuerza como:
Fmuelle = -k * (modulo_anchor - distancia) * anchor , donde 'anchor' el vector distancia entre las partículas, normalizado.
Aparte de añadir dichas fuerzas a la matriz de aceleraciones, también añadimos el 'damping' (amortiguación) a la matriz de fuerzas, resultado de multiplicar la constante damping negativa por cada componente de la matriz de velocidad.
(En este vídeo podemos visualizar mejor la tela por las luces)
Implementar un mapa de alturas para simular distinto tipo de olas.
·Perturbación radial
·Ola oceánica
·Ola de Gerstner
La deformación de objetos se obtiene al conectar las partículas mediante una red de muelles, es decir, una malla de partículas con estructura basada en muelles, que puede ser Structured, Shear o, entre otras, Bend. En esta práctica implementaremos la clase Ola, el mapa de alturas, y calibraremos los modelos que empleemos para obtener un comportamiento físico realista del mar.
Nos ayudaremos de este capítulo del libro GPU Gems y de este libro de Jerry Tessenford.
Contamos con tres clases:
Mar: Programa principal. Creamos un objeto HeighMap, y las olas en función de la tecla que pulsemos. Definimos el tamaños de las celdas y del lado de estas.
HighMap: Mapa de alturas. Creamos, en el constructor, la malla [ y * tamaño_lado, x * tamaño_lado, 0 ], en componentes 'x' e 'y' solamente, ya que la componente 'z' la calculamos en el 'update'; esto es, dependiendo del tipo (parámetro que le pasamos al crear la ola), llamaremos a un método y otro de la clase Wave. Por último, dibujaremos la malla con las partículas y su vecinos.
Wave: En el constructor tenemos los siguientes parámetros: velocidad, amplitud, frecuencia, longitud, periodo, tipo, origen. En esta clase es donde vamos a calcular la componente 'z' de las olas.
- Onda Radial: aplicamos la fórmula h(s) = a * cos (wP * s * tiempo), donde wP = 2 * PI / l, y 's' es la distancia entre el origen y la perturbación en la malla.
- Onda Simple: aplicamos la fórmula h(s) = a * cos(wP * s * tiempo) perteneciente a este tipo de ondas.
- Onda Gerstner: siguiendo el libro anteriormente citado, calculamos el punto en la malla donde no hay perturbación, resultado de multiplicar los coeficientes 'i' y 'j' (de vert[i][j] de la clase HeighMap) por el tamaño de la celda. En vez de devolver sólo la componente 'z' del vector de la malla, devolveremos el vector entero. Las componentes 'x' e 'y' se obtienen de la fórmula ' x = x0 − (k/k)A sin(k · x0 − ωt) ' , y el escalar de la 'z' de ' y1 = a * cos ( (k_dir.dot(x0)) - (w * tiempo)) '. Así, este método devuelve (x1.x, x1.y, y1).
(Abrir vídeo en YouTube para visualizar mejor, con texturas y luces)
Se pretende simular un juego de petacos (Pimball).
La práctica se divide en dos partes. A continuación contaremos la parte común, para después especificar cada parte en concreto.
(Para la realización de esta práctica hemos utilizado el libro 'Physics for Flash Games, Animation and Simulations')
Contamos con tres clases:
Pimball: Programa principal, donde se crean los objetos principales (el tablero y el array de bolas), se actualizan las partículas (llamando al método 'update' de Particle), se recogen los eventos de teclado, ...
Particle: Aquí tenemos el constructor de partículas (localización, velocidad, aceleración, radio). También aplicamos la gravedad, e integramos con Euler (semi), es decir, calculamos la velocidad, y luego la localización y la aceleración.
Tablero: Clase Plano y clase Tablero.
Un plano está formado por cuatro puntos: 'x' e 'y' de cada uno de los dos puntos que se unen para formar una superficie plana.
El tablero está formado por un array de planos.
El método más relevante es 'checkCollisions (Particle b)'; con este detectamos las colisiones con el plano y redireccionamos las partículas.
Calculamos:
- los vectores distancia entre los puntos del plano y la posición de la bola.
- la proyección de los anteriores vectores sobre el plano
- el vector distancia perpendicular entre la bola y el plano
Si las proyecciones entran en el módulo del plano, calculamos:
- si la bola atraviesa el plano (v.perpendicular < radio), invertimos la normal del plano
- el ángulo entre la velocidad de la bola y el plano
- el redireccionamiento de la bola (según la fórmula del libro Δs = radio + v.distancia • normal / sin (θ) )
- la descomposición de la velocidad (normal y tangencial al plano)
- invertir la componente normal y aplicar elasticidad, para sacar la velocidad de salida de las partículas tras el choque.
PARTE I: Colisiones partícula-plano (Diseño del escenario)
Como hemos comentado, en la clase Tablero calculamos la colisión de los planos con las bolas [ checkCollisions(Particle b) ], y en la clase Particle aplicamos la gravedad e integramos con Euler Semi-implícito.
PARTE II: Colisiones partícula-partícula (Varias bolas)
En cambio, en esta parte hemos añadido el método 'collision(Particle p) en la clase Particle. Para la detección de choque entre dos partículas, comparamos (p2 − p1).magnitude() < r1 + r2. Si es así, calculamos:
- la descomposición de la velocidad en normal [ velocidad.project(dist) ] y tangencial [ velocidad - normal ]
- la restitución:
· [ L = r1 + r2 − distancia.magnitude() ]
· la diferencia entre las normales [ vrel = (normalp1 − normalp2).magnitude() ]
· la nueva posición de la partícula [ a la posición de las partículas le sumamos ' mult(normal, -L / vrel) ' ]
- las velocidades de salida:
· Escalar: u1 = normal.projection(dist)
· Módulo: v1 = ((masa1−masa2)∗unitario1+2∗masa2∗uunitario2) / (masa1+masa2)
· Vector normal′ : necesitamos vectores paralelos al vector 'd' de módulo 'v1' y/o 'v2', por tanto multiplicamos 'v' por un vector unitario 'unitD' en la dirección de 'd' [ normalV1 = PVector.mult(unitD, v1); ]
· Velocidad final tras la colision = normal′ + tangencial
Creamos una variable booleana 'plano_puntos', que pasamos como parámetro al crear un plano. Nos indica si dicho plano suma puntos en el score. Así, observamos que hay determinados planos que hacen aumentar el indicador de puntos.
Se pretende hacer un juego con una cuerda hecha con muelles con la que
poder interactuar con el ratón (dragging). La cuerda debería estar
compuesta por muelles en cuyos extremos se visualizaría una circunferencia
con radio proporcional a la masa considerada.
Contamos con tres clases:
Main: Inicializamos posiciones, recogemos valores del teclado, le pasamos al update de los extremos el modelo de integrador que queremos aplicar, recogemos la energía cinética de cada extremo, mostramos por pantalla dicha energía y la máxima, recogemos los clicks del ratón para realizar el dragging, dibujamos la gráfica (plot_func) con la energía cinética, ...
Mover: Es la clase de un Extremo. Aquí es donde tendremos el constructor de cada uno, y los métodos de integración (en función de qué tecla hayamos pulsado).
-Euler explícito: Primero estimamos la posición [ R = R + V * d(t) ] y después la velocidad [ V = V + A * d(t) ].
-Euler semi-implícito: Al revés que Euler explícito; primero la velocidad y después la posición.
-RK2: Derivada en punto medio.
-Heun: Estimamos pendientes para obtener la aproximación buscada.
Tras integrar, aplicamos la ley de Newton en la función 'applyForce': Fuerza = Masa * Aceleración. Cargamos en la aceleración, la aceleración correspondiente a cada una de las fuerzas.
También tenemos en esta clase los métodos necesarios para la interacción del ratón con los muelles, y los valores de masa, incremento de tiempo y amortiguación.
Spring: Es la clase Muelle. Después del constructor del muelle, en el 'update' calculamos la fuerza de este con dos extremos y su longitud en reposo. Le restamos a la fuerza proporcional al desplazamiento [ Fk = k * anchor * (mod - len) ], la fuerza con amortiguación [ Fk_amort = Fk − kd*vmuelle ] de cada extremo, mediante el 'applyForce' aplicamos estas fuerzas obtenidas a cada extremo, 'a' y 'b'.
Haciendo click aquí se puede visualizar la memoria de esta práctica, con el análisis de la estabilidad de los integradores ( Euler, Euler Semi, Heun y RK2), el análisis de la energía cinética mostrada en el gráfico y las conclusiones.
(Hacer click en la imagen para después interactuar)
En este espacio se muestran las prácticas y tareas realizadas en la asignatura SIMULACIÓN, en el tercer curso de Ingeniería Multimedia.
He utilizado la página Open Processing para crear mi Portfolio con todos los trabajos. Aquellos ejercicios en los que, debido a la exportación, no se visualizan bien los elementos o no funcionaban en HTML, he añadido vídeos complementarios de su funcionamiento.
Se puede acceder a mi perfil de Open Processing clickando en el enlace que hay debajo de la presentación de cada actividad; allí se encuentran todos los archivos exportados desde Processing a HTML, en el código de los cuales se puede encontrar comentarios acerca de las funciones utilizadas que explican la realización de la práctica.