/*
	Copyright (C) 1996-1997  Id Software, Inc.

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

	This program 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 this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

	See file, 'COPYING', for details.
*/
static const char rcsid[] =
	"$Id$";

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_STRING_H
# include "string.h"
#endif

#include "QF/sys.h"

#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];

	return newf;
}

void
SplitFace (face_t *in, plane_t *split, face_t **front, face_t **back)
{
	face_t     *newf, *new2;
	int         i, j;
	int         sides[MAXEDGES + 1];
	int         counts[3];
	vec_t       dot;
	vec_t       dists[MAXEDGES + 1];
	vec_t      *p1, *p2;
	vec3_t      mid;

	if (in->numpoints < 0)
		Sys_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)
			Sys_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)
		Sys_Error ("SplitFace: numpoints > MAXEDGES");

	// 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     *insidelist, *next, *f;
	face_t     *frags[2];
	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) {	// always 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         planenum, i;

	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     *next, *f;

	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     *count;
	face_t    **f;
	int         i;
	surface_t  *surfhead, *s;

	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;
}

void
CopyFacesToOutside (brush_t *b)
{
	face_t     *newf, *f;

	outside = NULL;

	for (f = b->faces; f; f = f->next) {
		brushfaces++;
		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;
	face_t     *f;
	int         i;
	qboolean    overwrite;
	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)
		Sys_Error ("No faces");
#endif

	surfhead = BuildSurfaces ();

	qprintf ("%5i brushfaces\n", brushfaces);
	qprintf ("%5i csgfaces\n", csgfaces);
	qprintf ("%5i mergedfaces\n", csgmergefaces);

	return surfhead;
}