/*  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.
*/

// solidbsp.c

#include "bsp5.h"

int		leaffaces;
int		nodefaces;
int		splitnodes;

int		c_solid, c_empty, c_water;

qboolean		usemidsplit;

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

/*
==================
FaceSide

For BSP hueristic
==================
*/
int FaceSide (face_t *in, plane_t *split)
{
	int		frontcount, backcount;
	vec_t	dot;
	int		i;
	vec_t	*p;
	
	
	frontcount = backcount = 0;
	
// axial planes are fast
	if (split->type < 3)
		for (i=0, p = in->pts[0]+split->type ; i<in->numpoints ; i++, p+=3)
		{
			if (*p > split->dist + ON_EPSILON)
			{
				if (backcount)
					return SIDE_ON;
				frontcount = 1;
			}
			else if (*p < split->dist - ON_EPSILON)
			{
				if (frontcount)
					return SIDE_ON;
				backcount = 1;
			}
		}
	else	
// sloping planes take longer
		for (i=0, p = in->pts[0] ; i<in->numpoints ; i++, p+=3)
		{
			dot = DotProduct (p, split->normal);
			dot -= split->dist;
			if (dot > ON_EPSILON)
			{
				if (backcount)
					return SIDE_ON;
				frontcount = 1;
			}
			else if (dot < -ON_EPSILON)
			{
				if (frontcount)
					return SIDE_ON;
				backcount = 1;
			}
		}
	
	if (!frontcount)
		return SIDE_BACK;
	if (!backcount)
		return SIDE_FRONT;
	
	return SIDE_ON;
}

/*
==================
ChooseMidPlaneFromList

The clipping hull BSP doesn't worry about avoiding splits
==================
*/
surface_t *ChooseMidPlaneFromList (surface_t *surfaces, vec3_t mins, vec3_t maxs)
{
	int			j,l;
	surface_t	*p, *bestsurface;
	vec_t		bestvalue, value, dist;
	plane_t		*plane;

//
// pick the plane that splits the least
//
	bestvalue = 6*8192*8192;
	bestsurface = NULL;
	
	for (p=surfaces ; p ; p=p->next)
	{
		if (p->onnode)
			continue;

		plane = &planes[p->planenum];
		
	// check for axis aligned surfaces
		l = plane->type;
		if (l > PLANE_Z)
			continue;

	//
	// calculate the split metric along axis l, smaller values are better
	//
		value = 0;

		dist = plane->dist * plane->normal[l];
		for (j=0 ; j<3 ; j++)
		{
			if (j == l)
			{
				value += (maxs[l]-dist)*(maxs[l]-dist);
				value += (dist-mins[l])*(dist-mins[l]);
			}
			else
				value += 2*(maxs[j]-mins[j])*(maxs[j]-mins[j]);
		}
		
		if (value > bestvalue)
			continue;
		
	//
	// currently the best!
	//
		bestvalue = value;
		bestsurface = p;
	}

	if (!bestsurface)
	{
		for (p=surfaces ; p ; p=p->next)
			if (!p->onnode)
				return p;		// first valid surface
		Error ("ChooseMidPlaneFromList: no valid planes");
	}
		
	return bestsurface;
}



