KI-Programmierung Teil 11: Lenkwaffen – Zielverfolgung und Ausrichtung

In diesem Artikel dreht sich mal wieder alles um das Thema Zielverfolgung – genauer gesagt um die Zielverfolgung und Ausrichtung von Lenkwaffen.
Bei unseren früheren Betrachtungen haben wir die aktuelle Ausrichtung unserer KI-Einheiten stets vernachlässigt, um die Sache nicht unnötig zu verkomplizieren. Dies ist nun nicht mehr möglich, denn Lenkwaffen können ihre Flugrichtung nicht mal eben so verändern – die Kursänderungen müssen im Einklang mit der Manövrierfähigkeit und dem Lenksystem der Waffe erfolgen.
Keine Sorge, das Ganze ist weitaus weniger kompliziert als es sich im ersten Moment anhört. Um die Übersicht zu behalten, teilen wir die Flugsteuerung in drei Schritte auf:

// Aktuelle Zielposition speichern und optimalen
// Zielanflugsvektor (MovementDirectionDesired) berechnen:
MovableObject->Set_Destination(&Destination);

// Flugsteuerung:
// Momentane Flugrichtung mit jedem Aufruf schrittweise an den
// optimalen Zielanflugsvektor anpassen:
MovableObject->Update_MovementDirection(0.95f, 1.0f);

// Flugbewegung:
MovableObject->Update_Movement(0.05f, 0.3f);

Im ersten Schritt muss zunächst das Flugziel der Lenkwaffe festgelegt werden. Hierzu gehören zum einen die Zielposition und zum anderen der optimale Zielanflugsvektor (MovementDirectionDesired). Der optimale Zielanflugsvektor kennzeichnet die Richtung, die auf direktem Wege zum Ziel führt und berechnet sich wie folgt:

MovementDirectionDesired = DestinationPosition - WorldSpacePosition;
D3DXVec3Normalize(&MovementDirectionDesired, &MovementDirectionDesired);

Im zweiten Schritt – der Flugsteuerung – wird die momentane Flugrichtung schrittweise an den optimalen Zielanflugsvektor angepasst. Bevor man mit der Implementierung der Richtungssteuerung beginnen kann, muss man sich zunächst darüber im Klaren sein, wie das Leitwerk (das Steuersystem) überhaupt funktioniert.

In unserem Beispiel soll die Lenkwaffe mit einem 2-Achsen-Steuersystem ausgestattet werden:

  • Achse 1 sei die vertikale Achse der Waffe und ermöglicht horizontale Kursänderungen.
  • Achse 2 sei die horizontale Achse der Waffe und ermöglicht vertikale Kursänderungen.

Die Änderung der horizontalen Flugrichtung berechnet sich wie folgt:

D3DXMatrixRotationAxis(&FrameRotationMatrix, &VerticalAxis, RotationVel);
OrientationMatrix = OrientationMatrix*FrameRotationMatrix;

Die Änderung der vertikalen Flugrichtung berechnet sich analog:

D3DXMatrixRotationAxis(&FrameRotationMatrix, &HorizontalAxis, RotationVel);
OrientationMatrix = OrientationMatrix*FrameRotationMatrix;


Die OrientationMatrix beschreibt die aktuelle Orientierung (Ausrichtung) der Lenkwaffe und wird zum einen für die Darstellung des Waffen-3D-Modells und zum anderen für die Berechnung der lokalen Achsen des Steuersystems benötigt (siehe hierzu 3D-Programmierung (Mathematik) Teil 10):

HorizontalAxis.x = OrientationMatrix._11;
HorizontalAxis.y = OrientationMatrix._12;
HorizontalAxis.z = OrientationMatrix._13;

VerticalAxis.x = OrientationMatrix._21;
VerticalAxis.y = OrientationMatrix._22;
VerticalAxis.z = OrientationMatrix._23;

MovementDirection.x = OrientationMatrix._31;
MovementDirection.y = OrientationMatrix._32;
MovementDirection.z = OrientationMatrix._33;

Wie können wir feststellen, ob sich die Waffe auf dem korrekten Kurst befindet.
Die Antwort liefert das Skalarprodukt aus der aktuellen Flugrichtung und der angestrebten Flugrichtung:

dot = D3DXVec3Dot(&MovementDirectionDesired, &MovementDirection);

