
/*
   This is a sample main program for use with the object-oriented
   graphics system defined in Models.h/Models.cc.  In this version,
   a car and some trees are on a disk.  When animation is started
   by pressing the A key, the car moves around the edge of the
   disk.  The whole world can be rotated with the arrow and Home
   keys.
*/

#include "Models.h"
#include <GL/glut.h>
#include <iostream>
using namespace std;


Model *world;     // Contains the entire 3D world.
View *worldView;  // Our view of the world.

Rotate *xWorldRotate;  // x-rotation applied to the world as a whole.
Rotate *yWorldRotate;  // y-rotation applied to the world as a whole.

Rotate *sunRotate,     // Rotations applied to objects during animation.
       *carRotate, 
       *moonRotate;

bool animating = false;  // Is an animation in progress?
const int milliseconds_per_frame = 50;  // Time between animation frames.


/**
 * The createWorld() function is called before the OpenGL window is 
 * displayed.  This is where the whole 3D world is set up.  Here,
 * the world contains nothing but a teapot and a light.
 */
void createWorld() {


   Model *world = new Model();  // This holds all the objects in the world.

   xWorldRotate = new Rotate(5,1,0,0);  // (Variables defined in gl_canvas.h.)
   yWorldRotate = new Rotate(0,0,1,0);

   world->addTransform(xWorldRotate)  // The rotations affect the entire world.
        ->addTransform(yWorldRotate);
        
   sunRotate = new Rotate(-60,0,0,1);
   moonRotate = new Rotate(-240,0,0,1);
   carRotate = new Rotate(0,0,1,0);
        
   Model *sun = new Model();
   sun->addObject( (new BasicObject(SPHERE))
                          ->setEmissiveColor(0.8,0.8,0));
   sun->addObject( (new Light(0,0,0,1))
                          ->setColor(.8,.8,.6) );
   sun->addTransform( new Translate(-5,0,0) );
   sun->addTransform(sunRotate);
        
   Model *moon = new Model();
   moon->addObject( (new BasicObject(SPHERE))
                          ->addTransform(new Scale(0.6))
                          ->setEmissiveColor(0.5,0.5,0.6));
   moon->addObject( (new Light(0,0,0,1))
                          ->setColor(.3,.3,.4) );
   moon->addTransform( new Translate(-5,0,0) );
   moon->addTransform(moonRotate);
        
   Model *axel = new Model();
   axel->setColor(0,0,1);
   axel->setSpecularColor(0,0,0);
   axel->addObject( (new BasicObject(CYLINDER))
                         ->addTransform(new Scale(0.3,6,0.3))
                         ->addTransform(new Rotate(90,1,0,0)) );
   axel->addObject( (new BasicObject(TORUS))
                         ->addTransform(new Scale(2))
                         ->addTransform(new Translate(0,0,3)) );
   axel->addObject( (new BasicObject(TORUS))
                         ->addTransform(new Scale(2))
                         ->addTransform(new Translate(0,0,-3)) );
                         
   Model *car = new Model();
   car->setSpecularColor(0.2,0.2,0.2);
   car->addObject( new Model(axel,new Translate(5,0,0)) );
   car->addObject( new Model(axel,new Translate(-5,0,0)) );
   car->addObject( (new BasicObject(CUBE)) 
                         ->setColor(1,0,0)
                         ->addTransform(new Scale(12,2,5))
                         ->addTransform(new Translate(0,1,0)) );
   car->addObject( (new BasicObject(CUBE)) 
                         ->setColor(1,0,0)
                         ->addTransform(new Scale(5,2,4.5))
                         ->addTransform(new Translate(1,2,0)) );
   car->addObject( (new BasicObject(SPHERE))
                         ->setColor(1,1,0)
                         ->addTransform(new Scale(0.2,1,1))
                         ->addTransform(new Translate(-6,1,-1.7)) );
   car->addObject( (new BasicObject(SPHERE))
                         ->setColor(1,1,0)
                         ->addTransform(new Scale(0.2,1,1))
                         ->addTransform(new Translate(-6,1,1.7)) );
   car->addTransform(new Scale(0.15))
      ->addTransform(new Translate(0,0.15,-3))
      ->addTransform(carRotate);
 
   Model *tree = new Model();
   tree->setSpecularColor(0,0,0);
   tree->addObject( (new BasicObject(CONE))
                         ->setColor(0.2,0.8,0.3)
                         ->addTransform(new Translate(0,0.5,0))
                         ->addTransform(new Scale(1,1.5,1)) );
   tree->addObject( (new BasicObject(CYLINDER))
                         ->setColor(0.6,0.5,0.2)
                         ->addTransform(new Scale(0.2,1,0.2)) );
   tree->addTransform( new Translate(0,0.5,0) );
                         
   Object *ground = (new BasicObject(CYLINDER))
                         ->setColor(0,0.5,0.2)
                         ->setSpecularColor(0,0,0)
                         ->addTransform( new Scale(8,0.1,8) )
                         ->addTransform( new Translate(0,-0.05,0) );

   world->addObject( sun );
   world->addObject( moon );
   world->addObject( ground );
   world->addObject( car );
   world->addObject( tree );
   world->addObject( (new Model(tree))
                       ->addTransform(new Scale(0.7))
                       ->addTransform(new Translate(1.5,0,1)) );
   world->addObject( (new Model(tree))
                       ->addTransform(new Scale(0.5))
                       ->addTransform(new Translate(-1.8,0,1.2)) );
   world->addObject( (new Model(tree))
                       ->addTransform(new Scale(0.7))
                       ->addTransform(new Translate(0.3,0,-2)) );
   world->addObject( (new Model(tree))
                       ->addTransform(new Scale(0.3))
                       ->addTransform(new Translate(-0.5,0,-1)) );

   float ambient[4] = {0.3,0.3,0.3,1};
   glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambient);
   
   
   worldView = new View(world);  // Let worldview be a view of the world
                                 // that was just created.

}


