/* 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; }