Orientamento di un modello per affrontare un obiettivo


28

Ho due oggetti (bersaglio e giocatore), entrambi hanno Posizione (Vector3) e Rotazione (Quaternion). Voglio che il bersaglio ruoti e sia rivolto verso il giocatore. Il bersaglio, quando spara qualcosa, dovrebbe sparare direttamente al giocatore.

Ho visto un sacco di esempi di slerping per il giocatore, ma non voglio una rotazione incrementale, beh, suppongo che sarebbe ok fintanto che potrò rendere lo slerp al 100% e finché funzionerà davvero.

Cordiali saluti - Sono in grado di utilizzare la posizione e la rotazione per fare molte altre cose e tutto funziona alla grande tranne questo ultimo pezzo che non riesco a capire.

I campioni di codice vengono eseguiti nella classe del bersaglio, Posizione = posizione del bersaglio, Avatar = giocatore.

MODIFICARE

Ora sto usando il codice c # di Maik che ha fornito e funziona benissimo!


4
Se stai eseguendo uno slerp al 100%, non stai usando slerp. Stai solo impostando la rotazione su 0*(rotation A) + 1*(rotation B)- in altre parole, stai solo impostando la rotazione su rotazione B nel lungo cammino. Slerp serve solo a determinare l'aspetto della rotazione (0% <x <100%) nel mezzo.
doppelgreener,

Ok, ha senso, ma il bersaglio non ruota ancora completamente verso il giocatore ... "la lunga strada" con quel codice.
Marc

Risposte:


20

Esistono più modi per farlo. Puoi calcolare l'orientamento assoluto o la rotazione relativa al tuo avatar, ciò significa che il tuo nuovo orientamento = avatarOrientation * q. Ecco l'ultimo:

  1. Calcola l'asse di rotazione portando il prodotto incrociato del vettore in avanti dell'unità del tuo avatar e il vettore di unità dall'avatar al target, il nuovo vettore in avanti:

    vector newForwardUnit = vector::normalize(target - avatarPosition);
    vector rotAxis = vector::cross(avatarForwardUnit, newForwardUnit);
    
  2. Calcola l'angolo di rotazione usando il punto-prodotto

    float rotAngle = acos(vector::dot(avatarForwardUnit, newForwardUnit));
  3. Crea il quaternione usando rotAxis e rotAngle e moltiplicalo con l'orientamento corrente dell'avatar

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

Se hai bisogno di aiuto per trovare l'attuale vettore in avanti dell'avatar, l'ingresso per 1. basta sparare :)

EDIT: il calcolo dell'orientamento assoluto è in realtà un po 'più semplice, usa il vettore in avanti della matrice identità invece del vettore in avanti degli avatar come input per 1) e 2). E non moltiplicarlo per 3), invece utilizzalo direttamente come nuovo orientamento:newRot = q


Importante da notare: la soluzione presenta 2 anomalie causate dalla natura del prodotto incrociato:

  1. Se i vettori in avanti sono uguali. La soluzione qui è semplicemente restituire il quaternione identità

  2. Se i vettori puntano esattamente nella direzione opposta. La soluzione qui è quella di creare il quaternione usando gli avatar sull'asse come asse di rotazione e l'angolo di 180,0 gradi.

Ecco l'implementazione in C ++ che si occupa di quei casi limite. La conversione in C # dovrebbe essere 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: versione corretta del codice XNA di 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);
}

Ok, ho dato uno scatto come puoi vedere dalla mia modifica nella domanda, codice fornito. Non sono sicuro di quale sia il problema. gli ingressi aeb sono vettori diretti, o almeno supponiamo di esserlo.
Marc

@Marc vedi la mia versione corretta del tuo codice XNA nella mia risposta. Ci sono stati 2 problemi: 1) il calcolo del nuovo vettore forward era errato, deve essere normalizzato AvatarPosition - TargetPosition 2) rotAxis deve essere normalizzato dopo il prodotto incrociato in GetRotation
Maik Semder

@Marc, 2 piccole modifiche: 3) source e dest sono già normalizzati, non è necessario normalizzarli di nuovo in GetRotation 4) non testare 1 / -1 assoluto in GetRotation, piuttosto usare un po 'di tolleranza, ho usato 0.000001f
Maik Semder

Hmm, non funziona ancora. La stessa cosa di ridimensionamento accade con il modello e il bersaglio non ruota verso l'avatar (che ho notato nei tuoi commenti che stai cercando di ruotare l'avatar verso il bersaglio .... dovrebbe essere il contrario) ... sostanzialmente, provando ottenere un mob per affrontare il giocatore (avatar in una telecamera di terza persona). Il metodo GetRotation non dovrebbe sapere qualcosa sulle rotazioni attuali del target e dell'avatar, come in, pensi che il nuovoForward sia stato creato correttamente?
Marc

Se l'oggetto viene ridimensionato, ciò significa che il quaternione non ha una lunghezza unitaria, ciò significa che rotAxis non è normalizzato. Hai aggiunto il mio ultimo cambio di codice con la normalizzazione di rotAxis? Tuttavia, ti preghiamo di mostrare il tuo codice attuale e, per un caso di esempio in cui non funziona, inserisci anche i valori di: newForward rotAngle rotAxise the returned quaternion. Il codice per il mob sarà lo stesso, una volta che l'abbiamo fatto funzionare con l'avatar, sarà facile cambiare l'intestazione di qualsiasi oggetto.
Maik Semder,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.