//***********************************************************//
// Demo:    VertexLight 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;         // текстурные коорд. lightmap'а
	uint      *LMTextureID;      // id текстуры lightmap'а
	LightMapTexels *LMTexels;    // пиксели lightmap'а
	vector3ub *tVertexLight;     // цвет вершин
	int        VertsCount;

	void init()
	{
		verts  = NULL;
		tverts  = NULL;
		LMtverts = NULL;
		LMTexels  = NULL;
		VertsCount = NULL;
		LMTextureID = NULL;
		tVertexLight = NULL;
	}
};


Object Obj[2];
float rotz,rotx=-90.0f; // углы поворота камеры
uint tex[2]={0,0};
vector lpos ( 3.0f, 38.0f, 15.0f ); // коорд. источника света
float  lrad = 500.0f;               // радиус источника света
bool LeftButton = false;
int oldX,oldY;

PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL;
PFNGLMULTITEXCOORD2FVARBPROC glMultiTexCoord2fvARB = NULL;
typedef void (APIENTRY * PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture);
extern PFNGLCLIENTACTIVETEXTUREARBPROC  glClientActiveTextureARB;
PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = NULL;


//
// Загрузка карты
//
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].tVertexLight, vector3ub, Obj[i].VertsCount );
		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;
	fread ( Obj[0].LMTexels, sizeof(LightMapTexels), Obj[0].VertsCount/3, File );
	for ( j = 0; j < Obj[0].VertsCount/3; j++, i1+=3 )
	{
		fread ( lm, sizeof(vector), 3, File);
		Obj[0].LMtverts[i1]   = lm[0];
		Obj[0].LMtverts[i1+1] = lm[1];
		Obj[0].LMtverts[i1+2] = lm[2];
	}
	for ( j = 0; j < Obj[0].VertsCount/3; j++ )
	{
		UploadMemImage ( Obj[0].LMTexels[j].texels, LIGHTMAP_SIZE, LIGHTMAP_SIZE,
			Obj[0].LMTextureID[j] );
	}
	fclose ( File );
	return true;
}


//
// Проверка пересечения линии и треугольников сцены.
//
bool RayTrace ( vector p1, vector p2 )
{
	vector dir, cp, fcp;
	float lineDist = p1.GetDistance ( p2 );
	float mintparm=9e9f,tparm;
	dir.FindDirection ( p1, p2 );
	triangle_t t1;
	for ( int i = 0; i < 2; i++ )
		for ( int j = 0; j < Obj[i].VertsCount/3; j+=3 )
		{
			t1.Set ( Obj[i].verts[j], Obj[i].verts[j+1], Obj[i].verts[j+2] );
			if ( t1.Intersect ( p1, dir, cp, tparm, 3.4e+38F ))
				if (tparm<mintparm)
				{ mintparm=tparm; fcp = cp; }
		}
	if ( p1.GetDistance ( fcp ) <= lineDist-6.0f )
		return true;
	return false;
}


//
// Находим цвет в каждой вершине второго объекта
//
void InitVertexLight ( void )
{
	int i = 0;
	vector c;
	float  a,d;
	for ( int j = 0; j < Obj[1].VertsCount; j++ )
	{
		c.Set ( 130.0f );    // цвет тени
		if ( lpos.GetDistance ( Obj[1].verts[j] ) > lrad ) continue;
		if ( RayTrace ( lpos, Obj[1].verts[j] ))
		{
			Obj[1].tVertexLight[j].v[0] = (byte)c.v[0];
			Obj[1].tVertexLight[j].v[1] = (byte)c.v[1];
			Obj[1].tVertexLight[j].v[2] = (byte)c.v[2];
			continue;
		}
		d = lpos.GetDistance ( Obj[1].verts[j] );
		a = 1.0f - ( d / lrad );
		c.v[0] += 255.0f * a;
		c.v[1] += 255.0f * a;
		c.v[2] += 255.0f * a;
		if ( c.v[0] > 255 ) c.v[0] = 255; if ( c.v[0] < 0 ) c.v[0] = 0;
		if ( c.v[1] > 255 ) c.v[1] = 255; if ( c.v[1] < 0 ) c.v[1] = 0;
		if ( c.v[2] > 255 ) c.v[2] = 255; if ( c.v[2] < 0 ) c.v[2] = 0;
		Obj[1].tVertexLight[j].v[0] = (byte)c.v[0];
		Obj[1].tVertexLight[j].v[1] = (byte)c.v[1];
		Obj[1].tVertexLight[j].v[2] = (byte)c.v[2];
	}
}


