Viewing axes flip when camera crosses origin

I have this simple OpenGL code to rotate the camera in a circle around the scene. When the user presses a key, the camera moves further along the circle.

#include <GL/glut.h> 
#include <stdio.h>
#include <math.h>

float X_MIN = -3.0;
float X_MAX = 3.0;
float Y_MIN = -3.0;
float Y_MAX = 3.0;

int WINDOW_WIDTH = 500;
int WINDOW_HEIGHT = 500;

float vInc = 0.01f;

// Viewing parameters
GLfloat cameraLoc[3] = {0, 8, 0};
GLfloat v = 0.0f;
GLfloat viewingRadius = 8.0f;

GLfloat xref = 0.0, yref = 0.0, zref = 0.0;
GLfloat x_up = 0.0, y_up = 1.0, z_up = 0.0;

GLfloat dnear = 1.0, dfar = 8.0;

void printMat(GLfloat mat[16])
{
  printf("\n**************************************");
  for (int r = 0; r < 4; r++)
  {
    printf("\n");
    printf("%f ", mat[r]);
    printf("%f ", mat[r+4]);
    printf("%f ", mat[r+8]);
    printf("%f ", mat[r+12]);
  }
  printf("\n");
}

void setupView()
{ 

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  // Specify the viewing parameters
  // view[] is the origin of the viewing frame
  // ref[] is the reference position (this is NOT projection reference point)
  // up[] is the view up vector
  gluLookAt(cameraLoc[0], cameraLoc[1], cameraLoc[2], xref, yref, zref, x_up, y_up, z_up);

  // Get and print the matrix
  GLfloat mvmat[16];
  glGetFloatv(GL_MODELVIEW_MATRIX, mvmat);
  printf("\nModelview matrix:");
  printMat(mvmat);

  // Projection
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(120.0, 1.0f, dnear, dfar);

  // Get and print the matrix
  GLfloat projmat[16];
  glGetFloatv(GL_PROJECTION_MATRIX, projmat);
  printf("\nProjection matrix:");
  printMat(projmat);
}


// Clear buffers, setup view, enable depth
void init(void)
{
  glClearColor(1.0,1.0,1.0,0.0);
  glClear(GL_COLOR_BUFFER_BIT);

  setupView();
}

void reshapeCb(GLint newWidth, GLint newHeight)
{
  glViewport(0, 0, newWidth, newHeight);

  WINDOW_WIDTH = newWidth;
  WINDOW_HEIGHT = newHeight;

  glutPostRedisplay();
} 

void checkViewUp()
{
  // At v=.25 and v=.75, the x value sign changes
  // adjust the view up vector if needed
  if(cameraLoc[0] > 0)
  {
    y_up = -1;
  }
  else
  {
    y_up = 1;
  }
}

// ascii key and (x,y) of mouse when the key is pressed
void keyboardCb(unsigned char key, int x, int y)
{
  switch(key)
  {
    case 'a':
      v += vInc;
      printf("\nPressed a, v: %f\n", v);
      if(v > 1)
      {
        v = 0;
      }

      break;
    case 'd':
      printf("\nPressed d, v: %f\n", v);
      v -= 0.01;
      if(v < 0)
      {
        v = 1;
      }
      break;
  }

  // May have changed the camera location, so redraw the scene
  glutPostRedisplay();
}

void computeCameraLoc()
{

  float pi = 3.14159;

  cameraLoc[0] = viewingRadius * cos(2*pi * v);
  cameraLoc[1] = viewingRadius * sin(2*pi * v);
  cameraLoc[2] = 0;

  printf("\ncameraLoc: [%f,%f,%f]\n", cameraLoc[0], cameraLoc[1], cameraLoc[2]);
}

void drawCube()
{
  glColor3f(1.0, 0.0, 0.0);
  int z = 1;

  // front
  glBegin(GL_QUADS);
    glVertex3i(-1, -1, z);
    glVertex3i(1, -1, z);
    glVertex3i(1, 1, z);
    glVertex3i(-1, 1, z);
  glEnd(); 

  // back
  glColor3f(0.0, 0.0, 0.0);
  z = -1;
  glBegin(GL_QUADS);
    glVertex3i(-1, -1, z);
    glVertex3i(1, -1, z);
    glVertex3i(1, 1, z);
    glVertex3i(-1, 1, z);
  glEnd();

  // top
  glColor3f(0.0, 1.0, 0.0);
  int y = 1;
  glBegin(GL_QUADS);
    glVertex3i(-1, y, -1);
    glVertex3i(1, y, -1);
    glVertex3i(1, y, 1);
    glVertex3i(-1, y, 1);
  glEnd();

  // bottom
  glColor3f(1.0, 1.0, 0.0);
  y = -1;
  glBegin(GL_QUADS);
    glVertex3i(-1, y, -1);
    glVertex3i(1, y, -1);
    glVertex3i(1, y, 1);
    glVertex3i(-1, y, 1);
  glEnd();

  // left
  glColor3f(0.0, 1.0, 1.0);
  int x = 1;
  glBegin(GL_QUADS);
    glVertex3i(x, 1, 1);
    glVertex3i(x, 1, -1);
    glVertex3i(x, -1, -1);
    glVertex3i(x, -1, 1);
  glEnd();

  // right
  glColor3f(1.0, 0.0, 1.0);
  x = -1;
  glBegin(GL_QUADS);
    glVertex3i(x, 1, 1);
    glVertex3i(x, 1, -1);
    glVertex3i(x, -1, -1);
    glVertex3i(x, -1, 1);
  glEnd();
}


