// csg4.c

#include "bsp5.h"

/*

NOTES
-----
Brushes that touch still need to be split at the cut point to make a tjunction

*/

face_t	*validfaces[MAX_MAP_PLANES];


face_t	*inside, *outside;
int		brushfaces;
int		csgfaces;
int		csgmergefaces;

void DrawList (face_t *list)
{
	for ( ; list ; list=list->next)
		Draw_DrawFace (list);
}


/*
==================
NewFaceFromFace

Duplicates the non point information of a face, used by SplitFace and
MergeFace.
==================
*/
face_t *NewFaceFromFace (face_t *in)
{
	face_t	*newf;
	
	newf = AllocFace ();

	newf->planenum = in->planenum;
	newf->texturenum = in->texturenum;	
	newf->planeside = in->planeside;
	newf->original = in->original;
	newf->contents[0] = in->contents[0];
	newf->contents[1] = in->contents[1];
	newf->Light = in->Light;
	
	return newf;
}


/*
==================
SplitFace

==================
*/
void SplitFace (face_t *in, plane_t *split, face_t **front, face_t **back)
{
	double	dists[MAXEDGES+1];
	int		sides[MAXEDGES+1];
	int		counts[3];
	double	dot;
	int		i, j;
	face_t	*newf, *new2;
	double	*p1, *p2;
	vec3_t	mid;
	
	if (in->numpoints < 0)
		Error ("SplitFace: freed face");
	counts[0] = counts[1] = counts[2] = 0;

// determine sides for each point
	for (i=0 ; i<in->numpoints ; i++)
	{
		dot = DotProduct (in->pts[i], split->normal);
		dot -= split->dist;
		dists[i] = dot;
		if (dot > ON_EPSILON)
			sides[i] = SIDE_FRONT;
		else if (dot < -ON_EPSILON)
			sides[i] = SIDE_BACK;
		else
			sides[i] = SIDE_ON;
		counts[sides[i]]++;
	}
	sides[i] = sides[0];
	dists[i] = dists[0];
	
	if (!counts[0])
	{
		*front = NULL;
		*back = in;
		return;
	}
	if (!counts[1])
	{
		*front = in;
		*back = NULL;
		return;
	}
	
	*back = newf = NewFaceFromFace (in);
	*front = new2 = NewFaceFromFace (in);
	
// distribute the points and generate splits

	for (i=0 ; i<in->numpoints ; i++)
	{
		if (newf->numpoints > MAXEDGES || new2->numpoints > MAXEDGES)
			Error ("SplitFace: numpoints > MAXEDGES");

		p1 = in->pts[i];
		
		if (sides[i] == SIDE_ON)
		{
			VectorCopy (p1, newf->pts[newf->numpoints]);
			newf->numpoints++;
			VectorCopy (p1, new2->pts[new2->numpoints]);
			new2->numpoints++;
			continue;
		}
	
		if (sides[i] == SIDE_FRONT)
		{
			VectorCopy (p1, new2->pts[new2->numpoints]);
			new2->numpoints++;
		}
		else
		{
			VectorCopy (p1, newf->pts[newf->numpoints]);
			newf->numpoints++;
		}
		
		if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
			continue;
			
	// generate a split point
		p2 = in->pts[(i+1)%in->numpoints];
		
		dot = dists[i] / (dists[i]-dists[i+1]);
		for (j=0 ; j<3 ; j++)
		{	// avoid round off error when possible
			if (split->normal[j] == 1)
				mid[j] = split->dist;
			else if (split->normal[j] == -1)
				mid[j] = -split->dist;
			else
				mid[j] = p1[j] + dot*(p2[j]-p1[j]);
		}
	
		VectorCopy (mid, newf->pts[newf->numpoints]);
		newf->numpoints++;
		VectorCopy (mid, new2->pts[new2->numpoints]);
		new2->numpoints++;
	}

	if (newf->numpoints > MAXEDGES || new2->numpoints > MAXEDGES)
		Error ("SplitFace: numpoints > MAXEDGES");

#if 0	
CheckFace (newf);
CheckFace (new2);
#endif

// free the original face now that is is represented by the fragments
	FreeFace (in);
}

/*
=================
ClipInside

Clips all of the faces in the inside list, possibly moving them to the
outside list or spliting it into a piece in each list.

Faces exactly on the plane will stay inside unless overdrawn by later brush

frontside is the side of the plane that holds the outside list
=================
*/
void ClipInside (int splitplane, int frontside, qboolean precedence)
{
	face_t	*f, *next;
	face_t	*frags[2];
	face_t	*insidelist;
	plane_t *split;
	
	split = &planes[splitplane];
	
	insidelist = NULL;
	for (f=inside ; f ; f=next)
	{
		next = f->next;
		
		if (f->planenum == splitplane)
		{	// exactly on, handle special
			if ( frontside != f->planeside || precedence )
			{	// allways clip off opposite faceing
				frags[frontside] = NULL;
				frags[!frontside] = f;
			}
			else
			{	// leave it on the outside
				frags[frontside] = f;
				frags[!frontside] = NULL;
			}
		}
		else
		{	// proper split
			SplitFace (f, split, &frags[0], &frags[1]);
		}
		
		if (frags[frontside])
		{
			frags[frontside]->next = outside;
			outside = frags[frontside];
		}
		if (frags[!frontside])
		{
			frags[!frontside]->next = insidelist;
			insidelist = frags[!frontside];
		}
	}
	
	inside = insidelist;
}


