/*
 * Copyright (c) 1996-1999  Silicon Graphics, Inc.  All rights reserved.
 *
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 * IN NO EVENT SHALL SILICON GRAPHICS BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF THE
 * POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


/* movingLight.c - Set up a light that moves independent 
 *    of the objects in the scene.
 *
 *  Left Mouse Button   - change incidence and azimuth angles
 *  Middle Mousebutton    - change the twist angle based on
 *          horizontal mouse movement
 *  Right Mousebutton   - zoom in and out based on vertical
 *          mouse movement
 *  <a> key     - toggle light animation
 *  <m> key     - toggle local/infinite viewer
 *  Escape key      - exit the program
 */

#include <GL/glut.h>  /* includes gl.h, glu.h */

#include <math.h>
#include <stdio.h>


#include "axes.h"

/*  Function Prototypes  */

GLvoid  initgfx( GLvoid );
GLvoid  animate( GLvoid );
GLvoid  visibility( GLint );
GLvoid  drawScene( GLvoid );
GLvoid  reshape( GLsizei, GLsizei );
GLvoid  keyboard( GLubyte, GLint, GLint );
GLvoid  mouse( GLint, GLint, GLint, GLint );
GLvoid  motion( GLint, GLint );

void resetView( GLvoid );
void polarView( GLfloat, GLfloat, GLfloat, GLfloat );
void printHelp( char * );

/* Global Definitions */

#define KEY_ESC 27  /* ascii value for the escape key */

/* Global Variables */

static GLfloat    lightAngle = 0.0f;  /* controls light rotation */

static GLboolean  animateLight = GL_TRUE;
static GLboolean  localLight = GL_FALSE;

enum    actions { MOVE_EYE, TWIST_EYE, ZOOM, MOVE_NONE };
static GLint    action;

static GLdouble   xStart = 0.0, yStart = 0.0;

static GLfloat    fovy, nearClip, farClip, distance, twistAngle, incAngle, azimAngle;


static GLint        before;
static GLint        fixFrameRate = 0;



void
main( int argc, char *argv[] )
{
  GLsizei width, height;

  glutInit( &argc, argv );

  width = glutGet( GLUT_SCREEN_WIDTH ); 
  height = glutGet( GLUT_SCREEN_HEIGHT );
  glutInitWindowPosition( width / 4, height / 4 );
  glutInitWindowSize( (width / 2) - 4, height / 2 );
  glutInitDisplayMode( GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE );
  glutCreateWindow( argv[0] );

  initgfx();

  glutIdleFunc( animate );
  glutVisibilityFunc( visibility );
  glutMouseFunc( mouse );
  glutMotionFunc( motion );
  glutKeyboardFunc( keyboard );
  glutReshapeFunc( reshape );
  glutDisplayFunc( drawScene ); 

  printHelp( argv[0] );

  glutMainLoop();
}

void
printHelp( char *progname )
{
  fprintf(stdout, "\n%s - demonstrate how to add a moving light\n\n" 
    "Left Mousebutton - move eye position\n"
    "Middle Mousebutton - change twist angle\n"
    "Right Mousebutton  - move up / down to zoom in / out\n"
    "<a> Key      - toggle light animation\n"
    "<m> Key      - toggle local/infinite viewer\n"
    "<f> Key      - toggle fixed/not fixed frame rate\n"
    "Escape Key   - exit the program\n\n",
    progname);
}

GLvoid
initgfx( GLvoid )
{
  glClearColor( 0.0, 0.0, 0.0, 1.0 );
  glEnable( GL_DEPTH_TEST );

  nearClip = 2.0f;  /* Near clipping plane location */
  farClip  = 15.0f; /* Far clipping plane location */

  resetView();

  /* Turn on a default light */
  glEnable( GL_LIGHT0 );
  before = glutGet( GLUT_ELAPSED_TIME );
}

GLvoid 
keyboard( GLubyte key, GLint x, GLint y )
{
  GLfloat infinite[] = {0.0f};
  GLfloat local[] = {1.0f};

  switch (key) {
  case 'a': /* toggle light animation */
    animateLight = !animateLight;
    if ( animateLight )
      glutIdleFunc(animate);
    else 
      glutIdleFunc(NULL);
    glutPostRedisplay();
    break;
  case 'm': /* toggle lighting model */
    localLight = !localLight;
    if ( localLight )
    {
                        glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local);
      printf("local viewer ON\n");
    }
    else 
    {
                        glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, infinite);
      printf("local viewer OFF\n");
    }
    glutPostRedisplay();
    break;
  case 'f':
    fixFrameRate = !fixFrameRate;
    glutPostRedisplay();
    break;
  case KEY_ESC: /* Exit when the Escape key is pressed */
    exit(0);
  }
}

GLvoid 
mouse( GLint button, GLint state, GLint x, GLint y )
{
  static GLint buttons_down = 0;

  if (state == GLUT_DOWN) {
    switch (button) {
    case GLUT_LEFT_BUTTON:
      action = MOVE_EYE;
      break;
    case GLUT_MIDDLE_BUTTON:
      action = TWIST_EYE;
      break;
    case GLUT_RIGHT_BUTTON:
      action = ZOOM;
      break;
    }

    /* Update the saved mouse position */
    xStart = x;
    yStart = y;
  } else {
    if (--buttons_down == 0) 
      action = MOVE_NONE;
  }
}

