882 lines
24 KiB
C++
882 lines
24 KiB
C++
#include "qrad.h"
|
|
|
|
#ifdef HLRAD_AMBIENTCUBES
|
|
|
|
// the angle between consecutive g_anorms[] vectors is ~14.55 degrees
|
|
#define MIN_LOCAL_SAMPLES 4
|
|
#define MAX_LOCAL_SAMPLES 16 // unsigned byte limit
|
|
#define MAX_SAMPLES 32 // enough
|
|
#define MAX_LEAF_PLANES 512 // QuArK cylinder :-)
|
|
#define AMBIENT_SCALE 128.0 // ambient clamp at 128
|
|
#define GAMMA ( 2.2f ) // Valve Software gamma
|
|
#define INVGAMMA ( 1.0f / 2.2f ) // back to 1.0
|
|
|
|
static vec3_t g_BoxDirections[6] =
|
|
{
|
|
{ 1, 0, 0 },
|
|
{ -1, 0, 0 },
|
|
{ 0, 1, 0 },
|
|
{ 0, -1, 0 },
|
|
{ 0, 0, 1 },
|
|
{ 0, 0, -1 },
|
|
};
|
|
|
|
// this stores each sample of the ambient lighting
|
|
struct ambientsample_t
|
|
{
|
|
vec3_t cube[6];
|
|
vec3_t pos;
|
|
};
|
|
|
|
struct ambientlist_t
|
|
{
|
|
int numSamples;
|
|
ambientsample_t *samples;
|
|
};
|
|
|
|
struct ambientlocallist_t
|
|
{
|
|
ambientsample_t samples[MAX_LOCAL_SAMPLES];
|
|
int numSamples;
|
|
};
|
|
|
|
struct leafplanes_t
|
|
{
|
|
dplane_t planes[MAX_LEAF_PLANES];
|
|
int numLeafPlanes;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
vec3_t diffuse;
|
|
vec3_t average;
|
|
float fraction;
|
|
dface_t *surf;
|
|
bool hitsky;
|
|
} lightpoint_t;
|
|
|
|
static int leafparents[MAX_MAP_LEAFS];
|
|
static int nodeparents[MAX_MAP_NODES];
|
|
ambientlist_t g_leaf_samples[MAX_MAP_LEAFS];
|
|
|
|
static void MakeParents( const int nodenum, const int parent )
|
|
{
|
|
dnode_t *node;
|
|
int i, j;
|
|
|
|
nodeparents[nodenum] = parent;
|
|
node = g_dnodes + nodenum;
|
|
|
|
for( i = 0; i < 2; i++ )
|
|
{
|
|
j = node->children[i];
|
|
if( j < 0 ) leafparents[-j - 1] = nodenum;
|
|
else MakeParents(j, nodenum);
|
|
}
|
|
}
|
|
|
|
static vec_t LightAngle( const dworldlight_t *wl, const vec3_t lnormal, const vec3_t snormal, const vec3_t delta, float dist )
|
|
{
|
|
vec_t dot, dot2;
|
|
|
|
ASSERT( wl->emittype == emit_surface );
|
|
|
|
dot = DotProduct( snormal, delta );
|
|
if( dot <= NORMAL_EPSILON )
|
|
return 0; // behind light surface
|
|
|
|
dot2 = -DotProduct( delta, lnormal );
|
|
if( dot2 * dist <= MINIMUM_PATCH_DISTANCE )
|
|
return 0; // behind light surface
|
|
|
|
// variable power falloff (1 = inverse linear, 2 = inverse square)
|
|
vec_t denominator = dist * wl->fade;
|
|
|
|
if( wl->falloff == 2 )
|
|
denominator *= dist;
|
|
|
|
return dot * dot2 / denominator;
|
|
}
|
|
|
|
static vec_t LightDistanceFalloff( const dworldlight_t *wl, const vec3_t delta )
|
|
{
|
|
vec_t radius = DotProduct( delta, delta );
|
|
|
|
ASSERT( wl->emittype == emit_surface );
|
|
|
|
// cull out stuff that's too far
|
|
if( wl->radius != 0 )
|
|
{
|
|
if( radius > ( wl->radius * wl->radius ))
|
|
return 0.0f;
|
|
}
|
|
|
|
if( radius < 1.0 )
|
|
return 1.0;
|
|
return 1.0 / radius;
|
|
}
|
|
|
|
static void AddEmitSurfaceLights( int threadnum, const vec3_t vStart, vec3_t lightBoxColor[6] )
|
|
{
|
|
vec3_t wlOrigin;
|
|
trace_t trace;
|
|
|
|
for( int iLight = 0; iLight < g_numworldlights; iLight++ )
|
|
{
|
|
dworldlight_t *wl = &g_dworldlights[iLight];
|
|
|
|
// Should this light even go in the ambient cubes?
|
|
if( !FBitSet( wl->flags, DWL_FLAGS_INAMBIENTCUBE ))
|
|
continue;
|
|
|
|
ASSERT( wl->emittype == emit_surface );
|
|
|
|
VectorCopy( wl->origin, wlOrigin ); // short to float
|
|
|
|
// Can this light see the point?
|
|
TestLine( threadnum, vStart, wlOrigin, &trace );
|
|
|
|
if( trace.contents != CONTENTS_EMPTY )
|
|
continue;
|
|
|
|
// add this light's contribution.
|
|
vec3_t vDelta, vDeltaNorm;
|
|
VectorSubtract( wlOrigin, vStart, vDelta );
|
|
float flDistanceScale = LightDistanceFalloff( wl, vDelta );
|
|
|
|
VectorCopy( vDelta, vDeltaNorm );
|
|
VectorMA( vDeltaNorm, -DEFAULT_HUNT_OFFSET * 0.5, wl->normal, vDeltaNorm );
|
|
float dist = VectorNormalize( vDeltaNorm );
|
|
dist = Q_max( dist, 1.0 );
|
|
|
|
float flAngleScale = LightAngle( wl, wl->normal, vDeltaNorm, vDeltaNorm, dist );
|
|
|
|
float ratio = flDistanceScale * flAngleScale * trace.fraction;
|
|
if( ratio == 0.0 ) continue;
|
|
|
|
for( int i = 0; i < 6; i++ )
|
|
{
|
|
vec_t t = DotProduct( g_BoxDirections[i], vDeltaNorm );
|
|
if( t > 0.0 ) VectorMA( lightBoxColor[i], (t * ratio), wl->intensity, lightBoxColor[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool R_GetDirectLightFromSurface( dface_t *surf, const vec3_t point, lightpoint_t *info )
|
|
{
|
|
faceneighbor_t *fn = &g_faceneighbor[surf - g_dfaces];
|
|
int texture_step = GetTextureStep( surf );
|
|
dtexinfo_t *tex = g_texinfo + surf->texinfo;
|
|
int map, size, smax, tmax;
|
|
float lmvecs[2][4];
|
|
vec_t s, t;
|
|
byte *lm;
|
|
|
|
LightMatrixFromTexMatrix( tex, lmvecs );
|
|
|
|
// recalc face extents here
|
|
s = DotProduct( point, lmvecs[0] ) + lmvecs[0][3] - fn->lightmapmins[0];
|
|
t = DotProduct( point, lmvecs[1] ) + lmvecs[1][3] - fn->lightmapmins[1];
|
|
|
|
if(( s < 0 || s > fn->lightextents[0] ) || ( t < 0 || t > fn->lightextents[1] ))
|
|
return false;
|
|
|
|
if( FBitSet( tex->flags, TEX_SPECIAL ))
|
|
{
|
|
const char *texname = GetTextureByTexinfo( surf->texinfo );
|
|
|
|
if( !Q_strnicmp( texname, "sky", 3 ))
|
|
info->hitsky = true;
|
|
return false; // no lightmaps
|
|
}
|
|
|
|
if( surf->lightofs == -1 )
|
|
return true;
|
|
|
|
smax = (fn->lightextents[0] / texture_step) + 1;
|
|
tmax = (fn->lightextents[1] / texture_step) + 1;
|
|
s /= (vec_t)texture_step;
|
|
t /= (vec_t)texture_step;
|
|
|
|
byte *samples = g_dlightdata + (unsigned int)surf->lightofs;
|
|
lm = samples + (unsigned int)Q_rint( t ) * smax + Q_rint( s );
|
|
size = smax * tmax;
|
|
|
|
VectorClear( info->diffuse );
|
|
|
|
for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ )
|
|
{
|
|
info->diffuse[0] += (float)lm[0] * 264.0f;
|
|
info->diffuse[1] += (float)lm[1] * 264.0f;
|
|
info->diffuse[2] += (float)lm[2] * 264.0f;
|
|
lm += size; // skip to next lightmap
|
|
}
|
|
|
|
// same as shift >> 7 in quake
|
|
info->diffuse[0] = Q_min( info->diffuse[0] * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f);
|
|
info->diffuse[1] = Q_min( info->diffuse[1] * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f);
|
|
info->diffuse[2] = Q_min( info->diffuse[2] * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f);
|
|
VectorClear( info->average );
|
|
lm = samples;
|
|
|
|
// also collect the average value
|
|
for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ )
|
|
{
|
|
for( int i = 0; i < size; i++, lm += 3 )
|
|
{
|
|
info->average[0] += (float)lm[0] * 264.0f;
|
|
info->average[1] += (float)lm[1] * 264.0f;
|
|
info->average[2] += (float)lm[2] * 264.0f;
|
|
}
|
|
VectorScale( info->average, ( 1.0f / (float)size ), info->average );
|
|
}
|
|
|
|
// same as shift >> 7 in quake
|
|
info->average[0] = Q_min( info->average[0] * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f);
|
|
info->average[1] = Q_min( info->average[1] * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f);
|
|
info->average[2] = Q_min( info->average[2] * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f);
|
|
info->surf = surf;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_RecursiveLightPoint
|
|
=================
|
|
*/
|
|
static bool R_RecursiveLightPoint( const int nodenum, float p1f, float p2f, const vec3_t start, const vec3_t end, lightpoint_t *info )
|
|
{
|
|
vec3_t mid;
|
|
|
|
// hit a leaf
|
|
if( nodenum < 0 ) return false;
|
|
dnode_t *node = g_dnodes + nodenum;
|
|
dplane_t *plane = g_dplanes + node->planenum;
|
|
|
|
// calculate mid point
|
|
float front = PlaneDiff( start, plane );
|
|
float back = PlaneDiff( end, plane );
|
|
|
|
int side = front < 0.0f;
|
|
if(( back < 0.0f ) == side )
|
|
return R_RecursiveLightPoint( node->children[side], p1f, p2f, start, end, info );
|
|
|
|
float frac = front / ( front - back );
|
|
|
|
float midf = p1f + ( p2f - p1f ) * frac;
|
|
VectorLerp( start, frac, end, mid );
|
|
|
|
// co down front side
|
|
if( R_RecursiveLightPoint( node->children[side], p1f, midf, start, mid, info ))
|
|
return true; // hit something
|
|
|
|
if(( back < 0.0f ) == side )
|
|
return false;// didn't hit anything
|
|
|
|
// check for impact on this node
|
|
for( int i = 0; i < node->numfaces; i++ )
|
|
{
|
|
dface_t *surf = g_dfaces + node->firstface + i;
|
|
|
|
if( R_GetDirectLightFromSurface( surf, mid, info ))
|
|
{
|
|
info->fraction = midf;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// go down back side
|
|
return R_RecursiveLightPoint( node->children[!side], midf, p2f, mid, end, info );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Finds ambient sky lights
|
|
//-----------------------------------------------------------------------------
|
|
static dworldlight_t *FindAmbientSkyLight( void )
|
|
{
|
|
static dworldlight_t *s_pCachedSkylight = NULL;
|
|
|
|
// Don't keep searching for the same light.
|
|
if( !s_pCachedSkylight )
|
|
{
|
|
// find any ambient lights
|
|
for( int iLight = 0; iLight < g_numworldlights; iLight++ )
|
|
{
|
|
dworldlight_t *wl = &g_dworldlights[iLight];
|
|
|
|
if( wl->emittype == emit_skylight )
|
|
{
|
|
s_pCachedSkylight = wl;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return s_pCachedSkylight;
|
|
}
|
|
|
|
static void ComputeLightmapColorFromPoint( lightpoint_t *info, dworldlight_t* pSkylight, float scale, vec3_t radcolor, bool average )
|
|
{
|
|
vec3_t color;
|
|
|
|
if( !info->surf && info->hitsky )
|
|
{
|
|
if( pSkylight )
|
|
{
|
|
VectorScale( pSkylight->intensity, scale * 0.5, color );
|
|
VectorScale( pSkylight->intensity, (1.0 / 255.0), color );
|
|
VectorAdd( radcolor, color, radcolor );
|
|
}
|
|
return;
|
|
}
|
|
|
|
if( info->surf != NULL )
|
|
{
|
|
if( average ) VectorScale( info->average, scale, color );
|
|
else VectorScale( info->diffuse, scale, color );
|
|
#if 0
|
|
vec3_t light, reflectivity;
|
|
|
|
BaseLightForFace( info->surf, light, reflectivity );
|
|
|
|
if( !VectorCompare( reflectivity, vec3_origin ))
|
|
VectorMultiply( color, reflectivity, color );
|
|
#endif
|
|
VectorAdd( radcolor, color, radcolor );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Computes ambient lighting along a specified ray.
|
|
// Ray represents a cone, tanTheta is the tan of the inner cone angle
|
|
//-----------------------------------------------------------------------------
|
|
static void CalcRayAmbientLighting( const vec3_t vStart, const vec3_t vEnd, dworldlight_t *pSkyLight, float tanTheta, vec3_t radcolor )
|
|
{
|
|
lightpoint_t info;
|
|
vec3_t vDelta;
|
|
|
|
memset( &info, 0, sizeof( lightpoint_t ));
|
|
info.fraction = 1.0f;
|
|
VectorClear( radcolor );
|
|
|
|
// Now that we've got a ray, see what surface we've hit
|
|
R_RecursiveLightPoint( 0, 0.0f, 1.0f, vStart, vEnd, &info );
|
|
|
|
VectorSubtract( vEnd, vStart, vDelta );
|
|
|
|
// compute the approximate radius of a circle centered around the intersection point
|
|
float dist = VectorLength( vDelta ) * tanTheta * info.fraction;
|
|
|
|
// until 20" we use the point sample, then blend in the average until we're covering 40"
|
|
// This is attempting to model the ray as a cone - in the ideal case we'd simply sample all
|
|
// luxels in the intersection of the cone with the surface. Since we don't have surface
|
|
// neighbor information computed we'll just approximate that sampling with a blend between
|
|
// a point sample and the face average.
|
|
// This yields results that are similar in that aliasing is reduced at distance while
|
|
// point samples provide accuracy for intersections with near geometry
|
|
float scaleAvg = RemapValClamped( dist, 20, 40, 0.0f, 1.0f );
|
|
|
|
if( !info.surf )
|
|
{
|
|
// don't have luxel UV, so just use average sample
|
|
scaleAvg = 1.0;
|
|
}
|
|
|
|
float scaleSample = 1.0f - scaleAvg;
|
|
|
|
if( scaleAvg != 0 )
|
|
{
|
|
ComputeLightmapColorFromPoint( &info, pSkyLight, scaleAvg, radcolor, true );
|
|
}
|
|
|
|
if( scaleSample != 0 )
|
|
{
|
|
ComputeLightmapColorFromPoint( &info, pSkyLight, scaleSample, radcolor, false );
|
|
}
|
|
}
|
|
|
|
static void ComputeAmbientFromSphericalSamples( int threadnum, const vec3_t p1, vec3_t lightBoxColor[6] )
|
|
{
|
|
// Figure out the color that rays hit when shot out from this position.
|
|
float tanTheta = tan( VERTEXNORMAL_CONE_INNER_ANGLE );
|
|
dworldlight_t *pSkyLight = FindAmbientSkyLight();
|
|
vec3_t radcolor[NUMVERTEXNORMALS], p2;
|
|
|
|
for( int i = 0; i < NUMVERTEXNORMALS; i++ )
|
|
{
|
|
VectorMA( p1, (65536.0 * 1.74), g_anorms[i], p2 );
|
|
|
|
// Now that we've got a ray, see what surface we've hit
|
|
CalcRayAmbientLighting( p1, p2, pSkyLight, tanTheta, radcolor[i] );
|
|
}
|
|
|
|
// accumulate samples into radiant box
|
|
for ( int j = 0; j < 6; j++ )
|
|
{
|
|
float t = 0.0f;
|
|
|
|
VectorClear( lightBoxColor[j] );
|
|
|
|
for( int i = 0; i < NUMVERTEXNORMALS; i++ )
|
|
{
|
|
float c = DotProduct( g_anorms[i], g_BoxDirections[j] );
|
|
|
|
if( c > 0.0f )
|
|
{
|
|
VectorMA( lightBoxColor[j], c, radcolor[i], lightBoxColor[j] );
|
|
t += c;
|
|
}
|
|
}
|
|
|
|
VectorScale( lightBoxColor[j], ( 1.0 / t ), lightBoxColor[j] );
|
|
}
|
|
|
|
// Now add direct light from the emit_surface lights. These go in the ambient cube because
|
|
// there are a ton of them and they are often so dim that they get filtered out by r_worldlightmin.
|
|
AddEmitSurfaceLights( threadnum, p1, lightBoxColor );
|
|
}
|
|
|
|
bool IsLeafAmbientSurfaceLight( dworldlight_t *wl )
|
|
{
|
|
const float g_flWorldLightMinEmitSurfaceDistanceRatio = 0.000003f;
|
|
const float g_flWorldLightMinEmitSurface = 0.005f;
|
|
|
|
if( wl->emittype != emit_surface )
|
|
return false;
|
|
|
|
if( wl->style != 0 )
|
|
return false;
|
|
|
|
float intensity = VectorMax( wl->intensity );
|
|
|
|
return ( intensity * g_flWorldLightMinEmitSurfaceDistanceRatio ) < g_flWorldLightMinEmitSurface;
|
|
}
|
|
|
|
// Generate a random point in the leaf's bounding volume
|
|
// reject any points that aren't actually in the leaf
|
|
// do a couple of tracing heuristics to eliminate points that are inside detail brushes
|
|
// or underneath displacement surfaces in the leaf
|
|
// return once we have a valid point, use the center if one can't be computed quickly
|
|
void GenerateLeafSamplePosition( int leafIndex, ambientlocallist_t *list, const leafplanes_t *leafPlanes, vec3_t samplePosition )
|
|
{
|
|
dleaf_t *pLeaf = g_dleafs + leafIndex;
|
|
vec3_t vCenter, leafMins, leafMaxs;
|
|
|
|
VectorCopy( pLeaf->mins, leafMins );
|
|
VectorCopy( pLeaf->maxs, leafMaxs );
|
|
bool bValid = false;
|
|
|
|
VectorAverage( leafMins, leafMaxs, vCenter );
|
|
|
|
// place first sample always at center to leaf
|
|
if( list->numSamples == 0 )
|
|
{
|
|
VectorCopy( vCenter, samplePosition );
|
|
return;
|
|
}
|
|
|
|
// lock so random float will working properly
|
|
ThreadLock();
|
|
|
|
for( int i = 0; i < 1024 && !bValid; i++ )
|
|
{
|
|
VectorLerp( leafMins, RandomFloat( 0.01f, 0.99f ), leafMaxs, samplePosition );
|
|
vec3_t vDiff;
|
|
|
|
int l;
|
|
for( l = 0; l < list->numSamples; l++ )
|
|
{
|
|
VectorSubtract( samplePosition, list->samples[l].pos, vDiff );
|
|
float flLength = VectorLength( vDiff );
|
|
if( flLength < 32.0f ) break; // too tight
|
|
}
|
|
|
|
if( l != list->numSamples )
|
|
continue;
|
|
|
|
bValid = true;
|
|
|
|
for( int k = leafPlanes->numLeafPlanes - 1; --k >= 0 && bValid; )
|
|
{
|
|
float d = PlaneDiff( samplePosition, leafPlanes->planes + k );
|
|
|
|
if( d < DIST_EPSILON )
|
|
{
|
|
// not inside the leaf, try again
|
|
bValid = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ThreadUnlock();
|
|
|
|
if( !bValid )
|
|
{
|
|
// didn't generate a valid sample point, just use the center of the leaf bbox
|
|
VectorCopy( vCenter, samplePosition );
|
|
}
|
|
}
|
|
|
|
// gets a list of the planes pointing into a leaf
|
|
void GetLeafBoundaryPlanes( leafplanes_t *list, int leafIndex )
|
|
{
|
|
int nodeIndex = leafparents[leafIndex];
|
|
int child = -(leafIndex + 1);
|
|
|
|
while( nodeIndex >= 0 )
|
|
{
|
|
dnode_t *pNode = g_dnodes + nodeIndex;
|
|
dplane_t *pNodePlane = g_dplanes + pNode->planenum;
|
|
|
|
if( pNode->children[0] == child )
|
|
{
|
|
// front side
|
|
list->planes[list->numLeafPlanes] = *pNodePlane;
|
|
}
|
|
else
|
|
{
|
|
// back side
|
|
int plane = list->numLeafPlanes;
|
|
list->planes[plane].dist = -pNodePlane->dist;
|
|
list->planes[plane].normal[0] = -pNodePlane->normal[0];
|
|
list->planes[plane].normal[1] = -pNodePlane->normal[1];
|
|
list->planes[plane].normal[2] = -pNodePlane->normal[2];
|
|
list->planes[plane].type = pNodePlane->type;
|
|
list->numLeafPlanes++;
|
|
}
|
|
|
|
if( list->numLeafPlanes >= MAX_LEAF_PLANES )
|
|
break; // there was a too many planes
|
|
|
|
child = nodeIndex;
|
|
nodeIndex = nodeparents[child];
|
|
}
|
|
}
|
|
|
|
// add the sample to the list. If we exceed the maximum number of samples, the worst sample will
|
|
// be discarded. This has the effect of converging on the best samples when enough are added.
|
|
void AddSampleToList( ambientlocallist_t *list, const vec3_t samplePosition, vec3_t pCube[6] )
|
|
{
|
|
int i, index = list->numSamples++;
|
|
|
|
VectorCopy( samplePosition, list->samples[index].pos );
|
|
|
|
for( int k = 0; k < 6; k++ )
|
|
{
|
|
VectorCopy( pCube[k], list->samples[index].cube[k] );
|
|
}
|
|
|
|
if( list->numSamples <= MAX_SAMPLES )
|
|
return;
|
|
|
|
ambientlocallist_t oldlist;
|
|
int nearestNeighborIndex = 0;
|
|
float nearestNeighborDist = FLT_MAX;
|
|
float nearestNeighborTotal = 0;
|
|
|
|
// do a copy of current list
|
|
memcpy( &oldlist, list, sizeof( ambientlocallist_t ));
|
|
|
|
for( i = 0; i < oldlist.numSamples; i++ )
|
|
{
|
|
int closestIndex = 0;
|
|
float closestDist = FLT_MAX;
|
|
float totalDC = 0;
|
|
|
|
for( int j = 0; j < oldlist.numSamples; j++ )
|
|
{
|
|
if( j == i ) continue;
|
|
|
|
vec3_t vDelta;
|
|
|
|
VectorSubtract( oldlist.samples[i].pos, oldlist.samples[j].pos, vDelta );
|
|
|
|
float dist = VectorLength( vDelta );
|
|
float maxDC = 0;
|
|
|
|
for( int k = 0; k < 6; k++ )
|
|
{
|
|
// color delta is computed per-component, per cube side
|
|
for( int s = 0; s < 3; s++ )
|
|
{
|
|
float dc = fabs( oldlist.samples[i].cube[k][s] - oldlist.samples[j].cube[k][s] );
|
|
maxDC = Q_max( maxDC, dc );
|
|
}
|
|
|
|
totalDC += maxDC;
|
|
}
|
|
|
|
// need a measurable difference in color or we'll just rely on position
|
|
if( maxDC < 1e-4f )
|
|
{
|
|
maxDC = 0;
|
|
}
|
|
else if( maxDC > 1.0f )
|
|
{
|
|
maxDC = 1.0f;
|
|
}
|
|
|
|
// selection criteria is 10% distance, 90% color difference
|
|
// choose samples that fill the space (large distance from each other)
|
|
// and have largest color variation
|
|
float distanceFactor = 0.1f + (maxDC * 0.9f);
|
|
dist *= distanceFactor;
|
|
|
|
// find the "closest" sample to this one
|
|
if( dist < closestDist )
|
|
{
|
|
closestDist = dist;
|
|
closestIndex = j;
|
|
}
|
|
}
|
|
|
|
// the sample with the "closest" neighbor is rejected
|
|
if( closestDist < nearestNeighborDist || ( closestDist == nearestNeighborDist && totalDC < nearestNeighborTotal ))
|
|
{
|
|
nearestNeighborDist = closestDist;
|
|
nearestNeighborIndex = i;
|
|
}
|
|
}
|
|
|
|
list->numSamples = 0;
|
|
|
|
// copy the entries back but skip nearestNeighborIndex
|
|
for( i = 0; i < oldlist.numSamples; i++ )
|
|
{
|
|
if( i != nearestNeighborIndex )
|
|
{
|
|
memcpy( &list->samples[list->numSamples], &oldlist.samples[i], sizeof( ambientsample_t ));
|
|
list->numSamples++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// max number of units in gamma space of per-side delta
|
|
int CubeDeltaColor( vec3_t pCube0[6], vec3_t pCube1[6] )
|
|
{
|
|
int maxDelta = 0;
|
|
|
|
// do this comparison in gamma space to try and get a perceptual basis for the compare
|
|
for( int i = 0; i < 6; i++ )
|
|
{
|
|
for ( int j = 0; j < 3; j++ )
|
|
{
|
|
int val0 = pCube0[i][j];
|
|
int val1 = pCube1[i][j];
|
|
int delta = abs( val0 - val1 );
|
|
|
|
if( delta > maxDelta )
|
|
maxDelta = delta;
|
|
}
|
|
}
|
|
|
|
return maxDelta;
|
|
}
|
|
// reconstruct the ambient lighting for a leaf at the given position in worldspace
|
|
// optionally skip one of the entries in the list
|
|
void Mod_LeafAmbientColorAtPos( vec3_t pOut[6], const vec3_t pos, ambientlocallist_t *list, int skipIndex )
|
|
{
|
|
vec3_t vDelta;
|
|
int i;
|
|
|
|
for( i = 0; i < 6; i++ )
|
|
{
|
|
VectorClear( pOut[i] );
|
|
}
|
|
|
|
float totalFactor = 0.0f;
|
|
|
|
for( i = 0; i < list->numSamples; i++ )
|
|
{
|
|
if ( i == skipIndex )
|
|
continue;
|
|
|
|
// do an inverse squared distance weighted average of the samples to reconstruct
|
|
// the original function
|
|
VectorSubtract( list->samples[i].pos, pos, vDelta );
|
|
float dist = DotProduct( vDelta, vDelta );
|
|
float factor = 1.0f / (dist + 1.0f);
|
|
totalFactor += factor;
|
|
|
|
for( int j = 0; j < 6; j++ )
|
|
{
|
|
VectorMA( pOut[j], factor, list->samples[i].cube[j], pOut[j] );
|
|
}
|
|
}
|
|
|
|
for( i = 0; i < 6; i++ )
|
|
{
|
|
VectorScale( pOut[i], (1.0f / totalFactor), pOut[i] );
|
|
}
|
|
}
|
|
|
|
// this samples the lighting at each sample and removes any unnecessary samples
|
|
void CompressAmbientSampleList( ambientlocallist_t *list )
|
|
{
|
|
ambientlocallist_t oldlist;
|
|
vec3_t testCube[6];
|
|
|
|
// do a copy of current list
|
|
memcpy( &oldlist, list, sizeof( ambientlocallist_t ));
|
|
list->numSamples = 0;
|
|
|
|
for( int i = 0; i < oldlist.numSamples; i++ )
|
|
{
|
|
Mod_LeafAmbientColorAtPos( testCube, oldlist.samples[i].pos, &oldlist, i );
|
|
|
|
// at least one sample must be included in the list
|
|
if( i == 0 || CubeDeltaColor( testCube, oldlist.samples[i].cube ) >= 10 )
|
|
{
|
|
memcpy( &list->samples[list->numSamples], &oldlist.samples[i], sizeof( ambientsample_t ));
|
|
list->numSamples++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ComputeAmbientForLeaf( int threadnum, int leafID, ambientlocallist_t *list )
|
|
{
|
|
leafplanes_t leafPlanes;
|
|
|
|
leafPlanes.numLeafPlanes = 0;
|
|
|
|
GetLeafBoundaryPlanes( &leafPlanes, leafID );
|
|
|
|
// this heuristic tries to generate at least one sample per volume (chosen to be similar to the size of a player) in the space
|
|
int xSize = (g_dleafs[leafID].maxs[0] - g_dleafs[leafID].mins[0]) / 64;
|
|
int ySize = (g_dleafs[leafID].maxs[1] - g_dleafs[leafID].mins[1]) / 64;
|
|
int zSize = (g_dleafs[leafID].maxs[2] - g_dleafs[leafID].mins[2]) / 64;
|
|
|
|
xSize = Q_max( xSize, 1 );
|
|
ySize = Q_max( ySize, 1 );
|
|
zSize = Q_max( zSize, 1 );
|
|
|
|
// generate update 128 candidate samples, always at least one sample
|
|
int volumeCount = xSize * ySize * zSize;
|
|
|
|
if( g_fastmode )
|
|
volumeCount *= 0.01;
|
|
else if( !g_extra )
|
|
volumeCount *= 0.05;
|
|
else volumeCount *= 0.1;
|
|
|
|
int sampleCount = bound( MIN_LOCAL_SAMPLES, volumeCount, MAX_LOCAL_SAMPLES );
|
|
|
|
if( g_dleafs[leafID].contents == CONTENTS_SOLID )
|
|
{
|
|
// don't generate any samples in solid leaves
|
|
// NOTE: We copy the nearest non-solid leaf
|
|
// sample pointers into this leaf at the end
|
|
return;
|
|
}
|
|
|
|
vec3_t cube[6];
|
|
|
|
for( int i = 0; i < sampleCount; i++ )
|
|
{
|
|
// compute each candidate sample and add to the list
|
|
vec3_t samplePosition;
|
|
GenerateLeafSamplePosition( leafID, list, &leafPlanes, samplePosition );
|
|
ComputeAmbientFromSphericalSamples( threadnum, samplePosition, cube );
|
|
// note this will remove the least valuable sample once the limit is reached
|
|
AddSampleToList( list, samplePosition, cube );
|
|
}
|
|
|
|
// remove any samples that can be reconstructed with the remaining data
|
|
CompressAmbientSampleList( list );
|
|
}
|
|
|
|
static void LeafAmbientLighting( int threadnum )
|
|
{
|
|
ambientlocallist_t list;
|
|
|
|
while( 1 )
|
|
{
|
|
int leafID = GetThreadWork ();
|
|
if( leafID == -1 ) break;
|
|
|
|
list.numSamples = 0;
|
|
|
|
ComputeAmbientForLeaf( threadnum, leafID, &list );
|
|
|
|
// copy to the output array
|
|
g_leaf_samples[leafID].numSamples = list.numSamples;
|
|
g_leaf_samples[leafID].samples = (ambientsample_t *)Mem_Alloc( sizeof( ambientsample_t ) * list.numSamples );
|
|
memcpy( g_leaf_samples[leafID].samples, list.samples, sizeof( ambientsample_t ) * list.numSamples );
|
|
}
|
|
}
|
|
|
|
void ComputeLeafAmbientLighting( void )
|
|
{
|
|
// Figure out which lights should go in the per-leaf ambient cubes.
|
|
int nInAmbientCube = 0;
|
|
int nSurfaceLights = 0;
|
|
int i;
|
|
|
|
// always matched
|
|
memset( g_leaf_samples, 0, sizeof( g_leaf_samples ));
|
|
|
|
for( i = 0; i < g_numworldlights; i++ )
|
|
{
|
|
dworldlight_t *wl = &g_dworldlights[i];
|
|
|
|
if( IsLeafAmbientSurfaceLight( wl ))
|
|
SetBits( wl->flags, DWL_FLAGS_INAMBIENTCUBE );
|
|
else ClearBits( wl->flags, DWL_FLAGS_INAMBIENTCUBE );
|
|
|
|
if( wl->emittype == emit_surface )
|
|
nSurfaceLights++;
|
|
|
|
if( FBitSet( wl->flags, DWL_FLAGS_INAMBIENTCUBE ))
|
|
nInAmbientCube++;
|
|
}
|
|
|
|
srand( time( NULL )); // init random generator
|
|
MakeParents( 0, -1 );
|
|
|
|
MsgDev( D_REPORT, "%d of %d (%d%% of) surface lights went in leaf ambient cubes.\n",
|
|
nInAmbientCube, nSurfaceLights, nSurfaceLights ? ((nInAmbientCube*100) / nSurfaceLights) : 0 );
|
|
|
|
RunThreadsOn( g_dmodels[0].visleafs + 1, true, LeafAmbientLighting );
|
|
|
|
// clear old samples
|
|
g_numleaflights = 0;
|
|
|
|
for ( int leafID = 0; leafID < g_dmodels[0].visleafs + 1; leafID++ )
|
|
{
|
|
ambientlist_t *list = &g_leaf_samples[leafID];
|
|
|
|
ASSERT( list->numSamples <= 255 );
|
|
|
|
if( !list->numSamples ) continue;
|
|
|
|
// compute the samples in disk format. Encode the positions in 8-bits using leaf bounds fractions
|
|
for ( int i = 0; i < list->numSamples; i++ )
|
|
{
|
|
if( g_numleaflights == MAX_MAP_LEAFLIGHTS )
|
|
COM_FatalError( "MAX_MAP_LEAFLIGHTS limit exceeded\n" );
|
|
|
|
dleafsample_t *sample = &g_dleaflights[g_numleaflights];
|
|
|
|
for( int j = 0; j < 3; j++ )
|
|
sample->origin[j] = (short)bound( -32767, (int)list->samples[i].pos[j], 32767 );
|
|
sample->leafnum = leafID;
|
|
|
|
for( int side = 0; side < 6; side++ )
|
|
{
|
|
sample->ambient.color[side][0] = bound( 0, list->samples[i].cube[side][0] * 255, 255 );
|
|
sample->ambient.color[side][1] = bound( 0, list->samples[i].cube[side][1] * 255, 255 );
|
|
sample->ambient.color[side][2] = bound( 0, list->samples[i].cube[side][2] * 255, 255 );
|
|
}
|
|
g_numleaflights++;
|
|
}
|
|
|
|
Mem_Free( list->samples ); // release src
|
|
list->samples = NULL;
|
|
}
|
|
|
|
MsgDev( D_REPORT, "%i ambient samples stored\n", g_numleaflights );
|
|
}
|
|
|
|
#endif |