You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
828 lines
25 KiB
828 lines
25 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
|
|
|
|
#include "render_pch.h" |
|
#include "client.h" |
|
#include "bitmap/imageformat.h" |
|
#include "bitmap/tgawriter.h" |
|
#include <float.h> |
|
#include "collisionutils.h" |
|
#include "cl_main.h" |
|
#include "tier0/vprof.h" |
|
#include "debugoverlay.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
// Forward declarations |
|
//----------------------------------------------------------------------------- |
|
extern ConVar r_avglight; |
|
extern int r_surfacevisframe; |
|
|
|
static model_t* s_pLightVecModel = 0; |
|
|
|
ConVar r_visualizetraces( "r_visualizetraces", "0", FCVAR_CHEAT ); |
|
ConVar r_visualizelighttraces( "r_visualizelighttraces", "0", FCVAR_CHEAT ); |
|
ConVar r_visualizelighttracesshowfulltrace( "r_visualizelighttracesshowfulltrace", "0", FCVAR_CHEAT ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// State associated with R_LightVec |
|
//----------------------------------------------------------------------------- |
|
struct LightVecState_t |
|
{ |
|
LightVecState_t() = default; |
|
Ray_t m_Ray; |
|
float m_HitFrac; |
|
float* m_pTextureS; |
|
float* m_pTextureT; |
|
float* m_pLightmapS; |
|
float* m_pLightmapT; |
|
SurfaceHandle_t m_nSkySurfID; |
|
bool m_bUseLightStyles; |
|
CUtlVector<IDispInfo *> m_LightTestDisps; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Globals associated with dynamic lighting |
|
//----------------------------------------------------------------------------- |
|
int r_dlightchanged; |
|
int r_dlightactive; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Displacements to test against for R_LightVec |
|
//----------------------------------------------------------------------------- |
|
|
|
|
|
/* |
|
================== |
|
R_AnimateLight |
|
================== |
|
*/ |
|
void R_AnimateLight (void) |
|
{ |
|
INetworkStringTable *table = cl.m_pLightStyleTable; |
|
|
|
if ( !table ) |
|
return; |
|
|
|
// light animations |
|
// 'm' is normal light, 'a' is no light, 'z' is double bright |
|
int i = (int)(cl.GetTime()*10); |
|
|
|
for (int j=0 ; j<MAX_LIGHTSTYLES ; j++) |
|
{ |
|
int length; |
|
const char * lightstyle = (const char*) table->GetStringUserData( j, &length ); |
|
length--; |
|
|
|
if (!lightstyle || !lightstyle[0]) |
|
{ |
|
d_lightstylevalue[j] = 256; |
|
d_lightstylenumframes[j] = 0; |
|
continue; |
|
} |
|
d_lightstylenumframes[j] = length; |
|
int k = i % length; |
|
k = lightstyle[k] - 'a'; |
|
k = k*22; |
|
if (d_lightstylevalue[j] != k) |
|
{ |
|
d_lightstylevalue[j] = k; |
|
d_lightstyleframe[j] = r_framecount; |
|
} |
|
} |
|
} |
|
|
|
/* |
|
============================================================================= |
|
|
|
DYNAMIC LIGHTS |
|
|
|
============================================================================= |
|
*/ |
|
|
|
// Returns true if the surface has the specified dlight already set on it for this frame. |
|
inline bool R_IsDLightAlreadyMarked( msurfacelighting_t *pLighting, int bit ) |
|
{ |
|
return (pLighting->m_nDLightFrame == r_framecount) && (pLighting->m_fDLightBits & bit); |
|
} |
|
|
|
// Mark the surface as changed by the specified dlight (so its texture gets updated when |
|
// it comes time to render). |
|
inline void R_MarkSurfaceDLight( SurfaceHandle_t surfID, msurfacelighting_t *pLighting, int bit) |
|
{ |
|
pLighting->m_nDLightFrame = r_framecount; |
|
pLighting->m_fDLightBits |= bit; |
|
MSurf_Flags( surfID ) |= SURFDRAW_HASDLIGHT; |
|
} |
|
|
|
int R_TryLightMarkSurface( dlight_t *light, msurfacelighting_t *pLighting, SurfaceHandle_t surfID, int bit ) |
|
{ |
|
// Make sure this light actually intersects the surface cache of the surfaces it hits |
|
mtexinfo_t *tex; |
|
|
|
// FIXME: No worky for brush models |
|
|
|
// Find the perpendicular distance to the surface we're lighting |
|
// NOTE: Allow some stuff that's slightly behind it because view models can get behind walls |
|
// FIXME: We should figure out a better way to deal with view models |
|
float perpDistSq = DotProduct (light->origin, MSurf_Plane( surfID ).normal) - MSurf_Plane( surfID ).dist; |
|
if (perpDistSq < DLIGHT_BEHIND_PLANE_DIST) |
|
return 0; |
|
|
|
perpDistSq *= perpDistSq; |
|
|
|
float flInPlaneRadiusSq = light->GetRadiusSquared() - perpDistSq; |
|
if (flInPlaneRadiusSq <= 0) |
|
return 0; |
|
|
|
tex = MSurf_TexInfo( surfID ); |
|
|
|
Vector2D mins, maxs; |
|
mins.Init( pLighting->m_LightmapMins[0], pLighting->m_LightmapMins[1] ); |
|
maxs.Init( mins.x + pLighting->m_LightmapExtents[0], mins.y + pLighting->m_LightmapExtents[1] ); |
|
|
|
// Project light center into texture coordinates |
|
Vector2D vecCircleCenter; |
|
vecCircleCenter.x = DotProduct (light->origin, tex->lightmapVecsLuxelsPerWorldUnits[0].AsVector3D()) + |
|
tex->lightmapVecsLuxelsPerWorldUnits[0][3]; |
|
vecCircleCenter.y = DotProduct (light->origin, tex->lightmapVecsLuxelsPerWorldUnits[1].AsVector3D()) + |
|
tex->lightmapVecsLuxelsPerWorldUnits[1][3]; |
|
|
|
// convert from world space to luxel space and convert to int |
|
float flInPlaneLuxelRadius = sqrtf( flInPlaneRadiusSq * tex->luxelsPerWorldUnit * tex->luxelsPerWorldUnit ); |
|
|
|
// Does the circle intersect the square? |
|
if ( !IsCircleIntersectingRectangle( mins, maxs, vecCircleCenter, flInPlaneLuxelRadius ) ) |
|
return 0; |
|
|
|
// Ok, mark the surface as using this light. |
|
R_MarkSurfaceDLight( surfID, pLighting, bit); |
|
return 1; |
|
} |
|
|
|
int R_MarkLightsLeaf( dlight_t *light, int bit, mleaf_t *pLeaf ) |
|
{ |
|
int countMarked = 0; |
|
for ( int i = 0; i < pLeaf->dispCount; i++ ) |
|
{ |
|
IDispInfo *pDispInfo = MLeaf_Disaplcement( pLeaf, i ); |
|
|
|
SurfaceHandle_t parentSurfID = pDispInfo->GetParent(); |
|
if ( parentSurfID ) |
|
{ |
|
// Don't redo all this work if we already hit this surface and decided it's lit by this light. |
|
msurfacelighting_t *pLighting = SurfaceLighting( parentSurfID ); |
|
if( !R_IsDLightAlreadyMarked( pLighting, bit) ) |
|
{ |
|
// Do a different test for displacement surfaces. |
|
Vector bmin, bmax; |
|
MSurf_DispInfo( parentSurfID )->GetBoundingBox( bmin, bmax ); |
|
if ( IsBoxIntersectingSphere(bmin, bmax, light->origin, light->GetRadius()) ) |
|
{ |
|
R_MarkSurfaceDLight( parentSurfID, pLighting, bit ); |
|
countMarked++; |
|
} |
|
} |
|
} |
|
} |
|
|
|
SurfaceHandle_t *pHandle = &host_state.worldbrush->marksurfaces[pLeaf->firstmarksurface]; |
|
for ( int i = 0; i < pLeaf->nummarksurfaces; i++ ) |
|
{ |
|
SurfaceHandle_t surfID = pHandle[i]; |
|
ASSERT_SURF_VALID( surfID ); |
|
|
|
// only process leaf surfaces |
|
if ( MSurf_Flags( surfID ) & SURFDRAW_NODE ) |
|
continue; |
|
|
|
// Don't redo all this work if we already hit this surface and decided it's lit by this light. |
|
msurfacelighting_t *pLighting = SurfaceLighting( surfID ); |
|
if(R_IsDLightAlreadyMarked(pLighting, bit)) |
|
continue; |
|
|
|
float dist = DotProduct( light->origin, MSurf_Plane( surfID ).normal) - MSurf_Plane( surfID ).dist; |
|
|
|
if ( dist > light->GetRadius() || dist < -light->GetRadius() ) |
|
continue; |
|
|
|
countMarked += R_TryLightMarkSurface( light, pLighting, surfID, bit ); |
|
} |
|
return countMarked; |
|
} |
|
|
|
|
|
/* |
|
============= |
|
R_MarkLights |
|
============= |
|
*/ |
|
int R_MarkLights (dlight_t *light, int bit, mnode_t *node) |
|
{ |
|
cplane_t *splitplane; |
|
float dist; |
|
int i; |
|
|
|
if (node->contents >= 0) |
|
{ |
|
// This is a leaf, so check displacement surfaces and leaf faces |
|
return R_MarkLightsLeaf( light, bit, (mleaf_t*)node ); |
|
} |
|
|
|
splitplane = node->plane; |
|
dist = DotProduct (light->origin, splitplane->normal) - splitplane->dist; |
|
|
|
if (dist > light->GetRadius()) |
|
{ |
|
return R_MarkLights (light, bit, node->children[0]); |
|
} |
|
if (dist < -light->GetRadius()) |
|
{ |
|
return R_MarkLights (light, bit, node->children[1]); |
|
} |
|
|
|
// mark the polygons |
|
int countMarked = 0; |
|
SurfaceHandle_t surfID = SurfaceHandleFromIndex( node->firstsurface ); |
|
for (i=0 ; i<node->numsurfaces ; i++, surfID++) |
|
{ |
|
// Don't redo all this work if we already hit this surface and decided it's lit by this light. |
|
msurfacelighting_t *pLighting = SurfaceLighting( surfID ); |
|
if(R_IsDLightAlreadyMarked( pLighting, bit)) |
|
continue; |
|
|
|
countMarked += R_TryLightMarkSurface( light, pLighting, surfID, bit ); |
|
} |
|
|
|
countMarked += R_MarkLights( light, bit, node->children[0] ); |
|
return countMarked + R_MarkLights( light, bit, node->children[1] ); |
|
} |
|
|
|
|
|
void R_MarkDLightsOnSurface( mnode_t* pNode ) |
|
{ |
|
if (!pNode || !g_bActiveDlights) |
|
return; |
|
|
|
dlight_t *l = cl_dlights; |
|
for (int i=0 ; i<MAX_DLIGHTS ; i++, l++) |
|
{ |
|
if (l->die < cl.GetTime() || !l->IsRadiusGreaterThanZero() ) |
|
continue; |
|
if (l->flags & DLIGHT_NO_WORLD_ILLUMINATION) |
|
continue; |
|
|
|
R_MarkLights ( l, 1<<i, pNode ); |
|
} |
|
} |
|
|
|
/* |
|
============= |
|
R_PushDlights |
|
============= |
|
*/ |
|
void R_PushDlights (void) |
|
{ |
|
R_MarkDLightsOnSurface( host_state.worldbrush->nodes ); |
|
MarkDLightsOnStaticProps(); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes s and t coords of texture at intersection pt |
|
//----------------------------------------------------------------------------- |
|
|
|
static void ComputeTextureCoordsAtIntersection( mtexinfo_t* pTex, Vector const& pt, float *textureS, float *textureT ) |
|
{ |
|
if( pTex->material && textureS && textureT ) |
|
{ |
|
*textureS = DotProduct( pt, pTex->textureVecsTexelsPerWorldUnits[0].AsVector3D() ) + |
|
pTex->textureVecsTexelsPerWorldUnits[0][3]; |
|
*textureT = DotProduct( pt, pTex->textureVecsTexelsPerWorldUnits[1].AsVector3D() ) + |
|
pTex->textureVecsTexelsPerWorldUnits[1][3]; |
|
|
|
*textureS /= pTex->material->GetMappingWidth(); |
|
*textureT /= pTex->material->GetMappingHeight(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes s and t coords of texture at intersection pt |
|
//----------------------------------------------------------------------------- |
|
static void ComputeLightmapCoordsAtIntersection( msurfacelighting_t *pLighting, float ds, |
|
float dt, float *lightmapS, float *lightmapT ) |
|
{ |
|
if( lightmapS && lightmapT ) |
|
{ |
|
if( pLighting->m_LightmapExtents[0] != 0 ) |
|
*lightmapS = (ds + 0.5f) / ( float )pLighting->m_LightmapExtents[0]; |
|
else |
|
*lightmapS = 0.5f; |
|
|
|
if( pLighting->m_LightmapExtents[1] != 0 ) |
|
*lightmapT = (dt + 0.5f) / ( float )pLighting->m_LightmapExtents[1]; |
|
else |
|
*lightmapT = 0.5f; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the lightmap color at a particular point |
|
//----------------------------------------------------------------------------- |
|
static void ComputeLightmapColor( SurfaceHandle_t surfID, int ds, int dt, bool bUseLightStyles, Vector& c ) |
|
{ |
|
msurfacelighting_t *pLighting = SurfaceLighting( surfID ); |
|
|
|
ColorRGBExp32* pLightmap = pLighting->m_pSamples; |
|
if( !pLightmap ) |
|
{ |
|
static int messagecount = 0; |
|
if ( ++messagecount < 10 ) |
|
{ |
|
// Stop spamming. I heard you already!!! |
|
ConMsg( "hit surface has no samples\n" ); |
|
} |
|
return; |
|
} |
|
|
|
int smax = ( pLighting->m_LightmapExtents[0] ) + 1; |
|
int tmax = ( pLighting->m_LightmapExtents[1] ) + 1; |
|
int offset = smax * tmax; |
|
if ( SurfHasBumpedLightmaps( surfID ) ) |
|
{ |
|
offset *= ( NUM_BUMP_VECTS + 1 ); |
|
} |
|
|
|
pLightmap += dt * smax + ds; |
|
int nMaxMaps = bUseLightStyles ? MAXLIGHTMAPS : 1; |
|
for (int maps = 0 ; maps < nMaxMaps && pLighting->m_nStyles[maps] != 255 ; ++maps) |
|
{ |
|
float scale = LightStyleValue( pLighting->m_nStyles[maps] ); |
|
|
|
c[0] += TexLightToLinear( pLightmap->r, pLightmap->exponent ) * scale; |
|
c[1] += TexLightToLinear( pLightmap->g, pLightmap->exponent ) * scale; |
|
c[2] += TexLightToLinear( pLightmap->b, pLightmap->exponent ) * scale; |
|
|
|
// Check version 32 in source safe for some debugging crap |
|
pLightmap += offset; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the lightmap color at a particular point |
|
//----------------------------------------------------------------------------- |
|
static void ComputeLightmapColorFromAverage( msurfacelighting_t *pLighting, bool bUseLightStyles, Vector& c ) |
|
{ |
|
int nMaxMaps = bUseLightStyles ? MAXLIGHTMAPS : 1; |
|
for (int maps = 0 ; maps < nMaxMaps && pLighting->m_nStyles[maps] != 255 ; ++maps) |
|
{ |
|
float scale = LightStyleValue( pLighting->m_nStyles[maps] ); |
|
|
|
ColorRGBExp32* pAvgColor = pLighting->AvgLightColor(maps); |
|
c[0] += TexLightToLinear( pAvgColor->r, pAvgColor->exponent ) * scale; |
|
c[1] += TexLightToLinear( pAvgColor->g, pAvgColor->exponent ) * scale; |
|
c[2] += TexLightToLinear( pAvgColor->b, pAvgColor->exponent ) * scale; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Tests a particular surface |
|
//----------------------------------------------------------------------------- |
|
static bool FASTCALL FindIntersectionAtSurface( SurfaceHandle_t surfID, float f, |
|
Vector& c, LightVecState_t& state ) |
|
{ |
|
// no lightmaps on this surface? punt... |
|
// FIXME: should be water surface? |
|
if (MSurf_Flags( surfID ) & SURFDRAW_NOLIGHT) |
|
return false; |
|
|
|
// Compute the actual point |
|
Vector pt; |
|
VectorMA( state.m_Ray.m_Start, f, state.m_Ray.m_Delta, pt ); |
|
|
|
mtexinfo_t* pTex = MSurf_TexInfo( surfID ); |
|
|
|
// See where in lightmap space our intersection point is |
|
float s, t; |
|
s = DotProduct (pt, pTex->lightmapVecsLuxelsPerWorldUnits[0].AsVector3D()) + |
|
pTex->lightmapVecsLuxelsPerWorldUnits[0][3]; |
|
t = DotProduct (pt, pTex->lightmapVecsLuxelsPerWorldUnits[1].AsVector3D()) + |
|
pTex->lightmapVecsLuxelsPerWorldUnits[1][3]; |
|
|
|
// Not in the bounds of our lightmap? punt... |
|
msurfacelighting_t *pLighting = SurfaceLighting( surfID ); |
|
if( s < pLighting->m_LightmapMins[0] || |
|
t < pLighting->m_LightmapMins[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 - pLighting->m_LightmapMins[0]; |
|
float dt = t - pLighting->m_LightmapMins[1]; |
|
if ( !pLighting->m_LightmapExtents[0] && !pLighting->m_LightmapExtents[1] ) |
|
{ |
|
worldbrushdata_t *pBrushData = host_state.worldbrush; |
|
|
|
// |
|
float lightMaxs[2]; |
|
lightMaxs[ 0 ] = pLighting->m_LightmapMins[0]; |
|
lightMaxs[ 1 ] = pLighting->m_LightmapMins[1]; |
|
int i; |
|
for (i=0 ; i<MSurf_VertCount( surfID ); i++) |
|
{ |
|
int e = pBrushData->vertindices[MSurf_FirstVertIndex( surfID )+i]; |
|
mvertex_t *v = &pBrushData->vertexes[e]; |
|
|
|
int j; |
|
for ( j=0 ; j<2 ; j++) |
|
{ |
|
float sextent, textent; |
|
sextent = DotProduct (v->position, pTex->lightmapVecsLuxelsPerWorldUnits[0].AsVector3D()) + |
|
pTex->lightmapVecsLuxelsPerWorldUnits[0][3] - pLighting->m_LightmapMins[0]; |
|
textent = DotProduct (v->position, pTex->lightmapVecsLuxelsPerWorldUnits[1].AsVector3D()) + |
|
pTex->lightmapVecsLuxelsPerWorldUnits[1][3] - pLighting->m_LightmapMins[1]; |
|
|
|
if ( sextent > lightMaxs[ 0 ] ) |
|
{ |
|
lightMaxs[ 0 ] = sextent; |
|
} |
|
if ( textent > lightMaxs[ 1 ] ) |
|
{ |
|
lightMaxs[ 1 ] = textent; |
|
} |
|
} |
|
} |
|
if( ds > lightMaxs[0] || dt > lightMaxs[1] ) |
|
return false; |
|
} |
|
else |
|
{ |
|
if( ds > pLighting->m_LightmapExtents[0] || dt > pLighting->m_LightmapExtents[1] ) |
|
return false; |
|
} |
|
|
|
// Store off the hit distance... |
|
state.m_HitFrac = f; |
|
|
|
// You heard the man! |
|
ComputeTextureCoordsAtIntersection( pTex, pt, state.m_pTextureS, state.m_pTextureT ); |
|
|
|
#ifdef USE_CONVARS |
|
if ( r_avglight.GetInt() ) |
|
#else |
|
if ( 1 ) |
|
#endif |
|
{ |
|
// This is the faster path; it looks slightly different though |
|
ComputeLightmapColorFromAverage( pLighting, state.m_bUseLightStyles, c ); |
|
} |
|
else |
|
{ |
|
// Compute lightmap coords |
|
ComputeLightmapCoordsAtIntersection( pLighting, ds, dt, state.m_pLightmapS, state.m_pLightmapT ); |
|
|
|
// Check out the value of the lightmap at the intersection point |
|
ComputeLightmapColor( surfID, (int)ds, (int)dt, state.m_bUseLightStyles, c ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Tests a particular node |
|
//----------------------------------------------------------------------------- |
|
|
|
// returns a surfID |
|
static SurfaceHandle_t FindIntersectionSurfaceAtNode( mnode_t *node, float t, |
|
Vector& c, LightVecState_t& state ) |
|
{ |
|
SurfaceHandle_t surfID = SurfaceHandleFromIndex( node->firstsurface ); |
|
for (int i=0 ; i<node->numsurfaces ; ++i, ++surfID) |
|
{ |
|
// Don't immediately return when we hit sky; |
|
// we may actually hit another surface |
|
if (MSurf_Flags( surfID ) & SURFDRAW_SKY) |
|
{ |
|
state.m_nSkySurfID = surfID; |
|
continue; |
|
} |
|
|
|
// Don't let water surfaces affect us |
|
if (MSurf_Flags( surfID ) & SURFDRAW_WATERSURFACE) |
|
continue; |
|
|
|
// Check this surface to see if there's an intersection |
|
if (FindIntersectionAtSurface( surfID, t, c, state )) |
|
{ |
|
return surfID; |
|
} |
|
} |
|
|
|
return SURFACE_HANDLE_INVALID; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Tests a ray against displacements |
|
//----------------------------------------------------------------------------- |
|
|
|
// returns surfID |
|
static SurfaceHandle_t R_LightVecDisplacementChain( LightVecState_t& state, bool bUseLightStyles, Vector& c ) |
|
{ |
|
// test the ray against displacements |
|
SurfaceHandle_t surfID = SURFACE_HANDLE_INVALID; |
|
|
|
for ( int i = 0; i < state.m_LightTestDisps.Count(); i++ ) |
|
{ |
|
|
|
float dist; |
|
Vector2D luv, tuv; |
|
IDispInfo *pDispInfo = state.m_LightTestDisps[i]; |
|
if (pDispInfo->TestRay( state.m_Ray, 0.0f, state.m_HitFrac, dist, &luv, &tuv )) |
|
{ |
|
// It hit it, and at a point closer than the previously computed |
|
// nearest intersection point |
|
state.m_HitFrac = dist; |
|
surfID = pDispInfo->GetParent(); |
|
ComputeLightmapColor( surfID, (int)luv.x, (int)luv.y, bUseLightStyles, c ); |
|
|
|
if (state.m_pLightmapS && state.m_pLightmapT) |
|
{ |
|
ComputeLightmapCoordsAtIntersection( SurfaceLighting(surfID), (int)luv.x, (int)luv.y, state.m_pLightmapS, state.m_pLightmapT ); |
|
} |
|
|
|
if (state.m_pTextureS && state.m_pTextureT) |
|
{ |
|
*state.m_pTextureS = tuv.x; |
|
*state.m_pTextureT = tuv.y; |
|
} |
|
} |
|
} |
|
|
|
return surfID; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Adds displacements in a leaf to a list to be tested against |
|
//----------------------------------------------------------------------------- |
|
|
|
static void AddDisplacementsInLeafToTestList( mleaf_t* pLeaf, LightVecState_t& state ) |
|
{ |
|
// add displacement surfaces |
|
for ( int i = 0; i < pLeaf->dispCount; i++ ) |
|
{ |
|
// NOTE: We're not using the displacement's touched method here |
|
// because we're just using the parent surface's visframe in the |
|
// surface add methods below |
|
IDispInfo *pDispInfo = MLeaf_Disaplcement( pLeaf, i ); |
|
SurfaceHandle_t parentSurfID = pDispInfo->GetParent(); |
|
|
|
// already processed this frame? Then don't do it again! |
|
if (MSurf_VisFrame( parentSurfID ) != r_surfacevisframe) |
|
{ |
|
MSurf_VisFrame( parentSurfID ) = r_surfacevisframe; |
|
state.m_LightTestDisps.AddToTail( pDispInfo ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Tests a particular leaf |
|
//----------------------------------------------------------------------------- |
|
|
|
// returns surfID |
|
static SurfaceHandle_t FASTCALL FindIntersectionSurfaceAtLeaf( mleaf_t *pLeaf, |
|
float start, float end, Vector& c, LightVecState_t& state ) |
|
{ |
|
Vector pt; |
|
SurfaceHandle_t closestSurfID = SURFACE_HANDLE_INVALID; |
|
|
|
// Adds displacements in the leaf to a list of displacements to test at the end |
|
AddDisplacementsInLeafToTestList( pLeaf, state ); |
|
|
|
// Add non-displacement surfaces |
|
// Since there's no BSP tree here, we gotta test *all* surfaces! (blech) |
|
SurfaceHandle_t *pHandle = &host_state.worldbrush->marksurfaces[pLeaf->firstmarksurface]; |
|
// NOTE: Skip all marknodesurfaces, only check detail/leaf faces |
|
for ( int i = pLeaf->nummarknodesurfaces; i < pLeaf->nummarksurfaces; i++ ) |
|
{ |
|
SurfaceHandle_t surfID = pHandle[i]; |
|
ASSERT_SURF_VALID( surfID ); |
|
|
|
// Don't add surfaces that have displacement; they are handled above |
|
// In fact, don't even set the vis frame; we need it unset for translucent |
|
// displacement code |
|
if ( SurfaceHasDispInfo(surfID) ) |
|
continue; |
|
Assert(!(MSurf_Flags( surfID ) & SURFDRAW_NODE)); |
|
|
|
if ( MSurf_Flags( surfID ) & (SURFDRAW_NODE|SURFDRAW_NODRAW | SURFDRAW_WATERSURFACE) ) |
|
continue; |
|
|
|
cplane_t* pPlane = &MSurf_Plane( surfID ); |
|
|
|
// Backface cull... |
|
if (DotProduct( pPlane->normal, state.m_Ray.m_Delta ) > 0.f) |
|
continue; |
|
|
|
float startDotN = DotProduct( state.m_Ray.m_Start, pPlane->normal ); |
|
float deltaDotN = DotProduct( state.m_Ray.m_Delta, pPlane->normal ); |
|
|
|
float front = startDotN + start * deltaDotN - pPlane->dist; |
|
float back = startDotN + end * deltaDotN - pPlane->dist; |
|
|
|
int side = front < 0.f; |
|
|
|
// Blow it off if it doesn't split the plane... |
|
if ( (back < 0.f) == side ) |
|
continue; |
|
|
|
// Don't test a surface that is farther away from the closest found intersection |
|
float frac = front / (front-back); |
|
if (frac >= state.m_HitFrac) |
|
continue; |
|
|
|
float mid = start * (1.0f - frac) + end * frac; |
|
|
|
// Check this surface to see if there's an intersection |
|
if (FindIntersectionAtSurface( surfID, mid, c, state )) |
|
{ |
|
closestSurfID = surfID; |
|
} |
|
} |
|
|
|
// Return the closest surface hit |
|
return closestSurfID; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// LIGHT SAMPLING |
|
//----------------------------------------------------------------------------- |
|
|
|
// returns surfID |
|
SurfaceHandle_t RecursiveLightPoint (mnode_t *node, float start, float end, |
|
Vector& c, LightVecState_t& state ) |
|
{ |
|
// didn't hit anything |
|
if (node->contents >= 0) |
|
{ |
|
// FIXME: Should we always do this? It could get expensive... |
|
// Check all the faces at the leaves |
|
return FindIntersectionSurfaceAtLeaf( (mleaf_t*)node, start, end, c, state ); |
|
} |
|
|
|
// Determine which side of the node plane our points are on |
|
// FIXME: optimize for axial |
|
cplane_t* plane = node->plane; |
|
|
|
float startDotN = DotProduct( state.m_Ray.m_Start, plane->normal ); |
|
float deltaDotN = DotProduct( state.m_Ray.m_Delta, plane->normal ); |
|
|
|
float front = startDotN + start * deltaDotN - plane->dist; |
|
float back = startDotN + end * deltaDotN - plane->dist; |
|
int side = front < 0; |
|
|
|
// If they're both on the same side of the plane, don't bother to split |
|
// just check the appropriate child |
|
SurfaceHandle_t surfID; |
|
if ( (back < 0) == side ) |
|
{ |
|
surfID = RecursiveLightPoint (node->children[side], start, end, c, state); |
|
return surfID; |
|
} |
|
|
|
// calculate mid point |
|
float frac = front / (front-back); |
|
float mid = start * (1.0f - frac) + end * frac; |
|
|
|
// go down front side |
|
surfID = RecursiveLightPoint (node->children[side], start, mid, c, state ); |
|
if ( IS_SURF_VALID( surfID ) ) |
|
return surfID; // hit something |
|
|
|
// check for impact on this node |
|
surfID = FindIntersectionSurfaceAtNode( node, mid, c, state ); |
|
if ( IS_SURF_VALID( surfID ) ) |
|
return surfID; |
|
|
|
// go down back side |
|
surfID = RecursiveLightPoint (node->children[!side], mid, end, c, state ); |
|
return surfID; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Allows us to use a different model for R_LightVec |
|
//----------------------------------------------------------------------------- |
|
void R_LightVecUseModel( model_t* pModel ) |
|
{ |
|
s_pLightVecModel = pModel; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// returns light in range from 0 to 1. |
|
// lightmapS/T is in [0,1] within the space of the surface. |
|
// returns surfID |
|
//----------------------------------------------------------------------------- |
|
SurfaceHandle_t R_LightVec (const Vector& start, const Vector& end, bool bUseLightStyles, Vector& c, |
|
float *textureS, float *textureT, float *lightmapS, float *lightmapT ) |
|
{ |
|
VPROF_INCREMENT_COUNTER( "R_LightVec", 1 ); |
|
|
|
SurfaceHandle_t retSurfID; |
|
SurfaceHandle_t dispSurfID; |
|
|
|
// We're using the vis frame here for lightvec tests |
|
// to make sure we test each displacement only once |
|
++r_surfacevisframe; |
|
|
|
LightVecState_t state; |
|
state.m_HitFrac = 1.0f; |
|
state.m_Ray.Init( start, end ); |
|
state.m_pTextureS = textureS; |
|
state.m_pTextureT = textureT; |
|
state.m_pLightmapS = lightmapS; |
|
state.m_pLightmapT = lightmapT; |
|
state.m_nSkySurfID = SURFACE_HANDLE_INVALID; |
|
state.m_bUseLightStyles = bUseLightStyles; |
|
|
|
c[0] = c[1] = c[2] = 0.0f; |
|
|
|
model_t* model = s_pLightVecModel ? s_pLightVecModel : host_state.worldmodel; |
|
retSurfID = RecursiveLightPoint(&model->brush.pShared->nodes[model->brush.firstnode], |
|
0.0f, 1.0f, c, state ); |
|
|
|
// While doing recursive light point, we built a list of all |
|
// displacement surfaces which we need to test, so let's test them |
|
dispSurfID = R_LightVecDisplacementChain( state, bUseLightStyles, c ); |
|
|
|
if( r_visualizelighttraces.GetBool() ) |
|
{ |
|
if( r_visualizelighttracesshowfulltrace.GetBool() ) |
|
{ |
|
CDebugOverlay::AddLineOverlay( start, end, 0, 255, 0, 255, true, -1.0f ); |
|
} |
|
else |
|
{ |
|
CDebugOverlay::AddLineOverlay( start, start + ( end - start ) * state.m_HitFrac, 0, 255, 0, 255, true, -1.0f ); |
|
} |
|
} |
|
|
|
if ( IS_SURF_VALID( dispSurfID ) ) |
|
retSurfID = dispSurfID; |
|
|
|
// ConMsg( "R_LightVec: %f %f %f\n", c[0], c[1], c[2] ); |
|
|
|
// If we didn't hit anything else, but we hit a sky surface at |
|
// some point along the ray cast, return the sky id. |
|
if ( ( retSurfID == SURFACE_HANDLE_INVALID ) && ( state.m_nSkySurfID != SURFACE_HANDLE_INVALID ) ) |
|
return state.m_nSkySurfID; |
|
|
|
return retSurfID; |
|
} |
|
|
|
// returns light in range from 0 to 1. |
|
colorVec R_LightPoint (Vector& p) |
|
{ |
|
SurfaceHandle_t surfID; |
|
Vector end; |
|
colorVec c; |
|
Vector color; |
|
|
|
end[0] = p[0]; |
|
end[1] = p[1]; |
|
end[2] = p[2] - 2048; |
|
|
|
surfID = R_LightVec( p, end, true, color ); |
|
|
|
if( IS_SURF_VALID( surfID ) ) |
|
{ |
|
c.r = LinearToScreenGamma( color[0] ) * 255; |
|
c.g = LinearToScreenGamma( color[1] ) * 255; |
|
c.b = LinearToScreenGamma( color[2] ) * 255; |
|
c.a = 1; |
|
} |
|
else |
|
{ |
|
c.r = c.g = c.b = c.a = 0; |
|
} |
|
return c; |
|
}
|
|
|