/*
==================
ChoosePlaneFromList

The real BSP hueristic
==================
*/
surface_t *ChoosePlaneFromList (surface_t *surfaces, vec3_t mins, vec3_t maxs, qboolean usefloors)
{
	int			j,k,l;
	surface_t	*p, *p2, *bestsurface;
	vec_t		bestvalue, bestdistribution, value, dist;
	plane_t		*plane;
	face_t		*f;
	
//
// pick the plane that splits the least
//
	bestvalue = 99999;
	bestsurface = NULL;
	bestdistribution = 9e30;
	
	for (p=surfaces ; p ; p=p->next)
	{
		if (p->onnode)
			continue;

		plane = &planes[p->planenum];
		k = 0;

		if (!usefloors && plane->normal[2] == 1)
			continue;

		for (p2=surfaces ; p2 ; p2=p2->next)
		{
			if (p2 == p)
				continue;
			if (p2->onnode)
				continue;
				
			for (f=p2->faces ; f ; f=f->next)
			{
				if (FaceSide (f, plane) == SIDE_ON)
				{
					k++;
					if (k >= bestvalue)
						break;
				}
				
			}
			if (k > bestvalue)
				break;
		}

		if (k > bestvalue)
			continue;
			
	// if equal numbers, axial planes win, then decide on spatial subdivision
	
		if (k < bestvalue || (k == bestvalue && plane->type < PLANE_ANYX) )
		{
		// check for axis aligned surfaces
			l = plane->type;
	
			if (l <= PLANE_Z)
			{	// axial aligned						
			//
			// calculate the split metric along axis l
			//
				value = 0;
		
				for (j=0 ; j<3 ; j++)
				{
					if (j == l)
					{
						dist = plane->dist * plane->normal[l];
						value += (maxs[l]-dist)*(maxs[l]-dist);
						value += (dist-mins[l])*(dist-mins[l]);
					}
					else
						value += 2*(maxs[j]-mins[j])*(maxs[j]-mins[j]);
				}
				
				if (value > bestdistribution && k == bestvalue)
					continue;
				bestdistribution = value;
			}
		//
		// currently the best!
		//
			bestvalue = k;
			bestsurface = p;
		}

	}


	return bestsurface;
}


/*
==================
SelectPartition

Selects a surface from a linked list of surfaces to split the group on
returns NULL if the surface list can not be divided any more (a leaf)
==================
*/
surface_t *SelectPartition (surface_t *surfaces)
{
	int			i,j;
	vec3_t		mins, maxs;
	surface_t	*p, *bestsurface;

//
// count onnode surfaces
//
	i = 0;
	bestsurface = NULL;
	for (p=surfaces ; p ; p=p->next)
		if (!p->onnode)
		{
			i++;
			bestsurface = p;
		}
		
	if (i==0)
		return NULL;
		
	if (i==1)
		return bestsurface;	// this is a final split
	
//
// calculate a bounding box of the entire surfaceset
//
	for (i=0 ; i<3 ; i++)
	{
		mins[i] = 99999;
		maxs[i] = -99999;
	}

	for (p=surfaces ; p ; p=p->next)
		for (j=0 ; j<3 ; j++)
		{
			if (p->mins[j] < mins[j])
				mins[j] = p->mins[j];
			if (p->maxs[j] > maxs[j])
				maxs[j] = p->maxs[j];
		}

	if (usemidsplit) // do fast way for clipping hull
		return ChooseMidPlaneFromList (surfaces, mins, maxs);
		
// do slow way to save poly splits for drawing hull
#if 0
	bestsurface = ChoosePlaneFromList (surfaces, mins, maxs, false);
	if (bestsurface)	
		return bestsurface;
#endif		
	return ChoosePlaneFromList (surfaces, mins, maxs, true);
}

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

/*
=================
CalcSurfaceInfo

Calculates the bounding box
=================
*/
void CalcSurfaceInfo (surface_t *surf)
{
	int		i,j;
	face_t	*f;
	
	if (!surf->faces)
		Error ("CalcSurfaceInfo: surface without a face");
		
//
// calculate a bounding box
//
	for (i=0 ; i<3 ; i++)
	{
		surf->mins[i] = 99999;
		surf->maxs[i] = -99999;
	}

	for (f=surf->faces ; f ; f=f->next)
	{
if (f->contents[0] >= 0 || f->contents[1] >= 0)
Error ("Bad contents");
		for (i=0 ; i<f->numpoints ; i++)
			for (j=0 ; j<3 ; j++)
			{
				if (f->pts[i][j] < surf->mins[j])
					surf->mins[j] = f->pts[i][j];
				if (f->pts[i][j] > surf->maxs[j])
					surf->maxs[j] = f->pts[i][j];
			}
	}
}



