//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: Builds/merges the BSP tree of detail brushes
//
// $NoKeywords: $
//=============================================================================

#include "vbsp.h"
#include "detail.h"
#include "utlvector.h"
#include <assert.h>

face_t *NewFaceFromFace (face_t *f);
face_t *ComputeVisibleBrushSides( bspbrush_t *list );

//-----------------------------------------------------------------------------
// Purpose: Copies a face and its winding
// Input  : *pFace - 
// Output : face_t
//-----------------------------------------------------------------------------
face_t *CopyFace( face_t *pFace )
{
	face_t *f = NewFaceFromFace( pFace );
	f->w = CopyWinding( pFace->w );

	return f;
}

//-----------------------------------------------------------------------------
// Purpose: Link this brush into the list for this leaf
// Input  : *node - 
//			*brush - 
//-----------------------------------------------------------------------------
void AddBrushToLeaf( node_t *node, bspbrush_t *brush )
{
	brush->next = node->brushlist;
	node->brushlist = brush;
}

//-----------------------------------------------------------------------------
// Purpose: Recursively filter a brush through the tree
// Input  : *node - 
//			*brush - 
//-----------------------------------------------------------------------------
void MergeBrush_r( node_t *node, bspbrush_t *brush )
{
	if ( node->planenum == PLANENUM_LEAF )
	{
		if ( node->contents & CONTENTS_SOLID )
		{
			FreeBrush( brush );
		}
		else
		{
			AddBrushToLeaf( node, brush );
		}
		return;
	}

	bspbrush_t *front, *back;
	SplitBrush( brush, node->planenum, &front, &back );
	FreeBrush( brush );

	if ( front )
	{
		MergeBrush_r( node->children[0], front );
	}
	if ( back )
	{
		MergeBrush_r( node->children[1], back );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Recursively filter a face into the tree leaving references to the
//			original face in any visible leaves that a clipped fragment falls
//			into.
// Input  : *node - current head of tree
//			*face - clipped face fragment
//			*original - unclipped original face
// Output : Returns true if any references were left
//-----------------------------------------------------------------------------
bool MergeFace_r( node_t *node, face_t *face, face_t *original )
{
	bool referenced = false;

	if ( node->planenum == PLANENUM_LEAF )
	{
		if ( node->contents & CONTENTS_SOLID )
		{
			FreeFace( face );
			return false;
		}

		leafface_t *plist = new leafface_t;
		plist->pFace = original;
		plist->pNext = node->leaffacelist;
		node->leaffacelist = plist;

		referenced = true;
	}
	else
	{
		// UNDONE: Don't copy the faces each time unless it's necessary!?!?!
		plane_t *plane = &mapplanes[node->planenum];
		winding_t *frontwinding, *backwinding, *onwinding;

		// UNDONE: Export epsilon from original face clipping code
		ClassifyWindingEpsilon(face->w, plane->normal, plane->dist, 0.001, &frontwinding, &backwinding, &onwinding);

		if ( onwinding )
		{
			// face is in the split plane, go down the appropriate side according to the facing direction
			assert( frontwinding == NULL );
			assert( backwinding == NULL );

			if ( DotProduct( mapplanes[face->planenum].normal, mapplanes[node->planenum].normal ) > 0 )
			{
				frontwinding = onwinding;
			}
			else
			{
				backwinding = onwinding;
			}
		}

		if ( frontwinding )
		{
			face_t *tmp = NewFaceFromFace( face );
			tmp->w = frontwinding;
			referenced = MergeFace_r( node->children[0], tmp, original );
		}
		if ( backwinding )
		{
			face_t *tmp = NewFaceFromFace( face );
			tmp->w = backwinding;
			bool test = MergeFace_r( node->children[1], tmp, original );
			referenced = referenced || test;
		}
	}
	FreeFace( face );

	return referenced;
}

//-----------------------------------------------------------------------------
// Purpose: Loop through each face and filter it into the tree
// Input  : *out - 
//			*pFaces - 
//-----------------------------------------------------------------------------
face_t *FilterFacesIntoTree( tree_t *out, face_t *pFaces )
{
	face_t *pLeafFaceList = NULL;
	for ( face_t *f = pFaces; f; f = f->next )
	{
		if( f->merged || f->split[0] || f->split[1] )
			continue;

		face_t *tmp = CopyFace( f );
		face_t *original = CopyFace( f );

		if ( MergeFace_r( out->headnode, tmp, original ) )
		{
			// clear out portal (comes from a different tree)
			original->portal = NULL;
			original->next = pLeafFaceList;
			pLeafFaceList = original;
		}
		else
		{
			FreeFace( original );
		}
	}

	return pLeafFaceList;
}


//-----------------------------------------------------------------------------
// Purpose: Splits the face list into faces from the same plane and tries to merge
//			them if possible
// Input  : **pFaceList - 
//-----------------------------------------------------------------------------
void TryMergeFaceList( face_t **pFaceList )
{
	face_t **pPlaneList = NULL;

	// divide the list into buckets by plane number
	pPlaneList = new face_t *[nummapplanes];
	memset( pPlaneList, 0, sizeof(face_t *) * nummapplanes );

	face_t *pFaces = *pFaceList;
	face_t *pOutput = NULL;

	while ( pFaces )
	{
		face_t *next = pFaces->next;

		// go ahead and delete the old split/merged faces
		if ( pFaces->merged || pFaces->split[0] || pFaces->split[1] )
		{
			Error("Split face in merge list!");
		}
		else
		{
			// add to the list for this plane
			pFaces->next = pPlaneList[pFaces->planenum];
			pPlaneList[pFaces->planenum] = pFaces;
		}

		pFaces = next;
	}

	// now merge each plane's list of faces
	int merged = 0;
	for ( int i = 0; i < nummapplanes; i++ )
	{
		if ( pPlaneList[i] )
		{
			MergeFaceList( &pPlaneList[i] );
		}
		
		// move these over to the output face list
		face_t *list = pPlaneList[i];
		while ( list )
		{
			face_t *next = list->next;

			if ( list->merged )
				merged++;

			list->next = pOutput;
			pOutput = list;
			list = next;
		}
	}

	if ( merged )
	{
		Msg("\nMerged %d detail faces...", merged );
	}
	delete[] pPlaneList;

	*pFaceList = pOutput;
}


//-----------------------------------------------------------------------------
// Purpose: filter each brush in the list into the tree
// Input  : *out - 
//			*brushes - 
//-----------------------------------------------------------------------------
void FilterBrushesIntoTree( tree_t *out, bspbrush_t *brushes )
{
	// Merge all of the brushes into the world tree
	for ( bspbrush_t *plist = brushes; plist; plist = plist->next )
	{
		MergeBrush_r( out->headnode, CopyBrush(plist) );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Build faces for the detail brushes and merge them into the BSP
// Input  : *worldtree - 
//			brush_start - 
//			brush_end - 
//-----------------------------------------------------------------------------
face_t *MergeDetailTree( tree_t *worldtree, int brush_start, int brush_end )
{
	int			start;
	bspbrush_t	*detailbrushes = NULL;
	tree_t		*detailtree = NULL;
	face_t		*pFaces = NULL;
	face_t		*pLeafFaceList = NULL;

	// Grab the list of detail brushes
	detailbrushes = MakeBspBrushList (brush_start, brush_end, map_mins, map_maxs, ONLY_DETAIL );
	if (detailbrushes)
	{
		start = I_FloatTime();
		Msg("Chop Details...");
		// if there are detail brushes, chop them against each other
		if (!nocsg)
			detailbrushes = ChopBrushes (detailbrushes);

		Msg("done (%d)\n", (int)(I_FloatTime() - start) );
		// Now mark the visible sides so we can eliminate all detail brush sides
		// that are covered by other detail brush sides
		// NOTE: This still leaves detail brush sides that are covered by the world. (these are removed in the merge operation)
		Msg("Find Visible Detail Sides...");
		pFaces = ComputeVisibleBrushSides( detailbrushes );
		TryMergeFaceList( &pFaces );
		SubdivideFaceList( &pFaces );
		Msg("done (%d)\n", (int)(I_FloatTime() - start) );

		start = I_FloatTime();
		Msg("Merging details...");
		// Merge the detail solids and faces into the world tree
		// Merge all of the faces into the world tree
		pLeafFaceList = FilterFacesIntoTree( worldtree, pFaces );
		FilterBrushesIntoTree( worldtree, detailbrushes );

		FreeFaceList( pFaces );
		FreeBrushList(detailbrushes);

		Msg("done (%d)\n", (int)(I_FloatTime() - start) );
	}

	return pLeafFaceList;
}


//-----------------------------------------------------------------------------
// Purpose: Quick overlap test for brushes
// Input  : *p1 - 
//			*p2 - 
// Output : Returns false if the brushes cannot intersect
//-----------------------------------------------------------------------------
bool BrushBoxOverlap( bspbrush_t *p1, bspbrush_t *p2 )
{
	if ( p1 == p2 )
		return false;

	for ( int i = 0; i < 3; i++ )
	{
		if ( p1->mins[i] > p2->maxs[i] || p1->maxs[i] < p2->mins[i] )
			return false;
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pFace - input face to test
//			*pbrush - brush to clip face against
//			**pOutputList - list of faces clipped from pFace
// Output : Returns true if the brush completely clips the face
//-----------------------------------------------------------------------------
// NOTE: This assumes the brushes have already been chopped so that no solid space
// is enclosed by more than one brush!!
bool ClipFaceToBrush( face_t *pFace, bspbrush_t *pbrush, face_t **pOutputList )
{
	int planenum = pFace->planenum & (~1);
	int foundSide = -1;

	CUtlVector<int> sortedSides;

	int i;
	for ( i = 0; i < pbrush->numsides && foundSide < 0; i++ )
	{
		int bplane = pbrush->sides[i].planenum & (~1);
		if ( bplane == planenum )
			foundSide = i;
	}

	face_t *currentface = CopyFace( pFace );

	if ( foundSide >= 0 )
	{
		sortedSides.RemoveAll();
		int top = 0;
		for ( i = 0; i < pbrush->numsides; i++ )
		{
			// don't clip to bevels
			if ( pbrush->sides[i].bevel )
				continue;

			if ( mapplanes[pbrush->sides[i].planenum].type <= PLANE_Z )
			{
				sortedSides.AddToHead( i );
			}
			else
			{
				sortedSides.AddToTail( i );
			}
		}

		for ( i = 0; i < sortedSides.Size(); i++ )
		{
			int index = sortedSides[i];
			if ( index == foundSide )
				continue;
			
			plane_t *plane = &mapplanes[pbrush->sides[index].planenum];
			winding_t *frontwinding, *backwinding;
			ClipWindingEpsilon(currentface->w, plane->normal, plane->dist, 0.001, &frontwinding, &backwinding);
			
			// only clip if some part of this face is on the back side of all brush sides
			if ( !backwinding )
			{
				FreeFaceList( *pOutputList );
				*pOutputList = NULL;
				break;
			}
			if ( frontwinding )
			{
				// add this fragment to the return list
				// make a face for the fragment
				face_t *f = NewFaceFromFace( pFace );
				f->w = frontwinding;
				
				// link the fragment in
				f->next = *pOutputList;
				*pOutputList = f;
			}

			// update the current winding to be the part behind each plane
			FreeWinding( currentface->w );
			currentface->w = backwinding;
		}

		// free the bit that is left in solid or not clipped (if we broke out early)
		FreeFace( currentface );

		// if we made it all the way through and didn't produce any fragments then the whole face was clipped away
		if ( !*pOutputList && i == sortedSides.Size() )
		{
			return true;
		}
	}

	if ( nodetailcuts )
	{
		face_t *f = *pOutputList;
		while ( f )
		{
			face_t *next = f->next;
			FreeFace( f );
			f = next;
		}

		*pOutputList = NULL;
	}
	return false;
}


//-----------------------------------------------------------------------------
// Purpose: Given an original side and chopped winding, make a face_t 
// Input  : *side - side of the original brush
//			*winding - winding for this face (portion of the side)
// Output : face_t
//-----------------------------------------------------------------------------
face_t *MakeBrushFace( side_t *originalSide, winding_t *winding )
{
	face_t *f = AllocFace();
	f->merged = NULL;
	f->split[0] = f->split[1] = NULL;
	f->w = CopyWinding( winding );
	f->originalface = originalSide;
	//
	// save material info
	//
	f->texinfo = originalSide->texinfo;
	f->dispinfo = -1;

	// save plane info
	f->planenum = originalSide->planenum;
	f->contents = originalSide->contents;

	return f;
}


//-----------------------------------------------------------------------------
// Purpose: Chop away sides that are inside other brushes.
//			Brushes have already been chopped up so that they do not overlap, 
//			they merely touch.
// Input  : *list - list of brushes
// Output : face_t * - list of visible faces (some marked bad/split)
//-----------------------------------------------------------------------------
// assumes brushes were chopped!


side_t *FindOriginalSide( mapbrush_t *mb, side_t *pBspSide )
{
	side_t *bestside = NULL;
	float bestdot = 0;

	plane_t *p1 = mapplanes + pBspSide->planenum;

	for (int i=0 ; i<mb->numsides ; i++)
	{
		side_t *side = &mb->original_sides[i];
		if (side->bevel)
			continue;
		if (side->texinfo == TEXINFO_NODE)
			continue;		// non-visible
		if ((side->planenum&~1) == (pBspSide->planenum&~1))
		{	// exact match
			return mb->original_sides + i;
		}
		// see how close the match is
		plane_t *p2 = &mapplanes[side->planenum&~1];
		float dot = DotProduct (p1->normal, p2->normal);
		if (dot > bestdot)
		{
			bestdot = dot;
			bestside = side;
		}
	}

	if ( !bestside )
	{
		Error( "Bad detail brush side\n" );
	}
	return bestside;
}


face_t *ComputeVisibleBrushSides( bspbrush_t *list )
{
	face_t *pTotalFaces = NULL;
	
	for ( bspbrush_t *pbrush = list; pbrush; pbrush = pbrush->next )
	{
		face_t *pFaces = NULL;
		mapbrush_t *mb = pbrush->original;

		if ( !(mb->contents & ALL_VISIBLE_CONTENTS) )
			continue;
		for ( int i = 0; i < pbrush->numsides; i++ )
		{
			winding_t *winding = pbrush->sides[i].winding;
			if ( !winding )
				continue;
			
			if (! (pbrush->sides[i].contents & ALL_VISIBLE_CONTENTS) )
				continue;

			side_t *side = FindOriginalSide( mb, pbrush->sides + i );
			face_t *f = MakeBrushFace( side, winding );

			// link to head of face list
			f->next = pFaces;
			pFaces = f;
		}
		for ( bspbrush_t *walk = list; walk; walk = walk->next )
		{
			if ( walk == pbrush )
				continue;

			// only clip to transparent brushes if the original brush is transparent
			if ( walk->original->contents & TRANSPARENT_CONTENTS )
			{
				if ( !(mb->contents & TRANSPARENT_CONTENTS) )
					continue;
			}

			// don't clip to clip brushes, etc.
			if ( !(walk->original->contents & ALL_VISIBLE_CONTENTS) )
				continue;

			// brushes overlap, test faces
			if ( BrushBoxOverlap( pbrush, walk ) )
			{
				for ( face_t *f = pFaces; f; f = f->next )
				{
					// already split? skip this face
					if ( f->split[0] )
						continue;

					// if this brush clips away part of the face, it will return a new list of faces to replace this one
					
					face_t *pClip = NULL;
					
					if ( ClipFaceToBrush( f, walk, &pClip ) )
					{
						// mark face bad, the brush clipped it away
						f->split[0] = f;
					}
					else if ( pClip )
					{
						// mark this face as split
						f->split[0] = f;

						// insert face fragments at head of list (UNDONE: reverses order, do we care?)
						while ( pClip )
						{
							face_t *next = pClip->next;
							pClip->next = pFaces;
							pFaces = pClip;
							pClip = next;
						}
					}
				}
			}
		}
	
		// move to global face list
		while ( pFaces )
		{
			face_t *next = pFaces->next;
			if ( pFaces->split[0] )
			{
				FreeFace( pFaces );
			}
			else
			{
				pFaces->next = pTotalFaces;
				pTotalFaces = pFaces;
			}
			pFaces = next;
		}
	}

	return pTotalFaces;
}
