transformations geometric opengl matrix graphics 3d transformation

opengl - geometric - ¿Transformar correctamente un nodo en relación con un espacio especificado?



geometric transformations opengl (3)

worldTranslation = parentNode.worldTranslation * localTranslation; worldRotation = parentNode.worldRotation * localRotation; worldScale = parentNode.worldScale * localScale;

Esta no es la forma en que funciona la acumulación de transformaciones sucesivas. Y es obvio por qué no si lo piensas bien.

Digamos que tienes dos nodos: un padre y un hijo. El padre tiene una rotación local de 90 grados en sentido antihorario sobre el eje Z. El niño tiene un desplazamiento de +5 en el eje X. Bueno, una rotación en sentido contrario a las agujas del reloj debería hacer que tenga un +5 en el eje Y, sí (suponiendo un sistema de coordenadas a la derecha)?

Pero no es así Su localTranslation nunca se ve afectada por ninguna forma de rotación.

Esto es cierto de todas sus transformaciones. Las traducciones se ven afectadas solo por las traducciones, no por escalas o rotaciones. Las rotaciones no se ven afectadas por las traducciones. Etc.

Eso es lo que su código dice que haga, y no se trata de cómo se supone que debe hacerlo.

Mantener los componentes de sus matrices descompuestos es una buena idea. Es decir, tener componentes separados de traducción, rotación y escala (TRS) es una buena idea. Hace más fácil aplicar sucesivas transformaciones locales en el orden correcto.

Ahora, mantener los componentes como matrices es incorrecto, porque realmente no tiene sentido y desperdicia tiempo y espacio sin una razón real. Una traducción es solo un vec3 , y no hay nada que ganar almacenando otros 13 componentes con él. Cuando acumulas traducciones localmente, simplemente las agregas.

Sin embargo, en el momento en que necesite acumular la matriz final para un nodo, debe convertir cada descomposición de TRS en su propia matriz local, luego transformarla en la transformación general de los padres, no en los componentes TRS individuales del padre. Es decir, necesita componer las transformaciones separadas localmente, luego multiplíquelas con la matriz de transformación primaria. En pseudo-código:

function AccumRotation(parentTM) local localMatrix = TranslationMat(localTranslation) * RotationMat(localRotation) * ScaleMat(localScale) local fullMatrix = parentTM * localMatrix for each child child.AccumRotation(fullMatrix) end end

Cada padre pasa su propia rotación acumulada al niño. El nodo raíz recibe una matriz de identidad.

Ahora, la descomposición de TRS está perfectamente bien, pero solo funciona cuando se trata de transformaciones locales . Es decir, transformaciones relativas al padre. Si desea rotar un objeto en su espacio local, aplique un cuaternario a su orientación.

Pero realizar una transformación en un espacio no local es una historia completamente diferente. Si desea, por ejemplo, aplicar una traducción en el espacio-mundo a un objeto que tiene algunas series arbitrarias de transformaciones aplicadas ... eso no es una tarea trivial. Bueno, en realidad, es una tarea fácil: calcula la matriz de espacio-mundo del objeto, luego aplica una matriz de traducción a la izquierda de eso, luego usa la inversa de la matriz de espacio-mundo de los padres para calcular la transformación relativa al padre.

function TranslateWorld(transVec) local parentMat = this->parent ? this->parent.ComputeTransform() : IdentityMatrix local localMat = this->ComputeLocalTransform() local offsetMat = TranslationMat(localTranslation) local myMat = parentMat.Inverse() * offsetMat * parentMat * localMat end

El significado de la cosa P -1 O P es en realidad una construcción común. Significa transformar la transformación general O en el espacio de P Por lo tanto, transforma una compensación mundial en el espacio de la matriz principal. Luego aplicamos eso a nuestra transformación local.

myMat ahora contiene una matriz de transformación que, cuando se multiplica por las transformaciones de los padres, aplicará transVec como si estuviera en el espacio mundial. Eso es lo que querías.

El problema es que myMat es una matriz , no una descomposición de TRS. ¿Cómo vuelves a la descomposición de TRS? Bueno ... eso requiere matemáticas matriciales realmente no triviales. Requiere hacer algo llamado Descomposición de valor singular . E incluso después de implementar las matemáticas feas, SVD puede fallar . Es posible tener una matriz no descomponible.