// Bewegungsrichtung OK
if(dot > precision)
    return;

Hat das Skalarprodukt den Wert 1, dann bewegt sich die Waffe direkt auf das Ziel zu.

Bleibt noch die letzte Frage zu klären – wie ermittelt man, welche Kurskorrekturen konkret auszuführen sind?

Sie wissen bereits, wie sich die Rotationsmatrix für eine Kurskorrektur berechnen läst. Betrachten wir zunächst eine mögliche horizontale Kurskorrektur:

D3DXMatrixRotationAxis(&FrameRotationMatrix, &VerticalAxis, RotationVel);

Mithilfe dieser Matrix können wir testweise die neue Flugrichtung berechnen:

Multiply3DVectorWithRotationMatrix(&TestDir, &MovementDirection,
                                           &FrameRotationMatrix);

Im nächsten Schritt können wir überprüfen, ob die neue Flugrichtung besser mit der angestrebten Flugrichtung übereinstimmt:

dotTest = D3DXVec3Dot(&MovementDirectionDesired, &TestDir);

// neue Flugrichtung ist besser!
if(dotTest > dot)
    OrientationMatrix = OrientationMatrix*FrameRotationMatrix;

Falls dies nicht der Fall sein sollte, testen wir, ob eine Kursänderung in die entgegengesetzte Richtung vielleicht zu einem besseren Ergebnis führt:

D3DXMatrixRotationAxis(&FrameRotationMatrix, &VerticalAxis, -RotationVel);
. . .

Sollte auch dies keine Verbesserungen bringen, dann ist eine horizontale Kurskorrektur unnötig. Im nächsten Schritt muss überprüft werden, ob evtl. eine vertikale Kurskorrektur notwendig ist. Dies erfolgt analog zur Überprüfung etwaiger horizontaler Kurskorrekturen.


Im letzten Schritt wird schließlich die Flugbewegung berechnet und darüber hinaus überprüft, ob die Waffe ihr Ziel erreicht hat.

Verschaffen wir uns nun einen Überblick über die einzelnen Methoden der CMovableObject-(Lenkwaffen-) Klasse:

Set_Destination(): Speichert die aktuelle Zielposition und berechnet den optimalen Zielanflugsvektor (MovementDirectionDesired).

Update_Movement(): Berechnet die aktuelle Position (Flugbewegung).

Update_MovementDirection(): Passt die momentane Flugrichtung mit jedem Aufruf schrittweise an den optimalen Zielanflugsvektor an.

Set_RotationVelocity(): Legt die Rotationsgeschwindigkeit bei der Kurskorrektur und damit die Manövrierfähigkeit fest.

Set_WorldSpacePosition(): Legt die Position im Weltkoordinatensystem fest.

Und hier nun die vollständige Implementierung der CMovableObject-Klasse:

class CMovableObject
{
public:

    D3DXVECTOR3 DestinationPosition;
    D3DXVECTOR3 WorldSpacePosition;

    // tatsächliche Bewegungsrichtung
    D3DXVECTOR3 MovementDirection;

    // angestrebte Bewegungsrichtung, die
    // auf direktem Wege zum Ziel führt
    D3DXVECTOR3 MovementDirectionDesired;

    // horizontale und vertikale Achse
    // zur Beschreibung der Orientierung
    D3DXVECTOR3 HorizontalAxis;
    D3DXVECTOR3 VerticalAxis;

    float RotationVelocity;

    // Orientierung im 3D-Raum
    D3DXMATRIXA16 OrientationMatrix;

    CMovableObject()
    {
        RotationVelocity = 0.0f;

        DestinationPosition = g_NullVector;
        WorldSpacePosition  = g_NullVector;

        MovementDirectionDesired = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

        // anfängliche Ausrichtung/Orientierung:
        HorizontalAxis    = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
        VerticalAxis      = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
        MovementDirection = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

        D3DXMatrixIdentity(&OrientationMatrix);
    }

    ~CMovableObject()
    {}

    void Set_RotationVelocity(float vel)
    {
        RotationVelocity = vel;
    }

    void Set_WorldSpacePosition(D3DXVECTOR3* pWorldSpacePosition)
    {
        WorldSpacePosition = *pWorldSpacePosition;
    }

