Calcular um quaternion para que um osso aponte em uma direção especificada

7

Em uma tentativa de resolver essa questão , decidi descobrir as direções absolutas (espaço do mundo) de cada articulação na posição original (como vetores unitários normalizados) e depois girar as articulações do alvo para corresponder a essa direção. O problema é: como faço para calcular o quaternion de rotação de modo que o osso (o vetor da articulação para o filho) esteja na direção especificada no espaço do mundo.

ATUALIZAÇÃO: Aqui está o meu pensamento atual. Para cada osso, a matriz de transformação será:

(1) absolute transform = local rotation * relative transform to parent in bind pose * parent absolute trasform

Então, digamos que temos 3 articulações: Ombro -> Cotovelo -> Pulso. Já temos uma transformação absoluta para Shoulder, e estamos tentando calcular a rotação para Elbow, de modo que Elbow -> Wrist vá na mesma direção que na animação de origem. A partir de (1), as transformações absolutas serão:

(2) ELBOW absolute = ELBOW local * SHOULDER to ELBOW relative * SHOULDER absolute

(3) WRIST absolute = WRIST local * ELBOW to WRIST relative * ELBOW absolute

Por substituição, obtemos:

(4) WRIST absolute = WRIST local * ELBOW to WRIST relative * ELBOW local *
                     SHOULDER to ELBOW relative * SHOULDER absolute

Nossos constantes são: ELBOW to WRIST relative, SHOULDER to ELBOW relative, e SHOULDER absolute. WRIST localconsistirá apenas em rotação, sem translação / escala. O problema é encontrar um valor para ELBOW localtal que ...

(5) normalize(position(ELBOW absolute) - position(WRIST absolute)) = desired direction

(6) ELBOW local consists only of rotations (no translation/scale)

Agora, como sabemos o comprimento do osso e a direção desejada, é possível estimar a parte da posiçãoWRIST absolute , mas a rotação será desconhecida (e esse problema parece impossível de fazer recursivamente, AFAICT).

Meu pensamento inicial era que, como WRIST localé somente rotação, e eu o solucionarei na próxima iteração do loop, eu poderia ignorá-lo completamente no cálculo de ELBOW local. A resposta de Steven Stadnicki abaixo sugeriu que pode não ser tão fácil, uma vez que a orientação do osso é importante para a esfola. No entanto, se eu conseguir fazer isso funcionar em um boneco, posso atravessar a ponte mais tarde.

Robert Fraser
fonte
2
Hum, então se eu entendi direito: targetDir = normalize (childPos - selfPos) . Se você tem um currDir , então por que não computar o quat de eixo ângulo .. semelhante à forma como eles propuseram-lo aqui
teodron
@teodron - Se fosse assim tão fácil, mas existem outras 3 matrizes, será multiplicado por. Vou atualizar a pergunta.
Robert Fraser
Uma pergunta, porque tenho a sensação de que sua pergunta atualizada contém uma resposta óbvia que você não deseja: Por que você não escreve: M = inv(childRelative) * childAbsolute * inv(parentAbsolute) * inv(selfRelative)como faria com essas equações matriciais? Quais são as matrizes selfRelative e childRelative? A maneira usual de fazer o encadeamento de transformação é: childTransform = parentToChild * parentTransform . As outras matrizes não são tão óbvias quanto ao seu propósito. O problema é interessante e pode ser incrível descobrir o mistério :).
Teodron # 1/12
@teodron - Desculpe, eu deveria ter sido mais claro. selfRelative é a matriz de pose de ligação do pai para esse osso (o parentToChild do qual você fala). childRelative é a matriz de ligação do osso atual para a criança em questão. Estes são do esqueleto e são fixos. Quanto a "por que não resolvê-lo como uma equação de matriz normal", duas razões: primeiro, M precisa ser apenas de rotação e eu também não tenho a matriz childAbsolute completa, apenas a parte da posição.
Robert Fraser
11
Problema complicado, parece ser muito sub-determinado , portanto, mínimos quadrados / truques pseudo-inversos podem ajudar, mas é uma bagunça e você deve fazê-lo numericamente. Antes de tudo, apenas sua matriz de rotação adiciona 4 incógnitas e não é linear, enquanto que você elbow_localintroduz outras 8 .. ou seja, 4 equações com 8 incógnitas. Considere o uso de cadeias cinemáticas. Com menos graus de liberdade. Você já ouviu falar da convenção Denavit-Hartenberg ? Eu usei isso com resultados decentes bonitas
teodron

