// vis.c

#include "vis.h"

#define LOGFILENAME "VIS.LOG"
#define MAX_THREADS     256

extern qboolean StateLoaded;

int	numportals;
int	portalleafs;

portal_t	*portals;
leaf_t		*leafs;
state_t		State;

int c_portaltest, c_portalpass, c_portalcheck;

int leafon; // the next leaf to be given to a thread to process

#ifndef __alpha
HANDLE	read_mutex;
HANDLE	save_mutex;
#endif

byte	*vismap, *vismap_p, *vismap_end;                 // past visfile
int	originalvismapsize;

byte *uncompressed; // [bitbytes*portalleafs]

unsigned int	bitbytes;                       // (portalleafs+63)>>3
unsigned int	bitlongs;

int numthreads = 1;

qboolean	fastvis;
qboolean	verbose;
qboolean	NoAmbient = false;              // Disable all ambient sounds
qboolean	NoAmbientLava = false;          // Disable ambient lava sound
qboolean	NoAmbientSky = false;           // Disable ambient sky sound
qboolean	NoAmbientSlime = false;         // Disable ambient slime sound
qboolean	NoAmbientWater = false;         // Disable ambient water sound
int			testlevel = 4;
int			visdist = 0;                    // Max vis distance, 0 = disabled
double		OnEpsilon = 0.1;

double	start = 0, end;
char	portalfile[ 1024 ];

int	startMS;
int	timeMS;
int	prevTimeMS;

#ifdef FEATURE_DETAIL
int	numrealleafs;
unsigned int	bitbytes_l; // (numrealleafs+63)>>3
int	*leafcluster; // leaf to cluster mappings as read from .prt file
#endif

FILE *logfile;

BOOL
WINAPI
HandlerRoutine( DWORD dwCtrlType ) {
	if( dwCtrlType == CTRL_C_EVENT ) {
		SAVE_LOCK;

		exit( 0 );
	}

	return FALSE;
}

void
logvprintf( char *fmt, va_list argptr ) {
	ShowPercent( NULL, 0, -1 );

	vprintf( fmt, argptr );
	fflush( stdout );
	vfprintf( logfile, fmt, argptr );
	fflush( logfile );
}

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

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

void
PrintFinish( void ) {
	double	SessionTime;
	time_t	StateTime;
	char	Str[ 20 ];

	if( start == 0 )
		return;

	MSecToStr( GetMilliSpan( startMS ), Str );

	logprintf( "\nElapsed time : %s\n", Str );

	if( StateLoaded ) {
		SessionTime = State.BaseTime;

		if( !fastvis )
			SessionTime += State.CalcTime;

		SecToStr((time_t) ( SessionTime + 0.5 ), Str, true );
		logprintf( "Session time : %s\n", Str );
	}

	if( !fastvis ) {
		StateTime = State.StateTime + 0.5;

		if( StateTime > 0 ) {
			SecToStr( StateTime, Str, true );
			logprintf( "State   time : %s\n", Str );
		}
	}

	fclose( logfile );
}

void
ErrorExit( void ) {
	PrintFinish();
	exit( 1 );
}

void
PlaneFromWinding( winding_t *w, plane_t *plane ) {
	vec3_t v1, v2;
	int i, bestI;
	vec_t minDot = 99999.0;
	vec_t curDot;

#if 1
	VectorSubtract( w->points[ 0 ], w->points[ 1 ], v2 );
	VectorNormalize( v2 );

	bestI = 2;

	for( i = 2; i < w->numpoints; ++i ) {
		VectorSubtract( w->points[ i ], w->points[ 1 ], v1 );
		VectorNormalize( v1 );

		curDot = fabs( DotProduct( v1, v2 ));

		if( curDot < minDot && VectorLength( v1 ) > ON_EPSILON ) {
			minDot = curDot;
			bestI = i;
		}
	}

	VectorSubtract( w->points[ 0 ], w->points[ 1 ], v2 );
	VectorSubtract( w->points[ bestI ], w->points[ 1 ], v1 );
#else
	// calc plane
	VectorSubtract( w->points[ 2 ], w->points[ 1 ], v1 );
	VectorSubtract( w->points[ 0 ], w->points[ 1 ], v2 );
#endif

	CrossProduct( v2, v1, plane->normal );
	VectorNormalize( plane->normal );

	plane->dist = DotProduct( w->points[ 0 ], plane->normal );
}

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

/*
==================
NewWinding
==================
*/
winding_t *
NewWinding( int points ) {
	winding_t *w;
	int size;

	if( points > MAX_POINTS_ON_WINDING )
		Error( "NewWinding: %i points", points );

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

	return w;
}

void
FreeWinding( winding_t *w ) {
	if( w && !w->original ) {
		free( w );
	}
}

/*
==================
CopyWinding
==================
*/
winding_t *
CopyWinding( winding_t *w ) {
	int size;
	winding_t *c;

	size = (int) ((winding_t *) 0 )->points[ w->numpoints ];
	c = malloc( size );
	memcpy( c, w, size );
	c->original = false;
	return c;
}

