rotate around arbitrary point

Hi, all

I’d like to do something like Gooogle earth e.g., if I click on an arbitrary location on the map and drag the mouse, the whole map should rotate about the location where mouse is clicked at.

Up to now, what I achieved is when I click on a point on the white line, the sheet rotate around that point, but if I click not on the white line, the sheet will rotate around a point on the white line, and that point is aligned vertically with the point I click.

Not sure if I have made myself clear, maybe the code explains better.

(You can press ESC to reset the scene)


#include <GL/gl.h>
#include <GL/glu.h>
#include <glut.h>
#include <math.h>

int mWidth, mHeight;
float mZTrans;
float mYAngle;
int mMouseX, mMouseY;
double mPosX, mPosY, mPosZ;
int mMouseMove;

void getClickCoord(int x, int y, double *posX, double *posY, double *posZ)
{
  int viewport[4];
  double modelview[16];
  double projection[16];
  float winX, winY, winZ;  

  glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
  glGetDoublev(GL_PROJECTION_MATRIX, projection);
  glGetIntegerv(GL_VIEWPORT, viewport);

  winX = (float)x;
  winY = (float)(viewport[3] - y - 1);
  glReadPixels((int)winX, (int)winY, 1, 1,
      GL_DEPTH_COMPONENT, GL_FLOAT, &winZ );
  gluUnProject(winX, winY, winZ, modelview, projection, viewport,
      posX, posY, posZ);
}

float normalizeAngle(float angle)
{
  angle -= (360*(int)(angle/360));
  if (angle < 0) angle += 360;
  return angle;
}

void glOrthoBegin()
{
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0, mWidth, 0, mHeight, -1, 1);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
}

void glOrthoEnd()
{
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
}

void paintCircle(float radius, GLint xoffset, GLint yoffset)
{
  float delta = 0.05;
  glPushAttrib(GL_CURRENT_BIT);
  glColor3f(0.0, 1.0, 0.0);
  glBegin(GL_LINE_LOOP);
  float theta;
  for (theta = 0; theta < 2*M_PI; theta += delta) {
    glVertex2i((int)(radius*cos(theta))+xoffset,
        (int)(radius*sin(theta))+yoffset);
  }
  glEnd();
  glPopAttrib();
}

void paintQuads()
{
  int row, col;
  float xoffset, zoffset;

  glPushAttrib(GL_CURRENT_BIT);
  glBegin(GL_QUADS);
  for (row = 0; row < 10; row++) {
    zoffset = 1*row+0.5 - 5;
    for (col = 0; col < 10; col++) {
      xoffset = 1*col+0.5 - 5;
      glColor3f(row*0.1, col*0.1, 0.0);
      glVertex3f(-0.5+xoffset, 0, -0.5+zoffset);
      glVertex3f(-0.5+xoffset, 0,  0.5+zoffset);
      glVertex3f( 0.5+xoffset, 0,  0.5+zoffset);
      glVertex3f( 0.5+xoffset, 0, -0.5+zoffset);
    }
  }
  glEnd();
  glPopAttrib();
}

void reset()
{
  mZTrans = -15.0;
  mYAngle = 0.0;
  mMouseX = mMouseY = 0;
  mMouseMove = 0;
}

void init() 
{
  glClearColor(0.0, 0.0, 0.0, 1.0);
  glClearDepth(1.0);
  glShadeModel(GL_SMOOTH);
  glDepthFunc(GL_LEQUAL);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);

  float ambient[] = {0.5, 0.5, 0.5, 1.0};
  float diffuse[] = {0.8, 0.8, 0.8, 1.0};
  float specular[] = {1.0, 1.0, 1.0, 1.0};
  float position[] = {0.0, 0.0, 0.0, 1.0};
  glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
  glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
  glLightfv(GL_LIGHT0, GL_POSITION, position);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_COLOR_MATERIAL);
}

void display()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();
  glTranslatef(0, 0, mZTrans);
  if (mMouseMove) glTranslatef(mPosX, mPosY, mPosZ);
  glRotatef(45.0, 1, 0, 0);
  glRotatef(mYAngle, 0, 1, 0);
  if (mMouseMove) glTranslatef(-mPosX, -mPosY, -mPosZ);
  paintQuads();
  if (mMouseMove) {
    glOrthoBegin();
    paintCircle(20.0, mMouseX, (mHeight-mMouseY-1));
    glOrthoEnd();
  }
  mMouseMove = 0;
  glutSwapBuffers();
}

void reshape(int width, int height) 
{
  glViewport(0, 0, width, height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  mWidth = width; mHeight = height;
  float aspect = (float)width/(float)height;

  float top, bottom, left, right;
  top = 0.1*tan(20.0/180.0*M_PI);
  if (aspect < 1.0) top *= (1.0/aspect);
  bottom = -top;
  right = top*aspect;
  left = -right;
  glFrustum(left, right, bottom, top, 0.1, 100);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void mouse(int button, int state, int x, int y)
{
  if (state == GLUT_DOWN) {
    mMouseX = x; mMouseY = y;
    getClickCoord(x, y, &mPosX, &mPosY, &mPosZ);
  }
  else if (state == GLUT_UP) {
    glutPostRedisplay();
  }
}

void motion(int x, int y)
{
  mYAngle = normalizeAngle(mYAngle + 5);
  mMouseMove = 1;
  glutPostRedisplay();
}

void keyboard(unsigned char key, int x, int y)
{
  switch (key) {
    case 27:
      reset();
      glutPostRedisplay();
      break;

    case '=':
    case '+':
      mZTrans /= 1.2;
      glutPostRedisplay();
      break;

    case '-':
    case '_':
      mZTrans *= 1.2;
      glutPostRedisplay();
      break;

    case 'Q':
    case 'q':
      exit(0);
      break;

    default:
      break;
  }
}

int main(int argc, char** argv)
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
  glutInitWindowSize(640, 480); 
  glutInitWindowPosition(100, 60);
  glutCreateWindow(argv[0]);
  reset();
  init();
  glutDisplayFunc(display); 
  glutReshapeFunc(reshape);
  glutMouseFunc(mouse);
  glutMotionFunc(motion);
  glutKeyboardFunc(keyboard);
  glutMainLoop();
  return 0;
}