////////////////////////////////////////////////////////////
//                                                        //
//  PC MAGAZIN - PC Underground                           //
//                                                        //
//  Reflection/Refraction/Fresnel Demo                    //
//                                                        //
//  (w)(c)2002 Carsten Dachsbacher                        //
//                                                        //
////////////////////////////////////////////////////////////
//  weitere Informationen gibts im Artikel des PC MAGAZIN
//

#include	<windows.h>			
#include	<math.h>
#include	<GL\gl.h>			
#include	<GL\glu.h>			
#include	<GL\glut.h>			
#include	<GL\glaux.h>			
#include	"GLvertexprogram.h"
#include	"texture.h"
#include	"3dobject.h"

PCUTexture  *keyTexture;

C3DObject   *renderObject;
C3DObject   *object[ 4 ];

int			renderMode = 0;
float		fresnelConstant = 2.0f;
float		refractConstant = 1.1f;
float		refractAdd = 0.02f;

int			surfaceType;
GLint		surfaceCombiner[ 4 ];

// wir verwenden nvparse !
// ACHTUNG: die mitgelieferte nvparse Bibliothek beinhaltet
// einen Bugfix (aber nur für PS1.0 Programme notwendig, der in
// der offiziellen Version von nVidia noch nicht enthalten ist
#define GLH_EXT_SINGLE_FILE
#include	"glh_extensions.h"
#include	"openglhelpers.h"
#include	"nvparse.h"

unsigned int vpFresnelCubemap;

MATRIX44 cameraMatrix, cameraMatrixInverse;

const	unsigned char vpFresnelCubemapTxt [] = 
	"!!VP1.0										\n"
	"DP4 o[HPOS].x, c[0], v[OPOS];					\n"
	"DP4 o[HPOS].y, c[1], v[OPOS];					\n"
	"DP4 o[HPOS].z, c[2], v[OPOS];					\n"
	"DP4 o[HPOS].w, c[3], v[OPOS];					\n"

	"DP3 R5.x, c[4], v[NRML];						\n"
	"DP3 R5.y, c[5], v[NRML];						\n"
	"DP3 R5.z, c[6], v[NRML];						\n"

	"DP4 R0.x, c[8], v[OPOS];						\n"
	"DP4 R0.y, c[9], v[OPOS];						\n"
	"DP4 R0.z, c[10], v[OPOS];						\n"
	"DP4 R0.w, c[11], v[OPOS];						\n"

	"#R0 = c[20] - R0    \n"
	"ADD	R0, -R0, c[20];							\n"

	"DP3	R8.w, R0, R0; # R8.w=Länge²				\n"
	"RSQ	R8.w, R8.w;   # R8.w=1.0/sqrt(R8.w)		\n"
	"MUL	R8, R0, R8.w; # R8 = V					\n"

	"# R0 = NdotI									\n"
	"DP3 R0.x, R5, -R8;								\n"

	"# R1.x = 1 - NdotI*NdotI						\n"
	"MAD R1.x, -R0.x, R0.x, c[23].y;				\n"

	"# R1.x = eta² * ( 1 - NdotI*NdotI )			\n"
	"MUL R1.x, R1.x, c[22].y;						\n"

	"# R1.x = 1 - eta² * ( 1 - NdotI*NdotI )		\n"
	"ADD R1.x, c[23].y, -R1.x;						\n"

	"# R2.x = sqrt( R1.x )							\n"
	"RSQ R2.x, R1.x; # 1.0 / sqrt( R1.x )			\n"
	"RCP R2.x, R2.x; # sqrt( R1.x )					\n"

	"# R2.x = eta * NdotI + sqrt( R1.x )			\n"
	"MAD R2.x, c[22].x, R0.x, R2.x;					\n"

	"# R2 = N * R2.x								\n"
	"MUL R2, R5, R2.x;								\n"

	"# R2 = eta * I + R2							\n"
	"MAD R2, c[22].x, -R8, R2;						\n"

	"# R0 = 2N										\n"
	"MUL R0, R5, c[23].z;							\n"

	"# R3 = 2N * NdotI + V							\n"
	"DP3 R4.w, R5, R8;								\n"
	"MAD R3, R4.w, R0, -R8;							\n"

	"DP3 o[TEX0].x, c[12], R2;						\n"
	"DP3 o[TEX0].y, c[13], R2;						\n"
	"DP3 o[TEX0].z, c[14], R2;						\n"

	"DP3 o[TEX1].x, c[12], R3;						\n"
	"DP3 o[TEX1].y, c[13], R3;						\n"
	"DP3 o[TEX1].z, c[14], R3;						\n"

	"ADD R4.w, c[23].y,-R4.w; # 1 - VdotN			\n"
	"MUL R4.w, R4.w, R4.w;    # ()²					\n"
	"MUL o[COL0],R4.w, c[21]; # k*(1-VdotN)²		\n"
	"END											\n";

