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

Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").

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

In addition, the Doom 3 BFG Edition 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 BFG Edition 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.

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

#pragma hdrstop
#include "precompiled.h"

#include "tr_local.h"

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

TRIANGLE MESH PROCESSING

The functions in this file have no vertex / index count limits.

Truly identical vertexes that match in position, normal, and texcoord can
be merged away.

Vertexes that match in position and texcoord, but have distinct normals will
remain distinct for all purposes.  This is usually a poor choice for models,
as adding a bevel face will not add any more vertexes, and will tend to
look better.

Match in position and normal, but differ in texcoords are referenced together
for calculating tangent vectors for bump mapping.
Artists should take care to have identical texels in all maps (bump/diffuse/specular)
in this case

Vertexes that only match in position are merged for shadow edge finding.

Degenerate triangles.

Overlapped triangles, even if normals or texcoords differ, must be removed.
for the silhoette based stencil shadow algorithm to function properly.
Is this true???
Is the overlapped triangle problem just an example of the trippled edge problem?

Interpenetrating triangles are not currently clipped to surfaces.
Do they effect the shadows?

if vertexes are intended to deform apart, make sure that no vertexes
are on top of each other in the base frame, or the sil edges may be
calculated incorrectly.

We might be able to identify this from topology.

Dangling edges are acceptable, but three way edges are not.

Are any combinations of two way edges unacceptable, like one facing
the backside of the other?

Topology is determined by a collection of triangle indexes.

The edge list can be built up from this, and stays valid even under
deformations.

Somewhat non-intuitively, concave edges cannot be optimized away, or the
stencil shadow algorithm miscounts.

Face normals are needed for generating shadow volumes and for calculating
the silhouette, but they will change with any deformation.

Vertex normals and vertex tangents will change with each deformation,
but they may be able to be transformed instead of recalculated.

bounding volume, both box and sphere will change with deformation.

silhouette indexes
shade indexes
texture indexes

  shade indexes will only be > silhouette indexes if there is facet shading present

	lookups from texture to sil and texture to shade?

The normal and tangent vector smoothing is simple averaging, no attempt is
made to better handle the cases where the distribution around the shared vertex
is highly uneven.

  we may get degenerate triangles even with the uniquing and removal
  if the vertexes have different texcoords.

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

// this shouldn't change anything, but previously renderbumped models seem to need it
#define USE_INVA

// instead of using the texture T vector, cross the normal and S vector for an orthogonal axis
#define DERIVE_UNSMOOTHED_BITANGENT

/*
=================
R_TriSurfMemory

For memory profiling
=================
*/
int R_TriSurfMemory( const srfTriangles_t* tri )
{
	int total = 0;
	
	if( tri == NULL )
	{
		return total;
	}
	
	if( tri->preLightShadowVertexes != NULL )
	{
		total += tri->numVerts * 2 * sizeof( tri->preLightShadowVertexes[0] );
	}
	if( tri->staticShadowVertexes != NULL )
	{
		total += tri->numVerts * 2 * sizeof( tri->staticShadowVertexes[0] );
	}
	if( tri->verts != NULL )
	{
		if( tri->ambientSurface == NULL || tri->verts != tri->ambientSurface->verts )
		{
			total += tri->numVerts * sizeof( tri->verts[0] );
		}
	}
	if( tri->indexes != NULL )
	{
		if( tri->ambientSurface == NULL || tri->indexes != tri->ambientSurface->indexes )
		{
			total += tri->numIndexes * sizeof( tri->indexes[0] );
		}
	}
	if( tri->silIndexes != NULL )
	{
		total += tri->numIndexes * sizeof( tri->silIndexes[0] );
	}
	if( tri->silEdges != NULL )
	{
		total += tri->numSilEdges * sizeof( tri->silEdges[0] );
	}
	if( tri->dominantTris != NULL )
	{
		total += tri->numVerts * sizeof( tri->dominantTris[0] );
	}
	if( tri->mirroredVerts != NULL )
	{
		total += tri->numMirroredVerts * sizeof( tri->mirroredVerts[0] );
	}
	if( tri->dupVerts != NULL )
	{
		total += tri->numDupVerts * sizeof( tri->dupVerts[0] );
	}
	
	total += sizeof( *tri );
	
	return total;
}

/*
==============
R_FreeStaticTriSurfVertexCaches
==============
*/
void R_FreeStaticTriSurfVertexCaches( srfTriangles_t* tri )
{
	// we don't support reclaiming static geometry memory
	// without a level change
	tri->ambientCache = 0;
	tri->indexCache = 0;
	tri->shadowCache = 0;
}

/*
==============
R_FreeStaticTriSurf
==============
*/
void R_FreeStaticTriSurf( srfTriangles_t* tri )
{
	if( !tri )
	{
		return;
	}
	
	R_FreeStaticTriSurfVertexCaches( tri );
	
	if( !tri->referencedVerts )
	{
		if( tri->verts != NULL )
		{
			// R_CreateLightTris points tri->verts at the verts of the ambient surface
			if( tri->ambientSurface == NULL || tri->verts != tri->ambientSurface->verts )
			{
				Mem_Free( tri->verts );
			}
		}
	}
	
	if( !tri->referencedIndexes )
	{
		if( tri->indexes != NULL )
		{
			// if a surface is completely inside a light volume R_CreateLightTris points tri->indexes at the indexes of the ambient surface
			if( tri->ambientSurface == NULL || tri->indexes != tri->ambientSurface->indexes )
			{
				Mem_Free( tri->indexes );
			}
		}
		if( tri->silIndexes != NULL )
		{
			Mem_Free( tri->silIndexes );
		}
		if( tri->silEdges != NULL )
		{
			Mem_Free( tri->silEdges );
		}
		if( tri->dominantTris != NULL )
		{
			Mem_Free( tri->dominantTris );
		}
		if( tri->mirroredVerts != NULL )
		{
			Mem_Free( tri->mirroredVerts );
		}
		if( tri->dupVerts != NULL )
		{
			Mem_Free( tri->dupVerts );
		}
	}
	
	if( tri->preLightShadowVertexes != NULL )
	{
		Mem_Free( tri->preLightShadowVertexes );
	}
	if( tri->staticShadowVertexes != NULL )
	{
		Mem_Free( tri->staticShadowVertexes );
	}
	
	// clear the tri out so we don't retain stale data
	memset( tri, 0, sizeof( srfTriangles_t ) );
	
	Mem_Free( tri );
}

/*
==============
R_FreeStaticTriSurfVerts
==============
*/
void R_FreeStaticTriSurfVerts( srfTriangles_t* tri )
{
	// we don't support reclaiming static geometry memory
	// without a level change
	tri->ambientCache = 0;
	
	if( tri->verts != NULL )
	{
		// R_CreateLightTris points tri->verts at the verts of the ambient surface
		if( tri->ambientSurface == NULL || tri->verts != tri->ambientSurface->verts )
		{
			Mem_Free( tri->verts );
		}
	}
}

/*
==============
R_AllocStaticTriSurf
==============
*/
srfTriangles_t* R_AllocStaticTriSurf()
{
	srfTriangles_t* tris = ( srfTriangles_t* )Mem_ClearedAlloc( sizeof( srfTriangles_t ), TAG_SRFTRIS );
	return tris;
}

/*
=================
R_CopyStaticTriSurf

This only duplicates the indexes and verts, not any of the derived data.
=================
*/
srfTriangles_t* R_CopyStaticTriSurf( const srfTriangles_t* tri )
{
	srfTriangles_t*	newTri;
	
	newTri = R_AllocStaticTriSurf();
	R_AllocStaticTriSurfVerts( newTri, tri->numVerts );
	R_AllocStaticTriSurfIndexes( newTri, tri->numIndexes );
	newTri->numVerts = tri->numVerts;
	newTri->numIndexes = tri->numIndexes;
	memcpy( newTri->verts, tri->verts, tri->numVerts * sizeof( newTri->verts[0] ) );
	memcpy( newTri->indexes, tri->indexes, tri->numIndexes * sizeof( newTri->indexes[0] ) );
	
	return newTri;
}

/*
=================
R_AllocStaticTriSurfVerts
=================
*/
void R_AllocStaticTriSurfVerts( srfTriangles_t* tri, int numVerts )
{
	assert( tri->verts == NULL );
	tri->verts = ( idDrawVert* )Mem_Alloc16( numVerts * sizeof( idDrawVert ), TAG_TRI_VERTS );
}

/*
=================
R_AllocStaticTriSurfIndexes
=================
*/
void R_AllocStaticTriSurfIndexes( srfTriangles_t* tri, int numIndexes )
{
	assert( tri->indexes == NULL );
	tri->indexes = ( triIndex_t* )Mem_Alloc16( numIndexes * sizeof( triIndex_t ), TAG_TRI_INDEXES );
}

