2015-06-27 36 views
23

Pracuję nad symulatorem ruchu z 2 DOF (pitch & roll). Czytam matrycę transformacji z gry i potrzebuję uzyskać kąty i przesłać sprzęt do napędu silników. Ponieważ kąty Eulera mają osobliwości, nie mogę ich naprawdę użyć. Zachowuje się tak:Get pitch and roll z macierzy bez osobliwości

kiedy należy to tak:

przygotowałem przykład online, aby lepiej pokazać problem:

// Get euler angles from model matrix 
var mat = model.matrix; 
mat.transpose(); 

var e = new THREE.Euler(); 
e.setFromRotationMatrix(mat, 'XZY'); 
var v = e.toVector3(); 

var pitch = -v.z; 
var roll = -v.x; 

http://jsfiddle.net/qajro0ny/3/

O ile rozumiem, są tu dwa problemy.

  1. Na symulatorze nie ma osi obrotu.
  2. Nawet jeśli istniała oś odchylenia, silniki po prostu nie zachowują się jak grafika komputerowa, tj. Potrzebują czasu, aby dojść do pozycji docelowej.

Przeczytałem o blokadzie kardana, a nawet zaimplementowanym filtrem Eulera, ale to nie działało zgodnie z oczekiwaniami. Większość porad związanych z blokadą kardana polegała na użyciu kwateroruchów, ale nie mogę prowadzić silnika fizycznego z kwaternionem (lub czy mogę?).

Kolejność osi nie ma tu większego znaczenia, ponieważ zmiana spowoduje przeniesienie pojedynczej pozycji z jednej osi na drugą.

Muszę sobie z tym poradzić w inny sposób.

Próbowałem wektory liczby mnialnej przez matrycę, a następnie za pomocą produktu krzyżowego i punktowego uzyskać kąty, ale to też się nie udało. Sądzę, że powinno być również odwzorowanie osi, aby to naprawić, ale nie mogłem tego rozgryźć. Ale coś mi mówi, że jest to właściwy sposób na zrobienie tego. To było coś takiego: http://jsfiddle.net/qajro0ny/53/

Potem wpadłem na inny pomysł. Wiem, że wcześniejsze stanowisko, więc może wykonać następujące czynności: matryca

  1. Konwersja do quaternion
  2. różnicę Compute między obecnym a poprzednim kwaterniony
  3. Convert wynikającą kwaternion do kąty Eulera
  4. Dodaj te kąty na boisku statycznej zmienne przechylenia i odchylenia.

Więc spróbowałem i ... zadziałało! Brak osobliwości w żadnym z kierunków, doskonały obrót o 360 stopni w skoku, toczyć i odchylać. Idealne rozwiązanie! Z wyjątkiem ... nie jest. Ramki nie zostały zsynchronizowane, więc po chwili kąty były odbiegające od tego, czym powinny być. Myślałem o jakimś mechanizmie synchronizacji, ale pomyślałem, że to nie jest właściwa droga.

Wyglądało to tak: http://jsfiddle.net/qajro0ny/52/

I z tą samą logiką, lecz bezpośrednio z macierzy: http://jsfiddle.net/qajro0ny/54/

Szukałem wysokiej i niskiej internetowych, czytałem dziesiątki dokumentów i inne pytania/wiadomości i po prostu nie mogę uwierzyć, że w moim przypadku nic tak naprawdę nie działa.

I nie może zrozumieć lub coś przeoczyć, więc o to wszystko znalazłem i próbowałem:

Linki: http://pastebin.com/3G0dYLvu

Kod: http://pastebin.com/PiZKwE2t (Wrzuciłem to wszystko razem, więc jest to brudny)

Muszę czegoś nie mieć lub patrzę na to pod niewłaściwym kątem.

Odpowiedz

7

Jeśli wiesz, że matryca zawiera tylko dwa obroty (w podanej kolejności T = RZ * RX), to można zrobić coś jak poniżej:

Lokalna oś x nie wpływa drugiej rotacji. Możesz więc obliczyć pierwszy kąt tylko z lokalną osią x. Następnie można usunąć ten obrót z matrycy i obliczyć pozostały kąt z jednego z pozostałych dwóch osiach:

function calculateAngles() { 
    var mat = model.matrix; 

    //calculates the angle from the local x-axis 
    var pitch = Math.atan2(mat.elements[1], mat.elements[0]); 

    //this matrix is used to remove the first rotation 
    var invPitch = new THREE.Matrix4(); 
    invPitch.makeRotationZ(-pitch); 

    //this matrix will only contain the roll rotation 
    // rollRot = RZ^-1 * T 
    //   = RZ^-1 * RZ * RX 
    //   = RX 
    var rollRot = new THREE.Matrix4(); 
    rollRot.multiplyMatrices(invPitch, mat); 

    //calculate the remaining angle from the local y-axis 
    var roll = Math.atan2(rollRot.elements[6], rollRot.elements[5]); 

    updateSimAngles(pitch, roll); 
} 

To oczywiście nie tylko działać, jeśli dana matryca odpowiada wymaganiom. Nie może zawierać trzeciej rotacji. W przeciwnym razie prawdopodobnie będziesz musiał znaleźć nieliniowe rozwiązanie najmniejszych kwadratów.

+0

Dzięki, ale niestety moja macierz zawiera trzeci obrót. Czy mógłbyś bardziej rozwinąć nieliniowe rozwiązanie najmniejszych kwadratów? Czytałem o algorytmie Levenberga-Marquardta, ale nie mam pojęcia, jak go wykorzystać w moim problemie. – AdrianEddy

