/***
*
*	Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*	
*	This product contains software technology licensed from Id 
*	Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc. 
*	All Rights Reserved.
*
****/

// qrad.c

#include "qrad.h"


/*

NOTES
-----

every surface must be divided into at least two patches each axis

*/

patch_t		*face_patches[MAX_MAP_FACES];
entity_t	*face_entity[MAX_MAP_FACES];
patch_t		patches[MAX_PATCHES];
unsigned	num_patches;
vec3_t		emitlight[MAX_PATCHES];
vec3_t		addlight[MAX_PATCHES];
vec3_t		face_offset[MAX_MAP_FACES];		// for rotating bmodels
dplane_t	backplanes[MAX_MAP_PLANES];

unsigned	numbounce = 1; // 3; /* Originally this was 8 */

float		maxchop = 64;
float		minchop = 64;
qboolean	dumppatches;

int TestLine (vec3_t start, vec3_t stop);

int			junk;

vec3_t		ambient = { 0, 0, 0 };
float		maxlight = 256; // 196  /* Originally this was 196 */

float		lightscale = 1.0;
float		dlight_threshold = 25.0;  // was DIRECT_LIGHT constant

char		source[MAX_PATH] = "";

char		global_lights[MAX_PATH] = "";
char		designer_lights[MAX_PATH] = "";
char		level_lights[MAX_PATH] = "";

char		transferfile[MAX_PATH] = "";
char		vismatfile[_MAX_PATH] = "";
char		incrementfile[_MAX_PATH] = "";
qboolean	incremental = 0;
float		gamma = 0.5;
float		indirect_sun = 1.0;
qboolean	extra = false;
float		smoothing_threshold = 0; // default: cos(45.0*(Q_PI/180)); 
// Cosine of smoothing angle(in radians)
float		coring = 1.0;	// Light threshold to force to blackness(minimizes lightmaps)
qboolean	texscale = true;

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

MISC

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


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

	for (i=0 ; i<numplanes ; i++)
	{
		backplanes[i].dist = -dplanes[i].dist;
		VectorSubtract (vec3_origin, dplanes[i].normal, backplanes[i].normal);
	}
}

int		leafparents[MAX_MAP_LEAFS];
int		nodeparents[MAX_MAP_NODES];

/*
=============
MakeParents
=============
*/
void MakeParents (int nodenum, int parent)
{
	int		i, j;
	dnode_t	*node;

	nodeparents[nodenum] = parent;
	node = dnodes+nodenum;

	for (i=0 ; i<2 ; i++)
	{
		j = node->children[i];
		if (j < 0)
			leafparents[-j - 1] = nodenum;
		else
			MakeParents (j, nodenum);
	}
}


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

  TEXTURE LIGHT VALUES

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

typedef struct
{
	char	name[256];
	vec3_t	value;
	char	*filename;
} texlight_t;

#define	MAX_TEXLIGHTS	128

texlight_t	texlights[MAX_TEXLIGHTS];
int			num_texlights;

/*
============
ReadLightFile
============
*/
void ReadLightFile (char *filename)
{
	FILE	*f;
	char	scan[128];
	short	argCnt;
	vec_t	intensity;
	int		i = 1.0, j, file_texlights = 0;

	f = fopen (filename, "r");
	if (!f)
		Error ("ERROR: Couldn't open texlight file %s", filename);
	else
		printf("[Reading texlights from '%s']\n", filename);

	while ( fgets(scan, sizeof(scan), f) )
	{
		char	szTexlight[256];
		vec_t	r, g, b, i = 1;
		if (num_texlights == MAX_TEXLIGHTS)
			Error ("MAX_TEXLIGHTS");

		argCnt = sscanf (scan, "%s %f %f %f %f",szTexlight, &r, &g, &b, &i );
		
		if( argCnt == 2 )
		{
			// With 1+1 args, the R,G,B values are all equal to the first value
			g = b = r;
		}
		else if ( argCnt == 5 )
		{
			// With 1+4 args, the R,G,B values are "scaled" by the fourth numeric value i;
			r *= i / 255.0;
			g *= i / 255.0;
			b *= i / 255.0;
		}
		else if( argCnt != 4 )
		{
			if (strlen( scan ) > 4)
				printf("ignoring bad texlight '%s' in %s", scan, filename );
			continue;
		}

		for( j=0; j<num_texlights; j++ )
		{
			if ( strcmp( texlights[j].name, szTexlight ) == 0 )
			{
				if ( strcmp(texlights[j].filename, filename ) == 0 )
				{
					printf( "ERROR\a: Duplication of '%s' in file '%s'!\n",
							texlights[j].name, texlights[j].filename );
				} 
				else if ( texlights[j].value[0] != r
				  || texlights[j].value[1] != g
				  || texlights[j].value[2] != b )
				{
					printf( "Warning: Overriding '%s' from '%s' with '%s'!\n", 
							texlights[j].name, texlights[j].filename, filename );
				}
				else
				{
					printf( "Warning: Redundant '%s' def in '%s' AND '%s'!\n", 
							texlights[j].name, texlights[j].filename, filename );
				}
				break;
			}
		}
		strcpy( texlights[j].name, szTexlight );
		texlights[j].value[0] = r;
		texlights[j].value[1] = g;
		texlights[j].value[2] = b;
		texlights[j].filename = filename;
		file_texlights++;

		num_texlights = max( num_texlights, j+1 );
	}		
	qprintf ("[%i texlights parsed from '%s']\n\n", file_texlights, filename);
}