/*
=================
R_AllocStaticTriSurfSilIndexes
=================
*/
void R_AllocStaticTriSurfSilIndexes( srfTriangles_t* tri, int numIndexes )
{
	assert( tri->silIndexes == NULL );
	tri->silIndexes = ( triIndex_t* )Mem_Alloc16( numIndexes * sizeof( triIndex_t ), TAG_TRI_SIL_INDEXES );
}

/*
=================
R_AllocStaticTriSurfDominantTris
=================
*/
void R_AllocStaticTriSurfDominantTris( srfTriangles_t* tri, int numVerts )
{
	assert( tri->dominantTris == NULL );
	tri->dominantTris = ( dominantTri_t* )Mem_Alloc16( numVerts * sizeof( dominantTri_t ), TAG_TRI_DOMINANT_TRIS );
}

/*
=================
R_AllocStaticTriSurfMirroredVerts
=================
*/
void R_AllocStaticTriSurfMirroredVerts( srfTriangles_t* tri, int numMirroredVerts )
{
	assert( tri->mirroredVerts == NULL );
	tri->mirroredVerts = ( int* )Mem_Alloc16( numMirroredVerts * sizeof( *tri->mirroredVerts ), TAG_TRI_MIR_VERT );
}

/*
=================
R_AllocStaticTriSurfDupVerts
=================
*/
void R_AllocStaticTriSurfDupVerts( srfTriangles_t* tri, int numDupVerts )
{
	assert( tri->dupVerts == NULL );
	tri->dupVerts = ( int* )Mem_Alloc16( numDupVerts * 2 * sizeof( *tri->dupVerts ), TAG_TRI_DUP_VERT );
}

/*
=================
R_AllocStaticTriSurfSilEdges
=================
*/
void R_AllocStaticTriSurfSilEdges( srfTriangles_t* tri, int numSilEdges )
{
	assert( tri->silEdges == NULL );
	tri->silEdges = ( silEdge_t* )Mem_Alloc16( numSilEdges * sizeof( silEdge_t ), TAG_TRI_SIL_EDGE );
}

/*
=================
R_AllocStaticTriSurfPreLightShadowVerts
=================
*/
void R_AllocStaticTriSurfPreLightShadowVerts( srfTriangles_t* tri, int numVerts )
{
	assert( tri->preLightShadowVertexes == NULL );
	tri->preLightShadowVertexes = ( idShadowVert* )Mem_Alloc16( numVerts * sizeof( idShadowVert ), TAG_TRI_SHADOW );
}

/*
=================
R_ResizeStaticTriSurfVerts
=================
*/
void R_ResizeStaticTriSurfVerts( srfTriangles_t* tri, int numVerts )
{
	idDrawVert* newVerts = ( idDrawVert* )Mem_Alloc16( numVerts * sizeof( idDrawVert ), TAG_TRI_VERTS );
	const int copy = std::min( numVerts, tri->numVerts );
	memcpy( newVerts, tri->verts, copy * sizeof( idDrawVert ) );
	Mem_Free( tri->verts );
	tri->verts = newVerts;
}

/*
=================
R_ResizeStaticTriSurfIndexes
=================
*/
void R_ResizeStaticTriSurfIndexes( srfTriangles_t* tri, int numIndexes )
{
	triIndex_t* newIndexes = ( triIndex_t* )Mem_Alloc16( numIndexes * sizeof( triIndex_t ), TAG_TRI_INDEXES );
	const int copy = std::min( numIndexes, tri->numIndexes );
	memcpy( newIndexes, tri->indexes, copy * sizeof( triIndex_t ) );
	Mem_Free( tri->indexes );
	tri->indexes = newIndexes;
}

/*
=================
R_ReferenceStaticTriSurfVerts
=================
*/
void R_ReferenceStaticTriSurfVerts( srfTriangles_t* tri, const srfTriangles_t* reference )
{
	tri->verts = reference->verts;
}

/*
=================
R_ReferenceStaticTriSurfIndexes
=================
*/
void R_ReferenceStaticTriSurfIndexes( srfTriangles_t* tri, const srfTriangles_t* reference )
{
	tri->indexes = reference->indexes;
}

/*
=================
R_FreeStaticTriSurfSilIndexes
=================
*/
void R_FreeStaticTriSurfSilIndexes( srfTriangles_t* tri )
{
	Mem_Free( tri->silIndexes );
	tri->silIndexes = NULL;
}

/*
===============
R_RangeCheckIndexes

Check for syntactically incorrect indexes, like out of range values.
Does not check for semantics, like degenerate triangles.

No vertexes is acceptable if no indexes.
No indexes is acceptable.
More vertexes than are referenced by indexes are acceptable.
===============
*/
void R_RangeCheckIndexes( const srfTriangles_t* tri )
{
	int		i;
	
	if( tri->numIndexes < 0 )
	{
		common->Error( "R_RangeCheckIndexes: numIndexes < 0" );
	}
	if( tri->numVerts < 0 )
	{
		common->Error( "R_RangeCheckIndexes: numVerts < 0" );
	}
	
	// must specify an integral number of triangles
	if( tri->numIndexes % 3 != 0 )
	{
		common->Error( "R_RangeCheckIndexes: numIndexes %% 3" );
	}
	
	for( i = 0; i < tri->numIndexes; i++ )
	{
		if( tri->indexes[i] >= tri->numVerts )
		{
			common->Error( "R_RangeCheckIndexes: index out of range" );
		}
	}
	
	// this should not be possible unless there are unused verts
	if( tri->numVerts > tri->numIndexes )
	{
		// FIXME: find the causes of these
		// common->Printf( "R_RangeCheckIndexes: tri->numVerts > tri->numIndexes\n" );
	}
}

/*
=================
R_BoundTriSurf
=================
*/
void R_BoundTriSurf( srfTriangles_t* tri )
{
	SIMDProcessor->MinMax( tri->bounds[0], tri->bounds[1], tri->verts, tri->numVerts );
}

/*
=================
R_CreateSilRemap
=================
*/
static int* R_CreateSilRemap( const srfTriangles_t* tri )
{
	int		c_removed, c_unique;
	int*		remap;
	int		i, j, hashKey;
	const idDrawVert* v1, *v2;
	
	remap = ( int* )R_ClearedStaticAlloc( tri->numVerts * sizeof( remap[0] ) );
	
	if( !r_useSilRemap.GetBool() )
	{
		for( i = 0; i < tri->numVerts; i++ )
		{
			remap[i] = i;
		}
		return remap;
	}
	
	idHashIndex		hash( 1024, tri->numVerts );
	
	c_removed = 0;
	c_unique = 0;
	for( i = 0; i < tri->numVerts; i++ )
	{
		v1 = &tri->verts[i];
		
		// see if there is an earlier vert that it can map to
		hashKey = hash.GenerateKey( v1->xyz );
		for( j = hash.First( hashKey ); j >= 0; j = hash.Next( j ) )
		{
			v2 = &tri->verts[j];
			if( v2->xyz[0] == v1->xyz[0]
					&& v2->xyz[1] == v1->xyz[1]
					&& v2->xyz[2] == v1->xyz[2] )
			{
				c_removed++;
				remap[i] = j;
				break;
			}
		}
		if( j < 0 )
		{
			c_unique++;
			remap[i] = i;
			hash.Add( hashKey, i );
		}
	}
	
	return remap;
}

/*
=================
R_CreateSilIndexes

Uniquing vertexes only on xyz before creating sil edges reduces
the edge count by about 20% on Q3 models
=================
*/
void R_CreateSilIndexes( srfTriangles_t* tri )
{
	int		i;
	int*		remap;
	
	if( tri->silIndexes )
	{
		Mem_Free( tri->silIndexes );
		tri->silIndexes = NULL;
	}
	
	remap = R_CreateSilRemap( tri );
	
	// remap indexes to the first one
	R_AllocStaticTriSurfSilIndexes( tri, tri->numIndexes );
	assert( tri->silIndexes != NULL );
	for( i = 0; i < tri->numIndexes; i++ )
	{
		tri->silIndexes[i] = remap[tri->indexes[i]];
	}
	
	R_StaticFree( remap );
}

/*
=====================
R_CreateDupVerts
=====================
*/
void R_CreateDupVerts( srfTriangles_t* tri )
{
	int i;
	
	idTempArray<int> remap( tri->numVerts );
	
	// initialize vertex remap in case there are unused verts
	for( i = 0; i < tri->numVerts; i++ )
	{
		remap[i] = i;
	}
	
	// set the remap based on how the silhouette indexes are remapped
	for( i = 0; i < tri->numIndexes; i++ )
	{
		remap[tri->indexes[i]] = tri->silIndexes[i];
	}
	
	// create duplicate vertex index based on the vertex remap
	idTempArray<int> tempDupVerts( tri->numVerts * 2 );
	tri->numDupVerts = 0;
	for( i = 0; i < tri->numVerts; i++ )
	{
		if( remap[i] != i )
		{
			tempDupVerts[tri->numDupVerts * 2 + 0] = i;
			tempDupVerts[tri->numDupVerts * 2 + 1] = remap[i];
			tri->numDupVerts++;
		}
	}
	
	R_AllocStaticTriSurfDupVerts( tri, tri->numDupVerts );
	memcpy( tri->dupVerts, tempDupVerts.Ptr(), tri->numDupVerts * 2 * sizeof( tri->dupVerts[0] ) );
}