/*
==================
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[ 2 ];
	int			i, j;
	vec_t		*p1, *p2;
	vec3_t		mid;
	winding_t	*neww;
	int			maxpts;
	vec_t		dot;

	if( !in )
		return NULL;

	if( in->numpoints > MAX_POINTS_ON_WINDING ) {
		Error("Winding points exceed maximum point limit !\n");
	}

	counts[ 0 ] = counts[ 1 ] = 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;
			++counts[ SIDE_FRONT ];
			continue;
		}
		if( dot < -ON_EPSILON ) {
			sides[ i ] = SIDE_BACK;
			++counts[ SIDE_BACK ];
			continue;
		}

		sides[ i ] = SIDE_ON;
	}

	sides[ i ] = sides[ 0 ];
	dists[ i ] = dists[ 0 ];

	if( keepon && !counts[ SIDE_FRONT ] && !counts[ SIDE_BACK ] )
		return in;

	if( !counts[ SIDE_FRONT ] ) {
		FreeWinding( in );
		return NULL;
	}
	if( !counts[ SIDE_BACK ] )
		return in;

	//maxpts = in->numpoints + 4; // can't use counts[0]+2 because
	maxpts = ( in->numpoints - counts[SIDE_BACK] ) + 2;

	// 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
		if( in->numpoints == 4 )
			p2 = in->points[ ( i + 1 ) % 4 ]; // Speed hack
		else
			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.0 )
				mid[ j ] = split->dist;
			else if( split->normal[ j ] == -1.0 )
				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;
}

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

double		LoadTime = 0;    // Remember time of load
int			SaveTime = 60;   // Default 60 seconds
char		StateFileName[ 1024 ];
char		StateFileNameTmp[ 1024 ];
qboolean	NoSave = false;  // No AutoSave
qboolean	StateLoaded = false; // Indicates if state file is loaded

/*
==============
ZipData

Simple RLE format (1st byte is char, 2nd byte is # bytes (if 1st is 0 or FF)
==============
*/
int
ZipData( byte *Data, int Lth, byte *Zip ) {
	int i, j, k;

	for( i = j = 0; i < Lth && j < Lth; ++j ) {
		Zip[ j ] = Data[ i++ ]; // Character

		if( Zip[ j ] != 0 && Zip[ j ] != 0xFF )
			continue; // Only encode 0/FF chars

		k = i - 1;

		while( i < Lth && i - k < 255 && Data[ i ] == Zip[ j ] )
			++i;

		Zip[ ++j ] = i - k; // # bytes
	}

	if( j >= Lth ) {
		// No compression
		memcpy( Zip, Data, Lth );
		j = Lth;
	}

	return j;
}

/*
==============
UnZipData
==============
*/
int
UnZipData( byte *Zip, int Lth, byte *Data, int Max ) {
	int i, j, Size;

	if( Lth == Max ) {
		// No compression
		memcpy( Data, Zip, Max );
		return Max;
	}

	for( i = j = 0; i < Lth && j < Max; ++i, j += Size ) {
		Data[ j ] = Zip[ i ];

		if( Data[ j ] != 0 && Data[ j ] != 0xFF ) {
			Size = 1;
			continue; // Only decode 0/FF chars
		}

		Size = Zip[ ++i ];

		if( j + Size > Max || Size == 1 )
			continue;

		if( Size == 2 )
			Data[ j + 1 ] = Data[ j ]; // Speed
		else if( Size > 2 )
			memset( &Data[ j + 1 ], Data[ j ], Size - 1 );
	}

	if( j > Max )
		Error( "UnZipData: Overflow, %d > %d", j, Max );

	return j;
}

/*
==============
ZipWrite
==============
*/
void
ZipWrite( FILE *StateFile, byte *Data, byte *ZData ) {
	short ZLth;

	ZLth = ZipData( Data, bitbytes, ZData );

	SafeWrite( StateFile, &ZLth, sizeof( short ));
	SafeWrite( StateFile, ZData, ZLth );
}

/*
==============
UnZipRead
==============
*/
void
UnZipRead( FILE *StateFile, byte *ZData, byte **Data ) {
	short ZLth;

	*Data = malloc( bitbytes );

	SafeRead( StateFile, &ZLth, sizeof( short ));
	SafeRead( StateFile, ZData, ZLth );
	UnZipData( ZData, ZLth, *Data, bitbytes );
}

/*
==============
TruncCheck
==============
*/
void
TruncCheck( int nummightsee, int numcansee, byte *ZData, FILE *StateFile ) {
	// Check for truncation
	if( nummightsee > 65535 || numcansee > 65535 ) {
		free( ZData );
		fclose( StateFile );
		Error( "State file truncation error: nummightsee=%d, numcansee=%d", nummightsee, numcansee );
	}
}

