/*---------------------------------------------------------------------------
Name: space.c (main file for spaceport.cpp)
Author: Christopher Huyler
Date: April 30, 2002
Description:
Animation, Lighting, and Materials project.
Run the program and watch the animation (all user controls are disabled).
Once the camera starts following the robot, you can control the objects and
camera by pressing keys and moving the mouse.  If the animation is too fast or
slow, try changing the global constants below.

camera controls:
look            -   move mouse
forward         -   e
backward        -   d
straff right    -   f
straff left     -   s
camera light    -   left click
print coords to stdout - right click

camera views:
change camera   -   w/r (or +/-)
outside shot    -   1
crane follow    -   2
corner shot     -   3
far outside     -   4
follow ship     -   5
inside ship     -   6
follow droid    -   7

crane controls: (number pad)
left/right      -   4/6
for/aft         -   8/2
up/down         -   9/3
open/close      -   7/1
twist           -   0/.

ship controls:
open/close wings-   [/]

----------------------------------------------------------------------------*/
#pragma hdrstop
//---------------------------------------------------------------------------
#pragma package(smart_init)
#include <math.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include "materials.c"
#include "objects.c"

// some global variables - change if animation is too fast!
#define CAMERA_SPEED        1.0    // p2: 2.0   p4: 1.0
#define ANIMATION_SPEED     0.2    // p2: 1.0   p4: 0.2
GLfloat angleToRadians = 3.14159265358979/180.0;
int done_animating = 0;

// camera variables ----------------------------------------------------------
int cv = 0,             // current camera angle
    num_cameras = 7;    // total number of camera angles
// structure to hold camera angles
struct objStruct cam_arr[] = {
    {0.0,15.0,-150.0,0.0,0.0},           // start - outside hangar
    {22.0,7.8,16.0,198.5,18.0},          // watch crane
    {-28.0,25.0,-53.0,-333.0},
    {0.0,40.8,-289.5,0.0,-13.5},         // zoom out for ship
    {56.3,21.8,-97.4,-134.0,-13.5},      // watch ship enter
    {0.0,25.0,40.0,180.0,-20.0},         // entrance view
    {28.0,26.0,-45.0,-52.0,-33.0}        // follow robot
};
int changex = 0,
    changey = 0,
    camera_light; // toggle camera light on or off
// current camera coords (init to camera 0)
struct objStruct camera = {0.0,15.0,-150.0,0.0,0.0};

