Como girar um objeto em torno de eixos alinhados ao mundo?

15

Eu tenho um Vector3 que tem um ângulo de euler para cada eixo.

Normalmente, quando eu quero criar uma matriz de rotação, usarei funções como D3DXMatrixRotationX passando o ângulo respectivo do meu vetor de rotação acima e multiplico as matrizes (ZXY) para criar a matriz de rotação geral usada para formar a matriz de transformação de objeto completa.

No entanto, esse método produzirá um conjunto de rotações no espaço do objeto. Ou seja, passar um vetor de (90, 0, 90) para o meu método criará uma rotação no espaço mundial efetivamente de (90, 90, 0).

Existe uma maneira de sempre garantir que cada componente do meu vetor de rotação resulte em uma rotação em torno dos respectivos eixos alinhados ao espaço mundial?

EDITAR:

Esta é uma animação do que está acontecendo atualmente - eu quero uma maneira de girar em torno dos eixos azuis, não do vermelho.

Ângulos de Euler

EDIT 2:

Apenas para observar, não estou procurando uma solução que envolva os ângulos de Euler, mas simplesmente uma maneira de representar uma transformação de múltiplas rotações nos eixos mundiais.

Syntac_
fonte
O que há de errado em chamar as funções differnet três vezes e filtrar as partes dos vetores que você não deseja (configurando-as para 0 antes de chamar a função)? Caso contrário, não tenho certeza do que você está tentando alcançar.
TravisG
Filtrando o quê? Eu chamo as 3 funções separadas e as multiplico para criar a matriz de transformação. Isso arquiva uma rotação local.
Syntac_
Deseja ângulos de Euler ou rotação sobre eixos mundiais? Observe que, pela definição de ângulos de Euler (por exemplo, en.wikipedia.org/wiki/Euler_angles ), apenas o ângulo alfa é estritamente sobre um eixo mundial. Os outros dois ângulos são relativos a eixos inclinados que não coincidem necessariamente com os eixos mundiais.
DMGregory
11
Usando os ângulos de Euler, você multiplica todas as três matrizes de rotação antes de aplicar no vértice. Se M, N, O são as matrizes de rotação, a operação de resultado é MNO v. O que propus é aplicar cada matriz separadamente: v1 = O v0, então v2 = N v1 e, finalmente, v3 = M v2. Dessa forma, cada vi estará nas coordenadas do mundo e você só precisará usar uma matriz de rotação para o eixo atual nas coordenadas do mundo.
Dsilva.vinicius
3
@ dsilva.vinicius seus transformações separadas são exactamente o mesmo que a um conjugado, ou, dito de outra maneira: MNO v == H * (* N (O v))
GuyRT

Respostas:

1

Com base nos seus comentários, parece que você está armazenando a orientação do objeto como um conjunto de ângulos de Euler e diminuindo / diminuindo os ângulos quando o jogador gira o objeto. Ou seja, você tem algo parecido com este pseudocódigo:

// in player input handling:
if (axis == AXIS_X) object.angleX += dir;
else if (axis == AXIS_Y) object.angleY += dir;
else if (axis == AXIS_Z) object.angleZ += dir;

// in physics update and/or draw code:
matrix = eulerAnglesToMatrix(object.angleX, object.angleY, object.angleZ);

Como observa Charles Beattie , como as rotações não são comutadas, isso não funcionará conforme o esperado, a menos que o jogador gire o objeto na mesma ordem em que eulerAnglesToMatrix()as rotações são aplicadas.

Em particular, considere a seguinte sequência de rotações:

  1. gire o objeto em x graus ao redor do eixo X;
  2. girar objeto por y graus ao redor do eixo Y;
  3. gire o objeto em - x graus em torno do eixo X;
  4. gire o objeto em - y graus em torno do eixo Y.

Na representação ingênua do ângulo de Euler, conforme implementado no pseudocódigo acima, essas rotações serão canceladas e o objeto retornará à sua orientação original. No mundo real, isso não acontece - se você não acredita em mim, pegue um dado de seis lados ou um cubo de Rubik, deixe x = y = 90 ° e experimente você mesmo!

A solução, como você observa em sua própria resposta , é armazenar a orientação do objeto como uma matriz de rotação (ou um quaternion) e atualizar essa matriz com base nas informações do usuário. Ou seja, em vez do pseudocódigo acima, você faria algo assim:

// in player input handling:
if (axis == AXIS_X) object.orientation *= eulerAnglesToMatrix(dir, 0, 0);
else if (axis == AXIS_Y) object.orientation *= eulerAnglesToMatrix(0, dir, 0);
else if (axis == AXIS_Z) object.orientation *= eulerAnglesToMatrix(0, 0, dir);

// in physics update and/or draw code:
matrix = object.orientation;  // already in matrix form!

(Tecnicamente, como qualquer matriz de rotação ou quaternion pode ser representada como um conjunto de ângulos de Euler, é possível usá-los para armazenar a orientação do objeto. Mas a regra fisicamente correta para combinar duas rotações sequenciais, cada uma representada como ângulos de Euler, em uma única rotação é bastante complicado e equivale essencialmente a converter as rotações em matrizes / quaternions, multiplicando-as e convertendo o resultado novamente em ângulos de Euler.)

Ilmari Karonen
fonte
Sim, você está correto, esta foi a solução. Acho que isso é um pouco melhor que a resposta de concept3d, pois ele dá a impressão de que um quaternion é necessário, mas isso não é verdade. Desde que eu armazenei a rotação atual como uma matriz e não os três ângulos de Euler, tudo bem.
Syntac_
14

O problema das rotações é que, a maioria das pessoas pensa nisso em termos de ângulos de Euler, pois são fáceis de entender.

No entanto, a maioria das pessoas esquece o ponto em que Euler são três ângulos seqüenciais . Isso significa que a rotação ao redor do primeiro eixo fará com que a próxima rotação seja relativa à primeira rotação original; portanto, você não pode girar independentemente um vetor em torno de cada um dos três eixos usando ângulos de Euler.

Isso se traduz diretamente em matrizes quando você multiplica duas matrizes. Você pode pensar nessa multiplicação como transformar uma matriz no espaço da outra matriz.

Isso deve acontecer com quaisquer três rotações sequenciais, mesmo ao usar quaternions.

insira a descrição da imagem aqui

Quero enfatizar o fato de que os quaternions não são uma solução para o bloqueio flexível. Na verdade, o bloqueio flexível sempre acontecerá se você representou os ângulos de Euler usando quaterniões. O problema não é a representação, o problema é a três etapas seqüenciais.

A solução?

A solução para girar um vetor em torno de 3 eixos de forma independente é combinar um único eixo e um único ângulo, dessa forma você pode se livrar da etapa em que é necessário fazer uma multiplicação seqüencial. Isso se traduzirá efetivamente em:

Minha matriz de rotação representa o resultado da rotação em torno de X, Y e Z.

ao invés da interpretação de Euler de

Minha matriz de rotação representa a rotação em torno de X, depois Y e Z.

Para esclarecer isso, citarei o teorema da rotação de wikipedia Euler:

De acordo com o teorema da rotação de Euler, qualquer rotação ou sequência de rotações de um corpo rígido ou sistema de coordenadas sobre um ponto fixo é equivalente a uma única rotação por um determinado ângulo θ em torno de um eixo fixo (chamado eixo de Euler) que atravessa o ponto fixo. O eixo de Euler é normalmente representado por um vetor de unidade u →. Portanto, qualquer rotação em três dimensões pode ser representada como uma combinação de um vetor u → e um escalar θ. Os quaternions fornecem uma maneira simples de codificar essa representação eixo-ângulo em quatro números e de aplicar a rotação correspondente a um vetor de posição que representa um ponto relativo à origem em R3.

Observe que a multiplicação de 3 matrizes sempre representará 3 rotações seqüenciais.

Agora, para combinar rotações em torno de 3 eixos, é necessário obter um único eixo e ângulos únicos que representam a rotação em torno de X, Y, Z. Em outras palavras, você precisa usar uma representação de eixo / ângulo ou quaternário para se livrar das rotações seqüenciais.

Isso geralmente é feito, iniciando com uma orientação inicial (a orientação pode ser vista como um ângulo de eixo), geralmente representada como um quaternião ou um ângulo de eixo, e modificando essa oração para representar sua orientação de destino. Por exemplo, você começa com a pergunta de identidade e depois gira pela diferença para alcançar a orientação de destino. Dessa forma, você não perde nenhum grau de liberdade.

