
#include "qbsp.h"


int			c_faceLeafs;


/*
================
AllocBspFace
================
*/
bspface_t	*AllocBspFace( void ) {
	bspface_t	*f;

	f = malloc(sizeof(*f));
	memset( f, 0, sizeof(*f) );

	return f;
}

/*
================
FreeBspFace
================
*/
void	FreeBspFace( bspface_t *f ) {
	if ( f->w ) {
		FreeWinding( f->w );
	}
	free( f );
}


/*
================
SelectSplitPlaneNum
================
*/
int hintsplit;

#define	BLOCK_SIZE	1024
int SelectSplitPlaneNum( node_t *node, bspface_t *list ) {
	bspface_t	*split;
	bspface_t	*check;
	bspface_t	*bestSplit;
	int			splits, facing, front, back;
	int			side;
	plane_t		*plane;
	int			value, bestValue;
	int			i;
	vec3_t		normal;
	float		dist;
	int			planenum;

	hintsplit = qfalse;
	// if it is crossing a 1k block boundary, force a split
	for ( i = 0 ; i < 2 ; i++ ) {
		dist = BLOCK_SIZE * ( floor( node->mins[i] / BLOCK_SIZE ) + 1 );	
		if ( node->maxs[i] > dist ) {
			VectorClear( normal );
			normal[i] = 1;
			planenum = FindFloatPlane( normal, dist );
			return planenum;
		}
	}

	// pick one of the face planes
	bestValue = -99999;
	bestSplit = list;

	for ( split = list ; split ; split = split->next ) {
		split->checked = qfalse;
	}

	for ( split = list ; split ; split = split->next ) {
		if ( split->checked ) {
			continue;
		}
		plane = &mapplanes[ split->planenum ];
		splits = 0;
		facing = 0;
		front = 0;
		back = 0;
		for ( check = list ; check ; check = check->next ) {
			if ( check->planenum == split->planenum ) {
				facing++;
				check->checked = qtrue;	// won't need to test this plane again
				continue;
			}
			side = WindingOnPlaneSide( check->w, plane->normal, plane->dist );
			if ( side == SIDE_CROSS ) {
				splits++;
			} else if ( side == SIDE_FRONT ) {
				front++;
			} else if ( side == SIDE_BACK ) {
				back++;
			}
		}
		value =  5*facing - 5*splits; // - abs(front-back);
		if ( plane->type < 3 ) {
			value+=5;		// axial is better
		}
		value += split->priority;		// prioritize hints higher

		if ( value > bestValue ) {
			bestValue = value;
			bestSplit = split;
		}
	}

	if ( bestValue == -99999 ) {
		return -1;
	}

	if (bestSplit->hint)
		hintsplit = qtrue;

	return bestSplit->planenum;
}

int	CountFaceList( bspface_t *list ) {
	int		c;
	c = 0;
	for ( ; list ; list = list->next ) {
		c++;
	}
	return c;
}

/*
================
BuildFaceTree_r
================
*/
void	BuildFaceTree_r( node_t *node, bspface_t *list ) {
	bspface_t	*split;
	bspface_t	*next;
	int			side;
	plane_t		*plane;
	bspface_t	*newFace;
	bspface_t	*childLists[2];
	winding_t	*frontWinding, *backWinding;
	int			i;
	int			splitPlaneNum;

	i = CountFaceList( list );

	splitPlaneNum = SelectSplitPlaneNum( node, list );
	// if we don't have any more faces, this is a node
	if ( splitPlaneNum == -1 ) {
		node->planenum = PLANENUM_LEAF;
		c_faceLeafs++;
		return;
	}

	// partition the list
	node->planenum = splitPlaneNum;
	node->hint = hintsplit;
	plane = &mapplanes[ splitPlaneNum ];
	childLists[0] = NULL;
	childLists[1] = NULL;
	for ( split = list ; split ; split = next ) {
		next = split->next;

		if ( split->planenum == node->planenum ) {
			FreeBspFace( split );
			continue;
		}

		side = WindingOnPlaneSide( split->w, plane->normal, plane->dist );

		if ( side == SIDE_CROSS ) {
			ClipWindingEpsilon( split->w, plane->normal, plane->dist, CLIP_EPSILON * 2,
				&frontWinding, &backWinding );
			if ( frontWinding ) {
				newFace = AllocBspFace();
				newFace->w = frontWinding;
				newFace->next = childLists[0];
				newFace->planenum = split->planenum;
				newFace->priority = split->priority;
				newFace->hint = split->hint;
				childLists[0] = newFace;
			}
			if ( backWinding ) {
				newFace = AllocBspFace();
				newFace->w = backWinding;
				newFace->next = childLists[1];
				newFace->planenum = split->planenum;
				newFace->priority = split->priority;
				newFace->hint = split->hint;
				childLists[1] = newFace;
			}
			FreeBspFace( split );
		} else if ( side == SIDE_FRONT ) {
			split->next = childLists[0];
			childLists[0] = split;
		} else if ( side == SIDE_BACK ) {
			split->next = childLists[1];
			childLists[1] = split;
		}
	}


	// recursively process children
	for ( i = 0 ; i < 2 ; i++ ) {
		node->children[i] = AllocNode();
		node->children[i]->parent = node;
		VectorCopy( node->mins, node->children[i]->mins );
		VectorCopy( node->maxs, node->children[i]->maxs );
	}

	for ( i = 0 ; i < 3 ; i++ ) {
		if ( plane->normal[i] == 1 ) {
			node->children[0]->mins[i] = plane->dist;
			node->children[1]->maxs[i] = plane->dist;
			break;
		}
	}

	for ( i = 0 ; i < 2 ; i++ ) {
		BuildFaceTree_r ( node->children[i], childLists[i]);
	}
}


