Arbitrary Rotation about a Sphere

12

Koduję mechanikę, która pozwala użytkownikowi poruszać się po powierzchni kuli. Pozycja na kuli jest obecnie zapisywana jako thetai phi, gdzie thetajest kąt między osią z i rzutem xz bieżącej pozycji (tj. Obrót wokół osi y), i phijest kątem od osi y do pozycji. Wyjaśniłem, że źle, ale to jest w istocie theta = yaw,phi = pitch

Vector3 position = new Vector3(0,0,1);
position.X = (float)Math.Sin(phi) * (float)Math.Sin(theta);
position.Y = (float)Math.Sin(phi) * (float)Math.Cos(theta);
position.Z = (float)Math.Cos(phi);
position *= r;

Uważam, że jest to poprawne, jednak mogę się mylić. Muszę być w stanie poruszać się w dowolnym, pseudo-dwuwymiarowym kierunku wokół powierzchni kuli na początku przestrzeni świata o promieniu r. Na przykład przytrzymanie Wpowinno poruszać się po kuli w kierunku do góry w stosunku do orientacji gracza.

Uważam, że powinienem używać czwartorzędu do przedstawienia pozycji / orientacji na kuli, ale nie mogę wymyślić właściwego sposobu na zrobienie tego. Geometria sferyczna nie jest moją mocną stroną.

Zasadniczo muszę wypełnić następujący blok:

public void Move(Direction dir)
{   
    switch (dir)
    {
        case Direction.Left:
            // update quaternion to rotate left
            break;
        case Direction.Right:   
            // update quaternion to rotate right
            break;
        case Direction.Up:
            // update quaternion to rotate upward
            break;
        case Direction.Down:
            // update quaternion to rotate downward
            break;
    }
}
azz
źródło
Co powinno się stać, jeśli gracz dotrze do biegunów? Zauważyłem, że napisałeś „w górę”, czy dosłownie masz na myśli „w górę” (to znaczy z dala od powierzchni kuli), „na wprost” lub „w kierunku bieguna północnego” (dwa ostatnie są takie same, jeśli gracz nie można zmienić ich orientacji, a „przed nimi” lub „w górę” na ekranie zawsze znajduje się północ)?
Martin Sojka
Być może było źle sformułowane. Gracz nie powinien opuszczać powierzchni kuli i nie powinien być świadomy osi głównej. Kiedy więc poruszasz się „w górę”, poruszasz się wzdłuż powierzchni kuli w kierunku do góry w stosunku do orientacji gracza. np. jeśli jesteś przy (r, 0,0) i naciskasz w górę, pójdziesz w kierunku bieguna z +, ale jeśli będziesz kontynuował, powinieneś się zawinąć i iść dalej.
azz
Pozostaje jeszcze jedno pytanie: czy gracz może zmienić orientację (obrócić „w lewo” i „w prawo”)?
Martin Sojka
Być może lepszy przykład tego, co zamierzam: gracz (1,1,1)trzymający lewy obracałby się wokół kuli, przechodząc (~1.2,0,~-1.2)wtedy (-1,-1,-1), potem, potem z (~-1.2,0,~1.2)powrotem (1,1,1).
azz
1
Jeśli zamierzasz zawsze śledzić, thetaa phiwraz z aktualizacją pozycji, problem staje się niepotrzebnie złożony. Znacznie łatwiej jest po prostu obliczyć 2 osie obrotu każdej klatki (jedna z nich (odchylenie) nigdy się nie zmienia) i Vector3.Transormwokół kuli. Uprości to Twój problem, ale spowoduje rozłączenie się z phi& theta.
Steve H

Odpowiedzi:

5

W rzeczywistości okazuje się, że nie możesz mieć tego „na dwa sposoby”: jeśli twoim celem jest brak poczucia „absolutnej orientacji” na kuli (to znaczy, jeśli gracze nie zawsze są np. Skierowani w stronę biegunów ), musisz mieć pojęcie orientacji gracza. Wynika to z tego, że wbrew intuicji, która może sugerować, ruch na kuli nie jest dokładnie taki jak ruch na płaszczyźnie, nawet lokalnie (całkiem); wewnętrzna krzywizna kuli oznacza, że ​​gracze mogą wykonywać działania, które same się obracają!