concept3d
fonte
Marcado como resposta, como parece perspicaz.
Syntac_
Estou tendo problemas para descobrir o que você está tentando dizer com esta resposta. É simplesmente "não armazene a orientação de um objeto como ângulos de Euler"? E se sim, por que não dizê-lo?
Ilmari Karonen
@IlmariKaronen Poderia ser mais claro, mas acho que o concept3d está promovendo a representação do eixo-ângulo; veja a seção 1.2.2 deste documento para a relação entre eixo-ângulo e quaterniões. A representação do ângulo do eixo é mais fácil de implementar pelas razões acima, não sofre bloqueio do cardan e (pelo menos para mim) é tão fácil de entender quanto os ângulos de Euler.
NauticalMile
@ concept3d, isso é muito interessante, e eu realmente gosto da sua resposta. Há uma coisa que falta para mim, porém, as pessoas interagem com o computador usando um teclado e um mouse, se pensarmos no mouse, estaremos falando sobre os deltas do mouse xey. Como representar esses deltas x, y com um único quaternion que podemos usar para gerar a matriz de rotação, por exemplo, para alterar a orientação de um objeto?
gmagno 12/03/19
@ gmagno a abordagem geralmente é projetar o movimento do mouse nos objetos ou cena e calcular deltas nesse espaço, você faz isso lançando um raio e calculando a interseção. Na busca por fundição de raios, projeto e projeto, sou grosseiro com os detalhes, pois não trabalhava em CG há anos. espero que ajude.
precisa
2

Mudar uma combinação de rotações do espaço do objeto para o espaço do mundo é trivial: basta reverter a ordem em que as rotações são aplicadas.

No seu caso, em vez de multiplicar matrizes Z × X × Y, você só precisa calcular Y × X × Z.

Uma justificativa para isso pode ser encontrada na Wikipedia: conversão entre rotações intrínsecas e extrínsecas .

sam hocevar
fonte
Se isso fosse verdade, a seguinte declaração da sua fonte não seria verdadeira, porque as rotações seriam diferentes: "Qualquer rotação extrínseca é equivalente a uma rotação intrínseca pelos mesmos ângulos, mas com ordem invertida de rotações elementares e vice-versa. . "
Syntac_
11
Não vejo contradição aqui. Tanto a minha resposta como essa afirmação são verdadeiras. E sim, executar rotações no espaço de objetos e no espaço do mundo produz diferentes rotações; esse é exatamente o ponto, não é?
21713 Sam 13:40
Essa afirmação diz que alterar a ordem sempre resultará na mesma rotação. Se um pedido está produzindo a rotação incorreta, o outro pedido também, o que significa que não é uma solução.
SyncDoc
11
Você está lendo errado. Alterar a ordem não resulta na mesma rotação. Alterar a ordem e alternar de rotações intrínsecas para rotações extrínsecas resulta na mesma rotação.
21713 Sam
11
Acho que não entendi sua pergunta. Seu GIF mostra uma rotação de cerca de 50 graus ao redor Z(espaço do objeto), depois 50 graus ao redor X(espaço do objeto) e 45 graus ao redorY (espaço do objeto). É exatamente o mesmo que uma rotação de 45 graus ao redor Y( espaço do mundo ), depois 50 graus ao redor X( espaço do mundo ) e depois 50 graus ao redor Z( espaço do mundo ).
Sam Hocevar
1

Fornecerei minha solução como resposta até que alguém possa explicar por que isso funciona.

A cada renderização eu estava reconstruindo meu quaternion usando os ângulos armazenados no meu vetor de rotação e depois aplicando o quaternion à minha transformação final.

No entanto, para mantê-lo em todos os eixos do mundo, tive que reter o quaternion em todos os quadros e girar apenas objetos usando uma diferença de ângulo, ou seja.

// To rotate an angle around X - note this is an additional rotation.
// If currently rotated 90, apply this function with angle of 90, total rotation = 180.
D3DXQUATERNION q;
D3DXQuaternionRotation(&q, D3DXVECTOR3(1.0f, 0.0f, 0.0f), fAngle);
m_qRotation *= q; 

//...

// When rendering rebuild world matrix
D3DXMATRIX mTemp;
D3DXMatrixIdentity(&m_mWorld);

// Scale
D3DXMatrixScaling(&mTemp, m_vScale.x, m_vScale.y, m_vScale.z);
m_mWorld *= mTemp;

