/*****************************************************************************
The Dark Mod GPL Source Code

This file is part of the The Dark Mod Source Code, originally based
on the Doom 3 GPL Source Code as published in 2011.

The Dark Mod 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. For details, see LICENSE.TXT.

Project: The Dark Mod (http://www.thedarkmod.com/)

******************************************************************************/

#include "precompiled.h"
#pragma hdrstop



#include "dmap.h"
#include "../../../renderer/tr_local.h"

/*

  given a set of faces that are clipped to the required frustum

  make 2D projection for each vertex

  for each edge
	add edge, generating new points at each edge intersection

  ?add all additional edges to make a full triangulation

  make full triangulation

  for each triangle
	find midpoint
	find original triangle with midpoint closest to view
	annotate triangle with that data
	project all vertexes to that plane
	output the triangle as a front cap

  snap all vertexes
  make a back plane projection for all vertexes

  for each edge
	if one side doesn't have a triangle
		make a sil edge to back plane projection
		continue
	if triangles on both sides have two verts in common
		continue
	make a sil edge from one triangle to the other
		



  classify triangles on common planes, so they can be optimized 

  what about interpenetrating triangles???

  a perfect shadow volume will have every edge exactly matched with
  an opposite, and no two triangles covering the same area on either
  the back projection or a silhouette edge.

  Optimizing the triangles on the projected plane can give a significant
  improvement, but the quadratic time nature of the optimization process
  probably makes it untenable.

  There exists some small room for further triangle count optimizations of the volumes
  by collapsing internal surface geometry in some cases, or allowing original triangles
  to extend outside the exactly light frustum without being clipped, but it probably
  isn't worth it.

  Triangle count optimizations at the expense of a slight fill rate cost
  may be apropriate in some cases.


  Perform the complete clipping on all triangles
  for each vertex
	project onto the apropriate plane and mark plane bit as in use
for each triangle
	if points project onto different planes, clip 
*/


typedef struct {
	idVec3	v[3];
	idVec3	edge[3];	// positive side is inside the triangle
	glIndex_t	index[3];
	idPlane	plane;		// positive side is forward for the triangle, which is away from the light
	int		planeNum;	// from original triangle, not calculated from the clipped verts
} shadowTri_t;

static const int MAX_SHADOW_TRIS = 32768;

static	shadowTri_t	outputTris[MAX_SHADOW_TRIS];
static	int		numOutputTris;

typedef struct shadowOptEdge_s {
	glIndex_t	index[2];
	struct shadowOptEdge_s	*nextEdge;
} shadowOptEdge_t;

static const int MAX_SIL_EDGES = MAX_SHADOW_TRIS*3;
static	shadowOptEdge_t	silEdges[MAX_SIL_EDGES];
static	int		numSilEdges;

typedef struct silQuad_s {
	int		nearV[2];
	int		farV[2];		// will always be a projection of near[]
	struct silQuad_s	*nextQuad;
} silQuad_t;

static const int MAX_SIL_QUADS = MAX_SHADOW_TRIS*3;
static	silQuad_t	silQuads[MAX_SIL_QUADS];
static int		numSilQuads;


typedef struct {
	idVec3	normal;	// all sil planes go through the projection origin
	shadowOptEdge_t	*edges;
	silQuad_t		*fragmentedQuads;
} silPlane_t;

static float EDGE_PLANE_EPSILON	= 0.1f;
static float UNIQUE_EPSILON = 0.1f;

static int		numSilPlanes;
static silPlane_t	*silPlanes;

// the uniqued verts are still in projection centered space, not global space
static	int		numUniqued;
static	int		numUniquedBeforeProjection;
static	int		maxUniqued;
static	idVec3	*uniqued;

static	optimizedShadow_t	ret;
static	int		maxRetIndexes;

static int FindUniqueVert( idVec3 &v );

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

/*
=================
CreateEdgesForTri
=================
*/
static void CreateEdgesForTri( shadowTri_t *tri ) {
	for ( int j = 0 ; j < 3 ; j++ ) {
		idVec3 &v1 = tri->v[j];
		idVec3 &v2 = tri->v[(j+1)%3];

		tri->edge[j].Cross( v2, v1 );
		tri->edge[j].Normalize();
	}
}


static const float EDGE_EPSILON = 0.1f;

static bool TriOutsideTri( const shadowTri_t *a, const shadowTri_t *b ) {
#if 0
	if ( a->v[0] * b->edge[0] <= EDGE_EPSILON
		&& a->v[1] * b->edge[0] <= EDGE_EPSILON
		&& a->v[2] * b->edge[0] <= EDGE_EPSILON ) {
			return true;
		}
	if ( a->v[0] * b->edge[1] <= EDGE_EPSILON
		&& a->v[1] * b->edge[1] <= EDGE_EPSILON
		&& a->v[2] * b->edge[1] <= EDGE_EPSILON ) {
			return true;
		}
	if ( a->v[0] * b->edge[2] <= EDGE_EPSILON
		&& a->v[1] * b->edge[2] <= EDGE_EPSILON
		&& a->v[2] * b->edge[2] <= EDGE_EPSILON ) {
			return true;
		}
#else
	for ( int i = 0 ; i < 3 ; i++ ) {
		int		j;
		for ( j = 0 ; j < 3 ; j++ ) {
			float	d = a->v[j] * b->edge[i];
			if ( d > EDGE_EPSILON ) {
				break;
			}
		}
		if ( j == 3 ) {
			return true;
		}
	}
#endif
	return false;
}

