//***********************************************************//
// Demo:    CubeMap Demo
// Author:  terror
//***********************************************************//
#include <stdio.h>
#include <gl/glut.h>
#include <gl/glext.h>
#include "plane.h"
#include "utils.h"
#include "vector.h"
#include "texture.h"

#pragma comment ( lib, "glut32.lib" )
#pragma comment ( lib, "jpeg.lib"   )


struct LightMapTexels
{
	byte texels[LIGHTMAP_SIZE*LIGHTMAP_SIZE*3];
	int size;
};

struct Object
{
	vector    *verts;
	vector2   *tverts;
	vector    *LMtverts;
	uint      *LMTextureID;
	LightMapTexels *LMTexels;
	int        VertsCount;

	void init()
	{
		verts  = NULL;
		tverts  = NULL;
		LMtverts = NULL;
		LMTexels  = NULL;
		VertsCount = NULL;
		LMTextureID = NULL;
	}
};

ImageJPG_t jpg[6];

Object Obj[4];
float rotz,rotx=-60.0f;
uint tex[4]={0,0,0,0};
vector lpos ( 0.0f, 0.0f, 15.0f ); // коорд. сферы
bool LeftButton = false;
int oldX,oldY;
int ViewWidth  = 800;
int ViewHeight = 600;


PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL;
PFNGLMULTITEXCOORD2FVARBPROC glMultiTexCoord2fvARB = NULL;
typedef void (APIENTRY * PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture);
extern PFNGLCLIENTACTIVETEXTUREARBPROC  glClientActiveTextureARB;
PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = NULL;
void reshape(int,int);


#define OBJECTS_COUNT     4         // количество объектов на сцене
#define CUBEMAP_SIZE    256         // размеры текстуры CubeMap
#define CUBEMAP_OFFSET   60         // сдвиг при рендере в текстуру
uint texCubeMap = 0;                // id текстуры CubeMap
int CubeMapTarget[6];               // параметры CubeMap


void transpose ( const float *m1, float *m2 )
{
	m2[0] = m1[0];  m2[4] = m1[1];  m2[8]  = m1[2];  m2[12] = m1[3];
	m2[1] = m1[4];  m2[5] = m1[5];  m2[9]  = m1[6];  m2[13] = m1[7];
	m2[2] = m1[8];  m2[6] = m1[9];  m2[10] = m1[10]; m2[14] = m1[11];
	m2[3] = m1[12]; m2[7] = m1[13]; m2[11] = m1[14]; m2[15] = m1[15];
}


bool LoadMap ( char *filename )
{
	FILE *file = NULL;
	int Data = 0;
	int i = 0, j = 0, k = 0, NumVerts = 0;
	int ObjectsCount = 0;
	char  SIGNATURE[4];
	file = fopen ( filename, "rb" );
	if ( file == NULL )
	{
		ShowFileError ( filename );
		return false;
	}
	fread ( SIGNATURE, sizeof (char), 4, file );
	if ( strcmp ( SIGNATURE, "W2B" ))
	{ MessageBox ( 0, "Bad file", "Error", MB_ICONERROR|MB_OK ); return false; }
	fread ( &ObjectsCount, sizeof(int), 1, file );
	for ( i = 0; i < ObjectsCount; i++ )
	{
		Obj[i].init();
		fread ( &Obj[i].VertsCount, sizeof(int), 1, file);

		NEW_ARRAY_MEM ( Obj[i].verts,    vector,  Obj[i].VertsCount );
		NEW_ARRAY_MEM ( Obj[i].tverts,   vector2, Obj[i].VertsCount );
		NEW_ARRAY_MEM ( Obj[i].LMtverts, vector,  Obj[i].VertsCount );
		NEW_ARRAY_MEM ( Obj[i].LMTextureID, uint, Obj[i].VertsCount/3 );
		NEW_ARRAY_MEM ( Obj[i].LMTexels,     LightMapTexels, Obj[i].VertsCount/3 );
	}
	for ( i = 0; i < ObjectsCount; i++ )
	{
		fread ( Obj[i].verts,  sizeof(vector),  Obj[i].VertsCount, file );
		fread ( Obj[i].tverts, sizeof(vector2), Obj[i].VertsCount, file );
	}
	fclose ( file );
	return true;
}

