/*
 * portals.c
 * $Id: portals.c,v 1.9 2007-12-14 16:41:25 sezero Exp $
 *
 * Copyright (C) 1996-1997  Id Software, Inc.
 * Copyright (C) 1997-1998  Raven Software Corp.
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "q_stdinc.h"
#include "compiler.h"
#include "arch_def.h"
#include "cmdlib.h"
#include "mathlib.h"
#include "bspfile.h"
#include "bsp5.h"


node_t	outside_node;		// portals outside the world face this

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

/*
=============
AddPortalToNodes
=============
*/
static void AddPortalToNodes (portal_t *p, node_t *front, node_t *back)
{
	if (p->nodes[0] || p->nodes[1])
		COM_Error ("%s: already included", __thisfunc__);

	p->nodes[0] = front;
	p->next[0] = front->portals;
	front->portals = p;

	p->nodes[1] = back;
	p->next[1] = back->portals;
	back->portals = p;
}


/*
=============
RemovePortalFromNode
=============
*/
static void RemovePortalFromNode (portal_t *portal, node_t *l)
{
	portal_t	**pp, *t;

// remove reference to the current portal
	pp = &l->portals;
	while (1)
	{
		t = *pp;
		if (!t)
			COM_Error ("%s: portal not in leaf", __thisfunc__);

		if (t == portal)
			break;

		if (t->nodes[0] == l)
			pp = &t->next[0];
		else if (t->nodes[1] == l)
			pp = &t->next[1];
		else
			COM_Error ("%s: portal not bounding leaf", __thisfunc__);
	}

	if (portal->nodes[0] == l)
	{
		*pp = portal->next[0];
		portal->nodes[0] = NULL;
	}
	else if (portal->nodes[1] == l)
	{
		*pp = portal->next[1];
		portal->nodes[1] = NULL;
	}
}

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

#if 0	// no users
void PrintPortal (portal_t *p)
{
	int			i;
	winding_t	*w;

	w = p->winding;
	for (i = 0 ; i < w->numpoints ; i++)
		printf ("(%5.0f,%5.0f,%5.0f)\n",w->points[i][0],
				w->points[i][1], w->points[i][2]);
}
#endif

/*
================
MakeHeadnodePortals

The created portals will face the global outside_node
================
*/
static void MakeHeadnodePortals (node_t *node)
{
	vec3_t		bounds[2];
	int			i, j, n;
	portal_t	*p, *portals[6];
	plane_t		bplanes[6], *pl;
	int			side;

	Draw_ClearWindow ();

// pad with some space so there will never be null volume leafs
	for (i = 0 ; i < 3 ; i++)
	{
		bounds[0][i] = brushset->mins[i] - SIDESPACE;
		bounds[1][i] = brushset->maxs[i] + SIDESPACE;
	}

	outside_node.contents = CONTENTS_SOLID;
	outside_node.portals = NULL;

	for (i = 0 ; i < 3 ; i++)
	{
		for (j = 0 ; j < 2 ; j++)
		{
			n = j*3 + i;

			p = AllocPortal ();
			portals[n] = p;

			pl = &bplanes[n];
			memset (pl, 0, sizeof(*pl));
			if (j)
			{
				pl->normal[i] = -1;
				pl->dist = -bounds[j][i];
			}
			else
			{
				pl->normal[i] = 1;
				pl->dist = bounds[j][i];
			}
			p->planenum = FindPlane (pl, &side);

			p->winding = BaseWindingForPlane (pl);
			if (side)
				AddPortalToNodes (p, &outside_node, node);
			else
				AddPortalToNodes (p, node, &outside_node);
		}
	}

// clip the basewindings by all the other planes
	for (i = 0 ; i < 6 ; i++)
	{
		for (j = 0 ; j < 6 ; j++)
		{
			if (j == i)
				continue;
			portals[i]->winding = ClipWinding (portals[i]->winding, &bplanes[j], true);
		}
	}
}

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

static void PlaneFromWinding (winding_t *w, plane_t *plane)
{
	vec3_t		v1, v2;

// calc plane
	VectorSubtract (w->points[2], w->points[1], v1);
	VectorSubtract (w->points[0], w->points[1], v2);
	CrossProduct (v2, v1, plane->normal);
	VectorNormalize (plane->normal);
	plane->dist = DotProduct (w->points[0], plane->normal);
}

#if 0	// all uses are commented out
static void CheckWindingInNode (winding_t *w, node_t *node)
{
	int		i, j;

	for (i = 0 ; i < w->numpoints ; i++)
	{
		for (j = 0 ; j < 3 ; j++)
		{
			if (w->points[i][j] < node->mins[j] - 1
				|| w->points[i][j] > node->maxs[j] + 1)
			{
				printf ("WARNING: %s: outside\n", __thisfunc__);
				return;
			}
		}
	}
}