/*
==============
SaveState
==============
*/
void
SaveState( char *source, int CurrPortal, qboolean Flush ) {
	FILE *StateFile;
	portal_t	*p;
	portals_t	ps;
	double		Time, SavedTime, SavingTime;
	int	i;
	byte	*ZData;
	static double PrevTime;

	SAVE_LOCK;

	if( source != NULL ) {
		// Init
		strcpy( StateFileName, source );
		StripExtension( StateFileName );
		DefaultExtension( StateFileName, ".vis" );
		strcpy( StateFileNameTmp, StateFileName );
		StripExtension( StateFileNameTmp );
		DefaultExtension( StateFileNameTmp, ".vi0" );

		memset( &State, 0, sizeof( state_t ));

		State.Version = STATE_VERSION;

		// Prevent prt+vis mismatch
		State.portalleafs = portalleafs;
		State.numportals = numportals;

		// Prevent bsp+vis mismatch
		State.numleafs = numleafs;

		PrevTime = I_FloatTime();

		SAVE_UNLOCK;

		return;
	}

	if( !Flush ) {
		State.CurrPortal = CurrPortal + 1;
	}

	if( NoSave ) {
		SAVE_UNLOCK;

		return;
	}

	Time = I_FloatTime();

	if( !Flush ) {
		if( Time < PrevTime + SaveTime ) {
			SAVE_UNLOCK;

			return;
		}
	}

	PrevTime = Time;

	State.PercRate = PercRate;
	State.HiResPercent = HiResPercent;
	State.visdist = visdist;
	State.c_chains = c_chains;

	// Tricky handling of session timing and testlevel
	if( !State.BaseDone || !State.CalcDone && Flush ) {
		// Inside or flushing Base
		SavedTime = State.BaseTime;
		State.BaseTime += Time - LoadTime;

		if( Flush )
			LoadTime = Time; // This is the new load time for Calc

		State.testlevel = -1; // testlevel not determined yet
	}
	else {
		// Inside or flushing Calc
		SavedTime = State.CalcTime;
		State.CalcTime += Time - LoadTime;

		State.testlevel = testlevel; // Save real testlevel during fullvis
	}

	StateFile = SafeOpenWrite( StateFileNameTmp );

	SafeWrite( StateFile, &State, sizeof( state_t ));

	// Restore timing values if inside Base/Calc
	if( !State.BaseDone )
		State.BaseTime = SavedTime;
	else if( !State.CalcDone && !Flush )
		State.CalcTime = SavedTime;

	ZData = malloc( bitbytes * 2 );

	for( i = 0, p = portals; i < 2 * numportals; ++i, ++p ) {
		ps.status = ( p->status == stat_working ) ? stat_none : p->status;
		ps.visbits = p->visbits != NULL;
		ps.mightsee = p->mightsee != NULL;

		TruncCheck( p->nummightsee, p->numcansee, ZData, StateFile );

		ps.nummightsee = p->nummightsee;
		ps.numcansee = p->numcansee;

		SafeWrite( StateFile, &ps, sizeof( portals_t ));

		if( ps.visbits )
			ZipWrite( StateFile, p->visbits, ZData );

		if( ps.mightsee )
			ZipWrite( StateFile, p->mightsee, ZData );
	}

	free( ZData );
	fclose( StateFile );

	remove( StateFileName );
	rename( StateFileNameTmp, StateFileName );

	SavingTime = I_FloatTime() - Time;
	State.StateTime += SavingTime;

	// logprintf ("State save: %.2lf\n", SavingTime);

	SAVE_UNLOCK;
}

/*
==============
LoadState
==============
*/
void
LoadState( void ) {
	FILE *StateFile;
	portal_t	*p;
	portals_t	ps;
	portals0_t	ps0;
	double		LoadingTime;
	long	PrtTime, VisTime;
	int	i;
	byte	*ZData;

	if( NoSave )
		return;

	LoadTime = I_FloatTime();

	if( AccessFile( StateFileName, 0 ) != 0 ) {
		if( AccessFile( StateFileNameTmp, 0 ) != 0 )
			return; // No state files

		rename( StateFileNameTmp, StateFileName );
	}

	// Check if PRT file changed since last state file
	GetFTime( portalfile, &PrtTime );
	GetFTime( StateFileName, &VisTime );

	if( PrtTime > VisTime ) {
		// State file out of date
		logprintf( "State file out of date, will be overwritten\n" );
		return;
	}

	logprintf( "Loading state file: %s\n", StateFileName );

	StateFile = SafeOpenRead( StateFileName );

	SafeRead( StateFile, &State, sizeof( state_t ));

	// Sanity checks
	if( State.Version != STATE_VERSION && State.Version != 0 ) {
		fclose( StateFile );
		Error( "State file version (%d) does not match vis version (%d)", State.Version, STATE_VERSION );
	}

	if( State.numportals != numportals || State.portalleafs != portalleafs ) {
		fclose( StateFile );
		Error( "State file does not match portal file" );
	}

	if( State.numleafs == 0 )
		State.numleafs = numleafs; // State file created with an earlier vis version than 2.27

	if( State.numleafs != numleafs )
		logprintf( "WARNING: State file might not match bsp file\n" );

	if( AutoRate ) {
		PercRate = State.PercRate;
		HiResPercent = State.HiResPercent;
	}

	visdist = State.visdist;
	c_chains = State.c_chains;

	if( !fastvis && State.Version > 0 && State.testlevel >= 0 )
		testlevel = State.testlevel; // Only load if fullvis, correct version and testlevel is saved

	ZData = malloc( bitbytes * 2 );

	for( i = 0, p = portals; i < 2 * numportals; ++i, ++p ) {
		if( State.Version == 0 ) {
			// Compatibility
			SafeRead( StateFile, &ps0, sizeof( portals0_t ));

			ps.status = ps0.status;
			ps.visbits = ps0.visbits;
			ps.mightsee = ps0.mightsee;

			TruncCheck( ps0.nummightsee, ps0.numcansee, ZData, StateFile );

			ps.nummightsee = ps0.nummightsee;
			ps.numcansee = ps0.numcansee;
		}
		else
			SafeRead( StateFile, &ps, sizeof( portals_t ));

		p->status = ( ps.status == stat_working ) ? stat_none : ps.status;
		p->nummightsee = ps.nummightsee;
		p->numcansee = ps.numcansee;

		if( ps.visbits )
			UnZipRead( StateFile, ZData, &p->visbits );

		if( ps.mightsee )
			UnZipRead( StateFile, ZData, &p->mightsee );
	}

	free( ZData );
	fclose( StateFile );

	State.Version = STATE_VERSION;
	StateLoaded = true;

	LoadingTime = I_FloatTime() - LoadTime;
	State.StateTime += LoadingTime;

	// logprintf ("State load: %.2lf\n", LoadingTime);
}

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

