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

// bsp5.c

#include "bsp5.h"

//
// command line flags
//
qboolean	drawflag;
qboolean nofill;
qboolean notjunc;
qboolean noclip;
qboolean onlyents;
qboolean	verbose = true;
qboolean	allverbose;
qboolean	usehulls;

int		subdivide_size = 240;

brushset_t	*brushset;

int		valid;

char	bspfilename[1024];
char	pointfilename[1024];
char	portfilename[1024];
char	hullfilename[1024];

char	*argv0;					// changed after fork();

qboolean	worldmodel;

int		hullnum;

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

void qprintf (char *fmt, ...)
{
	va_list argptr;

	if (!verbose)
		return;

	va_start (argptr, fmt);
	vprintf (fmt,argptr);
	va_end (argptr);
}

/*
=================
BaseWindingForPlane
=================
*/
winding_t *BaseWindingForPlane (plane_t *p)
{
	int		i, x;
	vec_t	max, v;
	vec3_t	org, vright, vup;
	winding_t	*w;
	
// find the major axis

	max = -BOGUS_RANGE;
	x = -1;
	for (i=0 ; i<3; i++)
	{
		v = fabs(p->normal[i]);
		if (v > max)
		{
			x = i;
			max = v;
		}
	}
	if (x==-1)
		Error ("BaseWindingForPlane: no axis found");
		
	VectorCopy (vec3_origin, vup);	
	switch (x)
	{
	case 0:
	case 1:
		vup[2] = 1;
		break;		
	case 2:
		vup[0] = 1;
		break;		
	}

	v = DotProduct (vup, p->normal);
	VectorMA (vup, -v, p->normal, vup);
	VectorNormalize (vup);
		
	VectorScale (p->normal, p->dist, org);
	
	CrossProduct (vup, p->normal, vright);
	
	VectorScale (vup, 8192, vup);
	VectorScale (vright, 8192, vright);

// project a really big	axis aligned box onto the plane
	w = NewWinding (4);
	
	VectorSubtract (org, vright, w->points[0]);
	VectorAdd (w->points[0], vup, w->points[0]);
	
	VectorAdd (org, vright, w->points[1]);
	VectorAdd (w->points[1], vup, w->points[1]);
	
	VectorAdd (org, vright, w->points[2]);
	VectorSubtract (w->points[2], vup, w->points[2]);
	
	VectorSubtract (org, vright, w->points[3]);
	VectorSubtract (w->points[3], vup, w->points[3]);
	
	w->numpoints = 4;
	
	return w;	
}


/*
==================
CheckWinding

Check for possible errors
==================
*/
void CheckWinding (winding_t *w)
{
}


