Rotação arbitrária sobre uma esfera

12

Estou codificando um mecânico que permite que um usuário se mova pela superfície de uma esfera. A posição na esfera está atualmente armazenada como thetae phi, onde thetaé o ângulo entre o eixo z e a projeção xz da posição atual (ou seja, rotação em torno do eixo y) e phié o ângulo do eixo y para a posição. Eu expliquei isso mal, mas é essencialmente theta = yaw,phi = pitch

Vector3 position = new Vector3(0,0,1);
position.X = (float)Math.Sin(phi) * (float)Math.Sin(theta);
position.Y = (float)Math.Sin(phi) * (float)Math.Cos(theta);
position.Z = (float)Math.Cos(phi);
position *= r;

Acredito que isso seja preciso, mas posso estar errado. Eu preciso ser capaz de me mover em uma direção pseudo-bidimensional arbitrária em torno da superfície de uma esfera na origem do espaço do mundo com raio r. Por exemplo, segurar Wdeve se mover pela esfera em uma direção para cima em relação à orientação do jogador.

Acredito que deveria usar um Quaternion para representar a posição / orientação na esfera, mas não consigo pensar na maneira correta de fazê-lo. Geometria esférica não é meu ponto forte.

Essencialmente, preciso preencher o seguinte bloco:

public void Move(Direction dir)
{   
    switch (dir)
    {
        case Direction.Left:
            // update quaternion to rotate left
            break;
        case Direction.Right:   
            // update quaternion to rotate right
            break;
        case Direction.Up:
            // update quaternion to rotate upward
            break;
        case Direction.Down:
            // update quaternion to rotate downward
            break;
    }
}
azz
fonte
O que deve acontecer se o jogador alcançar os polos? Notei que você escreveu "direção para cima", você quer dizer literalmente "para cima" (ou seja, longe da superfície da esfera), "sempre em frente" ou "em direção ao polo norte" (os dois últimos são iguais se o jogador não é possível alterar a orientação e "na frente deles" ou "na tela" é sempre o norte)?
27512 Martin Sojka #:
Talvez tenha sido mal formulado. O jogador não deve deixar a superfície da esfera e não deve estar ciente do eixo cardinal. Então, quando você move "para cima", move-se pela superfície da esfera em uma direção para cima em relação à orientação do jogador. Por exemplo, se você estiver em (r, 0,0) e pressionar para cima, irá para o pólo z +, mas, se continuar, deve contornar e continuar.
21312 azz
Ainda resta uma pergunta: o jogador pode mudar de orientação (gire "esquerda" e "direita")?
Martin Sojka
Talvez um exemplo melhor do que eu estou indo para: Jogador na (1,1,1)exploração deixou giraria em torno da esfera, passando (~1.2,0,~-1.2), em seguida (-1,-1,-1), em seguida, (~-1.2,0,~1.2)e de volta para (1,1,1).
21312 azz
1
Se você deseja acompanhar sempre thetae à phimedida que sua posição é atualizada, você está tornando seu problema desnecessariamente complexo. Muito mais fácil calcular simplesmente os 2 eixos de rotação de cada quadro (um dos quais (guinada) nunca muda) e Vector3.Transormao redor da esfera. Isso simplificaria seu problema, mas fará com que você se desconecte com phi& theta.
21712 Steve

Respostas:

5

Na verdade, acontece que você não pode ter 'os dois lados': se sua intenção é não ter nenhum senso de 'orientação absoluta' na esfera (isto é, se os jogadores nem sempre estão, por exemplo, olhando para os polos) ), você precisará ter uma noção da orientação do jogador. Isso ocorre porque, ao contrário do que a intuição pode sugerir, o movimento na esfera não é exatamente como o movimento de um avião, nem mesmo localmente (bastante); a curvatura intrínseca da esfera significa que os jogadores podem realizar ações que se revezarão!

Para o exemplo mais extremo do que estou falando, imagine que o jogador comece em um ponto do equador (por conveniência, imaginemos um mostrador de relógio mapeado no equador de cima e coloque o jogador às 6 horas ), voltado para cima, isto é, em direção ao Polo Norte. Suponha que o jogador caminhe até o Polo Norte; então eles estarão voltados diretamente para o ponto das 12 horas. Agora, deixe o jogador se mover diretamente para a direita, do Polo Norte de volta ao Equador; eles acabam no ponto das 3 horas - mas porque o rosto deles não muda quando se movem para a direita(a ideia é que o rosto deles não mude, não importa como eles se movam), eles ainda estarão enfrentando o ponto das 12 horas - agora estão enfrentando ao longo do equador! Agora, deixe-os recuar 'para trás' até o ponto inicial (6 horas); então eles ainda estarão de frente para o equador, de modo que estarão voltados para o ponto das 3 horas - apenas mover-se ao longo da esfera sem nunca mudar sua orientação 'pessoal' os fez girar de frente para o pólo norte para de frente para o equador! De certa forma, essa é uma elaboração da velha piada 'um caçador se move uma milha ao sul, uma milha a oeste e depois uma milha ao norte' - mas aqui estamos aproveitando a curvatura da esfera para efetuar uma mudança de direção. Observe que o mesmo efeito ainda acontece mesmo em escalas muito menores;