Respostas:

3

A partir da descrição do seu problema, parece que o cerne da questão pode ser que sua representação esteja perdendo dados no momento. Um osso não é simplesmente o vetor de uma articulação para a próxima - é uma matriz de transformação completa que representa a orientação da estrutura local, seja com relação ao espaço do mundo ou com relação à estrutura local do próximo osso 'na cadeia' . Por exemplo, imagine um personagem com uma cicatriz no antebraço; ter o vetor do cotovelo do personagem até o pulso indica onde a mão será posicionada, mas não contém informações suficientes para que você saiba onde estaria a cicatriz - por exemplo, se está apontando para cima ou para baixo. Essas informações de orientação devem fazer parte do seu esqueleto de origem.

Depois de ter as coisas representadas em termos de quadros de orientação local completos e não simplesmente vetores direcionais, o restante das informações deve cair facilmente; exatamente como você sugeriu em seu post, você usaria inversos de matriz para 'descolar' as camadas de suas transformações e chegar à matriz suculenta M no meio. Por exemplo, se você tiver childAbs = childRel * M * selfRel * parentAbs, multiplique por childRel -1 à esquerda de ambos os lados e parentAbs -1 * selfRel -1 à direita de ambos os lados para obter M = childRel - 1 * childAbs * parentAbs -1 * selfRel -1. Observe que a inversão de um produto de matrizes altera a ordem em que seus inversos são multiplicados, de modo que a ordem é importante aqui - (A * B) -1 não é (em geral) igual a A -1 * B -1 , mas sim a B -1 * A -1 .

Depois de obter sua matriz M, deve haver muita informação na Web sobre como convertê-la em um quaternion; se você estiver procurando por ajuda com essa etapa específica, avise-me e eu posso detalhar isso com alguns links, mas parece que existem vários outros problemas que você precisará resolver agora mesmo antes de precisar considere isso.

Edição: Depois de alguns dias para pensar sobre isso, escrevi essa descrição aproximada do processo de passar do espaço mundial para as informações locais; espero que isso ajude a esclarecer um pouco mais.

Vou trabalhar em termos de um esquema dissociado em que cada osso consiste em uma matriz de rotação e um deslocamento; a matriz de rotação é aplicada na raiz do osso e o deslocamento é dado nas coordenadas locais do osso. Isso faz duas coisas importantes:

  1. Ele permite que vários ossos sejam anexados ao mesmo encaixe, facilitando um pouco a árvore esquelética - por exemplo, as clavículas podem ser facilmente presas a um ponto central entre as omoplatas ou os ossos dos dedos a um ponto central do pulso.
  2. Isso significa que a maioria das animações de um osso pode ser feita apenas alterando a rotação associada a esse osso; por exemplo, um bíceps pode ser movido alterando sua orientação, fazendo com que a localização do cotovelo seja deslocada. De fato, os ossos geralmente terão um deslocamento de (0, 0, L), onde L é o comprimento do osso - a extremidade mais distante do osso está diretamente ao longo do eixo Z local a partir do ponto de fixação do osso.

Essa não é necessariamente a abordagem que eu recomendaria para a implementação, mas é a mais direta para falar sobre como fazê-lo; Embora os resultados precisos não sejam diretamente aplicáveis ​​à sua situação, o IMHO é realmente o melhor: oferece melhores chances de entender como a derivação funciona e como derivar as fórmulas precisas que serão aplicadas à sua própria versão do problema.