En un sistema de gráficos de escena que escribí, creé una clase especial que efectivamente era una unión entre una descomposición de TRS y la matriz que representa. Podría consultar si se descompuso, y si fuera así podría modificar los componentes de TRS. Pero una vez que trataste de asignarle un valor de matriz 4x4 directamente, se convirtió en una matriz compuesta y ya no podías aplicar las transformadas descompuestas locales. Ni siquiera intenté implementar SVD.

Oh, podrías acumular matrices en él. Pero la acumulación sucesiva de transformaciones arbitrarias no arrojará el mismo resultado que las modificaciones de componentes descompuestos. Si desea afectar la rotación sin afectar las traducciones anteriores, solo puede hacer eso si la clase está en un estado descompuesto.

En cualquier caso, su código tiene algunas ideas correctas, pero también algunas muy incorrectas. Debe decidir qué tan importante es tener una descomposición de TRS y qué tan importante es poder aplicar una transformación no local.

Actualmente estoy trabajando con nodos en un gráfico de escena jerárquico y tengo dificultades para traducir / rotar correctamente un nodo en relación con un espacio de transformación específico (por ejemplo, un nodo padre).

¿Cómo puedo traducir / rotar un nodo en relación con su nodo padre en un gráfico de escena?

El problema

Considere el siguiente diagrama de molécula de agua (sin las líneas de conexión) para la estructura padre / hijo de los nodos de escena, siendo el átomo O xygen el nodo primario y los 2 nódulos de hidrógeno H como los nodos secundarios.

Problema de traducción

Si agarras el átomo de oxígeno de los padres y traduces la estructura, esperas que los hijos de H ydrógeno sigan y se mantengan en la misma posición relativa con respecto a sus padres. Si tomas un átomo H infantil y lo traduces, entonces solo el niño se vería afectado. Por lo general, así es como funciona actualmente. Cuando se traducen átomos de O , los átomos de H se mueven automáticamente con él, como se espera de un gráfico jerárquico.

Sin embargo , al traducir al padre, los niños también terminan acumulando una traducción adicional , lo que esencialmente hace que los niños "traduzcan dos veces" en la misma dirección y se alejen de sus padres en lugar de permanecer a la misma distancia relativa.

Problema de rotación

Si toma el nodo O padre y lo gira, espera que los nodos H hijos también roten, pero en una órbita, porque el padre está realizando la rotación. Esto funciona según lo previsto.

Sin embargo , si tomas un nodo hijo H y le dices que gire en relación con su padre , esperaba que solo el niño terminaría orbitando alrededor de su padre de la misma manera, pero esto no sucede. En cambio, el niño gira en su propio eje a un ritmo más rápido (por ejemplo, dos veces más rápido que girar en relación con su propio espacio local) en su posición actual.

Realmente espero que esta descripción sea justa, pero avíseme si no es así y lo aclararé según sea necesario.

Las matemáticas

Estoy usando matrices de columnas 4x4 principales (es decir, Matrix4 ) y vectores de columnas (es decir, Vector4 , Vector4 ).

La siguiente lógica incorrecta es la más cercana a la que he llegado al comportamiento correcto. Tenga en cuenta que he elegido usar una sintaxis similar a Java, con sobrecarga del operador para que las matemáticas sean más fáciles de leer aquí. Intenté cosas diferentes cuando pensé que lo había descubierto, pero realmente no lo había hecho.

Lógica de traducción actual

translate(Vector3 tv /* translation vector */, TransformSpace relativeTo): switch (relativeTo): case LOCAL: localTranslation = localTranslation * TranslationMatrix4(tv); break; case PARENT: if parentNode != null: localTranslation = parentNode.worldTranslation * localTranslation * TranslationMatrix4(tv); else: localTranslation = localTranslation * TranslationMatrix4(tv); break; case WORLD: localTranslation = localTranslation * TranslationMatrix4(tv); break;

Lógica de rotación actual

rotate(Angle angle, Vector3 axis, TransformSpace relativeTo): switch (relativeTo): case LOCAL: localRotation = localRotation * RotationMatrix4(angle, axis); break; case PARENT: if parentNode != null: localRotation = parentNode.worldRotation * localRotation * RotationMatrix4(angle, axis); else: localRotation = localRotation * RotationMatrix4(angle, axis); break; case WORLD: localRotation = localRotation * RotationMatrix4(angle, axis); break;

Cálculo de transformaciones del espacio mundial

En aras de la integridad, las transformaciones mundiales para this nodo se calculan de la siguiente manera:

if parentNode != null: worldTranslation = parent.worldTranslation * localTranslation; worldRotation = parent.worldRotation * localRotation; worldScale = parent.worldScale * localScale; else: worldTranslation = localTranslation; worldRotation = localRotation; worldScale = localScale;