/*
============
LightForTexture
============
*/
void LightForTexture( char *name, vec3_t result )
{
	int		i;

	result[ 0 ] = result[ 1 ] = result[ 2 ] = 0;

	for (i=0 ; i<num_texlights ; i++)
	{
		if (!Q_strcasecmp (name, texlights[i].name))
		{
			VectorCopy( texlights[i].value, result );
			return;
		}
	}
}

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

MAKE FACES

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

/*
=============
WindingFromFace
=============
*/
winding_t	*WindingFromFace (dface_t *f)
{
	int			i;
	int			se;
	dvertex_t	*dv;
	int			v;
	winding_t	*w;

	w = AllocWinding (f->numedges);
	w->numpoints = f->numedges;

	for (i=0 ; i<f->numedges ; i++)
	{
		se = dsurfedges[f->firstedge + i];
		if (se < 0)
			v = dedges[-se].v[1];
		else
			v = dedges[se].v[0];

		dv = &dvertexes[v];
		VectorCopy (dv->point, w->p[i]);
	}

	RemoveColinearPoints (w);

	return w;
}

/*
=============
BaseLightForFace
=============
*/
void BaseLightForFace( dface_t *f, vec3_t light, vec3_t reflectivity )
{
	texinfo_t	*tx;
	miptex_t	*mt;
	int			ofs;

	long		sum[3];
	long		samples = 0;
	int			x, y, i;

	//
	// check for light emited by texture
	//
	tx = &texinfo[f->texinfo];

	ofs = ((dmiptexlump_t *)dtexdata)->dataofs[tx->miptex];
	mt = (miptex_t *) ( (byte *)dtexdata + ofs);

	LightForTexture (mt->name, light);

#ifdef TEXTURE_REFLECTIVITY
	// Average up the texture pixels' color for an average reflectivity
	for ( x = 0; x < ; x++ )
		for ( y = 0; y < ; y++ )
			{
			samples++;
			for(i=0; i < 3; i++)
				sum[i] += mt[][x][y][i] // FIXME later
			}
	for(i=0; i < 3; i++)
		reflectivity[i] = samples ? (BYTE)(sum[i] / samples) : 0;
#endif
}

/*
=============
IsSky
=============
*/
qboolean IsSky (dface_t *f)
{
	texinfo_t	*tx;
	miptex_t	*mt;
	int			ofs;

	tx = &texinfo[f->texinfo];
	ofs = ((dmiptexlump_t *)dtexdata)->dataofs[tx->miptex];
	mt = (miptex_t *) ( (byte *)dtexdata + ofs);
	if (!strncmp (mt->name, "sky", 3) )
		return true;
	if (!strncmp (mt->name, "SKY", 3) )
		return true;
	return false;
}

/*
=============
MakePatchForFace
=============
*/
float	totalarea;
void MakePatchForFace (int fn, winding_t *w)
{
	dface_t *f = dfaces + fn;

	// No patches at all for the sky!
	if ( !IsSky(f) )
	{
		float	area;
		patch_t		*patch;
		vec3_t light;
		vec3_t		centroid = {0,0,0};
		int			i, j;
		texinfo_t	*tx = &texinfo[f->texinfo];

		area = WindingArea (w);
		totalarea += area;

		patch = &patches[num_patches];
		if (num_patches == MAX_PATCHES)
			Error ("num_patches == MAX_PATCHES");
		patch->next = face_patches[fn];
		face_patches[fn] = patch;

		if ( texscale )
			{
			// Compute the texture "scale" in s,t
			for( i=0; i<2; i++ )
				{
				patch->scale[i] = 0.0;
				for( j=0; j<3; j++ )
					patch->scale[i] += tx->vecs[i][j] * tx->vecs[i][j];
				patch->scale[i] = sqrt( patch->scale[i] );
				}
			}
		else
			patch->scale[0] = patch->scale[1] = 1.0;

		patch->area = area;
		patch->chop = maxchop / (int)((patch->scale[0]+patch->scale[1])/2);
		patch->sky = FALSE;

		patch->winding = w;

		if (f->side)
			patch->plane = &backplanes[f->planenum];
		else
			patch->plane = &dplanes[f->planenum];

		for (j=0 ; j<f->numedges ; j++)
		{
			int edge = dsurfedges[ f->firstedge + j ];
			int edge2 = dsurfedges[ j==f->numedges-1 ? f->firstedge : f->firstedge + j + 1 ];

			if (edge > 0)
			{
				VectorAdd( dvertexes[dedges[edge].v[0]].point, centroid, centroid );
				VectorAdd( dvertexes[dedges[edge].v[1]].point, centroid, centroid );
			}
			else
			{
				VectorAdd( dvertexes[dedges[-edge].v[1]].point, centroid, centroid );
				VectorAdd( dvertexes[dedges[-edge].v[0]].point, centroid, centroid );
			}
		}
		VectorScale( centroid, 1.0 / (f->numedges * 2), centroid );
		VectorCopy( centroid, face_centroids[fn] );  // Save them for generating the patch normals later.

		patch->faceNumber = fn;
		WindingCenter (w, patch->origin);

#ifdef PHONG_NORMAL_PATCHES
// This seems to be a bad idea for some reason.  Leave it turned off for now.
		VectorAdd (patch->origin, patch->plane->normal, patch->origin);
		GetPhongNormal( fn, patch->origin, patch->normal );
		VectorSubtract (patch->origin, patch->plane->normal, patch->origin);
		if ( !VectorCompare( patch->plane->normal, patch->normal ) )
			patch->chop = 16; // Chop it fine!
#else
		VectorCopy( patch->plane->normal, patch->normal );
#endif
		VectorAdd (patch->origin, patch->normal, patch->origin);

		WindingBounds (w, patch->face_mins, patch->face_maxs);
		VectorCopy( patch->face_mins, patch->mins );
		VectorCopy( patch->face_maxs, patch->maxs );

		BaseLightForFace( f, light, patch->reflectivity );
		VectorCopy( light, patch->totallight );
		VectorCopy( light, patch->baselight );

		// Chop all texlights very fine.
		if ( !VectorCompare( light, vec3_origin ) )
			patch->chop = extra ? minchop / 2 : minchop;

		num_patches++;
	}
}


