//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//

#include "vbsp.h"
#include "collisionutils.h"
/*
==============================================================================

PORTAL FILE GENERATION

Save out name.prt for qvis to read
==============================================================================
*/


#define	PORTALFILE	"PRT1"

struct cluster_portals_t
{
	CUtlVector<portal_t *>	portals;
};

int		num_visclusters;				// clusters the player can be in
int		num_visportals;

int g_SkyCluster = -1;

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


/*
=================
WritePortalFile_r
=================
*/
void WritePortalFile(FILE *pFile, const CUtlVector<cluster_portals_t> &list)
{
	portal_t	*p;
	winding_t	*w;
	Vector		normal;
	vec_t		dist;

	for ( int clusterIndex = 0; clusterIndex < list.Count(); clusterIndex++ )
	{
		for ( int j = 0; j < list[clusterIndex].portals.Count(); j++ )
		{
			p = list[clusterIndex].portals[j];
			w = p->winding;
			// 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
			// FIXME: is this still relevent?
			WindingPlane (w, normal, &dist);
			if ( DotProduct (p->plane.normal, normal) < 0.99 )
			{	// backwards...
				fprintf (pFile,"%i %i %i ",w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster);
			}
			else
			{
				fprintf (pFile,"%i %i %i ",w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster);
			}
			
			for (int i=0 ; i<w->numpoints ; i++)
			{
				fprintf (pFile,"(");
				WriteFloat (pFile, w->p[i][0]);
				WriteFloat (pFile, w->p[i][1]);
				WriteFloat (pFile, w->p[i][2]);
				fprintf (pFile,") ");
			}
			fprintf (pFile,"\n");
		}
	}
}

struct viscluster_t
{
	bspbrush_t *pBrushes;
	int clusterIndex;
	int leafCount;
	int leafStart;
};

static CUtlVector<viscluster_t> g_VisClusters;

// add to the list of brushes the merge leaves into single vis clusters
void AddVisCluster( entity_t *pFuncVisCluster )
{
	viscluster_t tmp;
	Vector clipMins, clipMaxs;
	clipMins[0] = clipMins[1] = clipMins[2] = MIN_COORD_INTEGER;
	clipMaxs[0] = clipMaxs[1] = clipMaxs[2] = MAX_COORD_INTEGER;

	// build the map brushes out into the minimum non-overlapping set of brushes
	bspbrush_t *pBSPBrush = MakeBspBrushList( pFuncVisCluster->firstbrush, pFuncVisCluster->firstbrush + pFuncVisCluster->numbrushes, 
		clipMins, clipMaxs, NO_DETAIL);
	tmp.pBrushes = ChopBrushes( pBSPBrush );

	// store the entry in the list
	tmp.clusterIndex = -1;
	tmp.leafCount = 0;
	tmp.leafStart = 0;

#if DEBUG_VISUALIZE_CLUSTERS
	int debug = atoi(ValueForKey(pFuncVisCluster,"debug"));
	if ( debug )
	{
		Msg("Debug vis cluster %d\n", g_VisClusters.Count() );
	}
#endif

	g_VisClusters.AddToTail( tmp );
	
	// clear out this entity so it won't get written to the bsp
	pFuncVisCluster->epairs = NULL;
	pFuncVisCluster->numbrushes = 0;
}

// returns the total overlapping volume of intersection between the node and the brush list
float VolumeOfIntersection( bspbrush_t *pBrushList, node_t *pNode )
{
	float volume = 0.0f;
	for ( bspbrush_t *pBrush = pBrushList; pBrush; pBrush = pBrush->next )
	{
		if ( IsBoxIntersectingBox( pNode->mins, pNode->maxs, pBrush->mins, pBrush->maxs ) )
		{
			bspbrush_t *pIntersect = IntersectBrush( pNode->volume, pBrush );
			if ( pIntersect )
			{
				volume += BrushVolume( pIntersect );
				FreeBrush( pIntersect );
			}
		}
	}

	return volume;
}

// Search for a forced cluster that this node is within
// NOTE: Returns the first one found, these won't merge themselves together
int GetVisCluster( node_t *pNode )
{
	float maxVolume = BrushVolume(pNode->volume) * 0.10f;		// needs to cover at least 10% of the volume to overlap
	int maxIndex = -1;
	// UNDONE: This could get slow
	for ( int i = g_VisClusters.Count(); --i >= 0; )
	{
		float volume = VolumeOfIntersection( g_VisClusters[i].pBrushes, pNode );
		if ( volume > maxVolume )
		{
			volume = maxVolume;
			maxIndex = i;
		}
	}
	return maxIndex;
}
/*
================
NumberLeafs_r
================
*/
void BuildVisLeafList_r (node_t *node, CUtlVector<node_t *> &leaves)
{
	if (node->planenum != PLANENUM_LEAF)
	{	// decision node
		node->cluster = -99;
		BuildVisLeafList_r (node->children[0], leaves);
		BuildVisLeafList_r (node->children[1], leaves);
		return;
	}
	
	if ( node->contents & CONTENTS_SOLID )
	{	// solid block, viewpoint never inside
		node->cluster = -1;
		return;
	}
	leaves.AddToTail(node);
}

