Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| /* | |
| =========================================================================== | |
| 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<argc ; i++) { | |
| if (!strcmp(argv[i],"-tempname")) | |
| { | |
| i++; | |
| } else if (!strcmp(argv[i],"-v")) { | |
| verbose = qtrue; | |
| } else if (!strcmp(argv[i],"-threads")) { | |
| numthreads = atoi (argv[i+1]); | |
| i++; | |
| } else if (!strcmp(argv[i],"-area")) { | |
| areaScale *= atof(argv[i+1]); | |
| _printf ("area light scaling at %f\n", areaScale); | |
| i++; | |
| } else if (!strcmp(argv[i],"-point")) { | |
| pointScale *= atof(argv[i+1]); | |
| _printf ("point light scaling at %f\n", pointScale); | |
| i++; | |
| } else if (!strcmp(argv[i],"-notrace")) { | |
| notrace = qtrue; | |
| _printf ("No occlusion tracing\n"); | |
| } else if (!strcmp(argv[i],"-patchshadows")) { | |
| patchshadows = qtrue; | |
| _printf ("Patch shadow casting enabled\n"); | |
| } else if (!strcmp(argv[i],"-extra")) { | |
| extra = qtrue; | |
| _printf ("Extra detail tracing\n"); | |
| } else if (!strcmp(argv[i],"-extrawide")) { | |
| extra = qtrue; | |
| extraWide = qtrue; | |
| _printf ("Extra wide detail tracing\n"); | |
| } else if (!strcmp(argv[i], "-samplesize")) { | |
| samplesize = atoi(argv[i+1]); | |
| if (samplesize < 1) samplesize = 1; | |
| i++; | |
| _printf("lightmap sample size is %dx%d units\n", samplesize, samplesize); | |
| } else if (!strcmp(argv[i], "-novertex")) { | |
| novertexlighting = qtrue; | |
| _printf("no vertex lighting = true\n"); | |
| } else if (!strcmp(argv[i], "-nogrid")) { | |
| nogridlighting = qtrue; | |
| _printf("no grid lighting = true\n"); | |
| } else if (!strcmp(argv[i],"-border")) { | |
| lightmapBorder = qtrue; | |
| _printf ("Adding debug border to lightmaps\n"); | |
| } else if (!strcmp(argv[i],"-nosurf")) { | |
| noSurfaces = qtrue; | |
| _printf ("Not tracing against surfaces\n" ); | |
| } else if (!strcmp(argv[i],"-dump")) { | |
| dump = qtrue; | |
| _printf ("Dumping occlusion maps\n"); | |
| } else { | |
| break; | |
| } | |
| } | |
| ThreadSetDefault (); | |
| if (i != argc - 1) { | |
| _printf("usage: q3map -light [-<switch> [-<switch> ...]] <mapname>\n" | |
| "\n" | |
| "Switches:\n" | |
| " v = verbose output\n" | |
| " threads <X> = set number of threads to X\n" | |
| " area <V> = set the area light scale to V\n" | |
| " point <W> = 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 <N> = 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; | |
| } | |