From 952c5c128f9efaea89d41d882c4ea3ade7df4591 Mon Sep 17 00:00:00 2001 From: zakk Date: Fri, 26 Aug 2005 04:48:05 +0000 Subject: Itsa me, quake3io! git-svn-id: svn://svn.icculus.org/quake3/trunk@2 edf5b092-35ff-0310-97b2-ce42778d08ea --- q3map/light.c | 2149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2149 insertions(+) create mode 100755 q3map/light.c (limited to 'q3map/light.c') diff --git a/q3map/light.c b/q3map/light.c new file mode 100755 index 0000000..a3d368b --- /dev/null +++ b/q3map/light.c @@ -0,0 +1,2149 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// light.c + +#include "light.h" +#ifdef _WIN32 +#ifdef _TTIMOBUILD +#include "pakstuff.h" +#else +#include "../libs/pakstuff.h" +#endif +#endif + + +#define EXTRASCALE 2 + +typedef struct { + float plane[4]; + vec3_t origin; + vec3_t vectors[2]; + shaderInfo_t *si; +} filter_t; + +#define MAX_FILTERS 1024 +filter_t filters[MAX_FILTERS]; +int numFilters; + +extern char source[1024]; + +qboolean notrace; +qboolean patchshadows; +qboolean dump; +qboolean extra; +qboolean extraWide; +qboolean lightmapBorder; + +qboolean noSurfaces; + +int samplesize = 16; //sample size in units +int novertexlighting = 0; +int nogridlighting = 0; + +// for run time tweaking of all area sources in the level +float areaScale = 0.25; + +// for run time tweaking of all point sources in the level +float pointScale = 7500; + +qboolean exactPointToPolygon = qtrue; + +float formFactorValueScale = 3; + +float linearScale = 1.0 / 8000; + +light_t *lights; +int numPointLights; +int numAreaLights; + +FILE *dumpFile; + +int c_visible, c_occluded; + +//int defaultLightSubdivide = 128; // vary by surface size? +int defaultLightSubdivide = 999; // vary by surface size? + +vec3_t ambientColor; + +vec3_t surfaceOrigin[ MAX_MAP_DRAW_SURFS ]; +int entitySurface[ MAX_MAP_DRAW_SURFS ]; + +// 7,9,11 normalized to avoid being nearly coplanar with common faces +//vec3_t sunDirection = { 0.441835, 0.56807, 0.694313 }; +//vec3_t sunDirection = { 0.45, 0, 0.9 }; +//vec3_t sunDirection = { 0, 0, 1 }; + +// these are usually overrided by shader values +vec3_t sunDirection = { 0.45, 0.3, 0.9 }; +vec3_t sunLight = { 100, 100, 50 }; + + + +typedef struct { + dbrush_t *b; + vec3_t bounds[2]; +} skyBrush_t; + +int numSkyBrushes; +skyBrush_t skyBrushes[MAX_MAP_BRUSHES]; + + +/* + +the corners of a patch mesh will always be exactly at lightmap samples. +The dimensions of the lightmap will be equal to the average length of the control +mesh in each dimension divided by 2. +The lightmap sample points should correspond to the chosen subdivision points. + +*/ + +/* +=============================================================== + +SURFACE LOADING + +=============================================================== +*/ + +#define MAX_FACE_POINTS 128 + +/* +=============== +SubdivideAreaLight + +Subdivide area lights that are very large +A light that is subdivided will never backsplash, avoiding weird pools of light near edges +=============== +*/ +void SubdivideAreaLight( shaderInfo_t *ls, winding_t *w, vec3_t normal, + float areaSubdivide, qboolean backsplash ) { + float area, value, intensity; + light_t *dl, *dl2; + vec3_t mins, maxs; + int axis; + winding_t *front, *back; + vec3_t planeNormal; + float planeDist; + + if ( !w ) { + return; + } + + WindingBounds( w, mins, maxs ); + + // check for subdivision + for ( axis = 0 ; axis < 3 ; axis++ ) { + if ( maxs[axis] - mins[axis] > areaSubdivide ) { + VectorClear( planeNormal ); + planeNormal[axis] = 1; + planeDist = ( maxs[axis] + mins[axis] ) * 0.5; + ClipWindingEpsilon ( w, planeNormal, planeDist, ON_EPSILON, &front, &back ); + SubdivideAreaLight( ls, front, normal, areaSubdivide, qfalse ); + SubdivideAreaLight( ls, back, normal, areaSubdivide, qfalse ); + FreeWinding( w ); + return; + } + } + + // create a light from this + area = WindingArea (w); + if ( area <= 0 || area > 20000000 ) { + return; + } + + numAreaLights++; + dl = malloc(sizeof(*dl)); + memset (dl, 0, sizeof(*dl)); + dl->next = lights; + lights = dl; + dl->type = emit_area; + + WindingCenter( w, dl->origin ); + dl->w = w; + VectorCopy ( normal, dl->normal); + dl->dist = DotProduct( dl->origin, normal ); + + value = ls->value; + intensity = value * area * areaScale; + VectorAdd( dl->origin, dl->normal, dl->origin ); + + VectorCopy( ls->color, dl->color ); + + dl->photons = intensity; + + // emitColor is irrespective of the area + VectorScale( ls->color, value*formFactorValueScale*areaScale, dl->emitColor ); + + dl->si = ls; + + if ( ls->contents & CONTENTS_FOG ) { + dl->twosided = qtrue; + } + + // optionally create a point backsplash light + if ( backsplash && ls->backsplashFraction > 0 ) { + dl2 = malloc(sizeof(*dl)); + memset (dl2, 0, sizeof(*dl2)); + dl2->next = lights; + lights = dl2; + dl2->type = emit_point; + + VectorMA( dl->origin, ls->backsplashDistance, normal, dl2->origin ); + + VectorCopy( ls->color, dl2->color ); + + dl2->photons = dl->photons * ls->backsplashFraction; + dl2->si = ls; + } +} + + +/* +=============== +CountLightmaps +=============== +*/ +void CountLightmaps( void ) { + int count; + int i; + dsurface_t *ds; + + qprintf ("--- CountLightmaps ---\n"); + count = 0; + for ( i = 0 ; i < numDrawSurfaces ; i++ ) { + // see if this surface is light emiting + ds = &drawSurfaces[i]; + if ( ds->lightmapNum > count ) { + count = ds->lightmapNum; + } + } + + count++; + numLightBytes = count * LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * 3; + if ( numLightBytes > MAX_MAP_LIGHTING ) { + Error("MAX_MAP_LIGHTING exceeded"); + } + + qprintf( "%5i drawSurfaces\n", numDrawSurfaces ); + qprintf( "%5i lightmaps\n", count ); +} + +/* +=============== +CreateSurfaceLights + +This creates area lights +=============== +*/ +void CreateSurfaceLights( void ) { + int i, j, side; + dsurface_t *ds; + shaderInfo_t *ls; + winding_t *w; + cFacet_t *f; + light_t *dl; + vec3_t origin; + drawVert_t *dv; + int c_lightSurfaces; + float lightSubdivide; + vec3_t normal; + + qprintf ("--- CreateSurfaceLights ---\n"); + c_lightSurfaces = 0; + + for ( i = 0 ; i < numDrawSurfaces ; i++ ) { + // see if this surface is light emiting + ds = &drawSurfaces[i]; + + ls = ShaderInfoForShader( dshaders[ ds->shaderNum].shader ); + if ( ls->value == 0 ) { + continue; + } + + // determine how much we need to chop up the surface + if ( ls->lightSubdivide ) { + lightSubdivide = ls->lightSubdivide; + } else { + lightSubdivide = defaultLightSubdivide; + } + + c_lightSurfaces++; + + // an autosprite shader will become + // a point light instead of an area light + if ( ls->autosprite ) { + // autosprite geometry should only have four vertexes + if ( surfaceTest[i] ) { + // curve or misc_model + f = surfaceTest[i]->facets; + if ( surfaceTest[i]->numFacets != 1 || f->numBoundaries != 4 ) { + _printf( "WARNING: surface at (%i %i %i) has autosprite shader but isn't a quad\n", + (int)f->points[0], (int)f->points[1], (int)f->points[2] ); + } + VectorAdd( f->points[0], f->points[1], origin ); + VectorAdd( f->points[2], origin, origin ); + VectorAdd( f->points[3], origin, origin ); + VectorScale( origin, 0.25, origin ); + } else { + // normal polygon + dv = &drawVerts[ ds->firstVert ]; + if ( ds->numVerts != 4 ) { + _printf( "WARNING: surface at (%i %i %i) has autosprite shader but %i verts\n", + (int)dv->xyz[0], (int)dv->xyz[1], (int)dv->xyz[2] ); + continue; + } + + VectorAdd( dv[0].xyz, dv[1].xyz, origin ); + VectorAdd( dv[2].xyz, origin, origin ); + VectorAdd( dv[3].xyz, origin, origin ); + VectorScale( origin, 0.25, origin ); + } + + + numPointLights++; + dl = malloc(sizeof(*dl)); + memset (dl, 0, sizeof(*dl)); + dl->next = lights; + lights = dl; + + VectorCopy( origin, dl->origin ); + VectorCopy( ls->color, dl->color ); + dl->photons = ls->value * pointScale; + dl->type = emit_point; + continue; + } + + // possibly create for both sides of the polygon + for ( side = 0 ; side <= ls->twoSided ; side++ ) { + // create area lights + if ( surfaceTest[i] ) { + // curve or misc_model + for ( j = 0 ; j < surfaceTest[i]->numFacets ; j++ ) { + f = surfaceTest[i]->facets + j; + w = AllocWinding( f->numBoundaries ); + w->numpoints = f->numBoundaries; + memcpy( w->p, f->points, f->numBoundaries * 12 ); + + VectorCopy( f->surface, normal ); + if ( side ) { + winding_t *t; + + t = w; + w = ReverseWinding( t ); + FreeWinding( t ); + VectorSubtract( vec3_origin, normal, normal ); + } + SubdivideAreaLight( ls, w, normal, lightSubdivide, qtrue ); + } + } else { + // normal polygon + + w = AllocWinding( ds->numVerts ); + w->numpoints = ds->numVerts; + for ( j = 0 ; j < ds->numVerts ; j++ ) { + VectorCopy( drawVerts[ds->firstVert+j].xyz, w->p[j] ); + } + VectorCopy( ds->lightmapVecs[2], normal ); + if ( side ) { + winding_t *t; + + t = w; + w = ReverseWinding( t ); + FreeWinding( t ); + VectorSubtract( vec3_origin, normal, normal ); + } + SubdivideAreaLight( ls, w, normal, lightSubdivide, qtrue ); + } + } + } + + _printf( "%5i light emitting surfaces\n", c_lightSurfaces ); +} + + + +/* +================ +FindSkyBrushes +================ +*/ +void FindSkyBrushes( void ) { + int i, j; + dbrush_t *b; + skyBrush_t *sb; + shaderInfo_t *si; + dbrushside_t *s; + + // find the brushes + for ( i = 0 ; i < numbrushes ; i++ ) { + b = &dbrushes[i]; + for ( j = 0 ; j < b->numSides ; j++ ) { + s = &dbrushsides[ b->firstSide + j ]; + if ( dshaders[ s->shaderNum ].surfaceFlags & SURF_SKY ) { + sb = &skyBrushes[ numSkyBrushes ]; + sb->b = b; + sb->bounds[0][0] = -dplanes[ dbrushsides[ b->firstSide + 0 ].planeNum ].dist - 1; + sb->bounds[1][0] = dplanes[ dbrushsides[ b->firstSide + 1 ].planeNum ].dist + 1; + sb->bounds[0][1] = -dplanes[ dbrushsides[ b->firstSide + 2 ].planeNum ].dist - 1; + sb->bounds[1][1] = dplanes[ dbrushsides[ b->firstSide + 3 ].planeNum ].dist + 1; + sb->bounds[0][2] = -dplanes[ dbrushsides[ b->firstSide + 4 ].planeNum ].dist - 1; + sb->bounds[1][2] = dplanes[ dbrushsides[ b->firstSide + 5 ].planeNum ].dist + 1; + numSkyBrushes++; + break; + } + } + } + + // default + VectorNormalize( sunDirection, sunDirection ); + + // find the sky shader + for ( i = 0 ; i < numDrawSurfaces ; i++ ) { + si = ShaderInfoForShader( dshaders[ drawSurfaces[i].shaderNum ].shader ); + if ( si->surfaceFlags & SURF_SKY ) { + VectorCopy( si->sunLight, sunLight ); + VectorCopy( si->sunDirection, sunDirection ); + break; + } + } +} + +/* +================================================================= + + LIGHT SETUP + +================================================================= +*/ + +/* +================== +FindTargetEntity +================== +*/ +entity_t *FindTargetEntity( const char *target ) { + int i; + const char *n; + + for ( i = 0 ; i < num_entities ; i++ ) { + n = ValueForKey (&entities[i], "targetname"); + if ( !strcmp (n, target) ) { + return &entities[i]; + } + } + + return NULL; +} + + + +/* +============= +CreateEntityLights +============= +*/ +void CreateEntityLights (void) +{ + int i; + light_t *dl; + entity_t *e, *e2; + const char *name; + const char *target; + vec3_t dest; + const char *_color; + float intensity; + int spawnflags; + + // + // entities + // + for ( i = 0 ; i < num_entities ; i++ ) { + e = &entities[i]; + name = ValueForKey (e, "classname"); + if (strncmp (name, "light", 5)) + continue; + + numPointLights++; + dl = malloc(sizeof(*dl)); + memset (dl, 0, sizeof(*dl)); + dl->next = lights; + lights = dl; + + spawnflags = FloatForKey (e, "spawnflags"); + if ( spawnflags & 1 ) { + dl->linearLight = qtrue; + } + + GetVectorForKey (e, "origin", dl->origin); + dl->style = FloatForKey (e, "_style"); + if (!dl->style) + dl->style = FloatForKey (e, "style"); + if (dl->style < 0) + dl->style = 0; + + intensity = FloatForKey (e, "light"); + if (!intensity) + intensity = FloatForKey (e, "_light"); + if (!intensity) + intensity = 300; + _color = ValueForKey (e, "_color"); + if (_color && _color[0]) + { + sscanf (_color, "%f %f %f", &dl->color[0],&dl->color[1],&dl->color[2]); + ColorNormalize (dl->color, dl->color); + } + else + dl->color[0] = dl->color[1] = dl->color[2] = 1.0; + + intensity = intensity * pointScale; + dl->photons = intensity; + + dl->type = emit_point; + + // lights with a target will be spotlights + target = ValueForKey (e, "target"); + + if ( target[0] ) { + float radius; + float dist; + + e2 = FindTargetEntity (target); + if (!e2) { + _printf ("WARNING: light at (%i %i %i) has missing target\n", + (int)dl->origin[0], (int)dl->origin[1], (int)dl->origin[2]); + } else { + GetVectorForKey (e2, "origin", dest); + VectorSubtract (dest, dl->origin, dl->normal); + dist = VectorNormalize (dl->normal, dl->normal); + radius = FloatForKey (e, "radius"); + if ( !radius ) { + radius = 64; + } + if ( !dist ) { + dist = 64; + } + dl->radiusByDist = (radius + 16) / dist; + dl->type = emit_spotlight; + } + } + } +} + +//================================================================= + +/* +================ +SetEntityOrigins + +Find the offset values for inline models +================ +*/ +void SetEntityOrigins( void ) { + int i, j; + entity_t *e; + vec3_t origin; + const char *key; + int modelnum; + dmodel_t *dm; + + for ( i=0 ; i < num_entities ; i++ ) { + e = &entities[i]; + key = ValueForKey (e, "model"); + if ( key[0] != '*' ) { + continue; + } + modelnum = atoi( key + 1 ); + dm = &dmodels[ modelnum ]; + + // set entity surface to true for all surfaces for this model + for ( j = 0 ; j < dm->numSurfaces ; j++ ) { + entitySurface[ dm->firstSurface + j ] = qtrue; + } + + key = ValueForKey (e, "origin"); + if ( !key[0] ) { + continue; + } + GetVectorForKey ( e, "origin", origin ); + + // set origin for all surfaces for this model + for ( j = 0 ; j < dm->numSurfaces ; j++ ) { + VectorCopy( origin, surfaceOrigin[ dm->firstSurface + j ] ); + } + } +} + + +/* +================================================================= + + +================================================================= +*/ + +#define MAX_POINTS_ON_WINDINGS 64 + +/* +================ +PointToPolygonFormFactor +================ +*/ +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; + + 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] ); + + total = 0; + for ( i = 0 ; i < w->numpoints ; i++ ) { + j = i+1; + dot = DotProduct( dirs[i], dirs[j] ); + + // roundoff can cause slight creep, which gives an IND from acos + if ( dot > 1.0 ) { + dot = 1.0; + } else if ( dot < -1.0 ) { + dot = -1.0; + } + + angle = acos( dot ); + CrossProduct( dirs[i], dirs[j], triVector ); + if ( VectorNormalize( triVector, triNormal ) < 0.0001 ) { + continue; + } + facing = DotProduct( normal, triNormal ); + total += facing * angle; + + if ( total > 6.3 || total < -6.3 ) { + static qboolean printed; + + if ( !printed ) { + printed = qtrue; + _printf( "WARNING: bad PointToPolygonFormFactor: %f at %1.1f %1.1f %1.1f from %1.1f %1.1f %1.1f\n", total, + w->p[i][0], w->p[i][1], w->p[i][2], point[0], point[1], point[2]); + } + return 0; + } + + } + + total /= 2*3.141592657; // now in the range of 0 to 1 over the entire incoming hemisphere + + return total; +} + + +/* +================ +FilterTrace + +Returns 0 to 1.0 filter fractions for the given trace +================ +*/ +void FilterTrace( const vec3_t start, const vec3_t end, vec3_t filter ) { + float d1, d2; + filter_t *f; + int filterNum; + vec3_t point; + float frac; + int i; + float s, t; + int u, v; + int x, y; + byte *pixel; + float radius; + float len; + vec3_t total; + + filter[0] = 1.0; + filter[1] = 1.0; + filter[2] = 1.0; + + for ( filterNum = 0 ; filterNum < numFilters ; filterNum++ ) { + f = &filters[ filterNum ]; + + // see if the plane is crossed + d1 = DotProduct( start, f->plane ) - f->plane[3]; + d2 = DotProduct( end, f->plane ) - f->plane[3]; + + if ( ( d1 < 0 ) == ( d2 < 0 ) ) { + continue; + } + + // calculate the crossing point + frac = d1 / ( d1 - d2 ); + + for ( i = 0 ; i < 3 ; i++ ) { + point[i] = start[i] + frac * ( end[i] - start[i] ); + } + + VectorSubtract( point, f->origin, point ); + + s = DotProduct( point, f->vectors[0] ); + t = 1.0 - DotProduct( point, f->vectors[1] ); + if ( s < 0 || s >= 1.0 || t < 0 || t >= 1.0 ) { + continue; + } + + // decide the filter size + radius = 10 * frac; + len = VectorLength( f->vectors[0] ); + if ( !len ) { + continue; + } + radius = radius * len * f->si->width; + + // look up the filter, taking multiple samples + VectorClear( total ); + for ( u = -1 ; u <= 1 ; u++ ) { + for ( v = -1 ; v <=1 ; v++ ) { + x = s * f->si->width + u * radius; + if ( x < 0 ) { + x = 0; + } + if ( x >= f->si->width ) { + x = f->si->width - 1; + } + y = t * f->si->height + v * radius; + if ( y < 0 ) { + y = 0; + } + if ( y >= f->si->height ) { + y = f->si->height - 1; + } + + pixel = f->si->pixels + ( y * f->si->width + x ) * 4; + total[0] += pixel[0]; + total[1] += pixel[1]; + total[2] += pixel[2]; + } + } + + filter[0] *= total[0]/(255.0*9); + filter[1] *= total[1]/(255.0*9); + filter[2] *= total[2]/(255.0*9); + } + +} + +/* +================ +SunToPoint + +Returns an amount of light to add at the point +================ +*/ +int c_sunHit, c_sunMiss; +void SunToPoint( const vec3_t origin, traceWork_t *tw, vec3_t addLight ) { + int i; + trace_t trace; + skyBrush_t *b; + vec3_t end; + + if ( !numSkyBrushes ) { + VectorClear( addLight ); + return; + } + + VectorMA( origin, MAX_WORLD_COORD * 2, sunDirection, end ); + + TraceLine( origin, end, &trace, qtrue, tw ); + + // see if trace.hit is inside a sky brush + for ( i = 0 ; i < numSkyBrushes ; i++) { + b = &skyBrushes[ i ]; + + // this assumes that sky brushes are axial... + if ( trace.hit[0] < b->bounds[0][0] + || trace.hit[0] > b->bounds[1][0] + || trace.hit[1] < b->bounds[0][1] + || trace.hit[1] > b->bounds[1][1] + || trace.hit[2] < b->bounds[0][2] + || trace.hit[2] > b->bounds[1][2] ) { + continue; + } + + + // trace again to get intermediate filters + TraceLine( origin, trace.hit, &trace, qtrue, tw ); + + // we hit the sky, so add sunlight + if ( numthreads == 1 ) { + c_sunHit++; + } + addLight[0] = trace.filter[0] * sunLight[0]; + addLight[1] = trace.filter[1] * sunLight[1]; + addLight[2] = trace.filter[2] * sunLight[2]; + + return; + } + + if ( numthreads == 1 ) { + c_sunMiss++; + } + + VectorClear( addLight ); +} + +/* +================ +SunToPlane +================ +*/ +void SunToPlane( const vec3_t origin, const vec3_t normal, vec3_t color, traceWork_t *tw ) { + float angle; + vec3_t sunColor; + + if ( !numSkyBrushes ) { + return; + } + + angle = DotProduct( normal, sunDirection ); + if ( angle <= 0 ) { + return; // facing away + } + + SunToPoint( origin, tw, sunColor ); + VectorMA( color, angle, sunColor, color ); +} + +/* +================ +LightingAtSample +================ +*/ +void LightingAtSample( vec3_t origin, vec3_t normal, vec3_t color, + qboolean testOcclusion, qboolean forceSunLight, traceWork_t *tw ) { + light_t *light; + trace_t trace; + float angle; + float add; + float dist; + vec3_t dir; + + VectorCopy( ambientColor, color ); + + // trace to all the lights + for ( light = lights ; light ; light = light->next ) { + + //MrE: if the light is behind the surface + if ( DotProduct(light->origin, normal) - DotProduct(normal, origin) < 0 ) + continue; + // testing exact PTPFF + if ( exactPointToPolygon && light->type == emit_area ) { + float factor; + float d; + vec3_t pushedOrigin; + + // see if the point is behind the light + d = DotProduct( origin, light->normal ) - light->dist; + if ( !light->twosided ) { + if ( d < -1 ) { + continue; // point is behind light + } + } + + // test occlusion and find light filters + // clip the line, tracing from the surface towards the light + if ( !notrace && testOcclusion ) { + TraceLine( origin, light->origin, &trace, qfalse, tw ); + + // other light rays must not hit anything + if ( trace.passSolid ) { + continue; + } + } else { + trace.filter[0] = 1.0; + trace.filter[1] = 1.0; + trace.filter[2] = 1.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 && d < 8 ) { + VectorMA( origin, (8-d), light->normal, pushedOrigin ); + } else { + VectorCopy( origin, pushedOrigin ); + } + + // calculate the contribution + factor = PointToPolygonFormFactor( pushedOrigin, normal, light->w ); + if ( factor <= 0 ) { + if ( light->twosided ) { + factor = -factor; + } else { + continue; + } + } + color[0] += factor * light->emitColor[0] * trace.filter[0]; + color[1] += factor * light->emitColor[1] * trace.filter[1]; + color[2] += factor * light->emitColor[2] * trace.filter[2]; + + continue; + } + + // calculate the amount of light at this sample + if ( light->type == emit_point ) { + VectorSubtract( light->origin, origin, dir ); + dist = VectorNormalize( dir, dir ); + // clamp the distance to prevent super hot spots + if ( dist < 16 ) { + dist = 16; + } + angle = DotProduct( normal, dir ); + if ( light->linearLight ) { + add = angle * light->photons * linearScale - dist; + if ( add < 0 ) { + add = 0; + } + } else { + add = light->photons / ( dist * dist ) * angle; + } + } else if ( light->type == emit_spotlight ) { + float distByNormal; + vec3_t pointAtDist; + float radiusAtDist; + float sampleRadius; + vec3_t distToSample; + float coneScale; + + VectorSubtract( light->origin, origin, dir ); + + distByNormal = -DotProduct( dir, light->normal ); + if ( distByNormal < 0 ) { + continue; + } + VectorMA( light->origin, distByNormal, light->normal, pointAtDist ); + radiusAtDist = light->radiusByDist * distByNormal; + + VectorSubtract( origin, pointAtDist, distToSample ); + sampleRadius = VectorLength( distToSample ); + + if ( sampleRadius >= radiusAtDist ) { + continue; // outside the cone + } + if ( sampleRadius <= radiusAtDist - 32 ) { + coneScale = 1.0; // fully inside + } else { + coneScale = ( radiusAtDist - sampleRadius ) / 32.0; + } + + dist = VectorNormalize( dir, dir ); + // clamp the distance to prevent super hot spots + if ( dist < 16 ) { + dist = 16; + } + angle = DotProduct( normal, dir ); + add = light->photons / ( dist * dist ) * angle * coneScale; + + } else if ( light->type == emit_area ) { + VectorSubtract( light->origin, origin, dir ); + dist = VectorNormalize( dir, dir ); + // clamp the distance to prevent super hot spots + if ( dist < 16 ) { + dist = 16; + } + angle = DotProduct( normal, dir ); + if ( angle <= 0 ) { + continue; + } + angle *= -DotProduct( light->normal, dir ); + if ( angle <= 0 ) { + continue; + } + + if ( light->linearLight ) { + add = angle * light->photons * linearScale - dist; + if ( add < 0 ) { + add = 0; + } + } else { + add = light->photons / ( dist * dist ) * angle; + } + } + + if ( add <= 1.0 ) { + continue; + } + + // clip the line, tracing from the surface towards the light + if ( !notrace && testOcclusion ) { + TraceLine( origin, light->origin, &trace, qfalse, tw ); + + // other light rays must not hit anything + if ( trace.passSolid ) { + continue; + } + } else { + trace.filter[0] = 1; + trace.filter[1] = 1; + trace.filter[2] = 1; + } + + // add the result + color[0] += add * light->color[0] * trace.filter[0]; + color[1] += add * light->color[1] * trace.filter[1]; + color[2] += add * light->color[2] * trace.filter[2]; + } + + // + // trace directly to the sun + // + if ( testOcclusion || forceSunLight ) { + SunToPlane( origin, normal, color, tw ); + } +} + +/* +============= +PrintOccluded + +For debugging +============= +*/ +void PrintOccluded( byte occluded[LIGHTMAP_WIDTH*EXTRASCALE][LIGHTMAP_HEIGHT*EXTRASCALE], + int width, int height ) { + int i, j; + + _printf( "\n" ); + + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width ; j++ ) { + _printf("%i", (int)occluded[j][i] ); + } + _printf( "\n" ); + } +} + + +/* +============= +VertexLighting + +Vertex lighting will completely ignore occlusion, because +shadows would not be resolvable anyway. +============= +*/ +void VertexLighting( dsurface_t *ds, qboolean testOcclusion, qboolean forceSunLight, float scale, traceWork_t *tw ) { + int i, j; + drawVert_t *dv; + vec3_t sample, normal; + float max; + + VectorCopy( ds->lightmapVecs[2], normal ); + + // generate vertex lighting + for ( i = 0 ; i < ds->numVerts ; i++ ) { + dv = &drawVerts[ ds->firstVert + i ]; + + if ( ds->patchWidth ) { + LightingAtSample( dv->xyz, dv->normal, sample, testOcclusion, forceSunLight, tw ); + } + else if (ds->surfaceType == MST_TRIANGLE_SOUP) { + LightingAtSample( dv->xyz, dv->normal, sample, testOcclusion, forceSunLight, tw ); + } + else { + LightingAtSample( dv->xyz, normal, sample, testOcclusion, forceSunLight, tw ); + } + + if (scale >= 0) + VectorScale(sample, scale, sample); + // clamp with color normalization + max = sample[0]; + if ( sample[1] > max ) { + max = sample[1]; + } + if ( sample[2] > max ) { + max = sample[2]; + } + if ( max > 255 ) { + VectorScale( sample, 255/max, sample ); + } + + // save the sample + for ( j = 0 ; j < 3 ; j++ ) { + if ( sample[j] > 255 ) { + sample[j] = 255; + } + dv->color[j] = sample[j]; + } + + // Don't bother writing alpha since it will already be set to 255, + // plus we don't want to write over alpha generated by SetTerrainTextures + //dv->color[3] = 255; + } +} + + +/* +================= +LinearSubdivideMesh + +For extra lighting, just midpoint one of the axis. +The edges are clamped at the original edges. +================= +*/ +mesh_t *LinearSubdivideMesh( mesh_t *in ) { + int i, j; + mesh_t *out; + drawVert_t *v1, *v2, *vout; + + out = malloc( sizeof( *out ) ); + + out->width = in->width * 2; + out->height = in->height; + out->verts = malloc( out->width * out->height * sizeof(*out->verts) ); + for ( j = 0 ; j < in->height ; j++ ) { + out->verts[ j * out->width + 0 ] = in->verts[ j * in->width + 0 ]; + out->verts[ j * out->width + out->width - 1 ] = in->verts[ j * in->width + in->width - 1 ]; + for ( i = 1 ; i < out->width - 1 ; i+= 2 ) { + v1 = in->verts + j * in->width + (i >> 1); + v2 = v1 + 1; + vout = out->verts + j * out->width + i; + + vout->xyz[0] = 0.75 * v1->xyz[0] + 0.25 * v2->xyz[0]; + vout->xyz[1] = 0.75 * v1->xyz[1] + 0.25 * v2->xyz[1]; + vout->xyz[2] = 0.75 * v1->xyz[2] + 0.25 * v2->xyz[2]; + + vout->normal[0] = 0.75 * v1->normal[0] + 0.25 * v2->normal[0]; + vout->normal[1] = 0.75 * v1->normal[1] + 0.25 * v2->normal[1]; + vout->normal[2] = 0.75 * v1->normal[2] + 0.25 * v2->normal[2]; + + VectorNormalize( vout->normal, vout->normal ); + + vout++; + + vout->xyz[0] = 0.25 * v1->xyz[0] + 0.75 * v2->xyz[0]; + vout->xyz[1] = 0.25 * v1->xyz[1] + 0.75 * v2->xyz[1]; + vout->xyz[2] = 0.25 * v1->xyz[2] + 0.75 * v2->xyz[2]; + + vout->normal[0] = 0.25 * v1->normal[0] + 0.75 * v2->normal[0]; + vout->normal[1] = 0.25 * v1->normal[1] + 0.75 * v2->normal[1]; + vout->normal[2] = 0.25 * v1->normal[2] + 0.75 * v2->normal[2]; + + VectorNormalize( vout->normal, vout->normal ); + + } + } + + FreeMesh( in ); + + return out; +} + +/* +============== +ColorToBytes +============== +*/ +void ColorToBytes( const float *color, byte *colorBytes ) { + float max; + vec3_t sample; + + VectorCopy( color, sample ); + + // clamp with color normalization + max = sample[0]; + if ( sample[1] > max ) { + max = sample[1]; + } + if ( sample[2] > max ) { + max = sample[2]; + } + if ( max > 255 ) { + VectorScale( sample, 255/max, sample ); + } + colorBytes[ 0 ] = sample[0]; + colorBytes[ 1 ] = sample[1]; + colorBytes[ 2 ] = sample[2]; +} + + + +/* +============= +TraceLtm +============= +*/ +void TraceLtm( int num ) { + dsurface_t *ds; + int i, j, k; + int x, y; + int position, numPositions; + vec3_t base, origin, normal; + byte occluded[LIGHTMAP_WIDTH*EXTRASCALE][LIGHTMAP_HEIGHT*EXTRASCALE]; + vec3_t color[LIGHTMAP_WIDTH*EXTRASCALE][LIGHTMAP_HEIGHT*EXTRASCALE]; + traceWork_t tw; + vec3_t average; + int count; + mesh_t srcMesh, *mesh, *subdivided; + shaderInfo_t *si; + static float nudge[2][9] = { + { 0, -1, 0, 1, -1, 1, -1, 0, 1 }, + { 0, -1, -1, -1, 0, 0, 1, 1, 1 } + }; + int sampleWidth, sampleHeight, ssize; + vec3_t lightmapOrigin, lightmapVecs[2]; + int widthtable[LIGHTMAP_WIDTH], heighttable[LIGHTMAP_WIDTH]; + + ds = &drawSurfaces[num]; + si = ShaderInfoForShader( dshaders[ ds->shaderNum].shader ); + + // vertex-lit triangle model + if ( ds->surfaceType == MST_TRIANGLE_SOUP ) { + VertexLighting( ds, !si->noVertexShadows, si->forceSunLight, 1.0, &tw ); + return; + } + + if ( ds->lightmapNum == -1 ) { + return; // doesn't need lighting at all + } + + if (!novertexlighting) { + // calculate the vertex lighting for gouraud shade mode + VertexLighting( ds, si->vertexShadows, si->forceSunLight, si->vertexScale, &tw ); + } + + if ( ds->lightmapNum < 0 ) { + return; // doesn't need lightmap lighting + } + + si = ShaderInfoForShader( dshaders[ ds->shaderNum].shader ); + ssize = samplesize; + if (si->lightmapSampleSize) + ssize = si->lightmapSampleSize; + + if (si->patchShadows) + tw.patchshadows = qtrue; + else + tw.patchshadows = patchshadows; + + if ( ds->surfaceType == MST_PATCH ) { + srcMesh.width = ds->patchWidth; + srcMesh.height = ds->patchHeight; + srcMesh.verts = drawVerts + ds->firstVert; + mesh = SubdivideMesh( srcMesh, 8, 999 ); + PutMeshOnCurve( *mesh ); + MakeMeshNormals( *mesh ); + + subdivided = RemoveLinearMeshColumnsRows( mesh ); + FreeMesh(mesh); + + mesh = SubdivideMeshQuads( subdivided, ssize, LIGHTMAP_WIDTH, widthtable, heighttable); + if ( mesh->width != ds->lightmapWidth || mesh->height != ds->lightmapHeight ) { + Error( "Mesh lightmap miscount"); + } + + if ( extra ) { + mesh_t *mp; + + // chop it up for more light samples (leaking memory...) + mp = mesh;//CopyMesh( mesh ); + mp = LinearSubdivideMesh( mp ); + mp = TransposeMesh( mp ); + mp = LinearSubdivideMesh( mp ); + mp = TransposeMesh( mp ); + + mesh = mp; + } + } else { + VectorCopy( ds->lightmapVecs[2], normal ); + + if ( !extra ) { + VectorCopy( ds->lightmapOrigin, lightmapOrigin ); + VectorCopy( ds->lightmapVecs[0], lightmapVecs[0] ); + VectorCopy( ds->lightmapVecs[1], lightmapVecs[1] ); + } else { + // sample at a closer spacing for antialiasing + VectorCopy( ds->lightmapOrigin, lightmapOrigin ); + VectorScale( ds->lightmapVecs[0], 0.5, lightmapVecs[0] ); + VectorScale( ds->lightmapVecs[1], 0.5, lightmapVecs[1] ); + VectorMA( lightmapOrigin, -0.5, lightmapVecs[0], lightmapOrigin ); + VectorMA( lightmapOrigin, -0.5, lightmapVecs[1], lightmapOrigin ); + } + } + + if ( extra ) { + sampleWidth = ds->lightmapWidth * 2; + sampleHeight = ds->lightmapHeight * 2; + } else { + sampleWidth = ds->lightmapWidth; + sampleHeight = ds->lightmapHeight; + } + + memset ( color, 0, sizeof( color ) ); + + // determine which samples are occluded + memset ( occluded, 0, sizeof( occluded ) ); + for ( i = 0 ; i < sampleWidth ; i++ ) { + for ( j = 0 ; j < sampleHeight ; j++ ) { + + if ( ds->patchWidth ) { + numPositions = 9; + VectorCopy( mesh->verts[j*mesh->width+i].normal, normal ); + // VectorNormalize( normal, normal ); + // push off of the curve a bit + VectorMA( mesh->verts[j*mesh->width+i].xyz, 1, normal, base ); + + MakeNormalVectors( normal, lightmapVecs[0], lightmapVecs[1] ); + } else { + numPositions = 9; + for ( k = 0 ; k < 3 ; k++ ) { + base[k] = lightmapOrigin[k] + normal[k] + + i * lightmapVecs[0][k] + + j * lightmapVecs[1][k]; + } + } + VectorAdd( base, surfaceOrigin[ num ], base ); + + // we may need to slightly nudge the sample point + // if directly on a wall + for ( position = 0 ; position < numPositions ; position++ ) { + // calculate lightmap sample position + for ( k = 0 ; k < 3 ; k++ ) { + origin[k] = base[k] + + + ( nudge[0][position]/16 ) * lightmapVecs[0][k] + + ( nudge[1][position]/16 ) * lightmapVecs[1][k]; + } + + if ( notrace ) { + break; + } + if ( !PointInSolid( origin ) ) { + break; + } + } + + // if none of the nudges worked, this sample is occluded + if ( position == numPositions ) { + occluded[i][j] = qtrue; + if ( numthreads == 1 ) { + c_occluded++; + } + continue; + } + + if ( numthreads == 1 ) { + c_visible++; + } + occluded[i][j] = qfalse; + LightingAtSample( origin, normal, color[i][j], qtrue, qfalse, &tw ); + } + } + + if ( dump ) { + PrintOccluded( occluded, sampleWidth, sampleHeight ); + } + + // calculate average values for occluded samples + for ( i = 0 ; i < sampleWidth ; i++ ) { + for ( j = 0 ; j < sampleHeight ; j++ ) { + if ( !occluded[i][j] ) { + continue; + } + // scan all surrounding samples + count = 0; + VectorClear( average ); + for ( x = -1 ; x <= 1; x++ ) { + for ( y = -1 ; y <= 1 ; y++ ) { + if ( i + x < 0 || i + x >= sampleWidth ) { + continue; + } + if ( j + y < 0 || j + y >= sampleHeight ) { + continue; + } + if ( occluded[i+x][j+y] ) { + continue; + } + count++; + VectorAdd( color[i+x][j+y], average, average ); + } + } + if ( count ) { + VectorScale( average, 1.0/count, color[i][j] ); + } + } + } + + // average together the values if we are extra sampling + if ( ds->lightmapWidth != sampleWidth ) { + for ( i = 0 ; i < ds->lightmapWidth ; i++ ) { + for ( j = 0 ; j < ds->lightmapHeight ; j++ ) { + for ( k = 0 ; k < 3 ; k++ ) { + float value, coverage; + + value = color[i*2][j*2][k] + color[i*2][j*2+1][k] + + color[i*2+1][j*2][k] + color[i*2+1][j*2+1][k]; + coverage = 4; + if ( extraWide ) { + // wider than box filter + if ( i > 0 ) { + value += color[i*2-1][j*2][k] + color[i*2-1][j*2+1][k]; + value += color[i*2-2][j*2][k] + color[i*2-2][j*2+1][k]; + coverage += 4; + } + if ( i < ds->lightmapWidth - 1 ) { + value += color[i*2+2][j*2][k] + color[i*2+2][j*2+1][k]; + value += color[i*2+3][j*2][k] + color[i*2+3][j*2+1][k]; + coverage += 4; + } + if ( j > 0 ) { + value += color[i*2][j*2-1][k] + color[i*2+1][j*2-1][k]; + value += color[i*2][j*2-2][k] + color[i*2+1][j*2-2][k]; + coverage += 4; + } + if ( j < ds->lightmapHeight - 1 ) { + value += color[i*2][j*2+2][k] + color[i*2+1][j*2+2][k]; + value += color[i*2][j*2+3][k] + color[i*2+1][j*2+3][k]; + coverage += 2; + } + } + + color[i][j][k] = value / coverage; + } + } + } + } + + // optionally create a debugging border around the lightmap + if ( lightmapBorder ) { + for ( i = 0 ; i < ds->lightmapWidth ; i++ ) { + color[i][0][0] = 255; + color[i][0][1] = 0; + color[i][0][2] = 0; + + color[i][ds->lightmapHeight-1][0] = 255; + color[i][ds->lightmapHeight-1][1] = 0; + color[i][ds->lightmapHeight-1][2] = 0; + } + for ( i = 0 ; i < ds->lightmapHeight ; i++ ) { + color[0][i][0] = 255; + color[0][i][1] = 0; + color[0][i][2] = 0; + + color[ds->lightmapWidth-1][i][0] = 255; + color[ds->lightmapWidth-1][i][1] = 0; + color[ds->lightmapWidth-1][i][2] = 0; + } + } + + // clamp the colors to bytes and store off + for ( i = 0 ; i < ds->lightmapWidth ; i++ ) { + for ( j = 0 ; j < ds->lightmapHeight ; j++ ) { + k = ( ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + j) + * LIGHTMAP_WIDTH + ds->lightmapX + i; + + ColorToBytes( color[i][j], lightBytes + k*3 ); + } + } + + if (ds->surfaceType == MST_PATCH) + { + FreeMesh(mesh); + } +} + + +//============================================================================= + +vec3_t gridMins; +vec3_t gridSize = { 64, 64, 128 }; +int gridBounds[3]; + + +/* +======================== +LightContributionToPoint +======================== +*/ +qboolean LightContributionToPoint( const light_t *light, const vec3_t origin, + vec3_t color, traceWork_t *tw ) { + trace_t trace; + float add; + + add = 0; + + VectorClear( color ); + + // testing exact PTPFF + if ( exactPointToPolygon && light->type == emit_area ) { + float factor; + float d; + vec3_t normal; + + // see if the point is behind the light + d = DotProduct( origin, light->normal ) - light->dist; + if ( !light->twosided ) { + if ( d < 1 ) { + return qfalse; // point is behind light + } + } + + // test occlusion + // clip the line, tracing from the surface towards the light + TraceLine( origin, light->origin, &trace, qfalse, tw ); + if ( trace.passSolid ) { + return qfalse; + } + + // calculate the contribution + VectorSubtract( light->origin, origin, normal ); + if ( VectorNormalize( normal, normal ) == 0 ) { + return qfalse; + } + factor = PointToPolygonFormFactor( origin, normal, light->w ); + if ( factor <= 0 ) { + if ( light->twosided ) { + factor = -factor; + } else { + return qfalse; + } + } + VectorScale( light->emitColor, factor, color ); + return qtrue; + } + + // calculate the amount of light at this sample + if ( light->type == emit_point || light->type == emit_spotlight ) { + vec3_t dir; + float dist; + + VectorSubtract( light->origin, origin, dir ); + dist = VectorLength( dir ); + // clamp the distance to prevent super hot spots + if ( dist < 16 ) { + dist = 16; + } + if ( light->linearLight ) { + add = light->photons * linearScale - dist; + if ( add < 0 ) { + add = 0; + } + } else { + add = light->photons / ( dist * dist ); + } + } else { + return qfalse; + } + + if ( add <= 1.0 ) { + return qfalse; + } + + // clip the line, tracing from the surface towards the light + TraceLine( origin, light->origin, &trace, qfalse, tw ); + + // other light rays must not hit anything + if ( trace.passSolid ) { + return qfalse; + } + + // add the result + color[0] = add * light->color[0]; + color[1] = add * light->color[1]; + color[2] = add * light->color[2]; + + return qtrue; +} + +typedef struct { + vec3_t dir; + vec3_t color; +} contribution_t; + +/* +============= +TraceGrid + +Grid samples are foe quickly determining the lighting +of dynamically placed entities in the world +============= +*/ +#define MAX_CONTRIBUTIONS 1024 +void TraceGrid( int num ) { + int x, y, z; + vec3_t origin; + light_t *light; + vec3_t color; + int mod; + vec3_t directedColor; + vec3_t summedDir; + contribution_t contributions[MAX_CONTRIBUTIONS]; + int numCon; + int i; + traceWork_t tw; + float addSize; + + mod = num; + z = mod / ( gridBounds[0] * gridBounds[1] ); + mod -= z * ( gridBounds[0] * gridBounds[1] ); + + y = mod / gridBounds[0]; + mod -= y * gridBounds[0]; + + x = mod; + + origin[0] = gridMins[0] + x * gridSize[0]; + origin[1] = gridMins[1] + y * gridSize[1]; + origin[2] = gridMins[2] + z * gridSize[2]; + + if ( PointInSolid( origin ) ) { + vec3_t baseOrigin; + int step; + + VectorCopy( origin, baseOrigin ); + + // try to nudge the origin around to find a valid point + for ( step = 9 ; step <= 18 ; step += 9 ) { + for ( i = 0 ; i < 8 ; i++ ) { + VectorCopy( baseOrigin, origin ); + if ( i & 1 ) { + origin[0] += step; + } else { + origin[0] -= step; + } + if ( i & 2 ) { + origin[1] += step; + } else { + origin[1] -= step; + } + if ( i & 4 ) { + origin[2] += step; + } else { + origin[2] -= step; + } + + if ( !PointInSolid( origin ) ) { + break; + } + } + if ( i != 8 ) { + break; + } + } + if ( step > 18 ) { + // can't find a valid point at all + for ( i = 0 ; i < 8 ; i++ ) { + gridData[ num*8 + i ] = 0; + } + return; + } + } + + VectorClear( summedDir ); + + // 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 + numCon = 0; + for ( light = lights ; light ; light = light->next ) { + vec3_t add; + vec3_t dir; + float addSize; + + if ( !LightContributionToPoint( light, origin, add, &tw ) ) { + continue; + } + + VectorSubtract( light->origin, origin, dir ); + VectorNormalize( dir, dir ); + + VectorCopy( add, contributions[numCon].color ); + VectorCopy( dir, contributions[numCon].dir ); + numCon++; + + addSize = VectorLength( add ); + VectorMA( summedDir, addSize, dir, summedDir ); + + if ( numCon == MAX_CONTRIBUTIONS-1 ) { + break; + } + } + + // + // trace directly to the sun + // + SunToPoint( origin, &tw, color ); + addSize = VectorLength( color ); + if ( addSize > 0 ) { + VectorCopy( color, contributions[numCon].color ); + VectorCopy( sunDirection, contributions[numCon].dir ); + VectorMA( summedDir, addSize, sunDirection, summedDir ); + numCon++; + } + + + // now that we have identified the primary light direction, + // go back and seperate all the light into directed and ambient + VectorNormalize( summedDir, summedDir ); + VectorCopy( ambientColor, color ); + VectorClear( directedColor ); + + for ( i = 0 ; i < numCon ; i++ ) { + float d; + + d = DotProduct( contributions[i].dir, summedDir ); + if ( d < 0 ) { + d = 0; + } + + VectorMA( directedColor, d, contributions[i].color, directedColor ); + + // the ambient light will be at 1/4 the value of directed light + d = 0.25 * ( 1.0 - d ); + VectorMA( color, d, contributions[i].color, color ); + } + + // now do some fudging to keep the ambient from being too low + VectorMA( color, 0.25, directedColor, color ); + + // + // save the resulting value out + // + ColorToBytes( color, gridData + num*8 ); + ColorToBytes( directedColor, gridData + num*8 + 3 ); + + VectorNormalize( summedDir, summedDir ); + NormalToLatLong( summedDir, gridData + num*8 + 6); +} + + +/* +============= +SetupGrid +============= +*/ +void SetupGrid( void ) { + int i; + vec3_t maxs; + + for ( i = 0 ; i < 3 ; i++ ) { + gridMins[i] = gridSize[i] * ceil( dmodels[0].mins[i] / gridSize[i] ); + maxs[i] = gridSize[i] * floor( dmodels[0].maxs[i] / gridSize[i] ); + gridBounds[i] = (maxs[i] - gridMins[i])/gridSize[i] + 1; + } + + numGridPoints = gridBounds[0] * gridBounds[1] * gridBounds[2]; + if (numGridPoints * 8 >= MAX_MAP_LIGHTGRID) + Error("MAX_MAP_LIGHTGRID"); + qprintf( "%5i gridPoints\n", numGridPoints ); +} + +//============================================================================= + +/* +============= +RemoveLightsInSolid +============= +*/ +void RemoveLightsInSolid(void) +{ + light_t *light, *prev; + int numsolid = 0; + + prev = NULL; + for ( light = lights ; light ; ) { + if (PointInSolid(light->origin)) + { + if (prev) prev->next = light->next; + else lights = light->next; + if (light->w) + FreeWinding(light->w); + free(light); + numsolid++; + if (prev) + light = prev->next; + else + light = lights; + } + else + { + prev = light; + light = light->next; + } + } + _printf (" %7i lights in solid\n", numsolid); +} + +/* +============= +LightWorld +============= +*/ +void LightWorld (void) { + float f; + + // determine the number of grid points + SetupGrid(); + + // find the optional world ambient + GetVectorForKey( &entities[0], "_color", ambientColor ); + f = FloatForKey( &entities[0], "ambient" ); + VectorScale( ambientColor, f, ambientColor ); + + // create lights out of patches and lights + qprintf ("--- CreateLights ---\n"); + CreateEntityLights (); + qprintf ("%i point lights\n", numPointLights); + qprintf ("%i area lights\n", numAreaLights); + + if (!nogridlighting) { + qprintf ("--- TraceGrid ---\n"); + RunThreadsOnIndividual( numGridPoints, qtrue, TraceGrid ); + qprintf( "%i x %i x %i = %i grid\n", gridBounds[0], gridBounds[1], + gridBounds[2], numGridPoints); + } + + qprintf ("--- TraceLtm ---\n"); + RunThreadsOnIndividual( numDrawSurfaces, qtrue, TraceLtm ); + qprintf( "%5i visible samples\n", c_visible ); + qprintf( "%5i occluded samples\n", c_occluded ); +} + +/* +======== +CreateFilters + +EXPERIMENTAL, UNUSED + +Look for transparent light filter surfaces. + +This will only work for flat 3*3 patches that exactly hold one copy of the texture. +======== +*/ +#define PLANAR_PATCH_EPSILON 0.1 +void CreateFilters( void ) { + int i; + filter_t *f; + dsurface_t *ds; + shaderInfo_t *si; + drawVert_t *v1, *v2, *v3; + vec3_t d1, d2; + int vertNum; + + numFilters = 0; + + return; + + for ( i = 0 ; i < numDrawSurfaces ; i++ ) { + ds = &drawSurfaces[i]; + if ( !ds->patchWidth ) { + continue; + } + si = ShaderInfoForShader( dshaders[ ds->shaderNum ].shader ); +/* + if ( !(si->surfaceFlags & SURF_LIGHTFILTER) ) { + continue; + } +*/ + + // we have a filter patch + v1 = &drawVerts[ ds->firstVert ]; + + if ( ds->patchWidth != 3 || ds->patchHeight != 3 ) { + _printf("WARNING: patch at %i %i %i has SURF_LIGHTFILTER but isn't a 3 by 3\n", + v1->xyz[0], v1->xyz[1], v1->xyz[2] ); + continue; + } + + if ( numFilters == MAX_FILTERS ) { + Error( "MAX_FILTERS" ); + } + f = &filters[ numFilters ]; + numFilters++; + + v2 = &drawVerts[ ds->firstVert + 2 ]; + v3 = &drawVerts[ ds->firstVert + 6 ]; + + VectorSubtract( v2->xyz, v1->xyz, d1 ); + VectorSubtract( v3->xyz, v1->xyz, d2 ); + VectorNormalize( d1, d1 ); + VectorNormalize( d2, d2 ); + CrossProduct( d1, d2, f->plane ); + f->plane[3] = DotProduct( v1->xyz, f->plane ); + + // make sure all the control points are on the plane + for ( vertNum = 0 ; vertNum < ds->numVerts ; vertNum++ ) { + float d; + + d = DotProduct( drawVerts[ ds->firstVert + vertNum ].xyz, f->plane ) - f->plane[3]; + if ( fabs( d ) > PLANAR_PATCH_EPSILON ) { + break; + } + } + if ( vertNum != ds->numVerts ) { + numFilters--; + _printf("WARNING: patch at %i %i %i has SURF_LIGHTFILTER but isn't flat\n", + v1->xyz[0], v1->xyz[1], v1->xyz[2] ); + continue; + } + } + + f = &filters[0]; + numFilters = 1; + + f->plane[0] = 1; + f->plane[1] = 0; + f->plane[2] = 0; + f->plane[3] = 448; + + f->origin[0] = 448; + f->origin[1] = 192; + f->origin[2] = 0; + + f->vectors[0][0] = 0; + f->vectors[0][1] = -1.0 / 128; + f->vectors[0][2] = 0; + + f->vectors[1][0] = 0; + f->vectors[1][1] = 0; + f->vectors[1][2] = 1.0 / 128; + + f->si = ShaderInfoForShader( "textures/hell/blocks11ct" ); +} + +/* +============= +VertexLightingThread +============= +*/ +void VertexLightingThread(int num) { + dsurface_t *ds; + traceWork_t tw; + shaderInfo_t *si; + + ds = &drawSurfaces[num]; + + // vertex-lit triangle model + if ( ds->surfaceType == MST_TRIANGLE_SOUP ) { + return; + } + + if (novertexlighting) + return; + + if ( ds->lightmapNum == -1 ) { + return; // doesn't need lighting at all + } + + si = ShaderInfoForShader( dshaders[ ds->shaderNum].shader ); + + // calculate the vertex lighting for gouraud shade mode + VertexLighting( ds, si->vertexShadows, si->forceSunLight, si->vertexScale, &tw ); +} + +/* +============= +TriSoupLightingThread +============= +*/ +void TriSoupLightingThread(int num) { + dsurface_t *ds; + traceWork_t tw; + shaderInfo_t *si; + + ds = &drawSurfaces[num]; + si = ShaderInfoForShader( dshaders[ ds->shaderNum].shader ); + + // vertex-lit triangle model + if ( ds->surfaceType == MST_TRIANGLE_SOUP ) { + VertexLighting( ds, !si->noVertexShadows, si->forceSunLight, 1.0, &tw ); + } +} + +/* +============= +GridAndVertexLighting +============= +*/ +void GridAndVertexLighting(void) { + SetupGrid(); + + FindSkyBrushes(); + CreateFilters(); + InitTrace(); + CreateEntityLights (); + CreateSurfaceLights(); + + if (!nogridlighting) { + _printf ("--- TraceGrid ---\n"); + RunThreadsOnIndividual( numGridPoints, qtrue, TraceGrid ); + } + + if (!novertexlighting) { + _printf ("--- Vertex Lighting ---\n"); + RunThreadsOnIndividual( numDrawSurfaces, qtrue, VertexLightingThread ); + } + + _printf("--- Model Lighting ---\n"); + RunThreadsOnIndividual( numDrawSurfaces, qtrue, TriSoupLightingThread ); +} + +/* +======== +LightMain + +======== +*/ +int LightMain (int argc, char **argv) { + int i; + double start, end; + const char *value; + + _printf ("----- Lighting ----\n"); + + verbose = qfalse; + + for (i=1 ; i [- ...]] \n" + "\n" + "Switches:\n" + " v = verbose output\n" + " threads = set number of threads to X\n" + " area = set the area light scale to V\n" + " point = set the point light scale to W\n" + " notrace = don't cast any shadows\n" + " extra = enable super sampling for anti-aliasing\n" + " extrawide = same as extra but smoothen more\n" + " nogrid = don't calculate light grid for dynamic model lighting\n" + " novertex = don't calculate vertex lighting\n" + " samplesize = set the lightmap pixel size to NxN units\n"); + exit(0); + } + + start = I_FloatTime (); + + SetQdirFromPath (argv[i]); + +#ifdef _WIN32 + InitPakFile(gamedir, NULL); +#endif + + strcpy (source, ExpandArg(argv[i])); + StripExtension (source); + DefaultExtension (source, ".bsp"); + + LoadShaderInfo(); + + _printf ("reading %s\n", source); + + LoadBSPFile (source); + + FindSkyBrushes(); + + ParseEntities(); + + value = ValueForKey( &entities[0], "gridsize" ); + if (strlen(value)) { + sscanf( value, "%f %f %f", &gridSize[0], &gridSize[1], &gridSize[2] ); + _printf("grid size = {%1.1f, %1.1f, %1.1f}\n", gridSize[0], gridSize[1], gridSize[2]); + } + + CreateFilters(); + + InitTrace(); + + SetEntityOrigins(); + + CountLightmaps(); + + CreateSurfaceLights(); + + LightWorld(); + + _printf ("writing %s\n", source); + WriteBSPFile (source); + + end = I_FloatTime (); + _printf ("%5.0f seconds elapsed\n", end-start); + + return 0; +} + -- cgit v1.2.3