bicanul123
bicanul123

Reputation: 427

Can't rotate object around its own center on mouse click OpenGL

I succesfully managed to rotate my object but I need to right click on the object to stop its rotation. I don't know how to make the object to rotate just around its center and then stop, I'll attach the code to see exactly what's happening with this shape. The problem, I think, is with the spinDisplay() function... the thing is that I need to rotate around its center on left mouse click and on the right mouse click I should change the color of the object....

#include <stdlib.h>
#include <math.h>
#include "dependente\freeglut\freeglut.h"
#include "dependente\glfw\glfw3.h"
#include <stdio.h> //incluziuni librarii

float ORG[3] = { 0,0,0 };
static GLfloat spin = 0.0;
GLfloat viewangle = 0, tippangle = 0, traj[120][3]; //variabila pentru unghi camera

GLfloat d[3] = { 0.1, 0.1, 0.1 }; //vector directie

GLfloat  xAngle = 0.0, yAngle = 0.0, zAngle = 0.0;
bool draw_triangle = false; //variabila desenat figuri 
bool draw_square = false;
bool draw_decagon = false;


void Triangle(void) //draw the triangle shape
{
    glBegin(GL_TRIANGLE_FAN);//triangles have a common vertex, which is the central vertex
    glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f);   //V0(red)
    glColor3f(0.0f, 1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);   //V1(green)
    glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(1.0f, -1.0f, 1.0f);   //V2(blue)
    glEnd();
}
void Square(void) {
    glBegin(GL_QUADS);
    glVertex2f(-1.0f, 1.0f); // top left
    glVertex2f(1.0f, 1.0f); // top right 
    glVertex2f(1.0f, -1.0f); // bottom right
    glVertex2f(-1.0f, -1.0f); // bottom left
    glEnd();
}
void Decagon(void) //draw the decagon shape
{
    glBegin(GL_POLYGON);
                glColor3f(0.0f, 0.0f, 1.0f);
                glVertex3f(0.72f,0.8f, 0.0f); //a1
                glVertex3f(0.52f,   0.8f,0.0f);  //z
                glVertex3f(0.35f,   0.64f, 0.0f); //b1
                glVertex3f(0.3f,   0.48f, 0.0f); //d1
                glVertex3f(0.35f,   0.3f, 0.0f); //e1
                glVertex3f(0.52f, 0.16f, 0.0f); //l1
                glVertex3f(0.72f, 0.16f, 0.0f); //m1
                glVertex3f(0.9f, 0.3f, 0.0f); //o1
                glVertex3f(0.95f, 0.48f, 0.0f); //p1
                glVertex3f(0.9f, 0.64f, 0.0f); //c1
                glScalef(10, 10, 10);
                glTranslatef(1, 2, 3);
    glEnd();
}

void Keyboard(unsigned char key, int x, int y) //press a key to perform actions
{
    switch (key) {

    case 'd': d[0] += 0.1;  break; //camera right
    case 'a': d[0] -= 0.1;  break; //camera left
    case 'w': d[1] += 0.1;  break; //camera up 
    case 's': d[1] -= 0.1;  break; //camera down 
    case 'm': d[2] += 0.1;  break; //magnify
    case 'n': d[2] -= 0.1;  break; //minify
    case 't': draw_triangle = true; draw_decagon = false;   break; //draw pyramid when key is pressed
    case 'q': draw_square = true; draw_decagon = false; draw_triangle = false; break; //draw cube when key is pressed
    case 'l': draw_decagon = true; draw_triangle = false; draw_square = false; break; //draw prism when key is pressed

    case 'x': xAngle += 5;  break; //modify x axis angle
    case 'y': yAngle += 5;  break; //modify y axis angle
    case 'z': zAngle += 5;  break;  //modify z axis angle

    default: printf("   Keyboard %c == %d", key, key); //see what key it's pressed
    }

    glutPostRedisplay();
}

