Notifications
Article
Struktura Quaternion – Dot(), operator == i !=,
FromToRotation(), SetFromToRotation(),
LookRotation(), SetLookRotation()
Published 19 days ago
26
0
Struktura Quaternion – Dot(), operator == i !=, FromToRotation(), SetFromToRotation(), LookRotation(), SetLookRotation()

Metoda Dot()

W strukturze quaternion znajduje się metoda Dot() służąca do obliczania iloczynu skalarnego między dwoma quaternionami.
https://docs.unity3d.com/ScriptReference/Quaternion.Dot.html
Wynik iloczynu skalarnego pozwala nam porównywać ze sobą dwa quaterniony. Dzięki czemu możemy zobaczyć m.in. czy quaterniony reprezentują tą samą rotacje bądź czy reprezentują przeciwną rotacje.

Jak definiujemy tą metodę?

Metoda Dot() posiada dwa parametry typu Quaternion: a i b. Obliczamy iloczyn skalarny tych rotacji. Kolejność rotacji w metodzie Dot() nie ma wpływu na wynik operacji.
Quaternion rotationA; Quaternion rotationB; // Nie ma znaczenia w jakiej kolejności podamy rotacje jako parametry float dotProduct = Quaternion.Dot (a: rotationA, b: rotationB);

Co zwraca metoda Dot?

Przygotowałem skrypt, który pozwoli nam odczytać wartość iloczynu skalarnego dwóch quaternionów.
Tu definiujemy rotacje w postaci kątów eulera.

A tu możemy odczytać wartość iloczynu skalarnego dwóch quaternionów.

W skrypcie obliczyłem także quaternion będący różnicą między quaternionem b i a.
// Wynikiem tej operacji jest quaternion potrzebny do obrotu od rotacji a do rotacji b Quaternion rotationDifferenceBA = Quaternion.Inverse(a) * b;
Tu możecie odczytać wartość parametru "w" tak utworzonego quaternionu.

Zauważcie, że w przypadku quaternionów a i b, parametr „w" rotacji będącej różniącą tych quaternionów jest równy iloczynowi skalarnemu tych quaternionów.
W pierwszej części artykułu mieliście okazje poznać działanie parametru "w", dzięki czemu łatwiej będzie nam wykorzystać właściwości iloczynu skalarnego dwóch quaternionów.

W dalszej części tego artykułu będę zamiennie używał nazwy "iloczyn skalarny" i "parametr w" bo obie wielkości zwracają te same wartości.

Co mówi nam wartość iloczynu skalarnego (lub parametru w) gdy porównujemy dwa quaterniony?

W tych wizualizacjach każda ze strzałek reprezentuje jakąś rotacje.
Gdy strzałki się nachodzą (czyli różnica ich rotacji wynosi Quaternion.identity) to iloczyn skalarny (parametr "w") wynosi 1.
Gdy dwie rotacje są sobie całkowicie przeciwne. Czyli dzieli obrót o 180° to wtedy iloczyn skalarny (parametr "w") zwraca wartość 0.
Odczytując wartość iloczynu skalarnego możemy określić jak bardzo dwie rotacje się różnią.

Operator == i !=

W strukturze Quaternion znajdują się operatory "==" i "!=", które pozwala na porównywanie ze sobą dwóch quaternionów.
Quaternion a; Quaternion b; // sprawdzamy czy dwa quaterniony są takie same bool areEqual = a==b; // sprawdzamy czy dwa quaterniony są od siebie różne bool areDifferent = a!=b;
Wewnętrznie ten operator wykorzystuję iloczyn skalarny quaternionów do ich porównania (czyli metodę Dot())
Quaternion lhs; Quaternion rhs; // tak wewnętrznie działa operator == bool areEqual = Quaternion.Dot(lhs, rhs) > 0.999999f

Problem z porównaniem rotacji, które dzieli obrót o 360° .

Metoda Dot() jak i operatory oparte na tej metodzie czyli " == " i " != " nie zawsze działają poprawnie.
Przykładem takiej sytuacji jest porównywanie quaternionów, które dzieli obrót o 360° .
Zauważcie, że gdy obrócę jakiś wektor o 360° to wróci on do pozycji początkowej.

