/*********************************************************************//**
*	Modul pro vykreslovani sceny.
*	Tento modul se stara o vykreslovani sceny. K teto operaci pouziva
*	BSP strom a prirazene informace o  vzajemne viditelnosti listu.
*	Umoznuje vykreslovat pomoci techto informaci a take ve smeru
*	Back to Front diky BSP stromu.
*
*	author: Michal Jirous
*	date: 11.02.2009
*	file: level_render.h
**********************************************************************/


#include "level_render.h"
#include "level_loader.h"
#include "level_collision.h"
#include "graphics.h"

SceneRenderer renderer;


SceneRenderer::SceneRenderer()
{
	m_bAlphaRendering = false; 

}

Point *g_pCameraForSorting = NULL;

bool boundingBoxSort( CBaseEntity *a, CBaseEntity *b )
{

	if( a->m_pSortBoundingBox && b->m_pSortBoundingBox )
	{
		const BoundingBox &box1 = *a->m_pSortBoundingBox;
		const BoundingBox &box2 = *b->m_pSortBoundingBox;
		
		//zkusime najit nejakou rovinu :)
		for( int i = 0; i < 3; ++i )
		{
			if( box1.m_fBounds[i+3] < box2.m_fBounds[i] )	//mezi nima je dira
			{
				if( (*g_pCameraForSorting)[i] > box1.m_fBounds[i+3] )
					return true;
				else
					return false;
			}
			
			if( box1.m_fBounds[i] > box2.m_fBounds[i+3] )
			{
				if( (*g_pCameraForSorting)[i] > box2.m_fBounds[i+3] )
					return false;
				else
					return true;
			}
		}
	}

	//kdyz neexistuje rovina, tak to udelame podle vzdalenosti
	float box1_d = Vector( a->origin - *g_pCameraForSorting).absolute();
	float box2_d = Vector( b->origin - *g_pCameraForSorting).absolute();
	
	return box1_d > box2_d;


}
#include "level_decals.h"
#include "snow_system.h"
#include "gibs_system.h"
float fog_color[] = { 0.5f,0.5f,0.5f, 1.0f };
/** @param renderData Informace o pozici kamery a aktualni frustum. */
void SceneRenderer::draw( RenderData &renderData )
{
	/*======================================
	*	1. Test jestli se ma neco vykreslit
	*=====================================*/
	
	if( !levelLoader.isLevelLoaded() )	//vykreslujeme pouze pokud je nejaky level nacteny
		return;

	/*======================================
	*	2. Zvysit session key o 1
	*=====================================*/
	LevelData &scene = levelLoader.m_LevelData;

	scene.current_session += 1;

	/*======================================
	*	3. Vykreslovat zde budeme jen
	*		model[0], coz jsou staticke
	*		polygony. Vykresleni ostatnich
	*		ploch entit prenechame samotnym
	*		entitam.
	*	-  Musime nalezt Cluster, kde je
	*		kamera. Prochazime tedy jen
	*		model 0.
	*=====================================*/

	const BSPLeaf &leaf = findCameraLeaf( *renderData.cameraPosition );

	/*======================================
	*	4. Nyni muzeme pripravit data
	*		k vykresleni.
	*	!! toto je standardni nastaveni !!
	*=====================================*/
	glPushAttrib( GL_ENABLE_BIT | GL_CURRENT_BIT );/*
	glPushClientAttrib( GL_CLIENT_ALL_ATTRIB_BITS );*/
	glDisable( GL_CULL_FACE );


	glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
	
	glVertexPointer(3, GL_FLOAT, 0, scene.vertices_array );
 
	if( graphics_Fog )
	{
		//glEnableClientState( GL_FOG_COORD_ARRAY );
		//glFogCoordPointer( GL_FLOAT, 0, scene.fog_coords_array );
		////
		////glFogi(GL_FOG_MODE, GL_LINEAR);						// Fog Fade Is Linear
		//glFogfv(GL_FOG_COLOR, fog_color);					// Set The Fog Color
		//glFogf(GL_FOG_START,  0.0f);						// Set The Fog Start (Least Dense)
		//glFogf(GL_FOG_END,    1.0f);						// Set The Fog End (Most Dense)
		//glHint(GL_FOG_HINT, GL_NICEST);						// Per-Pixel Fog Calculation
		//glFogi(GL_FOG_COORDINATE_SOURCE_EXT, GL_FOG_COORDINATE_EXT);		// Set Fog Based On Vertice Coordinates
	}


	if( graphics_multiTexturingEnabled )
	{
        glClientActiveTextureARB( GL_TEXTURE0_ARB );
		glEnable( GL_TEXTURE_2D );
        glEnableClientState( GL_TEXTURE_COORD_ARRAY );
		glTexCoordPointer( 2, GL_FLOAT, 0, scene.texture_coords_array );
 
        glClientActiveTextureARB( GL_TEXTURE1_ARB );
		glEnable( GL_TEXTURE_2D );
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer( 2, GL_FLOAT, 0, scene.lightmap_coords_array );
    }
	//else se musi provadet multitexturing pomoci blendingu manualne
	
	/*======================================
	*	5. Vykreslime cluster a vsechny
	*		viditelne polygony
	*=====================================*/
	
	if( leaf.visible_leafs_count == 0 )	//No visibility info?
		backToFrontRendering( renderData, 0 );	//model 0
	else
		renderCluster( leaf, *renderData.frustum, *renderData.cameraPosition );
	
	glColor4f(1,1,1,1);
	glDisable( GL_FOG );
	decalSystem.renderDecalsForModel( 0, renderData );

	/*======================================
	*	6. Vykreslime vsechny entity, ktere
	*		to potrebuji
	*		Pokud vyzaduji blending, tak
	*		je ulozime do seznamu blending
	*	objektu
	*=====================================*/

	for( std::list<CBaseEntity*>::iterator iter = levelLoader.m_Entities.m_Render.begin(); iter != levelLoader.m_Entities.m_Render.end(); ++iter )
	{
		if( (*iter)->renderCullTest( renderData ) )
		{
			if( (*iter)->isBlending() )
				m_BlendList.push_back( (*iter) );
			else
				(*iter)->render( renderData );
		}
	}

	snowSystem.render( renderData );
	gibsSystem.render( renderData.frameTimes.elapsed_seconds );

	/*======================================
	*	7. Seradime blending objekty, k cemuz
	*	pouzijeme pozici kamery
	*=====================================*/
	g_pCameraForSorting = renderData.cameraPosition;

	if( !m_BlendList.empty() )
		m_BlendList.sort( boundingBoxSort );

	/*======================================
	*	8. Nyni vykreslime blending objekty
	*	pekne Back To front
	*=====================================*/
	for( std::list<CBaseEntity*>::iterator iter = m_BlendList.begin(); iter != m_BlendList.end(); ++iter )
		(*iter)->render( renderData );

	if( graphics_multiTexturingEnabled )
	{
		glClientActiveTextureARB( GL_TEXTURE1_ARB );
		glDisable( GL_TEXTURE_2D );
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		

        glClientActiveTextureARB( GL_TEXTURE0_ARB );
		glDisable( GL_TEXTURE_2D );
        glDisableClientState( GL_TEXTURE_COORD_ARRAY );
     }

	if( graphics_Fog )
	{
		glDisableClientState( GL_FOG_COORD_ARRAY );
	}

	glPopAttrib();

	m_BlendList.clear();
}

