/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006-2011 Robert Beckebans <trebor_7@users.sourceforge.net>

This file is part of Daemon source code.

Daemon source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Daemon source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Daemon source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
// tr_mesh.c -- triangle model functions
#include "tr_local.h"

/*
=============
R_CullMDV
=============
*/
static void R_CullMDV( mdvModel_t *model, trRefEntity_t *ent )
{
	mdvFrame_t *oldFrame, *newFrame;
	int        i;
	vec3_t     v;
	vec3_t     transformed;

	// compute frame pointers
	newFrame = model->frames + ent->e.frame;
	oldFrame = model->frames + ent->e.oldframe;

	// calculate a bounding box in the current coordinate system
	for ( i = 0; i < 3; i++ )
	{
		ent->localBounds[ 0 ][ i ] =
		  oldFrame->bounds[ 0 ][ i ] < newFrame->bounds[ 0 ][ i ] ? oldFrame->bounds[ 0 ][ i ] : newFrame->bounds[ 0 ][ i ];
		ent->localBounds[ 1 ][ i ] =
		  oldFrame->bounds[ 1 ][ i ] > newFrame->bounds[ 1 ][ i ] ? oldFrame->bounds[ 1 ][ i ] : newFrame->bounds[ 1 ][ i ];
	}

	// setup world bounds for intersection tests
	ClearBounds( ent->worldBounds[ 0 ], ent->worldBounds[ 1 ] );

	for ( i = 0; i < 8; i++ )
	{
		v[ 0 ] = ent->localBounds[ i & 1 ][ 0 ];
		v[ 1 ] = ent->localBounds[( i >> 1 ) & 1 ][ 1 ];
		v[ 2 ] = ent->localBounds[( i >> 2 ) & 1 ][ 2 ];

		// transform local bounds vertices into world space
		R_LocalPointToWorld( v, transformed );

		AddPointToBounds( transformed, ent->worldBounds[ 0 ], ent->worldBounds[ 1 ] );
	}

	// cull bounding sphere ONLY if this is not an upscaled entity
	if ( !ent->e.nonNormalizedAxes )
	{
		if ( ent->e.frame == ent->e.oldframe )
		{
			switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) )
			{
				case CULL_OUT:
					tr.pc.c_sphere_cull_mdx_out++;
					ent->cull = CULL_OUT;
					return;

				case CULL_IN:
					tr.pc.c_sphere_cull_mdx_in++;
					ent->cull = CULL_IN;
					return;

				case CULL_CLIP:
					tr.pc.c_sphere_cull_mdx_clip++;
					break;
			}
		}
		else
		{
			int sphereCull, sphereCullB;

			sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius );

			if ( newFrame == oldFrame )
			{
				sphereCullB = sphereCull;
			}
			else
			{
				sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius );
			}

			if ( sphereCull == sphereCullB )
			{
				if ( sphereCull == CULL_OUT )
				{
					tr.pc.c_sphere_cull_mdx_out++;
					ent->cull = CULL_OUT;
					return;
				}
				else if ( sphereCull == CULL_IN )
				{
					tr.pc.c_sphere_cull_mdx_in++;
					ent->cull = CULL_IN;
					return;
				}
				else
				{
					tr.pc.c_sphere_cull_mdx_clip++;
				}
			}
		}
	}

	switch ( R_CullLocalBox( ent->localBounds ) )
	{
		case CULL_IN:
			tr.pc.c_box_cull_mdx_in++;
			ent->cull = CULL_IN;
			return;

		case CULL_CLIP:
			tr.pc.c_box_cull_mdx_clip++;
			ent->cull = CULL_CLIP;
			return;

		case CULL_OUT:
		default:
			tr.pc.c_box_cull_mdx_out++;
			ent->cull = CULL_OUT;
			return;
	}
}

/*
=================
R_ComputeLOD
=================
*/
int R_ComputeLOD( trRefEntity_t *ent )
{
	float      radius;
	float      flod, lodscale;
	float      projectedRadius;
	mdvFrame_t *frame;
	int        lod;

	if ( tr.currentModel->numLods < 2 )
	{
		// model has only 1 LOD level, skip computations and bias
		lod = 0;
	}
	else
	{
		// multiple LODs exist, so compute projected bounding sphere
		// and use that as a criteria for selecting LOD

		frame = tr.currentModel->mdv[ 0 ]->frames;
		frame += ent->e.frame;

		radius = RadiusFromBounds( frame->bounds[ 0 ], frame->bounds[ 1 ] );

		if ( ( projectedRadius = R_ProjectRadius( radius, ent->e.origin ) ) != 0 )
		{
			lodscale = r_lodScale->value;

			if ( lodscale > 20 )
			{
				lodscale = 20;
			}

			flod = 1.0f - projectedRadius * lodscale;
		}
		else
		{
			// object intersects near view plane, e.g. view weapon
			flod = 0;
		}

		flod *= tr.currentModel->numLods;
		lod = XreaL_Q_ftol( flod );

		if ( lod < 0 )
		{
			lod = 0;
		}
		else if ( lod >= tr.currentModel->numLods )
		{
			lod = tr.currentModel->numLods - 1;
		}
	}

	lod += r_lodBias->integer;

	if ( lod >= tr.currentModel->numLods )
	{
		lod = tr.currentModel->numLods - 1;
	}

	if ( lod < 0 )
	{
		lod = 0;
	}

	return lod;
}

