

#include <GL/glew.h>
#include <stdio.h>

#include "iglu.h"
using namespace iglu;

// Where is our object located at?
vec3                    objCenter    = vec3( 0.2, -0.3, 0 );

// Where does the camera start out?
vec3                    origEye      = vec3( -0.7, 0.7, 0.0 );

// Viewing parameters for the current pinhole camera
vec3                    viewEye      = origEye;
vec3                    viewAt       = vec3( 0.2, -0.2, 0 );

// Given the eye & at, and that this is a 2D image, find an "up" that's in the image plane
vec3                    viewUp       = vec3( 0.0, 0.0, 1.0 ).Cross(viewAt-viewEye).vNormalize(); 

// Out model reader
IGLUOBJReader::Ptr      objReader    = 0;

// Projection matrix for our pinhole camera.  (Essentially a simple 
//     IGLU::Perspective() with x & z swapped since we're in 2D)
float mat[] = { 0, 0, 1, 0,   0, 1, 0, 0,  1, 0, 0, -1,   1, 0, 0, 0 };
IGLUMatrix4x4           projPin      = IGLUMatrix4x4( mat );

// Key matriices variable for this program
IGLUMatrix4x4           modelOrtho   = IGLUMatrix4x4::Identity();
IGLUMatrix4x4           modelPinhole = IGLUMatrix4x4::LookAt( viewEye, viewAt, viewUp );
IGLUMatrix4x4           project      = IGLUMatrix4x4::Ortho( -1, 1, -1, 1 ); 

// Shaders used in this sample program
IGLUShaderProgram::Ptr  ortho2D      = 0;

// An intermediate framebuffer object we'll use to store a multisample buffer
IGLUFramebuffer::Ptr    fbo          = 0;
IGLUFramebuffer::Ptr    fboPersp     = 0;

// A vertex array to store our line geometry
IGLUVertexArray::Ptr    pinLineArray = 0;

// A frame timer we'll use to display a counter on screen
IGLUFrameRate::Ptr      frameTime    = 0;

// A simple trackball to rotate our geometry
IGLUMouseTranslator::Ptr     move    = 0;
IGLU2DMouseRotator::Ptr    rotate    = 0;

// The current size of our window and viewport positioning
//    This is (re-)computed inside the reshape callback
int  winWidth, winHeight;
int  viewWidth, viewHeight;
int  view1Off, view2Off;

// Data for the camera lines we'll draw eminating from the pinhole.
//    Most of this is a garbage array, as we'll call SetPinholeCameraRays()
//    to fill this up with data for the current view (e.g., from viewEye)
float pinholeLineData[48] = {
	0, 0.7,   -0.7, 0.0,
	-0.7, 0.7,    100.0, -50,
	-0.7, 0.7,    -0.6, -1.5,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};




