/* ------------------------------------------------------------------------------- Copyright (C) 1999-2007 id Software, Inc. and contributors. For a list of contributors, see the accompanying CONTRIBUTORS file. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ---------------------------------------------------------------------------------- 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_C /* dependencies */ #include "q3map2.h" /* CreateSunLight() - ydnar this creates a sun light */ static void CreateSunLight( sun_t *sun ) { int i; float photons, d, angle, elevation, da, de; vec3_t direction; light_t *light; /* dummy check */ if( sun == NULL ) return; /* fixup */ if( sun->numSamples < 1 ) sun->numSamples = 1; /* set photons */ photons = sun->photons / sun->numSamples; /* create the right number of suns */ for( i = 0; i < sun->numSamples; i++ ) { /* calculate sun direction */ if( i == 0 ) VectorCopy( sun->direction, direction ); else { /* sun->direction[ 0 ] = cos( angle ) * cos( elevation ); sun->direction[ 1 ] = sin( angle ) * cos( elevation ); sun->direction[ 2 ] = sin( elevation ); xz_dist = sqrt( x*x + z*z ) latitude = atan2( xz_dist, y ) * RADIANS longitude = atan2( x, z ) * RADIANS */ d = sqrt( sun->direction[ 0 ] * sun->direction[ 0 ] + sun->direction[ 1 ] * sun->direction[ 1 ] ); angle = atan2( sun->direction[ 1 ], sun->direction[ 0 ] ); elevation = atan2( sun->direction[ 2 ], d ); /* jitter the angles (loop to keep random sample within sun->deviance steridians) */ do { da = (Random() * 2.0f - 1.0f) * sun->deviance; de = (Random() * 2.0f - 1.0f) * sun->deviance; } while( (da * da + de * de) > (sun->deviance * sun->deviance) ); angle += da; elevation += de; /* debug code */ //% Sys_Printf( "%d: Angle: %3.4f Elevation: %3.3f\n", sun->numSamples, (angle / Q_PI * 180.0f), (elevation / Q_PI * 180.0f) ); /* create new vector */ direction[ 0 ] = cos( angle ) * cos( elevation ); direction[ 1 ] = sin( angle ) * cos( elevation ); direction[ 2 ] = sin( elevation ); } /* create a light */ numSunLights++; light = safe_malloc( sizeof( *light ) ); memset( light, 0, sizeof( *light ) ); light->next = lights; lights = light; /* initialize the light */ light->flags = LIGHT_SUN_DEFAULT; light->type = EMIT_SUN; light->fade = 1.0f; light->falloffTolerance = falloffTolerance; light->filterRadius = sun->filterRadius / sun->numSamples; light->style = noStyles ? LS_NORMAL : sun->style; /* set the light's position out to infinity */ VectorMA( vec3_origin, (MAX_WORLD_COORD * 8.0f), direction, light->origin ); /* MAX_WORLD_COORD * 2.0f */ /* set the facing to be the inverse of the sun direction */ VectorScale( direction, -1.0, light->normal ); light->dist = DotProduct( light->origin, light->normal ); /* set color and photons */ VectorCopy( sun->color, light->color ); light->photons = photons * skyScale; } /* another sun? */ if( sun->next != NULL ) CreateSunLight( sun->next ); } /* CreateSkyLights() - ydnar simulates sky light with multiple suns */ static void CreateSkyLights( vec3_t color, float value, int iterations, float filterRadius, int style ) { int i, j, numSuns; int angleSteps, elevationSteps; float angle, elevation; float angleStep, elevationStep; float step, start; sun_t sun; /* dummy check */ if( value <= 0.0f || iterations < 2 ) return; /* calculate some stuff */ step = 2.0f / (iterations - 1); start = -1.0f; /* basic sun setup */ VectorCopy( color, sun.color ); sun.deviance = 0.0f; sun.filterRadius = filterRadius; sun.numSamples = 1; sun.style = noStyles ? LS_NORMAL : style; sun.next = NULL; /* setup */ elevationSteps = iterations - 1; angleSteps = elevationSteps * 4; angle = 0.0f; elevationStep = DEG2RAD( 90.0f / iterations ); /* skip elevation 0 */ angleStep = DEG2RAD( 360.0f / angleSteps ); /* calc individual sun brightness */ numSuns = angleSteps * elevationSteps + 1; sun.photons = value / numSuns; /* iterate elevation */ elevation = elevationStep * 0.5f; angle = 0.0f; for( i = 0, elevation = elevationStep * 0.5f; i < elevationSteps; i++ ) { /* iterate angle */ for( j = 0; j < angleSteps; j++ ) { /* create sun */ sun.direction[ 0 ] = cos( angle ) * cos( elevation ); sun.direction[ 1 ] = sin( angle ) * cos( elevation ); sun.direction[ 2 ] = sin( elevation ); CreateSunLight( &sun ); /* move */ angle += angleStep; } /* move */ elevation += elevationStep; angle += angleStep / elevationSteps; } /* create vertical sun */ VectorSet( sun.direction, 0.0f, 0.0f, 1.0f ); CreateSunLight( &sun ); /* short circuit */ return; } /* CreateEntityLights() creates lights from light entities */ void CreateEntityLights( void ) { int i, j; light_t *light, *light2; entity_t *e, *e2; const char *name; const char *target; vec3_t dest; const char *_color; float intensity, scale, deviance, filterRadius; int spawnflags, flags, numSamples; qboolean junior; /* go throught entity list and find lights */ for( i = 0; i < numEntities; i++ ) { /* get entity */ e = &entities[ i ]; name = ValueForKey( e, "classname" ); /* ydnar: check for lightJunior */ if( Q_strncasecmp( name, "lightJunior", 11 ) == 0 ) junior = qtrue; else if( Q_strncasecmp( name, "light", 5 ) == 0 ) junior = qfalse; else continue; /* lights with target names (and therefore styles) are only parsed from BSP */ target = ValueForKey( e, "targetname" ); if( target[ 0 ] != '\0' && i >= numBSPEntities ) continue; /* create a light */ numPointLights++; light = safe_malloc( sizeof( *light ) ); memset( light, 0, sizeof( *light ) ); light->next = lights; lights = light; /* handle spawnflags */ spawnflags = IntForKey( e, "spawnflags" ); /* ydnar: quake 3+ light behavior */ if( wolfLight == qfalse ) { /* set default flags */ flags = LIGHT_Q3A_DEFAULT; /* linear attenuation? */ if( spawnflags & 1 ) { flags |= LIGHT_ATTEN_LINEAR; flags &= ~LIGHT_ATTEN_ANGLE; } /* no angle attenuate? */ if( spawnflags & 2 ) flags &= ~LIGHT_ATTEN_ANGLE; } /* ydnar: wolf light behavior */ else { /* set default flags */ flags = LIGHT_WOLF_DEFAULT; /* inverse distance squared attenuation? */ if( spawnflags & 1 ) { flags &= ~LIGHT_ATTEN_LINEAR; flags |= LIGHT_ATTEN_ANGLE; } /* angle attenuate? */ if( spawnflags & 2 ) flags |= LIGHT_ATTEN_ANGLE; } /* other flags (borrowed from wolf) */ /* wolf dark light? */ if( (spawnflags & 4) || (spawnflags & 8) ) flags |= LIGHT_DARK; /* nogrid? */ if( spawnflags & 16 ) flags &= ~LIGHT_GRID; /* junior? */ if( junior ) { flags |= LIGHT_GRID; flags &= ~LIGHT_SURFACES; } /* store the flags */ light->flags = flags; /* ydnar: set fade key (from wolf) */ light->fade = 1.0f; if( light->flags & LIGHT_ATTEN_LINEAR ) { light->fade = FloatForKey( e, "fade" ); if( light->fade == 0.0f ) light->fade = 1.0f; } /* ydnar: set angle scaling (from vlight) */ light->angleScale = FloatForKey( e, "_anglescale" ); if( light->angleScale != 0.0f ) light->flags |= LIGHT_ATTEN_ANGLE; /* set origin */ GetVectorForKey( e, "origin", light->origin); light->style = IntForKey( e, "_style" ); if( light->style == LS_NORMAL ) light->style = IntForKey( e, "style" ); if( light->style < LS_NORMAL || light->style >= LS_NONE ) Error( "Invalid lightstyle (%d) on entity %d", light->style, i ); /* override */ if( noStyles ) light->style = LS_NORMAL; /* set light intensity */ intensity = FloatForKey( e, "_light" ); if( intensity == 0.0f ) intensity = FloatForKey( e, "light" ); if( intensity == 0.0f) intensity = 300.0f; /* ydnar: set light scale (sof2) */ scale = FloatForKey( e, "scale" ); if( scale == 0.0f ) scale = 1.0f; intensity *= scale; /* ydnar: get deviance and samples */ deviance = FloatForKey( e, "_deviance" ); if( deviance == 0.0f ) deviance = FloatForKey( e, "_deviation" ); if( deviance == 0.0f ) deviance = FloatForKey( e, "_jitter" ); numSamples = IntForKey( e, "_samples" ); if( deviance < 0.0f || numSamples < 1 ) { deviance = 0.0f; numSamples = 1; } intensity /= numSamples; /* ydnar: get filter radius */ filterRadius = FloatForKey( e, "_filterradius" ); if( filterRadius == 0.0f ) filterRadius = FloatForKey( e, "_filteradius" ); if( filterRadius == 0.0f ) filterRadius = FloatForKey( e, "_filter" ); if( filterRadius < 0.0f ) filterRadius = 0.0f; light->filterRadius = filterRadius; /* set light color */ _color = ValueForKey( e, "_color" ); if( _color && _color[ 0 ] ) { sscanf( _color, "%f %f %f", &light->color[ 0 ], &light->color[ 1 ], &light->color[ 2 ] ); ColorNormalize( light->color, light->color ); } else light->color[ 0 ] = light->color[ 1 ] = light->color[ 2 ] = 1.0f; intensity = intensity * pointScale; light->photons = intensity; light->type = EMIT_POINT; /* set falloff threshold */ light->falloffTolerance = falloffTolerance / numSamples; /* lights with a target will be spotlights */ target = ValueForKey( e, "target" ); if( target[ 0 ] ) { float radius; float dist; sun_t sun; const char *_sun; /* get target */ e2 = FindTargetEntity( target ); if( e2 == NULL ) { Sys_Printf( "WARNING: light at (%i %i %i) has missing target\n", (int) light->origin[ 0 ], (int) light->origin[ 1 ], (int) light->origin[ 2 ] ); } else { /* not a point light */ numPointLights--; numSpotLights++; /* make a spotlight */ GetVectorForKey( e2, "origin", dest ); VectorSubtract( dest, light->origin, light->normal ); dist = VectorNormalize( light->normal, light->normal ); radius = FloatForKey( e, "radius" ); if( !radius ) radius = 64; if( !dist ) dist = 64; light->radiusByDist = (radius + 16) / dist; light->type = EMIT_SPOT; /* ydnar: wolf mods: spotlights always use nonlinear + angle attenuation */ light->flags &= ~LIGHT_ATTEN_LINEAR; light->flags |= LIGHT_ATTEN_ANGLE; light->fade = 1.0f; /* ydnar: is this a sun? */ _sun = ValueForKey( e, "_sun" ); if( _sun[ 0 ] == '1' ) { /* not a spot light */ numSpotLights--; /* unlink this light */ lights = light->next; /* make a sun */ VectorScale( light->normal, -1.0f, sun.direction ); VectorCopy( light->color, sun.color ); sun.photons = (intensity / pointScale); sun.deviance = deviance / 180.0f * Q_PI; sun.numSamples = numSamples; sun.style = noStyles ? LS_NORMAL : light->style; sun.next = NULL; /* make a sun light */ CreateSunLight( &sun ); /* free original light */ free( light ); light = NULL; /* skip the rest of this love story */ continue; } } } /* jitter the light */ for( j = 1; j < numSamples; j++ ) { /* create a light */ light2 = safe_malloc( sizeof( *light ) ); memcpy( light2, light, sizeof( *light ) ); light2->next = lights; lights = light2; /* add to counts */ if( light->type == EMIT_SPOT ) numSpotLights++; else numPointLights++; /* jitter it */ light2->origin[ 0 ] = light->origin[ 0 ] + (Random() * 2.0f - 1.0f) * deviance; light2->origin[ 1 ] = light->origin[ 1 ] + (Random() * 2.0f - 1.0f) * deviance; light2->origin[ 2 ] = light->origin[ 2 ] + (Random() * 2.0f - 1.0f) * deviance; } } } /* CreateSurfaceLights() - ydnar this hijacks the radiosity code to generate surface lights for first pass */ #define APPROX_BOUNCE 1.0f void CreateSurfaceLights( void ) { int i; bspDrawSurface_t *ds; surfaceInfo_t *info; shaderInfo_t *si; light_t *light; float subdivide; vec3_t origin; clipWork_t cw; const char *nss; /* get sun shader supressor */ nss = ValueForKey( &entities[ 0 ], "_noshadersun" ); /* walk the list of surfaces */ for( i = 0; i < numBSPDrawSurfaces; i++ ) { /* get surface and other bits */ ds = &bspDrawSurfaces[ i ]; info = &surfaceInfos[ i ]; si = info->si; /* sunlight? */ if( si->sun != NULL && nss[ 0 ] != '1' ) { Sys_FPrintf( SYS_VRB, "Sun: %s\n", si->shader ); CreateSunLight( si->sun ); si->sun = NULL; /* FIXME: leak! */ } /* sky light? */ if( si->skyLightValue > 0.0f ) { Sys_FPrintf( SYS_VRB, "Sky: %s\n", si->shader ); CreateSkyLights( si->color, si->skyLightValue, si->skyLightIterations, si->lightFilterRadius, si->lightStyle ); si->skyLightValue = 0.0f; /* FIXME: hack! */ } /* try to early out */ if( si->value <= 0 ) continue; /* autosprite shaders become point lights */ if( si->autosprite ) { /* create an average xyz */ VectorAdd( info->mins, info->maxs, origin ); VectorScale( origin, 0.5f, origin ); /* create a light */ light = safe_malloc( sizeof( *light ) ); memset( light, 0, sizeof( *light ) ); light->next = lights; lights = light; /* set it up */ light->flags = LIGHT_Q3A_DEFAULT; light->type = EMIT_POINT; light->photons = si->value * pointScale; light->fade = 1.0f; light->si = si; VectorCopy( origin, light->origin ); VectorCopy( si->color, light->color ); light->falloffTolerance = falloffTolerance; light->style = si->lightStyle; /* add to point light count and continue */ numPointLights++; continue; } /* get subdivision amount */ if( si->lightSubdivide > 0 ) subdivide = si->lightSubdivide; else subdivide = defaultLightSubdivide; /* switch on type */ switch( ds->surfaceType ) { case MST_PLANAR: case MST_TRIANGLE_SOUP: RadLightForTriangles( i, 0, info->lm, si, APPROX_BOUNCE, subdivide, &cw ); break; case MST_PATCH: RadLightForPatch( i, 0, info->lm, si, APPROX_BOUNCE, subdivide, &cw ); break; default: break; } } } /* SetEntityOrigins() find the offset values for inline models */ void SetEntityOrigins( void ) { int i, j, k, f; entity_t *e; vec3_t origin; const char *key; int modelnum; bspModel_t *dm; bspDrawSurface_t *ds; /* ydnar: copy drawverts into private storage for nefarious purposes */ yDrawVerts = safe_malloc( numBSPDrawVerts * sizeof( bspDrawVert_t ) ); memcpy( yDrawVerts, bspDrawVerts, numBSPDrawVerts * sizeof( bspDrawVert_t ) ); /* set the entity origins */ for( i = 0; i < numEntities; i++ ) { /* get entity and model */ e = &entities[ i ]; key = ValueForKey( e, "model" ); if( key[ 0 ] != '*' ) continue; modelnum = atoi( key + 1 ); dm = &bspModels[ modelnum ]; /* get entity origin */ key = ValueForKey( e, "origin" ); if( key[ 0 ] == '\0' ) continue; GetVectorForKey( e, "origin", origin ); /* set origin for all surfaces for this model */ for( j = 0; j < dm->numBSPSurfaces; j++ ) { /* get drawsurf */ ds = &bspDrawSurfaces[ dm->firstBSPSurface + j ]; /* set its verts */ for( k = 0; k < ds->numVerts; k++ ) { f = ds->firstVert + k; VectorAdd( origin, bspDrawVerts[ f ].xyz, yDrawVerts[ f ].xyz ); } } } } /* PointToPolygonFormFactor() calculates the area over a point/normal hemisphere a winding covers ydnar: fixme: there has to be a faster way to calculate this without the expensive per-vert sqrts and transcendental functions ydnar 2002-09-30: added -faster switch because only 19% deviance > 10% between this and the approximation */ #define ONE_OVER_2PI 0.159154942f //% (1.0f / (2.0f * 3.141592657f)) float PointToPolygonFormFactor( const vec3_t point, const vec3_t normal, const winding_t *w ) { vec3_t triVector, triNormal; int i, j; vec3_t dirs[ MAX_POINTS_ON_WINDING ]; float total; float dot, angle, facing; /* this is expensive */ for( i = 0; i < w->numpoints; i++ ) { VectorSubtract( w->p[ i ], point, dirs[ i ] ); VectorNormalize( dirs[ i ], dirs[ i ] ); } /* duplicate first vertex to avoid mod operation */ VectorCopy( dirs[ 0 ], dirs[ i ] ); /* calculcate relative area */ total = 0.0f; for( i = 0; i < w->numpoints; i++ ) { /* get a triangle */ j = i + 1; dot = DotProduct( dirs[ i ], dirs[ j ] ); /* roundoff can cause slight creep, which gives an IND from acos */ if( dot > 1.0f ) dot = 1.0f; else if( dot < -1.0f ) dot = -1.0f; /* get the angle */ angle = acos( dot ); CrossProduct( dirs[ i ], dirs[ j ], triVector ); if( VectorNormalize( triVector, triNormal ) < 0.0001f ) continue; facing = DotProduct( normal, triNormal ); total += facing * angle; /* ydnar: this was throwing too many errors with radiosity + crappy maps. ignoring it. */ if( total > 6.3f || total < -6.3f ) return 0.0f; } /* now in the range of 0 to 1 over the entire incoming hemisphere */ //% total /= (2.0f * 3.141592657f); total *= ONE_OVER_2PI; return total; } /* LightContributionTosample() determines the amount of light reaching a sample (luxel or vertex) from a given light */ int LightContributionToSample( trace_t *trace ) { light_t *light; float angle; float add; float dist; /* get light */ light = trace->light; /* clear color */ VectorClear( trace->color ); /* ydnar: early out */ if( !(light->flags & LIGHT_SURFACES) || light->envelope <= 0.0f ) return 0; /* do some culling checks */ if( light->type != EMIT_SUN ) { /* MrE: if the light is behind the surface */ if( trace->twoSided == qfalse ) if( DotProduct( light->origin, trace->normal ) - DotProduct( trace->origin, trace->normal ) < 0.0f ) return 0; /* ydnar: test pvs */ if( !ClusterVisible( trace->cluster, light->cluster ) ) return 0; } /* exact point to polygon form factor */ if( light->type == EMIT_AREA ) { float factor; float d; vec3_t pushedOrigin; /* project sample point into light plane */ d = DotProduct( trace->origin, light->normal ) - light->dist; if( d < 3.0f ) { /* sample point behind plane? */ if( !(light->flags & LIGHT_TWOSIDED) && d < -1.0f ) return 0; /* sample plane coincident? */ if( d > -3.0f && DotProduct( trace->normal, light->normal ) > 0.9f ) return 0; } /* nudge the point so that it is clearly forward of the light */ /* so that surfaces meeting a light emiter don't get black edges */ if( d > -8.0f && d < 8.0f ) VectorMA( trace->origin, (8.0f - d), light->normal, pushedOrigin ); else VectorCopy( trace->origin, pushedOrigin ); /* get direction and distance */ VectorCopy( light->origin, trace->end ); dist = SetupTrace( trace ); if( dist >= light->envelope ) return 0; /* ptpff approximation */ if( faster ) { /* angle attenuation */ angle = DotProduct( trace->normal, trace->direction ); /* twosided lighting */ if( trace->twoSided ) angle = fabs( angle ); /* attenuate */ angle *= -DotProduct( light->normal, trace->direction ); if( angle == 0.0f ) return 0; else if( angle < 0.0f && (trace->twoSided || (light->flags & LIGHT_TWOSIDED)) ) angle = -angle; add = light->photons / (dist * dist) * angle; } else { /* calculate the contribution */ factor = PointToPolygonFormFactor( pushedOrigin, trace->normal, light->w ); if( factor == 0.0f ) return 0; else if( factor < 0.0f ) { /* twosided lighting */ if( trace->twoSided || (light->flags & LIGHT_TWOSIDED) ) { factor = -factor; /* push light origin to other side of the plane */ VectorMA( light->origin, -2.0f, light->normal, trace->end ); dist = SetupTrace( trace ); if( dist >= light->envelope ) return 0; } else return 0; } /* ydnar: moved to here */ add = factor * light->add; } } /* point/spot lights */ else if( light->type == EMIT_POINT || light->type == EMIT_SPOT ) { /* get direction and distance */ VectorCopy( light->origin, trace->end ); dist = SetupTrace( trace ); if( dist >= light->envelope ) return 0; /* clamp the distance to prevent super hot spots */ if( dist < 16.0f ) dist = 16.0f; /* angle attenuation */ angle = (light->flags & LIGHT_ATTEN_ANGLE) ? DotProduct( trace->normal, trace->direction ) : 1.0f; if( light->angleScale != 0.0f ) { angle /= light->angleScale; if( angle > 1.0f ) angle = 1.0f; } /* twosided lighting */ if( trace->twoSided ) angle = fabs( angle ); /* attenuate */ if( light->flags & LIGHT_ATTEN_LINEAR ) { add = angle * light->photons * linearScale - (dist * light->fade); if( add < 0.0f ) add = 0.0f; } else add = light->photons / (dist * dist) * angle; /* handle spotlights */ if( light->type == EMIT_SPOT ) { float distByNormal, radiusAtDist, sampleRadius; vec3_t pointAtDist, distToSample; /* do cone calculation */ distByNormal = -DotProduct( trace->displacement, light->normal ); if( distByNormal < 0.0f ) return 0; VectorMA( light->origin, distByNormal, light->normal, pointAtDist ); radiusAtDist = light->radiusByDist * distByNormal; VectorSubtract( trace->origin, pointAtDist, distToSample ); sampleRadius = VectorLength( distToSample ); /* outside the cone */ if( sampleRadius >= radiusAtDist ) return 0; /* attenuate */ if( sampleRadius > (radiusAtDist - 32.0f) ) add *= ((radiusAtDist - sampleRadius) / 32.0f); } } /* ydnar: sunlight */ else if( light->type == EMIT_SUN ) { /* get origin and direction */ VectorAdd( trace->origin, light->origin, trace->end ); dist = SetupTrace( trace ); /* angle attenuation */ angle = (light->flags & LIGHT_ATTEN_ANGLE) ? DotProduct( trace->normal, trace->direction ) : 1.0f; /* twosided lighting */ if( trace->twoSided ) angle = fabs( angle ); /* attenuate */ add = light->photons * angle; if( add <= 0.0f ) return 0; /* setup trace */ trace->testAll = qtrue; VectorScale( light->color, add, trace->color ); /* trace to point */ if( trace->testOcclusion && !trace->forceSunlight ) { /* trace */ TraceLine( trace ); if( !(trace->compileFlags & C_SKY) || trace->opaque ) { VectorClear( trace->color ); return -1; } } /* return to sender */ return 1; } /* ydnar: changed to a variable number */ if( add <= 0.0f || (add <= light->falloffTolerance && (light->flags & LIGHT_FAST_ACTUAL)) ) return 0; /* setup trace */ trace->testAll = qfalse; VectorScale( light->color, add, trace->color ); /* raytrace */ TraceLine( trace ); if( trace->passSolid || trace->opaque ) { VectorClear( trace->color ); return -1; } /* return to sender */ return 1; } /* LightingAtSample() determines the amount of light reaching a sample (luxel or vertex) */ void LightingAtSample( trace_t *trace, byte styles[ MAX_LIGHTMAPS ], vec3_t colors[ MAX_LIGHTMAPS ] ) { int i, lightmapNum; /* clear colors */ for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) VectorClear( colors[ lightmapNum ] ); /* ydnar: normalmap */ if( normalmap ) { colors[ 0 ][ 0 ] = (trace->normal[ 0 ] + 1.0f) * 127.5f; colors[ 0 ][ 1 ] = (trace->normal[ 1 ] + 1.0f) * 127.5f; colors[ 0 ][ 2 ] = (trace->normal[ 2 ] + 1.0f) * 127.5f; return; } /* ydnar: don't bounce ambient all the time */ if( !bouncing ) VectorCopy( ambientColor, colors[ 0 ] ); /* ydnar: trace to all the list of lights pre-stored in tw */ for( i = 0; i < trace->numLights && trace->lights[ i ] != NULL; i++ ) { /* set light */ trace->light = trace->lights[ i ]; /* style check */ for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) { if( styles[ lightmapNum ] == trace->light->style || styles[ lightmapNum ] == LS_NONE ) break; } /* max of MAX_LIGHTMAPS (4) styles allowed to hit a sample */ if( lightmapNum >= MAX_LIGHTMAPS ) continue; /* sample light */ LightContributionToSample( trace ); if( trace->color[ 0 ] == 0.0f && trace->color[ 1 ] == 0.0f && trace->color[ 2 ] == 0.0f ) continue; /* handle negative light */ if( trace->light->flags & LIGHT_NEGATIVE ) VectorScale( trace->color, -1.0f, trace->color ); /* set style */ styles[ lightmapNum ] = trace->light->style; /* add it */ VectorAdd( colors[ lightmapNum ], trace->color, colors[ lightmapNum ] ); /* cheap mode */ if( cheap && colors[ 0 ][ 0 ] >= 255.0f && colors[ 0 ][ 1 ] >= 255.0f && colors[ 0 ][ 2 ] >= 255.0f ) break; } } /* LightContributionToPoint() for a given light, how much light/color reaches a given point in space (with no facing) note: this is similar to LightContributionToSample() but optimized for omnidirectional sampling */ int LightContributionToPoint( trace_t *trace ) { light_t *light; float add, dist; /* get light */ light = trace->light; /* clear color */ VectorClear( trace->color ); /* ydnar: early out */ if( !(light->flags & LIGHT_GRID) || light->envelope <= 0.0f ) return qfalse; /* is this a sun? */ if( light->type != EMIT_SUN ) { /* sun only? */ if( sunOnly ) return qfalse; /* test pvs */ if( !ClusterVisible( trace->cluster, light->cluster ) ) return qfalse; } /* ydnar: check origin against light's pvs envelope */ if( trace->origin[ 0 ] > light->maxs[ 0 ] || trace->origin[ 0 ] < light->mins[ 0 ] || trace->origin[ 1 ] > light->maxs[ 1 ] || trace->origin[ 1 ] < light->mins[ 1 ] || trace->origin[ 2 ] > light->maxs[ 2 ] || trace->origin[ 2 ] < light->mins[ 2 ] ) { gridBoundsCulled++; return qfalse; } /* set light origin */ if( light->type == EMIT_SUN ) VectorAdd( trace->origin, light->origin, trace->end ); else VectorCopy( light->origin, trace->end ); /* set direction */ dist = SetupTrace( trace ); /* test envelope */ if( dist > light->envelope ) { gridEnvelopeCulled++; return qfalse; } /* ptpff approximation */ if( light->type == EMIT_AREA && faster ) { /* clamp the distance to prevent super hot spots */ if( dist < 16.0f ) dist = 16.0f; /* attenuate */ add = light->photons / (dist * dist); } /* exact point to polygon form factor */ else if( light->type == EMIT_AREA ) { float factor, d; vec3_t pushedOrigin; /* see if the point is behind the light */ d = DotProduct( trace->origin, light->normal ) - light->dist; if( !(light->flags & LIGHT_TWOSIDED) && d < -1.0f ) return qfalse; /* nudge the point so that it is clearly forward of the light */ /* so that surfaces meeting a light emiter don't get black edges */ if( d > -8.0f && d < 8.0f ) VectorMA( trace->origin, (8.0f - d), light->normal, pushedOrigin ); else VectorCopy( trace->origin, pushedOrigin ); /* calculate the contribution (ydnar 2002-10-21: [bug 642] bad normal calc) */ factor = PointToPolygonFormFactor( pushedOrigin, trace->direction, light->w ); if( factor == 0.0f ) return qfalse; else if( factor < 0.0f ) { if( light->flags & LIGHT_TWOSIDED ) factor = -factor; else return qfalse; } /* ydnar: moved to here */ add = factor * light->add; } /* point/spot lights */ else if( light->type == EMIT_POINT || light->type == EMIT_SPOT ) { /* clamp the distance to prevent super hot spots */ if( dist < 16.0f ) dist = 16.0f; /* attenuate */ if( light->flags & LIGHT_ATTEN_LINEAR ) { add = light->photons * linearScale - (dist * light->fade); if( add < 0.0f ) add = 0.0f; } else add = light->photons / (dist * dist); /* handle spotlights */ if( light->type == EMIT_SPOT ) { float distByNormal, radiusAtDist, sampleRadius; vec3_t pointAtDist, distToSample; /* do cone calculation */ distByNormal = -DotProduct( trace->displacement, light->normal ); if( distByNormal < 0.0f ) return qfalse; VectorMA( light->origin, distByNormal, light->normal, pointAtDist ); radiusAtDist = light->radiusByDist * distByNormal; VectorSubtract( trace->origin, pointAtDist, distToSample ); sampleRadius = VectorLength( distToSample ); /* outside the cone */ if( sampleRadius >= radiusAtDist ) return qfalse; /* attenuate */ if( sampleRadius > (radiusAtDist - 32.0f) ) add *= ((radiusAtDist - sampleRadius) / 32.0f); } } /* ydnar: sunlight */ else if( light->type == EMIT_SUN ) { /* attenuate */ add = light->photons; if( add <= 0.0f ) return qfalse; /* setup trace */ trace->testAll = qtrue; VectorScale( light->color, add, trace->color ); /* trace to point */ if( trace->testOcclusion && !trace->forceSunlight ) { /* trace */ TraceLine( trace ); if( !(trace->compileFlags & C_SKY) || trace->opaque ) { VectorClear( trace->color ); return -1; } } /* return to sender */ return qtrue; } /* unknown light type */ else return qfalse; /* ydnar: changed to a variable number */ if( add <= 0.0f || (add <= light->falloffTolerance && (light->flags & LIGHT_FAST_ACTUAL)) ) return qfalse; /* setup trace */ trace->testAll = qfalse; VectorScale( light->color, add, trace->color ); /* trace */ TraceLine( trace ); if( trace->passSolid ) { VectorClear( trace->color ); return qfalse; } /* we have a valid sample */ return qtrue; } /* TraceGrid() grid samples are for quickly determining the lighting of dynamically placed entities in the world */ #define MAX_CONTRIBUTIONS 1024 typedef struct { vec3_t dir; vec3_t color; int style; } contribution_t; void TraceGrid( int num ) { int i, j, x, y, z, mod, step, numCon, numStyles; float d; vec3_t baseOrigin, cheapColor, color; rawGridPoint_t *gp; bspGridPoint_t *bgp; contribution_t contributions[ MAX_CONTRIBUTIONS ]; trace_t trace; /* get grid points */ gp = &rawGridPoints[ num ]; bgp = &bspGridPoints[ num ]; /* get grid origin */ mod = num; z = mod / (gridBounds[ 0 ] * gridBounds[ 1 ]); mod -= z * (gridBounds[ 0 ] * gridBounds[ 1 ]); y = mod / gridBounds[ 0 ]; mod -= y * gridBounds[ 0 ]; x = mod; trace.origin[ 0 ] = gridMins[ 0 ] + x * gridSize[ 0 ]; trace.origin[ 1 ] = gridMins[ 1 ] + y * gridSize[ 1 ]; trace.origin[ 2 ] = gridMins[ 2 ] + z * gridSize[ 2 ]; /* set inhibit sphere */ if( gridSize[ 0 ] > gridSize[ 1 ] && gridSize[ 0 ] > gridSize[ 2 ] ) trace.inhibitRadius = gridSize[ 0 ] * 0.5f; else if( gridSize[ 1 ] > gridSize[ 0 ] && gridSize[ 1 ] > gridSize[ 2 ] ) trace.inhibitRadius = gridSize[ 1 ] * 0.5f; else trace.inhibitRadius = gridSize[ 2 ] * 0.5f; /* find point cluster */ trace.cluster = ClusterForPointExt( trace.origin, GRID_EPSILON ); if( trace.cluster < 0 ) { /* try to nudge the origin around to find a valid point */ VectorCopy( trace.origin, baseOrigin ); for( step = 9; step <= 18; step += 9 ) { for( i = 0; i < 8; i++ ) { VectorCopy( baseOrigin, trace.origin ); if( i & 1 ) trace.origin[ 0 ] += step; else trace.origin[ 0 ] -= step; if( i & 2 ) trace.origin[ 1 ] += step; else trace.origin[ 1 ] -= step; if( i & 4 ) trace.origin[ 2 ] += step; else trace.origin[ 2 ] -= step; /* ydnar: changed to find cluster num */ trace.cluster = ClusterForPointExt( trace.origin, VERTEX_EPSILON ); if( trace.cluster >= 0 ) break; } if( i != 8 ) break; } /* can't find a valid point at all */ if( step > 18 ) return; } /* setup trace */ trace.testOcclusion = !noTrace; trace.forceSunlight = qfalse; trace.recvShadows = WORLDSPAWN_RECV_SHADOWS; trace.numSurfaces = 0; trace.surfaces = NULL; trace.numLights = 0; trace.lights = NULL; /* clear */ numCon = 0; VectorClear( cheapColor ); /* trace to all the lights, find the major light direction, and divide the total light between that along the direction and the remaining in the ambient */ for( trace.light = lights; trace.light != NULL; trace.light = trace.light->next ) { float addSize; /* sample light */ if( !LightContributionToPoint( &trace ) ) continue; /* handle negative light */ if( trace.light->flags & LIGHT_NEGATIVE ) VectorScale( trace.color, -1.0f, trace.color ); /* add a contribution */ VectorCopy( trace.color, contributions[ numCon ].color ); VectorCopy( trace.direction, contributions[ numCon ].dir ); contributions[ numCon ].style = trace.light->style; numCon++; /* push average direction around */ addSize = VectorLength( trace.color ); VectorMA( gp->dir, addSize, trace.direction, gp->dir ); /* stop after a while */ if( numCon >= (MAX_CONTRIBUTIONS - 1) ) break; /* ydnar: cheap mode */ VectorAdd( cheapColor, trace.color, cheapColor ); if( cheapgrid && cheapColor[ 0 ] >= 255.0f && cheapColor[ 1 ] >= 255.0f && cheapColor[ 2 ] >= 255.0f ) break; } /* normalize to get primary light direction */ VectorNormalize( gp->dir, gp->dir ); /* now that we have identified the primary light direction, go back and separate all the light into directed and ambient */ numStyles = 1; for( i = 0; i < numCon; i++ ) { /* get relative directed strength */ d = DotProduct( contributions[ i ].dir, gp->dir ); if( d < 0.0f ) d = 0.0f; /* find appropriate style */ for( j = 0; j < numStyles; j++ ) { if( gp->styles[ j ] == contributions[ i ].style ) break; } /* style not found? */ if( j >= numStyles ) { /* add a new style */ if( numStyles < MAX_LIGHTMAPS ) { gp->styles[ numStyles ] = contributions[ i ].style; bgp->styles[ numStyles ] = contributions[ i ].style; numStyles++; //% Sys_Printf( "(%d, %d) ", num, contributions[ i ].style ); } /* fallback */ else j = 0; } /* add the directed color */ VectorMA( gp->directed[ j ], d, contributions[ i ].color, gp->directed[ j ] ); /* ambient light will be at 1/4 the value of directed light */ /* (ydnar: nuke this in favor of more dramatic lighting?) */ d = 0.25f * (1.0f - d); VectorMA( gp->ambient[ j ], d, contributions[ i ].color, gp->ambient[ j ] ); } /* store off sample */ for( i = 0; i < MAX_LIGHTMAPS; i++ ) { /* do some fudging to keep the ambient from being too low (2003-07-05: 0.25 -> 0.125) */ if( !bouncing ) VectorMA( gp->ambient[ i ], 0.125f, gp->directed[ i ], gp->ambient[ i ] ); /* set minimum light and copy off to bytes */ VectorCopy( gp->ambient[ i ], color ); for( j = 0; j < 3; j++ ) if( color[ j ] < minGridLight[ j ] ) color[ j ] = minGridLight[ j ]; ColorToBytes( color, bgp->ambient[ i ], 1.0f ); ColorToBytes( gp->directed[ i ], bgp->directed[ i ], 1.0f ); } /* debug code */ #if 0 //% Sys_FPrintf( SYS_VRB, "%10d %10d %10d ", &gp->ambient[ 0 ][ 0 ], &gp->ambient[ 0 ][ 1 ], &gp->ambient[ 0 ][ 2 ] ); Sys_FPrintf( SYS_VRB, "%9d Amb: (%03.1f %03.1f %03.1f) Dir: (%03.1f %03.1f %03.1f)\n", num, gp->ambient[ 0 ][ 0 ], gp->ambient[ 0 ][ 1 ], gp->ambient[ 0 ][ 2 ], gp->directed[ 0 ][ 0 ], gp->directed[ 0 ][ 1 ], gp->directed[ 0 ][ 2 ] ); #endif /* store direction */ if( !bouncing ) NormalToLatLong( gp->dir, bgp->latLong ); } /* SetupGrid() calculates the size of the lightgrid and allocates memory */ void SetupGrid( void ) { int i, j; vec3_t maxs, oldGridSize; const char *value; char temp[ 64 ]; /* don't do this if not grid lighting */ if( noGridLighting ) return; /* ydnar: set grid size */ value = ValueForKey( &entities[ 0 ], "gridsize" ); if( value[ 0 ] != '\0' ) sscanf( value, "%f %f %f", &gridSize[ 0 ], &gridSize[ 1 ], &gridSize[ 2 ] ); /* quantize it */ VectorCopy( gridSize, oldGridSize ); for( i = 0; i < 3; i++ ) gridSize[ i ] = gridSize[ i ] >= 8.0f ? floor( gridSize[ i ] ) : 8.0f; /* ydnar: increase gridSize until grid count is smaller than max allowed */ numRawGridPoints = MAX_MAP_LIGHTGRID + 1; j = 0; while( numRawGridPoints > MAX_MAP_LIGHTGRID ) { /* get world bounds */ for( i = 0; i < 3; i++ ) { gridMins[ i ] = gridSize[ i ] * ceil( bspModels[ 0 ].mins[ i ] / gridSize[ i ] ); maxs[ i ] = gridSize[ i ] * floor( bspModels[ 0 ].maxs[ i ] / gridSize[ i ] ); gridBounds[ i ] = (maxs[ i ] - gridMins[ i ]) / gridSize[ i ] + 1; } /* set grid size */ numRawGridPoints = gridBounds[ 0 ] * gridBounds[ 1 ] * gridBounds[ 2 ]; /* increase grid size a bit */ if( numRawGridPoints > MAX_MAP_LIGHTGRID ) gridSize[ j++ % 3 ] += 16.0f; } /* print it */ Sys_Printf( "Grid size = { %1.0f, %1.0f, %1.0f }\n", gridSize[ 0 ], gridSize[ 1 ], gridSize[ 2 ] ); /* different? */ if( !VectorCompare( gridSize, oldGridSize ) ) { sprintf( temp, "%.0f %.0f %.0f", gridSize[ 0 ], gridSize[ 1 ], gridSize[ 2 ] ); SetKeyValue( &entities[ 0 ], "gridsize", (const char*) temp ); Sys_FPrintf( SYS_VRB, "Storing adjusted grid size\n" ); } /* 2nd variable. fixme: is this silly? */ numBSPGridPoints = numRawGridPoints; /* allocate lightgrid */ rawGridPoints = safe_malloc( numRawGridPoints * sizeof( *rawGridPoints ) ); memset( rawGridPoints, 0, numRawGridPoints * sizeof( *rawGridPoints ) ); if( bspGridPoints != NULL ) free( bspGridPoints ); bspGridPoints = safe_malloc( numBSPGridPoints * sizeof( *bspGridPoints ) ); memset( bspGridPoints, 0, numBSPGridPoints * sizeof( *bspGridPoints ) ); /* clear lightgrid */ for( i = 0; i < numRawGridPoints; i++ ) { VectorCopy( ambientColor, rawGridPoints[ i ].ambient[ j ] ); rawGridPoints[ i ].styles[ 0 ] = LS_NORMAL; bspGridPoints[ i ].styles[ 0 ] = LS_NORMAL; for( j = 1; j < MAX_LIGHTMAPS; j++ ) { rawGridPoints[ i ].styles[ j ] = LS_NONE; bspGridPoints[ i ].styles[ j ] = LS_NONE; } } /* note it */ Sys_Printf( "%9d grid points\n", numRawGridPoints ); } /* LightWorld() does what it says... */ void LightWorld( void ) { vec3_t color; float f; int b, bt; qboolean minVertex, minGrid; const char *value; /* ydnar: smooth normals */ if( shade ) { Sys_Printf( "--- SmoothNormals ---\n" ); SmoothNormals(); } /* determine the number of grid points */ Sys_Printf( "--- SetupGrid ---\n" ); SetupGrid(); /* find the optional minimum lighting values */ GetVectorForKey( &entities[ 0 ], "_color", color ); if( VectorLength( color ) == 0.0f ) VectorSet( color, 1.0, 1.0, 1.0 ); /* ambient */ f = FloatForKey( &entities[ 0 ], "_ambient" ); if( f == 0.0f ) f = FloatForKey( &entities[ 0 ], "ambient" ); VectorScale( color, f, ambientColor ); /* minvertexlight */ minVertex = qfalse; value = ValueForKey( &entities[ 0 ], "_minvertexlight" ); if( value[ 0 ] != '\0' ) { minVertex = qtrue; f = atof( value ); VectorScale( color, f, minVertexLight ); } /* mingridlight */ minGrid = qfalse; value = ValueForKey( &entities[ 0 ], "_mingridlight" ); if( value[ 0 ] != '\0' ) { minGrid = qtrue; f = atof( value ); VectorScale( color, f, minGridLight ); } /* minlight */ value = ValueForKey( &entities[ 0 ], "_minlight" ); if( value[ 0 ] != '\0' ) { f = atof( value ); VectorScale( color, f, minLight ); if( minVertex == qfalse ) VectorScale( color, f, minVertexLight ); if( minGrid == qfalse ) VectorScale( color, f, minGridLight ); } /* create world lights */ Sys_FPrintf( SYS_VRB, "--- CreateLights ---\n" ); CreateEntityLights(); CreateSurfaceLights(); Sys_Printf( "%9d point lights\n", numPointLights ); Sys_Printf( "%9d spotlights\n", numSpotLights ); Sys_Printf( "%9d diffuse (area) lights\n", numDiffuseLights ); Sys_Printf( "%9d sun/sky lights\n", numSunLights ); /* calculate lightgrid */ if( !noGridLighting ) { /* ydnar: set up light envelopes */ SetupEnvelopes( qtrue, fastgrid ); Sys_Printf( "--- TraceGrid ---\n" ); RunThreadsOnIndividual( numRawGridPoints, qtrue, TraceGrid ); Sys_Printf( "%d x %d x %d = %d grid\n", gridBounds[ 0 ], gridBounds[ 1 ], gridBounds[ 2 ], numBSPGridPoints ); /* ydnar: emit statistics on light culling */ Sys_FPrintf( SYS_VRB, "%9d grid points envelope culled\n", gridEnvelopeCulled ); Sys_FPrintf( SYS_VRB, "%9d grid points bounds culled\n", gridBoundsCulled ); } /* slight optimization to remove a sqrt */ subdivideThreshold *= subdivideThreshold; /* map the world luxels */ Sys_Printf( "--- MapRawLightmap ---\n" ); RunThreadsOnIndividual( numRawLightmaps, qtrue, MapRawLightmap ); Sys_Printf( "%9d luxels\n", numLuxels ); Sys_Printf( "%9d luxels mapped\n", numLuxelsMapped ); Sys_Printf( "%9d luxels occluded\n", numLuxelsOccluded ); /* dirty them up */ if( dirty ) { Sys_Printf( "--- DirtyRawLightmap ---\n" ); RunThreadsOnIndividual( numRawLightmaps, qtrue, DirtyRawLightmap ); } /* ydnar: set up light envelopes */ SetupEnvelopes( qfalse, fast ); /* light up my world */ lightsPlaneCulled = 0; lightsEnvelopeCulled = 0; lightsBoundsCulled = 0; lightsClusterCulled = 0; Sys_Printf( "--- IlluminateRawLightmap ---\n" ); RunThreadsOnIndividual( numRawLightmaps, qtrue, IlluminateRawLightmap ); Sys_Printf( "%9d luxels illuminated\n", numLuxelsIlluminated ); StitchSurfaceLightmaps(); Sys_Printf( "--- IlluminateVertexes ---\n" ); RunThreadsOnIndividual( numBSPDrawSurfaces, qtrue, IlluminateVertexes ); Sys_Printf( "%9d vertexes illuminated\n", numVertsIlluminated ); /* ydnar: emit statistics on light culling */ Sys_FPrintf( SYS_VRB, "%9d lights plane culled\n", lightsPlaneCulled ); Sys_FPrintf( SYS_VRB, "%9d lights envelope culled\n", lightsEnvelopeCulled ); Sys_FPrintf( SYS_VRB, "%9d lights bounds culled\n", lightsBoundsCulled ); Sys_FPrintf( SYS_VRB, "%9d lights cluster culled\n", lightsClusterCulled ); /* radiosity */ b = 1; bt = bounce; while( bounce > 0 ) { /* store off the bsp between bounces */ StoreSurfaceLightmaps(); Sys_Printf( "Writing %s\n", source ); WriteBSPFile( source ); /* note it */ Sys_Printf( "\n--- Radiosity (bounce %d of %d) ---\n", b, bt ); /* flag bouncing */ bouncing = qtrue; VectorClear( ambientColor ); /* generate diffuse lights */ RadFreeLights(); RadCreateDiffuseLights(); /* setup light envelopes */ SetupEnvelopes( qfalse, fastbounce ); if( numLights == 0 ) { Sys_Printf( "No diffuse light to calculate, ending radiosity.\n" ); break; } /* add to lightgrid */ if( bouncegrid ) { gridEnvelopeCulled = 0; gridBoundsCulled = 0; Sys_Printf( "--- BounceGrid ---\n" ); RunThreadsOnIndividual( numRawGridPoints, qtrue, TraceGrid ); Sys_FPrintf( SYS_VRB, "%9d grid points envelope culled\n", gridEnvelopeCulled ); Sys_FPrintf( SYS_VRB, "%9d grid points bounds culled\n", gridBoundsCulled ); } /* light up my world */ lightsPlaneCulled = 0; lightsEnvelopeCulled = 0; lightsBoundsCulled = 0; lightsClusterCulled = 0; Sys_Printf( "--- IlluminateRawLightmap ---\n" ); RunThreadsOnIndividual( numRawLightmaps, qtrue, IlluminateRawLightmap ); Sys_Printf( "%9d luxels illuminated\n", numLuxelsIlluminated ); Sys_Printf( "%9d vertexes illuminated\n", numVertsIlluminated ); StitchSurfaceLightmaps(); Sys_Printf( "--- IlluminateVertexes ---\n" ); RunThreadsOnIndividual( numBSPDrawSurfaces, qtrue, IlluminateVertexes ); Sys_Printf( "%9d vertexes illuminated\n", numVertsIlluminated ); /* ydnar: emit statistics on light culling */ Sys_FPrintf( SYS_VRB, "%9d lights plane culled\n", lightsPlaneCulled ); Sys_FPrintf( SYS_VRB, "%9d lights envelope culled\n", lightsEnvelopeCulled ); Sys_FPrintf( SYS_VRB, "%9d lights bounds culled\n", lightsBoundsCulled ); Sys_FPrintf( SYS_VRB, "%9d lights cluster culled\n", lightsClusterCulled ); /* interate */ bounce--; b++; } } /* LightMain() main routine for light processing */ int LightMain( int argc, char **argv ) { int i; float f; char mapSource[ 1024 ]; const char *value; /* note it */ Sys_Printf( "--- Light ---\n" ); /* set standard game flags */ wolfLight = game->wolfLight; lmCustomSize = game->lightmapSize; lightmapGamma = game->lightmapGamma; lightmapCompensate = game->lightmapCompensate; /* process commandline arguments */ for( i = 1; i < (argc - 1); i++ ) { /* lightsource scaling */ if( !strcmp( argv[ i ], "-point" ) || !strcmp( argv[ i ], "-pointscale" ) ) { f = atof( argv[ i + 1 ] ); pointScale *= f; Sys_Printf( "Point (entity) light scaled by %f to %f\n", f, pointScale ); i++; } else if( !strcmp( argv[ i ], "-area" ) || !strcmp( argv[ i ], "-areascale" ) ) { f = atof( argv[ i + 1 ] ); areaScale *= f; Sys_Printf( "Area (shader) light scaled by %f to %f\n", f, areaScale ); i++; } else if( !strcmp( argv[ i ], "-sky" ) || !strcmp( argv[ i ], "-skyscale" ) ) { f = atof( argv[ i + 1 ] ); skyScale *= f; Sys_Printf( "Sky/sun light scaled by %f to %f\n", f, skyScale ); i++; } else if( !strcmp( argv[ i ], "-bouncescale" ) ) { f = atof( argv[ i + 1 ] ); bounceScale *= f; Sys_Printf( "Bounce (radiosity) light scaled by %f to %f\n", f, bounceScale ); i++; } else if( !strcmp( argv[ i ], "-scale" ) ) { f = atof( argv[ i + 1 ] ); pointScale *= f; areaScale *= f; skyScale *= f; bounceScale *= f; Sys_Printf( "All light scaled by %f\n", f ); i++; } else if( !strcmp( argv[ i ], "-gamma" ) ) { f = atof( argv[ i + 1 ] ); lightmapGamma = f; Sys_Printf( "Lighting gamma set to %f\n", lightmapGamma ); i++; } else if( !strcmp( argv[ i ], "-compensate" ) ) { f = atof( argv[ i + 1 ] ); if( f <= 0.0f ) f = 1.0f; lightmapCompensate = f; Sys_Printf( "Lighting compensation set to 1/%f\n", lightmapCompensate ); i++; } /* ydnar switches */ else if( !strcmp( argv[ i ], "-bounce" ) ) { bounce = atoi( argv[ i + 1 ] ); if( bounce < 0 ) bounce = 0; else if( bounce > 0 ) Sys_Printf( "Radiosity enabled with %d bounce(s)\n", bounce ); i++; } else if( !strcmp( argv[ i ], "-supersample" ) || !strcmp( argv[ i ], "-super" ) ) { superSample = atoi( argv[ i + 1 ] ); if( superSample < 1 ) superSample = 1; else if( superSample > 1 ) Sys_Printf( "Ordered-grid supersampling enabled with %d sample(s) per lightmap texel\n", (superSample * superSample) ); i++; } else if( !strcmp( argv[ i ], "-samples" ) ) { lightSamples = atoi( argv[ i + 1 ] ); if( lightSamples < 1 ) lightSamples = 1; else if( lightSamples > 1 ) Sys_Printf( "Adaptive supersampling enabled with %d sample(s) per lightmap texel\n", lightSamples ); i++; } else if( !strcmp( argv[ i ], "-filter" ) ) { filter = qtrue; Sys_Printf( "Lightmap filtering enabled\n" ); } else if( !strcmp( argv[ i ], "-dark" ) ) { dark = qtrue; Sys_Printf( "Dark lightmap seams enabled\n" ); } else if( !strcmp( argv[ i ], "-shadeangle" ) ) { shadeAngleDegrees = atof( argv[ i + 1 ] ); if( shadeAngleDegrees < 0.0f ) shadeAngleDegrees = 0.0f; else if( shadeAngleDegrees > 0.0f ) { shade = qtrue; Sys_Printf( "Phong shading enabled with a breaking angle of %f degrees\n", shadeAngleDegrees ); } i++; } else if( !strcmp( argv[ i ], "-thresh" ) ) { subdivideThreshold = atof( argv[ i + 1 ] ); if( subdivideThreshold < 0 ) subdivideThreshold = DEFAULT_SUBDIVIDE_THRESHOLD; else Sys_Printf( "Subdivision threshold set at %.3f\n", subdivideThreshold ); i++; } else if( !strcmp( argv[ i ], "-approx" ) ) { approximateTolerance = atoi( argv[ i + 1 ] ); if( approximateTolerance < 0 ) approximateTolerance = 0; else if( approximateTolerance > 0 ) Sys_Printf( "Approximating lightmaps within a byte tolerance of %d\n", approximateTolerance ); i++; } else if( !strcmp( argv[ i ], "-deluxe" ) || !strcmp( argv[ i ], "-deluxemap" ) ) { deluxemap = qtrue; Sys_Printf( "Generating deluxemaps for average light direction\n" ); } else if( !strcmp( argv[ i ], "-external" ) ) { externalLightmaps = qtrue; Sys_Printf( "Storing all lightmaps externally\n" ); } else if( !strcmp( argv[ i ], "-lightmapsize" ) ) { lmCustomSize = atoi( argv[ i + 1 ] ); /* must be a power of 2 and greater than 2 */ if( ((lmCustomSize - 1) & lmCustomSize) || lmCustomSize < 2 ) { Sys_Printf( "WARNING: Lightmap size must be a power of 2, greater or equal to 2 pixels.\n" ); lmCustomSize = game->lightmapSize; } i++; Sys_Printf( "Default lightmap size set to %d x %d pixels\n", lmCustomSize, lmCustomSize ); /* enable external lightmaps */ if( lmCustomSize != game->lightmapSize ) { externalLightmaps = qtrue; Sys_Printf( "Storing all lightmaps externally\n" ); } } /* ydnar: add this to suppress warnings */ else if( !strcmp( argv[ i ], "-custinfoparms") ) { Sys_Printf( "Custom info parms enabled\n" ); useCustomInfoParms = qtrue; } else if( !strcmp( argv[ i ], "-wolf" ) ) { /* -game should already be set */ wolfLight = qtrue; Sys_Printf( "Enabling Wolf lighting model (linear default)\n" ); } else if( !strcmp( argv[ i ], "-q3" ) ) { /* -game should already be set */ wolfLight = qfalse; Sys_Printf( "Enabling Quake 3 lighting model (nonlinear default)\n" ); } else if( !strcmp( argv[ i ], "-sunonly" ) ) { sunOnly = qtrue; Sys_Printf( "Only computing sunlight\n" ); } else if( !strcmp( argv[ i ], "-bounceonly" ) ) { bounceOnly = qtrue; Sys_Printf( "Storing bounced light (radiosity) only\n" ); } else if( !strcmp( argv[ i ], "-nocollapse" ) ) { noCollapse = qtrue; Sys_Printf( "Identical lightmap collapsing disabled\n" ); } else if( !strcmp( argv[ i ], "-shade" ) ) { shade = qtrue; Sys_Printf( "Phong shading enabled\n" ); } else if( !strcmp( argv[ i ], "-bouncegrid") ) { bouncegrid = qtrue; if( bounce > 0 ) Sys_Printf( "Grid lighting with radiosity enabled\n" ); } else if( !strcmp( argv[ i ], "-smooth" ) ) { lightSamples = EXTRA_SCALE; Sys_Printf( "The -smooth argument is deprecated, use \"-samples 2\" instead\n" ); } else if( !strcmp( argv[ i ], "-fast" ) ) { fast = qtrue; fastgrid = qtrue; fastbounce = qtrue; Sys_Printf( "Fast mode enabled\n" ); } else if( !strcmp( argv[ i ], "-faster" ) ) { faster = qtrue; fast = qtrue; fastgrid = qtrue; fastbounce = qtrue; Sys_Printf( "Faster mode enabled\n" ); } else if( !strcmp( argv[ i ], "-fastgrid" ) ) { fastgrid = qtrue; Sys_Printf( "Fast grid lighting enabled\n" ); } else if( !strcmp( argv[ i ], "-fastbounce" ) ) { fastbounce = qtrue; Sys_Printf( "Fast bounce mode enabled\n" ); } else if( !strcmp( argv[ i ], "-cheap" ) ) { cheap = qtrue; cheapgrid = qtrue; Sys_Printf( "Cheap mode enabled\n" ); } else if( !strcmp( argv[ i ], "-cheapgrid" ) ) { cheapgrid = qtrue; Sys_Printf( "Cheap grid mode enabled\n" ); } else if( !strcmp( argv[ i ], "-normalmap" ) ) { normalmap = qtrue; Sys_Printf( "Storing normal map instead of lightmap\n" ); } else if( !strcmp( argv[ i ], "-trisoup" ) ) { trisoup = qtrue; Sys_Printf( "Converting brush faces to triangle soup\n" ); } else if( !strcmp( argv[ i ], "-debug" ) ) { debug = qtrue; Sys_Printf( "Lightmap debugging enabled\n" ); } else if( !strcmp( argv[ i ], "-debugsurfaces" ) || !strcmp( argv[ i ], "-debugsurface" ) ) { debugSurfaces = qtrue; Sys_Printf( "Lightmap surface debugging enabled\n" ); } else if( !strcmp( argv[ i ], "-debugunused" ) ) { debugUnused = qtrue; Sys_Printf( "Unused luxel debugging enabled\n" ); } else if( !strcmp( argv[ i ], "-debugaxis" ) ) { debugAxis = qtrue; Sys_Printf( "Lightmap axis debugging enabled\n" ); } else if( !strcmp( argv[ i ], "-debugcluster" ) ) { debugCluster = qtrue; Sys_Printf( "Luxel cluster debugging enabled\n" ); } else if( !strcmp( argv[ i ], "-debugorigin" ) ) { debugOrigin = qtrue; Sys_Printf( "Luxel origin debugging enabled\n" ); } else if( !strcmp( argv[ i ], "-debugdeluxe" ) ) { deluxemap = qtrue; debugDeluxemap = qtrue; Sys_Printf( "Deluxemap debugging enabled\n" ); } else if( !strcmp( argv[ i ], "-export" ) ) { exportLightmaps = qtrue; Sys_Printf( "Exporting lightmaps\n" ); } else if( !strcmp(argv[ i ], "-notrace" )) { noTrace = qtrue; Sys_Printf( "Shadow occlusion disabled\n" ); } else if( !strcmp(argv[ i ], "-patchshadows" ) ) { patchShadows = qtrue; Sys_Printf( "Patch shadow casting enabled\n" ); } else if( !strcmp( argv[ i ], "-extra" ) ) { superSample = EXTRA_SCALE; /* ydnar */ Sys_Printf( "The -extra argument is deprecated, use \"-super 2\" instead\n" ); } else if( !strcmp( argv[ i ], "-extrawide" ) ) { superSample = EXTRAWIDE_SCALE; /* ydnar */ filter = qtrue; /* ydnar */ Sys_Printf( "The -extrawide argument is deprecated, use \"-filter [-super 2]\" instead\n"); } else if( !strcmp( argv[ i ], "-samplesize" ) ) { sampleSize = atoi( argv[ i + 1 ] ); if( sampleSize < 1 ) sampleSize = 1; i++; Sys_Printf( "Default lightmap sample size set to %dx%d units\n", sampleSize, sampleSize ); } else if( !strcmp( argv[ i ], "-novertex" ) ) { noVertexLighting = qtrue; Sys_Printf( "Disabling vertex lighting\n" ); } else if( !strcmp( argv[ i ], "-nogrid" ) ) { noGridLighting = qtrue; Sys_Printf( "Disabling grid lighting\n" ); } else if( !strcmp( argv[ i ], "-border" ) ) { lightmapBorder = qtrue; Sys_Printf( "Adding debug border to lightmaps\n" ); } else if( !strcmp( argv[ i ], "-nosurf" ) ) { noSurfaces = qtrue; Sys_Printf( "Not tracing against surfaces\n" ); } else if( !strcmp( argv[ i ], "-dump" ) ) { dump = qtrue; Sys_Printf( "Dumping radiosity lights into numbered prefabs\n" ); } else if( !strcmp( argv[ i ], "-lomem" ) ) { loMem = qtrue; Sys_Printf( "Enabling low-memory (potentially slower) lighting mode\n" ); } else if( !strcmp( argv[ i ], "-nostyle" ) || !strcmp( argv[ i ], "-nostyles" ) ) { noStyles = qtrue; Sys_Printf( "Disabling lightstyles\n" ); } else if( !strcmp( argv[ i ], "-cpma" ) ) { cpmaHack = qtrue; Sys_Printf( "Enabling Challenge Pro Mode Asstacular Vertex Lighting Mode (tm)\n" ); } /* r7: dirtmapping */ else if( !strcmp( argv[ i ], "-dirty" ) ) { dirty = qtrue; Sys_Printf( "Dirtmapping enabled\n" ); } else if( !strcmp( argv[ i ], "-dirtdebug" ) || !strcmp( argv[ i ], "-debugdirt" ) ) { dirtDebug = qtrue; Sys_Printf( "Dirtmap debugging enabled\n" ); } else if( !strcmp( argv[ i ], "-dirtmode" ) ) { dirtMode = atoi( argv[ i + 1 ] ); if( dirtMode != 0 && dirtMode != 1 ) dirtMode = 0; if( dirtMode == 1 ) Sys_Printf( "Enabling randomized dirtmapping\n" ); else Sys_Printf( "Enabling ordered dir mapping\n" ); } else if( !strcmp( argv[ i ], "-dirtdepth" ) ) { dirtDepth = atof( argv[ i + 1 ] ); if( dirtDepth <= 0.0f ) dirtDepth = 128.0f; Sys_Printf( "Dirtmapping depth set to %.1f\n", dirtDepth ); } else if( !strcmp( argv[ i ], "-dirtscale" ) ) { dirtScale = atof( argv[ i + 1 ] ); if( dirtScale <= 0.0f ) dirtScale = 1.0f; Sys_Printf( "Dirtmapping scale set to %.1f\n", dirtScale ); } else if( !strcmp( argv[ i ], "-dirtgain" ) ) { dirtGain = atof( argv[ i + 1 ] ); if( dirtGain <= 0.0f ) dirtGain = 1.0f; Sys_Printf( "Dirtmapping gain set to %.1f\n", dirtGain ); } /* unhandled args */ else Sys_Printf( "WARNING: Unknown argument \"%s\"\n", argv[ i ] ); } /* clean up map name */ strcpy( source, ExpandArg( argv[ i ] ) ); StripExtension( source ); DefaultExtension( source, ".bsp" ); strcpy( mapSource, ExpandArg( argv[ i ] ) ); StripExtension( mapSource ); DefaultExtension( mapSource, ".map" ); /* ydnar: set default sample size */ SetDefaultSampleSize( sampleSize ); /* ydnar: handle shaders */ BeginMapShaderFile( source ); LoadShaderInfo(); /* note loading */ Sys_Printf( "Loading %s\n", source ); /* ydnar: load surface file */ LoadSurfaceExtraFile( source ); /* load bsp file */ LoadBSPFile( source ); /* parse bsp entities */ ParseEntities(); /* load map file */ value = ValueForKey( &entities[ 0 ], "_keepLights" ); if( value[ 0 ] != '1' ) LoadMapFile( mapSource, qtrue ); /* set the entity/model origins and init yDrawVerts */ SetEntityOrigins(); /* ydnar: set up optimization */ SetupBrushes(); SetupDirt(); SetupSurfaceLightmaps(); /* initialize the surface facet tracing */ SetupTraceNodes(); /* light the world */ LightWorld(); /* ydnar: store off lightmaps */ StoreSurfaceLightmaps(); /* write out the bsp */ UnparseEntities(); Sys_Printf( "Writing %s\n", source ); WriteBSPFile( source ); /* ydnar: export lightmaps */ if( exportLightmaps && !externalLightmaps ) ExportLightmaps(); /* return to sender */ return 0; }