Rotar objeto alrededor de eje fijo

12

Estoy tratando de permitir que el usuario de mi aplicación gire un objeto 3D dibujado en el centro de la pantalla arrastrando su dedo sobre la pantalla. Un movimiento horizontal en la pantalla significa rotación alrededor de un eje Y fijo, y un movimiento vertical significa rotación alrededor del eje X. El problema que tengo es que si solo permito la rotación alrededor de un eje, el objeto gira bien, pero tan pronto como introduzco una segunda rotación, el objeto no gira como se esperaba.

Aquí hay una imagen de lo que está sucediendo:

ingrese la descripción de la imagen aquí

El eje azul representa mi eje fijo. Imagen de la pantalla que tiene este eje azul fijo. Esto es a lo que quiero que rote el objeto en relación. Lo que está sucediendo está en rojo.

Esto es lo que sé:

  1. La primera rotación alrededor de Y (0, 1, 0) hace que el modelo se mueva del espacio azul (llame a este espacio A) a otro espacio (llame a este espacio B)
  2. Intentar rotar nuevamente usando el vector (1, 0, 0) gira alrededor del eje x en el espacio B NO en el espacio A, que no es lo que quiero hacer.

Esto es lo que probé, dado lo que (creo) que sé (dejando de lado la W coord por brevedad):

  1. Primero gire alrededor de Y (0, 1, 0) usando un Quaternion.
  2. Convierta la rotación Y Quaternion en una matriz.
  3. Multiplique la matriz de rotación Y por mi eje fijo x Vector (1, 0, 0) para obtener el eje X en relación con el nuevo espacio.
  4. Gire alrededor de este nuevo Vector X usando un Quaternion.

Aquí está el código:

private float[] rotationMatrix() {

    final float[] xAxis = {1f, 0f, 0f, 1f};
    final float[] yAxis = {0f, 1f, 0f, 1f};
    float[] rotationY = Quaternion.fromAxisAngle(yAxis, -angleX).toMatrix();

    // multiply x axis by rotationY to put it in object space
    float[] xAxisObjectSpace = new float[4];
    multiplyMV(xAxisObjectSpace, 0, rotationY, 0, xAxis, 0);

    float[] rotationX = Quaternion.fromAxisAngle(xAxisObjectSpace, -angleY).toMatrix();

    float[] rotationMatrix = new float[16];
    multiplyMM(rotationMatrix, 0, rotationY, 0, rotationX, 0);
    return rotationMatrix;
  }

Esto no funciona como espero. La rotación parece funcionar, pero en algún momento el movimiento horizontal no gira sobre el eje Y, parece girar sobre el eje Z.

No estoy seguro si mi comprensión es incorrecta o si algo más está causando un problema. Tengo algunas otras transformaciones que estoy haciendo al objeto además de la rotación. Muevo el objeto al centro antes de aplicar la rotación. Lo giro usando la matriz devuelta por mi función anterior, luego lo traduzco -2 en la dirección Z para poder ver el objeto. No creo que esto esté arruinando mis rotaciones, pero de todos modos aquí está el código para eso:

private float[] getMvpMatrix() {
    // translates the object to where we can see it
    final float[] translationMatrix = new float[16];
    setIdentityM(translationMatrix, 0);
    translateM(translationMatrix, 0, translationMatrix, 0, 0f, 0f, -2);

    float[] rotationMatrix = rotationMatrix();

    // centers the object
    final float[] centeringMatrix = new float[16];
    setIdentityM(centeringMatrix, 0);
    float moveX = (extents.max[0] + extents.min[0]) / 2f;
    float moveY = (extents.max[1] + extents.min[1]) / 2f;
    float moveZ = (extents.max[2] + extents.min[2]) / 2f;
    translateM(centeringMatrix, 0, //
      -moveX, //
      -moveY, //
      -moveZ //
    );

    // apply the translations/rotations
    final float[] modelMatrix = new float[16];
    multiplyMM(modelMatrix, 0, translationMatrix, 0, rotationMatrix, 0);
    multiplyMM(modelMatrix, 0, modelMatrix, 0, centeringMatrix, 0);

    final float[] mvpMatrix = new float[16];
    multiplyMM(mvpMatrix, 0, projectionMatrix, 0, modelMatrix, 0);
    return mvpMatrix;
  }

He estado atrapado en esto por unos días. La ayuda es muy apreciada.

================================================== ================

ACTUALIZAR:

Hacer que esto funcione en Unity es sencillo. Aquí hay un código que gira un cubo centrado en el origen:

public class CubeController : MonoBehaviour {

    Vector3 xAxis = new Vector3 (1f, 0f, 0f);
    Vector3 yAxis = new Vector3 (0f, 1f, 0f);

    // Update is called once per frame
    void FixedUpdate () {
        float horizontal = Input.GetAxis ("Horizontal");
        float vertical = Input.GetAxis ("Vertical");

        transform.Rotate (xAxis, vertical, Space.World);
        transform.Rotate (yAxis, -horizontal, Space.World);
    }
}

La parte que hace que las rotaciones se comporten como espero es el Space.Worldparámetro de la Rotatefunción en la transformación.

Si pudiera usar Unity lo haría, desafortunadamente tengo que codificar este comportamiento yo mismo.

Christopher Perry
fuente
1
Mi respuesta aquí gamedev.stackexchange.com/questions/67199/… podría ayudarte ...
concept3d
Entiendo el concepto detrás de su respuesta, pero cómo implementarlo se me escapa.
Christopher Perry
Si marcó las otras respuestas, la respuesta sintáctica implementa la idea que expliqué.
concept3d
No, no lo hace, está haciendo múltiples rotaciones sobre diferentes ejes. Sugieres hacer una sola rotación.
Christopher Perry

