Paranoia2/utils/p2rad/ambientcube.cpp
2020-08-31 19:50:41 +03:00

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