web-dev-qa-db-fra.com

Est-il possible de faire pivoter un objet autour de son propre axe et non autour de l'axe de la coordonnée de base?

Je suis les exemples de rotation OpenGL es de Google pour faire pivoter un simple carré (et non un cube) sur mon application Android, par exemple ce code:

gl.glRotatef(xrot, 1.0f, 0.0f, 0.0f);   //X
gl.glRotatef(yrot, 0.0f, 1.0f, 0.0f);   //Y
gl.glRotatef(zrot, 0.0f, 0.0f, 1.0f);   //Z

Cela fonctionne bien si vous ne faites que pivoter autour d'un axe.

Mais si vous faites la rotation autour d'un axe, et après cela, vous faites la rotation autour d'un autre axe, la rotation n'est pas juste. Je veux dire que la rotation est faite autour des axes du système de coordonnées de base (global) et non du système de coordonnées du carré.

EDIT avec code pour Shahbaz

public void onDrawFrame(GL10 gl) {
    //Limpiamos pantalla y Depth Buffer
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);    
    gl.glLoadIdentity();
    //Dibujado
    gl.glTranslatef(0.0f, 0.0f, z);         //Move z units into the screen
    gl.glScalef(0.8f, 0.8f, 0.8f);          //Escalamos para que quepa en la pantalla
    //Rotamos sobre los ejes.
    gl.glRotatef(xrot, 1.0f, 0.0f, 0.0f);   //X
    gl.glRotatef(yrot, 0.0f, 1.0f, 0.0f);   //Y
    gl.glRotatef(zrot, 0.0f, 0.0f, 1.0f);   //Z
    //Dibujamos el cuadrado
    square.draw(gl);    
    //Factores de rotación.
    xrot += xspeed;
    yrot += yspeed;
}

Dessin du carré:

    public void draw(GL10 gl) {
    gl.glFrontFace(GL10.GL_CCW);
    //gl.glEnable(GL10.GL_BLEND);
    //Bind our only previously generated texture in this case
    gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
    //Point to our vertex buffer
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
    gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
    //Enable vertex buffer
    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    //Draw the vertices as triangle strip
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);
    //Disable the client state before leaving
    gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    //gl.glDisable(GL10.GL_BLEND);

}

VERTEX BUFFER VALUES:

private FloatBuffer vertexBuffer;