GLuint cubeMapConstants[ 6 ] = 
{
	GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB,
	GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB,
	GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB,
	GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB,
	GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB,
	GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB
};

GLuint cubeMap;

GLuint createCubeMap()
{
	GLuint texture;

	glEnable( GL_TEXTURE_CUBE_MAP_ARB );

	glGenTextures( 1, &texture );

	glBindTexture( GL_TEXTURE_CUBE_MAP_ARB, texture );

#ifdef MIPMAP_CUBE
	if ( automipmap )
	{
		glTexParameteri( GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
		glTexParameteri( GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		glTexParameteri( GL_TEXTURE_CUBE_MAP_ARB, GL_GENERATE_MIPMAP_SGIS, GL_TRUE );
	} else
	{
		glTexParameteri( GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		glTexParameteri( GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	}
#else
	glTexParameteri( GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
#endif

	const char cubeMapTextures[6][64] = 
	{ 
		"./data/skybox3.bmp",
		"./data/skybox1.bmp",
		"./data/skybox_bottom.bmp",
		"./data/skybox_up.bmp",
		"./data/skybox2.bmp", 
		"./data/skybox4.bmp",
	};

	for ( int i = 0; i < 6; i++ )
	{
		AUX_RGBImageRec *texture;

		texture = auxDIBImageLoad( cubeMapTextures[ i ] );

		if ( !texture )
			return false;

		glTexImage2D(
			cubeMapConstants[ i ], 0, GL_RGB8, 
			texture->sizeX, texture->sizeY,
			0, GL_RGB, GL_UNSIGNED_BYTE, texture->data );
	}

	return texture;
}

void drawSkyBox(void)
{
    glActiveTextureARB( GL_TEXTURE1_ARB );
	glDisable( GL_TEXTURE_CUBE_MAP_ARB );
  
    glActiveTextureARB( GL_TEXTURE0_ARB );
	glBindTexture( GL_TEXTURE_CUBE_MAP_ARB, cubeMap );
	glEnable( GL_TEXTURE_CUBE_MAP_ARB );

    glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT );
	glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT );
	glTexGeni( GL_R, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT );

	glEnable( GL_TEXTURE_GEN_S );
	glEnable( GL_TEXTURE_GEN_T );
	glEnable( GL_TEXTURE_GEN_R );

	glMatrixMode( GL_TEXTURE );
	glPushMatrix();
    glLoadIdentity();
    glScalef( 1.0, -1.0, 1.0 );
	glMultMatrixf( cameraMatrixInverse );

	glutSolidSphere( 8.0f, 4, 4 );

	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);

	glDisable( GL_TEXTURE_GEN_S );
	glDisable( GL_TEXTURE_GEN_T );
	glDisable( GL_TEXTURE_GEN_R );
}

void	init3DEngine()
{
	if ( !getVertexProgramExtensions() )
	{
		MessageBox( NULL, "Es konnten nicht alle Funktionsadressen für Vertex Programs geholt werden !", "Schade !", MB_OK );
		exit( 1 );
	}

    glShadeModel( GL_SMOOTH );

	glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );

	glDisable( GL_POLYGON_SMOOTH );

	glEnable( GL_DEPTH_TEST );
	glDisable( GL_CULL_FACE );

	glDepthFunc( GL_LEQUAL );

	// vertex programs
	glGenProgramsNV( 1, &vpFresnelCubemap );

	glEnable( GL_VERTEX_PROGRAM_NV );
	glBindProgramNV( GL_VERTEX_PROGRAM_NV, vpFresnelCubemap );

	glLoadProgramNV( GL_VERTEX_PROGRAM_NV, vpFresnelCubemap, strlen( (char*)vpFresnelCubemapTxt ), vpFresnelCubemapTxt );

	glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 0,  GL_MODELVIEW_PROJECTION_NV, GL_IDENTITY_NV);
	glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 4,  GL_MODELVIEW,               GL_INVERSE_TRANSPOSE_NV);
	glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 8,  GL_MODELVIEW,               GL_IDENTITY_NV);
	glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 12, GL_TEXTURE,                 GL_IDENTITY_NV);

	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 20, 0.0f, 0.0f, 0.0f, 1.0f );
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 23, 0.0f, 1.0f, 2.0f, 3.0f );

	
	// Combiner Setting in Display Lists speichern => schneller beim Umschalten

	// glass
	surfaceCombiner[ 0 ] = glGenLists( 1 );
	glNewList( surfaceCombiner[ 0 ], GL_COMPILE );
		nvparse(
			"!!RC1.0									\n"
			"{											\n"
			"  rgb										\n"
			"  {										\n"
			"    discard = tex0*unsigned_invert(col0);  \n"
			"    spare0 = tex1*col0;					\n"
			"    spare1 = sum();						\n"
			"  }										\n"
			"}											\n"
			"out.rgb = spare1;							\n"
			);
	glEndList();


	// halbtransparent
	surfaceCombiner[ 1 ] = glGenLists( 1 );
	glNewList( surfaceCombiner[ 1 ], GL_COMPILE );
		nvparse(
			"!!RC1.0									\n"
			"const0 = ( 0.8, 0.7, 0.4, 1.0 );			\n"
			"{											\n"
			"  rgb										\n"
			"  {										\n"
			"    discard = const0;						\n"
			"    spare0 = tex0;							\n"
			"    spare1 = sum();						\n"
			"    scale_by_one_half();					\n"
			"  }										\n"
			"}											\n"
			"{											\n"
			"  rgb										\n"
			"  {										\n"
			"    discard = spare1*unsigned_invert(col0);\n"
			"    spare0 = tex1*col0;					\n"
			"    spare1 = sum();						\n"
			"  }										\n"
			"}											\n"
			"out.rgb = spare1;							\n"
			);
	glEndList();

	// plastik
	surfaceCombiner[ 2 ] = glGenLists( 1 );
	glNewList( surfaceCombiner[ 2 ], GL_COMPILE );
		nvparse(
			"!!RC1.0									\n"
			"const0 = ( 1.0, 0.6, 0.3, 1.0 );			\n"
			"{											\n"
			"  rgb										\n"
			"  {										\n"
			"    discard = const0*unsigned_invert(col0);  \n"
			"    spare0 = tex1*col0;					\n"
			"    spare1 = sum();						\n"
			"  }										\n"
			"}											\n"
			"out.rgb = spare1;							\n"
			);
	glEndList();

	// metall
	surfaceCombiner[ 3 ] = glGenLists( 1 );
	glNewList( surfaceCombiner[ 3 ], GL_COMPILE );
		nvparse(
			"!!RC1.0									\n"
			"out.rgb = tex1;							\n"
			);
	glEndList();

	surfaceType = 0;
	glCallList( surfaceCombiner[ surfaceType ] );

	// 3D Objekte laden
	object[ 0 ] = new C3DObject( "./data/shape.off", 0.32f );
	object[ 1 ] = new C3DObject( "./data/tric.off", 0.12f );
	object[ 2 ] = new C3DObject( "./data/cow.off", 0.32f );
	object[ 3 ] = new C3DObject( "./data/horse.off", 0.01f );

	renderObject = object[ 0 ];

	// Textures
	cubeMap = createCubeMap();

	keyTexture = new PCUTexture();
	keyTexture->loadBMP( "./data/keys.bmp" );
}