static bool TriBehindTri( const shadowTri_t *a, const shadowTri_t *b ) {
	float	d;
	
	d = b->plane.Distance( a->v[0] );
	if ( d > 0 ) {
		return true;
	}
	d = b->plane.Distance( a->v[1] );
	if ( d > 0 ) {
		return true;
	}
	d = b->plane.Distance( a->v[2] );
	if ( d > 0 ) {
		return true;
	}

	return false;
}

/*
===================
ClipTriangle_r
===================
*/
static int c_removedFragments;
static void ClipTriangle_r( const shadowTri_t *tri, int startTri, int skipTri, int numTris, const shadowTri_t *tris ) {
	// create edge planes for this triangle

	// compare against all the other triangles
	for ( int i = startTri ; i < numTris ; i++ ) {
		if ( i == skipTri ) {
			continue;
		}
		const shadowTri_t	*other = &tris[i];

		if ( TriOutsideTri( tri, other ) ) {
			continue;
		}
		if ( TriOutsideTri( other, tri ) ) {
			continue;
		}
		// they overlap to some degree

		// if other is behind tri, it doesn't clip it
		if ( !TriBehindTri( tri, other ) ) {
			continue;
		}

		// clip it
		idWinding	*w = new idWinding( tri->v, 3 );

		for ( int j = 0 ; j < 4 && w ; j++ ) {
			idWinding	*front, *back;

			// keep any portion in front of other's plane
			if ( j == 0 ) {
				w->Split( other->plane, ON_EPSILON, &front, &back );
			} else {
				w->Split( idPlane( other->edge[j-1], 0.0f ), ON_EPSILON, &front, &back );
			}
			if ( back ) {
				// recursively clip these triangles to all subsequent triangles
				for ( int k = 2 ; k < back->GetNumPoints() ; k++ ) {
					shadowTri_t	fragment = *tri;
					
					fragment.v[0] = (*back)[0].ToVec3();
					fragment.v[1] = (*back)[k-1].ToVec3();
					fragment.v[2] = (*back)[k].ToVec3();
					CreateEdgesForTri( &fragment );
					ClipTriangle_r( &fragment, i + 1, skipTri, numTris, tris );
				}
				delete back;
			}

			delete w;
			w = front;
		}
		if ( w ) {
			delete w;
		}

		c_removedFragments++;
		// any fragments will have been added recursively
		return;
	}

	// this fragment is frontmost, so add it to the output list
	if ( numOutputTris == MAX_SHADOW_TRIS ) {
		common->Error( "numOutputTris == MAX_SHADOW_TRIS" );
	}

	outputTris[numOutputTris] = *tri;
	numOutputTris++;
}


/*
====================
ClipOccluders

Generates outputTris by clipping all the triangles against each other,
retaining only those closest to the projectionOrigin
====================
*/
static void ClipOccluders( idVec4 *verts, glIndex_t *indexes, int numIndexes, 
										 idVec3 projectionOrigin ) {
	int					numTris = numIndexes / 3;
	int					i;
	shadowTri_t			*tris = (shadowTri_t *)_alloca( numTris * sizeof( *tris ) );
	shadowTri_t			*tri;

	common->Printf( "ClipOccluders: %i triangles\n", numTris );

	for ( i = 0 ; i < numTris ; i++ ) {
		tri = &tris[i];

		// the indexes are in reversed order from tr_stencilshadow
		tri->v[0] = verts[indexes[i*3+2]].ToVec3() - projectionOrigin;
		tri->v[1] = verts[indexes[i*3+1]].ToVec3() - projectionOrigin;
		tri->v[2] = verts[indexes[i*3+0]].ToVec3() - projectionOrigin;

		idVec3	d1 = tri->v[1] - tri->v[0];
		idVec3	d2 = tri->v[2] - tri->v[0];
		
		tri->plane.ToVec4().ToVec3().Cross( d2, d1 );
		tri->plane.ToVec4().ToVec3().Normalize();
		tri->plane[3] = - ( tri->v[0] * tri->plane.ToVec4().ToVec3() );

		// get the plane number before any clipping
		// we should avoid polluting the regular dmap planes with these
		// that are offset from the light origin...
		tri->planeNum = FindFloatPlane( tri->plane );

		CreateEdgesForTri( tri );
	}

	// clear our output buffer
	numOutputTris = 0;

	// for each triangle, clip against all other triangles
	int	numRemoved = 0;
	int	numComplete = 0;
	int numFragmented = 0;

	for ( i = 0 ; i < numTris ; i++ ) {
		int oldOutput = numOutputTris;
		c_removedFragments = 0;
		ClipTriangle_r( &tris[i], 0, i, numTris, tris );
		if ( numOutputTris == oldOutput ) {
			numRemoved++;		// completely unused
		} else if ( c_removedFragments == 0 ) {
			// the entire triangle is visible
			numComplete++;
			shadowTri_t	*out = &outputTris[oldOutput];
			*out = tris[i];
			numOutputTris = oldOutput+1;
		} else {
			numFragmented++;
			// we made at least one fragment

			// if we are at the low optimization level, just use a single
			// triangle if it produced any fragments
			if ( dmapGlobals.shadowOptLevel == SO_CULL_OCCLUDED ) {
				shadowTri_t	*out = &outputTris[oldOutput];
				*out = tris[i];
				numOutputTris = oldOutput+1;
			}
		}
	}
	common->Printf( "%i triangles completely invisible\n", numRemoved );
	common->Printf( "%i triangles completely visible\n", numComplete );
	common->Printf( "%i triangles fragmented\n", numFragmented );
	common->Printf( "%i shadowing fragments before optimization\n", numOutputTris );
}

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

