Orientando um modelo para enfrentar um alvo

28

Eu tenho dois objetos (alvo e jogador), ambos têm Posição (Vetor3) e Rotação (Quaternion). Quero que o alvo gire e fique de frente para o jogador. O alvo, quando atira em alguma coisa, deve atirar diretamente no jogador.

Eu já vi muitos exemplos de slerping para o jogador, mas não quero rotação incremental, bem, suponho que tudo ficaria bem desde que eu possa fazer o slerp ser 100%, e enquanto ele realmente funcionou.

Para sua informação, sou capaz de usar a posição e a rotação para fazer muitas outras coisas e tudo funciona muito bem, exceto esta última peça que não consigo entender.

Amostras de código são executadas na classe Target, Position = posição do alvo, Avatar = jogador.

EDITAR

Agora estou usando o código c # de Maik que ele forneceu e funciona muito bem!

Marc
fonte
4
Se você está fazendo um slerp 100%, não está usando slerp. Você está apenas configurando a rotação para 0*(rotation A) + 1*(rotation B)- em outras palavras, você está apenas configurando a rotação para a rotação B pelo caminho mais longo. Slerp é apenas para determinar como deve ser a rotação (0% <x <100%) do caminho.
Doppelgreener
Ok, faz sentido, mas o alvo ainda não está girando totalmente em direção ao jogador ... "o longo caminho" com esse código.
Marc

Respostas:

20

Existem mais de uma maneira de fazer isso. Você pode calcular a orientação absoluta ou a rotação relativa ao seu avatar, o que significa sua nova orientação = avatarOrientation * q. Aqui está o último:

  1. Calcule o eixo de rotação levando o produto cruzado do vetor de unidade para a frente do seu avatar e o vetor de unidade do avatar para o destino, o novo vetor para a frente:

    vector newForwardUnit = vector::normalize(target - avatarPosition);
    vector rotAxis = vector::cross(avatarForwardUnit, newForwardUnit);
    
  2. Calcular o ângulo de rotação usando o produto escalar

    float rotAngle = acos(vector::dot(avatarForwardUnit, newForwardUnit));
  3. Crie o quaternion usando rotAxis e rotAngle e multiplique-o com a orientação atual do avatar

    quaternion q(rotAxis, rotAngle);
    quaternion newRot = avatarRot * q;
    

Se você precisar de ajuda para encontrar o vetor para a frente atual do avatar, a entrada para 1. basta atirar :)

EDIT: calcular a orientação absoluta é realmente um pouco mais fácil, use o vetor direto da matriz de identidade em vez do vetor direto de avatares como entrada para 1) e 2). E não multiplique em 3); use-o diretamente como a nova orientação:newRot = q


Importante observar: A solução possui 2 anomalias causadas pela natureza do produto cruzado:

  1. Se os vetores avançados forem iguais. A solução aqui é simplesmente retornar o quaternion de identidade

  2. Se os vetores apontam exatamente na direção oposta. A solução aqui é criar o quaternion usando avatares acima do eixo como eixo de rotação e o ângulo 180,0 graus.

Aqui está a implementação em C ++ que cuida desses casos extremos. Convertê-lo em c # deve ser fácil.

// returns a quaternion that rotates vector a to vector b
quaternion get_rotation(const vector &a, const vector &b, const vector &up)
{   
    ASSERT_VECTOR_NORMALIZED(a);
    ASSERT_VECTOR_NORMALIZED(b);

    float dot = vector::dot(a, b);    
    // test for dot -1
    if(nearly_equal_eps_f(dot, -1.0f, 0.000001f))
    {
        // vector a and b point exactly in the opposite direction, 
        // so it is a 180 degrees turn around the up-axis
        return quaternion(up, gdeg2rad(180.0f));
    }
    // test for dot 1
    else if(nearly_equal_eps_f(dot, 1.0f, 0.000001f))
    {
        // vector a and b point exactly in the same direction
        // so we return the identity quaternion
        return quaternion(0.0f, 0.0f, 0.0f, 1.0f);
    }

    float rotAngle = acos(dot);
    vector rotAxis = vector::cross(a, b);
    rotAxis = vector::normalize(rotAxis);
    return quaternion(rotAxis, rotAngle);
}

Edição: Versão corrigida do código XNA do Marc

// the new forward vector, so the avatar faces the target
Vector3 newForward = Vector3.Normalize(Position - GameState.Avatar.Position);
// calc the rotation so the avatar faces the target
Rotation = Helpers.GetRotation(Vector3.Forward, newForward, Vector3.Up);
Cannon.Shoot(Position, Rotation, this);


public static Quaternion GetRotation(Vector3 source, Vector3 dest, Vector3 up)
{
    float dot = Vector3.Dot(source, dest);

    if (Math.Abs(dot - (-1.0f)) < 0.000001f)
    {
        // vector a and b point exactly in the opposite direction, 
        // so it is a 180 degrees turn around the up-axis
        return new Quaternion(up, MathHelper.ToRadians(180.0f));
    }
    if (Math.Abs(dot - (1.0f)) < 0.000001f)
    {
        // vector a and b point exactly in the same direction
        // so we return the identity quaternion
        return Quaternion.Identity;
    }

    float rotAngle = (float)Math.Acos(dot);
    Vector3 rotAxis = Vector3.Cross(source, dest);
    rotAxis = Vector3.Normalize(rotAxis);
    return Quaternion.CreateFromAxisAngle(rotAxis, rotAngle);
}
Maik Semder
fonte
Ok, eu dei uma chance, como você pode ver pela minha edição na pergunta, código fornecido. Não tenho muita certeza de qual é o problema. as entradas aeb são vetores diretos, ou pelo menos supõem que sejam.
Marc
@ Marc ver minha versão corrigida do seu código XNA na minha resposta. Foram 2 problemas: 1) cálculo do novo vector para a frente foi errado, deve ser normalizada AvatarPosition - TargetPosition 2) rotAxis deve ser normalizada após o produto cruzado em GetRotation
Maik Semder
@ Marc, 2 pequenas alterações: 3) origem e dest já estão normalizados, não há necessidade de normalizá-los novamente no GetRotation 4) não teste para absoluto 1 / -1 no GetRotation, use alguma tolerância, usei
0.000001f
Hmm, isso ainda não está funcionando. A mesma coisa de escala acontece com o modelo e o alvo não gira em direção ao avatar (o que eu notei em seus comentários, você está tentando girar o avatar em direção ao alvo ... deve ser o contrário) ... basicamente, tentando obter uma multidão para enfrentar o jogador (avatar em uma câmera de terceira pessoa). O método GetRotation não deveria saber algo sobre as rotações atuais do alvo e do avatar, como você acha que o newForward está sendo criado corretamente?
Marc
Se o objeto for dimensionado, isso significa que o quaternion não possui um comprimento unitário, isso significa que rotAxis não é normalizado. Você adicionou minha última alteração de código com a normalização do rotAxis? No entanto, mostre seu código atual e, para um exemplo de caso em que ele não funcione, publique também os valores de: newForward rotAngle rotAxise the returned quaternion. O código para o mob será o mesmo, uma vez que fizemos o trabalho com o avatar, será fácil alterar o cabeçalho de qualquer objeto.
Maik Semder