static shader_t *GetMDVSurfaceShader( const trRefEntity_t *ent, mdvSurface_t *mdvSurface )
{
	shader_t *shader = 0;

	if ( ent->e.customShader )
	{
		shader = R_GetShaderByHandle( ent->e.customShader );
	}
	else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins )
	{
		skin_t *skin;
		int    j;

		skin = R_GetSkinByHandle( ent->e.customSkin );

		// match the surface name to something in the skin file
		shader = tr.defaultShader;

		for ( j = 0; j < skin->numSurfaces; j++ )
		{
			// the names have both been lowercased
			if ( !strcmp( skin->surfaces[ j ]->name, mdvSurface->name ) )
			{
				shader = skin->surfaces[ j ]->shader;
				break;
			}
		}

		if ( shader == tr.defaultShader )
		{
			ri.Printf( PRINT_DEVELOPER, "WARNING: no shader for surface %s in skin %s\n", mdvSurface->name, skin->name );
		}
		else if ( shader->defaultShader )
		{
			ri.Printf( PRINT_DEVELOPER, "WARNING: shader %s in skin %s not found\n", shader->name, skin->name );
		}
	}
	else
	{
		shader = mdvSurface->shader;
	}

	return shader;
}

/*
=================
R_AddMDVSurfaces
=================
*/
void R_AddMDVSurfaces( trRefEntity_t *ent )
{
	int          i;
	mdvModel_t   *model = 0;
	mdvSurface_t *mdvSurface = 0;
	shader_t     *shader = 0;
	int          lod;
	qboolean     personalModel;
	int          fogNum;

	// don't add third_person objects if not in a portal
	personalModel = ( ent->e.renderfx & RF_THIRD_PERSON ) && !tr.viewParms.isPortal;

	if ( ent->e.renderfx & RF_WRAP_FRAMES )
	{
		ent->e.frame %= tr.currentModel->mdv[ 0 ]->numFrames;
		ent->e.oldframe %= tr.currentModel->mdv[ 0 ]->numFrames;
	}

	// compute LOD
	if ( ent->e.renderfx & RF_FORCENOLOD )
	{
		lod = 0;
	}
	else
	{
		lod = R_ComputeLOD( ent );
	}

	// Validate the frames so there is no chance of a crash.
	// This will write directly into the entity structure, so
	// when the surfaces are rendered, they don't need to be
	// range checked again.
	if ( ( ent->e.frame >= tr.currentModel->mdv[ lod ]->numFrames )
	     || ( ent->e.frame < 0 ) || ( ent->e.oldframe >= tr.currentModel->mdv[ lod ]->numFrames ) || ( ent->e.oldframe < 0 ) )
	{
		ri.Printf( PRINT_DEVELOPER, "R_AddMDVSurfaces: no such frame %d to %d for '%s' (%d)\n",
		           ent->e.oldframe, ent->e.frame, tr.currentModel->name, tr.currentModel->mdv[ lod ]->numFrames );
		ent->e.frame = 0;
		ent->e.oldframe = 0;
	}

	model = tr.currentModel->mdv[ lod ];

	// cull the entire model if merged bounding box of both frames
	// is outside the view frustum.
	R_CullMDV( model, ent );

	if ( ent->cull == CULL_OUT )
	{
		return;
	}

	// set up lighting now that we know we aren't culled
	if ( !personalModel || r_shadows->integer > SHADOWING_BLOB )
	{
		R_SetupEntityLighting( &tr.refdef, ent, NULL );
	}

	// see if we are in a fog volume
	fogNum = R_FogWorldBox( ent->worldBounds );

	// draw all surfaces
	if ( r_vboModels->integer && model->numVBOSurfaces )
	{
		int             i;
		srfVBOMDVMesh_t *vboSurface;
		shader_t        *shader;

		for ( i = 0; i < model->numVBOSurfaces; i++ )
		{
			vboSurface = model->vboSurfaces[ i ];
			mdvSurface = vboSurface->mdvSurface;

			shader = GetMDVSurfaceShader( ent, mdvSurface );

			// don't add third_person objects if not viewing through a portal
			if ( !personalModel )
			{
				R_AddDrawSurf( ( void * ) vboSurface, shader, -1, fogNum );
			}
		}
	}
	else
	{
		for ( i = 0, mdvSurface = model->surfaces; i < model->numSurfaces; i++, mdvSurface++ )
		{
			shader = GetMDVSurfaceShader( ent, mdvSurface );

			// we will add shadows even if the main object isn't visible in the view

			// don't add third_person objects if not viewing through a portal
			if ( !personalModel )
			{
				R_AddDrawSurf( ( void * ) mdvSurface, shader, -1, fogNum );
			}
		}
	}
}