/*
================
OptimizeOutputTris
================
*/
static void OptimizeOutputTris( void ) {
	int		i;

	// optimize the clipped surfaces
	optimizeGroup_t		*optGroups = NULL;
	optimizeGroup_t *checkGroup;

	for ( i = 0 ; i < numOutputTris ; i++ ) {
		shadowTri_t		*tri = &outputTris[i];

		int	planeNum = tri->planeNum;

		// add it to an optimize group
		for ( checkGroup = optGroups ; checkGroup ; checkGroup = checkGroup->nextGroup ) {
			if ( checkGroup->planeNum == planeNum ) {
				break;
			}
		}
		if ( !checkGroup ) {
			// create a new optGroup
			checkGroup = (optimizeGroup_t *)Mem_ClearedAlloc( sizeof( *checkGroup ) );
			checkGroup->planeNum = planeNum;
			checkGroup->nextGroup = optGroups;
			optGroups = checkGroup;
		}

		// create a mapTri for the optGroup
		mapTri_t	*mtri = (mapTri_t *)Mem_ClearedAlloc( sizeof( *mtri ) );
		mtri->v[0].xyz = tri->v[0];
		mtri->v[1].xyz = tri->v[1];
		mtri->v[2].xyz = tri->v[2];
		mtri->next = checkGroup->triList;
		checkGroup->triList = mtri;
	}

	OptimizeGroupList( optGroups );

	numOutputTris = 0;
	for ( checkGroup = optGroups ; checkGroup ; checkGroup = checkGroup->nextGroup ) {
		for ( mapTri_t *mtri = checkGroup->triList ; mtri ; mtri = mtri->next ) {
			shadowTri_t *tri = &outputTris[numOutputTris];
			numOutputTris++;
			tri->v[0] = mtri->v[0].xyz;
			tri->v[1] = mtri->v[1].xyz;
			tri->v[2] = mtri->v[2].xyz;
		}
	}
	FreeOptimizeGroupList( optGroups ); 
}

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

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

/*
=====================
GenerateSilEdges

Output tris must be tjunction fixed and vertex uniqued
A edge that is not exactly matched is a silhouette edge
We could skip this and rely completely on the matched quad removal
for all sil edges, but this will avoid the bulk of the checks.
=====================
*/
static void GenerateSilEdges( void ) {
	int		i, j;

	unsigned	*edges = (unsigned *)_alloca( (numOutputTris*3+1)*sizeof(*edges) );
	int		numEdges = 0;

	numSilEdges = 0;

	for ( i = 0 ; i < numOutputTris ; i++ ) {
		int a = outputTris[i].index[0];
		int b = outputTris[i].index[1];
		int c = outputTris[i].index[2];
		if ( a == b || a == c || b == c ) {
			continue;		// degenerate
		}

		for ( j = 0 ; j < 3 ; j++ ) {
			int	v1, v2;

			v1 = outputTris[i].index[j];
			v2 = outputTris[i].index[(j+1)%3];
			if ( v1 == v2 ) {
				continue;		// degenerate
			}
			if ( v1 > v2 ) {
				edges[numEdges] = ( v1 << 16 ) | ( v2 << 1 );
			} else {
				edges[numEdges] = ( v2 << 16 ) | ( v1 << 1 ) | 1;
			}
			numEdges++;
		}
	}

	qsort( edges, numEdges, sizeof( edges[0] ), EdgeSort );
	edges[numEdges] = -1;	// force the last to make an edge if no matched to previous

	for ( i = 0 ; i < numEdges ; i++ ) {
		if ( ( edges[i] ^ edges[i+1] ) == 1 ) {
			// skip the next one, because we matched and
			// removed both
			i++;
			continue;
		}
		// this is an unmatched edge, so we need to generate a sil plane
		int		v1, v2;
		if ( edges[i] & 1 ) {
			v2 = edges[i] >> 16;
			v1 = ( edges[i] >> 1 ) & 0x7fff;
		} else {
			v1 = edges[i] >> 16;
			v2 = ( edges[i] >> 1 ) & 0x7fff;
		}

		if ( numSilEdges == MAX_SIL_EDGES ) {
			common->Error( "numSilEdges == MAX_SIL_EDGES" );
		}
		silEdges[numSilEdges].index[0] = v1;
		silEdges[numSilEdges].index[1] = v2;
		numSilEdges++;
	}
}

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