static void CheckWindingArea (winding_t *w)
{
	int		i;
	float	total, add;
	vec3_t	v1, v2, cross;

	total = 0.0F;
	for (i = 1 ; i < w->numpoints ; i++)
	{
		VectorSubtract (w->points[i], w->points[0], v1);
		VectorSubtract (w->points[i+1], w->points[0], v2);
		CrossProduct (v1, v2, cross);
		add = VectorLength (cross);
		total += add*0.5;
	}
	if (total < 16)
		printf ("WARNING: winding area %f\n", total);
}

static void CheckLeafPortalConsistancy (node_t *node)
{
	int			side, side2;
	portal_t	*p, *p2;
	plane_t		plane, plane2;
	int			i;
	winding_t	*w;
	float		dist;

	side = side2 = 0;		// quiet compiler warning

	for (p = node->portals ; p ; p = p->next[side])	
	{
		if (p->nodes[0] == node)
			side = 0;
		else if (p->nodes[1] == node)
			side = 1;
		else
			COM_Error ("%s: mislinked portal", __thisfunc__);
		CheckWindingInNode (p->winding, node);
		CheckWindingArea (p->winding);

	// check that the side orders are correct
		plane = planes[p->planenum];
		PlaneFromWinding (p->winding, &plane2);

		for (p2 = node->portals ; p2 ; p2 = p2->next[side2])
		{
			if (p2->nodes[0] == node)
				side2 = 0;
			else if (p2->nodes[1] == node)
				side2 = 1;
			else
				COM_Error ("%s: mislinked portal", __thisfunc__);
			w = p2->winding;
			for (i = 0 ; i < w->numpoints ; i++)
			{
				dist = DotProduct (w->points[i], plane.normal) - plane.dist;
				if ( (side == 0 && dist < -1) || (side == 1 && dist > 1) )
				{
					printf ("WARNING: portal siding direction is wrong\n");
					return;
				}
			}
		}
	}
}
#endif

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

/*
================
CutNodePortals_r

================
*/
static void CutNodePortals_r (node_t *node)
{
	plane_t		*plane, clipplane;
	node_t		*f, *b, *other_node;
	portal_t	*p, *new_portal, *next_portal;
	winding_t	*w, *frontwinding, *backwinding;
	int			side;

//	CheckLeafPortalConsistancy (node);

//
// seperate the portals on node into it's children
//
	if (node->contents)
	{
		return;		// at a leaf, no more dividing
	}

	plane = &planes[node->planenum];

	f = node->children[0];
	b = node->children[1];

//
// 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
//
	new_portal = AllocPortal ();
	new_portal->planenum = node->planenum;

	w = BaseWindingForPlane (&planes[node->planenum]);
	for (p = node->portals ; p ; p = p->next[side])
	{
		clipplane = planes[p->planenum];
		if (p->nodes[0] == node)
			side = 0;
		else if (p->nodes[1] == node)
		{
			clipplane.dist = -clipplane.dist;
			VectorNegate (clipplane.normal, clipplane.normal);
			side = 1;
		}
		else
		{
			COM_Error ("%s: mislinked portal", __thisfunc__);
			side = 0;	// shut up compiler warnings
		}

		w = ClipWinding (w, &clipplane, true);
		if (!w)
		{
			printf ("WARNING: %s: new portal was clipped away\n", __thisfunc__);
			break;
		}
	}

	if (w)
	{
	// if the plane was not clipped on all sides, there was an error
		new_portal->winding = w;
		AddPortalToNodes (new_portal, f, b);
	}

//
// partition the portals
//
	for (p = node->portals ; p ; p = next_portal)
	{
		if (p->nodes[0] == node)
			side = 0;
		else if (p->nodes[1] == node)
			side = 1;
		else
			COM_Error ("%s: mislinked portal", __thisfunc__);
		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);
		}
	}

	DrawLeaf (f,1);
	DrawLeaf (b,2);

	CutNodePortals_r (f);
	CutNodePortals_r (b);
}


/*
==================
PortalizeWorld

Builds the exact polyhedrons for the nodes and leafs
==================
*/
void PortalizeWorld (node_t *headnode)
{
	qprintf ("----- portalize ----\n");

	MakeHeadnodePortals (headnode);
	CutNodePortals_r (headnode);
}