//-----------------------------------------------------------------------------
// display callback -----------------------------------------------------------
void display(void)
{
   // lighting variables
   GLfloat new_pos[3];      // new position for light
   GLfloat new_dir[3];      // new direction for light

   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   // position camera --------------------------------------------------------
   glLoadIdentity();
   switch(cv) {
        case 1:     // second camera watches crane movements
            gluLookAt(camera.xpos,camera.ypos,camera.zpos,
                crane.xpos,crane.ypos+20,crane.zpos-15,
                0.0,1.0,0.0);
            break;
        case 4: case 5:   // third and fourth cameras watch ship
            gluLookAt(camera.xpos,camera.ypos,camera.zpos,
                ship.xpos,ship.ypos,ship.zpos,
                0.0,1.0,0.0);
            break;
        case 6:           // seventh camera follows robot!
            gluLookAt(robot1.xpos-10*sin(robot1.h_angle*angleToRadians),
                      5,
                      robot1.zpos-10*cos(robot1.h_angle*angleToRadians),
                      robot1.xpos,5,robot1.zpos,
                      0.0,1.0,0.0);
            break;
        default:          // default camera uses all camera coordinates
            gluLookAt(camera.xpos,camera.ypos,camera.zpos,
             camera.xpos+10*sin(camera.h_angle*angleToRadians),
             camera.ypos+10*sin(camera.v_angle*angleToRadians),
             camera.zpos+10*cos(camera.h_angle*angleToRadians),
             0.0,1.0,0.0);
            break;
   }

   // adjust lights ---------------------------------------------------------
   if(camera_light) {        // camera light points in direction of camera
     new_pos[0] = camera.xpos;
     new_pos[1] = camera.ypos;
     new_pos[2] = camera.zpos;
     new_dir[0] = sin(camera.h_angle*angleToRadians);
     new_dir[1] = sin(camera.v_angle*angleToRadians);
     new_dir[2] = cos(camera.h_angle*angleToRadians);
     position_spot(&light1,new_pos,new_dir);
     light_on(&light1);
   }
   else {
     light_off(&light1);
   }
   new_dir[0] = sin(ship.h_angle*angleToRadians);
   new_dir[1] = sin(ship.v_angle*angleToRadians);
   new_dir[2] = cos(ship.h_angle*angleToRadians);
   new_pos[0] = ship.xpos+4*sin(ship.h_angle*angleToRadians);
   new_pos[1] = ship.ypos;
   new_pos[2] = ship.zpos+4*cos(ship.h_angle*angleToRadians);
   position_spot(&light3,new_pos,new_dir);

   // draw objects -----------------------------------------------------------
   draw_hangar(0.0,0.0,0.0);    // draw hangar with floor centered at 0,0,0
   draw_elevator(-30.0,elevator_height,-30.0);   // draw elevator
   draw_scaffolding();          // draw ceiling scaffolding
   draw_crane(crane.xpos,crane.ypos,crane.zpos);   // draw crane
   glPushMatrix();
     if(holding) {              // draw tie-fighter attached to crane
        glLoadIdentity();
        glMultMatrixf(crane_tie);
     }
     else
        glTranslatef(30.0,6.0,-5.0);
     draw_tiefighter(0.0,-2.0,0.0);
   glPopMatrix();

   glPushMatrix();              // draw multiple tie fighters
     glTranslatef(30.0,-1.0,-35.0);
     draw_tiefighter(0.0,5.0,0.0);
     glTranslatef(0.0,0.0,10.0);
     draw_tiefighter(0.0,5.0,0.0);
     glTranslatef(0.0,0.0,10.0);
     draw_tiefighter(0.0,5.0,0.0);
   glPopMatrix();

   glPushMatrix();
     glRotatef(tie_angle,0.0,1.0,0.0);
     glTranslatef(0.0,0.0,-150.0);
     glPushMatrix();
       glRotatef(tie_spin,1.0,0.0,0.0);
       draw_tiefighter(0.0,0.0,0.0);
     glPopMatrix();
     glPushMatrix();
       glTranslatef(15.0,20.0,-10.0);
       glRotatef(tie_spin,1.0,0.0,0.0);
       draw_tiefighter(0.0,0.0,0.0);
     glPopMatrix();
   glPopMatrix();
   glPushMatrix();
     glRotatef(5.0,1.0,0.0,0.0);
     glRotatef(tie_angle,0.0,1.0,0.0);
     glTranslatef(0.0,10.0,-135.0);
     glRotatef(tie_spin,1.0,0.0,0.0);
     draw_tiefighter(0.0,0.0,0.0);
   glPopMatrix();

   glPushMatrix();                  // draw robot
     glTranslatef(robot1.xpos,robot1.ypos,robot1.zpos);
     glRotatef(robot1.h_angle,0.0,1.0,0.0);
     draw_droid();
   glPopMatrix();
   glPushMatrix();                  // draw second robot
     glTranslatef(robot2.xpos,robot2.ypos,robot2.zpos);
     glRotatef(robot2.h_angle,0.0,1.0,0.0);
     draw_droid();
   glPopMatrix();

   glPushMatrix();                  // draw ship
     glTranslatef(ship.xpos,ship.ypos,ship.zpos);
     draw_ship(wing_angle);
   glPopMatrix();

   glutSwapBuffers();
}