/*
=====================
GenerateSilPlanes

Groups the silEdges into common planes
=====================
*/
void GenerateSilPlanes( void ) {
	numSilPlanes = 0;
	silPlanes = (silPlane_t *)Mem_Alloc( sizeof( *silPlanes ) * numSilEdges );

	// identify the silPlanes
	numSilPlanes = 0;
	for ( int i = 0 ; i < numSilEdges ; i++ ) {
		if ( silEdges[i].index[0] == silEdges[i].index[1] ) {
			continue;	// degenerate
		}

		idVec3 &v1 = uniqued[silEdges[i].index[0]];
		idVec3 &v2 = uniqued[silEdges[i].index[1]];

		// search for an existing plane
		int	j;
		for ( j = 0 ; j < numSilPlanes ; j++ ) {
			float d = v1 * silPlanes[j].normal;
			float d2 = v2 * silPlanes[j].normal;

			if ( fabs( d ) < EDGE_PLANE_EPSILON
				&& fabs( d2 ) < EDGE_PLANE_EPSILON ) {
				silEdges[i].nextEdge = silPlanes[j].edges;
				silPlanes[j].edges = &silEdges[i];
				break;
			}
		}

		if ( j == numSilPlanes ) {
			// create a new silPlane
			silPlanes[j].normal.Cross( v2, v1 );
			silPlanes[j].normal.Normalize();
			silEdges[i].nextEdge = NULL;
			silPlanes[j].edges = &silEdges[i];
			silPlanes[j].fragmentedQuads = NULL;
			numSilPlanes++;
		}
	}
}

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

/*
=============
SaveQuad
=============
*/
static void SaveQuad( silPlane_t *silPlane, silQuad_t &quad ) {
	// this fragment is a final fragment
	if ( numSilQuads == MAX_SIL_QUADS ) {
		common->Error( "numSilQuads == MAX_SIL_QUADS" );
	}
	silQuads[numSilQuads] = quad;
	silQuads[numSilQuads].nextQuad = silPlane->fragmentedQuads;
	silPlane->fragmentedQuads = &silQuads[numSilQuads];
	numSilQuads++;
}


/*
===================
FragmentSilQuad

Clip quads, or reconstruct?
Generate them T-junction free, or require another pass of fix-tjunc?
Call optimizer on a per-sil-plane basis?
	will this ever introduce tjunctions with the front faces?
	removal of planes can allow the rear projection to be farther optimized

For quad clipping
	PlaneThroughEdge

quad clipping introduces new vertexes

Cannot just fragment edges, must emit full indexes

what is the bounds on max indexes?
	the worst case is that all edges but one carve an existing edge in the middle,
	giving twice the input number of indexes (I think)

can we avoid knowing about projected positions and still optimize?

Fragment all edges first
Introduces T-junctions
create additional silEdges, linked to silPlanes

In theory, we should never have more than one edge clipping a given
fragment, but it is more robust if we check them all
===================
*/
static void FragmentSilQuad( silQuad_t quad, silPlane_t *silPlane, 
							shadowOptEdge_t *startEdge, shadowOptEdge_t *skipEdge ) {
	if ( quad.nearV[0] == quad.nearV[1] ) {
		return;
	}

	for ( shadowOptEdge_t *check = startEdge ; check ; check = check->nextEdge ) {
		if ( check == skipEdge ) {
			// don't clip against self
			continue;
		}

		if ( check->index[0] == check->index[1] ) {
			continue;
		}

		// make planes through both points of check
		for ( int i = 0 ; i < 2 ; i++ ) {
			idVec3	plane;

			plane.Cross( uniqued[check->index[i]], silPlane->normal );
			plane.Normalize();

			if ( plane.Length() < 0.9 ) {
				continue;
			}

			// if the other point on check isn't on the negative side of the plane,
			// flip the plane
			if ( uniqued[check->index[!i]] * plane > 0 ) {
				plane = -plane;
			}

			float d1 = uniqued[quad.nearV[0]] * plane;
			float d2 = uniqued[quad.nearV[1]] * plane;

			float d3 = uniqued[quad.farV[0]] * plane;
			float d4 = uniqued[quad.farV[1]] * plane;

			// it is better to conservatively NOT split the quad, which, at worst,
			// will leave some extra overdraw

			// if the plane divides the incoming edge, split it and recurse
			// with the outside fraction before continuing with the inside fraction
			if ( ( d1 > EDGE_PLANE_EPSILON && d3 > EDGE_PLANE_EPSILON && d2 < -EDGE_PLANE_EPSILON && d4 < -EDGE_PLANE_EPSILON )
				||  ( d2 > EDGE_PLANE_EPSILON && d4 > EDGE_PLANE_EPSILON && d1 < -EDGE_PLANE_EPSILON && d3 < -EDGE_PLANE_EPSILON ) ) {
				float f = d1 / ( d1 - d2 );
				float f2 = d3 / ( d3 - d4 );
f = f2;
				if ( f <= 0.0001 || f >= 0.9999 ) {
					common->Error( "Bad silQuad fraction" );
				}

				// finding uniques may be causing problems here
				idVec3	nearMid = (1-f) * uniqued[quad.nearV[0]] + f * uniqued[quad.nearV[1]];
				int nearMidIndex = FindUniqueVert( nearMid );
				idVec3	farMid = (1-f) * uniqued[quad.farV[0]] + f * uniqued[quad.farV[1]];
				int farMidIndex = FindUniqueVert( farMid );

				silQuad_t	clipped = quad;

				if ( d1 > EDGE_PLANE_EPSILON ) {
					clipped.nearV[1] = nearMidIndex;
					clipped.farV[1] = farMidIndex;
					FragmentSilQuad( clipped, silPlane, check->nextEdge, skipEdge );
					quad.nearV[0] = nearMidIndex;
					quad.farV[0] = farMidIndex;
				} else {
					clipped.nearV[0] = nearMidIndex;
					clipped.farV[0] = farMidIndex;
					FragmentSilQuad( clipped, silPlane, check->nextEdge, skipEdge );
					quad.nearV[1] = nearMidIndex;
					quad.farV[1] = farMidIndex;
				}
			}
		}

		// make a plane through the line of check
		idPlane	separate;

		idVec3	dir = uniqued[check->index[1]] - uniqued[check->index[0]];
		separate.Normal().Cross( dir, silPlane->normal );
		separate.Normal().Normalize();
		separate.ToVec4()[3] = -(uniqued[check->index[1]] * separate.Normal());

		// this may miss a needed separation when the quad would be
		// clipped into a triangle and a quad
		float d1 = separate.Distance( uniqued[quad.nearV[0]] );
		float d2 = separate.Distance( uniqued[quad.farV[0]] );

		if ( ( d1 < EDGE_PLANE_EPSILON && d2 < EDGE_PLANE_EPSILON )
			|| ( d1 > -EDGE_PLANE_EPSILON && d2 > -EDGE_PLANE_EPSILON ) ) {
				continue;
		}

		// split the quad at this plane
		float f = d1 / ( d1 - d2 );
		idVec3	mid0 = (1-f) * uniqued[quad.nearV[0]] + f * uniqued[quad.farV[0]];
		int mid0Index = FindUniqueVert( mid0 );

		d1 = separate.Distance( uniqued[quad.nearV[1]] );
		d2 = separate.Distance( uniqued[quad.farV[1]] );
		f = d1 / ( d1 - d2 );
		if ( f < 0 || f > 1 ) {
			continue;
		}

		idVec3	mid1 = (1-f) * uniqued[quad.nearV[1]] + f * uniqued[quad.farV[1]];
		int mid1Index = FindUniqueVert( mid1 );

		silQuad_t	clipped = quad;

		clipped.nearV[0] = mid0Index;
		clipped.nearV[1] = mid1Index;
		FragmentSilQuad( clipped, silPlane, check->nextEdge, skipEdge );
		quad.farV[0] = mid0Index;
		quad.farV[1] = mid1Index;
	}

	SaveQuad( silPlane, quad );
}


