14

I am following the OpenGL es rotation examples from google to rotate a simple square (not a cube) on my Android App, for example this 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

It works fine if you only rotate around one axis.

But if you rotate around one axis, and after that, you rotate around another axis, the rotation is not fair. I mean that the rotation is done around the axes of base (global) coordinate system and not the square's own coordinate system.

EDIT with code for 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;
}

Draw of the square:

    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);
.
.
.
NullPointerException
  • 36,107
  • 79
  • 222
  • 382

5 Answers5

28

First thing you should know is that in OpenGL, transformation matrices are multiplied from right. What does it mean? It means that the last transformation you write gets applied to the object first.

So let's look at your 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);    

This means that, first, the object is moved to (0.0f, 0.0f, z). Then it is rotated around Z, then around Y, then around X, then moved by (0.0f, 0.0f, -z) and finally scaled.

You got the scaling right. You put it first, so it gets applied last. You also got

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

in the right place, because you first want to rotate the object then move it. Note that, when you rotate an object, it ALWAYS rotates around the base coordinate, that is (0, 0, 0). If you want to rotate the object around its own axes, the object itself should be in (0, 0, 0).

So, right before you write

square.draw(gl);

you should have the rotations. The way your code is right now, you move the object far (by writing

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

before square.draw(gl);) and THEN rotate which messes things up. Removing that line gets you much closer to what you need. So, your code will look like this:

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

Now the square should rotate in place.

Note: After you run this, you will see that the rotation of the square would be rather awkward. For example, if you rotate around z by 90 degrees, then rotating around x would look like rotating around y because of the previous rotation. For now, this may be ok for you, but if you want to it to look really good, you should do it like this:

Imagine, you are not rotating the object, but rotating a camera around the object, looking at the object. By changing xrot, yrot and zrot, you are moving the camera on a sphere around the object. Then, once finding out the location of the camera, you could either do the math and get the correct parameters to call glRotatef and glTranslatef or, use gluLookAt.

This requires some understanding of math and 3d imagination. So if you don't get it right the first day, don't get frustrated.

Edit: This is the idea of how to rotate along rotated object coordinates;

First, let's say you do the rotation around z. Therefore you have

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

Now, the global Y unit vector is obviously (0, 1, 0), but the object has rotated and thus its Y unit vector has also rotated. This vector is given by:

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

Therefore, your rotation around y, should be like this:

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

You can try this so far (disable rotation around x) and see that it looks like the way you want it (I did it, and it worked).

Now for x, it gets very complicated. Why? Because, the X unit vector is not only first rotated around the z vector, but after it is rotated around the (-sin(zrot), cos(zrot), 0) vector.

So now the X unit vector in the object's cooridnate is

                   [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        ]

Let's call this vector (u_x, u_y, u_z). Then your final rotation (the one around X), would be like this:

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

So! How to find the matrix Rot_around_new_y? See here about rotation around arbitrary axis. Go to section 6.2, the first matrix, get the 3*3 sub matrix rotation (that is ignore the rightmost column which is related to translation) and put (-sin(zrot), cos(zrot), 0) as the (u, v, w) axis and theta as yrot.

I won't do the math here because it requires a lot of effort and eventually I'm going to make a mistake somewhere around there anyway. However, if you are very careful and ready to double check them a couple of times, you could write it down and do the matrix multiplications.


Additional note: one way to calculate Rot_around_new_y could also be using Quaternions. A quaternion is defined as a 4d vector [xs, ys, zs, c], which corresponds to rotation around [x, y, z] by an angle whose sin is s and whose cos is c.

This [x, y, z] is our "new Y", i.e. [-sin(zrot), cos(zrot), 0]. The angle is yrot. The quaternion for rotation around Y is thus given as:

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

Finally, if you have a quaternion [a, b, c, d], the corresponding rotation matrix is given as:

