mirror of
https://github.com/alliedmodders/hl2sdk.git
synced 2025-12-11 16:18:41 +00:00
4398 lines
129 KiB
C++
4398 lines
129 KiB
C++
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//
|
|
//=============================================================================//
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#endif
|
|
#include "vrad.h"
|
|
#include "lightmap.h"
|
|
#include "radial.h"
|
|
#include <bumpvects.h>
|
|
#include "tier1/utlvector.h"
|
|
#include "vmpi.h"
|
|
#include "anorms.h"
|
|
#include "map_utils.h"
|
|
#include "mathlib/halton.h"
|
|
#include "imagepacker.h"
|
|
#include "tier1/utlrbtree.h"
|
|
#include "tier1/utlbuffer.h"
|
|
#include "bitmap/tgawriter.h"
|
|
#include "vtf/swizzler.h"
|
|
#include "quantize.h"
|
|
#include "bitmap/imageformat.h"
|
|
|
|
enum
|
|
{
|
|
AMBIENT_ONLY = 0x1,
|
|
NON_AMBIENT_ONLY = 0x2,
|
|
};
|
|
|
|
#define SMOOTHING_GROUP_HARD_EDGE 0xff000000
|
|
|
|
//==========================================================================//
|
|
// CNormalList.
|
|
//==========================================================================//
|
|
|
|
// This class keeps a list of unique normals and provides a fast
|
|
class CNormalList
|
|
{
|
|
public:
|
|
CNormalList();
|
|
|
|
// Adds the normal if unique. Otherwise, returns the normal's index into m_Normals.
|
|
int FindOrAddNormal( Vector const &vNormal );
|
|
|
|
|
|
public:
|
|
|
|
CUtlVector<Vector> m_Normals;
|
|
|
|
|
|
private:
|
|
|
|
// This represents a grid from (-1,-1,-1) to (1,1,1).
|
|
enum {NUM_SUBDIVS = 8};
|
|
CUtlVector<int> m_NormalGrid[NUM_SUBDIVS][NUM_SUBDIVS][NUM_SUBDIVS];
|
|
};
|
|
|
|
|
|
int g_iCurFace;
|
|
edgeshare_t edgeshare[MAX_MAP_EDGES];
|
|
|
|
Vector face_centroids[MAX_MAP_EDGES];
|
|
|
|
int vertexref[MAX_MAP_VERTS];
|
|
int *vertexface[MAX_MAP_VERTS];
|
|
faceneighbor_t faceneighbor[MAX_MAP_FACES];
|
|
|
|
static directlight_t *gSkyLight = NULL;
|
|
static directlight_t *gAmbient = NULL;
|
|
|
|
//==========================================================================//
|
|
// CNormalList implementation.
|
|
//==========================================================================//
|
|
|
|
CNormalList::CNormalList() : m_Normals( 128 )
|
|
{
|
|
for( int i=0; i < sizeof(m_NormalGrid)/sizeof(m_NormalGrid[0][0][0]); i++ )
|
|
{
|
|
(&m_NormalGrid[0][0][0] + i)->SetGrowSize( 16 );
|
|
}
|
|
}
|
|
|
|
int CNormalList::FindOrAddNormal( Vector const &vNormal )
|
|
{
|
|
int gi[3];
|
|
|
|
// See which grid element it's in.
|
|
for( int iDim=0; iDim < 3; iDim++ )
|
|
{
|
|
gi[iDim] = (int)( ((vNormal[iDim] + 1.0f) * 0.5f) * NUM_SUBDIVS - 0.000001f );
|
|
gi[iDim] = MIN( gi[iDim], NUM_SUBDIVS );
|
|
gi[iDim] = MAX( gi[iDim], 0 );
|
|
}
|
|
|
|
// Look for a matching vector in there.
|
|
CUtlVector<int> *pGridElement = &m_NormalGrid[gi[0]][gi[1]][gi[2]];
|
|
for( int i=0; i < pGridElement->Size(); i++ )
|
|
{
|
|
int iNormal = pGridElement->Element(i);
|
|
|
|
Vector *pVec = &m_Normals[iNormal];
|
|
//if( pVec->DistToSqr(vNormal) < 0.00001f )
|
|
if( *pVec == vNormal )
|
|
return iNormal;
|
|
}
|
|
|
|
// Ok, add a new one.
|
|
pGridElement->AddToTail( m_Normals.Size() );
|
|
return m_Normals.AddToTail( vNormal );
|
|
}
|
|
|
|
// FIXME: HACK until the plane normals are made more happy
|
|
void GetBumpNormals( const float* sVect, const float* tVect, const Vector& flatNormal,
|
|
const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] )
|
|
{
|
|
Vector stmp( sVect[0], sVect[1], sVect[2] );
|
|
Vector ttmp( tVect[0], tVect[1], tVect[2] );
|
|
GetBumpNormals( stmp, ttmp, flatNormal, phongNormal, bumpNormals );
|
|
}
|
|
|
|
int EdgeVertex( dface_t *f, int edge )
|
|
{
|
|
int k;
|
|
|
|
if (edge < 0)
|
|
edge += f->numedges;
|
|
else if (edge >= f->numedges)
|
|
edge = edge % f->numedges;
|
|
|
|
k = dsurfedges[f->firstedge + edge];
|
|
if (k < 0)
|
|
{
|
|
// Msg("(%d %d) ", dedges[-k].v[1], dedges[-k].v[0] );
|
|
return dedges[-k].v[1];
|
|
}
|
|
else
|
|
{
|
|
// Msg("(%d %d) ", dedges[k].v[0], dedges[k].v[1] );
|
|
return dedges[k].v[0];
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
PairEdges
|
|
============
|
|
*/
|
|
void PairEdges (void)
|
|
{
|
|
int i, j, k, n, m;
|
|
dface_t *f;
|
|
int numneighbors;
|
|
int tmpneighbor[64];
|
|
faceneighbor_t *fn;
|
|
|
|
// count number of faces that reference each vertex
|
|
for (i=0, f = g_pFaces; i<numfaces ; i++, f++)
|
|
{
|
|
for (j=0 ; j<f->numedges ; j++)
|
|
{
|
|
// Store the count in vertexref
|
|
vertexref[EdgeVertex(f,j)]++;
|
|
}
|
|
}
|
|
|
|
// allocate room
|
|
for (i = 0; i < numvertexes; i++)
|
|
{
|
|
// use the count from above to allocate a big enough array
|
|
vertexface[i] = ( int* )calloc( vertexref[i], sizeof( vertexface[0] ) );
|
|
// clear the temporary data
|
|
vertexref[i] = 0;
|
|
}
|
|
|
|
// store a list of every face that uses a perticular vertex
|
|
for (i=0, f = g_pFaces ; i<numfaces ; i++, f++)
|
|
{
|
|
for (j=0 ; j<f->numedges ; j++)
|
|
{
|
|
n = EdgeVertex(f,j);
|
|
|
|
for (k = 0; k < vertexref[n]; k++)
|
|
{
|
|
if (vertexface[n][k] == i)
|
|
break;
|
|
}
|
|
if (k >= vertexref[n])
|
|
{
|
|
// add the face to the list
|
|
vertexface[n][k] = i;
|
|
vertexref[n]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// calc normals and set displacement surface flag
|
|
for (i=0, f = g_pFaces; i<numfaces ; i++, f++)
|
|
{
|
|
fn = &faceneighbor[i];
|
|
|
|
// get face normal
|
|
VectorCopy( dplanes[f->planenum].normal, fn->facenormal );
|
|
|
|
// set displacement surface flag
|
|
fn->bHasDisp = false;
|
|
if( ValidDispFace( f ) )
|
|
{
|
|
fn->bHasDisp = true;
|
|
}
|
|
}
|
|
|
|
// find neighbors
|
|
for (i=0, f = g_pFaces ; i<numfaces ; i++, f++)
|
|
{
|
|
numneighbors = 0;
|
|
fn = &faceneighbor[i];
|
|
|
|
// allocate room for vertex normals
|
|
fn->normal = ( Vector* )calloc( f->numedges, sizeof( fn->normal[0] ) );
|
|
|
|
// look up all faces sharing vertices and add them to the list
|
|
for (j=0 ; j<f->numedges ; j++)
|
|
{
|
|
n = EdgeVertex(f,j);
|
|
|
|
for (k = 0; k < vertexref[n]; k++)
|
|
{
|
|
double cos_normals_angle;
|
|
Vector *pNeighbornormal;
|
|
|
|
// skip self
|
|
if (vertexface[n][k] == i)
|
|
continue;
|
|
|
|
// if this face doens't have a displacement -- don't consider displacement neighbors
|
|
if( ( !fn->bHasDisp ) && ( faceneighbor[vertexface[n][k]].bHasDisp ) )
|
|
continue;
|
|
|
|
pNeighbornormal = &faceneighbor[vertexface[n][k]].facenormal;
|
|
cos_normals_angle = DotProduct( *pNeighbornormal, fn->facenormal );
|
|
|
|
// add normal if >= threshold or its a displacement surface (this is only if the original
|
|
// face is a displacement)
|
|
if ( fn->bHasDisp )
|
|
{
|
|
// Always smooth with and against a displacement surface.
|
|
VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] );
|
|
}
|
|
else
|
|
{
|
|
// No smoothing - use of method (backwards compatibility).
|
|
if ( ( f->smoothingGroups == 0 ) && ( g_pFaces[vertexface[n][k]].smoothingGroups == 0 ) )
|
|
{
|
|
if ( cos_normals_angle >= smoothing_threshold )
|
|
{
|
|
VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] );
|
|
}
|
|
else
|
|
{
|
|
// not considered a neighbor
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsigned int smoothingGroup = ( f->smoothingGroups & g_pFaces[vertexface[n][k]].smoothingGroups );
|
|
|
|
// Hard edge.
|
|
if ( ( smoothingGroup & SMOOTHING_GROUP_HARD_EDGE ) != 0 )
|
|
continue;
|
|
|
|
if ( smoothingGroup != 0 )
|
|
{
|
|
VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] );
|
|
}
|
|
else
|
|
{
|
|
// not considered a neighbor
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// look to see if we've already added this one
|
|
for (m = 0; m < numneighbors; m++)
|
|
{
|
|
if (tmpneighbor[m] == vertexface[n][k])
|
|
break;
|
|
}
|
|
|
|
if (m >= numneighbors)
|
|
{
|
|
// add to neighbor list
|
|
tmpneighbor[m] = vertexface[n][k];
|
|
numneighbors++;
|
|
if ( numneighbors > ARRAYSIZE(tmpneighbor) )
|
|
{
|
|
Error("Stack overflow in neighbors\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (numneighbors)
|
|
{
|
|
// copy over neighbor list
|
|
fn->numneighbors = numneighbors;
|
|
fn->neighbor = ( int* )calloc( numneighbors, sizeof( fn->neighbor[0] ) );
|
|
for (m = 0; m < numneighbors; m++)
|
|
{
|
|
fn->neighbor[m] = tmpneighbor[m];
|
|
}
|
|
}
|
|
|
|
// fixup normals
|
|
for (j = 0; j < f->numedges; j++)
|
|
{
|
|
VectorAdd( fn->normal[j], fn->facenormal, fn->normal[j] );
|
|
VectorNormalize( fn->normal[j] );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SaveVertexNormals( void )
|
|
{
|
|
faceneighbor_t *fn;
|
|
int i, j;
|
|
dface_t *f;
|
|
CNormalList normalList;
|
|
|
|
g_numvertnormalindices = 0;
|
|
|
|
for( i = 0 ;i<numfaces ; i++ )
|
|
{
|
|
fn = &faceneighbor[i];
|
|
f = &g_pFaces[i];
|
|
|
|
for( j = 0; j < f->numedges; j++ )
|
|
{
|
|
Vector vNormal;
|
|
if( fn->normal )
|
|
{
|
|
vNormal = fn->normal[j];
|
|
}
|
|
else
|
|
{
|
|
// original faces don't have normals
|
|
vNormal.Init( 0, 0, 0 );
|
|
}
|
|
|
|
if( g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES )
|
|
{
|
|
Error( "g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES" );
|
|
}
|
|
|
|
g_vertnormalindices[g_numvertnormalindices] = (unsigned short)normalList.FindOrAddNormal( vNormal );
|
|
g_numvertnormalindices++;
|
|
}
|
|
}
|
|
|
|
if( normalList.m_Normals.Size() > MAX_MAP_VERTNORMALS )
|
|
{
|
|
Error( "g_numvertnormals > MAX_MAP_VERTNORMALS" );
|
|
}
|
|
|
|
// Copy the list of unique vert normals into g_vertnormals.
|
|
g_numvertnormals = normalList.m_Normals.Size();
|
|
memcpy( g_vertnormals, normalList.m_Normals.Base(), sizeof(g_vertnormals[0]) * normalList.m_Normals.Size() );
|
|
}
|
|
|
|
/*
|
|
=================================================================
|
|
|
|
LIGHTMAP SAMPLE GENERATION
|
|
|
|
=================================================================
|
|
*/
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Spits out an error message with information about a lightinfo_t.
|
|
// Input : s - Error message string.
|
|
// l - lightmap info struct.
|
|
//-----------------------------------------------------------------------------
|
|
void ErrorLightInfo(const char *s, lightinfo_t *l)
|
|
{
|
|
texinfo_t *tex = &texinfo[l->face->texinfo];
|
|
winding_t *w = WindingFromFace(&g_pFaces[l->facenum], l->modelorg);
|
|
|
|
//
|
|
// Show the face center and material name if possible.
|
|
//
|
|
if (w != NULL)
|
|
{
|
|
// Don't exit, we'll try to recover...
|
|
Vector vecCenter;
|
|
WindingCenter(w, vecCenter);
|
|
// FreeWinding(w);
|
|
|
|
Warning("%s at (%g, %g, %g)\n\tmaterial=%s\n", s, (double)vecCenter.x, (double)vecCenter.y, (double)vecCenter.z, TexDataStringTable_GetString( dtexdata[tex->texdata].nameStringTableID ) );
|
|
}
|
|
//
|
|
// If not, just show the material name.
|
|
//
|
|
else
|
|
{
|
|
Warning("%s at (degenerate face)\n\tmaterial=%s\n", TexDataStringTable_GetString( dtexdata[tex->texdata].nameStringTableID ));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CalcFaceVectors(lightinfo_t *l)
|
|
{
|
|
texinfo_t *tex;
|
|
int i, j;
|
|
|
|
tex = &texinfo[l->face->texinfo];
|
|
|
|
// move into lightinfo_t
|
|
for (i=0 ; i<2 ; i++)
|
|
{
|
|
for (j=0 ; j<3 ; j++)
|
|
{
|
|
l->worldToLuxelSpace[i][j] = tex->lightmapVecsLuxelsPerWorldUnits[i][j];
|
|
}
|
|
}
|
|
|
|
//Solve[ { x * w00 + y * w01 + z * w02 - s == 0, x * w10 + y * w11 + z * w12 - t == 0, A * x + B * y + C * z + D == 0 }, { x, y, z } ]
|
|
//Rule(x,( C*s*w11 - B*s*w12 + B*t*w02 - C*t*w01 + D*w02*w11 - D*w01*w12) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )),
|
|
//Rule(y,( A*s*w12 - C*s*w10 + C*t*w00 - A*t*w02 + D*w00*w12 - D*w02*w10) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )),
|
|
//Rule(z,( B*s*w10 - A*s*w11 + A*t*w01 - B*t*w00 + D*w01*w10 - D*w00*w11) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 ))))
|
|
|
|
Vector luxelSpaceCross;
|
|
|
|
luxelSpaceCross[0] =
|
|
tex->lightmapVecsLuxelsPerWorldUnits[1][1] * tex->lightmapVecsLuxelsPerWorldUnits[0][2] -
|
|
tex->lightmapVecsLuxelsPerWorldUnits[1][2] * tex->lightmapVecsLuxelsPerWorldUnits[0][1];
|
|
luxelSpaceCross[1] =
|
|
tex->lightmapVecsLuxelsPerWorldUnits[1][2] * tex->lightmapVecsLuxelsPerWorldUnits[0][0] -
|
|
tex->lightmapVecsLuxelsPerWorldUnits[1][0] * tex->lightmapVecsLuxelsPerWorldUnits[0][2];
|
|
luxelSpaceCross[2] =
|
|
tex->lightmapVecsLuxelsPerWorldUnits[1][0] * tex->lightmapVecsLuxelsPerWorldUnits[0][1] -
|
|
tex->lightmapVecsLuxelsPerWorldUnits[1][1] * tex->lightmapVecsLuxelsPerWorldUnits[0][0];
|
|
|
|
float det = -DotProduct( l->facenormal, luxelSpaceCross );
|
|
|
|
// invert the matrix
|
|
l->luxelToWorldSpace[0][0] = (l->facenormal[2] * l->worldToLuxelSpace[1][1] - l->facenormal[1] * l->worldToLuxelSpace[1][2]) / det;
|
|
l->luxelToWorldSpace[1][0] = (l->facenormal[1] * l->worldToLuxelSpace[0][2] - l->facenormal[2] * l->worldToLuxelSpace[0][1]) / det;
|
|
l->luxelOrigin[0] = -(l->facedist * luxelSpaceCross[0]) / det;
|
|
l->luxelToWorldSpace[0][1] = (l->facenormal[0] * l->worldToLuxelSpace[1][2] - l->facenormal[2] * l->worldToLuxelSpace[1][0]) / det;
|
|
l->luxelToWorldSpace[1][1] = (l->facenormal[2] * l->worldToLuxelSpace[0][0] - l->facenormal[0] * l->worldToLuxelSpace[0][2]) / det;
|
|
l->luxelOrigin[1] = -(l->facedist * luxelSpaceCross[1]) / det;
|
|
l->luxelToWorldSpace[0][2] = (l->facenormal[1] * l->worldToLuxelSpace[1][0] - l->facenormal[0] * l->worldToLuxelSpace[1][1]) / det;
|
|
l->luxelToWorldSpace[1][2] = (l->facenormal[0] * l->worldToLuxelSpace[0][1] - l->facenormal[1] * l->worldToLuxelSpace[0][0]) / det;
|
|
l->luxelOrigin[2] = -(l->facedist * luxelSpaceCross[2]) / det;
|
|
|
|
// adjust for luxel offset
|
|
VectorMA( l->luxelOrigin, -tex->lightmapVecsLuxelsPerWorldUnits[0][3], l->luxelToWorldSpace[0], l->luxelOrigin );
|
|
VectorMA( l->luxelOrigin, -tex->lightmapVecsLuxelsPerWorldUnits[1][3], l->luxelToWorldSpace[1], l->luxelOrigin );
|
|
|
|
// compensate for org'd bmodels
|
|
VectorAdd (l->luxelOrigin, l->modelorg, l->luxelOrigin);
|
|
}
|
|
|
|
|
|
|
|
winding_t *LightmapCoordWindingForFace( lightinfo_t *l )
|
|
{
|
|
int i;
|
|
winding_t *w;
|
|
|
|
w = WindingFromFace( l->face, l->modelorg );
|
|
|
|
for (i = 0; i < w->numpoints; i++)
|
|
{
|
|
Vector2D coord;
|
|
WorldToLuxelSpace( l, w->p[i], coord );
|
|
w->p[i].x = coord.x;
|
|
w->p[i].y = coord.y;
|
|
w->p[i].z = 0;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
|
|
void WriteCoordWinding (FILE *out, lightinfo_t *l, winding_t *w, Vector& color )
|
|
{
|
|
int i;
|
|
Vector pos;
|
|
|
|
fprintf (out, "%i\n", w->numpoints);
|
|
for (i=0 ; i<w->numpoints ; i++)
|
|
{
|
|
LuxelSpaceToWorld( l, w->p[i][0], w->p[i][1], pos );
|
|
fprintf (out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n",
|
|
pos[0],
|
|
pos[1],
|
|
pos[2],
|
|
color[ 0 ] / 256,
|
|
color[ 1 ] / 256,
|
|
color[ 2 ] / 256 );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void DumpFaces( lightinfo_t *pLightInfo, int ndxFace )
|
|
{
|
|
static FileHandle_t out;
|
|
|
|
// get face data
|
|
faceneighbor_t *fn = &faceneighbor[ndxFace];
|
|
Vector ¢roid = face_centroids[ndxFace];
|
|
|
|
// disable threading (not a multi-threadable function!)
|
|
ThreadLock();
|
|
|
|
if( !out )
|
|
{
|
|
// open the file
|
|
out = g_pFileSystem->Open( "face.txt", "w" );
|
|
if( !out )
|
|
return;
|
|
}
|
|
|
|
//
|
|
// write out face
|
|
//
|
|
for( int ndxEdge = 0; ndxEdge < pLightInfo->face->numedges; ndxEdge++ )
|
|
{
|
|
// int edge = dsurfedges[pLightInfo->face->firstedge+ndxEdge];
|
|
|
|
Vector p1, p2;
|
|
VectorAdd( dvertexes[EdgeVertex( pLightInfo->face, ndxEdge )].point, pLightInfo->modelorg, p1 );
|
|
VectorAdd( dvertexes[EdgeVertex( pLightInfo->face, ndxEdge+1 )].point, pLightInfo->modelorg, p2 );
|
|
|
|
Vector &n1 = fn->normal[ndxEdge];
|
|
Vector &n2 = fn->normal[(ndxEdge+1)%pLightInfo->face->numedges];
|
|
|
|
CmdLib_FPrintf( out, "3\n");
|
|
|
|
CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", p1[0], p1[1], p1[2], n1[0] * 0.5 + 0.5, n1[1] * 0.5 + 0.5, n1[2] * 0.5 + 0.5 );
|
|
|
|
CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", p2[0], p2[1], p2[2], n2[0] * 0.5 + 0.5, n2[1] * 0.5 + 0.5, n2[2] * 0.5 + 0.5 );
|
|
|
|
CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", centroid[0] + pLightInfo->modelorg[0],
|
|
centroid[1] + pLightInfo->modelorg[1],
|
|
centroid[2] + pLightInfo->modelorg[2],
|
|
fn->facenormal[0] * 0.5 + 0.5,
|
|
fn->facenormal[1] * 0.5 + 0.5,
|
|
fn->facenormal[2] * 0.5 + 0.5 );
|
|
|
|
}
|
|
|
|
// enable threading
|
|
ThreadUnlock();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
bool BuildFacesamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight )
|
|
{
|
|
// lightmap size
|
|
int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1;
|
|
int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1;
|
|
|
|
// ratio of world area / lightmap area
|
|
texinfo_t *pTex = &texinfo[pLightInfo->face->texinfo];
|
|
pFaceLight->worldAreaPerLuxel = 1.0 / ( sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[0],
|
|
pTex->lightmapVecsLuxelsPerWorldUnits[0] ) ) *
|
|
sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[1],
|
|
pTex->lightmapVecsLuxelsPerWorldUnits[1] ) ) );
|
|
|
|
//
|
|
// quickly create samples and luxels (copy over samples)
|
|
//
|
|
pFaceLight->numsamples = width * height;
|
|
pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) );
|
|
if( !pFaceLight->sample )
|
|
return false;
|
|
|
|
pFaceLight->numluxels = width * height;
|
|
pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) );
|
|
if( !pFaceLight->luxel )
|
|
return false;
|
|
|
|
sample_t *pSamples = pFaceLight->sample;
|
|
Vector *pLuxels = pFaceLight->luxel;
|
|
|
|
for( int t = 0; t < height; t++ )
|
|
{
|
|
for( int s = 0; s < width; s++ )
|
|
{
|
|
pSamples->s = s;
|
|
pSamples->t = t;
|
|
pSamples->coord[0] = s;
|
|
pSamples->coord[1] = t;
|
|
// unused but initialized anyway
|
|
pSamples->mins[0] = s - 0.5;
|
|
pSamples->mins[1] = t - 0.5;
|
|
pSamples->maxs[0] = s + 0.5;
|
|
pSamples->maxs[1] = t + 0.5;
|
|
pSamples->area = pFaceLight->worldAreaPerLuxel;
|
|
LuxelSpaceToWorld( pLightInfo, pSamples->coord[0], pSamples->coord[1], pSamples->pos );
|
|
VectorCopy( pSamples->pos, *pLuxels );
|
|
|
|
pSamples++;
|
|
pLuxels++;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
bool BuildSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace )
|
|
{
|
|
// build samples for a "face"
|
|
if( pLightInfo->face->dispinfo == -1 )
|
|
{
|
|
return BuildFacesamplesAndLuxels_DoFast( pLightInfo, pFaceLight );
|
|
}
|
|
// build samples for a "displacement"
|
|
else
|
|
{
|
|
return StaticDispMgr()->BuildDispSamplesAndLuxels_DoFast( pLightInfo, pFaceLight, ndxFace );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
bool BuildFacesamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight )
|
|
{
|
|
// lightmap size
|
|
int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1;
|
|
int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1;
|
|
|
|
// ratio of world area / lightmap area
|
|
texinfo_t *pTex = &texinfo[pLightInfo->face->texinfo];
|
|
pFaceLight->worldAreaPerLuxel = 1.0 / ( sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[0],
|
|
pTex->lightmapVecsLuxelsPerWorldUnits[0] ) ) *
|
|
sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[1],
|
|
pTex->lightmapVecsLuxelsPerWorldUnits[1] ) ) );
|
|
|
|
// allocate a large number of samples for creation -- get copied later!
|
|
char sampleData[sizeof(sample_t)*SINGLE_BRUSH_MAP*2];
|
|
sample_t *samples = (sample_t*)sampleData; // use a char array to speed up the debug version.
|
|
sample_t *pSamples = samples;
|
|
|
|
// lightmap space winding
|
|
winding_t *pLightmapWinding = LightmapCoordWindingForFace( pLightInfo );
|
|
|
|
//
|
|
// build vector pointing along the lightmap cutting planes
|
|
//
|
|
Vector sNorm( 1.0f, 0.0f, 0.0f );
|
|
Vector tNorm( 0.0f, 1.0f, 0.0f );
|
|
|
|
// sample center offset
|
|
float sampleOffset = ( do_centersamples ) ? 0.5 : 1.0;
|
|
|
|
//
|
|
// clip the lightmap "spaced" winding by the lightmap cutting planes
|
|
//
|
|
winding_t *pWindingT1, *pWindingT2;
|
|
winding_t *pWindingS1, *pWindingS2;
|
|
float dist;
|
|
|
|
for( int t = 0; t < height && pLightmapWinding; t++ )
|
|
{
|
|
dist = t + sampleOffset;
|
|
|
|
// lop off a sample in the t dimension
|
|
// hack - need a separate epsilon for lightmap space since ON_EPSILON is for texture space
|
|
ClipWindingEpsilon( pLightmapWinding, tNorm, dist, ON_EPSILON / 16.0f, &pWindingT1, &pWindingT2 );
|
|
|
|
for( int s = 0; s < width && pWindingT2; s++ )
|
|
{
|
|
dist = s + sampleOffset;
|
|
|
|
// lop off a sample in the s dimension, and put it in ws2
|
|
// hack - need a separate epsilon for lightmap space since ON_EPSILON is for texture space
|
|
ClipWindingEpsilon( pWindingT2, sNorm, dist, ON_EPSILON / 16.0f, &pWindingS1, &pWindingS2 );
|
|
|
|
//
|
|
// s2 winding is a single sample worth of winding
|
|
//
|
|
if( pWindingS2 )
|
|
{
|
|
// save the s, t positions
|
|
pSamples->s = s;
|
|
pSamples->t = t;
|
|
|
|
// get the lightmap space area of ws2 and convert to world area
|
|
// and find the center (then convert it to 2D)
|
|
Vector center;
|
|
pSamples->area = WindingAreaAndBalancePoint( pWindingS2, center ) * pFaceLight->worldAreaPerLuxel;
|
|
pSamples->coord[0] = center.x;
|
|
pSamples->coord[1] = center.y;
|
|
|
|
// find winding bounds (then convert it to 2D)
|
|
Vector minbounds, maxbounds;
|
|
WindingBounds( pWindingS2, minbounds, maxbounds );
|
|
pSamples->mins[0] = minbounds.x;
|
|
pSamples->mins[1] = minbounds.y;
|
|
pSamples->maxs[0] = maxbounds.x;
|
|
pSamples->maxs[1] = maxbounds.y;
|
|
|
|
// convert from lightmap space to world space
|
|
LuxelSpaceToWorld( pLightInfo, pSamples->coord[0], pSamples->coord[1], pSamples->pos );
|
|
|
|
if (dumppatches || (do_extra && pSamples->area < pFaceLight->worldAreaPerLuxel - EQUAL_EPSILON))
|
|
{
|
|
//
|
|
// convert the winding from lightmaps space to world for debug rendering and sub-sampling
|
|
//
|
|
Vector worldPos;
|
|
for( int ndxPt = 0; ndxPt < pWindingS2->numpoints; ndxPt++ )
|
|
{
|
|
LuxelSpaceToWorld( pLightInfo, pWindingS2->p[ndxPt].x, pWindingS2->p[ndxPt].y, worldPos );
|
|
VectorCopy( worldPos, pWindingS2->p[ndxPt] );
|
|
}
|
|
pSamples->w = pWindingS2;
|
|
}
|
|
else
|
|
{
|
|
// winding isn't needed, free it.
|
|
pSamples->w = NULL;
|
|
FreeWinding( pWindingS2 );
|
|
}
|
|
|
|
pSamples++;
|
|
}
|
|
|
|
//
|
|
// if winding T2 still exists free it and set it equal S1 (the rest of the row minus the sample just created)
|
|
//
|
|
if( pWindingT2 )
|
|
{
|
|
FreeWinding( pWindingT2 );
|
|
}
|
|
|
|
// clip the rest of "s"
|
|
pWindingT2 = pWindingS1;
|
|
}
|
|
|
|
//
|
|
// if the original lightmap winding exists free it and set it equal to T1 (the rest of the winding not cut into samples)
|
|
//
|
|
if( pLightmapWinding )
|
|
{
|
|
FreeWinding( pLightmapWinding );
|
|
}
|
|
|
|
if( pWindingT2 )
|
|
{
|
|
FreeWinding( pWindingT2 );
|
|
}
|
|
|
|
pLightmapWinding = pWindingT1;
|
|
}
|
|
|
|
//
|
|
// copy over samples
|
|
//
|
|
pFaceLight->numsamples = pSamples - samples;
|
|
pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) );
|
|
if( !pFaceLight->sample )
|
|
return false;
|
|
|
|
memcpy( pFaceLight->sample, samples, pFaceLight->numsamples * sizeof( *pFaceLight->sample ) );
|
|
|
|
// supply a default sample normal (face normal - assumed flat)
|
|
for( int ndxSample = 0; ndxSample < pFaceLight->numsamples; ndxSample++ )
|
|
{
|
|
Assert ( VectorLength ( pLightInfo->facenormal ) > 1.0e-20);
|
|
pFaceLight->sample[ndxSample].normal = pLightInfo->facenormal;
|
|
}
|
|
|
|
// statistics - warning?!
|
|
if( pFaceLight->numsamples == 0 )
|
|
{
|
|
Msg( "no samples %d\n", pLightInfo->face - g_pFaces );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Free any windings used by this facelight. It's currently assumed they're not needed again
|
|
//-----------------------------------------------------------------------------
|
|
void FreeSampleWindings( facelight_t *fl )
|
|
{
|
|
int i;
|
|
for (i = 0; i < fl->numsamples; i++)
|
|
{
|
|
if (fl->sample[i].w)
|
|
{
|
|
FreeWinding( fl->sample[i].w );
|
|
fl->sample[i].w = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: build the sample data for each lightmapped primitive type
|
|
//-----------------------------------------------------------------------------
|
|
bool BuildSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace )
|
|
{
|
|
// build samples for a "face"
|
|
if( pLightInfo->face->dispinfo == -1 )
|
|
{
|
|
return BuildFacesamples( pLightInfo, pFaceLight );
|
|
}
|
|
// build samples for a "displacement"
|
|
else
|
|
{
|
|
return StaticDispMgr()->BuildDispSamples( pLightInfo, pFaceLight, ndxFace );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
bool BuildFaceLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight )
|
|
{
|
|
// lightmap size
|
|
int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1;
|
|
int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1;
|
|
|
|
// calcuate actual luxel points
|
|
pFaceLight->numluxels = width * height;
|
|
pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) );
|
|
if( !pFaceLight->luxel )
|
|
return false;
|
|
|
|
for( int t = 0; t < height; t++ )
|
|
{
|
|
for( int s = 0; s < width; s++ )
|
|
{
|
|
LuxelSpaceToWorld( pLightInfo, s, t, pFaceLight->luxel[s+t*width] );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: build the luxels (find the luxel centers) for each lightmapped
|
|
// primitive type
|
|
//-----------------------------------------------------------------------------
|
|
bool BuildLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace )
|
|
{
|
|
// build luxels for a "face"
|
|
if( pLightInfo->face->dispinfo == -1 )
|
|
{
|
|
return BuildFaceLuxels( pLightInfo, pFaceLight );
|
|
}
|
|
// build luxels for a "displacement"
|
|
else
|
|
{
|
|
return StaticDispMgr()->BuildDispLuxels( pLightInfo, pFaceLight, ndxFace );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: for each face, find the center of each luxel; for each texture
|
|
// aligned grid point, back project onto the plane and get the world
|
|
// xyz value of the sample point
|
|
// NOTE: ndxFace = facenum
|
|
//-----------------------------------------------------------------------------
|
|
void CalcPoints( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace )
|
|
{
|
|
// debugging!
|
|
if( dumppatches )
|
|
{
|
|
DumpFaces( pLightInfo, ndxFace );
|
|
}
|
|
|
|
// quick and dirty!
|
|
if( do_fast )
|
|
{
|
|
if( !BuildSamplesAndLuxels_DoFast( pLightInfo, pFaceLight, ndxFace ) )
|
|
{
|
|
Msg( "Face %d: (Fast)Error Building Samples and Luxels\n", ndxFace );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// build the samples
|
|
if( !BuildSamples( pLightInfo, pFaceLight, ndxFace ) )
|
|
{
|
|
Msg( "Face %d: Error Building Samples\n", ndxFace );
|
|
}
|
|
|
|
// build the luxels
|
|
if( !BuildLuxels( pLightInfo, pFaceLight, ndxFace ) )
|
|
{
|
|
Msg( "Face %d: Error Building Luxels\n", ndxFace );
|
|
}
|
|
}
|
|
|
|
|
|
//==============================================================
|
|
|
|
directlight_t *activelights;
|
|
directlight_t *freelights;
|
|
|
|
facelight_t facelight[MAX_MAP_FACES];
|
|
int numdlights;
|
|
|
|
/*
|
|
==================
|
|
FindTargetEntity
|
|
==================
|
|
*/
|
|
entity_t *FindTargetEntity (char *target)
|
|
{
|
|
int i;
|
|
char *n;
|
|
|
|
for (i=0 ; i<num_entities ; i++)
|
|
{
|
|
n = ValueForKey (&entities[i], "targetname");
|
|
if (!strcmp (n, target))
|
|
return &entities[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=============
|
|
AllocDLight
|
|
=============
|
|
*/
|
|
|
|
int GetVisCache( int lastoffset, int cluster, byte *pvs );
|
|
void SetDLightVis( directlight_t *dl, int cluster );
|
|
void MergeDLightVis( directlight_t *dl, int cluster );
|
|
|
|
directlight_t *AllocDLight( Vector& origin, bool bAddToList )
|
|
{
|
|
directlight_t *dl;
|
|
|
|
dl = ( directlight_t* )calloc(1, sizeof(directlight_t));
|
|
dl->index = numdlights++;
|
|
|
|
VectorCopy( origin, dl->light.origin );
|
|
|
|
dl->light.cluster = ClusterFromPoint(dl->light.origin);
|
|
SetDLightVis( dl, dl->light.cluster );
|
|
|
|
dl->facenum = -1;
|
|
|
|
if ( bAddToList )
|
|
{
|
|
dl->next = activelights;
|
|
activelights = dl;
|
|
}
|
|
|
|
return dl;
|
|
}
|
|
|
|
void AddDLightToActiveList( directlight_t *dl )
|
|
{
|
|
dl->next = activelights;
|
|
activelights = dl;
|
|
}
|
|
|
|
void FreeDLights()
|
|
{
|
|
gSkyLight = NULL;
|
|
gAmbient = NULL;
|
|
|
|
directlight_t *pNext;
|
|
for( directlight_t *pCur=activelights; pCur; pCur=pNext )
|
|
{
|
|
pNext = pCur->next;
|
|
free( pCur );
|
|
}
|
|
activelights = 0;
|
|
}
|
|
|
|
|
|
void SetDLightVis( directlight_t *dl, int cluster )
|
|
{
|
|
if (dl->pvs == NULL)
|
|
{
|
|
dl->pvs = (byte *)calloc( 1, (dvis->numclusters / 8) + 1 );
|
|
}
|
|
|
|
GetVisCache( -1, cluster, dl->pvs );
|
|
}
|
|
|
|
void MergeDLightVis( directlight_t *dl, int cluster )
|
|
{
|
|
if (dl->pvs == NULL)
|
|
{
|
|
SetDLightVis( dl, cluster );
|
|
}
|
|
else
|
|
{
|
|
byte pvs[MAX_MAP_CLUSTERS/8];
|
|
GetVisCache( -1, cluster, pvs );
|
|
|
|
// merge both vis graphs
|
|
for (int i = 0; i < (dvis->numclusters / 8) + 1; i++)
|
|
{
|
|
dl->pvs[i] |= pvs[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
LightForKey
|
|
=============
|
|
*/
|
|
int LightForKey (entity_t *ent, char *key, Vector& intensity )
|
|
{
|
|
char *pLight;
|
|
|
|
pLight = ValueForKey( ent, key );
|
|
|
|
return LightForString( pLight, intensity );
|
|
}
|
|
|
|
int LightForString( char *pLight, Vector& intensity )
|
|
{
|
|
double r, g, b, scaler;
|
|
int argCnt;
|
|
|
|
VectorFill( intensity, 0 );
|
|
|
|
// scanf into doubles, then assign, so it is vec_t size independent
|
|
r = g = b = scaler = 0;
|
|
double r_hdr,g_hdr,b_hdr,scaler_hdr;
|
|
argCnt = sscanf ( pLight, "%lf %lf %lf %lf %lf %lf %lf %lf",
|
|
&r, &g, &b, &scaler, &r_hdr,&g_hdr,&b_hdr,&scaler_hdr );
|
|
|
|
// This is a special case for HDR lights. If we have a vector of [-1, -1, -1, 1], then we
|
|
// need to fall back to the non-HDR lighting since the HDR lighting hasn't been defined
|
|
// for this light source.
|
|
if( ( argCnt == 3 && r == -1.0f && g == -1.0f && b == -1.0f ) ||
|
|
( argCnt == 4 && r == -1.0f && g == -1.0f && b == -1.0f && scaler == 1.0f ) )
|
|
{
|
|
intensity.Init( -1.0f, -1.0f, -1.0f );
|
|
return true;
|
|
}
|
|
|
|
if (argCnt==8) // 2 4-tuples
|
|
{
|
|
if (g_bHDR)
|
|
{
|
|
r=r_hdr;
|
|
g=g_hdr;
|
|
b=b_hdr;
|
|
scaler=scaler_hdr;
|
|
}
|
|
argCnt=4;
|
|
}
|
|
|
|
intensity[0] = pow( r / 255.0, 2.2 ) * 255; // convert to linear
|
|
|
|
switch( argCnt)
|
|
{
|
|
case 1:
|
|
// The R,G,B values are all equal.
|
|
intensity[1] = intensity[2] = intensity[0];
|
|
break;
|
|
|
|
case 3:
|
|
case 4:
|
|
// Save the other two G,B values.
|
|
intensity[1] = pow( g / 255.0, 2.2 ) * 255;
|
|
intensity[2] = pow( b / 255.0, 2.2 ) * 255;
|
|
|
|
// Did we also get an "intensity" scaler value too?
|
|
if ( argCnt == 4 )
|
|
{
|
|
// Scale the normalized 0-255 R,G,B values by the intensity scaler
|
|
VectorScale( intensity, scaler / 255.0, intensity );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
printf("unknown light specifier type - %s\n",pLight);
|
|
return false;
|
|
}
|
|
// scale up source lights by scaling factor
|
|
VectorScale( intensity, lightscale, intensity );
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Various parsing methods
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static void ParseLightGeneric( entity_t *e, directlight_t *dl )
|
|
{
|
|
entity_t *e2;
|
|
char *target;
|
|
Vector dest;
|
|
|
|
dl->light.style = (int)FloatForKey (e, "style");
|
|
|
|
// get intenfsity
|
|
if( g_bHDR )
|
|
{
|
|
if( LightForKey( e, "_lightHDR", dl->light.intensity ) == 0 ||
|
|
( dl->light.intensity.x == -1.0f &&
|
|
dl->light.intensity.y == -1.0f &&
|
|
dl->light.intensity.z == -1.0f ) )
|
|
{
|
|
LightForKey( e, "_light", dl->light.intensity );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LightForKey( e, "_light", dl->light.intensity );
|
|
}
|
|
|
|
// check angle, targets
|
|
target = ValueForKey (e, "target");
|
|
if (target[0])
|
|
{ // point towards target
|
|
e2 = FindTargetEntity (target);
|
|
if (!e2)
|
|
Warning("WARNING: light at (%i %i %i) has missing target\n",
|
|
(int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]);
|
|
else
|
|
{
|
|
GetVectorForKey (e2, "origin", dest);
|
|
VectorSubtract (dest, dl->light.origin, dl->light.normal);
|
|
VectorNormalize (dl->light.normal);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// point down angle
|
|
Vector angles;
|
|
GetVectorForKey( e, "angles", angles );
|
|
float pitch = FloatForKey (e, "pitch");
|
|
float angle = FloatForKey (e, "angle");
|
|
SetupLightNormalFromProps( QAngle( angles.x, angles.y, angles.z ), angle, pitch, dl->light.normal );
|
|
}
|
|
}
|
|
|
|
static void SetLightFalloffParams(entity_t *e, directlight_t *dl)
|
|
{
|
|
float d50=FloatForKey(e,"_fifty_percent_distance");
|
|
if (d50)
|
|
{
|
|
float d0=FloatForKey(e,"_zero_percent_distance");
|
|
if (d0<d50)
|
|
{
|
|
Warning("light has _fifty_percent_distance of %f but no zero_percent_distance\n",d50);
|
|
d0=2.0*d50;
|
|
}
|
|
float a=0,b=1,c=0;
|
|
if (! SolveInverseQuadraticMonotonic(0,1.0,d50,2.0,d0,256.0,a,b,c))
|
|
{
|
|
Warning("can't solve quadratic for light %f %f\n",d50,d0);
|
|
}
|
|
// it it possible that the parameters couldn't be used because of enforing monoticity. If so, rescale so at
|
|
// least the 50 percent value is right
|
|
// printf("50 percent=%f 0 percent=%f\n",d50,d0);
|
|
// printf("a=%f b=%f c=%f\n",a,b,c);
|
|
float v50=c+d50*(b+d50*a);
|
|
float scale=2.0/v50;
|
|
a*=scale;
|
|
b*=scale;
|
|
c*=scale;
|
|
// printf("scaled=%f a=%f b=%f c=%f\n",scale,a,b,c);
|
|
// for(float d=0;d<1000;d+=20)
|
|
// printf("at %f, %f\n",d,1.0/(c+d*(b+d*a)));
|
|
dl->light.quadratic_attn=a;
|
|
dl->light.linear_attn=b;
|
|
dl->light.constant_attn=c;
|
|
}
|
|
else
|
|
{
|
|
dl->light.constant_attn = FloatForKey (e, "_constant_attn");
|
|
dl->light.linear_attn = FloatForKey (e, "_linear_attn");
|
|
dl->light.quadratic_attn = FloatForKey (e, "_quadratic_attn");
|
|
|
|
dl->light.radius = FloatForKey (e, "_distance");
|
|
|
|
// clamp values to >= 0
|
|
if (dl->light.constant_attn < EQUAL_EPSILON)
|
|
dl->light.constant_attn = 0;
|
|
|
|
if (dl->light.linear_attn < EQUAL_EPSILON)
|
|
dl->light.linear_attn = 0;
|
|
|
|
if (dl->light.quadratic_attn < EQUAL_EPSILON)
|
|
dl->light.quadratic_attn = 0;
|
|
|
|
if (dl->light.constant_attn < EQUAL_EPSILON && dl->light.linear_attn < EQUAL_EPSILON && dl->light.quadratic_attn < EQUAL_EPSILON)
|
|
dl->light.constant_attn = 1;
|
|
|
|
// scale intensity for unit 100 distance
|
|
float ratio = (dl->light.constant_attn + 100 * dl->light.linear_attn + 100 * 100 * dl->light.quadratic_attn);
|
|
if (ratio > 0)
|
|
{
|
|
VectorScale( dl->light.intensity, ratio, dl->light.intensity );
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ParseLightSpot( entity_t* e, directlight_t* dl )
|
|
{
|
|
Vector dest;
|
|
GetVectorForKey (e, "origin", dest );
|
|
dl = AllocDLight( dest, true );
|
|
|
|
ParseLightGeneric( e, dl );
|
|
|
|
dl->light.type = emit_spotlight;
|
|
|
|
dl->light.stopdot = FloatForKey (e, "_inner_cone");
|
|
if (!dl->light.stopdot)
|
|
dl->light.stopdot = 10;
|
|
|
|
dl->light.stopdot2 = FloatForKey (e, "_cone");
|
|
if (!dl->light.stopdot2)
|
|
dl->light.stopdot2 = dl->light.stopdot;
|
|
if (dl->light.stopdot2 < dl->light.stopdot)
|
|
dl->light.stopdot2 = dl->light.stopdot;
|
|
|
|
// This is a point light if stop dots are 180...
|
|
if ((dl->light.stopdot == 180) && (dl->light.stopdot2 == 180))
|
|
{
|
|
dl->light.stopdot = dl->light.stopdot2 = 0;
|
|
dl->light.type = emit_point;
|
|
dl->light.exponent = 0;
|
|
}
|
|
else
|
|
{
|
|
// Clamp to 90, that's all DX8 can handle!
|
|
if (dl->light.stopdot > 90)
|
|
{
|
|
Warning("WARNING: light_spot at (%i %i %i) has inner angle larger than 90 degrees! Clamping to 90...\n",
|
|
(int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]);
|
|
dl->light.stopdot = 90;
|
|
}
|
|
|
|
if (dl->light.stopdot2 > 90)
|
|
{
|
|
Warning("WARNING: light_spot at (%i %i %i) has outer angle larger than 90 degrees! Clamping to 90...\n",
|
|
(int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]);
|
|
dl->light.stopdot2 = 90;
|
|
}
|
|
|
|
dl->light.stopdot2 = (float)cos(dl->light.stopdot2/180*M_PI);
|
|
dl->light.stopdot = (float)cos(dl->light.stopdot/180*M_PI);
|
|
dl->light.exponent = FloatForKey (e, "_exponent");
|
|
}
|
|
|
|
SetLightFalloffParams(e,dl);
|
|
}
|
|
|
|
// NOTE: This is just a heuristic. It traces a finite number of rays to find sky
|
|
// NOTE: Full vis is necessary to make this 100% correct.
|
|
bool CanLeafTraceToSky( int iLeaf )
|
|
{
|
|
// UNDONE: Really want a point inside the leaf here. Center is a guess, may not be in the leaf
|
|
// UNDONE: Clip this to each plane bounding the leaf to guarantee
|
|
Vector center = vec3_origin;
|
|
for ( int i = 0; i < 3; i++ )
|
|
{
|
|
center[i] = ( (float)(dleafs[iLeaf].mins[i] + dleafs[iLeaf].maxs[i]) ) * 0.5f;
|
|
}
|
|
|
|
Vector delta;
|
|
for ( int j = 0; j < NUMVERTEXNORMALS; j++ )
|
|
{
|
|
// search back to see if we can hit a sky brush
|
|
VectorScale( g_anorms[j], -MAX_TRACE_LENGTH, delta );
|
|
VectorAdd( center, delta, delta );
|
|
|
|
texinfo_t *tx = TestLine_Surface( 0, center, delta, 0 );
|
|
|
|
if (tx == NULL || !(tx->flags & SURF_SKY))
|
|
continue; // no sky here
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void BuildVisForLightEnvironment( void )
|
|
{
|
|
// Create the vis.
|
|
for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf )
|
|
{
|
|
dleafs[iLeaf].flags &= ~LEAF_FLAGS_SKY;
|
|
unsigned int iFirstFace = dleafs[iLeaf].firstleafface;
|
|
for ( int iLeafFace = 0; iLeafFace < dleafs[iLeaf].numleaffaces; ++iLeafFace )
|
|
{
|
|
unsigned int iFace = dleaffaces[iFirstFace+iLeafFace];
|
|
|
|
texinfo_t &tex = texinfo[g_pFaces[iFace].texinfo];
|
|
if ( tex.flags & SURF_SKY )
|
|
{
|
|
dleafs[iLeaf].flags |= LEAF_FLAGS_SKY;
|
|
MergeDLightVis( gSkyLight, dleafs[iLeaf].cluster );
|
|
MergeDLightVis( gAmbient, dleafs[iLeaf].cluster );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Second pass to set flags on leaves that don't contain sky, but touch leaves that
|
|
// contain sky.
|
|
byte pvs[MAX_MAP_CLUSTERS / 8];
|
|
|
|
int nLeafBytes = (numleafs >> 3) + 1;
|
|
unsigned char *pLeafBits = (unsigned char *)stackalloc( nLeafBytes * sizeof(unsigned char) );
|
|
memset( pLeafBits, 0, nLeafBytes );
|
|
|
|
for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf )
|
|
{
|
|
// If this leaf has light in it, then don't bother
|
|
if ( dleafs[iLeaf].flags & LEAF_FLAGS_SKY )
|
|
continue;
|
|
|
|
// Don't bother with this leaf if it's solid
|
|
if ( dleafs[iLeaf].contents & CONTENTS_SOLID )
|
|
continue;
|
|
|
|
// See what other leaves are visible from this leaf
|
|
GetVisCache( -1, dleafs[iLeaf].cluster, pvs );
|
|
|
|
// Now check out all other leaves
|
|
for ( int iLeaf2 = 0; iLeaf2 < numleafs; ++iLeaf2 )
|
|
{
|
|
if ( iLeaf2 == iLeaf )
|
|
continue;
|
|
|
|
if ( !(dleafs[iLeaf2].flags & LEAF_FLAGS_SKY) )
|
|
continue;
|
|
|
|
// Can this leaf see into the leaf with the sky in it?
|
|
if ( PVSCheck( pvs, dleafs[iLeaf2].cluster ) )
|
|
{
|
|
pLeafBits[ iLeaf >> 3 ] |= 1 << ( iLeaf & 0x7 );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Must set the bits in a separate pass so as to not flood-fill LEAF_FLAGS_SKY everywhere
|
|
// pLeafbits is a bit array of all leaves that need to be marked as seeing sky
|
|
for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf )
|
|
{
|
|
if ( dleafs[iLeaf].flags & LEAF_FLAGS_SKY )
|
|
continue;
|
|
|
|
// Don't bother with this leaf if it's solid
|
|
if ( dleafs[iLeaf].contents & CONTENTS_SOLID )
|
|
continue;
|
|
|
|
if ( pLeafBits[ iLeaf >> 3 ] & (1 << ( iLeaf & 0x7 )) )
|
|
{
|
|
dleafs[iLeaf].flags |= LEAF_FLAGS_SKY;
|
|
}
|
|
else
|
|
{
|
|
// if radial vis was used on this leaf some of the portals leading
|
|
// to sky may have been culled. Try tracing to find sky.
|
|
if ( dleafs[iLeaf].flags & LEAF_FLAGS_RADIAL )
|
|
{
|
|
if ( CanLeafTraceToSky(iLeaf) )
|
|
{
|
|
dleafs[iLeaf].flags |= LEAF_FLAGS_SKY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ParseLightEnvironment( entity_t* e, directlight_t* dl )
|
|
{
|
|
Vector dest;
|
|
GetVectorForKey (e, "origin", dest );
|
|
dl = AllocDLight( dest, false );
|
|
|
|
ParseLightGeneric( e, dl );
|
|
|
|
if ( !gSkyLight )
|
|
{
|
|
// Sky light.
|
|
gSkyLight = dl;
|
|
dl->light.type = emit_skylight;
|
|
|
|
// Sky ambient light.
|
|
gAmbient = AllocDLight( dl->light.origin, false );
|
|
gAmbient->light.type = emit_skyambient;
|
|
if( g_bHDR && LightForKey( e, "_ambientHDR", gAmbient->light.intensity ) &&
|
|
gAmbient->light.intensity.x != -1.0f &&
|
|
gAmbient->light.intensity.y != -1.0f &&
|
|
gAmbient->light.intensity.z != -1.0f )
|
|
{
|
|
// we have a valid HDR ambient light value
|
|
}
|
|
else if ( !LightForKey( e, "_ambient", gAmbient->light.intensity ) )
|
|
{
|
|
VectorScale( dl->light.intensity, 0.5, gAmbient->light.intensity );
|
|
}
|
|
|
|
BuildVisForLightEnvironment();
|
|
|
|
// Add sky and sky ambient lights to the list.
|
|
AddDLightToActiveList( gSkyLight );
|
|
AddDLightToActiveList( gAmbient );
|
|
}
|
|
}
|
|
|
|
static void ParseLightPoint( entity_t* e, directlight_t* dl )
|
|
{
|
|
Vector dest;
|
|
GetVectorForKey (e, "origin", dest );
|
|
dl = AllocDLight( dest, true );
|
|
|
|
ParseLightGeneric( e, dl );
|
|
|
|
dl->light.type = emit_point;
|
|
|
|
SetLightFalloffParams(e,dl);
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CreateDirectLights
|
|
=============
|
|
*/
|
|
#define DIRECT_SCALE (100.0*100.0)
|
|
void CreateDirectLights (void)
|
|
{
|
|
unsigned i;
|
|
patch_t *p = NULL;
|
|
directlight_t *dl = NULL;
|
|
entity_t *e = NULL;
|
|
char *name;
|
|
Vector dest;
|
|
|
|
numdlights = 0;
|
|
|
|
FreeDLights();
|
|
|
|
//
|
|
// surfaces
|
|
//
|
|
unsigned int uiPatchCount = patches.Size();
|
|
for (i=0; i< uiPatchCount; i++)
|
|
{
|
|
p = &patches.Element( i );
|
|
|
|
// skip parent patches
|
|
if (p->child1 != patches.InvalidIndex() )
|
|
continue;
|
|
|
|
if (p->basearea < 1e-6)
|
|
continue;
|
|
|
|
if( VectorAvg( p->baselight ) >= dlight_threshold )
|
|
{
|
|
dl = AllocDLight( p->origin, true );
|
|
|
|
dl->light.type = emit_surface;
|
|
VectorCopy (p->normal, dl->light.normal);
|
|
Assert( VectorLength( p->normal ) > 1.0e-20 );
|
|
// scale intensity by number of texture instances
|
|
VectorScale( p->baselight, lightscale * p->area * p->scale[0] * p->scale[1] / p->basearea, dl->light.intensity );
|
|
|
|
// scale to a range that results in actual light
|
|
VectorScale( dl->light.intensity, DIRECT_SCALE, dl->light.intensity );
|
|
}
|
|
}
|
|
|
|
//
|
|
// entities
|
|
//
|
|
for (i=0 ; i<(unsigned)num_entities ; i++)
|
|
{
|
|
e = &entities[i];
|
|
name = ValueForKey (e, "classname");
|
|
if (strncmp (name, "light", 5))
|
|
continue;
|
|
|
|
// Light_dynamic is actually a real entity; not to be included here...
|
|
if (!strcmp (name, "light_dynamic"))
|
|
continue;
|
|
|
|
if (!strcmp (name, "light_spot"))
|
|
{
|
|
ParseLightSpot( e, dl );
|
|
}
|
|
else if (!strcmp(name, "light_environment"))
|
|
{
|
|
ParseLightEnvironment( e, dl );
|
|
}
|
|
else if (!strcmp(name, "light"))
|
|
{
|
|
ParseLightPoint( e, dl );
|
|
}
|
|
else
|
|
{
|
|
qprintf( "unsupported light entity: \"%s\"\n", name );
|
|
}
|
|
}
|
|
|
|
qprintf ("%i direct lights\n", numdlights);
|
|
// exit(1);
|
|
}
|
|
|
|
/*
|
|
=============
|
|
ExportDirectLightsToWorldLights
|
|
=============
|
|
*/
|
|
|
|
void ExportDirectLightsToWorldLights()
|
|
{
|
|
directlight_t *dl;
|
|
|
|
// In case the level has already been VRADed.
|
|
*pNumworldlights = 0;
|
|
|
|
for (dl = activelights; dl != NULL; dl = dl->next )
|
|
{
|
|
dworldlight_t *wl = &dworldlights[(*pNumworldlights)++];
|
|
|
|
if (*pNumworldlights > MAX_MAP_WORLDLIGHTS)
|
|
{
|
|
Error("too many lights %d / %d\n", *pNumworldlights, MAX_MAP_WORLDLIGHTS );
|
|
}
|
|
|
|
wl->cluster = dl->light.cluster;
|
|
wl->type = dl->light.type;
|
|
wl->style = dl->light.style;
|
|
VectorCopy( dl->light.origin, wl->origin );
|
|
// FIXME: why does vrad want 0 to 255 and not 0 to 1??
|
|
VectorScale( dl->light.intensity, (1.0 / 255.0), wl->intensity );
|
|
VectorCopy( dl->light.normal, wl->normal );
|
|
wl->stopdot = dl->light.stopdot;
|
|
wl->stopdot2 = dl->light.stopdot2;
|
|
wl->exponent = dl->light.exponent;
|
|
wl->radius = dl->light.radius;
|
|
wl->constant_attn = dl->light.constant_attn;
|
|
wl->linear_attn = dl->light.linear_attn;
|
|
wl->quadratic_attn = dl->light.quadratic_attn;
|
|
wl->flags = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
GatherSampleLight
|
|
=============
|
|
*/
|
|
#define NORMALFORMFACTOR 40.156979 // accumuated dot products for hemisphere
|
|
|
|
#define NSAMPLES_SUN_AREA_LIGHT 30 // number of samples to take for an
|
|
// non-point sun light
|
|
|
|
// returns dot product with normal and delta
|
|
// dl - light
|
|
// pos - position of sample
|
|
// normal - surface normal of sample
|
|
// out.dot[] - returned dot products with light vector and each normal
|
|
// out.falloff - amount of light falloff
|
|
bool GatherSampleLight( sampleLightOutput_t &out, directlight_t *dl, int facenum,
|
|
Vector const& pos, Vector *pNormals, int normalCount, int iThread,
|
|
bool force_fast,
|
|
int static_prop_index_to_ignore)
|
|
{
|
|
float dot, dot2;
|
|
float dist;
|
|
Vector delta;
|
|
|
|
Assert( normalCount <= (NUM_BUMP_VECTS+1) );
|
|
|
|
// skylights work fundamentally differently than normal lights
|
|
if (dl->light.type == emit_skylight)
|
|
{
|
|
// make sure the angle is okay
|
|
dot = -DotProduct( pNormals[0], dl->light.normal );
|
|
if (dot <= EQUAL_EPSILON)
|
|
return false;
|
|
|
|
int nsamples=1;
|
|
if (g_SunAngularExtent>0.0)
|
|
{
|
|
nsamples=NSAMPLES_SUN_AREA_LIGHT;
|
|
if (do_fast || force_fast )
|
|
nsamples/=4;
|
|
}
|
|
|
|
int nmisses=0;
|
|
DirectionalSampler_t ldisp;
|
|
|
|
for(int d=0;d<nsamples;d++)
|
|
{
|
|
// now, determine visibility of the skylight
|
|
// search back to see if we can hit a sky brush
|
|
VectorScale( dl->light.normal, -MAX_TRACE_LENGTH, delta );
|
|
if (d)
|
|
{
|
|
// jitter light source location
|
|
Vector ofs=ldisp.NextValue();
|
|
ofs*=(MAX_TRACE_LENGTH*g_SunAngularExtent);
|
|
delta+=ofs;
|
|
}
|
|
VectorAdd( pos, delta, delta );
|
|
|
|
texinfo_t *tx = TestLine_Surface( 0, pos, delta, iThread, true,
|
|
static_prop_index_to_ignore );
|
|
|
|
if (tx == NULL || !(tx->flags & SURF_SKY))
|
|
nmisses++;
|
|
}
|
|
if (nmisses==nsamples)
|
|
return false;
|
|
float see_percent=(nsamples-nmisses)*(1.0/nsamples);
|
|
out.dot[0] = dot*see_percent;
|
|
|
|
out.falloff = 1.0f;
|
|
for ( int i = 1; i < normalCount; i++ )
|
|
{
|
|
out.dot[i] = -DotProduct( pNormals[i], dl->light.normal );
|
|
}
|
|
return true;
|
|
}
|
|
else if (dl->light.type == emit_skyambient)
|
|
{
|
|
float sumdot=0;
|
|
float ambient_intensity[NUM_BUMP_VECTS+1];
|
|
int i, j;
|
|
int possibleHitCount[NUM_BUMP_VECTS+1];
|
|
float dots[NUM_BUMP_VECTS+1];
|
|
|
|
for ( i = 0; i < normalCount; i++ )
|
|
{
|
|
ambient_intensity[i] = 0;
|
|
}
|
|
|
|
// count all of the directions that could have projected something on to the bump basis normal
|
|
memset( possibleHitCount, 0, sizeof(possibleHitCount) );
|
|
|
|
DirectionalSampler_t sampler;
|
|
int nsky_samples=NUMVERTEXNORMALS;
|
|
if (do_fast || force_fast )
|
|
nsky_samples/=4;
|
|
if (g_dofinal)
|
|
nsky_samples*=16;
|
|
|
|
for (j = 0; j < nsky_samples; j++)
|
|
{
|
|
// make sure the angle is okay
|
|
Vector anorm=sampler.NextValue();
|
|
dots[0] = -DotProduct( pNormals[0], anorm );
|
|
if (dots[0] <= EQUAL_EPSILON)
|
|
continue;
|
|
|
|
sumdot+=dots[0];
|
|
possibleHitCount[0]++;
|
|
for ( i = 1; i < normalCount; i++ )
|
|
{
|
|
dots[i] = -DotProduct( pNormals[i], anorm );
|
|
if ( dots[i] <= EQUAL_EPSILON )
|
|
{
|
|
dots[i] = 0;
|
|
}
|
|
else
|
|
{
|
|
possibleHitCount[i]++;
|
|
}
|
|
}
|
|
// search back to see if we can hit a sky brush
|
|
VectorScale( anorm, -MAX_TRACE_LENGTH, delta );
|
|
VectorAdd( pos, delta, delta );
|
|
|
|
texinfo_t *tx = TestLine_Surface( 0, pos, delta, iThread, true,
|
|
static_prop_index_to_ignore );
|
|
|
|
if (tx == NULL || !(tx->flags & SURF_SKY))
|
|
// if (!tx || tx->texdata != dl->texdata)
|
|
continue; // occluded
|
|
|
|
for ( i = 0; i < normalCount; i++ )
|
|
{
|
|
ambient_intensity[i] += dots[i];
|
|
}
|
|
}
|
|
|
|
out.falloff = 1.0f;
|
|
for ( i = 0; i < normalCount; i++ )
|
|
{
|
|
// now scale out the missing parts of the hemisphere of this bump basis vector
|
|
float factor = (float)possibleHitCount[i] / (float)possibleHitCount[0];
|
|
out.dot[i] = ambient_intensity[i] / (factor * sumdot);
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Vector src;
|
|
|
|
if (dl->facenum == -1)
|
|
{
|
|
VectorCopy( dl->light.origin, src );
|
|
}
|
|
else
|
|
{
|
|
src.Init( 0, 0, 0 );
|
|
}
|
|
|
|
VectorSubtract (src, pos, delta);
|
|
dist = VectorNormalize (delta);
|
|
dot = DotProduct (delta, pNormals[0]);
|
|
if (dot <= EQUAL_EPSILON)
|
|
return false; // behind sample surface
|
|
|
|
if (dist < 1.0)
|
|
dist = 1.0;
|
|
|
|
switch (dl->light.type)
|
|
{
|
|
case emit_point:
|
|
out.falloff = 1.0 / (dl->light.constant_attn + dl->light.linear_attn * dist + dl->light.quadratic_attn * dist * dist);
|
|
break;
|
|
|
|
case emit_surface:
|
|
dot2 = -DotProduct (delta, dl->light.normal);
|
|
if (dot2 <= EQUAL_EPSILON)
|
|
return false; // behind light surface
|
|
out.falloff = dot2 / (dist * dist);
|
|
break;
|
|
|
|
case emit_spotlight:
|
|
dot2 = -DotProduct (delta, dl->light.normal);
|
|
if (dot2 <= dl->light.stopdot2)
|
|
return false; // outside light cone
|
|
|
|
out.falloff = dot2 / (dl->light.constant_attn + dl->light.linear_attn * dist + dl->light.quadratic_attn * dist * dist);
|
|
if (dot2 <= dl->light.stopdot) // outside inner cone
|
|
{
|
|
if ((dl->light.exponent == 0.0f) || (dl->light.exponent == 1.0f))
|
|
out.falloff *= (dot2 - dl->light.stopdot2) / (dl->light.stopdot - dl->light.stopdot2);
|
|
else
|
|
out.falloff *= pow((dot2 - dl->light.stopdot2) / (dl->light.stopdot - dl->light.stopdot2), dl->light.exponent);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
Error ("Bad dl->light.type");
|
|
return false;
|
|
}
|
|
|
|
if ( TestLine (pos, src, 0, iThread, static_prop_index_to_ignore) != CONTENTS_EMPTY )
|
|
return false; // occluded
|
|
}
|
|
out.dot[0] = dot;
|
|
for ( int i = 1; i < normalCount; i++ )
|
|
{
|
|
out.dot[i] = DotProduct( pNormals[i], delta );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=============
|
|
AddSampleToPatch
|
|
|
|
Take the sample's collected light and
|
|
add it back into the apropriate patch
|
|
for the radiosity pass.
|
|
=============
|
|
*/
|
|
void AddSampleToPatch (sample_t *s, Vector& light, int facenum)
|
|
{
|
|
patch_t *patch;
|
|
Vector mins, maxs;
|
|
int i;
|
|
|
|
if (numbounce == 0)
|
|
return;
|
|
if( VectorAvg( light ) < 1)
|
|
return;
|
|
|
|
//
|
|
// fixed the sample position and normal -- need to find the equiv pos, etc to set up
|
|
// patches
|
|
//
|
|
if( facePatches.Element( facenum ) == facePatches.InvalidIndex() )
|
|
return;
|
|
|
|
bool bDisp = ( g_pFaces[facenum].dispinfo != -1 );
|
|
|
|
float radius = sqrt( s->area ) / 2.0;
|
|
|
|
patch_t *pNextPatch = NULL;
|
|
for( patch = &patches.Element( facePatches.Element( facenum ) ); patch; patch = pNextPatch )
|
|
{
|
|
// next patch
|
|
pNextPatch = NULL;
|
|
if( patch->ndxNext != patches.InvalidIndex() )
|
|
{
|
|
pNextPatch = &patches.Element( patch->ndxNext );
|
|
}
|
|
|
|
if (patch->sky)
|
|
continue;
|
|
|
|
// skip patches with children
|
|
if ( patch->child1 != patches.InvalidIndex() )
|
|
continue;
|
|
|
|
// see if the point is in this patch (roughly)
|
|
if( !bDisp )
|
|
{
|
|
WindingBounds (patch->winding, mins, maxs);
|
|
}
|
|
else
|
|
{
|
|
mins = patch->mins;
|
|
maxs = patch->maxs;
|
|
}
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
if (mins[i] > s->pos[i] + radius)
|
|
goto nextpatch;
|
|
if (maxs[i] < s->pos[i] - radius)
|
|
goto nextpatch;
|
|
}
|
|
|
|
// add the sample to the patch
|
|
patch->samplearea += s->area;
|
|
VectorMA( patch->samplelight, s->area, light, patch->samplelight );
|
|
|
|
nextpatch:;
|
|
}
|
|
// don't worry if some samples don't find a patch
|
|
}
|
|
|
|
|
|
void GetPhongNormal( int facenum, Vector const& spot, Vector& phongnormal )
|
|
{
|
|
int j;
|
|
dface_t *f = &g_pFaces[facenum];
|
|
// dplane_t *p = &dplanes[f->planenum];
|
|
Vector facenormal, vspot;
|
|
|
|
VectorCopy( dplanes[f->planenum].normal, facenormal );
|
|
VectorCopy( facenormal, phongnormal );
|
|
|
|
if ( smoothing_threshold != 1 )
|
|
{
|
|
faceneighbor_t *fn = &faceneighbor[facenum];
|
|
|
|
// Calculate modified point normal for surface
|
|
// Use the edge normals iff they are defined. Bend the surface towards the edge normal(s)
|
|
// Crude first attempt: find nearest edge normal and do a simple interpolation with facenormal.
|
|
// Second attempt: find edge points+center that bound the point and do a three-point triangulation(baricentric)
|
|
// Better third attempt: generate the point normals for all vertices and do baricentric triangulation.
|
|
|
|
for (j=0 ; j<f->numedges ; j++)
|
|
{
|
|
Vector v1, v2;
|
|
//int e = dsurfedges[f->firstedge + j];
|
|
//int e1 = dsurfedges[f->firstedge + ((j+f->numedges-1)%f->numedges)];
|
|
//int e2 = dsurfedges[f->firstedge + ((j+1)%f->numedges)];
|
|
|
|
//edgeshare_t *es = &edgeshare[abs(e)];
|
|
//edgeshare_t *es1 = &edgeshare[abs(e1)];
|
|
//edgeshare_t *es2 = &edgeshare[abs(e2)];
|
|
// dface_t *f2;
|
|
float a1, a2, aa, bb, ab;
|
|
int vert1, vert2;
|
|
|
|
Vector& n1 = fn->normal[j];
|
|
Vector& n2 = fn->normal[(j+1)%f->numedges];
|
|
|
|
/*
|
|
if (VectorCompare( n1, fn->facenormal )
|
|
&& VectorCompare( n2, fn->facenormal) )
|
|
continue;
|
|
*/
|
|
|
|
vert1 = EdgeVertex( f, j );
|
|
vert2 = EdgeVertex( f, j+1 );
|
|
|
|
Vector& p1 = dvertexes[vert1].point;
|
|
Vector& p2 = dvertexes[vert2].point;
|
|
|
|
// Build vectors from the middle of the face to the edge vertexes and the sample pos.
|
|
VectorSubtract( p1, face_centroids[facenum], v1 );
|
|
VectorSubtract( p2, face_centroids[facenum], v2 );
|
|
VectorSubtract( spot, face_centroids[facenum], vspot );
|
|
aa = DotProduct( v1, v1 );
|
|
bb = DotProduct( v2, v2 );
|
|
ab = DotProduct( v1, v2 );
|
|
a1 = (bb * DotProduct( v1, vspot ) - ab * DotProduct( vspot, v2 )) / (aa * bb - ab * ab);
|
|
a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb;
|
|
|
|
// Test center to sample vector for inclusion between center to vertex vectors (Use dot product of vectors)
|
|
if ( a1 >= 0.0 && a2 >= 0.0)
|
|
{
|
|
// calculate distance from edge to pos
|
|
Vector temp;
|
|
float scale;
|
|
|
|
// Interpolate between the center and edge normals based on sample position
|
|
scale = 1.0 - a1 - a2;
|
|
VectorScale( fn->facenormal, scale, phongnormal );
|
|
VectorScale( n1, a1, temp );
|
|
VectorAdd( phongnormal, temp, phongnormal );
|
|
VectorScale( n2, a2, temp );
|
|
VectorAdd( phongnormal, temp, phongnormal );
|
|
Assert( VectorLength( phongnormal ) > 1.0e-20 );
|
|
VectorNormalize( phongnormal );
|
|
|
|
/*
|
|
if (a1 > 1 || a2 > 1 || a1 + a2 > 1)
|
|
{
|
|
Msg("\n%.2f %.2f\n", a1, a2 );
|
|
Msg("%.2f %.2f %.2f\n", v1[0], v1[1], v1[2] );
|
|
Msg("%.2f %.2f %.2f\n", v2[0], v2[1], v2[2] );
|
|
Msg("%.2f %.2f %.2f\n", vspot[0], vspot[1], vspot[2] );
|
|
exit(1);
|
|
|
|
a1 = 0;
|
|
}
|
|
*/
|
|
/*
|
|
phongnormal[0] = (((j + 1) & 4) != 0) * 255;
|
|
phongnormal[1] = (((j + 1) & 2) != 0) * 255;
|
|
phongnormal[2] = (((j + 1) & 1) != 0) * 255;
|
|
*/
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int GetVisCache( int lastoffset, int cluster, byte *pvs )
|
|
{
|
|
// get the PVS for the pos to limit the number of checks
|
|
if ( !visdatasize )
|
|
{
|
|
memset (pvs, 255, (dvis->numclusters+7)/8 );
|
|
lastoffset = -1;
|
|
}
|
|
else
|
|
{
|
|
if (cluster < 0)
|
|
{
|
|
// Error, point embedded in wall
|
|
// sampled[0][1] = 255;
|
|
memset (pvs, 255, (dvis->numclusters+7)/8 );
|
|
lastoffset = -1;
|
|
}
|
|
else
|
|
{
|
|
int thisoffset = dvis->bitofs[ cluster ][DVIS_PVS];
|
|
if ( thisoffset != lastoffset )
|
|
{
|
|
if ( thisoffset == -1 )
|
|
{
|
|
Error ("visofs == -1");
|
|
}
|
|
|
|
DecompressVis (&dvisdata[thisoffset], pvs);
|
|
}
|
|
lastoffset = thisoffset;
|
|
}
|
|
}
|
|
return lastoffset;
|
|
}
|
|
|
|
|
|
void BuildPatchLights( int facenum );
|
|
|
|
void DumpSamples( int ndxFace, facelight_t *pFaceLight )
|
|
{
|
|
ThreadLock();
|
|
|
|
dface_t *pFace = &g_pFaces[ndxFace];
|
|
if( pFace )
|
|
{
|
|
for( int ndxStyle = 0; ndxStyle < 4; ndxStyle++ )
|
|
{
|
|
if( pFace->styles[ndxStyle] != 255 )
|
|
{
|
|
for( int ndxSample = 0; ndxSample < pFaceLight->numsamples; ndxSample++ )
|
|
{
|
|
sample_t *pSample = &pFaceLight->sample[ndxSample];
|
|
WriteWinding( pFileSamples[ndxStyle], pSample->w, pFaceLight->light[ndxStyle][0][ndxSample] );
|
|
if( bDumpNormals )
|
|
{
|
|
WriteNormal( pFileSamples[ndxStyle], pSample->pos, pSample->normal, 15.0f, pSample->normal * 255.0f );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ThreadUnlock();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Allocates light sample data
|
|
//-----------------------------------------------------------------------------
|
|
static inline void AllocateLightstyleSamples( facelight_t* fl, int styleIndex, int numnormals )
|
|
{
|
|
for (int n = 0; n < numnormals; ++n)
|
|
{
|
|
fl->light[styleIndex][n] = ( Vector* )calloc(fl->numsamples, sizeof(Vector));
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Used to find an existing lightstyle on a face
|
|
//-----------------------------------------------------------------------------
|
|
static inline int FindLightstyle( dface_t* f, int lightstyle )
|
|
{
|
|
for (int k = 0; k < MAXLIGHTMAPS; k++)
|
|
{
|
|
if (f->styles[k] == lightstyle)
|
|
return k;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int FindOrAllocateLightstyleSamples( dface_t* f, facelight_t *fl, int lightstyle, int numnormals )
|
|
{
|
|
// Search the lightstyles associated with the face for a match
|
|
int k;
|
|
for (k = 0; k < MAXLIGHTMAPS; k++)
|
|
{
|
|
if (f->styles[k] == lightstyle)
|
|
break;
|
|
|
|
// Found an empty entry, we can use it for a new lightstyle
|
|
if (f->styles[k] == 255)
|
|
{
|
|
AllocateLightstyleSamples( fl, k, numnormals );
|
|
f->styles[k] = lightstyle;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check for overflow
|
|
if (k >= MAXLIGHTMAPS)
|
|
return -1;
|
|
|
|
return k;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Compute the illumination point + normal for the sample
|
|
//-----------------------------------------------------------------------------
|
|
static void ComputeIlluminationPointAndNormals( lightinfo_t const& l,
|
|
Vector const& samplePosition, Vector const& sampleNormal, SampleInfo_t* pInfo )
|
|
{
|
|
// FIXME: move sample point off the surface a bit, this is done so that
|
|
// light sampling will not be affected by a bug where raycasts will
|
|
// intersect with the face being lit. We really should just have that
|
|
// logic in GatherSampleLight
|
|
VectorAdd( samplePosition, l.facenormal, pInfo->m_Point );
|
|
|
|
if( pInfo->m_IsDispFace )
|
|
{
|
|
// FIXME: un-work around the bug fix workaround above; displacements
|
|
// correctly deal with self-shadowing issues
|
|
pInfo->m_Point = samplePosition;
|
|
Assert( VectorLength( sampleNormal ) > 1.0e-20 );
|
|
pInfo->m_PointNormal[0] = sampleNormal;
|
|
|
|
if( pInfo->m_NormalCount > 1 )
|
|
{
|
|
// use facenormal along with the smooth normal to build the three bump map vectors
|
|
GetBumpNormals( pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[0],
|
|
pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[1], l.facenormal,
|
|
sampleNormal, &pInfo->m_PointNormal[1] );
|
|
}
|
|
}
|
|
else if (!l.isflat)
|
|
{
|
|
// If the face isn't flat, use a phong-based normal instead
|
|
Vector vecSample = samplePosition - l.modelorg;
|
|
GetPhongNormal( pInfo->m_FaceNum, vecSample, pInfo->m_PointNormal[0] );
|
|
|
|
if( pInfo->m_NormalCount > 1 )
|
|
{
|
|
// use facenormal along with the smooth normal to build the three bump map vectors
|
|
GetBumpNormals( pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[0],
|
|
pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[1], l.facenormal,
|
|
pInfo->m_PointNormal[0], &pInfo->m_PointNormal[1] );
|
|
}
|
|
}
|
|
|
|
// Compute the cluster, used for a fast cull for visibility of lights
|
|
// from the sample position
|
|
pInfo->m_Cluster = ClusterFromPoint( samplePosition );
|
|
|
|
Assert( VectorLength( pInfo->m_PointNormal[0]) > 1.0e-20 );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Iterates over all lights and computes lighting at a sample point
|
|
//-----------------------------------------------------------------------------
|
|
static void GatherSampleLightAtPoint( SampleInfo_t& info, int sampleIdx )
|
|
{
|
|
sampleLightOutput_t out;
|
|
|
|
// Iterate over all direct lights and add them to the particular sample
|
|
for (directlight_t *dl = activelights; dl != NULL; dl = dl->next)
|
|
{
|
|
// is this lights cluster visible?
|
|
if ( !PVSCheck( dl->pvs, info.m_Cluster ) )
|
|
continue;
|
|
|
|
// NOTE: Notice here that if the light is on the back side of the face
|
|
// (tested by checking the dot product of the face normal and the light position)
|
|
// we don't want it to contribute to *any* of the bumped lightmaps. It glows
|
|
// in disturbing ways if we don't do this.
|
|
if ( !GatherSampleLight( out, dl, info.m_FaceNum, info.m_Point, info.m_PointNormal, info.m_NormalCount, info.m_iThread ) )
|
|
continue;
|
|
|
|
// Figure out the lightstyle for this particular sample
|
|
int lightStyleIndex = FindOrAllocateLightstyleSamples( info.m_pFace, info.m_pFaceLight,
|
|
dl->light.style, info.m_NormalCount );
|
|
if (lightStyleIndex < 0)
|
|
{
|
|
if (info.m_WarnFace != info.m_FaceNum)
|
|
{
|
|
Warning ("\nWARNING: Too many light styles on a face (%.0f,%.0f,%.0f)\n", info.m_Point[0], info.m_Point[1], info.m_Point[2] );
|
|
info.m_WarnFace = info.m_FaceNum;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// pLightmaps is an array of the lightmaps for each normal direction,
|
|
// here's where the result of the sample gathering goes
|
|
Vector** pLightmaps = info.m_pFaceLight->light[lightStyleIndex];
|
|
|
|
// Incremental lighting only cares about lightstyle zero
|
|
if( g_pIncremental && (dl->light.style == 0) )
|
|
{
|
|
g_pIncremental->AddLightToFace( dl->m_IncrementalID, info.m_FaceNum, sampleIdx,
|
|
info.m_LightmapSize, out.falloff * out.dot[0], info.m_iThread );
|
|
}
|
|
|
|
// Compute the contributions to each of the bumped lightmaps
|
|
// The first sample is for non-bumped lighting.
|
|
// The other sample are for bumpmapping.
|
|
if (! (
|
|
(out.dot[0]>=0)
|
|
)
|
|
)
|
|
{
|
|
// do again for debug
|
|
GatherSampleLight( out, dl, info.m_FaceNum, info.m_Point, info.m_PointNormal, info.m_NormalCount, info.m_iThread );
|
|
}
|
|
|
|
VectorMA( pLightmaps[0][sampleIdx], out.falloff * out.dot[0], dl->light.intensity, pLightmaps[0][sampleIdx] );
|
|
Assert( pLightmaps[0][sampleIdx].x >= 0 && pLightmaps[0][sampleIdx].y >= 0 && pLightmaps[0][sampleIdx].z >= 0 );
|
|
Assert( pLightmaps[0][sampleIdx].x < 1e10 && pLightmaps[0][sampleIdx].y < 1e10 && pLightmaps[0][sampleIdx].z < 1e10 );
|
|
|
|
for( int n = 1; n < info.m_NormalCount; ++n)
|
|
{
|
|
if (out.dot[n] > 0)
|
|
{
|
|
VectorMA( pLightmaps[n][sampleIdx], out.falloff * out.dot[n], dl->light.intensity, pLightmaps[n][sampleIdx] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Iterates over all lights and computes lighting at a sample point
|
|
//-----------------------------------------------------------------------------
|
|
static void ResampleLightAtPoint( SampleInfo_t& info, int lightStyleIndex, int flags, Vector* pLightmap )
|
|
{
|
|
sampleLightOutput_t out;
|
|
|
|
// Iterate over all direct lights and add them to the particular sample
|
|
for (directlight_t *dl = activelights; dl != NULL; dl = dl->next)
|
|
{
|
|
if ((flags & AMBIENT_ONLY) && (dl->light.type != emit_skyambient))
|
|
continue;
|
|
|
|
if ((flags & NON_AMBIENT_ONLY) && (dl->light.type == emit_skyambient))
|
|
continue;
|
|
|
|
// Only add contributions that match the lightstyle
|
|
Assert( lightStyleIndex <= MAXLIGHTMAPS );
|
|
Assert( info.m_pFace->styles[lightStyleIndex] != 255 );
|
|
if (dl->light.style != info.m_pFace->styles[lightStyleIndex])
|
|
continue;
|
|
|
|
// is this lights cluster visible?
|
|
if ( !PVSCheck( dl->pvs, info.m_Cluster ) )
|
|
continue;
|
|
|
|
// NOTE: Notice here that if the light is on the back side of the face
|
|
// (tested by checking the dot product of the face normal and the light position)
|
|
// we don't want it to contribute to *any* of the bumped lightmaps. It glows
|
|
// in disturbing ways if we don't do this.
|
|
if ( !GatherSampleLight( out, dl, info.m_FaceNum, info.m_Point, info.m_PointNormal, info.m_NormalCount, info.m_iThread ) )
|
|
continue;
|
|
|
|
// Compute the contributions to each of the bumped lightmaps
|
|
// The first sample is for non-bumped lighting.
|
|
// The other sample are for bumpmapping.
|
|
VectorMA( pLightmap[0], out.falloff * out.dot[0], dl->light.intensity, pLightmap[0] );
|
|
for( int n = 1; n < info.m_NormalCount; ++n)
|
|
{
|
|
if (out.dot[n] > 0)
|
|
{
|
|
VectorMA( pLightmap[n], out.falloff * out.dot[n], dl->light.intensity, pLightmap[n] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Perform supersampling at a particular point
|
|
//-----------------------------------------------------------------------------
|
|
static int SupersampleLightAtPoint( lightinfo_t& l, SampleInfo_t& info,
|
|
int sampleIndex, int lightStyleIndex, Vector *pDirectLight )
|
|
{
|
|
Vector superSamplePosition;
|
|
Vector2D sampleLightOrigin;
|
|
Vector2D superSampleLightCoord;
|
|
|
|
// Check out the sample we're currently dealing with...
|
|
sample_t& sample = info.m_pFaceLight->sample[sampleIndex];
|
|
|
|
// Get the position of the original sample in lightmapspace
|
|
WorldToLuxelSpace( &l, sample.pos, sampleLightOrigin );
|
|
// Msg("coord %f %f\n", coord[0], coord[1] );
|
|
|
|
// Some parameters related to supersampling
|
|
int subsample = 4; // FIXME: make a parameter
|
|
float cscale = 1.0 / subsample;
|
|
float csshift = -((subsample - 1) * cscale) / 2.0;
|
|
|
|
// Clear out the direct light values
|
|
for (int i = 0; i < info.m_NormalCount; ++i )
|
|
pDirectLight[i].Init( 0, 0, 0 );
|
|
|
|
int subsampleCount = 0;
|
|
for (int s = 0; s < subsample; ++s)
|
|
{
|
|
for (int t = 0; t < subsample; ++t)
|
|
{
|
|
// make sure the coordinate is inside of the sample's winding and when normalizing
|
|
// below use the number of samples used, not just numsamples and some of them
|
|
// will be skipped if they are not inside of the winding
|
|
superSampleLightCoord[0] = sampleLightOrigin[0] + s * cscale + csshift;
|
|
superSampleLightCoord[1] = sampleLightOrigin[1] + t * cscale + csshift;
|
|
// Msg("subsample %f %f\n", superSampleLightCoord[0], superSampleLightCoord[1] );
|
|
|
|
// Figure out where the supersample exists in the world, and make sure
|
|
// it lies within the sample winding
|
|
LuxelSpaceToWorld( &l, superSampleLightCoord[0], superSampleLightCoord[1], superSamplePosition );
|
|
|
|
// A winding should exist only if the sample wasn't a uniform luxel, or if dumppatches is true.
|
|
if ( sample.w )
|
|
{
|
|
if( !PointInWinding( superSamplePosition, sample.w ) )
|
|
continue;
|
|
}
|
|
|
|
// Compute the super-sample illumination point and normal
|
|
// We're assuming the flat normal is the same for all supersamples
|
|
ComputeIlluminationPointAndNormals( l, superSamplePosition, sample.normal, &info );
|
|
|
|
// Resample the non-ambient light at this point...
|
|
ResampleLightAtPoint( info, lightStyleIndex, NON_AMBIENT_ONLY, pDirectLight );
|
|
|
|
// Got another subsample
|
|
++subsampleCount;
|
|
}
|
|
}
|
|
|
|
return subsampleCount;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Compute gradients of a lightmap
|
|
//-----------------------------------------------------------------------------
|
|
static void ComputeLightmapGradients( SampleInfo_t& info, bool const* pHasProcessedSample,
|
|
float* pIntensity, float* gradient )
|
|
{
|
|
int w = info.m_LightmapWidth;
|
|
int h = info.m_LightmapHeight;
|
|
facelight_t* fl = info.m_pFaceLight;
|
|
|
|
for (int i=0 ; i<fl->numsamples ; i++)
|
|
{
|
|
// Don't supersample the same sample twice
|
|
if (pHasProcessedSample[i])
|
|
continue;
|
|
|
|
gradient[i] = 0.0f;
|
|
sample_t& sample = fl->sample[i];
|
|
|
|
// Choose the maximum gradient of all bumped lightmap intensities
|
|
for ( int n = 0; n < info.m_NormalCount; ++n )
|
|
{
|
|
int j = n * info.m_LightmapSize + sample.s + sample.t * w;
|
|
|
|
if (sample.t > 0)
|
|
{
|
|
if (sample.s > 0) gradient[i] = MAX( gradient[i], fabs( pIntensity[j] - pIntensity[j-1-w] ) );
|
|
gradient[i] = MAX( gradient[i], fabs( pIntensity[j] - pIntensity[j-w] ) );
|
|
if (sample.s < w-1) gradient[i] = MAX( gradient[i], fabs( pIntensity[j] - pIntensity[j+1-w] ) );
|
|
}
|
|
if (sample.t < h-1)
|
|
{
|
|
if (sample.s > 0) gradient[i] = MAX( gradient[i], fabs( pIntensity[j] - pIntensity[j-1+w] ) );
|
|
gradient[i] = MAX( gradient[i], fabs( pIntensity[j] - pIntensity[j+w] ) );
|
|
if (sample.s < w-1) gradient[i] = MAX( gradient[i], fabs( pIntensity[j] - pIntensity[j+1+w] ) );
|
|
}
|
|
if (sample.s > 0) gradient[i] = MAX( gradient[i], fabs( pIntensity[j] - pIntensity[j-1] ) );
|
|
if (sample.s < w-1) gradient[i] = MAX( gradient[i], fabs( pIntensity[j] - pIntensity[j+1] ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// ComputeLuxelIntensity...
|
|
//-----------------------------------------------------------------------------
|
|
static inline void ComputeLuxelIntensity( SampleInfo_t& info, int sampleIdx,
|
|
Vector **ppLightSamples, float* pSampleIntensity )
|
|
{
|
|
// Compute a separate intensity for each
|
|
sample_t& sample = info.m_pFaceLight->sample[sampleIdx];
|
|
int destIdx = sample.s + sample.t * info.m_LightmapWidth;
|
|
for (int n = 0; n < info.m_NormalCount; ++n)
|
|
{
|
|
float intensity = ppLightSamples[n][sampleIdx][0] + ppLightSamples[n][sampleIdx][1] + ppLightSamples[n][sampleIdx][2];
|
|
|
|
// convert to a linear perception space
|
|
pSampleIntensity[n * info.m_LightmapSize + destIdx] = pow( intensity / 256.0, 1.0 / 2.2 );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Compute the maximum intensity based on all bumped lighting
|
|
//-----------------------------------------------------------------------------
|
|
static void ComputeSampleIntensities( SampleInfo_t& info, Vector **ppLightSamples, float* pSampleIntensity )
|
|
{
|
|
for (int i=0; i<info.m_pFaceLight->numsamples; i++)
|
|
{
|
|
ComputeLuxelIntensity( info, i, ppLightSamples, pSampleIntensity );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Perform supersampling on a particular lightstyle
|
|
//-----------------------------------------------------------------------------
|
|
static void BuildSupersampleFaceLights( lightinfo_t& l, SampleInfo_t& info, int lightstyleIndex )
|
|
{
|
|
Vector pAmbientLight[NUM_BUMP_VECTS+1];
|
|
Vector pDirectLight[NUM_BUMP_VECTS+1];
|
|
|
|
// This is used to make sure we don't supersample a light sample more than once
|
|
int processedSampleSize = info.m_LightmapSize * sizeof(bool);
|
|
bool* pHasProcessedSample = (bool*)stackalloc( processedSampleSize );
|
|
memset( pHasProcessedSample, 0, processedSampleSize );
|
|
|
|
// This is used to compute a simple gradient computation of the light samples
|
|
// We're going to store the maximum intensity of all bumped samples at each sample location
|
|
float* pGradient = (float*)stackalloc( info.m_pFaceLight->numsamples * sizeof(float) );
|
|
float* pSampleIntensity = (float*)stackalloc( info.m_NormalCount * info.m_LightmapSize * sizeof(float) );
|
|
|
|
// Compute the maximum intensity of all lighting associated with this lightstyle
|
|
// for all bumped lighting
|
|
Vector **ppLightSamples = info.m_pFaceLight->light[lightstyleIndex];
|
|
ComputeSampleIntensities( info, ppLightSamples, pSampleIntensity );
|
|
|
|
Vector *pVisualizePass = NULL;
|
|
if (debug_extra)
|
|
{
|
|
int visualizationSize = info.m_pFaceLight->numsamples * sizeof(Vector);
|
|
pVisualizePass = (Vector*)stackalloc( visualizationSize );
|
|
memset( pVisualizePass, 0, visualizationSize );
|
|
}
|
|
|
|
// What's going on here is that we're looking for large lighting discontinuities
|
|
// (large light intensity gradients) as a clue that we should probably be supersampling
|
|
// in that area. Because the supersampling operation will cause lighting changes,
|
|
// we've found that it's good to re-check the gradients again and see if any other
|
|
// areas should be supersampled as a result of the previous pass. Keep going
|
|
// until all the gradients are reasonable or until we hit a max number of passes
|
|
bool do_anotherpass = true;
|
|
int pass = 1;
|
|
while (do_anotherpass && pass <= extrapasses)
|
|
{
|
|
// Look for lighting discontinuities to see what we should be supersampling
|
|
ComputeLightmapGradients( info, pHasProcessedSample, pSampleIntensity, pGradient );
|
|
|
|
do_anotherpass = false;
|
|
|
|
// Now check all of the samples and supersample those which we have
|
|
// marked as having high gradients
|
|
for (int i=0 ; i<info.m_pFaceLight->numsamples; ++i)
|
|
{
|
|
// Don't supersample the same sample twice
|
|
if (pHasProcessedSample[i])
|
|
continue;
|
|
|
|
// Don't supersample if the lighting is pretty uniform near the sample
|
|
if (pGradient[i] < 0.0625)
|
|
continue;
|
|
|
|
// Joy! We're supersampling now, and we therefore must do another pass
|
|
// Also, we need never bother with this sample again
|
|
pHasProcessedSample[i] = true;
|
|
do_anotherpass = true;
|
|
|
|
if (debug_extra)
|
|
{
|
|
// Mark the little visualization bitmap with a color indicating
|
|
// which pass it was updated on.
|
|
pVisualizePass[i][0] = (pass & 1) * 255;
|
|
pVisualizePass[i][1] = (pass & 2) * 128;
|
|
pVisualizePass[i][2] = (pass & 4) * 64;
|
|
}
|
|
|
|
// Figure out the position + normal direction of the sample under consideration
|
|
sample_t& sample = info.m_pFaceLight->sample[i];
|
|
ComputeIlluminationPointAndNormals( l, sample.pos, sample.normal, &info );
|
|
|
|
// Compute the ambient light for each bump direction vector
|
|
for (int j = 0; j < info.m_NormalCount; ++j )
|
|
pAmbientLight[j].Init( 0, 0, 0 );
|
|
ResampleLightAtPoint( info, lightstyleIndex, AMBIENT_ONLY, pAmbientLight );
|
|
|
|
// Supersample the non-ambient light for each bump direction vector
|
|
int supersampleCount = SupersampleLightAtPoint( l, info, i, lightstyleIndex, pDirectLight );
|
|
|
|
// Because of sampling problems, small area triangles may have no samples.
|
|
// In this case, just use what we already have
|
|
if (supersampleCount > 0)
|
|
{
|
|
// Add the ambient + directional terms togather, stick it back into the lightmap
|
|
for (int n = 0; n < info.m_NormalCount; ++n)
|
|
{
|
|
if( supersampleCount > 0 )
|
|
{
|
|
VectorDivide( pDirectLight[n], supersampleCount, ppLightSamples[n][i] );
|
|
ppLightSamples[n][i] += pAmbientLight[n];
|
|
}
|
|
}
|
|
|
|
// Recompute the luxel intensity based on the supersampling
|
|
ComputeLuxelIntensity( info, i, ppLightSamples, pSampleIntensity );
|
|
}
|
|
|
|
}
|
|
|
|
// We've finished another pass
|
|
pass++;
|
|
}
|
|
|
|
if (debug_extra)
|
|
{
|
|
// Copy colors representing which supersample pass the sample was messed with
|
|
// into the actual lighting values so we can visualize it
|
|
for (int i=0 ; i<info.m_pFaceLight->numsamples ; ++i)
|
|
{
|
|
for (int j = 0; j <info.m_NormalCount; ++j)
|
|
{
|
|
VectorCopy( pVisualizePass[i], ppLightSamples[j][i] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void InitLightinfo( lightinfo_t *pl, int facenum )
|
|
{
|
|
dface_t *f;
|
|
|
|
f = &g_pFaces[facenum];
|
|
|
|
memset (pl, 0, sizeof(*pl));
|
|
pl->facenum = facenum;
|
|
|
|
pl->face = f;
|
|
|
|
//
|
|
// rotate plane
|
|
//
|
|
VectorCopy (dplanes[f->planenum].normal, pl->facenormal);
|
|
pl->facedist = dplanes[f->planenum].dist;
|
|
|
|
// get the origin offset for rotating bmodels
|
|
VectorCopy (face_offset[facenum], pl->modelorg);
|
|
|
|
CalcFaceVectors( pl );
|
|
|
|
// figure out if the surface is flat
|
|
pl->isflat = true;
|
|
if (smoothing_threshold != 1)
|
|
{
|
|
faceneighbor_t *fn = &faceneighbor[facenum];
|
|
|
|
for (int j=0 ; j<f->numedges ; j++)
|
|
{
|
|
float dot = DotProduct( pl->facenormal, fn->normal[j] );
|
|
if (dot < 1.0 - EQUAL_EPSILON)
|
|
{
|
|
pl->isflat = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void InitSampleInfo( lightinfo_t const& l, int iThread, SampleInfo_t& info )
|
|
{
|
|
info.m_LightmapWidth = l.face->m_LightmapTextureSizeInLuxels[0]+1;
|
|
info.m_LightmapHeight = l.face->m_LightmapTextureSizeInLuxels[1]+1;
|
|
info.m_LightmapSize = info.m_LightmapWidth * info.m_LightmapHeight;
|
|
|
|
// How many lightmaps are we going to need?
|
|
info.m_pTexInfo = &texinfo[l.face->texinfo];
|
|
info.m_NormalCount = (info.m_pTexInfo->flags & SURF_BUMPLIGHT) ? NUM_BUMP_VECTS + 1 : 1;
|
|
info.m_FaceNum = l.facenum;
|
|
info.m_pFace = l.face;
|
|
info.m_pFaceLight = &facelight[info.m_FaceNum];
|
|
info.m_IsDispFace = ValidDispFace( info.m_pFace );
|
|
info.m_iThread = iThread;
|
|
info.m_WarnFace = -1;
|
|
|
|
// initialize normals if the surface is flat
|
|
if (l.isflat)
|
|
{
|
|
VectorCopy( l.facenormal, info.m_PointNormal[0] );
|
|
|
|
// use facenormal along with the smooth normal to build the three bump map vectors
|
|
if( info.m_NormalCount > 1 )
|
|
{
|
|
GetBumpNormals( info.m_pTexInfo->textureVecsTexelsPerWorldUnits[0],
|
|
info.m_pTexInfo->textureVecsTexelsPerWorldUnits[1], l.facenormal,
|
|
l.facenormal, &info.m_PointNormal[1] );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void BuildFacelightsOld(int facenum, int iThread);
|
|
|
|
/*
|
|
=============
|
|
BuildFacelights
|
|
=============
|
|
*/
|
|
void BuildFacelights (int iThread, int facenum)
|
|
{
|
|
int i, j;
|
|
|
|
#ifdef TEST_AGAINST_OLD_LIGHTING
|
|
BuildFacelightsOld( facenum, iThread );
|
|
|
|
// Store off the old lightmaps....
|
|
Vector *pOldLight[MAXLIGHTMAPS][NUM_BUMP_VECTS+1];
|
|
for ( i = 0; i < MAXLIGHTMAPS; ++i)
|
|
{
|
|
for ( j = 0; j < NUM_BUMP_VECTS+1; ++j)
|
|
{
|
|
pOldLight[i][j] = facelight[facenum].light[i][j];
|
|
}
|
|
}
|
|
#endif
|
|
|
|
lightinfo_t l;
|
|
dface_t *f;
|
|
facelight_t *fl;
|
|
SampleInfo_t sampleInfo;
|
|
directlight_t *dl;
|
|
Vector spot;
|
|
|
|
if( g_bInterrupt )
|
|
return;
|
|
|
|
// FIXME: Is there a better way to do this? Like, in RunThreadsOn, for instance?
|
|
// Don't pay this cost unless we have to; this is super perf-critical code.
|
|
if (g_pIncremental)
|
|
{
|
|
// Both threads will be accessing this so it needs to be protected or else thread A
|
|
// will load it in and thread B will increment it but its increment will be
|
|
// overwritten by thread A when thread A writes it back.
|
|
ThreadLock();
|
|
++g_iCurFace;
|
|
ThreadUnlock();
|
|
}
|
|
|
|
// some surfaces don't need lightmaps
|
|
f = &g_pFaces[facenum];
|
|
f->lightofs = -1;
|
|
for (j=0 ; j<MAXLIGHTMAPS ; j++)
|
|
f->styles[j] = 255;
|
|
|
|
// Trivial-reject the whole face?
|
|
if( !( g_FacesVisibleToLights[facenum>>3] & (1 << (facenum & 7)) ) )
|
|
return;
|
|
|
|
// check for patches for this face. If none it must be degenerate. Ignore.
|
|
if( facePatches.Element( facenum ) == facePatches.InvalidIndex() )
|
|
return;
|
|
|
|
fl = &facelight[facenum];
|
|
|
|
if ( texinfo[f->texinfo].flags & TEX_SPECIAL)
|
|
return; // non-lit texture
|
|
|
|
InitLightinfo( &l, facenum );
|
|
CalcPoints( &l, fl, facenum );
|
|
InitSampleInfo( l, iThread, sampleInfo );
|
|
|
|
// always allocate style 0 lightmap
|
|
f->styles[0] = 0;
|
|
AllocateLightstyleSamples( fl, 0, sampleInfo.m_NormalCount );
|
|
|
|
// sample the lights at each sample location
|
|
for (i=0 ; i<fl->numsamples ; i++)
|
|
{
|
|
// Figure out the position + normal direction of the sample under consideration
|
|
sample_t& sample = fl->sample[i];
|
|
ComputeIlluminationPointAndNormals( l, sample.pos, sample.normal, &sampleInfo );
|
|
|
|
// Fix up the sample normal in the case of smooth faces...
|
|
if (!l.isflat)
|
|
{
|
|
// The sample normal is now the phong normal
|
|
sample.normal = sampleInfo.m_PointNormal[0];
|
|
}
|
|
|
|
// Iterate over all the lights and add their contribution to this spot
|
|
GatherSampleLightAtPoint( sampleInfo, i );
|
|
}
|
|
|
|
// Tell the incremental light manager that we're done with this face.
|
|
if( g_pIncremental )
|
|
{
|
|
for (dl = activelights; dl != NULL; dl = dl->next)
|
|
{
|
|
// Only deal with lightstyle 0 for incremental lighting
|
|
if (dl->light.style == 0)
|
|
g_pIncremental->FinishFace( dl->m_IncrementalID, facenum, iThread );
|
|
}
|
|
|
|
// Don't have to deal with patch lights (only direct lighting is used)
|
|
// or supersampling
|
|
return;
|
|
}
|
|
|
|
// get rid of the -extra functionality on displacement surfaces
|
|
if (do_extra && !sampleInfo.m_IsDispFace)
|
|
{
|
|
// For each lightstyle, perform a supersampling pass
|
|
for ( i = 0; i < MAXLIGHTMAPS; ++i )
|
|
{
|
|
// Stop when we run out of lightstyles
|
|
if (f->styles[i] == 255)
|
|
break;
|
|
|
|
BuildSupersampleFaceLights( l, sampleInfo, i );
|
|
}
|
|
}
|
|
|
|
#ifdef TEST_AGAINST_OLD_LIGHTING
|
|
for ( i = 1; i < MAXLIGHTMAPS; ++i)
|
|
{
|
|
for ( j = 0; j < NUM_BUMP_VECTS+1; ++j)
|
|
{
|
|
if (!pOldLight[i][j])
|
|
{
|
|
Assert( !fl->light[i][j] );
|
|
}
|
|
else
|
|
{
|
|
for (int s = 0; s < fl->numsamples; ++s)
|
|
{
|
|
Assert( VectorsAreEqual( pOldLight[i][j][s], fl->light[i][j][s], 1e-8 ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!g_bUseMPI)
|
|
{
|
|
//
|
|
// This is done on the master node when MPI is used
|
|
//
|
|
BuildPatchLights( facenum );
|
|
}
|
|
|
|
if( dumppatches )
|
|
{
|
|
DumpSamples( facenum, fl );
|
|
}
|
|
|
|
FreeSampleWindings( fl );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
BuildFacelights
|
|
=============
|
|
*/
|
|
void BuildFacelightsOld(int facenum, int iThread)
|
|
{
|
|
int i, j, k;
|
|
int size;
|
|
lightinfo_t l;
|
|
dface_t *f;
|
|
facelight_t *fl;
|
|
directlight_t *dl;
|
|
Vector spot;
|
|
|
|
|
|
if( g_bInterrupt )
|
|
return;
|
|
|
|
// Both threads will be accessing this so it needs to be protected or else thread A
|
|
// will load it in and thread B will increment it but its increment will be
|
|
// overwritten by thread A when thread A writes it back.
|
|
ThreadLock();
|
|
++g_iCurFace;
|
|
ThreadUnlock();
|
|
|
|
|
|
// Trivial-reject the whole face?
|
|
if( !( g_FacesVisibleToLights[facenum>>3] & (1 << (facenum & 7)) ) )
|
|
return;
|
|
|
|
|
|
// check for patches for this face. If none it must be degenerate. Ignore.
|
|
if( facePatches.Element( facenum ) == facePatches.InvalidIndex() )
|
|
return;
|
|
|
|
f = &g_pFaces[facenum];
|
|
fl = &facelight[facenum];
|
|
|
|
//
|
|
// some surfaces don't need lightmaps
|
|
//
|
|
f->lightofs = -1;
|
|
for (j=0 ; j<MAXLIGHTMAPS ; j++)
|
|
f->styles[j] = 255;
|
|
|
|
if ( texinfo[f->texinfo].flags & TEX_SPECIAL)
|
|
return; // non-lit texture
|
|
|
|
InitLightinfo( &l, facenum );
|
|
|
|
CalcPoints( &l, fl, facenum );
|
|
|
|
int lightmapwidth = l.face->m_LightmapTextureSizeInLuxels[0]+1;
|
|
int lightmapheight = l.face->m_LightmapTextureSizeInLuxels[1]+1;
|
|
|
|
size = lightmapwidth*lightmapheight;
|
|
if (size > SINGLEMAP)
|
|
Error ("Bad lightmap size");
|
|
|
|
|
|
// set up suface normal list
|
|
texinfo_t *pTexinfo = &texinfo[f->texinfo];
|
|
int needsBumpmap = pTexinfo->flags & SURF_BUMPLIGHT ? true : false;
|
|
int numnormals = needsBumpmap ? NUM_BUMP_VECTS+1 : 1;
|
|
Vector pointnormal[ NUM_BUMP_VECTS + 1 ];
|
|
|
|
// initialize normals if the surface is flat
|
|
if (l.isflat)
|
|
{
|
|
VectorCopy( l.facenormal, pointnormal[0] );
|
|
|
|
if( needsBumpmap )
|
|
{
|
|
// use facenormal along with the smooth normal to build the three bump map vectors
|
|
GetBumpNormals( pTexinfo->textureVecsTexelsPerWorldUnits[0],
|
|
pTexinfo->textureVecsTexelsPerWorldUnits[1], l.facenormal,
|
|
pointnormal[0], &pointnormal[1] );
|
|
}
|
|
}
|
|
|
|
// always allocate style 0 bumpmap
|
|
int n;
|
|
f->styles[0] = 0; // Everyone gets the style zero map.
|
|
for (n = 0; n < numnormals; n++)
|
|
{
|
|
fl->light[0][n] = ( Vector* )calloc(fl->numsamples, sizeof(Vector));
|
|
}
|
|
|
|
// sample the lights
|
|
for (i=0 ; i<fl->numsamples ; i++)
|
|
{
|
|
// move sample point off the surface a bit
|
|
VectorAdd( fl->sample[i].pos, l.facenormal, spot );
|
|
|
|
// FIXME: only do this for faces that are parts of entities
|
|
int cluster = ClusterFromPoint( spot );
|
|
|
|
if( ValidDispFace( f ) )
|
|
{
|
|
// spot = fl->sample[i].pos;
|
|
// StaticDispMgr()->GetDispSurfNormal( facenum, spot, pointnormal[0], false );
|
|
// fl->sample[i].pos = spot;
|
|
// fl->sample[i].normal = pointnormal[0];
|
|
|
|
// copy the world/lightmap space transform data
|
|
// StaticDispMgr()->SetLightmapWorldSpaceTransformData( facenum, l );
|
|
|
|
spot = fl->sample[i].pos;
|
|
pointnormal[0] = fl->sample[i].normal;
|
|
|
|
if( needsBumpmap )
|
|
{
|
|
// use facenormal along with the smooth normal to build the three bump map vectors
|
|
GetBumpNormals( pTexinfo->textureVecsTexelsPerWorldUnits[0],
|
|
pTexinfo->textureVecsTexelsPerWorldUnits[1], l.facenormal,
|
|
pointnormal[0], &pointnormal[1] );
|
|
}
|
|
}
|
|
else if (!l.isflat)
|
|
{
|
|
GetPhongNormal( facenum, fl->sample[i].pos, pointnormal[0] );
|
|
fl->sample[i].normal = pointnormal[0];
|
|
|
|
if( needsBumpmap )
|
|
{
|
|
// use facenormal along with the smooth normal to build the three bump map vectors
|
|
GetBumpNormals( pTexinfo->textureVecsTexelsPerWorldUnits[0],
|
|
pTexinfo->textureVecsTexelsPerWorldUnits[1], l.facenormal,
|
|
pointnormal[0], &pointnormal[1] );
|
|
}
|
|
}
|
|
|
|
sampleLightOutput_t out;
|
|
for (dl = activelights; dl != NULL; dl = dl->next)
|
|
{
|
|
// is this lights cluster visible?
|
|
if ( !PVSCheck( dl->pvs, cluster ) )
|
|
continue;
|
|
|
|
if ( GatherSampleLight( out, dl, facenum, spot, pointnormal, numnormals, iThread ) )
|
|
{
|
|
for (k = 0; k < MAXLIGHTMAPS; k++)
|
|
{
|
|
if (f->styles[k] == dl->light.style)
|
|
break;
|
|
else if (f->styles[k] == 255)
|
|
{
|
|
for (n = 0; n < numnormals; n++)
|
|
{
|
|
fl->light[k][n] = ( Vector* )calloc(fl->numsamples, sizeof(Vector));
|
|
}
|
|
f->styles[k] = dl->light.style;
|
|
break;
|
|
}
|
|
}
|
|
if (k >= MAXLIGHTMAPS)
|
|
{
|
|
/*
|
|
Msg ("WARNING: Too many direct light styles on a face(%f,%f,%f)\n",
|
|
fl->sample[i].pos[0], fl->sample[i].pos[1], fl->sample[i].pos[2] );
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
// The first sample is for non-bumped lighting.
|
|
// The other sample are for bumpmapping.
|
|
float flContribution = out.falloff * out.dot[0];
|
|
|
|
VectorMA( fl->light[k][0][i], flContribution, dl->light.intensity, fl->light[k][0][i] );
|
|
|
|
if( g_pIncremental )
|
|
{
|
|
g_pIncremental->AddLightToFace(
|
|
dl->m_IncrementalID,
|
|
facenum,
|
|
i,
|
|
size,
|
|
flContribution,
|
|
iThread );
|
|
}
|
|
|
|
|
|
for( n = 1; n < numnormals; n++)
|
|
{
|
|
if (out.dot[n] > 0)
|
|
{
|
|
VectorMA( fl->light[k][n][i], out.falloff * out.dot[n], dl->light.intensity, fl->light[k][n][i] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tell the incremental light manager that we're done with this face.
|
|
if( g_pIncremental )
|
|
{
|
|
for (dl = activelights; dl != NULL; dl = dl->next)
|
|
g_pIncremental->FinishFace( dl->m_IncrementalID, facenum, iThread );
|
|
|
|
return;
|
|
}
|
|
|
|
bool bIsDisp = ValidDispFace( f );
|
|
|
|
int h = l.face->m_LightmapTextureSizeInLuxels[1]+1;
|
|
int w = l.face->m_LightmapTextureSizeInLuxels[0]+1;
|
|
int do_anotherpass = do_extra;
|
|
int consider[SINGLEMAP];
|
|
float sampled[SINGLEMAP];
|
|
|
|
// get rid of the -extra functionality on displacement surfaces
|
|
if( bIsDisp )
|
|
{
|
|
do_anotherpass = 0;
|
|
}
|
|
|
|
if (do_extra && !bIsDisp)
|
|
{
|
|
for (i=0 ; i<fl->numsamples ; i++)
|
|
{
|
|
consider[i] = true;
|
|
}
|
|
|
|
for (i=0 ; i<fl->numsamples ; i++)
|
|
{
|
|
// convert to a linear perception space
|
|
sampled[fl->sample[i].s + fl->sample[i].t * w] = pow( (fl->light[0][0][i][0] + fl->light[0][0][i][1] + fl->light[0][0][i][2]) / 256.0, 1.0 / 2.2 );
|
|
}
|
|
}
|
|
|
|
int pass = 1;
|
|
Vector passcycle[SINGLEMAP*2];
|
|
|
|
while (do_anotherpass && pass <= extrapasses)
|
|
{
|
|
if( pass == 1 )
|
|
{
|
|
for (i=0 ; i<fl->numsamples ; i++)
|
|
{
|
|
VectorFill( passcycle[i], 0 );
|
|
}
|
|
}
|
|
|
|
// really bad supersampling
|
|
float gradient[SINGLEMAP];
|
|
int s, t;
|
|
|
|
for (i=0 ; i<fl->numsamples ; i++)
|
|
{
|
|
if (!consider[i])
|
|
continue;
|
|
|
|
j = fl->sample[i].s + fl->sample[i].t * w;
|
|
|
|
gradient[i] = 0.0;
|
|
|
|
if (fl->sample[i].t > 0)
|
|
{
|
|
if (fl->sample[i].s > 0) gradient[i] = MAX( gradient[i], fabs( sampled[j] - sampled[j-1-w] ) );
|
|
gradient[i] = MAX( gradient[i], fabs( sampled[j] - sampled[j-w] ) );
|
|
if (fl->sample[i].s < w-1) gradient[i] = MAX( gradient[i], fabs( sampled[j] - sampled[j+1-w] ) );
|
|
}
|
|
if (fl->sample[i].t < h-1)
|
|
{
|
|
if (fl->sample[i].s > 0) gradient[i] = MAX( gradient[i], fabs( sampled[j] - sampled[j-1+w] ) );
|
|
gradient[i] = MAX( gradient[i], fabs( sampled[j] - sampled[j+w] ) );
|
|
if (fl->sample[i].s < w-1) gradient[i] = MAX( gradient[i], fabs( sampled[j] - sampled[j+1+w] ) );
|
|
}
|
|
if (fl->sample[i].s > 0) gradient[i] = MAX( gradient[i], fabs( sampled[j] - sampled[j-1] ) );
|
|
if (fl->sample[i].s < w-1) gradient[i] = MAX( gradient[i], fabs( sampled[j] - sampled[j+1] ) );
|
|
}
|
|
|
|
do_anotherpass = false;
|
|
|
|
for (i=0 ; i<fl->numsamples ; i++)
|
|
{
|
|
if (!consider[i])
|
|
continue;
|
|
|
|
// FIXME: only do this for faces that are parts of entities
|
|
int cluster = ClusterFromPoint( spot );
|
|
|
|
if (gradient[i] > 0.0625)
|
|
{
|
|
consider[i] = false;
|
|
do_anotherpass = true;
|
|
|
|
// Vector pos, normal;
|
|
Vector2D coord, coord2;
|
|
|
|
// SamplePos( fl, i, pos );
|
|
// SampleNormal( fl, i, normal );
|
|
// WorldToCoord( &l, pos, coord );
|
|
|
|
WorldToLuxelSpace( &l, fl->sample[i].pos, coord );
|
|
|
|
// Msg("coord %f %f\n", coord[0], coord[1] );
|
|
|
|
// clear previous light values
|
|
VectorFill( fl->light[0][0][i], 0 );
|
|
|
|
Vector spot2;
|
|
int subsample = 4; // FIXME: make a parameter
|
|
|
|
float cscale = 1.0 / subsample;
|
|
float csshift = -((subsample - 1) * cscale) / 2.0;
|
|
|
|
VectorAdd( fl->sample[i].pos, l.facenormal, spot2 );
|
|
|
|
Vector ambientLight( 0.0f, 0.0f, 0.0f );
|
|
for (dl = activelights; dl != NULL; dl = dl->next)
|
|
{
|
|
if (dl->light.type != emit_skyambient)
|
|
continue;
|
|
|
|
// is this lights cluster visible?
|
|
if ( !PVSCheck( dl->pvs, cluster ) )
|
|
continue;
|
|
|
|
sampleLightOutput_t out;
|
|
if ( GatherSampleLight( out, dl, facenum, spot2, &l.facenormal, 1, iThread ) )
|
|
{
|
|
// accumulate the ambient light here and multiply by the number of "active" subsamples below!!
|
|
VectorMA( ambientLight, ( out.falloff * out.dot[0] ), dl->light.intensity, ambientLight );
|
|
// VectorMA( fl->light[0][0][i], falloff * dot * subsample * subsample, dl->light.intensity, fl->light[0][0][i] );
|
|
}
|
|
}
|
|
|
|
unsigned int subsampleCount = 0;
|
|
for (s = 0; s < subsample; s++)
|
|
{
|
|
for (t = 0; t < subsample; t++)
|
|
{
|
|
// coord2[0] = coord[0] + (rand() % 16) - 7.5;
|
|
// coord2[1] = coord[1] + (rand() % 16) - 7.5;
|
|
// FIXME: should be limited to sample area!!
|
|
|
|
// make sure the coordinate is inside of the sample's winding and when normalizing
|
|
// below use the number of samples used, not just numsamples and some of them
|
|
// will be skipped if they are not inside of the winding
|
|
|
|
coord2[0] = coord[0] + s * cscale + csshift;
|
|
coord2[1] = coord[1] + t * cscale + csshift;
|
|
|
|
// Msg("subsample %f %f\n", coord2[0], coord2[1] );
|
|
|
|
LuxelSpaceToWorld( &l, coord2[0], coord2[1], spot2 );
|
|
|
|
if( PointInWinding( spot2, fl->sample[i].w ) )
|
|
{
|
|
VectorAdd( spot2, l.facenormal, spot2 );
|
|
|
|
for (dl = activelights; dl != NULL; dl = dl->next)
|
|
{
|
|
if (dl->light.type == emit_skyambient)
|
|
continue;
|
|
|
|
// is this lights cluster visible?
|
|
if ( !PVSCheck( dl->pvs, cluster ) )
|
|
continue;
|
|
|
|
sampleLightOutput_t out;
|
|
|
|
if ( GatherSampleLight( out, dl, facenum, spot2, &l.facenormal, 1, iThread ) )
|
|
{
|
|
VectorMA( fl->light[0][0][i], out.falloff * out.dot[0], dl->light.intensity, fl->light[0][0][i] );
|
|
}
|
|
}
|
|
|
|
subsampleCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( subsampleCount > 0 )
|
|
{
|
|
VectorMA( fl->light[0][0][i], ( float )subsampleCount, ambientLight, fl->light[0][0][i] );
|
|
VectorScale( fl->light[0][0][i], 1.0 / ( float )(subsampleCount), fl->light[0][0][i] );
|
|
// VectorScale( fl->light[0][0][i], 1.0 / (subsample * subsample), fl->light[0][0][i] );
|
|
sampled[fl->sample[i].s + fl->sample[i].t * w] = pow( (fl->light[0][0][i][0] + fl->light[0][0][i][1] + fl->light[0][0][i][2]) / 256.0, 1.0 / 2.2 );
|
|
}
|
|
|
|
passcycle[i][0] = (pass & 1) * 255;
|
|
passcycle[i][1] = (pass & 2) * 128;
|
|
passcycle[i][2] = (pass & 4) * 64;
|
|
}
|
|
}
|
|
pass++;
|
|
}
|
|
|
|
for (i=0 ; i<fl->numsamples ; i++)
|
|
{
|
|
// VectorCopy( passcycle[i], fl->light[0][0][i] );
|
|
}
|
|
|
|
// BuildPatchLights( facenum );
|
|
|
|
if( dumppatches )
|
|
{
|
|
DumpSamples( facenum, fl );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void BuildPatchLights( int facenum )
|
|
{
|
|
int i, k;
|
|
|
|
patch_t *patch;
|
|
|
|
dface_t *f = &g_pFaces[facenum];
|
|
facelight_t *fl = &facelight[facenum];
|
|
|
|
for( k = 0; k < MAXLIGHTMAPS; k++ )
|
|
{
|
|
if (f->styles[k] == 0)
|
|
break;
|
|
}
|
|
|
|
if (k >= MAXLIGHTMAPS)
|
|
return;
|
|
|
|
for (i = 0; i < fl->numsamples; i++)
|
|
{
|
|
AddSampleToPatch( &fl->sample[i], fl->light[k][0][i], facenum);
|
|
}
|
|
|
|
// check for a valid face
|
|
if( facePatches.Element( facenum ) == facePatches.InvalidIndex() )
|
|
return;
|
|
|
|
// push up sampled light to parents (children always exist first in the list)
|
|
patch_t *pNextPatch;
|
|
for( patch = &patches.Element( facePatches.Element( facenum ) ); patch; patch = pNextPatch )
|
|
{
|
|
// next patch
|
|
pNextPatch = NULL;
|
|
if( patch->ndxNext != patches.InvalidIndex() )
|
|
{
|
|
pNextPatch = &patches.Element( patch->ndxNext );
|
|
}
|
|
|
|
// skip patches without parents
|
|
if( patch->parent == patches.InvalidIndex() )
|
|
// if (patch->parent == -1)
|
|
continue;
|
|
|
|
patch_t *parent = &patches.Element( patch->parent );
|
|
|
|
parent->samplearea += patch->samplearea;
|
|
VectorAdd( parent->samplelight, patch->samplelight, parent->samplelight );
|
|
}
|
|
|
|
// average up the direct light on each patch for radiosity
|
|
if (numbounce > 0)
|
|
{
|
|
for( patch = &patches.Element( facePatches.Element( facenum ) ); patch; patch = pNextPatch )
|
|
{
|
|
// next patch
|
|
pNextPatch = NULL;
|
|
if( patch->ndxNext != patches.InvalidIndex() )
|
|
{
|
|
pNextPatch = &patches.Element( patch->ndxNext );
|
|
}
|
|
|
|
if (patch->samplearea)
|
|
{
|
|
float scale;
|
|
Vector v;
|
|
scale = 1.0 / patch->samplearea;
|
|
|
|
VectorScale( patch->samplelight, scale, v );
|
|
VectorAdd( patch->totallight.light[0], v, patch->totallight.light[0] );
|
|
VectorAdd( patch->directlight, v, patch->directlight );
|
|
}
|
|
}
|
|
}
|
|
|
|
// pull totallight from children (children always exist first in the list)
|
|
for( patch = &patches.Element( facePatches.Element( facenum ) ); patch; patch = pNextPatch )
|
|
{
|
|
// next patch
|
|
pNextPatch = NULL;
|
|
if( patch->ndxNext != patches.InvalidIndex() )
|
|
{
|
|
pNextPatch = &patches.Element( patch->ndxNext );
|
|
}
|
|
|
|
if ( patch->child1 != patches.InvalidIndex() )
|
|
{
|
|
float s1, s2;
|
|
patch_t *child1;
|
|
patch_t *child2;
|
|
|
|
child1 = &patches.Element( patch->child1 );
|
|
child2 = &patches.Element( patch->child2 );
|
|
|
|
s1 = child1->area / (child1->area + child2->area);
|
|
s2 = child2->area / (child1->area + child2->area);
|
|
|
|
VectorScale( child1->totallight.light[0], s1, patch->totallight.light[0] );
|
|
VectorMA( patch->totallight.light[0], s2, child2->totallight.light[0], patch->totallight.light[0] );
|
|
|
|
VectorCopy( patch->totallight.light[0], patch->directlight );
|
|
}
|
|
}
|
|
|
|
bool needsBumpmap = false;
|
|
if( texinfo[f->texinfo].flags & SURF_BUMPLIGHT )
|
|
{
|
|
needsBumpmap = true;
|
|
}
|
|
|
|
// add an ambient term if desired
|
|
if (ambient[0] || ambient[1] || ambient[2])
|
|
{
|
|
for( int j=0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ )
|
|
{
|
|
if ( f->styles[j] == 0 )
|
|
{
|
|
for (i = 0; i < fl->numsamples; i++)
|
|
{
|
|
VectorAdd( fl->light[j][0][i], ambient, fl->light[j][0][i] );
|
|
if( needsBumpmap )
|
|
{
|
|
VectorAdd( fl->light[j][1][i], ambient, fl->light[j][1][i] );
|
|
VectorAdd( fl->light[j][2][i], ambient, fl->light[j][2][i] );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// light from dlight_threshold and above is sent out, but the
|
|
// texture itself should still be full bright
|
|
|
|
#if 0
|
|
// if( VectorAvg( face_patches[facenum]->baselight ) >= dlight_threshold) // Now all lighted surfaces glow
|
|
{
|
|
for( j=0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ )
|
|
{
|
|
if ( f->styles[j] == 0 )
|
|
{
|
|
// BUG: shouldn't this be done for all patches on the face?
|
|
for (i=0 ; i<fl->numsamples ; i++)
|
|
{
|
|
// garymctchange
|
|
VectorAdd( fl->light[j][0][i], face_patches[facenum]->baselight, fl->light[j][0][i] );
|
|
if( needsBumpmap )
|
|
{
|
|
for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ )
|
|
{
|
|
VectorAdd( fl->light[j][bumpSample][i], face_patches[facenum]->baselight, fl->light[j][bumpSample][i] );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
PrecompLightmapOffsets
|
|
=============
|
|
*/
|
|
|
|
void PrecompLightmapOffsets()
|
|
{
|
|
int facenum;
|
|
dface_t *f;
|
|
int lightstyles;
|
|
int lightdatasize = 0;
|
|
|
|
// NOTE: We store avg face light data in this lump *before* the lightmap data itself
|
|
// in *reverse order* of the way the lightstyles appear in the styles array.
|
|
for( facenum = 0; facenum < numfaces; facenum++ )
|
|
{
|
|
f = &g_pFaces[facenum];
|
|
|
|
if ( texinfo[f->texinfo].flags & TEX_SPECIAL)
|
|
continue; // non-lit texture
|
|
|
|
if ( dlight_map != 0 )
|
|
f->styles[1] = 0;
|
|
|
|
for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ )
|
|
{
|
|
if ( f->styles[lightstyles] == 255 )
|
|
break;
|
|
}
|
|
|
|
if ( !lightstyles )
|
|
continue;
|
|
|
|
// Reserve room for the avg light color data
|
|
lightdatasize += lightstyles * 4;
|
|
|
|
f->lightofs = lightdatasize;
|
|
|
|
bool needsBumpmap = false;
|
|
if( texinfo[f->texinfo].flags & SURF_BUMPLIGHT )
|
|
{
|
|
needsBumpmap = true;
|
|
}
|
|
|
|
int nLuxels = (f->m_LightmapTextureSizeInLuxels[0]+1) * (f->m_LightmapTextureSizeInLuxels[1]+1);
|
|
if( needsBumpmap )
|
|
{
|
|
lightdatasize += nLuxels * 4 * lightstyles * ( NUM_BUMP_VECTS + 1 );
|
|
}
|
|
else
|
|
{
|
|
lightdatasize += nLuxels * 4 * lightstyles;
|
|
}
|
|
}
|
|
|
|
// The incremental lighting code needs us to preserve the contents of dlightdata
|
|
// since it only recomposites lighting for faces that have lights that touch them.
|
|
if( g_pIncremental && pdlightdata->Count() )
|
|
return;
|
|
|
|
pdlightdata->SetSize( lightdatasize );
|
|
}
|
|
CUtlVector<CImagePacker> g_ImagePackers;
|
|
CUtlVector<byte *> g_rawLightmapPages;
|
|
char g_currentMaterialName[256];
|
|
|
|
|
|
// Clamp the three values for bumped lighting such that we trade off directionality for brightness.
|
|
static void ColorClampBumped( Vector& color1, Vector& color2, Vector& color3 )
|
|
{
|
|
Vector maxs;
|
|
Vector *colors[3] = { &color1, &color2, &color3 };
|
|
maxs[0] = VectorMaximum( color1 );
|
|
maxs[1] = VectorMaximum( color2 );
|
|
maxs[2] = VectorMaximum( color3 );
|
|
|
|
// HACK! Clean this up, and add some else statements
|
|
#define CONDITION(a,b,c) do { if( maxs[a] >= maxs[b] && maxs[b] >= maxs[c] ) { order[0] = a; order[1] = b; order[2] = c; } } while( 0 )
|
|
|
|
int order[3];
|
|
CONDITION(0,1,2);
|
|
CONDITION(0,2,1);
|
|
CONDITION(1,0,2);
|
|
CONDITION(1,2,0);
|
|
CONDITION(2,0,1);
|
|
CONDITION(2,1,0);
|
|
|
|
int i;
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
float max = VectorMaximum( *colors[order[i]] );
|
|
if( max <= 1.0f )
|
|
{
|
|
continue;
|
|
}
|
|
// This channel is too bright. . take half of the amount that we are over and
|
|
// add it to the other two channel.
|
|
float factorToRedist = ( max - 1.0f ) / max;
|
|
Vector colorToRedist = factorToRedist * *colors[order[i]];
|
|
*colors[order[i]] -= colorToRedist;
|
|
colorToRedist *= 0.5f;
|
|
*colors[order[(i+1)%3]] += colorToRedist;
|
|
*colors[order[(i+2)%3]] += colorToRedist;
|
|
}
|
|
|
|
ColorClamp( color1 );
|
|
ColorClamp( color2 );
|
|
ColorClamp( color3 );
|
|
|
|
if( color1[0] < 0.f ) color1[0] = 0.f;
|
|
if( color1[1] < 0.f ) color1[1] = 0.f;
|
|
if( color1[2] < 0.f ) color1[2] = 0.f;
|
|
if( color2[0] < 0.f ) color2[0] = 0.f;
|
|
if( color2[1] < 0.f ) color2[1] = 0.f;
|
|
if( color2[2] < 0.f ) color2[2] = 0.f;
|
|
if( color3[0] < 0.f ) color3[0] = 0.f;
|
|
if( color3[1] < 0.f ) color3[1] = 0.f;
|
|
if( color3[2] < 0.f ) color3[2] = 0.f;
|
|
}
|
|
|
|
static void LinearToBumpedLightmap(
|
|
const float *linearColor,
|
|
const float *linearBumpColor1,
|
|
const float *linearBumpColor2,
|
|
const float *linearBumpColor3,
|
|
unsigned char *ret,
|
|
unsigned char *retBump1,
|
|
unsigned char *retBump2,
|
|
unsigned char *retBump3 )
|
|
{
|
|
const Vector &linearBump1 = *( ( const Vector * )linearBumpColor1 );
|
|
const Vector &linearBump2 = *( ( const Vector * )linearBumpColor2 );
|
|
const Vector &linearBump3 = *( ( const Vector * )linearBumpColor3 );
|
|
|
|
Vector gammaGoal;
|
|
// gammaGoal is premultiplied by 1/overbright, which we want
|
|
gammaGoal[0] = LinearToVertexLight( linearColor[0] );
|
|
gammaGoal[1] = LinearToVertexLight( linearColor[1] );
|
|
gammaGoal[2] = LinearToVertexLight( linearColor[2] );
|
|
Vector bumpAverage = linearBump1;
|
|
bumpAverage += linearBump2;
|
|
bumpAverage += linearBump3;
|
|
bumpAverage *= ( 1.0f / 3.0f );
|
|
|
|
Vector correctionScale;
|
|
if( *( int * )&bumpAverage[0] != 0 && *( int * )&bumpAverage[1] != 0 && *( int * )&bumpAverage[2] != 0 )
|
|
{
|
|
// fast path when we know that we don't have to worry about divide by zero.
|
|
VectorDivide( gammaGoal, bumpAverage, correctionScale );
|
|
// correctionScale = gammaGoal / bumpSum;
|
|
}
|
|
else
|
|
{
|
|
correctionScale.Init( 0.0f, 0.0f, 0.0f );
|
|
if( bumpAverage[0] != 0.0f )
|
|
{
|
|
correctionScale[0] = gammaGoal[0] / bumpAverage[0];
|
|
}
|
|
if( bumpAverage[1] != 0.0f )
|
|
{
|
|
correctionScale[1] = gammaGoal[1] / bumpAverage[1];
|
|
}
|
|
if( bumpAverage[2] != 0.0f )
|
|
{
|
|
correctionScale[2] = gammaGoal[2] / bumpAverage[2];
|
|
}
|
|
}
|
|
Vector correctedBumpColor1;
|
|
Vector correctedBumpColor2;
|
|
Vector correctedBumpColor3;
|
|
VectorMultiply( linearBump1, correctionScale, correctedBumpColor1 );
|
|
VectorMultiply( linearBump2, correctionScale, correctedBumpColor2 );
|
|
VectorMultiply( linearBump3, correctionScale, correctedBumpColor3 );
|
|
|
|
Vector check = ( correctedBumpColor1 + correctedBumpColor2 + correctedBumpColor3 ) / 3.0f;
|
|
|
|
ColorClampBumped( correctedBumpColor1, correctedBumpColor2, correctedBumpColor3 );
|
|
|
|
ret[0] = RoundFloatToByte( gammaGoal[0] * 255.0f );
|
|
ret[1] = RoundFloatToByte( gammaGoal[1] * 255.0f );
|
|
ret[2] = RoundFloatToByte( gammaGoal[2] * 255.0f );
|
|
retBump1[0] = RoundFloatToByte( correctedBumpColor1[0] * 255.0f );
|
|
retBump1[1] = RoundFloatToByte( correctedBumpColor1[1] * 255.0f );
|
|
retBump1[2] = RoundFloatToByte( correctedBumpColor1[2] * 255.0f );
|
|
retBump2[0] = RoundFloatToByte( correctedBumpColor2[0] * 255.0f );
|
|
retBump2[1] = RoundFloatToByte( correctedBumpColor2[1] * 255.0f );
|
|
retBump2[2] = RoundFloatToByte( correctedBumpColor2[2] * 255.0f );
|
|
retBump3[0] = RoundFloatToByte( correctedBumpColor3[0] * 255.0f );
|
|
retBump3[1] = RoundFloatToByte( correctedBumpColor3[1] * 255.0f );
|
|
retBump3[2] = RoundFloatToByte( correctedBumpColor3[2] * 255.0f );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Convert a RGBExp32 to a RGBA8888
|
|
// This matches the engine's conversion, so the lighting result is consistent.
|
|
//-----------------------------------------------------------------------------
|
|
void ConvertRGBExp32ToRGBA8888( const ColorRGBExp32 *pSrc, unsigned char *pDst )
|
|
{
|
|
Vector linearColor;
|
|
Vector vertexColor;
|
|
|
|
// convert from ColorRGBExp32 to linear space
|
|
linearColor[0] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->r, ((ColorRGBExp32 *)pSrc)->exponent );
|
|
linearColor[1] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->g, ((ColorRGBExp32 *)pSrc)->exponent );
|
|
linearColor[2] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->b, ((ColorRGBExp32 *)pSrc)->exponent );
|
|
|
|
// convert from linear space to lightmap space
|
|
// cannot use mathlib routine directly because it doesn't match
|
|
// the colorspace version found in the engine, which *is* the same sequence here
|
|
vertexColor[0] = LinearToVertexLight( linearColor[0] );
|
|
vertexColor[1] = LinearToVertexLight( linearColor[1] );
|
|
vertexColor[2] = LinearToVertexLight( linearColor[2] );
|
|
|
|
// this is really a color normalization with a floor
|
|
ColorClamp( vertexColor );
|
|
|
|
// final [0..255] scale
|
|
pDst[0] = RoundFloatToByte( vertexColor[0] * 255.0f );
|
|
pDst[1] = RoundFloatToByte( vertexColor[1] * 255.0f );
|
|
pDst[2] = RoundFloatToByte( vertexColor[2] * 255.0f );
|
|
pDst[3] = 255;
|
|
}
|
|
|
|
void SwizzleRect( const byte *pSource, byte *pTarget, int texWidth, int texHeight, int bytesPerPixel )
|
|
{
|
|
int x;
|
|
int y;
|
|
int n;
|
|
byte *pDst;
|
|
const byte *pSrc;
|
|
|
|
// Initialize a swizzler. The swizzler lets us easily convert texel
|
|
// addresses when the texture is swizzled.
|
|
Swizzler swizzle( texWidth, texHeight, 0 );
|
|
|
|
// Loop through and touch the texels. Note that SetU()/SetV() and
|
|
// IncU()/IncV() are used to control texture addressed. This way,
|
|
// the Get2D() function can be used to get the current address.
|
|
// Initialize the V texture coordinate
|
|
swizzle.SetV( 0 );
|
|
|
|
for ( y = 0; y < texHeight; y++ )
|
|
{
|
|
swizzle.SetU( 0 );
|
|
pSrc = pSource + y*texWidth*bytesPerPixel;
|
|
for ( x = 0; x < texWidth; x++ )
|
|
{
|
|
pDst = pTarget + swizzle.Get2D()*bytesPerPixel;
|
|
for ( n=0; n<bytesPerPixel; n++ )
|
|
{
|
|
pDst[n] = pSrc[n];
|
|
}
|
|
|
|
swizzle.IncU();
|
|
pSrc += bytesPerPixel;
|
|
}
|
|
swizzle.IncV();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Quantize the 32b lightmap pages into a palettized equivalent ready for serialization
|
|
//-----------------------------------------------------------------------------
|
|
void PalettizeLightmaps( void )
|
|
{
|
|
uint8 *pPixels;
|
|
uint8 *pPalette3;
|
|
uint8 *pPalette4;
|
|
int i;
|
|
int j;
|
|
|
|
g_dLightmapPages.Purge();
|
|
g_dLightmapPages.EnsureCount( g_rawLightmapPages.Count() );
|
|
|
|
if ( g_bExportLightmaps )
|
|
{
|
|
// write out the raw 32bpp lightmap page
|
|
for ( i=0; i<g_rawLightmapPages.Count(); ++i )
|
|
{
|
|
char buff[256];
|
|
sprintf( buff, "lightmap_%2.2d.tga", i );
|
|
|
|
CUtlBuffer outBuf;
|
|
TGAWriter::WriteToBuffer(
|
|
g_rawLightmapPages[i], outBuf, MAX_LIGHTMAPPAGE_WIDTH,
|
|
MAX_LIGHTMAPPAGE_HEIGHT, IMAGE_FORMAT_RGBA8888, IMAGE_FORMAT_RGBA8888 );
|
|
g_pFileSystem->WriteFile( buff, NULL, outBuf );
|
|
}
|
|
}
|
|
|
|
pPixels = (uint8 *)malloc( MAX_LIGHTMAPPAGE_WIDTH*MAX_LIGHTMAPPAGE_HEIGHT );
|
|
pPalette3 = (uint8 *)malloc( 256*3 );
|
|
pPalette4 = (uint8 *)malloc( 256*4 );
|
|
|
|
for (i=0; i<g_rawLightmapPages.Count(); i++)
|
|
{
|
|
// remap to 256x3 palette
|
|
ColorQuantize(
|
|
g_rawLightmapPages[i],
|
|
MAX_LIGHTMAPPAGE_WIDTH,
|
|
MAX_LIGHTMAPPAGE_HEIGHT,
|
|
QUANTFLAGS_NODITHER,
|
|
256,
|
|
pPixels,
|
|
pPalette3,
|
|
0 );
|
|
|
|
// fixup 256x3 rgb palette to d3d 256x4 bgra
|
|
for (j=0; j<256; j++)
|
|
{
|
|
pPalette4[j*4+0] = pPalette3[j*3+2];
|
|
pPalette4[j*4+1] = pPalette3[j*3+1];
|
|
pPalette4[j*4+2] = pPalette3[j*3+0];
|
|
pPalette4[j*4+3] = 0xFF;
|
|
}
|
|
|
|
SwizzleRect( pPixels, g_dLightmapPages[i].data, MAX_LIGHTMAPPAGE_WIDTH, MAX_LIGHTMAPPAGE_HEIGHT, 1 );
|
|
memcpy( g_dLightmapPages[i].palette, pPalette4, 256*4 );
|
|
|
|
if ( g_bExportLightmaps && g_pFullFileSystem )
|
|
{
|
|
// write out the palettize page
|
|
byte *pRaw4 = (byte *)malloc( MAX_LIGHTMAPPAGE_WIDTH*MAX_LIGHTMAPPAGE_HEIGHT*4 );
|
|
for (j=0; j<MAX_LIGHTMAPPAGE_WIDTH*MAX_LIGHTMAPPAGE_HEIGHT; j++)
|
|
{
|
|
// index the palette, fixup to tga rgba order
|
|
int color = pPixels[j]*4;
|
|
pRaw4[j*4+0] = pPalette4[color+2];
|
|
pRaw4[j*4+1] = pPalette4[color+1];
|
|
pRaw4[j*4+2] = pPalette4[color+0];
|
|
pRaw4[j*4+3] = pPalette4[color+3];
|
|
}
|
|
|
|
char buff[256];
|
|
sprintf(buff, "lightmap_%2.2d_256.tga", i);
|
|
|
|
CUtlBuffer outBuf;
|
|
TGAWriter::WriteToBuffer(
|
|
pRaw4,
|
|
outBuf,
|
|
MAX_LIGHTMAPPAGE_WIDTH,
|
|
MAX_LIGHTMAPPAGE_HEIGHT,
|
|
IMAGE_FORMAT_RGBA8888,
|
|
IMAGE_FORMAT_RGBA8888 );
|
|
g_pFullFileSystem->WriteFile( buff, NULL, outBuf );
|
|
|
|
free( pRaw4 );
|
|
}
|
|
}
|
|
|
|
free( pPixels );
|
|
free( pPalette3 );
|
|
free( pPalette4 );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Ensure the material names compare correctly by removing path or case disparity.
|
|
//-----------------------------------------------------------------------------
|
|
static int MaterialNameCompare(const char *pName1, const char *pName2)
|
|
{
|
|
char buff1[256];
|
|
char buff2[256];
|
|
Assert( strlen(pName1) < sizeof(buff1) );
|
|
Assert( strlen(pName2) < sizeof(buff2) );
|
|
|
|
// normalize
|
|
strcpy( buff1, pName1 );
|
|
strlwr( buff1 );
|
|
Q_FixSlashes(buff1, '/');
|
|
|
|
// normalize
|
|
strcpy( buff2, pName2 );
|
|
strlwr( buff2 );
|
|
Q_FixSlashes(buff2, '/');
|
|
|
|
return strcmp(buff1, buff2);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Allocate a surface's lightmap onto a page
|
|
// The materials must be presented in sorted order.
|
|
// Lightmaps are allocated onto succesive pages, and do not backfill.
|
|
//-----------------------------------------------------------------------------
|
|
static int AllocateLightmap( int width, int height, int offsetIntoLightmapPage[2], const char *pMaterialName )
|
|
{
|
|
// material change
|
|
int i;
|
|
int lightmapPage = -1;
|
|
int nPackCount;
|
|
static int imagePackerBase = 0;
|
|
bool bMaterialChange;
|
|
|
|
bMaterialChange = false;
|
|
nPackCount = g_ImagePackers.Count();
|
|
if ( g_currentMaterialName[0] && MaterialNameCompare( g_currentMaterialName, pMaterialName ) )
|
|
{
|
|
// advance the lightmap page base to track the start of the active image packers
|
|
// the closed out image packers are not suitable candidates for any further allocations
|
|
if (nPackCount)
|
|
{
|
|
imagePackerBase = nPackCount-1;
|
|
}
|
|
bMaterialChange = true;
|
|
}
|
|
|
|
strcpy(g_currentMaterialName, pMaterialName);
|
|
|
|
// Try to add it to any of the active image packers
|
|
bool bAdded = false;
|
|
for ( i = imagePackerBase; i < nPackCount; ++i )
|
|
{
|
|
bAdded = g_ImagePackers[i].AddBlock( width, height, &offsetIntoLightmapPage[0], &offsetIntoLightmapPage[1] );
|
|
if ( bAdded )
|
|
{
|
|
lightmapPage = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !bAdded )
|
|
{
|
|
// allocate a new page
|
|
lightmapPage = g_ImagePackers.AddToTail();
|
|
g_ImagePackers[lightmapPage].Reset( MAX_LIGHTMAPPAGE_WIDTH, MAX_LIGHTMAPPAGE_HEIGHT );
|
|
|
|
bAdded = g_ImagePackers[lightmapPage].AddBlock( width, height, &offsetIntoLightmapPage[0], &offsetIntoLightmapPage[1] );
|
|
if ( !bAdded )
|
|
{
|
|
// couldn't add to new empty page, undo the new allocation
|
|
g_ImagePackers.Remove( lightmapPage );
|
|
return -1;
|
|
}
|
|
|
|
g_rawLightmapPages.AddToTail();
|
|
g_rawLightmapPages[lightmapPage] = (byte *)malloc( MAX_LIGHTMAPPAGE_WIDTH*MAX_LIGHTMAPPAGE_HEIGHT*4 );
|
|
for (int n=0; n<MAX_LIGHTMAPPAGE_WIDTH*MAX_LIGHTMAPPAGE_HEIGHT; n++)
|
|
{
|
|
if ( g_bExportLightmaps )
|
|
{
|
|
// debugging, fill empty areas with bright green
|
|
((unsigned int *)g_rawLightmapPages[lightmapPage])[n] = 0xFF00FF00;
|
|
}
|
|
else
|
|
{
|
|
// fill empty areas with pure opaque black
|
|
((unsigned int *)g_rawLightmapPages[lightmapPage])[n] = 0xFF000000;
|
|
}
|
|
}
|
|
|
|
if (bMaterialChange && imagePackerBase == nPackCount-1)
|
|
{
|
|
// failed to add new material's lightmap to last open lightmap page, so forced to new page
|
|
// then succeeded on new page
|
|
// for integrity, ensure no further allocations end up on the old page
|
|
imagePackerBase++;
|
|
}
|
|
}
|
|
|
|
return lightmapPage;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Rectangular blit from RGBExp32 lightmaps to final bumped format in lightmap page
|
|
//-----------------------------------------------------------------------------
|
|
static void BlitBumpedLightmapsToPage32bpp( byte *pSource0, byte *pSource1, byte *pSource2, byte *pSource3, byte *pTarget, int targetX, int targetY, int targetW, int targetH, int targetStride)
|
|
{
|
|
int x;
|
|
int y;
|
|
byte *pDst0;
|
|
byte *pDst1;
|
|
byte *pDst2;
|
|
byte *pDst3;
|
|
byte *pSrc0;
|
|
byte *pSrc1;
|
|
byte *pSrc2;
|
|
byte *pSrc3;
|
|
float linearColor[3];
|
|
float linearBumpColor1[3];
|
|
float linearBumpColor2[3];
|
|
float linearBumpColor3[3];
|
|
byte color[4];
|
|
byte bumpColor1[4];
|
|
byte bumpColor2[4];
|
|
byte bumpColor3[4];
|
|
|
|
// add the ColorRGBExp32 bits to the page
|
|
pSrc0 = pSource0;
|
|
pSrc1 = pSource1;
|
|
pSrc2 = pSource2;
|
|
pSrc3 = pSource3;
|
|
for ( y=0; y<targetH; ++y )
|
|
{
|
|
pDst0 = pTarget + ( (y + targetY)*targetStride + targetX + 0*targetW)*sizeof(ColorRGBExp32);
|
|
pDst1 = pTarget + ( (y + targetY)*targetStride + targetX + 1*targetW)*sizeof(ColorRGBExp32);
|
|
pDst2 = pTarget + ( (y + targetY)*targetStride + targetX + 2*targetW)*sizeof(ColorRGBExp32);
|
|
pDst3 = pTarget + ( (y + targetY)*targetStride + targetX + 3*targetW)*sizeof(ColorRGBExp32);
|
|
for ( x=0; x<targetW; ++x )
|
|
{
|
|
// convert from ColorRGBExp32 to linear space
|
|
linearColor[0] = TexLightToLinear( ((ColorRGBExp32 *)pSrc0)->r, ((ColorRGBExp32 *)pSrc0)->exponent );
|
|
linearColor[1] = TexLightToLinear( ((ColorRGBExp32 *)pSrc0)->g, ((ColorRGBExp32 *)pSrc0)->exponent );
|
|
linearColor[2] = TexLightToLinear( ((ColorRGBExp32 *)pSrc0)->b, ((ColorRGBExp32 *)pSrc0)->exponent );
|
|
|
|
linearBumpColor1[0] = TexLightToLinear( ((ColorRGBExp32 *)pSrc1)->r, ((ColorRGBExp32 *)pSrc1)->exponent );
|
|
linearBumpColor1[1] = TexLightToLinear( ((ColorRGBExp32 *)pSrc1)->g, ((ColorRGBExp32 *)pSrc1)->exponent );
|
|
linearBumpColor1[2] = TexLightToLinear( ((ColorRGBExp32 *)pSrc1)->b, ((ColorRGBExp32 *)pSrc1)->exponent );
|
|
|
|
linearBumpColor2[0] = TexLightToLinear( ((ColorRGBExp32 *)pSrc2)->r, ((ColorRGBExp32 *)pSrc2)->exponent );
|
|
linearBumpColor2[1] = TexLightToLinear( ((ColorRGBExp32 *)pSrc2)->g, ((ColorRGBExp32 *)pSrc2)->exponent );
|
|
linearBumpColor2[2] = TexLightToLinear( ((ColorRGBExp32 *)pSrc2)->b, ((ColorRGBExp32 *)pSrc2)->exponent );
|
|
|
|
linearBumpColor3[0] = TexLightToLinear( ((ColorRGBExp32 *)pSrc3)->r, ((ColorRGBExp32 *)pSrc3)->exponent );
|
|
linearBumpColor3[1] = TexLightToLinear( ((ColorRGBExp32 *)pSrc3)->g, ((ColorRGBExp32 *)pSrc3)->exponent );
|
|
linearBumpColor3[2] = TexLightToLinear( ((ColorRGBExp32 *)pSrc3)->b, ((ColorRGBExp32 *)pSrc3)->exponent );
|
|
|
|
LinearToBumpedLightmap(
|
|
linearColor,
|
|
linearBumpColor1,
|
|
linearBumpColor2,
|
|
linearBumpColor3,
|
|
color,
|
|
bumpColor1,
|
|
bumpColor2,
|
|
bumpColor3 );
|
|
|
|
pDst0[0] = color[0];
|
|
pDst0[1] = color[1];
|
|
pDst0[2] = color[2];
|
|
pDst0[3] = 255;
|
|
|
|
pDst1[0] = bumpColor1[0];
|
|
pDst1[1] = bumpColor1[1];
|
|
pDst1[2] = bumpColor1[2];
|
|
pDst1[3] = 255;
|
|
|
|
pDst2[0] = bumpColor2[0];
|
|
pDst2[1] = bumpColor2[1];
|
|
pDst2[2] = bumpColor2[2];
|
|
pDst2[3] = 255;
|
|
|
|
pDst3[0] = bumpColor3[0];
|
|
pDst3[1] = bumpColor3[1];
|
|
pDst3[2] = bumpColor3[2];
|
|
pDst3[3] = 255;
|
|
|
|
pSrc0 += sizeof( ColorRGBExp32 );
|
|
pSrc1 += sizeof( ColorRGBExp32 );
|
|
pSrc2 += sizeof( ColorRGBExp32 );
|
|
pSrc3 += sizeof( ColorRGBExp32 );
|
|
|
|
pDst0 += 4;
|
|
pDst1 += 4;
|
|
pDst2 += 4;
|
|
pDst3 += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Rectangular blit from RGBExp32 lightmaps to final color format in lightmap page
|
|
//-----------------------------------------------------------------------------
|
|
static void BlitLightmapToPage32bpp( byte *pSource, byte *pTarget, int targetX, int targetY, int targetW, int targetH, int targetStride)
|
|
{
|
|
int x;
|
|
int y;
|
|
byte *pDst;
|
|
byte *pSrc;
|
|
|
|
// add the ColorRGBExp32 bits to the page
|
|
pSrc = pSource;
|
|
for ( y=0; y<targetH; ++y )
|
|
{
|
|
pDst = pTarget + ( (y + targetY)*targetStride + targetX )*4;
|
|
for ( x=0; x<targetW; ++x )
|
|
{
|
|
ConvertRGBExp32ToRGBA8888( (ColorRGBExp32 *)pSrc, pDst );
|
|
|
|
pSrc += sizeof( ColorRGBExp32 );
|
|
pDst += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Prepare and drive the allocation of the surface's lightmap
|
|
//-----------------------------------------------------------------------------
|
|
static void RegisterLightmappedSurface( dface_t *pFace )
|
|
{
|
|
int lightmapWidth;
|
|
int lightmapHeight;
|
|
int allocationWidth;
|
|
int allocationHeight;
|
|
int offsetIntoLightmapPage[2];
|
|
const char *pMaterialName;
|
|
int lightmapPage;
|
|
bool bBump;
|
|
|
|
pMaterialName = TexDataStringTable_GetString( dtexdata[texinfo[pFace->texinfo].texdata].nameStringTableID );
|
|
|
|
lightmapWidth = pFace->m_LightmapTextureSizeInLuxels[0] + 1;
|
|
lightmapHeight = pFace->m_LightmapTextureSizeInLuxels[1] + 1;
|
|
|
|
allocationWidth = lightmapWidth;
|
|
allocationHeight = lightmapHeight;
|
|
bBump = ( texinfo[pFace->texinfo].flags & SURF_BUMPLIGHT ) > 0;
|
|
if ( bBump )
|
|
{
|
|
// Allocate all bumped lightmaps next to each other so that we can just
|
|
// increment the s texcoord by pSurf->bumpSTexCoordOffset to render the next
|
|
// of the three lightmaps
|
|
allocationWidth *= NUM_BUMP_VECTS+1;
|
|
}
|
|
|
|
// register this surface's lightmap
|
|
lightmapPage = AllocateLightmap( allocationWidth, allocationHeight, offsetIntoLightmapPage, pMaterialName );
|
|
|
|
// Warning( "%s: page %d (%dx%d)\n", pMaterialName, lightmapPage, allocationWidth, allocationHeight );
|
|
|
|
if ( lightmapPage >= 0 )
|
|
{
|
|
// rectlinear transfer each page
|
|
if (!bBump)
|
|
{
|
|
BlitLightmapToPage32bpp(
|
|
pdlightdata->Base() + pFace->lightofs,
|
|
g_rawLightmapPages[lightmapPage],
|
|
offsetIntoLightmapPage[0],
|
|
offsetIntoLightmapPage[1],
|
|
lightmapWidth,
|
|
lightmapHeight,
|
|
MAX_LIGHTMAPPAGE_WIDTH );
|
|
}
|
|
else
|
|
{
|
|
int bumpMapSize = lightmapWidth*lightmapHeight*sizeof(ColorRGBExp32);
|
|
BlitBumpedLightmapsToPage32bpp(
|
|
pdlightdata->Base() + pFace->lightofs + 0*bumpMapSize,
|
|
pdlightdata->Base() + pFace->lightofs + 1*bumpMapSize,
|
|
pdlightdata->Base() + pFace->lightofs + 2*bumpMapSize,
|
|
pdlightdata->Base() + pFace->lightofs + 3*bumpMapSize,
|
|
g_rawLightmapPages[lightmapPage],
|
|
offsetIntoLightmapPage[0],
|
|
offsetIntoLightmapPage[1],
|
|
lightmapWidth,
|
|
lightmapHeight,
|
|
MAX_LIGHTMAPPAGE_WIDTH );
|
|
}
|
|
|
|
// build a new lightmap page info
|
|
// lightstyles are xbox deprecated, only need single average light color for static style 0
|
|
int pageInfo = g_dLightmapPageInfos.AddToTail();
|
|
g_dLightmapPageInfos[pageInfo].page = lightmapPage;
|
|
g_dLightmapPageInfos[pageInfo].offset[0] = offsetIntoLightmapPage[0];
|
|
g_dLightmapPageInfos[pageInfo].offset[1] = offsetIntoLightmapPage[1];
|
|
g_dLightmapPageInfos[pageInfo].avgColor = *dface_AvgLightColor( pFace, 0 );
|
|
|
|
// hijack lightofs, to index into page infos instead
|
|
pFace->lightofs = pageInfo;
|
|
}
|
|
else
|
|
{
|
|
Error( "AllocateLightmap: (%s) lightmap (%dx%d) too big to fit in page (%dx%d)\n",
|
|
pMaterialName, allocationWidth, allocationHeight, MAX_LIGHTMAPPAGE_WIDTH, MAX_LIGHTMAPPAGE_HEIGHT );
|
|
|
|
// mark as unallocated
|
|
pFace->lightofs = -1;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Comparison function for inserting surfaces to achieve desired sort order
|
|
//-----------------------------------------------------------------------------
|
|
static bool LightmapLess( const int &surfID1, const int &surfID2 )
|
|
{
|
|
dface_t *pFace1 = &g_pFaces[surfID1];
|
|
dface_t *pFace2 = &g_pFaces[surfID2];
|
|
|
|
bool hasLightmap1 = (texinfo[pFace1->texinfo].flags & SURF_NOLIGHT) == 0;
|
|
bool hasLightmap2 = (texinfo[pFace2->texinfo].flags & SURF_NOLIGHT) == 0;
|
|
|
|
// We want lightmapped surfaces to show up first
|
|
if (hasLightmap1 != hasLightmap2)
|
|
return hasLightmap1 > hasLightmap2;
|
|
|
|
// sort by name
|
|
const char *pName1 = TexDataStringTable_GetString( dtexdata[texinfo[pFace1->texinfo].texdata].nameStringTableID );
|
|
const char *pName2 = TexDataStringTable_GetString( dtexdata[texinfo[pFace2->texinfo].texdata].nameStringTableID );
|
|
int sort = MaterialNameCompare( pName1, pName2 );
|
|
if (sort)
|
|
return sort < 0;
|
|
|
|
// then sort by lightmap area for better packing... (big areas first)
|
|
// must account for bumped surfaces to ensure proper descending sequences of areas
|
|
int width = g_pFaces[surfID1].m_LightmapTextureSizeInLuxels[0]+1;
|
|
int height = g_pFaces[surfID1].m_LightmapTextureSizeInLuxels[1]+1;
|
|
if ( texinfo[pFace1->texinfo].flags & SURF_BUMPLIGHT )
|
|
{
|
|
width *= ( NUM_BUMP_VECTS+1 );
|
|
}
|
|
int area1 = width * height;
|
|
|
|
width = g_pFaces[surfID2].m_LightmapTextureSizeInLuxels[0]+1;
|
|
height = g_pFaces[surfID2].m_LightmapTextureSizeInLuxels[1]+1;
|
|
if ( texinfo[pFace2->texinfo].flags & SURF_BUMPLIGHT )
|
|
{
|
|
width *= ( NUM_BUMP_VECTS+1 );
|
|
}
|
|
int area2 = width * height;
|
|
|
|
return area2 < area1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Iterate surfaces generating cooked lightmap pages for palettizing
|
|
// Each page gets its own palette.
|
|
//-----------------------------------------------------------------------------
|
|
void BuildPalettedLightmaps( void )
|
|
{
|
|
int i;
|
|
int j;
|
|
|
|
g_rawLightmapPages.Purge();
|
|
g_currentMaterialName[0] = '\0';
|
|
|
|
g_dLightmapPageInfos.Purge();
|
|
|
|
CUtlRBTree< int, int > surfaces( 0, numfaces, LightmapLess );
|
|
for ( i = 0; i < numfaces; i++ )
|
|
{
|
|
surfaces.Insert( i );
|
|
}
|
|
|
|
// surfaces must be iterated in order to achieve proper lightmap allocation
|
|
for ( i = surfaces.FirstInorder(); i != surfaces.InvalidIndex(); i = surfaces.NextInorder(i) )
|
|
{
|
|
dface_t *pFace = &g_pFaces[surfaces[i]];
|
|
|
|
bool hasLightmap = (texinfo[pFace->texinfo].flags & SURF_NOLIGHT) == 0;
|
|
if ( hasLightmap && pFace->lightofs != -1 )
|
|
{
|
|
RegisterLightmappedSurface( pFace );
|
|
}
|
|
else
|
|
{
|
|
pFace->lightofs = -1;
|
|
}
|
|
|
|
// remove unsupported light styles
|
|
for (j=0; j<MAXLIGHTMAPS; j++)
|
|
pFace->styles[j] = 255;
|
|
}
|
|
|
|
// convert cooked 32b lightmap pages into their palettized versions
|
|
PalettizeLightmaps();
|
|
|
|
for (i=0; i<g_rawLightmapPages.Count(); i++)
|
|
free( g_rawLightmapPages[i] );
|
|
|
|
g_rawLightmapPages.Purge();
|
|
g_ImagePackers.Purge();
|
|
|
|
// no longer require raw rgb light data
|
|
// ensure light data doesn't get serialized
|
|
pdlightdata->Purge();
|
|
}
|
|
|
|
// a temporary struct that holds a x/y/z/u/v for recursive subdivision luxel rasterization
|
|
struct facevert_t
|
|
{
|
|
Vector pos;
|
|
Vector2D luxel;
|
|
};
|
|
|
|
// diff these two color channels
|
|
inline int ColorDelta( int c0, int c1 )
|
|
{
|
|
return abs(c0-c1);
|
|
}
|
|
|
|
// holds the lightmap data for a face for intermediate calcs
|
|
struct lightmapdata_t
|
|
{
|
|
bool bBump;
|
|
ColorRGBExp32 avgColor;
|
|
ColorRGBExp32 bumpColor[NUM_BUMP_VECTS];
|
|
int width; // width including *4 for bumps
|
|
int height;
|
|
int threshold;
|
|
ColorRGBExp32 *pLuxels;
|
|
|
|
void Init( ColorRGBExp32 *_pLuxels, bool _bBump, int _width, int _height, int _threshold )
|
|
{
|
|
pLuxels = _pLuxels;
|
|
bBump = _bBump;
|
|
width = _width;
|
|
height = _height;
|
|
threshold = _threshold;
|
|
}
|
|
|
|
// Get the color of the luxel at s/t
|
|
void GetLuxelAtCoords( ColorRGBExp32 *pDest, float s, float t, int bumpIndex = 0 ) const
|
|
{
|
|
s += 0.5f;
|
|
t += 0.5f;
|
|
int u = (int)s;
|
|
int v = (int)t;
|
|
#if _DEBUG
|
|
int w = bBump ? (width/4) : width;
|
|
Assert( u >= 0 && u <= (w-1) );
|
|
Assert( v >= 0 && v <= (height-1) );
|
|
#endif
|
|
int offset = u + v * width;
|
|
if ( bBump )
|
|
{
|
|
offset += bumpIndex * (width/4);
|
|
}
|
|
if ( offset > width * height )
|
|
offset = width * height;
|
|
*pDest = pLuxels[offset];
|
|
}
|
|
|
|
// Is this color within threshold of the average color for this lightmap
|
|
bool IsAvgColor( ColorRGBExp32 &color ) const
|
|
{
|
|
byte color0[4], color1[4];
|
|
ConvertRGBExp32ToRGBA8888( &avgColor, color0 );
|
|
ConvertRGBExp32ToRGBA8888( &color, color1 );
|
|
int delta = ColorDelta( color0[0], color1[0] );
|
|
delta += ColorDelta( color0[1], color1[1] );
|
|
delta += ColorDelta( color0[2], color1[2] );
|
|
return delta > threshold ? false : true;
|
|
}
|
|
|
|
bool IsAvgColor( float s, float t ) const
|
|
{
|
|
#if 0
|
|
GetLuxelAtCoords( &tmp, s, t );
|
|
return IsAvgColor( tmp );
|
|
#else
|
|
// billinear sample luxel at s,t
|
|
int u = s;
|
|
int v = t;
|
|
float fracU = fmod(s,1) + 0.5f;
|
|
float fracV = fmod(t,1) + 0.5f;
|
|
if ( fracU > 1 )
|
|
{
|
|
fracU -= 1.0f;
|
|
u++;
|
|
}
|
|
if ( fracV > 1 )
|
|
{
|
|
fracV -= 1.0f;
|
|
v++;
|
|
}
|
|
int w = bBump ? (width/4) : width;
|
|
Vector luxel[4];
|
|
for ( int i = 0; i < 4; i++ )
|
|
{
|
|
int sampU = u + ((i&1)?1:0);
|
|
int sampV = v + ((i&2)?1:0);
|
|
sampU = clamp(sampU, 0, w-1);
|
|
sampV = clamp(sampV, 0, height-1);
|
|
int offset = sampU + sampV*width;
|
|
luxel[i][0] = TexLightToLinear( pLuxels[offset].r, pLuxels[offset].exponent );
|
|
luxel[i][1] = TexLightToLinear( pLuxels[offset].g, pLuxels[offset].exponent );
|
|
luxel[i][2] = TexLightToLinear( pLuxels[offset].b, pLuxels[offset].exponent );
|
|
}
|
|
luxel[0] *= (1.0f - fracU) * (1.0f - fracV);
|
|
luxel[1] *= fracU * (1.0f - fracV);
|
|
luxel[2] *= (1.0f - fracU) * fracV;
|
|
luxel[3] *= fracU * fracV;
|
|
Vector out = luxel[0] + luxel[1] + luxel[2] + luxel[3];
|
|
|
|
Vector gammaOut;
|
|
for ( int i = 0; i < 3; i++ )
|
|
{
|
|
gammaOut[i] = LinearToVertexLight( out[i] );
|
|
}
|
|
ColorClamp(gammaOut);
|
|
byte avg[4];
|
|
ConvertRGBExp32ToRGBA8888( &avgColor, avg );
|
|
int delta = 0;
|
|
for ( int i = 0; i < 3; i++ )
|
|
{
|
|
int c = RoundFloatToByte( gammaOut[i] * 255.0f );
|
|
delta += ColorDelta( c, avg[i] );
|
|
}
|
|
return delta > threshold ? false : true;
|
|
#endif
|
|
}
|
|
};
|
|
|
|
// Returns true if this triangle has constant lighting over its surface
|
|
// This is evaluated by recursively subdividing the triangle until it is less than 1 luxel along each edge
|
|
// then the entire triangle is used to point-sample a luxel which is compared to the average for constant-ness
|
|
bool IsConstantTriangle_r( const lightmapdata_t &data, const facevert_t &v0, const facevert_t &v1, const facevert_t &v2 )
|
|
{
|
|
float edge[3];
|
|
edge[0] = (v1.luxel - v0.luxel).Length();
|
|
edge[1] = (v2.luxel - v1.luxel).Length();
|
|
edge[2] = (v0.luxel - v2.luxel).Length();
|
|
|
|
int maxIndex = 0;
|
|
for ( int i = 1; i < 3; i++ )
|
|
{
|
|
if ( edge[i] > edge[maxIndex] )
|
|
{
|
|
maxIndex = i;
|
|
}
|
|
}
|
|
if ( edge[maxIndex] < 0.5f )
|
|
{
|
|
// smaller than 1 luxel
|
|
return data.IsAvgColor( v0.luxel[0], v0.luxel[1] );
|
|
#if 0
|
|
ColorRGBExp32 tmp0, tmp1, tmp2;
|
|
data.GetLuxelAtCoords( &tmp0, v0.luxel[0], v0.luxel[1] );
|
|
data.GetLuxelAtCoords( &tmp1, v1.luxel[0], v1.luxel[1] );
|
|
data.GetLuxelAtCoords( &tmp2, v2.luxel[0], v2.luxel[1] );
|
|
return data.IsAvgColor( tmp0 ) && data.IsAvgColor( tmp1 ) && data.IsAvgColor( tmp2 );
|
|
#endif
|
|
}
|
|
// subdivide largest edge and recurse
|
|
switch( maxIndex )
|
|
{
|
|
case 0: // v1, v0
|
|
{
|
|
facevert_t tmp;
|
|
tmp.pos = 0.5f * v1.pos + 0.5f * v0.pos;
|
|
tmp.luxel = 0.5f * v1.luxel + 0.5f * v0.luxel;
|
|
if ( !IsConstantTriangle_r( data, tmp, v1, v2 ))
|
|
return false;
|
|
return IsConstantTriangle_r( data, v0, tmp, v2 );
|
|
}
|
|
break;
|
|
case 1: // v2, v1
|
|
{
|
|
facevert_t tmp;
|
|
tmp.pos = 0.5f * v2.pos + 0.5f * v1.pos;
|
|
tmp.luxel = 0.5f * v2.luxel + 0.5f * v1.luxel;
|
|
if ( !IsConstantTriangle_r( data, v0, v1, tmp ))
|
|
return false;
|
|
return IsConstantTriangle_r( data, v0, tmp, v2 );
|
|
}
|
|
break;
|
|
default: // v0, v2
|
|
case 2:
|
|
{
|
|
facevert_t tmp;
|
|
tmp.pos = 0.5f * v2.pos + 0.5f * v0.pos;
|
|
tmp.luxel = 0.5f * v2.luxel + 0.5f * v0.luxel;
|
|
if ( !IsConstantTriangle_r( data, v0, v1, tmp ))
|
|
return false;
|
|
return IsConstantTriangle_r( data, tmp, v1, v2 );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check to see if this face has constant lighting by recursive rasterization into the lightmap
|
|
bool IsConstantColor( dface_t *pFace, lightmapdata_t &data )
|
|
{
|
|
int vertIndex[256];
|
|
facevert_t v[256];
|
|
int i;
|
|
Assert( pFace->numedges < 256 );
|
|
texinfo_t *pTexInfo = &texinfo[ pFace->texinfo ];
|
|
BuildFaceCalcWindingData( pFace, vertIndex );
|
|
#if _DEBUG
|
|
int wLimit = data.bBump ? (data.width/4) : data.width;
|
|
int hLimit = data.height;
|
|
#endif
|
|
for ( i = 0; i < pFace->numedges; i++ )
|
|
{
|
|
v[i].pos = dvertexes[vertIndex[i]].point;
|
|
|
|
CalcTextureCoordsAtPoints( pTexInfo->lightmapVecsLuxelsPerWorldUnits, pFace->m_LightmapTextureMinsInLuxels, &v[i].pos, 1, &v[i].luxel );
|
|
if ( v[i].luxel[0] < 0 )
|
|
{
|
|
if ( v[i].luxel[0] > -1e-3f ) // clamp away a little bit of error
|
|
v[i].luxel[0] = 0;
|
|
}
|
|
if ( v[i].luxel[1] < 0 )
|
|
{
|
|
if ( v[i].luxel[1] > -1e-3f ) // clamp away a little bit of error
|
|
v[i].luxel[1] = 0;
|
|
}
|
|
Assert( v[i].luxel[0] >= 0 && v[i].luxel[0] < wLimit );
|
|
Assert( v[i].luxel[1] >= 0 && v[i].luxel[1] < hLimit );
|
|
}
|
|
|
|
// initialize the average color to be the color under the first vertex
|
|
data.GetLuxelAtCoords( &data.avgColor, v[0].luxel[0], v[0].luxel[1] );
|
|
if ( data.bBump )
|
|
{
|
|
for ( i = 0; i < NUM_BUMP_VECTS; i++ )
|
|
{
|
|
data.GetLuxelAtCoords( &data.bumpColor[i], v[0].luxel[0], v[0].luxel[1], i+1 );
|
|
}
|
|
}
|
|
|
|
if ( pFace->GetNumPrims() )
|
|
{
|
|
// use the stored indices
|
|
dprimitive_t *pPrim = &g_primitives[pFace->firstPrimID];
|
|
Assert(pPrim->vertCount == 0);
|
|
Assert(pPrim->indexCount == (pFace->numedges-2)*3);
|
|
unsigned short *pIndices = &g_primindices[pPrim->firstIndex];
|
|
int index = 0;
|
|
for ( i = 0; i < pFace->numedges-2; i++ )
|
|
{
|
|
if ( !IsConstantTriangle_r( data, v[pIndices[index]], v[pIndices[index+1]], v[pIndices[index+2]] ) )
|
|
return false;
|
|
index += 3;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// this surface is a fan
|
|
int index = 0;
|
|
for ( i = 0; i < pFace->numedges-2; i++ )
|
|
{
|
|
if ( !IsConstantTriangle_r( data, v[0], v[i+1], v[i+2] ) )
|
|
return false;
|
|
index += 3;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Iterate the faces and remove any lightmaps that are constant across their renderable luxels
|
|
void CompressConstantLightmaps( int threshold )
|
|
{
|
|
lightmapdata_t mapData;
|
|
|
|
for ( int i = 0; i < numfaces; i++ )
|
|
{
|
|
dface_t *pFace = &g_pFaces[i];
|
|
bool hasLightmap = (texinfo[pFace->texinfo].flags & SURF_NOLIGHT) == 0;
|
|
if ( !hasLightmap || pFace->lightofs == -1 )
|
|
continue;
|
|
|
|
int lightmapWidth = pFace->m_LightmapTextureSizeInLuxels[0] + 1;
|
|
int lightmapHeight = pFace->m_LightmapTextureSizeInLuxels[1] + 1;
|
|
bool bBump = ( texinfo[pFace->texinfo].flags & SURF_BUMPLIGHT ) > 0;
|
|
if ( bBump )
|
|
{
|
|
lightmapWidth *= (NUM_BUMP_VECTS+1);
|
|
}
|
|
|
|
ColorRGBExp32 *pLightdata = reinterpret_cast<ColorRGBExp32 *>(pdlightdata->Base() + pFace->lightofs);
|
|
|
|
mapData.Init( pLightdata, bBump, lightmapWidth, lightmapHeight, threshold );
|
|
if ( IsConstantColor( pFace, mapData ) )
|
|
{
|
|
pLightdata[0] = mapData.avgColor;
|
|
pFace->m_LightmapTextureSizeInLuxels[0] = 0;
|
|
pFace->m_LightmapTextureSizeInLuxels[1] = 0;
|
|
if ( bBump )
|
|
{
|
|
for ( int i = 0; i < NUM_BUMP_VECTS; i++ )
|
|
{
|
|
pLightdata[i+1] = mapData.bumpColor[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|