/*
===========================================================================

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 

This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  

Doom 3 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 3 of the License, or
(at your option) any later version.

Doom 3 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 Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/




#include "tr_local.h"

/*
===================
R_ListRenderLightDefs_f
===================
*/
void R_ListRenderLightDefs_f( const idCmdArgs &args ) {
	int			i;
	idRenderLightLocal	*ldef;

	if ( !tr.primaryWorld ) {
		return;
	}
	int active = 0;
	int	totalRef = 0;
	int	totalIntr = 0;

	for ( i = 0 ; i < tr.primaryWorld->lightDefs.Num() ; i++ ) {
		ldef = tr.primaryWorld->lightDefs[i];
		if ( !ldef ) {
			common->Printf( "%4i: FREED\n", i );
			continue;
		}

		// count up the interactions
		int	iCount = 0;
		for ( idInteraction *inter = ldef->firstInteraction; inter != NULL; inter = inter->lightNext ) {
			iCount++;
		}
		totalIntr += iCount;

		// count up the references
		int	rCount = 0;
		for ( areaReference_t *ref = ldef->references ; ref ; ref = ref->ownerNext ) {
			rCount++;
		}
		totalRef += rCount;

		common->Printf( "%4i: %3i intr %2i refs %s\n", i, iCount, rCount, ldef->lightShader->GetName());
		active++;
	}

	common->Printf( "%i lightDefs, %i interactions, %i areaRefs\n", active, totalIntr, totalRef );
}

/*
===================
R_ListRenderEntityDefs_f
===================
*/
void R_ListRenderEntityDefs_f( const idCmdArgs &args ) {
	int			i;
	idRenderEntityLocal	*mdef;

	if ( !tr.primaryWorld ) {
		return;
	}
	int active = 0;
	int	totalRef = 0;
	int	totalIntr = 0;

	for ( i = 0 ; i < tr.primaryWorld->entityDefs.Num() ; i++ ) {
		mdef = tr.primaryWorld->entityDefs[i];
		if ( !mdef ) {
			common->Printf( "%4i: FREED\n", i );
			continue;
		}

		// count up the interactions
		int	iCount = 0;
		for ( idInteraction *inter = mdef->firstInteraction; inter != NULL; inter = inter->entityNext ) {
			iCount++;
		}
		totalIntr += iCount;

		// count up the references
		int	rCount = 0;
		for ( areaReference_t *ref = mdef->entityRefs ; ref ; ref = ref->ownerNext ) {
			rCount++;
		}
		totalRef += rCount;

		common->Printf( "%4i: %3i intr %2i refs %s\n", i, iCount, rCount, mdef->parms.hModel->Name());
		active++;
	}

	common->Printf( "total active: %i\n", active );
}

/*
===================
idRenderWorldLocal::idRenderWorldLocal
===================
*/
idRenderWorldLocal::idRenderWorldLocal() {
	mapName.Clear();
	mapTimeStamp = FILE_NOT_FOUND_TIMESTAMP;
	mapFileCRC = 0u;

	generateAllInteractionsCalled = false;

	areaNodes = NULL;
	numAreaNodes = 0;

	portalAreas = NULL;
	numPortalAreas = 0;

	lightGridAvailabilityFrame = -1;
	anyLightGridAvailable = false;

	doublePortals = NULL;
	numInterAreaPortals = 0;
	md5rProcData = NULL;

	interactionTable = 0;
	interactionTableWidth = 0;
	interactionTableHeight = 0;
	pendingDemoTimeOffset = false;
}

/*
===================
idRenderWorldLocal::~idRenderWorldLocal
===================
*/
idRenderWorldLocal::~idRenderWorldLocal() {
	// free all the entityDefs, lightDefs, portals, etc
	FreeWorld();

	// free up the debug lines, polys, and text
	RB_ClearDebugPolygons( 0 );
	RB_ClearDebugLines( 0 );
	RB_ClearDebugText( 0 );
}

/*
===================
idRenderWorldLocal::FreeDeferredLightDefs
===================
*/
void idRenderWorldLocal::FreeDeferredLightDefs() {
	for ( int i = 0; i < deferredFreeLightDefs.Num(); i++ ) {
		idRenderLightLocal *light = deferredFreeLightDefs[i];
		if ( light == NULL ) {
			continue;
		}
		R_FreeLightDefDerivedData( light );
		delete light;
	}
	deferredFreeLightDefs.Clear();
}

/*
===================
ResizeInteractionTable
===================
*/
void idRenderWorldLocal::ResizeInteractionTable() {
	// we overflowed the interaction table, so dump it
	// we may want to resize this in the future if it turns out to be common
	common->Printf( "idRenderWorldLocal::ResizeInteractionTable: overflowed interactionTableWidth, dumping\n" );
	R_StaticFree( interactionTable );
	interactionTable = NULL;
}

/*
===================
AddEffectDef
===================
*/
qhandle_t idRenderWorldLocal::AddEffectDef(const renderEffect_t* reffect, int time) { 
	int effectHandle = effectsDef.FindNull();
	if (effectHandle == -1) {
		effectHandle = effectsDef.Append(NULL);
	}
	if ( UpdateEffectDef( effectHandle, reffect, time ) ) {
		FreeEffectDef( effectHandle );
		return -1;
	}
	return effectHandle;
}

/*
===================
UpdateEffectDef
===================
*/
bool idRenderWorldLocal::UpdateEffectDef(qhandle_t effectHandle, const renderEffect_t* reffect, int time) {
	// create new slots if needed
	if (effectHandle < 0 || effectHandle > LUDICROUS_INDEX) {
		common->Error("idRenderWorld::UpdateEffectDef: index = %i", effectHandle);
	}
	while ( effectHandle >= effectsDef.Num() ) {
		effectsDef.Append( NULL );
	}

	bool createNew = false;
	rvRenderEffectLocal* def = effectsDef[effectHandle];
	if ( def == NULL ) {
		def = new rvRenderEffectLocal();
		effectsDef[effectHandle] = def;
		def->index = effectHandle;
		def->world = this;
		def->dynamicModel = NULL;
		def->dynamicModelFrameCount = 0;
		def->updateFramenum = -1;
		def->newEffect = false;
		def->expired = true;
		def->archived = false;
		def->referenceBounds.Clear();
		def->effect = NULL;
		createNew = true;
	}

	const idDecl *previousDeclEffect = def->parms.declEffect;
	def->parms = *reffect;
	def->gameTime = time;
	def->lastModifiedFrameNum = tr.frameCount;
	if ( session->writeDemo ) {
		def->archived = false;
	}

	const bool declChanged = ( !createNew && previousDeclEffect != reffect->declEffect );
	// Expiry is a terminal state for the current effect instance. Callers such as
	// rvClientEffect expect UpdateEffectDef() to report that completion so they can
	// free the handle; auto-recreating an expired one-shot effect turns impact
	// sounds/decals into per-frame replays as long as the owner keeps updating it.
	const bool recreateEffect = createNew || declChanged || def->effect == NULL;
	if ( recreateEffect ) {
		if ( def->effect != NULL ) {
			bse->FreeEffect( def );
			def->effect = NULL;
		}
		def->serviceTime = time - 1;
		def->updateFramenum = -1;
		def->newEffect = true;
		def->expired = false;
		def->referenceBounds.Clear();

		const float startTimeSeconds = static_cast<float>( time ) * 0.001f;
		if ( !bse->PlayEffect( def, startTimeSeconds ) ) {
			def->expired = true;
			return true;
		}
	}

	const float effectTimeSeconds = static_cast<float>( time ) * 0.001f;
	def->updateFramenum = tr.frameCount;
	if ( bse->ServiceEffect( def, effectTimeSeconds, effectTimeSeconds ) ) {
		if ( def->dynamicModel ) {
			delete def->dynamicModel;
			def->dynamicModel = NULL;
			def->dynamicModelFrameCount = 0;
		}
		def->expired = true;
		return true;
	}

	return def->expired;
}

void idRenderWorldLocal::FreeEffectDef(qhandle_t effectHandle) {
	if (effectHandle < 0 || effectHandle >= effectsDef.Num()) {
		return;
	}
	if (effectsDef[effectHandle] == NULL) {
		return;
	}

	if ( session->writeDemo && effectsDef[effectHandle]->archived ) {
		WriteFreeEffect( effectHandle );
	}

	if (effectsDef[effectHandle]->dynamicModel) {
		delete effectsDef[effectHandle]->dynamicModel;
		effectsDef[effectHandle]->dynamicModel = NULL;
		effectsDef[effectHandle]->dynamicModelFrameCount = 0;
	}

	bse->FreeEffect(effectsDef[effectHandle]);

	if (effectsDef[effectHandle] != NULL)
		delete effectsDef[effectHandle];
	
	effectsDef[effectHandle] = NULL;
}

void idRenderWorldLocal::StopEffectDef(qhandle_t effectHandle) { 
	if (effectHandle < 0 || effectHandle >= effectsDef.Num()) {
		return;
	}
	if (effectsDef[effectHandle] == NULL)
		return;

	if ( session->writeDemo ) {
		WriteStopEffect( effectHandle );
		effectsDef[effectHandle]->archived = false;
	}

	bse->StopEffect(effectsDef[effectHandle]);
}

const class rvRenderEffectLocal* idRenderWorldLocal::GetEffectDef(qhandle_t effectHandle) const { 
	if (effectHandle < 0 || effectHandle >= effectsDef.Num()) {
		return NULL;
	}
	return effectsDef[effectHandle]; 
}

bool idRenderWorldLocal::EffectDefHasSound(const renderEffect_s* reffect) { 
	return bse->CheckDefForSound(reffect); 
}

/*
===================
AddEntityDef
===================
*/
qhandle_t idRenderWorldLocal::AddEntityDef( const renderEntity_t *re ){
	// try and reuse a free spot
	int entityHandle = entityDefs.FindNull();
	if ( entityHandle == -1 ) {
		entityHandle = entityDefs.Append( NULL );
		if ( interactionTable && entityDefs.Num() > interactionTableWidth ) {
			ResizeInteractionTable();
		}
	}

	UpdateEntityDef( entityHandle, re );
	
	return entityHandle;
}

/*
==============
UpdateEntityDef

Does not write to the demo file, which will only be updated for
visible entities
==============
*/
int c_callbackUpdate;

