c++ opengl 3d sfml quaternions

c++ - Uso de Quaternions para rotaciones de OpenGL



3d sfml (2)

Esta pregunta ya tiene una respuesta aquí:

Así que estoy escribiendo un programa en el que los objetos se mueven alrededor del estilo spaceim, para aprender a mover las cosas sin problemas a través del espacio 3D. Después de jugar un poco con los ángulos de Euler, parece que no son realmente apropiados para el movimiento 3D de forma libre en direcciones arbitrarias, así que decidí pasar a lo que parece mejor para el trabajo: los cuaterniones. Tengo la intención de que el objeto gire alrededor de sus ejes XYZ locales en todo momento, nunca alrededor de los ejes XYZ globales.

He intentado implementar un sistema de rotación utilizando cuaterniones, pero algo no funciona. Al girar el objeto a lo largo de un solo eje, si no se realizaron rotaciones anteriores, la cosa gira bien a lo largo de un eje dado. Sin embargo, cuando se aplica una rotación después de otra, la segunda rotación no siempre está a lo largo del eje local, sino que se supone que está girando, por ejemplo, después de una rotación de aproximadamente 90 ° alrededor del eje Z, una rotación alrededor del eje Y todavía tiene lugar alrededor del eje Y global, en lugar del nuevo eje Y local que está alineado con el eje X global.

Huh Así que vamos a ir a través de este paso a paso. El error debe estar aquí en alguna parte.

PASO 1 - Captura de entrada

Pensé que sería mejor usar los ángulos de Euler (o un esquema de Pitch-Yaw-Roll) para capturar la entrada del jugador. En este momento, las teclas de flecha controlan Pitch y Yaw, mientras que Q y E controlan Roll. Así capturo la entrada del jugador (estoy usando SFML 1.6):

///SPEEDS float ForwardSpeed = 0.05; float TurnSpeed = 0.5; //Rotation sf::Vector3<float> Rotation; Rotation.x = 0; Rotation.y = 0; Rotation.z = 0; //PITCH if (m_pApp->GetInput().IsKeyDown(sf::Key::Up) == true) { Rotation.x -= TurnSpeed; } if (m_pApp->GetInput().IsKeyDown(sf::Key::Down) == true) { Rotation.x += TurnSpeed; } //YAW if (m_pApp->GetInput().IsKeyDown(sf::Key::Left) == true) { Rotation.y -= TurnSpeed; } if (m_pApp->GetInput().IsKeyDown(sf::Key::Right) == true) { Rotation.y += TurnSpeed; } //ROLL if (m_pApp->GetInput().IsKeyDown(sf::Key::Q) == true) { Rotation.z -= TurnSpeed; } if (m_pApp->GetInput().IsKeyDown(sf::Key::E) == true) { Rotation.z += TurnSpeed; } //Translation sf::Vector3<float> Translation; Translation.x = 0; Translation.y = 0; Translation.z = 0; //Move the entity if (Rotation.x != 0 || Rotation.y != 0 || Rotation.z != 0) { m_Entity->ApplyForce(Translation, Rotation); }

m_Entity es lo que estoy tratando de rotar. También contiene las matrices de cuaternión y rotación que representan la rotación del objeto.

PASO 2 - Actualizar cuaternión

No estoy 100% seguro de que esta sea la forma en que se debe hacer, pero esto es lo que intenté hacer en Entity :: ApplyForce ():

//Rotation m_Rotation.x += Rotation.x; m_Rotation.y += Rotation.y; m_Rotation.z += Rotation.z; //Multiply the new Quaternion by the current one. m_qRotation = Quaternion(m_Rotation.x, m_Rotation.y, m_Rotation.z);// * m_qRotation; m_qRotation.RotationMatrix(m_RotationMatrix);

Como puede ver, no estoy seguro de si es mejor construir un nuevo cuaternión a partir de los ángulos de Euler actualizados, o si se supone que debo multiplicar el cuaternión que representa el cambio con el cuaternión que representa la rotación actual general, que es la impresión Lo tengo al leer esta guía . Si este último, mi código se vería así:

//Multiply the new Quaternion by the current one. m_qRotation = Quaternion(Rotation.x, Rotation.y, Rotation.z) * m_qRotation;

m_Rotation es la rotación actual del objeto almacenada en formato PYR; La rotación es el cambio exigido por la entrada del jugador. De cualquier manera, sin embargo, el problema podría estar en mi implementación de mi clase de Quaternion. Aquí está todo el asunto:

Quaternion::Quaternion(float Pitch, float Yaw, float Roll) { float Pi = 4 * atan(1); //Set the values, which came in degrees, to radians for C++ trig functions float rYaw = Yaw * Pi / 180; float rPitch = Pitch * Pi / 180; float rRoll = Roll * Pi / 180; //Components float C1 = cos(rYaw / 2); float C2 = cos(rPitch / 2); float C3 = cos(rRoll / 2); float S1 = sin(rYaw / 2); float S2 = sin(rPitch / 2); float S3 = sin(rRoll / 2); //Create the final values a = ((C1 * C2 * C3) - (S1 * S2 * S3)); x = (S1 * S2 * C3) + (C1 * C2 * S3); y = (S1 * C2 * C3) + (C1 * S2 * S3); z = (C1 * S2 * C3) - (S1 * C2 * S3); } //Overload the multiplier operator Quaternion Quaternion::operator* (Quaternion OtherQuat) { float A = (OtherQuat.a * a) - (OtherQuat.x * x) - (OtherQuat.y * y) - (OtherQuat.z * z); float X = (OtherQuat.a * x) + (OtherQuat.x * a) + (OtherQuat.y * z) - (OtherQuat.z * y); float Y = (OtherQuat.a * y) - (OtherQuat.x * z) - (OtherQuat.y * a) - (OtherQuat.z * x); float Z = (OtherQuat.a * z) - (OtherQuat.x * y) - (OtherQuat.y * x) - (OtherQuat.z * a); Quaternion NewQuat = Quaternion(0, 0, 0); NewQuat.a = A; NewQuat.x = X; NewQuat.y = Y; NewQuat.z = Z; return NewQuat; } //Calculates a rotation matrix and fills Matrix with it void Quaternion::RotationMatrix(GLfloat* Matrix) { //Column 1 Matrix[0] = (a*a) + (x*x) - (y*y) - (z*z); Matrix[1] = (2*x*y) + (2*a*z); Matrix[2] = (2*x*z) - (2*a*y); Matrix[3] = 0; //Column 2 Matrix[4] = (2*x*y) - (2*a*z); Matrix[5] = (a*a) - (x*x) + (y*y) - (z*z); Matrix[6] = (2*y*z) + (2*a*x); Matrix[7] = 0; //Column 3 Matrix[8] = (2*x*z) + (2*a*y); Matrix[9] = (2*y*z) - (2*a*x); Matrix[10] = (a*a) - (x*x) - (y*y) + (z*z); Matrix[11] = 0; //Column 4 Matrix[12] = 0; Matrix[13] = 0; Matrix[14] = 0; Matrix[15] = 1; }

Probablemente haya algo para hacer que alguien sea más sabio que yo, pero no puedo verlo. Para convertir los ángulos de Euler a un cuaternión, utilicé el "primer método" de acuerdo con esta fuente , que también parece sugerir que la ecuación crea automáticamente una unidad de cuaternión ("claramente normalizado"). Para multiplicar cuaterniones, otra vez dibujé en esta guía de C ++ .

PASO 3 - Derivando una matriz de rotación del cuaternión

Una vez hecho esto, de acuerdo con la respuesta de R. Martinho Fernandes a esta pregunta , trato de construir una matriz de rotación desde el cuaternión y usarla para actualizar la rotación de mi objeto, usando el código anterior Quaternion :: RotationMatrix () en la siguiente línea :

m_qRotation.RotationMatrix(m_RotationMatrix);

Debo tener en cuenta que m_RotationMatrix es GLfloat m_RotationMatrix[16] , según los parámetros requeridos de glMultMatrix , que creo que debo utilizar más adelante al mostrar el objeto. Se inicializa como:

m_RotationMatrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};

