title: OpenGL cube rotation using quaternions
body: <p>I have been trying to rotate a cube using OpenGL running in an SDL full-screen window. I managed to do this quite successfully with <code>glRotatef()</code>, however I experienced "gimbal lock" and other problems associated with Euler angles. Wanting to improve my program, I researched quaternions. I coded a quaternion class, following the directions on <a href="http://www.cprogramming.com/tutorial/3d/quaternions.html" rel="nofollow noreferrer" title="Using Quaternion to Perform 3D rotations">this page</a>, and tried to use it to rotate my cube using <code>glMultMatrixf()</code>, however <strong>the cube gets warped when rotating around more than 1 axis with angles that aren't a multiple of 90 degrees</strong>. I checked my quaternion to matrix conversion and my quaternion multiplication code, but I can't find anything wrong.</p>
  
  <p>Here is a photo of the problem:
  <img src="https://i.stack.imgur.com/H1Dcy.jpg" alt="enter image description here"></p>
  
  <p>And here is the complete program that displayed those cubes (requires SDL and OpenGL)</p>
  
  <pre><code>//==============================================================================
  #include &lt;cmath&gt;
  
  #include &lt;SDL/SDL.h&gt;
  #include &lt;SDL/SDL_opengl.h&gt;
  
  namespace game_lib
  {
      struct vec3
      {
          vec3() : x(0), y(0), z(0) { }
          vec3(float x, float y, float z) : x(x), y(y), z(z) { }
  
          vec3 normalize();
          inline float lenSqr() { return x*x + y*y + z*z; }
          float len();
  
          inline vec3 operator+(vec3 v) { v.x += x; v.y += y; v.z += z; return v; }
          inline vec3 operator-(vec3 v) { v.x = x - v.x; v.y = y - v.y; v.z = z - v.z; return v; }
          inline vec3 operator*(float f) { return vec3(f*x, f*y, f*z); }
          inline vec3 operator/(float f) { return vec3(x/f, y/f, z/f); }
  
  
          bool operator==(vec3 v);
  
          float x, y, z;
  
          enum faces { FRONT, BACK, LEFT, RIGHT, TOP, BOTTOM };
      };
      inline vec3 operator*(float f, vec3 v) { return v*f; }
  
      struct quaternion
      {
          quaternion() : w(1), x(0), y(0), z(0) { }
          quaternion(float w, float x, float y, float z) : w(w), x(x), y(y), z(z) { }
          quaternion(float angle, vec3 axis);
  
          quaternion normalize();
          inline float lenSqr() { return w*w + x*x + y*y + z*z; }
          float len();
  
          quaternion operator*(quaternion);
  
          float w, x, y, z;
      };
  
      void DrawGLCuboid(vec3 centre, vec3 dimensions, quaternion rotation, const vec3 colours[6]);
  }
  
  const int EPSILON = 0.001;
  
  inline bool feq(float f1, float f2)
  {
      const float diff = f1 - f2;
      return (diff &gt; -EPSILON) &amp;&amp; (diff &lt; EPSILON);
  }
  
  //{=================== vec3 methods =================
  game_lib::vec3 game_lib::vec3::normalize()
  {
      const float lengthSqr = lenSqr();
      if (lengthSqr &gt; 1-EPSILON*EPSILON and lengthSqr &lt; 1+EPSILON*EPSILON) // Optimisation to not re-normalize a normalized vector
          return *this;
      const float length = std::sqrt(lengthSqr);
      return game_lib::vec3(x/length, y/length, z/length);
  }
  
  float game_lib::vec3::len() { return std::sqrt(lenSqr()); }
  
  bool game_lib::vec3::operator==(vec3 v) { return feq(v.x,x) and feq(v.y, y) and feq(v.z, z); }
  //}==================================================
  
  
  //{================ quaternion methods ==============
  game_lib::quaternion::quaternion(float angle, vec3 axis)
  {
      const vec3 axisN = axis.normalize();
      const float sin_a_2 = std::sin(angle*M_PI/360);
      w = std::cos(angle*M_PI/360);
      x = axisN.x*sin_a_2;
      y = axisN.y*sin_a_2;
      z = axisN.z*sin_a_2;
  }
  
  game_lib::quaternion game_lib::quaternion::normalize()
  {
      const float lengthSqr = lenSqr();
      if (lengthSqr &gt; 1-EPSILON*EPSILON and lengthSqr &lt; 1+EPSILON*EPSILON) // Optimisation to not re-normalize a normalized quaternion
          return *this;
      const float length = std::sqrt(lengthSqr);
      return game_lib::quaternion(w/length, x/length, y/length, z/length);
  }
  
  float game_lib::quaternion::len() { return std::sqrt(lenSqr()); }
  
  game_lib::quaternion game_lib::quaternion::operator*(game_lib::quaternion q)
  {
      return game_lib::quaternion(w*q.w - x*q.x - y*q.y - z*q.z, w*q.x + x*q.w + y*q.z - z*q.y, w*q.y - x*q.z + y*q.w + z*q.x, w*q.z + x*q.y - y*q.x + z*q.w);
  }
  //}==================================================
  
  
  void game_lib::DrawGLCuboid(vec3 cen, vec3 dim, quaternion rot, const vec3 col[6])
  {
      glPushMatrix();
      glTranslatef(cen.x, cen.y, cen.z);
      vec3 dim_2 = 1/2*dim;
      const quaternion r_norm = rot.normalize();
  
      // Quaternion to matrix
      const float x_x = r_norm.x*r_norm.x,   y_y = r_norm.y*r_norm.y,   z_z = r_norm.z*r_norm.z;
      const float w_x = r_norm.w*r_norm.x,   w_y = r_norm.w*r_norm.y,   w_z = r_norm.w*r_norm.z;
      const float x_y = r_norm.x*r_norm.y,   x_z = r_norm.x*r_norm.z,   y_z = r_norm.y*r_norm.z;
  
      GLfloat matrix[16];
  
      // Column 1                  // Column 2                  // Column 3                  // Column 4
      matrix[0] = 1-2*(y_y+z_z);   matrix[4] = 2*(x_y-w_z);     matrix[8] = 2*(x_z+w_y);     matrix[12] = 0;
      matrix[1] = 2*(x_y+w_z);     matrix[5] = 1-2*(x_x+z_z);   matrix[9] = 2*(y_z+w_x);     matrix[13] = 0;
      matrix[2] = 2*(x_z-w_y);     matrix[6] = 2*(y_z-w_x);     matrix[10] = 1-2*(x_x+y_y);  matrix[14] = 0;
      matrix[3] = 0;               matrix[7] = 0;               matrix[11] = 0;              matrix[15] = 1;
  
      /* From http://www.cprogramming.com/tutorial/3d/quaternions.html
      1-2y2-2z2   2xy-2wz     2xz+2wy     0
  
      2xy+2wz     1-2x2-2z2   2yz+2wx     0
  
      2xz-2wy     2yz-2wx     1-2x2-2y2   0
  
      0           0           0           1
      */
  
      glMultMatrixf(matrix);
  
      glBegin(GL_QUADS);
          int i = vec3::FRONT;
          glColor3f(col[i].x, col[i].y, col[i].z);
  
          glVertex3f(-1, 1, 1);
          glVertex3f(1, 1, 1);
          glVertex3f(1, -1, 1);
          glVertex3f(-1, -1, 1);
  
  
          i = vec3::BACK;
          glColor3f(col[i].x, col[i].y, col[i].z);
  
          glVertex3f(-1, 1, -1);
          glVertex3f(1, 1, -1);
          glVertex3f(1, -1, -1);
          glVertex3f(-1, -1, -1);
  
  
          i = vec3::LEFT;
          glColor3f(col[i].x, col[i].y, col[i].z);
  
          glVertex3f(-1, 1, 1);
          glVertex3f(-1, -1, 1);
          glVertex3f(-1, -1, -1);
          glVertex3f(-1, 1, -1);
  
  
          i = vec3::RIGHT;
          glColor3f(col[i].x, col[i].y, col[i].z);
  
          glVertex3f(1, 1, 1);
          glVertex3f(1, -1, 1);
          glVertex3f(1, -1, -1);
          glVertex3f(1, 1, -1);
  
  
          i = vec3::BOTTOM;
          glColor3f(col[i].x, col[i].y, col[i].z);
  
          glVertex3f(-1, -1, 1);
          glVertex3f(1, -1, 1);
          glVertex3f(1, -1, -1);
          glVertex3f(-1, -1, -1);
  
  
          i = vec3::TOP;
          glColor3f(col[i].x, col[i].y, col[i].z);
  
          glVertex3f(-1, 1, 1);
          glVertex3f(1, 1, 1);
          glVertex3f(1, 1, -1);
          glVertex3f(-1, 1, -1);
  
          glColor3f(1, 1, 1);
  
          // Following three quads are axes to help determine rotational correctness...
          // x-axis
          glVertex3f(-2, 0.05, 0.05);
          glVertex3f(2, 0.05, 0.05);
          glVertex3f(2, -0.05, -0.05);
          glVertex3f(-2, -0.05, -0.05);
  
          // y-axis
          glVertex3f(0.05, -2, 0.05);
          glVertex3f(0.05, 2, 0.05);
          glVertex3f(-0.05, 2, -0.05);
          glVertex3f(-0.05, -2, -0.05);
  
          // z-axis
          glVertex3f(0.05, 0.05, -2);
          glVertex3f(0.05, 0.05, 2);
          glVertex3f(-0.05, -0.05, 2);
          glVertex3f(-0.05, -0.05, -2);
      glEnd();
      glPopMatrix();
  }
  
  using namespace game_lib;
  
  struct SDL_Surface;
  union SDL_Event;
  
  class CApp {
      private:
          bool m_running, m_init;
          SDL_Surface* m_screen;
          float depth;
  
      public:
          CApp();
          ~CApp();
          bool init();
          int execute();
          void cleanup();
  
      private:
          //void processEvent(SDL_Event* Event); // Usually I have this, but it's big and irrelevant (
          void render();
  };
  
  
  //==============================================================================
  CApp::CApp()
  : m_running(true), m_init(false), m_screen(NULL), depth(-6) { }
  
  CApp::~CApp()
  {
      if (m_init) cleanup();
  }
  
  //------------------------------------------------------------------------------
  
  bool CApp::init()
  {
      if(SDL_Init(SDL_INIT_EVERYTHING) &lt; 0)
          return false;
  
  
      SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  
      SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
      SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
      SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
      SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
  
      SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
      SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32);
  
      SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE, 8);
      SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE, 8);
      SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE, 8);
      SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE, 8);
  
      SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
      SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 2);
  
      const SDL_VideoInfo* inf = SDL_GetVideoInfo();
  
      if((m_screen = SDL_SetVideoMode(inf-&gt;current_w, inf-&gt;current_h, 0, SDL_OPENGL | SDL_FULLSCREEN)) == NULL)
          return false;
  
      glClearColor(0, 0, 0, 0);
  
      glClearDepth(1);
      glEnable(GL_DEPTH_TEST);
      glDepthFunc(GL_LEQUAL);
  
      glViewport(0, 0, inf-&gt;current_w, inf-&gt;current_h);
  
      glMatrixMode(GL_PROJECTION); // Camera space
      glLoadIdentity();
  
      gluPerspective(45.0f, 1024.0f/600.0f, 0.1f, 100.0f);
  
      glEnable(GL_TEXTURE_2D);
      glMatrixMode(GL_MODELVIEW); // Model space
      glLoadIdentity();
  
      m_init = true;
      return true;
  }
  
  int CApp::execute()
  {
      if(init() == false)
          return -1;
      SDL_Event event;
      while(m_running)
      {
          //while(SDL_PollEvent(&amp;event))
              // process events removed to save space
          render();
          SDL_Delay(10);
      }
      cleanup();
      return 0;
  }
  
  void CApp::cleanup()
  {
      if (m_screen)
      {
          SDL_FreeSurface(m_screen);
          m_screen = NULL;
      }
      SDL_Quit();
      m_init = false;
  }
  
  void CApp::render()
  {
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glLoadIdentity();
  
      glTranslatef(0.0f, 0.0f, depth);
  
      //glRotatef(63, 0, 1, 0); // How I used to do rotation
      //glRotatef(47, 1, 0, 0);
  
      const vec3 c1colours[] = {vec3(1, 0, 0), vec3(0, 1, 0), vec3(1, 0.2, 1), vec3(0, 0, 1), vec3(1, 1, 0), vec3(1, 0.5, 1)};
      const vec3 c2colours[] = {vec3(1, 0, 0), vec3(0, 1, 1), vec3(1, 1, 0), vec3(0, 1, 0), vec3(1, 0.5, 0), vec3(0.5, 0, 0)};
  
      // New rotation method, but doesn't work...
      const quaternion c1Rotation = quaternion(63, vec3(0, 1, 0)) * quaternion(47, vec3(1, 0, 0));
  
      DrawGLCuboid(vec3(-2, 0, -2), vec3(2, 2, 2), c1Rotation, c1colours);
      DrawGLCuboid(vec3(2.5, 0.3, -1.2), vec3(2, 2, 2), quaternion(72, vec3(0, 0, 1)), c2colours);
  
      SDL_GL_SwapBuffers();
  }
  
  
  
  
  int main(int argc, char* argv[])
  {
      CApp theApp;
      return theApp.execute();
  }
  </code></pre>