// sample problem discussed at the lecture 10/21/2004

#define GLUT_API_VERSION 4

#include <iostream>

#ifdef _WIN32
#include <windows.h>
#endif
#include <math.h>
#include <strstream>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#ifndef M_PI
#define M_PI            3.14159265358979323846
#endif 
#include "cvec2t.h"
#include "cvec3t.h"
#include "cvec4t.h"

typedef CVec2T<float> Vec2f;
typedef CVec3T<float> Vec3f;
typedef CVec4T<float> Vec4f;



typedef void (*DisplayFuncType)(void);


void Draw00(void); 
void Draw01(void); 
void Draw10(void); 
void Draw11(void); 

namespace WindowParams {
  static int WindowWidth = 800;
  static int WindowHeight = 600;
  static int MainWindow; 
  static int SubWindow[2][2];
  DisplayFuncType DrawFunc[2][2] = {{Draw00,Draw01},{Draw10,Draw11}};
  static int SubWindowLL;
  static int SubWindowUR;
};

namespace Params { 
  const float WorldWidth = 10*1.33;
  const float WorldHeight = 10;
  const Vec3f BackgroundColor(0,0,0);
  const float OrthoCameraOffset = 10;
  const float OrthoCameraWidth = 4*1.33;
  const float OrthoCameraHeight = 4;
};




// perspective camera parameters, for use in gluPerspective and gluLookAt
//this should be a class, but for this demo 
//there is no real reason to build a complete class yet
namespace Camera { 
  const Vec3f Position(10,10,10);
  const Vec3f ViewCenter(0,0,0);
  const Vec3f Up(0,1,0);
  const float FieldOfView = 20;
  // variable so that Reshape can change it
  float AspectRatio = 1.33;
  const float Near = 1;
  const float Far = 20;
};

const Vec3f red(1,0,0); 
const Vec3f blue(0,0,1); 
const Vec3f green(0,1,0); 
const Vec3f white(1,1,1);


void Reshape(int width, int height) {

  WindowParams::WindowWidth = width;
  WindowParams::WindowHeight = height;

  // make sure the aspect ratio of the camera in gluPerspective/glFrustum matches the window
  // aspect ratio
  Camera::AspectRatio = width/float(height);

  // adjust positions, shapes and viewports of subwindows; the four subwindows
  // split the main window into four (approx. up to a pixel) equal subwindows
  for(int i = 0; i < 2; i++) 
	  for(int j = 0; j < 2; j++) {
	   glutSetWindow(WindowParams::SubWindow[i][j]);
	   glutPositionWindow(i*width/2,j*height/2);
	   glutReshapeWindow(width/2,height/2);	
	   glViewport(0,0,width/2,height/2);
	  }
 

}




// red = X axis, green = Y, blue = Z
void drawAxes() {  
  glLineWidth(3.0);
  float d = min(Params::WorldWidth,Params::WorldHeight);
  glBegin(GL_LINES); 
    glColor3fv(red);
    glVertex3f(0,0,0); 
    glVertex3f(d,0,0); 
	glColor3fv(green);
    glVertex3f(0,0,0); 
    glVertex3f(0,d,0);
	glColor3fv(blue);
    glVertex3f(0,0,0); 
    glVertex3f(0,0,d); 
  glEnd();
}




// projection matrix setup for three axis aligned orthographics cameras
void setOrthoCameraParams() { 
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(
	  -Params::OrthoCameraWidth/2,Params::OrthoCameraWidth/2,   //left, right
	  -Params::OrthoCameraHeight/2,Params::OrthoCameraHeight/2, // bottom, top
	  1,Params::OrthoCameraOffset*2); // near, far
}

 
// all drawing except initializing the buffer and camera setup (projection matrix + lookat)
void Draw() {
  glEnable(GL_DEPTH_TEST);
  // enable automatic rescaling of normals to unit length
  glEnable(GL_NORMALIZE);
  // enable two lights
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHT1);
  // directional lights (w=0) along z axis
  glLightfv(GL_LIGHT0,GL_POSITION, Vec4f(0,  0,-1,0));
  glLightfv(GL_LIGHT1,GL_POSITION, Vec4f(0,  0, 1,0));
  // flip normals for polygons facing away from the screen
  // this ensures the back facing polygins are lit
  glLightModelf(GL_LIGHT_MODEL_TWO_SIDE,1);
  glEnable(GL_LIGHTING);
  glutSolidTeapot(1.0);
  glDisable(GL_LIGHTING);
  // axes are drawn without lighting
  drawAxes();
  glutSwapBuffers();
}