/*
===============
FragmentSilQuads
===============
*/
static void FragmentSilQuads( void ) {
	// group the edges into common planes
	GenerateSilPlanes();

	numSilQuads = 0;

	// fragment overlapping edges
	for ( int i = 0 ; i < numSilPlanes ; i++ ) {
		silPlane_t *sil = &silPlanes[i];

		for ( shadowOptEdge_t *e1 = sil->edges ; e1 ; e1 = e1->nextEdge ) {
			silQuad_t	quad;

			quad.nearV[0] = e1->index[0];
			quad.nearV[1] = e1->index[1];
			if ( e1->index[0] == e1->index[1] ) {
				common->Error( "FragmentSilQuads: degenerate edge" );
			}
			quad.farV[0] = e1->index[0] + numUniquedBeforeProjection;
			quad.farV[1] = e1->index[1] + numUniquedBeforeProjection;
			FragmentSilQuad( quad, sil, sil->edges, e1 );
		}
	}
}

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

/*
=====================
EmitFragmentedSilQuads

=====================
*/
static void EmitFragmentedSilQuads( void ) {
	int		i, j, k;
	mapTri_t	*mtri;

	for ( i = 0 ; i < numSilPlanes ; i++ ) {
		silPlane_t	*sil = &silPlanes[i];

		// prepare for optimizing the sil quads on each side of the sil plane
		optimizeGroup_t	groups[2];
		memset( &groups, 0, sizeof( groups ) );
		idPlane		planes[2];
		planes[0].Normal() = sil->normal;
		planes[0][3] = 0;
		planes[1] = -planes[0];
		groups[0].planeNum = FindFloatPlane( planes[0] );
		groups[1].planeNum = FindFloatPlane( planes[1] );

		// emit the quads that aren't matched
		for ( silQuad_t *f1 = sil->fragmentedQuads ; f1 ; f1 = f1->nextQuad ) {
			silQuad_t *f2;
			for ( f2 = sil->fragmentedQuads ; f2 ; f2 = f2->nextQuad ) {
				if ( f2 == f1 ) {
					continue;
				}
				// in theory, this is sufficient, but we might
				// have some cases of tripple+ matching, or unclipped rear projections
				if ( f1->nearV[0] == f2->nearV[1] && f1->nearV[1] == f2->nearV[0] ) {
					break;
				}
			}
			// if we went through all the quads without finding a match, emit the quad
			if ( !f2 ) {
				optimizeGroup_t	*gr;
				idVec3	v1, v2, normal;

				mtri = (mapTri_t *)Mem_ClearedAlloc( sizeof( *mtri ) );
				mtri->v[0].xyz = uniqued[f1->nearV[0]];
				mtri->v[1].xyz = uniqued[f1->nearV[1]];
				mtri->v[2].xyz = uniqued[f1->farV[1]];

				v1 = mtri->v[1].xyz - mtri->v[0].xyz;
				v2 = mtri->v[2].xyz - mtri->v[0].xyz;
				normal.Cross( v2, v1 );

				if ( normal * planes[0].Normal() > 0 ) {
					gr = &groups[0];
				} else {
					gr = &groups[1];
				}

				mtri->next = gr->triList;
				gr->triList = mtri;

				mtri = (mapTri_t *)Mem_ClearedAlloc( sizeof( *mtri ) );
				mtri->v[0].xyz = uniqued[f1->farV[0]];
				mtri->v[1].xyz = uniqued[f1->nearV[0]];
				mtri->v[2].xyz = uniqued[f1->farV[1]];

				mtri->next = gr->triList;
				gr->triList = mtri;

#if 0
				// emit a sil quad all the way to the projection plane
				int index = ret.totalIndexes;
				if ( index + 6 > maxRetIndexes ) {
					common->Error( "maxRetIndexes exceeded" );
				}
				ret.indexes[index+0] = f1->nearV[0];
				ret.indexes[index+1] = f1->nearV[1];
				ret.indexes[index+2] = f1->farV[1];
				ret.indexes[index+3] = f1->farV[0];
				ret.indexes[index+4] = f1->nearV[0];
				ret.indexes[index+5] = f1->farV[1];
				ret.totalIndexes += 6;
#endif
			}
		}


		// optimize
		for ( j = 0 ; j < 2 ; j++ ) {
			if ( !groups[j].triList ) {
				continue;
			}
			if ( dmapGlobals.shadowOptLevel == SO_SIL_OPTIMIZE ) {
				OptimizeGroupList( &groups[j] );
			}
			// add as indexes
			for ( mtri = groups[j].triList ; mtri ; mtri = mtri->next ) {
				for ( k = 0 ; k < 3 ; k++ ) {
					if ( ret.totalIndexes == maxRetIndexes ) {
						common->Error( "maxRetIndexes exceeded" );
					}
					ret.indexes[ret.totalIndexes] = FindUniqueVert( mtri->v[k].xyz );
					ret.totalIndexes++;
				}
			}
			FreeTriList( groups[j].triList );
		}
	}

	// we don't need the silPlane grouping anymore
	Mem_Free( silPlanes );
}

