//-----------------------------------------------------------------------------
// Shogun3D's Jellyfish OpenGL demo 
// 
// Disclamer:  This demo uses code from the "Procedurally Animated Jellyfish" demo
//			   from the NVIDIA SDK v5.21.  The Jellyfish.cpp/h files were originally
//			   written by Wade Lutgen of the 3D Pipeline Corp.
//
// File:	main.cpp
// Desc:	All the code that I actually wrote is in this file as well as the entry 
//			point.  This code requires an NVIDIA GeForce3 or better to run.
//
// Date:	Sunday, December 20, 2009
//-----------------------------------------------------------------------------

// Include files 

#ifdef WIN32 // For windows builds
#include <windows.h>
#include <mmsystem.h>
#endif

#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string>
#include <assert.h>

#include <gl\glut.h>
#include <gl\glext.h>
#include <gl\glaux.h>

#include <al\al.h>
#include <al\alc.h>
#include <al\alut.h>

//#include "include\nvparse.h"

#include "constants.h"
#include "Jellyfish.h"
//#include "bubble.h"
#include "Particles.h"
#include "Bubbles.h"
#include "PostProcess.h"

#define MAX_FOLIAGE 750

#define BUFFER_OFFSET(i) (void*)(NULL + (i))

struct Plant_t
{
	Vector3	Position;
	GLuint	Texture;
	GLfloat Height;
};

struct Params
{
	int set( char* szParams )
	{
		FILE* fp = fopen( szParams, "r" );
		if( !fp )
			return 0;

		fscanf( fp, "width: %d\n", &this->width );
		fscanf( fp, "height: %d\n", &this->height );
		fscanf( fp, "colour: %d\n", &this->colour );
		fscanf( fp, "depth: %d\n", &this->depth );
		fscanf( fp, "stencil: %d\n", &this->stencil );
		fscanf( fp, "fullscreen: %d\n", &this->fullscreen );
		fscanf( fp, "fovy: %f\n", &this->fovy );
		fscanf( fp, "fsaa: %d\n", &this->fsaa );
		fscanf( fp, "fogstart: %f\n", &this->fogstart );
		fscanf( fp, "fogend: %f\n", &this->fogend );
		fscanf( fp, "blur_passes: %d\n", &this->blur_passes );
		fscanf( fp, "particles: %d\n", &this->particles );
		fscanf( fp, "fish: %d\n", &this->fish );
		fscanf( fp, "jellyfish: %d\n", &this->jellyfish );
		fclose(fp);

		return 1;
	}

	int width;
	int height;
	int colour;
	int depth;
	int stencil;
	int fullscreen;
	float fovy;
	int fsaa;
	float fogstart;
	float fogend;
	int blur_passes;

	int particles;
	int fish;
	int jellyfish;
};



//-----------------------------------------------------------------------------
// Global variables
//-----------------------------------------------------------------------------
Jellyfish*	pJellyFish		= NULL;
int			fovy			= 35.0f;
int			distance		= 75.0f;
Vector3		vecPosition, vecLookAt;
Plant_t		Foliage[MAX_FOLIAGE];
CParticleManager*	particleManager = NULL;
CBubbleManager*		bubbleManager = NULL;
Params		params;
int			wireframe = 0;

GLuint TexNVLogo		= 0;
GLuint TexGLLogo		= 0;
GLuint TexSeaFloor		= 0;
GLuint TexCaustics[32]	= {0};
GLuint TexFoliage[28]	= {0};
GLuint TexSphereMap		= 0;
GLuint VboFoliage		= 0;
GLuint TexBubble		= 0;
GLuint TexLeaf			= 0;

GLuint vertexProgramsNV[1];

// GL_NV_vertex_program functions
PFNGLLOADPROGRAMNVPROC				glLoadProgramNV;
PFNGLPROGRAMPARAMETER4FNVPROC		glProgramParameter4fNV;
PFNGLVERTEXATTRIB2FVNVPROC			glVertexAttrib2fvNV;
PFNGLVERTEXATTRIB3FVNVPROC			glVertexAttrib3fvNV;
PFNGLVERTEXATTRIB4FVNVPROC			glVertexAttrib4fvNV;
PFNGLBINDPROGRAMNVPROC				glBindProgramNV;
PFNGLTRACKMATRIXNVPROC				glTrackMatrixNV;
PFNGLEXECUTEPROGRAMNVPROC			glExecuteProgramNV;
PFNGLGETPROGRAMPARAMETERFVNVPROC	glGetProgramParameterfvNV;
PFNGLGENPROGRAMSNVPROC				glGenProgramsNV;

// GL_ARB_vertex_program functions
PFNGLGENPROGRAMSARBPROC					glGenProgramsARB;
PFNGLDELETEPROGRAMSARBPROC				glDeleteProgramsARB;
PFNGLBINDPROGRAMARBPROC					glBindProgramARB;
PFNGLPROGRAMSTRINGARBPROC				glProgramStringARB;
PFNGLPROGRAMENVPARAMETER4FVARBPROC		glProgramEnvParameter4fvARB;
PFNGLPROGRAMLOCALPARAMETER4FVARBPROC	glProgramLocalParameter4fvARB;
PFNGLPROGRAMLOCALPARAMETER4FARBPROC		glProgramLocalParameter4fARB;

// GL_ARB_multitexture functions
PFNGLACTIVETEXTUREARBPROC			glActiveTextureARB;
PFNGLMULTITEXCOORD2FARBPROC			glMultiTexCoord2fARB;
PFNGLCLIENTACTIVETEXTUREARBPROC		glClientActiveTextureARB;

// GL_ARB_vertex_buffer_object functions
PFNGLGENBUFFERSARBPROC				glGenBuffersARB;
PFNGLDELETEBUFFERSARBPROC			glDeleteBuffersARB;
PFNGLBINDBUFFERARBPROC				glBindBufferARB;
PFNGLBUFFERDATAARBPROC				glBufferDataARB;
PFNGLBUFFERSUBDATAARBPROC			glBufferSubDataARB;

// GL_NV_register_combiners functions
PFNGLCOMBINERPARAMETERFVNVPROC		glCombinerParameterfvNV;

struct Mouse
{
	float phi;
	float theta;
	int x, y;
	bool leftButton;
};

Mouse mouseState;


//-----------------------------------------------------------------------------
// Name: queryElapsedTime
// Desc: Uses the GLUT libraries glutGet() function to query the elapsed time
//		 in milliseconds.  This function was taken from the Jellyfish demo from
//		 the NVIDIA SDK v5.21
//-----------------------------------------------------------------------------
float queryElapsedTime()
{
	return glutGet(GLenum(GLUT_ELAPSED_TIME)) * .001f;
}