private float vertices[] = 
{ 
    -1.0f, -1.0f, 0.0f,     //Bottom Left
    1.0f, -1.0f, 0.0f,      //Bottom Right
    -1.0f, 1.0f, 0.0f,      //Top Left
    1.0f, 1.0f, 0.0f        //Top Right
};
.
.
.
public Square(int resourceId) {
        ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
        byteBuf.order(ByteOrder.nativeOrder());
        vertexBuffer = byteBuf.asFloatBuffer();
        vertexBuffer.put(vertices);
        vertexBuffer.position(0);
.
.
.
11

La première chose à savoir, c’est que dans OpenGL, les matrices de transformation sont multipliées par la droite. Qu'est-ce que ça veut dire? Cela signifie que la dernière transformation que vous écrivez est d'abord appliquée à l'objet.

Alors regardons votre code:

gl.glScalef(0.8f, 0.8f, 0.8f);
gl.glTranslatef(0.0f, 0.0f, -z);
gl.glRotatef(xrot, 1.0f, 0.0f, 0.0f);   //X
gl.glRotatef(yrot, 0.0f, 1.0f, 0.0f);   //Y
gl.glRotatef(zrot, 0.0f, 0.0f, 1.0f);   //Z
gl.glTranslatef(0.0f, 0.0f, z);

square.draw(gl);    

Cela signifie que, tout d'abord, l'objet est déplacé vers (0.0f, 0.0f, z). Ensuite, il tourne autour de Z, puis de Y, puis de X, puis est déplacé de (0.0f, 0.0f, -z) et finalement redimensionné.

Vous avez la bonne échelle. Vous le mettez en premier, donc il est appliqué en dernier. Tu as aussi

gl.glTranslatef(0.0f, 0.0f, -z);

au bon endroit, parce que vous voulez d'abord faire pivoter l'objet, puis le déplacer. Notez que lorsque vous faites pivoter un objet, il pivote TOUJOURS autour de la coordonnée de base, c'est-à-dire (0, 0, 0). Si vous souhaitez faire pivoter l'objet autour de ses propres axes, l'objet lui-même doit se trouver dans (0, 0, 0).

Donc, juste avant d'écrire

square.draw(gl);

vous devriez avoir les rotations. Dans l’état actuel de votre code, vous déplacez l’objet loin (en écrivant

gl.glTranslatef(0.0f, 0.0f, z);

avant square.draw(gl);) et ALORS faire pivoter ce qui gâche tout. Supprimer cette ligne vous rapproche beaucoup de ce dont vous avez besoin. Donc, votre code ressemblera à ceci:

gl.glScalef(0.8f, 0.8f, 0.8f);
gl.glTranslatef(0.0f, 0.0f, -z);
gl.glRotatef(xrot, 1.0f, 0.0f, 0.0f);   //X
gl.glRotatef(yrot, 0.0f, 1.0f, 0.0f);   //Y
gl.glRotatef(zrot, 0.0f, 0.0f, 1.0f);   //Z

square.draw(gl);    

Maintenant, le carré doit tourner en place.

Note: Après avoir exécuté ceci, vous verrez que la rotation du carré serait plutôt gênante. Par exemple, si vous faites pivoter z de 90 degrés, la rotation autour de x ressemblerait à une rotation autour de y en raison de la rotation précédente. Pour l'instant, cela peut vous convenir, mais si vous voulez que ça ait l'air vraiment bien, vous devriez le faire comme ça:

Imaginez, vous ne faites pas pivoter l'objet, mais faites pivoter une caméra autour de l'objet, en le regardant. En modifiant xrot, yrot et zrot, vous déplacez la caméra sur une sphère autour de l'objet. Ensuite, une fois que vous avez trouvé l'emplacement de la caméra, vous pouvez soit faire le calcul et obtenir les paramètres corrects pour appeler glRotatef et glTranslatef, ou bien utiliser gluLookAt.

Cela nécessite une certaine compréhension des mathématiques et de l'imagination 3D. Donc, si vous ne le faites pas correctement le premier jour, ne soyez pas frustré.

Edit: Voici l’idée de faire pivoter les coordonnées de l’objet pivoté;

Commençons par la rotation autour de z. Donc vous avez

gl.glRotatef(zrot, 0.0f, 0.0f, 1.0f);   //Z

Maintenant, le vecteur d'unité Y global est évidemment (0, 1, 0), mais l'objet a pivoté et donc son vecteur d'unité Y a également pivoté. Ce vecteur est donné par:

[cos(zrot) -sin(zrot) 0]   [0]   [-sin(zrot)]
[sin(zrot)  cos(zrot) 0] x [1] = [ cos(zrot)]
[0          0         1]   [0]   [ 0        ]

Par conséquent, votre rotation autour de y devrait ressembler à ceci:

gl.glRotatef(yrot, -sin(zrot), cos(zrot), 0.0f);   //Y-object

Vous pouvez essayer ceci jusqu'à présent (désactiver la rotation autour de x) et voir que cela ressemble à ce que vous voulez (je l'ai fait, et cela a fonctionné).

Maintenant pour x, cela devient très compliqué. Pourquoi? En effet, le vecteur d'unité X est non seulement d'abord pivoté autour du vecteur z, mais également après avoir été pivoté autour du vecteur (-sin(zrot), cos(zrot), 0).

Alors maintenant, le vecteur d'unité X dans le cooridnate de l'objet est

                   [cos(zrot) -sin(zrot) 0]   [1]                      [cos(zrot)]
Rot_around_new_y * [sin(zrot)  cos(zrot) 0] x [0] = Rot_around_new_y * [sin(zrot)]
                   [0          0         1]   [0]                      [0        ]

