Jak obrócić obiekt wokół osi wyrównanych do świata?

15

Mam Vector3, który ma kąt eulera dla każdej osi.

Zwykle, gdy chcę utworzyć macierz obrotu, skorzystam z funkcji takich jak D3DXMatrixRotationX przekazując odpowiedni kąt z mojego wektora obrotu powyżej i pomnożę macierze (ZXY), aby utworzyć ogólną macierz obrotu, która jest używana do utworzenia kompletnej macierzy transformacji obiektu.

Jednak ta metoda spowoduje wygenerowanie zestawu obrotów w przestrzeni obiektów. Oznacza to, że przekazanie wektora (90, 0, 90) do mojej metody spowoduje efektywny obrót w przestrzeni świata o (90, 90, 0).

Czy istnieje sposób, aby zawsze zapewnić, że każdy element mojego wektora obrotu spowoduje obrót wokół odpowiednich osi wyrównanych w przestrzeni świata?

EDYTOWAĆ:

To jest animacja tego, co się obecnie dzieje - chcę sposób na obracanie się wokół niebieskich osi, a nie czerwonych.

Kąty Eulera

EDYCJA 2:

Należy zauważyć, że nie szukam rozwiązania obejmującego kąty Eulera, ale po prostu sposób, w jaki mogę przedstawić transformację wielu obrotów wokół osi świata.

Składnia_
źródło
Co jest złego w trzykrotnym wywołaniu funkcji differnet i odfiltrowaniu części wektorów, których nie chcesz (ustawiając je na 0 przed wywołaniem funkcji)? W przeciwnym razie nie jestem pewien, co próbujesz osiągnąć.
TravisG,
Filtrujesz co? Wywołuję 3 oddzielne funkcje, a następnie mnożę je, aby utworzyć macierz transformacji. To jednak powoduje lokalną rotację.
Syntac_
Czy chcesz kąty Eulera lub obrót wokół osi świata? Zauważ, że z definicji kątów Eulera (np. En.wikipedia.org/wiki/Euler_angles ), tylko kąt alfa dotyczy wyłącznie osi świata. Pozostałe dwa kąty odnoszą się do osi nachylonych, które niekoniecznie pokrywają się z osiami świata.
DMGregory
1
Za pomocą kątów Eulera mnożysz wszystkie trzy macierze obrotu przed zastosowaniem następnie na wierzchołku. Jeśli M, N, O są macierzami obrotu, wynikiem operacji jest MNO v. Zaproponowałem zastosowanie każdej macierzy osobno: v1 = O v0, następnie v2 = N v1, a na koniec v3 = M v2. W ten sposób każda vi będzie znajdować się we współrzędnych światowych i wystarczy użyć macierzy obrotu dla bieżącej osi również we współrzędnych światowych.
dsilva.vinicius,
3
@ dsilva.vinicius Twoje oddzielne transformacje są dokładnie takie same jak kombinacje lub, inaczej mówiąc: MNO v == M * (N * (O v))
GuyRT 12.12.2013

Odpowiedzi:

1

W oparciu o twoje komentarze, wydaje się, że przechowujesz orientację obiektu jako zestaw kątów Eulera i zwiększasz / zmniejszasz kąty, gdy gracz obraca obiekt. Oznacza to, że masz coś takiego jak ten pseudokod:

// in player input handling:
if (axis == AXIS_X) object.angleX += dir;
else if (axis == AXIS_Y) object.angleY += dir;
else if (axis == AXIS_Z) object.angleZ += dir;

// in physics update and/or draw code:
matrix = eulerAnglesToMatrix(object.angleX, object.angleY, object.angleZ);

Jak zauważa Charles Beattie , ponieważ rotacje nie dojeżdżają do pracy, nie zadziała to zgodnie z oczekiwaniami, chyba że gracz obróci obiekt w tej samej kolejności, w której eulerAnglesToMatrix()zastosuje rotacje.