// camera look-at angles determined by mouse motion --------------------------
void motion(int x, int y) {
    changex = changey = 0;
    if(x > 300) changex = 1;
    if(x < 200) changex = -1;
    if((500-y)>300) changey = 1;
    if((500-y)<200) changey = -1;
}  // end motion

// mouse click callback to get camera coords ---------------------------------
void click(int button, int state, int x, int y) {
    if(button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) {
        printf("{%.1f,%.1f,%.1f,%.1f,%.1f}\n",
                camera.xpos,camera.ypos,camera.zpos,camera.h_angle,camera.v_angle);
        printf("%.lf\n",wing_angle);
    }
    if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
        camera_light = (camera_light)?0:1;
    }
}


// user keyboard controls for camera and objects -----------------------------
void keyboard (unsigned char key, int x, int y)
{
   switch (key) {
      // camera switches -----------------------------------------------------
      case '-': case '+': case 'w': case 'r':
        if(key == '+' || key == 'r') cv++;
        else cv--;
        if (cv >= num_cameras) cv = 0;
        if (cv < 0) cv = num_cameras-1;

        camera.xpos = cam_arr[cv].xpos;
        camera.ypos = cam_arr[cv].ypos;
        camera.zpos = cam_arr[cv].zpos;
        camera.h_angle = cam_arr[cv].h_angle;
        camera.v_angle = cam_arr[cv].v_angle;
        if(cv == 6) {
            // only when the event driven camera changes are finished can the user
            // move the camera and switch camera positions.
            glutKeyboardFunc(keyboard);
            glutMouseFunc(click);
            glutPassiveMotionFunc(motion);
            done_animating = 1;
        }
        break;

      // camera movement -----------------------------------------------------
      case 'e': // move forward relative to h_angle and v_angle
        camera.xpos += sin(camera.h_angle*angleToRadians)*CAMERA_SPEED; // xpos incs by adjacent h component
        camera.ypos += sin(camera.v_angle*angleToRadians)*CAMERA_SPEED; // ypos incs by adjacent v component
        camera.zpos += cos(camera.h_angle*angleToRadians)*CAMERA_SPEED; // zpos incs by oposite h component
        break;
      case 'd': // move backward relative to h_angle and v_angle
        camera.xpos -= sin(camera.h_angle*angleToRadians)*CAMERA_SPEED; // xpos decs by adjacent h component
        camera.ypos -= sin(camera.v_angle*angleToRadians)*CAMERA_SPEED; // ypos decs by adjacent v component
        camera.zpos -= cos(camera.h_angle*angleToRadians)*CAMERA_SPEED; // zpos decs by oposite h component
        break;
      case 's': // straff left relative to h_angle (v_angle has no affect
        camera.xpos += cos(camera.h_angle*angleToRadians)*CAMERA_SPEED;
        camera.zpos -= sin(camera.h_angle*angleToRadians)*CAMERA_SPEED;
        break;
      case 'f': // straff right relative to h_angle (v_angle has no affect)
        camera.xpos -= cos(camera.h_angle*angleToRadians)*CAMERA_SPEED*4;
        camera.zpos += sin(camera.h_angle*angleToRadians)*CAMERA_SPEED;
        break;

      //crane motions done with num-pad! -------------------------------------
      case '8':
        if(crane.zpos-.2>=-13)
            crane.zpos-=.2;
        break;
      case '2':
        if(crane.zpos+.2<=13)
            crane.zpos+=.2;
        break;
      case '4':
        if(crane.xpos-.4>=-29)
            crane.xpos-=.2;
        break;
      case '6':
        if(crane.xpos+.4<=29)
            crane.xpos+=.2;
        break;
      case '3':
        if(crane.ypos-.4>=-20)
            crane.ypos-=.2;
        break;
      case '9':
        if(crane.ypos+.4<=-2.0)
            crane.ypos+=.2;
        break;
      case '1':
        if(crane.v_angle+.4 <= 3.0)
            crane.v_angle+=.2;
        break;
      case '7':
        if(crane.v_angle-.4 >= 0.0)
            crane.v_angle-=.2;
        break;
      case '0':
        crane.h_angle+=5;
        if(crane.h_angle>=360) crane.h_angle-=360;
        break;
      case '.':
        crane.h_angle-=5;
        if(crane.h_angle<=-360) crane.h_angle+=360;
        break;

      case '[':
        wing_angle+=5;
        break;
      case ']':
        wing_angle-=5;
        break;
   }
   glutPostRedisplay();
}  // end keyboard