void idRenderWorldLocal::UpdateEntityDef( qhandle_t entityHandle, const renderEntity_t *re ) {
	if ( r_skipUpdates.GetBool() ) {
		return;
	}

	tr.pc.c_entityUpdates++;

	if ( !re->hModel && !re->callback ) {
		common->Error( "idRenderWorld::UpdateEntityDef: NULL hModel" );
	}

	// create new slots if needed
	if ( entityHandle < 0 || entityHandle > LUDICROUS_INDEX ) {
		common->Error( "idRenderWorld::UpdateEntityDef: index = %i", entityHandle );
	}
	while ( entityHandle >= entityDefs.Num() ) {
		entityDefs.Append( NULL );
	}

	idRenderEntityLocal	*def = entityDefs[entityHandle];
	if ( def ) {

		if ( !re->forceUpdate ) {

			// check for exact match (OPTIMIZE: check through pointers more)
			if ( !re->joints && !re->callbackData && !def->dynamicModel && !memcmp( re, &def->parms, sizeof( *re ) ) ) {
				return;
			}

			// if the only thing that changed was shaderparms, we can just leave things as they are
			// after updating parms

			// if we have a callback function and the bounds, origin, axis and model match,
			// then we can leave the references as they are
			if ( re->callback ) {

				bool axisMatch = ( re->axis == def->parms.axis );
				bool originMatch = ( re->origin == def->parms.origin );
				bool boundsMatch = ( re->bounds == def->referenceBounds );
				bool modelMatch = ( re->hModel == def->parms.hModel );

				if ( boundsMatch && originMatch && axisMatch && modelMatch ) {
					// only clear the dynamic model and interaction surfaces if they exist
					c_callbackUpdate++;
					R_ClearEntityDefDynamicModel( def );
					def->parms = *re;
					return;
				}
			}
		}

		// a transform-only update (interpolated presentation frames, movers
		// carrying riders) leaves the model-space skinned snapshot intact, so
		// the renderer can skip regenerating it; interactions, shadow volumes,
		// and area references still rebuild against the new transform below
		bool keepDynamicModel = false;
		if ( r_useRepeatedStateReuse.GetBool()
			&& !re->forceUpdate
			&& !session->readDemo
			&& def->dynamicModel != NULL
			&& re->hModel != NULL
			&& re->hModel == def->parms.hModel
			&& re->hModel->IsDynamicModel() == DM_CACHED
			&& re->callback == NULL
			&& def->parms.callback == NULL
			&& re->callbackData == def->parms.callbackData
			&& re->customShader == def->parms.customShader
			&& re->customSkin == def->parms.customSkin
			&& re->referenceShader == def->parms.referenceShader
			&& re->suppressSurfaceMask == def->parms.suppressSurfaceMask
			&& re->suppressLOD == def->parms.suppressLOD
			&& re->numJoints == def->parms.numJoints
			&& re->joints == def->parms.joints
			&& memcmp( re->shaderParms, def->parms.shaderParms, sizeof( re->shaderParms ) ) == 0 ) {
			if ( re->joints == NULL ) {
				keepDynamicModel = true;
			} else if ( def->dynamicModelJointsHashValid
				&& R_HashJointMatrices( re->joints, re->numJoints ) == def->dynamicModelJointsHash ) {
				keepDynamicModel = true;
			}
			if ( keepDynamicModel ) {
				tr.pc.c_entitySnapshotsReused++;
			}
		}

		// save any decals if the model is the same, allowing marks to move with entities
		if ( def->parms.hModel == re->hModel ) {
			R_FreeEntityDefDerivedData( def, true, true, keepDynamicModel );
		} else {
			R_FreeEntityDefDerivedData( def, false, false );
		}
	} else {
		// creating a new one
		def = new idRenderEntityLocal;
		entityDefs[entityHandle] = def;

		def->world = this;
		def->index = entityHandle;
	}

	def->parms = *re;

	R_AxisToModelMatrix( def->parms.axis, def->parms.origin, def->modelMatrix );

	def->lastModifiedFrameNum = tr.frameCount;
	if ( session->writeDemo && def->archived ) {
		WriteFreeEntity( entityHandle );
		def->archived = false;
	}

	// optionally immediately issue any callbacks
	if ( !r_useEntityCallbacks.GetBool() && def->parms.callback ) {
		R_IssueEntityDefCallback( def );
	}

	// based on the model bounds, add references in each area
	// that may contain the updated surface
	R_CreateEntityRefs( def );
}

/*
===================
FreeEntityDef

Frees all references and lit surfaces from the model, and
NULL's out it's entry in the world list
===================
*/
void idRenderWorldLocal::FreeEntityDef( qhandle_t entityHandle ) {
	idRenderEntityLocal	*def;

	if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) {
		common->Printf( "idRenderWorld::FreeEntityDef: handle %i > %i\n", entityHandle, entityDefs.Num() );
		return;
	}

	def = entityDefs[entityHandle];
	if ( !def ) {
		common->Printf( "idRenderWorld::FreeEntityDef: handle %i is NULL\n", entityHandle );
		return;
	}

	R_FreeEntityDefDerivedData( def, false, false );

	if ( session->writeDemo && def->archived ) {
		WriteFreeEntity( entityHandle );
	}

	// if we are playing a demo, these will have been freed
	// in R_FreeEntityDefDerivedData(), otherwise the gui
	// object still exists in the game

	def->parms.gui[ 0 ] = NULL;
	def->parms.gui[ 1 ] = NULL;
	def->parms.gui[ 2 ] = NULL;
	if ( def->demoRemoteRenderView != NULL ) {
		delete def->demoRemoteRenderView;
		def->demoRemoteRenderView = NULL;
	}

	delete def;
	entityDefs[ entityHandle ] = NULL;
}

/*
==================
GetRenderEntity
==================
*/
const renderEntity_t *idRenderWorldLocal::GetRenderEntity( qhandle_t entityHandle ) const {
	idRenderEntityLocal	*def;

	if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) {
		common->Printf( "idRenderWorld::GetRenderEntity: invalid handle %i [0, %i]\n", entityHandle, entityDefs.Num() );
		return NULL;
	}

	def = entityDefs[entityHandle];
	if ( !def ) {
		common->Printf( "idRenderWorld::GetRenderEntity: handle %i is NULL\n", entityHandle );
		return NULL;
	}

	return &def->parms;
}

/*
==================
AddLightDef
==================
*/
qhandle_t idRenderWorldLocal::AddLightDef( const renderLight_t *rlight ) {
	// try and reuse a free spot
	int lightHandle = lightDefs.FindNull();

	if ( lightHandle == -1 ) {
		lightHandle = lightDefs.Append( NULL );
		if ( interactionTable && lightDefs.Num() > interactionTableHeight ) {
			ResizeInteractionTable();
		}
	}
	UpdateLightDef( lightHandle, rlight );

	return lightHandle;
}

/*
=================
UpdateLightDef

The generation of all the derived interaction data will
usually be deferred until it is visible in a scene

Does not write to the demo file, which will only be done for visible lights
=================
*/
void idRenderWorldLocal::UpdateLightDef( qhandle_t lightHandle, const renderLight_t *rlight ) {
	if ( r_skipUpdates.GetBool() ) {
		static bool s_warned = false;
		if ( !s_warned ) {
			common->Warning( "UpdateLightDef skipped due to r_skipUpdates=1" );
			s_warned = true;
		}
		return;
	}

	tr.pc.c_lightUpdates++;

	renderLight_t sanitizedLight = *rlight;
	sanitizedLight.prelightModel = R_SanitizePrelightModelPointer( sanitizedLight.prelightModel );
	rlight = &sanitizedLight;

	// create new slots if needed
	if ( lightHandle < 0 || lightHandle > LUDICROUS_INDEX ) {
		common->Error( "idRenderWorld::UpdateLightDef: index = %i", lightHandle );
	}
	while ( lightHandle >= lightDefs.Num() ) {
		lightDefs.Append( NULL );
	}

	bool justUpdate = false;
	idRenderLightLocal *light = lightDefs[lightHandle];
	if ( light ) {
		// if the shape of the light stays the same, we don't need to dump
		// any of our derived data, because shader parms are calculated every frame
		if ( rlight->axis == light->parms.axis && rlight->end == light->parms.end &&
			 rlight->lightCenter == light->parms.lightCenter && rlight->lightRadius == light->parms.lightRadius &&
			 rlight->noShadows == light->parms.noShadows && rlight->noDynamicShadows == light->parms.noDynamicShadows &&
			 rlight->origin == light->parms.origin &&
			 rlight->parallel == light->parms.parallel && rlight->pointLight == light->parms.pointLight &&
			 rlight->right == light->parms.right && rlight->start == light->parms.start &&
			 rlight->target == light->parms.target && rlight->up == light->parms.up && 
			 rlight->shader == light->lightShader && rlight->prelightModel == light->parms.prelightModel ) {
			justUpdate = true;
		} else {
			// if we are updating shadows, the prelight model is no longer valid
			light->lightHasMoved = true;
			R_FreeLightDefDerivedData( light );
		}
	} else {
		// create a new one
		light = new idRenderLightLocal;
		lightDefs[lightHandle] = light;

		light->world = this;
		light->index = lightHandle;
	}

	light->parms = *rlight;
	light->lastModifiedFrameNum = tr.frameCount;
	if ( session->writeDemo && light->archived ) {
		WriteFreeLight( lightHandle );
		light->archived = false;
	}

	if ( light->lightHasMoved ) {
		light->parms.prelightModel = NULL;
	}

	if (!justUpdate) {
		R_DeriveLightData( light );
		R_CreateLightRefs( light );
		R_CreateLightDefFogPortals( light );
	}
}

/*
====================
FreeLightDef

Frees all references and lit surfaces from the light, and
NULL's out it's entry in the world list
====================
*/
void idRenderWorldLocal::FreeLightDef( qhandle_t lightHandle ) {
	idRenderLightLocal	*light;

	if ( lightHandle < 0 || lightHandle >= lightDefs.Num() ) {
		common->Printf( "idRenderWorld::FreeLightDef: invalid handle %i [0, %i]\n", lightHandle, lightDefs.Num() );
		return;
	}

	light = lightDefs[lightHandle];
	if ( !light ) {
		common->Printf( "idRenderWorld::FreeLightDef: handle %i is NULL\n", lightHandle );
		return;
	}

	if ( session->writeDemo && light->archived ) {
		WriteFreeLight( lightHandle );
		light->archived = false;
	}

	if ( light->referencedFrameNum == tr.frameCount ) {
		deferredFreeLightDefs.Append( light );
		lightDefs[lightHandle] = NULL;
		return;
	}

	R_FreeLightDefDerivedData( light );
	delete light;
	lightDefs[lightHandle] = NULL;
}