/*
=================
EmitUnoptimizedSilEdges
=================
*/
static void EmitUnoptimizedSilEdges( void ) {
	int	i;

	for ( i = 0 ; i < numSilEdges ; i++ ) {
		int v1 = silEdges[i].index[0];
		int v2 = silEdges[i].index[1];
		int index = ret.totalIndexes;
		ret.indexes[index+0] = v1;
		ret.indexes[index+1] = v2;
		ret.indexes[index+2] = v2+numUniquedBeforeProjection;
		ret.indexes[index+3] = v1+numUniquedBeforeProjection;
		ret.indexes[index+4] = v1;
		ret.indexes[index+5] = v2+numUniquedBeforeProjection;
		ret.totalIndexes += 6;
	}
}

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

/*
================
FindUniqueVert
================
*/
static int FindUniqueVert( idVec3 &v ) {
	int	k;

	for ( k = 0 ; k < numUniqued ; k++ ) {
		idVec3 &check = uniqued[k];
		if ( fabs( v[0] - check[0] ) < UNIQUE_EPSILON
		&& fabs( v[1] - check[1] ) < UNIQUE_EPSILON
		&& fabs( v[2] - check[2] ) < UNIQUE_EPSILON ) {
			return k;
		}
	}
	if ( numUniqued == maxUniqued ) {
		common->Error( "FindUniqueVert: numUniqued == maxUniqued" );
	}
	uniqued[numUniqued] = v;
	numUniqued++;

	return k;
}

/*
===================
UniqueVerts

Snaps all triangle verts together, setting tri->index[]
and generating numUniqued and uniqued.
These are still in projection-centered space, not global space
===================
*/
static void UniqueVerts( void ) {
	int		i, j;

	// we may add to uniqued later when splitting sil edges, so leave
	// some extra room
	maxUniqued = 100000; // numOutputTris * 10 + 1000;
	uniqued = (idVec3 *)Mem_Alloc( sizeof( *uniqued ) * maxUniqued );
	numUniqued = 0;

	for ( i = 0 ; i < numOutputTris ; i++ ) {
		for ( j = 0 ; j < 3 ; j++ ) {
			outputTris[i].index[j] = FindUniqueVert( outputTris[i].v[j] );
		}
	}
}

/*
======================
ProjectUniqued
======================
*/
static void ProjectUniqued( idVec3 projectionOrigin, idPlane projectionPlane ) {
	// calculate the projection 
	idVec4		mat[4];

	R_LightProjectionMatrix( projectionOrigin, projectionPlane, mat );

	if ( numUniqued * 2 > maxUniqued ) {
		common->Error( "ProjectUniqued: numUniqued * 2 > maxUniqued" );
	}

	// this is goofy going back and forth between the spaces,
	// but I don't want to change R_LightProjectionMatrix righ tnow...
	for ( int i = 0 ; i < numUniqued ; i++ ) {
		// put the vert back in global space, instead of light centered space
		idVec3 in = uniqued[i] + projectionOrigin;

		// project to far plane
		float	w, oow;
		idVec3	out;

		w = in * mat[3].ToVec3() + mat[3][3];

		oow = 1.0 / w;
		out.x = ( in * mat[0].ToVec3() + mat[0][3] ) * oow;
		out.y = ( in * mat[1].ToVec3() + mat[1][3] ) * oow;
		out.z = ( in * mat[2].ToVec3() + mat[2][3] ) * oow;

		uniqued[numUniqued+i] = out - projectionOrigin;
	}
	numUniqued *= 2;
}