[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]
Shahbaz
  • 46,337
  • 19
  • 116
  • 182
  • thanks a lot for your amazings explanations, but i am in the same position that i was before the other guy answer. In the first i doesn't have a "gl.glTranslatef(0.0f, 0.0f, z);" line after my rotations, i have it before them. ¿why? because that line is for apply the zoom selected by the user, then i need the +z translate, and not the -z. I will update the code of the question with my original code, without making the modifications that maclema told me. Please, check my new code of the question and tell me how to solve this, i really apreciaty your help, you are a powerfull opengl writter – NullPointerException Oct 11 '11 at 12:06
  • Ok then, can you also include the code for square.draw? I need to know how you are drawing your square. – Shahbaz Oct 11 '11 at 12:09
  • Could you also give the values inside `vertexBuffer`? What I need to know is the coordinates in which you are drawing the square. – Shahbaz Oct 11 '11 at 12:22
  • That seems fine. Can you explain what happens? Did you read the **Note** I wrote in my answer? Is your problem that the square rotates _in place_ but along the wrong axis? If so, then you do need to consider `gluLookAt` – Shahbaz Oct 11 '11 at 12:37
  • For `gluLookAt`, you would let's say have two variables, theta and phi that show where in a sphere around the square you are. Then you calculate the coordinates of a point on the sphere (`eye`) with theta and phi (see polar coordinate systems) then write `gluLookAt(eye_x, eye_y, eye_z, 0, 0, 0, 0, 0, 1);` The `(0, 0, 0)` is the location of the object and the `(0, 0, 1)` is the _up_ vector. Search google for gluLookAt for more information – Shahbaz Oct 11 '11 at 12:40
  • I think that gluLookAt is not the way to solve this with OpenGL ES for android. Isn't possible to simply solve it with a few lines of code modifying or changing the order of the lines of my code? This is starting to be too munch hard for my low math/3D skills – NullPointerException Oct 11 '11 at 14:17
  • Unfortunately, no. If you start thinking about it, it is impossible. If you first rotate around z, then x, your rotation around x will not be as you want it. If you rotate first around x then z, the problem is now with z! Unless, you for example, first rotate around z, then rotate around Rotz(rotz)*[0 1 0] which is the Y unit vector rotated by the same amount of z and finally rotate around x by Roty(roty)*Rotz(rotz)*[1 0 0]. Actually I just got this last idea and I'm not sure if it works, but you could try it – Shahbaz Oct 11 '11 at 14:38
  • See [here](http://en.wikipedia.org/wiki/Rotation_matrix#Rotations_in_three_dimensions) for the rotation matrices – Shahbaz Oct 11 '11 at 14:39
  • By the way, why would you want to rotate a square around x and y axes anyway? If you rotate it around those axes by 90 degrees they basically disappear because the square is flat! Note that your problem diminishes if you have only one rotation, for example along z axis. – Shahbaz Oct 11 '11 at 14:41
  • Please, can you explain me a little more this: "first rotate around z, then rotate around Rotz(rotz)*[0 1 0] which is the Y unit vector rotated by the same amount of z and finally rotate around x by Roty(roty)*Rotz(rotz)*[1 0 0]." I can't understand it, maybe if you edit your answer with some lines of code i will get it. THanks a lot for all this help, im apreciating it a lot – NullPointerException Oct 11 '11 at 14:57
  • i don't understand anything, but it's absolutly amazing how you can strive to explain this to a dump like me, i apreciate a lot your help and you have a positive vote, thanks ! – NullPointerException Oct 14 '11 at 12:13
  • @Pableras84, I wish I could explain more, but this whole transformations and stuff is like a course! If you have a base of math enough to understand matrix multiplication and perhaps rotation in 2D, you could start learning from the basics slowly, testing whatever you learn with examples and **make sure you understand before you move on**. Especially, first learn things in 2D (by testing, I mean take a point and a set of angles, first manually find the transformed point by drawing a picture, then verify the formula with your result) – Shahbaz Oct 14 '11 at 12:26
  • @Shahbaz: thank you for the answer. I have calculated the vector around which X rotation will take place. It works well. However, when the cube is flipped, i.e., X = 180, the direction of the Z rotation is flipped. (clockwise is anticlockwise and vice versa). Any pointers? – Iceman Apr 25 '14 at 19:45
  • @iceman, I already mentioned this in the **note** inside the answer. The reason is that transformations, including rotation, apply one by one as you write the functions, from last to first. So you're first rotating clockwise, then flip the object, which makes it look like it's rotated counterclockwise. The solution, as I said is to make one rotation taking all rotations into account rather than doing one axis at a time. I recommend `gluLookAt` though. – Shahbaz Apr 27 '14 at 02:02
  • @Shahbaz: could you please give me a pointer/direction about the arguments to glLookAt? – Iceman Apr 30 '14 at 13:55
  • @Iceman, documentation on OpenGL is easily found on the web. A quick search shows up [the man page](https://www.opengl.org/sdk/docs/man2/xhtml/gluLookAt.xml) for example. – Shahbaz Apr 30 '14 at 14:04
  • @Shahbaz: After calculating the vector around which X rotation happens, rotations around X and Y are correct, since the respective axes are updated as the object rotates. However, since it is assumed in the answer that z rotation always happens first, it is not correct, since the z-axis is not updated. Will you please suggest a solution to this? Also could you elaborate on "make on rotation taking all rotations into account", as you said? – Iceman Jun 04 '14 at 14:05
  • @iceman, since the rotation around z-axis is done first, it doesn't need to be updated! The rotation is done before the change in axes. Even though after rotation of other axes, the z-axis also changes, it's irrelevant since you don't want to rotate around it anymore. – Shahbaz Jun 04 '14 at 14:32
  • 1
    Regarding one rotation taking all into account, it means finding a vector, based on the `xrot`, `yrot` and `zrot` parameters and rotating the abject around that vector. It's not a trivial thing and besides for purely mathematical fun, I don't recommend actually doing it. Like I said, `gluLookAt` is the easiest. Regardless, even if you would rather do the math manually, it's best to store other parameters. Instead of `xrot`, `yrot` and `zrot`, similar to using `gluLookAt`, you can keep a point on the surface of a sphere that looks at its center, and use it generate the transformation matrix. – Shahbaz Jun 04 '14 at 14:38
  • @Shahbaz: however, in my use case, z-rotation can happen after x- and y-rotations – Iceman Jun 04 '14 at 14:38
  • @iceman, well then you need to reorder the operations. My explanations remain the same. Except the rotation matrices look slightly different. If you understand the mathematics behind my explanation, you should be able to work your way with different order of rotations. – Shahbaz Jun 04 '14 at 14:44
  • @Shahbaz: thanks for keeping with me man. I meant x, y, z rotations can happen in any order, any number of times. – Iceman Jun 04 '14 at 14:48
  • @Iceman, Then the problem becomes increasingly difficult. Basically you have to apply the last step over and over for every new rotation. This gets increasingly slow as the number of rotations increase. My guess is that there is a better way to do what you want. Why do you want to do this? – Shahbaz Jun 04 '14 at 15:19
  • @Shahbaz: The application is similar to an airplane simulator, as I want to achieve roll, pitch, yaw- style rotations for a square. – Iceman Jun 04 '14 at 15:26
  • 1
    @Iceman, Then I would certainly try to keep three parameters, for roll, pitch and yaw and accumulate them. Once you get the accumulated value of the values, apply them on each axis only once. For example if input is `+roll, +pitch, +roll, +yaw, -pitch`, they could be aggregated to `+2*roll, +yaw`. The effect of applying the previous 5 operations is not the same as applying the later 2 operations. However, I think the second one (aggregate input, apply once) is in fact what anyone would expect from the simulator. – Shahbaz Jun 04 '14 at 16:59
  • I may be wrong though, so you need to verify if this is acceptable behavior for your application. – Shahbaz Jun 04 '14 at 17:00
  • @Shahbaz: Thank you so much! One quick question: If I can find the transformation matrix for rotation myself, I can avoid using glRotatef() altogether, correct? – Iceman Jun 04 '14 at 19:47
  • 1
    @iceman, yes you can apply the transformation yourself (I forgot the function name). Also I believe the modern opengl approach is also to calculate your transformation matrices yourself. – Shahbaz Jun 04 '14 at 21:37
4

I know next-to-nothing about openGL, but I imagine translating to 0, rotating and then translating back should work...

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);
Matt MacLean
  • 19,410
  • 7
  • 50
  • 53
3

I think you need quaternions to do what you want to do. Using rotations about the coordinate axes works some of the time, but ultimately suffers from "gimbal lock". This happens when the rotation you want passes close by a coordinate axis and creates an unwanted gyration as the rotation required around the axis approaches 180 degrees.

A quaternion is a mathematical object that represents a rotation about an arbitrary axis defined as a 3D vector. To use it in openGL you generate a matrix from the quaternion and multiply it by your modelview matrix. This will transform your world coordinates so that the square is rotated.

You can get more info here http://content.gpwiki.org/index.php/OpenGL:Tutorials:Using_Quaternions_to_represent_rotation

I have a Quaternion C++ class I could send you if it helps.

Stephen O'Connor
  • 1,465
  • 1
  • 10
  • 20
0

I'm using opentk, nevertheless it's the same. First move the object half all it's dimensions size, then rotate and move back:

model = Matrix4.CreateTranslation(new Vector3(-width/2, -height / 2, -depth / 2)) * 

Matrix4.CreateRotationX(rotationX) * 

Matrix4.CreateRotationY(rotationY) * 

Matrix4.CreateRotationZ(rotationZ) *

Matrix4.CreateTranslation(new Vector3(width / 2, height / 2, depth / 2));
Nys
  • 99
  • 1
  • 9
0

Try adding

glMatrixMode(GL_MODELVIEW);
glPushMatrix();

before the render code for a single cube that's being rotated, and then

glPopMatrix();

after the rendering is done. It will give you an extra view matrix to work with without affecting your primary modelview matrix.

Essentially what this does is create a new modelview camera, render, then destroy it.

Sean
  • 254
  • 2
  • 10
  • Can you explain me a little more where i have to put these lines: glMatrixMode(GL_MODELVIEW); glPushMatrix(); – NullPointerException Oct 13 '11 at 08:01
  • This should go immediately before any manipulation of rotation, vertexes, etc. Any model related call. You pop the matrix when you're done. It's like opening a new editing space and closing it to seal in the changes. – Sean Oct 15 '11 at 07:14