Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Programming

Cómo crear un motor de física 2D personalizado: cuerpos rígidos orientados

by
Read Time:17 minsLanguages:
This post is part of a series called How to Create a Custom Physics Engine.
How to Create a Custom 2D Physics Engine: Friction, Scene and Jump Table

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

Hasta ahora, hemos cubierto la resolución de impulsos, la arquitectura del núcleo y la fricción. En este tutorial final de esta serie, repasaremos un tema muy interesante: la orientación.

En este artículo vamos a discutir los siguientes temas:    

  • Rotación matemática
  •      Formas orientadas
  • Detección de colisiones
  • Resolución de colisión

Le recomiendo leer en los últimos tres artículos de la serie antes de tratar de abordar este. Gran parte de la información clave de los artículos anteriores son prerrequisitos para el resto de este artículo.


Código de muestra

He creado un pequeño motor de ejemplo en C ++, y le recomiendo que navegue y consulte el código fuente a lo largo de la lectura de este artículo, ya que muchos detalles prácticos de implementación no caben en el propio artículo.

Este repo GitHub contiene el motor de ejemplo en sí, junto con un proyecto de Visual Studio 2010. GitHub le permite ver la fuente sin necesidad de descargar la propia fuente, por conveniencia.


Matemáticas de Orientación

La matemática que implica rotaciones en 2D es bastante simple, aunque un dominio del tema será necesario para crear algo de valor en un motor de física. La segunda ley de Newton dice:

\ [Ecuación \: 1: \\
F = ma \]

Existe una ecuación similar que relaciona específicamente la fuerza angular y la aceleración angular. Sin embargo, antes de que se puedan mostrar estas ecuaciones, se requiere una descripción rápida del producto cruzado en 2D.

Producto cruzado

El producto cruzado en 3D es una operación bien conocida. Sin embargo, el producto cruzado en 2D puede ser muy confuso, ya que no hay realmente una sólida interpretación geométrica.

El producto cruzado 2D, a diferencia de la versión 3D, no devuelve un vector sino un escalar. Este valor escalar representa en realidad la magnitud del vector ortogonal a lo largo del eje z, si el producto cruzado fuera realmente realizado en 3D. De alguna manera, el producto cruzado 2D es sólo una versión simplificada del producto cruzado 3D, ya que es una extensión de la matemática vectorial 3D.

Si esto es confuso, no se preocupe: una comprensión completa del producto cruzado 2D no es todo lo necesario. Sólo sé exactamente cómo realizar la operación y saber que el orden de las operaciones es importante: \ (a \por b \) no es lo mismo que \ (b \por  a \). Este artículo hará uso intensivo del producto cruzado para transformar la velocidad angular en velocidad lineal.

Saber cómo realizar el producto cruzado en 2D es muy importante, sin embargo. Se pueden cruzar dos vectores, un escalar se puede cruzar con un vector, y un vector se puede cruzar con un escalar. Aquí están las operaciones:

Par y velocidad angular

Como todos deberíamos saber de los artículos anteriores, esta ecuación representa una relación entre la fuerza que actúa sobre un cuerpo con la masa de ese cuerpo y la aceleración. Hay un análogo para la rotación:

\ [Ecuación \: 2: \\
T = r \: \por \: \omega \]

\(T\) significa par. El par es la fuerza de rotación.

\(r\) es un vector desde el centro de masa (COM) hasta un punto particular de un objeto. \(r\) se puede considerar como referente a un "radio" de COM a un punto. Cada único punto único en un objeto requerirá un valor diferente de \(r\) para ser representado en la Ecuación 2.

\(\omega\) se llama "omega", y se refiere a la velocidad de rotación. Esta relación se utilizará para integrar la velocidad angular de un cuerpo rígido.

Es importante entender que la velocidad lineal es la velocidad de la COM de un cuerpo rígido. En el artículo anterior, todos los objetos no tenían componentes rotacionales, por lo que la velocidad lineal de la COM era la misma velocidad para todos los puntos de un cuerpo. Cuando se introduce la orientación, los puntos más alejados de la COM giran más rápido que los cerca de la COM. Esto significa que necesitamos una nueva ecuación para encontrar la velocidad de un punto en un cuerpo, ya que los cuerpos pueden ahora girar y traducir al mismo tiempo.

Utilice la siguiente ecuación para entender la relación entre un punto en un cuerpo y la velocidad de ese punto:

\[Ecuación \: 3:\\
\omega = r \: \por v \]

\(v\) representa la velocidad lineal. Para transformar la velocidad lineal en velocidad angular, cruce el radio \(r\) con \(v\).

Del mismo modo, podemos reorganizar la ecuación 3 para formar otra versión:

\[Ecuación \: 4:\\
v = \omega \: \por r \]

Las ecuaciones de la última sección son muy poderosas sólo si los cuerpos rígidos tienen una densidad uniforme. La densidad no uniforme hace que la matemática involucrada en el cálculo de cualquier cosa requiere la rotación y el comportamiento de un cuerpo rígido demasiado complicado. Por otra parte, si el punto que representa un cuerpo rígido no está en el COM, entonces los cálculos con respecto a \ (r \) van a ser enteramente wonky.

Inercia

En dos dimensiones, un objeto gira alrededor del eje z imaginario. Esta rotación puede ser bastante difícil dependiendo de la cantidad de masa que tenga un objeto, y cuán lejos de la COM es la masa del objeto. Un círculo con una masa igual a una barra delgada larga será más fácil de rotar que la barra. Este factor de "dificultad para girar" puede considerarse como el momento de inercia de un objeto.

En cierto sentido, la inercia es la masa rotacional de un objeto. Cuanto más inercia tiene algo, más difícil es conseguir que gire.

Sabiendo esto, uno podría almacenar la inercia de un objeto dentro del cuerpo como el mismo formato que la masa. Sería prudente también almacenar la inversa de este valor de inercia, teniendo cuidado de no realizar una división por cero. Consulte los artículos anteriores de esta serie para obtener más información sobre masa y masa inversa.

Integración

Cada cuerpo rígido requerirá unos pocos campos más para almacenar la información rotacional. He aquí un ejemplo rápido de una estructura que contiene algunos datos adicionales:

La integración de la velocidad angular y la orientación de un cuerpo son muy similares a la integración de la velocidad y la aceleración. Aquí hay un ejemplo de código rápido para mostrar cómo se hace (nota: los detalles sobre la integración se trataron en un artículo anterior):

Con la pequeña cantidad de información presentada hasta ahora, usted debe ser capaz de empezar a girar varias cosas en la pantalla sin ningún problema. Con sólo unas pocas líneas de código, algo más impresionante puede ser construido, tal vez lanzando una forma en el aire mientras gira alrededor de la COM como la gravedad lo tira hacia abajo para formar una trayectoria arqueada de viaje.

Mat22

La orientación debe almacenarse como un solo valor radiano, como se ha visto anteriormente, aunque a menudo el uso de una pequeña matriz de rotación puede ser una opción mucho mejor para ciertas formas.

Un buen ejemplo es el Oriented Bounding Box (OBB). El OBB consta de una extensión y una extensión de altura, las cuales pueden ser representadas por vectores. Estos dos vectores de extensión se pueden girar entonces mediante una matriz de rotación de dos por dos para representar los ejes de un OBB.

Sugiero la creación de una matriz Mat22 clase que se añade a cualquier biblioteca de matemáticas que está utilizando. Yo mismo uso una pequeña biblioteca de matemáticas personalizada que se empaqueta en la demo de código abierto. He aquí un ejemplo de cómo puede parecer un objeto de este tipo:

Algunas operaciones útiles incluyen: construcción a partir del ángulo, construcción de los vectores de la columna, transponer, multiplicar con Vec2, multiplicar con otro Mat22, valor absoluto.

La última función útil es poder recuperar la columna x o y de un vector. La función de columna se vería así:

Esta técnica es útil para recuperar un vector unitario a lo largo del eje de rotación, ya sea el eje x o y. Además, una matriz de dos por dos se puede construir a partir de dos vectores unitarios ortogonales, ya que cada vector puede insertarse directamente en las filas. Aunque este método de construcción es un poco raro para los motores de física 2D, todavía puede ser muy útil para entender cómo funcionan las rotaciones y matrices en general.

Este constructor podría parecerse a lo siguiente:

Dado que la operación más importante de una matriz de rotación es realizar rotaciones basadas en un ángulo, es importante poder construir una matriz desde un ángulo y multiplicar un vector por esta matriz (para girar el vector en sentido contrario a las agujas del reloj por el ángulo matriz se construyó):

Por razones de brevedad, no deduciré por qué la matriz de rotación a la izquierda es de la forma:

Sin embargo, es importante por lo menos saber que esta es la forma de la matriz de rotación. Para obtener más información sobre las matrices de rotación, consulte la página de Wikipedia.


Transformando a una base

Es importante entender la diferencia entre el modelo y el espacio mundial. El espacio modelo es el sistema de coordenadas local para una forma física. El origen está en el COM y la orientación del sistema de coordenadas está alineada con los ejes de la propia forma.

