/*
   Copyright (C) 1999-2007 id Software, Inc. and contributors.
   For a list of contributors, see the accompanying CONTRIBUTORS file.

   This file is part of GtkRadiant.

   GtkRadiant 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.

   GtkRadiant 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 GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
// 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 radiosity[MAX_PATCHES];          // light leaving a patch
vec3_t illumination[MAX_PATCHES];       // light arriving at a patch

vec3_t face_offset[MAX_MAP_FACES];          // for rotating bmodels
dplane_t backplanes[MAX_MAP_PLANES];

char inbase[32], outbase[32];

int fakeplanes;                         // created planes for origin offset

int numbounce = 8;
qboolean extrasamples;

float subdiv = 64;
qboolean dumppatches;

void BuildLightmaps( void );
int TestLine( vec3_t start, vec3_t stop );

int junk;

float ambient = 0;
float maxlight = 196;

float lightscale = 1.0;

qboolean glview;

qboolean nopvs;

char source[1024];

float direct_scale =  0.4;
float entity_scale =  1.0;

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

   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 );
		}
	}
}


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

   TRANSFER SCALES

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

int PointInLeafnum( vec3_t point ){
	int nodenum;
	vec_t dist;
	dnode_t *node;
	dplane_t    *plane;

	nodenum = 0;
	while ( nodenum >= 0 )
	{
		node = &dnodes[nodenum];
		plane = &dplanes[node->planenum];
		dist = DotProduct( point, plane->normal ) - plane->dist;
		if ( dist > 0 ) {
			nodenum = node->children[0];
		}
		else{
			nodenum = node->children[1];
		}
	}

	return -nodenum - 1;
}


dleaf_t     *Rad_PointInLeaf( vec3_t point ){
	int num;

	num = PointInLeafnum( point );
	return &dleafs[num];
}


qboolean PvsForOrigin( vec3_t org, byte *pvs ){
	dleaf_t *leaf;

	if ( !visdatasize ) {
		memset( pvs, 255, ( numleafs + 7 ) / 8 );
		return true;
	}

	leaf = Rad_PointInLeaf( org );
	if ( leaf->cluster == -1 ) {
		return false;       // in solid leaf

	}
	DecompressVis( dvisdata + dvis->bitofs[leaf->cluster][DVIS_PVS], pvs );
	return true;
}


/*
   =============
   MakeTransfers

   =============
 */
int total_transfer;

void MakeTransfers( int i ){
	int j;
	vec3_t delta;
	vec_t dist, scale;
	float trans;
	int itrans;
	patch_t     *patch, *patch2;
	float total;
	dplane_t plane;
	vec3_t origin;
	float transfers[MAX_PATCHES], *all_transfers;
	int s;
	int itotal;
	byte pvs[( MAX_MAP_LEAFS + 7 ) / 8];
	int cluster;

	patch = patches + i;
	total = 0;

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

	if ( !PvsForOrigin( patch->origin, pvs ) ) {
		return;
	}

	// find out which patch2s will collect light
	// from patch

	all_transfers = transfers;
	patch->numtransfers = 0;
	for ( j = 0, patch2 = patches ; j < num_patches ; j++, patch2++ )
	{
		transfers[j] = 0;

		if ( j == i ) {
			continue;
		}

		// check pvs bit
		if ( !nopvs ) {
			cluster = patch2->cluster;
			if ( cluster == -1 ) {
				continue;
			}
			if ( !( pvs[cluster >> 3] & ( 1 << ( cluster & 7 ) ) ) ) {
				continue;       // not in pvs
			}
		}

		// calculate vector
		VectorSubtract( patch2->origin, origin, delta );
		dist = VectorNormalize( delta, delta );
		if ( !dist ) {
			continue;   // should never happen

		}
		// reletive angles
		scale = DotProduct( delta, plane.normal );
		scale *= -DotProduct( delta, patch2->plane->normal );
		if ( scale <= 0 ) {
			continue;
		}

		// check exact tramsfer
		if ( TestLine_r( 0, patch->origin, patch2->origin ) ) {
			continue;
		}

		trans = scale * patch2->area / ( dist * dist );

		if ( trans < 0 ) {
			trans = 0;      // rounding errors...

		}
		transfers[j] = trans;
		if ( trans > 0 ) {
			total += trans;
			patch->numtransfers++;
		}
	}

	// copy the transfers out and normalize
	// total should be somewhere near PI if everything went right
	// because partial occlusion isn't accounted for, and nearby
	// patches have underestimated form factors, it will usually
	// be higher than PI
	if ( patch->numtransfers ) {
		transfer_t  *t;

		if ( patch->numtransfers < 0 || patch->numtransfers > MAX_PATCHES ) {
			Error( "Weird numtransfers" );
		}
		s = patch->numtransfers * sizeof( transfer_t );
		patch->transfers = malloc( s );
		if ( !patch->transfers ) {
			Error( "Memory allocation failure" );
		}

		//
		// normalize all transfers so all of the light
		// is transfered to the surroundings
		//
		t = patch->transfers;
		itotal = 0;
		for ( j = 0 ; j < num_patches ; j++ )
		{
			if ( transfers[j] <= 0 ) {
				continue;
			}
			itrans = transfers[j] * 0x10000 / total;
			itotal += itrans;
			t->transfer = itrans;
			t->patch = j;
			t++;
		}
	}

	// don't bother locking around this.  not that important.
	total_transfer += patch->numtransfers;
}


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

	for ( i = 0 ; i < num_patches ; i++ )
	{
		free( patches[i].transfers );
		patches[i].transfers = NULL;
	}
}


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