/*
=============
GetNextPortal

Returns the next portal for a thread to work on
Returns the portals from the least complex, so the later ones can reuse
the earlier information.
=============
*/
portal_t *
GetNextPortal( void ) {
	int j;
	portal_t *p, *tp;
	int min;

	READ_LOCK;

	min = 999999;
	p = NULL;

	for( j = 0, tp = portals; j < numportals * 2; j++, tp++ ) {
		if( tp->nummightsee < min && tp->status == stat_none ) {
			min = tp->nummightsee;
			p = tp;
		}
	}

	if( p )
		p->status = stat_working;

	READ_UNLOCK;

	return p;
}

/*
==============
LeafThread
==============
*/
#ifndef __alpha
DWORD WINAPI
LeafThread( LPVOID lpParam )

// HANDLE LeafThread (HANDLE thread)
#else
void *LeafThread( int thread )
#endif
{
	portal_t *p;

	TimeOffset = State.CalcTime;

	if( !State.CalcDone ) {
		do {
			p = GetNextPortal();
			if( !p )
				break;

			ShowPercent( NULL, State.CurrPortal + 1, numportals * 2 );

			if( verbose )
				logprintf( "portal %i %s might see %i ", (int) ( p - portals ), GetCoord( &leafs[ p->leaf ] ), p->nummightsee );

			PortalFlow( p );

			if( verbose )
				logprintf( " can see %i\n", p->numcansee );

			SaveState( NULL, State.CurrPortal, false );
		} while( 1 );
	}

	if( !SimpPercent )
		printf( "\n" );

	printf( "\n" );

	#ifndef __alpha
	return 0;

	#else
	return NULL;

	#endif
}

/*
===============
CompressRow

===============
*/
int
CompressRow( byte *vis, byte *dest ) {
	int	j;
	int	rep;
	int	visrow;
	byte	*dest_p;

	dest_p = dest;

	#ifdef FEATURE_DETAIL
	visrow = ( numrealleafs + 7 ) >> 3;
	#else
	visrow = ( portalleafs + 7 ) >> 3;
	#endif

	for( j = 0; j < visrow; j++ ) {
		*dest_p++ = vis[ j ];
		if( vis[ j ] )
			continue;

		rep = 1;
		for( j++; j < visrow; j++ )
			if( vis[ j ] || rep == 255 )
				break;
			else
				rep++;


		*dest_p++ = rep;
		j--;
	}

	return dest_p - dest;
}

/*
===============
GetCoord

Returns one of the coordinates for leaf
===============
*/
char *
GetCoord( leaf_t *leaf ) {
	vec_t *p1;
	static char Str[ 50 ];

	Str[ 0 ] = '\0';

	if( leaf->numportals != 0 ) {
		p1 = leaf->portals[ 0 ]->winding->points[ 0 ];
		sprintf( Str, " near (%.0f %.0f %.0f)", p1[ 0 ], p1[ 1 ], p1[ 2 ] );
	}

	return Str;
}

#ifdef FEATURE_DETAIL
void
ClusterFlowExpand( byte *src, byte *dest ) {
	int i, j;

	// byte *odest=dest;

	for( j = 1, i = 0; i < numrealleafs; i++ ) {
		if( src[ leafcluster[ i ] >> 3 ] & ( 1 << ( leafcluster[ i ] & 7 ))) {
			// if (cn==27) printf(" %i %i (%i,%i)\n",i,leafcluster[i],j,dest-odest);
			*dest |= j;
		}

		j <<= 1;
		if( j == 256 ) j = 1, dest++;
	}
}

#endif

/*
===============
LeafFlow

Builds the entire visibility list for a leaf
===============
*/
int totalvis, maxvis, maxleaf, maxcnt;