/*
===============
R_DefineEdge
===============
*/
static int c_duplicatedEdges, c_tripledEdges;
static const int MAX_SIL_EDGES			= 0x7ffff;

static void R_DefineEdge( const int v1, const int v2, const int planeNum, const int numPlanes,
						  idList<silEdge_t>& silEdges, idHashIndex&	 silEdgeHash )
{
	int		i, hashKey;
	
	// check for degenerate edge
	if( v1 == v2 )
	{
		return;
	}
	hashKey = silEdgeHash.GenerateKey( v1, v2 );
	// search for a matching other side
	for( i = silEdgeHash.First( hashKey ); i >= 0 && i < MAX_SIL_EDGES; i = silEdgeHash.Next( i ) )
	{
		if( silEdges[i].v1 == v1 && silEdges[i].v2 == v2 )
		{
			c_duplicatedEdges++;
			// allow it to still create a new edge
			continue;
		}
		if( silEdges[i].v2 == v1 && silEdges[i].v1 == v2 )
		{
			if( silEdges[i].p2 != numPlanes )
			{
				c_tripledEdges++;
				// allow it to still create a new edge
				continue;
			}
			// this is a matching back side
			silEdges[i].p2 = planeNum;
			return;
		}
		
	}
	
	// define the new edge
	silEdgeHash.Add( hashKey, silEdges.Num() );
	
	silEdge_t silEdge;
	
	silEdge.p1 = planeNum;
	silEdge.p2 = numPlanes;
	silEdge.v1 = v1;
	silEdge.v2 = v2;
	
	silEdges.Append( silEdge );
}

/*
=================
SilEdgeSort
=================
*/
static int SilEdgeSort( const void* a, const void* b )
{
	if( ( ( silEdge_t* )a )->p1 < ( ( silEdge_t* )b )->p1 )
	{
		return -1;
	}
	if( ( ( silEdge_t* )a )->p1 > ( ( silEdge_t* )b )->p1 )
	{
		return 1;
	}
	if( ( ( silEdge_t* )a )->p2 < ( ( silEdge_t* )b )->p2 )
	{
		return -1;
	}
	if( ( ( silEdge_t* )a )->p2 > ( ( silEdge_t* )b )->p2 )
	{
		return 1;
	}
	return 0;
}

/*
=================
R_IdentifySilEdges

If the surface will not deform, coplanar edges (polygon interiors)
can never create silhouette plains, and can be omited
=================
*/
int	c_coplanarSilEdges;
int	c_totalSilEdges;

void R_IdentifySilEdges( srfTriangles_t* tri, bool omitCoplanarEdges )
{
	int		i;
	int		shared, single;
	
	omitCoplanarEdges = false;	// optimization doesn't work for some reason
	
	static const int SILEDGE_HASH_SIZE		= 1024;
	
	const int numTris = tri->numIndexes / 3;
	
	idList<silEdge_t>	silEdges( MAX_SIL_EDGES );
	idHashIndex	silEdgeHash( SILEDGE_HASH_SIZE, MAX_SIL_EDGES );
	int			numPlanes = numTris;
	
	
	silEdgeHash.Clear();
	
	c_duplicatedEdges = 0;
	c_tripledEdges = 0;
	
	for( i = 0; i < numTris; i++ )
	{
		int		i1, i2, i3;
		
		i1 = tri->silIndexes[ i * 3 + 0 ];
		i2 = tri->silIndexes[ i * 3 + 1 ];
		i3 = tri->silIndexes[ i * 3 + 2 ];
		
		// create the edges
		R_DefineEdge( i1, i2, i, numPlanes, silEdges, silEdgeHash );
		R_DefineEdge( i2, i3, i, numPlanes, silEdges, silEdgeHash );
		R_DefineEdge( i3, i1, i, numPlanes, silEdges, silEdgeHash );
	}
	
	if( c_duplicatedEdges || c_tripledEdges )
	{
		common->DWarning( "%i duplicated edge directions, %i tripled edges", c_duplicatedEdges, c_tripledEdges );
	}
	
	// if we know that the vertexes aren't going
	// to deform, we can remove interior triangulation edges
	// on otherwise planar polygons.
	// I earlier believed that I could also remove concave
	// edges, because they are never silhouettes in the conventional sense,
	// but they are still needed to balance out all the true sil edges
	// for the shadow algorithm to function
	int		c_coplanarCulled;
	
	c_coplanarCulled = 0;
	if( omitCoplanarEdges )
	{
		for( i = 0; i < silEdges.Num(); i++ )
		{
			int			i1, i2, i3;
			idPlane		plane;
			int			base;
			int			j;
			float		d;
			
			if( silEdges[i].p2 == numPlanes )  	// the fake dangling edge
			{
				continue;
			}
			
			base = silEdges[i].p1 * 3;
			i1 = tri->silIndexes[ base + 0 ];
			i2 = tri->silIndexes[ base + 1 ];
			i3 = tri->silIndexes[ base + 2 ];
			
			plane.FromPoints( tri->verts[i1].xyz, tri->verts[i2].xyz, tri->verts[i3].xyz );
			
			// check to see if points of second triangle are not coplanar
			base = silEdges[i].p2 * 3;
			for( j = 0; j < 3; j++ )
			{
				i1 = tri->silIndexes[ base + j ];
				d = plane.Distance( tri->verts[i1].xyz );
				if( d != 0 )  		// even a small epsilon causes problems
				{
					break;
				}
			}
			
			if( j == 3 )
			{
				// we can cull this sil edge
				memmove( &silEdges[i], &silEdges[i + 1], ( silEdges.Num() - i - 1 ) * sizeof( silEdges[i] ) );
				c_coplanarCulled++;
				silEdges.SetNum( silEdges.Num() - 1 );
				i--;
			}
		}
		if( c_coplanarCulled )
		{
			c_coplanarSilEdges += c_coplanarCulled;
//			common->Printf( "%i of %i sil edges coplanar culled\n", c_coplanarCulled,
//				c_coplanarCulled + numSilEdges );
		}
	}
	c_totalSilEdges += silEdges.Num();
	
	// sort the sil edges based on plane number
	qsort( silEdges.Ptr(), silEdges.Num(), sizeof( silEdges[0] ), SilEdgeSort );
	
	// count up the distribution.
	// a perfectly built model should only have shared
	// edges, but most models will have some interpenetration
	// and dangling edges
	shared = 0;
	single = 0;
	for( i = 0; i < silEdges.Num(); i++ )
	{
		if( silEdges[i].p2 == numPlanes )
		{
			single++;
		}
		else
		{
			shared++;
		}
	}
	
	if( !single )
	{
		tri->perfectHull = true;
	}
	else
	{
		tri->perfectHull = false;
	}
	
	tri->numSilEdges = silEdges.Num();
	R_AllocStaticTriSurfSilEdges( tri, silEdges.Num() );
	memcpy( tri->silEdges, silEdges.Ptr(), silEdges.Num() * sizeof( tri->silEdges[0] ) );
}

/*
===============
R_FaceNegativePolarity

Returns true if the texture polarity of the face is negative, false if it is positive or zero
===============
*/
static bool R_FaceNegativePolarity( const srfTriangles_t* tri, int firstIndex )
{
	const idDrawVert* a = tri->verts + tri->indexes[firstIndex + 0];
	const idDrawVert* b = tri->verts + tri->indexes[firstIndex + 1];
	const idDrawVert* c = tri->verts + tri->indexes[firstIndex + 2];
	
	const idVec2 aST = a->GetTexCoord();
	const idVec2 bST = b->GetTexCoord();
	const idVec2 cST = c->GetTexCoord();
	
	float d0[5];
	d0[3] = bST[0] - aST[0];
	d0[4] = bST[1] - aST[1];
	
	float d1[5];
	d1[3] = cST[0] - aST[0];
	d1[4] = cST[1] - aST[1];
	
	const float area = d0[3] * d1[4] - d0[4] * d1[3];
	if( area >= 0 )
	{
		return false;
	}
	return true;
}