/*
================
FaceBSP

List will be freed before returning
================
*/
tree_t *FaceBSP( bspface_t *list ) {
	tree_t		*tree;
	bspface_t	*face;
	int			i;
	int			count;

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

	tree = AllocTree ();

	count = 0;
	for ( face = list ; face ; face = face->next ) {
		count++;
		for ( i = 0 ; i < face->w->numpoints ; i++ ) {
			AddPointToBounds( face->w->p[i], tree->mins, tree->maxs);
		}
	}
	qprintf( "%5i faces\n", count );

	tree->headnode = AllocNode();
	VectorCopy( tree->mins, tree->headnode->mins );
	VectorCopy( tree->maxs, tree->headnode->maxs );
	c_faceLeafs = 0;

	BuildFaceTree_r ( tree->headnode, list );

	qprintf( "%5i leafs\n", c_faceLeafs );

	return tree;
}


/*
=================
BspFaceForPortal
=================
*/
bspface_t *BspFaceForPortal( portal_t *p ) {
	bspface_t	*f;

	f = AllocBspFace();
	f->w = CopyWinding( p->winding );
	f->planenum = p->onnode->planenum & ~1;

	return f;
}



/*
=================
MakeStructuralBspFaceList
=================
*/
bspface_t	*MakeStructuralBspFaceList( bspbrush_t *list ) {
	bspbrush_t	*b;
	int			i;
	side_t		*s;
	winding_t	*w;
	bspface_t	*f, *flist;

	flist = NULL;
	for ( b = list ; b ; b = b->next ) {
		if ( b->detail ) {
			continue;
		}
		for ( i = 0 ; i < b->numsides ; i++ ) {
			s = &b->sides[i];
			w = s->winding;
			if ( !w ) {
				continue;
			}
			f = AllocBspFace();
			f->w = CopyWinding( w );
			f->planenum = s->planenum & ~1;
			f->next = flist;
			if (s->surfaceFlags & SURF_HINT) {
				//f->priority = HINT_PRIORITY;
				f->hint = qtrue;
			}
			flist = f;
		}
	}

	return flist;
}

/*
=================
MakeVisibleBspFaceList
=================
*/
bspface_t	*MakeVisibleBspFaceList( bspbrush_t *list ) {
	bspbrush_t	*b;
	int			i;
	side_t		*s;
	winding_t	*w;
	bspface_t	*f, *flist;

	flist = NULL;
	for ( b = list ; b ; b = b->next ) {
		if ( b->detail ) {
			continue;
		}
		for ( i = 0 ; i < b->numsides ; i++ ) {
			s = &b->sides[i];
			w = s->visibleHull;
			if ( !w ) {
				continue;
			}
			f = AllocBspFace();
			f->w = CopyWinding( w );
			f->planenum = s->planenum & ~1;
			f->next = flist;
			if (s->surfaceFlags & SURF_HINT) {
				//f->priority = HINT_PRIORITY;
				f->hint = qtrue;
			}
			flist = f;
		}
	}

	return flist;
}