bool LoadLM ( char *filename )
{
	FILE *File = fopen ( filename, "rb" );
	vector lm[3];
	if ( File == NULL )
	{
		ShowFileError ( filename );
		return false;
	}
	int j,i1=0;

	// мы знаем, что объектов всего четыре
	for ( int z = 0; z < 4; z++ )
	{
		i1=0;
		fread ( Obj[z].LMTexels, sizeof(LightMapTexels), Obj[z].VertsCount/3, File );
		for ( j = 0; j < Obj[z].VertsCount/3; j++, i1+=3 )
		{
			fread ( lm, sizeof(vector), 3, File);
			Obj[z].LMtverts[i1]   = lm[0];
			Obj[z].LMtverts[i1+1] = lm[1];
			Obj[z].LMtverts[i1+2] = lm[2];
		}
		for ( j = 0; j < Obj[z].VertsCount/3; j++ )
		{
			UploadMemImage ( Obj[z].LMTexels[j].texels, LIGHTMAP_SIZE, LIGHTMAP_SIZE,
				Obj[z].LMTextureID[j] );
		}
	}

	fclose ( File );
	return true;
}


//
// Настройка opengl
//
void Init()
{
	glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );
	glColor4f    ( 1.0f, 1.0f, 1.0f, 1.0f );
	glHint       ( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
	glEnable     ( GL_CULL_FACE );
	glShadeModel ( GL_FLAT );
	glCullFace   ( GL_BACK );
	glEnable     ( GL_DEPTH_TEST );
	glActiveTextureARB       = (PFNGLACTIVETEXTUREARBPROC)    wglGetProcAddress ( "glActiveTextureARB" );
	glMultiTexCoord2fvARB    = (PFNGLMULTITEXCOORD2FVARBPROC) wglGetProcAddress ( "glMultiTexCoord2fvARB" );
	glClientActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)    wglGetProcAddress ( "glClientActiveTextureARB");


	if ( !LoadMap ( "Data\\map.w2b" )) exit(0);
	if ( !LoadLM  ( "Data\\map.wlm" )) exit(0);
	if ( !UploadImage ( "Data\\tex1.jpg", tex[0] )) exit(0);
	if ( !UploadImage ( "Data\\tex2.jpg", tex[1] )) exit(0);
	if ( !UploadImage ( "Data\\tex3.jpg", tex[2] )) exit(0);
	if ( !UploadImage ( "Data\\tex4.jpg", tex[3] )) exit(0);

	CubeMapTarget[0] = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB;
	CubeMapTarget[1] = GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB;
	CubeMapTarget[2] = GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB;
	CubeMapTarget[3] = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB;
	CubeMapTarget[4] = GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB;
	CubeMapTarget[5] = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB;

	glEnable        ( GL_TEXTURE_CUBE_MAP_ARB );
	glGenTextures   ( 1, &texCubeMap );
	glPixelStorei		( GL_UNPACK_ALIGNMENT, 1 );
	glBindTexture   ( GL_TEXTURE_CUBE_MAP_ARB, texCubeMap );
	glTexEnvi				( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
	glTexParameteri ( GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_REPEAT );
	glTexParameteri ( GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_REPEAT );
	glTexParameteri ( GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, GL_REPEAT );

	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 );
	for ( int i = 0; i < 6; i++ )
	{
		glTexImage2D  ( CubeMapTarget[i], 0, GL_RGB, CUBEMAP_SIZE, CUBEMAP_SIZE, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
	}

	glDisable ( GL_TEXTURE_CUBE_MAP_ARB );

	glEnable( GL_LIGHTING);
	glEnable( GL_LIGHT0);

	GLfloat pos[4] = {10,0,0, -1};
	glLightfv(GL_LIGHT0, GL_POSITION, pos);
	GLfloat pos2[3] = {100,10,10};
	glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, pos2);
}