entity_t *EntityForModel (int modnum)
{
	int		i;
	char	*s;
	char	name[16];

	sprintf (name, "*%i", modnum);
	// search the entities for one using modnum
	for (i=0 ; i<num_entities ; i++)
	{
		s = ValueForKey (&entities[i], "model");
		if (!strcmp (s, name))
			return &entities[i];
	}

	return &entities[0];
}

/*
=============
MakePatches
=============
*/
void MakePatches (void)
{
	int		i, j, k;
	dface_t	*f;
	int		fn;
	winding_t	*w;
	dmodel_t	*mod;
	vec3_t		origin;
	entity_t	*ent;
	char		*s;

	ParseEntities ();
	qprintf ("%i faces\n", numfaces);

	for (i=0 ; i<nummodels ; i++)
	{
		mod = dmodels+i;
		ent = EntityForModel (i);
		VectorCopy (vec3_origin, origin);

		// bmodels with origin brushes need to be offset into their
		// in-use position
		if ( *(s = ValueForKey(ent,"origin")) )
		{
			double	v1, v2, v3;
			if ( sscanf (s, "%lf %lf %lf", &v1, &v2, &v3) == 3 )
			{
				origin[0] = v1;
				origin[1] = v2;
				origin[2] = v3;
			}
		}

		for (j=0 ; j<mod->numfaces ; j++)
		{
			fn = mod->firstface + j;
			face_entity[fn] = ent;
			VectorCopy (origin, face_offset[fn]);
			f = dfaces+fn;
			w = WindingFromFace (f);
			for (k=0 ; k<w->numpoints ; k++)
			{
				VectorAdd (w->p[k], origin, w->p[k]);
			}
			MakePatchForFace (fn, w);
		}
	}

	qprintf ("%i square feet [%.2f square inches]\n", (int)(totalarea/144), totalarea );
}

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

SUBDIVIDE

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

