Multiplicação
Pelo menos em termos da implementação de Quaternions pela Unity, a ordem de multiplicação descrita na pergunta não está correta. Isso é importante porque a rotação 3D não é comutativa .
Então, se eu quiser rotacionar um objeto, rotationChange
começando pelo seu, currentOrientation
eu o escreveria assim:
Quaternion newOrientation = rotationChange * currentOrientation;
(ie. As transformações se acumulam à esquerda - o mesmo que a convenção matricial do Unity. A rotação mais à direita é aplicada primeiro / no final "mais local")
E se eu quisesse transformar uma direção ou deslocar um vetor por uma rotação, escreveria assim:
Vector3 rotatedOffsetVector = rotationChange * currentOffsetVector;
(O Unity gerará um erro de compilação se você fizer o oposto)
Misturando
Na maioria dos casos, você pode se safar das rotações de leitura. Isso ocorre porque o ângulo usado "sob o capô" em um quaternion é metade do ângulo de rotação, tornando-o substancialmente mais próximo da aproximação linear de Lerp do que algo como uma Matrix (que geralmente não funciona bem com Lerp!). Confira cerca de 40 minutos deste vídeo para obter mais explicações .
O único caso em que você realmente precisa do Slerp é quando você precisa de velocidade consistente ao longo do tempo, como a interpolação entre quadros-chave em uma linha do tempo da animação. Nos casos em que você apenas se importa que uma saída seja intermediária entre duas entradas (como mesclar camadas de uma animação), geralmente o Lerp serve muito bem.
O quê mais?
O produto escalar de dois quaternions de unidades fornece o cosseno do ângulo entre eles, para que você possa usar o produto escalar como uma medida de similaridade, se precisar comparar rotações. Porém, isso é um pouco obscuro; portanto, para um código mais legível, eu usaria frequentemente o Quaternion.Angle (a, b) , que expressa mais claramente que estamos comparando ângulos, em unidades familiares (graus).
Esses tipos de métodos de conveniência que o Unity fornece para o Quaternions são super úteis. Em quase todos os projetos, uso este aqui pelo menos algumas vezes :
Quaternion.LookRotation(Vector3 forward, Vector3 up)
Isso cria um quaternion que:
- gira o eixo z + local para apontar exatamente ao longo do
forward
argumento do vetor
- gira o eixo y + local para apontar o mais próximo possível do
up
argumento do vetor, se fornecido, ou para (0, 1, 0)
se omitido
A razão pela qual o "up" fica "o mais próximo possível" é que o sistema está sobredeterminado. Enfrentar z + para forward
usa até dois graus de liberdade (ou seja, guinada e inclinação), então temos apenas um grau de liberdade restante (rolagem).
Acho que muitas vezes quero algo com as propriedades de exatidão opostas: quero que o local y + aponte exatamente ao longo up
, e o local z + fique o mais próximo possível forward
da liberdade restante.
Isso ocorre, por exemplo, ao tentar formar um quadro de coordenadas relativo à câmera para entrada de movimento: eu quero que minha direção local acima permaneça perpendicular ao chão ou à superfície inclinada normal, para que minha entrada não tente encapsular o caractere no terreno ou levite-os para fora dele.
Você também pode conseguir isso se quiser que o compartimento da torre de um tanque enfrente um alvo, sem sair do corpo do tanque ao mirar para cima / para baixo.
Podemos criar nossa própria função de conveniência para fazer isso, usando LookRotation
para o trabalho pesado:
Quaternion TurretLookRotation(Vector3 approximateForward, Vector3 exactUp)
{
Quaternion rotateZToUp = Quaternion.LookRotation(exactUp, -approximateForward);
Quaternion rotateYToZ = Quaternion.Euler(90f, 0f, 0f);
return rotateZToUp * rotateYToZ;
}
Aqui, giramos primeiro o local y + para z + e o local z + para y-.
Em seguida, rotacionamos o novo z + em nossa direção para cima (para que o resultado líquido seja local y + aponta diretamente ao longo exactUp
) e o novo y + o mais próximo possível da direção para frente negada (para que o resultado líquido seja local z + pontos o mais próximo possível ao longo approximateForward
)
Outro método prático de conveniência é o Quaternion.RotateTowards
que costumo usar da seguinte maneira:
Quaternion newRotation = Quaternion.RotateTowards(
oldRotation,
targetRotation,
maxDegreesPerSecond * Time.deltaTime
);
Isso nos permite chegar targetRotation
a uma velocidade consistente e controlável, independentemente da taxa de quadros - importante para rotações que afetam o resultado / imparcialidade da mecânica de jogo (como girar o movimento de um personagem ou rastrear a torre do jogador). Ingenuamente Ler / Slerping nessa situação pode facilmente levar a casos em que o movimento fica mais rápido em altas taxas de quadros, afetando o equilíbrio do jogo. (Isso não quer dizer que esses métodos estejam errados - há maneiras de usá-los corretamente sem alterar a imparcialidade, apenas requer cuidado. RotateTowards
Fornece um atalho conveniente que cuida disso para nós)
n
orientações diferentes (atitudes, poses, etc.). Então você pode calculá-las usando pesos, generalizando efetivamente slerp / lerp. Você também pode converter um quaternion em um rotor, o que equivale a aplicar uma velocidade angular por um certo período de tempo a um corpo rígido. Portanto, você também pode descrever a integração da velocidade angular com os quaternions. Você também pode estimar quão diferentes são as duas orientações (calcule o comprimento do arco medido pelos dois quaterniões na hiperesfera).