/*
==================
GetRenderLight
==================
*/
const renderLight_t *idRenderWorldLocal::GetRenderLight( qhandle_t lightHandle ) const {
	idRenderLightLocal *def;

	if ( lightHandle < 0 || lightHandle >= lightDefs.Num() ) {
		common->Printf( "idRenderWorld::GetRenderLight: handle %i > %i\n", lightHandle, lightDefs.Num() );
		return NULL;
	}

	def = lightDefs[lightHandle];
	if ( !def ) {
		common->Printf( "idRenderWorld::GetRenderLight: handle %i is NULL\n", lightHandle );
		return NULL;
	}

	return &def->parms;
}

/*
================
idRenderWorldLocal::ProjectDecalOntoWorld
================
*/
void idRenderWorldLocal::ProjectDecalOntoWorld( const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) {
	int i, areas[10], numAreas;
	const areaReference_t *ref;
	const portalArea_t *area;
	const idRenderModel *model;
	idRenderEntityLocal *def;
	decalProjectionInfo_t info, localInfo;

	if ( !idRenderModelDecal::CreateProjectionInfo( info, winding, projectionOrigin, parallel, fadeDepth, material, startTime ) ) {
		return;
	}

	// get the world areas touched by the projection volume
	numAreas = BoundsInAreas( info.projectionBounds, areas, 10 );

	// check all areas for models
	for ( i = 0; i < numAreas; i++ ) {

		area = &portalAreas[ areas[i] ];

		// check all models in this area
		for ( ref = area->entityRefs.areaNext; ref != &area->entityRefs; ref = ref->areaNext ) {
			def = ref->entity;

			// completely ignore any dynamic or callback models
			model = def->parms.hModel;
			if ( model == NULL || model->IsDynamicModel() != DM_STATIC || def->parms.callback ) {
				continue;
			}

			if ( def->parms.customShader != NULL && !def->parms.customShader->AllowOverlays() ) {
				continue;
			}

			idBounds bounds;
			bounds.FromTransformedBounds( model->Bounds( &def->parms ), def->parms.origin, def->parms.axis );

			// if the model bounds do not overlap with the projection bounds
			if ( !info.projectionBounds.IntersectsBounds( bounds ) ) {
				continue;
			}

			// transform the bounding planes, fade planes and texture axis into local space
			idRenderModelDecal::GlobalProjectionInfoToLocal( localInfo, info, def->parms.origin, def->parms.axis );
			localInfo.force = ( def->parms.customShader != NULL );

			if ( !def->decals ) {
				def->decals = idRenderModelDecal::Alloc();
			}
			def->decals->CreateDecal( model, localInfo );
		}
	}
}

/*
====================
idRenderWorldLocal::ProjectDecal
====================
*/
void idRenderWorldLocal::ProjectDecal( qhandle_t entityHandle, const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) {
	decalProjectionInfo_t info, localInfo;

	if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) {
		common->Error( "idRenderWorld::ProjectOverlay: index = %i", entityHandle );
		return;
	}

	idRenderEntityLocal	*def = entityDefs[ entityHandle ];
	if ( !def ) {
		return;
	}

	const idRenderModel *model = def->parms.hModel;

	if ( model == NULL || model->IsDynamicModel() != DM_STATIC || def->parms.callback ) {
		return;
	}

	if ( !idRenderModelDecal::CreateProjectionInfo( info, winding, projectionOrigin, parallel, fadeDepth, material, startTime ) ) {
		return;
	}

	idBounds bounds;
	bounds.FromTransformedBounds( model->Bounds( &def->parms ), def->parms.origin, def->parms.axis );

	// if the model bounds do not overlap with the projection bounds
	if ( !info.projectionBounds.IntersectsBounds( bounds ) ) {
		return;
	}

	// transform the bounding planes, fade planes and texture axis into local space
	idRenderModelDecal::GlobalProjectionInfoToLocal( localInfo, info, def->parms.origin, def->parms.axis );
	localInfo.force = ( def->parms.customShader != NULL );

	if ( def->decals == NULL ) {
		def->decals = idRenderModelDecal::Alloc();
	}
	def->decals->CreateDecal( model, localInfo );
}

/*
====================
idRenderWorldLocal::ProjectOverlay
====================
*/
void idRenderWorldLocal::ProjectOverlay( qhandle_t entityHandle, const idPlane localTextureAxis[2], const idMaterial *material ) {

	if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) {
		common->Error( "idRenderWorld::ProjectOverlay: index = %i", entityHandle );
		return;
	}

	idRenderEntityLocal	*def = entityDefs[ entityHandle ];
	if ( !def ) {
		return;
	}

	const renderEntity_t *refEnt = &def->parms;

	idRenderModel *model = refEnt->hModel;
	if ( model == NULL || model->IsDynamicModel() != DM_CACHED ) {	// FIXME: probably should be MD5 only
		return;
	}
	model = R_EntityDefDynamicModel( def, false );
	if ( model == NULL ) {
		return;
	}

	if ( def->overlay == NULL ) {
		def->overlay = idRenderModelOverlay::Alloc();
	}
	def->overlay->CreateOverlay( model, localTextureAxis, material );

	// the overlay surfaces are baked into the dynamic snapshot when it is
	// generated; clear it so the new overlay appears on the next rendered
	// frame even when the snapshot would otherwise be reused
	R_ClearEntityDefDynamicModel( def );
}

/*
====================
idRenderWorldLocal::RemoveDecals
====================
*/
void idRenderWorldLocal::RemoveDecals( qhandle_t entityHandle ) {
	if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) {
		common->Error( "idRenderWorld::ProjectOverlay: index = %i", entityHandle );
		return;
	}

	idRenderEntityLocal	*def = entityDefs[ entityHandle ];
	if ( !def ) {
		return;
	}

	R_FreeEntityDefDecals( def );
	R_FreeEntityDefOverlay( def );
}

/*
====================
SetRenderView

Sets the current view so any calls to the render world will use the correct parms.
====================
*/
void idRenderWorldLocal::SetRenderView( const renderView_t *renderView ) {
	tr.primaryRenderView = *renderView;
}

/*
====================
RenderScene

Draw a 3D view into a part of the window, then return
to 2D drawing.

Rendering a scene may require multiple views to be rendered
to handle mirrors,
====================
*/
void idRenderWorldLocal::RenderScene( const renderView_t *renderView, int renderFlags ) {
#ifndef	ID_DEDICATED
	renderView_t	copy;

	if ( !glConfig.isInitialized ) {
		return;
	}

	copy = *renderView;

	// skip front end rendering work, which will result
	// in only gui drawing
	if ( r_skipFrontEnd.GetBool() ) {
		return;
	}

	if ( renderView->fov_x <= 0 || renderView->fov_y <= 0 ) {
		common->Error( "idRenderWorld::RenderScene: bad FOVs: %f, %f", renderView->fov_x, renderView->fov_y );
	}

	// close any gui drawing
	tr.guiModel->EmitFullScreen();
	tr.guiModel->Clear();

	int startTime = Sys_Milliseconds();
	if ( tr.lastRenderTimeMsec > 0 ) {
		const int elapsedMsec = startTime - tr.lastRenderTimeMsec;
		tr.deltaTime = ( elapsedMsec > 0 ? elapsedMsec : 0 ) * 0.001f;
	} else {
		tr.deltaTime = 0.0f;
	}
	tr.lastRenderTimeMsec = startTime;

	// setup view parms for the initial view
	//
	viewDef_t		*parms = (viewDef_t *)R_ClearedFrameAlloc( sizeof( *parms ) );
	parms->renderView = *renderView;
	parms->renderFlags = renderFlags;

	if ( tr.takingScreenshot ) {
		parms->renderView.forceUpdate = true;
	}

	// set up viewport, adjusted for resolution and OpenGL style 0 at the bottom
	tr.RenderViewToViewport( &parms->renderView, &parms->viewport );

	// the scissor bounds may be shrunk in subviews even if
	// the viewport stays the same
	// this scissor range is local inside the viewport
	parms->scissor.x1 = 0;
	parms->scissor.y1 = 0;
	parms->scissor.x2 = parms->viewport.x2 - parms->viewport.x1;
	parms->scissor.y2 = parms->viewport.y2 - parms->viewport.y1;


	parms->isSubview = false;
	parms->initialViewAreaOrigin = renderView->vieworg;
	parms->floatTime = parms->renderView.time * 0.001f;
	parms->renderWorld = this;

	// use this time for any subsequent 2D rendering, so damage blobs/etc 
	// can use level time
	tr.SetFrameShaderTime( parms->renderView.time );

	// see if the view needs to reverse the culling sense in mirrors
	// or environment cube sides
	idVec3	cross;
	cross = parms->renderView.viewaxis[1].Cross( parms->renderView.viewaxis[2] );
	if ( cross * parms->renderView.viewaxis[0] > 0 ) {
		parms->isMirror = false;
	} else {
		parms->isMirror = true;
	}

	if ( r_lockSurfaces.GetBool() ) {
		R_LockSurfaceScene( parms );
		return;
	}

	// save this world for use by some console commands
	tr.primaryWorld = this;
	tr.primaryRenderView = *renderView;
	tr.primaryView = parms;

	// rendering this view may cause other views to be rendered
	// for mirrors / portals / shadows / environment maps
	// this will also cause any necessary entities and lights to be
	// updated to the demo file
	R_RenderView( parms );

	// now write delete commands for any modified-but-not-visible entities, and
	// add the renderView command to the demo
	if ( session->writeDemo ) {
		WriteRenderView( renderView );
	}

#if 0
	for ( int i = 0 ; i < entityDefs.Num() ; i++ ) {
		idRenderEntityLocal	*def = entityDefs[i];
		if ( !def ) {
			continue;
		}
		if ( def->parms.callback ) {
			continue;
		}
		if ( def->parms.hModel->IsDynamicModel() == DM_CONTINUOUS ) {
		}
	}
#endif

	int endTime = Sys_Milliseconds();

	tr.pc.frontEndMsec += endTime - startTime;

	// prepare for any 2D drawing after this
	tr.guiModel->Clear();
#endif
}

/*
===================
NumAreas
===================
*/
int idRenderWorldLocal::NumAreas( void ) const {
	return numPortalAreas;
}

/*
===================
NumPortalsInArea
===================
*/
int idRenderWorldLocal::NumPortalsInArea( int areaNum ) {
	portalArea_t	*area;
	int				count;
	portal_t		*portal;

	if ( areaNum >= numPortalAreas || areaNum < 0 ) {
		common->Error( "idRenderWorld::NumPortalsInArea: bad areanum %i", areaNum );
	}
	area = &portalAreas[areaNum];

	count = 0;
	for ( portal = area->portals ; portal ; portal = portal->next ) {
		count++;
	}
	return count;
}

