This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/common/bsplib/qrad3.c

1770 lines
40 KiB
C

//=======================================================================
// Copyright XashXT Group 2007 ©
// qrad3.c - calcaulate static light
//=======================================================================
#include "bsplib.h"
#include "const.h"
#define EXTRASCALE 2
#define MAX_FILTERS 1024
#define MAX_FACE_POINTS 128
#define MAX_CONTRIBUTIONS 1024
typedef struct
{
vec3_t dir;
vec3_t color;
} contribution_t;
typedef struct
{
float plane[4];
vec3_t origin;
vec3_t vectors[2];
bsp_shader_t *si;
} filter_t;
filter_t filters[MAX_FILTERS];
int numFilters;
bool notrace;
bool patchshadows;
bool extra;
bool extraWide;
bool lightmapBorder;
bool 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;
bool exactPointToPolygon = true;
float formFactorValueScale = 3;
float linearScale = 1.0 / 8000;
light_t *lights;
int numPointLights;
int numAreaLights;
int c_visible, c_occluded;
int defaultLightSubdivide = 999; // vary by surface size?
vec3_t ambientColor;
vec3_t surfaceOrigin[MAX_MAP_SURFACES];
int entitySurface[MAX_MAP_SURFACES];
// these are usually overrided by shader values
vec3_t sunDirection = { 0.45, 0.3, 0.9 };
vec3_t sunLight = { 100, 100, 50 };
// g-cont. moved it here to avoid stack overflow problems
byte occluded[LIGHTMAP_WIDTH*EXTRASCALE][LIGHTMAP_HEIGHT*EXTRASCALE];
vec3_t color[LIGHTMAP_WIDTH*EXTRASCALE][LIGHTMAP_HEIGHT*EXTRASCALE];
typedef struct
{
dbrush_t *b;
vec3_t bounds[2];
} skyBrush_t;
int numSkyBrushes;
skyBrush_t skyBrushes[MAX_MAP_BRUSHES];
/*
===============================================================
NormalToLatLong
We use two byte encoded normals in some space critical applications.
Lat = 0 at (1,0,0) to 360 (-1,0,0), encoded in 8-bit sine table format
Lng = 0 at (0,0,1) to 180 (0,0,-1), encoded in 8-bit sine table format
===============================================================
*/
void NormalToLatLong( const vec3_t normal, byte bytes[2] )
{
// check for singularities
if( normal[0] == 0 && normal[1] == 0 )
{
if( normal[2] > 0 )
{
bytes[0] = 0;
bytes[1] = 0; // lat = 0, long = 0
}
else
{
bytes[0] = 128;
bytes[1] = 0; // lat = 0, long = 128
}
}
else
{
int a, b;
a = RAD2DEG( atan2( normal[1], normal[0] ) ) * (255.0f / 360.0f );
a &= 0xff;
b = RAD2DEG( acos( normal[2] ) ) * ( 255.0f / 360.0f );
b &= 0xff;
bytes[0] = b; // longitude
bytes[1] = a; // lattitude
}
}
/*
===============================================================
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
===============================================================
*/
/*
===============
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( bsp_shader_t *ls, winding_t *w, vec3_t normal, float areaSubdivide, bool 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, false );
SubdivideAreaLight( ls, back, normal, areaSubdivide, false );
FreeWinding( w );
return;
}
}
// create a light from this
area = WindingArea( w );
if ( area <= 0 || area > 20000000 )
return;
numAreaLights++;
dl = BSP_Malloc(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 = true;
}
// optionally create a point backsplash light
if( backsplash && ls->backsplashFraction > 0 )
{
dl2 = BSP_Malloc(sizeof(*dl));
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 )
{
dsurface_t *ds;
int i, count = 0;
MsgDev( D_INFO, "--- CountLightmaps ---\n" );
for( i = 0; i < numsurfaces; i++ )
{
// see if this surface is light emiting
ds = &dsurfaces[i];
if( ds->lightmapnum > count )
count = ds->lightmapnum;
}
count++;
lightdatasize = count * LM_SIZE;
if( lightdatasize > MAX_MAP_LIGHTDATA )
Sys_Break( "MAX_MAP_LIGHTDATA limit exceeded\n" );
MsgDev( D_INFO, "%5i dsurfaces\n", numsurfaces );
MsgDev( D_INFO, "%5i lightmaps\n", count );
}
/*
===============
CreateSurfaceLights
This creates area lights
===============
*/
void CreateSurfaceLights( void )
{
int i, j, side;
dsurface_t *ds;
bsp_shader_t *ls;
winding_t *w;
cFacet_t *f;
light_t *dl;
vec3_t origin;
dvertex_t *dv;
int c_lightSurfaces;
float lightSubdivide;
vec3_t normal;
MsgDev( D_INFO, "--- CreateSurfaceLights ---\n" );
c_lightSurfaces = 0;
for( i = 0; i < numsurfaces; i++ )
{
// see if this surface is light emiting
ds = &dsurfaces[i];
ls = FindShader( dshaders[ds->shadernum].name );
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 )
{
MsgDev( D_WARN, "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 = &dvertexes[ds->firstvertex];
if( ds->numvertices != 4 )
{
MsgDev( D_WARN, "surface at (%i %i %i) has autosprite shader but %i verts\n",
(int)dv->point[0], (int)dv->point[1], (int)dv->point[2] );
continue;
}
VectorAdd( dv[0].point, dv[1].point, origin );
VectorAdd( dv[2].point, origin, origin );
VectorAdd( dv[3].point, origin, origin );
VectorScale( origin, 0.25, origin );
}
numPointLights++;
dl = BSP_Malloc(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;
Mem_Copy( 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, true );
}
}
else
{
// normal polygon
w = AllocWinding( ds->numvertices );
w->numpoints = ds->numvertices;
for( j = 0 ; j < ds->numvertices ; j++ )
{
VectorCopy( dvertexes[ds->firstvertex+j].point, w->p[j] );
}
VectorCopy( ds->normal, normal );
if ( side )
{
winding_t *t;
t = w;
w = ReverseWinding( t );
FreeWinding( t );
VectorSubtract( vec3_origin, normal, normal );
}
SubdivideAreaLight( ls, w, normal, lightSubdivide, true );
}
}
}
MsgDev( D_INFO, "%5i light emitting surfaces\n", c_lightSurfaces );
}
/*
================
FindSkyBrushes
================
*/
void FindSkyBrushes( void )
{
int i, j;
dbrush_t *b;
skyBrush_t *sb;
bsp_shader_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].flags & 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 );
// find the sky shader
for( i = 0; i < numsurfaces; i++ )
{
si = FindShader( dshaders[dsurfaces[i].shadernum].name );
if( si->surfaceFlags & SURF_SKY )
{
VectorCopy( si->sunLight, sunLight );
VectorCopy( si->sunDirection, sunDirection );
break;
}
}
}
/*
=================================================================
LIGHT SETUP
=================================================================
*/
/*
==================
FindTargetEntity
==================
*/
bsp_entity_t *FindTargetEntity( const char *target )
{
int i;
const char *n;
for( i = 0; i < num_entities; i++ )
{
n = ValueForKey( &entities[i], "targetname" );
if( !com.strcmp( n, target ))
return &entities[i];
}
return NULL;
}
/*
=============
CreateEntityLights
=============
*/
void CreateEntityLights( void )
{
int i, j;
light_t *dl;
bsp_entity_t *e, *e2;
const char *name;
const char *target;
vec3_t dest;
const char *_color;
float intensity;
int spawnflags;
bool valve_format = false;
// entities
for( i = 0; i < num_entities; i++ )
{
e = &entities[i];
j = FloatForKey( e, "mapversion" );
if( j == VALVE_FORMAT ) valve_format = true;
name = ValueForKey( e, "classname" );
if( com.strncmp( name, "light", 5 ))
continue;
numPointLights++;
dl = BSP_Malloc( sizeof( *dl ));
dl->next = lights;
lights = dl;
if( valve_format )
{
_color = ValueForKey( e, "_light" );
if( _color && _color[0] )
{
sscanf (_color, "%f %f %f %f", &dl->color[0], &dl->color[1], &dl->color[2], &intensity );
// convert to default OpenGl scale
dl->color[0] /= 255.0f;
dl->color[1] /= 255.0f;
dl->color[2] /= 255.0f;
ColorNormalize( dl->color, dl->color );
}
else dl->color[0] = dl->color[1] = dl->color[2] = 1.0;
}
else
{
spawnflags = FloatForKey( e, "spawnflags" );
if( spawnflags & 1 ) dl->linearLight = true;
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;
}
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 = 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 )
{
MsgDev( D_WARN, "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 = VectorNormalizeLength( 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;
bsp_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 = com.atoi( key + 1 );
dm = &dmodels[modelnum];
// set entity surface to true for all surfaces for this model
for( j = 0; j < dm->numfaces; j++ )
entitySurface[dm->firstface + j] = true;
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->numfaces; j++ )
{
VectorCopy( origin, surfaceOrigin[dm->firstface + j] );
}
}
}
/*
=================================================================
=================================================================
*/
/*
================
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] );
}
// 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 );
VectorCopy( triVector, triNormal );
if( VectorNormalizeLength( triNormal ) < 0.0001 )
continue;
facing = DotProduct( normal, triNormal );
total += facing * angle;
if( total > 6.3 || total < -6.3 )
{
static bool printed;
if( !printed )
{
printed = true;
MsgDev( D_WARN, "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;
lighttrace_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, true, 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, true, tw );
// we hit the sky, so add sunlight
if( GetNumThreads() == 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( GetNumThreads() == 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, bool testOcclusion, bool forceSunLight, traceWork_t *tw )
{
light_t *light;
lighttrace_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 )
{
// 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, false, 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 = VectorNormalizeLength( 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 = VectorNormalizeLength( 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 = VectorNormalizeLength( 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, false, 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 );
}
}
/*
==============
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;
traceWork_t tw;
vec3_t average;
int count;
bsp_shader_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];
memset( occluded, 0, LIGHTMAP_WIDTH*EXTRASCALE*LIGHTMAP_HEIGHT*EXTRASCALE );
memset( color, 0, LIGHTMAP_WIDTH*EXTRASCALE*LIGHTMAP_HEIGHT*EXTRASCALE );
ds = &dsurfaces[num];
si = FindShader( dshaders[ds->shadernum].name );
if( ds->lightmapnum == -1 ) return; // doesn't need lighting at all
si = FindShader( dshaders[ds->shadernum].name );
ssize = samplesize;
if( si->lightmapSampleSize )
ssize = si->lightmapSampleSize;
VectorCopy( ds->normal, normal );
if( !extra )
{
VectorCopy( ds->origin, lightmapOrigin );
VectorCopy( ds->vecs[0], lightmapVecs[0] );
VectorCopy( ds->vecs[1], lightmapVecs[1] );
}
else
{
// sample at a closer spacing for antialiasing
VectorCopy( ds->origin, lightmapOrigin );
VectorScale( ds->vecs[0], 0.5, lightmapVecs[0] );
VectorScale( ds->vecs[1], 0.5, lightmapVecs[1] );
VectorMA( lightmapOrigin, -0.5, lightmapVecs[0], lightmapOrigin );
VectorMA( lightmapOrigin, -0.5, lightmapVecs[1], lightmapOrigin );
}
if( extra )
{
sampleWidth = ds->lm_size[0] * 2;
sampleHeight = ds->lm_size[1] * 2;
}
else
{
sampleWidth = ds->lm_size[0];
sampleHeight = ds->lm_size[1];
}
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++ )
{
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] = true;
if( GetNumThreads() == 1 )
{
c_occluded++;
}
continue;
}
if( GetNumThreads() == 1 )
{
c_visible++;
}
occluded[i][j] = false;
LightingAtSample( origin, normal, color[i][j], true, false, &tw );
}
}
// 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->lm_size[0] != sampleWidth )
{
for( i = 0; i < ds->lm_size[0]; i++ )
{
for( j = 0; j < ds->lm_size[1]; 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->lm_size[0] - 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->lm_size[1] - 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->lm_size[0]; i++ )
{
color[i][0][0] = 255;
color[i][0][1] = 0;
color[i][0][2] = 0;
color[i][ds->lm_size[1]-1][0] = 255;
color[i][ds->lm_size[1]-1][1] = 0;
color[i][ds->lm_size[1]-1][2] = 0;
}
for( i = 0; i < ds->lm_size[1]; i++ )
{
color[0][i][0] = 255;
color[0][i][1] = 0;
color[0][i][2] = 0;
color[ds->lm_size[0]-1][i][0] = 255;
color[ds->lm_size[0]-1][i][1] = 0;
color[ds->lm_size[0]-1][i][2] = 0;
}
}
// clamp the colors to bytes and store off
for( i = 0; i < ds->lm_size[0]; i++ )
{
for( j = 0; j < ds->lm_size[1]; j++ )
{
k = ( ds->lightmapnum * LIGHTMAP_HEIGHT + ds->lm_base[1] + j) * LIGHTMAP_WIDTH + ds->lm_base[0] + i;
ColorToBytes( color[i][j], dlightdata + k * 3 );
}
}
}
//=============================================================================
vec3_t gridMins;
vec3_t gridSize = { 64, 64, 128 };
int gridBounds[3];
/*
========================
LightContributionToPoint
========================
*/
bool LightContributionToPoint( const light_t *light, const vec3_t origin, vec3_t color, traceWork_t *tw )
{
lighttrace_t trace;
float 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 false; // point is behind light
}
}
// test occlusion
// clip the line, tracing from the surface towards the light
TraceLine( origin, light->origin, &trace, false, tw );
if( trace.passSolid )
{
return false;
}
// calculate the contribution
VectorSubtract( light->origin, origin, normal );
if( VectorNormalizeLength( normal ) == 0 )
{
return false;
}
factor = PointToPolygonFormFactor( origin, normal, light->w );
if( factor <= 0 )
{
if( light->twosided )
{
factor = -factor;
}
else
{
return false;
}
}
VectorScale( light->emitColor, factor, color );
return true;
}
// 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 false;
}
if( add <= 1.0 )
{
return false;
}
// clip the line, tracing from the surface towards the light
TraceLine( origin, light->origin, &trace, false, tw );
// other light rays must not hit anything
if( trace.passSolid ) return false;
// add the result
color[0] = add * light->color[0];
color[1] = add * light->color[1];
color[2] = add * light->color[2];
return true;
}
/*
=============
TraceGrid
Grid samples are for quickly determining the lighting
of dynamically placed entities in the world
=============
*/
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++ )
dlightgrid[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 );
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 );
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, dlightgrid + num * 8 );
ColorToBytes( directedColor, dlightgrid + num * 8 + 3 );
VectorNormalize( summedDir );
NormalToLatLong( summedDir, dlightgrid + 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 * sizeof(dlightgrid_t) >= MAX_MAP_LIGHTGRID )
Sys_Break( "MAX_MAP_LIGHTGRID limit exceeded\n" );
Msg( "%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 );
Mem_Free( light );
numsolid++;
if( prev ) light = prev->next;
else light = lights;
}
else
{
prev = light;
light = light->next;
}
}
MsgDev( D_INFO, " %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
MsgDev( D_INFO, "--- CreateLights ---\n" );
CreateEntityLights();
Msg("%i point lights\n", numPointLights);
Msg("%i area lights\n", numAreaLights);
if( !nogridlighting )
{
MsgDev( D_INFO, "--- TraceGrid ---\n" );
RunThreadsOnIndividual( numgridpoints, true, TraceGrid );
MsgDev( D_INFO, "%i x %i x %i = %i grid\n", gridBounds[0], gridBounds[1], gridBounds[2], numgridpoints );
}
MsgDev( D_INFO, "--- TraceLtm ---\n" );
RunThreadsOnIndividual( numsurfaces, true, TraceLtm );
Msg( "%5i visible samples\n", c_visible );
Msg( "%5i occluded samples\n", c_occluded );
}
/*
========
WradMain
========
*/
void WradMain ( bool option )
{
string cmdparm;
const char *value;
if(!LoadBSPFile())
{
// map not exist, create it
WbspMain( false );
LoadBSPFile();
}
extra = option;
if( FS_GetParmFromCmdLine("-area", cmdparm ))
areaScale *= com.atof( cmdparm );
if( FS_GetParmFromCmdLine("-point", cmdparm ))
pointScale *= com.atof( cmdparm );
if( FS_CheckParm( "-notrace" )) notrace = true;
if( FS_CheckParm( "-patchshadows" )) patchshadows = true;
if( FS_CheckParm( "-extrawide" )) extra = extraWide = true;
samplesize = bsp_lightmap_size->integer;
if( samplesize < 1 ) samplesize = 1;
Msg("---- Radiocity ---- [%s]\n", extra ? "extra" : "normal" );
FindSkyBrushes();
ParseEntities();
value = ValueForKey( &entities[0], "gridsize" );
if( com.strlen( value )) sscanf( value, "%f %f %f", &gridSize[0], &gridSize[1], &gridSize[2] );
InitTrace();
SetEntityOrigins();
CountLightmaps();
CreateSurfaceLights();
LightWorld();
WriteBSPFile();
}