void spinDisplay() //here it's the problematic function
{
    spin = spin + 0.1;
    if (spin > 360.0)
    {
        spin = 0.0;
    }
    glutPostRedisplay();
}
void mouse(int buton, int state, int x, int y)
{
    switch (buton) {
    case GLUT_LEFT_BUTTON:
        if (state == GLUT_DOWN)
            glutIdleFunc(spinDisplay);
        break;
    case GLUT_RIGHT_BUTTON: //here I don't know how to change the color of the shape
        glutIdleFunc(NULL);
    default:glutIdleFunc(NULL);

        break;
    }
}
void redraw(void)
{

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    glLoadIdentity();

    glTranslatef(0, 0, -3);
    glRotatef(tippangle, 1, 0, 0);  // Up and down arrow keys 'tip' view.
    glRotatef(viewangle, 0, 1, 0);  // Right/left arrow keys 'turn' view.

    glDisable(GL_LIGHTING);


    glPushMatrix();
    glTranslatef(d[0], d[1], d[2]);    // Move box down X axis.
    glScalef(0.7f, 0.7f, 0.7f); //increase the object size
    glRotatef(zAngle, 0, 0, 1);
    glRotatef(yAngle, 0, 1, 0);
    glRotatef(xAngle, 1, 0, 0);
    glRotatef(spin, 0.0, 0.0, 1.0);

    if (draw_triangle)
        Triangle();

    if (draw_decagon)
        Decagon();
    if (draw_square)
        Square();

    glPopMatrix();

    glutSwapBuffers();
}

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitWindowSize(900, 600);
    glutInitWindowPosition(300, 300);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE);

    glutCreateWindow("Figure Rotation");
    glutDisplayFunc(redraw);
    glutKeyboardFunc(Keyboard);
    glutMouseFunc(mouse);
    glClearColor(0.1, 0.0, 0.1, 1.0);

    glMatrixMode(GL_PROJECTION);//specify which matrix is the current matrix, matrix that represents your camera's lens (aperture, far-field, near-field, etc).
    gluPerspective(60, 1.5, 1, 10); //set up a perspective projection matrix
    glMatrixMode(GL_MODELVIEW); //specify which matrix is the current matrix,matrix that represents your camera (position, pointing, and up vector).
    glutMainLoop();

    return 1;
}

Upvotes: 0

Views: 2030

Answers (1)

haxpor
haxpor

Reputation: 2601

Solely based on your code, the main problem is at Decagon() for its shape vertices definition. As such vertices are defined not at the center of the shape itself but defined towards the top right, thus it won't rotate around itself but seem to orbit around the center although your sequence of matrix multiplications are working ok.

For simplicity, I would visualize centering it at 0,0 along xy plane, then define right half of its shape then mirror it back to the left one. You can take advantage of - minus sign. Implicitly take advantage of defining shape in NDC (Normalizd Device Coordinate) space.

Note: not exactly the same ratio as per your original definition, but to get you an idea. You can try swapping the following into yours, then it should rotate around itself.

    glBegin(GL_POLYGON);
        glColor3f(0.0f, 0.0f, 1.0f);
        glVertex3f(0.25f, 0.5f, 0.0f);
        glVertex3f(0.45f, 0.30f, 0.0f);
        glVertex3f(0.55f, 0.0f, 0.0f);
        glVertex3f(0.45f, -0.30f, 0.0f);
        glVertex3f(0.25f, -0.5f, 0.0f);
        glVertex3f(-0.25f, -0.5f, 0.0f);
        glVertex3f(-0.45f, -0.30f, 0.0f);
        glVertex3f(-0.55f, 0.0f, 0.0f);
        glVertex3f(-0.45f, 0.30f, 0.0f);
        glVertex3f(-0.25f, 0.5f, 0.0f);
        //glScalef(10, 10, 10);  // this won't have any effect on result
        //glTranslatef(1, 2, -3);// the same
    glEnd();

You have 2 options here

  1. Completely change the vertices definition (only with Decagon to be similar to above relative to the origin). Other shapes are already good, it's defined relative to the origin.
  2. Carefully determine the origin of the shape regardless of how your defined shape's vertices. Use such position to translate back the shape as part of matrix operation firstly before all other operations (please read on to know why).

Concept of rotation around itself

