Ruota l'oggetto attorno all'asse fisso


12

Sto cercando di consentire all'utente della mia app di ruotare un oggetto 3D disegnato al centro dello schermo trascinando il dito sullo schermo. Un movimento orizzontale sullo schermo significa rotazione attorno ad un asse Y fisso, mentre un movimento verticale significa rotazione attorno all'asse X. Il problema che sto riscontrando è che se consento solo la rotazione attorno ad un asse l'oggetto ruota bene, ma non appena introduco una seconda rotazione l'oggetto non ruota come previsto.

Ecco una foto di ciò che sta accadendo:

inserisci qui la descrizione dell'immagine

L'asse blu rappresenta il mio asse fisso. Immagina lo schermo con questo asse blu fisso. Questo è ciò a cui voglio ruotare l'oggetto in relazione. Quello che sta succedendo è in rosso.

Ecco quello che so:

  1. La prima rotazione intorno a Y (0, 1, 0) fa spostare il modello dallo spazio blu (chiama questo spazio A) in un altro spazio (chiama questo spazio B)
  2. Cercare di ruotare di nuovo usando il vettore (1, 0, 0) ruota attorno all'asse x nello spazio B NON nello spazio A che non è ciò che intendo fare.

Ecco cosa ho provato, dato quello che (penso) lo so (tralasciando il W coord per brevità):

  1. Per prima cosa ruota attorno a Y (0, 1, 0) usando un Quaternione.
  2. Converti la rotazione Y Quaternion in una matrice.
  3. Moltiplica la matrice di rotazione Y per il mio asse fisso x Vector (1, 0, 0) per ottenere l'asse X in relazione al nuovo spazio.
  4. Ruota attorno a questo nuovo X Vector usando un Quaternion.

Ecco il codice:

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;
  }

Questo non funziona come mi aspetto. La rotazione sembra funzionare, ma a un certo punto il movimento orizzontale non ruota attorno all'asse Y, sembra ruotare attorno all'asse Z.

Non sono sicuro se la mia comprensione sia sbagliata o se qualcos'altro sta causando un problema. Ho alcune altre trasformazioni che sto facendo all'oggetto oltre alla rotazione. Sposto l'oggetto al centro prima di applicare la rotazione. Lo ruoto usando la matrice restituita dalla mia funzione sopra, quindi lo traduco -2 nella direzione Z in modo da poter vedere l'oggetto. Non penso che questo stia rovinando le mie rotazioni, ma ecco comunque il codice per quello:

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;
  }

Sono stato bloccato su questo per alcuni giorni. L'aiuto è molto apprezzato.

================================================== ================

AGGIORNARE:

Far funzionare questo in Unity è semplice. Ecco del codice che ruota un cubo centrato sull'origine:

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);
    }
}

La parte che fa funzionare le rotazioni come mi aspetto è il Space.Worldparametro per la Rotatefunzione sulla trasformazione.

Se potessi usare Unity, purtroppo dovrei codificare questo comportamento da solo.


1
La mia risposta qui gamedev.stackexchange.com/questions/67199/… potrebbe aiutarti ...
concept3d

Capisco il concetto alla base della tua risposta, ma come fuggire mi sfugge.
Christopher Perry,

Se hai controllato le altre risposte, la risposta syntac implementa l'idea che ho spiegato.
concept3d

No non lo fa, sta facendo più rotazioni su assi diversi. Suggerisci di fare una singola rotazione.
Christopher Perry,

Risposte:


3

Il problema che hai è chiamato gimble lock . Penso che quello che stai cercando di fare si chiami rotazione arcball . La matematica per l'arcball può essere un po 'complicata.

Un modo più semplice per farlo è trovare un vettore 2D perpendicolare al passaggio 2d sullo schermo.

Prendi il vettore e proiettalo sulla telecamera vicino all'aereo per ottenere un vettore 3d nello spazio mondiale. Spazio dello schermo allo spazio mondiale .