void display ( void )
{
	// Start timing this frame
	frameTime->StartFrame();
	
	// For our inset framebuffers, clear to black
	glClearColor(0,0,0,1);
	
	// Draw to the "pinhole" space, which is really an orthographic
	//    projection of worldspace with white lines drawn to represetn
	//    camera rays
	fbo->Bind();
	fbo->Clear();
		ortho2D->Enable();
		    // Draw the cow, scaled to a reasonable size, translated so it's not
			//    in the center, and then modified by whatever user translation may have occured
			ortho2D["color"] = vec4(1,0,0,1);
			ortho2D["model"] = modelOrtho * move->GetMatrix() * IGLUMatrix4x4::Translate( objCenter ) * IGLUMatrix4x4::Scale( 0.1 );
			objReader->Draw( ortho2D );

			// Draw the lines eminating from the pinhole camera.
			ortho2D["color"] = vec4(1,1,1,1);
			ortho2D["model"] = modelOrtho;
			pinLineArray->DrawArrays( GL_LINES, 0, 24 );
		ortho2D->Disable();
	fbo->Unbind();	

	// Draw to the "perspective" space, which is really an orthographic
	//    projection of normalized device coordinate.  The white lines
	//    eminating from the pinhole are also drawn here, but in NDC, they'll
	//    be parallel.
	fboPersp->Bind();
	fboPersp->Clear();
		ortho2D->Enable();
			// Draw the cow.
			ortho2D["color"] = vec4(1,0,0,1);
			ortho2D["model"] = projPin * modelPinhole * move->GetMatrix() * IGLUMatrix4x4::Translate( objCenter ) * IGLUMatrix4x4::Scale( 0.1 );
			objReader->Draw( ortho2D );

			// Draw the lines
			ortho2D["color"] = vec4(1,1,1,1);
			ortho2D["model"] = projPin * modelPinhole;
			pinLineArray->DrawArrays( GL_LINES, 0, 24 );
		ortho2D->Disable();
	fboPersp->Unbind();

	// For our actual displayed screen, clear to gray.
	glClearColor(0.6,0.6,0.6,1);
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	// Draw our viewport 1 ("pinhole") on screen
	glViewport( view1Off, 2, viewWidth, viewHeight );
	IGLUDraw::Fullscreen( fbo[0] );

	// Draw our viewport 1 ("perpspective") on screen
	glViewport( view2Off, 2, viewWidth, viewHeight );
	IGLUDraw::Fullscreen( fboPersp[0] );

	// Draw miscellaneous frame information.  This needs a full-screen viewport
	glViewport( 0, 0, winWidth, winHeight );

	// Draw framerate
	char buf[32];
	sprintf( buf, "%.1f fps", frameTime->EndFrame() );
	IGLUDraw::DrawText( IGLU_FONT_FIXED, winWidth-180, winHeight-27, buf );

	// Draw instructions
	IGLUDraw::DrawText( IGLU_FONT_VARIABLE, 5, winHeight-27, "(Left) Move object    (Right) Rotate camera" );

	// Label the two viewports
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, view1Off+5, 5, vec4(1.0, 1.0, 0.0, 1.0 ), "Pinhole Camera" );
	IGLUDraw::DrawColorText( IGLU_FONT_VARIABLE, view2Off+5, 5, vec4(1.0, 1.0, 0.0, 1.0 ), "Perspective Space" );

	// Swap the GLUT buffers so we're actually show what we drew.
	glutSwapBuffers();
}

void mouse( int button, int state, int x, int y )
{
	// Handle trackball clicks and releases
	if ( state == GLUT_DOWN && button == GLUT_LEFT_BUTTON && (x < view1Off+viewWidth))
		move->SetOnClick( x, y );
	else if ( state == GLUT_DOWN && button == GLUT_RIGHT_BUTTON && (x < view1Off+viewWidth))
		rotate->SetOnClick( x, y );
	else if (state == GLUT_UP )
	{
		move->Release();
		rotate->Release();
	}
}

// This gets called every time the eye moves to reset the lines eminating from it.
void SetPinholeCameraRays( void )
{
	// Create rays leaving the eye (viewEye) that go in the general direction
	//     of the lookAt point (viewAt) just like rays would for a camera.
	vec3 toAt  = (viewAt-viewEye).vNormalize();
	vec3 upVec = toAt.Cross(vec3(0,0,-1)).vNormalize();

	// Draw 11 rays equally spaced over a roungly 90 degree field of view.
	//    We skip i=0, since that stores the near-plane line (see below)
	for (int i=1; i<=11; i++)
	{
		// By using 8.975 degrees rather than 9, the near plane line and
		//    the rays right at the boundary of the 90 degree frustum will
		//    still be visibile.
		float angle = (i-6)*8.975*IGLU_PI/180.0;                            // Compute angle from view direction
		float cosAngle = 1000.0*cos(angle), sinAngle = 1000.0*sin(angle);   // Compute cos/sin for offset from view direction
		pinholeLineData[4*i+0]=viewEye.X();                                 // All rays start at eye
		pinholeLineData[4*i+1]=viewEye.Y();
		pinholeLineData[4*i+2]=viewEye.X()+cosAngle*toAt.X()+sinAngle*upVec.X();  // Move the appropriate offsets for other point
		pinholeLineData[4*i+3]=viewEye.Y()+cosAngle*toAt.Y()+sinAngle*upVec.Y();
	}

	// Draw the near-plane line.
	float angle = -5.0*8.975*IGLU_PI/180.0;
	float cosAngle = 0.5*IGLU_SQRT2*cos(angle), sinAngle = 0.5*IGLU_SQRT2*sin(angle);
	pinholeLineData[0]=viewEye.X()+cosAngle*toAt.X()+sinAngle*upVec.X();
	pinholeLineData[1]=viewEye.Y()+cosAngle*toAt.Y()+sinAngle*upVec.Y();
	pinholeLineData[2]=viewEye.X()+cosAngle*toAt.X()-sinAngle*upVec.X();
	pinholeLineData[3]=viewEye.Y()+cosAngle*toAt.Y()-sinAngle*upVec.Y();

}

