Girar objeto em torno do eixo fixo

12

Estou tentando permitir que o usuário do meu aplicativo gire um objeto 3D desenhado no centro da tela, arrastando o dedo na tela. Um movimento horizontal na tela significa rotação em torno de um eixo Y fixo, e um movimento vertical significa rotação em torno do eixo X. O problema que estou tendo é que, se eu permitir a rotação em torno de um eixo, o objeto gira bem, mas assim que introduzo uma segunda rotação, o objeto não gira conforme o esperado.

Aqui está uma imagem do que está acontecendo:

insira a descrição da imagem aqui

O eixo azul representa meu eixo fixo. Imagine a tela com este eixo azul fixo. É para isso que eu quero que o objeto gire em relação a. O que está acontecendo está em vermelho.

Aqui está o que eu sei:

  1. A primeira rotação em torno de Y (0, 1, 0) faz com que o modelo se mova do espaço azul (chame este espaço A) para outro espaço (chame este espaço B)
  2. Tentar girar novamente usando o vetor (1, 0, 0) gira em torno do eixo x no espaço B NÃO no espaço A, o que não é o que pretendo fazer.

Aqui está o que eu tentei, considerando o que eu (acho) eu sei (deixando de fora o W coord por brevidade):

  1. Primeiro gire em torno de Y (0, 1, 0) usando um Quaternion.
  2. Converta a rotação Y Quaternion em uma matriz.
  3. Multiplique a matriz de rotação Y pelo meu eixo fixo x Vetor (1, 0, 0) para obter o eixo X em relação ao novo espaço.
  4. Gire em torno deste novo vetor X usando um Quaternion.

Aqui está o 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;
  }

Isso não está funcionando como eu espero. A rotação parece funcionar, mas em algum momento o movimento horizontal não gira em torno do eixo Y, ele parece girar em torno do eixo Z.

Não tenho certeza se meu entendimento está errado ou se alguma outra coisa está causando um problema. Eu tenho algumas outras transformações que estou fazendo no objeto além da rotação. Movo o objeto para o centro antes de aplicar a rotação. Giro-o usando a matriz retornada da minha função acima e depois o traduzo -2 na direção Z para que eu possa ver o objeto. Eu não acho que isso está atrapalhando minhas rotações, mas aqui está o código para isso de qualquer maneira:

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;
  }

Estou preso nisso há alguns dias. A ajuda é muito apreciada.

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

ATUALIZAR:

Fazer isso funcionar no Unity é simples. Aqui está um código que gira um cubo centrado na origem:

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);
    }
}

A parte que faz as rotações se comportarem como eu espero é o Space.Worldparâmetro para a Rotatefunção na transformação.

Se eu pudesse usar o Unity, infelizmente, tenho que codificar esse comportamento.

Christopher Perry
fonte
1
A minha resposta aqui gamedev.stackexchange.com/questions/67199/... pode ajudá-lo ..
concept3d
Eu entendo o conceito por trás da sua resposta, mas como implementar isso me escapa.
22415 Christopher Christopher
Se você verificou as outras respostas, o syntac answer implementa a ideia que eu expliquei.
precisa
Não, não está fazendo várias rotações em diferentes eixos. Você sugere fazer uma única rotação.
21415 Christopher Christopher

Respostas:

3

O problema que você tem é chamado de bloqueio flexível . Eu acho que o que você quer fazer é chamado rotação de arcball . A matemática para o arcball pode ser um pouco complicada.

Uma maneira mais simples de fazer isso é encontrar um vetor 2D perpendicular ao furto 2D na tela.

Pegue o vetor e projete-o na câmera perto do avião para obter um vetor 3d no espaço do mundo. Espaço da tela para o espaço do mundo .

Em seguida, crie um quaternion com esse vetor e multiplique-o para o objeto game. Provavelmente com alguma transição slurp ou lerp.

Editar:

Exemplo de unidade: No exemplo de unidade, o estado interno da rotação dos objetos de jogo é um quaternion e não uma matriz. O método transform.rotation gera um quaternion com base no vetor e no ângulo fornecido e multiplica esse quaternion com o quaternion de rotação de objetos do jogo. Ele gera apenas a matriz de rotação para renderização ou física posteriormente. Os quaternions são aditivos e evitam o bloqueio flexível.

Seu código deve ser algo como isto:

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. 
  }

Tutorial Opengl de rotação do ArcBall

Anthony Raimondo
fonte
Não estou conseguindo o bloqueio do cardan, a primeira rotação move o eixo, de modo que uma segunda rotação é baseada no eixo movido. Por favor, dê uma segunda olhada na imagem que forneci.
21415 Christopher Christopher
Espero que você tenha entendido. Em resumo. Quaternions podem ser multiplicados juntos para aplicar rotação. Você só deve gerar a matriz no final de todos os cálculos de rotação. XQ * yQ também não é igual a yQ * xQ. Quaternions não são comutativos, como Christopher Perry disse.
Anthony Raimondo
Coloquei TODO o meu código aqui . Sinto como se tivesse tentado de tudo. Talvez os olhos de outra pessoa percebam o meu erro.
22415 Christopher Christopher
Eu não aceitei, o algoritmo de trocas de pilha atribuiu automaticamente os pontos a você. : /
Christopher Perry
Sinto muito por essa injustiça.
Anthony Raimondo 21/03
3

Consegui obter as rotações esperadas girando uma matriz de rotação 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
fonte
1

Sua imagem corresponde ao seu código rotationMatrix. Ao girar o eixo x com a rotação y anterior, você obtém o eixo x local, quando gira o objeto ao redor para obter o resultado exibido na imagem. Para que a rotação seja lógica do ponto de vista do usuário, você deseja girar o objeto usando o eixo de coordenadas do mundo.

Se você deseja que seu usuário possa girar seu objeto várias vezes, faria sentido armazenar sua rotação em um quaternion em vez de em uma matriz, com o tempo várias rotações (e imprecisões de ponto flutuante) farão com que a matriz pareça cada vez menos uma matriz de rotação, o mesmo acontece em um quaternion, é claro, mas apenas normalizar o quaternion traz de volta a uma boa rotação.

Simplesmente use o quaternion de identidade como valor inicial e, toda vez que o usuário passar o dedo na tela, você girará seu quaternion com o Quaternion.fromAxisAngle(yAxis, -angleX)código. Sempre usando (1,0,0,1) para rotações x e (0,1,0,1) para rotações 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 você não mencionou o idioma ou qualquer estrutura específica, os métodos no Quaternion podem ser chamados de algo diferente, é claro, e normalizar não é necessário chamar isso com frequência, mas como a rotação vem de um usuário passando a tela, eles não desacelera muito as coisas e, dessa forma, não há chance do Quaternion escapar de um quaternion da unidade.

Daniel Carlsson
fonte
Estou tendo exatamente o mesmo comportamento ao fazer isso.
22415 Christopher Christopher
1
@ChristopherPerry Tente reverter a ordem de rotação: myRotation = rotationX.rotate(rotationY).rotate(myRotation).normalize()eles não são comutativos, portanto a ordem que você faz conta. Adicione outro comentário com sua estrutura / idioma, se isso não funcionou, e eu vou cavar um pouco mais.
Daniel Carlsson
Isso também não funciona, tenho o mesmo comportamento.
22415 Christopher Christopher
Eu coloquei todo o meu código aqui
Christopher Perry