+0

Levenberg Marquardt to prawdopodobnie dobry wybór. Zasadniczo chcesz znaleźć minimizer '|| T - RZ (wysokość) * RX (rolka) || _2' powyżej 'wysokości' i 'toczyć'. Większość bibliotek umożliwia podłączenie tej definicji w mniej lub bardziej bezpośredni sposób. Ale nie znam żadnej biblioteki JavaScript dla tego. Poza tym, zawsze otrzymasz orientację przewracającą, jeśli trzeci obrót będzie stosunkowo duży. Możesz spróbować ominąć to za pomocą warunków regularyzacji (różnica ostatniego i obecnego rozwiązania). Ale nie jestem pewien, czy to się spełni. –

2

Twój pomysł na wykorzystanie delt obrotu jest całkiem obiecujący.

Nie jestem do końca pewien, co masz na myśli, mówiąc o "klatkach nie synchronizowano". Mogę sobie wyobrazić, że twoje obliczenia z kwaternionami mogą nie być w 100% dokładne (czy korzystasz z liczb zmiennoprzecinkowych?), Co powoduje małe błędy, które gromadzą się i ostatecznie powodują ruchy asynchroniczne.

Chodzi o to, że do przedstawienia rotacji należy używać jednostek kwaternych. Możesz to zrobić w modelu teoretycznym, ale jeśli reprezentujesz kwaternion przez 4 liczby zmiennoprzecinkowe, twoje kwaterunki w większości przypadków nie będą kwaterunkami jednostek, ale tylko będą naprawdę blisko (ich norma to 1+e dla niektórych małych - i być może ujemnych - wartość e). Jest to w porządku, ponieważ nie zauważysz tych niewielkich różnic, jednak jeśli rzucasz tonami operacji w swoich kwaterach (które robisz, ciągle roatingując swój model i obliczając delty), te małe błędy będą się kumulować. W związku z tym musisz renormalizować swoje kwaterunki, aby utrzymać je jak najbliżej jednostek kwaterunkowych, tak aby twoje obliczenia - zwłaszcza konwersja z powrotem na kąty Eulera - pozostały (prawie) dokładne.

+0

Nawet jeśli operuję wyłącznie na 64-bitowych debelach (w tym początkowej macierzy z gry) i normalizujących kwaterach po każdym obliczeniu, kąty są nadal znacznie wyłączone po kilku minutach ciągłego ruchu. Czytam matrycę 50 razy na sekundę. Próbuję teraz stworzyć jakieś rozwiązanie hybrydowe, używając kwaternionów do śledzenia właściwej orientacji, ale obliczając ostatnie kąty z kątów Eulera. – AdrianEddy

+0

Możesz to zobaczyć w akcji w tym skrzypcach: http://jsfiddle.net/qajro0ny/52/. Może gdzieś popełniłem błąd? Oczywiście jest to tylko przykład w JS, ale zachowanie jest prawie takie samo w C++ – AdrianEddy

2

Dodałem moje dwa centy w http://jsfiddle.net/qajro0ny/58/, zasadniczo stosując prace z http://blogs.msdn.com/b/mikepelton/archive/2004/10/29/249501.aspx, które natknąłem się na kilka lat temu. To w zasadzie sprowadza się do

var a = THREE.Math.clamp(-mat.elements[6], -1, 1); 
var xPitch = Math.asin(a); 
var yYaw = 0; 
var zRoll = 0; 
if (Math.cos(xPitch) > 0.0001) 
{ 
    zRoll = -Math.atan2(mat.elements[4], mat.elements[5]); 
    yYaw = -Math.atan2(mat.elements[2], mat.elements[10]); 
} 
else 
{ 
    zRoll = -Math.atan2(-mat.elements[1], mat.elements[0]); 
} 

zauważyłem, że przyjęto odwzorowanie yaw, pitch, rolki do osi (Y, X, Z), jak stosuje się w DirectX leworęczny układu współrzędnych się różni od jednego używasz OpenGL/WebGL, więc może trzeba to ostatecznie rozwiązać.

Mam nadzieję, że to pomoże.

+0

Świetny przykład BTW. – mkoertgen

1

Wyobrażam sobie, że faktycznie można napędzać silniki z kwaternionami. Pamiętaj, że kwaternion reprezentuje oś obrotu (x, y, z) i kąt. Przypuszczam, że masz trzy silniki (dla każdej osi), których prędkość obrotowa można skalować. Teraz, skalując prędkość obrotową tych trzech silników, można ustawić konkretną oś obrotu, a dzięki dokładnemu pomiarowi czasu można ustawić określony kąt obrotu. Powinno być łatwiejsze niż konwersja na delty kątów Eulera.

+0

Chodzi o to, że nie mam trzeciego silnika, a nie [tego projektu] (https://www.youtube.com/watch?v=tVhFCJWn_xw). W każdym razie musiałbym anulować trzecią rotację. Ale gdybym mógł niezawodnie anulować trzeci obrót, mogłem z łatwością obliczyć kąty Eulera. – AdrianEddy

+0

Ok. Załóżmy więc, że masz "kwaterę" kwaternioną - rotację, którą chcesz symulować. Masz dwa silniki, które obracają się w niektórych osiach. Można zdefiniować dwa kwaternionie odpowiadające tym znanym osiom, ale o nieznanych kątach. Niż mnożenie tych kwater powinno być jak najbliżej miejsca docelowego, które może dać ci nieznane kąty ... – dragn

+0

To jednak wymaga skomplikowanej matematyki ... :) – dragn