mirror of
https://github.com/UberGames/GtkRadiant.git
synced 2024-11-29 23:22:23 +00:00
ab3a99dbbe
git-svn-id: svn://svn.icculus.org/gtkradiant/GtkRadiant/branches/ZeroRadiant.ab@184 8a3a26a2-13c4-0310-b231-cf6edde360e5
3129 lines
82 KiB
C
3129 lines
82 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_YDNAR_C
|
|
|
|
|
|
|
|
/* dependencies */
|
|
#include "q3map2.h"
|
|
|
|
|
|
|
|
|
|
/*
|
|
ColorToBytes()
|
|
ydnar: moved to here 2001-02-04
|
|
*/
|
|
|
|
void ColorToBytes( const float *color, byte *colorBytes, float scale )
|
|
{
|
|
float max;
|
|
vec3_t sample;
|
|
|
|
|
|
/* ydnar: scaling necessary for simulating r_overbrightBits on external lightmaps */
|
|
if( scale <= 0.0f )
|
|
scale = 1.0f;
|
|
|
|
/* make a local copy */
|
|
VectorScale( color, scale, sample );
|
|
|
|
/* handle negative light */
|
|
if( sample[ 0 ] < 0.0f )
|
|
sample[ 0 ] = 0.0f;
|
|
if( sample[ 1 ] < 0.0f )
|
|
sample[ 1 ] = 0.0f;
|
|
if( sample[ 2 ] < 0.0f )
|
|
sample[ 2 ] = 0.0f;
|
|
|
|
/* clamp with color normalization */
|
|
max = sample[ 0 ];
|
|
if( sample[ 1 ] > max )
|
|
max = sample[ 1 ];
|
|
if( sample[ 2 ] > max )
|
|
max = sample[ 2 ];
|
|
if( max > 255.0f )
|
|
VectorScale( sample, (255.0f / max), sample );
|
|
|
|
/* store it off */
|
|
colorBytes[ 0 ] = sample[ 0 ];
|
|
colorBytes[ 1 ] = sample[ 1 ];
|
|
colorBytes[ 2 ] = sample[ 2 ];
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------------------
|
|
|
|
this section deals with phong shading (normal interpolation across brush faces)
|
|
|
|
------------------------------------------------------------------------------- */
|
|
|
|
/*
|
|
SmoothNormals()
|
|
smooths together coincident vertex normals across the bsp
|
|
*/
|
|
|
|
#define MAX_SAMPLES 256
|
|
#define THETA_EPSILON 0.000001
|
|
#define EQUAL_NORMAL_EPSILON 0.01
|
|
|
|
void SmoothNormals( void )
|
|
{
|
|
int i, j, k, f, cs, numVerts, numVotes, fOld, start;
|
|
float shadeAngle, defaultShadeAngle, maxShadeAngle, dot, testAngle;
|
|
bspDrawSurface_t *ds;
|
|
shaderInfo_t *si;
|
|
float *shadeAngles;
|
|
byte *smoothed;
|
|
vec3_t average, diff;
|
|
int indexes[ MAX_SAMPLES ];
|
|
vec3_t votes[ MAX_SAMPLES ];
|
|
|
|
|
|
/* allocate shade angle table */
|
|
shadeAngles = safe_malloc( numBSPDrawVerts * sizeof( float ) );
|
|
memset( shadeAngles, 0, numBSPDrawVerts * sizeof( float ) );
|
|
|
|
/* allocate smoothed table */
|
|
cs = (numBSPDrawVerts / 8) + 1;
|
|
smoothed = safe_malloc( cs );
|
|
memset( smoothed, 0, cs );
|
|
|
|
/* set default shade angle */
|
|
defaultShadeAngle = DEG2RAD( shadeAngleDegrees );
|
|
maxShadeAngle = 0;
|
|
|
|
/* run through every surface and flag verts belonging to non-lightmapped surfaces
|
|
and set per-vertex smoothing angle */
|
|
for( i = 0; i < numBSPDrawSurfaces; i++ )
|
|
{
|
|
/* get drawsurf */
|
|
ds = &bspDrawSurfaces[ i ];
|
|
|
|
/* get shader for shade angle */
|
|
si = surfaceInfos[ i ].si;
|
|
if( si->shadeAngleDegrees )
|
|
shadeAngle = DEG2RAD( si->shadeAngleDegrees );
|
|
else
|
|
shadeAngle = defaultShadeAngle;
|
|
if( shadeAngle > maxShadeAngle )
|
|
maxShadeAngle = shadeAngle;
|
|
|
|
/* flag its verts */
|
|
for( j = 0; j < ds->numVerts; j++ )
|
|
{
|
|
f = ds->firstVert + j;
|
|
shadeAngles[ f ] = shadeAngle;
|
|
if( ds->surfaceType == MST_TRIANGLE_SOUP )
|
|
smoothed[ f >> 3 ] |= (1 << (f & 7));
|
|
}
|
|
|
|
/* ydnar: optional force-to-trisoup */
|
|
if( trisoup && ds->surfaceType == MST_PLANAR )
|
|
{
|
|
ds->surfaceType = MST_TRIANGLE_SOUP;
|
|
ds->lightmapNum[ 0 ] = -3;
|
|
}
|
|
}
|
|
|
|
/* bail if no surfaces have a shade angle */
|
|
if( maxShadeAngle == 0 )
|
|
{
|
|
free( shadeAngles );
|
|
free( smoothed );
|
|
return;
|
|
}
|
|
|
|
/* init pacifier */
|
|
fOld = -1;
|
|
start = I_FloatTime();
|
|
|
|
/* go through the list of vertexes */
|
|
for( i = 0; i < numBSPDrawVerts; i++ )
|
|
{
|
|
/* print pacifier */
|
|
f = 10 * i / numBSPDrawVerts;
|
|
if( f != fOld )
|
|
{
|
|
fOld = f;
|
|
Sys_Printf( "%i...", f );
|
|
}
|
|
|
|
/* already smoothed? */
|
|
if( smoothed[ i >> 3 ] & (1 << (i & 7)) )
|
|
continue;
|
|
|
|
/* clear */
|
|
VectorClear( average );
|
|
numVerts = 0;
|
|
numVotes = 0;
|
|
|
|
/* build a table of coincident vertexes */
|
|
for( j = i; j < numBSPDrawVerts && numVerts < MAX_SAMPLES; j++ )
|
|
{
|
|
/* already smoothed? */
|
|
if( smoothed[ j >> 3 ] & (1 << (j & 7)) )
|
|
continue;
|
|
|
|
/* test vertexes */
|
|
if( VectorCompare( yDrawVerts[ i ].xyz, yDrawVerts[ j ].xyz ) == qfalse )
|
|
continue;
|
|
|
|
/* use smallest shade angle */
|
|
shadeAngle = (shadeAngles[ i ] < shadeAngles[ j ] ? shadeAngles[ i ] : shadeAngles[ j ]);
|
|
|
|
/* check shade angle */
|
|
dot = DotProduct( bspDrawVerts[ i ].normal, bspDrawVerts[ j ].normal );
|
|
if( dot > 1.0 )
|
|
dot = 1.0;
|
|
else if( dot < -1.0 )
|
|
dot = -1.0;
|
|
testAngle = acos( dot ) + THETA_EPSILON;
|
|
if( testAngle >= shadeAngle )
|
|
{
|
|
//Sys_Printf( "F(%3.3f >= %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) );
|
|
continue;
|
|
}
|
|
//Sys_Printf( "P(%3.3f < %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) );
|
|
|
|
/* add to the list */
|
|
indexes[ numVerts++ ] = j;
|
|
|
|
/* flag vertex */
|
|
smoothed[ j >> 3 ] |= (1 << (j & 7));
|
|
|
|
/* see if this normal has already been voted */
|
|
for( k = 0; k < numVotes; k++ )
|
|
{
|
|
VectorSubtract( bspDrawVerts[ j ].normal, votes[ k ], diff );
|
|
if( fabs( diff[ 0 ] ) < EQUAL_NORMAL_EPSILON &&
|
|
fabs( diff[ 1 ] ) < EQUAL_NORMAL_EPSILON &&
|
|
fabs( diff[ 2 ] ) < EQUAL_NORMAL_EPSILON )
|
|
break;
|
|
}
|
|
|
|
/* add a new vote? */
|
|
if( k == numVotes && numVotes < MAX_SAMPLES )
|
|
{
|
|
VectorAdd( average, bspDrawVerts[ j ].normal, average );
|
|
VectorCopy( bspDrawVerts[ j ].normal, votes[ numVotes ] );
|
|
numVotes++;
|
|
}
|
|
}
|
|
|
|
/* don't average for less than 2 verts */
|
|
if( numVerts < 2 )
|
|
continue;
|
|
|
|
/* average normal */
|
|
if( VectorNormalize( average, average ) > 0 )
|
|
{
|
|
/* smooth */
|
|
for( j = 0; j < numVerts; j++ )
|
|
VectorCopy( average, yDrawVerts[ indexes[ j ] ].normal );
|
|
}
|
|
}
|
|
|
|
/* free the tables */
|
|
free( shadeAngles );
|
|
free( smoothed );
|
|
|
|
/* print time */
|
|
Sys_Printf( " (%i)\n", (int) (I_FloatTime() - start) );
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------------------
|
|
|
|
this section deals with phong shaded lightmap tracing
|
|
|
|
------------------------------------------------------------------------------- */
|
|
|
|
/* 9th rewrite (recursive subdivision of a lightmap triangle) */
|
|
|
|
/*
|
|
CalcTangentVectors()
|
|
calculates the st tangent vectors for normalmapping
|
|
*/
|
|
|
|
static qboolean CalcTangentVectors( int numVerts, bspDrawVert_t **dv, vec3_t *stv, vec3_t *ttv )
|
|
{
|
|
int i;
|
|
float bb, s, t;
|
|
vec3_t bary;
|
|
|
|
|
|
/* calculate barycentric basis for the triangle */
|
|
bb = (dv[ 1 ]->st[ 0 ] - dv[ 0 ]->st[ 0 ]) * (dv[ 2 ]->st[ 1 ] - dv[ 0 ]->st[ 1 ]) - (dv[ 2 ]->st[ 0 ] - dv[ 0 ]->st[ 0 ]) * (dv[ 1 ]->st[ 1 ] - dv[ 0 ]->st[ 1 ]);
|
|
if( fabs( bb ) < 0.00000001f )
|
|
return qfalse;
|
|
|
|
/* do each vertex */
|
|
for( i = 0; i < numVerts; i++ )
|
|
{
|
|
/* calculate s tangent vector */
|
|
s = dv[ i ]->st[ 0 ] + 10.0f;
|
|
t = dv[ i ]->st[ 1 ];
|
|
bary[ 0 ] = ((dv[ 1 ]->st[ 0 ] - s) * (dv[ 2 ]->st[ 1 ] - t) - (dv[ 2 ]->st[ 0 ] - s) * (dv[ 1 ]->st[ 1 ] - t)) / bb;
|
|
bary[ 1 ] = ((dv[ 2 ]->st[ 0 ] - s) * (dv[ 0 ]->st[ 1 ] - t) - (dv[ 0 ]->st[ 0 ] - s) * (dv[ 2 ]->st[ 1 ] - t)) / bb;
|
|
bary[ 2 ] = ((dv[ 0 ]->st[ 0 ] - s) * (dv[ 1 ]->st[ 1 ] - t) - (dv[ 1 ]->st[ 0 ] - s) * (dv[ 0 ]->st[ 1 ] - t)) / bb;
|
|
|
|
stv[ i ][ 0 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 0 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 0 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 0 ];
|
|
stv[ i ][ 1 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 1 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 1 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 1 ];
|
|
stv[ i ][ 2 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 2 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 2 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 2 ];
|
|
|
|
VectorSubtract( stv[ i ], dv[ i ]->xyz, stv[ i ] );
|
|
VectorNormalize( stv[ i ], stv[ i ] );
|
|
|
|
/* calculate t tangent vector */
|
|
s = dv[ i ]->st[ 0 ];
|
|
t = dv[ i ]->st[ 1 ] + 10.0f;
|
|
bary[ 0 ] = ((dv[ 1 ]->st[ 0 ] - s) * (dv[ 2 ]->st[ 1 ] - t) - (dv[ 2 ]->st[ 0 ] - s) * (dv[ 1 ]->st[ 1 ] - t)) / bb;
|
|
bary[ 1 ] = ((dv[ 2 ]->st[ 0 ] - s) * (dv[ 0 ]->st[ 1 ] - t) - (dv[ 0 ]->st[ 0 ] - s) * (dv[ 2 ]->st[ 1 ] - t)) / bb;
|
|
bary[ 2 ] = ((dv[ 0 ]->st[ 0 ] - s) * (dv[ 1 ]->st[ 1 ] - t) - (dv[ 1 ]->st[ 0 ] - s) * (dv[ 0 ]->st[ 1 ] - t)) / bb;
|
|
|
|
ttv[ i ][ 0 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 0 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 0 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 0 ];
|
|
ttv[ i ][ 1 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 1 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 1 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 1 ];
|
|
ttv[ i ][ 2 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 2 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 2 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 2 ];
|
|
|
|
VectorSubtract( ttv[ i ], dv[ i ]->xyz, ttv[ i ] );
|
|
VectorNormalize( ttv[ i ], ttv[ i ] );
|
|
|
|
/* debug code */
|
|
//% Sys_FPrintf( SYS_VRB, "%d S: (%f %f %f) T: (%f %f %f)\n", i,
|
|
//% stv[ i ][ 0 ], stv[ i ][ 1 ], stv[ i ][ 2 ], ttv[ i ][ 0 ], ttv[ i ][ 1 ], ttv[ i ][ 2 ] );
|
|
}
|
|
|
|
/* return to caller */
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
PerturbNormal()
|
|
perterbs the normal by the shader's normalmap in tangent space
|
|
*/
|
|
|
|
static void PerturbNormal( bspDrawVert_t *dv, shaderInfo_t *si, vec3_t pNormal, vec3_t stv[ 3 ], vec3_t ttv[ 3 ] )
|
|
{
|
|
int i;
|
|
vec4_t bump;
|
|
|
|
|
|
/* passthrough */
|
|
VectorCopy( dv->normal, pNormal );
|
|
|
|
/* sample normalmap */
|
|
if( RadSampleImage( si->normalImage->pixels, si->normalImage->width, si->normalImage->height, dv->st, bump ) == qfalse )
|
|
return;
|
|
|
|
/* remap sampled normal from [0,255] to [-1,-1] */
|
|
for( i = 0; i < 3; i++ )
|
|
bump[ i ] = (bump[ i ] - 127.0f) * (1.0f / 127.5f);
|
|
|
|
/* scale tangent vectors and add to original normal */
|
|
VectorMA( dv->normal, bump[ 0 ], stv[ 0 ], pNormal );
|
|
VectorMA( pNormal, bump[ 1 ], ttv[ 0 ], pNormal );
|
|
VectorMA( pNormal, bump[ 2 ], dv->normal, pNormal );
|
|
|
|
/* renormalize and return */
|
|
VectorNormalize( pNormal, pNormal );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
MapSingleLuxel()
|
|
maps a luxel for triangle bv at
|
|
*/
|
|
|
|
#define NUDGE 0.5f
|
|
#define BOGUS_NUDGE -99999.0f
|
|
|
|
static int MapSingleLuxel( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv, vec4_t plane, float pass, vec3_t stv[ 3 ], vec3_t ttv[ 3 ] )
|
|
{
|
|
int i, x, y, numClusters, *clusters, pointCluster, *cluster;
|
|
float *luxel, *origin, *normal, d, lightmapSampleOffset;
|
|
shaderInfo_t *si;
|
|
vec3_t pNormal;
|
|
vec3_t vecs[ 3 ];
|
|
vec3_t nudged;
|
|
float *nudge;
|
|
static float nudges[][ 2 ] =
|
|
{
|
|
//%{ 0, 0 }, /* try center first */
|
|
{ -NUDGE, 0 }, /* left */
|
|
{ NUDGE, 0 }, /* right */
|
|
{ 0, NUDGE }, /* up */
|
|
{ 0, -NUDGE }, /* down */
|
|
{ -NUDGE, NUDGE }, /* left/up */
|
|
{ NUDGE, -NUDGE }, /* right/down */
|
|
{ NUDGE, NUDGE }, /* right/up */
|
|
{ -NUDGE, -NUDGE }, /* left/down */
|
|
{ BOGUS_NUDGE, BOGUS_NUDGE }
|
|
};
|
|
|
|
|
|
/* find luxel xy coords (fixme: subtract 0.5?) */
|
|
x = dv->lightmap[ 0 ][ 0 ];
|
|
y = dv->lightmap[ 0 ][ 1 ];
|
|
if( x < 0 )
|
|
x = 0;
|
|
else if( x >= lm->sw )
|
|
x = lm->sw - 1;
|
|
if( y < 0 )
|
|
y = 0;
|
|
else if( y >= lm->sh )
|
|
y = lm->sh - 1;
|
|
|
|
/* set shader and cluster list */
|
|
if( info != NULL )
|
|
{
|
|
si = info->si;
|
|
numClusters = info->numSurfaceClusters;
|
|
clusters = &surfaceClusters[ info->firstSurfaceCluster ];
|
|
}
|
|
else
|
|
{
|
|
si = NULL;
|
|
numClusters = 0;
|
|
clusters = NULL;
|
|
}
|
|
|
|
/* get luxel, origin, cluster, and normal */
|
|
luxel = SUPER_LUXEL( 0, x, y );
|
|
origin = SUPER_ORIGIN( x, y );
|
|
normal = SUPER_NORMAL( x, y );
|
|
cluster = SUPER_CLUSTER( x, y );
|
|
|
|
/* don't attempt to remap occluded luxels for planar surfaces */
|
|
if( (*cluster) == CLUSTER_OCCLUDED && lm->plane != NULL )
|
|
return (*cluster);
|
|
|
|
/* only average the normal for premapped luxels */
|
|
else if( (*cluster) >= 0 )
|
|
{
|
|
/* do bumpmap calculations */
|
|
if( stv != NULL )
|
|
PerturbNormal( dv, si, pNormal, stv, ttv );
|
|
else
|
|
VectorCopy( dv->normal, pNormal );
|
|
|
|
/* add the additional normal data */
|
|
VectorAdd( normal, pNormal, normal );
|
|
luxel[ 3 ] += 1.0f;
|
|
return (*cluster);
|
|
}
|
|
|
|
/* otherwise, unmapped luxels (*cluster == CLUSTER_UNMAPPED) will have their full attributes calculated */
|
|
|
|
/* get origin */
|
|
|
|
/* axial lightmap projection */
|
|
if( lm->vecs != NULL )
|
|
{
|
|
/* calculate an origin for the sample from the lightmap vectors */
|
|
VectorCopy( lm->origin, origin );
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
/* add unless it's the axis, which is taken care of later */
|
|
if( i == lm->axisNum )
|
|
continue;
|
|
origin[ i ] += (x * lm->vecs[ 0 ][ i ]) + (y * lm->vecs[ 1 ][ i ]);
|
|
}
|
|
|
|
/* project the origin onto the plane */
|
|
d = DotProduct( origin, plane ) - plane[ 3 ];
|
|
d /= plane[ lm->axisNum ];
|
|
origin[ lm->axisNum ] -= d;
|
|
}
|
|
|
|
/* non axial lightmap projection (explicit xyz) */
|
|
else
|
|
VectorCopy( dv->xyz, origin );
|
|
|
|
/* planar surfaces have precalculated lightmap vectors for nudging */
|
|
if( lm->plane != NULL )
|
|
{
|
|
VectorCopy( lm->vecs[ 0 ], vecs[ 0 ] );
|
|
VectorCopy( lm->vecs[ 1 ], vecs[ 1 ] );
|
|
VectorCopy( lm->plane, vecs[ 2 ] );
|
|
}
|
|
|
|
/* non-planar surfaces must calculate them */
|
|
else
|
|
{
|
|
if( plane != NULL )
|
|
VectorCopy( plane, vecs[ 2 ] );
|
|
else
|
|
VectorCopy( dv->normal, vecs[ 2 ] );
|
|
MakeNormalVectors( vecs[ 2 ], vecs[ 0 ], vecs[ 1 ] );
|
|
}
|
|
|
|
/* push the origin off the surface a bit */
|
|
if( si != NULL )
|
|
lightmapSampleOffset = si->lightmapSampleOffset;
|
|
else
|
|
lightmapSampleOffset = DEFAULT_LIGHTMAP_SAMPLE_OFFSET;
|
|
if( lm->axisNum < 0 )
|
|
VectorMA( origin, lightmapSampleOffset, vecs[ 2 ], origin );
|
|
else if( vecs[ 2 ][ lm->axisNum ] < 0.0f )
|
|
origin[ lm->axisNum ] -= lightmapSampleOffset;
|
|
else
|
|
origin[ lm->axisNum ] += lightmapSampleOffset;
|
|
|
|
/* get cluster */
|
|
pointCluster = ClusterForPointExtFilter( origin, LUXEL_EPSILON, numClusters, clusters );
|
|
|
|
/* another retarded hack, storing nudge count in luxel[ 1 ] */
|
|
luxel[ 1 ] = 0.0f;
|
|
|
|
/* point in solid? */
|
|
if( pointCluster < 0 )
|
|
{
|
|
/* nudge the the location around */
|
|
nudge = nudges[ 0 ];
|
|
while( nudge[ 0 ] > BOGUS_NUDGE && pointCluster < 0 )
|
|
{
|
|
/* nudge the vector around a bit */
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
/* set nudged point*/
|
|
nudged[ i ] = origin[ i ] + (nudge[ 0 ] * vecs[ 0 ][ i ]) + (nudge[ 1 ] * vecs[ 1 ][ i ]);
|
|
}
|
|
nudge += 2;
|
|
|
|
/* get pvs cluster */
|
|
pointCluster = ClusterForPointExtFilter( nudged, LUXEL_EPSILON, numClusters, clusters ); //% + 0.625 );
|
|
if( pointCluster >= 0 )
|
|
VectorCopy( nudged, origin );
|
|
luxel[ 1 ] += 1.0f;
|
|
}
|
|
}
|
|
|
|
/* as a last resort, if still in solid, try drawvert origin offset by normal */
|
|
if( pointCluster < 0 && si != NULL )
|
|
{
|
|
VectorMA( dv->xyz, lightmapSampleOffset, dv->normal, nudged );
|
|
pointCluster = ClusterForPointExtFilter( nudged, LUXEL_EPSILON, numClusters, clusters );
|
|
if( pointCluster >= 0 )
|
|
VectorCopy( nudged, origin );
|
|
luxel[ 1 ] += 1.0f;
|
|
}
|
|
|
|
/* valid? */
|
|
if( pointCluster < 0 )
|
|
{
|
|
(*cluster) = CLUSTER_OCCLUDED;
|
|
VectorClear( origin );
|
|
VectorClear( normal );
|
|
numLuxelsOccluded++;
|
|
return (*cluster);
|
|
}
|
|
|
|
/* debug code */
|
|
//% Sys_Printf( "%f %f %f\n", origin[ 0 ], origin[ 1 ], origin[ 2 ] );
|
|
|
|
/* do bumpmap calculations */
|
|
if( stv )
|
|
PerturbNormal( dv, si, pNormal, stv, ttv );
|
|
else
|
|
VectorCopy( dv->normal, pNormal );
|
|
|
|
/* store the cluster and normal */
|
|
(*cluster) = pointCluster;
|
|
VectorCopy( pNormal, normal );
|
|
|
|
/* store explicit mapping pass and implicit mapping pass */
|
|
luxel[ 0 ] = pass;
|
|
luxel[ 3 ] = 1.0f;
|
|
|
|
/* add to count */
|
|
numLuxelsMapped++;
|
|
|
|
/* return ok */
|
|
return (*cluster);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
MapTriangle_r()
|
|
recursively subdivides a triangle until its edges are shorter
|
|
than the distance between two luxels (thanks jc :)
|
|
*/
|
|
|
|
static void MapTriangle_r( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 3 ], vec4_t plane, vec3_t stv[ 3 ], vec3_t ttv[ 3 ] )
|
|
{
|
|
bspDrawVert_t mid, *dv2[ 3 ];
|
|
int max;
|
|
|
|
|
|
/* map the vertexes */
|
|
#if 0
|
|
MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv );
|
|
MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv );
|
|
MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv );
|
|
#endif
|
|
|
|
/* subdivide calc */
|
|
{
|
|
int i;
|
|
float *a, *b, dx, dy, dist, maxDist;
|
|
|
|
|
|
/* find the longest edge and split it */
|
|
max = -1;
|
|
maxDist = 0;
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
/* get verts */
|
|
a = dv[ i ]->lightmap[ 0 ];
|
|
b = dv[ (i + 1) % 3 ]->lightmap[ 0 ];
|
|
|
|
/* get dists */
|
|
dx = a[ 0 ] - b[ 0 ];
|
|
dy = a[ 1 ] - b[ 1 ];
|
|
dist = (dx * dx) + (dy * dy); //% sqrt( (dx * dx) + (dy * dy) );
|
|
|
|
/* longer? */
|
|
if( dist > maxDist )
|
|
{
|
|
maxDist = dist;
|
|
max = i;
|
|
}
|
|
}
|
|
|
|
/* try to early out */
|
|
if( max < 0 || maxDist <= subdivideThreshold ) /* ydnar: was i < 0 instead of max < 0 (?) */
|
|
return;
|
|
}
|
|
|
|
/* split the longest edge and map it */
|
|
LerpDrawVert( dv[ max ], dv[ (max + 1) % 3 ], &mid );
|
|
MapSingleLuxel( lm, info, &mid, plane, 1, stv, ttv );
|
|
|
|
/* push the point up a little bit to account for fp creep (fixme: revisit this) */
|
|
//% VectorMA( mid.xyz, 2.0f, mid.normal, mid.xyz );
|
|
|
|
/* recurse to first triangle */
|
|
VectorCopy( dv, dv2 );
|
|
dv2[ max ] = ∣
|
|
MapTriangle_r( lm, info, dv2, plane, stv, ttv );
|
|
|
|
/* recurse to second triangle */
|
|
VectorCopy( dv, dv2 );
|
|
dv2[ (max + 1) % 3 ] = ∣
|
|
MapTriangle_r( lm, info, dv2, plane, stv, ttv );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
MapTriangle()
|
|
seed function for MapTriangle_r()
|
|
requires a cw ordered triangle
|
|
*/
|
|
|
|
static qboolean MapTriangle( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 3 ], qboolean mapNonAxial )
|
|
{
|
|
int i;
|
|
vec4_t plane;
|
|
vec3_t *stv, *ttv, stvStatic[ 3 ], ttvStatic[ 3 ];
|
|
|
|
|
|
/* get plane if possible */
|
|
if( lm->plane != NULL )
|
|
{
|
|
VectorCopy( lm->plane, plane );
|
|
plane[ 3 ] = lm->plane[ 3 ];
|
|
}
|
|
|
|
/* otherwise make one from the points */
|
|
else if( PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) == qfalse )
|
|
return qfalse;
|
|
|
|
/* check to see if we need to calculate texture->world tangent vectors */
|
|
if( info->si->normalImage != NULL && CalcTangentVectors( 3, dv, stvStatic, ttvStatic ) )
|
|
{
|
|
stv = stvStatic;
|
|
ttv = ttvStatic;
|
|
}
|
|
else
|
|
{
|
|
stv = NULL;
|
|
ttv = NULL;
|
|
}
|
|
|
|
/* map the vertexes */
|
|
MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv );
|
|
MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv );
|
|
MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv );
|
|
|
|
/* 2002-11-20: prefer axial triangle edges */
|
|
if( mapNonAxial )
|
|
{
|
|
/* subdivide the triangle */
|
|
MapTriangle_r( lm, info, dv, plane, stv, ttv );
|
|
return qtrue;
|
|
}
|
|
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
float *a, *b;
|
|
bspDrawVert_t *dv2[ 3 ];
|
|
|
|
|
|
/* get verts */
|
|
a = dv[ i ]->lightmap[ 0 ];
|
|
b = dv[ (i + 1) % 3 ]->lightmap[ 0 ];
|
|
|
|
/* make degenerate triangles for mapping edges */
|
|
if( fabs( a[ 0 ] - b[ 0 ] ) < 0.01f || fabs( a[ 1 ] - b[ 1 ] ) < 0.01f )
|
|
{
|
|
dv2[ 0 ] = dv[ i ];
|
|
dv2[ 1 ] = dv[ (i + 1) % 3 ];
|
|
dv2[ 2 ] = dv[ (i + 1) % 3 ];
|
|
|
|
/* map the degenerate triangle */
|
|
MapTriangle_r( lm, info, dv2, plane, stv, ttv );
|
|
}
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
MapQuad_r()
|
|
recursively subdivides a quad until its edges are shorter
|
|
than the distance between two luxels
|
|
*/
|
|
|
|
static void MapQuad_r( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 4 ], vec4_t plane, vec3_t stv[ 4 ], vec3_t ttv[ 4 ] )
|
|
{
|
|
bspDrawVert_t mid[ 2 ], *dv2[ 4 ];
|
|
int max;
|
|
|
|
|
|
/* subdivide calc */
|
|
{
|
|
int i;
|
|
float *a, *b, dx, dy, dist, maxDist;
|
|
|
|
|
|
/* find the longest edge and split it */
|
|
max = -1;
|
|
maxDist = 0;
|
|
for( i = 0; i < 4; i++ )
|
|
{
|
|
/* get verts */
|
|
a = dv[ i ]->lightmap[ 0 ];
|
|
b = dv[ (i + 1) % 4 ]->lightmap[ 0 ];
|
|
|
|
/* get dists */
|
|
dx = a[ 0 ] - b[ 0 ];
|
|
dy = a[ 1 ] - b[ 1 ];
|
|
dist = (dx * dx) + (dy * dy); //% sqrt( (dx * dx) + (dy * dy) );
|
|
|
|
/* longer? */
|
|
if( dist > maxDist )
|
|
{
|
|
maxDist = dist;
|
|
max = i;
|
|
}
|
|
}
|
|
|
|
/* try to early out */
|
|
if( max < 0 || maxDist <= subdivideThreshold )
|
|
return;
|
|
}
|
|
|
|
/* we only care about even/odd edges */
|
|
max &= 1;
|
|
|
|
/* split the longest edges */
|
|
LerpDrawVert( dv[ max ], dv[ (max + 1) % 4 ], &mid[ 0 ] );
|
|
LerpDrawVert( dv[ max + 2 ], dv[ (max + 3) % 4 ], &mid[ 1 ] );
|
|
|
|
/* map the vertexes */
|
|
MapSingleLuxel( lm, info, &mid[ 0 ], plane, 1, stv, ttv );
|
|
MapSingleLuxel( lm, info, &mid[ 1 ], plane, 1, stv, ttv );
|
|
|
|
/* 0 and 2 */
|
|
if( max == 0 )
|
|
{
|
|
/* recurse to first quad */
|
|
dv2[ 0 ] = dv[ 0 ];
|
|
dv2[ 1 ] = &mid[ 0 ];
|
|
dv2[ 2 ] = &mid[ 1 ];
|
|
dv2[ 3 ] = dv[ 3 ];
|
|
MapQuad_r( lm, info, dv2, plane, stv, ttv );
|
|
|
|
/* recurse to second quad */
|
|
dv2[ 0 ] = &mid[ 0 ];
|
|
dv2[ 1 ] = dv[ 1 ];
|
|
dv2[ 2 ] = dv[ 2 ];
|
|
dv2[ 3 ] = &mid[ 1 ];
|
|
MapQuad_r( lm, info, dv2, plane, stv, ttv );
|
|
}
|
|
|
|
/* 1 and 3 */
|
|
else
|
|
{
|
|
/* recurse to first quad */
|
|
dv2[ 0 ] = dv[ 0 ];
|
|
dv2[ 1 ] = dv[ 1 ];
|
|
dv2[ 2 ] = &mid[ 0 ];
|
|
dv2[ 3 ] = &mid[ 1 ];
|
|
MapQuad_r( lm, info, dv2, plane, stv, ttv );
|
|
|
|
/* recurse to second quad */
|
|
dv2[ 0 ] = &mid[ 1 ];
|
|
dv2[ 1 ] = &mid[ 0 ];
|
|
dv2[ 2 ] = dv[ 2 ];
|
|
dv2[ 3 ] = dv[ 3 ];
|
|
MapQuad_r( lm, info, dv2, plane, stv, ttv );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
MapQuad()
|
|
seed function for MapQuad_r()
|
|
requires a cw ordered triangle quad
|
|
*/
|
|
|
|
#define QUAD_PLANAR_EPSILON 0.5f
|
|
|
|
static qboolean MapQuad( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 4 ] )
|
|
{
|
|
float dist;
|
|
vec4_t plane;
|
|
vec3_t *stv, *ttv, stvStatic[ 4 ], ttvStatic[ 4 ];
|
|
|
|
|
|
/* get plane if possible */
|
|
if( lm->plane != NULL )
|
|
{
|
|
VectorCopy( lm->plane, plane );
|
|
plane[ 3 ] = lm->plane[ 3 ];
|
|
}
|
|
|
|
/* otherwise make one from the points */
|
|
else if( PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) == qfalse )
|
|
return qfalse;
|
|
|
|
/* 4th point must fall on the plane */
|
|
dist = DotProduct( plane, dv[ 3 ]->xyz ) - plane[ 3 ];
|
|
if( fabs( dist ) > QUAD_PLANAR_EPSILON )
|
|
return qfalse;
|
|
|
|
/* check to see if we need to calculate texture->world tangent vectors */
|
|
if( info->si->normalImage != NULL && CalcTangentVectors( 4, dv, stvStatic, ttvStatic ) )
|
|
{
|
|
stv = stvStatic;
|
|
ttv = ttvStatic;
|
|
}
|
|
else
|
|
{
|
|
stv = NULL;
|
|
ttv = NULL;
|
|
}
|
|
|
|
/* map the vertexes */
|
|
MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv );
|
|
MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv );
|
|
MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv );
|
|
MapSingleLuxel( lm, info, dv[ 3 ], plane, 1, stv, ttv );
|
|
|
|
/* subdivide the quad */
|
|
MapQuad_r( lm, info, dv, plane, stv, ttv );
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
MapRawLightmap()
|
|
maps the locations, normals, and pvs clusters for a raw lightmap
|
|
*/
|
|
|
|
#define VectorDivide( in, d, out ) VectorScale( in, (1.0f / (d)), out ) //% (out)[ 0 ] = (in)[ 0 ] / (d), (out)[ 1 ] = (in)[ 1 ] / (d), (out)[ 2 ] = (in)[ 2 ] / (d)
|
|
|
|
void MapRawLightmap( int rawLightmapNum )
|
|
{
|
|
int n, num, i, x, y, sx, sy, pw[ 5 ], r, *cluster, mapNonAxial;
|
|
float *luxel, *origin, *normal, samples, radius, pass;
|
|
rawLightmap_t *lm;
|
|
bspDrawSurface_t *ds;
|
|
surfaceInfo_t *info;
|
|
mesh_t src, *subdivided, *mesh;
|
|
bspDrawVert_t *verts, *dv[ 4 ], fake;
|
|
|
|
|
|
/* bail if this number exceeds the number of raw lightmaps */
|
|
if( rawLightmapNum >= numRawLightmaps )
|
|
return;
|
|
|
|
/* get lightmap */
|
|
lm = &rawLightmaps[ rawLightmapNum ];
|
|
|
|
/* -----------------------------------------------------------------
|
|
map referenced surfaces onto the raw lightmap
|
|
----------------------------------------------------------------- */
|
|
|
|
/* walk the list of surfaces on this raw lightmap */
|
|
for( n = 0; n < lm->numLightSurfaces; n++ )
|
|
{
|
|
/* with > 1 surface per raw lightmap, clear occluded */
|
|
if( n > 0 )
|
|
{
|
|
for( y = 0; y < lm->sh; y++ )
|
|
{
|
|
for( x = 0; x < lm->sw; x++ )
|
|
{
|
|
/* get cluster */
|
|
cluster = SUPER_CLUSTER( x, y );
|
|
if( *cluster < 0 )
|
|
*cluster = CLUSTER_UNMAPPED;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* get surface */
|
|
num = lightSurfaces[ lm->firstLightSurface + n ];
|
|
ds = &bspDrawSurfaces[ num ];
|
|
info = &surfaceInfos[ num ];
|
|
|
|
/* bail if no lightmap to calculate */
|
|
if( info->lm != lm )
|
|
{
|
|
Sys_Printf( "!" );
|
|
continue;
|
|
}
|
|
|
|
/* map the surface onto the lightmap origin/cluster/normal buffers */
|
|
switch( ds->surfaceType )
|
|
{
|
|
case MST_PLANAR:
|
|
/* get verts */
|
|
verts = yDrawVerts + ds->firstVert;
|
|
|
|
/* map the triangles */
|
|
for( mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++ )
|
|
{
|
|
for( i = 0; i < ds->numIndexes; i += 3 )
|
|
{
|
|
dv[ 0 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i ] ];
|
|
dv[ 1 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 1 ] ];
|
|
dv[ 2 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 2 ] ];
|
|
MapTriangle( lm, info, dv, mapNonAxial );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MST_PATCH:
|
|
/* make a mesh from the drawsurf */
|
|
src.width = ds->patchWidth;
|
|
src.height = ds->patchHeight;
|
|
src.verts = &yDrawVerts[ ds->firstVert ];
|
|
//% subdivided = SubdivideMesh( src, 8, 512 );
|
|
subdivided = SubdivideMesh2( src, info->patchIterations );
|
|
|
|
/* fit it to the curve and remove colinear verts on rows/columns */
|
|
PutMeshOnCurve( *subdivided );
|
|
mesh = RemoveLinearMeshColumnsRows( subdivided );
|
|
FreeMesh( subdivided );
|
|
|
|
/* get verts */
|
|
verts = mesh->verts;
|
|
|
|
/* debug code */
|
|
#if 0
|
|
if( lm->plane )
|
|
{
|
|
Sys_Printf( "Planar patch: [%1.3f %1.3f %1.3f] [%1.3f %1.3f %1.3f] [%1.3f %1.3f %1.3f]\n",
|
|
lm->plane[ 0 ], lm->plane[ 1 ], lm->plane[ 2 ],
|
|
lm->vecs[ 0 ][ 0 ], lm->vecs[ 0 ][ 1 ], lm->vecs[ 0 ][ 2 ],
|
|
lm->vecs[ 1 ][ 0 ], lm->vecs[ 1 ][ 1 ], lm->vecs[ 1 ][ 2 ] );
|
|
}
|
|
#endif
|
|
|
|
/* map the mesh quads */
|
|
#if 0
|
|
|
|
for( mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++ )
|
|
{
|
|
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 and map first triangle */
|
|
dv[ 0 ] = &verts[ pw[ r + 0 ] ];
|
|
dv[ 1 ] = &verts[ pw[ r + 1 ] ];
|
|
dv[ 2 ] = &verts[ pw[ r + 2 ] ];
|
|
MapTriangle( lm, info, dv, mapNonAxial );
|
|
|
|
/* get drawverts and map second triangle */
|
|
dv[ 0 ] = &verts[ pw[ r + 0 ] ];
|
|
dv[ 1 ] = &verts[ pw[ r + 2 ] ];
|
|
dv[ 2 ] = &verts[ pw[ r + 3 ] ];
|
|
MapTriangle( lm, info, dv, mapNonAxial );
|
|
}
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
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 ] = pw[ 0 ];
|
|
|
|
/* set radix */
|
|
r = (x + y) & 1;
|
|
|
|
/* attempt to map quad first */
|
|
dv[ 0 ] = &verts[ pw[ r + 0 ] ];
|
|
dv[ 1 ] = &verts[ pw[ r + 1 ] ];
|
|
dv[ 2 ] = &verts[ pw[ r + 2 ] ];
|
|
dv[ 3 ] = &verts[ pw[ r + 3 ] ];
|
|
if( MapQuad( lm, info, dv ) )
|
|
continue;
|
|
|
|
/* get drawverts and map first triangle */
|
|
MapTriangle( lm, info, dv, mapNonAxial );
|
|
|
|
/* get drawverts and map second triangle */
|
|
dv[ 1 ] = &verts[ pw[ r + 2 ] ];
|
|
dv[ 2 ] = &verts[ pw[ r + 3 ] ];
|
|
MapTriangle( lm, info, dv, mapNonAxial );
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/* free the mesh */
|
|
FreeMesh( mesh );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* -----------------------------------------------------------------
|
|
average and clean up luxel normals
|
|
----------------------------------------------------------------- */
|
|
|
|
/* walk the luxels */
|
|
for( y = 0; y < lm->sh; y++ )
|
|
{
|
|
for( x = 0; x < lm->sw; x++ )
|
|
{
|
|
/* get luxel */
|
|
luxel = SUPER_LUXEL( 0, x, y );
|
|
normal = SUPER_NORMAL( x, y );
|
|
cluster = SUPER_CLUSTER( x, y );
|
|
|
|
/* only look at mapped luxels */
|
|
if( *cluster < 0 )
|
|
continue;
|
|
|
|
/* the normal data could be the sum of multiple samples */
|
|
if( luxel[ 3 ] > 1.0f )
|
|
VectorNormalize( normal, normal );
|
|
|
|
/* mark this luxel as having only one normal */
|
|
luxel[ 3 ] = 1.0f;
|
|
}
|
|
}
|
|
|
|
/* non-planar surfaces stop here */
|
|
if( lm->plane == NULL )
|
|
return;
|
|
|
|
/* -----------------------------------------------------------------
|
|
map occluded or unuxed luxels
|
|
----------------------------------------------------------------- */
|
|
|
|
/* walk the luxels */
|
|
radius = floor( superSample / 2 );
|
|
radius = radius > 0 ? radius : 1.0f;
|
|
radius += 1.0f;
|
|
for( pass = 2.0f; pass <= radius; pass += 1.0f )
|
|
{
|
|
for( y = 0; y < lm->sh; y++ )
|
|
{
|
|
for( x = 0; x < lm->sw; x++ )
|
|
{
|
|
/* get luxel */
|
|
luxel = SUPER_LUXEL( 0, x, y );
|
|
normal = SUPER_NORMAL( x, y );
|
|
cluster = SUPER_CLUSTER( x, y );
|
|
|
|
/* only look at unmapped luxels */
|
|
if( *cluster != CLUSTER_UNMAPPED )
|
|
continue;
|
|
|
|
/* divine a normal and origin from neighboring luxels */
|
|
VectorClear( fake.xyz );
|
|
VectorClear( fake.normal );
|
|
fake.lightmap[ 0 ][ 0 ] = x; //% 0.0001 + x;
|
|
fake.lightmap[ 0 ][ 1 ] = y; //% 0.0001 + y;
|
|
samples = 0.0f;
|
|
for( sy = (y - 1); sy <= (y + 1); sy++ )
|
|
{
|
|
if( sy < 0 || sy >= lm->sh )
|
|
continue;
|
|
|
|
for( sx = (x - 1); sx <= (x + 1); sx++ )
|
|
{
|
|
if( sx < 0 || sx >= lm->sw || (sx == x && sy == y) )
|
|
continue;
|
|
|
|
/* get neighboring luxel */
|
|
luxel = SUPER_LUXEL( 0, sx, sy );
|
|
origin = SUPER_ORIGIN( sx, sy );
|
|
normal = SUPER_NORMAL( sx, sy );
|
|
cluster = SUPER_CLUSTER( sx, sy );
|
|
|
|
/* only consider luxels mapped in previous passes */
|
|
if( *cluster < 0 || luxel[ 0 ] >= pass )
|
|
continue;
|
|
|
|
/* add its distinctiveness to our own */
|
|
VectorAdd( fake.xyz, origin, fake.xyz );
|
|
VectorAdd( fake.normal, normal, fake.normal );
|
|
samples += luxel[ 3 ];
|
|
}
|
|
}
|
|
|
|
/* any samples? */
|
|
if( samples == 0.0f )
|
|
continue;
|
|
|
|
/* average */
|
|
VectorDivide( fake.xyz, samples, fake.xyz );
|
|
//% VectorDivide( fake.normal, samples, fake.normal );
|
|
if( VectorNormalize( fake.normal, fake.normal ) == 0.0f )
|
|
continue;
|
|
|
|
/* map the fake vert */
|
|
MapSingleLuxel( lm, NULL, &fake, lm->plane, pass, NULL, NULL );
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -----------------------------------------------------------------
|
|
average and clean up luxel normals
|
|
----------------------------------------------------------------- */
|
|
|
|
/* walk the luxels */
|
|
for( y = 0; y < lm->sh; y++ )
|
|
{
|
|
for( x = 0; x < lm->sw; x++ )
|
|
{
|
|
/* get luxel */
|
|
luxel = SUPER_LUXEL( 0, x, y );
|
|
normal = SUPER_NORMAL( x, y );
|
|
cluster = SUPER_CLUSTER( x, y );
|
|
|
|
/* only look at mapped luxels */
|
|
if( *cluster < 0 )
|
|
continue;
|
|
|
|
/* the normal data could be the sum of multiple samples */
|
|
if( luxel[ 3 ] > 1.0f )
|
|
VectorNormalize( normal, normal );
|
|
|
|
/* mark this luxel as having only one normal */
|
|
luxel[ 3 ] = 1.0f;
|
|
}
|
|
}
|
|
|
|
/* debug code */
|
|
#if 0
|
|
Sys_Printf( "\n" );
|
|
for( y = 0; y < lm->sh; y++ )
|
|
{
|
|
for( x = 0; x < lm->sw; x++ )
|
|
{
|
|
vec3_t mins, maxs;
|
|
|
|
|
|
cluster = SUPER_CLUSTER( x, y );
|
|
origin = SUPER_ORIGIN( x, y );
|
|
normal = SUPER_NORMAL( x, y );
|
|
luxel = SUPER_LUXEL( x, y );
|
|
|
|
if( *cluster < 0 )
|
|
continue;
|
|
|
|
/* check if within the bounding boxes of all surfaces referenced */
|
|
ClearBounds( mins, maxs );
|
|
for( n = 0; n < lm->numLightSurfaces; n++ )
|
|
{
|
|
int TOL;
|
|
info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + n ] ];
|
|
TOL = info->sampleSize + 2;
|
|
AddPointToBounds( info->mins, mins, maxs );
|
|
AddPointToBounds( info->maxs, mins, maxs );
|
|
if( origin[ 0 ] > (info->mins[ 0 ] - TOL) && origin[ 0 ] < (info->maxs[ 0 ] + TOL) &&
|
|
origin[ 1 ] > (info->mins[ 1 ] - TOL) && origin[ 1 ] < (info->maxs[ 1 ] + TOL) &&
|
|
origin[ 2 ] > (info->mins[ 2 ] - TOL) && origin[ 2 ] < (info->maxs[ 2 ] + TOL) )
|
|
break;
|
|
}
|
|
|
|
/* inside? */
|
|
if( n < lm->numLightSurfaces )
|
|
continue;
|
|
|
|
/* report bogus origin */
|
|
Sys_Printf( "%6d [%2d,%2d] (%4d): XYZ(%+4.1f %+4.1f %+4.1f) LO(%+4.1f %+4.1f %+4.1f) HI(%+4.1f %+4.1f %+4.1f) <%3.0f>\n",
|
|
rawLightmapNum, x, y, *cluster,
|
|
origin[ 0 ], origin[ 1 ], origin[ 2 ],
|
|
mins[ 0 ], mins[ 1 ], mins[ 2 ],
|
|
maxs[ 0 ], maxs[ 1 ], maxs[ 2 ],
|
|
luxel[ 3 ] );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
SubmapRawLuxel()
|
|
calculates the pvs cluster, origin, normal of a sub-luxel
|
|
*/
|
|
|
|
static qboolean SubmapRawLuxel( rawLightmap_t *lm, int x, int y, float bx, float by, int *sampleCluster, vec3_t sampleOrigin, vec3_t sampleNormal )
|
|
{
|
|
int i, *cluster, *cluster2;
|
|
float *origin, *origin2, *normal; //% , *normal2;
|
|
vec3_t originVecs[ 2 ]; //% , normalVecs[ 2 ];
|
|
|
|
|
|
/* calulate x vector */
|
|
if( (x < (lm->sw - 1) && bx >= 0.0f) || (x == 0 && bx <= 0.0f) )
|
|
{
|
|
cluster = SUPER_CLUSTER( x, y );
|
|
origin = SUPER_ORIGIN( x, y );
|
|
//% normal = SUPER_NORMAL( x, y );
|
|
cluster2 = SUPER_CLUSTER( x + 1, y );
|
|
origin2 = *cluster2 < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x + 1, y );
|
|
//% normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x + 1, y );
|
|
}
|
|
else if( (x > 0 && bx <= 0.0f) || (x == (lm->sw - 1) && bx >= 0.0f) )
|
|
{
|
|
cluster = SUPER_CLUSTER( x - 1, y );
|
|
origin = *cluster < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x - 1, y );
|
|
//% normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x - 1, y );
|
|
cluster2 = SUPER_CLUSTER( x, y );
|
|
origin2 = SUPER_ORIGIN( x, y );
|
|
//% normal2 = SUPER_NORMAL( x, y );
|
|
}
|
|
else
|
|
Sys_Printf( "WARNING: Spurious lightmap S vector\n" );
|
|
|
|
VectorSubtract( origin2, origin, originVecs[ 0 ] );
|
|
//% VectorSubtract( normal2, normal, normalVecs[ 0 ] );
|
|
|
|
/* calulate y vector */
|
|
if( (y < (lm->sh - 1) && bx >= 0.0f) || (y == 0 && bx <= 0.0f) )
|
|
{
|
|
cluster = SUPER_CLUSTER( x, y );
|
|
origin = SUPER_ORIGIN( x, y );
|
|
//% normal = SUPER_NORMAL( x, y );
|
|
cluster2 = SUPER_CLUSTER( x, y + 1 );
|
|
origin2 = *cluster2 < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x, y + 1 );
|
|
//% normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y + 1 );
|
|
}
|
|
else if( (y > 0 && bx <= 0.0f) || (y == (lm->sh - 1) && bx >= 0.0f) )
|
|
{
|
|
cluster = SUPER_CLUSTER( x, y - 1 );
|
|
origin = *cluster < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x, y - 1 );
|
|
//% normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y - 1 );
|
|
cluster2 = SUPER_CLUSTER( x, y );
|
|
origin2 = SUPER_ORIGIN( x, y );
|
|
//% normal2 = SUPER_NORMAL( x, y );
|
|
}
|
|
else
|
|
Sys_Printf( "WARNING: Spurious lightmap T vector\n" );
|
|
|
|
VectorSubtract( origin2, origin, originVecs[ 1 ] );
|
|
//% VectorSubtract( normal2, normal, normalVecs[ 1 ] );
|
|
|
|
/* calculate new origin */
|
|
//% VectorMA( origin, bx, originVecs[ 0 ], sampleOrigin );
|
|
//% VectorMA( sampleOrigin, by, originVecs[ 1 ], sampleOrigin );
|
|
for( i = 0; i < 3; i++ )
|
|
sampleOrigin[ i ] = sampleOrigin[ i ] + (bx * originVecs[ 0 ][ i ]) + (by * originVecs[ 1 ][ i ]);
|
|
|
|
/* get cluster */
|
|
*sampleCluster = ClusterForPointExtFilter( sampleOrigin, (LUXEL_EPSILON * 2), lm->numLightClusters, lm->lightClusters );
|
|
if( *sampleCluster < 0 )
|
|
return qfalse;
|
|
|
|
/* calculate new normal */
|
|
//% VectorMA( normal, bx, normalVecs[ 0 ], sampleNormal );
|
|
//% VectorMA( sampleNormal, by, normalVecs[ 1 ], sampleNormal );
|
|
//% if( VectorNormalize( sampleNormal, sampleNormal ) <= 0.0f )
|
|
//% return qfalse;
|
|
normal = SUPER_NORMAL( x, y );
|
|
VectorCopy( normal, sampleNormal );
|
|
|
|
/* return ok */
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
/*
|
|
SubsampleRawLuxel_r()
|
|
recursively subsamples a luxel until its color gradient is low enough or subsampling limit is reached
|
|
*/
|
|
|
|
void SubsampleRawLuxel_r( rawLightmap_t *lm, trace_t *trace, vec3_t sampleOrigin, int x, int y, float bias, float *lightLuxel )
|
|
{
|
|
int b, samples, mapped, lighted;
|
|
int cluster[ 4 ];
|
|
vec4_t luxel[ 4 ];
|
|
vec3_t origin[ 4 ], normal[ 4 ];
|
|
float biasDirs[ 4 ][ 2 ] = { { -1.0f, -1.0f }, { 1.0f, -1.0f }, { -1.0f, 1.0f }, { 1.0f, 1.0f } };
|
|
vec3_t color, total;
|
|
|
|
|
|
/* limit check */
|
|
if( lightLuxel[ 3 ] >= lightSamples )
|
|
return;
|
|
|
|
/* setup */
|
|
VectorClear( total );
|
|
mapped = 0;
|
|
lighted = 0;
|
|
|
|
/* make 2x2 subsample stamp */
|
|
for( b = 0; b < 4; b++ )
|
|
{
|
|
/* set origin */
|
|
VectorCopy( sampleOrigin, origin[ b ] );
|
|
|
|
/* calculate position */
|
|
if( !SubmapRawLuxel( lm, x, y, (bias * biasDirs[ b ][ 0 ]), (bias * biasDirs[ b ][ 1 ]), &cluster[ b ], origin[ b ], normal[ b ] ) )
|
|
{
|
|
cluster[ b ] = -1;
|
|
continue;
|
|
}
|
|
mapped++;
|
|
|
|
/* increment sample count */
|
|
luxel[ b ][ 3 ] = lightLuxel[ 3 ] + 1.0f;
|
|
|
|
/* setup trace */
|
|
trace->cluster = *cluster;
|
|
VectorCopy( origin[ b ], trace->origin );
|
|
VectorCopy( normal[ b ], trace->normal );
|
|
|
|
/* sample light */
|
|
//% LightContributionToSample( light, cluster[ b ], origin[ b ], normal[ b ], luxel[ b ], qtrue, qfalse, lm->numLightSurfaces, &lightSurfaces[ lm->firstLightSurface ] );
|
|
LightContributionToSample( trace );
|
|
|
|
/* add to totals (fixme: make contrast function) */
|
|
VectorCopy( trace->color, luxel[ b ] );
|
|
VectorAdd( total, trace->color, total );
|
|
if( (luxel[ b ][ 0 ] + luxel[ b ][ 1 ] + luxel[ b ][ 2 ]) > 0.0f )
|
|
lighted++;
|
|
}
|
|
|
|
/* subsample further? */
|
|
if( (lightLuxel[ 3 ] + 1.0f) < lightSamples &&
|
|
(total[ 0 ] > 4.0f || total[ 1 ] > 4.0f || total[ 2 ] > 4.0f) &&
|
|
lighted != 0 && lighted != mapped )
|
|
{
|
|
for( b = 0; b < 4; b++ )
|
|
{
|
|
if( cluster[ b ] < 0 )
|
|
continue;
|
|
SubsampleRawLuxel_r( lm, trace, origin[ b ], x, y, (bias * 0.25f), luxel[ b ] );
|
|
}
|
|
}
|
|
|
|
/* average */
|
|
//% VectorClear( color );
|
|
//% samples = 0;
|
|
VectorCopy( lightLuxel, color );
|
|
samples = 1;
|
|
for( b = 0; b < 4; b++ )
|
|
{
|
|
if( cluster[ b ] < 0 )
|
|
continue;
|
|
VectorAdd( color, luxel[ b ], color );
|
|
samples++;
|
|
}
|
|
|
|
/* add to luxel */
|
|
if( samples > 0 )
|
|
{
|
|
/* average */
|
|
color[ 0 ] /= samples;
|
|
color[ 1 ] /= samples;
|
|
color[ 2 ] /= samples;
|
|
|
|
/* add to color */
|
|
VectorCopy( color, lightLuxel );
|
|
lightLuxel[ 3 ] += 1.0f;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
IlluminateRawLightmap()
|
|
illuminates the luxels
|
|
*/
|
|
|
|
#define LIGHT_LUXEL( x, y ) (lightLuxels + ((((y) * lm->sw) + (x)) * SUPER_LUXEL_SIZE))
|
|
|
|
void IlluminateRawLightmap( int rawLightmapNum )
|
|
{
|
|
int i, t, x, y, sx, sy, size, llSize, luxelFilterRadius, lightmapNum;
|
|
int *cluster, *cluster2, mapped, lighted, totalLighted;
|
|
rawLightmap_t *lm;
|
|
surfaceInfo_t *info;
|
|
qboolean filterColor, filterDir;
|
|
float brightness;
|
|
float *origin, *normal, *luxel, *luxel2, *deluxel, *deluxel2;
|
|
float *lightLuxels, *lightLuxel, samples, filterRadius, weight;
|
|
vec3_t color, averageColor, averageDir, total, temp, temp2;
|
|
float tests[ 4 ][ 2 ] = { { 0.0f, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } };
|
|
trace_t trace;
|
|
|
|
|
|
/* bail if this number exceeds the number of raw lightmaps */
|
|
if( rawLightmapNum >= numRawLightmaps )
|
|
return;
|
|
|
|
/* get lightmap */
|
|
lm = &rawLightmaps[ rawLightmapNum ];
|
|
|
|
/* setup trace */
|
|
trace.testOcclusion = !noTrace;
|
|
trace.forceSunlight = qfalse;
|
|
trace.recvShadows = lm->recvShadows;
|
|
trace.numSurfaces = lm->numLightSurfaces;
|
|
trace.surfaces = &lightSurfaces[ lm->firstLightSurface ];
|
|
trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS;
|
|
|
|
/* twosided lighting (may or may not be a good idea for lightmapped stuff) */
|
|
trace.twoSided = qfalse;
|
|
for( i = 0; i < trace.numSurfaces; i++ )
|
|
{
|
|
/* get surface */
|
|
info = &surfaceInfos[ trace.surfaces[ i ] ];
|
|
|
|
/* check twosidedness */
|
|
if( info->si->twoSided )
|
|
{
|
|
trace.twoSided = qtrue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* create a culled light list for this raw lightmap */
|
|
CreateTraceLightsForBounds( lm->mins, lm->maxs, lm->plane, lm->numLightClusters, lm->lightClusters, LIGHT_SURFACES, &trace );
|
|
|
|
/* -----------------------------------------------------------------
|
|
fill pass
|
|
----------------------------------------------------------------- */
|
|
|
|
/* test debugging state */
|
|
if( debugSurfaces || debugAxis || debugCluster || debugOrigin || normalmap )
|
|
{
|
|
/* debug fill the luxels */
|
|
for( y = 0; y < lm->sh; y++ )
|
|
{
|
|
for( x = 0; x < lm->sw; x++ )
|
|
{
|
|
/* get cluster */
|
|
cluster = SUPER_CLUSTER( x, y );
|
|
|
|
/* only fill mapped luxels */
|
|
if( *cluster < 0 )
|
|
continue;
|
|
|
|
/* get particulars */
|
|
luxel = SUPER_LUXEL( 0, x, y );
|
|
origin = SUPER_ORIGIN( x, y );
|
|
normal = SUPER_NORMAL( x, y );
|
|
|
|
/* color the luxel with raw lightmap num? */
|
|
if( debugSurfaces )
|
|
VectorCopy( debugColors[ rawLightmapNum % 12 ], luxel );
|
|
|
|
/* color the luxel with lightmap axis? */
|
|
else if( debugAxis )
|
|
{
|
|
luxel[ 0 ] = (lm->axis[ 0 ] + 1.0f) * 127.5f;
|
|
luxel[ 1 ] = (lm->axis[ 1 ] + 1.0f) * 127.5f;
|
|
luxel[ 2 ] = (lm->axis[ 2 ] + 1.0f) * 127.5f;
|
|
}
|
|
|
|
/* color the luxel with luxel cluster? */
|
|
else if( debugCluster )
|
|
VectorCopy( debugColors[ *cluster % 12 ], luxel );
|
|
|
|
/* color the luxel with luxel origin? */
|
|
else if( debugOrigin )
|
|
{
|
|
VectorSubtract( lm->maxs, lm->mins, temp );
|
|
VectorScale( temp, (1.0f / 255.0f), temp );
|
|
VectorSubtract( origin, lm->mins, temp2 );
|
|
luxel[ 0 ] = lm->mins[ 0 ] + (temp[ 0 ] * temp2[ 0 ]);
|
|
luxel[ 1 ] = lm->mins[ 1 ] + (temp[ 1 ] * temp2[ 1 ]);
|
|
luxel[ 2 ] = lm->mins[ 2 ] + (temp[ 2 ] * temp2[ 2 ]);
|
|
}
|
|
|
|
/* color the luxel with the normal */
|
|
else if( normalmap )
|
|
{
|
|
luxel[ 0 ] = (normal[ 0 ] + 1.0f) * 127.5f;
|
|
luxel[ 1 ] = (normal[ 1 ] + 1.0f) * 127.5f;
|
|
luxel[ 2 ] = (normal[ 2 ] + 1.0f) * 127.5f;
|
|
}
|
|
|
|
/* add to counts */
|
|
numLuxelsIlluminated++;
|
|
luxel[ 3 ] = 1.0f;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* allocate temporary per-light luxel storage */
|
|
llSize = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float );
|
|
lightLuxels = safe_malloc( llSize );
|
|
|
|
/* clear luxels */
|
|
//% memset( lm->superLuxels[ 0 ], 0, llSize );
|
|
|
|
/* set ambient color */
|
|
for( y = 0; y < lm->sh; y++ )
|
|
{
|
|
for( x = 0; x < lm->sw; x++ )
|
|
{
|
|
/* get cluster */
|
|
cluster = SUPER_CLUSTER( x, y );
|
|
luxel = SUPER_LUXEL( 0, x, y );
|
|
normal = SUPER_NORMAL( x, y );
|
|
deluxel = SUPER_DELUXEL( x, y );
|
|
|
|
/* blacken unmapped clusters */
|
|
if( *cluster < 0 )
|
|
VectorClear( luxel );
|
|
|
|
/* set ambient */
|
|
else
|
|
{
|
|
VectorCopy( ambientColor, luxel );
|
|
if( deluxemap )
|
|
VectorScale( normal, 0.00390625f, deluxel );
|
|
luxel[ 3 ] = 1.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* clear styled lightmaps */
|
|
size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float );
|
|
for( lightmapNum = 1; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
|
|
{
|
|
if( lm->superLuxels[ lightmapNum ] != NULL )
|
|
memset( lm->superLuxels[ lightmapNum ], 0, size );
|
|
}
|
|
|
|
/* debugging code */
|
|
//% if( trace.numLights <= 0 )
|
|
//% Sys_Printf( "Lightmap %9d: 0 lights, axis: %.2f, %.2f, %.2f\n", rawLightmapNum, lm->axis[ 0 ], lm->axis[ 1 ], lm->axis[ 2 ] );
|
|
|
|
/* walk light list */
|
|
for( i = 0; i < trace.numLights; i++ )
|
|
{
|
|
/* setup trace */
|
|
trace.light = trace.lights[ i ];
|
|
|
|
/* style check */
|
|
for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
|
|
{
|
|
if( lm->styles[ lightmapNum ] == trace.light->style ||
|
|
lm->styles[ lightmapNum ] == LS_NONE )
|
|
break;
|
|
}
|
|
|
|
/* max of MAX_LIGHTMAPS (4) styles allowed to hit a surface/lightmap */
|
|
if( lightmapNum >= MAX_LIGHTMAPS )
|
|
{
|
|
Sys_Printf( "WARNING: Hit per-surface style limit (%d)\n", MAX_LIGHTMAPS );
|
|
continue;
|
|
}
|
|
|
|
/* setup */
|
|
memset( lightLuxels, 0, llSize );
|
|
totalLighted = 0;
|
|
|
|
/* initial pass, one sample per luxel */
|
|
for( y = 0; y < lm->sh; y++ )
|
|
{
|
|
for( x = 0; x < lm->sw; x++ )
|
|
{
|
|
/* get cluster */
|
|
cluster = SUPER_CLUSTER( x, y );
|
|
if( *cluster < 0 )
|
|
continue;
|
|
|
|
/* get particulars */
|
|
lightLuxel = LIGHT_LUXEL( x, y );
|
|
deluxel = SUPER_DELUXEL( x, y );
|
|
origin = SUPER_ORIGIN( x, y );
|
|
normal = SUPER_NORMAL( x, y );
|
|
|
|
/* set contribution count */
|
|
lightLuxel[ 3 ] = 1.0f;
|
|
|
|
/* setup trace */
|
|
trace.cluster = *cluster;
|
|
VectorCopy( origin, trace.origin );
|
|
VectorCopy( normal, trace.normal );
|
|
|
|
/* get light for this sample */
|
|
LightContributionToSample( &trace );
|
|
VectorCopy( trace.color, lightLuxel );
|
|
|
|
/* add to count */
|
|
if( trace.color[ 0 ] || trace.color[ 1 ] || trace.color[ 2 ] )
|
|
totalLighted++;
|
|
|
|
/* add to light direction map (fixme: use luxel normal as starting point for deluxel?) */
|
|
if( deluxemap )
|
|
{
|
|
/* color to grayscale (photoshop rgb weighting) */
|
|
brightness = trace.color[ 0 ] * 0.3f + trace.color[ 1 ] * 0.59f + trace.color[ 2 ] * 0.11f;
|
|
brightness *= (1.0 / 255.0);
|
|
VectorScale( trace.direction, brightness, trace.direction );
|
|
VectorAdd( deluxel, trace.direction, deluxel );
|
|
}
|
|
}
|
|
}
|
|
|
|
/* don't even bother with everything else if nothing was lit */
|
|
if( totalLighted == 0 )
|
|
continue;
|
|
|
|
/* determine filter radius */
|
|
filterRadius = lm->filterRadius > trace.light->filterRadius
|
|
? lm->filterRadius
|
|
: trace.light->filterRadius;
|
|
if( filterRadius < 0.0f )
|
|
filterRadius = 0.0f;
|
|
|
|
/* set luxel filter radius */
|
|
luxelFilterRadius = superSample * filterRadius / lm->sampleSize;
|
|
if( luxelFilterRadius == 0 && (filterRadius > 0.0f || filter) )
|
|
luxelFilterRadius = 1;
|
|
|
|
/* secondary pass, adaptive supersampling (fixme: use a contrast function to determine if subsampling is necessary) */
|
|
/* 2003-09-27: changed it so filtering disamples supersampling, as it would waste time */
|
|
if( lightSamples > 1 && luxelFilterRadius == 0 )
|
|
{
|
|
/* walk luxels */
|
|
for( y = 0; y < (lm->sh - 1); y++ )
|
|
{
|
|
for( x = 0; x < (lm->sw - 1); x++ )
|
|
{
|
|
/* setup */
|
|
mapped = 0;
|
|
lighted = 0;
|
|
VectorClear( total );
|
|
|
|
/* test 2x2 stamp */
|
|
for( t = 0; t < 4; t++ )
|
|
{
|
|
/* set sample coords */
|
|
sx = x + tests[ t ][ 0 ];
|
|
sy = y + tests[ t ][ 1 ];
|
|
|
|
/* get cluster */
|
|
cluster = SUPER_CLUSTER( sx, sy );
|
|
if( *cluster < 0 )
|
|
continue;
|
|
mapped++;
|
|
|
|
/* get luxel */
|
|
lightLuxel = LIGHT_LUXEL( sx, sy );
|
|
VectorAdd( total, lightLuxel, total );
|
|
if( (lightLuxel[ 0 ] + lightLuxel[ 1 ] + lightLuxel[ 2 ]) > 0.0f )
|
|
lighted++;
|
|
}
|
|
|
|
/* if total color is under a certain amount, then don't bother subsampling */
|
|
if( total[ 0 ] <= 4.0f && total[ 1 ] <= 4.0f && total[ 2 ] <= 4.0f )
|
|
continue;
|
|
|
|
/* if all 4 pixels are either in shadow or light, then don't subsample */
|
|
if( lighted != 0 && lighted != mapped )
|
|
{
|
|
for( t = 0; t < 4; t++ )
|
|
{
|
|
/* set sample coords */
|
|
sx = x + tests[ t ][ 0 ];
|
|
sy = y + tests[ t ][ 1 ];
|
|
|
|
/* get luxel */
|
|
cluster = SUPER_CLUSTER( sx, sy );
|
|
if( *cluster < 0 )
|
|
continue;
|
|
lightLuxel = LIGHT_LUXEL( sx, sy );
|
|
origin = SUPER_ORIGIN( sx, sy );
|
|
|
|
/* only subsample shadowed luxels */
|
|
//% if( (lightLuxel[ 0 ] + lightLuxel[ 1 ] + lightLuxel[ 2 ]) <= 0.0f )
|
|
//% continue;
|
|
|
|
/* subsample it */
|
|
SubsampleRawLuxel_r( lm, &trace, origin, sx, sy, 0.25f, lightLuxel );
|
|
|
|
/* debug code to colorize subsampled areas to yellow */
|
|
//% luxel = SUPER_LUXEL( lightmapNum, sx, sy );
|
|
//% VectorSet( luxel, 255, 204, 0 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* allocate sampling lightmap storage */
|
|
if( lm->superLuxels[ lightmapNum ] == NULL )
|
|
{
|
|
/* allocate sampling lightmap storage */
|
|
size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float );
|
|
lm->superLuxels[ lightmapNum ] = safe_malloc( size );
|
|
memset( lm->superLuxels[ lightmapNum ], 0, size );
|
|
}
|
|
|
|
/* set style */
|
|
if( lightmapNum > 0 )
|
|
{
|
|
lm->styles[ lightmapNum ] = trace.light->style;
|
|
//% Sys_Printf( "Surface %6d has lightstyle %d\n", rawLightmapNum, trace.light->style );
|
|
}
|
|
|
|
/* copy to permanent luxels */
|
|
for( y = 0; y < lm->sh; y++ )
|
|
{
|
|
for( x = 0; x < lm->sw; x++ )
|
|
{
|
|
/* get cluster and origin */
|
|
cluster = SUPER_CLUSTER( x, y );
|
|
if( *cluster < 0 )
|
|
continue;
|
|
origin = SUPER_ORIGIN( x, y );
|
|
|
|
/* filter? */
|
|
if( luxelFilterRadius )
|
|
{
|
|
/* setup */
|
|
VectorClear( averageColor );
|
|
samples = 0.0f;
|
|
|
|
/* cheaper distance-based filtering */
|
|
for( sy = (y - luxelFilterRadius); sy <= (y + luxelFilterRadius); sy++ )
|
|
{
|
|
if( sy < 0 || sy >= lm->sh )
|
|
continue;
|
|
|
|
for( sx = (x - luxelFilterRadius); sx <= (x + luxelFilterRadius); sx++ )
|
|
{
|
|
if( sx < 0 || sx >= lm->sw )
|
|
continue;
|
|
|
|
/* get particulars */
|
|
cluster = SUPER_CLUSTER( sx, sy );
|
|
if( *cluster < 0 )
|
|
continue;
|
|
lightLuxel = LIGHT_LUXEL( sx, sy );
|
|
|
|
/* create weight */
|
|
weight = (abs( sx - x ) == luxelFilterRadius ? 0.5f : 1.0f);
|
|
weight *= (abs( sy - y ) == luxelFilterRadius ? 0.5f : 1.0f);
|
|
|
|
/* scale luxel by filter weight */
|
|
VectorScale( lightLuxel, weight, color );
|
|
VectorAdd( averageColor, color, averageColor );
|
|
samples += weight;
|
|
}
|
|
}
|
|
|
|
/* any samples? */
|
|
if( samples <= 0.0f )
|
|
continue;
|
|
|
|
/* scale into luxel */
|
|
luxel = SUPER_LUXEL( lightmapNum, x, y );
|
|
luxel[ 3 ] = 1.0f;
|
|
|
|
/* handle negative light */
|
|
if( trace.light->flags & LIGHT_NEGATIVE )
|
|
{
|
|
luxel[ 0 ] -= averageColor[ 0 ] / samples;
|
|
luxel[ 1 ] -= averageColor[ 1 ] / samples;
|
|
luxel[ 2 ] -= averageColor[ 2 ] / samples;
|
|
}
|
|
|
|
/* handle normal light */
|
|
else
|
|
{
|
|
luxel[ 0 ] += averageColor[ 0 ] / samples;
|
|
luxel[ 1 ] += averageColor[ 1 ] / samples;
|
|
luxel[ 2 ] += averageColor[ 2 ] / samples;
|
|
}
|
|
}
|
|
|
|
/* single sample */
|
|
else
|
|
{
|
|
/* get particulars */
|
|
lightLuxel = LIGHT_LUXEL( x, y );
|
|
luxel = SUPER_LUXEL( lightmapNum, x, y );
|
|
|
|
/* handle negative light */
|
|
if( trace.light->flags & LIGHT_NEGATIVE )
|
|
VectorScale( averageColor, -1.0f, averageColor );
|
|
|
|
/* add color */
|
|
luxel[ 3 ] = 1.0f;
|
|
|
|
/* handle negative light */
|
|
if( trace.light->flags & LIGHT_NEGATIVE )
|
|
VectorSubtract( luxel, lightLuxel, luxel );
|
|
|
|
/* handle normal light */
|
|
else
|
|
VectorAdd( luxel, lightLuxel, luxel );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* free temporary luxels */
|
|
free( lightLuxels );
|
|
}
|
|
|
|
/* free light list */
|
|
FreeTraceLights( &trace );
|
|
|
|
/* -----------------------------------------------------------------
|
|
filter pass
|
|
----------------------------------------------------------------- */
|
|
|
|
/* walk lightmaps */
|
|
for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
|
|
{
|
|
/* early out */
|
|
if( lm->superLuxels[ lightmapNum ] == NULL )
|
|
continue;
|
|
|
|
/* average occluded luxels from neighbors */
|
|
for( y = 0; y < lm->sh; y++ )
|
|
{
|
|
for( x = 0; x < lm->sw; x++ )
|
|
{
|
|
/* get particulars */
|
|
cluster = SUPER_CLUSTER( x, y );
|
|
luxel = SUPER_LUXEL( lightmapNum, x, y );
|
|
deluxel = SUPER_DELUXEL( x, y );
|
|
normal = SUPER_NORMAL( x, y );
|
|
|
|
/* determine if filtering is necessary */
|
|
filterColor = qfalse;
|
|
filterDir = qfalse;
|
|
if( *cluster < 0 ||
|
|
(lm->splotchFix && (luxel[ 0 ] <= ambientColor[ 0 ] || luxel[ 1 ] <= ambientColor[ 1 ] || luxel[ 2 ] <= ambientColor[ 2 ])) )
|
|
filterColor = qtrue;
|
|
if( deluxemap && lightmapNum == 0 && (*cluster < 0 || filter) )
|
|
filterDir = qtrue;
|
|
|
|
if( !filterColor && !filterDir )
|
|
continue;
|
|
|
|
/* choose seed amount */
|
|
VectorClear( averageColor );
|
|
VectorClear( averageDir );
|
|
samples = 0;
|
|
|
|
/* walk 3x3 matrix */
|
|
for( sy = (y - 1); sy <= (y + 1); sy++ )
|
|
{
|
|
if( sy < 0 || sy >= lm->sh )
|
|
continue;
|
|
|
|
for( sx = (x - 1); sx <= (x + 1); sx++ )
|
|
{
|
|
if( sx < 0 || sx >= lm->sw || (sx == x && sy == y) )
|
|
continue;
|
|
|
|
/* get neighbor's particulars */
|
|
cluster2 = SUPER_CLUSTER( sx, sy );
|
|
luxel2 = SUPER_LUXEL( lightmapNum, sx, sy );
|
|
deluxel2 = SUPER_DELUXEL( sx, sy );
|
|
|
|
/* ignore unmapped/unlit luxels */
|
|
if( *cluster2 < 0 || luxel2[ 3 ] == 0.0f ||
|
|
(lm->splotchFix && VectorCompare( luxel2, ambientColor )) )
|
|
continue;
|
|
|
|
/* add its distinctiveness to our own */
|
|
VectorAdd( averageColor, luxel2, averageColor );
|
|
samples += luxel2[ 3 ];
|
|
if( filterDir )
|
|
VectorAdd( averageDir, deluxel2, averageDir );
|
|
}
|
|
}
|
|
|
|
/* fall through */
|
|
if( samples == 0.0f )
|
|
continue;
|
|
|
|
/* average it */
|
|
if( filterColor )
|
|
{
|
|
VectorDivide( averageColor, samples, luxel );
|
|
luxel[ 3 ] = 1.0f;
|
|
}
|
|
if( filterDir )
|
|
VectorDivide( averageDir, samples, deluxel );
|
|
|
|
/* set cluster to -3 */
|
|
if( *cluster < 0 )
|
|
*cluster = CLUSTER_FLOODED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
IlluminateVertexes()
|
|
light the surface vertexes
|
|
*/
|
|
|
|
#define VERTEX_NUDGE 2.0f
|
|
|
|
void IlluminateVertexes( int num )
|
|
{
|
|
int i, x, y, z, x1, y1, z1, sx, sy, radius, maxRadius, *cluster;
|
|
int lightmapNum;
|
|
float samples, *vertLuxel, *radVertLuxel, *luxel;
|
|
vec3_t origin, temp, temp2, colors[ MAX_LIGHTMAPS ];
|
|
bspDrawSurface_t *ds;
|
|
surfaceInfo_t *info;
|
|
rawLightmap_t *lm;
|
|
bspDrawVert_t *verts;
|
|
trace_t trace;
|
|
|
|
|
|
/* der... */
|
|
if( noVertexLighting )
|
|
return;
|
|
|
|
/* get surface, info, and raw lightmap */
|
|
ds = &bspDrawSurfaces[ num ];
|
|
info = &surfaceInfos[ num ];
|
|
lm = info->lm;
|
|
|
|
/* -----------------------------------------------------------------
|
|
illuminate the vertexes
|
|
----------------------------------------------------------------- */
|
|
|
|
/* calculate vertex lighting for surfaces without lightmaps */
|
|
if( lm == NULL )
|
|
{
|
|
/* setup trace */
|
|
trace.testOcclusion = !noTrace;
|
|
trace.forceSunlight = info->si->forceSunlight;
|
|
trace.recvShadows = info->recvShadows;
|
|
trace.numSurfaces = 1;
|
|
trace.surfaces = #
|
|
trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS;
|
|
|
|
/* twosided lighting */
|
|
trace.twoSided = info->si->twoSided;
|
|
|
|
/* make light list for this surface */
|
|
CreateTraceLightsForSurface( num, &trace );
|
|
|
|
/* walk the surface verts */
|
|
verts = yDrawVerts + ds->firstVert;
|
|
for( i = 0; i < ds->numVerts; i++ )
|
|
{
|
|
/* get vertex luxel */
|
|
radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i );
|
|
|
|
/* color the luxel with raw lightmap num? */
|
|
if( debugSurfaces )
|
|
VectorCopy( debugColors[ num % 12 ], radVertLuxel );
|
|
|
|
/* color the luxel with luxel origin? */
|
|
else if( debugOrigin )
|
|
{
|
|
VectorSubtract( info->maxs, info->mins, temp );
|
|
VectorScale( temp, (1.0f / 255.0f), temp );
|
|
VectorSubtract( origin, lm->mins, temp2 );
|
|
radVertLuxel[ 0 ] = info->mins[ 0 ] + (temp[ 0 ] * temp2[ 0 ]);
|
|
radVertLuxel[ 1 ] = info->mins[ 1 ] + (temp[ 1 ] * temp2[ 1 ]);
|
|
radVertLuxel[ 2 ] = info->mins[ 2 ] + (temp[ 2 ] * temp2[ 2 ]);
|
|
}
|
|
|
|
/* color the luxel with the normal */
|
|
else if( normalmap )
|
|
{
|
|
radVertLuxel[ 0 ] = (verts[ i ].normal[ 0 ] + 1.0f) * 127.5f;
|
|
radVertLuxel[ 1 ] = (verts[ i ].normal[ 1 ] + 1.0f) * 127.5f;
|
|
radVertLuxel[ 2 ] = (verts[ i ].normal[ 2 ] + 1.0f) * 127.5f;
|
|
}
|
|
|
|
/* illuminate the vertex */
|
|
else
|
|
{
|
|
/* clear vertex luxel */
|
|
VectorCopy( ambientColor, radVertLuxel );
|
|
|
|
/* try at initial origin */
|
|
trace.cluster = ClusterForPointExtFilter( verts[ i ].xyz, VERTEX_EPSILON, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ] );
|
|
if( trace.cluster >= 0 )
|
|
{
|
|
/* setup trace */
|
|
VectorCopy( verts[ i ].xyz, trace.origin );
|
|
VectorCopy( verts[ i ].normal, trace.normal );
|
|
|
|
/* trace */
|
|
LightingAtSample( &trace, ds->vertexStyles, colors );
|
|
|
|
/* store */
|
|
for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
|
|
{
|
|
radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
|
|
VectorCopy( colors[ lightmapNum ], radVertLuxel );
|
|
}
|
|
}
|
|
|
|
/* is this sample bright enough? */
|
|
if( radVertLuxel[ 0 ] <= ambientColor[ 0 ] &&
|
|
radVertLuxel[ 1 ] <= ambientColor[ 1 ] &&
|
|
radVertLuxel[ 2 ] <= ambientColor[ 2 ] )
|
|
{
|
|
/* nudge the sample point around a bit */
|
|
for( x = 0; x < 4; x++ )
|
|
{
|
|
/* two's complement 0, 1, -1, 2, -2, etc */
|
|
x1 = ((x >> 1) ^ (x & 1 ? -1 : 0)) + (x & 1);
|
|
|
|
for( y = 0; y < 4; y++ )
|
|
{
|
|
y1 = ((y >> 1) ^ (y & 1 ? -1 : 0)) + (y & 1);
|
|
|
|
for( z = 0; z < 4; z++ )
|
|
{
|
|
z1 = ((z >> 1) ^ (z & 1 ? -1 : 0)) + (z & 1);
|
|
|
|
/* nudge origin */
|
|
trace.origin[ 0 ] = verts[ i ].xyz[ 0 ] + (VERTEX_NUDGE * x1);
|
|
trace.origin[ 1 ] = verts[ i ].xyz[ 1 ] + (VERTEX_NUDGE * y1);
|
|
trace.origin[ 2 ] = verts[ i ].xyz[ 2 ] + (VERTEX_NUDGE * z1);
|
|
|
|
/* try at nudged origin */
|
|
trace.cluster = ClusterForPointExtFilter( origin, VERTEX_EPSILON, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ] );
|
|
if( trace.cluster < 0 )
|
|
continue;
|
|
|
|
/* trace */
|
|
LightingAtSample( &trace, ds->vertexStyles, colors );
|
|
|
|
/* store */
|
|
for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
|
|
{
|
|
radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
|
|
VectorCopy( colors[ lightmapNum ], radVertLuxel );
|
|
}
|
|
|
|
/* bright enough? */
|
|
radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i );
|
|
if( radVertLuxel[ 0 ] > ambientColor[ 0 ] ||
|
|
radVertLuxel[ 1 ] > ambientColor[ 1 ] ||
|
|
radVertLuxel[ 2 ] > ambientColor[ 2 ] )
|
|
x = y = z = 1000;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* another happy customer */
|
|
numVertsIlluminated++;
|
|
|
|
/* store it */
|
|
for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
|
|
{
|
|
/* get luxels */
|
|
vertLuxel = VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
|
|
radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
|
|
|
|
/* store */
|
|
if( bouncing || bounce == 0 || !bounceOnly )
|
|
VectorAdd( vertLuxel, radVertLuxel, vertLuxel );
|
|
ColorToBytes( vertLuxel, verts[ i ].color[ lightmapNum ], info->si->vertexScale );
|
|
}
|
|
}
|
|
|
|
/* free light list */
|
|
FreeTraceLights( &trace );
|
|
|
|
/* return to sender */
|
|
return;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------
|
|
reconstitute vertex lighting from the luxels
|
|
----------------------------------------------------------------- */
|
|
|
|
/* set styles from lightmap */
|
|
for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
|
|
ds->vertexStyles[ lightmapNum ] = lm->styles[ lightmapNum ];
|
|
|
|
/* get max search radius */
|
|
maxRadius = lm->sw;
|
|
maxRadius = maxRadius > lm->sh ? maxRadius : lm->sh;
|
|
|
|
/* walk the surface verts */
|
|
verts = yDrawVerts + ds->firstVert;
|
|
for( i = 0; i < ds->numVerts; i++ )
|
|
{
|
|
/* do each lightmap */
|
|
for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
|
|
{
|
|
/* early out */
|
|
if( lm->superLuxels[ lightmapNum ] == NULL )
|
|
continue;
|
|
|
|
/* get luxel coords */
|
|
x = verts[ i ].lightmap[ lightmapNum ][ 0 ];
|
|
y = verts[ i ].lightmap[ lightmapNum ][ 1 ];
|
|
if( x < 0 )
|
|
x = 0;
|
|
else if( x >= lm->sw )
|
|
x = lm->sw - 1;
|
|
if( y < 0 )
|
|
y = 0;
|
|
else if( y >= lm->sh )
|
|
y = lm->sh - 1;
|
|
|
|
/* get vertex luxels */
|
|
vertLuxel = VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
|
|
radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
|
|
|
|
/* color the luxel with the normal? */
|
|
if( normalmap )
|
|
{
|
|
radVertLuxel[ 0 ] = (verts[ i ].normal[ 0 ] + 1.0f) * 127.5f;
|
|
radVertLuxel[ 1 ] = (verts[ i ].normal[ 1 ] + 1.0f) * 127.5f;
|
|
radVertLuxel[ 2 ] = (verts[ i ].normal[ 2 ] + 1.0f) * 127.5f;
|
|
}
|
|
|
|
/* color the luxel with surface num? */
|
|
else if( debugSurfaces )
|
|
VectorCopy( debugColors[ num % 12 ], radVertLuxel );
|
|
|
|
/* divine color from the superluxels */
|
|
else
|
|
{
|
|
/* increasing radius */
|
|
VectorClear( radVertLuxel );
|
|
samples = 0.0f;
|
|
for( radius = 0; radius < maxRadius && samples <= 0.0f; radius++ )
|
|
{
|
|
/* sample within radius */
|
|
for( sy = (y - radius); sy <= (y + radius); sy++ )
|
|
{
|
|
if( sy < 0 || sy >= lm->sh )
|
|
continue;
|
|
|
|
for( sx = (x - radius); sx <= (x + radius); sx++ )
|
|
{
|
|
if( sx < 0 || sx >= lm->sw )
|
|
continue;
|
|
|
|
/* get luxel particulars */
|
|
luxel = SUPER_LUXEL( lightmapNum, sx, sy );
|
|
cluster = SUPER_CLUSTER( sx, sy );
|
|
if( *cluster < 0 )
|
|
continue;
|
|
|
|
/* testing: must be brigher than ambient color */
|
|
//% if( luxel[ 0 ] <= ambientColor[ 0 ] || luxel[ 1 ] <= ambientColor[ 1 ] || luxel[ 2 ] <= ambientColor[ 2 ] )
|
|
//% continue;
|
|
|
|
/* add its distinctiveness to our own */
|
|
VectorAdd( radVertLuxel, luxel, radVertLuxel );
|
|
samples += luxel[ 3 ];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* any color? */
|
|
if( samples > 0.0f )
|
|
VectorDivide( radVertLuxel, samples, radVertLuxel );
|
|
else
|
|
VectorCopy( ambientColor, radVertLuxel );
|
|
}
|
|
|
|
/* store into floating point storage */
|
|
VectorAdd( vertLuxel, radVertLuxel, vertLuxel );
|
|
numVertsIlluminated++;
|
|
|
|
/* store into bytes (for vertex approximation) */
|
|
ColorToBytes( vertLuxel, verts[ i ].color[ lightmapNum ], 1.0f );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------------------
|
|
|
|
light optimization (-fast)
|
|
|
|
creates a list of lights that will affect a surface and stores it in tw
|
|
this is to optimize surface lighting by culling out as many of the
|
|
lights in the world as possible from further calculation
|
|
|
|
------------------------------------------------------------------------------- */
|
|
|
|
/*
|
|
SetupBrushes()
|
|
determines opaque brushes in the world and find sky shaders for sunlight calculations
|
|
*/
|
|
|
|
void SetupBrushes( void )
|
|
{
|
|
int i, j, b, compileFlags;
|
|
qboolean inside;
|
|
bspBrush_t *brush;
|
|
bspBrushSide_t *side;
|
|
bspShader_t *shader;
|
|
shaderInfo_t *si;
|
|
|
|
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "--- SetupBrushes ---\n" );
|
|
|
|
/* allocate */
|
|
if( opaqueBrushes == NULL )
|
|
opaqueBrushes = safe_malloc( numBSPBrushes / 8 + 1 );
|
|
|
|
/* clear */
|
|
memset( opaqueBrushes, 0, numBSPBrushes / 8 + 1 );
|
|
numOpaqueBrushes = 0;
|
|
|
|
/* walk the list of worldspawn brushes */
|
|
for( i = 0; i < bspModels[ 0 ].numBSPBrushes; i++ )
|
|
{
|
|
/* get brush */
|
|
b = bspModels[ 0 ].firstBSPBrush + i;
|
|
brush = &bspBrushes[ b ];
|
|
|
|
/* check all sides */
|
|
inside = qtrue;
|
|
compileFlags = 0;
|
|
for( j = 0; j < brush->numSides && inside; j++ )
|
|
{
|
|
/* do bsp shader calculations */
|
|
side = &bspBrushSides[ brush->firstSide + j ];
|
|
shader = &bspShaders[ side->shaderNum ];
|
|
|
|
/* get shader info */
|
|
si = ShaderInfoForShader( shader->shader );
|
|
if( si == NULL )
|
|
continue;
|
|
|
|
/* or together compile flags */
|
|
compileFlags |= si->compileFlags;
|
|
}
|
|
|
|
/* determine if this brush is opaque to light */
|
|
if( !(compileFlags & C_TRANSLUCENT) )
|
|
{
|
|
opaqueBrushes[ b >> 3 ] |= (1 << (b & 7));
|
|
numOpaqueBrushes++;
|
|
maxOpaqueBrush = i;
|
|
}
|
|
}
|
|
|
|
/* emit some statistics */
|
|
Sys_FPrintf( SYS_VRB, "%9d opaque brushes\n", numOpaqueBrushes );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ClusterVisible()
|
|
determines if two clusters are visible to each other using the PVS
|
|
*/
|
|
|
|
qboolean ClusterVisible( int a, int b )
|
|
{
|
|
int portalClusters, leafBytes;
|
|
byte *pvs;
|
|
|
|
|
|
/* dummy check */
|
|
if( a < 0 || b < 0 )
|
|
return qfalse;
|
|
|
|
/* early out */
|
|
if( a == b )
|
|
return qtrue;
|
|
|
|
/* not vised? */
|
|
if( numBSPVisBytes <=8 )
|
|
return qtrue;
|
|
|
|
/* get pvs data */
|
|
portalClusters = ((int *) bspVisBytes)[ 0 ];
|
|
leafBytes = ((int*) bspVisBytes)[ 1 ];
|
|
pvs = bspVisBytes + VIS_HEADER_SIZE + (a * leafBytes);
|
|
|
|
/* check */
|
|
if( (pvs[ b >> 3 ] & (1 << (b & 7))) )
|
|
return qtrue;
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
PointInLeafNum_r()
|
|
borrowed from vlight.c
|
|
*/
|
|
|
|
int PointInLeafNum_r( vec3_t point, int nodenum )
|
|
{
|
|
int leafnum;
|
|
vec_t dist;
|
|
bspNode_t *node;
|
|
bspPlane_t *plane;
|
|
|
|
|
|
while( nodenum >= 0 )
|
|
{
|
|
node = &bspNodes[ nodenum ];
|
|
plane = &bspPlanes[ node->planeNum ];
|
|
dist = DotProduct( point, plane->normal ) - plane->dist;
|
|
if( dist > 0.1 )
|
|
nodenum = node->children[ 0 ];
|
|
else if( dist < -0.1 )
|
|
nodenum = node->children[ 1 ];
|
|
else
|
|
{
|
|
leafnum = PointInLeafNum_r( point, node->children[ 0 ] );
|
|
if( bspLeafs[ leafnum ].cluster != -1 )
|
|
return leafnum;
|
|
nodenum = node->children[ 1 ];
|
|
}
|
|
}
|
|
|
|
leafnum = -nodenum - 1;
|
|
return leafnum;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
PointInLeafnum()
|
|
borrowed from vlight.c
|
|
*/
|
|
|
|
int PointInLeafNum( vec3_t point )
|
|
{
|
|
return PointInLeafNum_r( point, 0 );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ClusterVisibleToPoint() - ydnar
|
|
returns qtrue if point can "see" cluster
|
|
*/
|
|
|
|
qboolean ClusterVisibleToPoint( vec3_t point, int cluster )
|
|
{
|
|
int pointCluster;
|
|
|
|
|
|
/* get leafNum for point */
|
|
pointCluster = ClusterForPoint( point );
|
|
if( pointCluster < 0 )
|
|
return qfalse;
|
|
|
|
/* check pvs */
|
|
return ClusterVisible( pointCluster, cluster );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ClusterForPoint() - ydnar
|
|
returns the pvs cluster for point
|
|
*/
|
|
|
|
int ClusterForPoint( vec3_t point )
|
|
{
|
|
int leafNum;
|
|
|
|
|
|
/* get leafNum for point */
|
|
leafNum = PointInLeafNum( point );
|
|
if( leafNum < 0 )
|
|
return -1;
|
|
|
|
/* return the cluster */
|
|
return bspLeafs[ leafNum ].cluster;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ClusterForPointExt() - ydnar
|
|
also takes brushes into account for occlusion testing
|
|
*/
|
|
|
|
int ClusterForPointExt( vec3_t point, float epsilon )
|
|
{
|
|
int i, j, b, leafNum, cluster;
|
|
float dot;
|
|
qboolean inside;
|
|
int *brushes, numBSPBrushes;
|
|
bspLeaf_t *leaf;
|
|
bspBrush_t *brush;
|
|
bspPlane_t *plane;
|
|
|
|
|
|
/* get leaf for point */
|
|
leafNum = PointInLeafNum( point );
|
|
if( leafNum < 0 )
|
|
return -1;
|
|
leaf = &bspLeafs[ leafNum ];
|
|
|
|
/* get the cluster */
|
|
cluster = leaf->cluster;
|
|
if( cluster < 0 )
|
|
return -1;
|
|
|
|
/* transparent leaf, so check point against all brushes in the leaf */
|
|
brushes = &bspLeafBrushes[ leaf->firstBSPLeafBrush ];
|
|
numBSPBrushes = leaf->numBSPLeafBrushes;
|
|
for( i = 0; i < numBSPBrushes; i++ )
|
|
{
|
|
/* get parts */
|
|
b = brushes[ i ];
|
|
if( b > maxOpaqueBrush )
|
|
continue;
|
|
brush = &bspBrushes[ b ];
|
|
if( !(opaqueBrushes[ b >> 3 ] & (1 << (b & 7))) )
|
|
continue;
|
|
|
|
/* check point against all planes */
|
|
inside = qtrue;
|
|
for( j = 0; j < brush->numSides && inside; j++ )
|
|
{
|
|
plane = &bspPlanes[ bspBrushSides[ brush->firstSide + j ].planeNum ];
|
|
dot = DotProduct( point, plane->normal );
|
|
dot -= plane->dist;
|
|
if( dot > epsilon )
|
|
inside = qfalse;
|
|
}
|
|
|
|
/* if inside, return bogus cluster */
|
|
if( inside )
|
|
return -1 - b;
|
|
}
|
|
|
|
/* if the point made it this far, it's not inside any opaque brushes */
|
|
return cluster;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ClusterForPointExtFilter() - ydnar
|
|
adds cluster checking against a list of known valid clusters
|
|
*/
|
|
|
|
int ClusterForPointExtFilter( vec3_t point, float epsilon, int numClusters, int *clusters )
|
|
{
|
|
int i, cluster;
|
|
|
|
|
|
/* get cluster for point */
|
|
cluster = ClusterForPointExt( point, epsilon );
|
|
|
|
/* check if filtering is necessary */
|
|
if( cluster < 0 || numClusters <= 0 || clusters == NULL )
|
|
return cluster;
|
|
|
|
/* filter */
|
|
for( i = 0; i < numClusters; i++ )
|
|
{
|
|
if( cluster == clusters[ i ] || ClusterVisible( cluster, clusters[ i ] ) )
|
|
return cluster;
|
|
}
|
|
|
|
/* failed */
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ShaderForPointInLeaf() - ydnar
|
|
checks a point against all brushes in a leaf, returning the shader of the brush
|
|
also sets the cumulative surface and content flags for the brush hit
|
|
*/
|
|
|
|
int ShaderForPointInLeaf( vec3_t point, int leafNum, float epsilon, int wantContentFlags, int wantSurfaceFlags, int *contentFlags, int *surfaceFlags )
|
|
{
|
|
int i, j;
|
|
float dot;
|
|
qboolean inside;
|
|
int *brushes, numBSPBrushes;
|
|
bspLeaf_t *leaf;
|
|
bspBrush_t *brush;
|
|
bspBrushSide_t *side;
|
|
bspPlane_t *plane;
|
|
bspShader_t *shader;
|
|
int allSurfaceFlags, allContentFlags;
|
|
|
|
|
|
/* clear things out first */
|
|
*surfaceFlags = 0;
|
|
*contentFlags = 0;
|
|
|
|
/* get leaf */
|
|
if( leafNum < 0 )
|
|
return -1;
|
|
leaf = &bspLeafs[ leafNum ];
|
|
|
|
/* transparent leaf, so check point against all brushes in the leaf */
|
|
brushes = &bspLeafBrushes[ leaf->firstBSPLeafBrush ];
|
|
numBSPBrushes = leaf->numBSPLeafBrushes;
|
|
for( i = 0; i < numBSPBrushes; i++ )
|
|
{
|
|
/* get parts */
|
|
brush = &bspBrushes[ brushes[ i ] ];
|
|
|
|
/* check point against all planes */
|
|
inside = qtrue;
|
|
allSurfaceFlags = 0;
|
|
allContentFlags = 0;
|
|
for( j = 0; j < brush->numSides && inside; j++ )
|
|
{
|
|
side = &bspBrushSides[ brush->firstSide + j ];
|
|
plane = &bspPlanes[ side->planeNum ];
|
|
dot = DotProduct( point, plane->normal );
|
|
dot -= plane->dist;
|
|
if( dot > epsilon )
|
|
inside = qfalse;
|
|
else
|
|
{
|
|
shader = &bspShaders[ side->shaderNum ];
|
|
allSurfaceFlags |= shader->surfaceFlags;
|
|
allContentFlags |= shader->contentFlags;
|
|
}
|
|
}
|
|
|
|
/* handle if inside */
|
|
if( inside )
|
|
{
|
|
/* if there are desired flags, check for same and continue if they aren't matched */
|
|
if( wantContentFlags && !(wantContentFlags & allContentFlags) )
|
|
continue;
|
|
if( wantSurfaceFlags && !(wantSurfaceFlags & allSurfaceFlags) )
|
|
continue;
|
|
|
|
/* store the cumulative flags and return the brush shader (which is mostly useless) */
|
|
*surfaceFlags = allSurfaceFlags;
|
|
*contentFlags = allContentFlags;
|
|
return brush->shaderNum;
|
|
}
|
|
}
|
|
|
|
/* if the point made it this far, it's not inside any brushes */
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ChopBounds()
|
|
chops a bounding box by the plane defined by origin and normal
|
|
returns qfalse if the bounds is entirely clipped away
|
|
|
|
this is not exactly the fastest way to do this...
|
|
*/
|
|
|
|
qboolean ChopBounds( vec3_t mins, vec3_t maxs, vec3_t origin, vec3_t normal )
|
|
{
|
|
/* FIXME: rewrite this so it doesn't use bloody brushes */
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
SetupEnvelopes()
|
|
calculates each light's effective envelope,
|
|
taking into account brightness, type, and pvs.
|
|
*/
|
|
|
|
#define LIGHT_EPSILON 0.125f
|
|
#define LIGHT_NUDGE 2.0f
|
|
|
|
void SetupEnvelopes( qboolean forGrid, qboolean fastFlag )
|
|
{
|
|
int i, x, y, z, x1, y1, z1;
|
|
light_t *light, *light2, **owner;
|
|
bspLeaf_t *leaf;
|
|
vec3_t origin, dir, mins, maxs, nullVector = { 0, 0, 0 };
|
|
float radius, intensity;
|
|
light_t *buckets[ 256 ];
|
|
|
|
|
|
/* early out for weird cases where there are no lights */
|
|
if( lights == NULL )
|
|
return;
|
|
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "--- SetupEnvelopes%s ---\n", fastFlag ? " (fast)" : "" );
|
|
|
|
/* count lights */
|
|
numLights = 0;
|
|
numCulledLights = 0;
|
|
owner = &lights;
|
|
while( *owner != NULL )
|
|
{
|
|
/* get light */
|
|
light = *owner;
|
|
|
|
/* handle negative lights */
|
|
if( light->photons < 0.0f || light->add < 0.0f )
|
|
{
|
|
light->photons *= -1.0f;
|
|
light->add *= -1.0f;
|
|
light->flags |= LIGHT_NEGATIVE;
|
|
}
|
|
|
|
/* sunlight? */
|
|
if( light->type == EMIT_SUN )
|
|
{
|
|
/* special cased */
|
|
light->cluster = 0;
|
|
light->envelope = MAX_WORLD_COORD * 8.0f;
|
|
VectorSet( light->mins, MIN_WORLD_COORD * 8.0f, MIN_WORLD_COORD * 8.0f, MIN_WORLD_COORD * 8.0f );
|
|
VectorSet( light->maxs, MAX_WORLD_COORD * 8.0f, MAX_WORLD_COORD * 8.0f, MAX_WORLD_COORD * 8.0f );
|
|
}
|
|
|
|
/* everything else */
|
|
else
|
|
{
|
|
/* get pvs cluster for light */
|
|
light->cluster = ClusterForPointExt( light->origin, LIGHT_EPSILON );
|
|
|
|
/* invalid cluster? */
|
|
if( light->cluster < 0 )
|
|
{
|
|
/* nudge the sample point around a bit */
|
|
for( x = 0; x < 4; x++ )
|
|
{
|
|
/* two's complement 0, 1, -1, 2, -2, etc */
|
|
x1 = ((x >> 1) ^ (x & 1 ? -1 : 0)) + (x & 1);
|
|
|
|
for( y = 0; y < 4; y++ )
|
|
{
|
|
y1 = ((y >> 1) ^ (y & 1 ? -1 : 0)) + (y & 1);
|
|
|
|
for( z = 0; z < 4; z++ )
|
|
{
|
|
z1 = ((z >> 1) ^ (z & 1 ? -1 : 0)) + (z & 1);
|
|
|
|
/* nudge origin */
|
|
origin[ 0 ] = light->origin[ 0 ] + (LIGHT_NUDGE * x1);
|
|
origin[ 1 ] = light->origin[ 1 ] + (LIGHT_NUDGE * y1);
|
|
origin[ 2 ] = light->origin[ 2 ] + (LIGHT_NUDGE * z1);
|
|
|
|
/* try at nudged origin */
|
|
light->cluster = ClusterForPointExt( origin, LIGHT_EPSILON );
|
|
if( light->cluster < 0 )
|
|
continue;
|
|
|
|
/* set origin */
|
|
VectorCopy( origin, light->origin );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* only calculate for lights in pvs and outside of opaque brushes */
|
|
if( light->cluster >= 0 )
|
|
{
|
|
/* set light fast flag */
|
|
if( fastFlag )
|
|
light->flags |= LIGHT_FAST_TEMP;
|
|
else
|
|
light->flags &= ~LIGHT_FAST_TEMP;
|
|
if( light->si && light->si->noFast )
|
|
light->flags &= ~(LIGHT_FAST | LIGHT_FAST_TEMP);
|
|
|
|
/* clear light envelope */
|
|
light->envelope = 0;
|
|
|
|
/* handle area lights */
|
|
if( exactPointToPolygon && light->type == EMIT_AREA && light->w != NULL )
|
|
{
|
|
/* ugly hack to calculate extent for area lights, but only done once */
|
|
VectorScale( light->normal, -1.0f, dir );
|
|
for( radius = 100.0f; radius < 130000.0f && light->envelope == 0; radius += 10.0f )
|
|
{
|
|
float factor;
|
|
|
|
VectorMA( light->origin, radius, light->normal, origin );
|
|
factor = PointToPolygonFormFactor( origin, dir, light->w );
|
|
if( factor < 0.0f )
|
|
factor *= -1.0f;
|
|
if( (factor * light->add) <= light->falloffTolerance )
|
|
light->envelope = radius;
|
|
}
|
|
|
|
/* check for fast mode */
|
|
if( !(light->flags & LIGHT_FAST) && !(light->flags & LIGHT_FAST_TEMP) )
|
|
light->envelope = MAX_WORLD_COORD * 8.0f;
|
|
}
|
|
else
|
|
{
|
|
radius = 0.0f;
|
|
intensity = light->photons;
|
|
}
|
|
|
|
/* other calcs */
|
|
if( light->envelope <= 0.0f )
|
|
{
|
|
/* solve distance for non-distance lights */
|
|
if( !(light->flags & LIGHT_ATTEN_DISTANCE) )
|
|
light->envelope = MAX_WORLD_COORD * 8.0f;
|
|
|
|
/* solve distance for linear lights */
|
|
else if( (light->flags & LIGHT_ATTEN_LINEAR ) )
|
|
//% light->envelope = ((intensity / light->falloffTolerance) * linearScale - 1 + radius) / light->fade;
|
|
light->envelope = ((intensity * linearScale) - light->falloffTolerance) / light->fade;
|
|
|
|
/*
|
|
add = angle * light->photons * linearScale - (dist * light->fade);
|
|
T = (light->photons * linearScale) - (dist * light->fade);
|
|
T + (dist * light->fade) = (light->photons * linearScale);
|
|
dist * light->fade = (light->photons * linearScale) - T;
|
|
dist = ((light->photons * linearScale) - T) / light->fade;
|
|
*/
|
|
|
|
/* solve for inverse square falloff */
|
|
else
|
|
light->envelope = sqrt( intensity / light->falloffTolerance ) + radius;
|
|
|
|
/*
|
|
add = light->photons / (dist * dist);
|
|
T = light->photons / (dist * dist);
|
|
T * (dist * dist) = light->photons;
|
|
dist = sqrt( light->photons / T );
|
|
*/
|
|
}
|
|
|
|
/* chop radius against pvs */
|
|
{
|
|
/* clear bounds */
|
|
ClearBounds( mins, maxs );
|
|
|
|
/* check all leaves */
|
|
for( i = 0; i < numBSPLeafs; i++ )
|
|
{
|
|
/* get test leaf */
|
|
leaf = &bspLeafs[ i ];
|
|
|
|
/* in pvs? */
|
|
if( leaf->cluster < 0 )
|
|
continue;
|
|
if( ClusterVisible( light->cluster, leaf->cluster ) == qfalse ) /* ydnar: thanks Arnout for exposing my stupid error (this never failed before) */
|
|
continue;
|
|
|
|
/* add this leafs bbox to the bounds */
|
|
VectorCopy( leaf->mins, origin );
|
|
AddPointToBounds( origin, mins, maxs );
|
|
VectorCopy( leaf->maxs, origin );
|
|
AddPointToBounds( origin, mins, maxs );
|
|
}
|
|
|
|
/* test to see if bounds encompass light */
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
if( mins[ i ] > light->origin[ i ] || maxs[ i ] < light->origin[ i ] )
|
|
{
|
|
//% Sys_Printf( "WARNING: Light PVS bounds (%.0f, %.0f, %.0f) -> (%.0f, %.0f, %.0f)\ndo not encompass light %d (%f, %f, %f)\n",
|
|
//% mins[ 0 ], mins[ 1 ], mins[ 2 ],
|
|
//% maxs[ 0 ], maxs[ 1 ], maxs[ 2 ],
|
|
//% numLights, light->origin[ 0 ], light->origin[ 1 ], light->origin[ 2 ] );
|
|
AddPointToBounds( light->origin, mins, maxs );
|
|
}
|
|
}
|
|
|
|
/* chop the bounds by a plane for area lights and spotlights */
|
|
if( light->type == EMIT_AREA || light->type == EMIT_SPOT )
|
|
ChopBounds( mins, maxs, light->origin, light->normal );
|
|
|
|
/* copy bounds */
|
|
VectorCopy( mins, light->mins );
|
|
VectorCopy( maxs, light->maxs );
|
|
|
|
/* reflect bounds around light origin */
|
|
//% VectorMA( light->origin, -1.0f, origin, origin );
|
|
VectorScale( light->origin, 2, origin );
|
|
VectorSubtract( origin, maxs, origin );
|
|
AddPointToBounds( origin, mins, maxs );
|
|
//% VectorMA( light->origin, -1.0f, mins, origin );
|
|
VectorScale( light->origin, 2, origin );
|
|
VectorSubtract( origin, mins, origin );
|
|
AddPointToBounds( origin, mins, maxs );
|
|
|
|
/* calculate spherical bounds */
|
|
VectorSubtract( maxs, light->origin, dir );
|
|
radius = (float) VectorLength( dir );
|
|
|
|
/* if this radius is smaller than the envelope, then set the envelope to it */
|
|
if( radius < light->envelope )
|
|
{
|
|
light->envelope = radius;
|
|
//% Sys_FPrintf( SYS_VRB, "PVS Cull (%d): culled\n", numLights );
|
|
}
|
|
//% else
|
|
//% Sys_FPrintf( SYS_VRB, "PVS Cull (%d): failed (%8.0f > %8.0f)\n", numLights, radius, light->envelope );
|
|
}
|
|
|
|
/* add grid/surface only check */
|
|
if( forGrid )
|
|
{
|
|
if( !(light->flags & LIGHT_GRID) )
|
|
light->envelope = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
if( !(light->flags & LIGHT_SURFACES) )
|
|
light->envelope = 0.0f;
|
|
}
|
|
}
|
|
|
|
/* culled? */
|
|
if( light->cluster < 0 || light->envelope <= 0.0f )
|
|
{
|
|
/* debug code */
|
|
//% Sys_Printf( "Culling light: Cluster: %d Envelope: %f\n", light->cluster, light->envelope );
|
|
|
|
/* delete the light */
|
|
numCulledLights++;
|
|
*owner = light->next;
|
|
if( light->w != NULL )
|
|
free( light->w );
|
|
free( light );
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* square envelope */
|
|
light->envelope2 = (light->envelope * light->envelope);
|
|
|
|
/* increment light count */
|
|
numLights++;
|
|
|
|
/* set next light */
|
|
owner = &((**owner).next);
|
|
}
|
|
|
|
/* bucket sort lights by style */
|
|
memset( buckets, 0, sizeof( buckets ) );
|
|
light2 = NULL;
|
|
for( light = lights; light != NULL; light = light2 )
|
|
{
|
|
/* get next light */
|
|
light2 = light->next;
|
|
|
|
/* filter into correct bucket */
|
|
light->next = buckets[ light->style ];
|
|
buckets[ light->style ] = light;
|
|
}
|
|
|
|
/* filter back into light list */
|
|
lights = NULL;
|
|
for( i = 255; i >= 0; i-- )
|
|
{
|
|
light2 = NULL;
|
|
for( light = buckets[ i ]; light != NULL; light = light2 )
|
|
{
|
|
light2 = light->next;
|
|
light->next = lights;
|
|
lights = light;
|
|
}
|
|
}
|
|
|
|
/* emit some statistics */
|
|
Sys_Printf( "%9d total lights\n", numLights );
|
|
Sys_Printf( "%9d culled lights\n", numCulledLights );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
CreateTraceLightsForBounds()
|
|
creates a list of lights that affect the given bounding box and pvs clusters (bsp leaves)
|
|
*/
|
|
|
|
void CreateTraceLightsForBounds( vec3_t mins, vec3_t maxs, vec3_t normal, int numClusters, int *clusters, int flags, trace_t *trace )
|
|
{
|
|
int i;
|
|
light_t *light;
|
|
vec3_t origin, dir, nullVector = { 0.0f, 0.0f, 0.0f };
|
|
float radius, dist, length;
|
|
|
|
|
|
/* potential pre-setup */
|
|
if( numLights == 0 )
|
|
SetupEnvelopes( qfalse, fast );
|
|
|
|
/* debug code */
|
|
//% Sys_Printf( "CTWLFB: (%4.1f %4.1f %4.1f) (%4.1f %4.1f %4.1f)\n", mins[ 0 ], mins[ 1 ], mins[ 2 ], maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] );
|
|
|
|
/* allocate the light list */
|
|
trace->lights = safe_malloc( sizeof( light_t* ) * (numLights + 1) );
|
|
trace->numLights = 0;
|
|
|
|
/* calculate spherical bounds */
|
|
VectorAdd( mins, maxs, origin );
|
|
VectorScale( origin, 0.5f, origin );
|
|
VectorSubtract( maxs, origin, dir );
|
|
radius = (float) VectorLength( dir );
|
|
|
|
/* get length of normal vector */
|
|
if( normal != NULL )
|
|
length = VectorLength( normal );
|
|
else
|
|
{
|
|
normal = nullVector;
|
|
length = 0;
|
|
}
|
|
|
|
/* test each light and see if it reaches the sphere */
|
|
/* note: the attenuation code MUST match LightingAtSample() */
|
|
for( light = lights; light; light = light->next )
|
|
{
|
|
/* check zero sized envelope */
|
|
if( light->envelope <= 0 )
|
|
{
|
|
lightsEnvelopeCulled++;
|
|
continue;
|
|
}
|
|
|
|
/* check flags */
|
|
if( !(light->flags & flags) )
|
|
continue;
|
|
|
|
/* sunlight skips all this nonsense */
|
|
if( light->type != EMIT_SUN )
|
|
{
|
|
/* sun only? */
|
|
if( sunOnly )
|
|
continue;
|
|
|
|
/* check against pvs cluster */
|
|
if( numClusters > 0 && clusters != NULL )
|
|
{
|
|
for( i = 0; i < numClusters; i++ )
|
|
{
|
|
if( ClusterVisible( light->cluster, clusters[ i ] ) )
|
|
break;
|
|
}
|
|
|
|
/* fixme! */
|
|
if( i == numClusters )
|
|
{
|
|
lightsClusterCulled++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* if the light's bounding sphere intersects with the bounding sphere then this light needs to be tested */
|
|
VectorSubtract( light->origin, origin, dir );
|
|
dist = VectorLength( dir );
|
|
dist -= light->envelope;
|
|
dist -= radius;
|
|
if( dist > 0 )
|
|
{
|
|
lightsEnvelopeCulled++;
|
|
continue;
|
|
}
|
|
|
|
/* check bounding box against light's pvs envelope (note: this code never eliminated any lights, so disabling it) */
|
|
#if 0
|
|
skip = qfalse;
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
if( mins[ i ] > light->maxs[ i ] || maxs[ i ] < light->mins[ i ] )
|
|
skip = qtrue;
|
|
}
|
|
if( skip )
|
|
{
|
|
lightsBoundsCulled++;
|
|
continue;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* planar surfaces (except twosided surfaces) have a couple more checks */
|
|
if( length > 0.0f && trace->twoSided == qfalse )
|
|
{
|
|
/* lights coplanar with a surface won't light it */
|
|
if( !(light->flags & LIGHT_TWOSIDED) && DotProduct( light->normal, normal ) > 0.999f )
|
|
{
|
|
lightsPlaneCulled++;
|
|
continue;
|
|
}
|
|
|
|
/* check to see if light is behind the plane */
|
|
if( DotProduct( light->origin, normal ) - DotProduct( origin, normal ) < -1.0f )
|
|
{
|
|
lightsPlaneCulled++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* add this light */
|
|
trace->lights[ trace->numLights++ ] = light;
|
|
}
|
|
|
|
/* make last night null */
|
|
trace->lights[ trace->numLights ] = NULL;
|
|
}
|
|
|
|
|
|
|
|
void FreeTraceLights( trace_t *trace )
|
|
{
|
|
if( trace->lights != NULL )
|
|
free( trace->lights );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
CreateTraceLightsForSurface()
|
|
creates a list of lights that can potentially affect a drawsurface
|
|
*/
|
|
|
|
void CreateTraceLightsForSurface( int num, trace_t *trace )
|
|
{
|
|
int i;
|
|
vec3_t mins, maxs, normal;
|
|
bspDrawVert_t *dv;
|
|
bspDrawSurface_t *ds;
|
|
surfaceInfo_t *info;
|
|
|
|
|
|
/* dummy check */
|
|
if( num < 0 )
|
|
return;
|
|
|
|
/* get drawsurface and info */
|
|
ds = &bspDrawSurfaces[ num ];
|
|
info = &surfaceInfos[ num ];
|
|
|
|
/* get the mins/maxs for the dsurf */
|
|
ClearBounds( mins, maxs );
|
|
VectorCopy( bspDrawVerts[ ds->firstVert ].normal, normal );
|
|
for( i = 0; i < ds->numVerts; i++ )
|
|
{
|
|
dv = &yDrawVerts[ ds->firstVert + i ];
|
|
AddPointToBounds( dv->xyz, mins, maxs );
|
|
if( !VectorCompare( dv->normal, normal ) )
|
|
VectorClear( normal );
|
|
}
|
|
|
|
/* create the lights for the bounding box */
|
|
CreateTraceLightsForBounds( mins, maxs, normal, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ], LIGHT_SURFACES, trace );
|
|
}
|
|
|
|
|
|
|
|
|
|
|