//-----------------------------------------------------------------------------
// Name: MouseFunction
// Desc: Gets mouse input from the user using GLUT's APIs
//-----------------------------------------------------------------------------
void MouseFunction( int button, int state, int x, int y )
{
	if( button == GLUT_LEFT_BUTTON )
	{
		mouseState.x = 0;
		mouseState.y = 0;
		switch( state )
		{
		case GLUT_DOWN:
			mouseState.x = x;
			mouseState.y = y;
			mouseState.leftButton = true;
			break;
		case GLUT_UP:
			mouseState.leftButton = false;
			break;
		}
	}
}

void MotionFunction( int x, int y )
{
	if( mouseState.leftButton )
	{
		mouseState.theta += (x - mouseState.x) * 0.5f;
		mouseState.phi += (y - mouseState.y) * 0.5;
		mouseState.x = x;
		mouseState.y = y;
	}
}

//-----------------------------------------------------------------------------
// Name: glh_extension_supported
// Desc: Checks the list of OpenGL extensions supported by this videocard/driver
//		 to see if it's supported.  This function was borrowed from the NVIDIA
//		 SDK (v9.5).  I was too lazy to write my own extension checking function :)
//		 See glh_extensions.h
//-----------------------------------------------------------------------------
int glh_extension_supported( const char *extension )
{
    static const GLubyte *extensions = NULL;
    const GLubyte *start;
    GLubyte *where, *terminator;
    
    // Extension names should not have spaces. 
    where = (GLubyte *) strchr( extension, ' ');
    if ( where || *extension == '\0')
      return 0;
    
    if ( !extensions )
      extensions = glGetString( GL_EXTENSIONS );

    // It takes a bit of care to be fool-proof about parsing the
    // OpenGL extensions string.  Don't be fooled by sub-strings,
    // etc.
    start = extensions;
    for (;;) 
    {
        where = (GLubyte *) strstr( (const char *) start, extension );
        if ( !where )
            break;
        terminator = where + strlen( extension );
        if ( where == start || *(where - 1) == ' ' ) 
        {
            if ( *terminator == ' ' || *terminator == '\0' ) 
            {
                return 1;
            }
        }
        start = terminator;
    }
    return 0;
}

//-----------------------------------------------------------------------------
// Name: LoadProgramNV
// Desc: Opens a vertex or fragment program written to a text file and loads it
//		 into OpenGL.  This function is a modified version of the one found in
//		 the book known as "More OpenGL Game Programming".  This function uses 
//		 the NVIDIA extension (GL_NV_vertex_program/texture_shader) rather than 
//		 the ARB extensions (GL_ARB_vertex/fragment_program).
//-----------------------------------------------------------------------------
GLuint LoadProgramNV(GLenum target, std::string filename)
{
	GLubyte* code = NULL;

	//Open the program file
	std::ifstream programFile(filename.c_str());
	if(!programFile)
		return 0;

	//Read the file
	std::string programLine, programText;

	while(std::getline(programFile, programLine))
		programText += programLine + '\n';

	//Generate a program index
	unsigned int index;
	glGenProgramsNV(1, &index);
	if(index == 0)
		return 0;

	//Initialise the program
	glBindProgramNV(target, index);

	code = (GLubyte*) malloc( strlen( programText.c_str() ) );
	memcpy( code, programText.c_str(), strlen( programText.c_str() ) );
	glLoadProgramNV(target, index, static_cast<GLsizei>(programText.length()), code );
	free( code );

	return index;
}

//-----------------------------------------------------------------------------
// Name: LoadProgramARB
// Desc: Same as above, but for the ARB extensions (GL_ARB_vertex/fragment_program)
//-----------------------------------------------------------------------------
GLuint LoadProgramARB(GLenum target, std::string filename)
{
	//Open the program file
	std::ifstream programFile(filename.c_str());
	if(!programFile)
		return 0;

	//Read the file
	std::string programLine, programText;

	while(std::getline(programFile, programLine))
		programText += programLine + '\n';

	//Generate a program index
	unsigned int index;
	glGenProgramsARB(1, &index);
	if(index == 0)
		return 0;

	//Initialise the program
	glBindProgramARB(target, index);
	glProgramStringARB(	target, GL_PROGRAM_FORMAT_ASCII_ARB,
						static_cast<GLsizei>(programText.length()), programText.c_str());

	return index;
}