void motion( int x, int y )
{
	// Update the trackball matrix when we detect motion.
	move->UpdateOnMotion( x, y ); 
	rotate->UpdateOnMotion( x, y );
	
	// If we did, in fact have rotation occur, lets handle it
	if (rotate->HasChanged())
	{
		// Compute the new eye position.  (Rotate the view vector around the at point)
		vec3 newEye     = origEye-viewAt;
		vec4 rotatedEye = rotate->ApplyMatrix( vec4( newEye.X(), newEye.Y(), newEye.Z(), 1.0 ) );
		viewEye         = vec3( rotatedEye.X(), rotatedEye.Y(), rotatedEye.Z() ) + viewAt;
		viewUp          = vec3( 0.0, 0.0, 1.0 ).Cross(viewAt-viewEye).vNormalize(); 

		// Recompute the look-at matrix needed for transforming into perspective space
		modelPinhole    = IGLUMatrix4x4::LookAt( viewEye, viewAt, viewUp );

		// Recompute where the lines eminating from the eye are
		SetPinholeCameraRays();
		pinLineArray->SetVertexArraySubset( 0, sizeof( pinholeLineData ), pinholeLineData );
	}

	glutPostRedisplay();
}


void keys(unsigned char key, int x, int y)
{
	switch(key)
	{
	case 27:
	case'q':
	case'Q':
		exit(0);
		break;
	}
}

// Whenever nothing is happening, redraw.
void idle ( void )
{
	glutPostRedisplay();
}

// What happens when you resize?  This isn't particularly compelling,
//    But resize the viewports and store the sizes so we can position
//    text correctly
void reshape( int w, int h )
{
	glViewport( 0, 0, winWidth = w, winHeight = h );
	move->ResizeInteractorWindow( winWidth, winHeight );

	// Recompute viewport positioning information
	viewHeight = winHeight-28;
	viewWidth  = viewHeight;
	view1Off   = 2;
	view2Off   = 4+viewWidth;

	glutPostRedisplay();
}


int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA );
	glutInitWindowSize( 1030, 540 );
	glutInitWindowPosition( 20, 20 );
	glutCreateWindow( "Intermediate IGLU Example:  A 2D Perspective Camera Display" );
	glewInit();
	glutDisplayFunc( display );
	glutKeyboardFunc( keys );
	glutIdleFunc( idle );
	glutMouseFunc( mouse );
	glutMotionFunc( motion );
	glutReshapeFunc( reshape );

	// Create our frame timer
	frameTime = new IGLUFrameRate( 100 );

	// Create our interactors to interact with the scene
	move   = new IGLUMouseTranslator( 1030, 540, 1.0, vec3(4.0,0,0), vec3(0.0,2.0,0) );
	rotate = new IGLU2DMouseRotator( 1030, 540, 257, 273 );

	// Load the texture with IGLU
	objReader  = new IGLUOBJReader( "models/cow.obj" );
	
	// Create the framebuffers we'll use to draw our two inset viewports
	fbo      = IGLUFramebuffer::Create( GL_RGBA32F, 1024, 1024 );
	fboPersp = IGLUFramebuffer::Create( GL_RGBA32F, 1024, 1024 );
	
	// Load the shader that draws onto the screen.  It takes in various
	//    matrices, so you can use it to do almost anything you really want.
	ortho2D          = new IGLUShaderProgram( "shaders/drawGeometry.vert.glsl", "shaders/drawGeometry.frag.glsl");
	ortho2D->SetProgramDisables( IGLU_GLSL_DEPTH_TEST ); 
	ortho2D["proj"]  = project;
	ortho2D["model"] = modelOrtho;

	// Create the list storing the lines eminating from the light
	pinLineArray = new IGLUVertexArray();
	SetPinholeCameraRays();            // Performs computations to locate them.
	pinLineArray->SetVertexArray( sizeof( pinholeLineData ), pinholeLineData, IGLU_DYNAMIC|IGLU_DRAW );
	pinLineArray->EnableAttribute( IGLU_ATTRIB_VERTEX, 2, GL_FLOAT, 0, BUFFER_OFFSET(0) );

	// Start the GLUT interaction loop
	glutMainLoop();
	return 0;
}