void setRefraction( float eta )
{
	glProgramParameter4fNV( GL_VERTEX_PROGRAM_NV, 22, eta, eta*eta, 0.0f, 0.0f );
}

void setFresnel( float fresnel )
{
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 21, fresnel, fresnel, fresnel, 1.0f );
}

void	draw3DEngine()
{
	// Tastatur Abfrage

	extern bool keys[ 256 ];

	if ( keys[ '1' ] )
	{
		renderObject = object[ 0 ];
		keys[ '1' ] = 0;
	} else
	if ( keys[ '2' ] )
	{
		renderObject = object[ 1 ];
		keys[ '2' ] = 0;
	}  else
	if ( keys[ '3' ] )
	{
		renderObject = object[ 2 ];
		keys[ '3' ] = 0;
	}  else
	if ( keys[ '4' ] )
	{
		renderObject = object[ 3 ];
		keys[ '4' ] = 0;
	}

	if ( keys[ '5' ] )
	{
		surfaceType = 0;
		keys[ '5' ] = 0;
	} else
	if ( keys[ '6' ] )
	{
		surfaceType = 1;
		keys[ '6' ] = 0;
	} else
	if ( keys[ '7' ] )
	{
		surfaceType = 2;
		keys[ '7' ] = 0;
	} else
	if ( keys[ '8' ] )
	{
		surfaceType = 3;
		keys[ '8' ] = 0;
	}

	if ( keys[ 'm' ] || keys[ 'M' ] )
	{
		renderMode = 1 - renderMode;
		keys[ 'm' ] = keys[ 'M' ] = 0;
	}


	float x = GetTickCount() * 0.005643f;
	float y = GetTickCount() * 0.006346f;
	float z = GetTickCount() * 0.007346f;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// OpenGl Zeug
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();

	extern int windowX, windowY;
    gluPerspective ( 75, (float)windowX / (float)windowY, 1, 5000 );

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// Kamera Transformation
	//glRotatef( (float)sin( y * 0.1f ) * 10.0f, 0.0f, 1.0f, 0.0f );

	// Kamera Matrix holen und invertieren
	glGetFloatv( GL_MODELVIEW_MATRIX, cameraMatrix );

	InverseMatrixAnglePreserving( cameraMatrix, cameraMatrixInverse );

	// Skybox zeichnen
	glDisable( GL_VERTEX_PROGRAM_NV );
	glDisable( GL_REGISTER_COMBINERS_NV );
	glDisable(GL_DEPTH_TEST);

	drawSkyBox();
    
	glEnable(GL_DEPTH_TEST);
	glEnable( GL_VERTEX_PROGRAM_NV );
	glEnable( GL_REGISTER_COMBINERS_NV );

	// Objekt Transformation
	glTranslatef( 0.0f, 0.0f, -2.0f );

	glRotatef( x, 1.0f, 0.0f, 0.0f );
	glRotatef( y, 0.0f, 1.0f, 0.0f );
	glRotatef( z, 0.0f, 0.0f, 1.0f );

	// Cubemapping für beide Texture Stages anschalten
	// und inverse Kameramatrix laden
    glActiveTextureARB(GL_TEXTURE0_ARB);
	glBindTexture( GL_TEXTURE_CUBE_MAP_ARB, cubeMap );
	glEnable( GL_TEXTURE_CUBE_MAP_ARB );

	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glScalef(1.0, -1.0, 1.0);
	glMultMatrixf( cameraMatrixInverse );

    glActiveTextureARB(GL_TEXTURE1_ARB);
	glBindTexture( GL_TEXTURE_CUBE_MAP_ARB, cubeMap );
	glEnable( GL_TEXTURE_CUBE_MAP_ARB );

	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glScalef(1.0, -1.0, 1.0);
	glMultMatrixf( cameraMatrixInverse );

	// und zeichnen !
	setFresnel( fresnelConstant );

	// Combiner wählen
	glCallList( surfaceCombiner[ surfaceType ] );

	if ( renderMode == 0 )
	{
		setRefraction( refractConstant );

		renderObject->drawObject();
	} else
	{
		// rot
 		glColorMask( GL_TRUE, GL_FALSE, GL_FALSE, GL_FALSE );
		setRefraction( refractConstant );
		renderObject->drawObject();

		// grün
		glColorMask( GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE );
		setRefraction( refractConstant + refractAdd );
		renderObject->drawObject();

		// blau
		glColorMask( GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE );
		setRefraction( refractConstant + 2.0f * refractAdd );
		renderObject->drawObject();

		glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE );
	}

	// und Tastaturbitmap zeichnen
    glActiveTextureARB(GL_TEXTURE0_ARB);
	glDisable( GL_TEXTURE_CUBE_MAP_ARB );
	glEnable( GL_TEXTURE_2D );
	keyTexture->select();

	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();

    glActiveTextureARB(GL_TEXTURE1_ARB);
	glDisable( GL_TEXTURE_CUBE_MAP_ARB );
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();

	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();

	glTranslatef( -1.0f, -1.0f, 0.0f );
	glScalef( 1.0f / 256.0f, 1.0f / 256.0f, 1.0f );

	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();

	glDisable( GL_DEPTH_TEST );

	#define RENDERQUAD() \
			glBegin( GL_TRIANGLE_STRIP );\
				glTexCoord2f( 0, 0 );\
				glVertex3f( 512-192, 0.0f, 0.0f );\
				glTexCoord2f( 1, 0 );\
				glVertex3f( 512, 0.0f, 0.0f );\
				glTexCoord2f( 0, 1 );\
				glVertex3f( 512-192, 192.0f, 0.0f );\
				glTexCoord2f( 1, 1 );\
				glVertex3f( 512, 192.0f, 0.0f ); \
			glEnd();

	glDisable( GL_VERTEX_PROGRAM_NV );
	glDisable( GL_REGISTER_COMBINERS_NV );
	glDisable( GL_DEPTH_TEST );

	glEnable( GL_BLEND );
	glBlendFunc( GL_ONE, GL_ONE );

	RENDERQUAD();

	glDisable( GL_BLEND );
	glEnable(GL_DEPTH_TEST);
	glEnable( GL_VERTEX_PROGRAM_NV );
	glEnable( GL_REGISTER_COMBINERS_NV );


}

void	quit3DEngine()
{
}