/*
=================
R_AddMDVInteractions
=================
*/
void R_AddMDVInteractions( trRefEntity_t *ent, trRefLight_t *light )
{
	int               i;
	mdvModel_t        *model = 0;
	mdvSurface_t      *mdvSurface = 0;
	shader_t          *shader = 0;
	int               lod;
	qboolean          personalModel;
	byte              cubeSideBits;
	interactionType_t iaType = IA_DEFAULT;

	// cull the entire model if merged bounding box of both frames
	// is outside the view frustum and we don't care about proper shadowing
	if ( ent->cull == CULL_OUT )
	{
		if ( r_shadows->integer <= SHADOWING_BLOB || light->l.noShadows )
		{
			return;
		}
		else
		{
			iaType = IA_SHADOWONLY;
		}
	}

	// avoid drawing of certain objects
#if defined( USE_REFENTITY_NOSHADOWID )

	if ( light->l.inverseShadows )
	{
		if ( iaType != IA_LIGHTONLY && ( light->l.noShadowID && ( light->l.noShadowID != ent->e.noShadowID ) ) )
		{
			return;
		}
	}
	else
	{
		if ( iaType != IA_LIGHTONLY && ( light->l.noShadowID && ( light->l.noShadowID == ent->e.noShadowID ) ) )
		{
			return;
		}
	}

#endif

	// don't add third_person objects if not in a portal
	personalModel = ( ent->e.renderfx & RF_THIRD_PERSON ) && !tr.viewParms.isPortal;

	// compute LOD
	lod = R_ComputeLOD( ent );

	model = tr.currentModel->mdv[ lod ];

	// do a quick AABB cull
	if ( !BoundsIntersect( light->worldBounds[ 0 ], light->worldBounds[ 1 ], ent->worldBounds[ 0 ], ent->worldBounds[ 1 ] ) )
	{
		tr.pc.c_dlightSurfacesCulled += model->numSurfaces;
		return;
	}

	// do a more expensive and precise light frustum cull
	if ( !r_noLightFrustums->integer )
	{
		if ( R_CullLightWorldBounds( light, ent->worldBounds ) == CULL_OUT )
		{
			tr.pc.c_dlightSurfacesCulled += model->numSurfaces;
			return;
		}
	}

	cubeSideBits = R_CalcLightCubeSideBits( light, ent->worldBounds );

	// generate interactions with all surfaces
	if ( r_vboModels->integer && model->numVBOSurfaces )
	{
		// new brute force method: just render everthing with static VBOs
		int             i;
		srfVBOMDVMesh_t *vboSurface;
		shader_t        *shader;

		// static VBOs are fine for lighting and shadow mapping
		for ( i = 0; i < model->numVBOSurfaces; i++ )
		{
			vboSurface = model->vboSurfaces[ i ];
			mdvSurface = vboSurface->mdvSurface;

			shader = GetMDVSurfaceShader( ent, mdvSurface );

			// skip all surfaces that don't matter for lighting only pass
			if ( shader->isSky || ( !shader->interactLight && shader->noShadows ) )
			{
				continue;
			}

			// we will add shadows even if the main object isn't visible in the view

			// don't add third_person objects if not viewing through a portal
			if ( !personalModel )
			{
				R_AddLightInteraction( light, ( void * ) vboSurface, shader, cubeSideBits, iaType );
				tr.pc.c_dlightSurfaces++;
			}
		}
	}
	else
	{
		for ( i = 0, mdvSurface = model->surfaces; i < model->numSurfaces; i++, mdvSurface++ )
		{
			shader = GetMDVSurfaceShader( ent, mdvSurface );

			// skip all surfaces that don't matter for lighting only pass
			if ( shader->isSky || ( !shader->interactLight && shader->noShadows ) )
			{
				continue;
			}

			// we will add shadows even if the main object isn't visible in the view

			// don't add third_person objects if not viewing through a portal
			if ( !personalModel )
			{
				R_AddLightInteraction( light, ( void * ) mdvSurface, shader, cubeSideBits, iaType );
				tr.pc.c_dlightSurfaces++;
			}
		}
	}
}
