//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $Revision: $ // $NoKeywords: $ // // This file contains code to allow us to associate client data with bsp leaves. // //=============================================================================// #include "vrad.h" #include "Bsplib.h" #include "GameBSPFile.h" #include "UtlBuffer.h" #include "UtlVector.h" #include "CModel.h" #include "studio.h" #include "pacifier.h" #include "vraddetailprops.h" #include "mathlib/halton.h" #include "messbuf.h" #include "byteswap.h" bool LoadStudioModel( char const* pModelName, CUtlBuffer& buf ); //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- extern float SoftenCosineTerm( float flDot ); extern float CalculateAmbientOcclusion( Vector *pPosition, Vector *pNormal ); //----------------------------------------------------------------------------- // Purpose: Writes a glview text file containing the collision surface in question // Input : *pCollide - // *pFilename - //----------------------------------------------------------------------------- void DumpRayToGlView( Ray_t const& ray, float dist, Vector* pColor, const char *pFilename ) { Vector dir = ray.m_Delta; float len = VectorNormalize(dir); if (len < 1e-3) return; Vector up( 0, 0, 1 ); Vector crossDir; if (fabs(DotProduct(up, dir)) - 1.0f < -1e-3 ) { CrossProduct( dir, up, crossDir ); VectorNormalize(crossDir); } else { up.Init( 0, 1, 0 ); CrossProduct( dir, up, crossDir ); VectorNormalize(crossDir); } Vector end; Vector start1, start2; VectorMA( ray.m_Start, dist, ray.m_Delta, end ); VectorMA( ray.m_Start, -2, crossDir, start1 ); VectorMA( ray.m_Start, 2, crossDir, start2 ); FileHandle_t fp = g_pFileSystem->Open( pFilename, "a" ); int vert = 0; CmdLib_FPrintf( fp, "3\n" ); CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", start1.x, start1.y, start1.z, pColor->x, pColor->y, pColor->z ); vert++; CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", start2.x, start2.y, start2.z, pColor->x, pColor->y, pColor->z ); vert++; CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", end.x, end.y, end.z, pColor->x, pColor->y, pColor->z ); vert++; g_pFileSystem->Close( fp ); } //----------------------------------------------------------------------------- // This puppy is used to construct the game lumps //----------------------------------------------------------------------------- static CUtlVector s_DetailPropLightStyleLumpLDR; static CUtlVector s_DetailPropLightStyleLumpHDR; static CUtlVector *s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpLDR; //----------------------------------------------------------------------------- // An amount to add to each model to get to the model center //----------------------------------------------------------------------------- CUtlVector g_ModelCenterOffset; CUtlVector g_SpriteCenterOffset; void VRadDetailProps_SetHDRMode( bool bHDR ) { if( bHDR ) { s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpHDR; } else { s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpLDR; } } //----------------------------------------------------------------------------- // Finds ambient sky lights //----------------------------------------------------------------------------- static directlight_t* FindAmbientSkyLight() { static directlight_t *s_pCachedSkylight = NULL; // Don't keep searching for the same light. if ( !s_pCachedSkylight ) { // find any ambient lights directlight_t* dl; for (dl = activelights; dl != 0; dl = dl->next) { if (dl->light.type == emit_skyambient) { s_pCachedSkylight = dl; break; } } } return s_pCachedSkylight; } //----------------------------------------------------------------------------- // Compute world center of a prop //----------------------------------------------------------------------------- static void ComputeWorldCenter( DetailObjectLump_t& prop, Vector& center, Vector& normal ) { // Transform the offset into world space Vector forward, right; AngleVectors( prop.m_Angles, &forward, &right, &normal ); VectorCopy( prop.m_Origin, center ); // FIXME: Take orientation into account? switch (prop.m_Type ) { case DETAIL_PROP_TYPE_MODEL: VectorMA( center, g_ModelCenterOffset[prop.m_DetailModel].x, forward, center ); VectorMA( center, -g_ModelCenterOffset[prop.m_DetailModel].y, right, center ); VectorMA( center, g_ModelCenterOffset[prop.m_DetailModel].z, normal, center ); break; case DETAIL_PROP_TYPE_SPRITE: Vector vecOffset; VectorMultiply( g_SpriteCenterOffset[prop.m_DetailModel], prop.m_flScale, vecOffset ); VectorMA( center, vecOffset.x, forward, center ); VectorMA( center, -vecOffset.y, right, center ); VectorMA( center, vecOffset.z, normal, center ); break; } } //----------------------------------------------------------------------------- // Computes max direct lighting for a single detal prop //----------------------------------------------------------------------------- static void ComputeMaxDirectLighting( DetailObjectLump_t& prop, Vector* maxcolor, int iThread ) { // The max direct lighting must be along the direction to one // of the static lights.... Vector origin, normal; ComputeWorldCenter( prop, origin, normal ); if ( !origin.IsValid() || !normal.IsValid() ) { static bool s_Warned = false; if ( !s_Warned ) { Warning("WARNING: Bogus detail props encountered!\n" ); s_Warned = true; } // fill with debug color for ( int i = 0; i < MAX_LIGHTSTYLES; ++i) { maxcolor[i].Init(1,0,0); } return; } int cluster = ClusterFromPoint(origin); Vector delta; CUtlVector< directlight_t* > lights; CUtlVector< Vector > directions; directlight_t* dl; for (dl = activelights; dl != 0; dl = dl->next) { // skyambient doesn't affect dlights.. if (dl->light.type == emit_skyambient) continue; // is this lights cluster visible? if ( PVSCheck( dl->pvs, cluster ) ) { lights.AddToTail(dl); VectorSubtract( dl->light.origin, origin, delta ); VectorNormalize( delta ); directions.AddToTail( delta ); } } // Find the max illumination int i; for ( i = 0; i < MAX_LIGHTSTYLES; ++i) { maxcolor[i].Init(0,0,0); } // NOTE: See version 10 for a method where we choose a normal based on whichever // one produces the maximum possible illumination. This appeared to work better on // e3_town, so I'm trying it now; hopefully it'll be good for all cases. int j; for ( j = 0; j < lights.Count(); ++j) { dl = lights[j]; SSE_sampleLightOutput_t out; FourVectors origin4; FourVectors normal4; origin4.DuplicateVector( origin ); normal4.DuplicateVector( normal ); GatherSampleLightSSE ( out, dl, -1, origin4, &normal4, 1, iThread, GATHERLFLAGS_STATICPROP ); VectorMA( maxcolor[dl->light.style], out.m_flFalloff.m128_f32[0] * out.m_flDot[0].m128_f32[0], dl->light.intensity, maxcolor[dl->light.style] ); } } //----------------------------------------------------------------------------- // Computes the ambient term from a particular surface //----------------------------------------------------------------------------- static void ComputeAmbientFromSurface( dface_t* pFace, directlight_t* pSkylight, Vector& radcolor ) { texinfo_t* pTex = &texinfo[pFace->texinfo]; if (pTex) { // If we hit the sky, use the sky ambient if (pTex->flags & SURF_SKY) { if (pSkylight) { // add in sky ambient VectorDivide( pSkylight->light.intensity, 255.0f, radcolor ); } } else { VectorMultiply( radcolor, dtexdata[pTex->texdata].reflectivity, radcolor ); } } } //----------------------------------------------------------------------------- // Computes the lightmap color at a particular point //----------------------------------------------------------------------------- static void ComputeLightmapColorFromAverage( dface_t* pFace, directlight_t* pSkylight, float scale, Vector pColor[MAX_LIGHTSTYLES] ) { texinfo_t* pTex = &texinfo[pFace->texinfo]; if (pTex->flags & SURF_SKY) { if (pSkylight) { // add in sky ambient Vector amb = pSkylight->light.intensity / 255.0f; pColor[0] += amb * scale; } return; } for (int maps = 0 ; maps < MAXLIGHTMAPS && pFace->styles[maps] != 255 ; ++maps) { ColorRGBExp32* pAvgColor = dface_AvgLightColor( pFace, maps ); // this code expects values from [0..1] not [0..255] Vector color; color[0] = TexLightToLinear( pAvgColor->r, pAvgColor->exponent ); color[1] = TexLightToLinear( pAvgColor->g, pAvgColor->exponent ); color[2] = TexLightToLinear( pAvgColor->b, pAvgColor->exponent ); ComputeAmbientFromSurface( pFace, pSkylight, color ); int style = pFace->styles[maps]; pColor[style] += color * scale; } } //----------------------------------------------------------------------------- // Returns true if the surface has bumped lightmaps //----------------------------------------------------------------------------- static bool SurfHasBumpedLightmaps( dface_t *pSurf ) { bool hasBumpmap = false; if( ( texinfo[pSurf->texinfo].flags & SURF_BUMPLIGHT ) && ( !( texinfo[pSurf->texinfo].flags & SURF_NOLIGHT ) ) ) { hasBumpmap = true; } return hasBumpmap; } //----------------------------------------------------------------------------- // Computes the lightmap color at a particular point //----------------------------------------------------------------------------- static void ComputeLightmapColorPointSample( dface_t* pFace, directlight_t* pSkylight, Vector2D const& luv, float scale, Vector pColor[MAX_LIGHTSTYLES] ) { // face unaffected by light if (pFace->lightofs == -1 ) return; int smax = ( pFace->m_LightmapTextureSizeInLuxels[0] ) + 1; int tmax = ( pFace->m_LightmapTextureSizeInLuxels[1] ) + 1; // luv is in the space of the accumulated lightmap page; we need to convert // it to be in the space of the surface int ds = clamp( (int)luv.x, 0, smax-1 ); int dt = clamp( (int)luv.y, 0, tmax-1 ); int offset = smax * tmax; if ( SurfHasBumpedLightmaps( pFace ) ) offset *= ( NUM_BUMP_VECTS + 1 ); ColorRGBExp32* pLightmap = (ColorRGBExp32*)&pdlightdata->Base()[pFace->lightofs]; pLightmap += dt * smax + ds; for (int maps = 0 ; maps < MAXLIGHTMAPS && pFace->styles[maps] != 255 ; ++maps) { int style = pFace->styles[maps]; Vector color; color[0] = TexLightToLinear( pLightmap->r, pLightmap->exponent ); color[1] = TexLightToLinear( pLightmap->g, pLightmap->exponent ); color[2] = TexLightToLinear( pLightmap->b, pLightmap->exponent ); ComputeAmbientFromSurface( pFace, pSkylight, color ); pColor[style] += color * scale; pLightmap += offset; } } //----------------------------------------------------------------------------- // Tests a particular node //----------------------------------------------------------------------------- class CLightSurface : public IBSPNodeEnumerator { public: CLightSurface(int iThread) : m_pSurface(0), m_HitFrac(1.0f), m_bHasLuxel(false), m_iThread(iThread) {} // call back with a node and a context bool EnumerateNode( int node, Ray_t const& ray, float f, intp context ) { dface_t* pSkySurface = 0; // Compute the actual point Vector pt; VectorMA( ray.m_Start, f, ray.m_Delta, pt ); dnode_t* pNode = &dnodes[node]; dface_t* pFace = &g_pFaces[pNode->firstface]; for (int i=0 ; i < pNode->numfaces ; ++i, ++pFace) { // Don't take into account faces that are int a leaf if ( !pFace->onNode ) continue; // Don't test displacement faces if ( pFace->dispinfo != -1 ) continue; texinfo_t* pTex = &texinfo[pFace->texinfo]; // Don't immediately return when we hit sky; // we may actually hit another surface if (pTex->flags & SURF_SKY) { if (TestPointAgainstSkySurface( pt, pFace )) { pSkySurface = pFace; } continue; } if (TestPointAgainstSurface( pt, pFace, pTex )) { m_HitFrac = f; m_pSurface = pFace; m_bHasLuxel = true; return false; } } // if we hit a sky surface, return it m_pSurface = pSkySurface; return (m_pSurface == 0); } // call back with a leaf and a context virtual bool EnumerateLeaf( int leaf, Ray_t const& ray, float start, float end, intp context ) { bool hit = false; dleaf_t* pLeaf = &dleafs[leaf]; for (int i=0 ; i < pLeaf->numleaffaces ; ++i) { Assert( pLeaf->firstleafface + i < numleaffaces ); Assert( dleaffaces[pLeaf->firstleafface + i] < numfaces ); dface_t* pFace = &g_pFaces[dleaffaces[pLeaf->firstleafface + i]]; // Don't test displacement faces; we need to check another list if ( pFace->dispinfo != -1 ) continue; // Don't take into account faces that are on a node if ( pFace->onNode ) continue; // Find intersection point against detail brushes texinfo_t* pTex = &texinfo[pFace->texinfo]; dplane_t* pPlane = &dplanes[pFace->planenum]; // Backface cull... if (DotProduct( pPlane->normal, ray.m_Delta ) > 0) continue; float startDotN = DotProduct( ray.m_Start, pPlane->normal ); float deltaDotN = DotProduct( ray.m_Delta, pPlane->normal ); float front = startDotN + start * deltaDotN - pPlane->dist; float back = startDotN + end * deltaDotN - pPlane->dist; int side = front < 0; // Blow it off if it doesn't split the plane... if ( (back < 0) == side ) continue; // Don't test a surface that is farther away from the closest found intersection float f = front / (front-back); float mid = start * (1.0f - f) + end * f; if (mid >= m_HitFrac) continue; Vector pt; VectorMA( ray.m_Start, mid, ray.m_Delta, pt ); if (TestPointAgainstSurface( pt, pFace, pTex )) { m_HitFrac = mid; m_pSurface = pFace; hit = true; m_bHasLuxel = true; } } // Now try to clip against all displacements in the leaf float dist; Vector2D luxelCoord; dface_t *pDispFace; StaticDispMgr()->ClipRayToDispInLeaf( s_DispTested[m_iThread], ray, leaf, dist, pDispFace, luxelCoord ); if (dist < m_HitFrac) { m_HitFrac = dist; m_pSurface = pDispFace; Vector2DCopy( luxelCoord, m_LuxelCoord ); hit = true; m_bHasLuxel = true; } return !hit; } bool FindIntersection( Ray_t const& ray ) { StaticDispMgr()->StartRayTest( s_DispTested[m_iThread] ); return !EnumerateNodesAlongRay( ray, this, 0 ); } private: bool TestPointAgainstSurface( Vector const& pt, dface_t* pFace, texinfo_t* pTex ) { // no lightmaps on this surface? punt... // FIXME: should be water surface? if (pTex->flags & SURF_NOLIGHT) return false; // See where in lightmap space our intersection point is float s, t; s = DotProduct (pt.Base(), pTex->lightmapVecsLuxelsPerWorldUnits[0]) + pTex->lightmapVecsLuxelsPerWorldUnits[0][3]; t = DotProduct (pt.Base(), pTex->lightmapVecsLuxelsPerWorldUnits[1]) + pTex->lightmapVecsLuxelsPerWorldUnits[1][3]; // Not in the bounds of our lightmap? punt... if( s < pFace->m_LightmapTextureMinsInLuxels[0] || t < pFace->m_LightmapTextureMinsInLuxels[1] ) return false; // assuming a square lightmap (FIXME: which ain't always the case), // lets see if it lies in that rectangle. If not, punt... float ds = s - pFace->m_LightmapTextureMinsInLuxels[0]; float dt = t - pFace->m_LightmapTextureMinsInLuxels[1]; if( ds > pFace->m_LightmapTextureSizeInLuxels[0] || dt > pFace->m_LightmapTextureSizeInLuxels[1] ) return false; m_LuxelCoord.x = ds; m_LuxelCoord.y = dt; return true; } bool TestPointAgainstSkySurface( Vector const &pt, dface_t *pFace ) { // Create sky face winding. winding_t *pWinding = WindingFromFace( pFace, Vector( 0.0f, 0.0f, 0.0f ) ); // Test point in winding. (Since it is at the node, it is in the plane.) bool bRet = PointInWinding( pt, pWinding ); FreeWinding( pWinding ); return bRet; } public: int m_iThread; dface_t* m_pSurface; float m_HitFrac; Vector2D m_LuxelCoord; bool m_bHasLuxel; }; bool CastRayInLeaf( int iThread, const Vector &start, const Vector &end, int leafIndex, float *pFraction, Vector *pNormal ) { pFraction[0] = 1.0f; Ray_t ray; ray.Init( start, end, vec3_origin, vec3_origin ); CBaseTrace trace; if ( TraceLeafBrushes( leafIndex, start, end, trace ) != 1.0f ) { pFraction[0] = trace.fraction; *pNormal = trace.plane.normal; } else { Assert(!trace.startsolid && !trace.allsolid); } StaticDispMgr()->StartRayTest( s_DispTested[iThread] ); // Now try to clip against all displacements in the leaf float dist; Vector normal; StaticDispMgr()->ClipRayToDispInLeaf( s_DispTested[iThread], ray, leafIndex, dist, &normal ); if ( dist < pFraction[0] ) { pFraction[0] = dist; *pNormal = normal; } return pFraction[0] != 1.0f ? true : false; } //----------------------------------------------------------------------------- // Computes ambient lighting along a specified ray. // Ray represents a cone, tanTheta is the tan of the inner cone angle //----------------------------------------------------------------------------- void CalcRayAmbientLighting( int iThread, const Vector &vStart, const Vector &vEnd, float tanTheta, Vector color[MAX_LIGHTSTYLES] ) { Ray_t ray; ray.Init( vStart, vEnd, vec3_origin, vec3_origin ); directlight_t *pSkyLight = FindAmbientSkyLight(); CLightSurface surfEnum(iThread); if (!surfEnum.FindIntersection( ray )) return; // compute the approximate radius of a circle centered around the intersection point float dist = ray.m_Delta.Length() * tanTheta * surfEnum.m_HitFrac; // until 20" we use the point sample, then blend in the average until we're covering 40" // This is attempting to model the ray as a cone - in the ideal case we'd simply sample all // luxels in the intersection of the cone with the surface. Since we don't have surface // neighbor information computed we'll just approximate that sampling with a blend between // a point sample and the face average. // This yields results that are similar in that aliasing is reduced at distance while // point samples provide accuracy for intersections with near geometry float scaleAvg = RemapValClamped( dist, 20, 40, 0.0f, 1.0f ); if ( !surfEnum.m_bHasLuxel ) { // don't have luxel UV, so just use average sample scaleAvg = 1.0; } float scaleSample = 1.0f - scaleAvg; if (scaleAvg != 0) { ComputeLightmapColorFromAverage( surfEnum.m_pSurface, pSkyLight, scaleAvg, color ); } if (scaleSample != 0) { ComputeLightmapColorPointSample( surfEnum.m_pSurface, pSkyLight, surfEnum.m_LuxelCoord, scaleSample, color ); } } //----------------------------------------------------------------------------- // Compute ambient lighting component at specified position. //----------------------------------------------------------------------------- static void ComputeAmbientLightingAtPoint( int iThread, const Vector &origin, Vector radcolor[NUMVERTEXNORMALS], Vector color[MAX_LIGHTSTYLES] ) { // NOTE: I'm not dealing with shadow-casting static props here // This is for speed, although we can add it if it turns out to // be important // sample world by casting N rays distributed across a sphere Vector upend; int j; for ( j = 0; j < MAX_LIGHTSTYLES; ++j) { color[j].Init( 0,0,0 ); } float tanTheta = tan(VERTEXNORMAL_CONE_INNER_ANGLE); for (int i = 0; i < NUMVERTEXNORMALS; i++) { VectorMA( origin, COORD_EXTENT * 1.74, g_anorms[i], upend ); // Now that we've got a ray, see what surface we've hit CalcRayAmbientLighting( iThread, origin, upend, tanTheta, color ); // DumpRayToGlView( ray, surfEnum.m_HitFrac, &color[0], "test.out" ); } for ( j = 0; j < MAX_LIGHTSTYLES; ++j) { VectorMultiply( color[j], 255.0f / (float)NUMVERTEXNORMALS, color[j] ); } } //----------------------------------------------------------------------------- // // Trace a ray from position. in the specified direction to determine a positive // hit for indirect lighting. // // Fire ray out from start, with end as start + direction*MAX_TRACE_LENGTH // If hit then fire ray back to start to see if it hits a back facing surface that would natually block the incoming light ray // If still okay then test explicitly against light blockers, test only in the hit to start direction // Update surfEnum and return true if a valid intersection for indirect light. // //----------------------------------------------------------------------------- bool TraceIndirectLightingSample( Vector &position, Vector &direction, CLightSurface &surfEnum, int iThread, bool force_fast ) { Ray_t ray; // trace to determine surface Vector vEnd, vStart; VectorScale( direction, MAX_TRACE_LENGTH, vEnd ); VectorAdd( position, vEnd, vEnd ); if ( force_fast ) { vStart = position; } else { // offset ray start position to compensate for ray leakage due to coincident surfaces (we are seeing some ray tests leak in some situations - e.g. prop vertex lies on ground plane) VectorScale( direction, -EQUAL_EPSILON, vStart ); VectorAdd( position, vStart, vStart ); } ray.Init( vStart, vEnd, vec3_origin, vec3_origin ); if ( !surfEnum.FindIntersection( ray ) ) return false; // Now test explicitly against light blockers (surfaces don't exist in the bsp nodes we're checking here, and this feels a safer change than updating indirect lighting for static props to use the slower rte path for all rays) // test from hitfrac back to start only VectorScale( direction, MAX_TRACE_LENGTH * surfEnum.m_HitFrac, vEnd ); VectorAdd( position, vEnd, vEnd ); FourVectors rayStart, rayEnd, rayDirection; fltx4 fractionVisible = Four_Ones; rayStart.DuplicateVector( vStart ); rayEnd.DuplicateVector( vEnd ); // rayDirection.DuplicateVector( direction ); // TestLine_LightBlockers( rayStart, rayEnd, &fractionVisible ); rayDirection.DuplicateVector( -direction ); TestLine_LightBlockers( rayEnd, rayStart, &fractionVisible ); if ( fractionVisible.m128_f32[0] < 1.0f ) { // ray hit blocker return false; } return true; } //----------------------------------------------------------------------------- // Trace hemispherical rays from a vertex, accumulating indirect // sources at each ray termination. // // force_fast = false currently implies 'new/improved' static prop lighting is to be used. //----------------------------------------------------------------------------- void ComputeIndirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor, int iThread, bool force_fast, bool bIgnoreNormals, int nStaticPropToSkip ) { outColor.Zero(); int nSamples = NUMVERTEXNORMALS; if ( do_fast || force_fast ) nSamples /= 4; else nSamples *= g_flStaticPropSampleScale; float totalDot = 0; DirectionalSampler_t sampler; for (int j = 0; j < nSamples; j++) { Vector samplingNormal = sampler.NextValue(); float dot; if ( bIgnoreNormals ) dot = (0.7071/2); else dot = DotProduct( normal, samplingNormal ); if ( dot <= EQUAL_EPSILON ) { // reject angles behind our plane continue; } totalDot += dot; // trace static prop indirect Vector staticPropIndirectColor( 0.0f, 0.0f, 0.0f ); float flStaticPropHitDist = FLT_MAX; if ( g_bStaticPropBounce ) { FourRays myrays; myrays.origin.DuplicateVector( position ); myrays.direction.DuplicateVector( samplingNormal ); RayTracingResult rt_result; g_RtEnv_RadiosityPatches.Trace4Rays( myrays, ReplicateX4( 10.0f ), ReplicateX4( MAX_TRACE_LENGTH ), &rt_result ); if ( rt_result.HitIds[ 0 ] != -1 ) { const TriIntersectData_t &intersectData = g_RtEnv_RadiosityPatches.OptimizedTriangleList[ rt_result.HitIds[ 0 ] ].m_Data.m_IntersectData; int nId = intersectData.m_nTriangleID; if ( nId & TRACE_ID_PATCH ) { int nPatchId = nId & ~TRACE_ID_PATCH; CPatch &patch = g_Patches[ nPatchId ]; if ( patch.staticPropIdx != nStaticPropToSkip ) { staticPropIndirectColor = dot * ( patch.totallight.light[ 0 ] + patch.directlight ) * patch.reflectivity; flStaticPropHitDist = SubFloat( rt_result.HitDistance, 0 ); } } } } // important to put the constructor here to init m_hitfrac, etc CLightSurface surfEnum( iThread ); // trace to determine surface if ( !TraceIndirectLightingSample( position, samplingNormal, surfEnum, iThread, force_fast ) || flStaticPropHitDist < surfEnum.m_HitFrac * MAX_TRACE_LENGTH ) { VectorAdd( outColor, staticPropIndirectColor, outColor ); // we may have hit a static prop patch continue; } // get color from surface lightmap texinfo_t* pTex = &texinfo[surfEnum.m_pSurface->texinfo]; if ( !pTex || pTex->flags & SURF_SKY ) { // ignore contribution from sky // sky ambient already accounted for during direct pass continue; } if ( surfEnum.m_pSurface->styles[0] == 255 || surfEnum.m_pSurface->lightofs < 0 ) { // no light affects this face continue; } Vector lightmapColor; if ( !surfEnum.m_bHasLuxel ) { ColorRGBExp32* pAvgLightmapColor = dface_AvgLightColor( surfEnum.m_pSurface, 0 ); ColorRGBExp32ToVector( *pAvgLightmapColor, lightmapColor ); } else { // get color from displacement int smax = ( surfEnum.m_pSurface->m_LightmapTextureSizeInLuxels[0] ) + 1; int tmax = ( surfEnum.m_pSurface->m_LightmapTextureSizeInLuxels[1] ) + 1; // luxelcoord is in the space of the accumulated lightmap page; we need to convert // it to be in the space of the surface int ds = clamp( (int)surfEnum.m_LuxelCoord.x, 0, smax-1 ); int dt = clamp( (int)surfEnum.m_LuxelCoord.y, 0, tmax-1 ); ColorRGBExp32* pLightmap = (ColorRGBExp32*)&(*pdlightdata)[surfEnum.m_pSurface->lightofs]; pLightmap += dt * smax + ds; ColorRGBExp32ToVector( *pLightmap, lightmapColor ); } if ( force_fast ) { VectorMultiply( lightmapColor, dtexdata[pTex->texdata].reflectivity, lightmapColor ); } else { // Include dot falloff on accumulating irradiance here // have tried using inv sqr falloff from TF2 changes to vrad (CL#2394791 & 2395471), but the result is very sensitive to the scale factor that is used (too dark or too bright otherwise) // this seems to give the most natural looking result (static props matching brushes) VectorMultiply( lightmapColor, dot * dtexdata[pTex->texdata].reflectivity, lightmapColor ); } VectorAdd( outColor, lightmapColor, outColor ); } if ( totalDot ) { VectorScale( outColor, 1.0f / totalDot, outColor ); } } void ComputeIndirectLightingAtPoint( Vector &position, Vector *normals, Vector *outColors, int numNormals, int iThread, bool force_fast, bool bIgnoreNormals, int nStaticPropToSkip ) { const Vector vZero(0.0f, 0.0f, 0.0f); if ( numNormals != ( NUM_BUMP_VECTS + 1 ) ) { for ( int k = 0; k < numNormals; ++k ) { ComputeIndirectLightingAtPoint( position, normals[k], outColors[k], iThread, force_fast, bIgnoreNormals, nStaticPropToSkip ); } return; } // optimize/unroll for num_bump_vects = 3 outColors[0].Zero(); outColors[1].Zero(); outColors[2].Zero(); outColors[3].Zero(); int nSamples = NUMVERTEXNORMALS; if ( do_fast || force_fast ) nSamples /= 4; else nSamples *= g_flStaticPropSampleScale; float totalDot[4] = {0.0f, 0.0f, 0.0f, 0.0f}; DirectionalSampler_t sampler; for ( int j = 0; j < nSamples; j++ ) { Vector samplingNormal = sampler.NextValue(); float dot[4]; if ( bIgnoreNormals ) { dot[0] = dot[1] = dot[2] = dot[3] = (0.7071 / 2); } else { samplingNormal.NormalizeInPlace(); dot[0] = DotProduct( normals[0], samplingNormal ); dot[1] = DotProduct( normals[1], samplingNormal ); dot[2] = DotProduct( normals[2], samplingNormal ); dot[3] = DotProduct( normals[3], samplingNormal ); } bool bDoRayTrace = false; bool bIncLighting[4] = {false, false, false, false}; if ( dot[0] > EQUAL_EPSILON ) { dot[0] = SoftenCosineTerm( dot[0] ); totalDot[0] += dot[0]; bDoRayTrace = true; bIncLighting[0] = true; } else { dot[0] = 0.0f; } if ( dot[1] > EQUAL_EPSILON ) { dot[1] = SoftenCosineTerm( dot[1] ); totalDot[1] += dot[1]; bDoRayTrace = true; bIncLighting[1] = true; } else { dot[1] = 0.0f; } if ( dot[2] > EQUAL_EPSILON ) { dot[2] = SoftenCosineTerm( dot[2] ); totalDot[2] += dot[2]; bDoRayTrace = true; bIncLighting[2] = true; } else { dot[2] = 0.0f; } if ( dot[3] > EQUAL_EPSILON ) { dot[3] = SoftenCosineTerm( dot[3] ); totalDot[3] += dot[3]; bDoRayTrace = true; bIncLighting[3] = true; } else { dot[3] = 0.0f; } // important to skip if ( dot[0] <= EQUAL_EPSILON ) { continue; } if ( bDoRayTrace ) { Vector staticPropIndirectColor( 0.0f, 0.0f, 0.0f ); float flStaticPropHitDist = FLT_MAX; if ( g_bStaticPropBounce ) { FourRays myrays; myrays.origin.DuplicateVector( position ); myrays.direction.DuplicateVector( samplingNormal ); RayTracingResult rt_result; g_RtEnv_RadiosityPatches.Trace4Rays( myrays, ReplicateX4( 10.0f ), ReplicateX4( MAX_TRACE_LENGTH ), &rt_result ); if ( rt_result.HitIds[ 0 ] != -1 ) { const TriIntersectData_t &intersectData = g_RtEnv_RadiosityPatches.OptimizedTriangleList[ rt_result.HitIds[ 0 ] ].m_Data.m_IntersectData; int nId = intersectData.m_nTriangleID; if ( nId & TRACE_ID_PATCH ) { int nPatchId = nId & ~TRACE_ID_PATCH; CPatch &patch = g_Patches[ nPatchId ]; if ( patch.staticPropIdx != nStaticPropToSkip ) { staticPropIndirectColor = ( patch.totallight.light[ 0 ] + patch.directlight ) * patch.reflectivity; flStaticPropHitDist = SubFloat( rt_result.HitDistance, 0 ); } } } } // important to put the constructor here to init m_hitfrac, etc CLightSurface surfEnum( iThread ); // trace to determine surface if ( !TraceIndirectLightingSample( position, samplingNormal, surfEnum, iThread, force_fast ) || flStaticPropHitDist < surfEnum.m_HitFrac * MAX_TRACE_LENGTH ) { // The dot values are 0 if bIncLighting is false so we don't actually need to branch here. VectorAdd( outColors[ 0 ], dot[ 0 ] * staticPropIndirectColor, outColors[ 0 ] ); // we may have hit a static prop patch VectorAdd( outColors[ 1 ], dot[ 1 ] * staticPropIndirectColor, outColors[ 1 ] ); VectorAdd( outColors[ 2 ], dot[ 2 ] * staticPropIndirectColor, outColors[ 2 ] ); VectorAdd( outColors[ 3 ], dot[ 3 ] * staticPropIndirectColor, outColors[ 3 ] ); continue; } // get color from surface lightmap texinfo_t* pTex = &texinfo[surfEnum.m_pSurface->texinfo]; if ( !pTex || pTex->flags & SURF_SKY ) { // ignore contribution from sky // sky ambient already accounted for during direct pass continue; } if ( surfEnum.m_pSurface->styles[0] == 255 || surfEnum.m_pSurface->lightofs < 0 ) { // no light affects this face continue; } Vector lightmapColor; Vector lightmapColors[4]; if ( !surfEnum.m_bHasLuxel ) { ColorRGBExp32* pAvgLightmapColor = dface_AvgLightColor( surfEnum.m_pSurface, 0 ); ColorRGBExp32ToVector( *pAvgLightmapColor, lightmapColor ); } else { // get color from displacement int smax = (surfEnum.m_pSurface->m_LightmapTextureSizeInLuxels[0]) + 1; int tmax = (surfEnum.m_pSurface->m_LightmapTextureSizeInLuxels[1]) + 1; // luxelcoord is in the space of the accumulated lightmap page; we need to convert // it to be in the space of the surface int ds = clamp( (int)surfEnum.m_LuxelCoord.x, 0, smax - 1 ); int dt = clamp( (int)surfEnum.m_LuxelCoord.y, 0, tmax - 1 ); ColorRGBExp32* pLightmap = (ColorRGBExp32*)&(*pdlightdata)[surfEnum.m_pSurface->lightofs]; pLightmap += dt * smax + ds; ColorRGBExp32ToVector( *pLightmap, lightmapColor ); } lightmapColor.Max( vZero ); if ( force_fast ) { VectorMultiply( lightmapColor, dtexdata[pTex->texdata].reflectivity, lightmapColors[0] ); if ( bIncLighting[0] ) { VectorAdd( outColors[0], lightmapColors[0], outColors[0] ); } if ( bIncLighting[1] ) { VectorAdd( outColors[1], lightmapColors[0], outColors[1] ); } if ( bIncLighting[2] ) { VectorAdd( outColors[2], lightmapColors[0], outColors[2] ); } if ( bIncLighting[3] ) { VectorAdd( outColors[3], lightmapColors[0], outColors[3] ); } } else { // Include dot falloff on accumulating irradiance here // have tried using inv sqr falloff from TF2 changes to vrad (CL#2394791 & 2395471), but the result is very sensitive to the scale factor that is used (too dark or too bright otherwise) // this seems to give the most natural looking result (static props matching brushes) if ( bIncLighting[0] ) { VectorMultiply( lightmapColor, dot[0] * dtexdata[pTex->texdata].reflectivity, lightmapColors[0] ); VectorAdd( outColors[0], lightmapColors[0], outColors[0] ); } if ( bIncLighting[1] ) { VectorMultiply( lightmapColor, dot[1] * dtexdata[pTex->texdata].reflectivity, lightmapColors[1] ); VectorAdd( outColors[1], lightmapColors[1], outColors[1] ); } if ( bIncLighting[2] ) { VectorMultiply( lightmapColor, dot[2] * dtexdata[pTex->texdata].reflectivity, lightmapColors[2] ); VectorAdd( outColors[2], lightmapColors[2], outColors[2] ); } if ( bIncLighting[3] ) { VectorMultiply( lightmapColor, dot[3] * dtexdata[pTex->texdata].reflectivity, lightmapColors[3] ); VectorAdd( outColors[3], lightmapColors[3], outColors[3] ); } } } } if ( totalDot[0] ) { VectorScale( outColors[0], 1.0f / totalDot[0], outColors[0] ); } if ( totalDot[1] ) { VectorScale( outColors[1], 1.0f / totalDot[1], outColors[1] ); } if ( totalDot[2] ) { VectorScale( outColors[2], 1.0f / totalDot[2], outColors[2] ); } if ( totalDot[3] ) { VectorScale( outColors[3], 1.0f / totalDot[3], outColors[3] ); } } static void ComputeAmbientLighting( int iThread, DetailObjectLump_t& prop, Vector color[MAX_LIGHTSTYLES] ) { Vector origin, normal; ComputeWorldCenter( prop, origin, normal ); if ( !origin.IsValid() || !normal.IsValid() ) { static bool s_Warned = false; if ( !s_Warned ) { Warning("WARNING: Bogus detail props encountered!\n" ); s_Warned = true; } // fill with debug color for ( int i = 0; i < MAX_LIGHTSTYLES; ++i) { color[i].Init(1,0,0); } return; } Vector radcolor[NUMVERTEXNORMALS]; ComputeAmbientLightingAtPoint( iThread, origin, radcolor, color ); } //----------------------------------------------------------------------------- // Computes lighting for a single detal prop //----------------------------------------------------------------------------- static void ComputeLighting( DetailObjectLump_t& prop, int iThread ) { // We're going to take the maximum of the ambient lighting and // the strongest directional light. This works because we're assuming // the props will have built-in faked lighting. Vector directColor[MAX_LIGHTSTYLES]; Vector ambColor[MAX_LIGHTSTYLES]; // Get the max influence of all direct lights ComputeMaxDirectLighting( prop, directColor, iThread ); // Get the ambient lighting + lightstyles ComputeAmbientLighting( iThread, prop, ambColor ); // Base lighting Vector totalColor; VectorAdd( directColor[0], ambColor[0], totalColor ); VectorToColorRGBExp32( totalColor, prop.m_Lighting ); bool hasLightstyles = false; prop.m_LightStyleCount = 0; // lightstyles for (int i = 1; i < MAX_LIGHTSTYLES; ++i ) { VectorAdd( directColor[i], ambColor[i], totalColor ); totalColor *= 0.5f; if ((totalColor[0] != 0.0f) || (totalColor[1] != 0.0f) || (totalColor[2] != 0.0f) ) { if (!hasLightstyles) { prop.m_LightStyles = s_pDetailPropLightStyleLump->Count(); hasLightstyles = true; } int j = s_pDetailPropLightStyleLump->AddToTail(); VectorToColorRGBExp32( totalColor, (*s_pDetailPropLightStyleLump)[j].m_Lighting ); (*s_pDetailPropLightStyleLump)[j].m_Style = i; ++prop.m_LightStyleCount; } } } //----------------------------------------------------------------------------- // Unserialization //----------------------------------------------------------------------------- static void UnserializeModelDict( CUtlBuffer& buf ) { // Get origin offset for each model... int count = buf.GetInt(); while ( --count >= 0 ) { DetailObjectDictLump_t lump; buf.Get( &lump, sizeof(DetailObjectDictLump_t) ); int i = g_ModelCenterOffset.AddToTail(); CUtlBuffer mdlbuf; if (LoadStudioModel( lump.m_Name, mdlbuf )) { studiohdr_t* pHdr = (studiohdr_t*)mdlbuf.Base(); VectorAdd( pHdr->hull_min, pHdr->hull_max, g_ModelCenterOffset[i] ); g_ModelCenterOffset[i] *= 0.5f; } else { g_ModelCenterOffset[i].Init(0,0,0); } } } static void UnserializeSpriteDict( CUtlBuffer& buf ) { // Get origin offset for each model... int count = buf.GetInt(); while ( --count >= 0 ) { DetailSpriteDictLump_t lump; buf.Get( &lump, sizeof(DetailSpriteDictLump_t) ); // For these sprites, x goes out the front, y right, z up int i = g_SpriteCenterOffset.AddToTail(); g_SpriteCenterOffset[i].x = 0.0f; g_SpriteCenterOffset[i].y = lump.m_LR.x + lump.m_UL.x; g_SpriteCenterOffset[i].z = lump.m_LR.y + lump.m_UL.y; g_SpriteCenterOffset[i] *= 0.5f; } } //----------------------------------------------------------------------------- // Unserializes the detail props //----------------------------------------------------------------------------- static int UnserializeDetailProps( DetailObjectLump_t*& pProps ) { GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); if (g_GameLumps.GetGameLumpVersion(handle) != GAMELUMP_DETAIL_PROPS_VERSION) return 0; // Unserialize CUtlBuffer buf( g_GameLumps.GetGameLump(handle), g_GameLumps.GameLumpSize( handle ), CUtlBuffer::READ_ONLY ); UnserializeModelDict( buf ); UnserializeSpriteDict( buf ); // Now we're pointing to the detail prop data // This actually works because the scope of the game lump data // is global and the buf was just pointing to it. int count = buf.GetInt(); if (count) { pProps = (DetailObjectLump_t*)buf.PeekGet(); } else { pProps = 0; } return count; } //----------------------------------------------------------------------------- // Writes the detail lighting lump //----------------------------------------------------------------------------- static void WriteDetailLightingLump( int lumpID, int lumpVersion, CUtlVector &lumpData ) { GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(lumpID); if (handle != g_GameLumps.InvalidGameLump()) g_GameLumps.DestroyGameLump(handle); int lightsize = lumpData.Count() * sizeof(DetailPropLightstylesLump_t); int lumpsize = lightsize + sizeof(int); handle = g_GameLumps.CreateGameLump( lumpID, lumpsize, 0, lumpVersion ); // Serialize the data CUtlBuffer buf( g_GameLumps.GetGameLump(handle), lumpsize ); buf.PutInt( lumpData.Count() ); if (lightsize) buf.Put( lumpData.Base(), lightsize ); } static void WriteDetailLightingLumps( void ) { WriteDetailLightingLump( GAMELUMP_DETAIL_PROP_LIGHTING, GAMELUMP_DETAIL_PROP_LIGHTING_VERSION, s_DetailPropLightStyleLumpLDR ); WriteDetailLightingLump( GAMELUMP_DETAIL_PROP_LIGHTING_HDR, GAMELUMP_DETAIL_PROP_LIGHTING_HDR_VERSION, s_DetailPropLightStyleLumpHDR ); } // need to do this so that if we are building HDR data, the LDR data is intact, and vice versa.s void UnserializeDetailPropLighting( int lumpID, int lumpVersion, CUtlVector &lumpData ) { GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( lumpID ); if( handle == g_GameLumps.InvalidGameLump() ) { return; } if (g_GameLumps.GetGameLumpVersion(handle) != lumpVersion) return; // Unserialize CUtlBuffer buf( g_GameLumps.GetGameLump(handle), g_GameLumps.GameLumpSize( handle ), CUtlBuffer::READ_ONLY ); int count = buf.GetInt(); if( !count ) { return; } lumpData.SetCount( count ); int lightsize = lumpData.Count() * sizeof(DetailPropLightstylesLump_t); buf.Get( lumpData.Base(), lightsize ); } DetailObjectLump_t *g_pMPIDetailProps = NULL; void VMPI_ProcessDetailPropWU( int iThread, int iWorkUnit, MessageBuffer *pBuf ) { CUtlVector *pDetailPropLump = s_pDetailPropLightStyleLump; DetailObjectLump_t& prop = g_pMPIDetailProps[iWorkUnit]; ComputeLighting( prop, iThread ); // Send the results back... pBuf->write( &prop.m_Lighting, sizeof( prop.m_Lighting ) ); pBuf->write( &prop.m_LightStyleCount, sizeof( prop.m_LightStyleCount ) ); pBuf->write( &prop.m_LightStyles, sizeof( prop.m_LightStyles ) ); for ( int i=0; i < prop.m_LightStyleCount; i++ ) { DetailPropLightstylesLump_t *l = &pDetailPropLump->Element( i + prop.m_LightStyles ); pBuf->write( &l->m_Lighting, sizeof( l->m_Lighting ) ); pBuf->write( &l->m_Style, sizeof( l->m_Style ) ); } } void VMPI_ReceiveDetailPropWU( int iWorkUnit, MessageBuffer *pBuf, int iWorker ) { CUtlVector *pDetailPropLump = s_pDetailPropLightStyleLump; DetailObjectLump_t& prop = g_pMPIDetailProps[iWorkUnit]; pBuf->read( &prop.m_Lighting, sizeof( prop.m_Lighting ) ); pBuf->read( &prop.m_LightStyleCount, sizeof( prop.m_LightStyleCount ) ); pBuf->read( &prop.m_LightStyles, sizeof( prop.m_LightStyles ) ); pDetailPropLump->EnsureCount( prop.m_LightStyles + prop.m_LightStyleCount ); for ( int i=0; i < prop.m_LightStyleCount; i++ ) { DetailPropLightstylesLump_t *l = &pDetailPropLump->Element( i + prop.m_LightStyles ); pBuf->read( &l->m_Lighting, sizeof( l->m_Lighting ) ); pBuf->read( &l->m_Style, sizeof( l->m_Style ) ); } } //----------------------------------------------------------------------------- // Computes lighting for the detail props //----------------------------------------------------------------------------- void ComputeDetailPropLighting( int iThread ) { // illuminate them all DetailObjectLump_t* pProps; int count = UnserializeDetailProps( pProps ); if (!count) return; // unserialize the lump that we aren't computing. if( g_bHDR ) { UnserializeDetailPropLighting( GAMELUMP_DETAIL_PROP_LIGHTING, GAMELUMP_DETAIL_PROP_LIGHTING_VERSION, s_DetailPropLightStyleLumpLDR ); } else { UnserializeDetailPropLighting( GAMELUMP_DETAIL_PROP_LIGHTING_HDR, GAMELUMP_DETAIL_PROP_LIGHTING_HDR_VERSION, s_DetailPropLightStyleLumpHDR ); } StartPacifier("Computing detail prop lighting : "); for (int i = 0; i < count; ++i) { UpdatePacifier( (float)i / (float)count ); ComputeLighting( pProps[i], iThread ); } // Write detail prop lightstyle lump... WriteDetailLightingLumps(); EndPacifier( true ); }