mirror of
https://github.com/nillerusr/source-engine.git
synced 2024-12-22 22:27:05 +00:00
1037 lines
31 KiB
C++
1037 lines
31 KiB
C++
//========= Copyright 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 );
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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<DetailPropLightstylesLump_t> s_DetailPropLightStyleLumpLDR;
|
|
static CUtlVector<DetailPropLightstylesLump_t> s_DetailPropLightStyleLumpHDR;
|
|
static CUtlVector<DetailPropLightstylesLump_t> *s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpLDR;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// An amount to add to each model to get to the model center
|
|
//-----------------------------------------------------------------------------
|
|
CUtlVector<Vector> g_ModelCenterOffset;
|
|
CUtlVector<Vector> 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 );
|
|
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, int 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, int 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 hemispherical rays from a vertex, accumulating indirect
|
|
// sources at each ray termination.
|
|
//-----------------------------------------------------------------------------
|
|
void ComputeIndirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor,
|
|
int iThread, bool force_fast, bool bIgnoreNormals )
|
|
{
|
|
Ray_t ray;
|
|
CLightSurface surfEnum(iThread);
|
|
|
|
outColor.Init();
|
|
|
|
|
|
int nSamples = NUMVERTEXNORMALS;
|
|
if ( do_fast || force_fast )
|
|
nSamples /= 4;
|
|
else
|
|
nSamples *= g_flSkySampleScale;
|
|
|
|
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 to determine surface
|
|
Vector vEnd;
|
|
VectorScale( samplingNormal, MAX_TRACE_LENGTH, vEnd );
|
|
VectorAdd( position, vEnd, vEnd );
|
|
|
|
ray.Init( position, vEnd, vec3_origin, vec3_origin );
|
|
if ( !surfEnum.FindIntersection( ray ) )
|
|
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 );
|
|
}
|
|
|
|
float invLengthSqr = 1.0f / (1.0f + ((vEnd - position) * surfEnum.m_HitFrac / 128.0).LengthSqr());
|
|
// Include falloff using invsqrlaw.
|
|
VectorMultiply( lightmapColor, invLengthSqr * dtexdata[pTex->texdata].reflectivity, lightmapColor );
|
|
VectorAdd( outColor, lightmapColor, outColor );
|
|
}
|
|
|
|
if ( totalDot )
|
|
{
|
|
VectorScale( outColor, 1.0f/totalDot, outColor );
|
|
}
|
|
}
|
|
|
|
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->Size();
|
|
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<DetailPropLightstylesLump_t> &lumpData )
|
|
{
|
|
GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(lumpID);
|
|
if (handle != g_GameLumps.InvalidGameLump())
|
|
g_GameLumps.DestroyGameLump(handle);
|
|
int lightsize = lumpData.Size() * 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.Size() );
|
|
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<DetailPropLightstylesLump_t> &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.Size() * sizeof(DetailPropLightstylesLump_t);
|
|
buf.Get( lumpData.Base(), lightsize );
|
|
}
|
|
|
|
DetailObjectLump_t *g_pMPIDetailProps = NULL;
|
|
|
|
void VMPI_ProcessDetailPropWU( int iThread, int iWorkUnit, MessageBuffer *pBuf )
|
|
{
|
|
CUtlVector<DetailPropLightstylesLump_t> *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<DetailPropLightstylesLump_t> *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 );
|
|
}
|