/*
===================
R_DuplicateMirroredVertexes

Modifies the surface to bust apart any verts that are shared by both positive and
negative texture polarities, so tangent space smoothing at the vertex doesn't
degenerate.

This will create some identical vertexes (which will eventually get different tangent
vectors), so never optimize the resulting mesh, or it will get the mirrored edges back.

Reallocates tri->verts and changes tri->indexes in place
Silindexes are unchanged by this.

sets mirroredVerts and mirroredVerts[]
===================
*/
struct tangentVert_t
{
	bool	polarityUsed[2];
	int		negativeRemap;
};

static void	R_DuplicateMirroredVertexes( srfTriangles_t* tri )
{
	tangentVert_t*	vert;
	int				i, j;
	int				totalVerts;
	int				numMirror;
	
	idTempArray<tangentVert_t> tverts( tri->numVerts );
	tverts.Zero();
	
	// determine texture polarity of each surface
	
	// mark each vert with the polarities it uses
	for( i = 0; i < tri->numIndexes; i += 3 )
	{
		int	polarity = R_FaceNegativePolarity( tri, i );
		for( j = 0; j < 3; j++ )
		{
			tverts[tri->indexes[i + j]].polarityUsed[ polarity ] = true;
		}
	}
	
	// now create new vertex indices as needed
	totalVerts = tri->numVerts;
	for( i = 0; i < tri->numVerts; i++ )
	{
		vert = &tverts[i];
		if( vert->polarityUsed[0] && vert->polarityUsed[1] )
		{
			vert->negativeRemap = totalVerts;
			totalVerts++;
		}
	}
	
	tri->numMirroredVerts = totalVerts - tri->numVerts;
	
	if( tri->numMirroredVerts == 0 )
	{
		tri->mirroredVerts = NULL;
		return;
	}
	
	// now create the new list
	R_AllocStaticTriSurfMirroredVerts( tri, tri->numMirroredVerts );
	R_ResizeStaticTriSurfVerts( tri, totalVerts );
	
	// create the duplicates
	numMirror = 0;
	for( i = 0; i < tri->numVerts; i++ )
	{
		j = tverts[i].negativeRemap;
		if( j )
		{
			tri->verts[j] = tri->verts[i];
			tri->mirroredVerts[numMirror] = i;
			numMirror++;
		}
	}
	tri->numVerts = totalVerts;
	
	// change the indexes
	for( i = 0; i < tri->numIndexes; i++ )
	{
		if( tverts[tri->indexes[i]].negativeRemap && R_FaceNegativePolarity( tri, 3 * ( i / 3 ) ) )
		{
			tri->indexes[i] = tverts[tri->indexes[i]].negativeRemap;
		}
	}
}

/*
============
R_DeriveNormalsAndTangents

Derives the normal and orthogonal tangent vectors for the triangle vertices.
For each vertex the normal and tangent vectors are derived from all triangles
using the vertex which results in smooth tangents across the mesh.
============
*/
void R_DeriveNormalsAndTangents( srfTriangles_t* tri )
{
	idTempArray< idVec3 > vertexNormals( tri->numVerts );
	idTempArray< idVec3 > vertexTangents( tri->numVerts );
	idTempArray< idVec3 > vertexBitangents( tri->numVerts );
	
	vertexNormals.Zero();
	vertexTangents.Zero();
	vertexBitangents.Zero();
	
	for( int i = 0; i < tri->numIndexes; i += 3 )
	{
		const int v0 = tri->indexes[i + 0];
		const int v1 = tri->indexes[i + 1];
		const int v2 = tri->indexes[i + 2];
		
		const idDrawVert* a = tri->verts + v0;
		const idDrawVert* b = tri->verts + v1;
		const idDrawVert* c = tri->verts + v2;
		
		const idVec2 aST = a->GetTexCoord();
		const idVec2 bST = b->GetTexCoord();
		const idVec2 cST = c->GetTexCoord();
		
		float d0[5];
		d0[0] = b->xyz[0] - a->xyz[0];
		d0[1] = b->xyz[1] - a->xyz[1];
		d0[2] = b->xyz[2] - a->xyz[2];
		d0[3] = bST[0] - aST[0];
		d0[4] = bST[1] - aST[1];
		
		float d1[5];
		d1[0] = c->xyz[0] - a->xyz[0];
		d1[1] = c->xyz[1] - a->xyz[1];
		d1[2] = c->xyz[2] - a->xyz[2];
		d1[3] = cST[0] - aST[0];
		d1[4] = cST[1] - aST[1];
		
		idVec3 normal;
		normal[0] = d1[1] * d0[2] - d1[2] * d0[1];
		normal[1] = d1[2] * d0[0] - d1[0] * d0[2];
		normal[2] = d1[0] * d0[1] - d1[1] * d0[0];
		
		const float f0 = idMath::InvSqrt( normal.x * normal.x + normal.y * normal.y + normal.z * normal.z );
		
		normal.x *= f0;
		normal.y *= f0;
		normal.z *= f0;
		
		// area sign bit
		const float area = d0[3] * d1[4] - d0[4] * d1[3];
		unsigned int signBit = ( *( unsigned int* )&area ) & ( 1 << 31 );
		
		idVec3 tangent;
		tangent[0] = d0[0] * d1[4] - d0[4] * d1[0];
		tangent[1] = d0[1] * d1[4] - d0[4] * d1[1];
		tangent[2] = d0[2] * d1[4] - d0[4] * d1[2];
		
		const float f1 = idMath::InvSqrt( tangent.x * tangent.x + tangent.y * tangent.y + tangent.z * tangent.z );
		*( unsigned int* )&f1 ^= signBit;
		
		tangent.x *= f1;
		tangent.y *= f1;
		tangent.z *= f1;
		
		idVec3 bitangent;
		bitangent[0] = d0[3] * d1[0] - d0[0] * d1[3];
		bitangent[1] = d0[3] * d1[1] - d0[1] * d1[3];
		bitangent[2] = d0[3] * d1[2] - d0[2] * d1[3];
		
		const float f2 = idMath::InvSqrt( bitangent.x * bitangent.x + bitangent.y * bitangent.y + bitangent.z * bitangent.z );
		*( unsigned int* )&f2 ^= signBit;
		
		bitangent.x *= f2;
		bitangent.y *= f2;
		bitangent.z *= f2;
		
		vertexNormals[v0] += normal;
		vertexTangents[v0] += tangent;
		vertexBitangents[v0] += bitangent;
		
		vertexNormals[v1] += normal;
		vertexTangents[v1] += tangent;
		vertexBitangents[v1] += bitangent;
		
		vertexNormals[v2] += normal;
		vertexTangents[v2] += tangent;
		vertexBitangents[v2] += bitangent;
	}
	
	// add the normal of a duplicated vertex to the normal of the first vertex with the same XYZ
	for( int i = 0; i < tri->numDupVerts; i++ )
	{
		vertexNormals[tri->dupVerts[i * 2 + 0]] += vertexNormals[tri->dupVerts[i * 2 + 1]];
	}
	
	// copy vertex normals to duplicated vertices
	for( int i = 0; i < tri->numDupVerts; i++ )
	{
		vertexNormals[tri->dupVerts[i * 2 + 1]] = vertexNormals[tri->dupVerts[i * 2 + 0]];
	}
	
	// Project the summed vectors onto the normal plane and normalize.
	// The tangent vectors will not necessarily be orthogonal to each
	// other, but they will be orthogonal to the surface normal.
	for( int i = 0; i < tri->numVerts; i++ )
	{
		const float normalScale = idMath::InvSqrt( vertexNormals[i].x * vertexNormals[i].x + vertexNormals[i].y * vertexNormals[i].y + vertexNormals[i].z * vertexNormals[i].z );
		vertexNormals[i].x *= normalScale;
		vertexNormals[i].y *= normalScale;
		vertexNormals[i].z *= normalScale;
		
		vertexTangents[i] -= ( vertexTangents[i] * vertexNormals[i] ) * vertexNormals[i];
		vertexBitangents[i] -= ( vertexBitangents[i] * vertexNormals[i] ) * vertexNormals[i];
		
		const float tangentScale = idMath::InvSqrt( vertexTangents[i].x * vertexTangents[i].x + vertexTangents[i].y * vertexTangents[i].y + vertexTangents[i].z * vertexTangents[i].z );
		vertexTangents[i].x *= tangentScale;
		vertexTangents[i].y *= tangentScale;
		vertexTangents[i].z *= tangentScale;
		
		const float bitangentScale = idMath::InvSqrt( vertexBitangents[i].x * vertexBitangents[i].x + vertexBitangents[i].y * vertexBitangents[i].y + vertexBitangents[i].z * vertexBitangents[i].z );
		vertexBitangents[i].x *= bitangentScale;
		vertexBitangents[i].y *= bitangentScale;
		vertexBitangents[i].z *= bitangentScale;
	}
	
	// compress the normals and tangents
	for( int i = 0; i < tri->numVerts; i++ )
	{
		tri->verts[i].SetNormal( vertexNormals[i] );
		tri->verts[i].SetTangent( vertexTangents[i] );
		tri->verts[i].SetBiTangent( vertexBitangents[i] );
	}
}