/*
==================
DividePlane
==================
*/
void DividePlane (surface_t *in, plane_t *split, surface_t **front, surface_t **back)
{
	face_t		*facet, *next;
	face_t		*frontlist, *backlist;
	face_t		*frontfrag, *backfrag;
	surface_t	*news;
	plane_t		*inplane;	
	
	inplane = &planes[in->planenum];
	
// parallel case is easy
	if (VectorCompare (inplane->normal, split->normal))
	{
// check for exactly on node
		if (inplane->dist == split->dist)
		{	// divide the facets to the front and back sides
			news = AllocSurface ();
			*news = *in;

			facet=in->faces;
			in->faces = NULL;
			news->faces = NULL;
			in->onnode = news->onnode = true;
			
			for ( ; facet ; facet=next)
			{
				next = facet->next;
				if (facet->planeside == 1)
				{
					facet->next = news->faces;
					news->faces = facet;
				}
				else
				{
					facet->next = in->faces;
					in->faces = facet;
				}
			}
				
			if (in->faces)
				*front = in;
			else
				*front = NULL;
			if (news->faces)
				*back = news;
			else
				*back = NULL;
			return;
		}
		
		if (inplane->dist > split->dist)
		{
			*front = in;
			*back = NULL;
		}
		else
		{
			*front = NULL;
			*back = in;
		}
		return;
	}
	
// do a real split.  may still end up entirely on one side
// OPTIMIZE: use bounding box for fast test
	frontlist = NULL;
	backlist = NULL;
	
	for (facet = in->faces ; facet ; facet = next)
	{
		next = facet->next;
		SplitFace (facet, split, &frontfrag, &backfrag);
		if (frontfrag)
		{
			frontfrag->next = frontlist;
			frontlist = frontfrag;
		}
		if (backfrag)
		{
			backfrag->next = backlist;
			backlist = backfrag;
		}
	}

// if nothing actually got split, just move the in plane
	
	if (frontlist == NULL)
	{
		*front = NULL;
		*back = in;
		in->faces = backlist;
		return;
	}

	if (backlist == NULL)
	{
		*front = in;
		*back = NULL;
		in->faces = frontlist;
		return;
	}
	

// stuff got split, so allocate one new plane and reuse in
	news = AllocSurface ();
	*news = *in;
	news->faces = backlist;
	*back = news;
	
	in->faces = frontlist;
	*front = in;
	
// recalc bboxes and flags
	CalcSurfaceInfo (news);
	CalcSurfaceInfo (in);	
}

/*
==================
DivideNodeBounds
==================
*/
void DivideNodeBounds (node_t *node, plane_t *split)
{
	VectorCopy (node->mins, node->children[0]->mins);
	VectorCopy (node->mins, node->children[1]->mins);
	VectorCopy (node->maxs, node->children[0]->maxs);
	VectorCopy (node->maxs, node->children[1]->maxs);

// OPTIMIZE: sloping cuts can give a better bbox than this...
	if (split->type > 2)
		return;

	node->children[0]->mins[split->type] =
	node->children[1]->maxs[split->type] = split->dist;
}

/*
==================
LinkConvexFaces

Determines the contents of the leaf and creates the final list of
original faces that have some fragment inside this leaf
==================
*/
void LinkConvexFaces (surface_t *planelist, node_t *leafnode)
{
	face_t		*f, *next;
	surface_t	*surf, *pnext;
	int			i, count;
	
	leafnode->faces = NULL;
	leafnode->contents = 0;
	leafnode->planenum = -1;

	count = 0;
	for ( surf = planelist ; surf ; surf = surf->next)
	{
		for (f = surf->faces ; f ; f=f->next)
		{
			count++;
			if (!leafnode->contents)
				leafnode->contents = f->contents[0];
			else if (leafnode->contents != f->contents[0])
				Error ("Mixed face contents in leafnode");
		}
	}

	if (!leafnode->contents)
		leafnode->contents = CONTENTS_SOLID;
		
	switch (leafnode->contents)
	{
	case CONTENTS_EMPTY:
		c_empty++;
		break;
	case CONTENTS_SOLID:
		c_solid++;
		break;
	case CONTENTS_WATER:
	case CONTENTS_SLIME:
	case CONTENTS_LAVA:
	case CONTENTS_SKY:
		c_water++;
		break;
	default:
		Error ("LinkConvexFaces: bad contents number");
	}

//
// write the list of faces, and free the originals
//
	leaffaces += count;
	leafnode->markfaces = malloc(sizeof(face_t *)*(count+1));
	i = 0;
	for ( surf = planelist ; surf ; surf = pnext)
	{
		pnext = surf->next;
		for (f = surf->faces ; f ; f=next)
		{
			next = f->next;
			leafnode->markfaces[i] = f->original;
			i++;
			FreeFace (f);
		}
		FreeSurface (surf);
	}
	leafnode->markfaces[i] = NULL;	// sentinal
}