Na pierwszy rzut oka można by powiedzieć, że początkowa i końcowa rotacja są takie same bo wektor znalazł się w tym samym położeniu co na początku. Jednak z punktu matematycznego są to dwie różne rotacje.
Wykonajmy testy. Przykładowy skrypt z testami znajdziecie w tym gameObjectie "Dot - Equality Problem"
Poniżej mam 3 quaterniony, które są przesunięte względem siebie o 360° .
// quaterniony, które dzieli obrót będący wielokrotniością 360° Quaternion a = Quaternion.AngleAxis(30f, Vector3.forward); Quaternion b = Quaternion.AngleAxis(30f + 360f, Vector3.forward); Quaternion c = Quaternion.AngleAxis(30f + 720f, Vector3.forward);
Jeśli byśmy obrócili jakiś wektor (np. Vector3.right) o te rotacje to zawsze po obróceniu wskazywał by ten sam kierunek. Czyli teoretycznie te 3 rotacje powinny być identyczne.
Ale gdy spróbujemy te rotacje porównać z pomocą operatora == to okaże się, że rotacja a i b są od siebie różne.
// quaterniony, które dzieli obrót będący wielokrotniością 360° Quaternion a = Quaternion.AngleAxis(30f, Vector3.forward); Quaternion b = Quaternion.AngleAxis(30f + 360f, Vector3.forward); Quaternion c = Quaternion.AngleAxis(30f + 720f, Vector3.forward); // porównujemy quaterniony ze sobą // quaternion a i b dzieli obrót o 360° bool AIsEqualB = a == b; // zwraca false // quaternion a i c dzieli obrót o 360° bool AIsEqualC = a == c; // zwraca true

Dlaczego dwie rotacje, które dzieli 360° nie są sobie równe?

Iloczyn skalarny dwóch quaternionów obliczamy z pomocą następującego wzoru:
Quaternion a; Quaternion b; float dotProduct = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
Jeśli zmieni się znak przy wartościach parametrów jednego z quaternionów to zmieni się też wartość iloczynu skalarnego.

Przyjrzyjcie się wartościom parametrów poszczególnych quaternionów. W przypadku rotacji A i C mamy wartości dodatnie a w przypadku B - wartości ujemne parametrów. Obliczmy iloczyny skalarne tych quaternionów:
Quaternion a = Quaternion.AngleAxis(30f, Vector3.forward); Quaternion b = Quaternion.AngleAxis(30f + 360f, Vector3.forward); Quaternion c = Quaternion.AngleAxis(30f + 720f, Vector3.forward); bool AIsEqualB = a == b; // zwraca false bool AIsEqualC = a == c; // zwraca true float dotProductAB = Quaternion.Dot(a, b); // zwraca -1 float dotProductAC = Quaternion.Dot(a, c); // zwraca 1
Wcześniej wam wspominałem, że operator == porównuje dwa quaterniony obliczając ich iloczyn skalarny i sprawdzając czy iloczyn jest równy 1.
Quaternion lhs; Quaternion rhs; // tak wewnętrznie działa operator == bool areEqual = Quaternion.Dot(lhs, rhs) > 0.999999f
Jeśli iloczyn skalarny będzie różny od 1 to quaterniony zostaną uznane za różne.
W przypadku quaternionów, których różnica obrotu wynosi 360° stopni, iloczyn skalarny będzie równy -1. Tak jak miało to miejsce w przypadku iloczynu skalarnego dla quaternionu A i B
float dotProductAB = Quaternion.Dot(a, b); // zwraca -1
Przypominam, że quaterniony opisane są funcjami trygonometrycznymi połowy kąta obrotu, czyli jeden pełen cykl wynosi 720° nie 360° stopni. Gdy obrócimy jakiś quaternion o 360° stopni to wartości jego parametrów zmienią znak na przeciwny (zostaną wymnożone razy -1).
Poniżej rysunki pokazujące jak zmieniają się parametry quaternionu w zaleźności od wartości kąta obrotu.

Jak porównywać rotacje, których różnica w obrocie wynosi 360° ?

Możemy zawsze samodzielnie porównać quaterniony z pomocą poniższego równania:
bool areEqual = Mathf.Abs(Quaternion.Dot(a, b)) > 1f - Quaternion.kEpsilon;
Porównując w ten sposób nie znaczenia znak przy iloczynie skalarnym a tym samym możemy porównywać quaterniony, które dzieli obrót o 360° .

Metoda FromToRotation() i SetFromToRotation()

FromToRotation()

Teraz metoda FromToRotation(), która oblicza rotacje potrzebną do obrócenia wektora z jednej pozycji do drugiej bądź inaczej - metoda oblicza różnice obrotu między dwoma wektorami.
Jak definiujemy tą metodę:
// Obliczamy rotacje między tymi dwoma wektoram // od tego kierunku mierzymy obrót Vector3 startDirection = Vector3.right; //to kierunek końca obrotu Vector3 endDirection = Vector3.up; // jako parametry definiujemy wektory między, którymi obliczamy rotacje // uzyskaną rotacje możemy przypisać do zmiennej Quaternion rotation = Quaternion.FromToRotation (fromDirection: startDirection, toDirection: endDirection);
Poniżej znajduję się wizualizacja. Gdy zmieniam położenie wektorów wpływam na wartość uzyskanego quaternionu z pomocą metody FromToRotation()

SetFromToRotation()