//-----------------------------------------------------------------------------
// Name: CreateTexture
// Desc: Loads the texture from disk using the glaux library.  Supports multiple
//		 texture targets and also supports the use of ColorKeying (will convert
//		 your texture to 32-bit though).
//-----------------------------------------------------------------------------
GLuint CreateTexture( char* szTexture, GLuint Target, GLuint ColorKey )
{
	// Load the sprite texture from disk
	AUX_RGBImageRec* pTexture_RGB = auxDIBImageLoad( szTexture );
	GLubyte* pTexture_RGBA = NULL;
	GLuint uHandle = 0;

	// If the texture was loaded, we can add the alpha channel for transparency
	if( pTexture_RGB != NULL )
	{
		// Precalculate the sizes of both the 24-bit and the 32-bit texture
		GLint iTextureSize_RGB = pTexture_RGB->sizeX * pTexture_RGB->sizeY * 3;
		GLint iTextureSize_RGBA = ( iTextureSize_RGB / 3 ) * 4;

		// Allocate the buffer for the 32-bit RGBA texture
		pTexture_RGBA = (GLubyte*) malloc( iTextureSize_RGBA );
		if( !pTexture_RGBA )
		{
			printf( "ERROR: Unable to allocate memory buffer for 32-bit RGBA texture (%s)!\n", szTexture );
			return 0;
		}

		// Next we're going to loop through each pixel in the 24-bit texture and add in an
		// alpha value of 255 for those colors that match the colorkey (fully transparent)
		// and 0 (fully opaque) for those that don't. 

		int i, j;
		GLubyte r = ( ColorKey ) & 0xFF;
		GLubyte g = ( ColorKey >> 8 ) & 0xFF;
		GLubyte b = ( ColorKey >> 16 ) & 0xFF;

		for( i = 0, j = 0; i < iTextureSize_RGB; i += 3, j += 4 )
		{
			// Does the current pixel match the colour key?
			if( pTexture_RGB->data[i] == r &&
				pTexture_RGB->data[i+1] == g &&
				pTexture_RGB->data[i+2] == b && 
				ColorKey != 0 )
			{
				// We have a match, set this pixel to transparent black.
				pTexture_RGBA[j+0] = 0;
				pTexture_RGBA[j+1] = 0;
				pTexture_RGBA[j+2] = 0;
				pTexture_RGBA[j+3] = 0;
			}
			else
			{
				// This pixel does not match, retain the current pixel colour and
				// set the alpha to 255 (opaque).
				pTexture_RGBA[j] = pTexture_RGB->data[i];
				pTexture_RGBA[j+1] = pTexture_RGB->data[i+1];
				pTexture_RGBA[j+2] = pTexture_RGB->data[i+2];
				pTexture_RGBA[j+3] = 255;
			}
		}

		// Enable the desired texture target
		glEnable( Target );

		// Generate one texture ID
		glGenTextures( 1, &uHandle );
		// Bind the texture using the desired target
		glBindTexture( Target, uHandle );
		// Enable bilinear filtering on this texture
		glTexParameteri( Target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
		glTexParameteri( Target, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

		// Write the 32-bit RGBA texture buffer to video memory
		glTexImage2D( Target, 0, GL_RGBA, pTexture_RGB->sizeX, pTexture_RGB->sizeY,
					  0, GL_RGBA, GL_UNSIGNED_BYTE, pTexture_RGBA );
		gluBuild2DMipmaps( Target, GL_RGBA, pTexture_RGB->sizeX, pTexture_RGB->sizeY, GL_RGBA, 
						GL_UNSIGNED_BYTE, pTexture_RGBA );
	}
	else
	{
		printf( "ERROR: %s not found!\n", szTexture );
	}

	// Now that the texture is loaded, we need to free up the 
	// memory that is no longer needed.
	if( pTexture_RGB )
	{
		if( pTexture_RGB->data )
			free( pTexture_RGB->data );

		free( pTexture_RGB );
	}

	if( pTexture_RGBA )
		free( pTexture_RGBA );

	return uHandle;
}

//-----------------------------------------------------------------------------
// Name: loadVertexProgramsNV
// Desc: Loads any vertex programs used in this demo.  Instead of using NVParse,
//		 the shader is loaded from a text file on disk.
//-----------------------------------------------------------------------------
void loadVertexProgramsNV()
{
	// Load the vertex programs
	if( !( vertexProgramsNV[0] = LoadProgramNV( GL_VERTEX_PROGRAM_NV, "..\\programs\\jellyfish_nv.vp" ) ) )
		return;
	
	// Track the Projection matrix in c[0] to c[3].
	glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 0, GL_MODELVIEW_PROJECTION_NV, GL_IDENTITY_NV);

	// Track the inverse of the Modelview matrix in c[4] to c[7]
	glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 4, GL_MODELVIEW, GL_INVERSE_TRANSPOSE_NV);

	// Load the view position into c[8]
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 8, 0.0f, 0.0f, 100.0f, 1.0f);	

	// Load cosine series constants into c[9]
 	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 9, 1.0f, -1.0f/2.0f, 1.0f/24.0f, -1.0f/720.0f);	

	// Constants for cosine
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 10, 0.0f, 0.5f, 1.0f, 0.2f);
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, 0.25f, -9.0f, 0.75f, 1.0f / (2.0f * M_PI) );
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 12, 24.9808039603f, -24.9808039603f, -60.1458091736f, 60.1458091736f );
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 13, 85.4537887573f, -85.4537887573f, -64.9393539429f, 64.9393539429f );
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 14, 19.7392082214f, -19.7392082214f, - 1.0f,	1.0f );

	// Light position
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 15, 0.0f, 0.7f, 0.7f, 1.0f );

	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 17, 0.3f, 0.3f, 0.3f, 1.0f );
}

//-----------------------------------------------------------------------------
// Name: loadVertexProgramsARB
// Desc: Same as above, but uses the ARB extensions to load ARB vertex programs.
//-----------------------------------------------------------------------------
void loadVertexProgramsARB()
{
	// Load the vertex programs
	if( !( vertexProgramsNV[0] = LoadProgramARB( GL_VERTEX_PROGRAM_ARB, "..\\programs\\jellyfish_arb.vp" ) ) )
		return;
	
	// Track the Projection matrix in c[0] to c[3].
//	glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 0, GL_MODELVIEW_PROJECTION_NV, GL_IDENTITY_NV);

	// Track the inverse of the Modelview matrix in c[4] to c[7]
//	glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 4, GL_MODELVIEW, GL_INVERSE_TRANSPOSE_NV);

	// Load the view position into c[8]
	glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 8, 0.0f, 0.0f, 100.0f, 1.0f);	

	// Load cosine series constants into c[9]
 	glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 9, 1.0f, -1.0f/2.0f, 1.0f/24.0f, -1.0f/720.0f);	

	// Constants for cosine
	glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 10, 0.0f, 0.5f, 1.0f, 0.2f);
	glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 11, 0.25f, -9.0f, 0.75f, 1.0f / (2.0f * M_PI) );
	glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 12, 24.9808039603f, -24.9808039603f, -60.1458091736f, 60.1458091736f );
	glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 13, 85.4537887573f, -85.4537887573f, -64.9393539429f, 64.9393539429f );
	glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 14, 19.7392082214f, -19.7392082214f, - 1.0f,	1.0f );

	// Light position
	glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 15, 0.0f, 0.7f, 0.7f, 1.0f );

	glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 17, 0.3f, 0.3f, 0.3f, 1.0f );
}