/*
==================
LinkNodeFaces

Returns a duplicated list of all faces on surface
==================
*/
face_t *LinkNodeFaces (surface_t *surface)
{
	face_t	*f, *new, **prevptr;
	face_t	*list;
	
	list = NULL;
	
	
// subdivide
	prevptr = &surface->faces;
	while (1)
	{
		f = *prevptr;
		if (!f)
			break;
		SubdivideFace (f, prevptr);
		f = *prevptr;
		prevptr = &f->next;
	}

// copy
	for (f=surface->faces ; f ; f=f->next)
	{
		nodefaces++;
		new = AllocFace ();
		*new = *f;
		f->original = new;
		new->next = list;
		list = new;
	}

	return list;
}


/*
==================
PartitionSurfaces
==================
*/
void PartitionSurfaces (surface_t *surfaces, node_t *node)
{
	surface_t	*split, *p, *next;
	surface_t	*frontlist, *backlist;
	surface_t	*frontfrag, *backfrag;
	plane_t		*splitplane;
	
	split = SelectPartition (surfaces);
	if (!split)
	{	// this is a leaf node
		node->planenum = PLANENUM_LEAF;
		LinkConvexFaces (surfaces, node);
		return;
	}
		
	splitnodes++;
	node->faces = LinkNodeFaces (split);
	node->children[0] = AllocNode ();
	node->children[1] = AllocNode ();
	node->planenum = split->planenum;

	splitplane = &planes[split->planenum];
	
	DivideNodeBounds (node, splitplane);


//
// multiple surfaces, so split all the polysurfaces into front and back lists
//
	frontlist = NULL;
	backlist = NULL;
	
	for (p=surfaces ; p ; p=next)
	{
		next = p->next;
		DividePlane (p, splitplane, &frontfrag, &backfrag);
		if (frontfrag && backfrag)
		{
		// the plane was split, which may expose oportunities to merge
		// adjacent faces into a single face
//			MergePlaneFaces (frontfrag);
//			MergePlaneFaces (backfrag);
		}

		if (frontfrag)
		{
			if (!frontfrag->faces)
				Error ("surface with no faces");
			frontfrag->next = frontlist;
			frontlist = frontfrag;
		}
		if (backfrag)
		{
			if (!backfrag->faces)
				Error ("surface with no faces");
			backfrag->next = backlist;
			backlist = backfrag;
		}
	}

	PartitionSurfaces (frontlist, node->children[0]);
	PartitionSurfaces (backlist, node->children[1]);
}

/*
==================
DrawSurface
==================
*/
void DrawSurface (surface_t *surf)
{
	face_t	*f;
	
	for (f=surf->faces ; f ; f=f->next)
		Draw_DrawFace (f);
}

/*
==================
DrawSurfaceList
==================
*/
void DrawSurfaceList (surface_t *surf)
{	
	Draw_ClearWindow ();
	while (surf)
	{
		DrawSurface (surf);
		surf = surf->next;
	}
}

/*
==================
SolidBSP
==================
*/
node_t *SolidBSP (surface_t *surfhead, qboolean midsplit)
{
	int		i;
	node_t	*headnode;
	
	qprintf ("----- SolidBSP -----\n");

	headnode = AllocNode ();
	usemidsplit = midsplit;
	
//
// calculate a bounding box for the entire model
//
	for (i=0 ; i<3 ; i++)
	{
		headnode->mins[i] = brushset->mins[i] - SIDESPACE;
		headnode->maxs[i] = brushset->maxs[i] + SIDESPACE;
	}
	
//
// recursively partition everything
//
	Draw_ClearWindow ();
	splitnodes = 0;
	leaffaces = 0;
	nodefaces = 0;
	c_solid = c_empty = c_water = 0;

	PartitionSurfaces (surfhead, headnode);

	qprintf ("%5i split nodes\n", splitnodes);
	qprintf ("%5i solid leafs\n", c_solid);
	qprintf ("%5i empty leafs\n", c_empty);
	qprintf ("%5i water leafs\n", c_water);	
	qprintf ("%5i leaffaces\n",leaffaces);
	qprintf ("%5i nodefaces\n", nodefaces);
	
	return headnode;
}

