/* ------------------------------------------------------------------------------- This code is based on source provided under the terms of the Id Software LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. All changes and additions to the original source which have been developed by other contributors (see CONTRIBUTORS) are provided under the terms of the license the contributors choose (see LICENSE), to the extent permitted by the LICENSE_ID. If you did not receive a copy of the contributor license, please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------------- This code has been altered significantly from its original form, to support several games based on the Quake III Arena engine, in the form of "Q3Map2." ------------------------------------------------------------------------------- */ /* marker */ #define LIGHT_YDNAR_C /* dependencies */ #include "q3map2.h" /* ColorToBytes() ydnar: moved to here 2001-02-04 */ void ColorToBytes( const float *color, byte *colorBytes, float scale ) { int i; float max, gamma; vec3_t sample; float inv, dif; /* ydnar: scaling necessary for simulating r_overbrightBits on external lightmaps */ if( scale <= 0.0f ) scale = 1.0f; /* make a local copy */ VectorScale( color, scale, sample ); /* muck with it */ gamma = 1.0f / lightmapGamma; for( i = 0; i < 3; i++ ) { /* handle negative light */ if( sample[ i ] < 0.0f ) { sample[ i ] = 0.0f; continue; } /* gamma */ sample[ i ] = pow( sample[ i ] / 255.0f, gamma ) * 255.0f; } if (lightmapExposure == 1) { /* clamp with color normalization */ max = sample[ 0 ]; if( sample[ 1 ] > max ) max = sample[ 1 ]; if( sample[ 2 ] > max ) max = sample[ 2 ]; if( max > 255.0f ) VectorScale( sample, (255.0f / max), sample ); } else { if (lightmapExposure==0) { lightmapExposure=1.0f; } inv=1.f/lightmapExposure; //Exposure max = sample[ 0 ]; if( sample[ 1 ] > max ) max = sample[ 1 ]; if( sample[ 2 ] > max ) max = sample[ 2 ]; dif = (1- exp(-max * inv) ) * 255; if (max >0) { dif = dif / max; } else { dif = 0; } for (i=0;i<3;i++) { sample[i]*=dif; } } /* compensate for ingame overbrighting/bitshifting */ VectorScale( sample, (1.0f / lightmapCompensate), sample ); //Compensate for that radbump samples are darker then they should be /* store it off */ /*if ((sample[0]<0) ||(sample[0]>255) ) { sample[0]=0; } if ((sample[1]<0) || (sample[1]>255) ) { sample[1]=0; } if ((sample[2]<0) || (sample[2]>255) ) { sample[2]=0; }*/ colorBytes[ 0 ] = sample[ 0 ]; colorBytes[ 1 ] = sample[ 1 ]; colorBytes[ 2 ] = sample[ 2 ]; } /* ------------------------------------------------------------------------------- this section deals with phong shading (normal interpolation across brush faces) ------------------------------------------------------------------------------- */ /* SmoothNormals() smooths together coincident vertex normals across the bsp */ #define MAX_SAMPLES 256 #define THETA_EPSILON 0.000001 #define EQUAL_NORMAL_EPSILON 0.01 void SmoothNormals( void ) { int i, j, k, f, cs, numVerts, numVotes, fOld, start; float shadeAngle, defaultShadeAngle, maxShadeAngle, dot, testAngle; bspDrawSurface_t *ds; shaderInfo_t *si; float *shadeAngles; byte *smoothed; vec3_t average, diff; int indexes[ MAX_SAMPLES ]; vec3_t votes[ MAX_SAMPLES ]; /* allocate shade angle table */ shadeAngles = safe_malloc( numBSPDrawVerts * sizeof( float ) ); memset( shadeAngles, 0, numBSPDrawVerts * sizeof( float ) ); /* allocate smoothed table */ cs = (numBSPDrawVerts / 8) + 1; smoothed = safe_malloc( cs ); memset( smoothed, 0, cs ); /* set default shade angle */ defaultShadeAngle = DEG2RAD( shadeAngleDegrees ); maxShadeAngle = 0; /* run through every surface and flag verts belonging to non-lightmapped surfaces and set per-vertex smoothing angle */ for( i = 0; i < numBSPDrawSurfaces; i++ ) { /* get drawsurf */ ds = &bspDrawSurfaces[ i ]; /* get shader for shade angle */ si = surfaceInfos[ i ].si; if( si->shadeAngleDegrees ) shadeAngle = DEG2RAD( si->shadeAngleDegrees ); else shadeAngle = defaultShadeAngle; if( shadeAngle > maxShadeAngle ) maxShadeAngle = shadeAngle; /* flag its verts */ for( j = 0; j < ds->numVerts; j++ ) { f = ds->firstVert + j; shadeAngles[ f ] = shadeAngle; if( ds->surfaceType == MST_TRIANGLE_SOUP ) smoothed[ f >> 3 ] |= (1 << (f & 7)); } /* ydnar: optional force-to-trisoup */ if( trisoup && ds->surfaceType == MST_PLANAR ) { ds->surfaceType = MST_TRIANGLE_SOUP; ds->lightmapNum[ 0 ] = -3; } } /* bail if no surfaces have a shade angle */ if( maxShadeAngle == 0 ) { free( shadeAngles ); free( smoothed ); return; } /* init pacifier */ fOld = -1; start = I_FloatTime(); /* go through the list of vertexes */ for( i = 0; i < numBSPDrawVerts; i++ ) { /* print pacifier */ f = 10 * i / numBSPDrawVerts; if( f != fOld ) { fOld = f; Sys_Printf( "%i...", f ); } /* already smoothed? */ if( smoothed[ i >> 3 ] & (1 << (i & 7)) ) continue; /* clear */ VectorClear( average ); numVerts = 0; numVotes = 0; /* build a table of coincident vertexes */ for( j = i; j < numBSPDrawVerts && numVerts < MAX_SAMPLES; j++ ) { /* already smoothed? */ if( smoothed[ j >> 3 ] & (1 << (j & 7)) ) continue; /* test vertexes */ if( VectorCompare( yDrawVerts[ i ].xyz, yDrawVerts[ j ].xyz ) == qfalse ) continue; /* use smallest shade angle */ shadeAngle = (shadeAngles[ i ] < shadeAngles[ j ] ? shadeAngles[ i ] : shadeAngles[ j ]); /* check shade angle */ dot = DotProduct( bspDrawVerts[ i ].normal, bspDrawVerts[ j ].normal ); if( dot > 1.0 ) dot = 1.0; else if( dot < -1.0 ) dot = -1.0; testAngle = acos( dot ) + THETA_EPSILON; if( testAngle >= shadeAngle ) { //Sys_Printf( "F(%3.3f >= %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) ); continue; } //Sys_Printf( "P(%3.3f < %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) ); /* add to the list */ indexes[ numVerts++ ] = j; /* flag vertex */ smoothed[ j >> 3 ] |= (1 << (j & 7)); /* see if this normal has already been voted */ for( k = 0; k < numVotes; k++ ) { VectorSubtract( bspDrawVerts[ j ].normal, votes[ k ], diff ); if( fabs( diff[ 0 ] ) < EQUAL_NORMAL_EPSILON && fabs( diff[ 1 ] ) < EQUAL_NORMAL_EPSILON && fabs( diff[ 2 ] ) < EQUAL_NORMAL_EPSILON ) break; } /* add a new vote? */ if( k == numVotes && numVotes < MAX_SAMPLES ) { VectorAdd( average, bspDrawVerts[ j ].normal, average ); VectorCopy( bspDrawVerts[ j ].normal, votes[ numVotes ] ); numVotes++; } } /* don't average for less than 2 verts */ if( numVerts < 2 ) continue; /* average normal */ if( VectorNormalize( average, average ) > 0 ) { /* smooth */ for( j = 0; j < numVerts; j++ ) VectorCopy( average, yDrawVerts[ indexes[ j ] ].normal ); } } /* free the tables */ free( shadeAngles ); free( smoothed ); /* print time */ Sys_Printf( " (%i)\n", (int) (I_FloatTime() - start) ); } /* ------------------------------------------------------------------------------- this section deals with phong shaded lightmap tracing ------------------------------------------------------------------------------- */ /* 9th rewrite (recursive subdivision of a lightmap triangle) */ /* CalcTangentVectors() calculates the st tangent vectors for normalmapping */ static qboolean CalcTangentVectors( int numVerts, bspDrawVert_t **dv, vec3_t *stv, vec3_t *ttv ) { int i; float bb, s, t; vec3_t bary; vec3_t norm; vec3_t face_bi_tangent; vec3_t face_tangent; vec3_t facenorm; //calculate face tangents { vec3_t d1; vec3_t d2; vec3_t outA,outB; float deltaU_0; float deltaU_1; float deltaV_0; float deltaV_1; int a,b,c; a=0; b=1; c=2; VectorSubtract( dv[b]->xyz,dv[a]->xyz,d1); VectorSubtract( dv[c]->xyz,dv[a]->xyz,d2); //calculate the face normal CrossProduct(d1,d2,facenorm); VectorNormalize(facenorm,facenorm); deltaU_0 = dv[b]->st[0] - dv[a]->st[0]; deltaU_1 = dv[c]->st[0] - dv[a]->st[0]; deltaV_0 = dv[b]->st[1] - dv[a]->st[1]; deltaV_1 = dv[c]->st[1] - dv[a]->st[1]; //face_tangent = normalize( delta_V_1 * side_0 - delta_V_0 * side_1 ); //face_bi_tangent = normalize( delta_U_1 * side_0 - delta_U_0 * side_1 ); face_tangent[0] =( deltaV_1 * d1[0] - deltaV_0 * d2[0]); face_tangent[1] =( deltaV_1 * d1[1] - deltaV_0 * d2[1]); face_tangent[2] =( deltaV_1 * d1[2] - deltaV_0 * d2[2]); face_bi_tangent[0] =( deltaU_1 * d1[0] - deltaU_0 * d2[0]); face_bi_tangent[1] =( deltaU_1 * d1[1] - deltaU_0 * d2[1]); face_bi_tangent[2] =( deltaU_1 * d1[2] - deltaU_0 * d2[2]); VectorNormalize(face_tangent,face_tangent); VectorNormalize(face_bi_tangent,face_bi_tangent); //check for opposing tangents CrossProduct(face_tangent,face_bi_tangent,norm); if (DotProduct(norm,facenorm)<0) { //flip face_bi_tangent[0]=-face_bi_tangent[0]; face_bi_tangent[1]=-face_bi_tangent[1]; face_bi_tangent[2]=-face_bi_tangent[2]; } } /* do each vertex */ for( i = 0; i < numVerts; i++ ) { VectorCopy(face_tangent,stv[i]); VectorCopy(face_bi_tangent,ttv[i]); // VectorCopy(facenorm,dv[i]->normal); } /* return to caller */ return qtrue; } /* PerturbNormal() perterbs the normal by the shader's normalmap in tangent space */ //basis vectors.. these are our 3 sample directions in tangent space //const vec3_t basis[4]={ { 0.816497, 0, 0.577350}, {-0.408248, -0.707107, 0.577350} ,{-0.408248, 0.707107, 0.577350} , {0.0, 0.0, 1.0 } }; const vec3_t basis[4]={ { 1.0, 0, 0}, {-0.5, -0.86, 0.0} ,{-0.5, 0.86, 0.0} , {0.0, 0.0, 1.0 } }; static void RadbumpNormal( vec3_t dvNormal, vec3_t stv[ 3 ], vec3_t ttv[ 3 ], vec3_t pNormal ) { if (radbump==qfalse) return; /* scale tangent vectors and add to original normal */ VectorMA( dvNormal, basis[radbumppass ][0], stv[ 0 ], pNormal ); VectorMA( pNormal, basis[ radbumppass ][1], ttv[ 0 ], pNormal ); VectorMA( pNormal, basis[ radbumppass ][2], dvNormal, pNormal ); /* renormalize and return */ VectorNormalize( pNormal, pNormal ); } static void PerturbNormal( bspDrawVert_t *dv, shaderInfo_t *si, vec3_t pNormal, vec3_t stv[ 3 ], vec3_t ttv[ 3 ] ) { int i; vec4_t bump; /* passthrough */ VectorCopy( dv->normal, pNormal ); RadbumpNormal(dv->normal,stv,ttv,pNormal); if (si->normalImage==NULL) return; /* sample normalmap */ if( RadSampleImage( si->normalImage->pixels, si->normalImage->width, si->normalImage->height, dv->st, bump ) == qfalse ) return; /* remap sampled normal from [0,255] to [-1,-1] */ for( i = 0; i < 3; i++ ) bump[ i ] = (bump[ i ] - 127.0f) * (1.0f / 127.5f); /* scale tangent vectors and add to original normal */ VectorMA( dv->normal, bump[ 0 ], stv[ 0 ], pNormal ); VectorMA( pNormal, bump[ 1 ], ttv[ 0 ], pNormal ); VectorMA( pNormal, bump[ 2 ], dv->normal, pNormal ); /* renormalize and return */ VectorNormalize( pNormal, pNormal ); } /* MapSingleLuxel() maps a luxel for triangle bv at */ #define NUDGE 0.5f #define BOGUS_NUDGE -99999.0f static int MapSingleLuxel( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv, vec4_t plane, float pass, vec3_t stv[ 3 ], vec3_t ttv[ 3 ], vec3_t worldverts[ 3 ] ) { int i, x, y, numClusters, *clusters, pointCluster, *cluster; float *luxel, *origin, *normal, d, lightmapSampleOffset; shaderInfo_t *si; vec3_t pNormal; vec3_t vecs[ 3 ]; vec3_t nudged; vec3_t cverts[ 3 ]; vec3_t temp; vec4_t sideplane, hostplane; vec3_t origintwo; int j, next; float e; float *nudge; static float nudges[][ 2 ] = { //%{ 0, 0 }, /* try center first */ { -NUDGE, 0 }, /* left */ { NUDGE, 0 }, /* right */ { 0, NUDGE }, /* up */ { 0, -NUDGE }, /* down */ { -NUDGE, NUDGE }, /* left/up */ { NUDGE, -NUDGE }, /* right/down */ { NUDGE, NUDGE }, /* right/up */ { -NUDGE, -NUDGE }, /* left/down */ { BOGUS_NUDGE, BOGUS_NUDGE } }; /* find luxel xy coords (fixme: subtract 0.5?) */ x = dv->lightmap[ 0 ][ 0 ]; y = dv->lightmap[ 0 ][ 1 ]; if( x < 0 ) x = 0; else if( x >= lm->sw ) x = lm->sw - 1; if( y < 0 ) y = 0; else if( y >= lm->sh ) y = lm->sh - 1; /* set shader and cluster list */ if( info != NULL ) { si = info->si; numClusters = info->numSurfaceClusters; clusters = &surfaceClusters[ info->firstSurfaceCluster ]; } else { si = NULL; numClusters = 0; clusters = NULL; } /* get luxel, origin, cluster, and normal */ luxel = SUPER_LUXEL( 0, x, y ); origin = SUPER_ORIGIN( x, y ); normal = SUPER_NORMAL( x, y ); cluster = SUPER_CLUSTER( x, y ); /* don't attempt to remap occluded luxels for planar surfaces */ if( (*cluster) == CLUSTER_OCCLUDED && lm->plane != NULL ) return (*cluster); /* only average the normal for premapped luxels */ else if( (*cluster) >= 0 ) { /* do bumpmap calculations */ if( stv != NULL ) PerturbNormal( dv, si, pNormal, stv, ttv ); else VectorCopy( dv->normal, pNormal ); /* add the additional normal data */ VectorAdd( normal, pNormal, normal ); luxel[ 3 ] += 1.0f; return (*cluster); } /* otherwise, unmapped luxels (*cluster == CLUSTER_UNMAPPED) will have their full attributes calculated */ /* get origin */ /* axial lightmap projection */ if( lm->vecs != NULL ) { /* calculate an origin for the sample from the lightmap vectors */ VectorCopy( lm->origin, origin ); for( i = 0; i < 3; i++ ) { /* add unless it's the axis, which is taken care of later */ if( i == lm->axisNum ) continue; origin[ i ] += (x * lm->vecs[ 0 ][ i ]) + (y * lm->vecs[ 1 ][ i ]); } /* project the origin onto the plane */ d = DotProduct( origin, plane ) - plane[ 3 ]; d /= plane[ lm->axisNum ]; origin[ lm->axisNum ] -= d; } /* non axial lightmap projection (explicit xyz) */ else VectorCopy( dv->xyz, origin ); ////////////////////// //27's test to make sure samples stay within the triangle boundaries //1) Test the sample origin to see if it lays on the wrong side of any edge (x/y) //2) if it does, nudge it onto the correct side. if (worldverts!=NULL ) { for (j=0;j<3;j++) { VectorCopy(worldverts[j],cverts[j]); } PlaneFromPoints(hostplane,cverts[0],cverts[1],cverts[2]); if (noboundstest==qfalse) { for (j=0;j<3;j++) { for (i=0;i<3;i++) { //build plane using 2 edges and a normal next=(i+1)%3; VectorCopy(cverts[next],temp); VectorAdd(temp,hostplane,temp); PlaneFromPoints(sideplane,cverts[i],cverts[ next ], temp); //planetest sample point e=DotProduct(origin,sideplane); e=e-sideplane[3]; if (e>0) { //we're bad. //VectorClear(origin); //Move the sample point back inside triangle bounds origin[0]-=sideplane[0]*(e+1); origin[1]-=sideplane[1]*(e+1); origin[2]-=sideplane[2]*(e+1); #ifdef DEBUG_27_1 VectorClear(origin); #endif } } } } } //////////////////////// /* planar surfaces have precalculated lightmap vectors for nudging */ if( lm->plane != NULL ) { VectorCopy( lm->vecs[ 0 ], vecs[ 0 ] ); VectorCopy( lm->vecs[ 1 ], vecs[ 1 ] ); VectorCopy( lm->plane, vecs[ 2 ] ); } /* non-planar surfaces must calculate them */ else { if( plane != NULL ) VectorCopy( plane, vecs[ 2 ] ); else VectorCopy( dv->normal, vecs[ 2 ] ); MakeNormalVectors( vecs[ 2 ], vecs[ 0 ], vecs[ 1 ] ); } /* push the origin off the surface a bit */ if( si != NULL ) lightmapSampleOffset = si->lightmapSampleOffset; else lightmapSampleOffset = DEFAULT_LIGHTMAP_SAMPLE_OFFSET; if( lm->axisNum < 0 ) VectorMA( origin, lightmapSampleOffset, vecs[ 2 ], origin ); else if( vecs[ 2 ][ lm->axisNum ] < 0.0f ) origin[ lm->axisNum ] -= lightmapSampleOffset; else origin[ lm->axisNum ] += lightmapSampleOffset; VectorCopy(origin,origintwo); origintwo[0]+=vecs[2][0]; origintwo[1]+=vecs[2][1]; origintwo[2]+=vecs[2][2]; /* get cluster */ pointCluster = ClusterForPointExtFilter( origintwo, LUXEL_EPSILON, numClusters, clusters ); /* another retarded hack, storing nudge count in luxel[ 1 ] */ luxel[ 1 ] = 0.0f; /* point in solid? (except in dark mode) */ if( pointCluster < 0 && dark == qfalse ) { /* nudge the the location around */ nudge = nudges[ 0 ]; while( nudge[ 0 ] > BOGUS_NUDGE && pointCluster < 0 ) { /* nudge the vector around a bit */ for( i = 0; i < 3; i++ ) { /* set nudged point*/ nudged[ i ] = origintwo[ i ] + (nudge[ 0 ] * vecs[ 0 ][ i ]) + (nudge[ 1 ] * vecs[ 1 ][ i ]); } nudge += 2; /* get pvs cluster */ pointCluster = ClusterForPointExtFilter( nudged, LUXEL_EPSILON, numClusters, clusters ); //% + 0.625 ); //if( pointCluster >= 0 ) // VectorCopy( nudged, origin ); luxel[ 1 ] += 1.0f; } } /* as a last resort, if still in solid, try drawvert origin offset by normal (except in dark mode) */ if( pointCluster < 0 && si != NULL && dark == qfalse ) { VectorMA( dv->xyz, lightmapSampleOffset, dv->normal, nudged ); pointCluster = ClusterForPointExtFilter( nudged, LUXEL_EPSILON, numClusters, clusters ); //if( pointCluster >= 0 ) // VectorCopy( nudged, origin ); luxel[ 1 ] += 1.0f; } /* valid? */ if( pointCluster < 0 ) { (*cluster) = CLUSTER_OCCLUDED; VectorClear( origin ); VectorClear( normal ); numLuxelsOccluded++; return (*cluster); } /* debug code */ //% Sys_Printf( "%f %f %f\n", origin[ 0 ], origin[ 1 ], origin[ 2 ] ); /* do bumpmap calculations */ if( stv ) PerturbNormal( dv, si, pNormal, stv, ttv ); else VectorCopy( dv->normal, pNormal ); /* store the cluster and normal */ (*cluster) = pointCluster; VectorCopy( pNormal, normal ); /* store explicit mapping pass and implicit mapping pass */ luxel[ 0 ] = pass; luxel[ 3 ] = 1.0f; /* add to count */ numLuxelsMapped++; /* return ok */ return (*cluster); } /* MapTriangle_r() recursively subdivides a triangle until its edges are shorter than the distance between two luxels (thanks jc :) */ static void MapTriangle_r( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 3 ], vec4_t plane, vec3_t stv[ 3 ], vec3_t ttv[ 3 ], vec3_t worldverts[ 3 ] ) { bspDrawVert_t mid, *dv2[ 3 ]; int max; /* map the vertexes */ #if 0 MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv ); MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv ); MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv ); #endif /* subdivide calc */ { int i; float *a, *b, dx, dy, dist, maxDist; /* find the longest edge and split it */ max = -1; maxDist = 0; for( i = 0; i < 3; i++ ) { /* get verts */ a = dv[ i ]->lightmap[ 0 ]; b = dv[ (i + 1) % 3 ]->lightmap[ 0 ]; /* get dists */ dx = a[ 0 ] - b[ 0 ]; dy = a[ 1 ] - b[ 1 ]; dist = (dx * dx) + (dy * dy); //% sqrt( (dx * dx) + (dy * dy) ); /* longer? */ if( dist > maxDist ) { maxDist = dist; max = i; } } /* try to early out */ if( max < 0 || maxDist <= subdivideThreshold ) /* ydnar: was i < 0 instead of max < 0 (?) */ return; } /* split the longest edge and map it */ LerpDrawVert( dv[ max ], dv[ (max + 1) % 3 ], &mid ); MapSingleLuxel( lm, info, &mid, plane, 1, stv, ttv, worldverts ); /* push the point up a little bit to account for fp creep (fixme: revisit this) */ //% VectorMA( mid.xyz, 2.0f, mid.normal, mid.xyz ); /* recurse to first triangle */ VectorCopy( dv, dv2 ); dv2[ max ] = ∣ MapTriangle_r( lm, info, dv2, plane, stv, ttv, worldverts ); /* recurse to second triangle */ VectorCopy( dv, dv2 ); dv2[ (max + 1) % 3 ] = ∣ MapTriangle_r( lm, info, dv2, plane, stv, ttv, worldverts ); } /* MapTriangle() seed function for MapTriangle_r() requires a cw ordered triangle */ static qboolean MapTriangle( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 3 ], qboolean mapNonAxial ) { int i; vec4_t plane; vec3_t *stv, *ttv, stvStatic[ 3 ], ttvStatic[ 3 ]; vec3_t worldverts[ 3 ]; /* get plane if possible */ if( lm->plane != NULL ) { VectorCopy( lm->plane, plane ); plane[ 3 ] = lm->plane[ 3 ]; } /* otherwise make one from the points */ else if( PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) == qfalse ) return qfalse; /* check to see if we need to calculate texture->world tangent vectors */ if( (info->si->normalImage != NULL || radbump) && CalcTangentVectors( 3, dv, stvStatic, ttvStatic ) ) { stv = stvStatic; ttv = ttvStatic; } else { stv = NULL; ttv = NULL; } VectorCopy( dv[ 0 ]->xyz, worldverts[ 0 ] ); VectorCopy( dv[ 1 ]->xyz, worldverts[ 1 ] ); VectorCopy( dv[ 2 ]->xyz, worldverts[ 2 ] ); /* map the vertexes */ MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv, worldverts ); MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv, worldverts ); MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv, worldverts ); /* 2002-11-20: prefer axial triangle edges */ if( mapNonAxial ) { /* subdivide the triangle */ MapTriangle_r( lm, info, dv, plane, stv, ttv, worldverts ); return qtrue; } for( i = 0; i < 3; i++ ) { float *a, *b; bspDrawVert_t *dv2[ 3 ]; /* get verts */ a = dv[ i ]->lightmap[ 0 ]; b = dv[ (i + 1) % 3 ]->lightmap[ 0 ]; /* make degenerate triangles for mapping edges */ if( fabs( a[ 0 ] - b[ 0 ] ) < 0.01f || fabs( a[ 1 ] - b[ 1 ] ) < 0.01f ) { dv2[ 0 ] = dv[ i ]; dv2[ 1 ] = dv[ (i + 1) % 3 ]; dv2[ 2 ] = dv[ (i + 1) % 3 ]; /* map the degenerate triangle */ MapTriangle_r( lm, info, dv2, plane, stv, ttv, worldverts ); } } return qtrue; } /* MapQuad_r() recursively subdivides a quad until its edges are shorter than the distance between two luxels */ static void MapQuad_r( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 4 ], vec4_t plane, vec3_t stv[ 4 ], vec3_t ttv[ 4 ] ) { bspDrawVert_t mid[ 2 ], *dv2[ 4 ]; int max; /* subdivide calc */ { int i; float *a, *b, dx, dy, dist, maxDist; /* find the longest edge and split it */ max = -1; maxDist = 0; for( i = 0; i < 4; i++ ) { /* get verts */ a = dv[ i ]->lightmap[ 0 ]; b = dv[ (i + 1) % 4 ]->lightmap[ 0 ]; /* get dists */ dx = a[ 0 ] - b[ 0 ]; dy = a[ 1 ] - b[ 1 ]; dist = (dx * dx) + (dy * dy); //% sqrt( (dx * dx) + (dy * dy) ); /* longer? */ if( dist > maxDist ) { maxDist = dist; max = i; } } /* try to early out */ if( max < 0 || maxDist <= subdivideThreshold ) return; } /* we only care about even/odd edges */ max &= 1; /* split the longest edges */ LerpDrawVert( dv[ max ], dv[ (max + 1) % 4 ], &mid[ 0 ] ); LerpDrawVert( dv[ max + 2 ], dv[ (max + 3) % 4 ], &mid[ 1 ] ); /* map the vertexes */ MapSingleLuxel( lm, info, &mid[ 0 ], plane, 1, stv, ttv, NULL ); MapSingleLuxel( lm, info, &mid[ 1 ], plane, 1, stv, ttv, NULL ); /* 0 and 2 */ if( max == 0 ) { /* recurse to first quad */ dv2[ 0 ] = dv[ 0 ]; dv2[ 1 ] = &mid[ 0 ]; dv2[ 2 ] = &mid[ 1 ]; dv2[ 3 ] = dv[ 3 ]; MapQuad_r( lm, info, dv2, plane, stv, ttv ); /* recurse to second quad */ dv2[ 0 ] = &mid[ 0 ]; dv2[ 1 ] = dv[ 1 ]; dv2[ 2 ] = dv[ 2 ]; dv2[ 3 ] = &mid[ 1 ]; MapQuad_r( lm, info, dv2, plane, stv, ttv ); } /* 1 and 3 */ else { /* recurse to first quad */ dv2[ 0 ] = dv[ 0 ]; dv2[ 1 ] = dv[ 1 ]; dv2[ 2 ] = &mid[ 0 ]; dv2[ 3 ] = &mid[ 1 ]; MapQuad_r( lm, info, dv2, plane, stv, ttv ); /* recurse to second quad */ dv2[ 0 ] = &mid[ 1 ]; dv2[ 1 ] = &mid[ 0 ]; dv2[ 2 ] = dv[ 2 ]; dv2[ 3 ] = dv[ 3 ]; MapQuad_r( lm, info, dv2, plane, stv, ttv ); } } /* MapQuad() seed function for MapQuad_r() requires a cw ordered triangle quad */ #define QUAD_PLANAR_EPSILON 0.5f static qboolean MapQuad( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 4 ] ) { float dist; vec4_t plane; vec3_t *stv, *ttv, stvStatic[ 4 ], ttvStatic[ 4 ]; /* get plane if possible */ if( lm->plane != NULL ) { VectorCopy( lm->plane, plane ); plane[ 3 ] = lm->plane[ 3 ]; } /* otherwise make one from the points */ else if( PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) == qfalse ) return qfalse; /* 4th point must fall on the plane */ dist = DotProduct( plane, dv[ 3 ]->xyz ) - plane[ 3 ]; if( fabs( dist ) > QUAD_PLANAR_EPSILON ) return qfalse; /* check to see if we need to calculate texture->world tangent vectors */ if( (info->si->normalImage != NULL || radbump )&& CalcTangentVectors( 4, dv, stvStatic, ttvStatic ) ) { stv = stvStatic; ttv = ttvStatic; } else { stv = NULL; ttv = NULL; } /* map the vertexes */ MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv, NULL ); MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv, NULL ); MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv, NULL ); MapSingleLuxel( lm, info, dv[ 3 ], plane, 1, stv, ttv, NULL ); /* subdivide the quad */ MapQuad_r( lm, info, dv, plane, stv, ttv ); return qtrue; } /* MapRawLightmap() maps the locations, normals, and pvs clusters for a raw lightmap */ #define VectorDivide( in, d, out ) VectorScale( in, (1.0f / (d)), out ) //% (out)[ 0 ] = (in)[ 0 ] / (d), (out)[ 1 ] = (in)[ 1 ] / (d), (out)[ 2 ] = (in)[ 2 ] / (d) void MapRawLightmap( int rawLightmapNum ) { int n, num, i, x, y, sx, sy, pw[ 5 ], r, *cluster, mapNonAxial; float *luxel, *origin, *normal, samples, radius, pass; rawLightmap_t *lm; bspDrawSurface_t *ds; surfaceInfo_t *info; mesh_t src, *subdivided, *mesh; bspDrawVert_t *verts, *dv[ 4 ], fake; /* bail if this number exceeds the number of raw lightmaps */ if( rawLightmapNum >= numRawLightmaps ) return; /* get lightmap */ lm = &rawLightmaps[ rawLightmapNum ]; /* ----------------------------------------------------------------- map referenced surfaces onto the raw lightmap ----------------------------------------------------------------- */ /* walk the list of surfaces on this raw lightmap */ for( n = 0; n < lm->numLightSurfaces; n++ ) { /* with > 1 surface per raw lightmap, clear occluded */ if( n > 0 ) { for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get cluster */ cluster = SUPER_CLUSTER( x, y ); if( *cluster < 0 ) *cluster = CLUSTER_UNMAPPED; } } } /* get surface */ num = lightSurfaces[ lm->firstLightSurface + n ]; ds = &bspDrawSurfaces[ num ]; info = &surfaceInfos[ num ]; /* bail if no lightmap to calculate */ if( info->lm != lm ) { Sys_Printf( "!" ); continue; } /* map the surface onto the lightmap origin/cluster/normal buffers */ switch( ds->surfaceType ) { case MST_PLANAR: /* get verts */ verts = yDrawVerts + ds->firstVert; /* map the triangles */ for( mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++ ) { for( i = 0; i < ds->numIndexes; i += 3 ) { dv[ 0 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i ] ]; dv[ 1 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 1 ] ]; dv[ 2 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 2 ] ]; MapTriangle( lm, info, dv, mapNonAxial ); } } break; case MST_PATCH: /* make a mesh from the drawsurf */ src.width = ds->patchWidth; src.height = ds->patchHeight; src.verts = &yDrawVerts[ ds->firstVert ]; //% subdivided = SubdivideMesh( src, 8, 512 ); subdivided = SubdivideMesh2( src, info->patchIterations ); /* fit it to the curve and remove colinear verts on rows/columns */ PutMeshOnCurve( *subdivided ); mesh = RemoveLinearMeshColumnsRows( subdivided ); FreeMesh( subdivided ); /* get verts */ verts = mesh->verts; /* debug code */ #if 0 if( lm->plane ) { Sys_Printf( "Planar patch: [%1.3f %1.3f %1.3f] [%1.3f %1.3f %1.3f] [%1.3f %1.3f %1.3f]\n", lm->plane[ 0 ], lm->plane[ 1 ], lm->plane[ 2 ], lm->vecs[ 0 ][ 0 ], lm->vecs[ 0 ][ 1 ], lm->vecs[ 0 ][ 2 ], lm->vecs[ 1 ][ 0 ], lm->vecs[ 1 ][ 1 ], lm->vecs[ 1 ][ 2 ] ); } #endif /* map the mesh quads */ #if 0 for( mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++ ) { for( y = 0; y < (mesh->height - 1); y++ ) { for( x = 0; x < (mesh->width - 1); x++ ) { /* set indexes */ pw[ 0 ] = x + (y * mesh->width); pw[ 1 ] = x + ((y + 1) * mesh->width); pw[ 2 ] = x + 1 + ((y + 1) * mesh->width); pw[ 3 ] = x + 1 + (y * mesh->width); pw[ 4 ] = x + (y * mesh->width); /* same as pw[ 0 ] */ /* set radix */ r = (x + y) & 1; /* get drawverts and map first triangle */ dv[ 0 ] = &verts[ pw[ r + 0 ] ]; dv[ 1 ] = &verts[ pw[ r + 1 ] ]; dv[ 2 ] = &verts[ pw[ r + 2 ] ]; MapTriangle( lm, info, dv, mapNonAxial ); /* get drawverts and map second triangle */ dv[ 0 ] = &verts[ pw[ r + 0 ] ]; dv[ 1 ] = &verts[ pw[ r + 2 ] ]; dv[ 2 ] = &verts[ pw[ r + 3 ] ]; MapTriangle( lm, info, dv, mapNonAxial ); } } } #else for( y = 0; y < (mesh->height - 1); y++ ) { for( x = 0; x < (mesh->width - 1); x++ ) { /* set indexes */ pw[ 0 ] = x + (y * mesh->width); pw[ 1 ] = x + ((y + 1) * mesh->width); pw[ 2 ] = x + 1 + ((y + 1) * mesh->width); pw[ 3 ] = x + 1 + (y * mesh->width); pw[ 4 ] = pw[ 0 ]; /* set radix */ r = (x + y) & 1; /* attempt to map quad first */ dv[ 0 ] = &verts[ pw[ r + 0 ] ]; dv[ 1 ] = &verts[ pw[ r + 1 ] ]; dv[ 2 ] = &verts[ pw[ r + 2 ] ]; dv[ 3 ] = &verts[ pw[ r + 3 ] ]; if( MapQuad( lm, info, dv ) ) continue; /* get drawverts and map first triangle */ MapTriangle( lm, info, dv, mapNonAxial ); /* get drawverts and map second triangle */ dv[ 1 ] = &verts[ pw[ r + 2 ] ]; dv[ 2 ] = &verts[ pw[ r + 3 ] ]; MapTriangle( lm, info, dv, mapNonAxial ); } } #endif /* free the mesh */ FreeMesh( mesh ); break; default: break; } } /* ----------------------------------------------------------------- average and clean up luxel normals ----------------------------------------------------------------- */ /* walk the luxels */ for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get luxel */ luxel = SUPER_LUXEL( 0, x, y ); normal = SUPER_NORMAL( x, y ); cluster = SUPER_CLUSTER( x, y ); /* only look at mapped luxels */ if( *cluster < 0 ) continue; /* the normal data could be the sum of multiple samples */ if( luxel[ 3 ] > 1.0f ) VectorNormalize( normal, normal ); /* mark this luxel as having only one normal */ luxel[ 3 ] = 1.0f; } } /* non-planar surfaces stop here */ if( lm->plane == NULL ) return; /* ----------------------------------------------------------------- map occluded or unuxed luxels ----------------------------------------------------------------- */ /* walk the luxels */ radius = floor( superSample / 2 ); radius = radius > 0 ? radius : 1.0f; radius += 1.0f; for( pass = 2.0f; pass <= radius; pass += 1.0f ) { for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get luxel */ luxel = SUPER_LUXEL( 0, x, y ); normal = SUPER_NORMAL( x, y ); cluster = SUPER_CLUSTER( x, y ); /* only look at unmapped luxels */ if( *cluster != CLUSTER_UNMAPPED ) continue; /* divine a normal and origin from neighboring luxels */ VectorClear( fake.xyz ); VectorClear( fake.normal ); fake.lightmap[ 0 ][ 0 ] = x; //% 0.0001 + x; fake.lightmap[ 0 ][ 1 ] = y; //% 0.0001 + y; samples = 0.0f; for( sy = (y - 1); sy <= (y + 1); sy++ ) { if( sy < 0 || sy >= lm->sh ) continue; for( sx = (x - 1); sx <= (x + 1); sx++ ) { if( sx < 0 || sx >= lm->sw || (sx == x && sy == y) ) continue; /* get neighboring luxel */ luxel = SUPER_LUXEL( 0, sx, sy ); origin = SUPER_ORIGIN( sx, sy ); normal = SUPER_NORMAL( sx, sy ); cluster = SUPER_CLUSTER( sx, sy ); /* only consider luxels mapped in previous passes */ if( *cluster < 0 || luxel[ 0 ] >= pass ) continue; /* add its distinctiveness to our own */ VectorAdd( fake.xyz, origin, fake.xyz ); VectorAdd( fake.normal, normal, fake.normal ); samples += luxel[ 3 ]; } } /* any samples? */ if( samples == 0.0f ) continue; /* average */ VectorDivide( fake.xyz, samples, fake.xyz ); //% VectorDivide( fake.normal, samples, fake.normal ); if( VectorNormalize( fake.normal, fake.normal ) == 0.0f ) continue; /* map the fake vert */ MapSingleLuxel( lm, NULL, &fake, lm->plane, pass, NULL, NULL, NULL ); } } } /* ----------------------------------------------------------------- average and clean up luxel normals ----------------------------------------------------------------- */ /* walk the luxels */ for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get luxel */ luxel = SUPER_LUXEL( 0, x, y ); normal = SUPER_NORMAL( x, y ); cluster = SUPER_CLUSTER( x, y ); /* only look at mapped luxels */ if( *cluster < 0 ) continue; /* the normal data could be the sum of multiple samples */ if( luxel[ 3 ] > 1.0f ) VectorNormalize( normal, normal ); /* mark this luxel as having only one normal */ luxel[ 3 ] = 1.0f; } } /* debug code */ #if 0 Sys_Printf( "\n" ); for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { vec3_t mins, maxs; cluster = SUPER_CLUSTER( x, y ); origin = SUPER_ORIGIN( x, y ); normal = SUPER_NORMAL( x, y ); luxel = SUPER_LUXEL( x, y ); if( *cluster < 0 ) continue; /* check if within the bounding boxes of all surfaces referenced */ ClearBounds( mins, maxs ); for( n = 0; n < lm->numLightSurfaces; n++ ) { int TOL; info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + n ] ]; TOL = info->sampleSize + 2; AddPointToBounds( info->mins, mins, maxs ); AddPointToBounds( info->maxs, mins, maxs ); if( origin[ 0 ] > (info->mins[ 0 ] - TOL) && origin[ 0 ] < (info->maxs[ 0 ] + TOL) && origin[ 1 ] > (info->mins[ 1 ] - TOL) && origin[ 1 ] < (info->maxs[ 1 ] + TOL) && origin[ 2 ] > (info->mins[ 2 ] - TOL) && origin[ 2 ] < (info->maxs[ 2 ] + TOL) ) break; } /* inside? */ if( n < lm->numLightSurfaces ) continue; /* report bogus origin */ Sys_Printf( "%6d [%2d,%2d] (%4d): XYZ(%+4.1f %+4.1f %+4.1f) LO(%+4.1f %+4.1f %+4.1f) HI(%+4.1f %+4.1f %+4.1f) <%3.0f>\n", rawLightmapNum, x, y, *cluster, origin[ 0 ], origin[ 1 ], origin[ 2 ], mins[ 0 ], mins[ 1 ], mins[ 2 ], maxs[ 0 ], maxs[ 1 ], maxs[ 2 ], luxel[ 3 ] ); } } #endif } /* SetupDirt() sets up dirtmap (ambient occlusion) */ #define DIRT_CONE_ANGLE 88 /* degrees */ #define DIRT_NUM_ANGLE_STEPS 16 #define DIRT_NUM_ELEVATION_STEPS 3 #define DIRT_NUM_VECTORS (DIRT_NUM_ANGLE_STEPS * DIRT_NUM_ELEVATION_STEPS) static vec3_t dirtVectors[ DIRT_NUM_VECTORS ]; static int numDirtVectors = 0; void SetupDirt( void ) { int i, j; float angle, elevation, angleStep, elevationStep; /* note it */ Sys_FPrintf( SYS_VRB, "--- SetupDirt ---\n" ); /* calculate angular steps */ angleStep = DEG2RAD( 360.0f / DIRT_NUM_ANGLE_STEPS ); elevationStep = DEG2RAD( DIRT_CONE_ANGLE / DIRT_NUM_ELEVATION_STEPS ); /* iterate angle */ angle = 0.0f; for( i = 0, angle = 0.0f; i < DIRT_NUM_ANGLE_STEPS; i++, angle += angleStep ) { /* iterate elevation */ for( j = 0, elevation = elevationStep * 0.5f; j < DIRT_NUM_ELEVATION_STEPS; j++, elevation += elevationStep ) { dirtVectors[ numDirtVectors ][ 0 ] = sin( elevation ) * cos( angle ); dirtVectors[ numDirtVectors ][ 1 ] = sin( elevation ) * sin( angle ); dirtVectors[ numDirtVectors ][ 2 ] = cos( elevation ); numDirtVectors++; } } /* emit some statistics */ Sys_FPrintf( SYS_VRB, "%9d dirtmap vectors\n", numDirtVectors ); } /* DirtForSample() calculates dirt value for a given sample */ float DirtForSample( trace_t *trace ) { int i; float gatherDirt, outDirt, angle, elevation, ooDepth; vec3_t normal, worldUp, myUp, myRt, temp, direction, displacement; /* dummy check */ if( !dirty ) return 1.0f; if( trace == NULL || trace->cluster < 0 ) return 0.0f; /* setup */ gatherDirt = 0.0f; ooDepth = 1.0f / dirtDepth; VectorCopy( trace->normal, normal ); /* check if the normal is aligned to the world-up */ if( normal[ 0 ] == 0.0f && normal[ 1 ] == 0.0f ) { if( normal[ 2 ] == 1.0f ) { VectorSet( myRt, 1.0f, 0.0f, 0.0f ); VectorSet( myUp, 0.0f, 1.0f, 0.0f ); } else if( normal[ 2 ] == -1.0f ) { VectorSet( myRt, -1.0f, 0.0f, 0.0f ); VectorSet( myUp, 0.0f, 1.0f, 0.0f ); } } else { VectorSet( worldUp, 0.0f, 0.0f, 1.0f ); CrossProduct( normal, worldUp, myRt ); VectorNormalize( myRt, myRt ); CrossProduct( myRt, normal, myUp ); VectorNormalize( myUp, myUp ); } /* 1 = random mode, 0 (well everything else) = non-random mode */ if( dirtMode == 1 ) { /* iterate */ for( i = 0; i < numDirtVectors; i++ ) { /* get random vector */ angle = Random() * DEG2RAD( 360.0f ); elevation = Random() * DEG2RAD( DIRT_CONE_ANGLE ); temp[ 0 ] = cos( angle ) * sin( elevation ); temp[ 1 ] = sin( angle ) * sin( elevation ); temp[ 2 ] = cos( elevation ); /* transform into tangent space */ direction[ 0 ] = myRt[ 0 ] * temp[ 0 ] + myUp[ 0 ] * temp[ 1 ] + normal[ 0 ] * temp[ 2 ]; direction[ 1 ] = myRt[ 1 ] * temp[ 0 ] + myUp[ 1 ] * temp[ 1 ] + normal[ 1 ] * temp[ 2 ]; direction[ 2 ] = myRt[ 2 ] * temp[ 0 ] + myUp[ 2 ] * temp[ 1 ] + normal[ 2 ] * temp[ 2 ]; /* set endpoint */ VectorMA( trace->origin, dirtDepth, direction, trace->end ); SetupTrace( trace ); /* trace */ TraceLine( trace ); if( trace->opaque ) { VectorSubtract( trace->hit, trace->origin, displacement ); gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); } } } else { /* iterate through ordered vectors */ for( i = 0; i < numDirtVectors; i++ ) { /* transform vector into tangent space */ direction[ 0 ] = myRt[ 0 ] * dirtVectors[ i ][ 0 ] + myUp[ 0 ] * dirtVectors[ i ][ 1 ] + normal[ 0 ] * dirtVectors[ i ][ 2 ]; direction[ 1 ] = myRt[ 1 ] * dirtVectors[ i ][ 0 ] + myUp[ 1 ] * dirtVectors[ i ][ 1 ] + normal[ 1 ] * dirtVectors[ i ][ 2 ]; direction[ 2 ] = myRt[ 2 ] * dirtVectors[ i ][ 0 ] + myUp[ 2 ] * dirtVectors[ i ][ 1 ] + normal[ 2 ] * dirtVectors[ i ][ 2 ]; /* set endpoint */ VectorMA( trace->origin, dirtDepth, direction, trace->end ); SetupTrace( trace ); /* trace */ TraceLine( trace ); if( trace->opaque ) { VectorSubtract( trace->hit, trace->origin, displacement ); gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); } } } /* direct ray */ VectorMA( trace->origin, dirtDepth, normal, trace->end ); SetupTrace( trace ); /* trace */ TraceLine( trace ); if( trace->opaque ) { VectorSubtract( trace->hit, trace->origin, displacement ); gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); } /* early out */ if( gatherDirt <= 0.0f ) return 1.0f; /* apply gain (does this even do much? heh) */ outDirt = pow( gatherDirt / (numDirtVectors + 1), dirtGain ); if( outDirt > 1.0f ) outDirt = 1.0f; /* apply scale */ outDirt *= dirtScale; if( outDirt > 1.0f ) outDirt = 1.0f; /* return to sender */ return 1.0f - outDirt; } /* DirtyRawLightmap() calculates dirty fraction for each luxel */ void DirtyRawLightmap( int rawLightmapNum ) { int i, x, y, sx, sy, *cluster; float *origin, *normal, *dirt, *dirt2, average, samples; rawLightmap_t *lm; surfaceInfo_t *info; trace_t trace; /* bail if this number exceeds the number of raw lightmaps */ if( rawLightmapNum >= numRawLightmaps ) return; /* get lightmap */ lm = &rawLightmaps[ rawLightmapNum ]; /* setup trace */ trace.testOcclusion = qtrue; trace.forceSunlight = qfalse; trace.recvShadows = lm->recvShadows; trace.numSurfaces = lm->numLightSurfaces; trace.surfaces = &lightSurfaces[ lm->firstLightSurface ]; trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS; trace.testAll = qfalse; /* twosided lighting (may or may not be a good idea for lightmapped stuff) */ trace.twoSided = qfalse; for( i = 0; i < trace.numSurfaces; i++ ) { /* get surface */ info = &surfaceInfos[ trace.surfaces[ i ] ]; /* check twosidedness */ if( info->si->twoSided ) { trace.twoSided = qtrue; break; } } /* gather dirt */ for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get luxel */ cluster = SUPER_CLUSTER( x, y ); origin = SUPER_ORIGIN( x, y ); normal = SUPER_NORMAL( x, y ); dirt = SUPER_DIRT( x, y ); /* set default dirt */ *dirt = 0.0f; /* only look at mapped luxels */ if( *cluster < 0 ) continue; /* copy to trace */ trace.cluster = *cluster; VectorCopy( origin, trace.origin ); VectorCopy( normal, trace.normal ); /* get dirt */ *dirt = DirtForSample( &trace ); } } /* testing no filtering */ //% return; /* filter dirt */ for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get luxel */ cluster = SUPER_CLUSTER( x, y ); dirt = SUPER_DIRT( x, y ); /* filter dirt by adjacency to unmapped luxels */ average = *dirt; samples = 1.0f; for( sy = (y - 1); sy <= (y + 1); sy++ ) { if( sy < 0 || sy >= lm->sh ) continue; for( sx = (x - 1); sx <= (x + 1); sx++ ) { if( sx < 0 || sx >= lm->sw || (sx == x && sy == y) ) continue; /* get neighboring luxel */ cluster = SUPER_CLUSTER( sx, sy ); dirt2 = SUPER_DIRT( sx, sy ); if( *cluster < 0 || *dirt2 <= 0.0f ) continue; /* add it */ average += *dirt2; samples += 1.0f; } /* bail */ if( samples <= 0.0f ) break; } /* bail */ if( samples <= 0.0f ) continue; /* scale dirt */ *dirt = average / samples; } } } /* SubmapRawLuxel() calculates the pvs cluster, origin, normal of a sub-luxel */ static qboolean SubmapRawLuxel( rawLightmap_t *lm, int x, int y, float bx, float by, int *sampleCluster, vec3_t sampleOrigin, vec3_t sampleNormal ) { int i, *cluster, *cluster2; float *origin, *origin2, *normal; //% , *normal2; vec3_t originVecs[ 2 ]; //% , normalVecs[ 2 ]; /* calulate x vector */ if( (x < (lm->sw - 1) && bx >= 0.0f) || (x == 0 && bx <= 0.0f) ) { cluster = SUPER_CLUSTER( x, y ); origin = SUPER_ORIGIN( x, y ); //% normal = SUPER_NORMAL( x, y ); cluster2 = SUPER_CLUSTER( x + 1, y ); origin2 = *cluster2 < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x + 1, y ); //% normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x + 1, y ); } else if( (x > 0 && bx <= 0.0f) || (x == (lm->sw - 1) && bx >= 0.0f) ) { cluster = SUPER_CLUSTER( x - 1, y ); origin = *cluster < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x - 1, y ); //% normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x - 1, y ); cluster2 = SUPER_CLUSTER( x, y ); origin2 = SUPER_ORIGIN( x, y ); //% normal2 = SUPER_NORMAL( x, y ); } else Sys_Printf( "WARNING: Spurious lightmap S vector\n" ); VectorSubtract( origin2, origin, originVecs[ 0 ] ); //% VectorSubtract( normal2, normal, normalVecs[ 0 ] ); /* calulate y vector */ if( (y < (lm->sh - 1) && bx >= 0.0f) || (y == 0 && bx <= 0.0f) ) { cluster = SUPER_CLUSTER( x, y ); origin = SUPER_ORIGIN( x, y ); //% normal = SUPER_NORMAL( x, y ); cluster2 = SUPER_CLUSTER( x, y + 1 ); origin2 = *cluster2 < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x, y + 1 ); //% normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y + 1 ); } else if( (y > 0 && bx <= 0.0f) || (y == (lm->sh - 1) && bx >= 0.0f) ) { cluster = SUPER_CLUSTER( x, y - 1 ); origin = *cluster < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x, y - 1 ); //% normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y - 1 ); cluster2 = SUPER_CLUSTER( x, y ); origin2 = SUPER_ORIGIN( x, y ); //% normal2 = SUPER_NORMAL( x, y ); } else Sys_Printf( "WARNING: Spurious lightmap T vector\n" ); VectorSubtract( origin2, origin, originVecs[ 1 ] ); //% VectorSubtract( normal2, normal, normalVecs[ 1 ] ); /* calculate new origin */ //% VectorMA( origin, bx, originVecs[ 0 ], sampleOrigin ); //% VectorMA( sampleOrigin, by, originVecs[ 1 ], sampleOrigin ); for( i = 0; i < 3; i++ ) sampleOrigin[ i ] = sampleOrigin[ i ] + (bx * originVecs[ 0 ][ i ]) + (by * originVecs[ 1 ][ i ]); /* get cluster */ *sampleCluster = ClusterForPointExtFilter( sampleOrigin, (LUXEL_EPSILON * 2), lm->numLightClusters, lm->lightClusters ); if( *sampleCluster < 0 ) return qfalse; /* calculate new normal */ //% VectorMA( normal, bx, normalVecs[ 0 ], sampleNormal ); //% VectorMA( sampleNormal, by, normalVecs[ 1 ], sampleNormal ); //% if( VectorNormalize( sampleNormal, sampleNormal ) <= 0.0f ) //% return qfalse; normal = SUPER_NORMAL( x, y ); VectorCopy( normal, sampleNormal ); /* return ok */ return qtrue; } /* SubsampleRawLuxel_r() recursively subsamples a luxel until its color gradient is low enough or subsampling limit is reached */ static void SubsampleRawLuxel_r( rawLightmap_t *lm, trace_t *trace, vec3_t sampleOrigin, int x, int y, float bias, float *lightLuxel ) { int b, samples, mapped, lighted; int cluster[ 4 ]; vec4_t luxel[ 4 ]; vec3_t origin[ 4 ], normal[ 4 ]; float biasDirs[ 4 ][ 2 ] = { { -1.0f, -1.0f }, { 1.0f, -1.0f }, { -1.0f, 1.0f }, { 1.0f, 1.0f } }; vec3_t color, total; /* limit check */ if( lightLuxel[ 3 ] >= lightSamples ) return; /* setup */ VectorClear( total ); mapped = 0; lighted = 0; /* make 2x2 subsample stamp */ for( b = 0; b < 4; b++ ) { /* set origin */ VectorCopy( sampleOrigin, origin[ b ] ); /* calculate position */ if( !SubmapRawLuxel( lm, x, y, (bias * biasDirs[ b ][ 0 ]), (bias * biasDirs[ b ][ 1 ]), &cluster[ b ], origin[ b ], normal[ b ] ) ) { cluster[ b ] = -1; continue; } mapped++; /* increment sample count */ luxel[ b ][ 3 ] = lightLuxel[ 3 ] + 1.0f; /* setup trace */ trace->cluster = *cluster; VectorCopy( origin[ b ], trace->origin ); VectorCopy( normal[ b ], trace->normal ); /* sample light */ LightContributionToSample( trace ); /* add to totals (fixme: make contrast function) */ VectorCopy( trace->color, luxel[ b ] ); VectorAdd( total, trace->color, total ); if( (luxel[ b ][ 0 ] + luxel[ b ][ 1 ] + luxel[ b ][ 2 ]) > 0.0f ) lighted++; } /* subsample further? */ if( (lightLuxel[ 3 ] + 1.0f) < lightSamples && (total[ 0 ] > 4.0f || total[ 1 ] > 4.0f || total[ 2 ] > 4.0f) && lighted != 0 && lighted != mapped ) { for( b = 0; b < 4; b++ ) { if( cluster[ b ] < 0 ) continue; SubsampleRawLuxel_r( lm, trace, origin[ b ], x, y, (bias * 0.25f), luxel[ b ] ); } } /* average */ //% VectorClear( color ); //% samples = 0; VectorCopy( lightLuxel, color ); samples = 1; for( b = 0; b < 4; b++ ) { if( cluster[ b ] < 0 ) continue; VectorAdd( color, luxel[ b ], color ); samples++; } /* add to luxel */ if( samples > 0 ) { /* average */ color[ 0 ] /= samples; color[ 1 ] /= samples; color[ 2 ] /= samples; /* add to color */ VectorCopy( color, lightLuxel ); lightLuxel[ 3 ] += 1.0f; } } /* IlluminateRawLightmap() illuminates the luxels */ #define STACK_LL_SIZE (SUPER_LUXEL_SIZE * 64 * 64) #define LIGHT_LUXEL( x, y ) (lightLuxels + ((((y) * lm->sw) + (x)) * SUPER_LUXEL_SIZE)) void IlluminateRawLightmap( int rawLightmapNum ) { int i, t, x, y, sx, sy, size, llSize, luxelFilterRadius, lightmapNum; int *cluster, *cluster2, mapped, lighted, totalLighted; rawLightmap_t *lm; surfaceInfo_t *info; qboolean filterColor, filterDir; float brightness; float *origin, *normal, *dirt, *luxel, *luxel2, *deluxel, *deluxel2; float *lightLuxels, *lightLuxel, samples, filterRadius, weight; vec3_t color, averageColor, averageDir, total, temp, temp2; float tests[ 4 ][ 2 ] = { { 0.0f, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }; trace_t trace; float stackLightLuxels[ STACK_LL_SIZE ]; vec3_t flood; float *floodlight; /* bail if this number exceeds the number of raw lightmaps */ if( rawLightmapNum >= numRawLightmaps ) return; /* get lightmap */ lm = &rawLightmaps[ rawLightmapNum ]; /* setup trace */ trace.testOcclusion = !noTrace; trace.forceSunlight = qfalse; trace.recvShadows = lm->recvShadows; trace.numSurfaces = lm->numLightSurfaces; trace.surfaces = &lightSurfaces[ lm->firstLightSurface ]; trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS; /* twosided lighting (may or may not be a good idea for lightmapped stuff) */ trace.twoSided = qfalse; for( i = 0; i < trace.numSurfaces; i++ ) { /* get surface */ info = &surfaceInfos[ trace.surfaces[ i ] ]; /* check twosidedness */ if( info->si->twoSided ) { trace.twoSided = qtrue; break; } } /* create a culled light list for this raw lightmap */ CreateTraceLightsForBounds( lm->mins, lm->maxs, lm->plane, lm->numLightClusters, lm->lightClusters, LIGHT_SURFACES, &trace ); /* ----------------------------------------------------------------- fill pass ----------------------------------------------------------------- */ /* set counts */ numLuxelsIlluminated += (lm->sw * lm->sh); /* test debugging state */ if( debugSurfaces || debugAxis || debugCluster || debugOrigin || dirtDebug || normalmap ) { /* debug fill the luxels */ for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get cluster */ cluster = SUPER_CLUSTER( x, y ); /* only fill mapped luxels */ if( *cluster < 0 ) continue; /* get particulars */ luxel = SUPER_LUXEL( 0, x, y ); origin = SUPER_ORIGIN( x, y ); normal = SUPER_NORMAL( x, y ); /* color the luxel with raw lightmap num? */ if( debugSurfaces ) VectorCopy( debugColors[ rawLightmapNum % 12 ], luxel ); /* color the luxel with lightmap axis? */ else if( debugAxis ) { luxel[ 0 ] = (lm->axis[ 0 ] + 1.0f) * 127.5f; luxel[ 1 ] = (lm->axis[ 1 ] + 1.0f) * 127.5f; luxel[ 2 ] = (lm->axis[ 2 ] + 1.0f) * 127.5f; } /* color the luxel with luxel cluster? */ else if( debugCluster ) VectorCopy( debugColors[ *cluster % 12 ], luxel ); /* color the luxel with luxel origin? */ else if( debugOrigin ) { VectorSubtract( lm->maxs, lm->mins, temp ); VectorScale( temp, (1.0f / 255.0f), temp ); VectorSubtract( origin, lm->mins, temp2 ); luxel[ 0 ] = lm->mins[ 0 ] + (temp[ 0 ] * temp2[ 0 ]); luxel[ 1 ] = lm->mins[ 1 ] + (temp[ 1 ] * temp2[ 1 ]); luxel[ 2 ] = lm->mins[ 2 ] + (temp[ 2 ] * temp2[ 2 ]); } /* color the luxel with the normal */ else if( normalmap ) { luxel[ 0 ] = (normal[ 0 ] + 1.0f) * 127.5f; luxel[ 1 ] = (normal[ 1 ] + 1.0f) * 127.5f; luxel[ 2 ] = (normal[ 2 ] + 1.0f) * 127.5f; } /* otherwise clear it */ else VectorClear( luxel ); /* add to counts */ luxel[ 3 ] = 1.0f; } } } else { /* allocate temporary per-light luxel storage */ llSize = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float ); if( llSize <= (STACK_LL_SIZE * sizeof( float )) ) lightLuxels = stackLightLuxels; else lightLuxels = safe_malloc( llSize ); /* clear luxels */ //% memset( lm->superLuxels[ 0 ], 0, llSize ); /* set ambient color */ for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get cluster */ cluster = SUPER_CLUSTER( x, y ); luxel = SUPER_LUXEL( 0, x, y ); normal = SUPER_NORMAL( x, y ); deluxel = SUPER_DELUXEL( x, y ); /* blacken unmapped clusters */ if( *cluster < 0 ) VectorClear( luxel ); /* set ambient */ else { VectorCopy( ambientColor, luxel ); if( deluxemap ) VectorScale( normal, 0.00390625f, deluxel ); luxel[ 3 ] = 1.0f; } } } /* clear styled lightmaps */ size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float ); for( lightmapNum = 1; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) { if( lm->superLuxels[ lightmapNum ] != NULL ) memset( lm->superLuxels[ lightmapNum ], 0, size ); } /* debugging code */ //% if( trace.numLights <= 0 ) //% Sys_Printf( "Lightmap %9d: 0 lights, axis: %.2f, %.2f, %.2f\n", rawLightmapNum, lm->axis[ 0 ], lm->axis[ 1 ], lm->axis[ 2 ] ); /* walk light list */ for( i = 0; i < trace.numLights; i++ ) { /* setup trace */ trace.light = trace.lights[ i ]; /* style check */ for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) { if( lm->styles[ lightmapNum ] == trace.light->style || lm->styles[ lightmapNum ] == LS_NONE ) break; } /* max of MAX_LIGHTMAPS (4) styles allowed to hit a surface/lightmap */ if( lightmapNum >= MAX_LIGHTMAPS ) { Sys_Printf( "WARNING: Hit per-surface style limit (%d)\n", MAX_LIGHTMAPS ); continue; } /* setup */ memset( lightLuxels, 0, llSize ); totalLighted = 0; /* initial pass, one sample per luxel */ for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get cluster */ cluster = SUPER_CLUSTER( x, y ); if( *cluster < 0 ) continue; /* get particulars */ lightLuxel = LIGHT_LUXEL( x, y ); deluxel = SUPER_DELUXEL( x, y ); origin = SUPER_ORIGIN( x, y ); normal = SUPER_NORMAL( x, y ); ////////// 27's temp hack for testing edge clipping //// if( origin[0]==0 && origin[1]==0 && origin[2]==0 ) { lightLuxel[ 1 ] = 255; lightLuxel[ 3 ] = 1.0f; totalLighted++; } else { /* set contribution count */ lightLuxel[ 3 ] = 1.0f; /* setup trace */ trace.cluster = *cluster; VectorCopy( origin, trace.origin ); VectorCopy( normal, trace.normal ); /* get light for this sample */ LightContributionToSample( &trace ); VectorCopy( trace.color, lightLuxel ); /* add to count */ if( trace.color[ 0 ] || trace.color[ 1 ] || trace.color[ 2 ] ) totalLighted++; } /* add to light direction map (fixme: use luxel normal as starting point for deluxel?) */ if( deluxemap ) { /* color to grayscale (photoshop rgb weighting) */ brightness = trace.color[ 0 ] * 0.3f + trace.color[ 1 ] * 0.59f + trace.color[ 2 ] * 0.11f; brightness *= (1.0 / 255.0); VectorScale( trace.direction, brightness, trace.direction ); VectorAdd( deluxel, trace.direction, deluxel ); } } } /* don't even bother with everything else if nothing was lit */ if( totalLighted == 0 ) continue; /* determine filter radius */ filterRadius = lm->filterRadius > trace.light->filterRadius ? lm->filterRadius : trace.light->filterRadius; if( filterRadius < 0.0f ) filterRadius = 0.0f; /* set luxel filter radius */ luxelFilterRadius = superSample * filterRadius / lm->sampleSize; if( luxelFilterRadius == 0 && (filterRadius > 0.0f || filter) ) luxelFilterRadius = 1; /* secondary pass, adaptive supersampling (fixme: use a contrast function to determine if subsampling is necessary) */ /* 2003-09-27: changed it so filtering disamples supersampling, as it would waste time */ if( lightSamples > 1 && luxelFilterRadius == 0 ) { /* walk luxels */ for( y = 0; y < (lm->sh - 1); y++ ) { for( x = 0; x < (lm->sw - 1); x++ ) { /* setup */ mapped = 0; lighted = 0; VectorClear( total ); /* test 2x2 stamp */ for( t = 0; t < 4; t++ ) { /* set sample coords */ sx = x + tests[ t ][ 0 ]; sy = y + tests[ t ][ 1 ]; /* get cluster */ cluster = SUPER_CLUSTER( sx, sy ); if( *cluster < 0 ) continue; mapped++; /* get luxel */ lightLuxel = LIGHT_LUXEL( sx, sy ); VectorAdd( total, lightLuxel, total ); if( (lightLuxel[ 0 ] + lightLuxel[ 1 ] + lightLuxel[ 2 ]) > 0.0f ) lighted++; } /* if total color is under a certain amount, then don't bother subsampling */ if( total[ 0 ] <= 4.0f && total[ 1 ] <= 4.0f && total[ 2 ] <= 4.0f ) continue; /* if all 4 pixels are either in shadow or light, then don't subsample */ if( lighted != 0 && lighted != mapped ) { for( t = 0; t < 4; t++ ) { /* set sample coords */ sx = x + tests[ t ][ 0 ]; sy = y + tests[ t ][ 1 ]; /* get luxel */ cluster = SUPER_CLUSTER( sx, sy ); if( *cluster < 0 ) continue; lightLuxel = LIGHT_LUXEL( sx, sy ); origin = SUPER_ORIGIN( sx, sy ); /* only subsample shadowed luxels */ //% if( (lightLuxel[ 0 ] + lightLuxel[ 1 ] + lightLuxel[ 2 ]) <= 0.0f ) //% continue; /* subsample it */ SubsampleRawLuxel_r( lm, &trace, origin, sx, sy, 0.25f, lightLuxel ); /* debug code to colorize subsampled areas to yellow */ //% luxel = SUPER_LUXEL( lightmapNum, sx, sy ); //% VectorSet( luxel, 255, 204, 0 ); } } } } } /* tertiary pass, apply dirt map (ambient occlusion) */ if( 0 && dirty ) { /* walk luxels */ for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get cluster */ cluster = SUPER_CLUSTER( x, y ); if( *cluster < 0 ) continue; /* get particulars */ lightLuxel = LIGHT_LUXEL( x, y ); dirt = SUPER_DIRT( x, y ); /* scale light value */ VectorScale( lightLuxel, *dirt, lightLuxel ); } } } /* allocate sampling lightmap storage */ if( lm->superLuxels[ lightmapNum ] == NULL ) { /* allocate sampling lightmap storage */ size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float ); lm->superLuxels[ lightmapNum ] = safe_malloc( size ); memset( lm->superLuxels[ lightmapNum ], 0, size ); } /* set style */ if( lightmapNum > 0 ) { lm->styles[ lightmapNum ] = trace.light->style; //% Sys_Printf( "Surface %6d has lightstyle %d\n", rawLightmapNum, trace.light->style ); } /* copy to permanent luxels */ for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get cluster and origin */ cluster = SUPER_CLUSTER( x, y ); if( *cluster < 0 ) continue; origin = SUPER_ORIGIN( x, y ); /* filter? */ if( luxelFilterRadius ) { /* setup */ VectorClear( averageColor ); samples = 0.0f; /* cheaper distance-based filtering */ for( sy = (y - luxelFilterRadius); sy <= (y + luxelFilterRadius); sy++ ) { if( sy < 0 || sy >= lm->sh ) continue; for( sx = (x - luxelFilterRadius); sx <= (x + luxelFilterRadius); sx++ ) { if( sx < 0 || sx >= lm->sw ) continue; /* get particulars */ cluster = SUPER_CLUSTER( sx, sy ); if( *cluster < 0 ) continue; lightLuxel = LIGHT_LUXEL( sx, sy ); /* create weight */ weight = (abs( sx - x ) == luxelFilterRadius ? 0.5f : 1.0f); weight *= (abs( sy - y ) == luxelFilterRadius ? 0.5f : 1.0f); /* scale luxel by filter weight */ VectorScale( lightLuxel, weight, color ); VectorAdd( averageColor, color, averageColor ); samples += weight; } } /* any samples? */ if( samples <= 0.0f ) continue; /* scale into luxel */ luxel = SUPER_LUXEL( lightmapNum, x, y ); luxel[ 3 ] = 1.0f; /* handle negative light */ if( trace.light->flags & LIGHT_NEGATIVE ) { luxel[ 0 ] -= averageColor[ 0 ] / samples; luxel[ 1 ] -= averageColor[ 1 ] / samples; luxel[ 2 ] -= averageColor[ 2 ] / samples; } /* handle normal light */ else { luxel[ 0 ] += averageColor[ 0 ] / samples; luxel[ 1 ] += averageColor[ 1 ] / samples; luxel[ 2 ] += averageColor[ 2 ] / samples; } } /* single sample */ else { /* get particulars */ lightLuxel = LIGHT_LUXEL( x, y ); luxel = SUPER_LUXEL( lightmapNum, x, y ); /* handle negative light */ if( trace.light->flags & LIGHT_NEGATIVE ) VectorScale( averageColor, -1.0f, averageColor ); /* add color */ luxel[ 3 ] = 1.0f; /* handle negative light */ if( trace.light->flags & LIGHT_NEGATIVE ) VectorSubtract( luxel, lightLuxel, luxel ); /* handle normal light */ else VectorAdd( luxel, lightLuxel, luxel ); } } } } /* free temporary luxels */ if( lightLuxels != stackLightLuxels ) free( lightLuxels ); } /* free light list */ FreeTraceLights( &trace ); /* ----------------------------------------------------------------- floodlight pass ----------------------------------------------------------------- */ if( floodlighty ) { /* walk lightmaps */ for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) { /* early out */ if( lm->superLuxels[ lightmapNum ] == NULL ) continue; /* apply floodlight to each luxel */ for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get cluster */ cluster = SUPER_CLUSTER( x, y ); //% if( *cluster < 0 ) //% continue; /* get particulars */ luxel = SUPER_LUXEL( lightmapNum, x, y ); floodlight = SUPER_FLOODLIGHT( x, y ); flood[0]=floodlightRGB[0]*floodlightIntensity; flood[1]=floodlightRGB[1]*floodlightIntensity; flood[2]=floodlightRGB[2]*floodlightIntensity; /* scale light value */ VectorScale( flood, *floodlight, flood ); luxel[0]+=flood[0]; luxel[1]+=flood[1]; luxel[2]+=flood[2]; if (luxel[3]==0) luxel[3]=1; } } } } if (debugnormals) { for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) { /* early out */ if( lm->superLuxels[ lightmapNum ] == NULL ) continue; for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get cluster */ cluster = SUPER_CLUSTER( x, y ); //% if( *cluster < 0 ) //% continue; /* get particulars */ luxel = SUPER_LUXEL( lightmapNum, x, y ); normal = SUPER_NORMAL ( x, y ); luxel[0]=(normal[0]*127)+127; luxel[1]=(normal[1]*127)+127; luxel[2]=(normal[2]*127)+127; } } } } /* ----------------------------------------------------------------- dirt pass ----------------------------------------------------------------- */ if( dirty ) { /* walk lightmaps */ for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) { /* early out */ if( lm->superLuxels[ lightmapNum ] == NULL ) continue; /* apply dirt to each luxel */ for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get cluster */ cluster = SUPER_CLUSTER( x, y ); //% if( *cluster < 0 ) //% continue; /* get particulars */ luxel = SUPER_LUXEL( lightmapNum, x, y ); dirt = SUPER_DIRT( x, y ); /* apply dirt */ VectorScale( luxel, *dirt, luxel ); /* debugging */ if( dirtDebug ) VectorSet( luxel, *dirt * 255.0f, *dirt * 255.0f, *dirt * 255.0f ); } } } } /* ----------------------------------------------------------------- filter pass ----------------------------------------------------------------- */ /* walk lightmaps */ for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) { /* early out */ if( lm->superLuxels[ lightmapNum ] == NULL ) continue; /* average occluded luxels from neighbors */ for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get particulars */ cluster = SUPER_CLUSTER( x, y ); luxel = SUPER_LUXEL( lightmapNum, x, y ); deluxel = SUPER_DELUXEL( x, y ); normal = SUPER_NORMAL( x, y ); /* determine if filtering is necessary */ filterColor = qfalse; filterDir = qfalse; if( *cluster < 0 || (lm->splotchFix && (luxel[ 0 ] <= ambientColor[ 0 ] || luxel[ 1 ] <= ambientColor[ 1 ] || luxel[ 2 ] <= ambientColor[ 2 ])) ) filterColor = qtrue; if( deluxemap && lightmapNum == 0 && (*cluster < 0 || filter) ) filterDir = qtrue; if( !filterColor && !filterDir ) continue; /* choose seed amount */ VectorClear( averageColor ); VectorClear( averageDir ); samples = 0.0f; /* walk 3x3 matrix */ for( sy = (y - 1); sy <= (y + 1); sy++ ) { if( sy < 0 || sy >= lm->sh ) continue; for( sx = (x - 1); sx <= (x + 1); sx++ ) { if( sx < 0 || sx >= lm->sw || (sx == x && sy == y) ) continue; /* get neighbor's particulars */ cluster2 = SUPER_CLUSTER( sx, sy ); luxel2 = SUPER_LUXEL( lightmapNum, sx, sy ); deluxel2 = SUPER_DELUXEL( sx, sy ); /* ignore unmapped/unlit luxels */ if( *cluster2 < 0 || luxel2[ 3 ] == 0.0f || (lm->splotchFix && VectorCompare( luxel2, ambientColor )) ) continue; /* add its distinctiveness to our own */ VectorAdd( averageColor, luxel2, averageColor ); samples += luxel2[ 3 ]; if( filterDir ) VectorAdd( averageDir, deluxel2, averageDir ); } } /* fall through */ if( samples <= 0.0f ) continue; /* dark lightmap seams */ if( dark ) { if( lightmapNum == 0 ) VectorMA( averageColor, 2.0f, ambientColor, averageColor ); samples += 2.0f; } /* average it */ if( filterColor ) { VectorDivide( averageColor, samples, luxel ); luxel[ 3 ] = 1.0f; } if( filterDir ) VectorDivide( averageDir, samples, deluxel ); /* set cluster to -3 */ if( *cluster < 0 ) *cluster = CLUSTER_FLOODED; } } } } /* IlluminateVertexes() light the surface vertexes */ #define VERTEX_NUDGE 4.0f void IlluminateVertexes( int num ) { int i, x, y, z, x1, y1, z1, sx, sy, radius, maxRadius, *cluster; int lightmapNum, numAvg; float samples, *vertLuxel, *radVertLuxel, *luxel, dirt; vec3_t origin, temp, temp2, colors[ MAX_LIGHTMAPS ], avgColors[ MAX_LIGHTMAPS ]; bspDrawSurface_t *ds; surfaceInfo_t *info; rawLightmap_t *lm; bspDrawVert_t *verts; trace_t trace; /* get surface, info, and raw lightmap */ ds = &bspDrawSurfaces[ num ]; info = &surfaceInfos[ num ]; lm = info->lm; /* ----------------------------------------------------------------- illuminate the vertexes ----------------------------------------------------------------- */ /* calculate vertex lighting for surfaces without lightmaps */ if( lm == NULL || cpmaHack ) { /* setup trace */ trace.testOcclusion = (cpmaHack && lm != NULL) ? qfalse : !noTrace; trace.forceSunlight = info->si->forceSunlight; trace.recvShadows = info->recvShadows; trace.numSurfaces = 1; trace.surfaces = # trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS; /* twosided lighting */ trace.twoSided = info->si->twoSided; /* make light list for this surface */ CreateTraceLightsForSurface( num, &trace ); /* setup */ verts = yDrawVerts + ds->firstVert; numAvg = 0; memset( avgColors, 0, sizeof( avgColors ) ); /* walk the surface verts */ for( i = 0; i < ds->numVerts; i++ ) { /* get vertex luxel */ radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); /* color the luxel with raw lightmap num? */ if( debugSurfaces ) VectorCopy( debugColors[ num % 12 ], radVertLuxel ); /* color the luxel with luxel origin? */ else if( debugOrigin ) { VectorSubtract( info->maxs, info->mins, temp ); VectorScale( temp, (1.0f / 255.0f), temp ); VectorSubtract( origin, lm->mins, temp2 ); radVertLuxel[ 0 ] = info->mins[ 0 ] + (temp[ 0 ] * temp2[ 0 ]); radVertLuxel[ 1 ] = info->mins[ 1 ] + (temp[ 1 ] * temp2[ 1 ]); radVertLuxel[ 2 ] = info->mins[ 2 ] + (temp[ 2 ] * temp2[ 2 ]); } /* color the luxel with the normal */ else if( normalmap ) { radVertLuxel[ 0 ] = (verts[ i ].normal[ 0 ] + 1.0f) * 127.5f; radVertLuxel[ 1 ] = (verts[ i ].normal[ 1 ] + 1.0f) * 127.5f; radVertLuxel[ 2 ] = (verts[ i ].normal[ 2 ] + 1.0f) * 127.5f; } /* illuminate the vertex */ else { /* clear vertex luxel */ VectorSet( radVertLuxel, -1.0f, -1.0f, -1.0f ); /* try at initial origin */ trace.cluster = ClusterForPointExtFilter( verts[ i ].xyz, VERTEX_EPSILON, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ] ); if( trace.cluster >= 0 ) { /* setup trace */ VectorCopy( verts[ i ].xyz, trace.origin ); VectorCopy( verts[ i ].normal, trace.normal ); /* r7 dirt */ if( dirty ) dirt = DirtForSample( &trace ); else dirt = 1.0f; /* trace */ LightingAtSample( &trace, ds->vertexStyles, colors ); /* store */ for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) { /* r7 dirt */ VectorScale( colors[ lightmapNum ], dirt, colors[ lightmapNum ] ); /* store */ radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); VectorCopy( colors[ lightmapNum ], radVertLuxel ); VectorAdd( avgColors[ lightmapNum ], colors[ lightmapNum ], colors[ lightmapNum ] ); } } /* is this sample bright enough? */ radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); if( radVertLuxel[ 0 ] <= ambientColor[ 0 ] && radVertLuxel[ 1 ] <= ambientColor[ 1 ] && radVertLuxel[ 2 ] <= ambientColor[ 2 ] ) { /* nudge the sample point around a bit */ for( x = 0; x < 4; x++ ) { /* two's complement 0, 1, -1, 2, -2, etc */ x1 = ((x >> 1) ^ (x & 1 ? -1 : 0)) + (x & 1); for( y = 0; y < 4; y++ ) { y1 = ((y >> 1) ^ (y & 1 ? -1 : 0)) + (y & 1); for( z = 0; z < 4; z++ ) { z1 = ((z >> 1) ^ (z & 1 ? -1 : 0)) + (z & 1); /* nudge origin */ trace.origin[ 0 ] = verts[ i ].xyz[ 0 ] + (VERTEX_NUDGE * x1); trace.origin[ 1 ] = verts[ i ].xyz[ 1 ] + (VERTEX_NUDGE * y1); trace.origin[ 2 ] = verts[ i ].xyz[ 2 ] + (VERTEX_NUDGE * z1); /* try at nudged origin */ trace.cluster = ClusterForPointExtFilter( origin, VERTEX_EPSILON, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ] ); if( trace.cluster < 0 ) continue; /* trace */ LightingAtSample( &trace, ds->vertexStyles, colors ); /* store */ for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) { /* r7 dirt */ VectorScale( colors[ lightmapNum ], dirt, colors[ lightmapNum ] ); /* store */ radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); VectorCopy( colors[ lightmapNum ], radVertLuxel ); } /* bright enough? */ radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); if( radVertLuxel[ 0 ] > ambientColor[ 0 ] || radVertLuxel[ 1 ] > ambientColor[ 1 ] || radVertLuxel[ 2 ] > ambientColor[ 2 ] ) x = y = z = 1000; } } } } /* add to average? */ radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); if( radVertLuxel[ 0 ] > ambientColor[ 0 ] || radVertLuxel[ 1 ] > ambientColor[ 1 ] || radVertLuxel[ 2 ] > ambientColor[ 2 ] ) { numAvg++; for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) { radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); VectorAdd( avgColors[ lightmapNum ], radVertLuxel, avgColors[ lightmapNum ] ); } } } /* another happy customer */ numVertsIlluminated++; } /* set average color */ if( numAvg > 0 ) { for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) VectorScale( avgColors[ lightmapNum ], (1.0f / numAvg), avgColors[ lightmapNum ] ); } else { VectorCopy( ambientColor, avgColors[ 0 ] ); } /* clean up and store vertex color */ for( i = 0; i < ds->numVerts; i++ ) { /* get vertex luxel */ radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); /* store average in occluded vertexes */ if( radVertLuxel[ 0 ] < 0.0f ) { for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) { radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); VectorCopy( avgColors[ lightmapNum ], radVertLuxel ); /* debug code */ //% VectorSet( radVertLuxel, 255.0f, 0.0f, 0.0f ); } } /* store it */ for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) { /* get luxels */ vertLuxel = VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); /* store */ if( bouncing || bounce == 0 || !bounceOnly ) VectorAdd( vertLuxel, radVertLuxel, vertLuxel ); if( !info->si->noVertexLight ) ColorToBytes( vertLuxel, verts[ i ].color[ lightmapNum ], info->si->vertexScale ); } } /* free light list */ FreeTraceLights( &trace ); /* return to sender */ return; } /* ----------------------------------------------------------------- reconstitute vertex lighting from the luxels ----------------------------------------------------------------- */ /* set styles from lightmap */ for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) ds->vertexStyles[ lightmapNum ] = lm->styles[ lightmapNum ]; /* get max search radius */ maxRadius = lm->sw; maxRadius = maxRadius > lm->sh ? maxRadius : lm->sh; /* walk the surface verts */ verts = yDrawVerts + ds->firstVert; for( i = 0; i < ds->numVerts; i++ ) { /* do each lightmap */ for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) { /* early out */ if( lm->superLuxels[ lightmapNum ] == NULL ) continue; /* get luxel coords */ x = verts[ i ].lightmap[ lightmapNum ][ 0 ]; y = verts[ i ].lightmap[ lightmapNum ][ 1 ]; if( x < 0 ) x = 0; else if( x >= lm->sw ) x = lm->sw - 1; if( y < 0 ) y = 0; else if( y >= lm->sh ) y = lm->sh - 1; /* get vertex luxels */ vertLuxel = VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); /* color the luxel with the normal? */ if( normalmap ) { radVertLuxel[ 0 ] = (verts[ i ].normal[ 0 ] + 1.0f) * 127.5f; radVertLuxel[ 1 ] = (verts[ i ].normal[ 1 ] + 1.0f) * 127.5f; radVertLuxel[ 2 ] = (verts[ i ].normal[ 2 ] + 1.0f) * 127.5f; } /* color the luxel with surface num? */ else if( debugSurfaces ) VectorCopy( debugColors[ num % 12 ], radVertLuxel ); /* divine color from the superluxels */ else { /* increasing radius */ VectorClear( radVertLuxel ); samples = 0.0f; for( radius = 0; radius < maxRadius && samples <= 0.0f; radius++ ) { /* sample within radius */ for( sy = (y - radius); sy <= (y + radius); sy++ ) { if( sy < 0 || sy >= lm->sh ) continue; for( sx = (x - radius); sx <= (x + radius); sx++ ) { if( sx < 0 || sx >= lm->sw ) continue; /* get luxel particulars */ luxel = SUPER_LUXEL( lightmapNum, sx, sy ); cluster = SUPER_CLUSTER( sx, sy ); if( *cluster < 0 ) continue; /* testing: must be brigher than ambient color */ //% if( luxel[ 0 ] <= ambientColor[ 0 ] || luxel[ 1 ] <= ambientColor[ 1 ] || luxel[ 2 ] <= ambientColor[ 2 ] ) //% continue; /* add its distinctiveness to our own */ VectorAdd( radVertLuxel, luxel, radVertLuxel ); samples += luxel[ 3 ]; } } } /* any color? */ if( samples > 0.0f ) VectorDivide( radVertLuxel, samples, radVertLuxel ); else VectorCopy( ambientColor, radVertLuxel ); } /* store into floating point storage */ VectorAdd( vertLuxel, radVertLuxel, vertLuxel ); numVertsIlluminated++; /* store into bytes (for vertex approximation) */ if( !info->si->noVertexLight ) ColorToBytes( vertLuxel, verts[ i ].color[ lightmapNum ], 1.0f ); } } } /* ------------------------------------------------------------------------------- light optimization (-fast) creates a list of lights that will affect a surface and stores it in tw this is to optimize surface lighting by culling out as many of the lights in the world as possible from further calculation ------------------------------------------------------------------------------- */ /* SetupBrushes() determines opaque brushes in the world and find sky shaders for sunlight calculations */ void SetupBrushes( void ) { int i, j, b, compileFlags; qboolean inside; bspBrush_t *brush; bspBrushSide_t *side; bspShader_t *shader; shaderInfo_t *si; /* note it */ Sys_FPrintf( SYS_VRB, "--- SetupBrushes ---\n" ); /* allocate */ if( opaqueBrushes == NULL ) opaqueBrushes = safe_malloc( numBSPBrushes / 8 + 1 ); /* clear */ memset( opaqueBrushes, 0, numBSPBrushes / 8 + 1 ); numOpaqueBrushes = 0; /* walk the list of worldspawn brushes */ for( i = 0; i < bspModels[ 0 ].numBSPBrushes; i++ ) { /* get brush */ b = bspModels[ 0 ].firstBSPBrush + i; brush = &bspBrushes[ b ]; /* check all sides */ inside = qtrue; compileFlags = 0; for( j = 0; j < brush->numSides && inside; j++ ) { /* do bsp shader calculations */ side = &bspBrushSides[ brush->firstSide + j ]; shader = &bspShaders[ side->shaderNum ]; /* get shader info */ si = ShaderInfoForShader( shader->shader ); if( si == NULL ) continue; /* or together compile flags */ compileFlags |= si->compileFlags; } /* determine if this brush is opaque to light */ if( !(compileFlags & C_TRANSLUCENT) ) { opaqueBrushes[ b >> 3 ] |= (1 << (b & 7)); numOpaqueBrushes++; maxOpaqueBrush = i; } } /* emit some statistics */ Sys_FPrintf( SYS_VRB, "%9d opaque brushes\n", numOpaqueBrushes ); } /* ClusterVisible() determines if two clusters are visible to each other using the PVS */ qboolean ClusterVisible( int a, int b ) { int portalClusters, leafBytes; byte *pvs; /* dummy check */ if( a < 0 || b < 0 ) return qfalse; /* early out */ if( a == b ) return qtrue; /* not vised? */ if( numBSPVisBytes <=8 ) return qtrue; /* get pvs data */ portalClusters = ((int *) bspVisBytes)[ 0 ]; leafBytes = ((int*) bspVisBytes)[ 1 ]; pvs = bspVisBytes + VIS_HEADER_SIZE + (a * leafBytes); /* check */ if( (pvs[ b >> 3 ] & (1 << (b & 7))) ) return qtrue; return qfalse; } /* PointInLeafNum_r() borrowed from vlight.c */ int PointInLeafNum_r( vec3_t point, int nodenum ) { int leafnum; vec_t dist; bspNode_t *node; bspPlane_t *plane; while( nodenum >= 0 ) { node = &bspNodes[ nodenum ]; plane = &bspPlanes[ node->planeNum ]; dist = DotProduct( point, plane->normal ) - plane->dist; if( dist > 0.1 ) nodenum = node->children[ 0 ]; else if( dist < -0.1 ) nodenum = node->children[ 1 ]; else { leafnum = PointInLeafNum_r( point, node->children[ 0 ] ); if( bspLeafs[ leafnum ].cluster != -1 ) return leafnum; nodenum = node->children[ 1 ]; } } leafnum = -nodenum - 1; return leafnum; } /* PointInLeafnum() borrowed from vlight.c */ int PointInLeafNum( vec3_t point ) { return PointInLeafNum_r( point, 0 ); } /* ClusterVisibleToPoint() - ydnar returns qtrue if point can "see" cluster */ qboolean ClusterVisibleToPoint( vec3_t point, int cluster ) { int pointCluster; /* get leafNum for point */ pointCluster = ClusterForPoint( point ); if( pointCluster < 0 ) return qfalse; /* check pvs */ return ClusterVisible( pointCluster, cluster ); } /* ClusterForPoint() - ydnar returns the pvs cluster for point */ int ClusterForPoint( vec3_t point ) { int leafNum; /* get leafNum for point */ leafNum = PointInLeafNum( point ); if( leafNum < 0 ) return -1; /* return the cluster */ return bspLeafs[ leafNum ].cluster; } /* ClusterForPointExt() - ydnar also takes brushes into account for occlusion testing */ int ClusterForPointExt( vec3_t point, float epsilon ) { int i, j, b, leafNum, cluster; float dot; qboolean inside; int *brushes, numBSPBrushes; bspLeaf_t *leaf; bspBrush_t *brush; bspPlane_t *plane; /* get leaf for point */ leafNum = PointInLeafNum( point ); if( leafNum < 0 ) return -1; leaf = &bspLeafs[ leafNum ]; /* get the cluster */ cluster = leaf->cluster; if( cluster < 0 ) return -1; /* transparent leaf, so check point against all brushes in the leaf */ brushes = &bspLeafBrushes[ leaf->firstBSPLeafBrush ]; numBSPBrushes = leaf->numBSPLeafBrushes; for( i = 0; i < numBSPBrushes; i++ ) { /* get parts */ b = brushes[ i ]; if( b > maxOpaqueBrush ) continue; brush = &bspBrushes[ b ]; if( !(opaqueBrushes[ b >> 3 ] & (1 << (b & 7))) ) continue; /* check point against all planes */ inside = qtrue; for( j = 0; j < brush->numSides && inside; j++ ) { plane = &bspPlanes[ bspBrushSides[ brush->firstSide + j ].planeNum ]; dot = DotProduct( point, plane->normal ); dot -= plane->dist; if( dot > epsilon ) inside = qfalse; } /* if inside, return bogus cluster */ if( inside ) return -1 - b; } /* if the point made it this far, it's not inside any opaque brushes */ return cluster; } /* ClusterForPointExtFilter() - ydnar adds cluster checking against a list of known valid clusters */ int ClusterForPointExtFilter( vec3_t point, float epsilon, int numClusters, int *clusters ) { int i, cluster; /* get cluster for point */ cluster = ClusterForPointExt( point, epsilon ); /* check if filtering is necessary */ if( cluster < 0 || numClusters <= 0 || clusters == NULL ) return cluster; /* filter */ for( i = 0; i < numClusters; i++ ) { if( cluster == clusters[ i ] || ClusterVisible( cluster, clusters[ i ] ) ) return cluster; } /* failed */ return -1; } /* ShaderForPointInLeaf() - ydnar checks a point against all brushes in a leaf, returning the shader of the brush also sets the cumulative surface and content flags for the brush hit */ int ShaderForPointInLeaf( vec3_t point, int leafNum, float epsilon, int wantContentFlags, int wantSurfaceFlags, int *contentFlags, int *surfaceFlags ) { int i, j; float dot; qboolean inside; int *brushes, numBSPBrushes; bspLeaf_t *leaf; bspBrush_t *brush; bspBrushSide_t *side; bspPlane_t *plane; bspShader_t *shader; int allSurfaceFlags, allContentFlags; /* clear things out first */ *surfaceFlags = 0; *contentFlags = 0; /* get leaf */ if( leafNum < 0 ) return -1; leaf = &bspLeafs[ leafNum ]; /* transparent leaf, so check point against all brushes in the leaf */ brushes = &bspLeafBrushes[ leaf->firstBSPLeafBrush ]; numBSPBrushes = leaf->numBSPLeafBrushes; for( i = 0; i < numBSPBrushes; i++ ) { /* get parts */ brush = &bspBrushes[ brushes[ i ] ]; /* check point against all planes */ inside = qtrue; allSurfaceFlags = 0; allContentFlags = 0; for( j = 0; j < brush->numSides && inside; j++ ) { side = &bspBrushSides[ brush->firstSide + j ]; plane = &bspPlanes[ side->planeNum ]; dot = DotProduct( point, plane->normal ); dot -= plane->dist; if( dot > epsilon ) inside = qfalse; else { shader = &bspShaders[ side->shaderNum ]; allSurfaceFlags |= shader->surfaceFlags; allContentFlags |= shader->contentFlags; } } /* handle if inside */ if( inside ) { /* if there are desired flags, check for same and continue if they aren't matched */ if( wantContentFlags && !(wantContentFlags & allContentFlags) ) continue; if( wantSurfaceFlags && !(wantSurfaceFlags & allSurfaceFlags) ) continue; /* store the cumulative flags and return the brush shader (which is mostly useless) */ *surfaceFlags = allSurfaceFlags; *contentFlags = allContentFlags; return brush->shaderNum; } } /* if the point made it this far, it's not inside any brushes */ return -1; } /* ChopBounds() chops a bounding box by the plane defined by origin and normal returns qfalse if the bounds is entirely clipped away this is not exactly the fastest way to do this... */ qboolean ChopBounds( vec3_t mins, vec3_t maxs, vec3_t origin, vec3_t normal ) { /* FIXME: rewrite this so it doesn't use bloody brushes */ return qtrue; } /* SetupEnvelopes() calculates each light's effective envelope, taking into account brightness, type, and pvs. */ #define LIGHT_EPSILON 0.125f #define LIGHT_NUDGE 2.0f void SetupEnvelopes( qboolean forGrid, qboolean fastFlag ) { int i, x, y, z, x1, y1, z1; light_t *light, *light2, **owner; bspLeaf_t *leaf; vec3_t origin, dir, mins, maxs; float radius, intensity; light_t *buckets[ 256 ]; /* early out for weird cases where there are no lights */ if( lights == NULL ) return; /* note it */ Sys_FPrintf( SYS_VRB, "--- SetupEnvelopes%s ---\n", fastFlag ? " (fast)" : "" ); /* count lights */ numLights = 0; numCulledLights = 0; owner = &lights; while( *owner != NULL ) { /* get light */ light = *owner; /* handle negative lights */ if( light->photons < 0.0f || light->add < 0.0f ) { light->photons *= -1.0f; light->add *= -1.0f; light->flags |= LIGHT_NEGATIVE; } /* sunlight? */ if( light->type == EMIT_SUN ) { /* special cased */ light->cluster = 0; light->envelope = MAX_WORLD_COORD * 8.0f; VectorSet( light->mins, MIN_WORLD_COORD * 8.0f, MIN_WORLD_COORD * 8.0f, MIN_WORLD_COORD * 8.0f ); VectorSet( light->maxs, MAX_WORLD_COORD * 8.0f, MAX_WORLD_COORD * 8.0f, MAX_WORLD_COORD * 8.0f ); } /* everything else */ else { /* get pvs cluster for light */ light->cluster = ClusterForPointExt( light->origin, LIGHT_EPSILON ); /* invalid cluster? */ if( light->cluster < 0 ) { /* nudge the sample point around a bit */ for( x = 0; x < 4; x++ ) { /* two's complement 0, 1, -1, 2, -2, etc */ x1 = ((x >> 1) ^ (x & 1 ? -1 : 0)) + (x & 1); for( y = 0; y < 4; y++ ) { y1 = ((y >> 1) ^ (y & 1 ? -1 : 0)) + (y & 1); for( z = 0; z < 4; z++ ) { z1 = ((z >> 1) ^ (z & 1 ? -1 : 0)) + (z & 1); /* nudge origin */ origin[ 0 ] = light->origin[ 0 ] + (LIGHT_NUDGE * x1); origin[ 1 ] = light->origin[ 1 ] + (LIGHT_NUDGE * y1); origin[ 2 ] = light->origin[ 2 ] + (LIGHT_NUDGE * z1); /* try at nudged origin */ light->cluster = ClusterForPointExt( origin, LIGHT_EPSILON ); if( light->cluster < 0 ) continue; /* set origin */ VectorCopy( origin, light->origin ); } } } } /* only calculate for lights in pvs and outside of opaque brushes */ if( light->cluster >= 0 ) { /* set light fast flag */ if( fastFlag ) light->flags |= LIGHT_FAST_TEMP; else light->flags &= ~LIGHT_FAST_TEMP; if( light->si && light->si->noFast ) light->flags &= ~(LIGHT_FAST | LIGHT_FAST_TEMP); /* clear light envelope */ light->envelope = 0; /* handle area lights */ if( exactPointToPolygon && light->type == EMIT_AREA && light->w != NULL ) { /* ugly hack to calculate extent for area lights, but only done once */ VectorScale( light->normal, -1.0f, dir ); for( radius = 100.0f; radius < 130000.0f && light->envelope == 0; radius += 10.0f ) { float factor; VectorMA( light->origin, radius, light->normal, origin ); factor = PointToPolygonFormFactor( origin, dir, light->w ); if( factor < 0.0f ) factor *= -1.0f; if( (factor * light->add) <= light->falloffTolerance ) light->envelope = radius; } /* check for fast mode */ if( !(light->flags & LIGHT_FAST) && !(light->flags & LIGHT_FAST_TEMP) ) light->envelope = MAX_WORLD_COORD * 8.0f; } else { radius = 0.0f; intensity = light->photons; } /* other calcs */ if( light->envelope <= 0.0f ) { /* solve distance for non-distance lights */ if( !(light->flags & LIGHT_ATTEN_DISTANCE) ) light->envelope = MAX_WORLD_COORD * 8.0f; /* solve distance for linear lights */ else if( (light->flags & LIGHT_ATTEN_LINEAR ) ) //% light->envelope = ((intensity / light->falloffTolerance) * linearScale - 1 + radius) / light->fade; light->envelope = ((intensity * linearScale) - light->falloffTolerance) / light->fade; /* add = angle * light->photons * linearScale - (dist * light->fade); T = (light->photons * linearScale) - (dist * light->fade); T + (dist * light->fade) = (light->photons * linearScale); dist * light->fade = (light->photons * linearScale) - T; dist = ((light->photons * linearScale) - T) / light->fade; */ /* solve for inverse square falloff */ else light->envelope = sqrt( intensity / light->falloffTolerance ) + radius; /* add = light->photons / (dist * dist); T = light->photons / (dist * dist); T * (dist * dist) = light->photons; dist = sqrt( light->photons / T ); */ } /* chop radius against pvs */ { /* clear bounds */ ClearBounds( mins, maxs ); /* check all leaves */ for( i = 0; i < numBSPLeafs; i++ ) { /* get test leaf */ leaf = &bspLeafs[ i ]; /* in pvs? */ if( leaf->cluster < 0 ) continue; if( ClusterVisible( light->cluster, leaf->cluster ) == qfalse ) /* ydnar: thanks Arnout for exposing my stupid error (this never failed before) */ continue; /* add this leafs bbox to the bounds */ VectorCopy( leaf->mins, origin ); AddPointToBounds( origin, mins, maxs ); VectorCopy( leaf->maxs, origin ); AddPointToBounds( origin, mins, maxs ); } /* test to see if bounds encompass light */ for( i = 0; i < 3; i++ ) { if( mins[ i ] > light->origin[ i ] || maxs[ i ] < light->origin[ i ] ) { //% Sys_Printf( "WARNING: Light PVS bounds (%.0f, %.0f, %.0f) -> (%.0f, %.0f, %.0f)\ndo not encompass light %d (%f, %f, %f)\n", //% mins[ 0 ], mins[ 1 ], mins[ 2 ], //% maxs[ 0 ], maxs[ 1 ], maxs[ 2 ], //% numLights, light->origin[ 0 ], light->origin[ 1 ], light->origin[ 2 ] ); AddPointToBounds( light->origin, mins, maxs ); } } /* chop the bounds by a plane for area lights and spotlights */ if( light->type == EMIT_AREA || light->type == EMIT_SPOT ) ChopBounds( mins, maxs, light->origin, light->normal ); /* copy bounds */ VectorCopy( mins, light->mins ); VectorCopy( maxs, light->maxs ); /* reflect bounds around light origin */ //% VectorMA( light->origin, -1.0f, origin, origin ); VectorScale( light->origin, 2, origin ); VectorSubtract( origin, maxs, origin ); AddPointToBounds( origin, mins, maxs ); //% VectorMA( light->origin, -1.0f, mins, origin ); VectorScale( light->origin, 2, origin ); VectorSubtract( origin, mins, origin ); AddPointToBounds( origin, mins, maxs ); /* calculate spherical bounds */ VectorSubtract( maxs, light->origin, dir ); radius = (float) VectorLength( dir ); /* if this radius is smaller than the envelope, then set the envelope to it */ if( radius < light->envelope ) { light->envelope = radius; //% Sys_FPrintf( SYS_VRB, "PVS Cull (%d): culled\n", numLights ); } //% else //% Sys_FPrintf( SYS_VRB, "PVS Cull (%d): failed (%8.0f > %8.0f)\n", numLights, radius, light->envelope ); } /* add grid/surface only check */ if( forGrid ) { if( !(light->flags & LIGHT_GRID) ) light->envelope = 0.0f; } else { if( !(light->flags & LIGHT_SURFACES) ) light->envelope = 0.0f; } } /* culled? */ if( light->cluster < 0 || light->envelope <= 0.0f ) { /* debug code */ //% Sys_Printf( "Culling light: Cluster: %d Envelope: %f\n", light->cluster, light->envelope ); /* delete the light */ numCulledLights++; *owner = light->next; if( light->w != NULL ) free( light->w ); free( light ); continue; } } /* square envelope */ light->envelope2 = (light->envelope * light->envelope); /* increment light count */ numLights++; /* set next light */ owner = &((**owner).next); } /* bucket sort lights by style */ memset( buckets, 0, sizeof( buckets ) ); light2 = NULL; for( light = lights; light != NULL; light = light2 ) { /* get next light */ light2 = light->next; /* filter into correct bucket */ light->next = buckets[ light->style ]; buckets[ light->style ] = light; /* if any styled light is present, automatically set nocollapse */ if( light->style != LS_NORMAL ) noCollapse = qtrue; } /* filter back into light list */ lights = NULL; for( i = 255; i >= 0; i-- ) { light2 = NULL; for( light = buckets[ i ]; light != NULL; light = light2 ) { light2 = light->next; light->next = lights; lights = light; } } /* emit some statistics */ Sys_Printf( "%9d total lights\n", numLights ); Sys_Printf( "%9d culled lights\n", numCulledLights ); } /* CreateTraceLightsForBounds() creates a list of lights that affect the given bounding box and pvs clusters (bsp leaves) */ void CreateTraceLightsForBounds( vec3_t mins, vec3_t maxs, vec3_t normal, int numClusters, int *clusters, int flags, trace_t *trace ) { int i; light_t *light; vec3_t origin, dir, nullVector = { 0.0f, 0.0f, 0.0f }; float radius, dist, length; /* potential pre-setup */ if( numLights == 0 ) SetupEnvelopes( qfalse, fast ); /* debug code */ //% Sys_Printf( "CTWLFB: (%4.1f %4.1f %4.1f) (%4.1f %4.1f %4.1f)\n", mins[ 0 ], mins[ 1 ], mins[ 2 ], maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] ); /* allocate the light list */ trace->lights = safe_malloc( sizeof( light_t* ) * (numLights + 1) ); trace->numLights = 0; /* calculate spherical bounds */ VectorAdd( mins, maxs, origin ); VectorScale( origin, 0.5f, origin ); VectorSubtract( maxs, origin, dir ); radius = (float) VectorLength( dir ); /* get length of normal vector */ if( normal != NULL ) length = VectorLength( normal ); else { normal = nullVector; length = 0; } /* test each light and see if it reaches the sphere */ /* note: the attenuation code MUST match LightingAtSample() */ for( light = lights; light; light = light->next ) { /* check zero sized envelope */ if( light->envelope <= 0 ) { lightsEnvelopeCulled++; continue; } /* check flags */ if( !(light->flags & flags) ) continue; /* sunlight skips all this nonsense */ if( light->type != EMIT_SUN ) { /* sun only? */ if( sunOnly ) continue; /* check against pvs cluster */ if( numClusters > 0 && clusters != NULL ) { for( i = 0; i < numClusters; i++ ) { if( ClusterVisible( light->cluster, clusters[ i ] ) ) break; } /* fixme! */ if( i == numClusters ) { lightsClusterCulled++; continue; } } /* if the light's bounding sphere intersects with the bounding sphere then this light needs to be tested */ VectorSubtract( light->origin, origin, dir ); dist = VectorLength( dir ); dist -= light->envelope; dist -= radius; if( dist > 0 ) { lightsEnvelopeCulled++; continue; } /* check bounding box against light's pvs envelope (note: this code never eliminated any lights, so disabling it) */ #if 0 skip = qfalse; for( i = 0; i < 3; i++ ) { if( mins[ i ] > light->maxs[ i ] || maxs[ i ] < light->mins[ i ] ) skip = qtrue; } if( skip ) { lightsBoundsCulled++; continue; } #endif } /* planar surfaces (except twosided surfaces) have a couple more checks */ if( length > 0.0f && trace->twoSided == qfalse ) { /* lights coplanar with a surface won't light it */ if( !(light->flags & LIGHT_TWOSIDED) && DotProduct( light->normal, normal ) > 0.999f ) { lightsPlaneCulled++; continue; } /* check to see if light is behind the plane */ if( DotProduct( light->origin, normal ) - DotProduct( origin, normal ) < -1.0f ) { lightsPlaneCulled++; continue; } } /* add this light */ trace->lights[ trace->numLights++ ] = light; } /* make last night null */ trace->lights[ trace->numLights ] = NULL; } void FreeTraceLights( trace_t *trace ) { if( trace->lights != NULL ) free( trace->lights ); } /* CreateTraceLightsForSurface() creates a list of lights that can potentially affect a drawsurface */ void CreateTraceLightsForSurface( int num, trace_t *trace ) { int i; vec3_t mins, maxs, normal; bspDrawVert_t *dv; bspDrawSurface_t *ds; surfaceInfo_t *info; /* dummy check */ if( num < 0 ) return; /* get drawsurface and info */ ds = &bspDrawSurfaces[ num ]; info = &surfaceInfos[ num ]; /* get the mins/maxs for the dsurf */ ClearBounds( mins, maxs ); VectorCopy( bspDrawVerts[ ds->firstVert ].normal, normal ); for( i = 0; i < ds->numVerts; i++ ) { dv = &yDrawVerts[ ds->firstVert + i ]; AddPointToBounds( dv->xyz, mins, maxs ); if( !VectorCompare( dv->normal, normal ) ) VectorClear( normal ); } /* create the lights for the bounding box */ CreateTraceLightsForBounds( mins, maxs, normal, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ], LIGHT_SURFACES, trace ); } ///////////////////////////////////////////////////////////// #define FLOODLIGHT_CONE_ANGLE 88 /* degrees */ #define FLOODLIGHT_NUM_ANGLE_STEPS 16 #define FLOODLIGHT_NUM_ELEVATION_STEPS 4 #define FLOODLIGHT_NUM_VECTORS (FLOODLIGHT_NUM_ANGLE_STEPS * FLOODLIGHT_NUM_ELEVATION_STEPS) static vec3_t floodVectors[ FLOODLIGHT_NUM_VECTORS ]; static int numFloodVectors = 0; void SetupFloodLight( void ) { int i, j; float angle, elevation, angleStep, elevationStep; const char *value; double v1,v2,v3,v4,v5; /* note it */ Sys_FPrintf( SYS_VRB, "--- SetupFloodLight ---\n" ); /* calculate angular steps */ angleStep = DEG2RAD( 360.0f / FLOODLIGHT_NUM_ANGLE_STEPS ); elevationStep = DEG2RAD( FLOODLIGHT_CONE_ANGLE / FLOODLIGHT_NUM_ELEVATION_STEPS ); /* iterate angle */ angle = 0.0f; for( i = 0, angle = 0.0f; i < FLOODLIGHT_NUM_ANGLE_STEPS; i++, angle += angleStep ) { /* iterate elevation */ for( j = 0, elevation = elevationStep * 0.5f; j < FLOODLIGHT_NUM_ELEVATION_STEPS; j++, elevation += elevationStep ) { floodVectors[ numFloodVectors ][ 0 ] = sin( elevation ) * cos( angle ); floodVectors[ numFloodVectors ][ 1 ] = sin( elevation ) * sin( angle ); floodVectors[ numFloodVectors ][ 2 ] = cos( elevation ); numFloodVectors++; } } /* emit some statistics */ Sys_FPrintf( SYS_VRB, "%9d numFloodVectors\n", numFloodVectors ); /* floodlight */ value = ValueForKey( &entities[ 0 ], "_floodlight" ); if( value[ 0 ] != '\0' ) { v1=v2=v3=0; v4=floodlightDistance; v5=floodlightIntensity; sscanf( value, "%lf %lf %lf %lf %lf", &v1, &v2, &v3, &v4, &v5); floodlightRGB[0]=v1; floodlightRGB[1]=v2; floodlightRGB[2]=v3; if (VectorLength(floodlightRGB)==0) { VectorSet(floodlightRGB,240,240,255); } if (v4<1) v4=1024; if (v5<1) v5=128; floodlightDistance=v4; floodlightIntensity=v5; floodlighty = qtrue; Sys_Printf( "FloodLighting enabled via worldspawn _floodlight key.\n" ); } else { VectorSet(floodlightRGB,240,240,255); //floodlighty = qtrue; //Sys_Printf( "FloodLighting enabled via worldspawn _floodlight key.\n" ); } VectorNormalize(floodlightRGB,floodlightRGB); } //27 - lighttracer style ambient occlusion light hack. //Kudos to the dirtmapping author for most of this source. void FloodLightRawLightmap( int rawLightmapNum ) { int i, x, y, sx, sy, *cluster; float *origin, *normal, *floodlight, *floodlight2, average, samples; rawLightmap_t *lm; surfaceInfo_t *info; trace_t trace; /* bail if this number exceeds the number of raw lightmaps */ if( rawLightmapNum >= numRawLightmaps ) return; /* get lightmap */ lm = &rawLightmaps[ rawLightmapNum ]; memset(&trace,0,sizeof(trace_t)); /* setup trace */ trace.testOcclusion = qtrue; trace.forceSunlight = qfalse; trace.twoSided = qtrue; trace.recvShadows = lm->recvShadows; trace.numSurfaces = lm->numLightSurfaces; trace.surfaces = &lightSurfaces[ lm->firstLightSurface ]; trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS; trace.testAll = qfalse; trace.distance = 1024; /* twosided lighting (may or may not be a good idea for lightmapped stuff) */ //trace.twoSided = qfalse; for( i = 0; i < trace.numSurfaces; i++ ) { /* get surface */ info = &surfaceInfos[ trace.surfaces[ i ] ]; /* check twosidedness */ if( info->si->twoSided ) { trace.twoSided = qtrue; break; } } /* gather dirt */ for( y = 0; y < lm->sh; y++ ) { for( x = 0; x < lm->sw; x++ ) { /* get luxel */ cluster = SUPER_CLUSTER( x, y ); origin = SUPER_ORIGIN( x, y ); normal = SUPER_NORMAL( x, y ); floodlight = SUPER_FLOODLIGHT( x, y ); /* set default dirt */ *floodlight = 0.0f; /* only look at mapped luxels */ if( *cluster < 0 ) continue; /* copy to trace */ trace.cluster = *cluster; VectorCopy( origin, trace.origin ); VectorCopy( normal, trace.normal ); /* get dirt */ *floodlight = FloodLightForSample( &trace ); } } } /* FloodLightForSample() calculates floodlight value for a given sample once again, kudos to the dirtmapping coder */ float FloodLightForSample( trace_t *trace ) { int i; float d; float contribution; int sub = 0; float gatherLight, outLight; vec3_t normal, worldUp, myUp, myRt, direction, displacement; float dd; int vecs = 0; gatherLight=0; /* dummy check */ //if( !dirty ) // return 1.0f; if( trace == NULL || trace->cluster < 0 ) return 0.0f; /* setup */ dd = floodlightDistance; VectorCopy( trace->normal, normal ); /* check if the normal is aligned to the world-up */ if( normal[ 0 ] == 0.0f && normal[ 1 ] == 0.0f ) { if( normal[ 2 ] == 1.0f ) { VectorSet( myRt, 1.0f, 0.0f, 0.0f ); VectorSet( myUp, 0.0f, 1.0f, 0.0f ); } else if( normal[ 2 ] == -1.0f ) { VectorSet( myRt, -1.0f, 0.0f, 0.0f ); VectorSet( myUp, 0.0f, 1.0f, 0.0f ); } } else { VectorSet( worldUp, 0.0f, 0.0f, 1.0f ); CrossProduct( normal, worldUp, myRt ); VectorNormalize( myRt, myRt ); CrossProduct( myRt, normal, myUp ); VectorNormalize( myUp, myUp ); } /* iterate through ordered vectors */ for( i = 0; i < numFloodVectors; i++ ) { if (floodlight_lowquality==qtrue) { if (rand()%10 != 0 ) continue; } vecs++; /* transform vector into tangent space */ direction[ 0 ] = myRt[ 0 ] * floodVectors[ i ][ 0 ] + myUp[ 0 ] * floodVectors[ i ][ 1 ] + normal[ 0 ] * floodVectors[ i ][ 2 ]; direction[ 1 ] = myRt[ 1 ] * floodVectors[ i ][ 0 ] + myUp[ 1 ] * floodVectors[ i ][ 1 ] + normal[ 1 ] * floodVectors[ i ][ 2 ]; direction[ 2 ] = myRt[ 2 ] * floodVectors[ i ][ 0 ] + myUp[ 2 ] * floodVectors[ i ][ 1 ] + normal[ 2 ] * floodVectors[ i ][ 2 ]; /* set endpoint */ VectorMA( trace->origin, dd, direction, trace->end ); //VectorMA( trace->origin, 1, direction, trace->origin ); SetupTrace( trace ); /* trace */ TraceLine( trace ); contribution=1; if (trace->compileFlags & C_SKY ) { contribution=1.0f; } else if ( trace->opaque ) { VectorSubtract( trace->hit, trace->origin, displacement ); d=VectorLength( displacement ); // d=trace->distance; //if (d>256) gatherDirt+=1; contribution=d/dd; if (contribution>1) contribution=1.0f; //gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); } gatherLight+=contribution; } /* early out */ if( gatherLight <= 0.0f ) return 0.0f; sub=vecs; if (sub<1) sub=1; gatherLight/=(sub); outLight=gatherLight; if( outLight > 1.0f ) outLight = 1.0f; /* return to sender */ return outLight; }