void RenderObjects()
{
	// рисуем все объекты
	glColor3f ( 1.0f, 1.0f, 1.0f );
	for ( int i = 0; i < 4; i++ )
	{
		glEnableClientState ( GL_VERTEX_ARRAY );
		glVertexPointer ( 3, GL_FLOAT, sizeof(vector),     Obj[i].verts );
		glClientActiveTextureARB(GL_TEXTURE0_ARB);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(2, GL_FLOAT, sizeof(vector2),    Obj[i].tverts );
		glClientActiveTextureARB(GL_TEXTURE1_ARB);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(2, GL_FLOAT, sizeof(vector),     Obj[i].LMtverts );
		glActiveTextureARB ( GL_TEXTURE0_ARB );
		glEnable ( GL_TEXTURE_2D );
		glBindTexture ( GL_TEXTURE_2D, tex[i] );
		glActiveTextureARB(GL_TEXTURE1_ARB);
		glEnable(GL_TEXTURE_2D);
			for ( int j = 0; j < Obj[i].VertsCount/3; j++ )
			{
				glBindTexture ( GL_TEXTURE_2D, Obj[i].LMTextureID[j] );
				glDrawArrays  ( GL_TRIANGLES, j*3, 3 );
			}
		glDisable ( GL_TEXTURE_2D );
		glActiveTextureARB ( GL_TEXTURE1_ARB );
		glDisable ( GL_TEXTURE_2D );
		glActiveTextureARB ( GL_TEXTURE0_ARB );
		glDisableClientState(GL_VERTEX_ARRAY);
		glClientActiveTextureARB ( GL_TEXTURE1_ARB );
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glClientActiveTextureARB ( GL_TEXTURE0_ARB );
	}


	// белая сфера
	glDisable ( GL_TEXTURE_2D );
	glPushMatrix();
		static float ttt=0, vm[3];
		vm[0] = lpos.v[0]+20.0f*cos(ttt+=0.01f);
		vm[1] = lpos.v[1]+20.0f*sin(ttt);
		vm[2] = lpos.v[2]+5.0f*sin(ttt*3.1234);
		glTranslatef ( vm[0], vm[1], vm[2] );
		glColor4f(0.3,0.6,0.6,1);
		glutSolidSphere ( 3.0f, 64.0f, 64.0f );
		glColor4f(1,1,1,1);
	glPopMatrix();
}


//
// Создаем CubeMap
//
void CreateCubemap()
{
	glDisable(GL_LIGHTING);
	glViewport   ( CUBEMAP_OFFSET, CUBEMAP_OFFSET, CUBEMAP_SIZE, CUBEMAP_SIZE );
	glMatrixMode ( GL_PROJECTION );
	glLoadIdentity();
	gluPerspective ( 90.0, 1.0, 4.0, 1000.0 );
	glMatrixMode ( GL_MODELVIEW );
	glEnable ( GL_TEXTURE_2D );

	for ( int i = 0; i < 6; i++ )
	{
		glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
		glLoadIdentity();
		switch(i)
		{
			case 0: glRotatef (  90.0f, 0.0f, 1.0f, 0.0f );
	            glRotatef ( 180.0f, 1.0f, 0.0f, 0.0f ); break;
			case 1: glRotatef ( -90.0f, 0.0f, 1.0f, 0.0f );
				      glRotatef ( 180.0f, 1.0f, 0.0f, 0.0f ); break;
			case 2: glRotatef ( -90.0f, 1.0f, 0.0f, 0.0f ); break;
			case 3: glRotatef (  90.0f, 1.0f, 0.0f, 0.0f ); break;
			case 4: glRotatef ( 180.0f, 1.0f, 0.0f, 0.0f ); break;
			case 5: glRotatef ( 180.0f, 0.0f, 0.0f, 1.0f ); break;
		}
		glTranslatef ( -lpos.v[0], -lpos.v[1], -lpos.v[2] );
		RenderObjects();
		glEnable      ( GL_TEXTURE_CUBE_MAP_ARB );
		glBindTexture ( GL_TEXTURE_CUBE_MAP_ARB, texCubeMap );
		glCopyTexSubImage2D ( CubeMapTarget[i], 0, 0, 0,
			CUBEMAP_OFFSET, CUBEMAP_OFFSET, CUBEMAP_SIZE, CUBEMAP_SIZE );
		glDisable ( GL_TEXTURE_CUBE_MAP_ARB );
	}

	glDisable ( GL_TEXTURE_2D );
	reshape   ( ViewWidth, ViewHeight );
	glEnable(GL_LIGHTING);
}