    void Set_Destination(D3DXVECTOR3* pPosition)
    {
        DestinationPosition = *pPosition;

        // Aus der neuen Zielposition wird nun die angestrebte
        // Bewegungsrichtung berechnet, die auf direktem Wege zum Ziel führt

       MovementDirectionDesired = DestinationPosition - WorldSpacePosition;

       D3DXVec3Normalize(&MovementDirectionDesired,
                         &MovementDirectionDesired);
    }

    void Update_Movement(float vel, float minDistanceSqToDestination)
    {
        D3DXVECTOR3 DistanceVector = WorldSpacePosition -
                                     DestinationPosition;

        if(D3DXVec3LengthSq(&DistanceVector) < minDistanceSqToDestination)
            return;

        // Flugbewegung:
        WorldSpacePosition += MovementDirection*vel;
    }

    void Update_MovementDirection(float precision, float frameTime)
    {
        float dot, dotTest;

        // Abweichung vom Ziel ermitteln:
        dot = D3DXVec3Dot(&MovementDirectionDesired, &MovementDirection);

        // Bewegungsrichtung OK
        if(dot > precision)
            return;

        float RotationVel = RotationVelocity*frameTime;

        D3DXMATRIXA16 FrameRotationMatrix;
        D3DXVECTOR3 TestDir;

        // Durchführen möglicher Kurskorrekturen . . .

        // . . . durch Drehung um die vertikale Achse:
        D3DXMatrixRotationAxis(&FrameRotationMatrix, &VerticalAxis,
                               RotationVel);

        Multiply3DVectorWithRotationMatrix(&TestDir, &MovementDirection,
                                           &FrameRotationMatrix);

        dotTest = D3DXVec3Dot(&MovementDirectionDesired, &TestDir);

        // neue Flugrichtung ist besser!
        if(dotTest > dot)
            OrientationMatrix = OrientationMatrix*FrameRotationMatrix;

        // neue Flugrichtung ist schlechter, darum den Drehsinn ändern:
        else
        {
            D3DXMatrixRotationAxis(&FrameRotationMatrix, &VerticalAxis,
                                   -RotationVel);

            Multiply3DVectorWithRotationMatrix(&TestDir,
            &MovementDirection, &FrameRotationMatrix);

            dotTest = D3DXVec3Dot(&MovementDirectionDesired, &TestDir);

            if(dotTest > dot)
                OrientationMatrix = OrientationMatrix*FrameRotationMatrix;
        }

        // . . . durch Drehung um die horizontale Achse:
        D3DXMatrixRotationAxis(&FrameRotationMatrix, &HorizontalAxis,
                               RotationVel);

        Multiply3DVectorWithRotationMatrix(&TestDir, &MovementDirection,
                                           &FrameRotationMatrix);

        dotTest = D3DXVec3Dot(&MovementDirectionDesired, &TestDir);

        // neue Flugrichtung ist besser!
        if(dotTest > dot)
            OrientationMatrix = OrientationMatrix*FrameRotationMatrix;

        // neue Flugrichtung ist schlechter, darum den Drehsinn ändern:
        else
        {
            D3DXMatrixRotationAxis(&FrameRotationMatrix, &HorizontalAxis,
                                   -RotationVel);

            Multiply3DVectorWithRotationMatrix(&TestDir,
            &MovementDirection, &FrameRotationMatrix);

            dotTest = D3DXVec3Dot(&MovementDirectionDesired, &TestDir);

            if(dotTest > dot)
                OrientationMatrix = OrientationMatrix*FrameRotationMatrix;
        }

        // lokale Achsen aus der Orientierungsmatrix auslesen:

        HorizontalAxis.x = OrientationMatrix._11;
        HorizontalAxis.y = OrientationMatrix._12;
        HorizontalAxis.z = OrientationMatrix._13;

        VerticalAxis.x = OrientationMatrix._21;
        VerticalAxis.y = OrientationMatrix._22;
        VerticalAxis.z = OrientationMatrix._23;

        MovementDirection.x = OrientationMatrix._31;
        MovementDirection.y = OrientationMatrix._32;
        MovementDirection.z = OrientationMatrix._33;

        D3DXVec3Normalize(&HorizontalAxis, &HorizontalAxis);
        D3DXVec3Normalize(&VerticalAxis, &VerticalAxis);
        D3DXVec3Normalize(&MovementDirection, &MovementDirection);
    }
};