Hay muchas razones por las que podría querer crear un motor de física personalizado: primero, aprender y perfeccionar sus habilidades en matemáticas, física y programación son buenas razones para intentar un proyecto de este tipo; segundo, un motor de física personalizado puede abordar cualquier tipo de efecto técnico que el creador tenga la habilidad de crear. En este artículo, me gustaría proporcionar una introducción sólida sobre cómo crear un motor de física personalizado completamente desde cero.
La física proporciona un medio maravilloso para permitir que un jugador se sumerja en un juego. Tiene sentido que el dominio de un motor de física sea un activo poderoso para que cualquier programador tenga a su disposición. Las optimizaciones y especializaciones se pueden realizar en cualquier momento debido a un profundo conocimiento del funcionamiento interno del motor de física..
Al final de este tutorial, se habrán cubierto los siguientes temas, en dos dimensiones:
Aquí hay una demostración rápida:
Nota: Aunque este tutorial está escrito en C ++, debes poder usar las mismas técnicas y conceptos en casi cualquier entorno de desarrollo de juegos.
Este artículo involucra una buena cantidad de matemáticas y geometría, y en mucho menor grado de codificación real. Un par de requisitos previos para este artículo son:
Hay bastantes artículos y tutoriales en Internet, incluidos aquí en Tuts +, que cubren la detección de colisiones. Sabiendo esto, me gustaría repasar el tema muy rápidamente ya que esta sección no es el enfoque de este artículo.
Un cuadro delimitador alineado al eje (AABB) es un cuadro que tiene sus cuatro ejes alineados con el sistema de coordenadas en el que reside. Esto significa que es un cuadro que no puede girar, y siempre está cuadrado a 90 grados (generalmente alineado con la pantalla). En general, se le conoce como un "cuadro delimitador" porque las AABB se utilizan para enlazar otras formas más complejas.
Un ejemplo AABB.El AABB de una forma compleja se puede usar como una prueba simple para ver si es posible que se crucen formas más complejas dentro de las AABB. Sin embargo, en el caso de la mayoría de los juegos, la AABB se usa como una forma fundamental, y en realidad no se enlaza con ninguna otra cosa. La estructura de su AABB es importante. Hay algunas formas diferentes de representar una AABB, sin embargo, esta es mi favorita:
struct AABB Vec2 min; Vec2 max; ;
Esta forma permite que una AABB sea representada por dos puntos. El punto mínimo representa los límites inferiores de los ejes x e y, y el máximo representa los límites superiores; en otras palabras, representan las esquinas superior izquierda e inferior derecha. Para saber si dos formas AABB se están intersectando, deberá tener un conocimiento básico del Teorema del Eje Separador (SAT).
Aquí hay una prueba rápida tomada de Real-Time Collision Detection por Christer Ericson, que hace uso del SAT:
bool AABBvsAABB (AABB a, AABB b) // Salir sin intersección si se encuentra separado a lo largo de un eje si (a.max.x < b.min.x or a.min.x > b.max.x) devuelve false si (a.max.y < b.min.y or a.min.y > b.max.y) return false // No se ha encontrado ningún eje de separación, por lo tanto hay al menos un eje superpuesto return true
Un círculo está representado por un radio y un punto. Así es como debería verse la estructura de tu círculo:
struct Circle float radio Vec position;
Comprobar si dos círculos se intersecan o no es muy simple: tome los radios de los dos círculos y súmelos, luego verifique si esta suma es mayor que la distancia entre los dos círculos..
Una optimización importante para hacer aquí es deshacerse de cualquier necesidad de usar el operador de raíz cuadrada:
float Distance (Vec2 a, Vec2 b) return sqrt ((ax - bx) ^ 2 + (ay - by) ^ 2) bool CirclevsCircleUnoptimized (Circle a, Circle b) float r = a.radius + b.radius volver r < Distance( a.position, b.position ) bool CirclevsCircleOptimized( Circle a, Circle b ) float r = a.radius + b.radius r *= r return r < (a.x + b.x)^2 + (a.y + b.y)^2
En general, la multiplicación es una operación mucho más barata que tomar la raíz cuadrada de un valor.
La resolución de impulsos es un tipo particular de estrategia de resolución de colisiones. La resolución de colisiones es el acto de tomar dos objetos que se encuentran que se intersecan y modificarlos de tal manera que no les permita permanecer intersectados.
En general, un objeto dentro de un motor de física tiene tres grados principales de libertad (en dos dimensiones): movimiento en el plano xy y rotación. En este artículo, restringimos implícitamente la rotación y utilizamos solo AABB y círculos, por lo que el único grado de libertad que realmente debemos tener en cuenta es el movimiento a lo largo del plano xy.
Al resolver las colisiones detectadas, colocamos una restricción en el movimiento, de modo que los objetos no pueden permanecer intersectándose entre sí. La idea detrás de la resolución de impulsos es usar un impulso (cambio instantáneo en la velocidad) para separar los objetos encontrados en colisión. Para hacer esto, la masa, la posición y la velocidad de cada objeto deben tenerse en cuenta de alguna manera: queremos que los objetos grandes que chocan con los más pequeños se muevan un poco durante la colisión y que los objetos pequeños salgan volando. También queremos que los objetos con masa infinita no se muevan en absoluto..
Ejemplo simple de lo que puede lograr la resolución de impulsos..Para lograr tales efectos y seguir la intuición natural de cómo se comportan los objetos, usaremos cuerpos rígidos y un poco de matemáticas. Un cuerpo rígido es solo una forma definida por el usuario (es decir, por usted, el desarrollador) que se define implícitamente como no deformable. Tanto las AABB como los Círculos en este artículo no son deformables y siempre serán una AABB o un Círculo. No se permite aplastar o estirar.
Trabajar con cuerpos rígidos permite que muchas matemáticas y derivaciones se simplifiquen en gran medida. Esta es la razón por la que los cuerpos rígidos se usan comúnmente en simulaciones de juegos, y por qué los usaremos en este artículo.
Suponiendo que se encuentran dos formas que se cruzan, ¿cómo se separan las dos en realidad? Asumamos que nuestra detección de colisiones nos proporcionó dos datos importantes:
Para aplicar un impulso a ambos objetos y separarlos, necesitamos saber en qué dirección empujarlos y en qué medida. La colisión normal es la dirección en la que se aplicará el impulso. La profundidad de penetración (junto con algunas otras cosas) determina qué tan grande será el impulso que se utilizará. Esto significa que el único valor que debe resolverse es la magnitud de nuestro impulso..
Ahora vamos a hacer una larga caminata para descubrir cómo podemos resolver esta magnitud de impulso. Comenzaremos con nuestros dos objetos que se han encontrado que se intersecan:
Ecuación 1\ [V ^ AB = V ^ B - V ^ A \] Tenga en cuenta que para crear un vector desde la posición A a la posición B, debe hacer: punto final - punto de inicio
. \ (V ^ AB \) es la velocidad relativa de A a B. Esta ecuación debe expresarse en términos de la colisión normal \ (n \), es decir, nos gustaría saber la velocidad relativa de A a B a lo largo de la dirección de colisión normal:
\ [V ^ AB \ cdot n = (V ^ B - V ^ A) \ cdot n \]
Ahora estamos haciendo uso del producto punto. El producto punto es simple; Es la suma de los productos de componentes:
Ecuación 3\ [V_1 = \ begin bmatrix x_1 \\ y_1 \ end bmatrix, V_2 = \ begin bmatrix x_2 \\ y_2 \ end bmatrix \\ V_1 \ cdot V_2 = x_1 * x_2 + y_2 * y_2 \ ]
El siguiente paso es introducir lo que se llama el Coeficiente de restitución. La restitución es un término que significa elasticidad o rebote. Cada objeto en su motor de física tendrá una restitución representada como un valor decimal. Sin embargo, solo se utilizará un valor decimal durante el cálculo del impulso.
Para decidir qué restitución usar (indicada por \ (e \) para epsilon), siempre debe usar la restitución más baja involucrada en la colisión para obtener resultados intuitivos:
// Dados dos objetos A y B e = min (A. restitución, B. restitución)
Una vez que se adquiere \ (e \), podemos ubicarlo en nuestra ecuación para resolver la magnitud del impulso.
La Ley de Restitución de Newton establece lo siguiente:
Ecuación 4\ [V '= e * V \]
Todo lo que está diciendo es que la velocidad después de una colisión es igual a la velocidad anterior, multiplicada por alguna constante. Esta constante representa un "factor de rebote". Sabiendo esto, es bastante sencillo integrar la restitución en nuestra derivación actual:
Ecuación 5\ [V ^ AB \ cdot n = -e * (V ^ B - V ^ A) \ cdot n \]
Observe cómo introdujimos un signo negativo aquí. En la Ley de Restitución de Newton, \ (V '\), el vector resultante después del rebote, en realidad va en la dirección opuesta a V. Entonces, ¿cómo representamos las direcciones opuestas en nuestra derivación? Introducir un signo negativo.
Hasta ahora tan bueno. Ahora necesitamos poder expresar estas velocidades mientras estamos bajo la influencia de un impulso. Aquí hay una ecuación simple para modificar un vector por algún impulso escalar \ (j \) a lo largo de una dirección específica \ (n \):
Ecuación 6\ [V '= V + j * n \]
Esperemos que la ecuación anterior tenga sentido, ya que es muy importante de entender. Tenemos un vector unitario \ (n \) que representa una dirección. Tenemos un escalar \ (j \) que representa la duración de nuestro vector \ (n \). Luego agregamos nuestro vector \ (n \) escalado a \ (V \) para obtener \ (V '\). Esto es solo agregar un vector a otro, y podemos usar esta pequeña ecuación para aplicar un impulso de un vector a otro.
Hay un poco más de trabajo por hacer aquí. Formalmente, un impulso se define como un cambio en el impulso. El impulso es velocidad de la masa
. Sabiendo esto, podemos representar un impulso tal como se define formalmente así:
\ [Impulso = masa * Velocidad \\ Velocidad = \ frac Impulso masa \ por lo tanto V '= V + \ frac j * n masa \]
Los tres puntos en un pequeño triángulo (\ (\ por lo tanto \)) se pueden leer como "por lo tanto". Se usa para mostrar que la cosa de antemano se puede usar para concluir que todo lo que sigue es cierto..¡Se han hecho buenos progresos hasta ahora! Sin embargo, debemos poder expresar un impulso utilizando \ (j \) en términos de dos objetos diferentes. Durante una colisión con el objeto A y B, A será empujado en la dirección opuesta a B:
Ecuación 8\ [V '^ A = V ^ A + \ frac j * n masa ^ A \\ V' ^ B = V ^ B - \ frac j * n masa ^ B \]
Estas dos ecuaciones empujarán a A lejos de B a lo largo del vector de unidad de dirección \ (n \) mediante un impulso escalar (magnitud de \ (n \)) \ (j \).
Todo lo que ahora se requiere es combinar las ecuaciones 8 y 5. Nuestra ecuación resultante tendrá un aspecto similar al siguiente:
Ecuación 9\ [(V ^ A - V ^ V + \ frac j * n masa ^ A + \ frac j * n masa ^ B) * n = -e * (V ^ B - V ^ A) \ cdot n \\ \ por lo tanto \\ (V ^ A - V ^ V + \ frac j * n masa ^ A + \ frac j * n masa ^ B) * n + e * (V ^ B - V ^ A) \ cdot n = 0 \]
Si recuerdas, el objetivo original era aislar nuestra magnitud. Esto se debe a que sabemos en qué dirección se debe resolver la colisión (asumida por la detección de colisión), y solo nos queda resolver la magnitud de esta dirección. La magnitud que se desconoce en nuestro caso es \ (j \); debemos aislar \ (j \) y resolverlo.
Ecuación 10\ [(V ^ B - V ^ A) \ cdot n + j * (\ frac j * n masa ^ A + \ frac j * n masa ^ B) * n + e * ( V ^ B - V ^ A) \ cdot n = 0 \\ \ por lo tanto \\ (1 + e) ((V ^ B - V ^ A) \ cdot n) + j * (\ frac j * n masa ^ A + \ frac j * n masa ^ B) * n = 0 \\ \ por lo tanto \\ j = \ frac - (1 + e) ((V ^ B - V ^ A) \ cdot n) \ frac 1 masa ^ A + \ frac 1 masa ^ B \]
¡Uf! ¡Eso fue un poco de matemáticas! Aunque todo ha terminado por ahora. Es importante notar que en la versión final de la Ecuación 10 tenemos \ (j \) a la izquierda (nuestra magnitud) y todo lo que está a la derecha es conocido. Esto significa que podemos escribir algunas líneas de código para resolver nuestro escalar de impulso \ (j \). Y chico es el código mucho más legible que la notación matemática.!
void ResolveCollision (Objeto A, Objeto B) // Calcular velocidad relativa Vec2 rv = B.velocity - A.velocity // Calcular velocidad relativa en términos de la dirección normal float velAlongNormal = DotProduct (rv, normal) // No resolver si las velocidades se están separando si (velAlongNormal> 0) retorna; // Calcular la flotación de restitución e = min (A. restitución, B. restitución) // Calcular la flotación escalar de impulso j = - (1 + e) * velAlongNormal j / = 1 / A.mass + 1 / B.mass // Aplicar impulso Vec2 impulso = j * normal A.velocity - = 1 / A.mass * impulso B.velocity + = 1 / B.mass * impulse
Hay algunas cosas clave que se deben tener en cuenta en el ejemplo de código anterior. Lo primero es el cheque en la línea 10., if (VelAlongNormal> 0)
. Este chequeo es muy importante; se asegura de que solo resuelvas una colisión si los objetos se mueven uno hacia el otro.
Si los objetos se alejan unos de otros, no queremos hacer nada. Esto evitará que los objetos que en realidad no deberían considerarse colisionados se resuelvan entre sí. Esto es importante para crear una simulación que siga la intuición humana sobre lo que debería suceder durante la interacción del objeto..
La segunda cosa a tener en cuenta es que la masa inversa se calcula varias veces sin ningún motivo. Es mejor almacenar su masa inversa dentro de cada objeto y pre-calcularlo una vez:
A.inv_mass = 1 / A.massMuchos motores de física no almacenan masa cruda. Los motores de física a menudo almacenan masa inversa y masa inversa solo. Da la casualidad de que la mayoría de las matemáticas relacionadas con la masa son en forma de
1 / masa
. Lo último a tener en cuenta es que distribuimos inteligentemente nuestro impulso escalar \ (j \) sobre los dos objetos. Queremos que los objetos pequeños reboten en objetos grandes con una gran porción de \ (j \), y que los objetos grandes tengan sus velocidades modificadas por una porción muy pequeña de \ (j \).
Para hacer esto puedes hacer:
float mass_sum = A.mass + B.mass float ratio = A.mass / mass_sum A.velocity - = ratio * impulso = B.mass / mass_sum B.velocity + = ratio * impulse
Es importante darse cuenta de que el código anterior es equivalente al ResolveCollision ()
Función de muestra de antes. Como se dijo antes, las masas inversas son bastante útiles en un motor de física..
Si seguimos adelante y usamos el código que tenemos hasta ahora, los objetos se encontrarán y rebotarán. Esto es genial, aunque ¿qué pasa si uno de los objetos tiene una masa infinita? Bueno, necesitamos una buena manera de representar una masa infinita dentro de nuestra simulación..
Sugiero usar cero como masa infinita, aunque si intentamos calcular la masa inversa de un objeto con cero, tendremos una división por cero. La solución a esto es hacer lo siguiente al calcular la masa inversa:
if (A.mass == 0) A.inv_mass = 0 else A.inv_mass = 1 / A.mass
Un valor de cero resultará en cálculos apropiados durante la resolución del impulso. Esto todavía está bien. El problema de hundir objetos surge cuando algo comienza a hundirse en otro objeto debido a la gravedad. Quizás algo con baja restitución golpee una pared con masa infinita y comience a hundirse..
Este hundimiento se debe a errores de punto flotante. Durante cada cálculo de punto flotante se introduce un pequeño error de punto flotante debido al hardware. (Para más información, Google [error de punto flotante IEEE754].) Con el tiempo, este error se acumula en el error de posición, lo que hace que los objetos se hundan entre sí..
Para corregir este error hay que tenerlo en cuenta. Para corregir este error de posición, te mostraré un método llamado proyección lineal. La proyección lineal reduce la penetración de dos objetos en un pequeño porcentaje, y esto se realiza después de que se aplica el impulso. La corrección posicional es muy simple: mueva cada objeto a lo largo de la colisión normal \ (n \) en un porcentaje de la profundidad de penetración:
void PositionalCorrection (Objeto A, Objeto B) porcentaje flotante constante = 0.2 // generalmente corrección Vec2 del 20% al 80% = penetración Profundidad / (A.inv_mass + B.inv_mass)) * percent * n A.position - = A.inv_mass * corrección B.position + = B.inv_mass * corrección
Tenga en cuenta que escalamos profundidad de penetración
Por la masa total del sistema. Esto dará una corrección posicional proporcional a la cantidad de masa con la que estamos tratando. Los objetos pequeños se alejan más rápido que los objetos más pesados.
Hay un pequeño problema con esta implementación: si siempre resolvemos nuestro error de posición, los objetos se moverán de un lado a otro mientras descansan uno sobre el otro. Para evitar esto se debe dar alguna holgura. Solo realizamos la corrección posicional si la penetración está por encima de un umbral arbitrario, denominado "pendiente":
void PositionalCorrection (Objeto A, Objeto B) porcentaje constante flotante = 0.2 // generalmente 20% a 80% const float slop = 0.01 // generalmente 0.01 a 0.1 Vec2 corrección = max (penetración - k_slop, 0.0f) / (A. inv_mass + B.inv_mass)) * percent * n A.position - = A.inv_mass * corrección B.position + = B.inv_mass * corrección
Esto permite que los objetos penetren ligeramente, sin que la corrección de posición se active..
El último tema a tratar en este artículo es la generación múltiple simple. UNA colector en términos matemáticos hay algo parecido a "una colección de puntos que representa un área en el espacio". Sin embargo, cuando me refiero al término múltiple, me refiero a un objeto pequeño que contiene información sobre una colisión entre dos objetos.
Aquí está una configuración múltiple típica:
struct Manifold Objeto * A; Objeto * B; penetración del flotador Vec2 normal; ;
Durante la detección de colisiones, se debe calcular tanto la penetración como la colisión normal. Para encontrar esta información, se deben ampliar los algoritmos de detección de colisión originales de la parte superior de este artículo..
Comencemos con el algoritmo de colisión más simple: Círculo vs Círculo. Esta prueba es sobre todo trivial. ¿Te imaginas cuál será la dirección para resolver la colisión? Es el vector del círculo A al círculo B. Esto se puede obtener restando la posición de B de las de A.
La profundidad de penetración está relacionada con los radios y la distancia de los Círculos. La superposición de los círculos se puede calcular restando los radios sumados por la distancia de cada objeto.
Aquí hay un algoritmo de muestra completo para generar la variedad de una colisión entre círculo y círculo:
bool CirclevsCircle (Manifold * m) // Configure un par de punteros para cada objeto Object * A = m-> A; Objeto * B = m-> B; // Vector de A a B Vec2 n = B-> pos - A-> pos float r = A-> radio + B-> radio r * = r si (n.LengthSquared ()> r) devuelve false // Circles colisionó, ahora calcule manifold float d = n.Length () // realice el sqrt real // Si la distancia entre círculos no es cero si (d! = 0) // La distancia es la diferencia entre el radio y la distancia m-> penetration = r - d // Utilice nuestra d ya que realizamos sqrt en él ya dentro de Length () // Puntos de A a B, y es un vector unitario c-> normal = t / d return true // Los círculos están en la misma posición else // Elija valores aleatorios (pero consistentes) c-> penetración = A-> radio c-> normal = Vec (1, 0) devuelva verdadero
Las cosas más notables aquí son: no realizamos ninguna raíz cuadrada hasta que sea necesario (se encuentra que los objetos chocan), y nos aseguramos de que los círculos no estén en la misma posición exacta. Si están en la misma posición, nuestra distancia sería cero, y debemos evitar la división por cero cuando calculamos t / d
.
La prueba de AABB a AABB es un poco más compleja que la de círculo contra círculo. La colisión normal no será el vector de A a B, sino que será una cara normal. Una AABB es una caja con cuatro caras. Cada cara tiene una normalidad. Esta normal representa un vector unitario que es perpendicular a la cara..
Examina la ecuación general de una línea en 2D:
\ [ax + by + c = 0 \\ normal = \ begin bmatrix a \\ b \ end bmatrix \]
En la ecuación anterior, una
y segundo
son el vector normal para una línea, y el vector (a, b)
se asume que está normalizado (la longitud del vector es cero). Una vez más, nuestra colisión normal (dirección para resolver la colisión) será en la dirección de una de las normales de la cara..
do
Representa en la ecuación general de una recta.? do
Es la distancia desde el origen. Esto es muy útil para las pruebas para ver si un punto está en un lado de una línea u otra, como se verá en el siguiente artículo.. Ahora todo lo que se necesita es descubrir qué cara está chocando en uno de los objetos con el otro objeto, y tenemos nuestra normalidad. Sin embargo, a veces se pueden intersecar varias caras de dos AABB, como cuando dos esquinas se intersecan entre sí. Esto significa que debemos encontrar el eje de menor penetración.
Dos ejes de penetración; el eje x horizontal es el eje de menor penetración y esta colisión debe resolverse a lo largo del eje x.Aquí hay un algoritmo completo para la detección de colisiones y la generación de colectores AABB a AABB:
bool AABBvsAABB (Múltiple * m) // Configure un par de punteros a cada objeto Objeto * A = m-> A Objeto * B = m-> B // Vector de A a B Vec2 n = B-> pos - A- > pos AABB abox = A-> aabb AABB bbox = B-> aabb // Calcule la mitad de la extensión a lo largo del eje x para cada objeto float a_extent = (abox.max.x - abox.min.x) / 2 float b_extent = (bbox .max.x - bbox.min.x) / 2 // Calcular superposición en x axis float x_overlap = a_extent + b_extent - abs (nx) // SAT prueba en el eje x si (x_overlap> 0) // Calcular la mitad de la extensión a lo largo del eje x para cada objeto float a_extent = (abox.max.y - abox.min.y) / 2 float b_extent = (bbox.max.y - bbox.min.y) / 2 // Calcular superposición en el eje y float y_overlap = a_extent + b_extent - abs (ny) // SAT prueba en el eje y si (y_overlap> 0) // Averigua qué eje es el eje de menor penetración si (x_overlap> y_overlap) // Apunta hacia B sabiendo que n puntos de A a B si (nx < 0) m->normal = Vec2 (-1, 0) else m-> normal = Vec2 (0, 0) m-> penetration = x_overlap return true else // Apunte hacia B sabiendo que n apunta de A a B si (n.y < 0) m->normal = Vec2 (0, -1) else m-> normal = Vec2 (0, 1) m-> penetration = y_overlap return true
La última prueba que cubriré es la prueba Círculo vs AABB. La idea aquí es calcular el punto más cercano en la AABB al Círculo; a partir de ahí la prueba se convierte en algo similar a la prueba Círculo vs Círculo. Una vez que se calcula el punto más cercano y se detecta una colisión, lo normal es la dirección del punto más cercano al centro del círculo. La profundidad de penetración es la diferencia entre la distancia del punto más cercano al círculo y el radio del círculo..
Hay un caso especial difícil; Si el centro del círculo está dentro de la AABB, entonces el centro del círculo debe recortarse hasta el borde más cercano de la AABB, y la normal debe voltearse..
bool AABBvsCircle (Manifold * m) // Configure un par de punteros a cada objeto Object * A = m-> A Object * B = m-> B // Vector de A a B Vec2 n = B-> pos - A- > pos // Punto más cercano en A al centro de B Vec2 close = n // Calcule medias extensiones a lo largo de cada eje float x_extent = (A-> aabb.max.x - A-> aabb.min.x) / 2 float y_extent = (A-> aabb.max.y - A-> aabb.min.y) / 2 // Punto de sujeción a los bordes del AABB closest.x = Clamp (-x_extent, x_extent, closest.x) closest.y = Clamp (-y_extent, y_extent, closest.y) bool inside = false // El círculo está dentro de la AABB, por lo que debemos sujetar el centro del círculo // al borde más cercano si (n == más cercano) inside = true // Encuentre el eje más cercano si (abs (nx)> abs (ny)) // Sujete al extremo más cercano si (el más cercano.x> 0) closest.x = x_extent else closest.x = -x_extent // y y axis es más corto else // Sujete la pinza a la medida más cercana si (closest.y> 0) closest.y = y_extent else closest.y = -y_extent Vec2 normal = n - más cercano real d = normal.LengthSquared () real r = B-> radio // Ea rly fuera del radio es más corto que la distancia al punto más cercano y // Circunde no dentro de la AABB si (d> r * r &&! inside) devuelve false // Se evitó sqrt hasta que necesitáramos d = sqrt (d) // Colisión normal debe voltearse para apuntar hacia afuera si el círculo estaba // dentro de la AABB if (adentro) m-> normal = -n m-> penetración = r - d else m-> normal = n m-> penetración = r - d return true
Esperemos que ya hayas aprendido una o dos cosas sobre la simulación física. Este tutorial es suficiente para permitirle configurar un motor de física personalizado simple hecho completamente desde cero. En la siguiente parte, cubriremos todas las extensiones necesarias que requieren todos los motores de física, incluyendo:
Espero que hayan disfrutado este artículo y espero responder a las preguntas en los comentarios..