/*
=============
SubdividePatch
=============
*/
void	SubdividePatch (patch_t *patch)
{
	winding_t *w, *o1, *o2;
	vec3_t	total;
	vec3_t	split;
	vec_t	dist;
	vec_t	widest = -1;
	int		i, j, widest_axis = -1;
	int		subdivide_it = 0;
	vec_t	v;
	patch_t	*newp;

	w = patch->winding;

	VectorSubtract (patch->maxs, patch->mins, total);
	for (i=0 ; i<3 ; i++)
	{
		if ( total[i] > widest )
			{
			widest_axis = i;
			widest = total[i];
			}
		if ( total[i] > patch->chop
		  || (patch->face_maxs[i] == patch->maxs[i] || patch->face_mins[i] == patch->mins[i] )
		  && total[i] > minchop )
		{
			subdivide_it = 1;
		}
	}

	if ( subdivide_it )
	{
		//
		// split the winding
		//
		VectorCopy (vec3_origin, split);
		split[widest_axis] = 1;
		dist = (patch->mins[widest_axis] + patch->maxs[widest_axis])*0.5f;
		ClipWinding (w, split, dist, &o1, &o2);

		//
		// create a new patch
		//
		if (num_patches == MAX_PATCHES)
			Error ("MAX_PATCHES");
		newp = &patches[num_patches];

		newp->next = patch->next;
		patch->next = newp;

		patch->winding = o1;
		newp->winding = o2;

		VectorCopy( patch->face_mins, newp->face_mins );
		VectorCopy( patch->face_maxs, newp->face_maxs );

		VectorCopy( patch->baselight, newp->baselight );
		VectorCopy( patch->directlight, newp->directlight );
		VectorCopy( patch->totallight, newp->totallight );
		VectorCopy( patch->reflectivity, newp->reflectivity );
		newp->plane = patch->plane;
		newp->sky = patch->sky;
		newp->chop = patch->chop;
		newp->faceNumber = patch->faceNumber;

		num_patches++;

		patch->area = WindingArea (patch->winding);
		newp->area = WindingArea (newp->winding);

		WindingCenter (patch->winding, patch->origin);
		WindingCenter (newp->winding, newp->origin);

#ifdef PHONG_NORMAL_PATCHES
// This seems to be a bad idea for some reason.  Leave it turned off for now.
		// Set (Copy or Calculate) the synthetic normal for these new patches
		VectorAdd (patch->origin, patch->plane->normal, patch->origin);
		VectorAdd (newp->origin, newp->plane->normal, newp->origin);
		GetPhongNormal( patch->faceNumber, patch->origin, patch->normal );
		GetPhongNormal( newp->faceNumber, newp->origin, newp->normal );
		VectorSubtract( patch->origin, patch->plane->normal, patch->origin);
		VectorSubtract( newp->origin, newp->plane->normal, newp->origin);
#else
		VectorCopy( patch->plane->normal, patch->normal );
		VectorCopy( newp->plane->normal, newp->normal );
#endif
		VectorAdd( patch->origin, patch->normal, patch->origin );
		VectorAdd( newp->origin, newp->normal, newp->origin );

		WindingBounds(patch->winding, patch->mins, patch->maxs);
		WindingBounds(newp->winding, newp->mins, newp->maxs);

		// Subdivide patch even more if on the edge of the face; this is a hack!
		VectorSubtract (patch->maxs, patch->mins, total);
		if ( total[0] < patch->chop && total[1] < patch->chop && total[2] < patch->chop )
			for ( i=0; i<3; i++ )
				if ( (patch->face_maxs[i] == patch->maxs[i] || patch->face_mins[i] == patch->mins[i] )
				  && total[i] > minchop )
				{
					patch->chop = max( minchop, patch->chop / 2 );
					break;
				}

		SubdividePatch (patch);

		// Subdivide patch even more if on the edge of the face; this is a hack!
		VectorSubtract (newp->maxs, newp->mins, total);
		if ( total[0] < newp->chop && total[1] < newp->chop && total[2] < newp->chop )
			for ( i=0; i<3; i++ )
				if ( (newp->face_maxs[i] == newp->maxs[i] || newp->face_mins[i] == newp->mins[i] )
				  && total[i] > minchop )
				{
					newp->chop = max( minchop, newp->chop / 2 );
					break;
				}

		SubdividePatch (newp);
	}
}


/*
=============
SubdividePatches
=============
*/
void SubdividePatches (void)
{
	int		i, num;

	num = num_patches;	// because the list will grow
	for (i=0 ; i<num ; i++)
		{
		patch_t *patch = patches + i;
		SubdividePatch( patch );
		}
	qprintf ("%i patches after subdivision\n", num_patches);
}

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

/*
=============
MakeScales

  This is the primary time sink.
  It can be run multi threaded.
=============
*/
int	total_transfer;

void MakeScales (int threadnum)
{
	int		i;
	unsigned j;
	vec3_t	delta;
	vec_t	dist, scale;
	int		count;
	float	trans;
	patch_t		*patch, *patch2;
	float		total, send;
	dplane_t	plane;
	vec3_t		origin;
	vec_t		area;
	transfer_t	transfers[MAX_PATCHES], *all_transfers;

	count = 0;

	while (1)
	{
		i = GetThreadWork ();
		if (i == -1)
			break;

		patch = patches + i;

		total = 0;
		patch->numtransfers = 0;

		VectorCopy (patch->origin, origin);
		plane = *patch->plane;
		plane.dist = PatchPlaneDist( patch );

		area = patch->area;

		// find out which patch2's will collect light
		// from patch

		all_transfers = transfers;
		for (j=0, patch2 = patches ; j<num_patches ; j++, patch2++)
		{
			if (!CheckVisBit (i, j))
				continue;

			// calculate transferemnce
			VectorSubtract (patch2->origin, origin, delta);
			dist = VectorNormalize (delta);
			
			// skys don't care about the interface angle, but everything
			// else does
			if (!patch->sky)
				scale = DotProduct (delta, patch->normal);
			else
				scale = 1;

			scale *= -DotProduct (delta, patch2->normal);

			trans = scale / (dist*dist);

			if (trans < -ON_EPSILON)
				Error ("transfer < 0");
			send = trans*patch2->area;
			if (send > 0.4f)
			{
				trans = 0.4f / patch2->area;
				send = 0.4f;
			}
			total += send;


			// scale to 16 bit
			trans = trans * area * INVERSE_TRANSFER_SCALE;
			if (trans >= 0x10000)
				trans = 0xffff;
			if (!trans)
				continue;
			all_transfers->transfer = (unsigned short)trans;
			all_transfers->patch = j;
			all_transfers++;
			patch->numtransfers++;
			count++;

		}

		// copy the transfers out
		if (patch->numtransfers)
		{
			transfer_t	*t, *t2;

			patch->transfers = calloc (patch->numtransfers, sizeof(transfer_t));

			if (!patch->transfers)
				Error ("Memory allocation failure");

			//
			// normalize all transfers so exactly 50% of the light
			// is transfered to the surroundings
			//
			total = 0.5f/total;
			t = patch->transfers;
			t2 = transfers;
			for (j=0 ; j<(unsigned)patch->numtransfers ; j++, t++, t2++)
			{
				t->transfer = (unsigned short)(t2->transfer*total);
				t->patch = t2->patch;
			}
		}
	}

	ThreadLock ();
	total_transfer += count;
	ThreadUnlock ();
}