void RenderSphere()
{
	float m1[16],m2[16];
	glGetFloatv ( GL_MODELVIEW_MATRIX, m1 );
	transpose   ( m1, m2 );

	glEnable  ( GL_TEXTURE_CUBE_MAP_ARB );
	glTexGeni ( GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB );
	glTexGeni ( GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB );
	glTexGeni ( GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB );
	glEnable  ( GL_TEXTURE_GEN_S );
	glEnable  ( GL_TEXTURE_GEN_T );
	glEnable  ( GL_TEXTURE_GEN_R );
	glBindTexture ( GL_TEXTURE_CUBE_MAP_ARB, texCubeMap );
	glPushMatrix();
		glTranslatef ( lpos.v[0], lpos.v[1], lpos.v[2] );
		glMatrixMode ( GL_TEXTURE );
		glPushMatrix();
			glLoadMatrixf ( m2 );
			glutSolidSphere ( 15.0f, 64.0f, 64.0f );
		glPopMatrix();
		glMatrixMode ( GL_MODELVIEW );
	glPopMatrix();
	glDisable ( GL_TEXTURE_GEN_S );
	glDisable ( GL_TEXTURE_GEN_T );
	glDisable ( GL_TEXTURE_GEN_R );
	glDisable ( GL_TEXTURE_CUBE_MAP_ARB );
}


void redraw()
{
	CreateCubemap();

	glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glLoadIdentity();
	glTranslatef ( 0.0f, 0.0f, -80.0f );
	glRotatef ( rotx,  1.0f, 0.0f, 0.0f );
	glRotatef ( 0.0f,  0.0f, 1.0f, 0.0f );
	glRotatef ( rotz,  0.0f, 0.0f, 1.0f );

	RenderSphere();
	RenderObjects();
	glutSwapBuffers();
}


void motion(int x, int y)
{
	if ( !LeftButton ) return;
	rotz -= ((oldX-x)*180.0f)/200.0f;
	if ( rotz > 360.0f || rotz < -360.0f ) rotz = 0.0f;
	rotx -= ((oldY-y)*180.0f)/200.0f;
	if ( rotx > 360.0f || rotx < -360.0f ) rotx = 0.0f;
	oldX = x; oldY = y;
}

void mouse(int button, int state, int x, int y)
{
	if ( state == GLUT_DOWN ){
		if ( button == GLUT_LEFT_BUTTON )
		{ LeftButton = true; oldX = x; oldY = y; }}
	if ( state == GLUT_UP )
		if ( button == GLUT_LEFT_BUTTON )
			LeftButton = false;
}

void reshape(int width, int height)
{
	glViewport ( 0, 0, width, height );
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective ( 60.0, (double)width/(double)height, 1.0, 1000.0 );
	glMatrixMode (GL_MODELVIEW);
	glLoadIdentity();
	ViewWidth  = width; ViewHeight = height;
}

void die()
{
	for ( int i = 0; i < 4; i++ )
	{
		DELETE_ARRAY_MEM ( Obj[i].verts  );
		DELETE_ARRAY_MEM ( Obj[i].tverts );
		DELETE_ARRAY_MEM ( Obj[i].LMTextureID );
		DELETE_ARRAY_MEM ( Obj[i].LMtverts );
		DELETE_ARRAY_MEM ( Obj[i].LMTexels );
	}

	DeleteTextures ( 4, tex );
	DeleteTexture  ( texCubeMap );
}


int main ( int argc, char *argv[] )
{
	atexit(die);
	glutInit(&argc, argv);
	glutInitWindowSize(ViewWidth,ViewHeight);
	glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH|GLUT_DOUBLE);
	glutCreateWindow("CubeMap Demo");
	glutIdleFunc(redraw);
	glutDisplayFunc(redraw);
	glutMotionFunc(motion);
	glutMouseFunc(mouse);
	glutReshapeFunc(reshape);
	Init();
	glutMainLoop();

	return 0;
}
