Obróć obiekt wokół stałej osi

12

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:

wprowadź opis zdjęcia tutaj

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:

  1. 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)
  2. 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):

  1. Najpierw obróć wokół Y (0, 1, 0) za pomocą ćwiartki.
  2. Przekształć obrót Y kwaternion na macierz.
  3. Pomnóż macierz obrotu Y przez moją oś stałą x Wektor (1, 0, 0), aby uzyskać oś X względem nowej przestrzeni.
  4. 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.Worldparametr Rotatefunkcji w transformacji.

Gdybym mógł użyć Unity, zrobiłbym to, niestety, musiałbym sam zakodować to zachowanie.

Christopher Perry
źródło
1
Moja odpowiedź tutaj gamedev.stackexchange.com/questions/67199/... może ci pomóc ...
concept3d
Rozumiem koncepcję twojej odpowiedzi, ale ucieka mnie to, jak ją wdrożyć.
Christopher Perry
Jeśli zaznaczyłeś inne odpowiedzi, odpowiedź syntac realizuje pomysł, który wyjaśniłem.
concept3d
Nie, nie robi, robi wiele obrotów wokół różnych osi. Sugerujesz wykonanie jednego obrotu.
Christopher Perry

Odpowiedzi:

3

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:

private float[] rotationMatrix() {

    final float[] xAxis = {1f, 0f, 0f, 1f};
    final float[] yAxis = {0f, 1f, 0f, 1f};

    Quaternion qY = Quaternion.fromAxisAngle(yAxis, angleX);
    Quaternion qX = Quaternion.fromAxisAngle(xAxis, -angleY);

    return (qX * qY).getMatrix(); // should probably represent the gameobjects rotation as a quaternion(not a matrix) and multiply all 3 quaternions before generating the matrix. 
  }

Samouczek OpenGL dotyczący rotacji ArcBall

Anthony Raimondo
źródło
Nie dostaję blokady gimbala, pierwszy obrót przesuwa oś, więc drugi obrót opiera się na przesuniętej osi. Proszę spojrzeć ponownie na zdjęcie, które podałem.
Christopher Perry
Mam nadzieję, że to rozgryzłeś. W skrócie. Czwartorzędy można mnożyć razem, aby zastosować rotację. Powinieneś wygenerować macierz tylko na końcu wszystkich obliczeń obrotu. Również xQ * yQ nie jest równy yQ * xQ. Czwartorzędy są nieprzemienne, jak powiedział Christopher Perry.
Anthony Raimondo,
Umieszczam tutaj cały mój kod . Czuję, że próbowałem wszystkiego. Może ktoś na to spojrzy i złapie mój błąd.
Christopher Perry
Nie zaakceptowałem tego, algorytm wymiany stosu automatycznie przypisał ci punkty. : /
Christopher Perry
Przepraszam za tę niesprawiedliwość.
Anthony Raimondo
3

Udało mi się uzyskać oczekiwane obroty, obracając zgromadzoną macierz obrotu.

setIdentityM(currentRotation, 0);
rotateM(currentRotation, 0, angleY, 0, 1, 0);
rotateM(currentRotation, 0, angleX, 1, 0, 0);

// Multiply the current rotation by the accumulated rotation,
// and then set the accumulated rotation to the result.
multiplyMM(temporaryMatrix, 0, currentRotation, 0, accumulatedRotation, 0);
arraycopy(temporaryMatrix, 0, accumulatedRotation, 0, 16);
Christopher Perry
źródło
1

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.

static final float[] xAxis = {1f, 0f, 0f, 1f};
static final float[] yAxis = {0f, 1f, 0f, 1f};

private void rotateObject(float angleX, float angleY) {
  Quaternion rotationY = Quaternion.fromAxisAngle(yAxis, -angleX);
  Quaternion rotationX = Quaternion.fromAxisAngle(xAxis, -angleY);

  myRotation = myRotation.rotate(rotationY).rotate(rotationX).normalize();
}
private float[] rotationMatrix() {
  return myRotation.toMatrix();
}

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.

Daniel Carlsson
źródło
Robię to dokładnie tak samo.
Christopher Perry,
1
@ChristopherPerry Spróbuj odwrócić kolejność obrotu: 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ę.
Daniel Carlsson
To też nie działa, mam takie samo zachowanie.
Christopher Perry
Umieściłem tutaj cały
Christopher Perry