/*
===================
GetPortal
===================
*/
exitPortal_t idRenderWorldLocal::GetPortal( int areaNum, int portalNum ) {
	portalArea_t	*area;
	int				count;
	portal_t		*portal;
	exitPortal_t	ret;

	if ( areaNum > numPortalAreas ) {
		common->Error( "idRenderWorld::GetPortal: areaNum > numAreas" );
	}
	area = &portalAreas[areaNum];

	count = 0;
	for ( portal = area->portals ; portal ; portal = portal->next ) {
		if ( count == portalNum ) {
			ret.areas[0] = areaNum;
			ret.areas[1] = portal->intoArea;
			ret.w = portal->w;
			ret.blockingBits = portal->doublePortal->blockingBits;
			ret.portalHandle = portal->doublePortal - doublePortals + 1;
			return ret;
		}
		count++;
	}

	common->Error( "idRenderWorld::GetPortal: portalNum > numPortals" );

	memset( &ret, 0, sizeof( ret ) );
	return ret;
}

/*
===============
PointInAreaNum

Will return -1 if the point is not in an area, otherwise
it will return 0 <= value < tr.world->numPortalAreas
===============
*/
int idRenderWorldLocal::PointInArea( const idVec3 &point ) const {
	areaNode_t	*node;
	int			nodeNum;
	float		d;
	
	node = areaNodes;
	if ( !node ) {
		return -1;
	}
	while( 1 ) {
		d = point * node->plane.Normal() + node->plane[3];
		if (d > 0) {
			nodeNum = node->children[0];
		} else {
			nodeNum = node->children[1];
		}
		if ( nodeNum == 0 ) {
			return -1;		// in solid
		}
		if ( nodeNum < 0 ) {
			nodeNum = -1 - nodeNum;
			if ( nodeNum >= numPortalAreas ) {
				common->Error( "idRenderWorld::PointInArea: area out of range" );
			}
			return nodeNum;
		}
		node = areaNodes + nodeNum;
	}
	
	return -1;
}

/*
===================
BoundsInAreas_r
===================
*/
void idRenderWorldLocal::BoundsInAreas_r( int nodeNum, const idBounds &bounds, int *areas, int *numAreas, int maxAreas ) const {
	int side, i;
	areaNode_t *node;

	do {
		if ( nodeNum < 0 ) {
			nodeNum = -1 - nodeNum;

			for ( i = 0; i < (*numAreas); i++ ) {
				if ( areas[i] == nodeNum ) {
					break;
				}
			}
			if ( i >= (*numAreas) && (*numAreas) < maxAreas ) {
				areas[(*numAreas)++] = nodeNum;
			}
			return;
		}

		node = areaNodes + nodeNum;

		side = bounds.PlaneSide( node->plane );
		if ( side == PLANESIDE_FRONT ) {
			nodeNum = node->children[0];
		}
		else if ( side == PLANESIDE_BACK ) {
			nodeNum = node->children[1];
		}
		else {
			if ( node->children[1] != 0 ) {
				BoundsInAreas_r( node->children[1], bounds, areas, numAreas, maxAreas );
				if ( (*numAreas) >= maxAreas ) {
					return;
				}
			}
			nodeNum = node->children[0];
		}
	} while( nodeNum != 0 );

	return;
}

/*
===================
BoundsInAreas

  fills the *areas array with the number of the areas the bounds are in
  returns the total number of areas the bounds are in
===================
*/
int idRenderWorldLocal::BoundsInAreas( const idBounds &bounds, int *areas, int maxAreas ) const {
	int numAreas = 0;

	assert( areas );
	assert( bounds[0][0] <= bounds[1][0] && bounds[0][1] <= bounds[1][1] && bounds[0][2] <= bounds[1][2] );
	assert( bounds[1][0] - bounds[0][0] < 1e4f && bounds[1][1] - bounds[0][1] < 1e4f && bounds[1][2] - bounds[0][2] < 1e4f );

	if ( !areaNodes ) {
		return numAreas;
	}
	BoundsInAreas_r( 0, bounds, areas, &numAreas, maxAreas );
	return numAreas;
}

/*
================
GuiTrace

checks a ray trace against any gui surfaces in an entity, returning the
fraction location of the trace on the gui surface, or -1,-1 if no hit.
this doesn't do any occlusion testing, simply ignoring non-gui surfaces.
start / end are in global world coordinates.
================
*/
static idRenderModel *R_GuiTraceModelForEntity( idRenderEntityLocal *def ) {
	idRenderModel *model = def->parms.hModel;
	if ( model == NULL ) {
		return NULL;
	}

	if ( model->IsDynamicModel() == DM_STATIC ) {
		return def->parms.callback != NULL ? NULL : model;
	}

	return R_EntityDefDynamicModel( def );
}

guiPoint_t	idRenderWorldLocal::GuiTrace( qhandle_t entityHandle, const idVec3 start, const idVec3 end ) const {
	localTrace_t	local;
	localTrace_t	bestLocal;
	idVec3			localStart, localEnd;
	idVec3			bestOrigin, bestAxis[3];
	int				j;
	idRenderModel	*model;
	srfTriangles_t	*tri;
	const idMaterial *shader;
	const idMaterial *bestShader;
	float			bestAxisLen[2];
	guiPoint_t	pt;

	pt.x = pt.y = -1;
	pt.guiId = 0;
	pt.fraction = 1.0f;

	if ( ( entityHandle < 0 ) || ( entityHandle >= entityDefs.Num() ) ) {
		common->Printf( "idRenderWorld::GuiTrace: invalid handle %i\n", entityHandle );
		return pt;
	}

	idRenderEntityLocal *def = entityDefs[entityHandle];	
	if ( !def ) {
		common->Printf( "idRenderWorld::GuiTrace: handle %i is NULL\n", entityHandle );
		return pt;
	}

	model = R_GuiTraceModelForEntity( def );
	if ( model == NULL ) {
		return pt;
	}

	// transform the points into local space
	R_GlobalPointToLocal( def->modelMatrix, start, localStart );
	R_GlobalPointToLocal( def->modelMatrix, end, localEnd );


	float best = 1.0f;
	bestShader = NULL;

	const int surfaceCount = model->NumSurfaces();
	for ( j = 0 ; j < surfaceCount ; j++ ) {
		const modelSurface_t *surf = model->Surface( j );

		tri = surf->geometry;
		if ( !tri ) {
			continue;
		}

		shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader );
		if ( !shader ) {
			continue;
		}
		// only trace against gui surfaces
		if (!shader->HasGui()) {
			continue;
		}

		local = R_LocalTrace( localStart, localEnd, 0.0f, tri );
		if ( local.fraction < best ) {
			idVec3				origin, axis[3];
			float				axisLen[2];

			R_SurfaceToTextureAxis( tri, origin, axis );

			axisLen[0] = axis[0].Length();
			axisLen[1] = axis[1].Length();
			if ( axisLen[0] <= 0.0f || axisLen[1] <= 0.0f ) {
				continue;
			}

			best = local.fraction;
			bestLocal = local;
			bestShader = shader;
			bestOrigin = origin;
			bestAxis[0] = axis[0];
			bestAxis[1] = axis[1];
			bestAxisLen[0] = axisLen[0];
			bestAxisLen[1] = axisLen[1];
		}
	}

	if ( bestShader != NULL ) {
		const idVec3 cursor = bestLocal.point - bestOrigin;

		pt.x = ( cursor * bestAxis[0] ) / ( bestAxisLen[0] * bestAxisLen[0] );
		pt.y = ( cursor * bestAxis[1] ) / ( bestAxisLen[1] * bestAxisLen[1] );
		pt.guiId = bestShader->GetEntityGui();
		pt.fraction = bestLocal.fraction;
	}

	return pt;
}

/*
================
R_GuiTraceProbe_f

Diagnostic command: replicates the game-side in-world GUI focus chain (entity
gui pointer, IsInteractive, GuiTrace gate, and an actual trace through the
center of every gui surface) against the primary render world so in-world GUI
interactivity can be validated from a scripted map load. The optional 'click'
argument additionally drives the same park/move/click event sequence
idPlayer::UpdateFocus and Weapon_GUI send and reports what each interactive
gui returns; note that clicking executes the gui's onAction side effects.
================
*/
void R_GuiTraceProbe_f( const idCmdArgs &args ) {
	idRenderWorldLocal *world = tr.primaryWorld;
	if ( world == NULL ) {
		common->Printf( "guiTraceProbe: no primary render world\n" );
		return;
	}

	int guiEntities = 0;
	int guiSurfaces = 0;
	int hits = 0;

	const int entityCount = world->entityDefs.Num();
	for ( int i = 0; i < entityCount; i++ ) {
		idRenderEntityLocal *def = world->entityDefs[i];
		if ( def == NULL ) {
			continue;
		}
		const idRenderModel *sourceModel = def->parms.hModel;
		idRenderModel *model = R_GuiTraceModelForEntity( def );
		if ( model == NULL ) {
			continue;
		}

		bool reported = false;
		const int surfaceCount = model->NumSurfaces();
		for ( int j = 0; j < surfaceCount; j++ ) {
			const modelSurface_t *surf = model->Surface( j );
			if ( surf == NULL || surf->geometry == NULL ) {
				continue;
			}
			const idMaterial *shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader );
			if ( shader == NULL || !shader->HasGui() ) {
				continue;
			}

			const srfTriangles_t *tri = surf->geometry;

			if ( !reported ) {
				reported = true;
				guiEntities++;
				int guiNum = shader->GetEntityGui() - 1;
				idUserInterface *gui = ( guiNum >= 0 && guiNum < MAX_RENDERENTITY_GUI ) ? def->parms.gui[guiNum] : NULL;
				common->Printf( "guiTraceProbe: handle %d model '%s' traceModel '%s' dynamic=%d callback=%d gui='%s' interactive=%d\n",
					i, sourceModel != NULL ? sourceModel->Name() : "NULL", model->Name(),
					sourceModel != NULL ? (int)sourceModel->IsDynamicModel() : -1, def->parms.callback != NULL,
					gui != NULL ? gui->Name() : "NULL",
					gui != NULL ? (int)gui->IsInteractive() : -1 );
			}

			if ( tri->verts == NULL || tri->numVerts <= 0 || tri->numIndexes < 3 ) {
				common->Printf( "guiTraceProbe:   surf %d shader '%s': NO CPU VERTS (verts=%p numVerts=%d numIndexes=%d)\n",
					j, shader->GetName(), tri->verts, tri->numVerts, tri->numIndexes );
				continue;
			}
			guiSurfaces++;

			// trace through the centroid of the first triangle along its normal
			const idVec3 a = tri->verts[ tri->indexes[0] ].xyz;
			const idVec3 b = tri->verts[ tri->indexes[1] ].xyz;
			const idVec3 c = tri->verts[ tri->indexes[2] ].xyz;
			const idVec3 center = ( a + b + c ) * ( 1.0f / 3.0f );
			idPlane plane;
			plane.FromPoints( a, b, c );
			const idVec3 localStart = center + plane.Normal() * 8.0f;
			const idVec3 localEnd = center - plane.Normal() * 8.0f;

			idVec3 start, end;
			R_LocalPointToGlobal( def->modelMatrix, localStart, start );
			R_LocalPointToGlobal( def->modelMatrix, localEnd, end );

			const guiPoint_t pt = world->GuiTrace( i, start, end );
			if ( pt.x != -1.0f ) {
				hits++;
			}
			common->Printf( "guiTraceProbe:   surf %d shader '%s' entityGui=%d -> pt=(%.3f %.3f) guiId=%d fraction=%.3f\n",
				j, shader->GetName(), shader->GetEntityGui(), pt.x, pt.y, pt.guiId, pt.fraction );

			// optional 'click' mode: drive the same event sequence the game's
			// focus/click path sends and report what the gui returns
			if ( pt.x != -1.0f && idStr::Icmp( args.Argv( 1 ), "click" ) == 0 ) {
				int guiNum = shader->GetEntityGui() - 1;
				idUserInterface *clickGui = ( guiNum >= 0 && guiNum < MAX_RENDERENTITY_GUI ) ? def->parms.gui[guiNum] : NULL;
				if ( clickGui != NULL && clickGui->IsInteractive() ) {
					const int now = Sys_Milliseconds();
					sysEvent_t ev = sys->GenerateMouseMoveEvent( -2000, -2000 );
					const char *moveCmd1 = clickGui->HandleEvent( &ev, now );
					ev = sys->GenerateMouseMoveEvent( pt.x * 640.0f, pt.y * 480.0f );
					const char *moveCmd2 = clickGui->HandleEvent( &ev, now );
					ev = sys->GenerateMouseButtonEvent( 1, true );
					idStr downCmd = clickGui->HandleEvent( &ev, now );
					ev = sys->GenerateMouseButtonEvent( 1, false );
					idStr upCmd = clickGui->HandleEvent( &ev, now );
					common->Printf( "guiTraceProbe:   CLICK cursor=(%.1f %.1f) move1='%s' move2='%s' down='%s' up='%s' interactiveAfter=%d\n",
						clickGui->CursorX(), clickGui->CursorY(),
						moveCmd1 ? moveCmd1 : "", moveCmd2 ? moveCmd2 : "",
						downCmd.c_str(), upCmd.c_str(), (int)clickGui->IsInteractive() );
				}
			}
		}
	}

	common->Printf( "guiTraceProbe: %d gui entities, %d traceable gui surfaces, %d trace hits\n",
		guiEntities, guiSurfaces, hits );
}