W szczególności rozważ następującą sekwencję obrotów:

  1. obróć obiekt o x stopni wokół osi X;
  2. obróć obiekt o y stopni wokół osi Y;
  3. obróć obiekt o - x stopni wokół osi X;
  4. obróć obiekt o - y stopni wokół osi Y.

W naiwnej reprezentacji kąta Eulera, jak zaimplementowano w powyższym pseudokodzie, obroty te zostaną anulowane, a obiekt powróci do pierwotnej orientacji. W prawdziwym świecie tak się nie dzieje - jeśli mi nie wierzysz, weź sześciokątną kostkę lub kostkę Rubika, pozwól x = y = 90 ° i wypróbuj sam!

Jak zauważyłeś we własnej odpowiedzi , rozwiązaniem jest przechowywanie orientacji obiektu jako macierzy obrotu (lub czwartorzędu) i aktualizowanie tej macierzy na podstawie danych wprowadzonych przez użytkownika. Oznacza to, że zamiast powyższego pseudokodu zrobiłbyś coś takiego:

// in player input handling:
if (axis == AXIS_X) object.orientation *= eulerAnglesToMatrix(dir, 0, 0);
else if (axis == AXIS_Y) object.orientation *= eulerAnglesToMatrix(0, dir, 0);
else if (axis == AXIS_Z) object.orientation *= eulerAnglesToMatrix(0, 0, dir);

// in physics update and/or draw code:
matrix = object.orientation;  // already in matrix form!

(Technicznie, ponieważ każda macierz obrotu lub kwaternion może być przedstawiony jako zbiór kątów Eulera, to jest można je stosować do przechowywania orientacji obiektu. Jednak fizycznie były zasada łączenia dwóch kolejnych obrotów, reprezentowane jako kątów Eulera, w pojedynczy obrót jest raczej skomplikowany i zasadniczo sprowadza się do przekształcenia obrotu w macierze / ćwiartki, pomnożenia ich, a następnie przekształcenia wyniku z powrotem w kąty Eulera).

Ilmari Karonen
źródło
Tak, masz rację, to było rozwiązanie. Wydaje mi się, że jest to nieco lepsza odpowiedź niż concept3d, ponieważ sprawia wrażenie, że potrzebna jest ćwiartka, ale to nieprawda. Dopóki zapisałem bieżący obrót jako matrycę, a nie trzy kąty Eulera, wszystko było w porządku.
Syntac_
15

Problem z obrotami polega na tym, że większość ludzi myśli o tym pod kątem kątów Eulera, ponieważ są one łatwe do zrozumienia.

Jednak większość ludzi zapomina o tym, że kąty Eulera to trzy kolejne kąty . Oznacza to, że obrót wokół pierwszej osi spowoduje, że następny obrót będzie związany z pierwszym pierwotnym obrotem, dlatego nie można niezależnie obrócić wektora wokół każdej z 3 osi przy użyciu kątów Eulera.

To bezpośrednio przekłada się na macierze, kiedy mnożymy dwie macierze, można pomyśleć o tym zwielokrotnieniu jako przekształceniu jednej macierzy w przestrzeń drugiej macierzy.

Ma to nastąpić przy 3 dowolnych obrotach sekwencyjnych, nawet przy użyciu czwartorzędów.

wprowadź opis zdjęcia tutaj

Chcę podkreślić fakt, że czwartorzędy nierozwiązaniem blokady kłębków. W rzeczywistości blokada drgań zawsze będzie mieć miejsce, jeśli reprezentowałeś kąty Eulera za pomocą czwartorzędów. Problem ten nie jest reprezentacją, problemem jest to , że kolejne etapy 3.

Rozwiązanie?

Rozwiązaniem dla niezależnego obracania wektora wokół 3 osi jest połączenie w jedną oś i jeden kąt, w ten sposób możesz pozbyć się kroku, w którym musisz wykonać sekwencyjne mnożenie. To skutecznie przełoży się na:

Moja macierz obrotu reprezentuje wynik obrotu wokół X, Y i Z.

zamiast interpretacji Eulera

Moja macierz obrotu reprezentuje obrót wokół X, a następnie Y, a następnie Z.

Aby to wyjaśnić, zacytuję z wikipedii Twierdzenie o rotacji Eulera:

Zgodnie z twierdzeniem o rotacji Eulera, każdy obrót lub sekwencja obrotu ciała sztywnego lub układu współrzędnych wokół stałego punktu jest równoważny pojedynczemu obrotowi o zadany kąt θ wokół stałej osi (zwanej osią Eulera), która biegnie przez stały punkt. Oś Eulera jest zazwyczaj reprezentowana przez wektor jednostkowy u →. Dlatego dowolny obrót w trzech wymiarach można przedstawić jako kombinację wektora u → i skalara ar. Czwartorzędy dają prosty sposób zakodowania tej reprezentacji kąta osi w czterech liczbach i zastosowania odpowiedniego obrotu do wektora pozycji reprezentującego punkt względem początku w R3.

Zauważ, że mnożenie 3 macierzy zawsze będzie reprezentowało 3 sekwencyjne obroty.

Teraz, aby połączyć obroty wokół 3 osi, musisz uzyskać jedną oś i pojedyncze kąty, które reprezentują obrót wokół X, Y, Z. Innymi słowy, musisz użyć reprezentacji Oś / Kąt lub czwartorzędu, aby pozbyć się kolejnych obrotów.

Zazwyczaj dokonuje się tego, zaczynając od orientacji początkowej (orientacja może być traktowana jako kąt osi), zwykle reprezentowany jako ćwiartka lub kąt osi, a następnie modyfikując tę ​​orintację, aby reprezentowała orientację docelową. Na przykład zaczynasz od czwartego kwartału tożsamości, a następnie obracasz o różnicę, aby osiągnąć orientację docelową. W ten sposób nie tracisz żadnego stopnia swobody.

concept3d
źródło
Oznaczono jako odpowiedź, ponieważ wydaje się wnikliwa.
Syntac_
Mam problem z ustaleniem, co próbujesz powiedzieć za pomocą tej odpowiedzi. Czy to po prostu „nie przechowuj orientacji obiektu jako kąta Eulera”? A jeśli tak, to dlaczego tak nie powiedzieć?
Ilmari Karonen
@IlmariKaronen Można by to jaśniej powiedzieć, ale myślę, że concept3d promuje reprezentację kąta osi; patrz sekcja 1.2.2 tego dokumentu, aby uzyskać informacje na temat zależności między kątem osi a ćwiartkami. Reprezentacja kąta osi jest łatwiejsza do wdrożenia z powyższych powodów, nie cierpi z powodu blokady gimbala i (przynajmniej dla mnie) jest tak łatwa do zrozumienia jak kąty Eulera.
NauticalMile
@ concept3d, to bardzo interesujące i bardzo podoba mi się twoja odpowiedź. Brakuje mi jednak jednej rzeczy: ludzie wchodzą w interakcje z komputerem za pomocą klawiatury i myszy, jeśli myślimy o myszy, to mówimy o delcie myszy xiy. Jak przedstawić te delty x, y za pomocą pojedynczego czwartorzędu, którego możemy użyć do wygenerowania macierzy obrotu, na przykład do zmiany orientacji obiektu?
gmagno
@gmagno podejście polega zwykle na rzutowaniu ruchem myszy na obiekty lub scenę i obliczaniu delt w tej przestrzeni, robisz to, rzucając promień i obliczając przecięcie. Poszukaj odlewania promieniami, projektu i projektu, jestem szorstki w szczegółach, ponieważ od lat nie pracuję w CG. mam nadzieję, że to pomaga.
concept3d
2