Dla najbardziej ekstremalnego przykładu tego, o czym mówię, wyobraź sobie, że gracz zaczyna od punktu na równiku (dla wygody wyobrażamy sobie tarczę zegara odwzorowaną na równiku z góry i ustawiasz gracza na godzinie 6 ), przodem do „góry” - to znaczy w kierunku bieguna północnego. Załóżmy, że gracz idzie do bieguna północnego; wtedy będą skierowani bezpośrednio w stronę punktu o godzinie 12. Teraz pozwól graczowi przejść bezpośrednio w prawo, z Bieguna Północnego z powrotem do równika; skończą w punkcie o trzeciej godzinie - ale ponieważ ich twarze nie zmieniają się, gdy poruszą się w prawo(chodzi o to, że ich twarze nie zmieniają się bez względu na to, jak się poruszają), wciąż będą musieli stawić czoła punktowi o godzinie 12 - teraz są zwrócone wzdłuż równika! Teraz pozwól im przejść „do tyłu” z powrotem do punktu początkowego (godzina szósta); wtedy będą nadal zwrócone w kierunku równika, więc będą skierowane w stronę punktu 3-go - po prostu poruszanie się po kuli bez zmiany ich „osobistej” orientacji spowodowało, że obrócili się z kierunku w kierunku bieguna północnego, aby skierowane wzdłuż równika! W pewnym sensie jest to rozwinięcie starego żartu, „łowca porusza się milę na południe, milę na zachód, a potem milę na północ” - ale tutaj wykorzystujemy krzywiznę kuli, aby dokonać zmiany kierunku. Zauważ, że ten sam efekt nadal występuje nawet w znacznie mniejszych skalach;