/*
===================
idRenderWorldLocal::ModelTrace
===================
*/
static const rvDeclMatType *R_GetMaterialTypeForTrace( const idMaterial *shader, const srfTriangles_t *tri, const localTrace_t &localTrace ) {
	if ( shader == NULL ) {
		return NULL;
	}

	if ( shader->GetMaterialTypeArray() == NULL || tri == NULL || tri->verts == NULL ) {
		return shader->GetMaterialType();
	}

	const idVec3 points[3] = {
		tri->verts[ localTrace.indexes[0] ].xyz,
		tri->verts[ localTrace.indexes[1] ].xyz,
		tri->verts[ localTrace.indexes[2] ].xyz
	};
	const idVec2 texCoords[3] = {
		tri->verts[ localTrace.indexes[0] ].st,
		tri->verts[ localTrace.indexes[1] ].st,
		tri->verts[ localTrace.indexes[2] ].st
	};
	const float area = idMath::BarycentricTriangleArea( localTrace.normal, points[0], points[1], points[2] );
	if ( area == 0.0f ) {
		return shader->GetMaterialType();
	}

	idVec2 collisionTexCoord;
	idMath::BarycentricEvaluate( collisionTexCoord, localTrace.point, localTrace.normal, area, points, texCoords );
	return shader->GetMaterialType( collisionTexCoord );
}

bool idRenderWorldLocal::ModelTrace( modelTrace_t &trace, qhandle_t entityHandle, const idVec3 &start, const idVec3 &end, const float radius ) const {
	int i;
	bool collisionSurface;
	const modelSurface_t *surf;
	localTrace_t localTrace;
	idRenderModel *model;
	float modelMatrix[16];
	idVec3 localStart, localEnd;
	const idMaterial *shader;

	trace.fraction = 1.0f;

// jmarshall
	trace.materialType = NULL;
// jmarshall end

	if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) {
//		common->Error( "idRenderWorld::ModelTrace: index = %i", entityHandle );
		return false;
	}

	idRenderEntityLocal	*def = entityDefs[entityHandle];
	if ( !def ) {
		return false;
	}

	renderEntity_t *refEnt = &def->parms;

	model = R_EntityDefDynamicModel( def );
	if ( !model ) {
		return false;
	}

	// transform the points into local space
	R_AxisToModelMatrix( refEnt->axis, refEnt->origin, modelMatrix );
	R_GlobalPointToLocal( modelMatrix, start, localStart );
	R_GlobalPointToLocal( modelMatrix, end, localEnd );

	// if we have explicit collision surfaces, only collide against them
	// (FIXME, should probably have a parm to control this)
	collisionSurface = false;
	const int baseSurfaceCount = model->NumBaseSurfaces();
	for ( i = 0; i < baseSurfaceCount; i++ ) {
		surf = model->Surface( i );

		shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader );

		if ( shader->GetSurfaceFlags() & SURF_COLLISION ) {
			collisionSurface = true;
			break;
		}
	}

	// only use baseSurfaces, not any overlays
	for ( i = 0; i < baseSurfaceCount; i++ ) {
		surf = model->Surface( i );

		shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader );

		if ( !surf->geometry || !shader ) {
			continue;
		}

		if ( collisionSurface ) {
			// only trace vs collision surfaces
			if ( !( shader->GetSurfaceFlags() & SURF_COLLISION ) ) {
				continue;
			}
		} else {
			// skip if not drawn or translucent
			if ( !shader->IsDrawn() || ( shader->Coverage() != MC_OPAQUE && shader->Coverage() != MC_PERFORATED ) ) {
				continue;
			}
		}

		localTrace = R_LocalTrace( localStart, localEnd, radius, surf->geometry );

		if ( localTrace.fraction < trace.fraction ) {
			trace.fraction = localTrace.fraction;
			R_LocalPointToGlobal( modelMatrix, localTrace.point, trace.point );
			trace.normal = localTrace.normal * refEnt->axis;
			trace.material = shader;
			trace.entity = &def->parms;
// jmarshall
			trace.materialType = R_GetMaterialTypeForTrace( trace.material, surf->geometry, localTrace );
// jmarshall end
			trace.jointNumber = refEnt->hModel->NearestJoint( i, localTrace.indexes[0], localTrace.indexes[1], localTrace.indexes[2] );
		}
	}

	return ( trace.fraction < 1.0f );
}

/*
===================
idRenderWorldLocal::Trace
===================
*/
// FIXME: _D3XP added those.
const char* playerModelExcludeList[] = {
	"models/md5/characters/player/d3xp_spplayer.md5mesh",
	"models/md5/characters/player/head/d3xp_head.md5mesh",
	"models/md5/weapons/pistol_world/worldpistol.md5mesh",
	NULL
};

const char* playerMaterialExcludeList[] = {
	"muzzlesmokepuff",
	NULL
};

bool idRenderWorldLocal::Trace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, bool skipDynamic, bool skipPlayer /*_D3XP*/ ) const {
	areaReference_t * ref;
	idRenderEntityLocal *def;
	portalArea_t * area;
	idRenderModel * model;
	srfTriangles_t * tri;
	localTrace_t localTrace;
	int areas[128], numAreas, i, j, numSurfaces;
	idBounds traceBounds, bounds;
	float modelMatrix[16];
	idVec3 localStart, localEnd;
	const idMaterial *shader;

	trace.fraction = 1.0f;
	trace.point = end;
	trace.materialType = NULL;

	// bounds for the whole trace
	traceBounds.Clear();
	traceBounds.AddPoint( start );
	traceBounds.AddPoint( end );

	// get the world areas the trace is in
	numAreas = BoundsInAreas( traceBounds, areas, 128 );

	numSurfaces = 0;

	// check all areas for models
	for ( i = 0; i < numAreas; i++ ) {

		area = &portalAreas[ areas[i] ];

		// check all models in this area
		for ( ref = area->entityRefs.areaNext; ref != &area->entityRefs; ref = ref->areaNext ) {
			def = ref->entity;

			model = def->parms.hModel;
			if ( !model ) {
				continue;
			}

			if ( model->IsDynamicModel() != DM_STATIC ) {
				if ( skipDynamic ) {
					continue;
				}

#if 1	/* _D3XP addition. could use a cleaner approach */
				if ( skipPlayer ) {
					idStr name = model->Name();
					const char *exclude;
					int k;

					for ( k = 0; playerModelExcludeList[k]; k++ ) {
						exclude = playerModelExcludeList[k];
						if ( name == exclude ) {
							break;
						}
					}

					if ( playerModelExcludeList[k] ) {
						continue;
					}
				}
#endif

				model = R_EntityDefDynamicModel( def );
				if ( !model ) {
					continue;	// can happen with particle systems, which don't instantiate without a valid view
				}
			}

			bounds.FromTransformedBounds( model->Bounds( &def->parms ), def->parms.origin, def->parms.axis );

			// if the model bounds do not overlap with the trace bounds
			if ( !traceBounds.IntersectsBounds( bounds ) || !bounds.LineIntersection( start, trace.point ) ) {
				continue;
			}

			// check all model surfaces
			const int surfaceCount = model->NumSurfaces();
			for ( j = 0; j < surfaceCount; j++ ) {
				const modelSurface_t *surf = model->Surface( j );

				shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader );

				// if no geometry or no shader
				if ( !surf->geometry || !shader ) {
					continue;
				}

#if 1 /* _D3XP addition. could use a cleaner approach */
				if ( skipPlayer ) {
					idStr name = shader->GetName();
					const char *exclude;
					int k;

					for ( k = 0; playerMaterialExcludeList[k]; k++ ) {
						exclude = playerMaterialExcludeList[k];
						if ( name == exclude ) {
							break;
						}
					}

					if ( playerMaterialExcludeList[k] ) {
						continue;
					}
				}
#endif

				tri = surf->geometry;

				bounds.FromTransformedBounds( tri->bounds, def->parms.origin, def->parms.axis );

				// if triangle bounds do not overlap with the trace bounds
				if ( !traceBounds.IntersectsBounds( bounds ) || !bounds.LineIntersection( start, trace.point ) ) {
					continue;
				}

				numSurfaces++;

				// transform the points into local space
				R_AxisToModelMatrix( def->parms.axis, def->parms.origin, modelMatrix );
				R_GlobalPointToLocal( modelMatrix, start, localStart );
				R_GlobalPointToLocal( modelMatrix, end, localEnd );

				localTrace = R_LocalTrace( localStart, localEnd, radius, surf->geometry );

				if ( localTrace.fraction < trace.fraction ) {
					trace.fraction = localTrace.fraction;
					R_LocalPointToGlobal( modelMatrix, localTrace.point, trace.point );
					trace.normal = localTrace.normal * def->parms.axis;
					trace.material = shader;
					trace.materialType = R_GetMaterialTypeForTrace( trace.material, surf->geometry, localTrace );
					trace.entity = &def->parms;
					// Dynamic snapshots don't own joint-weight metadata; ask the source model.
					trace.jointNumber = def->parms.hModel->NearestJoint( j, localTrace.indexes[0], localTrace.indexes[1], localTrace.indexes[2] );

					traceBounds.Clear();
					traceBounds.AddPoint( start );
					traceBounds.AddPoint( start + trace.fraction * (end - start) );
				}
			}
		}
	}
	return ( trace.fraction < 1.0f );
}