Przełączanie kombinacji rotacji z przestrzeni obiektów na przestrzeń świata jest trywialne: wystarczy odwrócić kolejność stosowania rotacji.

W twoim przypadku zamiast mnożenia macierzy Z × X × Ywystarczy tylko wykonać obliczenia Y × X × Z.

Uzasadnienie tego można znaleźć na Wikipedii: konwersja między obrotami wewnętrznymi i zewnętrznymi .

sam hocevar
źródło
Jeśli to prawda, to poniższe stwierdzenie ze źródła nie byłoby prawdziwe, ponieważ obroty byłyby inne: „Każdy obrót zewnętrzny jest równoważny wewnętrznemu obróceniu o te same kąty, ale z odwróconą kolejnością obrotu elementarnego i odwrotnie . ”
Syntac_
1
Nie widzę tu sprzeczności. Zarówno moja odpowiedź, jak i to stwierdzenie są prawdziwe. I tak, wykonywanie obrotów w przestrzeni obiektów i w przestrzeni świata daje różne obroty; właśnie o to chodzi, prawda?
sam hocevar,
To oświadczenie mówi, że zmiana kolejności zawsze spowoduje taki sam obrót. Jeśli jedno zamówienie generuje niewłaściwy obrót, drugie zlecenie również, co oznacza, że ​​nie jest to rozwiązanie.
Syntac_
1
Źle się odczytujesz. Zmiana kolejności nie powoduje tego samego obrotu. Zmiana kolejności i przełączanie z obrotów wewnętrznych na obroty zewnętrzne powoduje taki sam obrót.
sam hocevar,
1
Nie sądzę, że rozumiem twoje pytanie. Twój GIF pokazuje obrót o około 50 stopni wokół Z(przestrzeń obiektu), następnie 50 stopni wokół X(przestrzeń obiektu), a następnie 45 stopni wokół Y(przestrzeń obiektu). Jest to dokładnie to samo, co obrót o 45 stopni dookoła Y( przestrzeń świata ), następnie o 50 stopni dookoła X( przestrzeń świata ), a następnie o 50 stopni dookoła Z( przestrzeń świata ).
sam hocevar
1

Podam moje rozwiązanie jako odpowiedź, dopóki ktoś nie wyjaśni, dlaczego to działa.

Przy każdym renderowaniu odbudowywałem czwartorzęd przy użyciu kątów zapisanych w wektorze rotacji, a następnie stosując czwartorzęd do mojej ostatecznej transformacji.

Jednak, aby utrzymać go wokół osi świata, musiałem zachować czwartorzęd we wszystkich ramkach i obracać obiekty tylko przy użyciu różnicy kątów, tj.

// To rotate an angle around X - note this is an additional rotation.
// If currently rotated 90, apply this function with angle of 90, total rotation = 180.
D3DXQUATERNION q;
D3DXQuaternionRotation(&q, D3DXVECTOR3(1.0f, 0.0f, 0.0f), fAngle);
m_qRotation *= q; 

//...

// When rendering rebuild world matrix
D3DXMATRIX mTemp;
D3DXMatrixIdentity(&m_mWorld);

// Scale
D3DXMatrixScaling(&mTemp, m_vScale.x, m_vScale.y, m_vScale.z);
m_mWorld *= mTemp;

// Rotate
D3DXMatrixRotationQuaternion(&mTemp, m_qRotation);
m_mWorld *= mTemp;

// Translation
D3DXMatrixTranslation(&mTemp, m_vPosition.x, m_vPosition.y, m_vPosition.z);
m_mWorld *= mTemp;

(Pełne dla czytelności)

Myślę, że dsilva.vinicius próbował dojść do tego punktu.

Składnia_
źródło
1

Będziesz musiał zapisać kolejność obrotów.

Rotating around x 90 then rotate around z 90 !=
Rotating around z 90 then rotate around x 90.

