/***
*
*	Copyright (c) 1998, Valve LLC. All rights reserved.
*	
*	This product contains software technology licensed from Id 
*	Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc. 
*	All Rights Reserved.
*
****/

// solidbsp.c

#include "bsp5.h"

/*

  Each node or leaf will have a set of portals that completely enclose
  the volume of the node and pass into an adjacent node.

*/

int		c_leaffaces;
int		c_nodefaces;
int		c_splitnodes;

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

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

For BSP hueristic
==================
*/
int FaceSide (face_t *in, dplane_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

When there are a huge number of planes, just choose one closest
to the middle.
==================
*/
surface_t *ChooseMidPlaneFromList (surface_t *surfaces, vec3_t mins, vec3_t maxs)
{
	int			j,l;
	surface_t	*p, *bestsurface;
	vec_t		bestvalue, value, dist;
	dplane_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 = &dplanes[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

Choose the plane that splits the least faces
==================
*/
surface_t *ChoosePlaneFromList (surface_t *surfaces, vec3_t mins, vec3_t maxs)
{
	int			j,k,l;
	surface_t	*p, *p2, *bestsurface;
	vec_t		bestvalue, bestdistribution, value, dist;
	dplane_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 = &dplanes[p->planenum];
		k = 0;

		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, node_t *node, qboolean usemidsplit)
{
	int			i,j;
	surface_t	*p, *bestsurface;

	//
	// count surface choices
	//
	i = 0;
	bestsurface = NULL;
	for (p=surfaces ; p ; p=p->next)
		if (!p->onnode)
		{
			i++;
			bestsurface = p;
		}
		
	if (i==0)
		return NULL;		// this is a leafnode
		
	if (i==1)
		return bestsurface;	// this is a final split
	

	if (usemidsplit) // do fast way for clipping hull
		return ChooseMidPlaneFromList (surfaces, node->mins, node->maxs);
		
	// do slow way to save poly splits for drawing hull
	return ChoosePlaneFromList (surfaces, node->mins, node->maxs);
}

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

/*
=================
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)
			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];
			}
	}
}



/*
==================
DivideSurface
==================
*/
void DivideSurface (surface_t *in, dplane_t *split, surface_t **front, surface_t **back)
{
	face_t		*facet, *next;
	face_t		*frontlist, *backlist;
	face_t		*frontfrag, *backfrag;
	surface_t	*news;
	dplane_t	*inplane;	

	inplane = &dplanes[in->planenum];
	
	// parallel case is easy

	if (inplane->normal[0] == split->normal[0]
	&& inplane->normal[1] == split->normal[1]
	&& inplane->normal[2] == split->normal[2])
	{
		if (inplane->dist > split->dist)
		{
			*front = in;
			*back = NULL;
		}
		else if (inplane->dist < split->dist)
		{
			*front = NULL;
			*back = in;
		}
		else
		{	// split the surface into front and back
			frontlist = NULL;
			backlist = NULL;
			for (facet = in->faces ; facet ; facet = next)
			{
				next = facet->next;
				if (facet->planenum & 1)
				{
					facet->next = backlist;
					backlist = facet;
				}
				else
				{
					facet->next = frontlist;
					frontlist = facet;
				}
			}
			goto makesurfs;
		}
		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
makesurfs:
	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 surface 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);	
}

/*
=============
SplitNodeSurfaces
=============
*/
void SplitNodeSurfaces (surface_t *surfaces, node_t *node)
{
	surface_t	*p, *next;
	surface_t	*frontlist, *backlist;
	surface_t	*frontfrag, *backfrag;
	dplane_t	*splitplane;

	splitplane = &dplanes[node->planenum];

	frontlist = NULL;
	backlist = NULL;
	
	for (p=surfaces ; p ; p=next)
	{
		next = p->next;
		DivideSurface (p, splitplane, &frontfrag, &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;
		}
	}

	node->children[0]->surfaces = frontlist;
	node->children[1]->surfaces = backlist;
}


int RankForContents (int contents)
{
	switch (contents)
	{
	case CONTENTS_EMPTY:		return 0;
	case CONTENTS_WATER:		return 1;
	case CONTENTS_TRANSLUCENT:	return 2;
	case CONTENTS_CURRENT_0:	return 3;
	case CONTENTS_CURRENT_90:	return 4;
	case CONTENTS_CURRENT_180:	return 5;
	case CONTENTS_CURRENT_270:	return 6;
	case CONTENTS_CURRENT_UP:	return 7;
	case CONTENTS_CURRENT_DOWN:	return 8;
	case CONTENTS_SLIME:		return 9;
	case CONTENTS_LAVA :		return 10;
	case CONTENTS_SKY  :		return 11;
	case CONTENTS_SOLID:		return 12;
	default:
		Error ("RankForContents: bad contents %i", contents);
	}
	return -1;
}

int ContentsForRank (int rank)
{
	switch (rank)
	{
	case -1:	return CONTENTS_SOLID;	// no faces at all
	case 0:		return CONTENTS_EMPTY;
	case 1:		return CONTENTS_WATER;
	case 2:		return CONTENTS_TRANSLUCENT;
	case 3:		return CONTENTS_CURRENT_0;
	case 4:		return CONTENTS_CURRENT_90;
	case 5:		return CONTENTS_CURRENT_180;
	case 6:		return CONTENTS_CURRENT_270;
	case 7:		return CONTENTS_CURRENT_UP;
	case 8:		return CONTENTS_CURRENT_DOWN;
	case 9:		return CONTENTS_SLIME;
	case 10:	return CONTENTS_LAVA;
	case 11:	return CONTENTS_SKY;
	case 12:	return CONTENTS_SOLID;
	default:
		Error ("ContentsForRank: bad rank %i", rank);
	}
	return -1;
}

/*
=============
FreeLeafSurfs
=============
*/
void FreeLeafSurfs (node_t *leaf)
{
	surface_t *surf, *snext;
	face_t		*f, *fnext;

	for (surf = leaf->surfaces ; surf ; surf=snext)
	{
		snext = surf->next;
		for (f=surf->faces ; f ; f=fnext)
		{
			fnext = f->next;
			FreeFace (f);
		}
		FreeSurface (surf);
	}

	leaf->surfaces = NULL;
}

/*
==================
LinkLeafFaces

Determines the contents of the leaf and creates the final list of
original faces that have some fragment inside this leaf
==================
*/
#define	MAX_LEAF_FACES	1024
void LinkLeafFaces (surface_t *planelist, node_t *leafnode)
{
	face_t		*f;
	surface_t	*surf;
	int			rank, r;
	int			nummarkfaces;
	face_t		*markfaces[MAX_LEAF_FACES];

	leafnode->faces = NULL;
	leafnode->planenum = -1;

	rank = -1;
	for ( surf = planelist ; surf ; surf = surf->next)
	{
		for (f = surf->faces ; f ; f=f->next)
		{
			r = RankForContents (f->contents);
			if (r > rank)
				rank = r;
		}
	}

	leafnode->contents = ContentsForRank (rank);

	if (leafnode->contents != CONTENTS_SOLID)
	{
		nummarkfaces = 0;
		for (surf = leafnode->surfaces ; surf ; surf=surf->next)
		{
			for (f=surf->faces ; f ; f=f->next)
			{
				if (nummarkfaces == MAX_LEAF_FACES)
					Error ("nummarkfaces == MAX_LEAF_FACES");

				markfaces[nummarkfaces++] = f->original;
			}
		}

		c_leaffaces += nummarkfaces;
		markfaces[nummarkfaces] = NULL;	// end marker
		nummarkfaces++;

		leafnode->markfaces = malloc(nummarkfaces * sizeof(*leafnode->markfaces));
		memcpy (leafnode->markfaces, markfaces, nummarkfaces * sizeof(*leafnode->markfaces));
	}

	FreeLeafSurfs (leafnode);
	leafnode->surfaces = NULL;
}


/*
==================
MakeNodePortal

create the new portal by taking the full plane winding for the cutting plane
and clipping it by all of the planes from the other portals.

Each portal tracks the node that created it, so unused nodes
can be removed later.
==================
*/
void MakeNodePortal (node_t *node)
{
	portal_t	*new_portal, *p;
	dplane_t	*plane;
	dplane_t	clipplane;
	winding_t	*w;
	int			side;

	plane = &dplanes[node->planenum];
	w = BaseWindingForPlane (plane);

	new_portal = AllocPortal ();
	new_portal->plane = *plane;
	new_portal->onnode = node;

	side = 0;	// shut up compiler warning
	for (p = node->portals ; p ; p = p->next[side])	
	{
		clipplane = p->plane;
		if (p->nodes[0] == node)
			side = 0;
		else if (p->nodes[1] == node)
		{
			clipplane.dist = -clipplane.dist;
			VectorSubtract (vec3_origin, clipplane.normal, clipplane.normal);
			side = 1;
		}
		else
			Error ("MakeNodePortal: mislinked portal");

		w = ClipWinding (w, &clipplane, true);
		if (!w)
		{
			printf ("WARNING: MakeNodePortal:new portal was clipped away from node@(%.0f,%.0f,%.0f)-(%.0f,%.0f,%.0f)\n",
					node->mins[0], node->mins[1], node->mins[2], 
					node->maxs[0], node->maxs[1], node->maxs[2]);
			FreePortal (new_portal);
			return;
		}
	}

	new_portal->winding = w;	
	AddPortalToNodes (new_portal, node->children[0], node->children[1]);
}

/*
==============
SplitNodePortals

Move or split the portals that bound node so that the node's
children have portals instead of node.
==============
*/
void SplitNodePortals (node_t *node)
{
	portal_t	*p, *next_portal, *new_portal;
	node_t		*f, *b, *other_node;
	int			side;
	dplane_t	*plane;
	winding_t	*frontwinding, *backwinding;

	plane = &dplanes[node->planenum];
	f = node->children[0];
	b = node->children[1];

	for (p = node->portals ; p ; p = next_portal)	
	{
		if (p->nodes[0] == node)
			side = 0;
		else if (p->nodes[1] == node)
			side = 1;
		else
			Error ("CutNodePortals_r: mislinked portal");
		next_portal = p->next[side];

		other_node = p->nodes[!side];
		RemovePortalFromNode (p, p->nodes[0]);
		RemovePortalFromNode (p, p->nodes[1]);

//
// cut the portal into two portals, one on each side of the cut plane
//
		DivideWinding (p->winding, plane, &frontwinding, &backwinding);
		
		if (!frontwinding)
		{
			if (side == 0)
				AddPortalToNodes (p, b, other_node);
			else
				AddPortalToNodes (p, other_node, b);
			continue;
		}
		if (!backwinding)
		{
			if (side == 0)
				AddPortalToNodes (p, f, other_node);
			else
				AddPortalToNodes (p, other_node, f);
			continue;
		}
		
	// the winding is split
		new_portal = AllocPortal ();
		*new_portal = *p;
		new_portal->winding = backwinding;
		FreeWinding (p->winding);
		p->winding = frontwinding;

		if (side == 0)
		{
			AddPortalToNodes (p, f, other_node);
			AddPortalToNodes (new_portal, b, other_node);
		}
		else
		{
			AddPortalToNodes (p, other_node, f);
			AddPortalToNodes (new_portal, other_node, b);
		}
	}

	node->portals = NULL;
}


/*
==================
CalcNodeBounds

Determines the boundaries of a node by
minmaxing all the portal points, whcih
completely enclose the node.

 Returns true if the node should be midsplit.(very large)
==================
*/
qboolean CalcNodeBounds (node_t *node)
{
	int		i, j;
	vec_t	v;
	portal_t	*p, *next_portal;
	int		side;

	node->mins[0] = node->mins[1] = node->mins[2] = 9999;
	node->maxs[0] = node->maxs[1] = node->maxs[2] = -9999;

	for (p = node->portals ; p ; p = next_portal)	
	{
		if (p->nodes[0] == node)
			side = 0;
		else if (p->nodes[1] == node)
			side = 1;
		else
			Error ("CutNodePortals_r: mislinked portal");
		next_portal = p->next[side];

		for (i=0 ; i<p->winding->numpoints ; i++)
		{
			for (j=0 ; j<3 ; j++)
			{
				v = p->winding->points[i][j];
				if (v < node->mins[j])
					node->mins[j] = v;
				if (v > node->maxs[j])
					node->maxs[j] = v;
			}
		}
	}

	for (i=0 ; i<3 ; i++)
		if (node->maxs[i] - node->mins[i] > 1024)
			return true;
	return false;
}

/*
==================
CopyFacesToNode

Do a final merge attempt, then subdivide the faces
to surface cache size if needed.

These are final faces that will be drawable in the game.
Copies of these faces are further chopped up into the leafs,
but they will reference these originals.
==================
*/
void CopyFacesToNode (node_t *node, surface_t *surf)
{
	face_t	**prevptr, *f, *newf;

	// merge as much as possible
	MergePlaneFaces (surf);

	// subdivide large faces
	prevptr = &surf->faces;
	while (1)
	{
		f = *prevptr;
		if (!f)
			break;
		SubdivideFace (f, prevptr);
		f = *prevptr;
		prevptr = &f->next;
	}

	// copy the faces to the node, and consider them the originals
	node->surfaces = NULL;
	node->faces = NULL;
	for (f=surf->faces ; f ; f=f->next)
	{
		if (f->contents != CONTENTS_SOLID)
		{
			newf = AllocFace ();
			*newf = *f;
			f->original = newf;
			newf->next = node->faces;
			node->faces = newf;
			c_nodefaces++;
		}
	}
}

/*
==================
DrawSurfaces
==================
*/
void DrawSurfaces (surface_t *surf)
{
	face_t	*f;

	Draw_ClearWindow ();
	for ( ; surf ; surf=surf->next)
	{
		for (f = surf->faces ; f ; f=f->next)
		{
			Draw_DrawFace (f);
		}
	}
}

/*
==================
BuildBspTree_r
==================
*/
void BuildBspTree_r (node_t *node)
{
	surface_t		*split;
	qboolean		midsplit;
	surface_t		*allsurfs;

	midsplit = CalcNodeBounds (node);

	DrawSurfaces (node->surfaces);

	split = SelectPartition (node->surfaces, node, midsplit);
	if (!split)
	{	// this is a leaf node
		node->planenum = PLANENUM_LEAF;
		LinkLeafFaces (node->surfaces, node);
		return;
	}

	//
	// these are final polygons
	//
	split->onnode = node;	// can't use again
	allsurfs = node->surfaces;
	node->planenum = split->planenum;
	node->faces = NULL;
	CopyFacesToNode (node, split);
	c_splitnodes++;

	node->children[0] = AllocNode ();
	node->children[1] = AllocNode ();

	//
	// split all the polysurfaces into front and back lists
	//
	SplitNodeSurfaces (allsurfs, node);

	//
	// create the portal that seperates the two children
	//
	MakeNodePortal (node);
	
	//
	// carve the portals on the boundaries of the node
	//
	SplitNodePortals (node);

	//
	// recursively do the children
	//
	BuildBspTree_r (node->children[0]);
	BuildBspTree_r (node->children[1]);
}

/*
==================
SolidBSP

Takes a chain of surfaces plus a split type, and
returns a bsp tree with faces off the nodes.

The original surface chain will be completely freed.
==================
*/
node_t *SolidBSP (surfchain_t *surfhead)
{
	int		i;
	node_t	*headnode;
	
	qprintf ("----- SolidBSP -----\n");

	headnode = AllocNode ();
	headnode->surfaces = surfhead->surfaces;
	
	Draw_ClearWindow ();
	c_splitnodes = 0;
	c_nodefaces = 0;
	c_leaffaces = 0;

	if (!surfhead->surfaces)
	{
		// nothing at all to build
		headnode->planenum = -1;
		headnode->contents = CONTENTS_EMPTY;
		return headnode;
	}

	//
	// generate six portals that enclose the entire world
	//
	MakeHeadnodePortals (headnode, surfhead->mins, surfhead->maxs);
	
	//
	// recursively partition everything
	//
	BuildBspTree_r (headnode);

	qprintf ("%5i split nodes\n", c_splitnodes);
	qprintf ("%5i node faces\n", c_nodefaces);
	qprintf ("%5i leaf faces\n", c_leaffaces);
	
	return headnode;
}