// reshape callback ----------------------------------------------------------
void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h); 
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   gluPerspective(65.0, (GLfloat) w/(GLfloat) h, 1.0, 500.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   gluLookAt(camera.xpos,camera.ypos,camera.zpos,
            camera.xpos+10*sin(camera.h_angle*angleToRadians),
            camera.ypos+10*sin(camera.v_angle*angleToRadians),
            camera.zpos+10*cos(camera.h_angle*angleToRadians),
            0.0,1.0,0.0);
}

// idle call back: object animation and camera positioning -------------------
void idle() {
    // change camera orientation by change variable controled by motion func
    camera.h_angle -= changex*CAMERA_SPEED/4;
    camera.v_angle += changey*CAMERA_SPEED/4;
    // avoid large angles
    if(camera.h_angle >= 360 || camera.h_angle <= -360) camera.h_angle = 0;
    if(camera.v_angle >= 360 || camera.v_angle <= -360) camera.v_angle = 0;
    if(!done_animating) {
        // event triggered camera movements
        if(crane.ypos >= -6 && crane_state == 0 && cv == 0)
            keyboard('+',0,0);    // second crane shot
         if(crane.ypos <= -20 && crane_state == 3 && cv == 1)
            keyboard('+',0,0);   // third crane shot
        if(ship.zpos >= -275 && cv == 2)
            keyboard('+',0,0);     // first ship camera
        if(ship.zpos >= -200 && cv == 3)
            keyboard('+',0,0);     // watch ship enter
        if(ship.zpos >= -50 && cv == 4)
            keyboard('+',0,0);      // go inside hangar
        if(ship.zpos >= -1 && cv == 5)
            keyboard('+',0,0);       // done with ship
    }
    // animate elevator
    /*if(e_count >= 50/ANIMATION_SPEED)   {
        e_angle += 5*ANIMATION_SPEED;
        if(((int)e_angle % 180 == 0) && (e_count >= 50/ANIMATION_SPEED))
            e_count = 0;
    }
    else e_count++;    */
    e_angle += 5*ANIMATION_SPEED;
    if(e_angle >= 360.0)
        e_angle = e_angle - 360.0;
    elevator_height = cos(e_angle*angleToRadians)*7+7;

    // animate tie-fighters
    tie_angle += 3*ANIMATION_SPEED;
    if(tie_angle >= 180) tie_angle -= 360;
    tie_spin +=1.5*ANIMATION_SPEED*tie_incr;
    if(tie_spin >= 90 || tie_spin <= 0) tie_incr *= -1;

    // animate robot
    if(abs(robot1.xpos + sin(robot1.h_angle*angleToRadians)*ANIMATION_SPEED*4) > 10 ||
       abs(robot1.zpos + cos(robot1.h_angle*angleToRadians)*ANIMATION_SPEED*4) > 10)
        robot1.h_angle+= (rand_num1<3)?-10*ANIMATION_SPEED:10*ANIMATION_SPEED;
    else {
        rand_num1 = rand()%10;
        if(rand_num1<3) robot1.h_angle-=10*ANIMATION_SPEED;
        if(rand_num1>7) robot1.h_angle+=10*ANIMATION_SPEED;
    }
    robot1.xpos += sin(robot1.h_angle*angleToRadians)*ANIMATION_SPEED*2;
    robot1.zpos += cos(robot1.h_angle*angleToRadians)*ANIMATION_SPEED*2;
    if(abs(robot2.xpos + sin(robot2.h_angle*angleToRadians)*ANIMATION_SPEED*4) > 10 ||
       abs(robot2.zpos + cos(robot2.h_angle*angleToRadians)*ANIMATION_SPEED*4) > 10)
        robot2.h_angle+= (rand_num2<3)?-10*ANIMATION_SPEED:10*ANIMATION_SPEED;
    else {
        rand_num2 = rand()%10;
        if(rand_num2<3) robot2.h_angle-=10*ANIMATION_SPEED;
        if(rand_num2>7) robot2.h_angle+=10*ANIMATION_SPEED;
    }
    robot2.xpos += sin(robot2.h_angle*angleToRadians)*ANIMATION_SPEED*2;
    robot2.zpos += cos(robot2.h_angle*angleToRadians)*ANIMATION_SPEED*2;

    // animate ship
    if(ship.zpos < 0) ship.zpos+=ANIMATION_SPEED;
    if(ship.zpos > -100 && wing_angle < 100) wing_angle+=2*ANIMATION_SPEED;
    blink = (blink>10)?0:blink+ANIMATION_SPEED/3;
    glutPostRedisplay();

    // animate crane and attached tie-fighter
    switch(crane_state) {
        case 0:
            if(crane.ypos > -5)
                crane_state++;
            else {
                crane.ypos+=ANIMATION_SPEED/4;
                if(crane.h_angle>0) crane.h_angle+=ANIMATION_SPEED*1.5;
            }
            break;
        case 1:
            if(crane.xpos > 30)
                crane_state++;
            else {
                crane.xpos+=ANIMATION_SPEED/2;
                if(crane.zpos < 10)
                    crane.zpos+=ANIMATION_SPEED/2;
            }
            break;
        case 2:
            if(crane.ypos < -20) {
                crane_state++;
                holding = 0;
            }
            else {
                crane.ypos-=ANIMATION_SPEED/4;
            }
            break;
        case 3:
            if(crane.ypos > -5)
                crane_state++;
            else {
                crane.ypos+=ANIMATION_SPEED/4;
            }
            break;
    }
} // end idle callback