/*
==================
FreeAllPortals

==================
*/
void FreeAllPortals (node_t *node)
{
	portal_t	*p, *nextp;

	if (!node->contents)
	{
		FreeAllPortals (node->children[0]);
		FreeAllPortals (node->children[1]);
	}

	for (p = node->portals ; p ; p = nextp)
	{
		if (p->nodes[0] == node)
			nextp = p->next[0];
		else
			nextp = p->next[1];
		RemovePortalFromNode (p, p->nodes[0]);
		RemovePortalFromNode (p, p->nodes[1]);
		FreeWinding (p->winding);
		FreePortal (p);
	}
}

/*
==============================================================================

PORTAL FILE GENERATION

==============================================================================
*/

#define	PORTALFILE	"PRT1"

static FILE	*pf;
static int		num_visleafs;	// leafs the player can be in
static int		num_visportals;

static void WriteFloat (FILE *f, double v)
{
	if ( fabs(v - Q_rint(v)) < 0.001 )
		fprintf (f,"%i ",(int)Q_rint(v));
	else
		fprintf (f,"%f ",v);
}

extern qboolean watervis;

static void WritePortalFile_r (node_t *node)
{
	int		i;
	portal_t	*p;
	winding_t	*w;
	plane_t		*pl, plane2;

	if (!node->contents)
	{
		WritePortalFile_r (node->children[0]);
		WritePortalFile_r (node->children[1]);
		return;
	}

	if (node->contents == CONTENTS_SOLID)
		return;

	for (p = node->portals ; p ; )
	{
		w = p->winding;
		if (w && p->nodes[0] == node)
		{
			if ( (watervis && 
				((p->nodes[0]->contents == CONTENTS_WATER && p->nodes[1]->contents == CONTENTS_EMPTY) ||
				 (p->nodes[0]->contents == CONTENTS_EMPTY && p->nodes[1]->contents == CONTENTS_WATER)) ) 
			    || (p->nodes[0]->contents == p->nodes[1]->contents) )
			{
			// write out to the file

			// sometimes planes get turned around when they are very near
			// the changeover point between different axis.  interpret the
			// plane the same way vis will, and flip the side orders if needed
				pl = &planes[p->planenum];
				PlaneFromWinding (w, &plane2);
				if ( DotProduct (pl->normal, plane2.normal) < 0.99 )
				{	// backwards...
					fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[1]->visleafnum, p->nodes[0]->visleafnum);
				}
				else
					fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[0]->visleafnum, p->nodes[1]->visleafnum);
				for (i = 0 ; i < w->numpoints ; i++)
				{
					fprintf (pf,"(");
					WriteFloat (pf, w->points[i][0]);
					WriteFloat (pf, w->points[i][1]);
					WriteFloat (pf, w->points[i][2]);
					fprintf (pf,") ");
				}
				fprintf (pf,"\n");
			}
		}

		if (p->nodes[0] == node)
			p = p->next[0];
		else
			p = p->next[1];
	}
}

/*
================
NumberLeafs_r
================
*/
static void NumberLeafs_r (node_t *node)
{
	portal_t	*p;

	if (!node->contents)
	{	// decision node
		node->visleafnum = -99;
		NumberLeafs_r (node->children[0]);
		NumberLeafs_r (node->children[1]);
		return;
	}

	Draw_ClearWindow ();
	DrawLeaf (node, 1);

	if (node->contents == CONTENTS_SOLID)
	{	// solid block, viewpoint never inside
		node->visleafnum = -1;
		return;
	}

	node->visleafnum = num_visleafs++;

	for (p = node->portals ; p ; )
	{
		if (p->nodes[0] == node)	// only write out from first leaf
		{
			if ( (watervis &&
				((p->nodes[0]->contents == CONTENTS_WATER && p->nodes[1]->contents == CONTENTS_EMPTY) ||
				 (p->nodes[0]->contents == CONTENTS_EMPTY && p->nodes[1]->contents == CONTENTS_WATER)) )
			    || (p->nodes[0]->contents == p->nodes[1]->contents) )
				num_visportals++;

			p = p->next[0];
		}
		else
			p = p->next[1];
	}
}


/*
================
WritePortalfile
================
*/
void WritePortalfile (node_t *headnode)
{
// set the visleafnum field in every leaf and count the total number of portals
	num_visleafs = 0;
	num_visportals = 0;
	NumberLeafs_r (headnode);

// write the file
	printf ("writing %s\n", portfilename);
	pf = fopen (portfilename, "w");
	if (!pf)
		COM_Error ("Error opening %s", portfilename);

	fprintf (pf, "%s\n", PORTALFILE);
	fprintf (pf, "%i\n", num_visleafs);
	fprintf (pf, "%i\n", num_visportals);

	WritePortalFile_r (headnode);

	fclose (pf);
}