//
// Настройка opengl
//
void Init()
{
	glClearColor ( 0, 0.0f, 0, 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);
	printf ( "Calculate VertexLight..." );
	InitVertexLight();
}


void RenderMap()
{
	int j;

	glColor4f ( 1.0f, 1.0f, 1.0f, 1.0f );


	// комната
	glEnableClientState ( GL_VERTEX_ARRAY );
	glVertexPointer ( 3, GL_FLOAT, sizeof(vector),     Obj[0].verts );
	glClientActiveTextureARB(GL_TEXTURE0_ARB);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, sizeof(vector2),    Obj[0].tverts );
	glClientActiveTextureARB(GL_TEXTURE1_ARB);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, sizeof(vector),     Obj[0].LMtverts );
	glActiveTextureARB ( GL_TEXTURE0_ARB );
	glEnable ( GL_TEXTURE_2D );
	glBindTexture ( GL_TEXTURE_2D, tex[0] );
	glActiveTextureARB(GL_TEXTURE1_ARB);
	glEnable(GL_TEXTURE_2D);
		for ( j = 0; j < Obj[0].VertsCount/3; j++ )
		{
			glBindTexture ( GL_TEXTURE_2D, Obj[0].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 );


	// горшок :)
	glEnable ( GL_TEXTURE_2D );
	glEnableClientState ( GL_VERTEX_ARRAY );
	glEnableClientState ( GL_TEXTURE_COORD_ARRAY );
	glEnableClientState ( GL_COLOR_ARRAY );
	glShadeModel ( GL_SMOOTH );
	glBindTexture     ( GL_TEXTURE_2D, tex[1] );
	glVertexPointer   ( 3, GL_FLOAT, sizeof(vector),  Obj[1].verts  );
	glTexCoordPointer ( 2, GL_FLOAT, sizeof(vector2), Obj[1].tverts );
	glColorPointer    ( 3, GL_UNSIGNED_BYTE, sizeof(vector3ub), Obj[1].tVertexLight );
	glDrawArrays      ( GL_TRIANGLES, 0, Obj[1].VertsCount );
	glShadeModel ( GL_FLAT );
	glDisableClientState ( GL_VERTEX_ARRAY );
	glDisableClientState ( GL_TEXTURE_COORD_ARRAY );
	glDisableClientState ( GL_COLOR_ARRAY );
	glDisable ( GL_TEXTURE_2D );


	// источник света
	glColor3f ( 0.0f, 1.0f, 0.0f);
	glPolygonMode ( GL_FRONT_AND_BACK, GL_LINE );
	glPushMatrix();
		glTranslatef ( lpos.v[0], lpos.v[1], lpos.v[2] );
		glutSolidCube ( 5.0f );
	glPopMatrix();
	glPolygonMode ( GL_FRONT_AND_BACK, GL_FILL );
}


void redraw()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	glTranslatef ( 0.0f, -25.0f, -100.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 );
	RenderMap();
	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)
{
	glMatrixMode(GL_PROJECTION);   
	glLoadIdentity();
	gluPerspective(60.0,(double)width/(double)height,0.1,1000.0);
	glMatrixMode(GL_MODELVIEW);    
	glViewport(0, 0, width, height);    
}


void die()
{
	for ( int i = 0; i < 2; 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 );
		DELETE_ARRAY_MEM ( Obj[i].tVertexLight );
	}

	DeleteTextures ( 2, tex );
}


int main ( int argc, char *argv[] )
{
	atexit(die);
	glutInit(&argc, argv);
	glutInitWindowSize(800,600);
	glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH|GLUT_DOUBLE);
	glutCreateWindow("VertexLight Demo");
	glutIdleFunc(redraw);
	glutDisplayFunc(redraw);
	glutMotionFunc(motion);
	glutMouseFunc(mouse);
	glutReshapeFunc(reshape);
	Init();
	glutMainLoop();

	return 0;
}