/*
=============
WriteWorld
=============
*/
void WriteWorld (char *name)
{
	int			i;
	unsigned	j;
	FILE		*out;
	patch_t		*patch;
	winding_t	*w;

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

	for (j=0, patch=patches ; j<num_patches ; j++, patch++)
	{
		w = patch->winding;
		fprintf (out, "%i\n", w->numpoints);
		for (i=0 ; i<w->numpoints ; i++)
		{
			fprintf (out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n",
				w->p[i][0],
				w->p[i][1],
				w->p[i][2],
				patch->totallight[ 0 ] / 256,
				patch->totallight[ 1 ] / 256,
				patch->totallight[ 2 ] / 256 );
		}
		fprintf (out, "\n");
	}

	fclose (out);
}

/*
=============
SwapTransfersTask

Change transfers from light sent out to light collected in.
In an ideal world, they would be exactly symetrical, but
because the form factors are only aproximated, then normalized,
they will actually be rather different.
=============
*/
void SwapTransfersTask (int patchnum)
{
	int		j, k, l, m, n, h;
	patch_t	*patch, *patch2;
	transfer_t	*t, *t2;
	int		transfer;

	patch = patches + patchnum;

	t = patch->transfers;
	for (j=0 ; j<patch->numtransfers ; j++, t++)
	{
		k = t->patch;
		if (k > patchnum)
			break;		// done with this list
		patch2 = &patches[k];
		t2 = patch2->transfers;

		if (!patch2->numtransfers)
		{
			printf ("WARNING: SwapTransfers: unmatched\n");
			continue;
		}
		//
		// binary search for match
		//
		l = 0;
		h = patch2->numtransfers-1;
		while (1)
		{
			m = (l+h)>>1;
			n = t2[m].patch;
			if (n < patchnum)
			{
				l = m+1;
				continue;
			}
			if (n > patchnum)
			{
				h = m-1;
				continue;
			}

			t2 += m;
			transfer = t2->transfer;
			t2->transfer = t->transfer;
			t->transfer = transfer;
			break;
		}

#if 0
		for (l=0 ; l<patch2->numtransfers ; l++, t2++)
		{
			if (t2->patch == i)
			{
				transfer = t2->transfer;
				t2->transfer = t->transfer;
				t->transfer = transfer;
				break;
			}
		}
#endif
		if (l == patch2->numtransfers)
			Error ("Didn't match transfer");
	}
}

/*
=============
CollectLight
=============
*/
void CollectLight( vec3_t total )
{
	unsigned i;
	patch_t	*patch;

	VectorFill( total, 0 );

	for (i=0, patch=patches ; i<num_patches ; i++, patch++)
	{
		// sky's never collect light, it is just dropped
		if (patch->sky)
		{
			VectorFill( emitlight[ i ], 0 );
			VectorFill( addlight[ i ], 0 );
			continue;
		}

		VectorAdd( patch->totallight, addlight[i], patch->totallight );
		VectorScale( addlight[i], TRANSFER_SCALE, emitlight[i] );
		VectorAdd( total, emitlight[i], total );
		VectorFill( addlight[ i ], 0 );
	}

	VectorScale( total, INVERSE_TRANSFER_SCALE, total );
}

/*
=============
GatherLight

Get light from other patches
  Run multi-threaded
=============
*/
void GatherLight (int threadnum)
{
	int			j, k;
	transfer_t	*trans;
	int			num;
	patch_t		*patch;
	vec3_t		sum, v;

	while (1)
	{
		j = GetThreadWork ();
		if (j == -1)
			break;

		patch = &patches[j];

		trans = patch->transfers;
		num = patch->numtransfers;

		VectorFill( sum, 0 )

		for (k=0 ; k<num ; k++, trans++)
		{
			VectorScale( emitlight[trans->patch], trans->transfer, v );
			VectorAdd( sum, v, sum );
		}

		VectorCopy( sum, addlight[j] );
	}
}