// initialization ------------------------------------------------------------
void init(void) {
  GLfloat global_ambient[] = {0.3,0.3,0.3,1.0};  // ambient light
  GLfloat light_dir[]={0.0,0.0,0.0};      // direction of camera light

  glClearColor (0.0, 0.0, 0.0, 0.0);

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  light_on(&light0);                            // center spot

  glLightf(light1.number,GL_SPOT_CUTOFF, 30);  // camera light
  glLightf(light1.number,GL_SPOT_EXPONENT,4.0);

  light_on(&light2);                // a red spot light on tie-fighters
  light_dir[0] = 1.0;
  light_dir[1] = -1.0;
  light_dir[2] = 0.0;
  glLightf(light2.number,GL_SPOT_CUTOFF, 45);
  glLightf(light2.number,GL_SPOT_EXPONENT,4.0);
  glLightfv(light2.number,GL_SPOT_DIRECTION,light_dir);

  light_on(&light3);                // ship spot light
  glLightf(light3.number,GL_SPOT_CUTOFF, 15);
  glLightf(light3.number,GL_SPOT_EXPONENT,1.0);

  glLightModelfv(GL_LIGHT_MODEL_AMBIENT,global_ambient);    // ambient

  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);
  glShadeModel(GL_SMOOTH);
  p = gluNewQuadric();
  gluQuadricDrawStyle(p, GLU_FILL);
}

// main function -------------------------------------------------------------
int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize (500, 500);
   glutCreateWindow (argv[0]);
   init();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutIdleFunc(idle);
   glutMainLoop();
   return 0;
}