// Give each leaf in the list of empty leaves a vis cluster number
// some are within func_viscluster volumes and will be merged together
// every other leaf gets its own unique number
void NumberLeafs( const CUtlVector<node_t *> &leaves )
{
	for ( int i = 0; i < leaves.Count(); i++ )
	{
		node_t *node = leaves[i];
		int visCluster = GetVisCluster( node );
		if ( visCluster >= 0 )
		{
			if ( g_VisClusters[visCluster].clusterIndex < 0 )
			{
				g_VisClusters[visCluster].clusterIndex = num_visclusters;
				num_visclusters++;
			}
			g_VisClusters[visCluster].leafCount++;
			node->cluster = g_VisClusters[visCluster].clusterIndex;
		}
		else
		{
			if ( !g_bSkyVis && Is3DSkyboxArea( node->area ) )
			{
				if ( g_SkyCluster < 0 )
				{
					// allocate a cluster for the sky
					g_SkyCluster = num_visclusters;
					num_visclusters++;
				}
				node->cluster = g_SkyCluster;
			}
			else
			{
				node->cluster = num_visclusters;
				num_visclusters++;
			}
		}
	}

#if DEBUG_VISUALIZE_CLUSTERS
	for ( int i = 0; i < g_VisClusters.Count(); i++ )
	{
		char name[256];
		sprintf(name, "u:\\main\\game\\ep2\\maps\\vis_%02d.gl", i );
		FileHandle_t fp = g_pFileSystem->Open( name, "w" );
		Msg("Writing %s\n", name );
		for ( bspbrush_t *pBrush = g_VisClusters[i].pBrushes; pBrush; pBrush = pBrush->next )
		{
			for (int i =  0; i < pBrush->numsides; i++ )
				OutputWindingColor( pBrush->sides[i].winding, fp, 0, 255, 0 );
		}
		for ( int j = 0; j < leaves.Count(); j++ )
		{
			if ( leaves[j]->cluster == g_VisClusters[i].clusterIndex )
			{
				bspbrush_t *pBrush = leaves[j]->volume;
				for (int k = 0; k < pBrush->numsides; k++ )
					OutputWindingColor( pBrush->sides[k].winding, fp, 64 + (j&31), 64, 64 - (j&31) );
			}
		}
		g_pFileSystem->Close(fp);
	}
#endif
}

// build a list of all vis portals that connect clusters
int BuildPortalList( CUtlVector<cluster_portals_t> &portalList, const CUtlVector<node_t *> &leaves )
{
	int portalCount = 0;
	for ( int i = 0; i < leaves.Count(); i++ )
	{
		node_t *node = leaves[i];
		// count the portals
		for (portal_t *p = node->portals ; p ; )
		{
			if (p->nodes[0] == node)		// only write out from first leaf
			{
				if ( p->nodes[0]->cluster != p->nodes[1]->cluster )
				{
					if (Portal_VisFlood (p))
					{
						portalCount++;
						portalList[node->cluster].portals.AddToTail(p);
					}
				}
				p = p->next[0];
			}
			else
				p = p->next[1];		
		}
	}
	return portalCount;
}


/*
================
CreateVisPortals_r
================
*/
void CreateVisPortals_r (node_t *node)
{
	// stop as soon as we get to a leaf
	if (node->planenum == PLANENUM_LEAF )
		return;

	MakeNodePortal (node);
	SplitNodePortals (node);

	CreateVisPortals_r (node->children[0]);
	CreateVisPortals_r (node->children[1]);
}

int		clusterleaf;
void SaveClusters_r (node_t *node)
{
	if (node->planenum == PLANENUM_LEAF)
	{
		dleafs[clusterleaf++].cluster = node->cluster;
		return;
	}
	SaveClusters_r (node->children[0]);
	SaveClusters_r (node->children[1]);
}

/*
================
WritePortalFile
================
*/
void WritePortalFile (tree_t *tree)
{
	char	filename[1024];
	node_t *headnode;
	int start = Plat_FloatTime();

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

	sprintf (filename, "%s.prt", source);
	Msg ("writing %s...", filename);

	headnode = tree->headnode;

	FreeTreePortals_r (headnode);
	MakeHeadnodePortals (tree);

	CreateVisPortals_r (headnode);

// set the cluster field in every leaf and count the total number of portals
	num_visclusters = 0;
	Msg("Building visibility clusters...\n");
	CUtlVector<node_t *> leaves;
	BuildVisLeafList_r( headnode, leaves );

	NumberLeafs (leaves);
	CUtlVector<cluster_portals_t> portalList;
	portalList.SetCount( num_visclusters );
	num_visportals = BuildPortalList( portalList, leaves );
// write the file
	FILE *pf = fopen (filename, "w");
	if (!pf)
		Error ("Error opening %s", filename);
		
	fprintf (pf, "%s\n", PORTALFILE);
	fprintf (pf, "%i\n", num_visclusters);
	fprintf (pf, "%i\n", num_visportals);

	qprintf ("%5i visclusters\n", num_visclusters);
	qprintf ("%5i visportals\n", num_visportals);

	WritePortalFile(pf, portalList);

	fclose (pf);

	// we need to store the clusters out now because ordering
	// issues made us do this after writebsp...
	clusterleaf = 1;
	SaveClusters_r (headnode);

	Msg("done (%d)\n", (int)(Plat_FloatTime() - start) );
}