//-----------------------------------------------------------------------------
// Name: InitExtensions
// Desc: Verifys support for any necessary extensions and initializes any func-
//		 tion pointers associated with it.
//-----------------------------------------------------------------------------
int InitExtensions( void )
{
	// Make sure the required NVIDIA and ARB extensions are supported
	if( !glh_extension_supported( "GL_NV_vertex_program" ) )
	{
		printf( "ERROR: Extension GL_NV_vertex_program was not found!\n" );
		exit(0);
	}

	if( !glh_extension_supported( "GL_NV_texture_shader" ) )
	{
		printf( "ERROR: Extension GL_NV_texture_shader was not found!\n" );
		exit(0);
	}

	if( !glh_extension_supported( "GL_NV_texture_rectangle" ) )
	{
		printf( "ERROR: Extension GL_NV_texture_rectangle was not found!\n" );
		exit(0);
	}

	if( !glh_extension_supported( "GL_NV_fog_distance" ) )
	{
		printf( "ERROR: Extension GL_NV_fog_distance not supported!\n" );
		exit(0);
	}

	if( !glh_extension_supported( "GL_ARB_multitexture" ) )
	{
		printf( "ERROR: Extension GL_ARB_multitexture was not found!\n" );
		exit(0);
	}

	if( !glh_extension_supported( "GL_ARB_vertex_buffer_object" ) )
	{
		printf( "ERROR: Extension GL_ARB_vertex_buffer_object not found!\n\n" );
		exit(1);
	}

	if( !glh_extension_supported( "GL_NV_register_combiners" ) )
	{
		printf( "ERROR: Extension GL_NV_register_combiners not supported!\n" );
		exit(1);
	}

	// Initialize any extension's function pointers

	/* 
	 * GL_NV_vertex_program/texture_shader functions
	 */

	glLoadProgramNV = (PFNGLLOADPROGRAMNVPROC) wglGetProcAddress("glLoadProgramNV");
	if ( glLoadProgramNV == 0 )
	{
		printf( "Error: could not load glLoadProgramNV" );
		exit(0);
	}
	
	glProgramParameter4fNV = (PFNGLPROGRAMPARAMETER4FNVPROC) wglGetProcAddress("glProgramParameter4fNV");
	if ( glProgramParameter4fNV == 0 )
	{
		printf( "Error: could not load glProgramParameter4fNV" );
		exit(0);
	}
	
	glVertexAttrib4fvNV = (PFNGLVERTEXATTRIB4FVNVPROC) wglGetProcAddress("glVertexAttrib4fvNV");
	if ( glVertexAttrib4fvNV == 0 )
	{
		printf( "Error: could not load glVertexAttrib4fvNV" );
		exit(0);
	}
	
	glVertexAttrib3fvNV = (PFNGLVERTEXATTRIB3FVNVPROC) wglGetProcAddress("glVertexAttrib3fvNV");
	if ( glVertexAttrib3fvNV == 0 )
	{
		printf( "Error: could not load glVertexAttrib3fvNV" );
		exit(0);
	}
	
	glVertexAttrib2fvNV = (PFNGLVERTEXATTRIB2FVNVPROC) wglGetProcAddress("glVertexAttrib2fvNV");
	if ( glVertexAttrib2fvNV == 0 )
	{
		printf( "Error: could not load glVertexAttrib2fvNV" );
		exit(0);
	}
	
	glBindProgramNV = (PFNGLBINDPROGRAMNVPROC) wglGetProcAddress("glBindProgramNV");
	if ( glBindProgramNV == 0 )
	{
		printf( "Error: could not load glBindProgramNV" );
		exit(0);
	}
	
	glTrackMatrixNV = (PFNGLTRACKMATRIXNVPROC) wglGetProcAddress("glTrackMatrixNV");
	if ( glTrackMatrixNV == 0 )
	{
		printf( "Error: could not load glTrackMatrixNV" );
		exit(0);
	}
	
	glExecuteProgramNV = (PFNGLEXECUTEPROGRAMNVPROC) wglGetProcAddress("glExecuteProgramNV");
	if ( glExecuteProgramNV == 0 )
	{
		printf( "Error: could not load glExecuteProgramNV" );
        exit(0);
	}
	
	glGetProgramParameterfvNV = (PFNGLGETPROGRAMPARAMETERFVNVPROC) wglGetProcAddress("glGetProgramParameterfvNV");
	if ( glGetProgramParameterfvNV == 0 )
	{
		printf( "Error: could not load glGetProgramParameterfvNV" );
		exit(0);
	}

	glGenProgramsNV = (PFNGLGENPROGRAMSNVPROC) wglGetProcAddress("glGenProgramsNV");
	if ( glGenProgramsNV == 0 )
	{
		printf( "Error: could not load glGenProgramsNV" );
		exit(0);
	}

	/*
	 * GL_ARB_fragment_program functions
	 */
	if( !glh_extension_supported( "GL_ARB_fragment_program" ) )
	{
		printf( "ERROR: Extension GL_ARB_fragment_program was not found!\n\n" );
		exit(0);
	}

	// Get addresses to the required function pointers
	glGenProgramsARB = (PFNGLGENPROGRAMSARBPROC) wglGetProcAddress( "glGenProgramsARB" );
	glDeleteProgramsARB = (PFNGLDELETEPROGRAMSARBPROC) wglGetProcAddress( "glDeleteProgramsARB" );
	glBindProgramARB = (PFNGLBINDPROGRAMARBPROC) wglGetProcAddress( "glBindProgramARB" );
	glProgramStringARB = (PFNGLPROGRAMSTRINGARBPROC) wglGetProcAddress( "glProgramStringARB" );
	glProgramEnvParameter4fvARB = (PFNGLPROGRAMENVPARAMETER4FVARBPROC) wglGetProcAddress( "glProgramEnvParameter4fvARB" );
	glProgramLocalParameter4fvARB = (PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) wglGetProcAddress( "glProgramLocalParameter4fvARB" );
	glProgramLocalParameter4fARB = (PFNGLPROGRAMLOCALPARAMETER4FARBPROC) wglGetProcAddress( "glProgramLocalParameter4fARB" );

	/*
	 * GL_ARB_multitexture functions
	 */
	glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) wglGetProcAddress( "glActiveTextureARB" );
	if( !glActiveTextureARB )
	{
		printf( "ERROR: could not load glActiveTextureARB" );
		exit(0);
	}

	glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC) wglGetProcAddress( "glClientActiveTextureARB" );
	if( !glClientActiveTextureARB )
	{
		printf( "ERROR: could not load glClientActiveTextureARB" );
		exit(0);
	}

	glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC) wglGetProcAddress( "glMultiTexCoord2fARB" );
	if( !glMultiTexCoord2fARB )
	{
		printf( "ERROR: could not load glMultiTexCoord2fARB" );
		exit(0);
	}

	/*
	 * GL_ARB_vertex_buffer_object functions
	 */
	glGenBuffersARB = (PFNGLGENBUFFERSARBPROC) wglGetProcAddress( "glGenBuffersARB" );
	if( !glGenBuffersARB )
	{
		printf( "ERROR: could not load glGenBuffersARB" );
		exit(0);
	}

	glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC) wglGetProcAddress( "glDeleteBuffersARB" );
	if( !glDeleteBuffersARB )
	{
		printf( "ERROR: could not load glDeleteBuffersARB" );
		exit(0);
	}

	glBindBufferARB = (PFNGLBINDBUFFERARBPROC) wglGetProcAddress( "glBindBufferARB" );
	if( !glBindBufferARB )
	{
		printf( "ERROR: could not load glBindBufferARB" );
		exit(0);
	}

	glBufferDataARB = (PFNGLBUFFERDATAARBPROC) wglGetProcAddress( "glBufferDataARB" );
	if( !glBufferDataARB )
	{
		printf( "ERROR: could not load glBufferDataARB" );
		exit(0);
	}

	glBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC) wglGetProcAddress( "glBufferSubDataARB" );
	if( !glBufferSubDataARB )
	{
		printf( "ERROR: could not load glBufferSubDataARB" );
		exit(0);
	}

	/*
	 * GL_NV_register_combiners functions
	 */
	glCombinerParameterfvNV = (PFNGLCOMBINERPARAMETERFVNVPROC) wglGetProcAddress( "glCombinerParameterfvNV" );
	if( !glCombinerParameterfvNV )
	{
		printf( "ERROR: could not load glCombinerParameterfvNV" );
		exit(0);
	}

	return 1;
}