void
LeafFlow( int leafnum ) {
	leaf_t		*leaf;
	byte		*outbuffer;
	byte		compressed[ MAX_MAP_LEAFS / 8 ];
	int		i, j;
	int		numvis;
	byte		*dest;
	portal_t	*p;

	//
	// flow through all portals, collecting visible bits
	//

	#ifdef FEATURE_DETAIL
	byte outbuffer2[ MAX_MAP_LEAFS ];

	outbuffer = uncompressed + leafnum * bitbytes_l;
	#else
	outbuffer = uncompressed + leafnum * bitbytes;
	#endif

	// do we have to use the compressed thing here actually for detail mode ?
	#ifdef FEATURE_DETAIL
	memset( outbuffer2, 0, sizeof( outbuffer2 ));
	#endif

	leaf = &leafs[ leafnum ];

	for( i = 0; i < leaf->numportals; i++ ) {
		p = leaf->portals[ i ];
		if( p->status != stat_done )
			Error( "portal not done" );
		for( j = 0; j < bitbytes; j++ ) {
			#ifdef FEATURE_DETAIL
			outbuffer2[ j ] |= p->visbits[ j ];
			#else
			outbuffer[ j ] |= p->visbits[ j ];
			#endif
		}
	}

	#ifdef FEATURE_DETAIL
	if( outbuffer2[ leafnum >> 3 ] & ( 1 << ( leafnum & 7 )))
	#else
	if( outbuffer[ leafnum >> 3 ] & ( 1 << ( leafnum & 7 )))
	#endif
		logprintf( "WARNING: Leaf portals saw into leaf%s\n", GetCoord( leaf ));

	#ifdef FEATURE_DETAIL
	outbuffer2[ leafnum >> 3 ] |= ( 1 << ( leafnum & 7 ));
	#else
	outbuffer[ leafnum >> 3 ] |= ( 1 << ( leafnum & 7 ));
	#endif

	numvis = 0;
	for( i = 0; i < portalleafs; i++ )
	#ifdef FEATURE_DETAIL
		if( outbuffer2[ i >> 3 ] & ( 1 << ( i & 3 )))
	#else
		if( outbuffer[ i >> 3 ] & ( 1 << ( i & 3 )))
		#endif
			numvis++;


	#ifdef FEATURE_DETAIL

	//
	// expand to cluster->leaf PVS
	//
	ClusterFlowExpand( outbuffer2, outbuffer );
	#endif

	//
	// compress the bit string
	//
	if( verbose )
		logprintf( "leaf %5i : %5i visible%s\n", leafnum, numvis, GetCoord( leaf ));

	totalvis += numvis;

	if( numvis > maxvis ) {
		maxleaf = leafnum;
		maxvis = numvis;
		maxcnt = 1;
	}
	else if( numvis == maxvis )
		++maxcnt;

	#if 0
	i = ( portalleafs + 7 ) >> 3;
	memcpy( compressed, outbuffer, i );
	#else
	i = CompressRow( outbuffer, compressed );
	#endif

	dest = vismap_p;
	vismap_p += i;

	if( vismap_p > vismap_end )
		Error( "Vismap expansion overflow, max = %s", PrtSize( visdatasize ));

	#ifdef FEATURE_DETAIL
	leaf->visofs = dest - vismap;
	#else
	dleafs[ leafnum + 1 ].visofs = dest - vismap; // leaf 0 is a common solid
	#endif

	memcpy( dest, compressed, i );
}

/*
==================
CalcPortalVis
==================
*/
void
CalcPortalVis( void ) {
	int i;

	// fastvis just uses mightsee for a very loose bound
	if( fastvis ) {
		for( i = 0; i < numportals * 2; i++ ) {
			portals[ i ].visbits = portals[ i ].mightsee;
			portals[ i ].status = stat_done;
		}
		return;
	}

	leafon = 0;

	#ifndef __alpha
	{
		HANDLE	work_threads[ MAX_THREADS ];
		int	i;

		read_mutex = CreateMutex( NULL, FALSE, NULL );
		save_mutex = CreateMutex( NULL, FALSE, NULL );

		ShowPercent( "Full", 0, 0 );

		for( i = 0; i < numthreads; i++ ) {
			work_threads[ i ] = CreateThread( NULL, 0, LeafThread, NULL, 0, (LPDWORD) &work_threads[ i ] );
		}

		WaitForMultipleObjects( numthreads, work_threads, TRUE, INFINITE );

		ShowPercent( NULL, 100, 100 );

		CloseHandle( read_mutex );
		CloseHandle( save_mutex );
	}
	#else
	LeafThread( 0 );
	#endif

	if( verbose ) {
		logprintf( "portalcheck: %i  portaltest: %i  portalpass: %i\n", c_portalcheck, c_portaltest, c_portalpass );
		logprintf( "c_vistest: %i  c_mighttest: %i\n", c_vistest, c_mighttest );
	}
}

/*
==================
CalcVis
==================
*/
void
CalcVis( void ) {
	int i;

	if( NewLine )
		fprintf( logfile, "\n" );
	else
		logprintf( "\n" );

	BasePortalVis();

	if( !State.BaseDone ) {
		State.BaseDone = true;
		State.CurrPortal = 0;
		SaveState( NULL, 0, true );
	}

	CalcPortalVis();

	if( !fastvis && !State.CalcDone ) {
		State.CalcDone = true;
		SaveState( NULL, 0, true );
	}

	//
	// assemble the leaf vis lists by oring and compressing the portal lists
	//
	for( i = 0; i < portalleafs; i++ )
		LeafFlow( i );

	#ifdef FEATURE_DETAIL
#ifdef FEATURE_BSP2
	if( isBSP2 ) {
		for( i = 0; i < numrealleafs; i++ ) {
			dleafsBSP2[ i + 1 ].visofs = leafs[ leafcluster[ i ] ].visofs;
		}
	} else {
		for( i = 0; i < numrealleafs; i++ ) {
			dleafs[ i + 1 ].visofs = leafs[ leafcluster[ i ] ].visofs;
		}
	}
#else
	for( i = 0; i < numrealleafs; i++ ) {
		dleafs[ i + 1 ].visofs = leafs[ leafcluster[ i ] ].visofs;

		// printf("leaf %4i: ofs %8i\n",i+1,dleafs[i+1].visofs);
	}
#endif
	#endif

	logprintf( "\naverage leafs visible: %i\n", totalvis / portalleafs );
	logprintf( "max leafs visible: %i%s", maxvis, GetCoord( &leafs[ maxleaf ] ));

	if( maxcnt > 1 )
		logprintf( ", 1/%i", maxcnt );

	logprintf( "\n" );
}

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