Con el fin de transformar una forma en el espacio mundial debe ser girado y traducido. La rotación debe ocurrir primero, ya que la rotación se realiza siempre alrededor del origen. Puesto que el objeto está en el espacio del modelo (origen en COM), la rotación girará alrededor del COM de la forma. La rotación se produciría con una matriz Mat22. En el código de ejemplo, las matrices de orientación son del nombre u.

Después de la rotación se realiza, el objeto puede ser traducido a su posición en el mundo mediante la adición de vectores.

Una vez que un objeto está en el espacio del mundo, puede ser traducido al espacio modelo de un objeto completamente diferente usando transformaciones inversas. La rotación inversa seguida por la traducción inversa se utilizan para hacerlo. ¡Esto es cuánto se simplifica la matemática durante la detección de colisiones!

Inverse transformation from world space to model space of the red polygon.Inverse transformation from world space to model space of the red polygon.Inverse transformation from world space to model space of the red polygon.
Transformación inversa (de izquierda a derecha) del espacio mundial al espacio modelo del polígono rojo.

Como se ve en la imagen anterior, si la transformación inversa del objeto rojo se aplica a ambos polígonos rojo y azul, entonces una prueba de detección de colisión puede reducirse a la forma de una prueba AABB vs OBB, en lugar de calcular matemáticas complejas entre dos orientadas.

En gran parte del código fuente de la muestra, los vértices se transforman constantemente de modelo a mundo y de nuevo a modelo, por todo tipo de razones. Debe tener una clara comprensión de lo que esto significa para comprender el código de detección de colisión de la muestra.


Detección de colisiones y generación de colectores

En esta sección, presentaré los contornos rápidos de las colisiones de polígonos y círculos. Consulte el código fuente de ejemplo para obtener más detalles de implementación en profundidad.

Polígono a polígono

Comencemos con la rutina de detección de colisiones más compleja en esta serie de artículos completa. La idea de comprobar la colisión entre dos polígonos se hace mejor (en mi opinión) con el Teorema del Eje Separador (SAT).

Sin embargo, en lugar de proyectar las extensiones de cada polígono entre sí, hay un método un poco más nuevo y más eficiente, como se describe por Dirk Gregorius en su conferencia de 2013 GDC (diapositivas disponibles aquí de forma gratuita).

Lo primero que hay que aprender es el concepto de puntos de apoyo.

Puntos de apoyo

El punto de apoyo de un polígono es el vértice que es el más lejano a lo largo de una dirección dada. Si dos vértices tienen distancias iguales a lo largo de la dirección dada, cualquiera de ellos es aceptable.

Con el fin de calcular un punto de apoyo, el producto punto se debe utilizar para encontrar una distancia firmada en una dirección dada. Puesto que esto es muy simple, mostraré un ejemplo rápido en este artículo:

El producto punto se utiliza en cada vértice. El producto punto representa una distancia firmada en una dirección dada, por lo que el vértice con la mayor distancia proyectada sería el vértice a devolver. Esta operación se realiza en el espacio de modelo del polígono dado dentro del motor de ejemplo.

Encontrar el eje de la separación

Mediante el uso del concepto de puntos de apoyo, se puede realizar una búsqueda del eje de separación entre dos polígonos (polígono A y polígono B). La idea de esta búsqueda es hacer un bucle a lo largo de todas las caras del polígono A y encontrar el punto de apoyo en el negativo normal a esa cara.

SupportPoints

En la imagen anterior, se muestran dos puntos de soporte: uno sobre cada objeto. La normal azul correspondería al punto de apoyo en el otro polígono como el vértice más lejano en la dirección opuesta a la normal azul. Del mismo modo, la normal roja se utilizaría para encontrar el punto de apoyo situado en el extremo de la flecha roja.

La distancia de cada punto de apoyo a la cara actual sería la penetración firmada. Almacenando la mayor distancia se puede registrar un eje mínimo de penetración posible.

Esta es una función de ejemplo del código fuente de ejemplo que encuentra el posible eje de penetración mínima utilizando la función GetSupport:

Puesto que esta función devuelve la mayor penetración, si esta penetración es positiva, significa que las dos formas no se superponen (penetración negativa no significaría eje de separación).

Esta función tendrá que ser llamada dos veces, volteando objetos A y B cada llamada.

Incidente de recorte y rostro de referencia

A partir de aquí, es necesario identificar la cara incidente y de referencia y la cara incidente necesita ser recortada contra los planos laterales de la cara de referencia. Esta es una operación algo no trivial, aunque Erin Catto (creador de Box2D, y toda la física usada actualmente por Blizzard) ha creado algunas grandes diapositivas que cubren este tema en detalle.

Este recorte generará dos puntos de contacto potenciales. Todos los puntos de contacto detrás de la cara de referencia pueden considerarse puntos de contacto.