/*
=============
BounceLight
=============
*/
void BounceLight (void)
{
	unsigned i;
	vec3_t	added;
	char	name[64];

	for (i=0 ; i<num_patches ; i++)
		VectorScale( patches[i].totallight, TRANSFER_SCALE, emitlight[i] );

	for (i=0 ; i<numbounce ; i++)
	{
		RunThreadsOn (num_patches, true, GatherLight);
		CollectLight( added );

		qprintf ("\tBounce #%i added RGB(%.0f, %.0f, %.0f)\n", i+1, added[0], added[1], added[2] );
		if ( dumppatches && (i==0 || i == (unsigned)numbounce-1) )
		{
			sprintf (name, "bounce%i.txt", i);
			WriteWorld (name);
		}
	}
}


/*
=============
writetransfers
=============
*/

long
writetransfers(char *transferfile, long total_patches)
{
	int		handle;
	long	writtenpatches = 0, writtentransfers = 0, totalbytes = 0;
	int		spacerequired = sizeof(long) + total_patches * sizeof(long) + total_transfer * sizeof(transfer_t);

	if ( spacerequired - getfilesize(transferfile) < getfreespace(transferfile) )
	{
		if ( (handle = _open( transferfile, _O_WRONLY | _O_BINARY | _O_CREAT | _O_TRUNC, _S_IREAD | _S_IWRITE )) != -1 )
		{
			unsigned			byteswritten;
			qprintf("Writing [%s] with new saved qrad data", transferfile );
			
			if ( (byteswritten = _write(handle, &total_patches, sizeof(total_patches))) == sizeof(total_patches) )
			{
				patch_t			*patch;

				totalbytes += byteswritten;

				for( patch = patches; total_patches-- > 0; patch++ )
				{
					if ( (byteswritten = _write(handle, &patch->numtransfers, sizeof(patch->numtransfers)))
					  == sizeof(patch->numtransfers) )
					{
						totalbytes += byteswritten;

						if ( patch->numtransfers && 
							 (byteswritten = _write(handle, patch->transfers, patch->numtransfers*sizeof(transfer_t)))
						      == patch->numtransfers*sizeof(transfer_t) )
						{
							totalbytes += byteswritten;
							writtentransfers += patch->numtransfers;
						}
						writtenpatches++;
					}
					else
					{
						break;
					}
				}
			}

			qprintf("(%d)\n", totalbytes );
			
			_close( handle );
		}
	}
	else
		printf("Insufficient disk space(%ld) for 'QRAD save file'[%s]!\n",
				spacerequired - getfilesize(transferfile), transferfile );


	return writtenpatches;
}

/*
=============
readtransfers
=============
*/

long
readtransfers(char *transferfile, long numpatches)
{
	int		handle;
	long	readpatches = 0, readtransfers = 0, totalbytes = 0;
	long	start, end;
	time(&start);
	if ( (handle = _open( transferfile, _O_RDONLY | _O_BINARY )) != -1 )
	{
		long			filepatches;
		unsigned long	bytesread;

		printf("%-20s Restoring [%-13s - ", "MakeAllScales:", transferfile );
		
		if ( (bytesread = _read(handle, &filepatches, sizeof(filepatches))) == sizeof(filepatches) )
		{
			if ( filepatches == numpatches )
			{

				patch_t			*patch;

				totalbytes += bytesread;

				for( patch = patches; readpatches < numpatches; patch++ )
				{
					if ( (bytesread = _read(handle, &patch->numtransfers, sizeof(patch->numtransfers)))
					  == sizeof(patch->numtransfers) )
					{
						if ( patch->transfers = calloc(patch->numtransfers, sizeof(patch->transfers[0])) )
						{
							totalbytes += bytesread;

							if ( patch->numtransfers )
							{
								if ( (bytesread = _read(handle, patch->transfers, patch->numtransfers*sizeof(transfer_t)))
								  == patch->numtransfers*sizeof(transfer_t) )
									{
										totalbytes += bytesread;
										readtransfers += patch->numtransfers;
									}
								else
								{
									printf("\nMissing transfer count!  Save file will now be rebuilt." );
									break;
								}
							}
							readpatches++;
						}
						else
						{
							printf("\nMemory allocation failure creating transfer lists(%d*%d)!\n",
								  patch->numtransfers, sizeof(transfer_t) );
							break;
						}
					}
					else
					{
						printf("\nMissing patch count!  Save file will now be rebuilt." );
						break;
					}
				}
			}
			else
				printf("\nIncorrect transfer patch count found!  Save file will now be rebuilt." );
		}
		_close( handle );
		time(&end);
		printf("%10.3fMB] (%d)\n",totalbytes/(1024.0*1024.0), end-start);
	}

	if (readpatches != numpatches )
		unlink(transferfile);
	else
		total_transfer = readtransfers;

	return readpatches;
}


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