Además, la transformación completa / acumulada de un Nodo para this es:

Matrix4 fullTransform(): Matrix4 localXform = worldTranslation * worldRotation * worldScale; if parentNode != null: return parent.fullTransform * localXform; return localXform;

Cuando se solicita que la transformación de un nodo se envíe uniformemente al sombreador de OpenGL, se fullTransform matriz de fullTransform .


El problema básico es cómo resolver el problema de la matriz de conmutación.

Supongamos que tiene una matriz X y un producto de matriz A B C. Y suponga que quiere multiplicar, encuentre una Y tal que

X*A*B*C = A*B*Y*C

o viceversa.

Suponiendo que las matrices no son singulares, primero elimine los términos comunes:

X*A*B = A*B*Y

A continuación, aislar. Hacer un seguimiento de izquierda a derecha, multiplicar por inversos:

A^-1*X*A*B = A^-1 *A *B *Y A^-1*X*A*B = B *Y B^-1*A^-1*X*A*B = Y

o en el caso donde tienes Y pero quieres X:

X*A*B *B^-1 *A^-1 = A*B*Y*B^-1 *A^-1 X = A*B*Y*B^-1 *A^-1

Lo anterior es solo un caso especial de la regla general:

X*A = A*Y

Medio

X=A*Y*A^-1 A^-1*X*A=Y

Con la nota que (A*B)^-1 = B^-1 * A^-1 .

Este procedimiento le permite examinar una cadena de transformaciones y preguntar "Quiero aplicar una transformación en un punto específico, pero almacénelo aplicándolo en otro lugar", que es el núcleo de su problema.

La cadena de matrices con la que trabajas debe incluir todas las transformaciones: traducciones, rotaciones, escalas, no solo transformaciones del mismo tipo, ya que resolver para X * B = B * Y no produce una solución para X * A * B = A * B * Y


Encontré que la respuesta de Nicol Bolas fue de alguna ayuda, a pesar de que todavía había algunos detalles en los que no estaba tan claro. Pero esa respuesta me ayudó a ver la naturaleza no trivial del problema en el que estaba trabajando, así que decidí simplificar las cosas.

Una solución más simple: siempre en el espacio para padres

Node.TransformSpace el Node.TransformSpace para simplificar el problema. Todas las transformaciones ahora se aplican en relación con el espacio de un Node primario, y las cosas funcionan como se esperaba. Los cambios en la estructura de datos que pretendía realizar después de hacer que las cosas funcionen (por ejemplo, reemplazar las matrices locales de traducción / escalado para vectores simples) también están ahora en su lugar.

Un resumen de las matemáticas actualizadas a continuación.

Traducción actualizada

La posición de un Node ahora está representada por un objeto Vector3 , con el Matrix4 construido a pedido (ver más adelante).

void translate(Vector3 tv /*, TransformSpace relativeTo */): localPosition += tv;

Rotación actualizada

Las rotaciones ahora están contenidas en una Matrix3 , es decir, una matriz de 3x3.

void rotate(Angle angle, Vector3 axis /*, TransformSpace relativeTo */): localRotation *= RotationMatrix3(angle, axis);

Todavía planeo mirar los cuaterniones más tarde, después de que pueda verificar que las conversiones de matriz de cuaterniones <=> sean correctas.

Escalado actualizado

Al igual que la posición de un Node , escalar ahora también es un objeto Vector3 :

void scale(Vector3 sv): localScale *= sv;

Cálculos actualizados de transformaciones locales / mundiales

Las siguientes actualizaciones transforman el mundo de un Node relación con su Node primario, si lo hay. Se corrigió un problema al eliminar una concatenación innecesaria a la transformación completa de los padres (ver publicación original).

void updateTransforms(): if parentNode != null: worldRotation = parent.worldRotation * localRotation; worldScale = parent.worldScale * localScale; worldPosition = parent.worldPosition + parent.worldRotation * (parent.worldScale * localPosition); else: derivedPosition = relativePosition; derivedRotation = relativeRotation; derivedScale = relativeScale; Matrix4 t, r, s; // cache local/world transforms t = TranslationMatrix4(localPosition); r = RotationMatrix4(localRotation); s = ScalingMatrix4(localScale); localTransform = t * r * s; t = TranslationMatrix4(worldPosition); r = RotationMatrix4(worldRotation); s = ScalingMatrix4(worldScale); worldTransform = t * r * s;