/*
============
R_DeriveUnsmoothedNormalsAndTangents
============
*/
void R_DeriveUnsmoothedNormalsAndTangents( srfTriangles_t* tri )
{
	for( int i = 0; i < tri->numVerts; i++ )
	{
		float d0, d1, d2, d3, d4;
		float d5, d6, d7, d8, d9;
		float s0, s1, s2;
		float n0, n1, n2;
		float t0, t1, t2;
		float t3, t4, t5;
		
		const dominantTri_t& dt = tri->dominantTris[i];
		
		idDrawVert* a = tri->verts + i;
		idDrawVert* b = tri->verts + dt.v2;
		idDrawVert* c = tri->verts + dt.v3;
		
		const idVec2 aST = a->GetTexCoord();
		const idVec2 bST = b->GetTexCoord();
		const idVec2 cST = c->GetTexCoord();
		
		d0 = b->xyz[0] - a->xyz[0];
		d1 = b->xyz[1] - a->xyz[1];
		d2 = b->xyz[2] - a->xyz[2];
		d3 = bST[0] - aST[0];
		d4 = bST[1] - aST[1];
		
		d5 = c->xyz[0] - a->xyz[0];
		d6 = c->xyz[1] - a->xyz[1];
		d7 = c->xyz[2] - a->xyz[2];
		d8 = cST[0] - aST[0];
		d9 = cST[1] - aST[1];
		
		s0 = dt.normalizationScale[0];
		s1 = dt.normalizationScale[1];
		s2 = dt.normalizationScale[2];
		
		n0 = s2 * ( d6 * d2 - d7 * d1 );
		n1 = s2 * ( d7 * d0 - d5 * d2 );
		n2 = s2 * ( d5 * d1 - d6 * d0 );
		
		t0 = s0 * ( d0 * d9 - d4 * d5 );
		t1 = s0 * ( d1 * d9 - d4 * d6 );
		t2 = s0 * ( d2 * d9 - d4 * d7 );
		
#ifndef DERIVE_UNSMOOTHED_BITANGENT
		t3 = s1 * ( d3 * d5 - d0 * d8 );
		t4 = s1 * ( d3 * d6 - d1 * d8 );
		t5 = s1 * ( d3 * d7 - d2 * d8 );
#else
		t3 = s1 * ( n2 * t1 - n1 * t2 );
		t4 = s1 * ( n0 * t2 - n2 * t0 );
		t5 = s1 * ( n1 * t0 - n0 * t1 );
#endif
		
		a->SetNormal( n0, n1, n2 );
		a->SetTangent( t0, t1, t2 );
		a->SetBiTangent( t3, t4, t5 );
	}
}

/*
=====================
R_CreateVertexNormals

Averages together the contributions of all faces that are
used by a vertex, creating drawVert->normal
=====================
*/
void R_CreateVertexNormals( srfTriangles_t* tri )
{
	if( tri->silIndexes == NULL )
	{
		R_CreateSilIndexes( tri );
	}
	
	idTempArray< idVec3 > vertexNormals( tri->numVerts );
	vertexNormals.Zero();
	
	assert( tri->silIndexes != NULL );
	for( int i = 0; i < tri->numIndexes; i += 3 )
	{
		const int i0 = tri->silIndexes[i + 0];
		const int i1 = tri->silIndexes[i + 1];
		const int i2 = tri->silIndexes[i + 2];
		
		const idDrawVert& v0 = tri->verts[i0];
		const idDrawVert& v1 = tri->verts[i1];
		const idDrawVert& v2 = tri->verts[i2];
		
		const idPlane plane( v0.xyz, v1.xyz, v2.xyz );
		
		vertexNormals[i0] += plane.Normal();
		vertexNormals[i1] += plane.Normal();
		vertexNormals[i2] += plane.Normal();
	}
	
	// replicate from silIndexes to all indexes
	for( int i = 0; i < tri->numIndexes; i++ )
	{
		vertexNormals[tri->indexes[i]] = vertexNormals[tri->silIndexes[i]];
	}
	
	// normalize
	for( int i = 0; i < tri->numVerts; i++ )
	{
		vertexNormals[i].Normalize();
	}
	
	// compress the normals
	for( int i = 0; i < tri->numVerts; i++ )
	{
		tri->verts[i].SetNormal( vertexNormals[i] );
	}
}

/*
=================
R_DeriveTangentsWithoutNormals

Build texture space tangents for bump mapping
If a surface is deformed, this must be recalculated

This assumes that any mirrored vertexes have already been duplicated, so
any shared vertexes will have the tangent spaces smoothed across.

Texture wrapping slightly complicates this, but as long as the normals
are shared, and the tangent vectors are projected onto the normals, the
separate vertexes should wind up with identical tangent spaces.

mirroring a normalmap WILL cause a slightly visible seam unless the normals
are completely flat around the edge's full bilerp support.

Vertexes which are smooth shaded must have their tangent vectors
in the same plane, which will allow a seamless
rendering as long as the normal map is even on both sides of the
seam.

A smooth shaded surface may have multiple tangent vectors at a vertex
due to texture seams or mirroring, but it should only have a single
normal vector.

Each triangle has a pair of tangent vectors in it's plane

Should we consider having vertexes point at shared tangent spaces
to save space or speed transforms?

this version only handles bilateral symetry
=================
*/
void R_DeriveTangentsWithoutNormals( srfTriangles_t* tri )
{
	idTempArray< idVec3 > triangleTangents( tri->numIndexes / 3 );
	idTempArray< idVec3 > triangleBitangents( tri->numIndexes / 3 );
	
	//
	// calculate tangent vectors for each face in isolation
	//
	int c_positive = 0;
	int c_negative = 0;
	int c_textureDegenerateFaces = 0;
	for( int i = 0; i < tri->numIndexes; i += 3 )
	{
		idVec3	temp;
		
		idDrawVert* a = tri->verts + tri->indexes[i + 0];
		idDrawVert* b = tri->verts + tri->indexes[i + 1];
		idDrawVert* c = tri->verts + tri->indexes[i + 2];
		
		const idVec2 aST = a->GetTexCoord();
		const idVec2 bST = b->GetTexCoord();
		const idVec2 cST = c->GetTexCoord();
		
		float d0[5];
		d0[0] = b->xyz[0] - a->xyz[0];
		d0[1] = b->xyz[1] - a->xyz[1];
		d0[2] = b->xyz[2] - a->xyz[2];
		d0[3] = bST[0] - aST[0];
		d0[4] = bST[1] - aST[1];
		
		float d1[5];
		d1[0] = c->xyz[0] - a->xyz[0];
		d1[1] = c->xyz[1] - a->xyz[1];
		d1[2] = c->xyz[2] - a->xyz[2];
		d1[3] = cST[0] - aST[0];
		d1[4] = cST[1] - aST[1];
		
		const float area = d0[3] * d1[4] - d0[4] * d1[3];
		if( fabs( area ) < 1e-20f )
		{
			triangleTangents[i / 3].Zero();
			triangleBitangents[i / 3].Zero();
			c_textureDegenerateFaces++;
			continue;
		}
		if( area > 0.0f )
		{
			c_positive++;
		}
		else
		{
			c_negative++;
		}
		
#ifdef USE_INVA
		float inva = ( area < 0.0f ) ? -1.0f : 1.0f;		// was = 1.0f / area;
		
		temp[0] = ( d0[0] * d1[4] - d0[4] * d1[0] ) * inva;
		temp[1] = ( d0[1] * d1[4] - d0[4] * d1[1] ) * inva;
		temp[2] = ( d0[2] * d1[4] - d0[4] * d1[2] ) * inva;
		temp.Normalize();
		triangleTangents[i / 3] = temp;
		
		temp[0] = ( d0[3] * d1[0] - d0[0] * d1[3] ) * inva;
		temp[1] = ( d0[3] * d1[1] - d0[1] * d1[3] ) * inva;
		temp[2] = ( d0[3] * d1[2] - d0[2] * d1[3] ) * inva;
		temp.Normalize();
		triangleBitangents[i / 3] = temp;
#else
		temp[0] = ( d0[0] * d1[4] - d0[4] * d1[0] );
		temp[1] = ( d0[1] * d1[4] - d0[4] * d1[1] );
		temp[2] = ( d0[2] * d1[4] - d0[4] * d1[2] );
		temp.Normalize();
		triangleTangents[i / 3] = temp;
		
		temp[0] = ( d0[3] * d1[0] - d0[0] * d1[3] );
		temp[1] = ( d0[3] * d1[1] - d0[1] * d1[3] );
		temp[2] = ( d0[3] * d1[2] - d0[2] * d1[3] );
		temp.Normalize();
		triangleBitangents[i / 3] = temp;
#endif
	}
	
	idTempArray< idVec3 > vertexTangents( tri->numVerts );
	idTempArray< idVec3 > vertexBitangents( tri->numVerts );
	
	// clear the tangents
	for( int i = 0; i < tri->numVerts; ++i )
	{
		vertexTangents[i].Zero();
		vertexBitangents[i].Zero();
	}
	
	// sum up the neighbors
	for( int i = 0; i < tri->numIndexes; i += 3 )
	{
		// for each vertex on this face
		for( int j = 0; j < 3; j++ )
		{
			vertexTangents[tri->indexes[i + j]] += triangleTangents[i / 3];
			vertexBitangents[tri->indexes[i + j]] += triangleBitangents[i / 3];
		}
	}
	
	// Project the summed vectors onto the normal plane and normalize.
	// The tangent vectors will not necessarily be orthogonal to each
	// other, but they will be orthogonal to the surface normal.
	for( int i = 0; i < tri->numVerts; i++ )
	{
		idVec3 normal = tri->verts[i].GetNormal();
		normal.Normalize();
		
		vertexTangents[i] -= ( vertexTangents[i] * normal ) * normal;
		vertexTangents[i].Normalize();
		
		vertexBitangents[i] -= ( vertexBitangents[i] * normal ) * normal;
		vertexBitangents[i].Normalize();
	}
	
	for( int i = 0; i < tri->numVerts; i++ )
	{
		tri->verts[i].SetTangent( vertexTangents[i] );
		tri->verts[i].SetBiTangent( vertexBitangents[i] );
	}
	
	tri->tangentsCalculated = true;
}