void MakeAllScales (void)
{
	strcpy(transferfile, source);
	StripExtension( transferfile );
	DefaultExtension( transferfile, ".r2" );

	if ( !incremental
	  || !IsIncremental(incrementfile)
	  || (unsigned)readtransfers(transferfile, num_patches) != num_patches )
	{
		// determine visibility between patches
		BuildVisMatrix ();

		RunThreadsOn (num_patches, true, MakeScales);
		if ( incremental )
			writetransfers(transferfile, num_patches);
		else
			unlink(transferfile);

		// release visibility matrix
		FreeVisMatrix ();
	}

	qprintf ("transfer lists: %5.1f megs\n"
		, (float)total_transfer * sizeof(transfer_t) / (1024*1024));
}

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

	MakeBackplanes ();
	MakeParents (0, -1);
	MakeTnodes (&dmodels[0]);

	// turn each face into a single patch
	MakePatches ();
	PairEdges ();

	// subdivide patches to a maximum dimension
	SubdividePatches ();

	do
	{
		// create directlights out of patches and lights
		CreateDirectLights ();

		// build initial facelights
		RunThreadsOnIndividual (numfaces, true, BuildFacelights);

		// free up the direct lights now that we have facelights
		DeleteDirectLights ();
	}
	while( numbounce != 0 && ProgressiveRefinement() );

	if (numbounce > 0)
	{
		// build transfer lists
		MakeAllScales ();

		// invert the transfers for gather vs scatter
		RunThreadsOnIndividual (num_patches, true, SwapTransfersTask);

		// spread light around
		BounceLight ();

		for( i=0; i < num_patches; i++ )
			if ( !VectorCompare( patches[i].directlight, vec3_origin ) )
				VectorSubtract( patches[i].totallight, patches[i].directlight, patches[i].totallight );
	}

	// blend bounced light into direct light and save
	PrecompLightmapOffsets();

	RunThreadsOnIndividual (numfaces, true, FinalLightFace);
}


