Teoria
Kwartenionów (z ang. Quaternion) można użyć do zapobieżenia utraty stopnia swobody (tzw. Gimbal Lock).
Każdy Kwartenion składa się z dwóch elementów – wektora w przestrzeni urojonej, służącego do określenia osi obrotu w trzech wymiarach, oraz skalara oznaczającego kąt obrotu wokół zdefiniowanej osi.
Poniżej znajduje się rysunek obrazujący ideę użycia Kwartenionu do przeprowadzenia rotacji w trójwymiarowym świecie.
Kolejne kolorowe osie (czerwona, zielona i niebieska) reprezentują poszczególne kierunki trójwymiarowego świata. Czarna strzałka jest wektorem w przestrzeni urojonej, i jest zarazem osią obrotu (rotation axis). Kąt obrotu (rotation angle) określany jest przez rzeczywisty skalar Kwartenionu.
Praktyka
Użyjmy Kwartenionów do przeprowadzenia rotacji w trójwymiarowym świecie. Jako wejście posłuży nam ruch myszki – odbywa się on w dwóch kierunkach, tzn. na boki, i z góry na dół. Przyjmijmy zatem, że odległość od środka ekranu będzie wartością zmiany położenia kamery:
dx = mouse.getX() dy = mouse.getY()
Można założyć, że dx i dy to zmiany w poziomie i w pionie w przestrzeni Eulerowskiej. Jeśli użylibyśmy bezpośrednio tych zmiennych do rotacji utracilibyśmy stopień swobody przy osiągnięciu rotacji 90′:
glRotated(dx, 1, 0, 0) glRotated(dy, 0, 1, 0)
Sięgnijmy po Kwaterniony. Określmy najpierw jeden Kwaternion, będący naszym punktem w przestrzeni, którego będziemy obracać. Zacznijmy od Kwartenionu jednostkowego:
Quaternion position = new Quaternion(0, 0, 0, 1);
To on będzie wyznaczał nasze osie obrotu – nie Eulerowski układ współrzędnych. Weźmy nasze zmiany prędkości obrotu i stwórzmy odpowiedni Kwartenion:
Quaternion rotation = new Quaternion().fromEuler(dx, dy, 0)
Teraz zaaplikujmy naszą rotację do pozycji, którą określiliśmy wcześniej. Wykorzystamy mnożenie Kwartenionów:
position = rotation * position
Otrzymaliśmy przekształconą, niezależną pozycję naszego punktu w przestrzeni. Wykonajmy więc rotację macierzy obrotu przy pomocy naszego Kwaterniona. Aby to zrobić, musimy zamienić go na postać osi obrotu:
x, y, z rotationAngle = position.toAxisAngle(x, y, z)
Czyli musimy uzyskać naszą oś i kąt obrotu. W takiej postaci możemy zaaplikować naszą rotację do macierzy obrotu:
glRotated(rotationAngle, x, y, z)
W ten sposób uwolniliśmy się od utraty stopni swobody przy pomocy niezależnego od osi obrotu Kwarteniona.