BSPLeaf & SceneRenderer::findCameraLeaf( const Point &cameraPosition ) const
{
	LevelData &scene = levelLoader.m_LevelData;

	int leaf = collisionSystem.findLeaf( cameraPosition );


	return scene.leaves[ leaf ];
}


/** Tato funkce zacne vykreslovat CLuster, coz je jen list BSP stromu, ktery ma 
*	informace o viditelnosti ostatnich clusteru. Tyto clustery prochazi sekvencne
*	a ty, ktere splni test na frustum culling necha vykreslit.
*
*	@param leaf Cluster s informacemi o viditelnosti (VIS)
*	@param frustum Frustum aktualni transformacni matice
*	@param cameraposition Pozice kamery
*/
void SceneRenderer::renderCluster( const BSPLeaf &leaf, const Frustum &frustum, const Point &cameraPosition  ) const
{
	LevelData &scene = levelLoader.m_LevelData;

	
	for( unsigned short i = 0; i < leaf.visible_leafs_count; ++i )
	{
		const BSPLeaf &cluster = scene.leaves[ leaf.visible_leafs[i] ];
	
		//frustum culling
		if( frustum.isBoundsInsideFrustum( cluster.bounds ) )
		{
			//musime vykreslit
		
			//vsechny faces
			for( unsigned short j = 0; j < cluster.mark_surfaces_count; ++j )
			{
				Face &face = scene.faces[ scene.marked_faces[ cluster.first_mark_surface + j] ];
			
				//nesmime vykreslovat stejny face vicekrat
				if( face.session_key == scene.current_session )
					continue;

				face.session_key = scene.current_session;

				Plane &plane = scene.planes[ face.planenum ];
				
				//backface culling
				int visible = (plane.m_vecNormal.multiply( cameraPosition ) + plane.m_fDistance) > 0.0f;
				visible ^= face.side;	//kdyz je side 1, tak to znamena obraceny smer vektoru roviny
			
				if( !visible )
					continue;

				renderFace( face );
			}
		}
	}
}



