// wireframe.cc
// Draw a wireframe sphere in OpenGL
//
// Copyright (C) 2004-2005, 2009 Frank C. Langbein, http://www.langbein.org/
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// Compile with
//
//   c++ -o wireframe wireframe.cc -L/usr/X11R6/lib -lm -lX11 -lXext -lXt \
//      -lXmu -lXi -lGL -lGLU -lglut


// Standard headers
#include <stdlib.h>
#include <unistd.h>
#include <math.h>

// OpenGL header (includes everything necessary for OpenGL)
#include <GL/glut.h>


//////////////////////////////////////////////////////////////////////////////
// Constants, global variables, etc.

// Conversion factors for degrees to radians
const double deg_to_rad (M_PI / 180.0);
const double rad_to_deg (180.0 / M_PI);

// Id of display list containing the scene describtion
GLuint scene_dl (0U);

// Track mouse position
int mouse_x (0);
int mouse_y (0);
bool mouse_button1 (false);
bool mouse_button2 (false);
bool mouse_button3 (false);

// Camera position
double camera_x (3.0);
double camera_y (0.0);
double camera_z (0.0);
double camera_yaw (0.0);
double camera_pitch (0.0);

// Camera movement sensitivity
const double angle_sensitivity (0.005);
const double dist_sensitivity (0.01);

// Scene rotation
double scene_yaw (0.0);
double scene_pitch (0.0);


//////////////////////////////////////////////////////////////////////////////
// Functions to draw the models in the scene

// Draw a sphere
void sphere () {

  // Quadrilateral strips
  for (double phi = -80.0; phi <= 80.0; phi += 10.0) {
    glBegin (GL_QUAD_STRIP);
    for (double thet = -180.0; thet <= 180.0; thet += 10.0) {
      glVertex3d (sin (deg_to_rad * thet) * cos (deg_to_rad * phi),
		  cos (deg_to_rad * thet) * cos (deg_to_rad * phi),
		  sin (deg_to_rad * phi)); 
      glVertex3d (sin (deg_to_rad * thet) * cos (deg_to_rad * (phi + 10.0)),
		  cos (deg_to_rad * thet) * cos (deg_to_rad * (phi + 10.0)),
		  sin (deg_to_rad * (phi + 10.0)));  
    }
    glEnd();
  }

  // North pole
  glBegin (GL_TRIANGLE_FAN);
  glVertex3d (0.0, 0.0, 1.0);
  for (double thet = -180.0; thet <= 180.0; thet += 10.0) {
    glVertex3d (sin (deg_to_rad * thet) * cos (deg_to_rad * 80.0),
		cos (deg_to_rad * thet) * cos (deg_to_rad * 80.0),
		sin (deg_to_rad * 80.0));
  }
  glEnd();

  // South pole
  glBegin (GL_TRIANGLE_FAN);
  glVertex3d (0.0, 0.0, -1.0);
  for (double thet = -180.0; thet <= 180.0; thet += 10.0) {
    glVertex3d (sin (deg_to_rad * thet) * cos (deg_to_rad * -80.0),
		cos (deg_to_rad * thet) * cos (deg_to_rad * -80.0),
		sin (deg_to_rad * -80.0));
  }
  glEnd();

  return;
}

// Main function called to setup the scene contents
void scene () {

  // Draw sphere
  sphere ();
  
  return;
}


//////////////////////////////////////////////////////////////////////////////
// Callback functions for user interaction, etc.

// Display callback to render scene
void display_callback () {

  // Clear frame buffer
  glClear (GL_COLOR_BUFFER_BIT);

  // Setup modelview matrix (model and viewing transformation)
  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();

  // Camera
  gluLookAt (camera_x + cos (camera_yaw) * cos (camera_pitch),
	     camera_y + sin (camera_yaw) * cos (camera_pitch),
	     camera_z + sin (camera_pitch),
	     camera_x, camera_y, camera_z,
	     0.0, 0.0, 1.0);

  // Scene
  glRotated (scene_yaw * rad_to_deg, 0.0, 0.0, 1.0);
  glRotated (scene_pitch * rad_to_deg, 0.0, 1.0, 0.0);
  
  // Render scene by calling display list
  // (without display lists call scene () here)
  glCallList (scene_dl);

  // Flush OpenGL pipeline
  glFlush ();

  // Swap buffers (double buffering for smooth animation)
  glutSwapBuffers ();

  return;
}