/*
==================
idRenderWorldLocal::RecurseProcBSP
==================
*/
void idRenderWorldLocal::RecurseProcBSP_r( modelTrace_t *results, int parentNodeNum, int nodeNum, float p1f, float p2f, const idVec3 &p1, const idVec3 &p2 ) const {
	float		t1, t2;
	float		frac;
	idVec3		mid;
	int			side;
	float		midf;
	areaNode_t *node;

	if ( results->fraction <= p1f) {
		return;		// already hit something nearer
	}
	// empty leaf
	if ( nodeNum < 0 ) {
		return;
	}
	// if solid leaf node
	if ( nodeNum == 0 ) {
		if ( parentNodeNum != -1 ) {

			results->fraction = p1f;
			results->point = p1;
			node = &areaNodes[parentNodeNum];
			results->normal = node->plane.Normal();
			return;
		}
	}
	node = &areaNodes[nodeNum];

	// distance from plane for trace start and end
	t1 = node->plane.Normal() * p1 + node->plane[3];
	t2 = node->plane.Normal() * p2 + node->plane[3];

	if ( t1 >= 0.0f && t2 >= 0.0f ) {
		RecurseProcBSP_r( results, nodeNum, node->children[0], p1f, p2f, p1, p2 );
		return;
	}
	if ( t1 < 0.0f && t2 < 0.0f ) {
		RecurseProcBSP_r( results, nodeNum, node->children[1], p1f, p2f, p1, p2 );
		return;
	}
	side = t1 < t2;
	frac = t1 / (t1 - t2);
	midf = p1f + frac*(p2f - p1f);
	mid[0] = p1[0] + frac*(p2[0] - p1[0]);
	mid[1] = p1[1] + frac*(p2[1] - p1[1]);
	mid[2] = p1[2] + frac*(p2[2] - p1[2]);
	RecurseProcBSP_r( results, nodeNum, node->children[side], p1f, midf, p1, mid );
	RecurseProcBSP_r( results, nodeNum, node->children[side^1], midf, p2f, mid, p2 );
}

/*
==================
idRenderWorldLocal::FastWorldTrace
==================
*/
bool idRenderWorldLocal::FastWorldTrace( modelTrace_t &results, const idVec3 &start, const idVec3 &end ) const {
	memset( &results, 0, sizeof( modelTrace_t ) );
	results.fraction = 1.0f;
	if ( areaNodes != NULL ) {
		RecurseProcBSP_r( &results, -1, 0, 0.0f, 1.0f, start, end );
		return ( results.fraction < 1.0f );
	}
	return false;
}

/*
=================================================================================

CREATE MODEL REFS

=================================================================================
*/

/*
=================
AddEntityRefToArea

This is called by R_PushVolumeIntoTree and also directly
for the world model references that are precalculated.
=================
*/
void idRenderWorldLocal::AddEntityRefToArea( idRenderEntityLocal *def, portalArea_t *area ) {
	areaReference_t	*ref;

	if ( !def ) {
		common->Error( "idRenderWorldLocal::AddEntityRefToArea: NULL def" );
	}

	ref = areaReferenceAllocator.Alloc();

	tr.pc.c_entityReferences++;

	ref->entity = def;

	// link to entityDef
	ref->ownerNext = def->entityRefs;
	def->entityRefs = ref;

	// link to end of area list
	ref->area = area;
	ref->areaNext = &area->entityRefs;
	ref->areaPrev = area->entityRefs.areaPrev;
	ref->areaNext->areaPrev = ref;
	ref->areaPrev->areaNext = ref;
}

/*
===================
AddLightRefToArea

===================
*/
void idRenderWorldLocal::AddLightRefToArea( idRenderLightLocal *light, portalArea_t *area ) {
	areaReference_t	*lref;

	// add a lightref to this area
	lref = areaReferenceAllocator.Alloc();
	lref->light = light;
	lref->area = area;
	lref->ownerNext = light->references;
	light->references = lref;
	tr.pc.c_lightReferences++;

	// doubly linked list so we can free them easily later
	area->lightRefs.areaNext->areaPrev = lref;
	lref->areaNext = area->lightRefs.areaNext;
	lref->areaPrev = &area->lightRefs;
	area->lightRefs.areaNext = lref;
}

/*
===================
GenerateAllInteractions

Force the generation of all light / surface interactions at the start of a level
If this isn't called, they will all be dynamically generated

This really isn't all that helpful anymore, because the calculation of shadows
and light interactions is deferred from idRenderWorldLocal::CreateLightDefInteractions(), but we
use it as an oportunity to size the interactionTable
===================
*/
void idRenderWorldLocal::GenerateAllInteractions() {
	if ( !glConfig.isInitialized ) {
		return;
	}

	int start = Sys_Milliseconds();

	generateAllInteractionsCalled = false;

	// watch how much memory we allocate
	tr.staticAllocCount = 0;

	// let idRenderWorldLocal::CreateLightDefInteractions() know that it shouldn't
	// try and do any view specific optimizations
	tr.viewDef = NULL;

	for ( int i = 0 ; i < this->lightDefs.Num() ; i++ ) {
		idRenderLightLocal	*ldef = this->lightDefs[i];
		if ( !ldef ) {
			continue;
		}
		this->CreateLightDefInteractions( ldef );
	}

	int end = Sys_Milliseconds();
	int	msec = end - start;

	common->Printf( "idRenderWorld::GenerateAllInteractions, msec = %i, staticAllocCount = %i.\n", msec, tr.staticAllocCount );


	// build the interaction table
	if ( r_useInteractionTable.GetBool() ) {
		interactionTableWidth = entityDefs.Num() + 100;
		interactionTableHeight = lightDefs.Num() + 100;
		int	size =  interactionTableWidth * interactionTableHeight * sizeof( *interactionTable );
		interactionTable = (idInteraction **)R_ClearedStaticAlloc( size );

		int	count = 0;
		for ( int i = 0 ; i < this->lightDefs.Num() ; i++ ) {
			idRenderLightLocal	*ldef = this->lightDefs[i];
			if ( !ldef ) {
				continue;
			}
			idInteraction	*inter;
			for ( inter = ldef->firstInteraction; inter != NULL; inter = inter->lightNext ) {
				idRenderEntityLocal	*edef = inter->entityDef;
				int index = ldef->index * interactionTableWidth + edef->index;

				interactionTable[ index ] = inter;
				count++;
			}
		}

		common->Printf( "interactionTable size: %i bytes\n", size );
		common->Printf( "%i interaction take %i bytes\n", count, count * sizeof( idInteraction ) );
	}

	// entities flagged as noDynamicInteractions will no longer make any
	generateAllInteractionsCalled = true;
}

/*
===================
idRenderWorldLocal::FreeInteractions
===================
*/
void idRenderWorldLocal::FreeInteractions() {
	int			i;
	idRenderEntityLocal	*def;

	for ( i = 0 ; i < entityDefs.Num(); i++ ) {
		def = entityDefs[i];
		if ( !def ) {
			continue;
		}
		// free all the interactions
		while ( def->firstInteraction != NULL ) {
			def->firstInteraction->UnlinkAndFree();
		}
	}
}

/*
==================
PushPolytopeIntoTree

Quake 4 uses an oriented-box BSP walk for entity refs. The older
bounding-sphere path can drop thin inline models entirely when all of the
derived AABB corner points classify into solid space.
==================
*/
void idRenderWorldLocal::PushPolytopeIntoTree_r( idRenderEntityLocal *def, idRenderLightLocal *light, const idBox &box, const idVec3 *points, int numPoints, int nodeNum ) {
	while ( nodeNum >= 0 ) {
		areaNode_t *node = areaNodes + nodeNum;

		if ( r_useNodeCommonChildren.GetBool() &&
			node->commonChildrenArea != CHILDREN_HAVE_MULTIPLE_AREAS &&
			portalAreas[ node->commonChildrenArea ].viewCount == tr.viewCount ) {
			return;
		}

		const idVec3 normal = node->plane.Normal();
		const idMat3 &axis = box.GetAxis();
		const idVec3 &extents = box.GetExtents();
		const float distance = box.GetCenter() * normal + node->plane[3];
		const float radius =
			idMath::Fabs( ( axis[0] * normal ) * extents[0] ) +
			idMath::Fabs( ( axis[1] * normal ) * extents[1] ) +
			idMath::Fabs( ( axis[2] * normal ) * extents[2] );

		if ( distance - radius >= 0.0f ) {
			nodeNum = node->children[0];
			if ( !nodeNum ) {
				return;
			}
			continue;
		}

		if ( distance + radius <= 0.0f ) {
			nodeNum = node->children[1];
			if ( !nodeNum ) {
				return;
			}
			continue;
		}

		if ( points != NULL && numPoints > 0 ) {
			bool front = false;
			bool back = false;

			for ( int i = 0; i < numPoints; ++i ) {
				const float d = points[i] * normal + node->plane[3];
				if ( d > 0.0f ) {
					front = true;
				} else if ( d < 0.0f ) {
					back = true;
				}

				if ( front && back ) {
					break;
				}
			}

			if ( front && back ) {
				if ( node->children[0] ) {
					PushPolytopeIntoTree_r( def, light, box, points, numPoints, node->children[0] );
				}
				nodeNum = node->children[1];
				if ( !nodeNum ) {
					return;
				}
				continue;
			}

			if ( front ) {
				nodeNum = node->children[0];
				if ( !nodeNum ) {
					return;
				}
				continue;
			}
		} else {
			if ( node->children[0] ) {
				PushPolytopeIntoTree_r( def, light, box, points, numPoints, node->children[0] );
			}
		}

		nodeNum = node->children[1];
		if ( !nodeNum ) {
			return;
		}
	}

	portalArea_t *area = &portalAreas[ -1 - nodeNum ];
	if ( area->viewCount == tr.viewCount ) {
		return;
	}
	area->viewCount = tr.viewCount;

	if ( def ) {
		AddEntityRefToArea( def, area );
	}
	if ( light ) {
		AddLightRefToArea( light, area );
	}
}