Respuestas:

3

El problema que tienes se llama gimble lock . Creo que lo que estás buscando hacer se llama rotación de bola de arco . Las matemáticas para el arcball pueden ser un poco complicadas.

Una forma más sencilla de hacerlo es encontrar un vector 2D perpendicular al deslizamiento 2D en la pantalla.

Tome el vector y proyecte en la cámara cerca del plano para obtener un vector 3D en el espacio mundial. Pantalla de espacio al espacio mundial .

Luego crea un cuaternión con este vector y multiplícalo en un objeto de juego. Probablemente con un poco de sorbo o transición lerp.

Editar:

Ejemplo de unidad: en el ejemplo de unidad, el estado interno de la rotación de los objetos del juego es un cuaternión, no una matriz. El método transform.rotation genera un cuaternión basado en el vector y el ángulo proporcionados y multiplica ese cuaternión con el cuaternión de rotación de los objetos del juego. Solo genera la matriz de rotación para renderización o física en un punto posterior. Los cuaterniones son aditivos y evitan el bloqueo de los gimbles.

Su código debería verse así:

private float[] rotationMatrix() {

    final float[] xAxis = {1f, 0f, 0f, 1f};
    final float[] yAxis = {0f, 1f, 0f, 1f};

    Quaternion qY = Quaternion.fromAxisAngle(yAxis, angleX);
    Quaternion qX = Quaternion.fromAxisAngle(xAxis, -angleY);

    return (qX * qY).getMatrix(); // should probably represent the gameobjects rotation as a quaternion(not a matrix) and multiply all 3 quaternions before generating the matrix. 
  }

ArcBall Rotation Opengl Tutorial

Anthony Raimondo
fuente
No obtengo el bloqueo del cardán, la primera rotación mueve el eje, por lo que una segunda rotación se basa en el eje movido. Por favor, eche un vistazo a la imagen que proporcioné.
Christopher Perry
Espero que lo hayas resuelto. En breve. Los cuaterniones se pueden multiplicar para aplicar rotación. Solo debe generar la matriz al final de todos los cálculos de rotación. Además, xQ * yQ no es igual a yQ * xQ. Los cuaterniones no son conmutativos, como dijo Christopher Perry.
Anthony Raimondo
Puse TODO mi código aquí . Siento que lo he intentado todo. Tal vez los ojos de alguien más en esto atrapen mi error.
Christopher Perry
No lo acepté, el algoritmo de intercambio de pilas te asignó automáticamente los puntos. : /
Christopher Perry
Lamento esta injusticia.
Anthony Raimondo
3

Pude obtener las rotaciones esperadas al girar una matriz de rotación acumulada.

setIdentityM(currentRotation, 0);
rotateM(currentRotation, 0, angleY, 0, 1, 0);
rotateM(currentRotation, 0, angleX, 1, 0, 0);

// Multiply the current rotation by the accumulated rotation,
// and then set the accumulated rotation to the result.
multiplyMM(temporaryMatrix, 0, currentRotation, 0, accumulatedRotation, 0);
arraycopy(temporaryMatrix, 0, accumulatedRotation, 0, 16);
Christopher Perry
fuente
1

Su imagen corresponde con su código de Matriz de rotación, al girar el eje x con su rotación y anterior obtiene el eje x local, cuando luego gira el objeto alrededor del cual obtendrá el resultado que muestra en su imagen. Para que la rotación sea lógica desde el punto de vista de los usuarios, desearía rotar el objeto utilizando el eje de coordenadas del mundo.

Si desea que su usuario pueda girar su objeto muchas veces, tendría sentido almacenar su rotación en un cuaternión en lugar de una matriz, con el tiempo múltiples giros (e inexactitudes de coma flotante) harán que la matriz se vea cada vez menos una matriz de rotación, lo mismo sucede en un cuaternión, por supuesto, pero la normalización del cuaternión lo devuelve a una buena rotación.

Simplemente use el quaternion de identidad como valor inicial, y cada vez que el usuario desliza la pantalla, gira su quaternion con el Quaternion.fromAxisAngle(yAxis, -angleX)código. Siempre usando (1,0,0,1) para rotaciones x y (0,1,0,1) para rotaciones y.

static final float[] xAxis = {1f, 0f, 0f, 1f};
static final float[] yAxis = {0f, 1f, 0f, 1f};

private void rotateObject(float angleX, float angleY) {
  Quaternion rotationY = Quaternion.fromAxisAngle(yAxis, -angleX);
  Quaternion rotationX = Quaternion.fromAxisAngle(xAxis, -angleY);

  myRotation = myRotation.rotate(rotationY).rotate(rotationX).normalize();
}
private float[] rotationMatrix() {
  return myRotation.toMatrix();
}

Como no mencionó el idioma ni ningún marco específico, los métodos en Quaternion pueden llamarse algo diferente, por supuesto, y normalizar no es necesario llamar con tanta frecuencia, pero dado que la rotación proviene de que un usuario desliza la pantalla, no lo hará. ralentiza mucho las cosas y de esa manera no hay posibilidad de que el Quaternion se escape de una unidad quaternion.

Daniel Carlsson
fuente
Estoy obteniendo exactamente el mismo comportamiento al hacer esto.
Christopher Perry
1
@ChristopherPerry Intente invertir el orden de rotación: myRotation = rotationX.rotate(rotationY).rotate(myRotation).normalize()no son conmutativos, por lo que el orden en que los hace cuenta. Agregue otro comentario con su marco / idioma si eso no funcionó y lo profundizaré un poco más.
Daniel Carlsson
Eso tampoco funciona, tengo el mismo comportamiento.
Christopher Perry
Puse TODO mi código aquí
Christopher Perry