Algumas definições rápidas das quantidades com as quais estamos trabalhando: Um sobrescrito de 'w' designa um valor do espaço no mundo, enquanto 'l' indica um valor local. Usaremos P para posição e R para rotação; por exemplo, a P w osso seria a posição alvo espaço de mundo de um osso, (em oposição à sua posição de base, que, naturalmente, é a posição do alvo da sua mãe), enquanto que R l osso seria a orientação local do osso - que é como é girado em relação ao quadro de seu pai.

Agora, considerando isso, podemos derivar a posição de destino e a orientação de qualquer articulação - como alguns dos comentários sugerem, o relacionamento é recursivo. Em particular, temos

R w filho = R w pai * R l filho

e

P w filho = P w pai + R w filho * P l filho

(Eles dizem, respectivamente, 'A orientação mundial da criança é sua orientação local composta pela orientação mundial de seus pais' e 'A posição mundial da criança é a posição mundial de seus pais compensada por seu deslocamento local (conforme traduzido no apropriado coordenadas do worldspace) '.)

Diante disso, podemos agora resolver estas equações para encontrar os parâmetros do osso - ou seja, R l e P l - em termos dos dados WorldSpace que nos são dadas. Isso também dá outra razão pela qual precisamos não apenas da posição, mas também da orientação do espaço mundial de todas as juntas; é um componente essencial para a solução.

Encontrar a orientação local é simples; podemos apenas multiplicar ambos os lados da nossa equação de orientação pelo inverso da orientação do espaço no mundo dos pais, obtendo

R l filho = R w pai -1 * R w filho

O deslocamento local também é relativamente fácil de entender, já que sabemos o deslocamento no espaço mundial e a matriz de transformação para ir do espaço local para o espaço mundial (o que significa que sabemos que a matriz deve seguir o outro caminho):

P l filho = R w filho -1 * (P w filho - P w pai )

Observe uma coisa também: nenhuma dessas equações depende da posição ou orientação local do osso parental, apenas dos valores do espaço mundial para o pai e o filho - portanto, enquanto eles se sentem recursivos (e o cálculo para encontrar os valores do espaço mundial para os ossos) precisa ir da raiz do esqueleto até as folhas devido às dependências dos resultados) eles podem realmente ser feitos em qualquer ordem.

Espero que isso lhe dê uma idéia melhor do que está acontecendo - é fácil ficar atolado em todas as transformações que estão por aí. Avise-me se isso ajudar!

Steven Stadnicki
fonte
O esqueleto de origem consiste em rotações. A coisa do vetor de direção foi apenas uma tentativa de redirecioná-la. Posso facilmente obter a rotação da fonte (em relação ao seu próprio quadro), mas não sei como isso vai me ajudar, pois a fonte tem uma pose de ligação diferente. Quanto à segunda parte do seu post, o problema é descobrir qual será childAbsolute - eu sei a posição, mas não a rotação local.
Robert Fraser
... (não me permite editar) Com o objetivo de calcular a rotação do osso atual, não me importo necessariamente com o que será a rotação local do osso infantil, apenas se ele estiver na posição correta.
Robert Fraser
@Fraser Eu me preocupo com o fato de estarmos conversando um pouco, mas simplesmente não entendo como você não pode se importar com o que será a rotação local do osso da criança. Esses dados são essenciais para qualquer aplicação de skin no esqueleto, com pouca animação (ou 2D, mas estamos falando de um problema completamente diferente). O que você está fazendo com sua pose de destino, para que as orientações não façam parte dela?
Steven Stadnicki
@Fraser Se você está dizendo 'Eu só tenho posições nas articulações da pose de destino, nenhuma orientação' e você também precisa determinar orientações razoáveis ​​para os ossos da pose de destino, então esse é outro problema (e muito, muito mais difícil) , mas mesmo lá acho que o problema seria muito melhor resolvido tentando descobrir como você pode obter a orientação dos dados de pose de destino, em vez de tentar criar uma orientação 'razoável' por conta própria.
Steven Stadnicki
Reescrevi a pergunta para deixar mais claro o que estou tentando realizar. Aqui está um vídeo do meu progresso a partir de 2 semanas atrás: youtube.com/watch?v=H6Qq37TM4Pg ... Obrigado por toda a sua ajuda, cara.
Robert Fraser
2