// Window reshape callback
void reshape_callback (int w, int h) {

  // Setup viewport size
  glViewport (0, 0, w, h);
  
  // Setup projection matrix
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();

  // Perspective projection
  gluPerspective (60.0, 
		  static_cast<double>(w) / static_cast<double> (h),
		  0.1, 1000.0);

  return;
}

// Keyboard callback called when key pressed
void keyboard_callback (unsigned char key, int x, int y) {

  // ESC for exit
  if (key == 27) {
    exit (0);
  }

  return;
}

// Mouse callback called when mouse button is pressed
void mouse_callback (int btn, int state, int x, int y) {

  switch (btn) {
  case GLUT_LEFT_BUTTON:
    mouse_button1 = (state == GLUT_DOWN);
    break;
  case GLUT_MIDDLE_BUTTON:
    mouse_button2 = (state == GLUT_DOWN);
    break;
  case GLUT_RIGHT_BUTTON:
    mouse_button3 = (state == GLUT_DOWN);
    break;
  }

  // Update mouse position
  mouse_x = x;
  mouse_y = y;

  // Mark window for redrawing.
  glutPostRedisplay ();
  
  return;
}

// Motion callback to capture mouse movements while buttons pressed
void motion_callback (int x, int y) {

  // Button 1 pressed: change viewing direction
  if (mouse_button1) {
    camera_yaw += angle_sensitivity * static_cast<double> (mouse_x - x);
    if (camera_yaw > M_PI) {
      camera_yaw -= 2.0 * M_PI;
    } else if (camera_yaw < -M_PI) {
      camera_yaw += 2.0 * M_PI;
    }
    camera_pitch += angle_sensitivity * static_cast<double> (y - mouse_y);
    if (camera_pitch > M_PI_2) {
      camera_pitch = M_PI_2;
    } else if (camera_pitch < -M_PI_2) {
      camera_pitch = -M_PI_2;
    }
  }

  // Button 2 pressed: change position
  if (mouse_button2) {
    camera_x += dist_sensitivity * static_cast<double> (y - mouse_y)
      * cos (camera_yaw) * cos (camera_pitch);
    camera_y += dist_sensitivity * static_cast<double> (y - mouse_y)
      * sin (camera_yaw) * cos (camera_pitch);
    camera_z += dist_sensitivity * static_cast<double> (y - mouse_y)
      * sin (camera_pitch);
  }

  // Button 3 pressed: rotate scene
  if (mouse_button3) {
    scene_yaw += angle_sensitivity * static_cast<double> (x - mouse_x);
    if (scene_yaw > M_PI) {
      scene_yaw -= 2.0 * M_PI;
    } else if (scene_yaw < -M_PI) {
      scene_yaw += 2.0 * M_PI;
    }
    scene_pitch += angle_sensitivity * static_cast<double> (y - mouse_y);
    if (scene_pitch > M_PI) {
      scene_pitch -= 2.0 * M_PI;
    } else if (scene_pitch < -M_PI) {
      scene_pitch += 2.0 * M_PI;
    }
  }

  // Update mouse position
  mouse_x = x;
  mouse_y = y;
    
  // Mark window for redrawing.
  glutPostRedisplay ();
  
  return;
}

// Passive motion callback to capture mouse movements while buttons
// are not pressed
void passive_motion_callback (int x, int y) {

  // Update mouse position
  mouse_x = x;
  mouse_y = y;

  return;
}


//////////////////////////////////////////////////////////////////////////////
// Main Function

int main(int argc, char **argv) {

  // Initialise GLUT
  glutInit (&argc, argv);
  // Use double buffering and RGB colours
  glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);

  // Create display window
  glutInitWindowSize (500, 500);
  glutCreateWindow ("sphere");

  // Setup callback functions for rendering the scene, user interaction, ...
  glutDisplayFunc (display_callback);
  glutReshapeFunc (reshape_callback);
  glutKeyboardFunc (keyboard_callback);
  glutMouseFunc (mouse_callback);
  glutMotionFunc (motion_callback);
  glutPassiveMotionFunc (passive_motion_callback);

  // Set clear color to black
  glClearColor (0.0, 0.0, 0.0, 1.0);

  // Set wireframe mode for drawing the sphere
  glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);

  // Store scene in a display list: 
  //   first create one display list
  scene_dl = glGenLists (1);
  //   and then call the rendering functions to store the primitives
  //   in the list
  glNewList (scene_dl, GL_COMPILE);
  scene ();
  glEndList ();
  
  // Start GLUT event loop
  // (display callback will render the scene)
  glutMainLoop();

  return 0;
}