Na szczęście, jak zauważyliście, czwartorzędy radzą sobie z tą sytuacją; ponieważ czwartorzęd reprezentuje dowolny obrót, skutecznie reprezentuje on dowolny „punkt plus orientacja” na kuli: wyobraź sobie, że zaczynasz od „triaxis” na początku i dajesz mu dowolny dowolny obrót, a następnie przesuwasz jedną jednostkę w dowolnym kierunku obracanych osi ” Punkty na osi Z; mała myśl przekona cię, że to prowadzi cię do punktu na sferze jednostkowej z pewną „orientacją” (tj. pewnym ułożeniem osi X i Y twojej trójosiowej) i że możesz dostać się do każdego punktu + orientacja na sferę jednostkową w ten sposób (wystarczy przypisać oś Z do punktu wzdłuż linii od początku przez punkt na kuli, a następnie przetransportować triaxy z powrotem do początku wzdłuż tej linii). Co więcej, ponieważ mnożenie czwartorzędów odpowiada składowi rotacji, każdą z opisanych operacji można przedstawić, mnożąc swoją „aktualną orientację” przez odpowiednio wybrany czwartorzędowy: konkretnie, ponieważ czwartorzędowy (jednostka) (qx, qy, qz, qw) oznacza „obracaj się wokół osi (qx, qy, qz) o arccos (qw)”, a następnie (w zależności od konkretnego wyboru układu współrzędnych i pozwalając c_a być cos (alfa) i s_a być sin (alfa)) dwa z trzy czwarte M_x = (s_a, 0, 0, c_a), M_y = (0, s_a, 0, c_a), a M_z = (0, 0, s_a, c_a) będą reprezentować „obrót (tj. ruch) w kierunku I „jestem teraz skierowany w stronę alfa” i „obracasz się w kierunku prostopadłym do tego, w którym aktualnie jestem skierowany w stronę alfa”. (Trzeci z tych czwartorzędów będzie reprezentował „obróć moją postać wokół własnej osi”Cur_q = M_x * Cur_qjeśli gracz naciskał, lub Cur_q = M_y * Cur_qjeśli gracz naciskał w prawo (lub coś w rodzaju, Cur_q = M_yinv * Cur_qjeśli gracz naciskał w lewo, gdzie M_yinv jest „odwrotnością” czwartorzędu M_y, reprezentującą obrót w drugą stronę). Pamiętaj, że musisz uważać, po której „stronie” zastosujesz obrót, czy to przedwcześnie, czy później; szczerze mówiąc, najłatwiej jest to rozwiązać metodą prób i błędów, wypróbowując oba mnożenia i sprawdzając, które z nich działają.

Przejście od zaktualizowanego czwartorzędu do punktu na kuli (i do orientacji twojej postaci) jest również stosunkowo proste: zgodnie z ostatnim akapitem wszystko, co musisz zrobić, to użyć czwartorzędu na podstawie wektorów bazowych (1, 0,0), (0,1,0) i (0,0,1) ramki za pomocą operacji „obróć wektor o ćwiartkę” v → qvq -1 (gdzie mnożenia są mnożnikami czwartorzędu i identyfikujemy wektor v = (x, y, z) z „zdegenerowanym czwartorzędem” (x, y, z, 0)). Na przykład pozycję na kuli jednostkowej uzyskuje się po prostu przekształcając wektor z: pos = (qx, qy, qz, qw) * (0, 0, 1, 0) * (-qx, -qy, -qz, qw) = (qx, qy, qz, qw) * (qy, -qx, qw, qz) = (2 (qy * qw + qz * qx), 2 (qz * qy-qw * qx), (qz ^ 2 + qw ^ 2) - (qx ^ 2 + qy ^ 2), 0), więc(2(qy*qw+qz*qx), 2(qz*qy-qw*qx), (qz^2+qw^2)-(qx^2+qy^2))byłyby to współrzędne „transformowanego” użytkownika w sferze jednostkowej (i aby uzyskać współrzędne w dowolnej sferze, po prostu pomnożymy je przez promień sfery); podobne obliczenia działają dla innych osi, aby zdefiniować np. kierunek użytkownika.

Steven Stadnicki
źródło
Dokładnie to, co chcę osiągnąć. Po prostu nie mogłem wymyślić właściwego sposobu na uzyskanie pozycji z czwartorzędu orientacyjnego. Korzystając z tego, co podałeś, mogę napisać Move()procedurę, ale czy chciałbym uzyskać znormalizowaną oś (tj. Moją pozycję), czy po prostu bym wziął (sin(qx),sin(qy),sin(qw)) * r?
azz
@ Nie do końca - zaktualizuję mój post o szczegóły, ale krótka wersja jest taka, że ​​używasz czwartorzędu do transformacji wektora jednostkowego, np. (0,0,1), zwykłym v -> qvq <sup> -1 </sup> operacja; fakt, że transformujesz prosty wektor oznacza, że ​​(naturalnie) istnieje tutaj skrót, ale końcowe współrzędne są kwadratowe w wartościach twojego czwartorzędu, a nie liniowe.
Steven Stadnicki
1

Myślę, że chcesz czegoś podobnego do tego http://www.youtube.com/watch?v=L2YRZbRSD1k

Opracowałem to dla 48-godzinnego gamejamu ... możesz załadować kod tutaj ... http://archive.globalgamejam.org/2011/evil-god

Użyłem czegoś podobnego do twojego kodu, aby uzyskać współrzędne 3D ... ale obróciłem planetę i gracz był w tej samej pozycji, myślę, że jesteś zainteresowany ruchem stworzenia, to:

    // To add movement
    protected override void LocalUpdate(float seconds)
    {
        Creature.Alfa += Direction.X * seconds * Speed;
        Creature.Beta += Direction.Y * seconds * Speed;            
    }


    // To calculate position
       World.Planet.GetCartesian(Alfa, Beta, out Position); // as you do
       Matrix PositionMatrix = Matrix.CreateTranslation(Position) * World.Planet.RotationMatrix;           
       LastPositionAbsolute = PositionAbsolute;
       Vector3 Up = PositionAbsolute = Vector3.Transform(Vector3.Zero, PositionMatrix);           
       Up.Normalize();
       // This is to add and offset to the creature model position
       PositionAbsolute += Up * 8;  
      // calculate new forward vector if needed

       if ((PositionAbsolute - LastPositionAbsolute).Length() > 0.1f) {
           Forward = PositionAbsolute - LastPositionAbsolute;
           Forward.Normalize();
       }

       // Calculate the world transform with position, forward vector and up vector
       Matrix LocalWorld = Matrix.CreateWorld(PositionAbsolute, Forward, Up); 

       Transform = Matrix.CreateScale(Scale * ScaleFactor) * LocalWorld;
Blau
źródło