/*
========
main

light modelfile
========
*/
extern char qproject[];
int main (int argc, char **argv)
{
	int		i;
	double		start, end;

	printf( "qrad.exe v 1.5 (%s)\n", __DATE__ );
	printf ("----- Radiosity ----\n");

	verbose = true;  // Originally FALSE
	smoothing_threshold = cos(45.0*(Q_PI/180)); // Originally zero.

	for (i=1 ; i<argc ; i++)
	{
		if (!strcmp(argv[i],"-dump"))
			dumppatches = true;
		else if (!strcmp(argv[i],"-bounce"))
		{
			if ( ++i < argc )
			{
				numbounce = atoi (argv[i]);
				if ( numbounce < 0 )
				{
					fprintf(stderr, "Error: expected non-negative value after '-bounce'\n" );
					return 1;
				}
			}
			else
			{
				fprintf( stderr, "Error: expected a value after '-bounce'\n" );
				return 1;
			}
		}
		else if (!strcmp(argv[i],"-verbose"))
		{
			verbose = true;
		}
		else if (!strcmp(argv[i],"-terse"))
		{
			verbose = false;
		}
		else if (!strcmp(argv[i],"-threads"))
		{
			if ( ++i < argc )
			{
				numthreads = atoi (argv[i]);
				if ( numthreads <= 0 )
				{
					fprintf(stderr, "Error: expected positive value after '-threads'\n" );
					return 1;
				}
			}
			else
			{
				fprintf( stderr, "Error: expected a value after '-threads'\n" );
				return 1;
			}
		}
		else if (!strcmp(argv[i],"-maxchop"))
		{
			if ( ++i < argc )
			{
				maxchop = (float)atof (argv[i]);
				if ( maxchop < 2 )
				{
					fprintf(stderr, "Error: expected positive value after '-maxchop'\n" );
					return 1;
				}
			}
			else
			{
				fprintf( stderr, "Error: expected a value after '-maxchop'\n" );
				return 1;
			}
		}
		else if (!strcmp(argv[i],"-chop"))
		{
			if ( ++i < argc )
			{
				minchop = (float)atof (argv[i]);
				if ( minchop < 1 )
				{
					fprintf(stderr, "Error: expected positive value after '-chop'\n" );
					return 1;
				}
				if ( minchop < 32 )
				{
					fprintf(stderr, "WARNING: Chop values below 32 are not recommended.  Use -extra instead.\n");
				}
			}
			else
			{
				fprintf( stderr, "Error: expected a value after '-chop'\n" );
				return 1;
			}
		}
		else if (!strcmp(argv[i],"-scale"))
		{
			if ( ++i < argc )
			{
				lightscale = (float)atof (argv[i]);
			}
			else
			{
				fprintf( stderr, "Error: expected a value after '-scale'\n" );
				return 1;
			}
		}
		else if (!strcmp(argv[i],"-ambient"))
		{
			if ( i+3 < argc )
			{
 				ambient[0] = (float)atof (argv[++i]) * 128;
 				ambient[1] = (float)atof (argv[++i]) * 128;
 				ambient[2] = (float)atof (argv[++i]) * 128;
			}
			else
			{
				fprintf( stderr, "Error: expected three color values after '-ambient'\n" );
				return 1;
			}
		}
		else if( !strcmp(argv[i], "-proj") )
		{
			if ( ++i < argc && *argv[i] )
				strcpy( qproject, argv[i] );
			else
			{
				fprintf(stderr, "Error: expected path name after '-proj'\n" );
				return 1;
			}
		}
		else if ( !strcmp(argv[i], "-maxlight") )
		{
			if ( ++i < argc && *argv[i] )
			{
				maxlight = (float)atof (argv[i]) * 128;
				if ( maxlight <= 0 )
				{
					fprintf(stderr, "Error: expected positive value after '-maxlight'\n" );
					return 1;
				}
			}
			else
			{
				fprintf( stderr, "Error: expected a value after '-maxlight'\n" );
				return 1;
			}
		}
		else if ( !strcmp(argv[i], "-lights" ) )
		{
			if ( ++i < argc && *argv[i] )
			{
				strcpy( designer_lights, argv[i] );
			}
			else
			{
				fprintf( stderr, "Error: expected a filepath after '-lights'\n" );
				return 1;
			}
		}
		else if ( !strcmp(argv[i], "-inc" ) )
		{
			incremental = true;
		} 
		else if (!strcmp(argv[i],"-gamma"))
		{
			if ( ++i < argc )
			{
				gamma = (float)atof (argv[i]);
			}
			else
			{
				fprintf( stderr, "Error: expected a value after '-gamma'\n" );
				return 1;
			}
		}
		else if (!strcmp(argv[i],"-dlight"))
		{
			if ( ++i < argc )
			{
				dlight_threshold = (float)atof (argv[i]);
			}
			else
			{
				fprintf( stderr, "Error: expected a value after '-dlight'\n" );
				return 1;
			}
		}
		else if (!strcmp(argv[i],"-extra"))
		{
			extra = true;
		}
		else if (!strcmp(argv[i],"-sky"))
		{
			if ( ++i < argc )
			{
				indirect_sun = (float)atof (argv[i]);
			}
			else
			{
				fprintf( stderr, "Error: expected a value after '-gamma'\n" );
				return 1;
			}
		}
		else if (!strcmp(argv[i],"-smooth"))
		{
			if ( ++i < argc )
			{
				smoothing_threshold = (float)cos(atof(argv[i])*(Q_PI/180.0));
			}
			else
			{
				fprintf( stderr, "Error: expected an angle after '-smooth'\n" );
				return 1;
			}
		}
		else if (!strcmp(argv[i],"-coring"))
		{
			if ( ++i < argc )
			{
				coring = (float)atof( argv[i] );
			}
			else
			{
				fprintf( stderr, "Error: expected a light threshold after '-coring'\n" );
				return 1;
			}
		}
		else if (!strcmp(argv[i],"-notexscale"))
		{
			texscale = false;
		}
		else
		{
			break;
		}
	}

	ThreadSetDefault ();

	if (maxlight > 255)
		maxlight = 255;

	if (i != argc - 1)
		Error ("usage: qrad [-dump] [-inc] [-bounce n] [-threads n] [-verbose] [-terse] [-chop n] [-maxchop n] [-scale n] [-ambient red green blue] [-proj file] [-maxlight n] [-threads n] [-lights file] [-gamma n] [-dlight n] [-extra] [-smooth n] [-coring n] [-notexscale] bspfile");

	start = I_FloatTime ();

	strcpy (source, argv[i]);
	StripExtension (source);
	SetQdirFromPath (source);

	// Set the required global lights filename
	strcat( strcpy( global_lights, gamedir ), "lights.rad" );
	if ( _access( global_lights, 0x04) == -1 ) 
	{
		// try looking in qproject
		strcat( strcpy( global_lights, qproject ), "lights.rad" );
		if ( _access( global_lights, 0x04) == -1 ) 
		{
			// try looking in the directory we were run from
			GetModuleFileName( NULL, global_lights, sizeof( global_lights ) );
			ExtractFilePath( global_lights, global_lights );
			strcat( global_lights, "lights.rad" );
		}
	}

	// Set the optional level specific lights filename
	DefaultExtension( strcpy( level_lights, source ), ".rad" );
	if ( _access( level_lights, 0x04) == -1 ) *level_lights = 0;	

	ReadLightFile(global_lights);							// Required
	if ( *designer_lights ) ReadLightFile(designer_lights);	// Command-line
	if ( *level_lights )	ReadLightFile(level_lights);	// Optional & implied

	strcpy(incrementfile, source);
	DefaultExtension(incrementfile, ".r0");
	DefaultExtension(source, ".bsp");

	LoadBSPFile (source);
	ParseEntities ();

	if (!visdatasize)
	{
		printf ("No vis information, direct lighting only.\n");
		numbounce = 0;
		ambient[0] = ambient[1] = ambient[2] = 0.1f;
	}

	RadWorld ();

	if (verbose)
		PrintBSPFileSizes ();

	WriteBSPFile (source);

	if ( incremental )
	{
		if ( !IsIncremental(incrementfile) )
		{
			SaveIncremental(incrementfile);
		}
	}
	else
	{
		unlink(incrementfile);
	}

	end = I_FloatTime ();
	printf ("%5.0f seconds elapsed\n", end-start);
	
	return 0;
}