Felizmente, os quaternions (como você se notou) lidam com essa situação; Uma vez que um quaternion representa uma rotação arbitrária, ele efetivamente representa um 'ponto mais orientação' arbitrário na esfera: imagine começar com um 'triaxis' na origem e dar-lhe alguma rotação arbitrária, movendo uma unidade na direção que os eixos girados ' Pontos do eixo Z; um pouco de reflexão o convencerá de que isso o levará a um ponto na esfera unitária com alguma 'orientação' (ou seja, algum arranjo dos eixos X e Y da sua triaxis) e que você poderá chegar a todos os pontos + orientação na unidade esfera desta maneira (apenas atribua seu eixo Z para apontar ao longo da linha desde a origem até o ponto na esfera, depois transporte seus triaxes de volta para a origem ao longo dessa linha). O que mais, Como a multiplicação de quaternions corresponde à composição das rotações, cada uma das operações que você descreve pode ser representada multiplicando sua 'orientação atual' por um quaternion escolhido adequadamente: especificamente, desde o quaternion (unit) (qx, qy, qz, qw) significa 'girar em torno do eixo (qx, qy, qz) por arccos (qw)' e, em seguida (dependendo da sua escolha específica do sistema de coordenadas, e deixar c_a ser cos (alfa) e s_a ser sin (alfa)) dois dos três quaternions M_x = (s_a, 0, 0, c_a), M_y = (0, s_a, 0, c_a) e M_z = (0, 0, s_a, c_a) representarão 'rotate (ie move) na direção I atualmente estou enfrentando por alfa 'e' giro em uma direção ortogonal à que estou enfrentando atualmente por alfa '. (O terceiro desses quaternions representará 'rodar meu personagem em torno de seu próprio eixo'Cur_q = M_x * Cur_qse o jogador pressionou ou Cur_q = M_y * Cur_qse pressionou para a direita (ou possivelmente algo como Cur_q = M_yinv * Cur_qse o jogador pressionou para a esquerda, onde M_yinv é o 'inverso' do quaternion M_y, representando uma rotação para o outro lado). Observe que você deve ter cuidado em qual 'lado' você aplica a rotação, seja para pré-multiplicar ou pós-multiplicar; para ser franco, pode ser mais fácil resolver isso com tentativa e erro, tentando ambas as multiplicações e vendo o que funciona.

Ir de seu quaternion atualizado a um ponto na esfera (e a uma orientação de seu personagem) também é relativamente direto: pela correspondência do último parágrafo, tudo o que você precisa fazer é usar seu quaternion nos vetores básicos (1, 0,0), (0,1,0) e (0,0,1) do seu quadro através da operação 'girar vetor por quaternião' v → qvq -1 (onde as multiplicações aqui são quaternárias se multiplica e identificamos o vetor v = (x, y, z) com o 'quaternion degenerado' (x, y, z, 0)). Por exemplo, a posição na esfera unitária é obtida apenas pela transformação do vetor z: pos = (qx, qy, qz, qw) * (0, 0, 1, 0) * (-qx, -qy, -qz, qw) = (qx, qy, qz, qw) * (qy, -qx, qw, qz) = (2 (qy * qw + qz * qx), 2 (qz * qy-qw * qx), (qz ^ 2 + qw ^ 2) - (qx ^ 2 + qy ^ 2), 0), então(2(qy*qw+qz*qx), 2(qz*qy-qw*qx), (qz^2+qw^2)-(qx^2+qy^2))seriam as coordenadas do usuário 'transformado' na esfera unitária (e, para obter as coordenadas em uma esfera arbitrária, é claro, você apenas as multiplicaria pelo raio da esfera); cálculos semelhantes funcionam para os outros eixos, para definir, por exemplo, a direção do usuário.

Steven Stadnicki
fonte
Precisamente o que eu quero alcançar. Eu simplesmente não conseguia pensar na maneira correta de conseguir uma posição fora do quaternion de orientação. Usando o que você forneceu, eu posso escrever o Move()procedimento, mas para obter o eixo normalizado (ou seja, minha posição), eu apenas aceitaria (sin(qx),sin(qy),sin(qw)) * r?
23412 azz
@ Não exatamente - atualizarei meu post com os detalhes, mas a versão curta é que você usa seu quaternion para transformar um vetor de unidade, por exemplo (0,0,1), pelo usual v -> qvq <sup> -1 </sup> operação; o fato de você estar transformando um vetor simples significa que (naturalmente) há um atalho aqui, mas as coordenadas finais são quadráticas nos valores de seu quaternion, não lineares.
Steven Stadnicki 23/10/12
1

Eu acho que você deseja algo semelhante a este http://www.youtube.com/watch?v=L2YRZbRSD1k

Eu desenvolvi isso para um jogo de 48 horas ... você pode baixar o código aqui ... http://archive.globalgamejam.org/2011/evil-god

Usei algo semelhante ao seu código para obter as cordas 3D ... mas girei o planeta e o jogador estava na mesma posição, acho que você está interessado no movimento da criatura, é o seguinte:

    // To add movement
    protected override void LocalUpdate(float seconds)
    {
        Creature.Alfa += Direction.X * seconds * Speed;
        Creature.Beta += Direction.Y * seconds * Speed;            
    }


    // To calculate position
       World.Planet.GetCartesian(Alfa, Beta, out Position); // as you do
       Matrix PositionMatrix = Matrix.CreateTranslation(Position) * World.Planet.RotationMatrix;           
       LastPositionAbsolute = PositionAbsolute;
       Vector3 Up = PositionAbsolute = Vector3.Transform(Vector3.Zero, PositionMatrix);           
       Up.Normalize();
       // This is to add and offset to the creature model position
       PositionAbsolute += Up * 8;  
      // calculate new forward vector if needed

       if ((PositionAbsolute - LastPositionAbsolute).Length() > 0.1f) {
           Forward = PositionAbsolute - LastPositionAbsolute;
           Forward.Normalize();
       }

       // Calculate the world transform with position, forward vector and up vector
       Matrix LocalWorld = Matrix.CreateWorld(PositionAbsolute, Forward, Up); 

       Transform = Matrix.CreateScale(Scale * ScaleFactor) * LocalWorld;
Blau
fonte