/** @param renderData Informace o pozici kamery a aktualni frustum.
*	@param modelId Identifikacni cislo modelu.
*/
void SceneRenderer::backToFrontRendering( RenderData &renderData, size_t modelId ) const
{
	LevelData &scene = levelLoader.m_LevelData;
	dmodel_t &model = scene.models[modelId];
	
	BSPTreeBTFRender( renderData, model.headnode[HULL_REALWORLD] );
}


void SceneRenderer::BSPTreeBTFRender( RenderData &renderData, int nodeIdx ) const
{
	if( nodeIdx < 0 )
	{
		const BSPLeaf &cluster = levelLoader.m_LevelData.leaves[ -(nodeIdx+1) ];
			
		//musime vykreslit vsechny faces
		for( unsigned short j = 0; j < cluster.mark_surfaces_count; ++j )
		{
			Face &face = levelLoader.m_LevelData.faces[ levelLoader.m_LevelData.marked_faces[ cluster.first_mark_surface + j] ];
		
			//nesmime vykreslovat stejny face vicekrat
			if( face.session_key == levelLoader.m_LevelData.current_session )
				continue;

			face.session_key = levelLoader.m_LevelData.current_session;

			Plane &plane = levelLoader.m_LevelData.planes[ face.planenum ];
			
			//if( renderData.backfacecull )
			//{
				//backface culling
				int visible = (plane.m_vecNormal.multiply( *renderData.cameraPosition ) + plane.m_fDistance) > 0.0f;
				visible ^= face.side;	//kdyz je side 1, tak to znamena obraceny smer vektoru roviny
		
				if( !(visible) && renderData.backfacecull )
					continue;
				if( visible && !renderData.backfacecull )
					continue;
			//}
			renderFace( face );
		}
		
	
		return;
	}
	else
	{
		BSPNode &node = levelLoader.m_LevelData.nodes[nodeIdx];
		Plane &plane = levelLoader.m_LevelData.planes[ node.planenum ];	//potrebujeme znat rovinu
		float v;
		if( plane.m_type <= PLANE_Z )	//pro axialni rovinu je vypocet jednoduchy
			v = (*renderData.cameraPosition)[plane.m_type] + plane.m_fDistance;
		else
			v = plane.m_vecNormal.multiply( *renderData.cameraPosition ) + plane.m_fDistance;
		
		if( v >= 0 ) //pozice je ve FRONT
		{
			BSPTreeBTFRender( renderData, node.children[1] );	//nejprve BACK
			BSPTreeBTFRender( renderData, node.children[0] );	//pak FRONT
		}
		else	//pozice je ve BACK
		{
			BSPTreeBTFRender( renderData, node.children[0] );
			BSPTreeBTFRender( renderData, node.children[1] );
		}
	}
}

