Orienter un modèle pour faire face à une cible

28

J'ai deux objets (cible et joueur), les deux ont Position (Vector3) et Rotation (Quaternion). Je veux que la cible tourne et soit face au joueur. La cible, quand elle tire, doit tirer directement sur le joueur.

J'ai vu de nombreux exemples de slerping pour le joueur, mais je ne veux pas de rotation incrémentielle, eh bien, je suppose que ce serait ok tant que je peux faire que le slerp soit à 100%, et aussi longtemps que cela fonctionne.

FYI - Je suis capable d'utiliser la position et la rotation pour faire beaucoup d'autres choses et tout fonctionne très bien, sauf cette dernière pièce que je ne peux pas comprendre.

Échantillons de code exécutés dans la classe Target, Position = la position des cibles, Avatar = le joueur.

MODIFIER

J'utilise maintenant le code c # de Maik qu'il a fourni et cela fonctionne très bien!

Marc
la source
4
Si vous faites un slerp à 100%, vous n'utilisez pas de slerp. Vous définissez simplement la rotation sur 0*(rotation A) + 1*(rotation B)- en d'autres termes, vous définissez simplement la rotation sur la rotation B à long terme. Slerp sert uniquement à déterminer à quoi devrait ressembler la rotation (0% <x <100%) entre les deux.
doppelgreener
Ok, c'est logique, mais, la cible ne tourne toujours pas complètement vers le joueur ... "le long chemin" avec ce code.
Marc

Réponses:

20

Il existe plusieurs façons de procéder. Vous pouvez calculer l'orientation absolue ou la rotation par rapport à votre avatar, cela signifie que votre nouvelle orientation = avatarOrientation * q. Voici le dernier:

  1. Calculez l'axe de rotation en prenant le produit croisé du vecteur avant unitaire de votre avatar et le vecteur unitaire d'avatar à la cible, le nouveau vecteur avant:

    vector newForwardUnit = vector::normalize(target - avatarPosition);
    vector rotAxis = vector::cross(avatarForwardUnit, newForwardUnit);
    
  2. Calculer l'angle de rotation à l'aide du produit scalaire

    float rotAngle = acos(vector::dot(avatarForwardUnit, newForwardUnit));
  3. Créez le quaternion en utilisant rotAxis et rotAngle et multipliez-le avec l'orientation actuelle de l'avatar

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

Si vous avez besoin d'aide pour trouver le vecteur avant actuel de l'avatar, l'entrée pour 1. tirez juste :)

EDIT: Le calcul de l'orientation absolue est en fait un peu plus facile, utilisez le vecteur direct de la matrice d'identité au lieu du vecteur direct avatars comme entrée pour 1) et 2). Et ne le multipliez pas en 3), utilisez-le directement comme nouvelle orientation:newRot = q


Important à noter: La solution présente 2 anomalies dues à la nature du produit croisé:

  1. Si les vecteurs directs sont égaux. La solution ici est simplement de retourner le quaternion d'identité

  2. Si les vecteurs pointent exactement dans la direction opposée. La solution ici est de créer le quaternion en utilisant les avatars axe haut comme axe de rotation et l'angle 180,0 degrés.

Voici l'implémentation en C ++ qui prend en charge ces cas marginaux. Le convertir en C # devrait être facile.

// 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);
}

EDIT: Version corrigée du code 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
la source
Ok, j'ai donné un coup de feu comme vous pouvez le voir par mon montage dans la question, code fourni. Je ne sais pas vraiment quel est le problème. les entrées a et b sont des vecteurs directs, ou du moins supposent l'être.
Marc
@Marc voir ma version corrigée de votre code XNA dans ma réponse. Il y avait 2 problèmes: 1) le calcul du nouveau vecteur avant était incorrect, doit être normalisé AvatarPosition - TargetPosition 2) rotAxis doit être normalisé après le produit croisé dans GetRotation
Maik Semder
@Marc, 2 changements mineurs: 3) la source et la destination sont déjà normalisées, pas besoin de les normaliser à nouveau dans GetRotation 4) ne testez pas l'absolu 1 / -1 dans GetRotation, utilisez plutôt une certaine tolérance, j'ai utilisé 0.000001f
Maik Semder
Hmm, ça ne fonctionne toujours pas. La même chose à l'échelle se produit avec le modèle et la cible ne tourne pas vers l'avatar (ce que j'ai remarqué dans vos commentaires, vous essayez de faire pivoter l'avatar vers la cible ... devrait être l'inverse) ... en gros, essayer pour amener une foule à affronter le joueur (avatar dans une caméra à la 3ème personne). La méthode GetRotation ne devrait-elle pas savoir quelque chose sur les rotations actuelles de la cible et de l'avatar, comme dans, pensez-vous que newForward est créé correctement?
Marc
Si l'objet est mis à l'échelle, cela signifie que le quaternion n'a pas de longueur unitaire, cela signifie que rotAxis n'est pas normalisé. Avez-vous ajouté mon dernier changement de code avec la normalisation de rotAxis? Cependant, veuillez montrer votre code actuel et pour un exemple de cas où cela ne fonctionne pas, veuillez également publier les valeurs de: newForward rotAngle rotAxiset le returned quaternion. Le code pour la foule sera le même, une fois que nous l'aurons fait fonctionner avec l'avatar, il sera facile de changer le titre de n'importe quel objet.
Maik Semder