Se eu entendi direito, você está armazenando todos os vértices dos ossos do esqueleto em uma matriz. Acho que seu problema está mais relacionado à maneira como você está armazenando os Ossos do que aos próprios quaterniões.

Vou tentar explicar o que quero dizer:

Como você basicamente tem o resultado da rotação (um osso rotacionado para usar como modelo) e deseja conhecer o quaternion que geraria esse resultado para outros ossos, por que você não pode armazenar os ossos como vetores, em vez de gerar uma matriz?

Você pode armazenar um osso como um vetor composto de 2 pontos (Cabeça, Cauda). Como você está girando, a cabeça será o ponto de apoio e a cauda girará em torno de um eixo arbitrário. Esse é o trabalho de um quaternário.

Agora, como você tem o Osso A na posição 'descansada' e deseja corresponder à direção do Osso B, precisa saber a quantidade de rotação Alfa e o Eixo. Direita?

Ao obter o ângulo entre os 2 vetores ósseos A e B, você encontrará Alpha, e o resultado normalizado do produto cruzado do AxB obterá o eixo.

A única coisa que está ficando mais complicada para você é que você precisa extrapolar o vetor ósseo da matriz que está usando anteriormente, então talvez você possa armazenar os ossos como vetores (par de pontos) em vez de criar uma matriz.

Além disso, como você está basicamente girando um osso em relação à forma como seu pai foi rotacionado (foi o que obtive do seu código), provavelmente você poderia simplesmente ignorar tudo isso.

Como você deseja corresponder à posição de outro osso, você já sabe que essa posição é legítima; portanto, todo o material de rotação relativa não deve ser necessário.

Darkwings
fonte
O problema de armazená-los como vetores é a interpolação entre quadros ou a mistura de várias animações.
Robert Fraser
Não posso comentar as respostas de outros usuários (ainda), então ... enfim, se você precisar de mais dados do que apenas 2 pontos, armazene-os da maneira que mais lhe convier. As duas respostas que você obteve até agora apontam para o mesmo problema: você está perdendo dados ou está usando um formato que lhe causa problemas. Se um osso contiver rotação e translação, você pode simplesmente armazenar as informações respectivas sem lidar com uma matriz completa e assim evitar toda a conversão. Usando apenas quaterniões e 2 a 3 pontos por osso, a posição da criança seria dependente da posição dos pais, para que o sistema se cuidasse.
Darkwings
O objetivo disso é poder redirecionar as animações de um esqueleto para outro, para que os resultados possam ser processados ​​como uma animação criada pelo artista, bem como misturados a animações processuais e criadas pelo artista. Os dados que estou armazenando são um quaternion de rotação para cada osso em relação ao seu pai para cada quadro. Isso já está funcionando para animações importadas. Não quero mudar a maneira como armazeno as animações de resultados - esta etapa é totalmente parte do pipeline; Quero que o resultado final funcione da mesma maneira que (e interopera com) outras animações.
Robert Fraser
Eu vi o vídeo. Ainda estou convencido de que, para armazenar facilmente uma rotação em relação a um eixo conhecido (em relação ao osso parental), você ainda está complicando sua vida. Entendo que o motivo pelo qual você deseja uma matriz somente de rotação é impedir traduções indesejadas, resultando em uma posição pai-filho inválida. Algo que não poderia acontecer de qualquer maneira, se você estivesse usando o osso parental como o eixo ligado usando apenas quaterniões. Caso contrário, você precisaria converter infinitamente de matriz para quaternion e depois matriz novamente. Em resumo, compartilho o ponto de vista de Steven Stadnicki, quando ele diz que não é apenas um problema de matemática.
Darkwings