Quindi crea un quaternione con questo vettore e moltiplicalo per gameobject. Probabilmente con qualche transizione slurp o lerp.

Modificare:

Esempio di unità: nell'esempio di unità, lo stato interno della rotazione degli oggetti di gioco è un quaternione, non una matrice. Il metodo transform.rotation genera un quaternione basato sul vettore e sull'angolo forniti e moltiplica quel quaternione con il quaternione di rotazione degli oggetti di gioco. Genera solo la matrice di rotazione per il rendering o la fisica in un momento successivo. I quaternioni sono additivi ed evitano il blocco del gimble.

Il tuo codice dovrebbe essere simile al seguente:

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. 
  }

ArcBall Rotation Opengl Tutorial


Non ottengo il blocco del gimbal, la prima rotazione sposta l'asse, quindi una seconda rotazione si basa sull'asse spostato. Si prega di dare una seconda occhiata alla foto che ho fornito.
Christopher Perry,

Spero che tu l'abbia capito. In breve. I quaternioni possono essere moltiplicati insieme per applicare la rotazione. Dovresti generare la matrice solo alla fine di tutti i calcoli di rotazione. Anche xQ * yQ non è uguale a yQ * xQ. I quaternioni non sono commutativi come ha detto Christopher Perry.
Anthony Raimondo,

Ho inserito TUTTO il mio codice qui . Mi sento come se avessi provato di tutto. Forse gli occhi di qualcun altro su questo coglieranno il mio errore.
Christopher Perry,

Non l'ho accettato, l'algoritmo di scambio di stack ti ha assegnato automaticamente i punti. : /
Christopher Perry,

Mi dispiace per questa ingiustizia.
Anthony Raimondo,

3

Sono stato in grado di ottenere le rotazioni previste ruotando una matrice di rotazione accumulata.

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);

1

L'immagine corrisponde al tuo codice di rotazione Matrix, ruotando l'asse x con la tua precedente rotazione y ottieni l'asse x locale, quando poi ruota l'oggetto attorno al quale otterrai il risultato che mostri nella tua immagine. Per rendere la rotazione logica dal punto di vista degli utenti, si desidera ruotare l'oggetto utilizzando invece l'asse delle coordinate del mondo.

Se vuoi che il tuo utente sia in grado di ruotare il tuo oggetto molte volte, sarebbe logico memorizzare la tua rotazione in un quaternione anziché in una matrice, nel tempo più giri (e imprecisioni in virgola mobile) faranno apparire la matrice sempre meno simile una matrice di rotazione, lo stesso accade naturalmente in un quaternione, ma la normalizzazione del quaternione lo riporta a una buona rotazione.

Basta usare l'identità quaternione come valore iniziale e ogni volta che l'utente scorre lo schermo si ruota il quaternione con il Quaternion.fromAxisAngle(yAxis, -angleX)codice. Usando sempre (1,0,0,1) per le rotazioni x e (0,1,0,1) per le rotazioni 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();
}

Dal momento che non hai menzionato la lingua o alcun quadro specifico, i metodi su Quaternion possono essere chiamati in modo diverso in modo naturale, e normalizzare non è necessario per chiamarlo spesso, ma poiché la rotazione viene da un utente che passa lo schermo non lo faranno rallenta molto le cose e in questo modo non c'è alcuna possibilità che il Quaternion scivoli via da un'unità quaternion.


Sto ottenendo lo stesso identico comportamento nel fare questo.
Christopher Perry,

1
@ChristopherPerry Prova a invertire l'ordine di rotazione: myRotation = rotationX.rotate(rotationY).rotate(myRotation).normalize()non sono commutativi, quindi l'ordine in cui li fai conta. Aggiungi un altro commento con il tuo framework / linguaggio se questo non ha funzionato e ti approfondirò un po 'di più.
Daniel Carlsson,

Neanche questo funziona, ho lo stesso comportamento.
Christopher Perry,

Ho inserito TUTTO il mio codice qui
Christopher Perry,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.