// callbacks for 4 subwindows
// Drawij differ only in the camera position, and background color
void Draw00() { 
  glClearColor( 0.1, 0.1,0.1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  setOrthoCameraParams();
  glMatrixMode(GL_MODELVIEW); 
  glLoadIdentity();
  gluLookAt(
	  Params::OrthoCameraOffset,0,0, // camera position (on x-axis)
	  0,0,0, // the point the camera is looking at
	  0,1,0 // up direction 
	  ); 
  Draw();
 // drawFrustum(-ar*n*tan(fov_angle/180.0*M_PI),ar*n*tan(fov_angle/180.0*M_PI),-n*tan(fov_angle/180.0*M_PI),n*tan(fov_angle/180.0*M_PI),n,f);
  glutSwapBuffers(); 
}

void Draw01() { 
  glClearColor( 0.2, 0.2,0.2,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  setOrthoCameraParams();
  glMatrixMode(GL_MODELVIEW); 
  glLoadIdentity();
  gluLookAt(
	  0,Params::OrthoCameraOffset,0, 
	  0,0,0,
	  0,0,-1);
  Draw();
  glutSwapBuffers(); 
}
void Draw10() { 
  glClearColor( 0.4, 0.4,0.4,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  setOrthoCameraParams();
  glMatrixMode(GL_MODELVIEW); 
  glLoadIdentity();
  gluLookAt(
	  0,0,Params::OrthoCameraOffset,
	  0,0,0,
	  0,1,0);
  Draw();
  glutSwapBuffers(); 
}
void Draw11() {
  
  glClearColor( 0.6, 0.6,0.6,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glMatrixMode(GL_PROJECTION); 
  glLoadIdentity();
  float fov_angle = Camera::FieldOfView; 
  float ar = Camera::AspectRatio;
  float n = Camera::Near;
  float f = Camera::Far;

  // this gluPerspective call is equivalent to glFrustum call below

  gluPerspective(fov_angle, // field of view in degrees
	  ar, // aspect ratio
	  n, // near distance
	  f); // far distance
	  
  /*glFrustum(
	 ar*n*tan(fov_angle/180.0*M_PI/2),// left
	 ar*n*tan(fov_angle/180.0*M_PI/2), // right
	-n*tan(fov_angle/180.0*M_PI/2), // bottom
	 n*tan(fov_angle/180.0*M_PI/2), // top
	 n,f
	 );
	*/
  glMatrixMode(GL_MODELVIEW); 
  glLoadIdentity();
  gluLookAt( 
	  Camera::Position.x(), Camera::Position.y(),Camera::Position.z(), 
	  Camera::ViewCenter.x(),Camera::ViewCenter.y(),Camera::ViewCenter.z(), 
	  Camera::Up.x(),Camera::Up.y(),Camera::Up.z());
  Draw();
  glutSwapBuffers(); 
}



// dummy callback for the main window
void DrawMain() { 
}


int main(int argc, char* argv[]) {

  // initialize glut and parse command-line aguments that glut understands
  glutInit(&argc, argv);

  // initialize dislay mode: 4 color components, double buffer and depth buffer
 
  glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH);

  glutInitWindowSize(WindowParams::WindowWidth,WindowParams::WindowHeight);

  WindowParams::MainWindow = glutCreateWindow("Cameras");

  // create 4 windows and assign draw callbacks
  for(int i =0; i < 2; i++) 
	for(int j=0; j < 2; j++) {
      WindowParams::SubWindow[i][j] = 
		  glutCreateSubWindow(WindowParams::MainWindow, 
		    i*WindowParams::WindowWidth/2,j*WindowParams::WindowHeight/2,
			  WindowParams::WindowWidth/2,WindowParams::WindowHeight/2);
      glutSetWindow(WindowParams::SubWindow[i][j]);
	  glutDisplayFunc(WindowParams::DrawFunc[i][j]); 
	}


  // register callbacks for main window

  glutSetWindow(WindowParams::MainWindow);
  // dummy callback, no part of the main window is ever visible
  glutDisplayFunc(DrawMain); 
  // only the main window has reshape callback, sizes of all subwindows are adjusted there
  glutReshapeFunc(Reshape);

  // this is an infinite loop get event - dispatch event which never returns
  glutMainLoop();
  return 0;
}