void SceneRenderer::renderFace( const Face &face ) const
{
	if( !face.pTexture )
		return;

	//pokud mame multitexturing a Face nema styly nebo je vyzadovan alpha rendering (blending nebo alpha test)
	if(  graphics_multiTexturingEnabled && ( !(face.flags & FACE_USES_STYLES) || m_bAlphaRendering ) )
	{
		//nejprve nastavime zakladni texturu
		glActiveTextureARB(GL_TEXTURE0_ARB);
		if( face.pTexture )
			face.pTexture->bind();
		glEnable( GL_TEXTURE_2D );

		//kdyz Face nema lightmapu, tak zakazeme druhou texturovaci jednotku
 		if( !(face.flags & FACE_NO_LIGHTMAP) )
		{
            glActiveTextureARB(GL_TEXTURE1_ARB);
            texBind( face.lightmaps[0] );
			glEnable(GL_TEXTURE_2D);
        }
 
		//vykreslime
        glDrawArrays( GL_POLYGON, (GLint)face.firstvertex, face.elements_count );
		
		//uklidime
        glActiveTextureARB(GL_TEXTURE1_ARB);
        glDisable( GL_TEXTURE_2D );
        
		glActiveTextureARB(GL_TEXTURE0_ARB);
        glDisable(GL_TEXTURE_2D);
	}
	//pokud nemame k dispozici multitexturing a nebo jsou vyzadovany styly
	else if( face.flags & FACE_USES_STYLES || !graphics_multiTexturingEnabled )
	{
		glActiveTextureARB(GL_TEXTURE0_ARB);
        glDisable(GL_TEXTURE_2D);
	
		//nejprve nastavime prvni lightmapu
		glActiveTextureARB(GL_TEXTURE1_ARB);
        glEnable(GL_TEXTURE_2D);
		texBind( face.lightmaps[0] );	//prvni svetlo

		//pokud je vyzadovan alpha rendering, tak musime provest tuto operaci
		if( m_bAlphaRendering && !graphics_multiTexturingEnabled )
		{
			//jinak by vznikly chyby
			glColor3f( 0.5f, 0.5f, 0.5f );
			glDrawArrays( GL_POLYGON, (GLint)face.firstvertex, face.elements_count );
			return;
		}

		//vykreslime prvni lightmapu
		glDrawArrays( GL_POLYGON, (GLint)face.firstvertex, face.elements_count );

		glEnable(GL_BLEND);
		glDepthMask(GL_FALSE);
		glDepthFunc(GL_EQUAL);

		//nastavime blending pro michani lightmap
		glBlendFunc( GL_ONE, GL_ONE );

		glPushAttrib( GL_CURRENT_BIT );

		//prochazime vsechny styly
		for( short i = 1; i < MAXLIGHTMAPS; ++i )
		{
			if( face.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[ face.styles[i] ];
			if( !pLight )
				break;
			if( pLight->isOn() )
			{
				texBind( face.lightmaps[i] );
				pLight->setGlColor();
				glDrawArrays( GL_POLYGON, (GLint)face.firstvertex, face.elements_count );
			}

		}
		glPopAttrib();



		glActiveTextureARB(GL_TEXTURE1_ARB);
        glDisable( GL_TEXTURE_2D );
		
		glActiveTextureARB(GL_TEXTURE0_ARB);
		
		glEnable(GL_TEXTURE_2D);
		if( face.pTexture )
			face.pTexture->bind();
		
		glBlendFunc( GL_ZERO, GL_SRC_COLOR );

		
		//nakonec vykreslime zed
		glDrawArrays( GL_POLYGON, (GLint)face.firstvertex, face.elements_count );
			
		glDisable(GL_BLEND);
		glDepthMask(GL_TRUE);
		glDepthFunc(GL_LESS);

   
		glActiveTextureARB(GL_TEXTURE0_ARB);
        glDisable(GL_TEXTURE_2D);
	}
}