/*
===================
R_BuildDominantTris

Find the largest triangle that uses each vertex
===================
*/
typedef struct
{
	int		vertexNum;
	int		faceNum;
} indexSort_t;

static int IndexSort( const void* a, const void* b )
{
	if( ( ( indexSort_t* )a )->vertexNum < ( ( indexSort_t* )b )->vertexNum )
	{
		return -1;
	}
	if( ( ( indexSort_t* )a )->vertexNum > ( ( indexSort_t* )b )->vertexNum )
	{
		return 1;
	}
	return 0;
}

void R_BuildDominantTris( srfTriangles_t* tri )
{
	int i, j;
	dominantTri_t* dt;
	const int numIndexes = tri->numIndexes;
	indexSort_t* ind = ( indexSort_t* )R_StaticAlloc( numIndexes * sizeof( indexSort_t ) );
	if( ind == NULL )
	{
		idLib::Error( "Couldn't allocate index sort array" );
		return;
	}
	
	for( i = 0; i < tri->numIndexes; i++ )
	{
		ind[i].vertexNum = tri->indexes[i];
		ind[i].faceNum = i / 3;
	}
	qsort( ind, tri->numIndexes, sizeof( *ind ), IndexSort );
	
	R_AllocStaticTriSurfDominantTris( tri, tri->numVerts );
	dt = tri->dominantTris;
	memset( dt, 0, tri->numVerts * sizeof( dt[0] ) );
	
	for( i = 0; i < numIndexes; i += j )
	{
		float	maxArea = 0;
#pragma warning( disable: 6385 ) // This is simply to get pass a false defect for /analyze -- if you can figure out a better way, please let Shawn know...
		int		vertNum = ind[i].vertexNum;
#pragma warning( default: 6385 )
		for( j = 0; i + j < tri->numIndexes && ind[i + j].vertexNum == vertNum; j++ )
		{
			float		d0[5], d1[5];
			idDrawVert*	a, *b, *c;
			idVec3		normal, tangent, bitangent;
			
			int	i1 = tri->indexes[ind[i + j].faceNum * 3 + 0];
			int	i2 = tri->indexes[ind[i + j].faceNum * 3 + 1];
			int	i3 = tri->indexes[ind[i + j].faceNum * 3 + 2];
			
			a = tri->verts + i1;
			b = tri->verts + i2;
			c = tri->verts + i3;
			
			const idVec2 aST = a->GetTexCoord();
			const idVec2 bST = b->GetTexCoord();
			const idVec2 cST = c->GetTexCoord();
			
			d0[0] = b->xyz[0] - a->xyz[0];
			d0[1] = b->xyz[1] - a->xyz[1];
			d0[2] = b->xyz[2] - a->xyz[2];
			d0[3] = bST[0] - aST[0];
			d0[4] = bST[1] - aST[1];
			
			d1[0] = c->xyz[0] - a->xyz[0];
			d1[1] = c->xyz[1] - a->xyz[1];
			d1[2] = c->xyz[2] - a->xyz[2];
			d1[3] = cST[0] - aST[0];
			d1[4] = cST[1] - aST[1];
			
			normal[0] = ( d1[1] * d0[2] - d1[2] * d0[1] );
			normal[1] = ( d1[2] * d0[0] - d1[0] * d0[2] );
			normal[2] = ( d1[0] * d0[1] - d1[1] * d0[0] );
			
			float area = normal.Length();
			
			// if this is smaller than what we already have, skip it
			if( area < maxArea )
			{
				continue;
			}
			maxArea = area;
			
			if( i1 == vertNum )
			{
				dt[vertNum].v2 = i2;
				dt[vertNum].v3 = i3;
			}
			else if( i2 == vertNum )
			{
				dt[vertNum].v2 = i3;
				dt[vertNum].v3 = i1;
			}
			else
			{
				dt[vertNum].v2 = i1;
				dt[vertNum].v3 = i2;
			}
			
			float	len = area;
			if( len < 0.001f )
			{
				len = 0.001f;
			}
			dt[vertNum].normalizationScale[2] = 1.0f / len;		// normal
			
			// texture area
			area = d0[3] * d1[4] - d0[4] * d1[3];
			
			tangent[0] = ( d0[0] * d1[4] - d0[4] * d1[0] );
			tangent[1] = ( d0[1] * d1[4] - d0[4] * d1[1] );
			tangent[2] = ( d0[2] * d1[4] - d0[4] * d1[2] );
			len = tangent.Length();
			if( len < 0.001f )
			{
				len = 0.001f;
			}
			dt[vertNum].normalizationScale[0] = ( area > 0 ? 1 : -1 ) / len;	// tangents[0]
			
			bitangent[0] = ( d0[3] * d1[0] - d0[0] * d1[3] );
			bitangent[1] = ( d0[3] * d1[1] - d0[1] * d1[3] );
			bitangent[2] = ( d0[3] * d1[2] - d0[2] * d1[3] );
			len = bitangent.Length();
			if( len < 0.001f )
			{
				len = 0.001f;
			}
#ifdef DERIVE_UNSMOOTHED_BITANGENT
			dt[vertNum].normalizationScale[1] = ( area > 0 ? 1 : -1 );
#else
			dt[vertNum].normalizationScale[1] = ( area > 0 ? 1 : -1 ) / len;	// tangents[1]
#endif
		}
	}
	
	R_StaticFree( ind );
}

/*
==================
R_DeriveTangents

This is called once for static surfaces, and every frame for deforming surfaces

Builds tangents, normals, and face planes
==================
*/
void R_DeriveTangents( srfTriangles_t* tri )
{
	if( tri->tangentsCalculated )
	{
		return;
	}
	
	tr.pc.c_tangentIndexes += tri->numIndexes;
	
	if( tri->dominantTris != NULL )
	{
		R_DeriveUnsmoothedNormalsAndTangents( tri );
	}
	else
	{
		R_DeriveNormalsAndTangents( tri );
	}
	tri->tangentsCalculated = true;
}

/*
=================
R_RemoveDuplicatedTriangles

silIndexes must have already been calculated

silIndexes are used instead of indexes, because duplicated
triangles could have different texture coordinates.
=================
*/
void R_RemoveDuplicatedTriangles( srfTriangles_t* tri )
{
	int		c_removed;
	int		i, j, r;
	int		a, b, c;
	
	c_removed = 0;
	
	// check for completely duplicated triangles
	// any rotation of the triangle is still the same, but a mirroring
	// is considered different
	for( i = 0; i < tri->numIndexes; i += 3 )
	{
		for( r = 0; r < 3; r++ )
		{
			a = tri->silIndexes[i + r];
			b = tri->silIndexes[i + ( r + 1 ) % 3];
			c = tri->silIndexes[i + ( r + 2 ) % 3];
			for( j = i + 3; j < tri->numIndexes; j += 3 )
			{
				if( tri->silIndexes[j] == a && tri->silIndexes[j + 1] == b && tri->silIndexes[j + 2] == c )
				{
					c_removed++;
					memmove( tri->indexes + j, tri->indexes + j + 3, ( tri->numIndexes - j - 3 ) * sizeof( tri->indexes[0] ) );
					memmove( tri->silIndexes + j, tri->silIndexes + j + 3, ( tri->numIndexes - j - 3 ) * sizeof( tri->silIndexes[0] ) );
					tri->numIndexes -= 3;
					j -= 3;
				}
			}
		}
	}
	
	if( c_removed )
	{
		common->Printf( "removed %i duplicated triangles\n", c_removed );
	}
}