GLvoid
motion( GLint x, GLint y )
{
  switch (action) {
  case MOVE_EYE:
    /* Adjust the eye position based on the mouse position */
    azimAngle += (GLdouble) (x - xStart);
    incAngle -= (GLdouble) (y - yStart);
    break;
  case TWIST_EYE:
    /* Adjust the eye twist based on the mouse position */
    twistAngle = fmod(twistAngle+(x - xStart), 360.0);
    break;
  case ZOOM:
    /* Adjust the eye distance based on the mouse position */
    distance -= (GLdouble) (y - yStart)/10.0;
    break;
  default:
    printf("unknown action %d\n", action);
  }
  
  /* Update the stored mouse position for later use */
  xStart = x;
  yStart = y;

  glutPostRedisplay();
}

void
resetView( GLvoid )
{
  distance = nearClip + (farClip - nearClip) / 2.0f;
  twistAngle = 0.0f;  /* rotation of viewing volume (camera) */
  incAngle = 60.0f;
  azimAngle = 0.0f;
  fovy = 60.0f; /* Field of view in Y angle */
}

GLvoid
reshape( GLsizei width, GLsizei height )
{
  GLdouble  aspect;

  glViewport( 0, 0, width, height );

  aspect = (GLdouble) width / (GLdouble) height;

  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  gluPerspective( fovy, aspect, nearClip, farClip );
  glMatrixMode( GL_MODELVIEW );
}

GLvoid 
animate( GLvoid )
{
  /* update the rotation of the light for each scene */
  GLint now;

  now = glutGet( GLUT_ELAPSED_TIME );

  if (abs(now - before) >= 40 || !fixFrameRate) {
    lightAngle = fmod( (lightAngle + 2.0), 360.0 );
    before = now;

    /* Tell GLUT to redraw the scene */
    glutPostRedisplay();
  }

}

GLvoid
visibility( int state ) 
{
  if (state == GLUT_VISIBLE && animateLight) {
    glutIdleFunc( animate );
  } else {
    glutIdleFunc( NULL );
  }
}

void
polarView( GLfloat distance, GLfloat azimuth, GLfloat incidence,
      GLfloat twist)
{
  glTranslatef( 0.0f, 0.0f, -distance);
  glRotatef( -twist, 0.0f, 0.0f, 1.0f);
  glRotatef( -incidence, 1.0f, 0.0f, 0.0f);
  glRotatef( -azimuth, 0.0f, 0.0f, 1.0f);
}

GLvoid
drawScene( GLvoid )
{
  /* Define a few materials properties */
  GLfloat   redAmbient[] = { 0.3f, 0.1f, 0.1f, 1.0f };
  GLfloat   redDiffuse[] = { 1.0f, 0.0f, 0.0f, 1.0f };
  GLfloat   blueAmbient[] = { 0.1f, 0.1f, 0.3f, 1.0f };
  GLfloat   blueDiffuse[] = { 0.0f, 0.0f, 1.0f, 1.0f };
  GLfloat   defaultEmission[] = { 0.0f, 0.0f, 0.0f, 1.0f };
  GLfloat   whiteSpecular[] = { 1.0f, 1.0f, 1.0f, 1.0f };
  GLfloat   greenSpecular[] = { 0.0f, 1.0f, 0.0f, 1.0f };
  GLfloat   defaultSpecular[] = { 0.0f, 0.0f, 0.0f, 1.0f };

  /* local light */
  GLfloat   lightPosition[] = { 0.0f, 0.0f, 0.0f, 1.0f };

  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

  glPushMatrix();

    polarView( distance, azimAngle, incAngle, twistAngle );

    XYZaxes();

    /* Animate the light with its own set of transformations */
    glPushMatrix();
      glRotatef( lightAngle, 0.0f, 1.0f, 1.0f );
      glTranslatef( 2.7f, 0.0f, 0.0f );

      /* By giving the light position its own modeling
       * transformations (due to the glPushMatrix() 
       * and glPopMatrix() calls), the light moves
       * independently of the objects in the scene.  
       */
      glLightfv( GL_LIGHT0, GL_POSITION, lightPosition );

      /* draw a small (unlit) white sphere to
       * represent the light in our scene. 
       */
      glColor3f( 1.0f, 1.0f, 1.0f );
      glutSolidSphere( 0.07, 4, 7);
    glPopMatrix();

    glEnable( GL_LIGHTING );

    glMaterialfv( GL_FRONT, GL_EMISSION, defaultEmission );

    /* Set properties for a shiny red material,
     * with a green highlight */
    glMaterialfv( GL_FRONT, GL_AMBIENT, redAmbient );
    glMaterialfv( GL_FRONT, GL_DIFFUSE, redDiffuse );
    glMaterialfv( GL_FRONT, GL_SPECULAR, greenSpecular );
    glMaterialf( GL_FRONT, GL_SHININESS, 128.0f );
    glPushMatrix();
      glTranslatef( -2.0f, 1.5f, 0.0f );
      glutSolidSphere( 0.7, 31, 31 );
    glPopMatrix();

    glPushMatrix();
      glTranslatef( 1.0f, 2.0f, 2.0f );
      glutSolidSphere( 0.9, 31, 31 );
    glPopMatrix();

    /* Set properties for a dull blue material with
     *   a small white highlight */
    glMaterialfv( GL_FRONT, GL_AMBIENT, blueAmbient );
    glMaterialfv( GL_FRONT, GL_DIFFUSE, blueDiffuse );
    glMaterialfv( GL_FRONT, GL_SPECULAR, whiteSpecular );
    glMaterialf( GL_FRONT, GL_SHININESS, 20.0f );  /* not very shiny */
    glPushMatrix();
      glTranslatef( 2.5f, 0.0f, 0.0f );
      glutSolidTorus( 0.25, 0.75, 16, 31 );
    glPopMatrix();

    glDisable( GL_LIGHTING );

  glPopMatrix();

  glutSwapBuffers();
}