Creo que es la matriz de rotación OpenGL "neutral" (cada 4 valores juntos representan una columna, ¿correcto? De nuevo, obtengo esto de la página glMultMatrix ).

PASO 4 - Exhibición!

Finalmente, llegamos a la función ejecutar cada ciclo para el objeto que se supone que debe mostrarlo.

glPushMatrix(); glTranslatef(m_Position.x, m_Position.y, m_Position.z); glMultMatrixf(m_RotationMatrix); //glRotatef(m_Rotation.y, 0.0, 1.0, 0.0); //glRotatef(m_Rotation.z, 0.0, 0.0, 1.0); //glRotatef(m_Rotation.x, 1.0, 0.0, 0.0); //glRotatef(m_qRotation.a, m_qRotation.x, m_qRotation.y, m_qRotation.z); //[...] various code displaying the object''s VBO glPopMatrix();

He dejado mis intentos fallidos anteriores allí, comentó.

Conclusión - panda triste

Esa es la conclusión del ciclo de vida de la participación del jugador, desde la cuna hasta la tumba administrada por OpenGL.

Obviamente no he entendido algo, ya que el comportamiento que obtengo no es el comportamiento que quiero o espero. Pero no tengo mucha experiencia con matrices matemáticas o cuaterniones, por lo que no tengo la visión necesaria para ver el error en mis formas.

¿Puede alguien ayudarme aquí?


Los cuaterniones representan orientaciones alrededor de ejes compuestos 3D. Pero también pueden representar ''delta-rotaciones''.

Para "rotar una orientación", necesitamos una orientación (un quat) y una rotación (también un quat), y los multiplicamos juntos, dando como resultado (lo adivinaste) un quat.

Notaste que no son conmutativos, eso significa que el orden en que los multiplicamos es absolutamente importante, al igual que para las matrices. El orden tiende a depender de la implementación de su biblioteca matemática, pero en realidad, solo hay dos formas posibles de hacerlo, por lo que no debería llevarle mucho tiempo descubrir cuál es la correcta, si las cosas están "en órbita". En lugar de ''rotar'', entonces los tienes al revés.

Para su ejemplo de giro y inclinación, construiría mi cuaternión de ''delta-rotación'' a partir de los ángulos de giro, inclinación y giro, con el desplazamiento establecido en cero, y luego lo aplicaré a mi cuaternión de ''orientación'', en lugar de hacer las rotaciones de un eje. a la vez


Todo lo que has hecho es implementar efectivamente los ángulos de Euler con cuaterniones. Eso no está ayudando.

El problema con los ángulos de Euler es que, al calcular las matrices, cada ángulo es relativo a la rotación de la matriz que vino antes. Lo que desea es tomar la orientación actual de un objeto y aplicar una rotación a lo largo de algún eje, produciendo una nueva orientación.

No puedes hacer eso con los ángulos de Euler. Puede hacerlo con matrices, y puede hacerlo con cuaterniones (ya que son solo la parte de rotación de una matriz). Pero no puedes hacerlo fingiendo que son ángulos de Euler.

Esto se hace no almacenando ángulos en absoluto . En su lugar, solo tienes un cuaternión que representa la orientación actual del objeto. Cuando decides aplicarle una rotación (de algún ángulo por algún eje), construyes un cuaternión que representa esa rotación por un ángulo alrededor de ese eje. Luego multiplica a la derecha ese cuaternión con el cuaternión de orientación actual, produciendo una nueva orientación actual.

Cuando renderiza el objeto, usa la orientación actual como ... la orientación.