Ein Modell so ausrichten, dass es einem Ziel zugewandt ist

28

Ich habe zwei Objekte (Ziel und Spieler), beide haben Position (Vektor3) und Rotation (Quaternion). Ich möchte, dass sich das Ziel dreht und direkt auf den Spieler gerichtet ist. Das Ziel sollte, wenn es schießt, direkt auf den Spieler schießen.

Ich habe viele Beispiele für das Slerping mit dem Player gesehen, aber ich möchte keine inkrementelle Rotation. Nun, ich nehme an, das wäre in Ordnung, solange ich das Slerp zu 100% machen kann und solange es tatsächlich funktioniert.

Zu Ihrer Information: Ich bin in der Lage, die Position und die Rotation zu verwenden, um viele andere Dinge zu tun, und alles funktioniert großartig, bis auf dieses letzte Stück, das ich nicht herausfinden kann.

Codebeispiele werden in der Klasse des Ziels ausgeführt, Position = die Position des Ziels, Avatar = der Spieler.

BEARBEITEN

Ich benutze jetzt Maiks C # -Code, den er zur Verfügung gestellt hat, und er funktioniert großartig!

Marc
quelle
4
Wenn Sie 100% slerp ausführen, verwenden Sie nicht slerp. Sie stellen nur die Rotation auf 0*(rotation A) + 1*(rotation B)- mit anderen Worten, Sie stellen nur die Rotation auf Rotation B auf dem langen Weg ein. Slerp dient nur zum Bestimmen, wie die Drehung zwischen den beiden Elementen aussehen soll (0% <x <100%).
Doppelgreener
Ok, macht Sinn, aber das Ziel dreht sich immer noch nicht vollständig in Richtung des Spielers ... "der lange Weg" mit diesem Code.
Marc

Antworten:

20

Es gibt mehr als eine Möglichkeit, dies zu tun. Sie können die absolute Ausrichtung oder die Drehung relativ zu Ihrem Avatar berechnen, dh Ihre neue Ausrichtung = avatarOrientation * q. Hier ist der letztere:

  1. Berechnen Sie die Rotationsachse, indem Sie das Kreuzprodukt des Einheitsvorwärtsvektors Ihres Avatars und den Einheitsvektor vom Avatar zum Ziel nehmen, den neuen Vorwärtsvektor:

    vector newForwardUnit = vector::normalize(target - avatarPosition);
    vector rotAxis = vector::cross(avatarForwardUnit, newForwardUnit);
    
  2. Berechnen Sie den Drehwinkel mit dem Skalarprodukt

    float rotAngle = acos(vector::dot(avatarForwardUnit, newForwardUnit));
  3. Erstellen Sie die Quaternion mit rotAxis und rotAngle und multiplizieren Sie sie mit der aktuellen Ausrichtung des Avatars

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

Wenn Sie Hilfe beim Auffinden des aktuellen Vorwärtsvektors des Avatars benötigen, geben Sie den Wert für 1 ein.

BEARBEITEN: Die Berechnung der absoluten Orientierung ist tatsächlich etwas einfacher. Verwenden Sie als Eingabe für 1) und 2) den Vorwärtsvektor der Identitätsmatrix anstelle des Avatars. Und multipliziere es nicht mit 3), sondern verwende es direkt als neue Orientierung:newRot = q


Wichtig zu beachten: Die Lösung weist zwei Anomalien auf, die durch die Art des Kreuzprodukts verursacht werden:

  1. Wenn die Vorwärtsvektoren gleich sind. Lösung hier ist einfach die Identität Quaternion zurückzugeben

  2. Wenn die Vektoren genau in die entgegengesetzte Richtung zeigen. Die Lösung besteht darin, das Quaternion mit der Aufwärtsachse des Avatars als Rotationsachse und dem Winkel von 180,0 Grad zu erstellen.

Hier ist die Implementierung in C ++, die sich um diese Randfälle kümmert. Das Konvertieren in C # sollte einfach sein.

// 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: Korrigierte Version von Marc's XNA Code

// 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
quelle
Ok, ich habe das probiert, wie du an meiner Bearbeitung in der Frage sehen kannst, Code zur Verfügung gestellt. Ich bin mir nicht sicher, wo das Problem liegt. Die Eingaben a und b sind Vorwärtsvektoren oder werden zumindest angenommen.
Marc
@Marc Meine korrigierte Version Ihres XNA-Codes finden Sie in meiner Antwort. Es gab 2 Probleme: 1) Berechnung des neuen Vorwärtsvektors war falsch, muss normalisiert werden AvatarPosition - TargetPosition 2) RotAxis muss nach dem Kreuzprodukt in GetRotation
Maik Semder 24.07.11
@Marc, 2 kleinere Änderungen: 3) Quelle und Ziel sind bereits normalisiert, müssen in GetRotation nicht erneut normalisiert werden 4) In GetRotation nicht auf absolut 1 / -1 testen, sondern Toleranz verwenden, ich habe 0.000001f
Maik Semder
Hmm, das funktioniert immer noch nicht. Dieselbe Skalierungssache passiert mit dem Modell und das Ziel dreht sich nicht in Richtung des Avatars (was ich in Ihren Kommentaren bemerkt habe, dass Sie versuchen, den Avatar in Richtung des Ziels zu drehen ... sollte umgekehrt sein) ... im Grunde genommen versuchen um einen Mob dazu zu bringen, sich dem Spieler zu stellen (Avatar in einer 3rd-Person-Kamera). Sollte die GetRotation-Methode nicht etwas über die aktuellen Rotationen von Ziel und Avatar wissen, wie in, denken Sie, dass der newForward ordnungsgemäß erstellt wird?
Marc
Wenn das Objekt skaliert ist, bedeutet dies, dass die Quaternion keine Längeneinheit hat, dh, dass rotAxis nicht normalisiert ist. Hast du meine letzte Codeänderung mit der Normalisierung von rotAxis hinzugefügt? Bitte zeigen Sie jedoch Ihren aktuellen Code an und geben Sie für einen Beispielfall, in dem dies nicht funktioniert, auch die Werte von: newForward rotAngle rotAxisund an returned quaternion. Der Code für den Mob ist derselbe, sobald wir ihn mit dem Avatar erstellt haben, ist es einfach, die Überschrift eines Objekts zu ändern.
Maik Semder