Ukierunkowanie modelu na cel

28

Mam dwa obiekty (cel i gracz), oba mają pozycję (wektor3) i rotację (czwartorzęd). Chcę, aby cel się obrócił i był skierowany w stronę gracza. Cel, gdy coś strzela, powinien trafić prosto w gracza.

Widziałem wiele przykładów slerowania dla gracza, ale nie chcę przyrostowej rotacji, cóż, przypuszczam, że byłoby to w porządku, o ile mogę sprawić, że slerp będzie w 100% i tak długo, jak faktycznie zadziała.

FYI - Jestem w stanie użyć pozycji i rotacji do robienia wielu innych rzeczy i wszystko działa świetnie, z wyjątkiem tego ostatniego kawałka, którego nie mogę rozgryźć.

Próbki kodu są uruchamiane w klasie celu, pozycja = pozycja celu, awatar = gracz.

EDYTOWAĆ

Im teraz używa kodu c # Maik, który dostarczył i działa świetnie!

Marc
źródło
4
Jeśli robisz 100% slerp, nie używasz slerp. Po prostu ustawiasz obrót na 0*(rotation A) + 1*(rotation B)- innymi słowy, ustawiasz obrót na obrót B na dłuższą metę. Slerp służy tylko do określenia, jak powinien wyglądać obrót (0% <x <100%) na drodze pomiędzy.
doppelgreener
Ok, ma to sens, ale cel wciąż nie obraca się całkowicie w kierunku odtwarzacza ... „daleko” z tym kodem.
Marc

Odpowiedzi:

20

Jest na to więcej niż jeden sposób. Możesz obliczyć bezwzględną orientację lub obrót względem swojego awatara, co oznacza, że ​​twoja nowa orientacja = awatarOrientacja * q. Oto ten ostatni:

  1. Oblicz oś obrotu, biorąc iloczyn wektorowy wektora jednostki awatara do przodu i wektora jednostki od awatara do celu, nowego wektora do przodu:

    vector newForwardUnit = vector::normalize(target - avatarPosition);
    vector rotAxis = vector::cross(avatarForwardUnit, newForwardUnit);
  2. Oblicz kąt obrotu za pomocą iloczynu punktowego

    float rotAngle = acos(vector::dot(avatarForwardUnit, newForwardUnit));
  3. Utwórz czwartorzęd przy użyciu rotAxis i rotAngle i pomnóż go przez bieżącą orientację awatara

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

Jeśli potrzebujesz pomocy w znalezieniu bieżącego wektora awataru, dane wejściowe dla 1. po prostu strzelaj :)

EDYCJA: Obliczenie orientacji absolutnej jest w rzeczywistości nieco łatwiejsze, użyj wektora do przodu matrycy tożsamości zamiast wektora do przodu awatarów jako danych wejściowych dla 1) i 2). I nie mnoż go w 3), zamiast tego użyj go bezpośrednio jako nowej orientacji:newRot = q


Ważne, aby pamiętać: Rozwiązanie ma 2 anomalie spowodowane charakterem produktu krzyżowego:

  1. Jeśli wektory przednie są równe. Rozwiązaniem jest po prostu zwrócenie czwartorzędu tożsamości

  2. Jeśli wektory wskazują dokładnie w przeciwnym kierunku. Rozwiązaniem jest tutaj utworzenie czwartorzędu przy użyciu awatarów w górę osi jako osi obrotu i kąta 180,0 stopni.

Oto implementacja w C ++, która zajmuje się tymi przypadkowymi przypadkami. Konwersja do C # powinna być łatwa.

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

EDYCJA: Poprawiona wersja kodu XNA 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
źródło
Ok, dałem temu szansę, jak widać po mojej edycji w pytaniu, pod warunkiem kodu. Nie bardzo wiem, na czym polega problem. dane wejściowe aib są wektorami do przodu, a przynajmniej tak powinno być.
Marc
@Marc widzi moją poprawioną wersję twojego kodu XNA w mojej odpowiedzi. Wystąpiły 2 problemy: 1) obliczenie nowego wektora do przodu było niepoprawne, należy znormalizować AvatarPosition - TargetPosition 2) rotAxis należy znormalizować po cross-produkcie w GetRotation
Maik Semder
@Marc, 2 drobne zmiany: 3) źródło i dest są już znormalizowane, nie ma potrzeby normalizacji ich ponownie w GetRotation 4) nie testuj absolutnej wartości 1 / -1 w GetRotation, raczej użyj pewnej tolerancji, użyłem 0,000001f
Maik Semder
Hmm, to wciąż nie działa. To samo skalowanie dzieje się z modelem, a cel nie obraca się w kierunku awatara (co zauważyłem w twoich komentarzach, że próbujesz obrócić awatara w kierunku celu ... powinno być na odwrót) ... w zasadzie, próbując aby zmusić moba do stawienia czoła graczowi (awatar w aparacie trzeciej osoby). Czy metoda GetRotation nie powinna wiedzieć czegoś o bieżących obrotach celu i awatara, czy jak myślisz, czy nowy Forward jest tworzony poprawnie?
Marc
Jeśli obiekt jest skalowany, oznacza to, że ćwiartka nie ma długości jednostkowej, co oznacza, że ​​rotAxis nie jest znormalizowany. Czy dodałeś moją ostatnią zmianę kodu z normalizacją rotAxis? Proszę jednak pokazać aktualny kod i na przykład, w przypadku gdy nie działa, proszę również podać wartości: newForward rotAngle rotAxisi returned quaternion. Kod moba będzie taki sam, gdy tylko będziemy pracować z awatarem, łatwo będzie zmienić nagłówek dowolnego obiektu.
Maik Semder