/*
===============
AddPortalToLeaf
===============
*/
void
AddPortalToLeaf( leaf_t *l, portal_t *p ) {
	portal_t **Tmp;

	Tmp = malloc(( l->numportals + 1 ) * sizeof( portal_t * ));

	if( l->numportals != 0 ) {
		memcpy( Tmp, l->portals, l->numportals * sizeof( portal_t * ));
		free( l->portals );
	}

	l->portals = Tmp;
	l->portals[ l->numportals ] = p;
	l->numportals++;
}

/*
============
LoadPortals
============
*/
void
LoadPortals( char *name ) {
	int i, j;
	portal_t	*p;
	leaf_t		*l;
	char		magic[ 80 ];
	FILE		*f;
	int numpoints;
	winding_t *w;
	int	leafnums[ 2 ];
	plane_t plane;
	#ifdef FEATURE_DETAIL
	int portalType; // 0 : standard, 1 : extended ( detail-brushes )
	#endif

	if( !strcmp( name, "-" ))
		f = stdin;
	else {
		f = fopen( name, "r" );
		if( !f ) {
			logprintf( "LoadPortals: couldn't read %s\n", name );
			logprintf( "No vising performed.\n" );
			exit( 1 );
		}
	}

	#ifdef FEATURE_DETAIL

	// if( fscanf( f, "%79s\n%i\n%i\n%i\n", magic, &portalleafs, &numportals, &numrealleafs ) != 4 )
	if( fscanf( f, "%79s\n%i\n%i\n", magic, &portalleafs, &numportals ) != 3 )
		Error( "LoadPortals: failed to read header" );

	if( !strcmp( magic, "PRT1" )) {
		portalType = 0;
		numrealleafs = portalleafs;
	}
	else if( !strcmp( magic, "PRT1-AM" )) {
		portalType = 1;

		if( fscanf( f, "%i\n", &numrealleafs ) != 1 )
			Error( "LoadPortals: failed to read realleafs" );
	}
	else
		Error( "LoadPortals: not a portal file" );

	#else
	if( fscanf( f, "%79s\n%i\n%i\n", magic, &portalleafs, &numportals ) != 3 )
		Error( "LoadPortals: failed to read header" );
	if( strcmp( magic, PORTALFILE ))
		Error( "LoadPortals: not a portal file" );

	if( portalleafs >= numleafs )
		Error( "LoadPortals: portalleafs %i exceed bsp leafs %i", portalleafs, numleafs );
	#endif

	logprintf( "%5i portalleafs\n", portalleafs );
	logprintf( "%5i numportals\n", numportals );
	#ifdef FEATURE_DETAIL
	logprintf( "%5i numrealleafs\n", numrealleafs );
	#endif

	bitbytes = (( portalleafs + 63 ) & ~63 ) >> 3;
	#ifdef FEATURE_DETAIL
	bitbytes_l = (( numrealleafs + 63 ) & ~63 ) >> 3;
	#endif
	bitlongs = bitbytes / sizeof( long );

	// each file portal is split into two memory portals
	portals = malloc( 2 * numportals * sizeof( portal_t ));
	memset( portals, 0, 2 * numportals * sizeof( portal_t ));

	leafs = malloc( portalleafs * sizeof( leaf_t ));
	memset( leafs, 0, portalleafs * sizeof( leaf_t ));

	#ifdef FEATURE_DETAIL
	originalvismapsize = numrealleafs * (( numrealleafs + 7 ) / 8 );
	#else
	originalvismapsize = portalleafs * (( portalleafs + 7 ) / 8 );
	#endif

	// Allocate new visdata. Normally less than originalvismapsize is required
	// due to compression, but sometimes about 10-20% more is needed. 50% more
	// should be more than enough
	if( dvisdata != NULL )
		free( dvisdata );

	//visdatasize = originalvismapsize * 3 / 2;
	visdatasize = originalvismapsize * 3;

	if( visdatasize < 1024 )
		visdatasize = 1024; // Just to be safe

	dvisdata = malloc( visdatasize );

	vismap = vismap_p = dvisdata;
	vismap_end = vismap + visdatasize;

	for( i = 0, p = portals; i < numportals; i++ ) {
		if( fscanf( f, "%i %i %i ", &numpoints, &leafnums[ 0 ], &leafnums[ 1 ] ) != 3 )
			Error( "LoadPortals: insufficient portal header %i", i );

		if( numpoints > MAX_POINTS_ON_WINDING )
			Error( "LoadPortals: portal %i has too many points", i );

		if((unsigned) leafnums[ 0 ] >= portalleafs || (unsigned) leafnums[ 1 ] >= portalleafs )
			Error( "LoadPortals: leaf out of bounds in portal %i", i );

		w = NewWinding( numpoints );
		w->original = true;
		w->numpoints = numpoints;

		for( j = 0; j < numpoints; j++ ) {
			double	v[ 3 ];
			int	k;

			// scanf into double, then assign to vec_t
			/*
			if( fscanf( f, "(%lf %lf %lf ) ", &v[ 0 ], &v[ 1 ], &v[ 2 ] ) != 3 )
				Error( "LoadPortals: insufficient point %i in portal %i", j, i );
			*/
			if( fscanf( f, "(%Lf %Lf %Lf ) ", &v[ 0 ], &v[ 1 ], &v[ 2 ] ) != 3 )
				Error( "LoadPortals: insufficient point %i in portal %i", j, i );

			for( k = 0; k < 3; k++ )
				w->points[ j ][ k ] = v[ k ];
		}
		fscanf( f, "\n" );

		// calc plane
		PlaneFromWinding( w, &plane );

		// create forward portal
		AddPortalToLeaf( &leafs[ leafnums[ 0 ] ], p );

		p->winding = w;
		VectorNegate( plane.normal, p->plane.normal );
		p->plane.dist = -plane.dist;
		p->leaf = leafnums[ 1 ];
		p++;

		// create backwards portal
		AddPortalToLeaf( &leafs[ leafnums[ 1 ] ], p );

		p->winding = w;
		p->plane = plane;
		p->leaf = leafnums[ 0 ];
		p++;
	}

	#ifdef FEATURE_DETAIL
	leafcluster = malloc( sizeof( int ) * numrealleafs );
	memset( leafcluster, 0, sizeof( int ) * numrealleafs );

	if( portalType == 0 ) {
		for( i = 0; i < numrealleafs; i++ ) {
			leafcluster[ i ] = i;
		}
	}
	else {
		for( i = 0; i < numrealleafs; i++ ) {
			if( fscanf( f, "%i\n", &leafcluster[ i ] ) != 1 )
				Error( "LoadPortals: parse error in leaf->cluster mappings" );
		}
	}
	#endif

	if( !feof( f ))
		Error( "LoadPortals: missing EOF after portal %i", i - 1 );

	fclose( f );
}