/*
==================
SaveOutside

Saves all of the faces in the outside list to the bsp plane list
==================
*/
void SaveOutside (qboolean mirror)
{
	face_t	*f , *next, *newf;
	int		i;
	int		planenum;
		
	for (f=outside ; f ; f=next)
	{
		next = f->next;
		csgfaces++;
		Draw_DrawFace (f);
		planenum = f->planenum;
		
		if (mirror)
		{
			newf = NewFaceFromFace (f);
			
			newf->numpoints = f->numpoints;
			newf->planeside = f->planeside ^ 1;	// reverse side
			newf->contents[0] = f->contents[1];
			newf->contents[1] = f->contents[0];
	
			for (i=0 ; i<f->numpoints ; i++)	// add points backwards
			{
				VectorCopy (f->pts[f->numpoints-1-i], newf->pts[i]);
			}
		}
		else
			newf = NULL;

		validfaces[planenum] = MergeFaceToList(f, validfaces[planenum]);
		if (newf)
			validfaces[planenum] = MergeFaceToList(newf, validfaces[planenum]);

		validfaces[planenum] = FreeMergeListScraps (validfaces[planenum]);
	}
}

/*
==================
FreeInside

Free all the faces that got clipped out
==================
*/
void FreeInside (int contents)
{
	face_t	*f, *next;
	
	for (f=inside ; f ; f=next)
	{
		next = f->next;
		
		if (contents != CONTENTS_SOLID)
		{
			f->contents[0] = contents;
			f->next = outside;
			outside = f;
		}
		else
			FreeFace (f);
	}
}


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

/*
==================
BuildSurfaces

Returns a chain of all the external surfaces with one or more visible
faces.
==================
*/
surface_t *BuildSurfaces (void)
{
	face_t			**f;
	face_t			*count;
	int				i;
	surface_t		*s;
	surface_t		*surfhead;
	
	surfhead = NULL;
	
	f = validfaces;
	for (i=0 ; i<numbrushplanes ; i++, f++)
	{
		if (!*f)
			continue;	// nothing left on this plane

// create a new surface to hold the faces on this plane
		s = AllocSurface ();
		s->planenum = i;
		s->next = surfhead;
		surfhead = s;
		s->faces = *f;
		for (count = s->faces ; count ; count=count->next)
			csgmergefaces++;
		CalcSurfaceInfo (s);	// bounding box and flags
	}	
	
	return surfhead;
}

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

/*
==================
CopyFacesToOutside
==================
*/
void CopyFacesToOutside (brush_t *b)
{
	face_t		*f, *newf;
	
	outside = NULL;
	
	for (f=b->faces ; f ; f=f->next)
	{
		brushfaces++;
#if 0
{
	int		i;
	
	for (i=0 ; i<f->numpoints ; i++)
		printf ("(%f,%f,%f) ",f->pts[i][0], f->pts[i][1], f->pts[i][2]);
	printf ("\n");
}
#endif
		newf = AllocFace ();
		*newf = *f;
		newf->next = outside;
		newf->contents[0] = CONTENTS_EMPTY;
		newf->contents[1] = b->contents;
		outside = newf;
	}
}


/*
==================
CSGFaces

Returns a list of surfaces containing aall of the faces
==================
*/
surface_t *CSGFaces (brushset_t *bs)
{
	brush_t		*b1, *b2;
	int			i;
	qboolean		overwrite;
	face_t		*f;
	surface_t	*surfhead;

	qprintf ("---- CSGFaces ----\n");

	memset (validfaces, 0, sizeof(validfaces));
	
	csgfaces = brushfaces = csgmergefaces = 0;
	
	Draw_ClearWindow ();
	
//
// do the solid faces
//
	for (b1=bs->brushes ; b1 ; b1 = b1->next)
	{
	// set outside to a copy of the brush's faces
		CopyFacesToOutside (b1);
		
		overwrite = false;
		
		for (b2=bs->brushes ; b2 ; b2 = b2->next)
		{
		// see if b2 needs to clip a chunk out of b1
		
			if (b1==b2)
			{
				overwrite = true;	// later brushes now overwrite
				continue;
			}

		// check bounding box first
			for (i=0 ; i<3 ; i++)
				if (b1->mins[i] > b2->maxs[i] || b1->maxs[i] < b2->mins[i])
					break;
			if (i<3)
				continue;

		// divide faces by the planes of the new brush
		
			inside = outside;
			outside = NULL;
			
			for (f=b2->faces ; f ; f=f->next)
				ClipInside (f->planenum, f->planeside, overwrite);

		// these faces are continued in another brush, so get rid of them
			if (b1->contents == CONTENTS_SOLID && b2->contents <= CONTENTS_WATER)
				FreeInside (b2->contents);
			else
				FreeInside (CONTENTS_SOLID);
		}
		
	// all of the faces left in outside are real surface faces
		if (b1->contents != CONTENTS_SOLID)
			SaveOutside (true);	// mirror faces for inside view
		else
			SaveOutside (false);
	}

#if 0
	if (!csgfaces)
		Error ("No faces");
#endif

	surfhead = BuildSurfaces ();
	
	qprintf ("%5i brushfaces\n", brushfaces);
	qprintf ("%5i csgfaces\n", csgfaces);
	qprintf ("%5i mergedfaces\n", csgmergefaces);
	
	return surfhead;
}