/*
=================
R_RemoveDegenerateTriangles

silIndexes must have already been calculated
=================
*/
void R_RemoveDegenerateTriangles( srfTriangles_t* tri )
{
	int		c_removed;
	int		i;
	int		a, b, c;
	
	assert( tri->silIndexes != NULL );
	
	// check for completely degenerate triangles
	c_removed = 0;
	for( i = 0; i < tri->numIndexes; i += 3 )
	{
		a = tri->silIndexes[i];
		b = tri->silIndexes[i + 1];
		c = tri->silIndexes[i + 2];
		if( a == b || a == c || b == c )
		{
			c_removed++;
			memmove( tri->indexes + i, tri->indexes + i + 3, ( tri->numIndexes - i - 3 ) * sizeof( tri->indexes[0] ) );
			memmove( tri->silIndexes + i, tri->silIndexes + i + 3, ( tri->numIndexes - i - 3 ) * sizeof( tri->silIndexes[0] ) );
			tri->numIndexes -= 3;
			i -= 3;
		}
	}
	
	// this doesn't free the memory used by the unused verts
	
	if( c_removed )
	{
		common->Printf( "removed %i degenerate triangles\n", c_removed );
	}
}

/*
=================
R_TestDegenerateTextureSpace
=================
*/
void R_TestDegenerateTextureSpace( srfTriangles_t* tri )
{
	int		c_degenerate;
	int		i;
	
	// check for triangles with a degenerate texture space
	c_degenerate = 0;
	for( i = 0; i < tri->numIndexes; i += 3 )
	{
		const idDrawVert& a = tri->verts[tri->indexes[i + 0]];
		const idDrawVert& b = tri->verts[tri->indexes[i + 1]];
		const idDrawVert& c = tri->verts[tri->indexes[i + 2]];
		
		// RB: compare texcoords instead of pointers
		if( a.GetTexCoord() == b.GetTexCoord() || b.GetTexCoord() == c.GetTexCoord() || c.GetTexCoord() == a.GetTexCoord() )
		{
			c_degenerate++;
		}
		// RB end
	}
	
	if( c_degenerate )
	{
//		common->Printf( "%d triangles with a degenerate texture space\n", c_degenerate );
	}
}

/*
=================
R_RemoveUnusedVerts
=================
*/
void R_RemoveUnusedVerts( srfTriangles_t* tri )
{
	int		i;
	int*		mark;
	int		index;
	int		used;
	
	mark = ( int* )R_ClearedStaticAlloc( tri->numVerts * sizeof( *mark ) );
	
	for( i = 0; i < tri->numIndexes; i++ )
	{
		index = tri->indexes[i];
		if( index < 0 || index >= tri->numVerts )
		{
			common->Error( "R_RemoveUnusedVerts: bad index" );
		}
		mark[ index ] = 1;
		
		if( tri->silIndexes )
		{
			index = tri->silIndexes[i];
			if( index < 0 || index >= tri->numVerts )
			{
				common->Error( "R_RemoveUnusedVerts: bad index" );
			}
			mark[ index ] = 1;
		}
	}
	
	used = 0;
	for( i = 0; i < tri->numVerts; i++ )
	{
		if( !mark[i] )
		{
			continue;
		}
		mark[i] = used + 1;
		used++;
	}
	
	if( used != tri->numVerts )
	{
		for( i = 0; i < tri->numIndexes; i++ )
		{
			tri->indexes[i] = mark[ tri->indexes[i] ] - 1;
			if( tri->silIndexes )
			{
				tri->silIndexes[i] = mark[ tri->silIndexes[i] ] - 1;
			}
		}
		tri->numVerts = used;
		
		for( i = 0; i < tri->numVerts; i++ )
		{
			index = mark[ i ];
			if( !index )
			{
				continue;
			}
			tri->verts[ index - 1 ] = tri->verts[i];
		}
		
		// this doesn't realloc the arrays to save the memory used by the unused verts
	}
	
	R_StaticFree( mark );
}

/*
=================
R_MergeSurfaceList

Only deals with vertexes and indexes, not silhouettes, planes, etc.
Does NOT perform a cleanup triangles, so there may be duplicated verts in the result.
=================
*/
srfTriangles_t* R_MergeSurfaceList( const srfTriangles_t** surfaces, int numSurfaces )
{
	srfTriangles_t*	newTri;
	const srfTriangles_t*	tri;
	int				i, j;
	int				totalVerts;
	int				totalIndexes;
	
	totalVerts = 0;
	totalIndexes = 0;
	for( i = 0; i < numSurfaces; i++ )
	{
		totalVerts += surfaces[i]->numVerts;
		totalIndexes += surfaces[i]->numIndexes;
	}
	
	newTri = R_AllocStaticTriSurf();
	newTri->numVerts = totalVerts;
	newTri->numIndexes = totalIndexes;
	R_AllocStaticTriSurfVerts( newTri, newTri->numVerts );
	R_AllocStaticTriSurfIndexes( newTri, newTri->numIndexes );
	
	totalVerts = 0;
	totalIndexes = 0;
	for( i = 0; i < numSurfaces; i++ )
	{
		tri = surfaces[i];
		memcpy( newTri->verts + totalVerts, tri->verts, tri->numVerts * sizeof( *tri->verts ) );
		for( j = 0; j < tri->numIndexes; j++ )
		{
			newTri->indexes[ totalIndexes + j ] = totalVerts + tri->indexes[j];
		}
		totalVerts += tri->numVerts;
		totalIndexes += tri->numIndexes;
	}
	
	return newTri;
}

/*
=================
R_MergeTriangles

Only deals with vertexes and indexes, not silhouettes, planes, etc.
Does NOT perform a cleanup triangles, so there may be duplicated verts in the result.
=================
*/
srfTriangles_t* R_MergeTriangles( const srfTriangles_t* tri1, const srfTriangles_t* tri2 )
{
	const srfTriangles_t*	tris[2];
	
	tris[0] = tri1;
	tris[1] = tri2;
	
	return R_MergeSurfaceList( tris, 2 );
}

/*
=================
R_ReverseTriangles

Lit two sided surfaces need to have the triangles actually duplicated,
they can't just turn on two sided lighting, because the normal and tangents
are wrong on the other sides.

This should be called before R_CleanupTriangles
=================
*/
void R_ReverseTriangles( srfTriangles_t* tri )
{
	int			i;
	
	// flip the normal on each vertex
	// If the surface is going to have generated normals, this won't matter,
	// but if it has explicit normals, this will keep it on the correct side
	for( i = 0; i < tri->numVerts; i++ )
	{
		tri->verts[i].SetNormal( vec3_origin - tri->verts[i].GetNormal() );
	}
	
	// flip the index order to make them back sided
	for( i = 0; i < tri->numIndexes; i += 3 )
	{
		triIndex_t	temp;
		
		temp = tri->indexes[ i + 0 ];
		tri->indexes[ i + 0 ] = tri->indexes[ i + 1 ];
		tri->indexes[ i + 1 ] = temp;
	}
}

/*
=================
R_CleanupTriangles

FIXME: allow createFlat and createSmooth normals, as well as explicit
=================
*/
void R_CleanupTriangles( srfTriangles_t* tri, bool createNormals, bool identifySilEdges, bool useUnsmoothedTangents )
{
	R_RangeCheckIndexes( tri );
	
	R_CreateSilIndexes( tri );
	
//	R_RemoveDuplicatedTriangles( tri );	// this may remove valid overlapped transparent triangles

	R_RemoveDegenerateTriangles( tri );
	
	R_TestDegenerateTextureSpace( tri );
	
//	R_RemoveUnusedVerts( tri );

	if( identifySilEdges )
	{
		R_IdentifySilEdges( tri, true );	// assume it is non-deformable, and omit coplanar edges
	}
	
	// bust vertexes that share a mirrored edge into separate vertexes
	R_DuplicateMirroredVertexes( tri );
	
	R_CreateDupVerts( tri );
	
	R_BoundTriSurf( tri );
	
	if( useUnsmoothedTangents )
	{
		R_BuildDominantTris( tri );
		R_DeriveTangents( tri );
	}
	else if( !createNormals )
	{
		R_DeriveTangentsWithoutNormals( tri );
	}
	else
	{
		R_DeriveTangents( tri );
	}
}

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

DEFORMED SURFACES

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

