gtkradiant/tools/quake3/q3map2/light_bounce.c
Thomas Debesse eb5fe5bce9 q3map2/light_bounce: prevent infinite loop on obscure bias compute
New code produces same result without loop at all, so
it cannot fall in infinite loop, and it is faster in
use cases requiring more than one loop in previous code.

The Unvanquished vega map is known to trigger the bug:
https://github.com/UnvanquishedAssets/map-vega_src.dpkdir
I reproduced it multiple time on various hardware (8 core FX-9590,
12 core/24 thread Ryzen 9 3900X) with commit af40508 and using
final compilation profile edited to use -fastbounce instead
of -fast option.

The symptom is simple, q3map2 stucks there:

--- Radiosity (bounce 1 of 8) ---
--- RadCreateDiffuseLights ---
0...1...2...3..

Or somewhere else in that progression bar given your hardware
and the amount of core your CPU has.

When stuck, all the CPU cores are running 100% but the thread
never returns (a strace can reveals it, a gdb backtrace too).

Thanks to @slipher for the precious advices and improving my first
attempt to fix it.

For more information on the issue, I asked:

> which negative value never can become positive
> when incremented infinitely?

slipher said:

> for a double, any value less than -2^53 would have this property
> don't know for float off the top of my head

But then, it means that's theorically verified this loop was able
to run forever in some case.

I don't know what this code is doing anyway, but at least we can
keep the behaviour without requiring to understand it.
2020-07-03 08:12:48 +02:00

961 lines
26 KiB
C