/*
   =============
   WriteWorld
   =============
 */
void WriteWorld( char *name ){
	int i, 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],
					 patch->totallight[1],
					 patch->totallight[2] );
		}
		fprintf( out, "\n" );
	}

	fclose( out );
}

/*
   =============
   WriteGlView
   =============
 */
void WriteGlView( void ){
	char name[1024];
	FILE    *f;
	int i, j;
	patch_t *p;
	winding_t   *w;

	strcpy( name, source );
	StripExtension( name );
	strcat( name, ".glr" );

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

	for ( j = 0 ; j < num_patches ; j++ )
	{
		p = &patches[j];
		w = p->winding;
		fprintf( f, "%i\n", w->numpoints );
		for ( i = 0 ; i < w->numpoints ; i++ )
		{
			fprintf( f, "%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],
					 p->totallight[0] / 128,
					 p->totallight[1] / 128,
					 p->totallight[2] / 128 );
		}
		fprintf( f, "\n" );
	}

	fclose( f );
}


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

/*
   =============
   CollectLight
   =============
 */
float CollectLight( void ){
	int i, j;
	patch_t *patch;
	vec_t total;

	total = 0;

	for ( i = 0, patch = patches ; i < num_patches ; i++, patch++ )
	{
		// skys never collect light, it is just dropped
		if ( patch->sky ) {
			VectorClear( radiosity[i] );
			VectorClear( illumination[i] );
			continue;
		}

		for ( j = 0 ; j < 3 ; j++ )
		{
			patch->totallight[j] += illumination[i][j] / patch->area;
			radiosity[i][j] = illumination[i][j] * patch->reflectivity[j];
		}

		total += radiosity[i][0] + radiosity[i][1] + radiosity[i][2];
		VectorClear( illumination[i] );
	}

	return total;
}


/*
   =============
   ShootLight

   Send light out to other patches
   Run multi-threaded
   =============
 */
void ShootLight( int patchnum ){
	int k, l;
	transfer_t  *trans;
	int num;
	patch_t     *patch;
	vec3_t send;

	// this is the amount of light we are distributing
	// prescale it so that multiplying by the 16 bit
	// transfer values gives a proper output value
	for ( k = 0 ; k < 3 ; k++ )
		send[k] = radiosity[patchnum][k] / 0x10000;
	patch = &patches[patchnum];

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

	for ( k = 0 ; k < num ; k++, trans++ )
	{
		for ( l = 0 ; l < 3 ; l++ )
			illumination[trans->patch][l] += send[l] * trans->transfer;
	}
}

