Faire pivoter l'objet autour d'un axe fixe

12

J'essaie de laisser l'utilisateur de mon application faire pivoter un objet 3D dessiné au centre de l'écran en faisant glisser son doigt sur l'écran. Un mouvement horizontal à l'écran signifie une rotation autour d'un axe Y fixe, et un mouvement vertical signifie une rotation autour de l'axe X. Le problème que j'ai est que si je permets juste une rotation autour d'un axe, l'objet tourne bien, mais dès que j'introduis une deuxième rotation, l'objet ne tourne pas comme prévu.

Voici une photo de ce qui se passe:

entrez la description de l'image ici

L'axe bleu représente mon axe fixe. Imaginez l'écran ayant cet axe bleu fixe. C'est à cela que je veux que l'objet tourne par rapport. Ce qui se passe est en rouge.

Voici ce que je sais:

  1. La première rotation autour de Y (0, 1, 0) provoque le déplacement du modèle de l'espace bleu (appelez cet espace A) vers un autre espace (appelez cet espace B)
  2. Essayer de pivoter à nouveau en utilisant le vecteur (1, 0, 0) tourne autour de l'axe x dans l'espace B PAS dans l'espace A, ce qui n'est pas ce que je veux faire.

Voici ce que j'ai essayé, compte tenu de ce que je pense (je pense) (en omettant la coordonnée W par souci de concision):

  1. Faites d'abord tourner autour de Y (0, 1, 0) à l'aide d'un Quaternion.
  2. Convertissez la rotation Y Quaternion en une matrice.
  3. Multipliez la matrice de rotation Y par mon axe fixe x vecteur (1, 0, 0) pour obtenir l'axe X par rapport au nouvel espace.
  4. Tournez autour de ce nouveau vecteur X à l'aide d'un Quaternion.

Voici le code:

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

Cela ne fonctionne pas comme je l'espère. La rotation semble fonctionner, mais à un moment donné, le mouvement horizontal ne tourne pas autour de l'axe Y, il semble tourner autour de l'axe Z.

Je ne sais pas si ma compréhension est fausse ou si quelque chose d'autre cause un problème. J'ai d'autres transformations que je fais à l'objet en plus de la rotation. Je déplace l'objet au centre avant d'appliquer la rotation. Je le fais pivoter en utilisant la matrice renvoyée par ma fonction ci-dessus, puis je le translate -2 dans la direction Z pour que je puisse voir l'objet. Je ne pense pas que cela perturbe mes rotations, mais voici le code pour cela de toute façon:

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

Je suis coincé là-dessus depuis quelques jours. L'aide est très appréciée.

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

MISE À JOUR:

Faire fonctionner cela dans Unity est simple. Voici un code qui fait pivoter un cube centré à l'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 partie qui fait que les rotations se comportent comme je l'attends est le Space.Worldparamètre de la Rotatefonction sur la transformation.

Si je pouvais utiliser Unity je le ferais, malheureusement, je dois coder ce comportement moi-même.

Christopher Perry
la source
1
Ma réponse ici gamedev.stackexchange.com/questions/67199/… pourrait vous aider ..
concept3d
Je comprends le concept derrière votre réponse, mais comment l'implémenter m'échappe.
Christopher Perry
Si vous avez coché les autres réponses, la syntaxe répond implémente l'idée que j'ai expliquée.
concept3d
Non, il fait plusieurs rotations autour de différents axes. Vous proposez de faire une seule rotation.
Christopher Perry

Réponses:

3

Le problème que vous rencontrez s'appelle gimble lock . Je pense que ce que vous cherchez à faire est appelé rotation d'arcball . Les mathématiques de l'arcball peuvent être assez compliquées.

Une façon plus simple de le faire consiste à trouver un vecteur 2D perpendiculaire au balayage 2D à l'écran.

Prenez le vecteur et projetez-le sur la caméra près du plan pour obtenir un vecteur 3D dans l'espace mondial. Écran écran vers l'espace mondial .