/*
==============
PrintOptions
==============
*/
void
PrintOptions( void ) {
	logprintf( "Vis performs visual optimization of Quake .BSP files\n\n" );
	logprintf( "vis [options] bspfile\n\n" );
	logprintf( "Options:\n" );
	logprintf( "   -level [n]      Full vis level 0-4, higher is better (default 4)\n" );
	logprintf( "   -fast           Fast vis (less accurate)\n" );
	logprintf( "   -visdist [n]    Set maximum viewing distance (default 0 = disabled)\n" );
	logprintf( "   -nosave         Disable AutoSave feature\n" );
	logprintf( "   -savetime [n]   Set AutoSave interval in seconds (default 60)\n" );
	logprintf( "   -priority [n]   Set thread priority 0-2 (below/normal/above, default 1)\n" );
	logprintf( "   -noambient      Disable all ambient sounds\n" );
	logprintf( "   -noambientlava  Disable ambient lava\n" );
	logprintf( "   -noambientsky   Disable ambient sky\n" );
	logprintf( "   -noambientslime Disable ambient slime\n" );
	logprintf( "   -noambientwater Disable ambient water\n" );
	logprintf( "   -verbose        Print out more detailed information\n" );
	logprintf( "   -rate [s,p,l,t] Control extended progress update rate and format,\n" );
	logprintf( "                   s=seconds, p=percent, l=line, t=total (default 10,1.0,1,1)\n" );
	logprintf( "   -barpercent     Simplified and weighted bargraph progress information\n" );
	logprintf( "   -numpercent     Simplified and weighted numerical progress information\n" );
	logprintf( "   -oldhformat     Enable hour format HH:MM:SS instead of HHh MMm\n" );
	logprintf( "   -threads [n]    Overrides the default number of threads\n" );
	logprintf( "                   (defaults to the number of processors/cores in your machine)\n" );
	logprintf( "   bspfile         .BSP file to process\n" );

	fclose( logfile );

	exit( 1 );
}

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

#define XT_BUILD_ID "1.2.4a"

