2020-04-22 12:56:21 -04:00
//========= 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 "mathlib/vector.h"
2023-05-09 19:57:22 +03:00
# include "utlbuffer.h"
2020-04-22 12:56:21 -04:00
# include "utlvector.h"
2023-05-09 19:57:22 +03:00
# include "gamebspfile.h"
# include "bsptreedata.h"
# include "vphysics_interface.h"
# include "studio.h"
# include "optimize.h"
# include "bsplib.h"
# include "cmodel.h"
# include "physdll.h"
2020-04-22 12:56:21 -04:00
# include "phyfile.h"
# include "collisionutils.h"
# include "tier1/KeyValues.h"
# include "pacifier.h"
# include "materialsystem/imaterial.h"
# include "materialsystem/hardwareverts.h"
# include "materialsystem/hardwaretexels.h"
# include "byteswap.h"
# include "vtf/vtf.h"
# include "tier1/utldict.h"
# include "tier1/utlsymbol.h"
# include "bitmap/tgawriter.h"
2023-05-09 19:57:22 +03:00
# ifdef MPI
# include "mpivrad.h"
2020-04-22 12:56:21 -04:00
# include "messbuf.h"
# include "vmpi.h"
# include "vmpi_distribute_work.h"
2023-05-09 19:57:22 +03:00
# endif
2020-04-22 12:56:21 -04:00
# define ALIGN_TO_POW2(x,y) (((x)+(y-1))&~(y-1))
// identifies a vertex embedded in solid
// lighting will be copied from nearest valid neighbor
struct badVertex_t
{
int m_ColorVertex ;
Vector m_Position ;
Vector m_Normal ;
} ;
// a final colored vertex
struct colorVertex_t
{
Vector m_Color ;
Vector m_Position ;
bool m_bValid ;
} ;
// a texel suitable for a model
struct colorTexel_t
{
Vector m_Color ;
Vector m_WorldPosition ;
Vector m_WorldNormal ;
float m_fDistanceToTri ; // If we are outside of the triangle, how far away is it?
bool m_bValid ;
bool m_bPossiblyInteresting ;
} ;
class CComputeStaticPropLightingResults
{
public :
~ CComputeStaticPropLightingResults ( )
{
m_ColorVertsArrays . PurgeAndDeleteElements ( ) ;
m_ColorTexelsArrays . PurgeAndDeleteElements ( ) ;
}
CUtlVector < CUtlVector < colorVertex_t > * > m_ColorVertsArrays ;
CUtlVector < CUtlVector < colorTexel_t > * > m_ColorTexelsArrays ;
} ;
//-----------------------------------------------------------------------------
struct Rasterizer
{
struct Location
{
Vector barycentric ;
Vector2D uv ;
bool insideTriangle ;
} ;
Rasterizer ( Vector2D t0 , Vector2D t1 , Vector2D t2 , size_t resX , size_t resY )
: mT0 ( t0 )
, mT1 ( t1 )
, mT2 ( t2 )
, mResX ( resX )
, mResY ( resY )
, mUvStepX ( 1.0f / resX )
, mUvStepY ( 1.0f / resY )
{
Build ( ) ;
}
CUtlVector < Location > : : iterator begin ( ) { return mRasterizedLocations . begin ( ) ; }
CUtlVector < Location > : : iterator end ( ) { return mRasterizedLocations . end ( ) ; }
void Build ( ) ;
inline size_t GetRow ( float y ) const { return size_t ( y * mResY ) ; }
inline size_t GetCol ( float x ) const { return size_t ( x * mResX ) ; }
inline size_t GetLinearPos ( const CUtlVector < Location > : : iterator & it ) const
{
// Given an iterator, return what the linear position in the buffer would be for the data.
return ( size_t ) ( GetRow ( it - > uv . y ) * mResX )
+ ( size_t ) ( GetCol ( it - > uv . x ) ) ;
}
private :
const Vector2D mT0 , mT1 , mT2 ;
const size_t mResX , mResY ;
const float mUvStepX , mUvStepY ;
// Right now, we just fill this out and directly iterate over it.
// It could be large. This is a memory/speed tradeoff. We could instead generate them
// on demand.
CUtlVector < Location > mRasterizedLocations ;
} ;
//-----------------------------------------------------------------------------
inline Vector ComputeBarycentric ( Vector2D _edgeC , Vector2D _edgeA , Vector2D _edgeB , float _dAA , float _dAB , float _dBB , float _invDenom )
{
float dCA = _edgeC . Dot ( _edgeA ) ;
float dCB = _edgeC . Dot ( _edgeB ) ;
Vector retVal ;
retVal . y = ( _dBB * dCA - _dAB * dCB ) * _invDenom ;
retVal . z = ( _dAA * dCB - _dAB * dCA ) * _invDenom ;
retVal . x = 1.0f - retVal . y - retVal . z ;
return retVal ;
}
//-----------------------------------------------------------------------------
void Rasterizer : : Build ( )
{
// For now, use the barycentric method. It's easy, I'm lazy.
// We can optimize later if it's a performance issue.
const float baseX = mUvStepX / 2.0f ;
const float baseY = mUvStepY / 2.0f ;
2023-05-09 19:57:22 +03:00
float fMinX = MIN ( MIN ( mT0 . x , mT1 . x ) , mT2 . x ) ;
float fMinY = MIN ( MIN ( mT0 . y , mT1 . y ) , mT2 . y ) ;
float fMaxX = MAX ( MAX ( mT0 . x , mT1 . x ) , mT2 . x ) ;
float fMaxY = MAX ( MAX ( mT0 . y , mT1 . y ) , mT2 . y ) ;
2020-04-22 12:56:21 -04:00
// Degenerate. Consider warning about these, but otherwise no problem.
if ( fMinX = = fMaxX | | fMinY = = fMaxY )
return ;
// Clamp to 0..1
2023-05-09 19:57:22 +03:00
fMinX = MAX ( 0 , fMinX ) ;
fMinY = MAX ( 0 , fMinY ) ;
fMaxX = MIN ( 1.0f , fMaxX ) ;
fMaxY = MIN ( 1.0f , fMaxY ) ;
2020-04-22 12:56:21 -04:00
// We puff the interesting area up by 1 so we can hit an inflated region for the necessary bilerp data.
// If we wanted to support better texturing (almost definitely unnecessary), we'd change this to a larger size.
const int kFilterSampleRadius = 1 ;
int iMinX = GetCol ( fMinX ) - kFilterSampleRadius ;
int iMinY = GetRow ( fMinY ) - kFilterSampleRadius ;
int iMaxX = GetCol ( fMaxX ) + 1 + kFilterSampleRadius ;
int iMaxY = GetRow ( fMaxY ) + 1 + kFilterSampleRadius ;
// Clamp to valid texture (integer) locations
2023-05-09 19:57:22 +03:00
iMinX = MAX ( 0 , iMinX ) ;
iMinY = MAX ( 0 , iMinY ) ;
iMaxX = MIN ( iMaxX , mResX - 1 ) ;
iMaxY = MIN ( iMaxY , mResY - 1 ) ;
2020-04-22 12:56:21 -04:00
// Set the size to be as expected.
// TODO: Pass this in from outside to minimize allocations
2023-05-09 19:57:22 +03:00
// TODO: Pass this in from outside to minimize allocations
2020-04-22 12:56:21 -04:00
int count = ( iMaxY - iMinY + 1 )
* ( iMaxX - iMinX + 1 ) ;
mRasterizedLocations . EnsureCount ( count ) ;
memset ( mRasterizedLocations . Base ( ) , 0 , mRasterizedLocations . Count ( ) * sizeof ( Location ) ) ;
// Computing Barycentrics adapted from here http://gamedev.stackexchange.com/questions/23743/whats-the-most-efficient-way-to-find-barycentric-coordinates
Vector2D edgeA = mT1 - mT0 ;
Vector2D edgeB = mT2 - mT0 ;
float dAA = edgeA . Dot ( edgeA ) ;
float dAB = edgeA . Dot ( edgeB ) ;
float dBB = edgeB . Dot ( edgeB ) ;
float invDenom = 1.0f / ( dAA * dBB - dAB * dAB ) ;
int linearPos = 0 ;
for ( int j = iMinY ; j < = iMaxY ; + + j ) {
for ( int i = iMinX ; i < = iMaxX ; + + i ) {
Vector2D testPt ( i * mUvStepX + baseX , j * mUvStepY + baseY ) ;
Vector barycentric = ComputeBarycentric ( testPt - mT0 , edgeA , edgeB , dAA , dAB , dBB , invDenom ) ;
// Test whether the point is inside the triangle.
// MCJOHNTODO: Edge rules and whatnot--right now we re-rasterize points on the edge.
Location & newLoc = mRasterizedLocations [ linearPos + + ] ;
newLoc . barycentric = barycentric ;
newLoc . uv = testPt ;
newLoc . insideTriangle = ( barycentric . x > = 0.0f & & barycentric . x < = 1.0f & & barycentric . y > = 0.0f & & barycentric . y < = 1.0f & & barycentric . z > = 0.0f & & barycentric . z < = 1.0f ) ;
}
}
}
//-----------------------------------------------------------------------------
// Globals
//-----------------------------------------------------------------------------
CUtlSymbolTable g_ForcedTextureShadowsModels ;
// DON'T USE THIS FROM WITHIN A THREAD. THERE IS A THREAD CONTEXT CREATED
// INSIDE PropTested_t. USE THAT INSTEAD.
IPhysicsCollision * s_pPhysCollision = NULL ;
static void ConvertTexelDataToTexture ( unsigned int _resX , unsigned int _resY , ImageFormat _destFmt , const CUtlVector < colorTexel_t > & _srcTexels , CUtlMemory < byte > * _outTexture ) ;
// Such a monstrosity. :(
static void GenerateLightmapSamplesForMesh ( const matrix3x4_t & _matPos , const matrix3x4_t & _matNormal , int _iThread , int _skipProp , int _nFlags , int _lightmapResX , int _lightmapResY ,
studiohdr_t * _pStudioHdr , mstudiomodel_t * _pStudioModel , OptimizedModel : : ModelHeader_t * _pVtxModel , int _meshID ,
CComputeStaticPropLightingResults * _pResults ) ;
// Debug function, converts lightmaps to linear space then dumps them out.
// TODO: Write out the file in a .dds instead of a .tga, in whatever format we're supposed to use.
static void DumpLightmapLinear ( const char * _dstFilename , const CUtlVector < colorTexel_t > & _srcTexels , int _width , int _height ) ;
//-----------------------------------------------------------------------------
// Vrad's static prop manager
//-----------------------------------------------------------------------------
class CVradStaticPropMgr : public IVradStaticPropMgr
{
public :
// constructor, destructor
CVradStaticPropMgr ( ) ;
virtual ~ CVradStaticPropMgr ( ) ;
// methods of IStaticPropMgr
void Init ( ) ;
void Shutdown ( ) ;
// iterate all the instanced static props and compute their vertex lighting
void ComputeLighting ( int iThread ) ;
private :
2023-05-09 19:57:22 +03:00
# ifdef MPI
2020-04-22 12:56:21 -04:00
// VMPI stuff.
static void VMPI_ProcessStaticProp_Static ( int iThread , uint64 iStaticProp , MessageBuffer * pBuf ) ;
static void VMPI_ReceiveStaticPropResults_Static ( uint64 iStaticProp , MessageBuffer * pBuf , int iWorker ) ;
void VMPI_ProcessStaticProp ( int iThread , int iStaticProp , MessageBuffer * pBuf ) ;
void VMPI_ReceiveStaticPropResults ( int iStaticProp , MessageBuffer * pBuf , int iWorker ) ;
2023-05-09 19:57:22 +03:00
# endif
2020-04-22 12:56:21 -04:00
// local thread version
static void ThreadComputeStaticPropLighting ( int iThread , void * pUserData ) ;
void ComputeLightingForProp ( int iThread , int iStaticProp ) ;
// Methods associated with unserializing static props
void UnserializeModelDict ( CUtlBuffer & buf ) ;
void UnserializeModels ( CUtlBuffer & buf ) ;
void UnserializeStaticProps ( ) ;
// Creates a collision model
void CreateCollisionModel ( char const * pModelName ) ;
private :
// Unique static prop models
struct StaticPropDict_t
{
vcollide_t m_loadedModel ;
CPhysCollide * m_pModel ;
Vector m_Mins ; // Bounding box is in local coordinates
Vector m_Maxs ;
studiohdr_t * m_pStudioHdr ;
CUtlBuffer m_VtxBuf ;
CUtlVector < int > m_textureShadowIndex ; // each texture has an index if this model casts texture shadows
CUtlVector < int > m_triangleMaterialIndex ; // each triangle has an index if this model casts texture shadows
} ;
struct MeshData_t
{
CUtlVector < Vector > m_VertexColors ;
CUtlMemory < byte > m_TexelsEncoded ;
int m_nLod ;
} ;
// A static prop instance
struct CStaticProp
{
Vector m_Origin ;
QAngle m_Angles ;
Vector m_mins ;
Vector m_maxs ;
Vector m_LightingOrigin ;
int m_ModelIdx ;
BSPTreeDataHandle_t m_Handle ;
CUtlVector < MeshData_t > m_MeshData ;
int m_Flags ;
bool m_bLightingOriginValid ;
// Note that all lightmaps for a given prop share the same resolution (and format)--and there can be multiple lightmaps
// per prop (if there are multiple pieces--the watercooler is an example).
// This is effectively because there's not a good way in hammer for a prop to say "this should be the resolution
// of each of my sub-pieces."
ImageFormat m_LightmapImageFormat ;
unsigned int m_LightmapImageWidth ;
unsigned int m_LightmapImageHeight ;
} ;
// Enumeration context
struct EnumContext_t
{
PropTested_t * m_pPropTested ;
Ray_t const * m_pRay ;
} ;
// The list of all static props
CUtlVector < StaticPropDict_t > m_StaticPropDict ;
CUtlVector < CStaticProp > m_StaticProps ;
bool m_bIgnoreStaticPropTrace ;
void ComputeLighting ( CStaticProp & prop , int iThread , int prop_index , CComputeStaticPropLightingResults * pResults ) ;
void ApplyLightingToStaticProp ( int iStaticProp , CStaticProp & prop , const CComputeStaticPropLightingResults * pResults ) ;
void SerializeLighting ( ) ;
void AddPolysForRayTrace ( ) ;
void BuildTriList ( CStaticProp & prop ) ;
} ;
//-----------------------------------------------------------------------------
// Expose IVradStaticPropMgr to vrad
//-----------------------------------------------------------------------------
static CVradStaticPropMgr g_StaticPropMgr ;
IVradStaticPropMgr * StaticPropMgr ( )
{
return & g_StaticPropMgr ;
}
//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
CVradStaticPropMgr : : CVradStaticPropMgr ( )
{
// set to ignore static prop traces
m_bIgnoreStaticPropTrace = false ;
}
CVradStaticPropMgr : : ~ CVradStaticPropMgr ( )
{
}
//-----------------------------------------------------------------------------
// Makes sure the studio model is a static prop
//-----------------------------------------------------------------------------
bool IsStaticProp ( studiohdr_t * pHdr )
{
if ( ! ( pHdr - > flags & STUDIOHDR_FLAGS_STATIC_PROP ) )
return false ;
return true ;
}
//-----------------------------------------------------------------------------
// Load a file into a Utlbuf
//-----------------------------------------------------------------------------
static bool LoadFile ( char const * pFileName , CUtlBuffer & buf )
{
if ( ! g_pFullFileSystem )
return false ;
return g_pFullFileSystem - > ReadFile ( pFileName , NULL , buf ) ;
}
//-----------------------------------------------------------------------------
// Constructs the file name from the model name
//-----------------------------------------------------------------------------
static char const * ConstructFileName ( char const * pModelName )
{
static char buf [ 1024 ] ;
sprintf ( buf , " %s%s " , gamedir , pModelName ) ;
return buf ;
}
//-----------------------------------------------------------------------------
// Computes a convex hull from a studio mesh
//-----------------------------------------------------------------------------
static CPhysConvex * ComputeConvexHull ( mstudiomesh_t * pMesh , studiohdr_t * pStudioHdr )
{
const mstudio_meshvertexdata_t * vertData = pMesh - > GetVertexData ( ( void * ) pStudioHdr ) ;
Assert ( vertData ) ; // This can only return NULL on X360 for now
// Generate a list of all verts in the mesh
Vector * * ppVerts = ( Vector * * ) _alloca ( pMesh - > numvertices * sizeof ( Vector * ) ) ;
for ( int i = 0 ; i < pMesh - > numvertices ; + + i )
{
ppVerts [ i ] = vertData - > Position ( i ) ;
}
// Generate a convex hull from the verts
return s_pPhysCollision - > ConvexFromVerts ( ppVerts , pMesh - > numvertices ) ;
}
//-----------------------------------------------------------------------------
// Computes a convex hull from the studio model
//-----------------------------------------------------------------------------
CPhysCollide * ComputeConvexHull ( studiohdr_t * pStudioHdr )
{
CUtlVector < CPhysConvex * > convexHulls ;
for ( int body = 0 ; body < pStudioHdr - > numbodyparts ; + + body )
{
mstudiobodyparts_t * pBodyPart = pStudioHdr - > pBodypart ( body ) ;
for ( int model = 0 ; model < pBodyPart - > nummodels ; + + model )
{
mstudiomodel_t * pStudioModel = pBodyPart - > pModel ( model ) ;
for ( int mesh = 0 ; mesh < pStudioModel - > nummeshes ; + + mesh )
{
// Make a convex hull for each mesh
// NOTE: This won't work unless the model has been compiled
// with $staticprop
mstudiomesh_t * pStudioMesh = pStudioModel - > pMesh ( mesh ) ;
convexHulls . AddToTail ( ComputeConvexHull ( pStudioMesh , pStudioHdr ) ) ;
}
}
}
// Convert an array of convex elements to a compiled collision model
// (this deletes the convex elements)
return s_pPhysCollision - > ConvertConvexToCollide ( convexHulls . Base ( ) , convexHulls . Size ( ) ) ;
}
//-----------------------------------------------------------------------------
// Load studio model vertex data from a file...
//-----------------------------------------------------------------------------
bool LoadStudioModel ( char const * pModelName , CUtlBuffer & buf )
{
// No luck, gotta build it
// Construct the file name...
if ( ! LoadFile ( pModelName , buf ) )
{
Warning ( " Error! Unable to load model \" %s \" \n " , pModelName ) ;
return false ;
}
// Check that it's valid
if ( strncmp ( ( const char * ) buf . PeekGet ( ) , " IDST " , 4 ) & &
strncmp ( ( const char * ) buf . PeekGet ( ) , " IDAG " , 4 ) )
{
Warning ( " Error! Invalid model file \" %s \" \n " , pModelName ) ;
return false ;
}
studiohdr_t * pHdr = ( studiohdr_t * ) buf . PeekGet ( ) ;
Studio_ConvertStudioHdrToNewVersion ( pHdr ) ;
if ( pHdr - > version ! = STUDIO_VERSION )
{
Warning ( " Error! Invalid model version \" %s \" \n " , pModelName ) ;
return false ;
}
if ( ! IsStaticProp ( pHdr ) )
{
Warning ( " Error! To use model \" %s \" \n "
" as a static prop, it must be compiled with $staticprop! \n " , pModelName ) ;
return false ;
}
// ensure reset
2023-05-09 19:57:22 +03:00
pHdr - > SetVertexBase ( NULL ) ;
pHdr - > SetIndexBase ( NULL ) ;
2020-04-22 12:56:21 -04:00
return true ;
}
bool LoadStudioCollisionModel ( char const * pModelName , CUtlBuffer & buf )
{
char tmp [ 1024 ] ;
Q_strncpy ( tmp , pModelName , sizeof ( tmp ) ) ;
Q_SetExtension ( tmp , " .phy " , sizeof ( tmp ) ) ;
// No luck, gotta build it
if ( ! LoadFile ( tmp , buf ) )
{
// this is not an error, the model simply has no PHY file
return false ;
}
phyheader_t * header = ( phyheader_t * ) buf . PeekGet ( ) ;
if ( header - > size ! = sizeof ( * header ) | | header - > solidCount < = 0 )
return false ;
return true ;
}
bool LoadVTXFile ( char const * pModelName , const studiohdr_t * pStudioHdr , CUtlBuffer & buf )
{
char filename [ MAX_PATH ] ;
// construct filename
Q_StripExtension ( pModelName , filename , sizeof ( filename ) ) ;
strcat ( filename , " .dx80.vtx " ) ;
if ( ! LoadFile ( filename , buf ) )
{
Warning ( " Error! Unable to load file \" %s \" \n " , filename ) ;
return false ;
}
OptimizedModel : : FileHeader_t * pVtxHdr = ( OptimizedModel : : FileHeader_t * ) buf . Base ( ) ;
// Check that it's valid
if ( pVtxHdr - > version ! = OPTIMIZED_MODEL_FILE_VERSION )
{
Warning ( " Error! Invalid VTX file version: %d, expected %d \" %s \" \n " , pVtxHdr - > version , OPTIMIZED_MODEL_FILE_VERSION , filename ) ;
return false ;
}
if ( pVtxHdr - > checkSum ! = pStudioHdr - > checksum )
{
Warning ( " Error! Invalid VTX file checksum: %d, expected %d \" %s \" \n " , pVtxHdr - > checkSum , pStudioHdr - > checksum , filename ) ;
return false ;
}
return true ;
}
//-----------------------------------------------------------------------------
// Gets a vertex position from a strip index
//-----------------------------------------------------------------------------
inline static Vector * PositionFromIndex ( const mstudio_meshvertexdata_t * vertData , mstudiomesh_t * pMesh , OptimizedModel : : StripGroupHeader_t * pStripGroup , int i )
{
OptimizedModel : : Vertex_t * pVert = pStripGroup - > pVertex ( i ) ;
return vertData - > Position ( pVert - > origMeshVertID ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Writes a glview text file containing the collision surface in question
// Input : *pCollide -
// *pFilename -
//-----------------------------------------------------------------------------
void DumpCollideToGlView ( vcollide_t * pCollide , const char * pFilename )
{
if ( ! pCollide )
return ;
Msg ( " Writing %s... \n " , pFilename ) ;
FILE * fp = fopen ( pFilename , " w " ) ;
for ( int i = 0 ; i < pCollide - > solidCount ; + + i )
{
Vector * outVerts ;
int vertCount = s_pPhysCollision - > CreateDebugMesh ( pCollide - > solids [ i ] , & outVerts ) ;
int triCount = vertCount / 3 ;
int vert = 0 ;
unsigned char r = ( i & 1 ) * 64 + 64 ;
unsigned char g = ( i & 2 ) * 64 + 64 ;
unsigned char b = ( i & 4 ) * 64 + 64 ;
float fr = r / 255.0f ;
float fg = g / 255.0f ;
float fb = b / 255.0f ;
for ( int i = 0 ; i < triCount ; i + + )
{
fprintf ( fp , " 3 \n " ) ;
fprintf ( fp , " %6.3f %6.3f %6.3f %.2f %.3f %.3f \n " ,
outVerts [ vert ] . x , outVerts [ vert ] . y , outVerts [ vert ] . z , fr , fg , fb ) ;
vert + + ;
fprintf ( fp , " %6.3f %6.3f %6.3f %.2f %.3f %.3f \n " ,
outVerts [ vert ] . x , outVerts [ vert ] . y , outVerts [ vert ] . z , fr , fg , fb ) ;
vert + + ;
fprintf ( fp , " %6.3f %6.3f %6.3f %.2f %.3f %.3f \n " ,
outVerts [ vert ] . x , outVerts [ vert ] . y , outVerts [ vert ] . z , fr , fg , fb ) ;
vert + + ;
}
s_pPhysCollision - > DestroyDebugMesh ( vertCount , outVerts ) ;
}
fclose ( fp ) ;
}
static bool PointInTriangle ( const Vector2D & p , const Vector2D & v0 , const Vector2D & v1 , const Vector2D & v2 )
{
float coords [ 3 ] ;
GetBarycentricCoords2D ( v0 , v1 , v2 , p , coords ) ;
for ( int i = 0 ; i < 3 ; i + + )
{
if ( coords [ i ] < 0.0f | | coords [ i ] > 1.0f )
return false ;
}
float sum = coords [ 0 ] + coords [ 1 ] + coords [ 2 ] ;
if ( sum > 1.0f )
return false ;
return true ;
}
bool LoadFileIntoBuffer ( CUtlBuffer & buf , const char * pFilename )
{
FileHandle_t fileHandle = g_pFileSystem - > Open ( pFilename , " rb " ) ;
if ( ! fileHandle )
return false ;
// Get the file size
int texSize = g_pFileSystem - > Size ( fileHandle ) ;
buf . EnsureCapacity ( texSize ) ;
int nBytesRead = g_pFileSystem - > Read ( buf . Base ( ) , texSize , fileHandle ) ;
g_pFileSystem - > Close ( fileHandle ) ;
buf . SeekPut ( CUtlBuffer : : SEEK_HEAD , nBytesRead ) ;
buf . SeekGet ( CUtlBuffer : : SEEK_HEAD , 0 ) ;
return true ;
}
// keeps a list of all textures that cast shadows via alpha channel
class CShadowTextureList
{
public :
// This loads a vtf and converts it to RGB8888 format
unsigned char * LoadVTFRGB8888 ( const char * pName , int * pWidth , int * pHeight , bool * pClampU , bool * pClampV )
{
char szPath [ MAX_PATH ] ;
Q_strncpy ( szPath , " materials/ " , sizeof ( szPath ) ) ;
Q_strncat ( szPath , pName , sizeof ( szPath ) , COPY_ALL_CHARACTERS ) ;
Q_strncat ( szPath , " .vtf " , sizeof ( szPath ) , COPY_ALL_CHARACTERS ) ;
Q_FixSlashes ( szPath , CORRECT_PATH_SEPARATOR ) ;
CUtlBuffer buf ;
if ( ! LoadFileIntoBuffer ( buf , szPath ) )
return NULL ;
IVTFTexture * pTex = CreateVTFTexture ( ) ;
if ( ! pTex - > Unserialize ( buf ) )
return NULL ;
Msg ( " Loaded alpha texture %s \n " , szPath ) ;
unsigned char * pSrcImage = pTex - > ImageData ( 0 , 0 , 0 , 0 , 0 , 0 ) ;
int iWidth = pTex - > Width ( ) ;
int iHeight = pTex - > Height ( ) ;
ImageFormat dstFormat = IMAGE_FORMAT_RGBA8888 ;
ImageFormat srcFormat = pTex - > Format ( ) ;
* pClampU = ( pTex - > Flags ( ) & TEXTUREFLAGS_CLAMPS ) ? true : false ;
* pClampV = ( pTex - > Flags ( ) & TEXTUREFLAGS_CLAMPT ) ? true : false ;
unsigned char * pDstImage = new unsigned char [ ImageLoader : : GetMemRequired ( iWidth , iHeight , 1 , dstFormat , false ) ] ;
if ( ! ImageLoader : : ConvertImageFormat ( pSrcImage , srcFormat ,
pDstImage , dstFormat , iWidth , iHeight , 0 , 0 ) )
{
delete [ ] pDstImage ;
return NULL ;
}
* pWidth = iWidth ;
* pHeight = iHeight ;
return pDstImage ;
}
// Checks the database for the material and loads if necessary
// returns true if found and pIndex will be the index, -1 if no alpha shadows
bool FindOrLoadIfValid ( const char * pMaterialName , int * pIndex )
{
* pIndex = - 1 ;
int index = m_Textures . Find ( pMaterialName ) ;
bool bFound = false ;
if ( index ! = m_Textures . InvalidIndex ( ) )
{
bFound = true ;
* pIndex = index ;
}
else
{
KeyValues * pVMT = new KeyValues ( " vmt " ) ;
CUtlBuffer buf ( 0 , 0 , CUtlBuffer : : TEXT_BUFFER ) ;
LoadFileIntoBuffer ( buf , pMaterialName ) ;
if ( pVMT - > LoadFromBuffer ( pMaterialName , buf ) )
{
bFound = true ;
if ( pVMT - > FindKey ( " $translucent " ) | | pVMT - > FindKey ( " $alphatest " ) )
{
KeyValues * pBaseTexture = pVMT - > FindKey ( " $basetexture " ) ;
if ( pBaseTexture )
{
const char * pBaseTextureName = pBaseTexture - > GetString ( ) ;
if ( pBaseTextureName )
{
int w , h ;
bool bClampU = false ;
bool bClampV = false ;
unsigned char * pImageBits = LoadVTFRGB8888 ( pBaseTextureName , & w , & h , & bClampU , & bClampV ) ;
if ( pImageBits )
{
int index = m_Textures . Insert ( pMaterialName ) ;
m_Textures [ index ] . InitFromRGB8888 ( w , h , pImageBits ) ;
* pIndex = index ;
if ( pVMT - > FindKey ( " $nocull " ) )
{
// UNDONE: Support this? Do we need to emit two triangles?
m_Textures [ index ] . allowBackface = true ;
}
m_Textures [ index ] . clampU = bClampU ;
m_Textures [ index ] . clampV = bClampV ;
delete [ ] pImageBits ;
}
}
}
}
}
pVMT - > deleteThis ( ) ;
}
return bFound ;
}
// iterate the textures for the model and load each one into the database
// this is used on models marked to cast texture shadows
void LoadAllTexturesForModel ( studiohdr_t * pHdr , int * pTextureList )
{
for ( int i = 0 ; i < pHdr - > numtextures ; i + + )
{
int textureIndex = - 1 ;
// try to add each texture to the transparent shadow manager
char szPath [ MAX_PATH ] ;
// iterate quietly through all specified directories until a valid material is found
for ( int j = 0 ; j < pHdr - > numcdtextures ; j + + )
{
Q_strncpy ( szPath , " materials/ " , sizeof ( szPath ) ) ;
Q_strncat ( szPath , pHdr - > pCdtexture ( j ) , sizeof ( szPath ) ) ;
const char * textureName = pHdr - > pTexture ( i ) - > pszName ( ) ;
Q_strncat ( szPath , textureName , sizeof ( szPath ) , COPY_ALL_CHARACTERS ) ;
Q_strncat ( szPath , " .vmt " , sizeof ( szPath ) , COPY_ALL_CHARACTERS ) ;
Q_FixSlashes ( szPath , CORRECT_PATH_SEPARATOR ) ;
if ( FindOrLoadIfValid ( szPath , & textureIndex ) )
break ;
}
pTextureList [ i ] = textureIndex ;
}
}
int AddMaterialEntry ( int shadowTextureIndex , const Vector2D & t0 , const Vector2D & t1 , const Vector2D & t2 )
{
int index = m_MaterialEntries . AddToTail ( ) ;
m_MaterialEntries [ index ] . textureIndex = shadowTextureIndex ;
m_MaterialEntries [ index ] . uv [ 0 ] = t0 ;
m_MaterialEntries [ index ] . uv [ 1 ] = t1 ;
m_MaterialEntries [ index ] . uv [ 2 ] = t2 ;
return index ;
}
// HACKHACK: Compute the average coverage for this triangle by sampling the AABB of its texture space
float ComputeCoverageForTriangle ( int shadowTextureIndex , const Vector2D & t0 , const Vector2D & t1 , const Vector2D & t2 )
{
2023-05-09 19:57:22 +03:00
float umin = MIN ( t0 . x , t1 . x ) ;
umin = MIN ( umin , t2 . x ) ;
float umax = MAX ( t0 . x , t1 . x ) ;
umax = MAX ( umax , t2 . x ) ;
2020-04-22 12:56:21 -04:00
2023-05-09 19:57:22 +03:00
float vmin = MIN ( t0 . y , t1 . y ) ;
vmin = MIN ( vmin , t2 . y ) ;
float vmax = MAX ( t0 . y , t1 . y ) ;
vmax = MAX ( vmax , t2 . y ) ;
2020-04-22 12:56:21 -04:00
// UNDONE: Do something about tiling
umin = clamp ( umin , 0 , 1 ) ;
umax = clamp ( umax , 0 , 1 ) ;
vmin = clamp ( vmin , 0 , 1 ) ;
vmax = clamp ( vmax , 0 , 1 ) ;
Assert ( umin > = 0.0f & & umax < = 1.0f ) ;
Assert ( vmin > = 0.0f & & vmax < = 1.0f ) ;
const alphatexture_t & tex = m_Textures . Element ( shadowTextureIndex ) ;
int u0 = umin * ( tex . width - 1 ) ;
int u1 = umax * ( tex . width - 1 ) ;
int v0 = vmin * ( tex . height - 1 ) ;
int v1 = vmax * ( tex . height - 1 ) ;
int total = 0 ;
int count = 0 ;
for ( int v = v0 ; v < = v1 ; v + + )
{
int row = ( v * tex . width ) ;
for ( int u = u0 ; u < = u1 ; u + + )
{
total + = tex . pAlphaTexels [ row + u ] ;
count + + ;
}
}
if ( count )
{
float coverage = float ( total ) / ( count * 255.0f ) ;
return coverage ;
}
return 1.0f ;
}
int SampleMaterial ( int materialIndex , const Vector & coords , bool bBackface )
{
const materialentry_t & mat = m_MaterialEntries [ materialIndex ] ;
const alphatexture_t & tex = m_Textures . Element ( m_MaterialEntries [ materialIndex ] . textureIndex ) ;
if ( bBackface & & ! tex . allowBackface )
return 0 ;
Vector2D uv = coords . x * mat . uv [ 0 ] + coords . y * mat . uv [ 1 ] + coords . z * mat . uv [ 2 ] ;
int u = RoundFloatToInt ( uv [ 0 ] * tex . width ) ;
int v = RoundFloatToInt ( uv [ 1 ] * tex . height ) ;
// asume power of 2, clamp or wrap
// UNDONE: Support clamp? This code should work
#if 0
u = tex . clampU ? clamp ( u , 0 , ( tex . width - 1 ) ) : ( u & ( tex . width - 1 ) ) ;
v = tex . clampV ? clamp ( v , 0 , ( tex . height - 1 ) ) : ( v & ( tex . height - 1 ) ) ;
# else
// for now always wrap
u & = ( tex . width - 1 ) ;
v & = ( tex . height - 1 ) ;
# endif
return tex . pAlphaTexels [ v * tex . width + u ] ;
}
struct alphatexture_t
{
short width ;
short height ;
bool allowBackface ;
bool clampU ;
bool clampV ;
unsigned char * pAlphaTexels ;
void InitFromRGB8888 ( int w , int h , unsigned char * pTexels )
{
width = w ;
height = h ;
pAlphaTexels = new unsigned char [ w * h ] ;
for ( int i = 0 ; i < h ; i + + )
{
for ( int j = 0 ; j < w ; j + + )
{
int index = ( i * w ) + j ;
pAlphaTexels [ index ] = pTexels [ index * 4 + 3 ] ;
}
}
}
} ;
struct materialentry_t
{
int textureIndex ;
Vector2D uv [ 3 ] ;
} ;
// this is the list of textures we've loaded
// only load each one once
CUtlDict < alphatexture_t , unsigned short > m_Textures ;
CUtlVector < materialentry_t > m_MaterialEntries ;
} ;
// global to keep the shadow-casting texture list and their alpha bits
CShadowTextureList g_ShadowTextureList ;
float ComputeCoverageFromTexture ( float b0 , float b1 , float b2 , int32 hitID )
{
const float alphaScale = 1.0f / 255.0f ;
// UNDONE: Pass ray down to determine backfacing?
//Vector normal( tri.m_flNx, tri.m_flNy, tri.m_flNz );
//bool bBackface = DotProduct(delta, tri.N) > 0 ? true : false;
Vector coords ( b0 , b1 , b2 ) ;
return alphaScale * g_ShadowTextureList . SampleMaterial ( g_RtEnv . GetTriangleMaterial ( hitID ) , coords , false ) ;
}
// this is here to strip models/ or .mdl or whatnot
void CleanModelName ( const char * pModelName , char * pOutput , int outLen )
{
// strip off leading models/ if it exists
const char * pModelDir = " models/ " ;
int modelLen = Q_strlen ( pModelDir ) ;
if ( ! Q_strnicmp ( pModelName , pModelDir , modelLen ) )
{
pModelName + = modelLen ;
}
Q_strncpy ( pOutput , pModelName , outLen ) ;
// truncate any .mdl extension
char * dot = strchr ( pOutput , ' . ' ) ;
if ( dot )
{
* dot = 0 ;
}
}
void ForceTextureShadowsOnModel ( const char * pModelName )
{
char buf [ 1024 ] ;
CleanModelName ( pModelName , buf , sizeof ( buf ) ) ;
if ( ! g_ForcedTextureShadowsModels . Find ( buf ) . IsValid ( ) )
{
g_ForcedTextureShadowsModels . AddString ( buf ) ;
}
}
bool IsModelTextureShadowsForced ( const char * pModelName )
{
char buf [ 1024 ] ;
CleanModelName ( pModelName , buf , sizeof ( buf ) ) ;
return g_ForcedTextureShadowsModels . Find ( buf ) . IsValid ( ) ;
}
//-----------------------------------------------------------------------------
// Creates a collision model (based on the render geometry!)
//-----------------------------------------------------------------------------
void CVradStaticPropMgr : : CreateCollisionModel ( char const * pModelName )
{
CUtlBuffer buf ;
CUtlBuffer bufvtx ;
CUtlBuffer bufphy ;
int i = m_StaticPropDict . AddToTail ( ) ;
m_StaticPropDict [ i ] . m_pModel = NULL ;
m_StaticPropDict [ i ] . m_pStudioHdr = NULL ;
if ( ! LoadStudioModel ( pModelName , buf ) )
{
VectorCopy ( vec3_origin , m_StaticPropDict [ i ] . m_Mins ) ;
VectorCopy ( vec3_origin , m_StaticPropDict [ i ] . m_Maxs ) ;
return ;
}
studiohdr_t * pHdr = ( studiohdr_t * ) buf . Base ( ) ;
VectorCopy ( pHdr - > hull_min , m_StaticPropDict [ i ] . m_Mins ) ;
VectorCopy ( pHdr - > hull_max , m_StaticPropDict [ i ] . m_Maxs ) ;
if ( LoadStudioCollisionModel ( pModelName , bufphy ) )
{
phyheader_t header ;
bufphy . Get ( & header , sizeof ( header ) ) ;
vcollide_t * pCollide = & m_StaticPropDict [ i ] . m_loadedModel ;
s_pPhysCollision - > VCollideLoad ( pCollide , header . solidCount , ( const char * ) bufphy . PeekGet ( ) , bufphy . TellPut ( ) - bufphy . TellGet ( ) ) ;
m_StaticPropDict [ i ] . m_pModel = m_StaticPropDict [ i ] . m_loadedModel . solids [ 0 ] ;
/*
static int propNum = 0 ;
char tmp [ 128 ] ;
sprintf ( tmp , " staticprop%03d.txt " , propNum ) ;
DumpCollideToGlView ( pCollide , tmp ) ;
+ + propNum ;
*/
}
else
{
// mark this as unused
m_StaticPropDict [ i ] . m_loadedModel . solidCount = 0 ;
// CPhysCollide* pPhys = CreatePhysCollide( pHdr, pVtxHdr );
m_StaticPropDict [ i ] . m_pModel = ComputeConvexHull ( pHdr ) ;
}
// clone it
m_StaticPropDict [ i ] . m_pStudioHdr = ( studiohdr_t * ) malloc ( buf . Size ( ) ) ;
memcpy ( m_StaticPropDict [ i ] . m_pStudioHdr , ( studiohdr_t * ) buf . Base ( ) , buf . Size ( ) ) ;
if ( ! LoadVTXFile ( pModelName , m_StaticPropDict [ i ] . m_pStudioHdr , m_StaticPropDict [ i ] . m_VtxBuf ) )
{
// failed, leave state identified as disabled
m_StaticPropDict [ i ] . m_VtxBuf . Purge ( ) ;
}
if ( g_bTextureShadows )
{
if ( ( pHdr - > flags & STUDIOHDR_FLAGS_CAST_TEXTURE_SHADOWS ) | | IsModelTextureShadowsForced ( pModelName ) )
{
m_StaticPropDict [ i ] . m_textureShadowIndex . RemoveAll ( ) ;
m_StaticPropDict [ i ] . m_triangleMaterialIndex . RemoveAll ( ) ;
m_StaticPropDict [ i ] . m_textureShadowIndex . AddMultipleToTail ( pHdr - > numtextures ) ;
g_ShadowTextureList . LoadAllTexturesForModel ( pHdr , m_StaticPropDict [ i ] . m_textureShadowIndex . Base ( ) ) ;
}
}
}
//-----------------------------------------------------------------------------
// Unserialize static prop model dictionary
//-----------------------------------------------------------------------------
void CVradStaticPropMgr : : UnserializeModelDict ( CUtlBuffer & buf )
{
int count = buf . GetInt ( ) ;
while ( - - count > = 0 )
{
StaticPropDictLump_t lump ;
buf . Get ( & lump , sizeof ( StaticPropDictLump_t ) ) ;
CreateCollisionModel ( lump . m_Name ) ;
}
}
void CVradStaticPropMgr : : UnserializeModels ( CUtlBuffer & buf )
{
int count = buf . GetInt ( ) ;
m_StaticProps . AddMultipleToTail ( count ) ;
for ( int i = 0 ; i < count ; + + i )
{
StaticPropLump_t lump ;
buf . Get ( & lump , sizeof ( StaticPropLump_t ) ) ;
VectorCopy ( lump . m_Origin , m_StaticProps [ i ] . m_Origin ) ;
VectorCopy ( lump . m_Angles , m_StaticProps [ i ] . m_Angles ) ;
VectorCopy ( lump . m_LightingOrigin , m_StaticProps [ i ] . m_LightingOrigin ) ;
m_StaticProps [ i ] . m_bLightingOriginValid = ( lump . m_Flags & STATIC_PROP_USE_LIGHTING_ORIGIN ) > 0 ;
m_StaticProps [ i ] . m_ModelIdx = lump . m_PropType ;
m_StaticProps [ i ] . m_Handle = TREEDATA_INVALID_HANDLE ;
m_StaticProps [ i ] . m_Flags = lump . m_Flags ;
// Changed this from using DXT1 to RGB888 because the compression artifacts were pretty nasty.
// TODO: Consider changing back or basing this on user selection in hammer.
m_StaticProps [ i ] . m_LightmapImageFormat = IMAGE_FORMAT_RGB888 ;
m_StaticProps [ i ] . m_LightmapImageWidth = lump . m_nLightmapResolutionX ;
m_StaticProps [ i ] . m_LightmapImageHeight = lump . m_nLightmapResolutionY ;
}
}
//-----------------------------------------------------------------------------
// Unserialize static props
//-----------------------------------------------------------------------------
void CVradStaticPropMgr : : UnserializeStaticProps ( )
{
// Unserialize static props, insert them into the appropriate leaves
GameLumpHandle_t handle = g_GameLumps . GetGameLumpHandle ( GAMELUMP_STATIC_PROPS ) ;
int size = g_GameLumps . GameLumpSize ( handle ) ;
if ( ! size )
return ;
if ( g_GameLumps . GetGameLumpVersion ( handle ) ! = GAMELUMP_STATIC_PROPS_VERSION )
{
2023-05-09 19:57:22 +03:00
Error ( " Cannot load the static props... encountered a stale map version. Re-vbsp the map. \n " ) ;
2020-04-22 12:56:21 -04:00
}
if ( g_GameLumps . GetGameLump ( handle ) )
{
CUtlBuffer buf ( g_GameLumps . GetGameLump ( handle ) , size , CUtlBuffer : : READ_ONLY ) ;
UnserializeModelDict ( buf ) ;
// Skip the leaf list data
int count = buf . GetInt ( ) ;
buf . SeekGet ( CUtlBuffer : : SEEK_CURRENT , count * sizeof ( StaticPropLeafLump_t ) ) ;
UnserializeModels ( buf ) ;
}
}
//-----------------------------------------------------------------------------
// Level init, shutdown
//-----------------------------------------------------------------------------
void CVradStaticPropMgr : : Init ( )
{
CreateInterfaceFn physicsFactory = GetPhysicsFactory ( ) ;
if ( ! physicsFactory )
Error ( " Unable to load vphysics DLL. " ) ;
s_pPhysCollision = ( IPhysicsCollision * ) physicsFactory ( VPHYSICS_COLLISION_INTERFACE_VERSION , NULL ) ;
if ( ! s_pPhysCollision )
{
Error ( " Unable to get '%s' for physics interface. " , VPHYSICS_COLLISION_INTERFACE_VERSION ) ;
return ;
}
// Read in static props that have been compiled into the bsp file
UnserializeStaticProps ( ) ;
}
void CVradStaticPropMgr : : Shutdown ( )
{
// Remove all static prop model data
for ( int i = m_StaticPropDict . Size ( ) ; - - i > = 0 ; )
{
studiohdr_t * pStudioHdr = m_StaticPropDict [ i ] . m_pStudioHdr ;
if ( pStudioHdr )
{
2023-05-09 19:57:22 +03:00
if ( pStudioHdr - > VertexBase ( ) )
2020-04-22 12:56:21 -04:00
{
2023-05-09 19:57:22 +03:00
free ( pStudioHdr - > VertexBase ( ) ) ;
2020-04-22 12:56:21 -04:00
}
free ( pStudioHdr ) ;
}
}
m_StaticProps . Purge ( ) ;
m_StaticPropDict . Purge ( ) ;
}
void ComputeLightmapColor ( dface_t * pFace , Vector & color )
{
texinfo_t * pTex = & texinfo [ pFace - > texinfo ] ;
if ( pTex - > flags & SURF_SKY )
{
// sky ambient already accounted for in direct component
return ;
}
}
bool PositionInSolid ( Vector & position )
{
int ndxLeaf = PointLeafnum ( position ) ;
if ( dleafs [ ndxLeaf ] . contents & CONTENTS_SOLID )
{
// position embedded in solid
return true ;
}
return false ;
}
//-----------------------------------------------------------------------------
// Trace from a vertex to each direct light source, accumulating its contribution.
//-----------------------------------------------------------------------------
void ComputeDirectLightingAtPoint ( Vector & position , Vector & normal , Vector & outColor , int iThread ,
int static_prop_id_to_skip = - 1 , int nLFlags = 0 )
{
SSE_sampleLightOutput_t sampleOutput ;
outColor . Init ( ) ;
// Iterate over all direct lights and accumulate their contribution
int cluster = ClusterFromPoint ( position ) ;
for ( directlight_t * dl = activelights ; dl ! = NULL ; dl = dl - > next )
{
if ( dl - > light . style )
{
// skip lights with style
continue ;
}
// is this lights cluster visible?
if ( ! PVSCheck ( dl - > pvs , cluster ) )
continue ;
// push the vertex towards the light to avoid surface acne
Vector adjusted_pos = position ;
float flEpsilon = 0.0 ;
if ( dl - > light . type ! = emit_skyambient )
{
// push towards the light
Vector fudge ;
if ( dl - > light . type = = emit_skylight )
fudge = - ( dl - > light . normal ) ;
else
{
fudge = dl - > light . origin - position ;
VectorNormalize ( fudge ) ;
}
fudge * = 4.0 ;
adjusted_pos + = fudge ;
}
else
{
// push out along normal
adjusted_pos + = 4.0 * normal ;
// flEpsilon = 1.0;
}
FourVectors adjusted_pos4 ;
FourVectors normal4 ;
adjusted_pos4 . DuplicateVector ( adjusted_pos ) ;
normal4 . DuplicateVector ( normal ) ;
GatherSampleLightSSE ( sampleOutput , dl , - 1 , adjusted_pos4 , & normal4 , 1 , iThread , nLFlags | GATHERLFLAGS_FORCE_FAST ,
static_prop_id_to_skip , flEpsilon ) ;
2023-05-09 19:57:22 +03:00
# ifdef VRAD_SSE
2020-04-22 12:56:21 -04:00
VectorMA ( outColor , sampleOutput . m_flFalloff . m128_f32 [ 0 ] * sampleOutput . m_flDot [ 0 ] . m128_f32 [ 0 ] , dl - > light . intensity , outColor ) ;
2023-05-09 19:57:22 +03:00
# else
VectorMA ( outColor , sampleOutput . m_flFalloff [ 0 ] * sampleOutput . m_flDot [ 0 ] [ 0 ] , dl - > light . intensity , outColor ) ;
# endif
2020-04-22 12:56:21 -04:00
}
}
//-----------------------------------------------------------------------------
// Takes the results from a ComputeLighting call and applies it to the static prop in question.
//-----------------------------------------------------------------------------
void CVradStaticPropMgr : : ApplyLightingToStaticProp ( int iStaticProp , CStaticProp & prop , const CComputeStaticPropLightingResults * pResults )
{
if ( pResults - > m_ColorVertsArrays . Count ( ) = = 0 & & pResults - > m_ColorTexelsArrays . Count ( ) = = 0 )
return ;
StaticPropDict_t & dict = m_StaticPropDict [ prop . m_ModelIdx ] ;
studiohdr_t * pStudioHdr = dict . m_pStudioHdr ;
OptimizedModel : : FileHeader_t * pVtxHdr = ( OptimizedModel : : FileHeader_t * ) dict . m_VtxBuf . Base ( ) ;
Assert ( pStudioHdr & & pVtxHdr ) ;
int iCurColorVertsArray = 0 ;
int iCurColorTexelsArray = 0 ;
for ( int bodyID = 0 ; bodyID < pStudioHdr - > numbodyparts ; + + bodyID )
{
OptimizedModel : : BodyPartHeader_t * pVtxBodyPart = pVtxHdr - > pBodyPart ( bodyID ) ;
mstudiobodyparts_t * pBodyPart = pStudioHdr - > pBodypart ( bodyID ) ;
for ( int modelID = 0 ; modelID < pBodyPart - > nummodels ; + + modelID )
{
OptimizedModel : : ModelHeader_t * pVtxModel = pVtxBodyPart - > pModel ( modelID ) ;
mstudiomodel_t * pStudioModel = pBodyPart - > pModel ( modelID ) ;
const CUtlVector < colorVertex_t > * colorVerts = pResults - > m_ColorVertsArrays . Count ( ) ? pResults - > m_ColorVertsArrays [ iCurColorVertsArray + + ] : nullptr ;
const CUtlVector < colorTexel_t > * colorTexels = pResults - > m_ColorTexelsArrays . Count ( ) ? pResults - > m_ColorTexelsArrays [ iCurColorTexelsArray + + ] : nullptr ;
for ( int nLod = 0 ; nLod < pVtxHdr - > numLODs ; nLod + + )
{
OptimizedModel : : ModelLODHeader_t * pVtxLOD = pVtxModel - > pLOD ( nLod ) ;
for ( int nMesh = 0 ; nMesh < pStudioModel - > nummeshes ; + + nMesh )
{
mstudiomesh_t * pMesh = pStudioModel - > pMesh ( nMesh ) ;
OptimizedModel : : MeshHeader_t * pVtxMesh = pVtxLOD - > pMesh ( nMesh ) ;
for ( int nGroup = 0 ; nGroup < pVtxMesh - > numStripGroups ; + + nGroup )
{
OptimizedModel : : StripGroupHeader_t * pStripGroup = pVtxMesh - > pStripGroup ( nGroup ) ;
int nMeshIdx = prop . m_MeshData . AddToTail ( ) ;
if ( colorVerts )
{
prop . m_MeshData [ nMeshIdx ] . m_VertexColors . AddMultipleToTail ( pStripGroup - > numVerts ) ;
prop . m_MeshData [ nMeshIdx ] . m_nLod = nLod ;
for ( int nVertex = 0 ; nVertex < pStripGroup - > numVerts ; + + nVertex )
{
int nIndex = pMesh - > vertexoffset + pStripGroup - > pVertex ( nVertex ) - > origMeshVertID ;
Assert ( nIndex < pStudioModel - > numvertices ) ;
prop . m_MeshData [ nMeshIdx ] . m_VertexColors [ nVertex ] = ( * colorVerts ) [ nIndex ] . m_Color ;
}
}
if ( colorTexels )
{
// TODO: Consider doing this work in the worker threads, because then we distribute it.
ConvertTexelDataToTexture ( prop . m_LightmapImageWidth , prop . m_LightmapImageHeight , prop . m_LightmapImageFormat , ( * colorTexels ) , & prop . m_MeshData [ nMeshIdx ] . m_TexelsEncoded ) ;
if ( g_bDumpPropLightmaps )
{
char buffer [ _MAX_PATH ] ;
V_snprintf (
buffer ,
_MAX_PATH - 1 ,
" staticprop_lightmap_%d_%.0f_%.0f_%.0f_%s_%d_%d_%d_%d_%d.tga " ,
iStaticProp ,
prop . m_Origin . x ,
prop . m_Origin . y ,
prop . m_Origin . z ,
dict . m_pStudioHdr - > pszName ( ) ,
bodyID ,
modelID ,
nLod ,
nMesh ,
nGroup
) ;
for ( int i = 0 ; buffer [ i ] ; + + i )
{
if ( buffer [ i ] = = ' / ' | | buffer [ i ] = = ' \\ ' )
buffer [ i ] = ' - ' ;
}
DumpLightmapLinear ( buffer , ( * colorTexels ) , prop . m_LightmapImageWidth , prop . m_LightmapImageHeight ) ;
}
}
}
}
}
}
}
}
//-----------------------------------------------------------------------------
// Trace rays from each unique vertex, accumulating direct and indirect
// sources at each ray termination. Use the winding data to distribute the unique vertexes
// into the rendering layout.
//-----------------------------------------------------------------------------
void CVradStaticPropMgr : : ComputeLighting ( CStaticProp & prop , int iThread , int prop_index , CComputeStaticPropLightingResults * pResults )
{
CUtlVector < badVertex_t > badVerts ;
StaticPropDict_t & dict = m_StaticPropDict [ prop . m_ModelIdx ] ;
studiohdr_t * pStudioHdr = dict . m_pStudioHdr ;
OptimizedModel : : FileHeader_t * pVtxHdr = ( OptimizedModel : : FileHeader_t * ) dict . m_VtxBuf . Base ( ) ;
if ( ! pStudioHdr | | ! pVtxHdr )
{
// must have model and its verts for lighting computation
// game will fallback to fullbright
return ;
}
const bool withVertexLighting = ( prop . m_Flags & STATIC_PROP_NO_PER_VERTEX_LIGHTING ) = = 0 ;
const bool withTexelLighting = ( prop . m_Flags & STATIC_PROP_NO_PER_TEXEL_LIGHTING ) = = 0 ;
if ( ! withVertexLighting & & ! withTexelLighting )
return ;
const int skip_prop = ( g_bDisablePropSelfShadowing | | ( prop . m_Flags & STATIC_PROP_NO_SELF_SHADOWING ) ) ? prop_index : - 1 ;
const int nFlags = ( prop . m_Flags & STATIC_PROP_IGNORE_NORMALS ) ? GATHERLFLAGS_IGNORE_NORMALS : 0 ;
2023-05-09 19:57:22 +03:00
# ifdef MPI
2020-04-22 12:56:21 -04:00
VMPI_SetCurrentStage ( " ComputeLighting " ) ;
2023-05-09 19:57:22 +03:00
# endif
2020-04-22 12:56:21 -04:00
matrix3x4_t matPos , matNormal ;
AngleMatrix ( prop . m_Angles , prop . m_Origin , matPos ) ;
AngleMatrix ( prop . m_Angles , matNormal ) ;
for ( int bodyID = 0 ; bodyID < pStudioHdr - > numbodyparts ; + + bodyID )
{
OptimizedModel : : BodyPartHeader_t * pVtxBodyPart = pVtxHdr - > pBodyPart ( bodyID ) ;
mstudiobodyparts_t * pBodyPart = pStudioHdr - > pBodypart ( bodyID ) ;
for ( int modelID = 0 ; modelID < pBodyPart - > nummodels ; + + modelID )
{
OptimizedModel : : ModelHeader_t * pVtxModel = pVtxBodyPart - > pModel ( modelID ) ;
mstudiomodel_t * pStudioModel = pBodyPart - > pModel ( modelID ) ;
if ( withTexelLighting )
{
CUtlVector < colorTexel_t > * pColorTexelArray = new CUtlVector < colorTexel_t > ;
pResults - > m_ColorTexelsArrays . AddToTail ( pColorTexelArray ) ;
}
// light all unique vertexes
CUtlVector < colorVertex_t > * pColorVertsArray = new CUtlVector < colorVertex_t > ;
pResults - > m_ColorVertsArrays . AddToTail ( pColorVertsArray ) ;
CUtlVector < colorVertex_t > & colorVerts = * pColorVertsArray ;
colorVerts . EnsureCount ( pStudioModel - > numvertices ) ;
memset ( colorVerts . Base ( ) , 0 , colorVerts . Count ( ) * sizeof ( colorVertex_t ) ) ;
int numVertexes = 0 ;
for ( int meshID = 0 ; meshID < pStudioModel - > nummeshes ; + + meshID )
{
mstudiomesh_t * pStudioMesh = pStudioModel - > pMesh ( meshID ) ;
const mstudio_meshvertexdata_t * vertData = pStudioMesh - > GetVertexData ( ( void * ) pStudioHdr ) ;
Assert ( vertData ) ; // This can only return NULL on X360 for now
// TODO: Move this into its own function. In fact, refactor this whole function.
if ( withTexelLighting )
{
GenerateLightmapSamplesForMesh ( matPos , matNormal , iThread , skip_prop , nFlags , prop . m_LightmapImageWidth , prop . m_LightmapImageHeight , pStudioHdr , pStudioModel , pVtxModel , meshID , pResults ) ;
}
// If we do lightmapping, we also do vertex lighting as a potential fallback. This may change.
for ( int vertexID = 0 ; vertexID < pStudioMesh - > numvertices ; + + vertexID )
{
Vector sampleNormal ;
Vector samplePosition ;
// transform position and normal into world coordinate system
VectorTransform ( * vertData - > Position ( vertexID ) , matPos , samplePosition ) ;
VectorTransform ( * vertData - > Normal ( vertexID ) , matNormal , sampleNormal ) ;
if ( PositionInSolid ( samplePosition ) )
{
// vertex is in solid, add to the bad list, and recover later
badVertex_t badVertex ;
badVertex . m_ColorVertex = numVertexes ;
badVertex . m_Position = samplePosition ;
badVertex . m_Normal = sampleNormal ;
badVerts . AddToTail ( badVertex ) ;
}
else
{
Vector direct_pos = samplePosition ;
Vector directColor ( 0 , 0 , 0 ) ;
ComputeDirectLightingAtPoint ( direct_pos ,
sampleNormal , directColor , iThread ,
skip_prop , nFlags ) ;
Vector indirectColor ( 0 , 0 , 0 ) ;
if ( g_bShowStaticPropNormals )
{
directColor = sampleNormal ;
directColor + = Vector ( 1.0 , 1.0 , 1.0 ) ;
directColor * = 50.0 ;
}
else
{
if ( numbounce > = 1 )
ComputeIndirectLightingAtPoint (
samplePosition , sampleNormal ,
indirectColor , iThread , true ,
( prop . m_Flags & STATIC_PROP_IGNORE_NORMALS ) ! = 0 ) ;
}
colorVerts [ numVertexes ] . m_bValid = true ;
colorVerts [ numVertexes ] . m_Position = samplePosition ;
VectorAdd ( directColor , indirectColor , colorVerts [ numVertexes ] . m_Color ) ;
}
numVertexes + + ;
}
}
// color in the bad vertexes
// when entire model has no lighting origin and no valid neighbors
// must punt, leave black coloring
if ( badVerts . Count ( ) & & ( prop . m_bLightingOriginValid | | badVerts . Count ( ) ! = numVertexes ) )
{
for ( int nBadVertex = 0 ; nBadVertex < badVerts . Count ( ) ; nBadVertex + + )
{
Vector bestPosition ;
if ( prop . m_bLightingOriginValid )
{
// use the specified lighting origin
VectorCopy ( prop . m_LightingOrigin , bestPosition ) ;
}
else
{
// find the closest valid neighbor
int best = 0 ;
float closest = FLT_MAX ;
for ( int nColorVertex = 0 ; nColorVertex < numVertexes ; nColorVertex + + )
{
if ( ! colorVerts [ nColorVertex ] . m_bValid )
{
// skip invalid neighbors
continue ;
}
Vector delta ;
VectorSubtract ( colorVerts [ nColorVertex ] . m_Position , badVerts [ nBadVertex ] . m_Position , delta ) ;
float distance = VectorLength ( delta ) ;
if ( distance < closest )
{
closest = distance ;
best = nColorVertex ;
}
}
// use the best neighbor as the direction to crawl
VectorCopy ( colorVerts [ best ] . m_Position , bestPosition ) ;
}
// crawl toward best position
// sudivide to determine a closer valid point to the bad vertex, and re-light
Vector midPosition ;
int numIterations = 20 ;
while ( - - numIterations > 0 )
{
VectorAdd ( bestPosition , badVerts [ nBadVertex ] . m_Position , midPosition ) ;
VectorScale ( midPosition , 0.5f , midPosition ) ;
if ( PositionInSolid ( midPosition ) )
break ;
bestPosition = midPosition ;
}
// re-light from better position
Vector directColor ;
ComputeDirectLightingAtPoint ( bestPosition , badVerts [ nBadVertex ] . m_Normal , directColor , iThread ) ;
Vector indirectColor ;
ComputeIndirectLightingAtPoint ( bestPosition , badVerts [ nBadVertex ] . m_Normal ,
indirectColor , iThread , true ) ;
// save results, not changing valid status
// to ensure this offset position is not considered as a viable candidate
colorVerts [ badVerts [ nBadVertex ] . m_ColorVertex ] . m_Position = bestPosition ;
VectorAdd ( directColor , indirectColor , colorVerts [ badVerts [ nBadVertex ] . m_ColorVertex ] . m_Color ) ;
}
}
// discard bad verts
badVerts . Purge ( ) ;
}
}
}
//-----------------------------------------------------------------------------
// Write the lighitng to bsp pak lump
//-----------------------------------------------------------------------------
void CVradStaticPropMgr : : SerializeLighting ( )
{
char filename [ MAX_PATH ] ;
CUtlBuffer utlBuf ;
// illuminate them all
int count = m_StaticProps . Count ( ) ;
if ( ! count )
{
// nothing to do
return ;
}
char mapName [ MAX_PATH ] ;
Q_FileBase ( source , mapName , sizeof ( mapName ) ) ;
int size ;
for ( int i = 0 ; i < count ; + + i )
{
// no need to write this file if we didn't compute the data
// props marked this way will not load the info anyway
if ( m_StaticProps [ i ] . m_Flags & STATIC_PROP_NO_PER_VERTEX_LIGHTING )
continue ;
if ( g_bHDR )
{
sprintf ( filename , " sp_hdr_%d.vhv " , i ) ;
}
else
{
sprintf ( filename , " sp_%d.vhv " , i ) ;
}
int totalVertexes = 0 ;
for ( int j = 0 ; j < m_StaticProps [ i ] . m_MeshData . Count ( ) ; j + + )
{
totalVertexes + = m_StaticProps [ i ] . m_MeshData [ j ] . m_VertexColors . Count ( ) ;
}
// allocate a buffer with enough padding for alignment
size = sizeof ( HardwareVerts : : FileHeader_t ) +
m_StaticProps [ i ] . m_MeshData . Count ( ) * sizeof ( HardwareVerts : : MeshHeader_t ) +
totalVertexes * 4 + 2 * 512 ;
utlBuf . EnsureCapacity ( size ) ;
Q_memset ( utlBuf . Base ( ) , 0 , size ) ;
HardwareVerts : : FileHeader_t * pVhvHdr = ( HardwareVerts : : FileHeader_t * ) utlBuf . Base ( ) ;
// align to start of vertex data
unsigned char * pVertexData = ( unsigned char * ) ( sizeof ( HardwareVerts : : FileHeader_t ) + m_StaticProps [ i ] . m_MeshData . Count ( ) * sizeof ( HardwareVerts : : MeshHeader_t ) ) ;
2023-05-09 19:57:22 +03:00
pVertexData = ( unsigned char * ) pVhvHdr + ALIGN_TO_POW2 ( ( uintp ) pVertexData , 512 ) ;
2020-04-22 12:56:21 -04:00
// construct header
pVhvHdr - > m_nVersion = VHV_VERSION ;
pVhvHdr - > m_nChecksum = m_StaticPropDict [ m_StaticProps [ i ] . m_ModelIdx ] . m_pStudioHdr - > checksum ;
pVhvHdr - > m_nVertexFlags = VERTEX_COLOR ;
pVhvHdr - > m_nVertexSize = 4 ;
pVhvHdr - > m_nVertexes = totalVertexes ;
pVhvHdr - > m_nMeshes = m_StaticProps [ i ] . m_MeshData . Count ( ) ;
for ( int n = 0 ; n < pVhvHdr - > m_nMeshes ; n + + )
{
// construct mesh dictionary
HardwareVerts : : MeshHeader_t * pMesh = pVhvHdr - > pMesh ( n ) ;
pMesh - > m_nLod = m_StaticProps [ i ] . m_MeshData [ n ] . m_nLod ;
pMesh - > m_nVertexes = m_StaticProps [ i ] . m_MeshData [ n ] . m_VertexColors . Count ( ) ;
2023-05-09 19:57:22 +03:00
pMesh - > m_nOffset = ( uintp ) pVertexData - ( uintp ) pVhvHdr ;
2020-04-22 12:56:21 -04:00
// construct vertexes
for ( int k = 0 ; k < pMesh - > m_nVertexes ; k + + )
{
Vector & vertexColor = m_StaticProps [ i ] . m_MeshData [ n ] . m_VertexColors [ k ] ;
ColorRGBExp32 rgbColor ;
VectorToColorRGBExp32 ( vertexColor , rgbColor ) ;
unsigned char dstColor [ 4 ] ;
ConvertRGBExp32ToRGBA8888 ( & rgbColor , dstColor ) ;
// b,g,r,a order
pVertexData [ 0 ] = dstColor [ 2 ] ;
pVertexData [ 1 ] = dstColor [ 1 ] ;
pVertexData [ 2 ] = dstColor [ 0 ] ;
pVertexData [ 3 ] = dstColor [ 3 ] ;
pVertexData + = 4 ;
}
}
// align to end of file
2023-05-09 19:57:22 +03:00
pVertexData = ( unsigned char * ) ( ( uintp ) pVertexData - ( uintp ) pVhvHdr ) ;
pVertexData = ( unsigned char * ) pVhvHdr + ALIGN_TO_POW2 ( ( uintp ) pVertexData , 512 ) ;
2020-04-22 12:56:21 -04:00
AddBufferToPak ( GetPakFile ( ) , filename , ( void * ) pVhvHdr , pVertexData - ( unsigned char * ) pVhvHdr , false ) ;
}
for ( int i = 0 ; i < count ; + + i )
{
const int kAlignment = 512 ;
// no need to write this file if we didn't compute the data
// props marked this way will not load the info anyway
if ( m_StaticProps [ i ] . m_Flags & STATIC_PROP_NO_PER_TEXEL_LIGHTING )
continue ;
sprintf ( filename , " texelslighting_%d.ppl " , i ) ;
ImageFormat fmt = m_StaticProps [ i ] . m_LightmapImageFormat ;
2023-05-09 19:57:22 +03:00
uintp totalTexelSizeBytes = 0 ;
2020-04-22 12:56:21 -04:00
for ( int j = 0 ; j < m_StaticProps [ i ] . m_MeshData . Count ( ) ; j + + )
{
totalTexelSizeBytes + = m_StaticProps [ i ] . m_MeshData [ j ] . m_TexelsEncoded . Count ( ) ;
}
// allocate a buffer with enough padding for alignment
size = sizeof ( HardwareTexels : : FileHeader_t )
+ m_StaticProps [ i ] . m_MeshData . Count ( ) * sizeof ( HardwareTexels : : MeshHeader_t )
+ totalTexelSizeBytes
+ 2 * kAlignment ;
utlBuf . EnsureCapacity ( size ) ;
Q_memset ( utlBuf . Base ( ) , 0 , size ) ;
HardwareTexels : : FileHeader_t * pVhtHdr = ( HardwareTexels : : FileHeader_t * ) utlBuf . Base ( ) ;
// align start of texel data
unsigned char * pTexelData = ( unsigned char * ) ( sizeof ( HardwareTexels : : FileHeader_t ) + m_StaticProps [ i ] . m_MeshData . Count ( ) * sizeof ( HardwareTexels : : MeshHeader_t ) ) ;
2023-05-09 19:57:22 +03:00
pTexelData = ( unsigned char * ) pVhtHdr + ALIGN_TO_POW2 ( ( uintp ) pTexelData , kAlignment ) ;
2020-04-22 12:56:21 -04:00
pVhtHdr - > m_nVersion = VHT_VERSION ;
pVhtHdr - > m_nChecksum = m_StaticPropDict [ m_StaticProps [ i ] . m_ModelIdx ] . m_pStudioHdr - > checksum ;
pVhtHdr - > m_nTexelFormat = fmt ;
pVhtHdr - > m_nMeshes = m_StaticProps [ i ] . m_MeshData . Count ( ) ;
for ( int n = 0 ; n < pVhtHdr - > m_nMeshes ; n + + )
{
HardwareTexels : : MeshHeader_t * pMesh = pVhtHdr - > pMesh ( n ) ;
pMesh - > m_nLod = m_StaticProps [ i ] . m_MeshData [ n ] . m_nLod ;
2023-05-09 19:57:22 +03:00
pMesh - > m_nOffset = ( uintp ) pTexelData - ( uintp ) pVhtHdr ;
2020-04-22 12:56:21 -04:00
pMesh - > m_nBytes = m_StaticProps [ i ] . m_MeshData [ n ] . m_TexelsEncoded . Count ( ) ;
pMesh - > m_nWidth = m_StaticProps [ i ] . m_LightmapImageWidth ;
pMesh - > m_nHeight = m_StaticProps [ i ] . m_LightmapImageHeight ;
Q_memcpy ( pTexelData , m_StaticProps [ i ] . m_MeshData [ n ] . m_TexelsEncoded . Base ( ) , m_StaticProps [ i ] . m_MeshData [ n ] . m_TexelsEncoded . Count ( ) ) ;
pTexelData + = m_StaticProps [ i ] . m_MeshData [ n ] . m_TexelsEncoded . Count ( ) ;
}
2023-05-09 19:57:22 +03:00
pTexelData = ( unsigned char * ) ( ( uintp ) pTexelData - ( uintp ) pVhtHdr ) ;
pTexelData = ( unsigned char * ) pVhtHdr + ALIGN_TO_POW2 ( ( uintp ) pTexelData , kAlignment ) ;
2020-04-22 12:56:21 -04:00
AddBufferToPak ( GetPakFile ( ) , filename , ( void * ) pVhtHdr , pTexelData - ( unsigned char * ) pVhtHdr , false ) ;
}
}
2023-05-09 19:57:22 +03:00
# ifdef MPI
2020-04-22 12:56:21 -04:00
void CVradStaticPropMgr : : VMPI_ProcessStaticProp_Static ( int iThread , uint64 iStaticProp , MessageBuffer * pBuf )
{
g_StaticPropMgr . VMPI_ProcessStaticProp ( iThread , iStaticProp , pBuf ) ;
}
void CVradStaticPropMgr : : VMPI_ReceiveStaticPropResults_Static ( uint64 iStaticProp , MessageBuffer * pBuf , int iWorker )
{
g_StaticPropMgr . VMPI_ReceiveStaticPropResults ( iStaticProp , pBuf , iWorker ) ;
}
//-----------------------------------------------------------------------------
// Called on workers to do the computation for a static prop and send
// it to the master.
//-----------------------------------------------------------------------------
void CVradStaticPropMgr : : VMPI_ProcessStaticProp ( int iThread , int iStaticProp , MessageBuffer * pBuf )
{
// Compute the lighting.
CComputeStaticPropLightingResults results ;
ComputeLighting ( m_StaticProps [ iStaticProp ] , iThread , iStaticProp , & results ) ;
VMPI_SetCurrentStage ( " EncodeLightingResults " ) ;
// Encode the results.
int nLists = results . m_ColorVertsArrays . Count ( ) ;
pBuf - > write ( & nLists , sizeof ( nLists ) ) ;
for ( int i = 0 ; i < nLists ; i + + )
{
CUtlVector < colorVertex_t > & curList = * results . m_ColorVertsArrays [ i ] ;
int count = curList . Count ( ) ;
pBuf - > write ( & count , sizeof ( count ) ) ;
pBuf - > write ( curList . Base ( ) , curList . Count ( ) * sizeof ( colorVertex_t ) ) ;
}
nLists = results . m_ColorTexelsArrays . Count ( ) ;
pBuf - > write ( & nLists , sizeof ( nLists ) ) ;
for ( int i = 0 ; i < nLists ; i + + )
{
CUtlVector < colorTexel_t > & curList = * results . m_ColorTexelsArrays [ i ] ;
int count = curList . Count ( ) ;
pBuf - > write ( & count , sizeof ( count ) ) ;
pBuf - > write ( curList . Base ( ) , curList . Count ( ) * sizeof ( colorTexel_t ) ) ;
}
}
//-----------------------------------------------------------------------------
// Called on the master when a worker finishes processing a static prop.
//-----------------------------------------------------------------------------
void CVradStaticPropMgr : : VMPI_ReceiveStaticPropResults ( int iStaticProp , MessageBuffer * pBuf , int iWorker )
{
// Read in the results.
CComputeStaticPropLightingResults results ;
int nLists ;
pBuf - > read ( & nLists , sizeof ( nLists ) ) ;
for ( int i = 0 ; i < nLists ; i + + )
{
CUtlVector < colorVertex_t > * pList = new CUtlVector < colorVertex_t > ;
results . m_ColorVertsArrays . AddToTail ( pList ) ;
int count ;
pBuf - > read ( & count , sizeof ( count ) ) ;
pList - > SetSize ( count ) ;
pBuf - > read ( pList - > Base ( ) , count * sizeof ( colorVertex_t ) ) ;
}
pBuf - > read ( & nLists , sizeof ( nLists ) ) ;
for ( int i = 0 ; i < nLists ; i + + )
{
CUtlVector < colorTexel_t > * pList = new CUtlVector < colorTexel_t > ;
results . m_ColorTexelsArrays . AddToTail ( pList ) ;
int count ;
pBuf - > read ( & count , sizeof ( count ) ) ;
pList - > SetSize ( count ) ;
pBuf - > read ( pList - > Base ( ) , count * sizeof ( colorTexel_t ) ) ;
}
// Apply the results.
ApplyLightingToStaticProp ( iStaticProp , m_StaticProps [ iStaticProp ] , & results ) ;
}
2023-05-09 19:57:22 +03:00
# endif
2020-04-22 12:56:21 -04:00
void CVradStaticPropMgr : : ComputeLightingForProp ( int iThread , int iStaticProp )
{
// Compute the lighting.
CComputeStaticPropLightingResults results ;
ComputeLighting ( m_StaticProps [ iStaticProp ] , iThread , iStaticProp , & results ) ;
ApplyLightingToStaticProp ( iStaticProp , m_StaticProps [ iStaticProp ] , & results ) ;
}
void CVradStaticPropMgr : : ThreadComputeStaticPropLighting ( int iThread , void * pUserData )
{
while ( 1 )
{
int j = GetThreadWork ( ) ;
if ( j = = - 1 )
break ;
CComputeStaticPropLightingResults results ;
g_StaticPropMgr . ComputeLightingForProp ( iThread , j ) ;
}
}
//-----------------------------------------------------------------------------
// Computes lighting for the static props.
// Must be after all other surface lighting has been computed for the indirect sampling.
//-----------------------------------------------------------------------------
void CVradStaticPropMgr : : ComputeLighting ( int iThread )
{
// illuminate them all
int count = m_StaticProps . Count ( ) ;
if ( ! count )
{
// nothing to do
return ;
}
StartPacifier ( " Computing static prop lighting : " ) ;
// ensure any traces against us are ignored because we have no inherit lighting contribution
m_bIgnoreStaticPropTrace = true ;
2023-05-09 19:57:22 +03:00
# ifdef MPI
2020-04-22 12:56:21 -04:00
if ( g_bUseMPI )
{
// Distribute the work among the workers.
VMPI_SetCurrentStage ( " CVradStaticPropMgr::ComputeLighting " ) ;
DistributeWork (
count ,
VMPI_DISTRIBUTEWORK_PACKETID ,
& CVradStaticPropMgr : : VMPI_ProcessStaticProp_Static ,
& CVradStaticPropMgr : : VMPI_ReceiveStaticPropResults_Static ) ;
}
else
2023-05-09 19:57:22 +03:00
# endif
2020-04-22 12:56:21 -04:00
{
RunThreadsOn ( count , true , ThreadComputeStaticPropLighting ) ;
}
// restore default
m_bIgnoreStaticPropTrace = false ;
// save data to bsp
SerializeLighting ( ) ;
EndPacifier ( true ) ;
}
//-----------------------------------------------------------------------------
// Adds all static prop polys to the ray trace store.
//-----------------------------------------------------------------------------
void CVradStaticPropMgr : : AddPolysForRayTrace ( void )
{
int count = m_StaticProps . Count ( ) ;
if ( ! count )
{
// nothing to do
return ;
}
// Triangle coverage of 1 (full coverage)
Vector fullCoverage ;
fullCoverage . x = 1.0f ;
for ( int nProp = 0 ; nProp < count ; + + nProp )
{
CStaticProp & prop = m_StaticProps [ nProp ] ;
StaticPropDict_t & dict = m_StaticPropDict [ prop . m_ModelIdx ] ;
if ( prop . m_Flags & STATIC_PROP_NO_SHADOW )
continue ;
// If not using static prop polys, use AABB
if ( ! g_bStaticPropPolys )
{
if ( dict . m_pModel )
{
VMatrix xform ;
xform . SetupMatrixOrgAngles ( prop . m_Origin , prop . m_Angles ) ;
ICollisionQuery * queryModel = s_pPhysCollision - > CreateQueryModel ( dict . m_pModel ) ;
for ( int nConvex = 0 ; nConvex < queryModel - > ConvexCount ( ) ; + + nConvex )
{
for ( int nTri = 0 ; nTri < queryModel - > TriangleCount ( nConvex ) ; + + nTri )
{
Vector verts [ 3 ] ;
queryModel - > GetTriangleVerts ( nConvex , nTri , verts ) ;
for ( int nVert = 0 ; nVert < 3 ; + + nVert )
verts [ nVert ] = xform . VMul4x3 ( verts [ nVert ] ) ;
g_RtEnv . AddTriangle ( TRACE_ID_STATICPROP | nProp , verts [ 0 ] , verts [ 1 ] , verts [ 2 ] , fullCoverage ) ;
}
}
s_pPhysCollision - > DestroyQueryModel ( queryModel ) ;
}
else
{
VectorAdd ( dict . m_Mins , prop . m_Origin , prop . m_mins ) ;
VectorAdd ( dict . m_Maxs , prop . m_Origin , prop . m_maxs ) ;
g_RtEnv . AddAxisAlignedRectangularSolid ( TRACE_ID_STATICPROP | nProp , prop . m_mins , prop . m_maxs , fullCoverage ) ;
}
continue ;
}
studiohdr_t * pStudioHdr = dict . m_pStudioHdr ;
OptimizedModel : : FileHeader_t * pVtxHdr = ( OptimizedModel : : FileHeader_t * ) dict . m_VtxBuf . Base ( ) ;
if ( ! pStudioHdr | | ! pVtxHdr )
{
// must have model and its verts for decoding triangles
return ;
}
// only init the triangle table the first time
bool bInitTriangles = dict . m_triangleMaterialIndex . Count ( ) ? false : true ;
int triangleIndex = 0 ;
// meshes are deeply hierarchial, divided between three stores, follow the white rabbit
// body parts -> models -> lod meshes -> strip groups -> strips
// the vertices and indices are pooled, the trick is knowing the offset to determine your indexed base
for ( int bodyID = 0 ; bodyID < pStudioHdr - > numbodyparts ; + + bodyID )
{
OptimizedModel : : BodyPartHeader_t * pVtxBodyPart = pVtxHdr - > pBodyPart ( bodyID ) ;
mstudiobodyparts_t * pBodyPart = pStudioHdr - > pBodypart ( bodyID ) ;
for ( int modelID = 0 ; modelID < pBodyPart - > nummodels ; + + modelID )
{
OptimizedModel : : ModelHeader_t * pVtxModel = pVtxBodyPart - > pModel ( modelID ) ;
mstudiomodel_t * pStudioModel = pBodyPart - > pModel ( modelID ) ;
// assuming lod 0, could iterate if required
int nLod = 0 ;
OptimizedModel : : ModelLODHeader_t * pVtxLOD = pVtxModel - > pLOD ( nLod ) ;
for ( int nMesh = 0 ; nMesh < pStudioModel - > nummeshes ; + + nMesh )
{
// check if this mesh's material is in the no shadow material name list
mstudiomesh_t * pMesh = pStudioModel - > pMesh ( nMesh ) ;
mstudiotexture_t * pTxtr = pStudioHdr - > pTexture ( pMesh - > material ) ;
//printf("mat idx=%d mat name=%s\n",pMesh->material,pTxtr->pszName());
bool bSkipThisMesh = false ;
for ( int check = 0 ; check < g_NonShadowCastingMaterialStrings . Count ( ) ; check + + )
{
if ( Q_stristr ( pTxtr - > pszName ( ) ,
g_NonShadowCastingMaterialStrings [ check ] ) )
{
//printf("skip mat name=%s\n",pTxtr->pszName());
bSkipThisMesh = true ;
break ;
}
}
if ( bSkipThisMesh )
continue ;
int shadowTextureIndex = - 1 ;
if ( dict . m_textureShadowIndex . Count ( ) )
{
shadowTextureIndex = dict . m_textureShadowIndex [ pMesh - > material ] ;
}
OptimizedModel : : MeshHeader_t * pVtxMesh = pVtxLOD - > pMesh ( nMesh ) ;
const mstudio_meshvertexdata_t * vertData = pMesh - > GetVertexData ( ( void * ) pStudioHdr ) ;
Assert ( vertData ) ; // This can only return NULL on X360 for now
for ( int nGroup = 0 ; nGroup < pVtxMesh - > numStripGroups ; + + nGroup )
{
OptimizedModel : : StripGroupHeader_t * pStripGroup = pVtxMesh - > pStripGroup ( nGroup ) ;
int nStrip ;
for ( nStrip = 0 ; nStrip < pStripGroup - > numStrips ; nStrip + + )
{
OptimizedModel : : StripHeader_t * pStrip = pStripGroup - > pStrip ( nStrip ) ;
if ( pStrip - > flags & OptimizedModel : : STRIP_IS_TRILIST )
{
for ( int i = 0 ; i < pStrip - > numIndices ; i + = 3 )
{
int idx = pStrip - > indexOffset + i ;
unsigned short i1 = * pStripGroup - > pIndex ( idx ) ;
unsigned short i2 = * pStripGroup - > pIndex ( idx + 1 ) ;
unsigned short i3 = * pStripGroup - > pIndex ( idx + 2 ) ;
int vertex1 = pStripGroup - > pVertex ( i1 ) - > origMeshVertID ;
int vertex2 = pStripGroup - > pVertex ( i2 ) - > origMeshVertID ;
int vertex3 = pStripGroup - > pVertex ( i3 ) - > origMeshVertID ;
// transform position into world coordinate system
matrix3x4_t matrix ;
AngleMatrix ( prop . m_Angles , prop . m_Origin , matrix ) ;
Vector position1 ;
Vector position2 ;
Vector position3 ;
VectorTransform ( * vertData - > Position ( vertex1 ) , matrix , position1 ) ;
VectorTransform ( * vertData - > Position ( vertex2 ) , matrix , position2 ) ;
VectorTransform ( * vertData - > Position ( vertex3 ) , matrix , position3 ) ;
unsigned short flags = 0 ;
int materialIndex = - 1 ;
Vector color = vec3_origin ;
if ( shadowTextureIndex > = 0 )
{
if ( bInitTriangles )
{
// add texture space and texture index to material database
// now
float coverage = g_ShadowTextureList . ComputeCoverageForTriangle ( shadowTextureIndex , * vertData - > Texcoord ( vertex1 ) , * vertData - > Texcoord ( vertex2 ) , * vertData - > Texcoord ( vertex3 ) ) ;
if ( coverage < 1.0f )
{
materialIndex = g_ShadowTextureList . AddMaterialEntry ( shadowTextureIndex , * vertData - > Texcoord ( vertex1 ) , * vertData - > Texcoord ( vertex2 ) , * vertData - > Texcoord ( vertex3 ) ) ;
color . x = coverage ;
}
else
{
materialIndex = - 1 ;
}
dict . m_triangleMaterialIndex . AddToTail ( materialIndex ) ;
}
else
{
materialIndex = dict . m_triangleMaterialIndex [ triangleIndex ] ;
triangleIndex + + ;
}
if ( materialIndex > = 0 )
{
flags = FCACHETRI_TRANSPARENT ;
}
}
// printf( "\ngl 3\n" );
// printf( "gl %6.3f %6.3f %6.3f 1 0 0\n", XYZ(position1));
// printf( "gl %6.3f %6.3f %6.3f 0 1 0\n", XYZ(position2));
// printf( "gl %6.3f %6.3f %6.3f 0 0 1\n", XYZ(position3));
g_RtEnv . AddTriangle ( TRACE_ID_STATICPROP | nProp ,
position1 , position2 , position3 ,
color , flags , materialIndex ) ;
}
}
else
{
// all tris expected to be discrete tri lists
// must fixme if stripping ever occurs
printf ( " unexpected strips found \n " ) ;
Assert ( 0 ) ;
return ;
}
}
}
}
}
}
}
}
struct tl_tri_t
{
Vector p0 ;
Vector p1 ;
Vector p2 ;
Vector n0 ;
Vector n1 ;
Vector n2 ;
bool operator = = ( const tl_tri_t & t ) const
{
return ( p0 = = t . p0 & &
p1 = = t . p1 & &
p2 = = t . p2 & &
n0 = = t . n0 & &
n1 = = t . n1 & &
n2 = = t . n2 ) ;
}
} ;
struct tl_vert_t
{
Vector m_position ;
CUtlLinkedList < tl_tri_t , int > m_triList ;
} ;
void AddTriVertsToList ( CUtlVector < tl_vert_t > & triListVerts , int vertIndex , Vector vertPosition , Vector p0 , Vector p1 , Vector p2 , Vector n0 , Vector n1 , Vector n2 )
{
tl_tri_t tlTri ;
tlTri . p0 = p0 ;
tlTri . p1 = p1 ;
tlTri . p2 = p2 ;
tlTri . n0 = n0 ;
tlTri . n1 = n1 ;
tlTri . n2 = n2 ;
triListVerts . EnsureCapacity ( vertIndex + 1 ) ;
triListVerts [ vertIndex ] . m_position = vertPosition ;
int index = triListVerts [ vertIndex ] . m_triList . Find ( tlTri ) ;
if ( ! triListVerts [ vertIndex ] . m_triList . IsValidIndex ( index ) )
{
// not in list, add to list of triangles
triListVerts [ vertIndex ] . m_triList . AddToTail ( tlTri ) ;
}
}
//-----------------------------------------------------------------------------
// Builds a list of tris for every vertex
//-----------------------------------------------------------------------------
void CVradStaticPropMgr : : BuildTriList ( CStaticProp & prop )
{
// the generated list will consist of a list of verts
// each vert will have a linked list of triangles that it belongs to
CUtlVector < tl_vert_t > triListVerts ;
StaticPropDict_t & dict = m_StaticPropDict [ prop . m_ModelIdx ] ;
studiohdr_t * pStudioHdr = dict . m_pStudioHdr ;
OptimizedModel : : FileHeader_t * pVtxHdr = ( OptimizedModel : : FileHeader_t * ) dict . m_VtxBuf . Base ( ) ;
if ( ! pStudioHdr | | ! pVtxHdr )
{
// must have model and its verts for decoding triangles
return ;
}
// meshes are deeply hierarchial, divided between three stores, follow the white rabbit
// body parts -> models -> lod meshes -> strip groups -> strips
// the vertices and indices are pooled, the trick is knowing the offset to determine your indexed base
for ( int bodyID = 0 ; bodyID < pStudioHdr - > numbodyparts ; + + bodyID )
{
OptimizedModel : : BodyPartHeader_t * pVtxBodyPart = pVtxHdr - > pBodyPart ( bodyID ) ;
mstudiobodyparts_t * pBodyPart = pStudioHdr - > pBodypart ( bodyID ) ;
for ( int modelID = 0 ; modelID < pBodyPart - > nummodels ; + + modelID )
{
OptimizedModel : : ModelHeader_t * pVtxModel = pVtxBodyPart - > pModel ( modelID ) ;
mstudiomodel_t * pStudioModel = pBodyPart - > pModel ( modelID ) ;
// get the specified lod, assuming lod 0
int nLod = 0 ;
OptimizedModel : : ModelLODHeader_t * pVtxLOD = pVtxModel - > pLOD ( nLod ) ;
// must reset because each model has their own vertexes [0..n]
// in order for this to be monolithic for the entire prop the list must be segmented
triListVerts . Purge ( ) ;
for ( int nMesh = 0 ; nMesh < pStudioModel - > nummeshes ; + + nMesh )
{
mstudiomesh_t * pMesh = pStudioModel - > pMesh ( nMesh ) ;
OptimizedModel : : MeshHeader_t * pVtxMesh = pVtxLOD - > pMesh ( nMesh ) ;
const mstudio_meshvertexdata_t * vertData = pMesh - > GetVertexData ( ( void * ) pStudioHdr ) ;
Assert ( vertData ) ; // This can only return NULL on X360 for now
for ( int nGroup = 0 ; nGroup < pVtxMesh - > numStripGroups ; + + nGroup )
{
OptimizedModel : : StripGroupHeader_t * pStripGroup = pVtxMesh - > pStripGroup ( nGroup ) ;
int nStrip ;
for ( nStrip = 0 ; nStrip < pStripGroup - > numStrips ; nStrip + + )
{
OptimizedModel : : StripHeader_t * pStrip = pStripGroup - > pStrip ( nStrip ) ;
if ( pStrip - > flags & OptimizedModel : : STRIP_IS_TRILIST )
{
for ( int i = 0 ; i < pStrip - > numIndices ; i + = 3 )
{
int idx = pStrip - > indexOffset + i ;
unsigned short i1 = * pStripGroup - > pIndex ( idx ) ;
unsigned short i2 = * pStripGroup - > pIndex ( idx + 1 ) ;
unsigned short i3 = * pStripGroup - > pIndex ( idx + 2 ) ;
int vertex1 = pStripGroup - > pVertex ( i1 ) - > origMeshVertID ;
int vertex2 = pStripGroup - > pVertex ( i2 ) - > origMeshVertID ;
int vertex3 = pStripGroup - > pVertex ( i3 ) - > origMeshVertID ;
// transform position into world coordinate system
matrix3x4_t matrix ;
AngleMatrix ( prop . m_Angles , prop . m_Origin , matrix ) ;
Vector position1 ;
Vector position2 ;
Vector position3 ;
VectorTransform ( * vertData - > Position ( vertex1 ) , matrix , position1 ) ;
VectorTransform ( * vertData - > Position ( vertex2 ) , matrix , position2 ) ;
VectorTransform ( * vertData - > Position ( vertex3 ) , matrix , position3 ) ;
Vector normal1 ;
Vector normal2 ;
Vector normal3 ;
VectorTransform ( * vertData - > Normal ( vertex1 ) , matrix , normal1 ) ;
VectorTransform ( * vertData - > Normal ( vertex2 ) , matrix , normal2 ) ;
VectorTransform ( * vertData - > Normal ( vertex3 ) , matrix , normal3 ) ;
AddTriVertsToList ( triListVerts , pMesh - > vertexoffset + vertex1 , position1 , position1 , position2 , position3 , normal1 , normal2 , normal3 ) ;
AddTriVertsToList ( triListVerts , pMesh - > vertexoffset + vertex2 , position2 , position1 , position2 , position3 , normal1 , normal2 , normal3 ) ;
AddTriVertsToList ( triListVerts , pMesh - > vertexoffset + vertex3 , position3 , position1 , position2 , position3 , normal1 , normal2 , normal3 ) ;
}
}
else
{
// all tris expected to be discrete tri lists
// must fixme if stripping ever occurs
printf ( " unexpected strips found \n " ) ;
Assert ( 0 ) ;
return ;
}
}
}
}
}
}
}
const vertexFileHeader_t * mstudiomodel_t : : CacheVertexData ( void * pModelData )
{
studiohdr_t * pActiveStudioHdr = static_cast < studiohdr_t * > ( pModelData ) ;
Assert ( pActiveStudioHdr ) ;
2023-05-09 19:57:22 +03:00
if ( pActiveStudioHdr - > VertexBase ( ) )
2020-04-22 12:56:21 -04:00
{
2023-05-09 19:57:22 +03:00
return ( vertexFileHeader_t * ) pActiveStudioHdr - > VertexBase ( ) ;
2020-04-22 12:56:21 -04:00
}
// mandatory callback to make requested data resident
// load and persist the vertex file
char fileName [ MAX_PATH ] ;
strcpy ( fileName , " models/ " ) ;
strcat ( fileName , pActiveStudioHdr - > pszName ( ) ) ;
Q_StripExtension ( fileName , fileName , sizeof ( fileName ) ) ;
strcat ( fileName , " .vvd " ) ;
// load the model
FileHandle_t fileHandle = g_pFileSystem - > Open ( fileName , " rb " ) ;
if ( ! fileHandle )
{
Error ( " Unable to load vertex data \" %s \" \n " , fileName ) ;
}
// Get the file size
int vvdSize = g_pFileSystem - > Size ( fileHandle ) ;
if ( vvdSize = = 0 )
{
g_pFileSystem - > Close ( fileHandle ) ;
Error ( " Bad size for vertex data \" %s \" \n " , fileName ) ;
}
vertexFileHeader_t * pVvdHdr = ( vertexFileHeader_t * ) malloc ( vvdSize ) ;
g_pFileSystem - > Read ( pVvdHdr , vvdSize , fileHandle ) ;
g_pFileSystem - > Close ( fileHandle ) ;
// check header
if ( pVvdHdr - > id ! = MODEL_VERTEX_FILE_ID )
{
Error ( " Error Vertex File %s id %d should be %d \n " , fileName , pVvdHdr - > id , MODEL_VERTEX_FILE_ID ) ;
}
if ( pVvdHdr - > version ! = MODEL_VERTEX_FILE_VERSION )
{
Error ( " Error Vertex File %s version %d should be %d \n " , fileName , pVvdHdr - > version , MODEL_VERTEX_FILE_VERSION ) ;
}
if ( pVvdHdr - > checksum ! = pActiveStudioHdr - > checksum )
{
Error ( " Error Vertex File %s checksum %d should be %d \n " , fileName , pVvdHdr - > checksum , pActiveStudioHdr - > checksum ) ;
}
// need to perform mesh relocation fixups
// allocate a new copy
vertexFileHeader_t * pNewVvdHdr = ( vertexFileHeader_t * ) malloc ( vvdSize ) ;
if ( ! pNewVvdHdr )
{
Error ( " Error allocating %d bytes for Vertex File '%s' \n " , vvdSize , fileName ) ;
}
// load vertexes and run fixups
Studio_LoadVertexes ( pVvdHdr , pNewVvdHdr , 0 , true ) ;
// discard original
free ( pVvdHdr ) ;
pVvdHdr = pNewVvdHdr ;
2023-05-09 19:57:22 +03:00
pActiveStudioHdr - > SetVertexBase ( ( void * ) pVvdHdr ) ;
2020-04-22 12:56:21 -04:00
return pVvdHdr ;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
struct ColorTexelValue
{
Vector mLinearColor ; // Linear color value for this texel
bool mValidData ; // Whether there is valid data in this texel.
size_t mTriangleIndex ; // Which triangle we used to generate the texel.
} ;
// ------------------------------------------------------------------------------------------------
inline int ComputeLinearPos ( int _x , int _y , int _resX , int _resY )
{
2023-05-09 19:57:22 +03:00
return MIN ( MAX ( 0 , _y ) , _resY - 1 ) * _resX
+ MIN ( MAX ( 0 , _x ) , _resX - 1 ) ;
2020-04-22 12:56:21 -04:00
}
// ------------------------------------------------------------------------------------------------
inline float ComputeBarycentricDistanceToTri ( Vector _barycentricCoord , Vector2D _v [ 3 ] )
{
Vector2D realPos = _barycentricCoord . x * _v [ 0 ]
+ _barycentricCoord . y * _v [ 1 ]
+ _barycentricCoord . z * _v [ 2 ] ;
int minIndex = 0 ;
float minVal = _barycentricCoord [ 0 ] ;
for ( int i = 1 ; i < 3 ; + + i ) {
if ( _barycentricCoord [ i ] < minVal ) {
minVal = _barycentricCoord [ i ] ;
minIndex = i ;
}
}
Vector2D & first = _v [ ( minIndex + 1 ) % 3 ] ;
Vector2D & second = _v [ ( minIndex + 2 ) % 3 ] ;
return CalcDistanceToLineSegment2D ( realPos , first , second ) ;
}
// ------------------------------------------------------------------------------------------------
static void GenerateLightmapSamplesForMesh ( const matrix3x4_t & _matPos , const matrix3x4_t & _matNormal , int _iThread , int _skipProp , int _flags , int _lightmapResX , int _lightmapResY , studiohdr_t * _pStudioHdr , mstudiomodel_t * _pStudioModel , OptimizedModel : : ModelHeader_t * _pVtxModel , int _meshID , CComputeStaticPropLightingResults * _outResults )
{
// Could iterate and gen this if needed.
int nLod = 0 ;
OptimizedModel : : ModelLODHeader_t * pVtxLOD = _pVtxModel - > pLOD ( nLod ) ;
CUtlVector < colorTexel_t > & colorTexels = ( * _outResults - > m_ColorTexelsArrays . Tail ( ) ) ;
const int cTotalPixelCount = _lightmapResX * _lightmapResY ;
colorTexels . EnsureCount ( cTotalPixelCount ) ;
memset ( colorTexels . Base ( ) , 0 , colorTexels . Count ( ) * sizeof ( colorTexel_t ) ) ;
for ( int i = 0 ; i < colorTexels . Count ( ) ; + + i ) {
colorTexels [ i ] . m_fDistanceToTri = FLT_MAX ;
}
mstudiomesh_t * pMesh = _pStudioModel - > pMesh ( _meshID ) ;
OptimizedModel : : MeshHeader_t * pVtxMesh = pVtxLOD - > pMesh ( _meshID ) ;
const mstudio_meshvertexdata_t * vertData = pMesh - > GetVertexData ( ( void * ) _pStudioHdr ) ;
Assert ( vertData ) ; // This can only return NULL on X360 for now
for ( int nGroup = 0 ; nGroup < pVtxMesh - > numStripGroups ; + + nGroup )
{
OptimizedModel : : StripGroupHeader_t * pStripGroup = pVtxMesh - > pStripGroup ( nGroup ) ;
int nStrip ;
for ( nStrip = 0 ; nStrip < pStripGroup - > numStrips ; nStrip + + )
{
OptimizedModel : : StripHeader_t * pStrip = pStripGroup - > pStrip ( nStrip ) ;
// If this hits, re-factor the code to iterate over triangles, and build the triangles
// from the underlying structures.
Assert ( ( pStrip - > flags & OptimizedModel : : STRIP_IS_TRISTRIP ) = = 0 ) ;
if ( pStrip - > flags & OptimizedModel : : STRIP_IS_TRILIST )
{
for ( int i = 0 ; i < pStrip - > numIndices ; i + = 3 )
{
int idx = pStrip - > indexOffset + i ;
unsigned short i1 = * pStripGroup - > pIndex ( idx ) ;
unsigned short i2 = * pStripGroup - > pIndex ( idx + 1 ) ;
unsigned short i3 = * pStripGroup - > pIndex ( idx + 2 ) ;
int vertex1 = pStripGroup - > pVertex ( i1 ) - > origMeshVertID ;
int vertex2 = pStripGroup - > pVertex ( i2 ) - > origMeshVertID ;
int vertex3 = pStripGroup - > pVertex ( i3 ) - > origMeshVertID ;
Vector modelPos [ 3 ] = {
* vertData - > Position ( vertex1 ) ,
* vertData - > Position ( vertex2 ) ,
* vertData - > Position ( vertex3 )
} ;
Vector modelNormal [ 3 ] = {
* vertData - > Normal ( vertex1 ) ,
* vertData - > Normal ( vertex2 ) ,
* vertData - > Normal ( vertex3 )
} ;
Vector worldPos [ 3 ] ;
Vector worldNormal [ 3 ] ;
VectorTransform ( modelPos [ 0 ] , _matPos , worldPos [ 0 ] ) ;
VectorTransform ( modelPos [ 1 ] , _matPos , worldPos [ 1 ] ) ;
VectorTransform ( modelPos [ 2 ] , _matPos , worldPos [ 2 ] ) ;
VectorTransform ( modelNormal [ 0 ] , _matNormal , worldNormal [ 0 ] ) ;
VectorTransform ( modelNormal [ 1 ] , _matNormal , worldNormal [ 1 ] ) ;
VectorTransform ( modelNormal [ 2 ] , _matNormal , worldNormal [ 2 ] ) ;
Vector2D texcoord [ 3 ] = {
* vertData - > Texcoord ( vertex1 ) ,
* vertData - > Texcoord ( vertex2 ) ,
* vertData - > Texcoord ( vertex3 )
} ;
Rasterizer rasterizer ( texcoord [ 0 ] , texcoord [ 1 ] , texcoord [ 2 ] ,
_lightmapResX , _lightmapResY ) ;
for ( auto it = rasterizer . begin ( ) ; it ! = rasterizer . end ( ) ; + + it )
{
size_t linearPos = rasterizer . GetLinearPos ( it ) ;
Assert ( linearPos < cTotalPixelCount ) ;
if ( colorTexels [ linearPos ] . m_bValid )
{
continue ;
}
float ourDistancetoTri = ComputeBarycentricDistanceToTri ( it - > barycentric , texcoord ) ;
bool doWrite = it - > insideTriangle
| | ! colorTexels [ linearPos ] . m_bPossiblyInteresting
| | colorTexels [ linearPos ] . m_fDistanceToTri > ourDistancetoTri ;
if ( doWrite )
{
Vector itWorldPos = worldPos [ 0 ] * it - > barycentric . x
+ worldPos [ 1 ] * it - > barycentric . y
+ worldPos [ 2 ] * it - > barycentric . z ;
Vector itWorldNormal = worldNormal [ 0 ] * it - > barycentric . x
+ worldNormal [ 1 ] * it - > barycentric . y
+ worldNormal [ 2 ] * it - > barycentric . z ;
itWorldNormal . NormalizeInPlace ( ) ;
colorTexels [ linearPos ] . m_WorldPosition = itWorldPos ;
colorTexels [ linearPos ] . m_WorldNormal = itWorldNormal ;
colorTexels [ linearPos ] . m_bValid = it - > insideTriangle ;
colorTexels [ linearPos ] . m_bPossiblyInteresting = true ;
colorTexels [ linearPos ] . m_fDistanceToTri = ourDistancetoTri ;
}
}
}
}
}
}
// Process neighbors to the valid region. Walk through the existing array, look for samples that
// are not valid but are adjacent to valid samples. Works if we are only bilinearly sampling
// on the other side.
// First attempt: Just pretend the triangle was larger and cast a ray from this new world pos
// as above.
int linearPos = 0 ;
for ( int j = 0 ; j < _lightmapResY ; + + j )
{
for ( int i = 0 ; i < _lightmapResX ; + + i )
{
bool shouldProcess = colorTexels [ linearPos ] . m_bValid ;
// Are any of the eight neighbors valid??
if ( colorTexels [ linearPos ] . m_bPossiblyInteresting )
{
// Look at our neighborhood (3x3 centerd on us).
shouldProcess = shouldProcess
| | colorTexels [ ComputeLinearPos ( i - 1 , j - 1 , _lightmapResX , _lightmapResY ) ] . m_bValid // TL
| | colorTexels [ ComputeLinearPos ( i , j - 1 , _lightmapResX , _lightmapResY ) ] . m_bValid // T
| | colorTexels [ ComputeLinearPos ( i + 1 , j - 1 , _lightmapResX , _lightmapResY ) ] . m_bValid // TR
| | colorTexels [ ComputeLinearPos ( i - 1 , j , _lightmapResX , _lightmapResY ) ] . m_bValid // L
| | colorTexels [ ComputeLinearPos ( i + 1 , j , _lightmapResX , _lightmapResY ) ] . m_bValid // R
| | colorTexels [ ComputeLinearPos ( i - 1 , j + 1 , _lightmapResX , _lightmapResY ) ] . m_bValid // BL
| | colorTexels [ ComputeLinearPos ( i , j + 1 , _lightmapResX , _lightmapResY ) ] . m_bValid // B
| | colorTexels [ ComputeLinearPos ( i + 1 , j + 1 , _lightmapResX , _lightmapResY ) ] . m_bValid ; // BR
}
if ( shouldProcess )
{
Vector directColor ( 0 , 0 , 0 ) ,
indirectColor ( 0 , 0 , 0 ) ;
ComputeDirectLightingAtPoint ( colorTexels [ linearPos ] . m_WorldPosition , colorTexels [ linearPos ] . m_WorldNormal , directColor , _iThread , _skipProp , _flags ) ;
if ( numbounce > = 1 ) {
ComputeIndirectLightingAtPoint ( colorTexels [ linearPos ] . m_WorldPosition , colorTexels [ linearPos ] . m_WorldNormal , indirectColor , _iThread , true , ( _flags & GATHERLFLAGS_IGNORE_NORMALS ) ! = 0 ) ;
}
VectorAdd ( directColor , indirectColor , colorTexels [ linearPos ] . m_Color ) ;
}
+ + linearPos ;
}
}
}
// ------------------------------------------------------------------------------------------------
static int GetTexelCount ( unsigned int _resX , unsigned int _resY , bool _mipmaps )
{
// Because they are unsigned, this is a != check--but if we were to change to ints, this would be
// the right assert (and it's no worse than != now).
Assert ( _resX > 0 & & _resY > 0 ) ;
if ( _mipmaps = = false )
return _resX * _resY ;
int retVal = 0 ;
while ( _resX > 1 | | _resY > 1 )
{
retVal + = _resX * _resY ;
2023-05-09 19:57:22 +03:00
_resX = MAX ( 1 , _resX > > 1 ) ;
_resY = MAX ( 1 , _resY > > 1 ) ;
2020-04-22 12:56:21 -04:00
}
// Add in the 1x1 mipmap level, which wasn't hit above. This could be done in the initializer of
// retVal, but it's more obvious here.
retVal + = 1 ;
return retVal ;
}
// ------------------------------------------------------------------------------------------------
static void FilterFineMipmap ( unsigned int _resX , unsigned int _resY , const CUtlVector < colorTexel_t > & _srcTexels , CUtlVector < Vector > * _outLinear )
{
Assert ( _outLinear ) ;
// We can't filter in place, so go ahead and create a linear buffer here.
CUtlVector < Vector > filterSrc ;
filterSrc . EnsureCount ( _srcTexels . Count ( ) ) ;
for ( int i = 0 ; i < _srcTexels . Count ( ) ; + + i )
{
ColorRGBExp32 rgbColor ;
VectorToColorRGBExp32 ( _srcTexels [ i ] . m_Color , rgbColor ) ;
ConvertRGBExp32ToLinear ( & rgbColor , & ( filterSrc [ i ] ) ) ;
}
const int cRadius = 1 ;
const float cOneOverDiameter = 1.0f / pow ( 2.0f * cRadius + 1.0f , 2.0f ) ;
// Filter here.
for ( int j = 0 ; j < _resY ; + + j )
{
for ( int i = 0 ; i < _resX ; + + i )
{
Vector value ( 0 , 0 , 0 ) ;
int thisIndex = ComputeLinearPos ( i , j , _resX , _resY ) ;
if ( ! _srcTexels [ thisIndex ] . m_bValid )
{
( * _outLinear ) [ thisIndex ] = filterSrc [ thisIndex ] ;
continue ;
}
// TODO: Check ASM for this, unroll by hand if needed.
for ( int offsetJ = - cRadius ; offsetJ < = cRadius ; + + offsetJ )
{
for ( int offsetI = - cRadius ; offsetI < = cRadius ; + + offsetI )
{
int finalIndex = ComputeLinearPos ( i + offsetI , j + offsetJ , _resX , _resY ) ;
if ( ! _srcTexels [ finalIndex ] . m_bValid )
{
finalIndex = thisIndex ;
}
value + = filterSrc [ finalIndex ] ;
}
}
( * _outLinear ) [ thisIndex ] = value * cOneOverDiameter ;
}
}
}
// ------------------------------------------------------------------------------------------------
static void BuildFineMipmap ( unsigned int _resX , unsigned int _resY , bool _applyFilter , const CUtlVector < colorTexel_t > & _srcTexels , CUtlVector < RGB888_t > * _outTexelsRGB888 , CUtlVector < Vector > * _outLinear )
{
// At least one of these needs to be non-null, otherwise what are we doing here?
Assert ( _outTexelsRGB888 | | _outLinear ) ;
Assert ( ! _applyFilter | | _outLinear ) ;
Assert ( _srcTexels . Count ( ) = = GetTexelCount ( _resX , _resY , false ) ) ;
int texelCount = GetTexelCount ( _resX , _resY , true ) ;
if ( _outTexelsRGB888 )
( * _outTexelsRGB888 ) . EnsureCount ( texelCount ) ;
if ( _outLinear )
( * _outLinear ) . EnsureCount ( GetTexelCount ( _resX , _resY , false ) ) ;
// This code can take awhile, so minimize the branchiness of the inner-loop.
if ( _applyFilter )
{
FilterFineMipmap ( _resX , _resY , _srcTexels , _outLinear ) ;
if ( _outTexelsRGB888 )
{
for ( int i = 0 ; i < _srcTexels . Count ( ) ; + + i )
{
RGBA8888_t encodedColor ;
Vector linearColor = ( * _outLinear ) [ i ] ;
ConvertLinearToRGBA8888 ( & linearColor , ( unsigned char * ) & encodedColor ) ;
( * _outTexelsRGB888 ) [ i ] . r = encodedColor . r ;
( * _outTexelsRGB888 ) [ i ] . g = encodedColor . g ;
( * _outTexelsRGB888 ) [ i ] . b = encodedColor . b ;
}
}
}
else
{
for ( int i = 0 ; i < _srcTexels . Count ( ) ; + + i )
{
ColorRGBExp32 rgbColor ;
RGBA8888_t encodedColor ;
VectorToColorRGBExp32 ( _srcTexels [ i ] . m_Color , rgbColor ) ;
ConvertRGBExp32ToRGBA8888 ( & rgbColor , ( unsigned char * ) & encodedColor , ( _outLinear ? ( & ( * _outLinear ) [ i ] ) : NULL ) ) ;
// We drop alpha on the floor here, if this were to fire we'd need to consider using a different compressed format.
Assert ( encodedColor . a = = 0xFF ) ;
if ( _outTexelsRGB888 )
{
( * _outTexelsRGB888 ) [ i ] . r = encodedColor . r ;
( * _outTexelsRGB888 ) [ i ] . g = encodedColor . g ;
( * _outTexelsRGB888 ) [ i ] . b = encodedColor . b ;
}
}
}
}
// ------------------------------------------------------------------------------------------------
static void FilterCoarserMipmaps ( unsigned int _resX , unsigned int _resY , CUtlVector < Vector > * _scratchLinear , CUtlVector < RGB888_t > * _outTexelsRGB888 )
{
Assert ( _outTexelsRGB888 ) ;
int srcResX = _resX ;
int srcResY = _resY ;
2023-05-09 19:57:22 +03:00
int dstResX = MAX ( 1 , ( srcResX > > 1 ) ) ;
int dstResY = MAX ( 1 , ( srcResY > > 1 ) ) ;
2020-04-22 12:56:21 -04:00
int dstOffset = GetTexelCount ( srcResX , srcResY , false ) ;
// Build mipmaps here, after being converted to linear space.
// TODO: Should do better filtering for downsampling. But this will work for now.
while ( srcResX > 1 | | srcResY > 1 )
{
for ( int j = 0 ; j < srcResY ; j + = 2 ) {
for ( int i = 0 ; i < srcResX ; i + = 2 ) {
int srcCol0 = i ;
int srcCol1 = i + 1 > srcResX - 1 ? srcResX - 1 : i + 1 ;
int srcRow0 = j ;
int srcRow1 = j + 1 > srcResY - 1 ? srcResY - 1 : j + 1 ; ;
int dstCol = i > > 1 ;
int dstRow = j > > 1 ;
const Vector & tl = ( * _scratchLinear ) [ srcCol0 + ( srcRow0 * srcResX ) ] ;
const Vector & tr = ( * _scratchLinear ) [ srcCol1 + ( srcRow0 * srcResX ) ] ;
const Vector & bl = ( * _scratchLinear ) [ srcCol0 + ( srcRow1 * srcResX ) ] ;
const Vector & br = ( * _scratchLinear ) [ srcCol1 + ( srcRow1 * srcResX ) ] ;
Vector sample = ( tl + tr + bl + br ) / 4.0f ;
ConvertLinearToRGBA8888 ( & sample , ( unsigned char * ) & ( * _outTexelsRGB888 ) [ dstOffset + dstCol + dstRow * dstResX ] ) ;
// Also overwrite the srcBuffer to filter the next loop. This is safe because we won't be reading this source value
// again during this mipmap level.
( * _scratchLinear ) [ dstCol + dstRow * dstResX ] = sample ;
}
}
srcResX = dstResX ;
srcResY = dstResY ;
2023-05-09 19:57:22 +03:00
dstResX = MAX ( 1 , ( srcResX > > 1 ) ) ;
dstResY = MAX ( 1 , ( srcResY > > 1 ) ) ;
2020-04-22 12:56:21 -04:00
dstOffset + = GetTexelCount ( srcResX , srcResY , false ) ;
}
}
// ------------------------------------------------------------------------------------------------
static void ConvertToDestinationFormat ( unsigned int _resX , unsigned int _resY , ImageFormat _destFmt , const CUtlVector < RGB888_t > & _scratchRBG888 , CUtlMemory < byte > * _outTexture )
{
const ImageFormat cSrcImageFormat = IMAGE_FORMAT_RGB888 ;
// Converts from the scratch RGB888 buffer, which should be fully filled out to the output texture.
int destMemoryUsage = ImageLoader : : GetMemRequired ( _resX , _resY , 1 , _destFmt , true ) ;
( * _outTexture ) . EnsureCapacity ( destMemoryUsage ) ;
int srcResX = _resX ;
int srcResY = _resY ;
int srcOffset = 0 ;
int dstOffset = 0 ;
// The usual case--that they'll be different.
if ( cSrcImageFormat ! = _destFmt )
{
while ( srcResX > 1 | | srcResY > 1 )
{
// Convert this mipmap level.
ImageLoader : : ConvertImageFormat ( ( unsigned char * ) ( & _scratchRBG888 [ srcOffset ] ) , cSrcImageFormat , ( * _outTexture ) . Base ( ) + dstOffset , _destFmt , srcResX , srcResY ) ;
// Then update offsets for the next mipmap level.
srcOffset + = GetTexelCount ( srcResX , srcResY , false ) ;
dstOffset + = ImageLoader : : GetMemRequired ( srcResX , srcResY , 1 , _destFmt , false ) ;
2023-05-09 19:57:22 +03:00
srcResX = MAX ( 1 , ( srcResX > > 1 ) ) ;
srcResY = MAX ( 1 , ( srcResY > > 1 ) ) ;
2020-04-22 12:56:21 -04:00
}
// Do the 1x1 level also.
ImageLoader : : ConvertImageFormat ( ( unsigned char * ) _scratchRBG888 . Base ( ) + srcOffset , cSrcImageFormat , ( * _outTexture ) . Base ( ) + dstOffset , _destFmt , srcResX , srcResY ) ;
} else {
// But sometimes (particularly for debugging) they will be the same.
Q_memcpy ( ( * _outTexture ) . Base ( ) , _scratchRBG888 . Base ( ) , destMemoryUsage ) ;
}
}
// ------------------------------------------------------------------------------------------------
static void ConvertTexelDataToTexture ( unsigned int _resX , unsigned int _resY , ImageFormat _destFmt , const CUtlVector < colorTexel_t > & _srcTexels , CUtlMemory < byte > * _outTexture )
{
Assert ( _outTexture ) ;
Assert ( _srcTexels . Count ( ) = = _resX * _resY ) ;
CUtlVector < RGB888_t > scratchRGB888 ;
CUtlVector < Vector > scratchLinear ;
BuildFineMipmap ( _resX , _resY , true , _srcTexels , & scratchRGB888 , & scratchLinear ) ;
FilterCoarserMipmaps ( _resX , _resY , & scratchLinear , & scratchRGB888 ) ;
ConvertToDestinationFormat ( _resX , _resY , _destFmt , scratchRGB888 , _outTexture ) ;
}
// ------------------------------------------------------------------------------------------------
static void DumpLightmapLinear ( const char * _dstFilename , const CUtlVector < colorTexel_t > & _srcTexels , int _width , int _height )
{
CUtlVector < Vector > linearFloats ;
CUtlVector < BGR888_t > linearBuffer ;
BuildFineMipmap ( _width , _height , true , _srcTexels , NULL , & linearFloats ) ;
linearBuffer . SetCount ( linearFloats . Count ( ) ) ;
for ( int i = 0 ; i < linearFloats . Count ( ) ; + + i ) {
linearBuffer [ i ] . b = RoundFloatToByte ( linearFloats [ i ] . z * 255.0f ) ;
linearBuffer [ i ] . g = RoundFloatToByte ( linearFloats [ i ] . y * 255.0f ) ;
linearBuffer [ i ] . r = RoundFloatToByte ( linearFloats [ i ] . x * 255.0f ) ;
}
TGAWriter : : WriteTGAFile ( _dstFilename , _width , _height , IMAGE_FORMAT_BGR888 , ( uint8 * ) ( linearBuffer . Base ( ) ) , _width * ImageLoader : : SizeInBytes ( IMAGE_FORMAT_BGR888 ) ) ;
}