/* -------------------------------------------------------------------------------
Copyright (C) 1999-2007 id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.
This file is part of GtkRadiant.
GtkRadiant 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.
GtkRadiant 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 GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
----------------------------------------------------------------------------------
This code has been altered significantly from its original form, to support
several games based on the Quake III Arena engine, in the form of "Q3Map2."
------------------------------------------------------------------------------- */
/* marker */
#define LIGHT_BOUNCE_C
/* dependencies */
#include "q3map2.h"
/* functions */
/*
RadFreeLights()
deletes any existing lights, freeing up memory for the next bounce
*/
void RadFreeLights( void ){
light_t *light, *next;
/* delete lights */
for ( light = lights; light; light = next )
{
next = light->next;
if ( light->w != NULL ) {
FreeWinding( light->w );
}
free( light );
}
numLights = 0;
lights = NULL;
}
/*
RadClipWindingEpsilon()
clips a rad winding by a plane
based off the regular clip winding code
*/
static void RadClipWindingEpsilon( radWinding_t *in, vec3_t normal, vec_t dist,
vec_t epsilon, radWinding_t *front, radWinding_t *back, clipWork_t *cw ){
vec_t *dists;
int *sides;
int counts[ 3 ];
vec_t dot; /* ydnar: changed from static b/c of threading */ /* VC 4.2 optimizer bug if not static? */
int i, j, k;
radVert_t *v1, *v2, mid;
int maxPoints;
/* crutch */
dists = cw->dists;
sides = cw->sides;
/* clear counts */
counts[ 0 ] = counts[ 1 ] = counts[ 2 ] = 0;
/* determine sides for each point */
for ( i = 0; i < in->numVerts; i++ )
{
dot = DotProduct( in->verts[ i ].xyz, normal );
dot -= dist;
dists[ i ] = dot;
if ( dot > epsilon ) {
sides[ i ] = SIDE_FRONT;
}
else if ( dot < -epsilon ) {
sides[ i ] = SIDE_BACK;
}
else{
sides[ i ] = SIDE_ON;
}
counts[ sides[ i ] ]++;
}
sides[ i ] = sides[ 0 ];
dists[ i ] = dists[ 0 ];
/* clear front and back */
front->numVerts = back->numVerts = 0;
/* handle all on one side cases */
if ( counts[ 0 ] == 0 ) {
memcpy( back, in, sizeof( radWinding_t ) );
return;
}
if ( counts[ 1 ] == 0 ) {
memcpy( front, in, sizeof( radWinding_t ) );
return;
}
/* setup windings */
maxPoints = in->numVerts + 4;
/* do individual verts */
for ( i = 0; i < in->numVerts; i++ )
{
/* do simple vertex copies first */
v1 = &in->verts[ i ];
if ( sides[ i ] == SIDE_ON ) {
memcpy( &front->verts[ front->numVerts++ ], v1, sizeof( radVert_t ) );
memcpy( &back->verts[ back->numVerts++ ], v1, sizeof( radVert_t ) );
continue;
}
if ( sides[ i ] == SIDE_FRONT ) {
memcpy( &front->verts[ front->numVerts++ ], v1, sizeof( radVert_t ) );
}
if ( sides[ i ] == SIDE_BACK ) {
memcpy( &back->verts[ back->numVerts++ ], v1, sizeof( radVert_t ) );
}
if ( sides[ i + 1 ] == SIDE_ON || sides[ i + 1 ] == sides[ i ] ) {
continue;
}
/* generate a split vertex */
v2 = &in->verts[ ( i + 1 ) % in->numVerts ];
dot = dists[ i ] / ( dists[ i ] - dists[ i + 1 ] );
/* average vertex values */
for ( j = 0; j < 4; j++ )
{
/* color */
if ( j < 4 ) {
for ( k = 0; k < MAX_LIGHTMAPS; k++ )
mid.color[ k ][ j ] = v1->color[ k ][ j ] + dot * ( v2->color[ k ][ j ] - v1->color[ k ][ j ] );
}
/* xyz, normal */
if ( j < 3 ) {
mid.xyz[ j ] = v1->xyz[ j ] + dot * ( v2->xyz[ j ] - v1->xyz[ j ] );
mid.normal[ j ] = v1->normal[ j ] + dot * ( v2->normal[ j ] - v1->normal[ j ] );
}
/* st, lightmap */
if ( j < 2 ) {
mid.st[ j ] = v1->st[ j ] + dot * ( v2->st[ j ] - v1->st[ j ] );
for ( k = 0; k < MAX_LIGHTMAPS; k++ )
mid.lightmap[ k ][ j ] = v1->lightmap[ k ][ j ] + dot * ( v2->lightmap[ k ][ j ] - v1->lightmap[ k ][ j ] );
}
}
/* normalize the averaged normal */
VectorNormalize( mid.normal, mid.normal );
/* copy the midpoint to both windings */
memcpy( &front->verts[ front->numVerts++ ], &mid, sizeof( radVert_t ) );
memcpy( &back->verts[ back->numVerts++ ], &mid, sizeof( radVert_t ) );
}
/* error check */
if ( front->numVerts > maxPoints ) {
Error( "RadClipWindingEpsilon: points exceeded estimate" );
}
if ( front->numVerts > MAX_POINTS_ON_WINDING ) {
Error( "RadClipWindingEpsilon: MAX_POINTS_ON_WINDING" );
}
}
/*
Modulo1IfNegative()
Previously the bias computation was doing:
while ( f < 0.0f ) {
f += 1.0f;
}
That may end in infinite loop in some case.
It may also be slower because of useless loops.
I don't know what that computation is for.
-- illwieckz
*/
float Modulo1IfNegative( float f ){
return f < 0.0f ? f - floor( f ) : f;
}
/*
RadSampleImage()
samples a texture image for a given color
returns qfalse if pixels are bad
*/
qboolean RadSampleImage( byte *pixels, int width, int height, float st[ 2 ], float color[ 4 ] ){
int x, y;
/* clear color first */
color[ 0 ] = color[ 1 ] = color[ 2 ] = color[ 3 ] = 255;
/* dummy check */
if ( pixels == NULL || width < 1 || height < 1 ) {
return qfalse;
}
/* get offsets */
x = ( (float) width * Modulo1IfNegative( st[ 0 ] ) ) + 0.5f;
x %= width;
y = ( (float) height * Modulo1IfNegative( st[ 1 ] ) ) + 0.5f;
y %= height;
/* get pixel */
pixels += ( y * width * 4 ) + ( x * 4 );
VectorCopy( pixels, color );
color[ 3 ] = pixels[ 3 ];
return qtrue;
}
/*
RadSample()
samples a fragment's lightmap or vertex color and returns an
average color and a color gradient for the sample
*/
#define MAX_SAMPLES 150
#define SAMPLE_GRANULARITY 6
static void RadSample( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm, shaderInfo_t *si, radWinding_t *rw, vec3_t average, vec3_t gradient, int *style ){
int i, j, k, l, v, x, y, samples;
vec3_t color, mins, maxs;
vec4_t textureColor;
float alpha, alphaI, bf;
vec3_t blend;
float st[ 2 ], lightmap[ 2 ], *radLuxel;
radVert_t *rv[ 3 ];
/* initial setup */
ClearBounds( mins, maxs );
VectorClear( average );
VectorClear( gradient );
alpha = 0;
/* dummy check */
if ( rw == NULL || rw->numVerts < 3 ) {
return;
}
/* start sampling */
samples = 0;
/* sample vertex colors if no lightmap or this is the initial pass */
if ( lm == NULL || lm->radLuxels[ lightmapNum ] == NULL || bouncing == qfalse ) {
for ( samples = 0; samples < rw->numVerts; samples++ )
{
/* multiply by texture color */
if ( !RadSampleImage( si->lightImage->pixels, si->lightImage->width, si->lightImage->height, rw->verts[ samples ].st, textureColor ) ) {
VectorCopy( si->averageColor, textureColor );
textureColor[ 3 ] = 255.0f;
}
for ( i = 0; i < 3; i++ )
color[ i ] = ( textureColor[ i ] / 255 ) * ( rw->verts[ samples ].color[ lightmapNum ][ i ] / 255.0f );
AddPointToBounds( color, mins, maxs );
VectorAdd( average, color, average );
/* get alpha */
alpha += ( textureColor[ 3 ] / 255.0f ) * ( rw->verts[ samples ].color[ lightmapNum ][ 3 ] / 255.0f );
}
/* set style */
*style = ds->vertexStyles[ lightmapNum ];
}
/* sample lightmap */
else
{
/* fracture the winding into a fan (including degenerate tris) */
for ( v = 1; v < ( rw->numVerts - 1 ) && samples < MAX_SAMPLES; v++ )
{
/* get a triangle */
rv[ 0 ] = &rw->verts[ 0 ];
rv[ 1 ] = &rw->verts[ v ];
rv[ 2 ] = &rw->verts[ v + 1 ];
/* this code is embarassing (really should just rasterize the triangle) */
for ( i = 1; i < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; i++ )
{
for ( j = 1; j < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; j++ )
{
for ( k = 1; k < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; k++ )
{
/* create a blend vector (barycentric coordinates) */
blend[ 0 ] = i;
blend[ 1 ] = j;
blend[ 2 ] = k;
bf = ( 1.0 / ( blend[ 0 ] + blend[ 1 ] + blend[ 2 ] ) );
VectorScale( blend, bf, blend );
/* create a blended sample */
st[ 0 ] = st[ 1 ] = 0.0f;
lightmap[ 0 ] = lightmap[ 1 ] = 0.0f;
alphaI = 0.0f;
for ( l = 0; l < 3; l++ )
{
st[ 0 ] += ( rv[ l ]->st[ 0 ] * blend[ l ] );
st[ 1 ] += ( rv[ l ]->st[ 1 ] * blend[ l ] );
lightmap[ 0 ] += ( rv[ l ]->lightmap[ lightmapNum ][ 0 ] * blend[ l ] );
lightmap[ 1 ] += ( rv[ l ]->lightmap[ lightmapNum ][ 1 ] * blend[ l ] );
alphaI += ( rv[ l ]->color[ lightmapNum ][ 3 ] * blend[ l ] );
}
/* get lightmap xy coords */
x = lightmap[ 0 ] / (float) superSample;
y = lightmap[ 1 ] / (float) superSample;
if ( x < 0 ) {
x = 0;
}
else if ( x >= lm->w ) {
x = lm->w - 1;
}
if ( y < 0 ) {
y = 0;
}
else if ( y >= lm->h ) {
y = lm->h - 1;
}
/* get radiosity luxel */
radLuxel = RAD_LUXEL( lightmapNum, x, y );
/* ignore unlit/unused luxels */
if ( radLuxel[ 0 ] < 0.0f ) {
continue;
}
/* inc samples */
samples++;
/* multiply by texture color */
if ( !RadSampleImage( si->lightImage->pixels, si->lightImage->width, si->lightImage->height, st, textureColor ) ) {
VectorCopy( si->averageColor, textureColor );
textureColor[ 3 ] = 255;
}
for ( i = 0; i < 3; i++ )
color[ i ] = ( textureColor[ i ] / 255 ) * ( radLuxel[ i ] / 255 );
AddPointToBounds( color, mins, maxs );
VectorAdd( average, color, average );
/* get alpha */
alpha += ( textureColor[ 3 ] / 255 ) * ( alphaI / 255 );
}
}
}
}
/* set style */
*style = ds->lightmapStyles[ lightmapNum ];
}
/* any samples? */
if ( samples <= 0 ) {
return;
}
/* average the color */
VectorScale( average, ( 1.0 / samples ), average );
/* create the color gradient */
//% VectorSubtract( maxs, mins, delta );
/* new: color gradient will always be 0-1.0, expressed as the range of light relative to overall light */
//% gradient[ 0 ] = maxs[ 0 ] > 0.0f ? (maxs[ 0 ] - mins[ 0 ]) / maxs[ 0 ] : 0.0f;
//% gradient[ 1 ] = maxs[ 1 ] > 0.0f ? (maxs[ 1 ] - mins[ 1 ]) / maxs[ 1 ] : 0.0f;
//% gradient[ 2 ] = maxs[ 2 ] > 0.0f ? (maxs[ 2 ] - mins[ 2 ]) / maxs[ 2 ] : 0.0f;
/* newer: another contrast function */
for ( i = 0; i < 3; i++ )
gradient[ i ] = ( maxs[ i ] - mins[ i ] ) * maxs[ i ];
}
/*
RadSubdivideDiffuseLight()
subdivides a radiosity winding until it is smaller than subdivide, then generates an area light
*/
#define RADIOSITY_MAX_GRADIENT 0.75f //% 0.25f
#define RADIOSITY_VALUE 500.0f
#define RADIOSITY_MIN 0.0001f
#define RADIOSITY_CLIP_EPSILON 0.125f
static void RadSubdivideDiffuseLight( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm, shaderInfo_t *si,
float scale, float subdivide, qboolean original, radWinding_t *rw, clipWork_t *cw ){
int i, style = 0;
float dist, area, value;
vec3_t mins, maxs, normal, d1, d2, cross, color, gradient;
light_t *light, *splash;
winding_t *w;
/* dummy check */
if ( rw == NULL || rw->numVerts < 3 ) {
return;
}
/* get bounds for winding */
ClearBounds( mins, maxs );
for ( i = 0; i < rw->numVerts; i++ )
AddPointToBounds( rw->verts[ i ].xyz, mins, maxs );
/* subdivide if necessary */
for ( i = 0; i < 3; i++ )
{
if ( maxs[ i ] - mins[ i ] > subdivide ) {
radWinding_t front, back;
/* make axial plane */
VectorClear( normal );
normal[ i ] = 1;
dist = ( maxs[ i ] + mins[ i ] ) * 0.5f;
/* clip the winding */
RadClipWindingEpsilon( rw, normal, dist, RADIOSITY_CLIP_EPSILON, &front, &back, cw );
/* recurse */
RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qfalse, &front, cw );
RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qfalse, &back, cw );
return;
}
}
/* check area */
area = 0.0f;
for ( i = 2; i < rw->numVerts; i++ )
{
VectorSubtract( rw->verts[ i - 1 ].xyz, rw->verts[ 0 ].xyz, d1 );
VectorSubtract( rw->verts[ i ].xyz, rw->verts[ 0 ].xyz, d2 );
CrossProduct( d1, d2, cross );
area += 0.5f * VectorLength( cross );
}
if ( area < 1.0f || area > 20000000.0f ) {
return;
}
/* more subdivision may be necessary */
if ( bouncing ) {
/* get color sample for the surface fragment */
RadSample( lightmapNum, ds, lm, si, rw, color, gradient, &style );
/* if color gradient is too high, subdivide again */
if ( subdivide > minDiffuseSubdivide &&
( gradient[ 0 ] > RADIOSITY_MAX_GRADIENT || gradient[ 1 ] > RADIOSITY_MAX_GRADIENT || gradient[ 2 ] > RADIOSITY_MAX_GRADIENT ) ) {
RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, ( subdivide / 2.0f ), qfalse, rw, cw );
return;
}
}
/* create a regular winding and an average normal */
w = AllocWinding( rw->numVerts );
w->numpoints = rw->numVerts;
VectorClear( normal );
for ( i = 0; i < rw->numVerts; i++ )
{
VectorCopy( rw->verts[ i ].xyz, w->p[ i ] );
VectorAdd( normal, rw->verts[ i ].normal, normal );
}
VectorScale( normal, ( 1.0f / rw->numVerts ), normal );
if ( VectorNormalize( normal, normal ) == 0.0f ) {
return;
}
/* early out? */
if ( bouncing && VectorLength( color ) < RADIOSITY_MIN ) {
return;
}
/* debug code */
//% Sys_Printf( "Size: %d %d %d\n", (int) (maxs[ 0 ] - mins[ 0 ]), (int) (maxs[ 1 ] - mins[ 1 ]), (int) (maxs[ 2 ] - mins[ 2 ]) );
//% Sys_Printf( "Grad: %f %f %f\n", gradient[ 0 ], gradient[ 1 ], gradient[ 2 ] );
/* increment counts */
numDiffuseLights++;
switch ( ds->surfaceType )
{
case MST_PLANAR:
numBrushDiffuseLights++;
break;
case MST_TRIANGLE_SOUP:
numTriangleDiffuseLights++;
break;
case MST_PATCH:
numPatchDiffuseLights++;
break;
}
/* create a light */
light = safe_malloc( sizeof( *light ) );
memset( light, 0, sizeof( *light ) );
/* attach it */
ThreadLock();
light->next = lights;
lights = light;
ThreadUnlock();
/* initialize the light */
light->flags = LIGHT_AREA_DEFAULT;
light->type = EMIT_AREA;
light->si = si;
light->fade = 1.0f;
light->w = w;
/* set falloff threshold */
light->falloffTolerance = falloffTolerance;
/* bouncing light? */
if ( !bouncing ) {
/* This is weird. This actually handles surfacelight and not
* bounces. */
/* handle first-pass lights in normal q3a style */
value = si->value;
light->photons = value * area * areaScale;
light->add = value * formFactorValueScale * areaScale;
VectorCopy( si->color, light->color );
VectorScale( light->color, light->add, light->emitColor );
light->style = noStyles ? LS_NORMAL : si->lightStyle;
if ( light->style < LS_NORMAL || light->style >= LS_NONE ) {
light->style = LS_NORMAL;
}
/* set origin */
VectorAdd( mins, maxs, light->origin );
VectorScale( light->origin, 0.5f, light->origin );
/* nudge it off the plane a bit */
VectorCopy( normal, light->normal );
VectorMA( light->origin, 1.0f, light->normal, light->origin );
light->dist = DotProduct( light->origin, normal );
/* optionally create a point splashsplash light for first pass */
if ( original && si->backsplashFraction > 0 ) {
/* allocate a new point light */
splash = safe_malloc( sizeof( *splash ) );
memset( splash, 0, sizeof( *splash ) );
splash->next = lights;
lights = splash;
/* set it up */
splash->flags = LIGHT_Q3A_DEFAULT;
splash->type = EMIT_POINT;
splash->photons = light->photons * si->backsplashFraction;
splash->fade = 1.0f;
splash->si = si;
VectorMA( light->origin, si->backsplashDistance, normal, splash->origin );
VectorCopy( si->color, splash->color );
splash->falloffTolerance = falloffTolerance;
splash->style = noStyles ? LS_NORMAL : light->style;
/* add to counts */
numPointLights++;
}
}
else
{
/* handle bounced light (radiosity) a little differently */
value = RADIOSITY_VALUE * si->bounceScale * 0.375f;
light->photons = value * area * bounceScale;
light->add = value * formFactorValueScale * bounceScale;
VectorCopy( color, light->color );
VectorScale( light->color, light->add, light->emitColor );
light->style = noStyles ? LS_NORMAL : style;
if ( light->style < LS_NORMAL || light->style >= LS_NONE ) {
light->style = LS_NORMAL;
}
/* set origin */
WindingCenter( w, light->origin );
/* nudge it off the plane a bit */
VectorCopy( normal, light->normal );
VectorMA( light->origin, 1.0f, light->normal, light->origin );
light->dist = DotProduct( light->origin, normal );
}
/* emit light from both sides? */
if ( si->compileFlags & C_FOG || si->twoSided ) {
light->flags |= LIGHT_TWOSIDED;
}
//% Sys_Printf( "\nAL: C: (%6f, %6f, %6f) [%6f] N: (%6f, %6f, %6f) %s\n",
//% light->color[ 0 ], light->color[ 1 ], light->color[ 2 ], light->add,
//% light->normal[ 0 ], light->normal[ 1 ], light->normal[ 2 ],
//% light->si->shader );
}
/*
RadLightForTriangles()
creates unbounced diffuse lights for triangle soup (misc_models, etc)
*/
void RadLightForTriangles( int num, int lightmapNum, rawLightmap_t *lm, shaderInfo_t *si, float scale, float subdivide, clipWork_t *cw ){
int i, j, k, v;
bspDrawSurface_t *ds;
surfaceInfo_t *info;
float *radVertexLuxel;
radWinding_t rw;
/* get surface */
ds = &bspDrawSurfaces[ num ];
info = &surfaceInfos[ num ];
/* each triangle is a potential emitter */
rw.numVerts = 3;
for ( i = 0; i < ds->numIndexes; i += 3 )
{
/* copy each vert */
for ( j = 0; j < 3; j++ )
{
/* get vertex index and rad vertex luxel */
v = ds->firstVert + bspDrawIndexes[ ds->firstIndex + i + j ];
/* get most everything */
memcpy( &rw.verts[ j ], &yDrawVerts[ v ], sizeof( bspDrawVert_t ) );
/* fix colors */
for ( k = 0; k < MAX_LIGHTMAPS; k++ )
{
radVertexLuxel = RAD_VERTEX_LUXEL( k, ds->firstVert + bspDrawIndexes[ ds->firstIndex + i + j ] );
VectorCopy( radVertexLuxel, rw.verts[ j ].color[ k ] );
rw.verts[ j ].color[ k ][ 3 ] = yDrawVerts[ v ].color[ k ][ 3 ];
}
}
/* subdivide into area lights */
RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw );
}
}
/*
RadLightForPatch()
creates unbounced diffuse lights for patches
*/
#define PLANAR_EPSILON 0.1f
void RadLightForPatch( int num, int lightmapNum, rawLightmap_t *lm, shaderInfo_t *si, float scale, float subdivide, clipWork_t *cw ){
int i, x, y, v, t, pw[ 5 ], r;
bspDrawSurface_t *ds;
surfaceInfo_t *info;
bspDrawVert_t *bogus;
bspDrawVert_t *dv[ 4 ];
mesh_t src, *subdivided, *mesh;
float *radVertexLuxel;
float dist;
vec4_t plane;
qboolean planar;
radWinding_t rw;
/* get surface */
ds = &bspDrawSurfaces[ num ];
info = &surfaceInfos[ num ];
/* construct a bogus vert list with color index stuffed into color[ 0 ] */
bogus = safe_malloc( ds->numVerts * sizeof( bspDrawVert_t ) );
memcpy( bogus, &yDrawVerts[ ds->firstVert ], ds->numVerts * sizeof( bspDrawVert_t ) );
for ( i = 0; i < ds->numVerts; i++ )
bogus[ i ].color[ 0 ][ 0 ] = i;
/* build a subdivided mesh identical to shadow facets for this patch */
/* this MUST MATCH FacetsForPatch() identically! */
src.width = ds->patchWidth;
src.height = ds->patchHeight;
src.verts = bogus;
//% subdivided = SubdivideMesh( src, 8, 512 );
subdivided = SubdivideMesh2( src, info->patchIterations );
PutMeshOnCurve( *subdivided );
//% MakeMeshNormals( *subdivided );
mesh = RemoveLinearMeshColumnsRows( subdivided );
FreeMesh( subdivided );
free( bogus );
/* FIXME: build interpolation table into color[ 1 ] */
/* fix up color indexes */
for ( i = 0; i < ( mesh->width * mesh->height ); i++ )
{
dv[ 0 ] = &mesh->verts[ i ];
if ( dv[ 0 ]->color[ 0 ][ 0 ] >= ds->numVerts ) {
dv[ 0 ]->color[ 0 ][ 0 ] = ds->numVerts - 1;
}
}
/* iterate through the mesh quads */
for ( y = 0; y < ( mesh->height - 1 ); y++ )
{
for ( x = 0; x < ( mesh->width - 1 ); x++ )
{
/* set indexes */
pw[ 0 ] = x + ( y * mesh->width );
pw[ 1 ] = x + ( ( y + 1 ) * mesh->width );
pw[ 2 ] = x + 1 + ( ( y + 1 ) * mesh->width );
pw[ 3 ] = x + 1 + ( y * mesh->width );
pw[ 4 ] = x + ( y * mesh->width ); /* same as pw[ 0 ] */
/* set radix */
r = ( x + y ) & 1;
/* get drawverts */
dv[ 0 ] = &mesh->verts[ pw[ r + 0 ] ];
dv[ 1 ] = &mesh->verts[ pw[ r + 1 ] ];
dv[ 2 ] = &mesh->verts[ pw[ r + 2 ] ];
dv[ 3 ] = &mesh->verts[ pw[ r + 3 ] ];
/* planar? */
planar = PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz );
if ( planar ) {
dist = DotProduct( dv[ 1 ]->xyz, plane ) - plane[ 3 ];
if ( fabs( dist ) > PLANAR_EPSILON ) {
planar = qfalse;
}
}
/* generate a quad */
if ( planar ) {
rw.numVerts = 4;
for ( v = 0; v < 4; v++ )
{
/* get most everything */
memcpy( &rw.verts[ v ], dv[ v ], sizeof( bspDrawVert_t ) );
/* fix colors */
for ( i = 0; i < MAX_LIGHTMAPS; i++ )
{
radVertexLuxel = RAD_VERTEX_LUXEL( i, ds->firstVert + dv[ v ]->color[ 0 ][ 0 ] );
VectorCopy( radVertexLuxel, rw.verts[ v ].color[ i ] );
rw.verts[ v ].color[ i ][ 3 ] = dv[ v ]->color[ i ][ 3 ];
}
}
/* subdivide into area lights */
RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw );
}
/* generate 2 tris */
else
{
rw.numVerts = 3;
for ( t = 0; t < 2; t++ )
{
for ( v = 0; v < 3 + t; v++ )
{
/* get "other" triangle (stupid hacky logic, but whatevah) */
if ( v == 1 && t == 1 ) {
v++;
}
/* get most everything */
memcpy( &rw.verts[ v ], dv[ v ], sizeof( bspDrawVert_t ) );
/* fix colors */
for ( i = 0; i < MAX_LIGHTMAPS; i++ )
{
radVertexLuxel = RAD_VERTEX_LUXEL( i, ds->firstVert + dv[ v ]->color[ 0 ][ 0 ] );
VectorCopy( radVertexLuxel, rw.verts[ v ].color[ i ] );
rw.verts[ v ].color[ i ][ 3 ] = dv[ v ]->color[ i ][ 3 ];
}
}
/* subdivide into area lights */
RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw );
}
}
}
}
/* free the mesh */
FreeMesh( mesh );
}
/*
RadLight()
creates unbounced diffuse lights for a given surface
*/
void RadLight( int num ){
int lightmapNum;
float scale, subdivide;
int contentFlags, surfaceFlags, compileFlags;
bspDrawSurface_t *ds;
surfaceInfo_t *info;
rawLightmap_t *lm;
shaderInfo_t *si;
clipWork_t cw;
/* get drawsurface, lightmap, and shader info */
ds = &bspDrawSurfaces[ num ];
info = &surfaceInfos[ num ];
lm = info->lm;
si = info->si;
scale = si->bounceScale;
/* find nodraw bit */
contentFlags = surfaceFlags = compileFlags = 0;
ApplySurfaceParm( "nodraw", &contentFlags, &surfaceFlags, &compileFlags );
/* early outs? */
if ( scale <= 0.0f || ( si->compileFlags & C_SKY ) || si->autosprite ||
( bspShaders[ ds->shaderNum ].contentFlags & contentFlags ) || ( bspShaders[ ds->shaderNum ].surfaceFlags & surfaceFlags ) ||
( si->compileFlags & compileFlags ) ) {
return;
}
/* determine how much we need to chop up the surface */
if ( si->lightSubdivide ) {
subdivide = si->lightSubdivide;
}
else{
subdivide = diffuseSubdivide;
}
/* inc counts */
numDiffuseSurfaces++;
/* iterate through styles (this could be more efficient, yes) */
for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
{
/* switch on type */
if ( ds->lightmapStyles[ lightmapNum ] != LS_NONE && ds->lightmapStyles[ lightmapNum ] != LS_UNUSED ) {
switch ( ds->surfaceType )
{
case MST_PLANAR:
case MST_TRIANGLE_SOUP:
RadLightForTriangles( num, lightmapNum, lm, si, scale, subdivide, &cw );
break;
case MST_PATCH:
RadLightForPatch( num, lightmapNum, lm, si, scale, subdivide, &cw );
break;
default:
break;
}
}
}
}
/*
RadCreateDiffuseLights()
creates lights for unbounced light on surfaces in the bsp
*/
int iterations = 0;
void RadCreateDiffuseLights( void ){
/* startup */
Sys_FPrintf( SYS_VRB, "--- RadCreateDiffuseLights ---\n" );
numDiffuseSurfaces = 0;
numDiffuseLights = 0;
numBrushDiffuseLights = 0;
numTriangleDiffuseLights = 0;
numPatchDiffuseLights = 0;
numAreaLights = 0;
/* hit every surface (threaded) */
RunThreadsOnIndividual( numBSPDrawSurfaces, qtrue, RadLight );
/* dump the lights generated to a file */
if ( dump ) {
char dumpName[ 1024 ], ext[ 64 ];
FILE *file;
light_t *light;
strcpy( dumpName, source );
StripExtension( dumpName );
sprintf( ext, "_bounce_%03d.map", iterations );
strcat( dumpName, ext );
file = fopen( dumpName, "wb" );
Sys_Printf( "Writing %s...\n", dumpName );
if ( file ) {
for ( light = lights; light; light = light->next )
{
fprintf( file,
"{\n"
"\"classname\" \"light\"\n"
"\"light\" \"%d\"\n"
"\"origin\" \"%.0f %.0f %.0f\"\n"
"\"_color\" \"%.3f %.3f %.3f\"\n"
"}\n",
(int) light->add,
light->origin[ 0 ],
light->origin[ 1 ],
light->origin[ 2 ],
light->color[ 0 ],
light->color[ 1 ],
light->color[ 2 ] );
}
fclose( file );
}
}
/* increment */
iterations++;
/* print counts */
Sys_Printf( "%8d diffuse surfaces\n", numDiffuseSurfaces );
Sys_FPrintf( SYS_VRB, "%8d total diffuse lights\n", numDiffuseLights );
Sys_FPrintf( SYS_VRB, "%8d brush diffuse lights\n", numBrushDiffuseLights );
Sys_FPrintf( SYS_VRB, "%8d patch diffuse lights\n", numPatchDiffuseLights );
Sys_FPrintf( SYS_VRB, "%8d triangle diffuse lights\n", numTriangleDiffuseLights );
}