// Rotate
D3DXMatrixRotationQuaternion(&mTemp, m_qRotation);
m_mWorld *= mTemp;

// Translation
D3DXMatrixTranslation(&mTemp, m_vPosition.x, m_vPosition.y, m_vPosition.z);
m_mWorld *= mTemp;

(Detalhado para facilitar a leitura)

Acho que dsilva.vinicius estava tentando chegar a esse ponto.

Syntac_
fonte
1

Você precisará armazenar a ordem das rotações.

Rotating around x 90 then rotate around z 90 !=
Rotating around z 90 then rotate around x 90.

Armazene sua matriz de rotação atual e pré-multiplique cada rotação à medida que elas surgirem.

Charles Beattie
fonte
0

Além da resposta ao @ concept3d, você pode usar 3 matrizes de rotação extrínseca para girar em torno do eixo nas coordenadas do mundo. Citando da Wikipedia :

Rotações extrínsecas são rotações elementares que ocorrem sobre os eixos do sistema de coordenadas fixo xyz. O sistema XYZ gira, enquanto xyz é fixo. Começando com XYZ sobreposto xyz, uma composição de três rotações extrínsecas pode ser usada para atingir qualquer orientação de destino para XYZ. Os ângulos de Euler ou Tait Bryan (α, β, γ) são as amplitudes dessas rotações elementares. Por exemplo, a orientação ao alvo pode ser alcançada da seguinte maneira:

O sistema XYZ gira em torno do eixo z em α. O eixo X está agora no ângulo α em relação ao eixo x.

O sistema XYZ gira novamente em torno do eixo x por β. O eixo Z está agora no ângulo β em relação ao eixo z.

O sistema XYZ gira uma terceira vez em torno do eixo z em γ.

Matrizes de rotação podem ser usadas para representar uma sequência de rotações extrínsecas. Por exemplo,

R = Z (γ) Y (β) X (α)

representa uma composição de rotações extrínsecas sobre os eixos xyz, se usado para pré-multiplicar vetores de coluna, enquanto

R = X (α) Y (β) Z (γ)

representa exatamente a mesma composição quando usada para pós-multiplicar vetores de linha.

Portanto, o que você precisa é inverter a ordem das rotações em relação ao que você faria usando rotações intrínsecas (ou no espaço local). O @Syntac pediu uma rotação zxy, então devemos fazer uma rotação extrínseca yxz para obter o mesmo resultado. O código está abaixo:

A matriz valoriza a explicação aqui .

// Init things.
D3DXMATRIX *rotationMatrixX = new D3DXMATRIX();
D3DXMATRIX *rotationMatrixY = new D3DXMATRIX();
D3DXMATRIX *rotationMatrixZ = new D3DXMATRIX();
D3DXMATRIX *resultRotationMatrix0 = new D3DXMATRIX();
D3DXMATRIX *resultRotationMatrix1 = new D3DXMATRIX();

D3DXMatrixRotationX(rotationMatrixX, angleX);
D3DXMatrixRotationY(rotationMatrixY, angleY);
D3DXMatrixRotationZ(rotationMatrixZ, angleZ);

// yx extrinsic rotation matrix
D3DXMatrixMultiply(resultRotationMatrix0, rotationMatrixY, rotationMatrixX);
// yxz extrinsic rotation matrix
D3DXMatrixMultiply(resultRotationMatrix1, resultRotationMatrix0, rotationMatrixZ);

D3DXVECTOR4* originalVector = // Original value to be transformed;
D3DXVECTOR4* transformedVector = new D3DXVECTOR4();

// Applying matrix to the vector.
D3DXVec4Transform(transformedVector, originalVector, resultRotationMatrix1);

// Don't forget to clean memory!

Esse código é didático, não ideal, pois você pode reutilizar várias matrizes D3DXMATRIX.

dsilva.vinicius
fonte
11
desculpe cara isso não está correto. a multiplicação de matrizes / vetores é associativa. isso é exatamente o mesmo que multiplicação combinada de matrizes.
precisa
Você está certo. Eu misturei os conceitos de rotações extrínsecas e intrínsecas.
dsilva.vinicius
Eu vou consertar essa resposta.
dsilva.vinicius
A resposta está corrigida agora.
dsilva.vinicius