/*
==================
ClipWinding

Clips the winding to the plane, returning the new winding on the positive side
Frees the input winding.
If keepon is true, an exactly on-plane winding will be saved, otherwise
it will be clipped away.
==================
*/
winding_t *ClipWinding (winding_t *in, plane_t *split, qboolean keepon)
{
	vec_t	dists[MAX_POINTS_ON_WINDING];
	int		sides[MAX_POINTS_ON_WINDING];
	int		counts[3];
	vec_t	dot;
	int		i, j;
	vec_t	*p1, *p2;
	vec3_t	mid;
	winding_t	*neww;
	int		maxpts;
	
	counts[0] = counts[1] = counts[2] = 0;

// determine sides for each point
	for (i=0 ; i<in->numpoints ; i++)
	{
		dot = DotProduct (in->points[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 (keepon && !counts[0] && !counts[1])
		return in;
		
	if (!counts[0])
	{
		FreeWinding (in);
		return NULL;
	}
	if (!counts[1])
		return in;
	
	maxpts = in->numpoints+4;	// can't use counts[0]+2 because
								// of fp grouping errors
	neww = NewWinding (maxpts);
		
	for (i=0 ; i<in->numpoints ; i++)
	{
		p1 = in->points[i];
		
		if (sides[i] == SIDE_ON)
		{
			VectorCopy (p1, neww->points[neww->numpoints]);
			neww->numpoints++;
			continue;
		}
	
		if (sides[i] == SIDE_FRONT)
		{
			VectorCopy (p1, neww->points[neww->numpoints]);
			neww->numpoints++;
		}
		
		if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
			continue;
			
	// generate a split point
		p2 = in->points[(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, neww->points[neww->numpoints]);
		neww->numpoints++;
	}
	
	if (neww->numpoints > maxpts)
		Error ("ClipWinding: points exceeded estimate");
		
// free the original winding
	FreeWinding (in);
	
	return neww;
}


/*
==================
DivideWinding

Divides a winding by a plane, producing one or two windings.  The
original winding is not damaged or freed.  If only on one side, the
returned winding will be the input winding.  If on both sides, two
new windings will be created.
==================
*/
void	DivideWinding (winding_t *in, plane_t *split, winding_t **front, winding_t **back)
{
	vec_t	dists[MAX_POINTS_ON_WINDING];
	int		sides[MAX_POINTS_ON_WINDING];
	int		counts[3];
	vec_t	dot;
	int		i, j;
	vec_t	*p1, *p2;
	vec3_t	mid;
	winding_t	*f, *b;
	int		maxpts;
	
	counts[0] = counts[1] = counts[2] = 0;

// determine sides for each point
	for (i=0 ; i<in->numpoints ; i++)
	{
		dot = DotProduct (in->points[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];
	
	*front = *back = NULL;

	if (!counts[0])
	{
		*back = in;
		return;
	}
	if (!counts[1])
	{
		*front = in;
		return;
	}

	maxpts = in->numpoints+4;	// can't use counts[0]+2 because
								// of fp grouping errors

	*front = f = NewWinding (maxpts);
	*back = b = NewWinding (maxpts);
		
	for (i=0 ; i<in->numpoints ; i++)
	{
		p1 = in->points[i];
		
		if (sides[i] == SIDE_ON)
		{
			VectorCopy (p1, f->points[f->numpoints]);
			f->numpoints++;
			VectorCopy (p1, b->points[b->numpoints]);
			b->numpoints++;
			continue;
		}
	
		if (sides[i] == SIDE_FRONT)
		{
			VectorCopy (p1, f->points[f->numpoints]);
			f->numpoints++;
		}
		if (sides[i] == SIDE_BACK)
		{
			VectorCopy (p1, b->points[b->numpoints]);
			b->numpoints++;
		}

		if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
			continue;
			
	// generate a split point
		p2 = in->points[(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, f->points[f->numpoints]);
		f->numpoints++;
		VectorCopy (mid, b->points[b->numpoints]);
		b->numpoints++;
	}
	
	if (f->numpoints > maxpts || b->numpoints > maxpts)
		Error ("ClipWinding: points exceeded estimate");
}


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

int			c_activefaces, c_peakfaces;
int			c_activesurfaces, c_peaksurfaces;
int			c_activewindings, c_peakwindings;
int			c_activeportals, c_peakportals;

void PrintMemory (void)
{
	printf ("faces   : %6i (%6i)\n", c_activefaces, c_peakfaces);
	printf ("surfaces: %6i (%6i)\n", c_activesurfaces, c_peaksurfaces);
	printf ("windings: %6i (%6i)\n", c_activewindings, c_peakwindings);
	printf ("portals : %6i (%6i)\n", c_activeportals, c_peakportals);
}

/*
==================
NewWinding
==================
*/
winding_t *NewWinding (int points)
{
	winding_t	*w;
	int			size;
	
	if (points > MAX_POINTS_ON_WINDING)
		Error ("NewWinding: %i points", points);
	
	c_activewindings++;
	if (c_activewindings > c_peakwindings)
		c_peakwindings = c_activewindings;

	size = (int)((winding_t *)0)->points[points];
	w = malloc (size);
	memset (w, 0, size);
	
	return w;
}


void FreeWinding (winding_t *w)
{
	c_activewindings--;
	free (w);
}



/*
===========
AllocFace
===========
*/
face_t *AllocFace (void)
{
	face_t	*f;
	
	c_activefaces++;
	if (c_activefaces > c_peakfaces)
		c_peakfaces = c_activefaces;
		
	f = malloc (sizeof(face_t));
	memset (f, 0, sizeof(face_t));
	f->planenum = -1;

	return f;
}


void FreeFace (face_t *f)
{
	c_activefaces--;
//	memset (f,0xff,sizeof(face_t));
	free (f);
}


/*
===========
AllocSurface
===========
*/
surface_t *AllocSurface (void)
{
	surface_t	*s;
	
	s = malloc (sizeof(surface_t));
	memset (s, 0, sizeof(surface_t));
	
	c_activesurfaces++;
	if (c_activesurfaces > c_peaksurfaces)
		c_peaksurfaces = c_activesurfaces;
		
	return s;
}

void FreeSurface (surface_t *s)
{
	c_activesurfaces--;
	free (s);
}

/*
===========
AllocPortal
===========
*/
portal_t *AllocPortal (void)
{
	portal_t	*p;
	
	c_activeportals++;
	if (c_activeportals > c_peakportals)
		c_peakportals = c_activeportals;
	
	p = malloc (sizeof(portal_t));
	memset (p, 0, sizeof(portal_t));
	
	return p;
}

void FreePortal (portal_t *p)
{
	c_activeportals--;
	free (p);
}


/*
===========
AllocNode
===========
*/
node_t *AllocNode (void)
{
	node_t	*n;
	
	n = malloc (sizeof(node_t));
	memset (n, 0, sizeof(node_t));
	
	return n;
}

/*
===========
AllocBrush
===========
*/
brush_t *AllocBrush (void)
{
	brush_t	*b;
	
	b = malloc (sizeof(brush_t));
	memset (b, 0, sizeof(brush_t));
	
	return b;
}

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

/*
===============
ProcessEntity
===============
*/
void ProcessEntity (int entnum)
{
	entity_t *ent;
	char	mod[80];
	surface_t	*surfs;
	node_t		*nodes;
	brushset_t	*bs;
	

	ent = &entities[entnum];
	if (!ent->brushes)
		return;		// non-bmodel entity

	if (entnum > 0)
	{
		worldmodel = false;
		if (entnum == 1)
			qprintf ("--- Internal Entities ---\n");
		sprintf (mod, "*%i", nummodels);
		if (verbose)
			PrintEntity (ent);

		if (hullnum == 0)
			printf ("MODEL: %s\n", mod);
		SetKeyValue (ent, "model", mod);
	}
	else
		worldmodel = true;
	

//
// take the brush_ts and clip off all overlapping and contained faces,
// leaving a perfect skin of the model with no hidden faces
//
	bs = Brush_LoadEntity (ent, hullnum);
	
	if (!bs->brushes)
	{
		PrintEntity (ent);
		Error ("Entity with no valid brushes");
	}
	
	brushset = bs;
	surfs = CSGFaces (bs);

	if (hullnum != 0)
	{
		nodes = SolidBSP (surfs, true);
		if (entnum == 0 && !nofill)	// assume non-world bmodels are simple
		{
			PortalizeWorld (nodes);
			if (FillOutside (nodes))
			{
				surfs = GatherNodeFaces (nodes);
				nodes = SolidBSP (surfs, false);	// make a really good tree
			}
			FreeAllPortals (nodes);
		}
		WriteNodePlanes (nodes);
		WriteClipNodes (nodes);
		BumpModel (hullnum);
	}
	else
	{
	//
	// SolidBSP generates a node tree
	//
	// if not the world, make a good tree first
	// the world is just going to make a bad tree
	// because the outside filling will force a regeneration later
		nodes = SolidBSP (surfs, entnum == 0);	
		
	//
	// build all the portals in the bsp tree
	// some portals are solid polygons, and some are paths to other leafs
	//
		if (entnum == 0 && !nofill)	// assume non-world bmodels are simple
		{
			PortalizeWorld (nodes);
		
			if (FillOutside (nodes))
			{
				FreeAllPortals (nodes);

			// get the remaining faces together into surfaces again
				surfs = GatherNodeFaces (nodes);
	
			// merge polygons
				MergeAll (surfs);
	
			// make a really good tree
				nodes = SolidBSP (surfs, false);
	
			// make the real portals for vis tracing
				PortalizeWorld (nodes);
	
			// save portal file for vis tracing
				WritePortalfile (nodes);
				
			// fix tjunctions
				tjunc (nodes);
			}
			FreeAllPortals (nodes);
		}

		WriteNodePlanes (nodes);
		MakeFaceEdges (nodes);
		WriteDrawNodes (nodes);
	}
}

/*
=================
UpdateEntLump

=================
*/
void UpdateEntLump (void)
{
	int		m, entnum;
	char	mod[80];
		
	m = 1;
	for (entnum = 1 ; entnum < num_entities ; entnum++)
	{
		if (!entities[entnum].brushes)
			continue;
		sprintf (mod, "*%i", m);
		SetKeyValue (&entities[entnum], "model", mod);
		m++;
	}

	printf ("Updating entities lump...\n");
	LoadBSPFile (bspfilename);
	WriteEntitiesToString();
	WriteBSPFile (bspfilename);	
}

/*
=================
WriteClipHull

Write the clipping hull out to a text file so the parent process can get it
=================
*/
void WriteClipHull (void)
{
	FILE	*f;
	int		i;
	dplane_t	*p;
	dclipnode_t	*d;

	hullfilename[strlen(hullfilename)-1] = '0' + hullnum;

	qprintf ("---- WriteClipHull ----\n");
	qprintf ("Writing %s\n", hullfilename);

	f = fopen (hullfilename, "w");
	if (!f)
		Error ("Couldn't open %s", hullfilename);

	fprintf (f, "%i\n", nummodels);

	for (i=0 ; i<nummodels ; i++)
		fprintf (f, "%i\n", dmodels[i].headnode[hullnum]);
		
	fprintf (f, "\n%i\n", numclipnodes);

	for (i=0 ; i<numclipnodes ; i++)
	{
		d = &dclipnodes[i];
		p = &dplanes[d->planenum];
		// the node number is only written out for human readability
		fprintf (f, "%5i : %f %f %f %f : %5i %5i\n", i, p->normal[0], p->normal[1], p->normal[2], p->dist, d->children[0], d->children[1]);
	}
	
	fclose (f);
}

/*
=================
ReadClipHull

Read the files written out by the child processes
=================
*/
void ReadClipHull (int hullnum)
{
	FILE		*f;
	int			i, j, n;
	int			firstclipnode;
	dplane_t	p;
	dclipnode_t	*d;
	int			c1, c2;
	float		f1, f2, f3, f4;
	int			junk;
	vec3_t		norm;

	hullfilename[strlen(hullfilename)-1] = '0' + hullnum;

	f = fopen (hullfilename, "r");
	if (!f)
		Error ("Couldn't open %s", hullfilename);

	if (fscanf (f,"%i\n", &n) != 1)
		Error ("Error parsing %s", hullfilename);

	if (n != nummodels)
		Error ("ReadClipHull: hull had %i models, base had %i", n, nummodels);

	for (i=0 ; i<n ; i++)
	{
		fscanf (f, "%i\n", &j);
		dmodels[i].headnode[hullnum] = numclipnodes + j;
	}
	
	
	fscanf (f,"\n%i\n", &n);
	firstclipnode = numclipnodes;
	
	for (i=0 ; i<n ; i++)
	{
		if (numclipnodes == MAX_MAP_CLIPNODES)
			Error ("ReadClipHull: MAX_MAP_CLIPNODES");
		d = &dclipnodes[numclipnodes];
		numclipnodes++;
		if (fscanf (f,"%i : %f %f %f %f : %i %i\n", &junk, &f1, &f2, &f3, &f4, &c1, &c2) != 7)
			Error ("Error parsing %s", hullfilename);
		

		p.normal[0] = f1;
		p.normal[1] = f2;
		p.normal[2] = f3;
		p.dist = f4;

		norm[0] = f1; norm[1] = f2; norm[2] = f3; 	// vec_t precision
		p.type = PlaneTypeForNormal (norm);
		
		d->children[0] = c1 >= 0 ? c1 + firstclipnode : c1;
		d->children[1] = c2 >= 0 ? c2 + firstclipnode : c2;
		d->planenum = FindFinalPlane (&p);
	}
	
}

/*
=================
CreateSingleHull

=================
*/
void CreateSingleHull (void)
{
	int			entnum;

// for each entity in the map file that has geometry
	for (entnum = 0 ; entnum < num_entities ; entnum++)
	{
		ProcessEntity (entnum);
		if (!allverbose)
			verbose = false;	// don't print rest of entities
	}

	if (hullnum)
		WriteClipHull ();
}

/*
=================
CreateHulls

=================
*/
void CreateHulls (void)
{
// commanded to create a single hull only
	if (hullnum)
	{
		CreateSingleHull ();
		exit (0);
	}
	
// commanded to use the allready existing hulls 1 and 2
	if (usehulls)
	{
		CreateSingleHull ();
		return;
	}
	
// commanded to ignore the hulls altogether
	if (noclip)
	{
		CreateSingleHull ();
		return;
	}


// create all the hulls

#ifdef __alpha
	printf ("forking hull processes...\n");
// fork a process for each clipping hull
	fflush (stdout);
	if (!fork ())
	{
		hullnum = 1;
		verbose = false;
		drawflag = false;
		sprintf (argv0, "HUL%i", hullnum);
	}
	else if (!fork ())
	{
		hullnum = 2;
		verbose = false;
		drawflag = false;
		sprintf (argv0, "HUL%i", hullnum);
	}
	CreateSingleHull ();

	if (hullnum)
		exit (0);
	
	wait (NULL);		// wait for clip hull process to finish
	wait (NULL);		// wait for clip hull process to finish

#else
// create the hulls sequentially
	printf ("building hulls sequentially...\n");

	hullnum = 1;
	CreateSingleHull ();
	
	nummodels = 0;
	numplanes = 0;
	numclipnodes = 0;
	hullnum = 2;
	CreateSingleHull ();
	
	nummodels = 0;
	numplanes = 0;
	numclipnodes = 0;
	hullnum = 0;
	CreateSingleHull ();
#endif	

}

/*
=================
ProcessFile

=================
*/
void ProcessFile (char *sourcebase, char *bspfilename1)
{
// create filenames
	strcpy (bspfilename, bspfilename1);
	StripExtension (bspfilename);
	strcat (bspfilename, ".bsp");

	strcpy (hullfilename, bspfilename1);
	StripExtension (hullfilename);
	strcat (hullfilename, ".h0");

	strcpy (portfilename, bspfilename1);
	StripExtension (portfilename);
	strcat (portfilename, ".prt");
	
	strcpy (pointfilename, bspfilename1);
	StripExtension (pointfilename);
	strcat (pointfilename, ".pts");

	if (!onlyents)
	{
		remove (bspfilename);
		if (!usehulls)
		{
			hullfilename[strlen(hullfilename)-1] = '1';
			remove (hullfilename);
			hullfilename[strlen(hullfilename)-1] = '2';
			remove (hullfilename);
		}
		remove (portfilename);
		remove (pointfilename);
	}
	
// load brushes and entities
	LoadMapFile (sourcebase);
	if (onlyents)
	{
		UpdateEntLump ();
		return;
	}
	
// init the tables to be shared by all models
	BeginBSPFile ();

// the clipping hulls will be written out to text files by forked processes
	CreateHulls ();

	ReadClipHull (1);
	ReadClipHull (2);

	WriteEntitiesToString();
	FinishBSPFile ();
}


/*
==================
main

==================
*/
int main (int argc, char **argv)
{
	int			i;
	double		start, end;
	char		sourcename[1024];
	char		destname[1024];
	
//	malloc_debug (15);

//
// check command line flags
//
	for (i=1 ; i<argc ; i++)
	{
		if (argv[i][0] != '-')
			break;
		else if (!strcmp (argv[i],"-draw"))
			drawflag = true;
		else if (!strcmp (argv[i],"-notjunc"))
			notjunc = true;
		else if (!strcmp (argv[i],"-nofill"))
			nofill = true;
		else if (!strcmp (argv[i],"-noclip"))
			noclip = true;
		else if (!strcmp (argv[i],"-onlyents"))
			onlyents = true;
		else if (!strcmp (argv[i],"-verbose"))
			allverbose = true;
		else if (!strcmp (argv[i],"-usehulls"))
			usehulls = true;		// don't fork -- use the existing files
		else if (!strcmp (argv[i],"-hullnum"))
		{
			hullnum = atoi(argv[i+1]);
			i++;
		}
		else if (!strcmp (argv[i],"-subdivide"))
		{
			subdivide_size = atoi(argv[i+1]);
			i++;
		}
		else
			Error ("qbsp: Unknown option '%s'", argv[i]);
	}
	
	if (i != argc - 2 && i != argc - 1)
		Error ("usage: qbsp [options] sourcefile [destfile]\noptions: -nojunc -nofill -threads[124] -draw -onlyents -verbose -proj <projectpath>");

//
// let forked processes change name for ps status
//
	argv0 = argv[0];

	
//
// create destination name if not specified
//
	strcpy (sourcename, argv[i]);
	DefaultExtension (sourcename, ".map");
	
	if (i != argc - 2)
	{
		strcpy (destname, argv[i]);
		StripExtension (destname);
		strcat (destname, ".bsp");
		printf ("outputfile: %s\n", destname);
	}
	else
		strcpy (destname, argv[i+1]);

//
// do it!
//
	start = I_FloatTime ();
	ProcessFile (sourcename, destname);
	end = I_FloatTime ();
	printf ("%5.1f seconds elapsed\n", end-start);
	
	return 0;
}