/*
====================
SuperOptimizeOccluders

This is the callback from the renderer shadow generation routine, after
verts have been culled against individual frustums of point lights

====================
*/
optimizedShadow_t SuperOptimizeOccluders( idVec4 *verts, glIndex_t *indexes, int numIndexes, 
										 idPlane projectionPlane, idVec3 projectionOrigin )
{
	memset( &ret, 0, sizeof( ret ) );

	// generate outputTris, removing fragments that are occluded by closer fragments
	ClipOccluders( verts, indexes, numIndexes, projectionOrigin );

	if ( dmapGlobals.shadowOptLevel >= SO_CULL_OCCLUDED ) {
		OptimizeOutputTris();
	}

	// match up common verts
	UniqueVerts();

	// now that we have uniqued the vertexes, we can find unmatched
	// edges, which are silhouette planes
	GenerateSilEdges();

	// generate the projected verts
	numUniquedBeforeProjection = numUniqued;
	ProjectUniqued( projectionOrigin, projectionPlane );

	// fragment the sil edges where the overlap,
	// possibly generating some additional unique verts
	if ( dmapGlobals.shadowOptLevel >= SO_CLIP_SILS ) {
		FragmentSilQuads();
	}

	// indexes for face and projection caps
	ret.numFrontCapIndexes = numOutputTris * 3;
	ret.numRearCapIndexes = numOutputTris * 3;
	if ( dmapGlobals.shadowOptLevel >= SO_CLIP_SILS ) {
		ret.numSilPlaneIndexes = numSilQuads * 12;	// this is the worst case with clipping
	} else {
		ret.numSilPlaneIndexes = numSilEdges * 6;	// this is the worst case with clipping
	}

	ret.totalIndexes = 0;
	
	maxRetIndexes = ret.numFrontCapIndexes + ret.numRearCapIndexes + ret.numSilPlaneIndexes;

	ret.indexes = (glIndex_t *)Mem_Alloc( maxRetIndexes * sizeof( ret.indexes[0] ) );
	for ( int i = 0 ; i < numOutputTris ; i++ ) {
		// flip the indexes so the surface triangle faces outside the shadow volume
		ret.indexes[i*3+0] = outputTris[i].index[2];
		ret.indexes[i*3+1] = outputTris[i].index[1];
		ret.indexes[i*3+2] = outputTris[i].index[0];

		ret.indexes[(numOutputTris+i)*3+0] = numUniquedBeforeProjection + outputTris[i].index[0];
		ret.indexes[(numOutputTris+i)*3+1] = numUniquedBeforeProjection + outputTris[i].index[1];
		ret.indexes[(numOutputTris+i)*3+2] = numUniquedBeforeProjection + outputTris[i].index[2];
	}
	// emit the sil planes
	ret.totalIndexes = ret.numFrontCapIndexes + ret.numRearCapIndexes;

	if ( dmapGlobals.shadowOptLevel >= SO_CLIP_SILS ) {
		// re-optimize the sil planes, cutting 
		EmitFragmentedSilQuads();
	} else {
		// indexes for silhouette edges
		EmitUnoptimizedSilEdges();
	}

	// we have all the verts now
	// create twice the uniqued verts
	ret.numVerts = numUniqued;
	ret.verts = (idVec3 *)Mem_Alloc( ret.numVerts * sizeof( ret.verts[0] ) );
	for ( int i = 0 ; i < numUniqued ; i++ ) {
		// put the vert back in global space, instead of light centered space
		ret.verts[i] = uniqued[i] + projectionOrigin;
	}

	// set the final index count
	ret.numSilPlaneIndexes = ret.totalIndexes - (ret.numFrontCapIndexes + ret.numRearCapIndexes);

	// free out local data
	Mem_Free( uniqued );

	return ret;
}

/*
=================
RemoveDegenerateTriangles
=================
*/
static void RemoveDegenerateTriangles( srfTriangles_t *tri ) {
	int		c_removed;
	int		i;
	int		a, b, c;

	// check for completely degenerate triangles
	c_removed = 0;
	for ( i = 0 ; i < tri->numIndexes ; i+=3 ) {
		a = tri->indexes[i];
		b = tri->indexes[i+1];
		c = tri->indexes[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] ) );
			tri->numIndexes -= 3;
			if ( i < tri->numShadowIndexesNoCaps ) {
				tri->numShadowIndexesNoCaps -= 3;
			}
			if ( i < tri->numShadowIndexesNoFrontCaps ) {
				tri->numShadowIndexesNoFrontCaps -= 3;
			}
			i -= 3;
		}
	}

	// this doesn't free the memory used by the unused verts
	/* grayman - message is useless, since the mapper can't do anything with it.
	   it also confuses new mappers, who always ask about it.
	if ( c_removed ) {
		common->Printf( "removed %i degenerate triangles from shadow\n", c_removed );
	}
	*/
}