/**
 *  This will be called over and over to drive the animation.
 *  Each time it is called, it updates any animation parameters
 *  to get ready for the next frame and posts a redisplay
 *  message.
 */
void timer(int id) {
   float angle = sunRotate->getAngle();
   angle -= 3;
   if (angle <= 0)
      angle += 360;
   sunRotate->setAngle(angle);
   moonRotate->setAngle(angle+180);
   angle = carRotate->getAngle();
   angle += 5;
   if (angle > 360)
      angle -= 360;
   carRotate->setAngle(angle);
   glutPostRedisplay();
}


/**
 *  The standard OpenGL display function.
 */
void display() { 
   if (animating)
      glutTimerFunc(milliseconds_per_frame,timer,1);  
   glClearColor(0,0,0,1);  // background color is black
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   
   worldView->render();
   glFlush();
   glutSwapBuffers();  // Makes the drawing appear on the screen!
}


/**
 *  The standard OpenGL display function.
 */
void reshape(int width, int height) { 
   glViewport(0,0,width,height);
   if (!animating)
      glutPostRedisplay();
}


/**
 *  The standard OpenGL keyboard function, for ordinary key presses.
 */
void keyboard(unsigned char key, int x, int y) {
   if (key == 27)
      exit(0);
   else if (key == 'a' || key == 'A') {
      animating = !animating;
      glutPostRedisplay();
   }
}


/**
 *  The standard OpenGL function for special key presses.
 */
void special(int key, int x, int y) {
   if (key == GLUT_KEY_HOME) {
      xWorldRotate->setAngle(5);
      yWorldRotate->setAngle(0);
   }
   else if (key == GLUT_KEY_UP) {
      xWorldRotate->setAngle(xWorldRotate->getAngle()-10);
   }
   else if (key == GLUT_KEY_DOWN) {
      xWorldRotate->setAngle(xWorldRotate->getAngle()+10);
   }
   else if (key == GLUT_KEY_RIGHT) {
      yWorldRotate->setAngle(yWorldRotate->getAngle()+10);
   }
   else if (key == GLUT_KEY_LEFT) {
      yWorldRotate->setAngle(yWorldRotate->getAngle()-10);
   }
   glutPostRedisplay();
}


int main(int argc, char **argv) {

   glutInit(&argc,argv);
   glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
   glutInitWindowSize(500,500);       // Set window size here.
   glutInitWindowPosition(150,50);    // Set position of upper left corner.
   glutCreateWindow("OpenGL Models"); // Set window title.
   
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);

   glutKeyboardFunc(keyboard); 
   glutSpecialFunc(special);   
   
   createWorld();
   glutMainLoop();
}
