Orientar un modelo para enfrentar un objetivo

28

Tengo dos objetos (objetivo y jugador), ambos tienen Posición (Vector3) y Rotación (Quaternion). Quiero que el objetivo gire y esté de frente al jugador. El objetivo, cuando dispara algo, debe disparar directamente al jugador.

He visto muchos ejemplos de slerping para el jugador, pero no quiero una rotación incremental, bueno, supongo que eso estaría bien siempre que pueda hacer que el slerp sea 100%, y mientras realmente funcione.

FYI: soy capaz de usar la posición y la rotación para hacer muchas otras cosas y todo funciona muy bien, excepto esta última pieza que no puedo entender.

Las muestras de código se ejecutan en la clase del Objetivo, Posición = la posición del objetivo, Avatar = el jugador

EDITAR

¡Ahora estoy usando el código C # de Maik que me ha proporcionado y funciona muy bien!

Bagazo
fuente
44
Si estás haciendo un slerp 100%, no estás usando slerp. Solo está configurando la rotación en 0*(rotation A) + 1*(rotation B), en otras palabras, solo está configurando la rotación en la rotación B a lo largo. Slerp es solo para determinar cómo debería verse la rotación (0% <x <100%) del camino intermedio.
doppelgreener
Ok, tiene sentido, pero el objetivo todavía no está girando completamente hacia el jugador ... "a la larga" con ese código.
Marc

Respuestas:

20

Hay más de una forma de hacerlo. Puede calcular la orientación absoluta o la rotación relativa a su avatar, eso significa que su nueva orientación = avatarOrientation * q. Aquí está el último:

  1. Calcule el eje de rotación tomando el producto cruzado del vector de unidad de avance de su avatar y el vector de unidad de avatar a destino, el nuevo vector de avance:

    vector newForwardUnit = vector::normalize(target - avatarPosition);
    vector rotAxis = vector::cross(avatarForwardUnit, newForwardUnit);
    
  2. Calcule el ángulo de rotación usando el producto punto

    float rotAngle = acos(vector::dot(avatarForwardUnit, newForwardUnit));
  3. Crea el cuaternión usando rotAxis y rotAngle y multiplícalo con la orientación actual del avatar

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

Si necesita ayuda para encontrar el vector de avance actual del avatar, la entrada para 1. simplemente dispare :)

EDITAR: Calcular la orientación absoluta es en realidad un poco más fácil, use el vector directo de la matriz de identidad en lugar del vector directo de avatares como entrada para 1) y 2). Y no lo multiplique en 3), en su lugar, úselo directamente como la nueva orientación:newRot = q


Importante tener en cuenta: la solución tiene 2 anomalías causadas por la naturaleza del producto cruzado:

  1. Si los vectores directos son iguales. La solución aquí es simplemente devolver la identidad quaternion

  2. Si los vectores apuntan exactamente en la dirección opuesta. La solución aquí es crear el cuaternión mediante el uso de avatares en el eje hacia arriba como eje de rotación y el ángulo de 180.0 grados.

Aquí está la implementación en C ++ que se ocupa de esos casos extremos. Convertirlo a C # debería 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);
}

EDITAR: versión corregida del código XNA de 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
fuente
Ok, le di una oportunidad, como puede ver en mi edición en la pregunta, con el código proporcionado. No estoy seguro de cuál es el problema. Las entradas ayb son vectores directos, o al menos se supone que lo son.
Marc
@Marc ve mi versión corregida de su código XNA en mi respuesta. Hubo 2 problemas: 1) el cálculo del nuevo vector de avance era incorrecto, debe normalizarse AvatarPosition - TargetPosition 2) rotAxis debe normalizarse después del producto cruzado en GetRotation
Maik Semder
@Marc, 2 cambios menores: 3) el origen y el destino ya están normalizados, no es necesario normalizarlos nuevamente en GetRotation 4) no pruebe el 1 / -1 absoluto en GetRotation, más bien use algo de tolerancia, usé 0.000001f
Maik Semder
Hmm, eso todavía no funciona. Lo mismo ocurre con el modelo y el objetivo no gira hacia el avatar (lo que noté en sus comentarios es que está tratando de girar el avatar hacia el objetivo ... debería ser al revés) ... básicamente, intentando hacer que una mafia se enfrente al jugador (avatar en una cámara en tercera persona). ¿No debería saber el método GetRotation algo sobre las rotaciones actuales del objetivo y el avatar, como en, crees que el nuevo Forward se está creando correctamente?
Marc
Si el objeto está escalado, eso significa que el cuaternión no tiene una unidad de longitud, eso significa que rotAxis no está normalizado. ¿Agregaste mi último cambio de código con la normalización de rotAxis? Sin embargo, muestre su código actual y para un caso de ejemplo donde no funciona, también publique los valores de: newForward rotAngle rotAxisy el returned quaternion. El código para la mafia será el mismo, una vez que lo hagamos funcionar con el avatar, será fácil cambiar el encabezado de cualquier objeto.
Maik Semder