//-----------------------------------------------------------------------------
// Name: LoadTextures
// Desc: Loads all the textures required for this demo.
//-----------------------------------------------------------------------------
int LoadTextures( void )
{
	// Load the NVIDIA logo to show the user that NVIDIA is responsible
	// for such outstanding graphics in this demo!  Also show that this
	// demo requires an NVIDIA GeForce 3 or better to run.
	TexNVLogo = CreateTexture( "..\\textures\\badge_gf3.bmp", GL_TEXTURE_RECTANGLE_NV, 0x00FF00FF );
	if( !TexNVLogo )
	{
		printf( "ERROR: Error loading badge_gf3.bmp!\n" );
		return 0;
	}

	TexGLLogo = CreateTexture( "..\\textures\\OpenGL.bmp", GL_TEXTURE_RECTANGLE_NV, 0 );
	if( !TexNVLogo )
	{
		printf( "ERROR: Error loading OpenGL.bmp!\n" );
		return 0;
	}

	// Load the sea floor texture
	TexSeaFloor = CreateTexture( "..\\textures\\seafloor.bmp", GL_TEXTURE_2D, 0 );
	if( !TexSeaFloor )
	{
		printf( "ERROR: Error loading seafloor.bmp!\n" );
		return 0;
	}

	// Load the water caustics textures
	for( int i = 0; i < 31; i++ )
	{
		char string[64];

		sprintf( string, "..\\textures\\caust%.02d.bmp", i );
		TexCaustics[i] = CreateTexture( string, GL_TEXTURE_2D, 0 );
		if( TexCaustics[i] == 0 )
		{
		//	printf( "ERROR: Error loading %s\n", string );
			return 0;
		}
	}

	// Load the foliage textures
	int n[] = { 75, 76, 77, 78, 79, 80, 81, 82, 83,
				84, 93, 94, 95, 96, 97, 114, 115, 116,
				117, 118, 119, 120, 121, 136, 137, 138, 139, 140 };

	for( i = 0; i < 28; i++ )
	{
		char string[64];
		
		sprintf( string, "..\\textures\\pflanzentex%.03d.bmp", n[i] );
		TexFoliage[i] = CreateTexture( string, GL_TEXTURE_2D, 0x00FF00FF );
		if( TexFoliage == 0 )
		{
			return 0;
		}
	}

	// Sphere map
	TexSphereMap = CreateTexture( "..\\textures\\spheremap.bmp", GL_TEXTURE_2D, 0 );
	if( !TexSphereMap )
		return 0;

	return 1;
}

//-----------------------------------------------------------------------------
// Name: InitFoliage
// Desc: Initializes the foliage geometry and gives each plant a texture and a
//		 position on the sea floor.
//-----------------------------------------------------------------------------
int InitFoliage( void )
{
	GLfloat fDiffuse[] = 
	{
		1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
		1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f
	};
	GLfloat fNormals[] = 
	{
		0.0f, 0.0f, 1.0f,
		0.0f, 0.0f, 1.0f,
		0.0f, 0.0f, 1.0f,
		0.0f, 0.0f, 1.0f,
	};
	GLfloat fVertices[] = 
	{
		-15.0f, -20.0f, 0.0f,
		-15.0f,  40.0f, 0.0f,
		 15.0f,  40.0f, 0.0f,
		 15.0f, -20.0f, 0.0f,
	};
	GLfloat fTexCoord0[] = 
	{
		0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f
	};
	GLfloat fTexCoord1[] = 
	{
		0.0f, 0.0f, 0.0f, 5.0f, 5.0f, 5.0f, 5.0f, 0.0f
	};

	GLsizei offset = 0;
	GLsizei vboSize = sizeof( GLfloat ) * 12 + 	// Diffuse colour
					  sizeof( GLfloat ) * 12 + 	// Normals
					  sizeof( GLfloat ) * 12 +	// Vertices
					  sizeof( GLfloat ) * 8 +	// Texture coordinates (0)
					  sizeof( GLfloat ) * 8;	// Texture coordinates (1)

	// Create one VBO for each plant being rendered
	glGenBuffersARB( 1, &VboFoliage );
	glBindBufferARB( GL_ARRAY_BUFFER, VboFoliage );

	// Initialize a static VBO.  Make it large enough for the entire
	// primitive, then fill it in as we go.
	glBufferDataARB( GL_ARRAY_BUFFER, vboSize, NULL, GL_STATIC_DRAW );

	// Set the diffuse colour data
	glBufferSubDataARB( GL_ARRAY_BUFFER, offset, sizeof( GLfloat ) * 12, fDiffuse );
	offset += sizeof( GLfloat ) * 12;
	// Set the normal data
	glBufferSubDataARB( GL_ARRAY_BUFFER, offset, sizeof( GLfloat ) * 12, fNormals );
	offset += sizeof( GLfloat ) * 12;
	// Set the vertex data
	glBufferSubDataARB( GL_ARRAY_BUFFER, offset, sizeof( GLfloat ) * 12, fVertices );
	offset += sizeof( GLfloat ) * 12;
	// Set the tex coord 0 data
	glBufferSubDataARB( GL_ARRAY_BUFFER, offset, sizeof( GLfloat ) * 8, fTexCoord0 );
	offset += sizeof( GLfloat ) * 8;
	// Set the tex coord 1 data
	glBufferSubDataARB( GL_ARRAY_BUFFER, offset, sizeof( GLfloat ) * 8, fTexCoord1 );
	offset += sizeof( GLfloat ) * 8;

	assert( offset == vboSize );

	// Generate random position and texture for each plant
	for( int i = 0; i < MAX_FOLIAGE; i++ )
	{
		extern float myRandom();
		float x = 1400.0f * myRandom() - 700.0f;
		float z = 1400.0f * myRandom() - 700.0f;
		static int t = 0;

		/*if( (i%3) == 1 )
			x = -x;
		if( (i%2) == 0 )
			z = -z;*/

		Foliage[i].Position.set( x, 0.0f, z );
		Foliage[i].Texture = t; //TexFoliage[t];
		Foliage[i].Height = myRandom() * 40.0f;

		if( ++t > 27 ) t = 0;
	}

	return 1;
}

//-----------------------------------------------------------------------------
// Name: InitScene
// Desc: Initializes extensions, textures, render states, etc. before rendering
//-----------------------------------------------------------------------------
int InitScene( void )
{
	//
	// Set default render states
	//

	// Enable depth testing 
	glEnable( GL_DEPTH_TEST );
	glDepthFunc( GL_LEQUAL );
	glClearDepth( 1.0f );

	// Disable lighting
	glDisable( GL_LIGHTING );

	// Disable texture mapping (for now)
	glDisable( GL_TEXTURE_2D );

	// Enable blending
	glEnable( GL_BLEND );
	glBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_ALPHA );

	// Disable dithering
	glDisable( GL_DITHER );

	// Disable culling
	glDisable( GL_CULL_FACE );

	// Enable perspective correction and anti-aliasing
	glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
	glHint( GL_POLYGON_SMOOTH_HINT, GL_NICEST );
	glHint( GL_POINT_SMOOTH_HINT, GL_NICEST );

	// Enable fog
	float fFogColour[] = { 0.01f, 0.01f, 0.01f, 1.0f };

	glEnable( GL_FOG );
	glFogfv( GL_FOG_COLOR, fFogColour );
	glFogf( GL_FOG_DENSITY, 0.4f );
	glFogf( GL_FOG_MODE, GL_LINEAR );
	glFogf( GL_FOG_START, 200.0f );
	glFogf( GL_FOG_END, 1000.0f );
