/*********************************************************************//**
*		Definice objektu Face
*	Face je ekvivalent polygonu, kde jsou ulozeny informace o hranach,
*	texturach, lightmapach a bodech. S temito informacemi lze
*	jednoduse provadet vykreslovani a detekci kolizi.
*                                                                        
*
*	author: Michal Jirous
*	date: 4.2.2009
*	file: level_face.cpp
**********************************************************************/  

#include "level_structs.h"
#include "level_loader.h"

/** @param face Objekt dface_t, jehoz cast informaci se zkopiruje do tohoto Face. */
void Face::create( const dface_t &face )
{
	planenum = face.planenum;
	side = face.side;
	flags = 0;
	first_surfedge = face.firstedge;
	elements_count = face.numedges;

	memcpy( styles, face.styles, sizeof(styles) );
}

int Face::getMaterial()
{
	if( pTexture && pTexture->m_pTextureElement )
		return pTexture->m_pTextureElement->material;
	return -1;
}

void Face::destroy()
{
	if( pTexture )
	{
		pTexture->unload();
		pTexture = NULL;
	}

	for( int i = 0; i < MAXLIGHTMAPS; ++i )
		if( lightmaps[i] )	//identifikacni cisla textur
		{
			glDeleteTextures( 1, &lightmaps[i] );
			lightmaps[i] = 0;
		}
}

/** @param direction Smer poloprimky.
*	@param position Startovni bod poloprimky.
*	@param result Vysledny bod pruniku.
*	@return True, pokud prunik nastal, jinak false.
*/
int Face::intersectByLine( const Vector &direction, const Point &position, Point &result ) const
{
	return intersectByLine( Line( position, direction ), result );
}




/** @param Line poloprimka.
*	@param result Vysledny bod pruniku.
*	@return True, pokud prunik nastal, jinak false.
*/
int Face::intersectByLine( const Line &line, Point &result ) const
{
	if( planeLineIntersect( line, levelLoader.m_LevelData.planes[planenum], result ) )
	{
		if( m_Bounds.isPointInBoundingBox( result ) )	
			return isPointIn( result );
	}
	return false;
}



/** @param target Testovany bod.
*	@return 1 pokud je bod uvnitr polygonu.
*/
int Face::isPointIn( const Point &target ) const
{
	Plane &plane = levelLoader.m_LevelData.planes[planenum];
	Point *points = levelLoader.m_LevelData.vertices_array + firstvertex;
	
	return isPointInPolygon( target, points, plane, elements_count );
}



/** @param thisPlane Rovina tohoto polygonu (Face). */
bool Face::collisionPhase1( CollisionData &collData, const Plane &thisPlane ) const
{
	/*============================================
	*	1. Nalezneme nejblizsi bod k rovine face
	*===========================================*/
	Point closest,result;
	
	if( thisPlane.m_vecNormal.x < 0 )
		closest.x = collData.model.m_StaticBounds.m_fBounds[MAX_X];
	else 
		closest.x = collData.model.m_StaticBounds.m_fBounds[MIN_X];

	if( thisPlane.m_vecNormal.y < 0 )
		closest.y = collData.model.m_StaticBounds.m_fBounds[MAX_Y];
	else 
		closest.y = collData.model.m_StaticBounds.m_fBounds[MIN_Y];

	if( thisPlane.m_vecNormal.z < 0 )
		closest.z = collData.model.m_StaticBounds.m_fBounds[MAX_Z];
	else 
		closest.z = collData.model.m_StaticBounds.m_fBounds[MIN_Z];
	
	/*============================================
	*	2. Provedeme line intersect
	*===========================================*/
	
	if( intersectByLine( collData.model.m_vecMovement, closest, result ) )
	{
		float distance = Vector(result-closest).absolute();
		if( distance < collData.distance )
		{
			collData.normal = thisPlane.m_vecNormal;
			collData.distance = distance;
			return true;
		}
	
	}
	return false;
}