void idRenderWorldLocal::PushPolytopeIntoTree( idRenderEntityLocal *def, idRenderLightLocal *light, const idBox &box, const idVec3 *points, int numPoints ) {
	if ( areaNodes == NULL ) {
		return;
	}

	PushPolytopeIntoTree_r( def, light, box, points, numPoints, 0 );
}

/*
==================
PushVolumeIntoTree

Used for both light volumes and model volumes.

This does not clip the points by the planes, so some slop
occurs.

tr.viewCount should be bumped before calling, allowing it
to prevent double checking areas.

We might alternatively choose to do this with an area flow.
==================
*/
void idRenderWorldLocal::PushVolumeIntoTree_r( idRenderEntityLocal *def, idRenderLightLocal *light, const idSphere *sphere, int numPoints, const idVec3 (*points), 
								 int nodeNum ) {
	int			i;
	areaNode_t	*node;
	bool	front, back;

	if ( nodeNum < 0 ) {
		portalArea_t	*area;
		int		areaNum = -1 - nodeNum;

		area = &portalAreas[ areaNum ];
		if ( area->viewCount == tr.viewCount ) {
			return;	// already added a reference here
		}
		area->viewCount = tr.viewCount;

		if ( def ) {
			AddEntityRefToArea( def, area );
		}
		if ( light ) {
			AddLightRefToArea( light, area );
		}

		return;
	}

	node = areaNodes + nodeNum;

	// if we know that all possible children nodes only touch an area
	// we have already marked, we can early out
	if ( r_useNodeCommonChildren.GetBool() &&
		node->commonChildrenArea != CHILDREN_HAVE_MULTIPLE_AREAS ) {
		// note that we do NOT try to set a reference in this area
		// yet, because the test volume may yet wind up being in the
		// solid part, which would cause bounds slightly poked into
		// a wall to show up in the next room
		if ( portalAreas[ node->commonChildrenArea ].viewCount == tr.viewCount ) {
			return;
		}
	}

	// if the bounding sphere is completely on one side, don't
	// bother checking the individual points
	float sd = node->plane.Distance( sphere->GetOrigin() );
	if ( sd >= sphere->GetRadius() ) {
		nodeNum = node->children[0];
		if ( nodeNum ) {	// 0 = solid
			PushVolumeIntoTree_r( def, light, sphere, numPoints, points, nodeNum );
		}
		return;
	}
	if ( sd <= -sphere->GetRadius() ) {
		nodeNum = node->children[1];
		if ( nodeNum ) {	// 0 = solid
			PushVolumeIntoTree_r( def, light, sphere, numPoints, points, nodeNum );
		}
		return;
	}

	// exact check all the points against the node plane
	front = back = false;
#ifdef MACOS_X	//loop unrolling & pre-fetching for performance
	const idVec3 norm = node->plane.Normal();
	const float plane3 = node->plane[3];
	float D0, D1, D2, D3;

	for ( i = 0 ; i < numPoints - 4; i+=4 ) {
		D0 = points[i+0] * norm + plane3;
		D1 = points[i+1] * norm + plane3;
		if ( !front && D0 >= 0.0f ) {
		    front = true;
		} else if ( !back && D0 <= 0.0f ) {
		    back = true;
		}
		D2 = points[i+1] * norm + plane3;
		if ( !front && D1 >= 0.0f ) {
		    front = true;
		} else if ( !back && D1 <= 0.0f ) {
		    back = true;
		}
		D3 = points[i+1] * norm + plane3;
		if ( !front && D2 >= 0.0f ) {
		    front = true;
		} else if ( !back && D2 <= 0.0f ) {
		    back = true;
		}
		
		if ( !front && D3 >= 0.0f ) {
		    front = true;
		} else if ( !back && D3 <= 0.0f ) {
		    back = true;
		}
		if ( back && front ) {
		    break;
		}
	}
	if(!(back && front)) {
		for (; i < numPoints ; i++ ) {
			float d;
			d = points[i] * node->plane.Normal() + node->plane[3];
			if ( d >= 0.0f ) {
				front = true;
			} else if ( d <= 0.0f ) {
				back = true;
			}
			if ( back && front ) {
				break;
			}
		}	
	}
#else
	for ( i = 0 ; i < numPoints ; i++ ) {
		float d;

		d = points[i] * node->plane.Normal() + node->plane[3];
		if ( d >= 0.0f ) {
		    front = true;
		} else if ( d <= 0.0f ) {
		    back = true;
		}
		if ( back && front ) {
		    break;
		}
	}
#endif
	if ( front ) {
		nodeNum = node->children[0];
		if ( nodeNum ) {	// 0 = solid
			PushVolumeIntoTree_r( def, light, sphere, numPoints, points, nodeNum );
		}
	}
	if ( back ) {
		nodeNum = node->children[1];
		if ( nodeNum ) {	// 0 = solid
			PushVolumeIntoTree_r( def, light, sphere, numPoints, points, nodeNum );
		}
	}
}

/*
==============
PushVolumeIntoTree
==============
*/
void idRenderWorldLocal::PushVolumeIntoTree( idRenderEntityLocal *def, idRenderLightLocal *light, int numPoints, const idVec3 (*points) ) {
	int i;
	float radSquared, lr;
	idVec3 mid, dir;

	if ( areaNodes == NULL ) {
		return;
	}

	// calculate a bounding sphere for the points
	mid.Zero();
	for ( i = 0; i < numPoints; i++ ) {
		mid += points[i];
	}
	mid *= ( 1.0f / numPoints );

	radSquared = 0;

	for ( i = 0; i < numPoints; i++ ) {
		dir = points[i] - mid;
		lr = dir * dir;
		if ( lr > radSquared ) {
			radSquared = lr;
		}
	}

	idSphere sphere( mid, sqrt( radSquared ) );

	PushVolumeIntoTree_r( def, light, &sphere, numPoints, points, 0 );
}

//===================================================================

/*
====================
idRenderWorldLocal::DebugClearLines
====================
*/
void idRenderWorldLocal::DebugClearLines( int time ) {
	RB_ClearDebugLines( time );
	RB_ClearDebugText( time );
}

/*
====================
idRenderWorldLocal::DebugLine
====================
*/
void idRenderWorldLocal::DebugLine( const idVec4 &color, const idVec3 &start, const idVec3 &end, const int lifetime, const bool depthTest ) {
	RB_AddDebugLine( color, start, end, lifetime, depthTest );
}

/*
================
idRenderWorldLocal::DebugArrow
================
*/
void idRenderWorldLocal::DebugArrow( const idVec4 &color, const idVec3 &start, const idVec3 &end, int size, const int lifetime ) {
	idVec3 forward, right, up, v1, v2;
	float a, s;
	int i;
	static float arrowCos[40];
	static float arrowSin[40];
	static int arrowStep;

	DebugLine( color, start, end, lifetime );

	if ( r_debugArrowStep.GetInteger() <= 10 ) {
		return;
	}
	// calculate sine and cosine when step size changes
	if ( arrowStep != r_debugArrowStep.GetInteger() ) {
		arrowStep = r_debugArrowStep.GetInteger();
		for (i = 0, a = 0; a < 360.0f; a += arrowStep, i++) {
			arrowCos[i] = idMath::Cos16( DEG2RAD( a ) );
			arrowSin[i] = idMath::Sin16( DEG2RAD( a ) );
		}
		arrowCos[i] = arrowCos[0];
		arrowSin[i] = arrowSin[0];
	}
	// draw a nice arrow
	forward = end - start;
	forward.Normalize();
	forward.NormalVectors( right, up);
	for (i = 0, a = 0; a < 360.0f; a += arrowStep, i++) {
		s = 0.5f * size * arrowCos[i];
		v1 = end - size * forward;
		v1 = v1 + s * right;
		s = 0.5f * size * arrowSin[i];
		v1 = v1 + s * up;

		s = 0.5f * size * arrowCos[i+1];
		v2 = end - size * forward;
		v2 = v2 + s * right;
		s = 0.5f * size * arrowSin[i+1];
		v2 = v2 + s * up;

		DebugLine( color, v1, end, lifetime );
		DebugLine( color, v1, v2, lifetime );
	}
}

/*
====================
idRenderWorldLocal::DebugWinding
====================
*/
void idRenderWorldLocal::DebugWinding( const idVec4 &color, const idWinding &w, const idVec3 &origin, const idMat3 &axis, const int lifetime, const bool depthTest ) {
	int i;
	idVec3 point, lastPoint;

	const int windingPointCount = w.GetNumPoints();
	if ( windingPointCount < 2 ) {
		return;
	}

	lastPoint = origin + w[windingPointCount - 1].ToVec3() * axis;
	for ( i = 0; i < windingPointCount; i++ ) {
		point = origin + w[i].ToVec3() * axis;
		DebugLine( color, lastPoint, point, lifetime, depthTest );
		lastPoint = point;
	}
}

/*
====================
idRenderWorldLocal::DebugCircle
====================
*/
void idRenderWorldLocal::DebugCircle( const idVec4 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const int lifetime, const bool depthTest ) {
	int i;
	float a;
	idVec3 left, up, point, lastPoint;

	dir.OrthogonalBasis( left, up );
	left *= radius;
	up *= radius;
	lastPoint = origin + up;
	for ( i = 1; i <= numSteps; i++ ) {
		a = idMath::TWO_PI * i / numSteps;
		point = origin + idMath::Sin16( a ) * left + idMath::Cos16( a ) * up;
		DebugLine( color, lastPoint, point, lifetime, depthTest );
		lastPoint = point;
	}
}