int
main( int argc, char **argv ) {
	char	source[ 1024 ], *Option, *NextOption;
	int	i;
	float	FPercRate;

	#ifndef __alpha

	SYSTEM_INFO sysinfo;
	GetSystemInfo( &sysinfo );

	numthreads = sysinfo.dwNumberOfProcessors;

	#endif

	SetConsoleCtrlHandler( HandlerRoutine, TRUE );

	logfile = fopen( LOGFILENAME, "w" );
	logprintf( "---- WVis 2.31 ( XT build %s ) ----\n", XT_BUILD_ID );
	logprintf( "  Modified by Bengt Jardrup\n" );
	logprintf( "  Multithreading enabled by Willem\n" );
	logprintf( "  Further mods by Tuna, rebb\n" );
	logprintf( "  Detail code by Alexander Malmberg <alexander@malmberg.org>\n\n" );

	for( i = 1; i < argc; i++ ) {
		Option = argv[ i ];
		NextOption = i + 1 < argc ? argv[ i + 1 ] : NULL;

		if( Option[ 0 ] != '-' )
			break;

		++Option;

		if( !stricmp( Option, "fast" ))
			fastvis = true;
		else if( !stricmp( Option, "level" )) {
			ChkArgument( Option, NextOption );
			testlevel = atoi( NextOption );

			if( testlevel < 0 || testlevel > 4 )
				Error( "Valid levels are 0-4" );

			i++;
		}
		else if( !stricmp( Option, "visdist" )) {
			ChkArgument( Option, NextOption );
			visdist = atoi( NextOption );
			i++;
		}
		else if( !stricmp( Option, "onepsilon" )) {
			ChkArgument( Option, NextOption );
			OnEpsilon = atof( NextOption );
			i++;
		}
		else if( !stricmp( Option, "savetime" )) {
			ChkArgument( Option, NextOption );
			SaveTime = atoi( NextOption );

			if( SaveTime <= 0 )
				Error( "Save time must be > 0" );

			i++;
		}
		else if( !stricmp( Option, "nosave" ))
			NoSave = true;
		else if( !stricmp( Option, "priority" )) {
			SetQPriority( GetArgument( Option, NextOption ));
			i++;
		}
		else if( !stricmp( Option, "noambient" ))
			NoAmbient = true;
		else if( !stricmp( Option, "noambientlava" ))
			NoAmbientLava = true;
		else if( !stricmp( Option, "noambientsky" ))
			NoAmbientSky = true;
		else if( !stricmp( Option, "noambientslime" ))
			NoAmbientSlime = true;
		else if( !stricmp( Option, "noambientwater" ))
			NoAmbientWater = true;
		else if( !stricmp( Option, "v" ) || !stricmp( Option, "verbose" )) {
			logprintf( "verbose = true\n" );
			verbose = true;
		}
		else if( !stricmp( Option, "rate" )) {
			ChkArgument( Option, NextOption );

			if( sscanf( NextOption, "%d,%f,%d,%d", &SecRate, &FPercRate, &NewLine, &TotTime ) > 1 )
				PercRate = ( FPercRate + 0.05 ) * 10; // Fix roundoff

			AutoRate = false;
			i++;
		}
		else if( !stricmp( Option, "barpercent" )) {
			SimpPercent = true;
			NumPercent = false;
		}
		else if( !stricmp( Option, "numpercent" )) {
			SimpPercent = true;
			NumPercent = true;
		}
		else if( !stricmp( Option, "oldhformat" ))
			OldHFormat = true;
		else if( !stricmp( Option, "threads" )) {
			ChkArgument( Option, NextOption );

			numthreads = atoi( NextOption );

			if( numthreads < 1 ) {
				numthreads = 1;
			}
			if( numthreads > MAX_THREADS ) {
				numthreads = MAX_THREADS;
			}

			i++;
		}
		else if( !stricmp( Option, "?" ) || !stricmp( Option, "help" ))
			PrintOptions();
		else
			Error( "Unknown option '%s'", Option );
	}

	if( i != argc - 1 )
		PrintOptions();

	start = I_FloatTime();
	startMS = GetMilliCount();

	strcpy( source, argv[ i ] );
	StripExtension( source );
	DefaultExtension( source, ".bsp" );

	WriteChk( source );

	logprintf( "File: %s\n", source );

	LoadBSPFile( source ); // , true);

	strcpy( portalfile, argv[ i ] );
	StripExtension( portalfile );
	strcat( portalfile, ".prt" );

	LoadPortals( portalfile );

	#ifdef FEATURE_DETAIL
	uncompressed = malloc( bitbytes_l * portalleafs );
	memset( uncompressed, 0, bitbytes_l * portalleafs );
	#else
	uncompressed = malloc( bitbytes * portalleafs );
	memset( uncompressed, 0, bitbytes * portalleafs );
	#endif

	SaveState( source, 0, false );
	LoadState();

	if( fastvis )
		logprintf( "fastvis = true" );
	else
		logprintf( "testlevel = %i", testlevel );

	logprintf( "\n" );

	if( visdist > 0 )
		logprintf( "visdist = %i\n", visdist );

	if( NoAmbient )
		logprintf( "all ambient sounds disabled\n" );
	else {
		if( NoAmbientLava )
			logprintf( "ambient lava disabled\n" );

		if( NoAmbientSky )
			logprintf( "ambient sky disabled\n" );

		if( NoAmbientSlime )
			logprintf( "ambient slime disabled\n" );

		if( NoAmbientWater )
			logprintf( "ambient water disabled\n" );
	}

	#ifndef __alpha
	if( numthreads > 1 ) {
		logprintf( "\nUsing %d threads.\n", numthreads );
	}
	else {
		logprintf( "\nUsing %d thread.\n", numthreads );
	}
	#endif

	CalcVis();

	if( !fastvis )
		logprintf( "c_chains: %lu\n", c_chains );

	visdatasize = vismap_p - dvisdata;
	logprintf( "visdatasize: %s compressed from %s\n", PrtSize( visdatasize ), PrtSize( originalvismapsize ));

#ifdef FEATURE_BSP2
	if( isBSP2 ) {
		CalcAmbientSoundsBSP2();
		WriteBSP2File( source );
	} else {
		CalcAmbientSounds();
		WriteBSPFile( source );
	}
#else
	CalcAmbientSounds();
	WriteBSPFile( source );
#endif

	PrintFinish();

	return 0;
}

