Próbuję pozwolić użytkownikowi mojej aplikacji obrócić obiekt 3D narysowany na środku ekranu, przeciągając palcem po ekranie. Ruch poziomy na ekranie oznacza obrót wokół stałej osi Y, a ruch pionowy oznacza obrót wokół osi X. Problem, jaki mam, polega na tym, że jeśli pozwolę na obrót wokół jednej osi, obiekt obraca się dobrze, ale gdy tylko wprowadzę drugi obrót, obiekt nie obraca się zgodnie z oczekiwaniami.
Oto obraz tego, co się dzieje:
Niebieska oś reprezentuje moją stałą oś. Wyobraź sobie ekran z tą stałą niebieską osią. To właśnie chcę, aby obiekt obracał się w stosunku do. To, co się dzieje, jest na czerwono.
Oto co wiem:
- Pierwszy obrót wokół Y (0, 1, 0) powoduje, że model przesuwa się z niebieskiej przestrzeni (nazwij to miejsce A) na inną przestrzeń (nazwij to miejsce B)
- Próba ponownego obrotu za pomocą wektora (1, 0, 0) obraca się wokół osi x w przestrzeni B NIE w przestrzeni A, co nie jest tym, co mam na myśli.
Oto, co próbowałem, biorąc pod uwagę to, co (myślę) wiem (pomijając zwięzłość W dla zwięzłości):
- Najpierw obróć wokół Y (0, 1, 0) za pomocą ćwiartki.
- Przekształć obrót Y kwaternion na macierz.
- Pomnóż macierz obrotu Y przez moją oś stałą x Wektor (1, 0, 0), aby uzyskać oś X względem nowej przestrzeni.
- Obracaj wokół tego nowego X wektora za pomocą ćwiartki.
Oto kod:
private float[] rotationMatrix() {
final float[] xAxis = {1f, 0f, 0f, 1f};
final float[] yAxis = {0f, 1f, 0f, 1f};
float[] rotationY = Quaternion.fromAxisAngle(yAxis, -angleX).toMatrix();
// multiply x axis by rotationY to put it in object space
float[] xAxisObjectSpace = new float[4];
multiplyMV(xAxisObjectSpace, 0, rotationY, 0, xAxis, 0);
float[] rotationX = Quaternion.fromAxisAngle(xAxisObjectSpace, -angleY).toMatrix();
float[] rotationMatrix = new float[16];
multiplyMM(rotationMatrix, 0, rotationY, 0, rotationX, 0);
return rotationMatrix;
}
To nie działa tak, jak się spodziewam. Obrót wydaje się działać, ale w pewnym momencie ruch poziomy nie obraca się wokół osi Y, wydaje się, że obraca się wokół osi Z.
Nie jestem pewien, czy moje rozumienie jest błędne lub czy coś innego powoduje problem. Mam inne transformacje, które wykonuję na obiekcie oprócz obrotu. Przed zastosowaniem obrotu przesuwam obiekt na środek. Obracam go za pomocą macierzy zwróconej z mojej powyższej funkcji, a następnie tłumaczę -2 w kierunku Z, aby zobaczyć obiekt. Nie sądzę, że to psuje moje rotacje, ale i tak oto kod tego:
private float[] getMvpMatrix() {
// translates the object to where we can see it
final float[] translationMatrix = new float[16];
setIdentityM(translationMatrix, 0);
translateM(translationMatrix, 0, translationMatrix, 0, 0f, 0f, -2);
float[] rotationMatrix = rotationMatrix();
// centers the object
final float[] centeringMatrix = new float[16];
setIdentityM(centeringMatrix, 0);
float moveX = (extents.max[0] + extents.min[0]) / 2f;
float moveY = (extents.max[1] + extents.min[1]) / 2f;
float moveZ = (extents.max[2] + extents.min[2]) / 2f;
translateM(centeringMatrix, 0, //
-moveX, //
-moveY, //
-moveZ //
);
// apply the translations/rotations
final float[] modelMatrix = new float[16];
multiplyMM(modelMatrix, 0, translationMatrix, 0, rotationMatrix, 0);
multiplyMM(modelMatrix, 0, modelMatrix, 0, centeringMatrix, 0);
final float[] mvpMatrix = new float[16];
multiplyMM(mvpMatrix, 0, projectionMatrix, 0, modelMatrix, 0);
return mvpMatrix;
}
Utknąłem na tym od kilku dni. Pomoc jest bardzo ceniona.
================================================== ================
AKTUALIZACJA:
Uruchomienie tego w Jedności jest proste. Oto kod, który obraca sześcian wyśrodkowany na początku:
public class CubeController : MonoBehaviour {
Vector3 xAxis = new Vector3 (1f, 0f, 0f);
Vector3 yAxis = new Vector3 (0f, 1f, 0f);
// Update is called once per frame
void FixedUpdate () {
float horizontal = Input.GetAxis ("Horizontal");
float vertical = Input.GetAxis ("Vertical");
transform.Rotate (xAxis, vertical, Space.World);
transform.Rotate (yAxis, -horizontal, Space.World);
}
}
Część, która powoduje, że obroty zachowują się tak, jak się spodziewam, to Space.World
parametr Rotate
funkcji w transformacji.
Gdybym mógł użyć Unity, zrobiłbym to, niestety, musiałbym sam zakodować to zachowanie.
Odpowiedzi:
Twój problem nazywa się blokadą gimble . Myślę, że to, co chcesz zrobić, nazywa się rotacją arcball . Matematyka dla arcball może być skomplikowana.
Prostszym sposobem na to jest znalezienie wektora 2d prostopadłego do przesunięcia 2d na ekranie.
Weź wektor i wyświetl go na kamerze w pobliżu samolotu, aby uzyskać wektor 3d w przestrzeni świata. Przestrzeń ekranu do przestrzeni świata .
Następnie utwórz wektor za pomocą tego wektora i pomnóż go do gameobject. Prawdopodobnie z pewnym przejściem slurp lub lerp.
Edytować:
Przykład jedności: W przykładzie jedności wewnętrzny stan obrotu obiektów gry jest czwartorzędem, a nie macierzą. Metoda transform.rotation generuje ćwiartkę na podstawie podanego wektora i kąta oraz pomnaża tę ćwiartkę z czwartorzędem obrotu obiektu gry. Generuje tylko macierz obrotu do renderowania lub fizyki w późniejszym punkcie. Czwartorzędy są addytywne i unikają blokady gimble.
Twój kod powinien wyglądać mniej więcej tak:
Samouczek OpenGL dotyczący rotacji ArcBall
źródło
Udało mi się uzyskać oczekiwane obroty, obracając zgromadzoną macierz obrotu.
źródło
Twój obraz odpowiada Twojemu kodowi obrotu, obracając oś x z poprzednim obrotem y, otrzymujesz lokalną oś x, a następnie obracasz obiekt wokół, aby uzyskać wynik wyświetlany na obrazie. Aby obrót był logiczny z punktu widzenia użytkownika, należy obrócić obiekt za pomocą osi współrzędnych świata.
Jeśli chcesz, aby użytkownik mógł wielokrotnie obracać obiekt, warto przechowywać rotację w ćwiartce zamiast w macierzy, z czasem wiele obrotów (i niedokładności w liczbach zmiennoprzecinkowych) sprawi, że matryca będzie wyglądać coraz mniej macierz obrotu, to samo dzieje się oczywiście w ćwiartce, ale zwykła normalizacja ćwiartki przywraca jej dobrą rotację.
Po prostu użyj czwartorzędu tożsamości jako wartości początkowej, a za każdym razem, gdy użytkownik przesunie się po ekranie, obróć czwartorzędowy
Quaternion.fromAxisAngle(yAxis, -angleX)
kod. Zawsze używaj (1,0,0,1) dla obrotów x i (0,1,0,1) dla obrotów y.Ponieważ nie wspomniałeś o języku ani żadnym konkretnym frameworku, metody na Quaternion mogą być oczywiście nazywane czymś innym, i normalizacja nie jest konieczna do wywoływania tego często, ale ponieważ obrót pochodzi od użytkownika przesuwającego ekran, nie będą znacznie spowalniać i w ten sposób nie ma szans, aby Kwaterion wymknął się z jednostki czwartorzędu.
źródło
myRotation = rotationX.rotate(rotationY).rotate(myRotation).normalize()
nie są one przemienne, więc liczy się kolejność, w jakiej je wykonujesz. Dodaj kolejny komentarz do swojego frameworka / języka, jeśli to nie zadziałało, i trochę go zagłębię.