You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2855 lines
89 KiB
2855 lines
89 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $Revision: $
|
|
// $NoKeywords: $
|
|
//
|
|
// This file contains code to allow us to associate client data with bsp leaves.
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "vrad.h"
|
|
#include "mathlib/vector.h"
|
|
#include "UtlBuffer.h"
|
|
#include "UtlVector.h"
|
|
#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"
|
|
#include "phyfile.h"
|
|
#include "collisionutils.h"
|
|
#include "tier1/KeyValues.h"
|
|
#include "pacifier.h"
|
|
#include "materialsystem/imaterial.h"
|
|
#include "materialsystem/hardwareverts.h"
|
|
#include "byteswap.h"
|
|
#include "mpivrad.h"
|
|
#include "vtf/vtf.h"
|
|
#include "tier1/utldict.h"
|
|
#include "tier1/utlsymbol.h"
|
|
#include "tier3/tier3.h"
|
|
|
|
#include "messbuf.h"
|
|
#include "vmpi.h"
|
|
#include "vmpi_distribute_work.h"
|
|
#include "iscratchpad3d.h"
|
|
//#include "glview_buffer.h"
|
|
|
|
#define ALIGN_TO_POW2(x,y) (((x)+(y-1))&~(y-1))
|
|
|
|
int g_numVradStaticPropsLightingStreams = 3;
|
|
|
|
static const TableVector g_localUpBumpBasis[NUM_BUMP_VECTS] =
|
|
{
|
|
// consistent basis wrt lightmaps
|
|
{ OO_SQRT_2_OVER_3, 0.0f, OO_SQRT_3 },
|
|
{ -OO_SQRT_6, OO_SQRT_2, OO_SQRT_3 },
|
|
{ -OO_SQRT_6, -OO_SQRT_2, OO_SQRT_3 }
|
|
};
|
|
|
|
void GetStaticPropBumpNormals( const Vector& sVect, const Vector& tVect, const Vector& flatNormal,
|
|
const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] )
|
|
{
|
|
Vector tmpNormal;
|
|
bool leftHanded;
|
|
int i;
|
|
|
|
assert( NUM_BUMP_VECTS == 3 );
|
|
|
|
// Are we left or right handed?
|
|
CrossProduct( sVect, tVect, tmpNormal );
|
|
if( DotProduct( flatNormal, tmpNormal ) < 0.0f )
|
|
{
|
|
leftHanded = true;
|
|
}
|
|
else
|
|
{
|
|
leftHanded = false;
|
|
}
|
|
|
|
// Build a basis for the face around the phong normal
|
|
matrix3x4_t smoothBasis;
|
|
CrossProduct( phongNormal.Base(), sVect.Base(), smoothBasis[1] );
|
|
VectorNormalize( smoothBasis[1] );
|
|
CrossProduct( smoothBasis[1], phongNormal.Base(), smoothBasis[0] );
|
|
VectorNormalize( smoothBasis[0] );
|
|
VectorCopy( phongNormal.Base(), smoothBasis[2] );
|
|
|
|
if( leftHanded )
|
|
{
|
|
VectorNegate( smoothBasis[1] );
|
|
}
|
|
|
|
// move the g_localUpBumpBasis into world space to create bumpNormals
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
VectorIRotate( g_localUpBumpBasis[i], smoothBasis, bumpNormals[i] );
|
|
}
|
|
}
|
|
|
|
// 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_Normals[ NUM_BUMP_VECTS + 1 ];
|
|
};
|
|
|
|
// a final colored vertex
|
|
struct colorVertex_t
|
|
{
|
|
Vector m_Colors[ NUM_BUMP_VECTS + 1 ];
|
|
float m_SunAmount[ NUM_BUMP_VECTS + 1 ];
|
|
Vector m_Position;
|
|
bool m_bValid;
|
|
};
|
|
|
|
class CComputeStaticPropLightingResults
|
|
{
|
|
public:
|
|
~CComputeStaticPropLightingResults()
|
|
{
|
|
m_ColorVertsArrays.PurgeAndDeleteElements();
|
|
}
|
|
|
|
CUtlVector< CUtlVector<colorVertex_t>* > m_ColorVertsArrays;
|
|
};
|
|
|
|
Vector NormalizeVertexBumpedLighting( Vector const *pColorNormal, Vector *pColorBumps )
|
|
{
|
|
const Vector &linearUnbumped = *( ( const Vector * )pColorNormal );
|
|
Vector linearBump1 = *( ( const Vector * )(pColorBumps + 0) );
|
|
Vector linearBump2 = *( ( const Vector * )(pColorBumps + 1) );
|
|
Vector linearBump3 = *( ( const Vector * )(pColorBumps + 2) );
|
|
|
|
const float flNormalizationFactor = 1.0f / 3.0f;
|
|
|
|
// find a scale factor which makes the average of the 3 bumped mapped vectors match the
|
|
// straight up vector (if possible), so that flat bumpmapped areas match non-bumpmapped
|
|
// areas.
|
|
Vector bumpAverage = linearBump1;
|
|
bumpAverage += linearBump2;
|
|
bumpAverage += linearBump3;
|
|
bumpAverage *= flNormalizationFactor;
|
|
|
|
Vector correctionScale;
|
|
|
|
if( *( int * )&bumpAverage[0] != 0 &&
|
|
*( int * )&bumpAverage[1] != 0 &&
|
|
*( int * )&bumpAverage[2] != 0 )
|
|
{
|
|
// fast path when we know that we don't have to worry about divide by zero.
|
|
VectorDivide( linearUnbumped, bumpAverage, correctionScale );
|
|
}
|
|
else
|
|
{
|
|
correctionScale.Init( 0.0f, 0.0f, 0.0f );
|
|
if( bumpAverage[0] != 0.0f )
|
|
{
|
|
correctionScale[0] = linearUnbumped[0] / bumpAverage[0];
|
|
}
|
|
if( bumpAverage[1] != 0.0f )
|
|
{
|
|
correctionScale[1] = linearUnbumped[1] / bumpAverage[1];
|
|
}
|
|
if( bumpAverage[2] != 0.0f )
|
|
{
|
|
correctionScale[2] = linearUnbumped[2] / bumpAverage[2];
|
|
}
|
|
}
|
|
linearBump1 *= correctionScale;
|
|
linearBump2 *= correctionScale;
|
|
linearBump3 *= correctionScale;
|
|
|
|
*((Vector *) (pColorBumps + 0)) = linearBump1;
|
|
*((Vector *) (pColorBumps + 1)) = linearBump2;
|
|
*((Vector *) (pColorBumps + 2)) = linearBump3;
|
|
|
|
return correctionScale;
|
|
}
|
|
|
|
|
|
void NormalizeVertexBumpedSunAmount( float const *pSunAmount0, float *pSunAmount1, float *pSunAmount2, float *pSunAmount3 )
|
|
{
|
|
const float &linearSunAmountUnbumped = *((const float *)pSunAmount0);
|
|
float linearSunAmount1 = *((const float *)(pSunAmount1));
|
|
float linearSunAmount2 = *((const float *)(pSunAmount2));
|
|
float linearSunAmount3 = *((const float *)(pSunAmount3));
|
|
|
|
const float flNormalizationFactor = 1.0f;// / 3.0f; - store in 0..1 space (for 0..255 alpha channel), multiply by 3.0 in the shader
|
|
|
|
// find a scale factor which makes the average of the 3 bumped mapped vectors match the
|
|
// straight up vector (if possible), so that flat bumpmapped areas match non-bumpmapped
|
|
// areas.
|
|
float bumpAverage = linearSunAmount1;
|
|
bumpAverage += linearSunAmount2;
|
|
bumpAverage += linearSunAmount3;
|
|
bumpAverage *= flNormalizationFactor;
|
|
|
|
float correctionScale;
|
|
|
|
if ( *(int *)&bumpAverage != 0 )
|
|
{
|
|
// fast path when we know that we don't have to worry about divide by zero.
|
|
correctionScale = linearSunAmountUnbumped / bumpAverage;
|
|
}
|
|
else
|
|
{
|
|
correctionScale = 1.0f;
|
|
if ( bumpAverage != 0.0f )
|
|
{
|
|
correctionScale = linearSunAmountUnbumped / bumpAverage;
|
|
}
|
|
}
|
|
linearSunAmount1 *= correctionScale;
|
|
linearSunAmount2 *= correctionScale;
|
|
linearSunAmount3 *= correctionScale;
|
|
|
|
*((float *)(pSunAmount1)) = linearSunAmount1;
|
|
*((float *)(pSunAmount2)) = linearSunAmount2;
|
|
*((float *)(pSunAmount3)) = linearSunAmount3;
|
|
}
|
|
|
|
|
|
void DumpElapsedTime( int timeTaken )
|
|
{
|
|
if ( g_bDumpBumpStaticProps && (g_numVradStaticPropsLightingStreams == 3) )
|
|
{
|
|
char mapName[MAX_PATH];
|
|
Q_FileBase( source, mapName, sizeof( mapName ) );
|
|
|
|
char bumpPropFilename[MAX_PATH];
|
|
sprintf( bumpPropFilename, "vrad_bumpstaticprops_%s.txt", mapName );
|
|
|
|
Msg( "Writing %s...\n", bumpPropFilename );
|
|
|
|
FILE *fp = fopen( bumpPropFilename, "a" );
|
|
|
|
if ( !fp )
|
|
{
|
|
Msg( "Writing %s...failed\n", bumpPropFilename );
|
|
return;
|
|
}
|
|
|
|
char str[512];
|
|
GetHourMinuteSecondsString( timeTaken, str, sizeof( str ) );
|
|
|
|
fprintf( fp, "\n\nUsing -staticpropsamplescale %f (-final defaults to 16)\n", g_flStaticPropSampleScale );
|
|
fprintf( fp, "\nTotal time taken to bake static prop lighting: %s\n", str );
|
|
|
|
fclose( fp );
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 );
|
|
|
|
virtual void MakePatches() override;
|
|
|
|
private:
|
|
// 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 );
|
|
|
|
// 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
|
|
Vector m_vReflectivity;
|
|
bool m_bHasBumpmap;
|
|
bool m_bHasPhong;
|
|
};
|
|
|
|
struct MeshData_t
|
|
{
|
|
CUtlVector<Vector4D> m_VertColorData; // w has the additional lightmap alpha data
|
|
int m_numVerts;
|
|
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;
|
|
int m_FlagsEx;
|
|
bool m_bLightingOriginValid;
|
|
Vector m_vReflectivity;
|
|
};
|
|
|
|
// 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( 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 ( ReadFileFromPak( GetPakFile(), pFileName, false, buf ) )
|
|
return true;
|
|
|
|
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.Count() );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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
|
|
pHdr->SetVertexBase( NULL );
|
|
pHdr->SetIndexBase( NULL );
|
|
|
|
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, ".dx90.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 )
|
|
{
|
|
float umin = min(t0.x, t1.x);
|
|
umin = min(umin, t2.x);
|
|
float umax = max(t0.x, t1.x);
|
|
umax = max(umax, t2.x);
|
|
|
|
float vmin = min(t0.y, t1.y);
|
|
vmin = min(vmin, t2.y);
|
|
float vmax = max(t0.y, t1.y);
|
|
vmax = max(vmax, t2.y);
|
|
|
|
// 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];
|
|
// bilinear filtered sample
|
|
float ou = uv[0] * tex.width;
|
|
float ov = uv[1] * tex.height;
|
|
int u = floor( ou );
|
|
int v = floor( ov );
|
|
int u1 = u+1;
|
|
int v1 = v+1;
|
|
u &= (tex.width-1);
|
|
u1 &= (tex.width-1);
|
|
v &= (tex.height-1);
|
|
v1 &= (tex.height-1);
|
|
float lerpU = ou - u;
|
|
float lerpV = ov - v;
|
|
int x = (tex.pAlphaTexels[v * tex.width + u] * (1-lerpU)) + (lerpU*tex.pAlphaTexels[v * tex.width + u1]);
|
|
int y = (tex.pAlphaTexels[v1 * tex.width + u] * (1-lerpU)) + (lerpU*tex.pAlphaTexels[v1 * tex.width + u1]);
|
|
return int( x * (1-lerpV) + (y*lerpV) );
|
|
}
|
|
|
|
void GetMapping( int shadowTextureIndex, int *pWidth, int *pHeight )
|
|
{
|
|
*pWidth = m_Textures[shadowTextureIndex].width;
|
|
*pHeight = m_Textures[shadowTextureIndex].height;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
int LoadShadowTexture( const char *pMaterialName )
|
|
{
|
|
int textureIndex = -1;
|
|
// try to add each texture to the transparent shadow manager
|
|
char szPath[MAX_PATH];
|
|
|
|
Q_strncpy( szPath, "materials/", sizeof( szPath ) );
|
|
Q_strncat( szPath, pMaterialName, sizeof( szPath ), COPY_ALL_CHARACTERS );
|
|
Q_strncat( szPath, ".vmt", sizeof( szPath ), COPY_ALL_CHARACTERS );
|
|
Q_FixSlashes( szPath, CORRECT_PATH_SEPARATOR );
|
|
g_ShadowTextureList.FindOrLoadIfValid( szPath, &textureIndex );
|
|
return textureIndex;
|
|
}
|
|
|
|
int AddShadowTextureTriangle( int shadowTextureIndex, const Vector2D &t0, const Vector2D &t1, const Vector2D &t2 )
|
|
{
|
|
return g_ShadowTextureList.AddMaterialEntry(shadowTextureIndex, t0, t1, t2 );
|
|
}
|
|
|
|
float ComputeCoverageForTriangle( int shadowTextureIndex, const Vector2D &t0, const Vector2D &t1, const Vector2D &t2 )
|
|
{
|
|
return g_ShadowTextureList.ComputeCoverageForTriangle(shadowTextureIndex, t0, t1, t2 );
|
|
}
|
|
|
|
void GetShadowTextureMapping( int shadowTextureIndex, int *pWidth, int *pHeight )
|
|
{
|
|
g_ShadowTextureList.GetMapping( shadowTextureIndex, pWidth, pHeight );
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
bool IsStaticPropBumpmapped( studiohdr_t *pStudioHdr )
|
|
{
|
|
if ( g_numVradStaticPropsLightingStreams == 1 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check if prop uses "$bumpmap" in any materials, use this as an indication of valid tangent data (availability of tangentdata does not imply it's valid/used)
|
|
for ( int textureIndex = 0; textureIndex < pStudioHdr->numtextures; textureIndex++ )
|
|
{
|
|
char szPath[MAX_PATH];
|
|
|
|
// iterate quietly through all specified directories until a valid material is found
|
|
for ( int i = 0; i < pStudioHdr->numcdtextures; i++ )
|
|
{
|
|
Q_strncpy( szPath, "materials/", sizeof( szPath ) );
|
|
Q_strncat( szPath, pStudioHdr->pCdtexture( i ), sizeof( szPath ) );
|
|
const char *textureName = pStudioHdr->pTexture( textureIndex )->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 );
|
|
|
|
KeyValues *pVMT = new KeyValues( "vmt" );
|
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
|
|
LoadFileIntoBuffer( buf, szPath );
|
|
if ( pVMT->LoadFromBuffer( szPath, buf ) )
|
|
{
|
|
if ( pVMT->FindKey( "$bumpmap" ) )
|
|
{
|
|
pVMT->deleteThis();
|
|
return true;
|
|
}
|
|
}
|
|
pVMT->deleteThis();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void StaticPropHasPhongBump( studiohdr_t *pStudioHdr, bool *pHasBumpmap, bool *pHasPhong )
|
|
{
|
|
if ( g_numVradStaticPropsLightingStreams == 1 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
*pHasBumpmap = false;
|
|
*pHasPhong = false;
|
|
|
|
// check if prop uses "$bumpmap" in any materials, use this as an indication of valid tangent data (availability of tangentdata does not imply it's valid/used)
|
|
for ( int textureIndex = 0; textureIndex < pStudioHdr->numtextures; textureIndex++ )
|
|
{
|
|
char szPath[MAX_PATH];
|
|
|
|
// iterate quietly through all specified directories until a valid material is found
|
|
for ( int i = 0; i < pStudioHdr->numcdtextures; i++ )
|
|
{
|
|
Q_strncpy( szPath, "materials/", sizeof( szPath ) );
|
|
Q_strncat( szPath, pStudioHdr->pCdtexture( i ), sizeof( szPath ) );
|
|
const char *textureName = pStudioHdr->pTexture( textureIndex )->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 );
|
|
|
|
KeyValues *pVMT = new KeyValues( "vmt" );
|
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
|
|
LoadFileIntoBuffer( buf, szPath );
|
|
if ( pVMT->LoadFromBuffer( szPath, buf ) )
|
|
{
|
|
if ( pVMT->FindKey( "$bumpmap" ) )
|
|
{
|
|
*pHasBumpmap = true;
|
|
|
|
// is it also phong
|
|
if ( pVMT->FindKey( "$phong" ) )
|
|
{
|
|
*pHasPhong = true;
|
|
|
|
pVMT->deleteThis();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
pVMT->deleteThis();
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
Vector ReadReflectivityFromVTF( const char *pName )
|
|
{
|
|
Vector vRefl( 0.18f, 0.18f, 0.18f );
|
|
|
|
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 );
|
|
|
|
int nHeaderSize = VTFFileHeaderSize();
|
|
unsigned char *pMem = (unsigned char *)stackalloc( nHeaderSize );
|
|
CUtlBuffer buf( pMem, nHeaderSize );
|
|
if ( g_pFullFileSystem->ReadFile( szPath, NULL, buf, nHeaderSize ) )
|
|
{
|
|
IVTFTexture *pTex = CreateVTFTexture();
|
|
if ( pTex->Unserialize( buf, true ) )
|
|
{
|
|
vRefl = pTex->Reflectivity();
|
|
}
|
|
DestroyVTFTexture( pTex );
|
|
}
|
|
return vRefl;
|
|
}
|
|
|
|
Vector ComputeStaticPropReflectivity( studiohdr_t *pStudioHdr )
|
|
{
|
|
Vector vReflectivity( 0.18f, 0.18f, 0.18f );
|
|
|
|
for ( int textureIndex = 0; textureIndex < pStudioHdr->numtextures; textureIndex++ )
|
|
{
|
|
char szPath[ MAX_PATH ];
|
|
|
|
// iterate quietly through all specified directories until a valid material is found
|
|
for ( int i = 0; i < pStudioHdr->numcdtextures; i++ )
|
|
{
|
|
Q_strncpy( szPath, "materials/", sizeof( szPath ) );
|
|
Q_strncat( szPath, pStudioHdr->pCdtexture( i ), sizeof( szPath ) );
|
|
const char *textureName = pStudioHdr->pTexture( textureIndex )->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 );
|
|
|
|
Vector vVtfRefl( 1.0f, 1.0f, 1.0f );
|
|
Vector vTint( 1.0f, 1.0f, 1.0f );
|
|
|
|
KeyValues *pVMT = new KeyValues( "vmt" );
|
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
|
|
LoadFileIntoBuffer( buf, szPath );
|
|
if ( pVMT->LoadFromBuffer( szPath, buf ) )
|
|
{
|
|
KeyValues *pBaseTexture = pVMT->FindKey( "$basetexture" );
|
|
if ( pBaseTexture )
|
|
{
|
|
const char *pBaseTextureName = pBaseTexture->GetString();
|
|
if ( pBaseTextureName )
|
|
{
|
|
vVtfRefl = ReadReflectivityFromVTF( pBaseTextureName );
|
|
}
|
|
}
|
|
|
|
vReflectivity = vVtfRefl;
|
|
|
|
KeyValues *pColorTint = pVMT->FindKey( "color" );
|
|
if ( pColorTint )
|
|
{
|
|
const char *pColorString = pColorTint->GetString();
|
|
if ( pColorString[ 0 ] == '{' )
|
|
{
|
|
int r = 0;
|
|
int g = 0;
|
|
int b = 0;
|
|
sscanf( pColorString, "{%d %d %d}", &r, &g, &b );
|
|
vTint.x = SrgbGammaToLinear( clamp( float( r ) / 255.0f, 0.0f, 1.0f ) );
|
|
vTint.y = SrgbGammaToLinear( clamp( float( r ) / 255.0f, 0.0f, 1.0f ) );
|
|
vTint.z = SrgbGammaToLinear( clamp( float( r ) / 255.0f, 0.0f, 1.0f ) );
|
|
}
|
|
else if ( pColorString[ 0 ] == '[' )
|
|
{
|
|
sscanf( pColorString, "[%f %f %f]", &vTint.x, &vTint.y, &vTint.z );
|
|
vTint.x = clamp( vTint.x, 0.0f, 1.0f );
|
|
vTint.y = clamp( vTint.y, 0.0f, 1.0f );
|
|
vTint.z = clamp( vTint.z, 0.0f, 1.0f );
|
|
}
|
|
}
|
|
}
|
|
pVMT->deleteThis();
|
|
|
|
vReflectivity = vVtfRefl * vTint;
|
|
if ( vReflectivity.x == 1.0f && vReflectivity.y == 1.0f && vReflectivity.z == 1.0f )
|
|
{
|
|
vReflectivity.Init( 0.18f, 0.18f, 0.18f );
|
|
}
|
|
return vReflectivity;
|
|
}
|
|
}
|
|
|
|
return vReflectivity;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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() );
|
|
}
|
|
}
|
|
|
|
// mark static props that use $bumpmap, $phong materials
|
|
StaticPropHasPhongBump( pHdr, &m_StaticPropDict[ i ].m_bHasBumpmap, &m_StaticPropDict[ i ].m_bHasPhong );
|
|
m_StaticPropDict[ i ].m_vReflectivity = ComputeStaticPropReflectivity( pHdr );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 );
|
|
}
|
|
|
|
// spew bump static prop info
|
|
if ( g_bDumpBumpStaticProps && (g_numVradStaticPropsLightingStreams == 3) )
|
|
{
|
|
char mapName[MAX_PATH];
|
|
Q_FileBase( source, mapName, sizeof( mapName ) );
|
|
|
|
char bumpPropFilename[MAX_PATH];
|
|
sprintf( bumpPropFilename, "vrad_bumpstaticprops_%s.txt", mapName);
|
|
|
|
Msg( "Writing %s...\n", bumpPropFilename );
|
|
|
|
FILE *fp = fopen( bumpPropFilename, "w" );
|
|
|
|
if ( !fp )
|
|
{
|
|
Msg( "Writing %s...failed\n", bumpPropFilename );
|
|
return;
|
|
}
|
|
|
|
fprintf( fp, "Bumpmap static prop list for %s\n", mapName );
|
|
|
|
int numBumpmapStaticProps = 0;
|
|
int numPhongStaticProps = 0;
|
|
for ( int i = m_StaticPropDict.Count(); --i >= 0; )
|
|
{
|
|
studiohdr_t *pStudioHdr = m_StaticPropDict[i].m_pStudioHdr;
|
|
|
|
if ( m_StaticPropDict[i].m_bHasBumpmap )
|
|
{
|
|
numBumpmapStaticProps++;
|
|
}
|
|
|
|
if ( m_StaticPropDict[i].m_bHasPhong )
|
|
{
|
|
numPhongStaticProps++;
|
|
}
|
|
|
|
if ( m_StaticPropDict[i].m_bHasBumpmap || m_StaticPropDict[i].m_bHasPhong )
|
|
{
|
|
fprintf( fp, "\nprop: %s\nvmt's containing $bumpmap, $phong:\n", pStudioHdr->pszName() );
|
|
|
|
for ( int textureIndex = 0; textureIndex < pStudioHdr->numtextures; textureIndex++ )
|
|
{
|
|
char szPath[MAX_PATH];
|
|
|
|
// iterate quietly through all specified directories until a valid material is found
|
|
for ( int i = 0; i < pStudioHdr->numcdtextures; i++ )
|
|
{
|
|
Q_strncpy( szPath, "materials/", sizeof( szPath ) );
|
|
Q_strncat( szPath, pStudioHdr->pCdtexture( i ), sizeof( szPath ) );
|
|
const char *textureName = pStudioHdr->pTexture( textureIndex )->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 );
|
|
|
|
KeyValues *pVMT = new KeyValues( "vmt" );
|
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
|
|
LoadFileIntoBuffer( buf, szPath );
|
|
if ( pVMT->LoadFromBuffer( szPath, buf ) )
|
|
{
|
|
if ( pVMT->FindKey( "$bumpmap" ) )
|
|
{
|
|
if ( pVMT->FindKey( "$phong" ) )
|
|
{
|
|
fprintf( fp, "$bump, $phong: %s\n", szPath );
|
|
}
|
|
else
|
|
{
|
|
fprintf( fp, "$bump: %s\n", szPath );
|
|
}
|
|
}
|
|
else if ( pVMT->FindKey( "$phong" ) )
|
|
{
|
|
// not possible/error?
|
|
fprintf( fp, "$phong: %s\n", szPath );
|
|
}
|
|
}
|
|
pVMT->deleteThis();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fprintf( fp, "\n%d static props, %d bumped static props (%d phong static props)\n", m_StaticPropDict.Count(), numBumpmapStaticProps, numPhongStaticProps );
|
|
fclose( fp );
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
m_StaticProps[ i ].m_FlagsEx = lump.m_FlagsEx;
|
|
m_StaticProps[ i ].m_vReflectivity.Init( SrgbGammaToLinear( float( lump.m_DiffuseModulation.r ) / 255.0f ),
|
|
SrgbGammaToLinear( float( lump.m_DiffuseModulation.g ) / 255.0f ),
|
|
SrgbGammaToLinear( float( lump.m_DiffuseModulation.b ) / 255.0f ) );
|
|
m_StaticProps[ i ].m_vReflectivity *= m_StaticPropDict[ m_StaticProps[ i ].m_ModelIdx ].m_vReflectivity;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 )
|
|
{
|
|
Error( "Cannot load the static props... encountered a stale map version. Re-vbsp the map." );
|
|
}
|
|
|
|
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.Count(); --i >= 0; )
|
|
{
|
|
studiohdr_t *pStudioHdr = m_StaticPropDict[i].m_pStudioHdr;
|
|
if ( pStudioHdr )
|
|
{
|
|
if ( pStudioHdr->VertexBase() )
|
|
{
|
|
free( pStudioHdr->VertexBase() );
|
|
pStudioHdr->SetVertexBase( nullptr );
|
|
}
|
|
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 )
|
|
{
|
|
/* Testing enabling/disabling since it erroneously reports verts inside light blockers
|
|
and there are a number of position offsets applied elsewhere to avoid surface acne
|
|
that might well be enough */
|
|
|
|
if ( g_bDisableStaticPropVertexInSolidTest )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int ndxLeaf = PointLeafnum( position );
|
|
if ( dleafs[ndxLeaf].contents & CONTENTS_SOLID )
|
|
{
|
|
// position embedded in solid
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool PositionIn3DSkybox( Vector &position )
|
|
{
|
|
int iLeaf = PointLeafnum( position );
|
|
int area = dleafs[ iLeaf ].area;
|
|
return area_sky_cameras[ area ] >= 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Trace from a vertex to each direct light source, accumulating its contribution.
|
|
//-----------------------------------------------------------------------------
|
|
void ComputeDirectLightingAtPoint( Vector &position, Vector *normals, Vector *outColors, float *outSunAmount, int numNormals, bool bSkipSkyLight, int iThread,
|
|
int static_prop_id_to_skip, int nLFlags )
|
|
{
|
|
SSE_sampleLightOutput_t sampleOutput;
|
|
|
|
for ( int k = 0; k < numNormals; ++ k )
|
|
{
|
|
outColors[k].Init();
|
|
outSunAmount[k] = 0.0f;
|
|
}
|
|
|
|
// 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;
|
|
|
|
const float flFudgeFactor = 4.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 *= flFudgeFactor;
|
|
adjusted_pos += fudge;
|
|
}
|
|
else
|
|
{
|
|
// push out along normal
|
|
adjusted_pos += flFudgeFactor * normals[0];
|
|
// flEpsilon = 1.0;
|
|
}
|
|
|
|
FourVectors adjusted_pos4;
|
|
adjusted_pos4.DuplicateVector( adjusted_pos );
|
|
|
|
FourVectors normal4;
|
|
switch( numNormals )
|
|
{
|
|
case 4:
|
|
normal4.LoadAndSwizzle( normals[0], normals[1], normals[2], normals[3] );
|
|
break;
|
|
case 3:
|
|
normal4.LoadAndSwizzle( normals[0], normals[1], normals[2], normals[0] );
|
|
break;
|
|
default:
|
|
normal4.DuplicateVector( normals[0] );
|
|
break;
|
|
}
|
|
|
|
GatherSampleLightSSE( sampleOutput, dl, -1, adjusted_pos4, &normal4,
|
|
1, // really it's number of FourVectors passed
|
|
iThread, g_bFastStaticProps ? ( nLFlags | GATHERLFLAGS_FORCE_FAST ) : nLFlags,
|
|
static_prop_id_to_skip, flEpsilon );
|
|
|
|
for ( int k = 0; k < numNormals; ++k )
|
|
{
|
|
if ( !((dl->light.type == emit_skylight) && bSkipSkyLight) )
|
|
{
|
|
VectorMA( outColors[k],
|
|
sampleOutput.m_flFalloff.m128_f32[k] * sampleOutput.m_flDot[0].m128_f32[k],
|
|
dl->light.intensity,
|
|
outColors[k] );
|
|
}
|
|
|
|
outSunAmount[k] += SubFloat( sampleOutput.m_flSunAmount[0], k ) * (sampleOutput.m_flDot[0].m128_f32[0] > 0.0f ? 1.0f : 0.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// version of above that just computes/returns the sun amount
|
|
//-----------------------------------------------------------------------------
|
|
void ComputeSunAmountAtPoint( Vector &position, Vector *normals, float *outSunAmount, int numNormals, int iThread,
|
|
int static_prop_id_to_skip = -1, int nLFlags = 0 )
|
|
{
|
|
SSE_sampleLightOutput_t sampleOutput;
|
|
|
|
for ( int k = 0; k < numNormals; ++k )
|
|
{
|
|
outSunAmount[k] = 0.0f;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
if ( dl->light.type != emit_skylight )
|
|
{
|
|
// skip lights that don't contribue to sunamount
|
|
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;
|
|
|
|
const float flFudgeFactor = 4.0;
|
|
|
|
// push towards the light
|
|
Vector fudge;
|
|
fudge = -(dl->light.normal);
|
|
fudge *= flFudgeFactor;
|
|
adjusted_pos += fudge;
|
|
|
|
FourVectors adjusted_pos4;
|
|
adjusted_pos4.DuplicateVector( adjusted_pos );
|
|
|
|
FourVectors normal4;
|
|
switch ( numNormals )
|
|
{
|
|
case 4:
|
|
normal4.LoadAndSwizzle( normals[0], normals[1], normals[2], normals[3] );
|
|
break;
|
|
case 3:
|
|
normal4.LoadAndSwizzle( normals[0], normals[1], normals[2], normals[0] );
|
|
break;
|
|
default:
|
|
normal4.DuplicateVector( normals[0] );
|
|
break;
|
|
}
|
|
|
|
GatherSampleLightSSE( sampleOutput, dl, -1, adjusted_pos4, &normal4,
|
|
1, // really it's number of FourVectors passed
|
|
iThread, g_bFastStaticProps ? (nLFlags | GATHERLFLAGS_FORCE_FAST) : nLFlags,
|
|
static_prop_id_to_skip, flEpsilon );
|
|
|
|
for ( int k = 0; k < numNormals; ++k )
|
|
{
|
|
outSunAmount[k] += SubFloat( sampleOutput.m_flSunAmount[0], k ) * (sampleOutput.m_flDot[0].m128_f32[0] > 0.0f ? 1.0f : 0.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Takes the results from a ComputeLighting call and applies it to the static prop in question.
|
|
//-----------------------------------------------------------------------------
|
|
void CVradStaticPropMgr::ApplyLightingToStaticProp( CStaticProp &prop, const CComputeStaticPropLightingResults *pResults )
|
|
{
|
|
if ( pResults->m_ColorVertsArrays.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 const numVertexLightComponents = g_numVradStaticPropsLightingStreams;
|
|
int iCurColorVertsArray = 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[iCurColorVertsArray++];
|
|
|
|
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();
|
|
prop.m_MeshData[nMeshIdx].m_VertColorData.AddMultipleToTail( pStripGroup->numVerts * numVertexLightComponents );
|
|
prop.m_MeshData[nMeshIdx].m_numVerts = 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 );
|
|
|
|
if ( numVertexLightComponents <= 1 )
|
|
{
|
|
prop.m_MeshData[nMeshIdx].m_VertColorData[nVertex].AsVector3D() = colorVerts[nIndex].m_Colors[0];
|
|
prop.m_MeshData[nMeshIdx].m_VertColorData[nVertex].w = colorVerts[nIndex].m_SunAmount[0];
|
|
}
|
|
else for ( int k = 0 ; k < numVertexLightComponents; ++ k )
|
|
{
|
|
prop.m_MeshData[nMeshIdx].m_VertColorData[nVertex * numVertexLightComponents + k].AsVector3D() = colorVerts[nIndex].m_Colors[k + 1];
|
|
prop.m_MeshData[nMeshIdx].m_VertColorData[nVertex * numVertexLightComponents + k].w = colorVerts[nIndex].m_SunAmount[k + 1];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
int nGatherFlags = (prop.m_Flags & STATIC_PROP_IGNORE_NORMALS) ? GATHERLFLAGS_IGNORE_NORMALS : 0;
|
|
nGatherFlags |= (prop.m_Flags & STATIC_PROP_NO_PER_VERTEX_LIGHTING) ? GATHERLFLAGS_NO_OCCLUSION : 0;
|
|
|
|
if ( dict.m_bHasPhong )
|
|
{
|
|
nGatherFlags &= ~GATHERLFLAGS_IGNORE_NORMALS;
|
|
}
|
|
|
|
nGatherFlags |= GATHERLFLAGS_STATICPROP;
|
|
|
|
VMPI_SetCurrentStage( "ComputeLighting" );
|
|
|
|
int numSampleNormals = (g_numVradStaticPropsLightingStreams > 1) ? (NUM_BUMP_VECTS + 1) : 1;
|
|
bool bCanUseTangents = dict.m_bHasBumpmap;
|
|
bool bSkipDirectSkylight = true; // Only computing indirect GI for all static props now. Direct sunlight applied in shader.
|
|
if ( PositionIn3DSkybox( prop.m_Origin ) )
|
|
{
|
|
bSkipDirectSkylight = false;
|
|
}
|
|
|
|
for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID )
|
|
{
|
|
mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID );
|
|
|
|
for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID )
|
|
{
|
|
mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID );
|
|
|
|
// 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
|
|
for ( int vertexID = 0; vertexID < pStudioMesh->numvertices; ++vertexID )
|
|
{
|
|
Vector sampleNormals[ NUM_BUMP_VECTS + 1 ];
|
|
Vector samplePosition;
|
|
// transform position and normal into world coordinate system
|
|
matrix3x4_t matrix;
|
|
AngleMatrix( prop.m_Angles, prop.m_Origin, matrix );
|
|
VectorTransform( *vertData->Position( vertexID ), matrix, samplePosition );
|
|
AngleMatrix( prop.m_Angles, matrix );
|
|
VectorTransform( *vertData->Normal( vertexID ), matrix, sampleNormals[0] );
|
|
|
|
if( numSampleNormals > 1 )
|
|
{
|
|
Vector *bumpVects = &sampleNormals[1];
|
|
Vector4D *vecTangentS = vertData->HasTangentData() ? vertData->TangentS( vertexID ) : NULL;
|
|
|
|
if ( vecTangentS && bCanUseTangents )
|
|
{
|
|
Vector vecTexS;
|
|
VectorTransform( vecTangentS->AsVector3D(), matrix, vecTexS );
|
|
|
|
Vector vecTexT;
|
|
CrossProduct( sampleNormals[0], vecTexS, vecTexT );
|
|
vecTexT.NormalizeInPlace();
|
|
|
|
// recompute S-vector to have S, T, N as an orthonormal basis for hl2 vectors
|
|
CrossProduct( vecTexT, sampleNormals[0], vecTexS );
|
|
|
|
// respect the flip-factor for T-vector
|
|
vecTexT *= vecTangentS->w;
|
|
|
|
GetStaticPropBumpNormals(
|
|
vecTexS, vecTexT,
|
|
sampleNormals[0],
|
|
sampleNormals[0],
|
|
bumpVects );
|
|
|
|
sampleNormals[0].NormalizeInPlace();
|
|
sampleNormals[1].NormalizeInPlace();
|
|
sampleNormals[2].NormalizeInPlace();
|
|
sampleNormals[3].NormalizeInPlace();
|
|
}
|
|
else
|
|
{
|
|
sampleNormals[1] = sampleNormals[0];
|
|
sampleNormals[2] = sampleNormals[0];
|
|
sampleNormals[3] = sampleNormals[0];
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
memcpy( badVertex.m_Normals, sampleNormals, sizeof( badVertex.m_Normals ) );
|
|
badVerts.AddToTail( badVertex );
|
|
}
|
|
else
|
|
{
|
|
Vector direct_pos=samplePosition;
|
|
int skip_prop = -1;
|
|
if ( g_bDisablePropSelfShadowing || ( prop.m_Flags & STATIC_PROP_NO_SELF_SHADOWING ) )
|
|
{
|
|
skip_prop = prop_index;
|
|
}
|
|
|
|
Vector directColors[ NUM_BUMP_VECTS + 1 ];
|
|
float sunAmount[ NUM_BUMP_VECTS + 1 ];
|
|
memset( directColors, 0, sizeof( directColors ) );
|
|
memset( sunAmount, 0, sizeof( sunAmount ) );
|
|
|
|
if ( bCanUseTangents )
|
|
{
|
|
ComputeDirectLightingAtPoint( direct_pos,
|
|
sampleNormals, directColors, sunAmount, numSampleNormals, bSkipDirectSkylight,
|
|
iThread,
|
|
skip_prop, nGatherFlags );
|
|
}
|
|
else
|
|
{
|
|
ComputeDirectLightingAtPoint( direct_pos,
|
|
sampleNormals, directColors, sunAmount, 1, bSkipDirectSkylight,
|
|
iThread,
|
|
skip_prop, nGatherFlags );
|
|
directColors[1] = directColors[0];
|
|
directColors[2] = directColors[0];
|
|
directColors[3] = directColors[0];
|
|
sunAmount[1] = sunAmount[0];
|
|
sunAmount[2] = sunAmount[0];
|
|
sunAmount[3] = sunAmount[0];
|
|
}
|
|
|
|
if ( numSampleNormals > 1 )
|
|
{
|
|
// doing this for direct and indirect separately helps eliminate errors with CSM blending
|
|
NormalizeVertexBumpedLighting( directColors, directColors + 1 );
|
|
}
|
|
|
|
|
|
Vector indirectColors[ NUM_BUMP_VECTS + 1 ];
|
|
memset( indirectColors, 0, sizeof( indirectColors ) );
|
|
|
|
if (g_bShowStaticPropNormals)
|
|
{
|
|
directColors[0] = sampleNormals[0];
|
|
directColors[0] += Vector(1.0,1.0,1.0);
|
|
directColors[0] *= 50.0;
|
|
directColors[1] = directColors[0];
|
|
directColors[2] = directColors[0];
|
|
directColors[3] = directColors[0];
|
|
}
|
|
else
|
|
{
|
|
if (numbounce >= 1)
|
|
{
|
|
if ( bCanUseTangents )
|
|
{
|
|
ComputeIndirectLightingAtPoint(
|
|
samplePosition, sampleNormals,
|
|
indirectColors, numSampleNormals, iThread, g_bFastStaticProps,
|
|
( prop.m_Flags & STATIC_PROP_IGNORE_NORMALS ) != 0, prop_index );
|
|
}
|
|
else
|
|
{
|
|
ComputeIndirectLightingAtPoint(
|
|
samplePosition, sampleNormals,
|
|
indirectColors, 1, iThread, g_bFastStaticProps,
|
|
( prop.m_Flags & STATIC_PROP_IGNORE_NORMALS ) != 0, prop_index );
|
|
indirectColors[1] = indirectColors[0];
|
|
indirectColors[2] = indirectColors[0];
|
|
indirectColors[3] = indirectColors[0];
|
|
}
|
|
|
|
if ( numSampleNormals > 1 )
|
|
{
|
|
// doing this for direct and indirect separately helps eliminate errors with CSM blending
|
|
NormalizeVertexBumpedLighting( indirectColors, indirectColors + 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
colorVerts[numVertexes].m_bValid = true;
|
|
colorVerts[numVertexes].m_Position = samplePosition;
|
|
for ( int k = 0; k < numSampleNormals; ++ k )
|
|
{
|
|
VectorAdd( directColors[k], indirectColors[k], colorVerts[numVertexes].m_Colors[k] );
|
|
colorVerts[numVertexes].m_SunAmount[k] = sunAmount[k];
|
|
}
|
|
if ( numSampleNormals > 1 )
|
|
{
|
|
float *pSunAmountUnbumped = &colorVerts[numVertexes].m_SunAmount[0];
|
|
NormalizeVertexBumpedSunAmount( pSunAmountUnbumped, pSunAmountUnbumped+1, pSunAmountUnbumped+2, pSunAmountUnbumped+3 );
|
|
}
|
|
}
|
|
|
|
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
|
|
// subdivide 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;
|
|
}
|
|
|
|
Vector directColors[ NUM_BUMP_VECTS + 1 ];
|
|
memset( directColors, 0, sizeof( directColors ) );
|
|
Vector indirectColors[ NUM_BUMP_VECTS + 1 ];
|
|
memset( indirectColors, 0, sizeof( indirectColors ) );
|
|
float sunAmount[NUM_BUMP_VECTS + 1];
|
|
memset( sunAmount, 0, sizeof( sunAmount ) );
|
|
|
|
// re-light from better position
|
|
if ( bCanUseTangents )
|
|
{
|
|
ComputeDirectLightingAtPoint( bestPosition, badVerts[nBadVertex].m_Normals,
|
|
directColors, sunAmount, numSampleNormals, bSkipDirectSkylight, iThread );
|
|
ComputeIndirectLightingAtPoint( bestPosition, badVerts[nBadVertex].m_Normals,
|
|
indirectColors, numSampleNormals, iThread, true, false, prop_index );
|
|
}
|
|
else
|
|
{
|
|
ComputeDirectLightingAtPoint( bestPosition, badVerts[nBadVertex].m_Normals,
|
|
directColors, sunAmount, 1, bSkipDirectSkylight, iThread );
|
|
// doing this for direct and indirect separately helps eliminate errors with CSM blending
|
|
ComputeIndirectLightingAtPoint( bestPosition, badVerts[nBadVertex].m_Normals,
|
|
indirectColors, 1, iThread, true, false, prop_index );
|
|
for ( int k = 1; k < numSampleNormals; ++k )
|
|
{
|
|
directColors[k] = directColors[0];
|
|
indirectColors[k] = indirectColors[0];
|
|
sunAmount[k] = sunAmount[0];
|
|
}
|
|
}
|
|
|
|
if ( numSampleNormals > 1 )
|
|
{
|
|
// doing this for direct and indirect separately helps eliminate errors with CSM blending
|
|
NormalizeVertexBumpedLighting( directColors, directColors + 1 );
|
|
NormalizeVertexBumpedLighting( indirectColors, indirectColors + 1 );
|
|
}
|
|
|
|
// save results, not changing valid status
|
|
// to ensure this offset position is not considered as a viable candidate
|
|
const int idxColorVertex = badVerts[nBadVertex].m_ColorVertex;
|
|
colorVerts[idxColorVertex].m_Position = bestPosition;
|
|
for ( int k = 0; k < numSampleNormals; ++ k )
|
|
{
|
|
VectorAdd( directColors[k], indirectColors[k], colorVerts[idxColorVertex].m_Colors[k] );
|
|
colorVerts[idxColorVertex].m_SunAmount[k] = sunAmount[k];
|
|
}
|
|
if ( numSampleNormals > 1 )
|
|
{
|
|
float *pSunAmountUnbumped = &colorVerts[idxColorVertex].m_SunAmount[0];
|
|
NormalizeVertexBumpedSunAmount( pSunAmountUnbumped, pSunAmountUnbumped + 1, pSunAmountUnbumped + 2, pSunAmountUnbumped + 3 );
|
|
}
|
|
}
|
|
}
|
|
|
|
// discard bad verts
|
|
badVerts.Purge();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Write the lighting 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)
|
|
{
|
|
|
|
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_numVerts;
|
|
}
|
|
|
|
int numLightingComponents = g_numVradStaticPropsLightingStreams;
|
|
|
|
// 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*numLightingComponents + 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));
|
|
pVertexData = (unsigned char*)pVhvHdr + ALIGN_TO_POW2( (unsigned int)pVertexData, 512 );
|
|
|
|
// construct header
|
|
pVhvHdr->m_nVersion = VHV_VERSION;
|
|
pVhvHdr->m_nChecksum = m_StaticPropDict[m_StaticProps[i].m_ModelIdx].m_pStudioHdr->checksum;
|
|
pVhvHdr->m_nVertexFlags = ( numLightingComponents > 1 ) ? VERTEX_NORMAL : VERTEX_COLOR;
|
|
pVhvHdr->m_nVertexSize = 4 * numLightingComponents;
|
|
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_numVerts;
|
|
pMesh->m_nOffset = (unsigned int)pVertexData - (unsigned int)pVhvHdr;
|
|
|
|
// construct vertexes
|
|
for (int k=0; k<m_StaticProps[i].m_MeshData[n].m_VertColorData.Count(); k++)
|
|
{
|
|
Vector &vector = m_StaticProps[i].m_MeshData[n].m_VertColorData[k].AsVector3D();
|
|
|
|
//if ( (vector.x > 1024.0f) || (vector.y > 1024.0f) || (vector.z > 1024.0f) )s
|
|
// Msg(" *** out of range prop lighting *** \n");
|
|
|
|
ColorRGBExp32 rgbColor;
|
|
VectorToColorRGBExp32( vector, 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];
|
|
|
|
// Use the unmodified lighting data to generate the sun percentage, not the output of the RGBE conversions above!
|
|
float flSunAmount = m_StaticProps[i].m_MeshData[n].m_VertColorData[k].w;
|
|
pVertexData[3] = uint8( clamp( flSunAmount, 0.0f, 1.0f ) * 255.0f + 0.5f );
|
|
|
|
pVertexData += 4;
|
|
}
|
|
}
|
|
|
|
// align to end of file
|
|
pVertexData = (unsigned char *)((unsigned int)pVertexData - (unsigned int)pVhvHdr);
|
|
pVertexData = (unsigned char*)pVhvHdr + ALIGN_TO_POW2( (unsigned int)pVertexData, 512 );
|
|
|
|
AddBufferToPak( GetPakFile(), filename, (void*)pVhvHdr, pVertexData - (unsigned char*)pVhvHdr, false );
|
|
}
|
|
}
|
|
|
|
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 ) );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 ) );
|
|
}
|
|
|
|
// Apply the results.
|
|
ApplyLightingToStaticProp( m_StaticProps[iStaticProp], &results );
|
|
}
|
|
|
|
|
|
void CVradStaticPropMgr::ComputeLightingForProp( int iThread, int iStaticProp )
|
|
{
|
|
// Compute the lighting.
|
|
CComputeStaticPropLightingResults results;
|
|
ComputeLighting( m_StaticProps[iStaticProp], iThread, iStaticProp, &results );
|
|
ApplyLightingToStaticProp( 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;
|
|
}
|
|
|
|
double start = Plat_FloatTime();
|
|
|
|
StartPacifier( "Computing static prop lighting : " );
|
|
|
|
#if 0
|
|
CGlViewBuffer glViewBuf;
|
|
glViewBuf.WriteKDTree( &g_RtEnv );
|
|
g_pFullFileSystem->WriteFile( "maps/rtenv.gl", "GAME", glViewBuf );
|
|
#endif
|
|
|
|
// ensure any traces against us are ignored because we have no inherit lighting contribution
|
|
m_bIgnoreStaticPropTrace = true;
|
|
|
|
if ( g_bUseMPI )
|
|
{
|
|
// Distribute the work among the workers.
|
|
VMPI_SetCurrentStage( "CVradStaticPropMgr::ComputeLighting" );
|
|
|
|
DistributeWork(
|
|
count,
|
|
&CVradStaticPropMgr::VMPI_ProcessStaticProp_Static,
|
|
&CVradStaticPropMgr::VMPI_ReceiveStaticPropResults_Static );
|
|
}
|
|
else
|
|
{
|
|
RunThreadsOn(count, true, ThreadComputeStaticPropLighting);
|
|
}
|
|
|
|
// restore default
|
|
m_bIgnoreStaticPropTrace = false;
|
|
|
|
// save data to bsp
|
|
SerializeLighting();
|
|
|
|
EndPacifier( true );
|
|
|
|
double end = Plat_FloatTime();
|
|
|
|
DumpElapsedTime( (int)(end - start) );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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
|
|
// must have model and its verts for decoding triangles
|
|
printf( "Can't get studio header (%p) and vertex data (%p) for %s\n", pStudioHdr, pVtxHdr,
|
|
pStudioHdr ? pStudioHdr->name : "***unknown***" );
|
|
continue;
|
|
}
|
|
// only init the triangle table the first time
|
|
bool bInitTriangles = dict.m_triangleMaterialIndex.Count() ? false : true;
|
|
int triangleIndex = 0;
|
|
// transform position into world coordinate system
|
|
matrix3x4_t matrix;
|
|
AngleMatrix( prop.m_Angles, prop.m_Origin, matrix );
|
|
|
|
// 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 );
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 );
|
|
|
|
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 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void *pModelData )
|
|
{
|
|
studiohdr_t *pActiveStudioHdr = static_cast<studiohdr_t *>(pModelData);
|
|
Assert( pActiveStudioHdr );
|
|
|
|
if ( pActiveStudioHdr->VertexBase() )
|
|
{
|
|
return (vertexFileHeader_t *)pActiveStudioHdr->VertexBase();
|
|
}
|
|
|
|
// 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
|
|
CUtlBuffer bufData;
|
|
if ( !LoadFile( fileName, bufData ) )
|
|
{
|
|
Error( "Unable to load vertex data \"%s\"\n", fileName );
|
|
}
|
|
|
|
// Get the file size
|
|
int vvdSize = bufData.TellPut();
|
|
if ( vvdSize == 0 )
|
|
{
|
|
Error( "Bad size for vertex data \"%s\"\n", fileName );
|
|
}
|
|
|
|
vertexFileHeader_t *pVvdHdr = (vertexFileHeader_t *) bufData.Base();
|
|
|
|
// 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
|
|
bool bExtraData = (pActiveStudioHdr->flags & STUDIOHDR_FLAGS_EXTRA_VERTEX_DATA) != 0;
|
|
Studio_LoadVertexes(pVvdHdr, pNewVvdHdr, 0, true, bExtraData);
|
|
|
|
// discard original
|
|
pVvdHdr = pNewVvdHdr;
|
|
|
|
pActiveStudioHdr->SetVertexBase( (void*)pVvdHdr );
|
|
return pVvdHdr;
|
|
}
|
|
|
|
extern float totalarea;
|
|
extern unsigned num_degenerate_faces;
|
|
extern int fakeplanes;
|
|
extern int PlaneTypeForNormal( Vector& normal );
|
|
|
|
void MakePatchForTriangle( winding_t *w, Vector vRefl, int nStaticPropIdx )
|
|
{
|
|
float area;
|
|
CPatch *patch;
|
|
Vector centroid( 0, 0, 0 );
|
|
|
|
area = WindingArea( w );
|
|
if ( area <= 0 )
|
|
{
|
|
num_degenerate_faces++;
|
|
return;
|
|
}
|
|
|
|
totalarea += area;
|
|
|
|
// get a patch
|
|
int ndxPatch = g_Patches.AddToTail();
|
|
patch = &g_Patches[ ndxPatch ];
|
|
memset( patch, 0, sizeof( CPatch ) );
|
|
patch->ndxNext = g_Patches.InvalidIndex();
|
|
patch->ndxNextParent = g_Patches.InvalidIndex();
|
|
patch->ndxNextClusterChild = g_Patches.InvalidIndex();
|
|
patch->child1 = g_Patches.InvalidIndex();
|
|
patch->child2 = g_Patches.InvalidIndex();
|
|
patch->parent = g_Patches.InvalidIndex();
|
|
patch->needsBumpmap = false;
|
|
patch->staticPropIdx = nStaticPropIdx;
|
|
|
|
patch->scale[ 0 ] = patch->scale[ 1 ] = 1.0f;
|
|
patch->area = area;
|
|
patch->sky = false;
|
|
|
|
// chop scaled up lightmaps coarser
|
|
patch->luxscale = 16.0f;
|
|
patch->chop = maxchop;
|
|
|
|
patch->winding = w;
|
|
|
|
patch->plane = new dplane_t;
|
|
|
|
Vector vecNormal;
|
|
CrossProduct( w->p[ 2 ] - w->p[ 0 ], w->p[ 1 ] - w->p[ 0 ], vecNormal );
|
|
VectorNormalize( vecNormal );
|
|
VectorCopy( vecNormal, patch->plane->normal );
|
|
|
|
patch->plane->dist = vecNormal.Dot( w->p[ 0 ] );
|
|
patch->plane->type = PlaneTypeForNormal( patch->plane->normal );
|
|
patch->planeDist = patch->plane->dist;
|
|
|
|
patch->faceNumber = -1; // This is a bit hacky and is used to identify static prop patches in other parts of the code
|
|
WindingCenter( w, patch->origin );
|
|
|
|
VectorCopy( patch->plane->normal, patch->normal );
|
|
|
|
WindingBounds( w, patch->face_mins, patch->face_maxs );
|
|
VectorCopy( patch->face_mins, patch->mins );
|
|
VectorCopy( patch->face_maxs, patch->maxs );
|
|
|
|
patch->baselight.Init( 0.0f, 0.0f, 0.0f );
|
|
patch->basearea = 1;
|
|
patch->reflectivity = vRefl;
|
|
}
|
|
|
|
|
|
|
|
void CVradStaticPropMgr::MakePatches()
|
|
{
|
|
int count = m_StaticProps.Count();
|
|
if ( !count )
|
|
{
|
|
// nothing to do
|
|
return;
|
|
}
|
|
|
|
// Triangle coverage of 1 (full coverage)
|
|
Vector fullCoverage;
|
|
fullCoverage.x = 1.0f;
|
|
int nPatchCount = 0;
|
|
|
|
//IScratchPad3D *pPad = ScratchPad3D_Create();
|
|
//pPad->SetAutoFlush( false );
|
|
for ( int nProp = 0; nProp < count; ++nProp )
|
|
{
|
|
CStaticProp &prop = m_StaticProps[ nProp ];
|
|
if ( ( prop.m_FlagsEx & STATIC_PROP_FLAGS_EX_ENABLE_LIGHT_BOUNCE ) == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
StaticPropDict_t &dict = m_StaticPropDict[ prop.m_ModelIdx ];
|
|
|
|
if ( dict.m_pModel )
|
|
{
|
|
// Get material, get reflectivity
|
|
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 ] );
|
|
|
|
//pPad->DrawPolygon( CSPVertList( verts, 3, CSPColor( prop.m_vReflectivity ) ) );
|
|
//pPad->DrawLine( CSPVert( g_Patches.Tail().origin ), CSPVert( g_Patches.Tail().origin + 5.0f * g_Patches.Tail().normal) );
|
|
|
|
winding_t *w = AllocWinding( 3 );
|
|
for ( int i = 0; i < 3; i++ )
|
|
{
|
|
w->p[ i ] = verts[ i ];
|
|
}
|
|
w->numpoints = 3;
|
|
MakePatchForTriangle( w, prop.m_vReflectivity, nProp );
|
|
//pPad->DrawPolygon( CSPVertList( verts, 3 ) );
|
|
//pPad->DrawLine( CSPVert( g_Patches.Tail().origin ), CSPVert( g_Patches.Tail().origin + 5.0f * g_Patches.Tail().normal) );
|
|
g_RtEnv_RadiosityPatches.AddTriangle( TRACE_ID_PATCH | (g_Patches.Count() - 1), verts[ 0 ], verts[ 1 ], verts[ 2 ], Vector( 1.0f, 1.0f, 1.0f ) );
|
|
nPatchCount++;
|
|
}
|
|
}
|
|
s_pPhysCollision->DestroyQueryModel( queryModel );
|
|
}
|
|
else
|
|
{
|
|
// FIXME
|
|
#if 0
|
|
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 );
|
|
#endif
|
|
}
|
|
}
|
|
//pPad->Release();
|
|
g_RtEnv_RadiosityPatches.SetupAccelerationStructure();
|
|
qprintf( "%i static prop patches\n", nPatchCount );
|
|
}
|