/*
============
idRenderWorldLocal::DebugSphere
============
*/
void idRenderWorldLocal::DebugSphere( const idVec4 &color, const idSphere &sphere, const int lifetime, const bool depthTest /*_D3XP*/ ) {
	int i, j, n, num;
	float s, c;
	idVec3 p, lastp, *lastArray;

	num = 360 / 15;
	lastArray = (idVec3 *) _alloca16( num * sizeof( idVec3 ) );
	lastArray[0] = sphere.GetOrigin() + idVec3( 0, 0, sphere.GetRadius() );
	for ( n = 1; n < num; n++ ) {
		lastArray[n] = lastArray[0];
	}

	for ( i = 15; i <= 360; i += 15 ) {
		s = idMath::Sin16( DEG2RAD(i) );
		c = idMath::Cos16( DEG2RAD(i) );
		lastp[0] = sphere.GetOrigin()[0];
		lastp[1] = sphere.GetOrigin()[1] + sphere.GetRadius() * s;
		lastp[2] = sphere.GetOrigin()[2] + sphere.GetRadius() * c;
		for ( n = 0, j = 15; j <= 360; j += 15, n++ ) {
			p[0] = sphere.GetOrigin()[0] + idMath::Sin16( DEG2RAD(j) ) * sphere.GetRadius() * s;
			p[1] = sphere.GetOrigin()[1] + idMath::Cos16( DEG2RAD(j) ) * sphere.GetRadius() * s;
			p[2] = lastp[2];

			DebugLine( color, lastp, p, lifetime,depthTest );
			DebugLine( color, lastp, lastArray[n], lifetime, depthTest );

			lastArray[n] = lastp;
			lastp = p;
		}
	}
}

/*
====================
idRenderWorldLocal::DebugBounds
====================
*/
void idRenderWorldLocal::DebugBounds( const idVec4 &color, const idBounds &bounds, const idVec3 &org, const int lifetime ) {
	int i;
	idVec3 v[8];

	if ( bounds.IsCleared() ) {
		return;
	}

	for ( i = 0; i < 8; i++ ) {
		v[i][0] = org[0] + bounds[(i^(i>>1))&1][0];
		v[i][1] = org[1] + bounds[(i>>1)&1][1];
		v[i][2] = org[2] + bounds[(i>>2)&1][2];
	}
	for ( i = 0; i < 4; i++ ) {
		DebugLine( color, v[i], v[(i+1)&3], lifetime );
		DebugLine( color, v[4+i], v[4+((i+1)&3)], lifetime );
		DebugLine( color, v[i], v[4+i], lifetime );
	}
}

/*
====================
idRenderWorldLocal::DebugBox
====================
*/
void idRenderWorldLocal::DebugBox( const idVec4 &color, const idBox &box, const int lifetime ) {
	int i;
	idVec3 v[8];

	box.ToPoints( v );
	for ( i = 0; i < 4; i++ ) {
		DebugLine( color, v[i], v[(i+1)&3], lifetime );
		DebugLine( color, v[4+i], v[4+((i+1)&3)], lifetime );
		DebugLine( color, v[i], v[4+i], lifetime );
	}
}

/*
================
idRenderWorldLocal::DebugFrustum
================
*/
void idRenderWorldLocal::DebugFrustum( const idVec4 &color, const idFrustum &frustum, const bool showFromOrigin, const int lifetime ) {
	int i;
	idVec3 v[8];

	frustum.ToPoints( v );

	if ( frustum.GetNearDistance() > 0.0f ) {
		for ( i = 0; i < 4; i++ ) {
			DebugLine( color, v[i], v[(i+1)&3], lifetime );
		}
		if ( showFromOrigin ) {
			for ( i = 0; i < 4; i++ ) {
				DebugLine( color, frustum.GetOrigin(), v[i], lifetime );
			}
		}
	}
	for ( i = 0; i < 4; i++ ) {
		DebugLine( color, v[4+i], v[4+((i+1)&3)], lifetime );
		DebugLine( color, v[i], v[4+i], lifetime );
	}
}

/*
============
idRenderWorldLocal::DebugCone

  dir is the cone axis
  radius1 is the radius at the apex
  radius2 is the radius at apex+dir
============
*/
void idRenderWorldLocal::DebugCone( const idVec4 &color, const idVec3 &apex, const idVec3 &dir, float radius1, float radius2, const int lifetime ) {
	int i;
	idMat3 axis;
	idVec3 top, p1, p2, lastp1, lastp2, d;

	axis[2] = dir;
	axis[2].Normalize();
	axis[2].NormalVectors( axis[0], axis[1] );
	axis[1] = -axis[1];

	top = apex + dir;
	lastp2 = top + radius2 * axis[1];

	if ( radius1 == 0.0f ) {
		for ( i = 20; i <= 360; i += 20 ) {
			d = idMath::Sin16( DEG2RAD(i) ) * axis[0] + idMath::Cos16( DEG2RAD(i) ) * axis[1];
			p2 = top + d * radius2;
			DebugLine( color, lastp2, p2, lifetime );
			DebugLine( color, p2, apex, lifetime );
			lastp2 = p2;
		}
	} else {
		lastp1 = apex + radius1 * axis[1];
		for ( i = 20; i <= 360; i += 20 ) {
			d = idMath::Sin16( DEG2RAD(i) ) * axis[0] + idMath::Cos16( DEG2RAD(i) ) * axis[1];
			p1 = apex + d * radius1;
			p2 = top + d * radius2;
			DebugLine( color, lastp1, p1, lifetime );
			DebugLine( color, lastp2, p2, lifetime );
			DebugLine( color, p1, p2, lifetime );
			lastp1 = p1;
			lastp2 = p2;
		}
	}
}

/*
================
idRenderWorldLocal::DebugAxis
================
*/
void idRenderWorldLocal::DebugAxis( const idVec3 &origin, const idMat3 &axis ) {
	idVec3 start = origin;
	idVec3 end = start + axis[0] * 20.0f;
	DebugArrow( colorWhite, start, end, 2 );
	end = start + axis[0] * -20.0f;
	DebugArrow( colorWhite, start, end, 2 );
	end = start + axis[1] * +20.0f;
	DebugArrow( colorGreen, start, end, 2 );
	end = start + axis[1] * -20.0f;
	DebugArrow( colorGreen, start, end, 2 );
	end = start + axis[2] * +20.0f;
	DebugArrow( colorBlue, start, end, 2 );
	end = start + axis[2] * -20.0f;
	DebugArrow( colorBlue, start, end, 2 );
}

/*
====================
idRenderWorldLocal::DebugClearPolygons
====================
*/
void idRenderWorldLocal::DebugClearPolygons( int time ) {
	RB_ClearDebugPolygons( time );
}

/*
====================
idRenderWorldLocal::DebugPolygon
====================
*/
void idRenderWorldLocal::DebugPolygon( const idVec4 &color, const idWinding &winding, const int lifeTime, const bool depthTest ) {
	RB_AddDebugPolygon( color, winding, lifeTime, depthTest );
}

/*
================
idRenderWorldLocal::DebugScreenRect
================
*/
void idRenderWorldLocal::DebugScreenRect( const idVec4 &color, const idScreenRect &rect, const viewDef_t *viewDef, const int lifetime ) {
	int i;
	float centerx, centery, dScale, hScale, vScale;
	idBounds bounds;
	idVec3 p[4];

	centerx = ( viewDef->viewport.x2 - viewDef->viewport.x1 ) * 0.5f;
	centery = ( viewDef->viewport.y2 - viewDef->viewport.y1 ) * 0.5f;

	dScale = r_znear.GetFloat() + 1.0f;
	hScale = dScale * idMath::Tan16( DEG2RAD( viewDef->renderView.fov_x * 0.5f ) );
	vScale = dScale * idMath::Tan16( DEG2RAD( viewDef->renderView.fov_y * 0.5f ) );

	bounds[0][0] = bounds[1][0] = dScale;
	bounds[0][1] = -( rect.x1 - centerx ) / centerx * hScale;
	bounds[1][1] = -( rect.x2 - centerx ) / centerx * hScale;
	bounds[0][2] = ( rect.y1 - centery ) / centery * vScale;
	bounds[1][2] = ( rect.y2 - centery ) / centery * vScale;

	for ( i = 0; i < 4; i++ ) {
		p[i].x = bounds[0][0];
		p[i].y = bounds[(i^(i>>1))&1].y;
		p[i].z = bounds[(i>>1)&1].z;
		p[i] = viewDef->renderView.vieworg + p[i] * viewDef->renderView.viewaxis;
	}
	for ( i = 0; i < 4; i++ ) {
		DebugLine( color, p[i], p[(i+1)&3], false );
	}
}

/*
================
idRenderWorldLocal::DrawTextLength

  returns the length of the given text
================
*/
float idRenderWorldLocal::DrawTextLength( const char *text, float scale, int len ) {
	return RB_DrawTextLength( text, scale, len );
}

/*
================
idRenderWorldLocal::DrawText

  oriented on the viewaxis
  align can be 0-left, 1-center (default), 2-right
================
*/
void idRenderWorldLocal::DrawText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align, const int lifetime, const bool depthTest ) {
	RB_AddDebugText( text, origin, scale, color, viewAxis, align, lifetime, depthTest );
}

/*
===============
idRenderWorldLocal::RegenerateWorld
===============
*/
void idRenderWorldLocal::RegenerateWorld() {
	R_RegenerateWorld_f( idCmdArgs() );
}

/*
===============
R_GlobalShaderOverride
===============
*/
bool R_GlobalShaderOverride( const idMaterial **shader ) {

	if ( !(*shader)->IsDrawn() ) {
		return false;
	}

	if ( tr.primaryRenderView.globalMaterial ) {
		*shader = tr.primaryRenderView.globalMaterial;
		return true;
	}

	if ( r_materialOverride.GetString()[0] != '\0' ) {
		*shader = declManager->FindMaterial( r_materialOverride.GetString() );
		return true;
	}
	
	return false;
}

/*
===============
R_RemapShaderBySkin
===============
*/
const idMaterial *R_RemapShaderBySkin( const idMaterial *shader, const idDeclSkin *skin, const idMaterial *customShader ) {

	if ( !shader ) {
		return NULL;
	}

	// never remap surfaces that were originally nodraw, like collision hulls
	if ( !shader->IsDrawn() ) {
		return shader;
	}

	if ( customShader ) {
		// this is sort of a hack, but cause deformed surfaces to map to empty surfaces,
		// so the item highlight overlay doesn't highlight the autosprite surface
		if ( shader->Deform() ) {
			return NULL;
		}
		return const_cast<idMaterial *>(customShader);
	}

	if ( !skin || !shader ) {
		return const_cast<idMaterial *>(shader);
	}

	return skin->RemapShaderBySkin( shader );
}