bool Face::collisionPhase2( CollisionData &collData ) const
{
	bool isColliding = false;
	/*========================================================
	*	1. Testujeme (vsechny hrany) x (vsechny roviny modelu)
	*=======================================================*/
	
	for( int i = 0; i < elements_count; ++i )
	{
		Edge &edge = *levelLoader.m_LevelData.surf_edges_pointer[ first_surfedge + i ];
		
		if( edge.session_key == levelLoader.m_LevelData.current_session )
			continue;

		edge.session_key = levelLoader.m_LevelData.current_session;

		for( int j = 0; j < collData.model.m_PlanesCount; ++j )
		{
			ActiveModelPlane &plane = collData.model.m_Planes[j];

			/*========================================================
			*	2. Test, zda rovina protina tuto hranu
			*=======================================================*/
			
			float v_A = plane.m_vecNormal.multiply( *edge.A ) + plane.m_fDistance;
			float v_B = plane.m_vecNormal.multiply( *edge.B ) + plane.m_fDistance;

			if( v_A < 0 )
			{
				if( v_B < 0 )
					continue;
			}
			else if( v_A > 0 )
			{
				if( v_B > 0 )
					continue;
			
			}
			else if( !(v_B != 0) )
				continue;
			
			Point intersected;
			Vector edgeVector = *edge.A - *edge.B;
			if( planeLineIntersect( Line( edge.B, edgeVector), plane, intersected ) )
			{
				/*========================================================
				*	3. Test, zda vysledny bod by vznikl hranou modelu,
				*		coz znamena, ze musi lezet v polygonu ohranicenym
				*		touto hranou + body vzniklymi posunutim
				*		bodu hrany vektorem pohybu.
				*=======================================================*/
			
				Point points[4];
				points[0] = *plane.m_Points[0];
				points[1] = *plane.m_Points[1];
				points[2] = *plane.m_Points[1] + collData.model.m_vecMovement;
				points[3] = *plane.m_Points[0] + collData.model.m_vecMovement;

				if( isPointInPolygon( intersected, points, plane, 4 ) )
				{
					/*========================================================
					*	4. Zjistime vzdalenost od modelu
					*=======================================================*/
					float distance;
					collData.model.m_StaticBounds.setRange( 0.001f );
					bool c = false;
					if( collData.model.m_StaticBounds.isPointInBoundingBox( intersected ) )
					{
						c = true;
						distance = 0.001f;
					}
					else if( collData.model.m_StaticBounds.lineIntersectDistance( Vector(-collData.model.m_vecMovement).normalize(), intersected, points[0], distance ) )	//points[0] jen tak
					{

						c = true;
					}

					
					if( c )
					{
						/*========================================================
						*	5. Zjisteni zda lze bod pouzit pro chuzi do schodu
						*=======================================================*/
						Vector horizontal = edgeVector;
						horizontal.z = 0.0f;
						float angle = fabs( edgeVector.scalarMultiply( horizontal ) );
						if( fabs( angle ) < ALG_STAIRS_DETECT_ANGLE && distance < collData.maxdistance )
						{
							collData.highestPoint = std::max( collData.highestPoint, intersected.z );
						}

						if( distance < collData.distance )
						{
							collData.distance = distance;	

							/*========================================================
							*	6. Zjisteni normaloveho vektoru pro budouci sliding 
							*		pohyb.
							*=======================================================*/
							alg::Vector planeLineVector( *plane.m_Points[0], *plane.m_Points[1] );	//vector primky hrany nalezici brushy
							collData.normal = planeLineVector * edgeVector;
							
							if( collData.normal.Dot( collData.model.m_vecMovement ) > 0.0f)
								collData.normal = -collData.normal;
							//collData.normal.normalize();
							isColliding = true;
						}
					}
					collData.model.m_StaticBounds.setRange( -0.001f );
				}
			}
		}
	}

	return isColliding;
}

bool Face::collisionPhase3( CollisionData &collData ) const
{
	bool isColliding = false;
	Point *point = &levelLoader.m_LevelData.vertices_array[ firstvertex ];
	Point intersected;
	Vector normal;
	int hrana;
	for( int i = 0; i < elements_count; ++i )
	{
		if( collData.model.m_StaticBounds.lineIntersectDetectNormal( -collData.model.m_vecMovement, point[i], intersected, normal, hrana) )
		{
			float distance = alg::Vector( point[i], intersected).absolute();

			if( distance < collData.maxdistance )
			{
				collData.highestPoint = std::max( collData.highestPoint, point[i].z );
			}

			if( distance < collData.distance )	//mame nove reseni
			{
				isColliding = true;
				collData.distance = distance;
				collData.normal = -normal;
			}
		}
	}

	return isColliding;
}

/** @param collData Informace o puvodci kolize a dalsich parametrech.
*	@return True, pokud nastala kolize s timto objektem.
*/
bool Face::collideWith( CollisionData &collData )
{
	//porovname cislo aktualniho kola, a pokud je cislo stejne, tak uz se 
	//tento face testoval.
	if( session_key == levelLoader.m_LevelData.current_session )
		return false;

	session_key = levelLoader.m_LevelData.current_session;


	/*============================================
	*	1. Backface cull
	*===========================================*/
	
	Plane thisPlane = levelLoader.m_LevelData.planes[planenum];
	if( side )
		thisPlane.m_vecNormal = -thisPlane.m_vecNormal;

	alg::Vector vecMovement = collData.model.m_vecMovement;		//smer pohybu modelu

	if( fabs( vecMovement.scalarMultiply( thisPlane.m_vecNormal ) ) < 90.0f )
		return false;
	
	/*============================================
	*	2. Prvni faze detekce
	*		nalezneme nejblizsi bod k rovine face
	*		a z tohoto bodu vedeme primku
	*		s vektorem pohybu proti tomuto face.
	*		pokud primka face protina, nastava
	*		kolize a testuje se vzdalenost.
	*===========================================*/
	
	bool isColliding = collisionPhase1( collData, thisPlane  );
	
	
	/*============================================
	*	3. Druha faze detekce. Kazdou hranou
	*		modelu vedeme rovinu ve smeru pohybu
	*		a testujeme prunik s hranou face.
	*===========================================*/

	isColliding |= collisionPhase2( collData );
	
	/*============================================
	*	4. Treti faze. Vedeme primku z bodu
	*		polygonu proti modelu.
	*===========================================*/

	isColliding |= collisionPhase3( collData );



	return isColliding;
}




/** @param values3 Pole hodnot 3x GLfloat, kam bude ulozena vysledna barva. */
void Face::getLightColor( GLfloat *values3 )
{
	memcpy( values3, average_face_color, 3*sizeof(GLfloat) );
	if( flags & FACE_USES_STYLES )	//slozitejsi
	{
		//musime projit vsechny styly a pokud jsou aktivni, tak pridat svetlo
		for( short i = 1; i < MAXLIGHTMAPS; ++i )
		{
			if( styles[i] == (byte)-1 )	//-1 znamena, ze styl neni
				break;

			//zjistime svetlo a pote nastavime jeho jas a vykreslime lightmapu
			Light *pLight = levelLoader.m_Entities.lightStyles[ styles[i] ];
			if( pLight && pLight->isOn() )
			{
				GLfloat *color = pLight->getColor();
				for( int j = 0; j < 3; ++j )
					values3[j] = std::min( 1.0f, color[j]+values3[j] );
			}

		}
	}

}