Appelons ce vecteur (u_x, u_y, u_z). Ensuite, votre dernière rotation (celle autour de X) serait la suivante:

gl.glRotatef(xrot, u_x, u_y, u_z);   //X-object

Alors! Comment trouver la matrice Rot_around_new_y? Voir ici sur la rotation autour d'axes arbitraires. Allez à la section 6.2, la première matrice, obtenez la rotation de la sous-matrice 3 * 3 (c’est-à-dire ignorez la colonne la plus à droite qui est liée à la traduction) et mettez (-sin(zrot), cos(zrot), 0) comme axe (u, v, w) et theta comme yrot.

Je ne ferai pas le calcul ici parce que cela demande beaucoup d'efforts et que je finirai par faire une erreur quelque part par là de toute façon. Cependant, si vous faites très attention et que vous êtes prêt à les revérifier plusieurs fois, vous pouvez l'écrire et multiplier la matrice.


Remarque supplémentaire: un moyen de calculer Rot_around_new_y pourrait également utiliser Quaternions . Un quaternion est défini comme un vecteur 4c [xs, ys, zs, c], ce qui correspond à une rotation autour de [x, y, z] sous un angle dont sin est s et dont cos est c.

Ce [x, y, z] est notre "nouveau Y", c’est-à-dire [-sin(zrot), cos(zrot), 0]. L'angle est yrot. Le quaternion pour la rotation autour de Y est donc donné par:

q_Y = [-sin(zrot)*sin(yrot), cos(zrot)*sin(yrot), 0, cos(yrot)]

Enfin, si vous avez un quaternion [a, b, c, d], la matrice de rotation correspondante est donnée par :

[1 - 2b^2 - 2c^2        2ab + 2cd           2ac - 2bd   ]
[   2ab - 2cd        1 - 2a^2 - 2c^2        2bc - 2ad   ]
[   2ac - 2bd           2bc + 2ad        1 - 2a^2 - 2b^2]
23
Shahbaz

Je ne connais pratiquement rien à l'open d'OpenGL, mais j'imagine que traduire à 0, pivoter puis retranscrire devrait fonctionner ...

gl.glTranslatef(-x, -y, -z);
gl.glRotatef(xrot, 1.0f, 0.0f, 0.0f);   //X
gl.glRotatef(yrot, 0.0f, 1.0f, 0.0f);   //Y
gl.glRotatef(zrot, 0.0f, 0.0f, 1.0f);   //Z
gl.glTranslatef(x, y, z);
3
Matt MacLean

Je pense que vous avez besoin de quaternions pour faire ce que vous voulez faire. L'utilisation de rotations autour des axes de coordonnées fonctionne parfois, mais finit par souffrir de "blocage du cardan". Cela se produit lorsque la rotation souhaitée passe à proximité d'un axe de coordonnées et crée un giration indésirable lorsque la rotation requise autour de l'axe s'approche de 180 degrés. 

Un quaternion est un objet mathématique représentant une rotation autour d'un axe arbitraire défini comme un vecteur 3D. Pour l'utiliser en openGL, vous générez une matrice à partir du quaternion et vous la multipliez par votre matrice modelview. Cela transformera vos coordonnées mondiales afin que le carré soit pivoté. 

Vous pouvez obtenir plus d'informations ici http://content.gpwiki.org/index.php/OpenGL:Tutorials:Using_Quaternions_to_represent_rotation

J'ai une classe Quaternion C++ que je pourrais vous envoyer si cela peut vous aider.

3
Steve O'Connor

Essayez d'ajouter

glMatrixMode(GL_MODELVIEW);
glPushMatrix();

avant le code de rendu pour un seul cube qui est en rotation, puis

glPopMatrix();

après le rendu est fait. Cela vous donnera une matrice de vues supplémentaire à utiliser sans affecter votre matrice principale viewview.

En gros, cela crée une nouvelle caméra modelview, la rend, puis la détruit.

0
Sean