void display()
{
  // Clear the screen
  glClear(GL_COLOR_BUFFER_BIT);

  // Compute camera position in world coordinates
  computeCameraLoc();

  // Check if we need to adjust the direction of view y axis
  checkViewUp();

  // Set viewing parameters and projection
  setupView();

  // Draw the cube
  drawCube();

  glutSwapBuffers();
}

int main(int argc, char** argv) 
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
    glutCreateWindow("Hello world!");

    // Setup callbacks
    glutReshapeFunc(reshapeCb);
    glutKeyboardFunc(keyboardCb);
    glutDisplayFunc(display);

    init();
    glutMainLoop();


    return 0;
}

The function checkViewUp will flip the sign of the up vector depending on the sign of the camera’s x position. If I do not do this, then the x and y axes of the view frame get their direction flipped when the camera’s x position sign changes. The effect of their directions changing is that the cube flips upside down when the camera crosses over the

Negating the sign of the y value for the view up vector when the camera’s x sign changes fixes this problem.

Why does this happen and why does flipping the direction of the view up vector fix it? I don’t quite understand the math of this issue. I think it has something to do with the z and y axes being aligned when the camera is directly above the origin, but modelview matrix values make it seem like OpenGL is deciding a new direction for y based on printing out the matrix.

If someone can explain exactly what is happening to cause the flipping and/or why negating the view up y direction fixes it, or point me in the direction of reading about this, then I would appreciate it.

Indeed. The result of gluLookAt is indeterminate if center-eye is parallel to up. The cross product of these vectors will be the zero vector, and normalising it will produce undefined results. If you’re moving the camera in a plane, the up direction would ideally be perpendicular to the plane. In this case, you’re moving the camera in the X-Y plane (Z=0), so the up direction would ideally be the Z axis (0,0,1) or (0,0,-1).

Imagine sitting in a seat which is attached to the end of an arm which is rotating about the Z axis. The seat swivels so that you’re always facing the centre of rotation (i.e. looking along the arm), with the roll chosen so that you’re as close to “upright” as possible. Starting from a position which is level with the centre, you move upwards while starting to look down; the roll doesn’t change. As you go over the top, the seat rolls through 180 degrees so that you aren’t upside-down, i.e. your head remains above your feet.

If you just want to “orbit” an object with the roll never changing, you’re probably better off using glTranslate and glRotate rather than gluLookAt.

When you say “the roll is chosen…” - do you mean the rotation around z? If z is pointing out, then roll would be rotation around z. Or do you mean some other axis?

Yes; specifically, about eye-space Z. The resulting matrix will have the eye-space Z axis parallel to center-eye, the X and Y axes will be perpendicular to that (and to each other). The up vector provides the final constraint; the X and Y axis are rotated about Z such that Y is as close as possible to the up vector.

Okay, I can see how the seat example is what is essentially happening in my scene with my viewing frame. So, the following is my understanding of what is happening, and why it is necessary to flip the viewing axes:

  1. The camera begins at some position [a,0,0], a< 0, and the camera begins orbiting in the xy-plane. The up vector is parallel to the world y axis.
  2. While the camera is in the second quadrant, the viewing y axis direction is not adjusted.
  3. When the camera is directly above the world origin, center-eye is parallel to the up vector. At this point, the x and y viewing directions are the same angle away from the up vector.
  4. Once the camera moves into the first quadrant, the viewing x and y axes have their directions flipped. Prior to flipping, he direction of the viewing x axis is more similar to the direction of up than the viewing y axis direction is. However, the direction of the viewing y axis should be closer to the direction of the up vector, so viewing x and y are flipped.

Is this correct? If not, then can you point out what’s wrong? Thanks for your help so far.

The view rotates by 180 degrees, so what was -X becomes +X and what was -Y becomes +Y. If this didn’t happen, by the time you get to [-a,0,0] you’d be upside-down. gluLookAt keeps you as close to “right way up” as possible while looking at the target.

The world-space “up” vector will always lie in the Y-Z plane in eye space. Specifically, in the +Y half, i.e. it will always be closer to +Y than to -Y.

The calculation is given in the gluLookAt reference page. The flip isn’t a consequence of an explicit check, but the way that a cross product generates a vector which is perpendicular to both operands. The matrix whose columns are [a,b,a×b] will always have a positive determinant, i.e. the transformation won’t create a “mirror image” of the input.

If you displace the view position slightly in the Z direction so you don’t go exactly over the top, the results will be largely the same except that the flip won’t quite be instantaneous. Instead of looking straight down you’ll be looking slightly to one side, and will quickly pivot around as you go over the top so that you’re looking slightly to the other side on the downward half.