Przechowuj swoją bieżącą macierz obrotu i przedwcześnie każdy obrót, jak tylko się pojawi.

Charles Beattie
źródło
0

Oprócz odpowiedzi @ concept3d możesz użyć 3 zewnętrznych macierzy obrotu, aby obracać się wokół osi we współrzędnych świata. Cytowanie z Wikipedii :

Obroty zewnętrzne to obroty elementarne, które występują wokół osi stałego układu współrzędnych xyz. System XYZ obraca się, a xyz jest stały. Zaczynając od nakładającego się XYZ xyz, można zastosować kompozycję trzech zewnętrznych obrotów, aby osiągnąć dowolną orientację docelową dla XYZ. Kąty Eulera lub Tait Bryana (α, β, γ) są amplitudami tych rotacji elementarnych. Na przykład orientację docelową można osiągnąć w następujący sposób:

System XYZ obraca się wokół osi Z o α. Oś X znajduje się teraz pod kątem α względem osi X.

System XYZ obraca się ponownie wokół osi X o β. Oś Z znajduje się teraz pod kątem β względem osi Z.

System XYZ obraca się po raz trzeci wokół osi Z o γ.

Macierze rotacji można wykorzystać do przedstawienia sekwencji rotacji zewnętrznych. Na przykład,

R = Z (γ) Y (β) X (α)

reprezentuje kompozycję rotacji zewnętrznych wokół osi xyz, jeśli jest używana do wstępnego zwielokrotnienia wektorów kolumnowych, natomiast

R = X (α) Y (β) Z (γ)

reprezentuje dokładnie ten sam skład, gdy jest używany do pomnożenia wektorów wierszowych.

Zatem potrzebujesz odwrócić kolejność obrotów w stosunku do tego, co zrobiłbyś przy użyciu wewnętrznych (lub lokalnych) obrotów. @Syntac poprosił o obrót zxy, więc powinniśmy wykonać obrót zewnętrzny yxz, aby osiągnąć ten sam wynik. Kod jest poniżej:

Wyjaśnienie wartości macierzy tutaj .

// Init things.
D3DXMATRIX *rotationMatrixX = new D3DXMATRIX();
D3DXMATRIX *rotationMatrixY = new D3DXMATRIX();
D3DXMATRIX *rotationMatrixZ = new D3DXMATRIX();
D3DXMATRIX *resultRotationMatrix0 = new D3DXMATRIX();
D3DXMATRIX *resultRotationMatrix1 = new D3DXMATRIX();

D3DXMatrixRotationX(rotationMatrixX, angleX);
D3DXMatrixRotationY(rotationMatrixY, angleY);
D3DXMatrixRotationZ(rotationMatrixZ, angleZ);

// yx extrinsic rotation matrix
D3DXMatrixMultiply(resultRotationMatrix0, rotationMatrixY, rotationMatrixX);
// yxz extrinsic rotation matrix
D3DXMatrixMultiply(resultRotationMatrix1, resultRotationMatrix0, rotationMatrixZ);

D3DXVECTOR4* originalVector = // Original value to be transformed;
D3DXVECTOR4* transformedVector = new D3DXVECTOR4();

// Applying matrix to the vector.
D3DXVec4Transform(transformedVector, originalVector, resultRotationMatrix1);

// Don't forget to clean memory!

Ten kod jest dydaktyczny, a nie optymalny, ponieważ można ponownie użyć kilku macierzy D3DXMATRIX.

dsilva.vinicius
źródło
1
przepraszam stary, to nie jest poprawne. mnożenie macierzy / wektora jest asocjacyjne. jest to dokładnie to samo, co połączone mnożenie macierzy.
concept3d
Masz rację. Połączyłem pojęcia rotacji zewnętrznej i wewnętrznej.
dsilva.vinicius,
Naprawię tę odpowiedź.
dsilva.vinicius
Odpowiedź jest już naprawiona.
dsilva.vinicius