//	glFogi( GL_FOG_DISTANCE_MODE_NV, GL_EYE_RADIAL_NV );
//	glFogi( GL_FOG_DISTANCE_MODE_NV, GL_EYE_PLANE_ABSOLUTE_NV );

	// Change the clear colour
	glClearColor( 0.01f, 0.01f, 0.01f, 1.0f );

	// Initialize OpenGL extensions
	InitExtensions();
	
	// Initialize the vertex program
#if USE_ARB_VERTEXPROGRAM
	loadVertexProgramsARB();
#else
	loadVertexProgramsNV();
#endif

	// Initialize post processing efects
	init_post_process_effects();

	// Initialize textures
	LoadTextures();

	// Create a new instance of the jellyfish
	pJellyFish = new Jellyfish();

	// Initialize foliage
	InitFoliage();

	// Initialize particle manager
	particleManager = new CParticleManager( 10000, 700.0f );

	// Initialize bubble manager
	bubbleManager = new CBubbleManager( 200.0f, 700.0f );
	bubbleManager->SetTexture( TexSphereMap );

	// Confirm that the texture rectangle extension is disabled
	glDisable( GL_TEXTURE_RECTANGLE_NV );

	return GL_TRUE;
}

//-----------------------------------------------------------------------------
// Name: glEnable2D
// Desc: Enabled 2D primitive rendering by setting up the appropriate orthographic
//		 perspectives and matrices.
//-----------------------------------------------------------------------------
void glEnable2D( void )
{
	GLint iViewport[4];

	// Get a copy of the viewport
	glGetIntegerv( GL_VIEWPORT, iViewport );

	// Save a copy of the projection matrix so that we can restore it 
	// when it's time to do 3D rendering again.
	glMatrixMode( GL_PROJECTION );
	glPushMatrix();
	glLoadIdentity();

	// Set up the orthographic projection
	glOrtho( iViewport[0], iViewport[0]+iViewport[2],
			 iViewport[1]+iViewport[3], iViewport[1], -1, 1 );
	glMatrixMode( GL_MODELVIEW );
	glPushMatrix();
	glLoadIdentity();

	// Make sure depth testing and lighting are disabled for 2D rendering until
	// we are finished rendering in 2D
	glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT );
	glDisable( GL_DEPTH_TEST );
	glDisable( GL_LIGHTING );
}


//-----------------------------------------------------------------------------
// Name: glDisable2D
// Desc: Disables 2D rendering and restores the previous matrix and render states
//		 before they were modified.
//-----------------------------------------------------------------------------
void glDisable2D( void )
{
	glPopAttrib();
	glMatrixMode( GL_PROJECTION );
	glPopMatrix();
	glMatrixMode( GL_MODELVIEW );
	glPopMatrix();
}

//-----------------------------------------------------------------------------
// Name: glDrawQuad
// Desc: Renders a quad to the screen.  Assumes that glEnable2D has been called.
//-----------------------------------------------------------------------------
void glDrawQuad( int x, int y, int width, int height, float colour[4] )
{
	// Render a quad to the screen
	glBegin( GL_QUADS );
		glColor4fv( colour );
		glTexCoord2i( 0, height );		glVertex2i( x, y );
		glTexCoord2i( width, height );	glVertex2i( x+width, y );
		glTexCoord2i( width, 0 );		glVertex2i( x+width, y+height );
		glTexCoord2i( 0, 0 );			glVertex2i( x, y+height );
	glEnd();
}

//-----------------------------------------------------------------------------
// Name: RenderSeaFloor
// Desc: 
//-----------------------------------------------------------------------------
void RenderSeaFloor( void )
{
	float fFloorSize = 1000.0f;
	static int c = 0;
	static DWORD dwStartTime = GetTickCount();
	static DWORD dwElapsedTime = 0;

	// Render the sea floor
//	glPushMatrix();
//	glLoadIdentity();

	// Save all render state values
	glPushAttrib( GL_ALL_ATTRIB_BITS );

	// Disable alpha blending
	glDisable( GL_BLEND );
	// Disable texture rectangles
	glDisable( GL_TEXTURE_RECTANGLE_NV );
	// Enable culling (counter clock wise)
	glEnable( GL_CULL_FACE );
	glCullFace( GL_CCW );
	
	// Apply the 2D texture map for texture unit 0
	glActiveTextureARB( GL_TEXTURE0_ARB );
	glEnable( GL_TEXTURE_2D );
	glBindTexture( GL_TEXTURE_2D, TexSeaFloor );
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );

	// Apply the water caustics texture for texture unit 1
	glActiveTextureARB( GL_TEXTURE1_ARB );
	glEnable( GL_TEXTURE_2D );
	glBindTexture( GL_TEXTURE_2D, TexCaustics[c] );
//	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB );
//	glTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2 );

	glBegin( GL_QUADS );
		glColor3f( 1.0f, 1.0f, 1.0f );
		glNormal3f( 0.0f, 1.0f, 0.0f );

		glMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0.0f, 0.0f );		
		glMultiTexCoord2fARB( GL_TEXTURE1_ARB, 0.0f, 0.0f );
		glVertex3f( -fFloorSize, -20.0f, -fFloorSize );

		glMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0.0f, 10.0f );
		glMultiTexCoord2fARB( GL_TEXTURE1_ARB, 0.0f, 50.0f );
		glVertex3f( -fFloorSize, -20.0f,  fFloorSize );

		glMultiTexCoord2fARB( GL_TEXTURE0_ARB, 10.0f, 10.0f );
		glMultiTexCoord2fARB( GL_TEXTURE1_ARB, 50.0f, 50.0f );
		glVertex3f(  fFloorSize, -20.0f,  fFloorSize );

		glMultiTexCoord2fARB( GL_TEXTURE0_ARB, 10.0f, 0.0f );	
		glMultiTexCoord2fARB( GL_TEXTURE1_ARB, 50.0f, 0.0f );
		glVertex3f(  fFloorSize, -20.0f, -fFloorSize );
	glEnd();
	
	// Disable texture unit 1
	glActiveTextureARB( GL_TEXTURE1_ARB );
	glDisable( GL_TEXTURE_2D );
	glDisable( GL_TEXTURE1_ARB );
	glActiveTextureARB( GL_TEXTURE0_ARB );

	// Restore the render state attributes
	glPopAttrib();

	// Update the caustic texture every 100 milliseconds
	dwElapsedTime = GetTickCount() - dwStartTime;

	if( dwElapsedTime >= 25 )
	{
		dwStartTime = GetTickCount();
		dwElapsedTime = 0;

		if( ++c > 30 )
			c = 0;
	}

