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