Más allá de las diapositivas de Erin Catto, el motor de ejemplo también tiene las rutinas de recorte implementadas como ejemplo.

Círculo a polígono

El círculo frente a la rutina de colisión de polígonos es bastante más simple que la detección de colisión de polígono frente a polígono. En primer lugar, la cara más cercana en el polígono al centro del círculo se calcula de manera similar a la utilización de puntos de apoyo de la sección anterior: haciendo un bucle sobre cada cara normal del polígono y encontrar la distancia desde el centro del círculo a la cara.

Si el centro del círculo está detrás de esta cara más cercana, se puede generar información de contacto específica y la rutina puede terminar inmediatamente.

Después de identificar la cara más cercana, la prueba se convierte en un segmento de línea frente a la prueba de círculo. Un segmento de línea tiene tres regiones interesantes llamadas regiones de Voronoi. Examine el siguiente diagrama:

Voronoi regions of a line segment.Voronoi regions of a line segment.Voronoi regions of a line segment.
Voronoi regiones de un segmento de línea.

Intuitivamente, dependiendo de donde se encuentre el centro del círculo, se puede derivar información de contacto. Imagine que el centro del círculo se encuentra en cualquiera de las regiones del vértice. Esto significa que el punto más cercano al centro del círculo será un vértice de borde, y la colisión normal normal será un vector de este vértice al centro del círculo.

Si el círculo está dentro de la región de la cara, entonces el punto más cercano del segmento al centro del círculo será el proyecto central del círculo sobre el segmento. La colisión normal será sólo la cara normal.

Para calcular en qué región de Voronoi se encuentra el círculo, usamos el producto punto entre un par de vértices. La idea es crear un triángulo imaginario y probar si el ángulo de la esquina construido con el vértice del segmento está por encima o por debajo de 90 grados. Se crea un triángulo para cada vértice del segmento de línea.

Projecting vector from edge vertex to circle center onto the edge.Projecting vector from edge vertex to circle center onto the edge.Projecting vector from edge vertex to circle center onto the edge.
Proyectar el vector desde el vértice del borde hasta el centro del círculo en el borde.

Un valor de más de 90 grados significa que se ha identificado una región de borde. Si ninguno de los ángulos del vértice del borde del triángulo está por encima de 90 grados, entonces el centro del círculo necesita ser proyectado sobre el propio segmento para generar información del múltiple. Como se ve en la imagen anterior, si el vector desde el vértice del borde al centro del círculo con el mismo vector de borde es negativo, entonces se conoce la región de Voronoi en la que se encuentra el círculo.

Por suerte, el producto punto se puede utilizar para calcular una proyección firmada, y este signo será negativo si por encima de 90 grados y positivo si está por debajo.


Resolución de colisión

Es esa vez otra vez: volveremos a nuestro código de resolución de impulso por tercera y última vez. Por ahora, debe estar completamente cómodo escribiendo su propio código de resolución que calcula los impulsos de resolución, junto con los impulsos de fricción, y también puede realizar proyección lineal para resolver la penetración sobrante.

Los componentes de rotación deben agregarse tanto a la fricción como a la resolución de penetración. Alguna energía será colocada en la velocidad angular.

Aquí está nuestra resolución de impulso como la dejamos del artículo anterior sobre la fricción:

\[Ecuación 5: \\
j {\ frac {1} {masa} {A}} \ frac {1} {masa} ^ {B}}}
\]

Si lanzamos componentes rotacionales, la ecuación final tiene este aspecto:

\[Ecuación 6: \\
j {\ frac {1} {masa} {A}} \ frac {1} {masa} ^ {B}} + \ frac {(r ^ {A} \ veces t) ^ {2}} {I} {A}} + \ frac { {I ^ {B}}}
\]

En la ecuación anterior, \ (r \) es de nuevo un "radio", como en un vector de la COM de un objeto al punto de contacto. Una derivación más profunda de esta ecuación se puede encontrar en el sitio de Chris Hecker.

Es importante darse cuenta de que la velocidad de un punto dado en un objeto es:

\[Ecuación 7: \\
V '= V + \omega \por r
\]

La aplicación de impulsos cambia ligeramente para dar cuenta de los términos de rotación:


Conclusión

Esto concluye el artículo final de esta serie. Hasta ahora, se han cubierto bastantes temas, incluyendo resolución basada en impulsos, generación de colectores, fricción y orientación, todo en dos dimensiones.

Si has llegado tan lejos, debo felicitarte! La programación del motor de la física para los juegos es un área de estudio extremadamente difícil. Deseo a todos los lectores suerte, y de nuevo, por favor no dude en comentar o hacer preguntas a continuación.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.