//	glPopMatrix();
}

//-----------------------------------------------------------------------------
// Name: RenderFoliage
// Desc: 
//-----------------------------------------------------------------------------
void RenderFoliage( void )
{
	static int c = 0;
	static DWORD dwStartTime = GetTickCount();
	static DWORD dwElapsedTime = 0;

	// Render the foliage
	glPushMatrix();
//	glLoadIdentity();

	// Save all render state values
	glPushAttrib( GL_ALL_ATTRIB_BITS );

	// Enable alpha blending (for the color key)
	//glEnable( GL_BLEND );
	glEnable( GL_ALPHA_TEST );
	glAlphaFunc( GL_GEQUAL, 0.5f );

	// Disable texture rectangles
	glDisable( GL_TEXTURE_RECTANGLE_NV );
	// Disable culling
	glDisable( GL_CULL_FACE );
	// Disable writes to the depth buffer
	//glDepthMask( GL_FALSE );
	
	// Set up billboarding
	/*Vector3 vecDir = vecLookAt - vecPosition;
	if( (vecDir.get())[0] > 0.0f )
		glRotatef( -atanf( (vecDir.get())[2] / (vecDir.get())[0] ) + 3.14f / 2, 0.0f, 1.0f, 0.0f );
	else
		glRotatef( -atanf( (vecDir.get())[2] / (vecDir.get())[0] ) - 3.14f / 2, 0.0f, 1.0f, 0.0f );*/

	for( int i = 0; i < MAX_FOLIAGE; i++ )
	{
		extern float myRandom();

		glPushMatrix();

		// Apply the 2D texture map for texture unit 0
		glActiveTextureARB( GL_TEXTURE0_ARB );
		glEnable( GL_TEXTURE_2D );
		glBindTexture( GL_TEXTURE_2D, TexFoliage[Foliage[i].Texture] );
		glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
		
		// Apply the water caustics texture for texture unit 1
		glActiveTextureARB( GL_TEXTURE1_ARB );
		glEnable( GL_TEXTURE_2D );
		glBindTexture( GL_TEXTURE_2D, TexCaustics[c] );
		glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB );
		glTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2 );

		// Billboarding...
		glTranslatef( (Foliage[i].Position.get())[0], 0.0f, (Foliage[i].Position.get())[2] );
		glRotatef( -mouseState.theta, 0.0f, 1.0f, 0.0f );

#if 1
		if( wireframe )
		{
			glActiveTextureARB( GL_TEXTURE0_ARB );
			glDisable( GL_TEXTURE_2D );
			glActiveTextureARB( GL_TEXTURE1_ARB );
			glDisable( GL_TEXTURE_2D );
		}

		glBegin( GL_QUADS );
			glColor3f( 1.0f, 1.0f, 1.0f );
			glNormal3f( 0.0f, 0.0f, 1.0f );

			glMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0.0f, 0.0f );		
			glMultiTexCoord2fARB( GL_TEXTURE1_ARB, 0.0f, 0.0f );
			glVertex3f( -15.0f, -20.0f, 0.0f );

			glMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0.0f, 1.0f );
			glMultiTexCoord2fARB( GL_TEXTURE1_ARB, 0.0f, 5.0f );
			glVertex3f( -15.0f, (Foliage[i].Height+40.0f)-20.0f,  0.0f );

			glMultiTexCoord2fARB( GL_TEXTURE0_ARB, 1.0f, 1.0f );
			glMultiTexCoord2fARB( GL_TEXTURE1_ARB, 5.0f, 5.0f );
			glVertex3f(  15.0f, (Foliage[i].Height+40.0f)-20.0f,  0.0f );

			glMultiTexCoord2fARB( GL_TEXTURE0_ARB, 1.0f, 0.0f );	
			glMultiTexCoord2fARB( GL_TEXTURE1_ARB, 5.0f, 0.0f );
			glVertex3f(  15.0f, -20.0f, 0.0f );
		glEnd();
#else
		GLsizei offset = 0;

		// Attach the VBO
		glBindBufferARB( GL_ARRAY_BUFFER, VboFoliage );

		// Tell OpenGL what order each vertex attribute is in and how large it is
		glColorPointer( 3, GL_FLOAT, 0, BUFFER_OFFSET( offset ) );
		offset += sizeof( GLfloat ) * 12;
		glNormalPointer( /*3,*/ GL_FLOAT, 0, BUFFER_OFFSET( offset ) );
		offset += sizeof( GLfloat ) * 12;
		glVertexPointer( 3, GL_FLOAT, 0, BUFFER_OFFSET( offset ) );
		offset += sizeof( GLfloat ) * 12;
		glClientActiveTextureARB( GL_TEXTURE0_ARB );
		glTexCoordPointer( 2, GL_FLOAT, 0, BUFFER_OFFSET( offset ) );
		glEnableClientState( GL_TEXTURE_COORD_POINTER_ARRAY );
		offset += sizeof( GLfloat ) * 8;
		glClientActiveTextureARB( GL_TEXTURE1_ARB );
		glTexCoordPointer( 2, GL_FLOAT, 0, BUFFER_OFFSET( offset ) );
		glEnableClientState( GL_TEXTURE_COORD_POINTER_ARRAY );
		offset += sizeof( GLfloat ) * 8;

		// Render the VBO
		glDrawArrays( GL_QUADS, 0, 4 );
#endif

		glPopMatrix();
	}

	// Disable texture unit 1
	glActiveTextureARB( GL_TEXTURE1_ARB );
	glDisable( GL_TEXTURE_2D );
	glDisable( GL_TEXTURE1_ARB );
	glActiveTextureARB( GL_TEXTURE0_ARB );

	// Restore the render state attributes
	glPopAttrib();

	// Update the caustic texture every 100 milliseconds
	dwElapsedTime = GetTickCount() - dwStartTime;

	if( dwElapsedTime >= 25 )
	{
		dwStartTime = GetTickCount();
		dwElapsedTime = 0;

		if( ++c > 30 )
			c = 0;
	}

	glPopMatrix();
}