The concept of rotation around itself is that we need to do the following operations in order

  1. Scale (in this case we don't have)
  2. Rotation
  3. Translation

Scaling although we don't have in this case, should be last otherwise it might affect other two operations.

If we translate first to the arbitrary position, then the rotation will happen around such point. In fact, rotation works relatively to the origin 0,0, thus we just need to do by any means to place the object back to origin first before we proceed, then we can rotate, translate to desire position it should be, and scale.

Let's see your matrix multiplication order

    glScalef(0.7f, 0.7f, 0.7f); //increase the object size
    glTranslatef(d[0], d[1], d[2]);    // Move box down X axis.
    glRotatef(zAngle, 0, 0, 1);
    glRotatef(yAngle, 0, 1, 0);
    glRotatef(xAngle, 1, 0, 0);
    glRotatef(spin, 0.0, 0.0, 1.0);

This means we do the following in order

  • rotate around z-axis with spin angle
  • rotate around x-axis with xAngle angle
  • rotate around y-axis with yAngle angle
  • rotate around z-axis with zAngle angle

Although we could possibly combine the first and last together, but anyway it's ok.

Also you might want to further look at Euler Angles when we rotate around 3 cardinal axes like this, it can lead to Gimbal lock problem but it can be solved by limiting angles user can rotate around a certain axis.

The order is right. This is translated into mathematics terms as S * T * Rz * Ry * Rx * Rspin in which you can see it's inverse of the order in code. Rspin happen first, then Rx then so on.

Now what happen if Decagon shape is defined not relative to the origin, but defined to in the way that it translated to the right.

Take my vertices definition, but + 0.55f for all x position, we will have

    glBegin(GL_POLYGON);
        glColor3f(0.0f, 0.0f, 1.0f);
        glVertex3f(0.80f, 0.5f, 0.0f);
        glVertex3f(1.0f, 0.30f, 0.0f);
        glVertex3f(1.10f, 0.0f, 0.0f);
        glVertex3f(1.0f, -0.30f, 0.0f);
        glVertex3f(0.80f, -0.5f, 0.0f);
        glVertex3f(0.30f, -0.5f, 0.0f);
        glVertex3f(0.10f, -0.30f, 0.0f);
        glVertex3f(0.0f, 0.0f, 0.0f);
        glVertex3f(0.1f, 0.30f, 0.0f);
        glVertex3f(0.30f, 0.5f, 0.0f);
    glEnd();

If you swap above code to your vertices definition, then it won't rotate around itself anymore. But we know that it takes -0.55f in x-axis to bring this shape back to origin, thus if we add glTranslatef(-0.55f, 0.0f, 0.0f) to be the first operation to execute then it will work the same.

We'd have

    glScalef(0.7f, 0.7f, 0.7f);
    glTranslatef(d[0], d[1], d[2]);
    glRotatef(zAngle, 0, 0, 1);
    glRotatef(yAngle, 0, 1, 0);
    glRotatef(xAngle, 1, 0, 0);
    glRotatef(spin, 0.0, 0.0, 1.0);
    glTranslatef(-0.55f, 0.0f, 0.0f);  // <------ add this

In short, translate target object to be at origin first before rotating (around itself), then proceed proper sequence as before.

If you desire to have such object to be located at the location you've defined the shape's vertices i.e. it's to the right +0.55f along x-axis and still rotate around itself. Then you use glTranslatef(d[0] + 0.55f, d[1], d[2]) instead.

Further Notes

  1. The last two gl operations glScalef() and glTranslatef() won't have any effect as you already drew the shape. These two operations get discarded every frame when you call glLoadIdentity().
  2. Just note that source code is still based on fixed-function pipeline of OpenGL. You might want to also take a look at modern programmable pipeline. This will allows you more flexibility in controlling virtual camera thus matrix operations are more clear cut and separated to the object itself whenever we need to move around. So this will make matrix operations easier to grasp, and to understand.

Edit For additional control and satisfy application requirement as follows

  • Left click to rotate indefinitely, then left click again to stop
  • Right click to cycle through the color for rendered shape

We have to have control flags, and information for us to change at any frame time as follows.

bool isSpinning = false;
#define NUM_COLOR 4
int sCurrColor = 0;
GLfloat sColor[NUM_COLOR][3] = {
    {1.0f, 1.0f, 1.0f},
    {1.0f, 0.0f, 0.0f},
    {0.0f, 1.0f, 0.0f},
    {0.0f, 0.0f, 1.0f}
};

So for colors, we have white, red, blue, and green. Total in 4 colors, each color has 3 component values for RGB. We start with white color as seen in sCurrColor for our index.

Now your mouse() function would looks like this

void mouse(int buton, int state, int x, int y)
{
    switch (buton) {
    case GLUT_LEFT_BUTTON:
        if (state == GLUT_DOWN && !isSpinning)
            isSpinning = true;
        else if (state == GLUT_DOWN && isSpinning)
            isSpinning = false;
        break;
    case GLUT_RIGHT_BUTTON: //here I don't know how to change the color of the shape
        if (state == GLUT_DOWN)
            sCurrColor = (sCurrColor + 1) % NUM_COLOR;
        break;
    default:glutIdleFunc(NULL);

        break;
    }
}

We optimized moving glutIdleFunc(spinDisplay); to be called inside main() function just before glutMainLoop(). As your requirements, we don't have to change it every frame.

Thus, spinDisplay() is now changed to be

void spinDisplay() //here it's the problematic function
{
    if (isSpinning)
    {
        spin = spin + 3.0;
        if (spin > 360.0)
        {
            spin = 0.0;
        }
    }
    glutPostRedisplay();
}

Probably better to change the name of function to something like display() as it's more generic to not confuse that we have to spin everytime. Anyway, I didn't change this for the sake of brevity and consistency of your code.

Now the last part is to plug in sColor to be used by all those shapes in rendering. For example for Decagon you can do this

glColor3f(sColor[sCurrColor][0], sColor[sCurrColor][1], sColor[sCurrColor][2]);

This will be the same for other shapes if you like to have the same effect by right clicking to cycle through the color.

Upvotes: 1

Related Questions