Créez ensuite un quaternion avec ce vecteur et multipliez-le en gameobject. Probablement avec une transition slurp ou lerp.

Éditer:

Exemple d'unité: Dans l'exemple d'unité, l'état interne de la rotation des objets de jeu est un quaternion et non une matrice. La méthode transform.rotation génère un quaternion basé sur le vecteur et l'angle fournis et multiplie ce quaternion par le quaternion de rotation des objets de jeu. Il génère uniquement la matrice de rotation pour le rendu ou la physique à un stade ultérieur. Les quaternions sont additifs et évitent le verrouillage de vrille.

Votre code devrait ressembler à ceci:

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

Anthony Raimondo
la source
Je n'obtiens pas de verrouillage du cardan, la première rotation déplace l'axe, donc une deuxième rotation est basée sur l'axe déplacé. Veuillez revoir la photo que j'ai fournie.
Christopher Perry
J'espère que vous l'avez compris. En bref. Les quaternions peuvent être multipliés ensemble pour appliquer la rotation. Vous ne devez générer la matrice qu'à la fin de tous les calculs de rotation. De plus, xQ * yQ n'est pas égal à yQ * xQ. Les quaternions ne sont pas commutatifs comme l'a dit Christopher Perry.
Anthony Raimondo
J'ai mis TOUT mon code ici . J'ai l'impression d'avoir tout essayé. Peut-être que les yeux de quelqu'un d'autre sur ce sujet comprendront mon erreur.
Christopher Perry
Je ne l'ai pas accepté, l'algorithme d'échange de pile vous a attribué automatiquement les points. : /
Christopher Perry
Je suis désolé pour cette injustice.
Anthony Raimondo
3

J'ai pu obtenir les rotations attendues en faisant tourner une matrice de rotation accumulée.

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
la source
1

Votre image correspond à votre code rotationMatrix, en faisant pivoter l'axe x avec votre rotation y précédente, vous obtenez l'axe x local, lorsque vous faites ensuite pivoter l'objet autour de vous pour obtenir le résultat que vous affichez dans votre image. Pour que la rotation soit logique du point de vue des utilisateurs, vous souhaitez plutôt faire pivoter l'objet en utilisant l'axe des coordonnées universelles.

Si vous voulez que votre utilisateur puisse faire tourner votre objet plusieurs fois, il serait logique de stocker votre rotation dans un quaternion au lieu d'une matrice, avec le temps, plusieurs rotations (et inexactitudes en virgule flottante) donneront à la matrice un aspect de moins en moins semblable à une matrice de rotation, la même chose se produit dans un quaternion bien sûr, mais juste normaliser le quaternion le ramène à une bonne rotation.

Utilisez simplement le quaternion d'identité comme valeur initiale, et chaque fois que l'utilisateur glisse sur l'écran, vous faites pivoter votre quaternion avec le Quaternion.fromAxisAngle(yAxis, -angleX)code. Toujours en utilisant (1,0,0,1) pour x rotations et (0,1,0,1) pour y rotations.

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

Puisque vous n'avez pas mentionné le langage ou un cadre spécifique, les méthodes sur Quaternion peuvent être appelées différemment bien sûr, et normaliser n'est pas nécessaire d'appeler souvent, mais comme la rotation provient d'un utilisateur qui balaie l'écran, elles ne le seront pas ralentir beaucoup les choses et de cette façon il n'y a aucune chance que le Quaternion glisse loin d'un quaternion unitaire.

Daniel Carlsson
la source
J'obtiens exactement le même comportement en faisant cela.
Christopher Perry
1
@ChristopherPerry Essayez d'inverser l'ordre de rotation: myRotation = rotationX.rotate(rotationY).rotate(myRotation).normalize()ils ne sont pas commutatifs, donc l'ordre dans lequel vous les effectuez compte. Ajoutez un autre commentaire avec votre framework / langage si cela n'a pas fonctionné et je creuserai un peu plus.
Daniel Carlsson
Ça ne marche pas non plus, j'ai le même comportement.
Christopher Perry
J'ai mis TOUT mon code ici
Christopher Perry