//-----------------------------------------------------------------------------
// Name: RenderHUD
// Desc: Renders 2D elements after 3D and post processing.
//-----------------------------------------------------------------------------
void RenderHUD( void )
{
	static GLfloat fLogoColour[] = { 1.0f, 1.0f, 1.0f, 0.5f };
	GLint iViewport[4];

	// Ensure that every 2D element is drawn relevant to the current viewport
	glGetIntegerv( GL_VIEWPORT, iViewport );

	// Show the OpenGL and NVIDIA GeForce 3 logo
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	glEnable( GL_TEXTURE_RECTANGLE_NV );
	glEnable2D();
	glBindTexture( GL_TEXTURE_RECTANGLE_NV, TexGLLogo );
	glDrawQuad( 20, iViewport[3] - 80, 128, 64, fLogoColour );
	glBindTexture( GL_TEXTURE_RECTANGLE_NV, TexNVLogo );
	glDrawQuad( iViewport[2] - 80, iViewport[3] - 80, 64, 64, fLogoColour );
	glDisable2D();
	glDisable( GL_TEXTURE_RECTANGLE_NV );
	glDisable( GL_BLEND );
}

//-----------------------------------------------------------------------------
// Name: DisplayFunction
// Desc: Our display function used to render our graphics in the main loop of
//		 our GLUT application.
//-----------------------------------------------------------------------------
void DisplayFunction( void )
{
	// Set camera position and direction
	vecPosition.set( 0, 75.0f, distance );
	vecLookAt.set( 0, 60.0f, 0 );

	// Clear the screen and depth buffer
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
	// Reset matrices to default position, scale, and rotation
	glLoadIdentity();

	// Position the camera
	gluLookAt( (vecPosition.get())[0], (vecPosition.get())[1], (vecPosition.get())[2],
			   (vecLookAt.get())[0], (vecLookAt.get())[1], (vecLookAt.get())[2], 0.0f, 1.0f, 0.0f );
	glRotatef( mouseState.phi, 1.0f, 0.0f, 0.0f );
	glRotatef( mouseState.theta, 0.0f, 1.0f, 0.0f );

	// Render the sea floor
	RenderSeaFloor();

	// Render the foliage
	RenderFoliage();

	// Render multiple jellyfish
	glTranslatef( -10.0f, 60.0f, 0.0f );
	pJellyFish->render();
	glTranslatef( 20.0f, 0.0f, 0.0f );
	pJellyFish->render();
	glTranslatef( -10.0f, 0.0f, -15.0f );
	pJellyFish->render();

	// Render particles
	particleManager->Render();
	particleManager->Update();

	// Render bubbles
	bubbleManager->Render();
	bubbleManager->Update();

	if( params.blur_passes )
		post_process_blur(params.blur_passes);

	// Render the HUD elements
	RenderHUD();

	// Display graphics to the user
	glutSwapBuffers();
}


//-----------------------------------------------------------------------------
// Name: IdleFunction
// Desc: The function that is executed whenever the program is idle
//-----------------------------------------------------------------------------
void IdleFunction( void )
{
	// Continue to display graphics even when messages aren't
	// being processed.
	glutPostRedisplay();
}


//-----------------------------------------------------------------------------
// Name: KeyboardFunction
// Desc: Handles keyboard input
//-----------------------------------------------------------------------------
void KeyboardFunction( GLubyte k, int x, int y )
{
	// Has escape been pressed?
	if( k == 27 || k == 'q' )
	{
		exit(0);
	}

	if( k == 'w' )
	{
		wireframe = !wireframe;
		if ( wireframe )
		  glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
		else
		  glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
	}

	if( k == 'z' )
		if ( distance > 30 )
		  distance--;

	if( k == 'x' )
		if ( distance < 75 )
			distance++;

	// If not, then continue rendering
	glutPostRedisplay();
}

//-----------------------------------------------------------------------------
// Name: ReshapeFunction
// Desc: Changes the viewport when the window is resized
//-----------------------------------------------------------------------------
void ReshapeFunction( GLsizei width, GLsizei height )
{
	// Prevent divide by zero exception
	if( !height ) height = 1;

	// Reset viewport
	glViewport( 0, 0, width, height );
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();

	// Reset perspective
	gluPerspective( fovy, (GLdouble) width/height, 0.1f, 10000.0f );
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();

	resize_post_process_textures( width, height );
}

//-----------------------------------------------------------------------------
// Name: ExitFunction
// Desc: Called when the program exits.  Do all uninitialization here.
//-----------------------------------------------------------------------------
void ExitFunction()
{
	// Uninitialize post processing effects
	uninit_post_process_effects();

	// Delete bubble manager
	if( bubbleManager )
	{
		delete bubbleManager;
		bubbleManager = NULL;
	}

	// Delete particle manager
	if( particleManager )
	{
		delete particleManager;
		particleManager = NULL;
	}
}


//-----------------------------------------------------------------------------
// Name: main
// Desc: Program entry point.
//-----------------------------------------------------------------------------
int main( int argc, char* argv[] )
{
	GLuint display_flags = GLUT_DOUBLE | GLUT_RGBA;

	// Open the params file so we can get our program settings :)
	if( argc > 1 )
	{
		if( !params.set( argv[1] ) )
		{
			printf( "ERROR: Error loading %s\n", argv[1] );
			exit(0);
		}
	}
	else
	{
		//printf( "Usage: jellyfish.exe [param file].txt\n" );
		//exit(0);

		if( !params.set( "params.txt" ) )
		{
			printf( "ERROR: Error loading params.txt!\n" );
			exit(0);
		}
	}

	// Enable fullscreen if desired
	if( params.fullscreen )
	{
		DEVMODE dm;

		ZeroMemory( &dm, sizeof( dm ) );
		dm.dmBitsPerPel = params.colour;
		dm.dmPelsWidth = params.width;
		dm.dmPelsHeight = params.height;
		dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
		dm.dmSize = sizeof( dm );

		if( ChangeDisplaySettings( &dm, CDS_FULLSCREEN ) != DISP_CHANGE_SUCCESSFUL )
			printf( "ERROR: Fullscreen mode not supported!\n\n" );
	}

	if( params.depth )
		display_flags |= GLUT_DEPTH;
	if( params.stencil )
		display_flags |= GLUT_STENCIL;
	if( params.fsaa )
		display_flags |= GLUT_MULTISAMPLE;

	// Initialize OpenGL via GLUT library
	glutInit( &argc, argv );
	glutInitWindowSize( params.width, params.height );
	glutInitDisplayMode( display_flags );
	glutCreateWindow( "Jellyfish" );

	if( params.fullscreen ) glutFullScreen();

	// Initialize scene objects and properties
	InitScene();
	PlaySound( "..\\audio\\underwater2.wav", NULL, SND_ASYNC | SND_LOOP );

	// Set our input/output functions and begin the main loop
	glutIdleFunc( IdleFunction );
	glutDisplayFunc( DisplayFunction );
	glutKeyboardFunc( KeyboardFunction );
	glutReshapeFunc( ReshapeFunction );
	glutMotionFunc( MotionFunction );
	glutMouseFunc( MouseFunction );
	atexit( ExitFunction );

	glutMainLoop();

	return 0;
}