Metoda SetFromToRotation() działa bardzo podobnie do metody FromToRotation() . Jedyną różnicą jest to, że metoda SetFromToRotation() zwraca uzyskaną rotacje bezpośrednio do quaternionu, na którym została wywołana metoda.
// Obliczamy rotacje między tymi dwoma wektoram // od tego kierunku mierzymy obrót Vector3 startDirection = Vector3.right; //to kierunek końca obrotu Vector3 endDirection = Vector3.up; Quaternion rotation; // obliczamy rotacje między dwoma wektorami // uzyskaną rotacje przypisujemy do quaternionu, na którym została wywołana // czyli nowa rotacja zostaje przypisana do quaternionu "rotation" rotation.SetFromToRotation (fromDirection: startDirection, toDirection: endDirection);



Metoda LookRotation() i SetLookRotation()

LookRotation()

Zacznę od tego, że każde ciało posiada 3 prostopadłe osie reprezentujące lokalny układ współrzędnych ciała (osie pozwalają nam określić gdzie znajduję się przód i tył ciała, góra i dół itd.).
Czerwonym kolorem oznaczona jest oś pokazująca prawą stronę ciała, zielonym oś pokazującą górną część ciała, a niebieskim oś pokazująca przód ciała.
Metoda LookRotation() zwraca quaternion potrzebny do obrócenia niebieskiej osi lokalnego ukł. współrzędnych do kierunku zdefiniowanego w tej metodzie jako parametr.
Vector3 forward = Vector3.left; // metoda zwraca quaternion potrzebny do obrócenia niebieskiej lokalnej osi do kierunku zdefiniowanego w parametrze forward metody Quaternion q = Quaternion.LookRotation (forward:forward);
Tu macie wizualizacje tej metody. Uzyskaną rotacje przypisuje ciału aby je obrócić. Zwróćcie uwagę jak zmienia się rotacja gdy zmieniam wartość wektor forward.

Ta metoda posiada także drugi parametr o nazwie upwards, który służy do wskazania przybliżonego kierunku gdzie znajduje się góra ciała.
Vector3 forward = Vector3.left; // wskazujemy gdzie jest w przybliżeniu góra ciała Vector3 upwards = Vector3.up; Quaternion q = Quaternion.LookRotation (forward:forward, upwards: upwards);
Mówię tu "przybliżony kierunek gdzie znajduje się góra ciała" z racji tego, że parametr "upwards " nie wskazuję bezpośrednio kierunku do góry ciała.
Parametr upwards służy do obliczenia kierunku lokalnej osi x w metodzie LookRotation():

Vector3 forward,upwards; // tak wewnętrznie metoda LookRotation uzyskuje kierunek czerwonej osi lokalnego układu współrzędnych Vector3 redLocalAxis = Vector3.Cross(forward, upwards);
Na bazie kierunku lokalnej osi x metoda LookRotation() oblicza kierunek zielonej osi (czyli gdzie znajduję się góra ciała.)
Vector3 forward; // tak wewnętrznie metoda LookRotation uzyskuje kierunek zielonej osi lokalnego układu współrzędnych // czyli kierunek w górę ciała Vector3 greenLocalAxis = Vector3.Cross(redLocalAxis,forward);
Unity na podstawie znajomości kierunków tych trzech osi oblicza rotacje potrzebną do obrócenia ciała, tak aby jego przód był skierowany w kierunku forward (taką rotacje zwraca metoda LookRotation ()).
Poniżej macie przykład jak parametr upwards wpływa na uzyskaną rotacje.
Na wizualizacji zielona oś reprezentuje wyliczony kierunek lokalnej osi y a nie sam wektor upwards.
Na początku zmieniam wartość parametru upwards. Gdy zmieniam wartość parametru forward możemy zobaczyć jaką rotacje dostaniemy przy wybranych wartościach parametrów. Niektóre rotacje powodują, że zielona oś się przekrzywia. Ale i tak metoda LookRotation() zawsze będzie się starać aby zielona oś była najbardziej skierowana w kierunku upwards.

Wspomnę jeszcze, że defaultowa wartość parametru upwards to Vector3.up.
Vector3 forward = Vector3.left; // ten zapis... Quaternion q = Quaternion.LookRotation (forward:forward); // ... jest równoważny takiemu zapisowi Quaternion q = Quaternion.LookRotation (forward:forward, upwards: Vector3.up);

Inne sprawy związane z metodą LookRotation ()

Jeśli ustawimy parametr forward lub upwards na Vector3.zero to metoda LookRotation() zwróci wartość Quaternion.identity czyli zerową rotacje.


SetLookRotation()

Metoda SetLookRotation() działa bardzo podobnie do metody LookRotation (). Są tylko dwie różnice między tymi metodami.
Pierwszą różnicą są nazwy parametrów - w przypadku metody SetLookRotation() parametry noszą nazwy view i up.
Drugą różnicą jest to, że zwraca ona wartość rotacji bezpośrednio do quaternionu, na którym została wywołana.
Quaternion q; // rotacja zwracana jest bezpośrednio do quaternionu, na którym została wywołana ta metoda q.SetLookRotation (view: forward, up: upwards);


Tags:
Rafał Tadajewski
I haven't duck. I took dino. - Programmer
9
Comments