/*
   =============
   BounceLight
   =============
 */
void BounceLight( void ){
	int i, j;
	float added;
	char name[64];
	patch_t *p;

	for ( i = 0 ; i < num_patches ; i++ )
	{
		p = &patches[i];
		for ( j = 0 ; j < 3 ; j++ )
		{
//			p->totallight[j] = p->samplelight[j];
			radiosity[i][j] = p->samplelight[j] * p->reflectivity[j] * p->area;
		}
	}

	for ( i = 0 ; i < numbounce ; i++ )
	{
		RunThreadsOnIndividual( num_patches, false, ShootLight );
		added = CollectLight();

		Sys_FPrintf( SYS_VRB, "bounce:%i added:%f\n", i, added );
		if ( dumppatches && ( i == 0 || i == numbounce - 1 ) ) {
			sprintf( name, "bounce%i.txt", i );
			WriteWorld( name );
		}
	}
}



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

void CheckPatches( void ){
	int i;
	patch_t *patch;

	for ( i = 0 ; i < num_patches ; i++ )
	{
		patch = &patches[i];
		if ( patch->totallight[0] < 0 || patch->totallight[1] < 0 || patch->totallight[2] < 0 ) {
			Error( "negative patch totallight\n" );
		}
	}
}

/*
   =============
   RadWorld
   =============
 */
void RadWorld( void ){
	if ( numnodes == 0 || numfaces == 0 ) {
		Error( "Empty map" );
	}
	MakeBackplanes();
	MakeParents( 0, -1 );
	MakeTnodes( &dmodels[0] );

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

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

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

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

	if ( numbounce > 0 ) {
		// build transfer lists
		RunThreadsOnIndividual( num_patches, true, MakeTransfers );
		Sys_FPrintf( SYS_VRB, "transfer lists: %5.1f megs\n"
					 , (float)total_transfer * sizeof( transfer_t ) / ( 1024 * 1024 ) );

		// spread light around
		BounceLight();

		FreeTransfers();

		CheckPatches();
	}

	if ( glview ) {
		WriteGlView();
	}

	// blend bounced light into direct light and save
	PairEdges();
	LinkPlaneFaces();

	lightdatasize = 0;
	RunThreadsOnIndividual( numfaces, true, FinalLightFace );
}


/*
   ========
   main

   light modelfile
   ========
 */
int RAD_Main(){
	double start, end;
	char name[1024];
	int total_rad_time;

	Sys_Printf( "\n----- RAD ----\n\n" );

	if ( maxlight > 255 ) {
		maxlight = 255;
	}

	start = I_FloatTime();

	if ( !strcmp( game, "heretic2" ) ) {
		CalcTextureReflectivity = &CalcTextureReflectivity_Heretic2;
	}
	else{
		CalcTextureReflectivity = &CalcTextureReflectivity_Quake2;
	}

	SetQdirFromPath( mapname );
	strcpy( source, ExpandArg( mapname ) );
	StripExtension( source );
	DefaultExtension( source, ".bsp" );

//	ReadLightFile ();

	sprintf( name, "%s%s", inbase, source );
	Sys_Printf( "reading %s\n", name );
	LoadBSPFile( name );
	ParseEntities();
	( *CalcTextureReflectivity )( );

	if ( !visdatasize ) {
		Sys_Printf( "No vis information, direct lighting only.\n" );
		numbounce = 0;
		ambient = 0.1;
	}

	RadWorld();

	sprintf( name, "%s%s", outbase, source );
	Sys_Printf( "writing %s\n", name );
	WriteBSPFile( name );

	end = I_FloatTime();
	total_rad_time = (int) ( end - start );
	Sys_Printf( "\nRAD Time: " );
	if ( total_rad_time > 59 ) {
		Sys_Printf( "%d Minutes ", total_rad_time / 60 );
	}
	Sys_Printf( "%d Seconds\n", total_rad_time % 60 );


	return 0;
}