/*
====================
CleanupOptimizedShadowTris

Uniques all verts across the frustums
removes matched sil quads at frustum seams
removes degenerate tris
====================
*/
void CleanupOptimizedShadowTris( srfTriangles_t *tri ) {
	int		i;

	// unique all the verts
	maxUniqued = tri->numVerts;
	uniqued = (idVec3 *)_alloca( sizeof( *uniqued ) * maxUniqued );
	numUniqued = 0;

	glIndex_t	*remap = (glIndex_t *)_alloca( sizeof( *remap ) * tri->numVerts );

	for ( i = 0 ; i < tri->numIndexes ; i++ ) {
		if ( tri->indexes[i] > tri->numVerts || tri->indexes[i] < 0 ) {
			common->Error( "CleanupOptimizedShadowTris: index out of range" );
		}
	}

	for ( i = 0 ; i < tri->numVerts ; i++ ) {
		remap[i] = FindUniqueVert( tri->shadowVertexes[i].xyz.ToVec3() );
	}
	tri->numVerts = numUniqued;
	for ( i = 0 ; i < tri->numVerts ; i++ ) {
		tri->shadowVertexes[i].xyz.ToVec3() = uniqued[i];
		tri->shadowVertexes[i].xyz[3] = 1;
	}

	for ( i = 0 ; i < tri->numIndexes ; i++ ) {
		tri->indexes[i] = remap[tri->indexes[i]];
	}

	// remove matched quads
	int	numSilIndexes = tri->numShadowIndexesNoCaps;
	for ( int i = 0 ; i < numSilIndexes ; i+=6 ) {
		int	j;
		for ( j = i+6 ; j < numSilIndexes ; j+=6 ) {
			// if there is a reversed quad match, we can throw both of them out
			// this is not a robust check, it relies on the exact ordering of
			// quad indexes
			if ( tri->indexes[i+0] == tri->indexes[j+1]
			&& tri->indexes[i+1] == tri->indexes[j+0]
			&& tri->indexes[i+2] == tri->indexes[j+3]
			&& tri->indexes[i+3] == tri->indexes[j+5]
			&& tri->indexes[i+4] == tri->indexes[j+1]
			&& tri->indexes[i+5] == tri->indexes[j+3] ) {
				break;
			}
		}
		if ( j == numSilIndexes ) {
			continue;
		}
		int	k;
		// remove first quad
		for ( k = i+6 ; k < j ; k++ ) {
			tri->indexes[k-6] = tri->indexes[k];
		}
		// remove second quad
		for ( k = j+6 ; k < tri->numIndexes ; k++ ) {
			tri->indexes[k-12] = tri->indexes[k];
		}
		numSilIndexes -= 12;
		i -= 6;
	}

	int	removed = tri->numShadowIndexesNoCaps - numSilIndexes;

	tri->numIndexes -= removed;
	tri->numShadowIndexesNoCaps -= removed;
	tri->numShadowIndexesNoFrontCaps -= removed;

	// remove degenerates after we have removed quads, so the double
	// triangle pairing isn't disturbed
	RemoveDegenerateTriangles( tri );
}

/*
========================
CreateLightShadow

This is called from dmap in util/surface.cpp
shadowerGroups should be exactly clipped to the light frustum before calling.
shadowerGroups is optimized by this function, but the contents can be freed, because the returned
lightShadow_t list is a further culling and optimization of the data.
========================
*/
srfTriangles_t *CreateLightShadow( optimizeGroup_t *shadowerGroups, const mapLight_t *light ) {;

	PrintIfVerbosityAtLeast( VL_ORIGDEFAULT, "----- CreateLightShadow %p -----\n", light );

	// optimize all the groups
	OptimizeGroupList( shadowerGroups );

	// combine all the triangles into one list
	mapTri_t	*combined;

	combined = NULL;
	for ( optimizeGroup_t *group = shadowerGroups ; group ; group = group->nextGroup ) {
		combined = MergeTriLists( combined, CopyTriList( group->triList ) );
	}

	if ( !combined ) {
		return NULL;
	}

	// find uniqued vertexes
	srfTriangles_t	*occluders = ShareMapTriVerts( combined );

	FreeTriList( combined );

	// find silhouette information for the triSurf
	R_CleanupTriangles( occluders, false, true, false );

	// let the renderer build the shadow volume normally
	idRenderEntityLocal		space;

	space.modelMatrix[0] = 1;
	space.modelMatrix[5] = 1;
	space.modelMatrix[10] = 1;
	space.modelMatrix[15] = 1;

	srfCullInfo_t cullInfo;
	memset( &cullInfo, 0, sizeof( cullInfo ) );

	// call the normal shadow creation, but with the superOptimize flag set, which will
	// call back to SuperOptimizeOccluders after clipping the triangles to each frustum
	srfTriangles_t	*shadowTris;
	if ( dmapGlobals.shadowOptLevel == SO_MERGE_SURFACES ) {
		shadowTris = R_CreateShadowVolume( &space, occluders, &light->def, SG_STATIC, cullInfo );
	} else {
		shadowTris = R_CreateShadowVolume( &space, occluders, &light->def, SG_OFFLINE, cullInfo );
	}
	R_FreeStaticTriSurf( occluders );

	R_FreeInteractionCullInfo( cullInfo );

	if ( shadowTris ) {
		dmapGlobals.totalShadowTriangles += shadowTris->numIndexes / 3;
		dmapGlobals.totalShadowVerts += shadowTris->numVerts / 3;
	}

	return shadowTris;
}
