From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/utils/vrad/lightmap.cpp | 7154 ++++++++++++++++++++-------------------- 1 file changed, 3577 insertions(+), 3577 deletions(-) (limited to 'mp/src/utils/vrad/lightmap.cpp') diff --git a/mp/src/utils/vrad/lightmap.cpp b/mp/src/utils/vrad/lightmap.cpp index bc3d254e..bfa4bd03 100644 --- a/mp/src/utils/vrad/lightmap.cpp +++ b/mp/src/utils/vrad/lightmap.cpp @@ -1,3577 +1,3577 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vrad.h" -#include "lightmap.h" -#include "radial.h" -#include "mathlib/bumpvects.h" -#include "tier1/utlvector.h" -#include "vmpi.h" -#include "mathlib/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 "mathlib/quantize.h" -#include "bitmap/imageformat.h" -#include "coordsize.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 m_Normals; - - -private: - - // This represents a grid from (-1,-1,-1) to (1,1,1). - enum {NUM_SUBDIVS = 8}; - CUtlVector 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 *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; inumedges ; 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 particular vertex - for (i=0, f = g_pFaces ; inumedges ; 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; iplanenum].normal, fn->facenormal ); - - // set displacement surface flag - fn->bHasDisp = false; - if( ValidDispFace( f ) ) - { - fn->bHasDisp = true; - } - } - - // find neighbors - for (i=0, f = g_pFaces ; inormal = ( Vector* )calloc( f->numedges, sizeof( fn->normal[0] ) ); - - // look up all faces sharing vertices and add them to the list - for (j=0 ; jnumedges ; 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 ;inumedges; 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", s, 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 ); - if ( fabs( det ) < 1.0e-20 ) - { - Warning(" warning - face vectors parallel to face normal. bad lighting will be produced\n" ); - l->luxelOrigin = vec3_origin; - } - else - { - // 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 ; inumpoints ; 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! - CUtlVector sampleData; - sampleData.SetCount( SINGLE_BRUSH_MAP * 2 ); - sample_t *samples = sampleData.Base(); - 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 (g_bDumpPatches || (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( g_bDumpPatches ) - { - 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 ; iindex = 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 ); - - if (argCnt==8) // 2 4-tuples - { - if (g_bHDR) - { - r=r_hdr; - g=g_hdr; - b=b_hdr; - scaler=scaler_hdr; - } - argCnt=4; - } - - // make sure light is legal - if( r < 0.0f || g < 0.0f || b < 0.0f || scaler < 0.0f ) - { - intensity.Init( 0.0f, 0.0f, 0.0f ); - return false; - } - - 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 && LightForKey( e, "_lightHDR", 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 ); - } - if ( g_bHDR ) - VectorScale( dl->light.intensity, - FloatForKeyWithDefault( e, "_lightscaleHDR", 1.0 ), - dl->light.intensity ); -} - -static void SetLightFalloffParams( entity_t * e, directlight_t * dl ) -{ - float d50=FloatForKey( e, "_fifty_percent_distance" ); - dl->m_flStartFadeDistance = 0; - dl->m_flEndFadeDistance = - 1; - dl->m_flCapDist = 1.0e22; - if ( d50 ) - { - float d0 = FloatForKey( e, "_zero_percent_distance" ); - if ( d0 < d50 ) - { - Warning( "light has _fifty_percent_distance of %f but _zero_percent_distance of %f\n", d50, d0); - 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; - - - - if ( IntForKey(e, "_hardfalloff" ) ) - { - dl->m_flEndFadeDistance = d0; - dl->m_flStartFadeDistance = 0.75 * d0 + 0.25 * d50; // start fading 3/4 way between 50 and 0. could allow adjust - } - else - { - // now, we will find the point at which the 1/x term reaches its maximum value, and - // prevent the light from going past there. If a user specifes an extreme falloff, the - // quadratic will start making the light brighter at some distance. We handle this by - // fading it from the minimum brightess point down to zero at 10x the minimum distance - if ( fabs( a ) > 0. ) - { - float flMax = b / ( - 2.0 * a ); // where f' = 0 - if ( flMax > 0.0 ) - { - dl->m_flCapDist = flMax; - dl->m_flStartFadeDistance = flMax; - dl->m_flEndFadeDistance = 10.0 * flMax; - } - } - } - } - 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; - } - - FourVectors center4, delta; - fltx4 fractionVisible; - for ( int j = 0; j < NUMVERTEXNORMALS; j+=4 ) - { - // search back to see if we can hit a sky brush - delta.LoadAndSwizzle( g_anorms[j], g_anorms[min( j+1, NUMVERTEXNORMALS-1 )], - g_anorms[min( j+2, NUMVERTEXNORMALS-1 )], g_anorms[min( j+3, NUMVERTEXNORMALS-1 )] ); - delta *= -MAX_TRACE_LENGTH; - delta += center4; - - // return true if any hits sky - TestLine_DoesHitSky ( center4, delta, &fractionVisible ); - if ( TestSignSIMD ( CmpGtSIMD ( fractionVisible, Four_Zeros ) ) ) - return true; - } - - return false; -} - -void BuildVisForLightEnvironment( void ) -{ - // Create the vis. - for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) - { - dleafs[iLeaf].flags &= ~( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ); - 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 ) - { - if ( tex.flags & SURF_SKY2D ) - { - dleafs[iLeaf].flags |= LEAF_FLAGS_SKY2D; - } - else - { - 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) ); - unsigned char *pLeaf2DBits = (unsigned char *)stackalloc( nLeafBytes * sizeof(unsigned char) ); - memset( pLeafBits, 0, nLeafBytes ); - memset( pLeaf2DBits, 0, nLeafBytes ); - - for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) - { - // If this leaf has light (3d skybox) 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 - int nByte = iLeaf >> 3; - int nBit = 1 << ( iLeaf & 0x7 ); - for ( int iLeaf2 = 0; iLeaf2 < numleafs; ++iLeaf2 ) - { - if ( iLeaf2 == iLeaf ) - continue; - - if ( !(dleafs[iLeaf2].flags & ( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ) ) ) - continue; - - // Can this leaf see into the leaf with the sky in it? - if ( !PVSCheck( pvs, dleafs[iLeaf2].cluster ) ) - continue; - - if ( dleafs[iLeaf2].flags & LEAF_FLAGS_SKY2D ) - { - pLeaf2DBits[ nByte ] |= nBit; - } - if ( dleafs[iLeaf2].flags & LEAF_FLAGS_SKY ) - { - pLeafBits[ nByte ] |= nBit; - - // As soon as we know this leaf needs to draw the 3d skybox, we're done - 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 this leaf has light (3d skybox) 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; - - // Check to see if this is a 2D skybox leaf - if ( pLeaf2DBits[ iLeaf >> 3 ] & (1 << ( iLeaf & 0x7 )) ) - { - dleafs[iLeaf].flags |= LEAF_FLAGS_SKY2D; - } - - // If this is a 3D skybox leaf, then we don't care if it was previously a 2D skybox leaf - if ( pLeafBits[ iLeaf >> 3 ] & (1 << ( iLeaf & 0x7 )) ) - { - dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; - dleafs[iLeaf].flags &= ~LEAF_FLAGS_SKY2D; - } - 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) ) - { - // FIXME: Should make a version that checks if we hit 2D skyboxes.. oh well. - dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; - } - } - } - } -} - -static char *ValueForKeyWithDefault (entity_t *ent, char *key, char *default_value = NULL) -{ - epair_t *ep; - - for (ep=ent->epairs ; ep ; ep=ep->next) - if (!strcmp (ep->key, key) ) - return ep->value; - return default_value; -} - -static void ParseLightEnvironment( entity_t* e, directlight_t* dl ) -{ - Vector dest; - GetVectorForKey (e, "origin", dest ); - dl = AllocDLight( dest, false ); - - ParseLightGeneric( e, dl ); - - char *angle_str=ValueForKeyWithDefault( e, "SunSpreadAngle" ); - if (angle_str) - { - g_SunAngularExtent=atof(angle_str); - g_SunAngularExtent=sin((M_PI/180.0)*g_SunAngularExtent); - printf("sun extent from map=%f\n",g_SunAngularExtent); - } - 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 ) ) - { - // 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 ); - } - if ( g_bHDR ) - { - VectorScale( gAmbient->light.intensity, - FloatForKeyWithDefault( e, "_AmbientScaleHDR", 1.0 ), - 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; - CPatch *p = NULL; - directlight_t *dl = NULL; - entity_t *e = NULL; - char *name; - Vector dest; - - numdlights = 0; - - FreeDLights(); - - // - // surfaces - // - unsigned int uiPatchCount = g_Patches.Count(); - for (i=0; i< uiPatchCount; i++) - { - p = &g_Patches.Element( i ); - - // skip parent patches - if (p->child1 != g_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 CONSTANT_DOT (.7/2) - -#define NSAMPLES_SUN_AREA_LIGHT 30 // number of samples to take for an - // non-point sun light - -// Helper function - gathers light from sun (emit_skylight) -void GatherSampleSkyLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, - FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, - int nLFlags, int static_prop_index_to_ignore, - float flEpsilon ) -{ - bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; - bool force_fast = ( nLFlags & GATHERLFLAGS_FORCE_FAST ) != 0; - - fltx4 dot; - - if ( bIgnoreNormals ) - dot = ReplicateX4( CONSTANT_DOT ); - else - dot = NegSIMD( pNormals[0] * dl->light.normal ); - - dot = MaxSIMD( dot, Four_Zeros ); - int zeroMask = TestSignSIMD ( CmpEqSIMD( dot, Four_Zeros ) ); - if (zeroMask == 0xF) - return; - - int nsamples = 1; - if ( g_SunAngularExtent > 0.0f ) - { - nsamples = NSAMPLES_SUN_AREA_LIGHT; - if ( do_fast || force_fast ) - nsamples /= 4; - } - - fltx4 totalFractionVisible = Four_Zeros; - fltx4 fractionVisible = Four_Zeros; - - DirectionalSampler_t sampler; - - for ( int d = 0; d < nsamples; d++ ) - { - // determine visibility of skylight - // serach back to see if we can hit a sky brush - Vector delta; - VectorScale( dl->light.normal, -MAX_TRACE_LENGTH, delta ); - if ( d ) - { - // jitter light source location - Vector ofs = sampler.NextValue(); - ofs *= MAX_TRACE_LENGTH * g_SunAngularExtent; - delta += ofs; - } - FourVectors delta4; - delta4.DuplicateVector ( delta ); - delta4 += pos; - - TestLine_DoesHitSky ( pos, delta4, &fractionVisible, true, static_prop_index_to_ignore ); - - totalFractionVisible = AddSIMD ( totalFractionVisible, fractionVisible ); - } - - fltx4 seeAmount = MulSIMD ( totalFractionVisible, ReplicateX4 ( 1.0f / nsamples ) ); - out.m_flDot[0] = MulSIMD ( dot, seeAmount ); - out.m_flFalloff = Four_Ones; - out.m_flSunAmount = MulSIMD ( seeAmount, ReplicateX4( 10000.0f ) ); - for ( int i = 1; i < normalCount; i++ ) - { - if ( bIgnoreNormals ) - out.m_flDot[i] = ReplicateX4 ( CONSTANT_DOT ); - else - { - out.m_flDot[i] = NegSIMD( pNormals[i] * dl->light.normal ); - out.m_flDot[i] = MulSIMD( out.m_flDot[i], seeAmount ); - } - } -} - -// Helper function - gathers light from ambient sky light -void GatherSampleAmbientSkySSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, - FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, - int nLFlags, int static_prop_index_to_ignore, - float flEpsilon ) -{ - - bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; - bool force_fast = ( nLFlags & GATHERLFLAGS_FORCE_FAST ) != 0; - - fltx4 sumdot = Four_Zeros; - fltx4 ambient_intensity[NUM_BUMP_VECTS+1]; - fltx4 possibleHitCount[NUM_BUMP_VECTS+1]; - fltx4 dots[NUM_BUMP_VECTS+1]; - - for ( int i = 0; i < normalCount; i++ ) - { - ambient_intensity[i] = Four_Zeros; - possibleHitCount[i] = Four_Zeros; - } - - DirectionalSampler_t sampler; - int nsky_samples = NUMVERTEXNORMALS; - if (do_fast || force_fast ) - nsky_samples /= 4; - else - nsky_samples *= g_flSkySampleScale; - - for (int j = 0; j < nsky_samples; j++) - { - FourVectors anorm; - anorm.DuplicateVector( sampler.NextValue() ); - - if ( bIgnoreNormals ) - dots[0] = ReplicateX4( CONSTANT_DOT ); - else - dots[0] = NegSIMD( pNormals[0] * anorm ); - - fltx4 validity = CmpGtSIMD( dots[0], ReplicateX4( EQUAL_EPSILON ) ); - - // No possibility of anybody getting lit - if ( !TestSignSIMD( validity ) ) - continue; - - dots[0] = AndSIMD( validity, dots[0] ); - sumdot = AddSIMD( dots[0], sumdot ); - possibleHitCount[0] = AddSIMD( AndSIMD( validity, Four_Ones ), possibleHitCount[0] ); - - for ( int i = 1; i < normalCount; i++ ) - { - if ( bIgnoreNormals ) - dots[i] = ReplicateX4( CONSTANT_DOT ); - else - dots[i] = NegSIMD( pNormals[i] * anorm ); - fltx4 validity2 = CmpGtSIMD( dots[i], ReplicateX4 ( EQUAL_EPSILON ) ); - dots[i] = AndSIMD( validity2, dots[i] ); - possibleHitCount[i] = AddSIMD( AndSIMD( AndSIMD( validity, validity2 ), Four_Ones ), possibleHitCount[i] ); - } - - // search back to see if we can hit a sky brush - FourVectors delta = anorm; - delta *= -MAX_TRACE_LENGTH; - delta += pos; - FourVectors surfacePos = pos; - FourVectors offset = anorm; - offset *= -flEpsilon; - surfacePos -= offset; - - fltx4 fractionVisible = Four_Ones; - TestLine_DoesHitSky( surfacePos, delta, &fractionVisible, true, static_prop_index_to_ignore ); - for ( int i = 0; i < normalCount; i++ ) - { - fltx4 addedAmount = MulSIMD( fractionVisible, dots[i] ); - ambient_intensity[i] = AddSIMD( ambient_intensity[i], addedAmount ); - } - - } - - out.m_flFalloff = Four_Ones; - for ( int i = 0; i < normalCount; i++ ) - { - // now scale out the missing parts of the hemisphere of this bump basis vector - fltx4 factor = ReciprocalSIMD( possibleHitCount[0] ); - factor = MulSIMD( factor, possibleHitCount[i] ); - out.m_flDot[i] = MulSIMD( factor, sumdot ); - out.m_flDot[i] = ReciprocalSIMD( out.m_flDot[i] ); - out.m_flDot[i] = MulSIMD( ambient_intensity[i], out.m_flDot[i] ); - } - -} - -// Helper function - gathers light from area lights, spot lights, and point lights -void GatherSampleStandardLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, - FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, - int nLFlags, int static_prop_index_to_ignore, - float flEpsilon ) -{ - bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; - - FourVectors src; - src.DuplicateVector( vec3_origin ); - - if (dl->facenum == -1) - { - src.DuplicateVector( dl->light.origin ); - } - - // Find light vector - FourVectors delta; - delta = src; - delta -= pos; - fltx4 dist2 = delta.length2(); - fltx4 rpcDist = ReciprocalSqrtSIMD( dist2 ); - delta *= rpcDist; - fltx4 dist = SqrtEstSIMD( dist2 );//delta.VectorNormalize(); - - // Compute dot - fltx4 dot = ReplicateX4( (float) CONSTANT_DOT ); - if ( !bIgnoreNormals ) - dot = delta * pNormals[0]; - dot = MaxSIMD( Four_Zeros, dot ); - - // Affix dot to zero if past fade distz - bool bHasHardFalloff = ( dl->m_flEndFadeDistance > dl->m_flStartFadeDistance ); - if ( bHasHardFalloff ) - { - fltx4 notPastFadeDist = CmpLeSIMD ( dist, ReplicateX4 ( dl->m_flEndFadeDistance ) ); - dot = AndSIMD( dot, notPastFadeDist ); // dot = 0 if past fade distance - if ( !TestSignSIMD ( notPastFadeDist ) ) - return; - } - - dist = MaxSIMD( dist, Four_Ones ); - fltx4 falloffEvalDist = MinSIMD( dist, ReplicateX4( dl->m_flCapDist ) ); - - fltx4 constant, linear, quadratic; - fltx4 dot2, inCone, inFringe, mult; - FourVectors offset; - - switch (dl->light.type) - { - case emit_point: - constant = ReplicateX4( dl->light.constant_attn ); - linear = ReplicateX4( dl->light.linear_attn ); - quadratic = ReplicateX4( dl->light.quadratic_attn ); - - out.m_flFalloff = MulSIMD( falloffEvalDist, falloffEvalDist ); - out.m_flFalloff = MulSIMD( out.m_flFalloff, quadratic ); - out.m_flFalloff = AddSIMD( out.m_flFalloff, MulSIMD( linear, falloffEvalDist ) ); - out.m_flFalloff = AddSIMD( out.m_flFalloff, constant ); - out.m_flFalloff = ReciprocalSIMD( out.m_flFalloff ); - break; - - case emit_surface: - dot2 = delta * dl->light.normal; - dot2 = NegSIMD( dot2 ); - - // Light behind surface yields zero dot - dot2 = MaxSIMD( Four_Zeros, dot2 ); - if ( TestSignSIMD( CmpEqSIMD( Four_Zeros, dot ) ) == 0xF ) - return; - - out.m_flFalloff = ReciprocalSIMD ( dist2 ); - out.m_flFalloff = MulSIMD( out.m_flFalloff, dot2 ); - - // move the endpoint away from the surface by epsilon to prevent hitting the surface with the trace - offset.DuplicateVector ( dl->light.normal ); - offset *= DIST_EPSILON; - src += offset; - break; - - case emit_spotlight: - dot2 = delta * dl->light.normal; - dot2 = NegSIMD( dot2 ); - - // Affix dot2 to zero if outside light cone - inCone = CmpGtSIMD( dot2, ReplicateX4( dl->light.stopdot2 ) ); - if ( !TestSignSIMD ( inCone ) ) - return; - dot = AndSIMD( inCone, dot ); - - constant = ReplicateX4( dl->light.constant_attn ); - linear = ReplicateX4( dl->light.linear_attn ); - quadratic = ReplicateX4( dl->light.quadratic_attn ); - - out.m_flFalloff = MulSIMD( falloffEvalDist, falloffEvalDist ); - out.m_flFalloff = MulSIMD( out.m_flFalloff, quadratic ); - out.m_flFalloff = AddSIMD( out.m_flFalloff, MulSIMD( linear, falloffEvalDist ) ); - out.m_flFalloff = AddSIMD( out.m_flFalloff, constant ); - out.m_flFalloff = ReciprocalSIMD( out.m_flFalloff ); - out.m_flFalloff = MulSIMD( out.m_flFalloff, dot2 ); - - // outside the inner cone - inFringe = CmpLeSIMD( dot2, ReplicateX4( dl->light.stopdot ) ); - mult = ReplicateX4( dl->light.stopdot - dl->light.stopdot2 ); - mult = ReciprocalSIMD( mult ); - mult = MulSIMD( mult, SubSIMD( dot2, ReplicateX4( dl->light.stopdot2 ) ) ); - mult = MinSIMD( mult, Four_Ones ); - mult = MaxSIMD( mult, Four_Zeros ); - - // pow is fixed point, so this isn't the most accurate, but it doesn't need to be - if ( (dl->light.exponent != 0.0f ) && ( dl->light.exponent != 1.0f ) ) - mult = PowSIMD( mult, dl->light.exponent ); - - // if not in between inner and outer cones, mult by 1 - mult = AndSIMD( inFringe, mult ); - mult = AddSIMD( mult, AndNotSIMD( inFringe, Four_Ones ) ); - out.m_flFalloff = MulSIMD( mult, out.m_flFalloff ); - break; - - } - - // we may be in the fade region - modulate lighting by the fade curve - //float t = ( dist - dl->m_flStartFadeDistance ) / - // ( dl->m_flEndFadeDistance - dl->m_flStartFadeDistance ); - if ( bHasHardFalloff ) - { - fltx4 t = ReplicateX4( dl->m_flEndFadeDistance - dl->m_flStartFadeDistance ); - t = ReciprocalSIMD( t ); - t = MulSIMD( t, SubSIMD( dist, ReplicateX4( dl->m_flStartFadeDistance ) ) ); - - // clamp t to [0...1] - t = MinSIMD( t, Four_Ones ); - t = MaxSIMD( t, Four_Zeros ); - t = SubSIMD( Four_Ones, t ); - - // Using QuinticInterpolatingPolynomial, SSE-ified - // t * t * t *( t * ( t* 6.0 - 15.0 ) + 10.0 ) - mult = SubSIMD( MulSIMD( ReplicateX4( 6.0f ), t ), ReplicateX4( 15.0f ) ); - mult = AddSIMD( MulSIMD( mult, t ), ReplicateX4( 10.0f ) ); - mult = MulSIMD( MulSIMD( t, t), mult ); - mult = MulSIMD( t, mult ); - out.m_flFalloff = MulSIMD( mult, out.m_flFalloff ); - } - - // Raytrace for visibility function - fltx4 fractionVisible = Four_Ones; - TestLine( pos, src, &fractionVisible, static_prop_index_to_ignore); - dot = MulSIMD( fractionVisible, dot ); - out.m_flDot[0] = dot; - - for ( int i = 1; i < normalCount; i++ ) - { - if ( bIgnoreNormals ) - out.m_flDot[i] = ReplicateX4( (float) CONSTANT_DOT ); - else - { - out.m_flDot[i] = pNormals[i] * delta; - out.m_flDot[i] = MaxSIMD( Four_Zeros, out.m_flDot[i] ); - } - } -} - -// returns dot product with normal and delta -// dl - light -// pos - position of sample -// normal - surface normal of sample -// out.m_flDot[] - returned dot products with light vector and each normal -// out.m_flFalloff - amount of light falloff -void GatherSampleLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, - FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, - int nLFlags, - int static_prop_index_to_ignore, - float flEpsilon ) -{ - for ( int b = 0; b < normalCount; b++ ) - out.m_flDot[b] = Four_Zeros; - out.m_flFalloff = Four_Zeros; - out.m_flSunAmount = Four_Zeros; - Assert( normalCount <= (NUM_BUMP_VECTS+1) ); - - // skylights work fundamentally differently than normal lights - switch( dl->light.type ) - { - case emit_skylight: - GatherSampleSkyLightSSE( out, dl, facenum, pos, pNormals, normalCount, - iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); - break; - case emit_skyambient: - GatherSampleAmbientSkySSE( out, dl, facenum, pos, pNormals, normalCount, - iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); - break; - case emit_point: - case emit_surface: - case emit_spotlight: - GatherSampleStandardLightSSE( out, dl, facenum, pos, pNormals, normalCount, - iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); - break; - default: - Error ("Bad dl->light.type"); - return; - } - - // 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. - out.m_flDot[0] = MaxSIMD ( out.m_flDot[0], Four_Zeros ); - fltx4 notZero = CmpGtSIMD( out.m_flDot[0], Four_Zeros ); - for ( int n = 1; n < normalCount; n++ ) - { - out.m_flDot[n] = MaxSIMD( out.m_flDot[n], Four_Zeros ); - out.m_flDot[n] = AndSIMD( out.m_flDot[n], notZero ); - } - -} - -/* - ============= - AddSampleToPatch - - Take the sample's collected light and - add it back into the apropriate patch - for the radiosity pass. - ============= -*/ -void AddSampleToPatch (sample_t *s, LightingValue_t& light, int facenum) -{ - CPatch *patch; - Vector mins, maxs; - int i; - - if (numbounce == 0) - return; - if( VectorAvg( light.m_vecLighting ) < 1) - return; - - // - // fixed the sample position and normal -- need to find the equiv pos, etc to set up - // patches - // - if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) - return; - - float radius = sqrt( s->area ) / 2.0; - - CPatch *pNextPatch = NULL; - for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) - { - // next patch - pNextPatch = NULL; - if( patch->ndxNext != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_Patches.Element( patch->ndxNext ); - } - - if (patch->sky) - continue; - - // skip patches with children - if ( patch->child1 != g_Patches.InvalidIndex() ) - continue; - - // see if the point is in this patch (roughly) - WindingBounds (patch->winding, mins, 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.m_vecLighting, 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 ; jnumedges ; 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; - } - } - } -} - -void GetPhongNormal( int facenum, FourVectors const& spot, FourVectors& phongnormal ) -{ - int j; - dface_t *f = &g_pFaces[facenum]; - // dplane_t *p = &dplanes[f->planenum]; - Vector facenormal; - FourVectors vspot; - - VectorCopy( dplanes[f->planenum].normal, facenormal ); - phongnormal.DuplicateVector( facenormal ); - - FourVectors faceCentroid; - faceCentroid.DuplicateVector( face_centroids[facenum] ); - - 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; - fltx4 a1, a2; - float aa, bb, ab; - int vert1, vert2; - - Vector& n1 = fn->normal[j]; - Vector& n2 = fn->normal[(j+1)%f->numedges]; - - 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 ); - vspot = spot; - vspot -= faceCentroid; - 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); - a1 = ReciprocalSIMD( ReplicateX4( aa * bb - ab * ab ) ); - a1 = MulSIMD( a1, SubSIMD( MulSIMD( ReplicateX4( bb ), vspot * v1 ), MulSIMD( ReplicateX4( ab ), vspot * v2 ) ) ); - //a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb; - a2 = ReciprocalSIMD( ReplicateX4( bb ) ); - a2 = MulSIMD( a2, SubSIMD( vspot * v2, MulSIMD( a1, ReplicateX4( ab ) ) ) ); - - fltx4 resultMask = AndSIMD( CmpGeSIMD( a1, Four_Zeros ), CmpGeSIMD( a2, Four_Zeros ) ); - - if ( !TestSignSIMD( resultMask ) ) - continue; - - // Store the old phong normal to avoid overwriting already computed phong normals - FourVectors oldPhongNormal = phongnormal; - - // calculate distance from edge to pos - FourVectors temp; - fltx4 scale; - - // Interpolate between the center and edge normals based on sample position - scale = SubSIMD( SubSIMD( Four_Ones, a1 ), a2 ); - phongnormal.DuplicateVector( fn->facenormal ); - phongnormal *= scale; - temp.DuplicateVector( n1 ); - temp *= a1; - phongnormal += temp; - temp.DuplicateVector( n2 ); - temp *= a2; - phongnormal += temp; - - // restore the old phong normals - phongnormal.x = AddSIMD( AndSIMD( resultMask, phongnormal.x ), AndNotSIMD( resultMask, oldPhongNormal.x ) ); - phongnormal.y = AddSIMD( AndSIMD( resultMask, phongnormal.y ), AndNotSIMD( resultMask, oldPhongNormal.y ) ); - phongnormal.z = AddSIMD( AndSIMD( resultMask, phongnormal.z ), AndNotSIMD( resultMask, oldPhongNormal.z ) ); - } - - phongnormal.VectorNormalize(); - } -} - - - -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 ) - { - bool bBumpped = ( ( texinfo[pFace->texinfo].flags & SURF_BUMPLIGHT ) != 0 ); - - for( int iStyle = 0; iStyle < 4; ++iStyle ) - { - if( pFace->styles[iStyle] != 255 ) - { - for ( int iBump = 0; iBump < 4; ++iBump ) - { - if ( iBump == 0 || ( iBump > 0 && bBumpped ) ) - { - for( int iSample = 0; iSample < pFaceLight->numsamples; ++iSample ) - { - sample_t *pSample = &pFaceLight->sample[iSample]; - WriteWinding( pFileSamples[iStyle][iBump], pSample->w, pFaceLight->light[iStyle][iBump][iSample].m_vecLighting ); - if( bDumpNormals ) - { - WriteNormal( pFileSamples[iStyle][iBump], 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] = ( LightingValue_t* )calloc( fl->numsamples, sizeof(LightingValue_t ) ); - } -} - - -//----------------------------------------------------------------------------- -// 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 ComputeIlluminationPointAndNormalsSSE( lightinfo_t const& l, FourVectors const &pos, FourVectors const &norm, SSE_SampleInfo_t* pInfo, int numSamples ) -{ - - Vector v[4]; - - pInfo->m_Points = pos; - bool computeNormals = ( pInfo->m_NormalCount > 1 && ( pInfo->m_IsDispFace || !l.isflat ) ); - - // 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 - FourVectors faceNormal; - faceNormal.DuplicateVector( l.facenormal ); - pInfo->m_Points += faceNormal; - - if ( pInfo->m_IsDispFace ) - { - pInfo->m_PointNormals[0] = norm; - } - else if ( !l.isflat ) - { - // If the face isn't flat, use a phong-based normal instead - FourVectors modelorg; - modelorg.DuplicateVector( l.modelorg ); - FourVectors vecSample = pos; - vecSample -= modelorg; - GetPhongNormal( pInfo->m_FaceNum, vecSample, pInfo->m_PointNormals[0] ); - } - - if ( computeNormals ) - { - Vector bv[4][NUM_BUMP_VECTS]; - for ( int i = 0; i < 4; ++i ) - { - // TODO: using Vec may slow things down a bit - GetBumpNormals( pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[0], - pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[1], - l.facenormal, pInfo->m_PointNormals[0].Vec( i ), bv[i] ); - } - for ( int b = 0; b < NUM_BUMP_VECTS; ++b ) - { - pInfo->m_PointNormals[b+1].LoadAndSwizzle ( bv[0][b], bv[1][b], bv[2][b], bv[3][b] ); - } - } - - // TODO: this may slow things down a bit ( using Vec ) - for ( int i = 0; i < 4; ++i ) - pInfo->m_Clusters[i] = ClusterFromPoint( pos.Vec( i ) ); -} - -//----------------------------------------------------------------------------- -// Iterates over all lights and computes lighting at up to 4 sample points -//----------------------------------------------------------------------------- -static void GatherSampleLightAt4Points( SSE_SampleInfo_t& info, int sampleIdx, int numSamples ) -{ - SSE_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? - fltx4 dotMask = Four_Zeros; - bool skipLight = true; - for( int s = 0; s < numSamples; s++ ) - { - if( PVSCheck( dl->pvs, info.m_Clusters[s] ) ) - { - dotMask = SetComponentSIMD( dotMask, s, 1.0f ); - skipLight = false; - } - } - if ( skipLight ) - continue; - - GatherSampleLightSSE( out, dl, info.m_FaceNum, info.m_Points, info.m_PointNormals, info.m_NormalCount, info.m_iThread ); - - // Apply the PVS check filter and compute falloff x dot - fltx4 fxdot[NUM_BUMP_VECTS + 1]; - skipLight = true; - for ( int b = 0; b < info.m_NormalCount; b++ ) - { - fxdot[b] = MulSIMD( out.m_flDot[b], dotMask ); - fxdot[b] = MulSIMD( fxdot[b], out.m_flFalloff ); - if ( !IsAllZeros( fxdot[b] ) ) - { - skipLight = false; - } - } - if ( skipLight ) - 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 at (%f, %f, %f)\n", - info.m_Points.x.m128_f32[0], info.m_Points.y.m128_f32[0], info.m_Points.z.m128_f32[0] ); - 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 - LightingValue_t** pLightmaps = info.m_pFaceLight->light[lightStyleIndex]; - - // Incremental lighting only cares about lightstyle zero - if( g_pIncremental && (dl->light.style == 0) ) - { - for ( int i = 0; i < numSamples; i++ ) - { - g_pIncremental->AddLightToFace( dl->m_IncrementalID, info.m_FaceNum, sampleIdx + i, - info.m_LightmapSize, SubFloat( fxdot[0], i ), info.m_iThread ); - } - } - - for( int n = 0; n < info.m_NormalCount; ++n ) - { - for ( int i = 0; i < numSamples; i++ ) - { - pLightmaps[n][sampleIdx + i].AddLight( SubFloat( fxdot[n], i ), dl->light.intensity, SubFloat( out.m_flSunAmount, i ) ); - } - } - } -} - - - -//----------------------------------------------------------------------------- -// Iterates over all lights and computes lighting at a sample point -//----------------------------------------------------------------------------- -static void ResampleLightAt4Points( SSE_SampleInfo_t& info, int lightStyleIndex, int flags, LightingValue_t pLightmap[4][NUM_BUMP_VECTS+1] ) -{ - SSE_sampleLightOutput_t out; - - // Clear result - for ( int i = 0; i < 4; ++i ) - { - for ( int n = 0; n < info.m_NormalCount; ++n ) - { - pLightmap[i][n].Zero(); - } - } - - // 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? - fltx4 dotMask = Four_Zeros; - bool skipLight = true; - for( int s = 0; s < 4; s++ ) - { - if( PVSCheck( dl->pvs, info.m_Clusters[s] ) ) - { - dotMask = SetComponentSIMD( dotMask, s, 1.0f ); - skipLight = false; - } - } - if ( skipLight ) - 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. - GatherSampleLightSSE( out, dl, info.m_FaceNum, info.m_Points, info.m_PointNormals, info.m_NormalCount, info.m_iThread ); - - // Apply the PVS check filter and compute falloff x dot - fltx4 fxdot[NUM_BUMP_VECTS + 1]; - for ( int b = 0; b < info.m_NormalCount; b++ ) - { - fxdot[b] = MulSIMD( out.m_flFalloff, out.m_flDot[b] ); - fxdot[b] = MulSIMD( fxdot[b], dotMask ); - } - - // Compute the contributions to each of the bumped lightmaps - // The first sample is for non-bumped lighting. - // The other sample are for bumpmapping. - for( int i = 0; i < 4; ++i ) - { - for( int n = 0; n < info.m_NormalCount; ++n ) - { - pLightmap[i][n].AddLight( SubFloat( fxdot[n], i ), dl->light.intensity, SubFloat( out.m_flSunAmount, i ) ); - } - } - } -} - -bool PointsInWinding ( FourVectors const & point, winding_t *w, int &invalidBits ) -{ - FourVectors edge, toPt, cross, testCross, p0, p1; - fltx4 invalidMask; - - // - // get the first normal to test - // - p0.DuplicateVector( w->p[0] ); - p1.DuplicateVector( w->p[1] ); - toPt = point; - toPt -= p0; - edge = p1; - edge -= p0; - testCross = edge ^ toPt; - testCross.VectorNormalizeFast(); - - for( int ndxPt = 1; ndxPt < w->numpoints; ndxPt++ ) - { - p0.DuplicateVector( w->p[ndxPt] ); - p1.DuplicateVector( w->p[(ndxPt+1)%w->numpoints] ); - toPt = point; - toPt -= p0; - edge = p1; - edge -= p0; - cross = edge ^ toPt; - cross.VectorNormalizeFast(); - - fltx4 dot = cross * testCross; - invalidMask = OrSIMD( invalidMask, CmpLtSIMD( dot, Four_Zeros ) ); - - invalidBits = TestSignSIMD ( invalidMask ); - if ( invalidBits == 0xF ) - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Perform supersampling at a particular point -//----------------------------------------------------------------------------- -static int SupersampleLightAtPoint( lightinfo_t& l, SSE_SampleInfo_t& info, - int sampleIndex, int lightStyleIndex, LightingValue_t *pLight, int flags ) -{ - sample_t& sample = info.m_pFaceLight->sample[sampleIndex]; - - // Get the position of the original sample in lightmapspace - Vector2D temp; - WorldToLuxelSpace( &l, sample.pos, temp ); - Vector sampleLightOrigin( temp[0], temp[1], 0.0f ); - - // Some parameters related to supersampling - float sampleWidth = ( flags & NON_AMBIENT_ONLY ) ? 4 : 2; - float cscale = 1.0f / sampleWidth; - float csshift = -((sampleWidth - 1) * cscale) / 2.0; - - // Clear out the light values - for (int i = 0; i < info.m_NormalCount; ++i ) - pLight[i].Zero(); - - int subsampleCount = 0; - - FourVectors superSampleNormal; - superSampleNormal.DuplicateVector( sample.normal ); - - FourVectors superSampleLightCoord; - FourVectors superSamplePosition; - - if ( flags & NON_AMBIENT_ONLY ) - { - float aRow[4]; - for ( int coord = 0; coord < 4; ++coord ) - aRow[coord] = csshift + coord * cscale; - fltx4 sseRow = LoadUnalignedSIMD( aRow ); - - for (int s = 0; s < 4; ++s) - { - // 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.DuplicateVector( sampleLightOrigin ); - superSampleLightCoord.x = AddSIMD( superSampleLightCoord.x, ReplicateX4( aRow[s] ) ); - superSampleLightCoord.y = AddSIMD( superSampleLightCoord.y, sseRow ); - - // 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 g_bDumpPatches is true. - int invalidBits = 0; - if ( sample.w && !PointsInWinding( superSamplePosition, sample.w, invalidBits ) ) - continue; - - // Compute the super-sample illumination point and normal - // We're assuming the flat normal is the same for all supersamples - ComputeIlluminationPointAndNormalsSSE( l, superSamplePosition, superSampleNormal, &info, 4 ); - - // Resample the non-ambient light at this point... - LightingValue_t result[4][NUM_BUMP_VECTS+1]; - ResampleLightAt4Points( info, lightStyleIndex, NON_AMBIENT_ONLY, result ); - - // Got more subsamples - for ( int i = 0; i < 4; i++ ) - { - if ( !( ( invalidBits >> i ) & 0x1 ) ) - { - for ( int n = 0; n < info.m_NormalCount; ++n ) - { - pLight[n].AddLight( result[i][n] ); - } - ++subsampleCount; - } - } - } - } - else - { - FourVectors superSampleOffsets; - superSampleOffsets.LoadAndSwizzle( Vector( csshift, csshift, 0 ), Vector( csshift, csshift + cscale, 0), - Vector( csshift + cscale, csshift, 0 ), Vector( csshift + cscale, csshift + cscale, 0 ) ); - superSampleLightCoord.DuplicateVector( sampleLightOrigin ); - superSampleLightCoord += superSampleOffsets; - - LuxelSpaceToWorld( &l, superSampleLightCoord[0], superSampleLightCoord[1], superSamplePosition ); - - int invalidBits = 0; - if ( sample.w && !PointsInWinding( superSamplePosition, sample.w, invalidBits ) ) - return 0; - - ComputeIlluminationPointAndNormalsSSE( l, superSamplePosition, superSampleNormal, &info, 4 ); - - LightingValue_t result[4][NUM_BUMP_VECTS+1]; - ResampleLightAt4Points( info, lightStyleIndex, AMBIENT_ONLY, result ); - - // Got more subsamples - for ( int i = 0; i < 4; i++ ) - { - if ( !( ( invalidBits >> i ) & 0x1 ) ) - { - for ( int n = 0; n < info.m_NormalCount; ++n ) - { - pLight[n].AddLight( result[i][n] ); - } - ++subsampleCount; - } - } - } - - return subsampleCount; -} - - -//----------------------------------------------------------------------------- -// Compute gradients of a lightmap -//----------------------------------------------------------------------------- -static void ComputeLightmapGradients( SSE_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 ; inumsamples ; 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( SSE_SampleInfo_t& info, int sampleIdx, - LightingValue_t **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].Intensity(); - - // 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( SSE_SampleInfo_t& info, LightingValue_t **ppLightSamples, float* pSampleIntensity ) -{ - for (int i=0; inumsamples; i++) - { - ComputeLuxelIntensity( info, i, ppLightSamples, pSampleIntensity ); - } -} - -//----------------------------------------------------------------------------- -// Perform supersampling on a particular lightstyle -//----------------------------------------------------------------------------- -static void BuildSupersampleFaceLights( lightinfo_t& l, SSE_SampleInfo_t& info, int lightstyleIndex ) -{ - LightingValue_t pAmbientLight[NUM_BUMP_VECTS+1]; - LightingValue_t 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 - LightingValue_t **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 ; inumsamples; ++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; - } - - // Supersample the ambient light for each bump direction vector - int ambientSupersampleCount = SupersampleLightAtPoint( l, info, i, lightstyleIndex, pAmbientLight, AMBIENT_ONLY ); - - // Supersample the non-ambient light for each bump direction vector - int directSupersampleCount = SupersampleLightAtPoint( l, info, i, lightstyleIndex, pDirectLight, NON_AMBIENT_ONLY ); - - // Because of sampling problems, small area triangles may have no samples. - // In this case, just use what we already have - if ( ambientSupersampleCount > 0 && directSupersampleCount > 0 ) - { - // Add the ambient + directional terms together, stick it back into the lightmap - for (int n = 0; n < info.m_NormalCount; ++n) - { - ppLightSamples[n][i].Zero(); - ppLightSamples[n][i].AddWeighted( pDirectLight[n],1.0f / directSupersampleCount ); - ppLightSamples[n][i].AddWeighted( pAmbientLight[n], 1.0f / ambientSupersampleCount ); - } - - // 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 ; inumsamples ; ++i) - { - for (int j = 0; j 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 ; jnumedges ; 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, SSE_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; - - info.m_NumSamples = info.m_pFaceLight->numsamples; - info.m_NumSampleGroups = ( info.m_NumSamples & 0x3) ? ( info.m_NumSamples / 4 ) + 1 : ( info.m_NumSamples / 4 ); - - // initialize normals if the surface is flat - if (l.isflat) - { - info.m_PointNormals[0].DuplicateVector( l.facenormal ); - - // use facenormal along with the smooth normal to build the three bump map vectors - if( info.m_NormalCount > 1 ) - { - Vector bumpVects[NUM_BUMP_VECTS]; - GetBumpNormals( info.m_pTexInfo->textureVecsTexelsPerWorldUnits[0], - info.m_pTexInfo->textureVecsTexelsPerWorldUnits[1], l.facenormal, - l.facenormal, bumpVects );//&info.m_PointNormal[1] ); - - for ( int b = 0; b < NUM_BUMP_VECTS; ++b ) - { - info.m_PointNormals[b + 1].DuplicateVector( bumpVects[b] ); - } - } - } -} - -void BuildFacelights (int iThread, int facenum) -{ - int i, j; - - lightinfo_t l; - dface_t *f; - facelight_t *fl; - SSE_SampleInfo_t sampleInfo; - directlight_t *dl; - Vector spot; - Vector v[4], n[4]; - - 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 ; jstyles[j] = 255; - - // Trivial-reject the whole face? - if( !( g_FacesVisibleToLights[facenum>>3] & (1 << (facenum & 7)) ) ) - return; - - if ( texinfo[f->texinfo].flags & TEX_SPECIAL) - return; // non-lit texture - - // check for patches for this face. If none it must be degenerate. Ignore. - if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) - return; - - fl = &facelight[facenum]; - - InitLightinfo( &l, facenum ); - CalcPoints( &l, fl, facenum ); - InitSampleInfo( l, iThread, sampleInfo ); - - // Allocate sample positions/normals to SSE - int numGroups = ( fl->numsamples & 0x3) ? ( fl->numsamples / 4 ) + 1 : ( fl->numsamples / 4 ); - - // always allocate style 0 lightmap - f->styles[0] = 0; - AllocateLightstyleSamples( fl, 0, sampleInfo.m_NormalCount ); - - // sample the lights at each sample location - for ( int grp = 0; grp < numGroups; ++grp ) - { - int nSample = 4 * grp; - - sample_t *sample = sampleInfo.m_pFaceLight->sample + nSample; - int numSamples = min ( 4, sampleInfo.m_pFaceLight->numsamples - nSample ); - - FourVectors positions; - FourVectors normals; - - for ( int i = 0; i < 4; i++ ) - { - v[i] = ( i < numSamples ) ? sample[i].pos : sample[numSamples - 1].pos; - n[i] = ( i < numSamples ) ? sample[i].normal : sample[numSamples - 1].normal; - } - positions.LoadAndSwizzle( v[0], v[1], v[2], v[3] ); - normals.LoadAndSwizzle( n[0], n[1], n[2], n[3] ); - - ComputeIlluminationPointAndNormalsSSE( l, positions, normals, &sampleInfo, numSamples ); - - // Fixup sample normals in case of smooth faces - if ( !l.isflat ) - { - for ( int i = 0; i < numSamples; i++ ) - sample[i].normal = sampleInfo.m_PointNormals[0].Vec( i ); - } - - // Iterate over all the lights and add their contribution to this group of spots - GatherSampleLightAt4Points( sampleInfo, nSample, numSamples ); - } - - // 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 ); - } - } - - if (!g_bUseMPI) - { - // - // This is done on the master node when MPI is used - // - BuildPatchLights( facenum ); - } - - if( g_bDumpPatches ) - { - DumpSamples( facenum, fl ); - } - else - { - FreeSampleWindings( fl ); - } - -} - -void BuildPatchLights( int facenum ) -{ - int i, k; - - CPatch *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( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) - return; - - // push up sampled light to parents (children always exist first in the list) - CPatch *pNextPatch; - for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) - { - // next patch - pNextPatch = NULL; - if( patch->ndxNext != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_Patches.Element( patch->ndxNext ); - } - - // skip patches without parents - if( patch->parent == g_Patches.InvalidIndex() ) -// if (patch->parent == -1) - continue; - - CPatch *parent = &g_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 = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) - { - // next patch - pNextPatch = NULL; - if( patch->ndxNext != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_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 = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) - { - // next patch - pNextPatch = NULL; - if( patch->ndxNext != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_Patches.Element( patch->ndxNext ); - } - - if ( patch->child1 != g_Patches.InvalidIndex() ) - { - float s1, s2; - CPatch *child1; - CPatch *child2; - - child1 = &g_Patches.Element( patch->child1 ); - child2 = &g_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++) - { - fl->light[j][0][i].m_vecLighting += ambient; - if( needsBumpmap ) - { - fl->light[j][1][i].m_vecLighting += ambient; - fl->light[j][2][i].m_vecLighting += ambient; - fl->light[j][3][i].m_vecLighting += ambient; - } - } - break; - } - } - } - - // light from dlight_threshold and above is sent out, but the - // texture itself should still be full bright - -#if 0 - // if( VectorAvg( g_FacePatches[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 ; inumsamples ; i++) - { - // garymctchange - VectorAdd( fl->light[j][0][i], g_FacePatches[facenum]->baselight, fl->light[j][0][i] ); - if( needsBumpmap ) - { - for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) - { - VectorAdd( fl->light[j][bumpSample][i], g_FacePatches[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 ); -} - -// 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; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vrad.h" +#include "lightmap.h" +#include "radial.h" +#include "mathlib/bumpvects.h" +#include "tier1/utlvector.h" +#include "vmpi.h" +#include "mathlib/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 "mathlib/quantize.h" +#include "bitmap/imageformat.h" +#include "coordsize.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 m_Normals; + + +private: + + // This represents a grid from (-1,-1,-1) to (1,1,1). + enum {NUM_SUBDIVS = 8}; + CUtlVector 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 *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; inumedges ; 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 particular vertex + for (i=0, f = g_pFaces ; inumedges ; 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; iplanenum].normal, fn->facenormal ); + + // set displacement surface flag + fn->bHasDisp = false; + if( ValidDispFace( f ) ) + { + fn->bHasDisp = true; + } + } + + // find neighbors + for (i=0, f = g_pFaces ; inormal = ( Vector* )calloc( f->numedges, sizeof( fn->normal[0] ) ); + + // look up all faces sharing vertices and add them to the list + for (j=0 ; jnumedges ; 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 ;inumedges; 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", s, 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 ); + if ( fabs( det ) < 1.0e-20 ) + { + Warning(" warning - face vectors parallel to face normal. bad lighting will be produced\n" ); + l->luxelOrigin = vec3_origin; + } + else + { + // 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 ; inumpoints ; 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! + CUtlVector sampleData; + sampleData.SetCount( SINGLE_BRUSH_MAP * 2 ); + sample_t *samples = sampleData.Base(); + 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 (g_bDumpPatches || (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( g_bDumpPatches ) + { + 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 ; iindex = 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 ); + + if (argCnt==8) // 2 4-tuples + { + if (g_bHDR) + { + r=r_hdr; + g=g_hdr; + b=b_hdr; + scaler=scaler_hdr; + } + argCnt=4; + } + + // make sure light is legal + if( r < 0.0f || g < 0.0f || b < 0.0f || scaler < 0.0f ) + { + intensity.Init( 0.0f, 0.0f, 0.0f ); + return false; + } + + 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 && LightForKey( e, "_lightHDR", 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 ); + } + if ( g_bHDR ) + VectorScale( dl->light.intensity, + FloatForKeyWithDefault( e, "_lightscaleHDR", 1.0 ), + dl->light.intensity ); +} + +static void SetLightFalloffParams( entity_t * e, directlight_t * dl ) +{ + float d50=FloatForKey( e, "_fifty_percent_distance" ); + dl->m_flStartFadeDistance = 0; + dl->m_flEndFadeDistance = - 1; + dl->m_flCapDist = 1.0e22; + if ( d50 ) + { + float d0 = FloatForKey( e, "_zero_percent_distance" ); + if ( d0 < d50 ) + { + Warning( "light has _fifty_percent_distance of %f but _zero_percent_distance of %f\n", d50, d0); + 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; + + + + if ( IntForKey(e, "_hardfalloff" ) ) + { + dl->m_flEndFadeDistance = d0; + dl->m_flStartFadeDistance = 0.75 * d0 + 0.25 * d50; // start fading 3/4 way between 50 and 0. could allow adjust + } + else + { + // now, we will find the point at which the 1/x term reaches its maximum value, and + // prevent the light from going past there. If a user specifes an extreme falloff, the + // quadratic will start making the light brighter at some distance. We handle this by + // fading it from the minimum brightess point down to zero at 10x the minimum distance + if ( fabs( a ) > 0. ) + { + float flMax = b / ( - 2.0 * a ); // where f' = 0 + if ( flMax > 0.0 ) + { + dl->m_flCapDist = flMax; + dl->m_flStartFadeDistance = flMax; + dl->m_flEndFadeDistance = 10.0 * flMax; + } + } + } + } + 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; + } + + FourVectors center4, delta; + fltx4 fractionVisible; + for ( int j = 0; j < NUMVERTEXNORMALS; j+=4 ) + { + // search back to see if we can hit a sky brush + delta.LoadAndSwizzle( g_anorms[j], g_anorms[min( j+1, NUMVERTEXNORMALS-1 )], + g_anorms[min( j+2, NUMVERTEXNORMALS-1 )], g_anorms[min( j+3, NUMVERTEXNORMALS-1 )] ); + delta *= -MAX_TRACE_LENGTH; + delta += center4; + + // return true if any hits sky + TestLine_DoesHitSky ( center4, delta, &fractionVisible ); + if ( TestSignSIMD ( CmpGtSIMD ( fractionVisible, Four_Zeros ) ) ) + return true; + } + + return false; +} + +void BuildVisForLightEnvironment( void ) +{ + // Create the vis. + for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) + { + dleafs[iLeaf].flags &= ~( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ); + 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 ) + { + if ( tex.flags & SURF_SKY2D ) + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY2D; + } + else + { + 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) ); + unsigned char *pLeaf2DBits = (unsigned char *)stackalloc( nLeafBytes * sizeof(unsigned char) ); + memset( pLeafBits, 0, nLeafBytes ); + memset( pLeaf2DBits, 0, nLeafBytes ); + + for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) + { + // If this leaf has light (3d skybox) 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 + int nByte = iLeaf >> 3; + int nBit = 1 << ( iLeaf & 0x7 ); + for ( int iLeaf2 = 0; iLeaf2 < numleafs; ++iLeaf2 ) + { + if ( iLeaf2 == iLeaf ) + continue; + + if ( !(dleafs[iLeaf2].flags & ( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ) ) ) + continue; + + // Can this leaf see into the leaf with the sky in it? + if ( !PVSCheck( pvs, dleafs[iLeaf2].cluster ) ) + continue; + + if ( dleafs[iLeaf2].flags & LEAF_FLAGS_SKY2D ) + { + pLeaf2DBits[ nByte ] |= nBit; + } + if ( dleafs[iLeaf2].flags & LEAF_FLAGS_SKY ) + { + pLeafBits[ nByte ] |= nBit; + + // As soon as we know this leaf needs to draw the 3d skybox, we're done + 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 this leaf has light (3d skybox) 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; + + // Check to see if this is a 2D skybox leaf + if ( pLeaf2DBits[ iLeaf >> 3 ] & (1 << ( iLeaf & 0x7 )) ) + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY2D; + } + + // If this is a 3D skybox leaf, then we don't care if it was previously a 2D skybox leaf + if ( pLeafBits[ iLeaf >> 3 ] & (1 << ( iLeaf & 0x7 )) ) + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; + dleafs[iLeaf].flags &= ~LEAF_FLAGS_SKY2D; + } + 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) ) + { + // FIXME: Should make a version that checks if we hit 2D skyboxes.. oh well. + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; + } + } + } + } +} + +static char *ValueForKeyWithDefault (entity_t *ent, char *key, char *default_value = NULL) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + return ep->value; + return default_value; +} + +static void ParseLightEnvironment( entity_t* e, directlight_t* dl ) +{ + Vector dest; + GetVectorForKey (e, "origin", dest ); + dl = AllocDLight( dest, false ); + + ParseLightGeneric( e, dl ); + + char *angle_str=ValueForKeyWithDefault( e, "SunSpreadAngle" ); + if (angle_str) + { + g_SunAngularExtent=atof(angle_str); + g_SunAngularExtent=sin((M_PI/180.0)*g_SunAngularExtent); + printf("sun extent from map=%f\n",g_SunAngularExtent); + } + 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 ) ) + { + // 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 ); + } + if ( g_bHDR ) + { + VectorScale( gAmbient->light.intensity, + FloatForKeyWithDefault( e, "_AmbientScaleHDR", 1.0 ), + 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; + CPatch *p = NULL; + directlight_t *dl = NULL; + entity_t *e = NULL; + char *name; + Vector dest; + + numdlights = 0; + + FreeDLights(); + + // + // surfaces + // + unsigned int uiPatchCount = g_Patches.Count(); + for (i=0; i< uiPatchCount; i++) + { + p = &g_Patches.Element( i ); + + // skip parent patches + if (p->child1 != g_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 CONSTANT_DOT (.7/2) + +#define NSAMPLES_SUN_AREA_LIGHT 30 // number of samples to take for an + // non-point sun light + +// Helper function - gathers light from sun (emit_skylight) +void GatherSampleSkyLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, int static_prop_index_to_ignore, + float flEpsilon ) +{ + bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; + bool force_fast = ( nLFlags & GATHERLFLAGS_FORCE_FAST ) != 0; + + fltx4 dot; + + if ( bIgnoreNormals ) + dot = ReplicateX4( CONSTANT_DOT ); + else + dot = NegSIMD( pNormals[0] * dl->light.normal ); + + dot = MaxSIMD( dot, Four_Zeros ); + int zeroMask = TestSignSIMD ( CmpEqSIMD( dot, Four_Zeros ) ); + if (zeroMask == 0xF) + return; + + int nsamples = 1; + if ( g_SunAngularExtent > 0.0f ) + { + nsamples = NSAMPLES_SUN_AREA_LIGHT; + if ( do_fast || force_fast ) + nsamples /= 4; + } + + fltx4 totalFractionVisible = Four_Zeros; + fltx4 fractionVisible = Four_Zeros; + + DirectionalSampler_t sampler; + + for ( int d = 0; d < nsamples; d++ ) + { + // determine visibility of skylight + // serach back to see if we can hit a sky brush + Vector delta; + VectorScale( dl->light.normal, -MAX_TRACE_LENGTH, delta ); + if ( d ) + { + // jitter light source location + Vector ofs = sampler.NextValue(); + ofs *= MAX_TRACE_LENGTH * g_SunAngularExtent; + delta += ofs; + } + FourVectors delta4; + delta4.DuplicateVector ( delta ); + delta4 += pos; + + TestLine_DoesHitSky ( pos, delta4, &fractionVisible, true, static_prop_index_to_ignore ); + + totalFractionVisible = AddSIMD ( totalFractionVisible, fractionVisible ); + } + + fltx4 seeAmount = MulSIMD ( totalFractionVisible, ReplicateX4 ( 1.0f / nsamples ) ); + out.m_flDot[0] = MulSIMD ( dot, seeAmount ); + out.m_flFalloff = Four_Ones; + out.m_flSunAmount = MulSIMD ( seeAmount, ReplicateX4( 10000.0f ) ); + for ( int i = 1; i < normalCount; i++ ) + { + if ( bIgnoreNormals ) + out.m_flDot[i] = ReplicateX4 ( CONSTANT_DOT ); + else + { + out.m_flDot[i] = NegSIMD( pNormals[i] * dl->light.normal ); + out.m_flDot[i] = MulSIMD( out.m_flDot[i], seeAmount ); + } + } +} + +// Helper function - gathers light from ambient sky light +void GatherSampleAmbientSkySSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, int static_prop_index_to_ignore, + float flEpsilon ) +{ + + bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; + bool force_fast = ( nLFlags & GATHERLFLAGS_FORCE_FAST ) != 0; + + fltx4 sumdot = Four_Zeros; + fltx4 ambient_intensity[NUM_BUMP_VECTS+1]; + fltx4 possibleHitCount[NUM_BUMP_VECTS+1]; + fltx4 dots[NUM_BUMP_VECTS+1]; + + for ( int i = 0; i < normalCount; i++ ) + { + ambient_intensity[i] = Four_Zeros; + possibleHitCount[i] = Four_Zeros; + } + + DirectionalSampler_t sampler; + int nsky_samples = NUMVERTEXNORMALS; + if (do_fast || force_fast ) + nsky_samples /= 4; + else + nsky_samples *= g_flSkySampleScale; + + for (int j = 0; j < nsky_samples; j++) + { + FourVectors anorm; + anorm.DuplicateVector( sampler.NextValue() ); + + if ( bIgnoreNormals ) + dots[0] = ReplicateX4( CONSTANT_DOT ); + else + dots[0] = NegSIMD( pNormals[0] * anorm ); + + fltx4 validity = CmpGtSIMD( dots[0], ReplicateX4( EQUAL_EPSILON ) ); + + // No possibility of anybody getting lit + if ( !TestSignSIMD( validity ) ) + continue; + + dots[0] = AndSIMD( validity, dots[0] ); + sumdot = AddSIMD( dots[0], sumdot ); + possibleHitCount[0] = AddSIMD( AndSIMD( validity, Four_Ones ), possibleHitCount[0] ); + + for ( int i = 1; i < normalCount; i++ ) + { + if ( bIgnoreNormals ) + dots[i] = ReplicateX4( CONSTANT_DOT ); + else + dots[i] = NegSIMD( pNormals[i] * anorm ); + fltx4 validity2 = CmpGtSIMD( dots[i], ReplicateX4 ( EQUAL_EPSILON ) ); + dots[i] = AndSIMD( validity2, dots[i] ); + possibleHitCount[i] = AddSIMD( AndSIMD( AndSIMD( validity, validity2 ), Four_Ones ), possibleHitCount[i] ); + } + + // search back to see if we can hit a sky brush + FourVectors delta = anorm; + delta *= -MAX_TRACE_LENGTH; + delta += pos; + FourVectors surfacePos = pos; + FourVectors offset = anorm; + offset *= -flEpsilon; + surfacePos -= offset; + + fltx4 fractionVisible = Four_Ones; + TestLine_DoesHitSky( surfacePos, delta, &fractionVisible, true, static_prop_index_to_ignore ); + for ( int i = 0; i < normalCount; i++ ) + { + fltx4 addedAmount = MulSIMD( fractionVisible, dots[i] ); + ambient_intensity[i] = AddSIMD( ambient_intensity[i], addedAmount ); + } + + } + + out.m_flFalloff = Four_Ones; + for ( int i = 0; i < normalCount; i++ ) + { + // now scale out the missing parts of the hemisphere of this bump basis vector + fltx4 factor = ReciprocalSIMD( possibleHitCount[0] ); + factor = MulSIMD( factor, possibleHitCount[i] ); + out.m_flDot[i] = MulSIMD( factor, sumdot ); + out.m_flDot[i] = ReciprocalSIMD( out.m_flDot[i] ); + out.m_flDot[i] = MulSIMD( ambient_intensity[i], out.m_flDot[i] ); + } + +} + +// Helper function - gathers light from area lights, spot lights, and point lights +void GatherSampleStandardLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, int static_prop_index_to_ignore, + float flEpsilon ) +{ + bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; + + FourVectors src; + src.DuplicateVector( vec3_origin ); + + if (dl->facenum == -1) + { + src.DuplicateVector( dl->light.origin ); + } + + // Find light vector + FourVectors delta; + delta = src; + delta -= pos; + fltx4 dist2 = delta.length2(); + fltx4 rpcDist = ReciprocalSqrtSIMD( dist2 ); + delta *= rpcDist; + fltx4 dist = SqrtEstSIMD( dist2 );//delta.VectorNormalize(); + + // Compute dot + fltx4 dot = ReplicateX4( (float) CONSTANT_DOT ); + if ( !bIgnoreNormals ) + dot = delta * pNormals[0]; + dot = MaxSIMD( Four_Zeros, dot ); + + // Affix dot to zero if past fade distz + bool bHasHardFalloff = ( dl->m_flEndFadeDistance > dl->m_flStartFadeDistance ); + if ( bHasHardFalloff ) + { + fltx4 notPastFadeDist = CmpLeSIMD ( dist, ReplicateX4 ( dl->m_flEndFadeDistance ) ); + dot = AndSIMD( dot, notPastFadeDist ); // dot = 0 if past fade distance + if ( !TestSignSIMD ( notPastFadeDist ) ) + return; + } + + dist = MaxSIMD( dist, Four_Ones ); + fltx4 falloffEvalDist = MinSIMD( dist, ReplicateX4( dl->m_flCapDist ) ); + + fltx4 constant, linear, quadratic; + fltx4 dot2, inCone, inFringe, mult; + FourVectors offset; + + switch (dl->light.type) + { + case emit_point: + constant = ReplicateX4( dl->light.constant_attn ); + linear = ReplicateX4( dl->light.linear_attn ); + quadratic = ReplicateX4( dl->light.quadratic_attn ); + + out.m_flFalloff = MulSIMD( falloffEvalDist, falloffEvalDist ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, quadratic ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, MulSIMD( linear, falloffEvalDist ) ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, constant ); + out.m_flFalloff = ReciprocalSIMD( out.m_flFalloff ); + break; + + case emit_surface: + dot2 = delta * dl->light.normal; + dot2 = NegSIMD( dot2 ); + + // Light behind surface yields zero dot + dot2 = MaxSIMD( Four_Zeros, dot2 ); + if ( TestSignSIMD( CmpEqSIMD( Four_Zeros, dot ) ) == 0xF ) + return; + + out.m_flFalloff = ReciprocalSIMD ( dist2 ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, dot2 ); + + // move the endpoint away from the surface by epsilon to prevent hitting the surface with the trace + offset.DuplicateVector ( dl->light.normal ); + offset *= DIST_EPSILON; + src += offset; + break; + + case emit_spotlight: + dot2 = delta * dl->light.normal; + dot2 = NegSIMD( dot2 ); + + // Affix dot2 to zero if outside light cone + inCone = CmpGtSIMD( dot2, ReplicateX4( dl->light.stopdot2 ) ); + if ( !TestSignSIMD ( inCone ) ) + return; + dot = AndSIMD( inCone, dot ); + + constant = ReplicateX4( dl->light.constant_attn ); + linear = ReplicateX4( dl->light.linear_attn ); + quadratic = ReplicateX4( dl->light.quadratic_attn ); + + out.m_flFalloff = MulSIMD( falloffEvalDist, falloffEvalDist ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, quadratic ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, MulSIMD( linear, falloffEvalDist ) ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, constant ); + out.m_flFalloff = ReciprocalSIMD( out.m_flFalloff ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, dot2 ); + + // outside the inner cone + inFringe = CmpLeSIMD( dot2, ReplicateX4( dl->light.stopdot ) ); + mult = ReplicateX4( dl->light.stopdot - dl->light.stopdot2 ); + mult = ReciprocalSIMD( mult ); + mult = MulSIMD( mult, SubSIMD( dot2, ReplicateX4( dl->light.stopdot2 ) ) ); + mult = MinSIMD( mult, Four_Ones ); + mult = MaxSIMD( mult, Four_Zeros ); + + // pow is fixed point, so this isn't the most accurate, but it doesn't need to be + if ( (dl->light.exponent != 0.0f ) && ( dl->light.exponent != 1.0f ) ) + mult = PowSIMD( mult, dl->light.exponent ); + + // if not in between inner and outer cones, mult by 1 + mult = AndSIMD( inFringe, mult ); + mult = AddSIMD( mult, AndNotSIMD( inFringe, Four_Ones ) ); + out.m_flFalloff = MulSIMD( mult, out.m_flFalloff ); + break; + + } + + // we may be in the fade region - modulate lighting by the fade curve + //float t = ( dist - dl->m_flStartFadeDistance ) / + // ( dl->m_flEndFadeDistance - dl->m_flStartFadeDistance ); + if ( bHasHardFalloff ) + { + fltx4 t = ReplicateX4( dl->m_flEndFadeDistance - dl->m_flStartFadeDistance ); + t = ReciprocalSIMD( t ); + t = MulSIMD( t, SubSIMD( dist, ReplicateX4( dl->m_flStartFadeDistance ) ) ); + + // clamp t to [0...1] + t = MinSIMD( t, Four_Ones ); + t = MaxSIMD( t, Four_Zeros ); + t = SubSIMD( Four_Ones, t ); + + // Using QuinticInterpolatingPolynomial, SSE-ified + // t * t * t *( t * ( t* 6.0 - 15.0 ) + 10.0 ) + mult = SubSIMD( MulSIMD( ReplicateX4( 6.0f ), t ), ReplicateX4( 15.0f ) ); + mult = AddSIMD( MulSIMD( mult, t ), ReplicateX4( 10.0f ) ); + mult = MulSIMD( MulSIMD( t, t), mult ); + mult = MulSIMD( t, mult ); + out.m_flFalloff = MulSIMD( mult, out.m_flFalloff ); + } + + // Raytrace for visibility function + fltx4 fractionVisible = Four_Ones; + TestLine( pos, src, &fractionVisible, static_prop_index_to_ignore); + dot = MulSIMD( fractionVisible, dot ); + out.m_flDot[0] = dot; + + for ( int i = 1; i < normalCount; i++ ) + { + if ( bIgnoreNormals ) + out.m_flDot[i] = ReplicateX4( (float) CONSTANT_DOT ); + else + { + out.m_flDot[i] = pNormals[i] * delta; + out.m_flDot[i] = MaxSIMD( Four_Zeros, out.m_flDot[i] ); + } + } +} + +// returns dot product with normal and delta +// dl - light +// pos - position of sample +// normal - surface normal of sample +// out.m_flDot[] - returned dot products with light vector and each normal +// out.m_flFalloff - amount of light falloff +void GatherSampleLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, + int static_prop_index_to_ignore, + float flEpsilon ) +{ + for ( int b = 0; b < normalCount; b++ ) + out.m_flDot[b] = Four_Zeros; + out.m_flFalloff = Four_Zeros; + out.m_flSunAmount = Four_Zeros; + Assert( normalCount <= (NUM_BUMP_VECTS+1) ); + + // skylights work fundamentally differently than normal lights + switch( dl->light.type ) + { + case emit_skylight: + GatherSampleSkyLightSSE( out, dl, facenum, pos, pNormals, normalCount, + iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); + break; + case emit_skyambient: + GatherSampleAmbientSkySSE( out, dl, facenum, pos, pNormals, normalCount, + iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); + break; + case emit_point: + case emit_surface: + case emit_spotlight: + GatherSampleStandardLightSSE( out, dl, facenum, pos, pNormals, normalCount, + iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); + break; + default: + Error ("Bad dl->light.type"); + return; + } + + // 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. + out.m_flDot[0] = MaxSIMD ( out.m_flDot[0], Four_Zeros ); + fltx4 notZero = CmpGtSIMD( out.m_flDot[0], Four_Zeros ); + for ( int n = 1; n < normalCount; n++ ) + { + out.m_flDot[n] = MaxSIMD( out.m_flDot[n], Four_Zeros ); + out.m_flDot[n] = AndSIMD( out.m_flDot[n], notZero ); + } + +} + +/* + ============= + AddSampleToPatch + + Take the sample's collected light and + add it back into the apropriate patch + for the radiosity pass. + ============= +*/ +void AddSampleToPatch (sample_t *s, LightingValue_t& light, int facenum) +{ + CPatch *patch; + Vector mins, maxs; + int i; + + if (numbounce == 0) + return; + if( VectorAvg( light.m_vecLighting ) < 1) + return; + + // + // fixed the sample position and normal -- need to find the equiv pos, etc to set up + // patches + // + if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) + return; + + float radius = sqrt( s->area ) / 2.0; + + CPatch *pNextPatch = NULL; + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + if (patch->sky) + continue; + + // skip patches with children + if ( patch->child1 != g_Patches.InvalidIndex() ) + continue; + + // see if the point is in this patch (roughly) + WindingBounds (patch->winding, mins, 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.m_vecLighting, 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 ; jnumedges ; 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; + } + } + } +} + +void GetPhongNormal( int facenum, FourVectors const& spot, FourVectors& phongnormal ) +{ + int j; + dface_t *f = &g_pFaces[facenum]; + // dplane_t *p = &dplanes[f->planenum]; + Vector facenormal; + FourVectors vspot; + + VectorCopy( dplanes[f->planenum].normal, facenormal ); + phongnormal.DuplicateVector( facenormal ); + + FourVectors faceCentroid; + faceCentroid.DuplicateVector( face_centroids[facenum] ); + + 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; + fltx4 a1, a2; + float aa, bb, ab; + int vert1, vert2; + + Vector& n1 = fn->normal[j]; + Vector& n2 = fn->normal[(j+1)%f->numedges]; + + 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 ); + vspot = spot; + vspot -= faceCentroid; + 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); + a1 = ReciprocalSIMD( ReplicateX4( aa * bb - ab * ab ) ); + a1 = MulSIMD( a1, SubSIMD( MulSIMD( ReplicateX4( bb ), vspot * v1 ), MulSIMD( ReplicateX4( ab ), vspot * v2 ) ) ); + //a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb; + a2 = ReciprocalSIMD( ReplicateX4( bb ) ); + a2 = MulSIMD( a2, SubSIMD( vspot * v2, MulSIMD( a1, ReplicateX4( ab ) ) ) ); + + fltx4 resultMask = AndSIMD( CmpGeSIMD( a1, Four_Zeros ), CmpGeSIMD( a2, Four_Zeros ) ); + + if ( !TestSignSIMD( resultMask ) ) + continue; + + // Store the old phong normal to avoid overwriting already computed phong normals + FourVectors oldPhongNormal = phongnormal; + + // calculate distance from edge to pos + FourVectors temp; + fltx4 scale; + + // Interpolate between the center and edge normals based on sample position + scale = SubSIMD( SubSIMD( Four_Ones, a1 ), a2 ); + phongnormal.DuplicateVector( fn->facenormal ); + phongnormal *= scale; + temp.DuplicateVector( n1 ); + temp *= a1; + phongnormal += temp; + temp.DuplicateVector( n2 ); + temp *= a2; + phongnormal += temp; + + // restore the old phong normals + phongnormal.x = AddSIMD( AndSIMD( resultMask, phongnormal.x ), AndNotSIMD( resultMask, oldPhongNormal.x ) ); + phongnormal.y = AddSIMD( AndSIMD( resultMask, phongnormal.y ), AndNotSIMD( resultMask, oldPhongNormal.y ) ); + phongnormal.z = AddSIMD( AndSIMD( resultMask, phongnormal.z ), AndNotSIMD( resultMask, oldPhongNormal.z ) ); + } + + phongnormal.VectorNormalize(); + } +} + + + +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 ) + { + bool bBumpped = ( ( texinfo[pFace->texinfo].flags & SURF_BUMPLIGHT ) != 0 ); + + for( int iStyle = 0; iStyle < 4; ++iStyle ) + { + if( pFace->styles[iStyle] != 255 ) + { + for ( int iBump = 0; iBump < 4; ++iBump ) + { + if ( iBump == 0 || ( iBump > 0 && bBumpped ) ) + { + for( int iSample = 0; iSample < pFaceLight->numsamples; ++iSample ) + { + sample_t *pSample = &pFaceLight->sample[iSample]; + WriteWinding( pFileSamples[iStyle][iBump], pSample->w, pFaceLight->light[iStyle][iBump][iSample].m_vecLighting ); + if( bDumpNormals ) + { + WriteNormal( pFileSamples[iStyle][iBump], 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] = ( LightingValue_t* )calloc( fl->numsamples, sizeof(LightingValue_t ) ); + } +} + + +//----------------------------------------------------------------------------- +// 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 ComputeIlluminationPointAndNormalsSSE( lightinfo_t const& l, FourVectors const &pos, FourVectors const &norm, SSE_SampleInfo_t* pInfo, int numSamples ) +{ + + Vector v[4]; + + pInfo->m_Points = pos; + bool computeNormals = ( pInfo->m_NormalCount > 1 && ( pInfo->m_IsDispFace || !l.isflat ) ); + + // 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 + FourVectors faceNormal; + faceNormal.DuplicateVector( l.facenormal ); + pInfo->m_Points += faceNormal; + + if ( pInfo->m_IsDispFace ) + { + pInfo->m_PointNormals[0] = norm; + } + else if ( !l.isflat ) + { + // If the face isn't flat, use a phong-based normal instead + FourVectors modelorg; + modelorg.DuplicateVector( l.modelorg ); + FourVectors vecSample = pos; + vecSample -= modelorg; + GetPhongNormal( pInfo->m_FaceNum, vecSample, pInfo->m_PointNormals[0] ); + } + + if ( computeNormals ) + { + Vector bv[4][NUM_BUMP_VECTS]; + for ( int i = 0; i < 4; ++i ) + { + // TODO: using Vec may slow things down a bit + GetBumpNormals( pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[0], + pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[1], + l.facenormal, pInfo->m_PointNormals[0].Vec( i ), bv[i] ); + } + for ( int b = 0; b < NUM_BUMP_VECTS; ++b ) + { + pInfo->m_PointNormals[b+1].LoadAndSwizzle ( bv[0][b], bv[1][b], bv[2][b], bv[3][b] ); + } + } + + // TODO: this may slow things down a bit ( using Vec ) + for ( int i = 0; i < 4; ++i ) + pInfo->m_Clusters[i] = ClusterFromPoint( pos.Vec( i ) ); +} + +//----------------------------------------------------------------------------- +// Iterates over all lights and computes lighting at up to 4 sample points +//----------------------------------------------------------------------------- +static void GatherSampleLightAt4Points( SSE_SampleInfo_t& info, int sampleIdx, int numSamples ) +{ + SSE_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? + fltx4 dotMask = Four_Zeros; + bool skipLight = true; + for( int s = 0; s < numSamples; s++ ) + { + if( PVSCheck( dl->pvs, info.m_Clusters[s] ) ) + { + dotMask = SetComponentSIMD( dotMask, s, 1.0f ); + skipLight = false; + } + } + if ( skipLight ) + continue; + + GatherSampleLightSSE( out, dl, info.m_FaceNum, info.m_Points, info.m_PointNormals, info.m_NormalCount, info.m_iThread ); + + // Apply the PVS check filter and compute falloff x dot + fltx4 fxdot[NUM_BUMP_VECTS + 1]; + skipLight = true; + for ( int b = 0; b < info.m_NormalCount; b++ ) + { + fxdot[b] = MulSIMD( out.m_flDot[b], dotMask ); + fxdot[b] = MulSIMD( fxdot[b], out.m_flFalloff ); + if ( !IsAllZeros( fxdot[b] ) ) + { + skipLight = false; + } + } + if ( skipLight ) + 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 at (%f, %f, %f)\n", + info.m_Points.x.m128_f32[0], info.m_Points.y.m128_f32[0], info.m_Points.z.m128_f32[0] ); + 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 + LightingValue_t** pLightmaps = info.m_pFaceLight->light[lightStyleIndex]; + + // Incremental lighting only cares about lightstyle zero + if( g_pIncremental && (dl->light.style == 0) ) + { + for ( int i = 0; i < numSamples; i++ ) + { + g_pIncremental->AddLightToFace( dl->m_IncrementalID, info.m_FaceNum, sampleIdx + i, + info.m_LightmapSize, SubFloat( fxdot[0], i ), info.m_iThread ); + } + } + + for( int n = 0; n < info.m_NormalCount; ++n ) + { + for ( int i = 0; i < numSamples; i++ ) + { + pLightmaps[n][sampleIdx + i].AddLight( SubFloat( fxdot[n], i ), dl->light.intensity, SubFloat( out.m_flSunAmount, i ) ); + } + } + } +} + + + +//----------------------------------------------------------------------------- +// Iterates over all lights and computes lighting at a sample point +//----------------------------------------------------------------------------- +static void ResampleLightAt4Points( SSE_SampleInfo_t& info, int lightStyleIndex, int flags, LightingValue_t pLightmap[4][NUM_BUMP_VECTS+1] ) +{ + SSE_sampleLightOutput_t out; + + // Clear result + for ( int i = 0; i < 4; ++i ) + { + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + pLightmap[i][n].Zero(); + } + } + + // 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? + fltx4 dotMask = Four_Zeros; + bool skipLight = true; + for( int s = 0; s < 4; s++ ) + { + if( PVSCheck( dl->pvs, info.m_Clusters[s] ) ) + { + dotMask = SetComponentSIMD( dotMask, s, 1.0f ); + skipLight = false; + } + } + if ( skipLight ) + 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. + GatherSampleLightSSE( out, dl, info.m_FaceNum, info.m_Points, info.m_PointNormals, info.m_NormalCount, info.m_iThread ); + + // Apply the PVS check filter and compute falloff x dot + fltx4 fxdot[NUM_BUMP_VECTS + 1]; + for ( int b = 0; b < info.m_NormalCount; b++ ) + { + fxdot[b] = MulSIMD( out.m_flFalloff, out.m_flDot[b] ); + fxdot[b] = MulSIMD( fxdot[b], dotMask ); + } + + // Compute the contributions to each of the bumped lightmaps + // The first sample is for non-bumped lighting. + // The other sample are for bumpmapping. + for( int i = 0; i < 4; ++i ) + { + for( int n = 0; n < info.m_NormalCount; ++n ) + { + pLightmap[i][n].AddLight( SubFloat( fxdot[n], i ), dl->light.intensity, SubFloat( out.m_flSunAmount, i ) ); + } + } + } +} + +bool PointsInWinding ( FourVectors const & point, winding_t *w, int &invalidBits ) +{ + FourVectors edge, toPt, cross, testCross, p0, p1; + fltx4 invalidMask; + + // + // get the first normal to test + // + p0.DuplicateVector( w->p[0] ); + p1.DuplicateVector( w->p[1] ); + toPt = point; + toPt -= p0; + edge = p1; + edge -= p0; + testCross = edge ^ toPt; + testCross.VectorNormalizeFast(); + + for( int ndxPt = 1; ndxPt < w->numpoints; ndxPt++ ) + { + p0.DuplicateVector( w->p[ndxPt] ); + p1.DuplicateVector( w->p[(ndxPt+1)%w->numpoints] ); + toPt = point; + toPt -= p0; + edge = p1; + edge -= p0; + cross = edge ^ toPt; + cross.VectorNormalizeFast(); + + fltx4 dot = cross * testCross; + invalidMask = OrSIMD( invalidMask, CmpLtSIMD( dot, Four_Zeros ) ); + + invalidBits = TestSignSIMD ( invalidMask ); + if ( invalidBits == 0xF ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Perform supersampling at a particular point +//----------------------------------------------------------------------------- +static int SupersampleLightAtPoint( lightinfo_t& l, SSE_SampleInfo_t& info, + int sampleIndex, int lightStyleIndex, LightingValue_t *pLight, int flags ) +{ + sample_t& sample = info.m_pFaceLight->sample[sampleIndex]; + + // Get the position of the original sample in lightmapspace + Vector2D temp; + WorldToLuxelSpace( &l, sample.pos, temp ); + Vector sampleLightOrigin( temp[0], temp[1], 0.0f ); + + // Some parameters related to supersampling + float sampleWidth = ( flags & NON_AMBIENT_ONLY ) ? 4 : 2; + float cscale = 1.0f / sampleWidth; + float csshift = -((sampleWidth - 1) * cscale) / 2.0; + + // Clear out the light values + for (int i = 0; i < info.m_NormalCount; ++i ) + pLight[i].Zero(); + + int subsampleCount = 0; + + FourVectors superSampleNormal; + superSampleNormal.DuplicateVector( sample.normal ); + + FourVectors superSampleLightCoord; + FourVectors superSamplePosition; + + if ( flags & NON_AMBIENT_ONLY ) + { + float aRow[4]; + for ( int coord = 0; coord < 4; ++coord ) + aRow[coord] = csshift + coord * cscale; + fltx4 sseRow = LoadUnalignedSIMD( aRow ); + + for (int s = 0; s < 4; ++s) + { + // 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.DuplicateVector( sampleLightOrigin ); + superSampleLightCoord.x = AddSIMD( superSampleLightCoord.x, ReplicateX4( aRow[s] ) ); + superSampleLightCoord.y = AddSIMD( superSampleLightCoord.y, sseRow ); + + // 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 g_bDumpPatches is true. + int invalidBits = 0; + if ( sample.w && !PointsInWinding( superSamplePosition, sample.w, invalidBits ) ) + continue; + + // Compute the super-sample illumination point and normal + // We're assuming the flat normal is the same for all supersamples + ComputeIlluminationPointAndNormalsSSE( l, superSamplePosition, superSampleNormal, &info, 4 ); + + // Resample the non-ambient light at this point... + LightingValue_t result[4][NUM_BUMP_VECTS+1]; + ResampleLightAt4Points( info, lightStyleIndex, NON_AMBIENT_ONLY, result ); + + // Got more subsamples + for ( int i = 0; i < 4; i++ ) + { + if ( !( ( invalidBits >> i ) & 0x1 ) ) + { + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + pLight[n].AddLight( result[i][n] ); + } + ++subsampleCount; + } + } + } + } + else + { + FourVectors superSampleOffsets; + superSampleOffsets.LoadAndSwizzle( Vector( csshift, csshift, 0 ), Vector( csshift, csshift + cscale, 0), + Vector( csshift + cscale, csshift, 0 ), Vector( csshift + cscale, csshift + cscale, 0 ) ); + superSampleLightCoord.DuplicateVector( sampleLightOrigin ); + superSampleLightCoord += superSampleOffsets; + + LuxelSpaceToWorld( &l, superSampleLightCoord[0], superSampleLightCoord[1], superSamplePosition ); + + int invalidBits = 0; + if ( sample.w && !PointsInWinding( superSamplePosition, sample.w, invalidBits ) ) + return 0; + + ComputeIlluminationPointAndNormalsSSE( l, superSamplePosition, superSampleNormal, &info, 4 ); + + LightingValue_t result[4][NUM_BUMP_VECTS+1]; + ResampleLightAt4Points( info, lightStyleIndex, AMBIENT_ONLY, result ); + + // Got more subsamples + for ( int i = 0; i < 4; i++ ) + { + if ( !( ( invalidBits >> i ) & 0x1 ) ) + { + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + pLight[n].AddLight( result[i][n] ); + } + ++subsampleCount; + } + } + } + + return subsampleCount; +} + + +//----------------------------------------------------------------------------- +// Compute gradients of a lightmap +//----------------------------------------------------------------------------- +static void ComputeLightmapGradients( SSE_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 ; inumsamples ; 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( SSE_SampleInfo_t& info, int sampleIdx, + LightingValue_t **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].Intensity(); + + // 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( SSE_SampleInfo_t& info, LightingValue_t **ppLightSamples, float* pSampleIntensity ) +{ + for (int i=0; inumsamples; i++) + { + ComputeLuxelIntensity( info, i, ppLightSamples, pSampleIntensity ); + } +} + +//----------------------------------------------------------------------------- +// Perform supersampling on a particular lightstyle +//----------------------------------------------------------------------------- +static void BuildSupersampleFaceLights( lightinfo_t& l, SSE_SampleInfo_t& info, int lightstyleIndex ) +{ + LightingValue_t pAmbientLight[NUM_BUMP_VECTS+1]; + LightingValue_t 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 + LightingValue_t **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 ; inumsamples; ++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; + } + + // Supersample the ambient light for each bump direction vector + int ambientSupersampleCount = SupersampleLightAtPoint( l, info, i, lightstyleIndex, pAmbientLight, AMBIENT_ONLY ); + + // Supersample the non-ambient light for each bump direction vector + int directSupersampleCount = SupersampleLightAtPoint( l, info, i, lightstyleIndex, pDirectLight, NON_AMBIENT_ONLY ); + + // Because of sampling problems, small area triangles may have no samples. + // In this case, just use what we already have + if ( ambientSupersampleCount > 0 && directSupersampleCount > 0 ) + { + // Add the ambient + directional terms together, stick it back into the lightmap + for (int n = 0; n < info.m_NormalCount; ++n) + { + ppLightSamples[n][i].Zero(); + ppLightSamples[n][i].AddWeighted( pDirectLight[n],1.0f / directSupersampleCount ); + ppLightSamples[n][i].AddWeighted( pAmbientLight[n], 1.0f / ambientSupersampleCount ); + } + + // 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 ; inumsamples ; ++i) + { + for (int j = 0; j 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 ; jnumedges ; 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, SSE_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; + + info.m_NumSamples = info.m_pFaceLight->numsamples; + info.m_NumSampleGroups = ( info.m_NumSamples & 0x3) ? ( info.m_NumSamples / 4 ) + 1 : ( info.m_NumSamples / 4 ); + + // initialize normals if the surface is flat + if (l.isflat) + { + info.m_PointNormals[0].DuplicateVector( l.facenormal ); + + // use facenormal along with the smooth normal to build the three bump map vectors + if( info.m_NormalCount > 1 ) + { + Vector bumpVects[NUM_BUMP_VECTS]; + GetBumpNormals( info.m_pTexInfo->textureVecsTexelsPerWorldUnits[0], + info.m_pTexInfo->textureVecsTexelsPerWorldUnits[1], l.facenormal, + l.facenormal, bumpVects );//&info.m_PointNormal[1] ); + + for ( int b = 0; b < NUM_BUMP_VECTS; ++b ) + { + info.m_PointNormals[b + 1].DuplicateVector( bumpVects[b] ); + } + } + } +} + +void BuildFacelights (int iThread, int facenum) +{ + int i, j; + + lightinfo_t l; + dface_t *f; + facelight_t *fl; + SSE_SampleInfo_t sampleInfo; + directlight_t *dl; + Vector spot; + Vector v[4], n[4]; + + 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 ; jstyles[j] = 255; + + // Trivial-reject the whole face? + if( !( g_FacesVisibleToLights[facenum>>3] & (1 << (facenum & 7)) ) ) + return; + + if ( texinfo[f->texinfo].flags & TEX_SPECIAL) + return; // non-lit texture + + // check for patches for this face. If none it must be degenerate. Ignore. + if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) + return; + + fl = &facelight[facenum]; + + InitLightinfo( &l, facenum ); + CalcPoints( &l, fl, facenum ); + InitSampleInfo( l, iThread, sampleInfo ); + + // Allocate sample positions/normals to SSE + int numGroups = ( fl->numsamples & 0x3) ? ( fl->numsamples / 4 ) + 1 : ( fl->numsamples / 4 ); + + // always allocate style 0 lightmap + f->styles[0] = 0; + AllocateLightstyleSamples( fl, 0, sampleInfo.m_NormalCount ); + + // sample the lights at each sample location + for ( int grp = 0; grp < numGroups; ++grp ) + { + int nSample = 4 * grp; + + sample_t *sample = sampleInfo.m_pFaceLight->sample + nSample; + int numSamples = min ( 4, sampleInfo.m_pFaceLight->numsamples - nSample ); + + FourVectors positions; + FourVectors normals; + + for ( int i = 0; i < 4; i++ ) + { + v[i] = ( i < numSamples ) ? sample[i].pos : sample[numSamples - 1].pos; + n[i] = ( i < numSamples ) ? sample[i].normal : sample[numSamples - 1].normal; + } + positions.LoadAndSwizzle( v[0], v[1], v[2], v[3] ); + normals.LoadAndSwizzle( n[0], n[1], n[2], n[3] ); + + ComputeIlluminationPointAndNormalsSSE( l, positions, normals, &sampleInfo, numSamples ); + + // Fixup sample normals in case of smooth faces + if ( !l.isflat ) + { + for ( int i = 0; i < numSamples; i++ ) + sample[i].normal = sampleInfo.m_PointNormals[0].Vec( i ); + } + + // Iterate over all the lights and add their contribution to this group of spots + GatherSampleLightAt4Points( sampleInfo, nSample, numSamples ); + } + + // 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 ); + } + } + + if (!g_bUseMPI) + { + // + // This is done on the master node when MPI is used + // + BuildPatchLights( facenum ); + } + + if( g_bDumpPatches ) + { + DumpSamples( facenum, fl ); + } + else + { + FreeSampleWindings( fl ); + } + +} + +void BuildPatchLights( int facenum ) +{ + int i, k; + + CPatch *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( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) + return; + + // push up sampled light to parents (children always exist first in the list) + CPatch *pNextPatch; + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + // skip patches without parents + if( patch->parent == g_Patches.InvalidIndex() ) +// if (patch->parent == -1) + continue; + + CPatch *parent = &g_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 = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_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 = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + if ( patch->child1 != g_Patches.InvalidIndex() ) + { + float s1, s2; + CPatch *child1; + CPatch *child2; + + child1 = &g_Patches.Element( patch->child1 ); + child2 = &g_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++) + { + fl->light[j][0][i].m_vecLighting += ambient; + if( needsBumpmap ) + { + fl->light[j][1][i].m_vecLighting += ambient; + fl->light[j][2][i].m_vecLighting += ambient; + fl->light[j][3][i].m_vecLighting += ambient; + } + } + break; + } + } + } + + // light from dlight_threshold and above is sent out, but the + // texture itself should still be full bright + +#if 0 + // if( VectorAvg( g_FacePatches[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 ; inumsamples ; i++) + { + // garymctchange + VectorAdd( fl->light[j][0][i], g_FacePatches[facenum]->baselight, fl->light[j][0][i] ); + if( needsBumpmap ) + { + for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + VectorAdd( fl->light[j][bumpSample][i], g_FacePatches[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 ); +} + +// 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; +} + -- cgit v1.2.3