/*
===================
R_BuildDeformInfo
===================
*/
deformInfo_t* R_BuildDeformInfo( int numVerts, const idDrawVert* verts, int numIndexes, const int* indexes,
								 bool useUnsmoothedTangents )
{
	srfTriangles_t	tri;
	memset( &tri, 0, sizeof( srfTriangles_t ) );
	
	tri.numVerts = numVerts;
	R_AllocStaticTriSurfVerts( &tri, tri.numVerts );
	SIMDProcessor->Memcpy( tri.verts, verts, tri.numVerts * sizeof( tri.verts[0] ) );
	
	tri.numIndexes = numIndexes;
	R_AllocStaticTriSurfIndexes( &tri, tri.numIndexes );
	
	// don't memcpy, so we can change the index type from int to short without changing the interface
	for( int i = 0; i < tri.numIndexes; i++ )
	{
		tri.indexes[i] = indexes[i];
	}
	
	R_RangeCheckIndexes( &tri );
	R_CreateSilIndexes( &tri );
	R_IdentifySilEdges( &tri, false );			// we cannot remove coplanar edges, because they can deform to silhouettes
	R_DuplicateMirroredVertexes( &tri );		// split mirror points into multiple points
	R_CreateDupVerts( &tri );
	if( useUnsmoothedTangents )
	{
		R_BuildDominantTris( &tri );
	}
	R_DeriveTangents( &tri );
	
	deformInfo_t* deform = ( deformInfo_t* )R_ClearedStaticAlloc( sizeof( *deform ) );
	
	deform->numSourceVerts = numVerts;
	deform->numOutputVerts = tri.numVerts;
	deform->verts = tri.verts;
	
	deform->numIndexes = numIndexes;
	deform->indexes = tri.indexes;
	
	deform->silIndexes = tri.silIndexes;
	
	deform->numSilEdges = tri.numSilEdges;
	deform->silEdges = tri.silEdges;
	
	deform->numMirroredVerts = tri.numMirroredVerts;
	deform->mirroredVerts = tri.mirroredVerts;
	
	deform->numDupVerts = tri.numDupVerts;
	deform->dupVerts = tri.dupVerts;
	
	if( tri.dominantTris != NULL )
	{
		Mem_Free( tri.dominantTris );
		tri.dominantTris = NULL;
	}
	
	idShadowVertSkinned* shadowVerts = ( idShadowVertSkinned* ) Mem_Alloc16( ALIGN( deform->numOutputVerts * 2 * sizeof( idShadowVertSkinned ), 16 ), TAG_MODEL );
	idShadowVertSkinned::CreateShadowCache( shadowVerts, deform->verts, deform->numOutputVerts );
	
	deform->staticAmbientCache = vertexCache.AllocStaticVertex( deform->verts, ALIGN( deform->numOutputVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) );
	deform->staticIndexCache = vertexCache.AllocStaticIndex( deform->indexes, ALIGN( deform->numIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) );
	deform->staticShadowCache = vertexCache.AllocStaticVertex( shadowVerts, ALIGN( deform->numOutputVerts * 2 * sizeof( idShadowVertSkinned ), VERTEX_CACHE_ALIGN ) );
	
	Mem_Free( shadowVerts );
	
	return deform;
}

/*
===================
R_FreeDeformInfo
===================
*/
void R_FreeDeformInfo( deformInfo_t* deformInfo )
{
	if( deformInfo->verts != NULL )
	{
		Mem_Free( deformInfo->verts );
	}
	if( deformInfo->indexes != NULL )
	{
		Mem_Free( deformInfo->indexes );
	}
	if( deformInfo->silIndexes != NULL )
	{
		Mem_Free( deformInfo->silIndexes );
	}
	if( deformInfo->silEdges != NULL )
	{
		Mem_Free( deformInfo->silEdges );
	}
	if( deformInfo->mirroredVerts != NULL )
	{
		Mem_Free( deformInfo->mirroredVerts );
	}
	if( deformInfo->dupVerts != NULL )
	{
		Mem_Free( deformInfo->dupVerts );
	}
	R_StaticFree( deformInfo );
}

/*
===================
R_DeformInfoMemoryUsed
===================
*/
int R_DeformInfoMemoryUsed( deformInfo_t* deformInfo )
{
	int total = 0;
	
	if( deformInfo->verts != NULL )
	{
		total += deformInfo->numOutputVerts * sizeof( deformInfo->verts[0] );
	}
	if( deformInfo->indexes != NULL )
	{
		total += deformInfo->numIndexes * sizeof( deformInfo->indexes[0] );
	}
	if( deformInfo->mirroredVerts != NULL )
	{
		total += deformInfo->numMirroredVerts * sizeof( deformInfo->mirroredVerts[0] );
	}
	if( deformInfo->dupVerts != NULL )
	{
		total += deformInfo->numDupVerts * sizeof( deformInfo->dupVerts[0] );
	}
	if( deformInfo->silIndexes != NULL )
	{
		total += deformInfo->numIndexes * sizeof( deformInfo->silIndexes[0] );
	}
	if( deformInfo->silEdges != NULL )
	{
		total += deformInfo->numSilEdges * sizeof( deformInfo->silEdges[0] );
	}
	
	total += sizeof( *deformInfo );
	return total;
}

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

VERTEX / INDEX CACHING

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

/*
===================
R_InitDrawSurfFromTri
===================
*/
void R_InitDrawSurfFromTri( drawSurf_t& ds, srfTriangles_t& tri )
{
	if( tri.numIndexes == 0 )
	{
		ds.numIndexes = 0;
		return;
	}
	
	// copy verts and indexes to this frame's hardware memory if they aren't already there
	//
	// deformed surfaces will not have any vertices but the ambient cache will have already
	// been created for them.
	if( ( tri.verts == NULL ) && !tri.referencedIndexes )
	{
		// pre-generated shadow models will not have any verts, just shadowVerts
		tri.ambientCache = 0;
	}
	else if( !vertexCache.CacheIsCurrent( tri.ambientCache ) )
	{
		tri.ambientCache = vertexCache.AllocVertex( tri.verts, ALIGN( tri.numVerts * sizeof( tri.verts[0] ), VERTEX_CACHE_ALIGN ) );
	}
	if( !vertexCache.CacheIsCurrent( tri.indexCache ) )
	{
		tri.indexCache = vertexCache.AllocIndex( tri.indexes, ALIGN( tri.numIndexes * sizeof( tri.indexes[0] ), INDEX_CACHE_ALIGN ) );
	}
	
	ds.numIndexes = tri.numIndexes;
	ds.ambientCache = tri.ambientCache;
	ds.indexCache = tri.indexCache;
	ds.shadowCache = tri.shadowCache;
	ds.jointCache = 0;
}

/*
===================
R_CreateStaticBuffersForTri

For static surfaces, the indexes, ambient, and shadow buffers can be pre-created at load
time, rather than being re-created each frame in the frame temporary buffers.
===================
*/
void R_CreateStaticBuffersForTri( srfTriangles_t& tri )
{
	tri.indexCache = 0;
	tri.ambientCache = 0;
	tri.shadowCache = 0;
	
	// index cache
	if( tri.indexes != NULL )
	{
		tri.indexCache = vertexCache.AllocStaticIndex( tri.indexes, ALIGN( tri.numIndexes * sizeof( tri.indexes[0] ), INDEX_CACHE_ALIGN ) );
	}
	
	// vertex cache
	if( tri.verts != NULL )
	{
		tri.ambientCache = vertexCache.AllocStaticVertex( tri.verts, ALIGN( tri.numVerts * sizeof( tri.verts[0] ), VERTEX_CACHE_ALIGN ) );
	}
	
	// shadow cache
	if( tri.preLightShadowVertexes != NULL )
	{
		// this should only be true for the _prelight<NAME> pre-calculated shadow volumes
		assert( tri.verts == NULL );	// pre-light shadow volume surfaces don't have ambient vertices
		const int shadowSize = ALIGN( tri.numVerts * 2 * sizeof( idShadowVert ), VERTEX_CACHE_ALIGN );
		tri.shadowCache = vertexCache.AllocStaticVertex( tri.preLightShadowVertexes, shadowSize );
	}
	else if( tri.verts != NULL )
	{
		// the shadowVerts for normal models include all the xyz values duplicated
		// for a W of 1 (near cap) and a W of 0 (end cap, projected to infinity)
		const int shadowSize = ALIGN( tri.numVerts * 2 * sizeof( idShadowVert ), VERTEX_CACHE_ALIGN );
		if( tri.staticShadowVertexes == NULL )
		{
			tri.staticShadowVertexes = ( idShadowVert* ) Mem_Alloc16( shadowSize, TAG_TEMP );
			idShadowVert::CreateShadowCache( tri.staticShadowVertexes, tri.verts, tri.numVerts );
		}
		tri.shadowCache = vertexCache.AllocStaticVertex( tri.staticShadowVertexes, shadowSize );
		
#if !defined( KEEP_INTERACTION_CPU_DATA )
		Mem_Free( tri.staticShadowVertexes );
		tri.staticShadowVertexes = NULL;
#endif
	}
}
