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.
5056 lines
165 KiB
5056 lines
165 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// models are the only shared resource between a client and server running |
|
// on the same machine. |
|
//===========================================================================// |
|
|
|
#include "render_pch.h" |
|
#include "client.h" |
|
#include "gl_model_private.h" |
|
#include "studio.h" |
|
#include "phyfile.h" |
|
#include "cdll_int.h" |
|
#include "istudiorender.h" |
|
#include "client_class.h" |
|
#include "float.h" |
|
#include "materialsystem/imaterialsystemhardwareconfig.h" |
|
#include "materialsystem/ivballoctracker.h" |
|
#include "modelloader.h" |
|
#include "lightcache.h" |
|
#include "studio_internal.h" |
|
#include "cdll_engine_int.h" |
|
#include "vphysics_interface.h" |
|
#include "utllinkedlist.h" |
|
#include "studio.h" |
|
#include "icliententitylist.h" |
|
#include "engine/ivmodelrender.h" |
|
#include "optimize.h" |
|
#include "icliententity.h" |
|
#include "sys_dll.h" |
|
#include "debugoverlay.h" |
|
#include "enginetrace.h" |
|
#include "l_studio.h" |
|
#include "filesystem_engine.h" |
|
#include "ModelInfo.h" |
|
#include "cl_main.h" |
|
#include "tier0/vprof.h" |
|
#include "r_decal.h" |
|
#include "vstdlib/random.h" |
|
#include "datacache/idatacache.h" |
|
#include "materialsystem/materialsystem_config.h" |
|
#include "materialsystem/itexture.h" |
|
#include "IHammer.h" |
|
#if defined( _WIN32 ) && !defined( _X360 ) |
|
#include <xmmintrin.h> |
|
#endif |
|
#include "staticpropmgr.h" |
|
#include "materialsystem/hardwaretexels.h" |
|
#include "materialsystem/hardwareverts.h" |
|
#include "tier1/callqueue.h" |
|
#include "filesystem/IQueuedLoader.h" |
|
#include "tier2/tier2.h" |
|
#include "tier1/UtlSortVector.h" |
|
#include "tier1/lzmaDecoder.h" |
|
#include "ipooledvballocator.h" |
|
#include "shaderapi/ishaderapi.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
// #define VISUALIZE_TIME_AVERAGE 1 |
|
|
|
extern ConVar r_flashlight_version2; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Forward declarations |
|
//----------------------------------------------------------------------------- |
|
void R_StudioInitLightingCache( void ); |
|
float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector& delta, bool bNoRadiusCheck = false ); |
|
void SetRootLOD_f( IConVar *var, const char *pOldString, float flOldValue ); |
|
void r_lod_f( IConVar *var, const char *pOldValue, float flOldValue ); |
|
void FlushLOD_f(); |
|
|
|
class CColorMeshData; |
|
static void CreateLightmapsFromData(CColorMeshData* _colorMeshData); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Global variables |
|
//----------------------------------------------------------------------------- |
|
|
|
ConVar r_drawmodelstatsoverlay( "r_drawmodelstatsoverlay", "0", FCVAR_CHEAT ); |
|
ConVar r_drawmodelstatsoverlaydistance( "r_drawmodelstatsoverlaydistance", "500", FCVAR_CHEAT ); |
|
ConVar r_drawmodellightorigin( "r_DrawModelLightOrigin", "0", FCVAR_CHEAT ); |
|
extern ConVar r_worldlights; |
|
ConVar r_lod( "r_lod", "-1", 0, "", r_lod_f ); |
|
static ConVar r_entity( "r_entity", "-1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
static ConVar r_lightaverage( "r_lightaverage", "1", 0, "Activates/deactivate light averaging" ); |
|
static ConVar r_lightinterp( "r_lightinterp", "5", FCVAR_CHEAT, "Controls the speed of light interpolation, 0 turns off interpolation" ); |
|
static ConVar r_eyeglintlodpixels( "r_eyeglintlodpixels", "20.0", FCVAR_CHEAT, "The number of pixels wide an eyeball has to be before rendering an eyeglint. Is a floating point value." ); |
|
ConVar r_rootlod( "r_rootlod", "0", FCVAR_MATERIAL_SYSTEM_THREAD | FCVAR_ARCHIVE, "Root LOD", true, 0, true, MAX_NUM_LODS-1, SetRootLOD_f ); |
|
static ConVar r_decalstaticprops( "r_decalstaticprops", "1", 0, "Decal static props test" ); |
|
static ConCommand r_flushlod( "r_flushlod", FlushLOD_f, "Flush and reload LODs." ); |
|
ConVar r_debugrandomstaticlighting( "r_debugrandomstaticlighting", "0", FCVAR_CHEAT, "Set to 1 to randomize static lighting for debugging. Must restart for change to take affect." ); |
|
ConVar r_proplightingfromdisk( "r_proplightingfromdisk", "1", FCVAR_CHEAT, "0=Off, 1=On, 2=Show Errors" ); |
|
static ConVar r_itemblinkmax( "r_itemblinkmax", ".3", FCVAR_CHEAT ); |
|
static ConVar r_itemblinkrate( "r_itemblinkrate", "4.5", FCVAR_CHEAT ); |
|
static ConVar r_proplightingpooling( "r_proplightingpooling", "-1.0", FCVAR_CHEAT, "0 - off, 1 - static prop color meshes are allocated from a single shared vertex buffer (on hardware that supports stream offset)" ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// StudioRender config |
|
//----------------------------------------------------------------------------- |
|
static ConVar r_showenvcubemap( "r_showenvcubemap", "0", FCVAR_CHEAT ); |
|
static ConVar r_eyemove ( "r_eyemove", "1", FCVAR_ARCHIVE ); // look around |
|
static ConVar r_eyeshift_x ( "r_eyeshift_x", "0", FCVAR_ARCHIVE ); // eye X position |
|
static ConVar r_eyeshift_y ( "r_eyeshift_y", "0", FCVAR_ARCHIVE ); // eye Y position |
|
static ConVar r_eyeshift_z ( "r_eyeshift_z", "0", FCVAR_ARCHIVE ); // eye Z position |
|
static ConVar r_eyesize ( "r_eyesize", "0", FCVAR_ARCHIVE ); // adjustment to iris textures |
|
static ConVar mat_softwareskin( "mat_softwareskin", "0", FCVAR_CHEAT ); |
|
static ConVar r_nohw ( "r_nohw", "0", FCVAR_CHEAT ); |
|
static ConVar r_nosw ( "r_nosw", "0", FCVAR_CHEAT ); |
|
static ConVar r_teeth ( "r_teeth", "1" ); |
|
static ConVar r_drawentities ( "r_drawentities", "1", FCVAR_CHEAT ); |
|
static ConVar r_flex ( "r_flex", "1" ); |
|
static ConVar r_eyes ( "r_eyes", "1" ); |
|
static ConVar r_skin ( "r_skin", "0", FCVAR_CHEAT ); |
|
static ConVar r_modelwireframedecal ( "r_modelwireframedecal", "0", FCVAR_CHEAT ); |
|
static ConVar r_maxmodeldecal ( "r_maxmodeldecal", "50" ); |
|
|
|
static StudioRenderConfig_t s_StudioRenderConfig; |
|
|
|
void UpdateStudioRenderConfig( void ) |
|
{ |
|
// This can happen during initialization |
|
if ( !g_pMaterialSystemConfig || !g_pStudioRender ) |
|
return; |
|
|
|
memset( &s_StudioRenderConfig, 0, sizeof(s_StudioRenderConfig) ); |
|
|
|
s_StudioRenderConfig.bEyeMove = !!r_eyemove.GetInt(); |
|
s_StudioRenderConfig.fEyeShiftX = r_eyeshift_x.GetFloat(); |
|
s_StudioRenderConfig.fEyeShiftY = r_eyeshift_y.GetFloat(); |
|
s_StudioRenderConfig.fEyeShiftZ = r_eyeshift_z.GetFloat(); |
|
s_StudioRenderConfig.fEyeSize = r_eyesize.GetFloat(); |
|
if ( IsPC() && ( mat_softwareskin.GetInt() || ShouldDrawInWireFrameMode() ) ) |
|
{ |
|
s_StudioRenderConfig.bSoftwareSkin = true; |
|
} |
|
else |
|
{ |
|
s_StudioRenderConfig.bSoftwareSkin = false; |
|
} |
|
s_StudioRenderConfig.bNoHardware = !!r_nohw.GetInt(); |
|
s_StudioRenderConfig.bNoSoftware = !!r_nosw.GetInt(); |
|
s_StudioRenderConfig.bTeeth = !!r_teeth.GetInt(); |
|
s_StudioRenderConfig.drawEntities = r_drawentities.GetInt(); |
|
s_StudioRenderConfig.bFlex = !!r_flex.GetInt(); |
|
s_StudioRenderConfig.bEyes = !!r_eyes.GetInt(); |
|
s_StudioRenderConfig.bWireframe = ShouldDrawInWireFrameMode(); |
|
s_StudioRenderConfig.bDrawNormals = mat_normals.GetBool(); |
|
s_StudioRenderConfig.skin = r_skin.GetInt(); |
|
s_StudioRenderConfig.maxDecalsPerModel = r_maxmodeldecal.GetInt(); |
|
s_StudioRenderConfig.bWireframeDecals = r_modelwireframedecal.GetInt() != 0; |
|
|
|
s_StudioRenderConfig.fullbright = g_pMaterialSystemConfig->nFullbright; |
|
s_StudioRenderConfig.bSoftwareLighting = g_pMaterialSystemConfig->bSoftwareLighting; |
|
|
|
s_StudioRenderConfig.bShowEnvCubemapOnly = r_showenvcubemap.GetInt() ? true : false; |
|
s_StudioRenderConfig.fEyeGlintPixelWidthLODThreshold = r_eyeglintlodpixels.GetFloat(); |
|
|
|
g_pStudioRender->UpdateConfig( s_StudioRenderConfig ); |
|
} |
|
|
|
void R_InitStudio( void ) |
|
{ |
|
#ifndef SWDS |
|
R_StudioInitLightingCache(); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Converts world lights to materialsystem lights |
|
//----------------------------------------------------------------------------- |
|
|
|
#define MIN_LIGHT_VALUE 0.03f |
|
|
|
bool WorldLightToMaterialLight( dworldlight_t* pWorldLight, LightDesc_t& light ) |
|
{ |
|
// BAD |
|
light.m_Attenuation0 = 0.0f; |
|
light.m_Attenuation1 = 0.0f; |
|
light.m_Attenuation2 = 0.0f; |
|
|
|
switch(pWorldLight->type) |
|
{ |
|
case emit_spotlight: |
|
light.m_Type = MATERIAL_LIGHT_SPOT; |
|
light.m_Attenuation0 = pWorldLight->constant_attn; |
|
light.m_Attenuation1 = pWorldLight->linear_attn; |
|
light.m_Attenuation2 = pWorldLight->quadratic_attn; |
|
light.m_Theta = 2.0 * acos( pWorldLight->stopdot ); |
|
light.m_Phi = 2.0 * acos( pWorldLight->stopdot2 ); |
|
light.m_ThetaDot = pWorldLight->stopdot; |
|
light.m_PhiDot = pWorldLight->stopdot2; |
|
light.m_Falloff = pWorldLight->exponent ? pWorldLight->exponent : 1.0f; |
|
break; |
|
|
|
case emit_surface: |
|
// A 180 degree spotlight |
|
light.m_Type = MATERIAL_LIGHT_SPOT; |
|
light.m_Attenuation2 = 1.0; |
|
light.m_Theta = M_PI; |
|
light.m_Phi = M_PI; |
|
light.m_ThetaDot = 0.0f; |
|
light.m_PhiDot = 0.0f; |
|
light.m_Falloff = 1.0f; |
|
break; |
|
|
|
case emit_point: |
|
light.m_Type = MATERIAL_LIGHT_POINT; |
|
light.m_Attenuation0 = pWorldLight->constant_attn; |
|
light.m_Attenuation1 = pWorldLight->linear_attn; |
|
light.m_Attenuation2 = pWorldLight->quadratic_attn; |
|
break; |
|
|
|
case emit_skylight: |
|
light.m_Type = MATERIAL_LIGHT_DIRECTIONAL; |
|
break; |
|
|
|
// NOTE: Can't do quake lights in hardware (x-r factor) |
|
case emit_quakelight: // not supported |
|
case emit_skyambient: // doesn't factor into local lighting |
|
// skip these |
|
return false; |
|
} |
|
|
|
// No attenuation case.. |
|
if ((light.m_Attenuation0 == 0.0f) && (light.m_Attenuation1 == 0.0f) && |
|
(light.m_Attenuation2 == 0.0f)) |
|
{ |
|
light.m_Attenuation0 = 1.0f; |
|
} |
|
|
|
// renormalize light intensity... |
|
memcpy( &light.m_Position, &pWorldLight->origin, 3 * sizeof(float) ); |
|
memcpy( &light.m_Direction, &pWorldLight->normal, 3 * sizeof(float) ); |
|
light.m_Color[0] = pWorldLight->intensity[0]; |
|
light.m_Color[1] = pWorldLight->intensity[1]; |
|
light.m_Color[2] = pWorldLight->intensity[2]; |
|
|
|
// Make it stop when the lighting gets to min%... |
|
float intensity = sqrtf( DotProduct( light.m_Color, light.m_Color ) ); |
|
|
|
// Compute the light range based on attenuation factors |
|
if (pWorldLight->radius != 0) |
|
{ |
|
light.m_Range = pWorldLight->radius; |
|
} |
|
else |
|
{ |
|
// FALLBACK: older lights use this |
|
if (light.m_Attenuation2 == 0.0f) |
|
{ |
|
if (light.m_Attenuation1 == 0.0f) |
|
{ |
|
light.m_Range = sqrtf(FLT_MAX); |
|
} |
|
else |
|
{ |
|
light.m_Range = (intensity / MIN_LIGHT_VALUE - light.m_Attenuation0) / light.m_Attenuation1; |
|
} |
|
} |
|
else |
|
{ |
|
float a = light.m_Attenuation2; |
|
float b = light.m_Attenuation1; |
|
float c = light.m_Attenuation0 - intensity / MIN_LIGHT_VALUE; |
|
float discrim = b * b - 4 * a * c; |
|
if (discrim < 0.0f) |
|
light.m_Range = sqrtf(FLT_MAX); |
|
else |
|
{ |
|
light.m_Range = (-b + sqrtf(discrim)) / (2.0f * a); |
|
if (light.m_Range < 0) |
|
light.m_Range = 0; |
|
} |
|
} |
|
} |
|
light.m_Flags = LIGHTTYPE_OPTIMIZATIONFLAGS_DERIVED_VALUES_CALCED; |
|
if( light.m_Attenuation0 != 0.0f ) |
|
{ |
|
light.m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION0; |
|
} |
|
if( light.m_Attenuation1 != 0.0f ) |
|
{ |
|
light.m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION1; |
|
} |
|
if( light.m_Attenuation2 != 0.0f ) |
|
{ |
|
light.m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION2; |
|
} |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets the hardware lighting state |
|
//----------------------------------------------------------------------------- |
|
|
|
static void R_SetNonAmbientLightingState( int numLights, dworldlight_t *locallight[MAXLOCALLIGHTS], |
|
int *pNumLightDescs, LightDesc_t *pLightDescs, bool bUpdateStudioRenderLights ) |
|
{ |
|
Assert( numLights >= 0 && numLights <= MAXLOCALLIGHTS ); |
|
|
|
// convert dworldlight_t's to LightDesc_t's and send 'em down to g_pStudioRender-> |
|
*pNumLightDescs = 0; |
|
|
|
LightDesc_t *pLightDesc; |
|
for ( int i = 0; i < numLights; i++) |
|
{ |
|
pLightDesc = &pLightDescs[*pNumLightDescs]; |
|
if (!WorldLightToMaterialLight( locallight[i], *pLightDesc )) |
|
continue; |
|
|
|
// Apply lightstyle |
|
float bias = LightStyleValue( locallight[i]->style ); |
|
|
|
// Deal with overbrighting + bias |
|
pLightDesc->m_Color[0] *= bias; |
|
pLightDesc->m_Color[1] *= bias; |
|
pLightDesc->m_Color[2] *= bias; |
|
|
|
*pNumLightDescs += 1; |
|
Assert( *pNumLightDescs <= MAXLOCALLIGHTS ); |
|
} |
|
|
|
if ( bUpdateStudioRenderLights ) |
|
{ |
|
g_pStudioRender->SetLocalLights( *pNumLightDescs, pLightDescs ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the center of the studio model for illumination purposes |
|
//----------------------------------------------------------------------------- |
|
void R_ComputeLightingOrigin( IClientRenderable *pRenderable, studiohdr_t* pStudioHdr, const matrix3x4_t &matrix, Vector& center ) |
|
{ |
|
int nAttachmentIndex = pStudioHdr->IllumPositionAttachmentIndex(); |
|
if ( nAttachmentIndex <= 0 ) |
|
{ |
|
VectorTransform( pStudioHdr->illumposition, matrix, center ); |
|
} |
|
else |
|
{ |
|
matrix3x4_t attachment; |
|
pRenderable->GetAttachment( nAttachmentIndex, attachment ); |
|
VectorTransform( pStudioHdr->illumposition, attachment, center ); |
|
} |
|
} |
|
|
|
|
|
|
|
#if 0 |
|
// garymct - leave this in here for now. . we might need this for bumped models |
|
void R_StudioCalculateVirtualLightAndLightCube( Vector& mid, Vector& virtualLightPosition, |
|
Vector& virtualLightColor, Vector* lightBoxColor ) |
|
{ |
|
int i, j; |
|
Vector delta; |
|
float dist2, ratio; |
|
byte *pvis; |
|
float t; |
|
static ConVar bumpLightBlendRatioMin( "bump_light_blend_ratio_min", "0.00002" ); |
|
static ConVar bumpLightBlendRatioMax( "bump_light_blend_ratio_max", "0.00004" ); |
|
|
|
if ( g_pMaterialSystemConfig->nFullbright == 1 ) |
|
return; |
|
|
|
VectorClear( virtualLightPosition ); |
|
VectorClear( virtualLightColor ); |
|
for( i = 0; i < 6; i++ ) |
|
{ |
|
VectorClear( lightBoxColor[i] ); |
|
} |
|
byte pvs[MAX_MAP_LEAFS/8]; |
|
pvis = CM_Vis( pvs, sizeof(pvs), CM_LeafCluster( CM_PointLeafnum( mid ), DVIS_PVS ); |
|
|
|
float sumBumpBlendParam = 0; |
|
for (i = 0; i < host_state.worldbrush->numworldlights; i++) |
|
{ |
|
dworldlight_t *wl = &host_state.worldbrush->worldlights[i]; |
|
|
|
if (wl->cluster < 0) |
|
continue; |
|
|
|
// only do it if the entity can see into the lights leaf |
|
if (!BIT_SET( pvis, (wl->cluster))) |
|
continue; |
|
|
|
// hack: for this test, only deal with point light sources. |
|
if( wl->type != emit_point ) |
|
continue; |
|
|
|
// check distance |
|
VectorSubtract( wl->origin, mid, delta ); |
|
dist2 = DotProduct( delta, delta ); |
|
|
|
ratio = R_WorldLightDistanceFalloff( wl, delta ); |
|
|
|
VectorNormalize( delta ); |
|
|
|
ratio = ratio * R_WorldLightAngle( wl, wl->normal, delta, delta ); |
|
|
|
float bumpBlendParam; // 0.0 = all cube, 1.0 = all bump |
|
|
|
// lerp |
|
bumpBlendParam = |
|
( ratio - bumpLightBlendRatioMin.GetFloat() ) / |
|
( bumpLightBlendRatioMax.GetFloat() - bumpLightBlendRatioMin.GetFloat() ); |
|
|
|
if( bumpBlendParam > 0.0 ) |
|
{ |
|
// Get the bit that goes into the bump light |
|
sumBumpBlendParam += bumpBlendParam; |
|
VectorMA( virtualLightPosition, bumpBlendParam, wl->origin, virtualLightPosition ); |
|
VectorMA( virtualLightColor, bumpBlendParam, wl->intensity, virtualLightColor ); |
|
} |
|
|
|
if( bumpBlendParam < 1.0f ) |
|
{ |
|
// Get the bit that goes into the cube |
|
float cubeBlendParam; |
|
cubeBlendParam = 1.0f - bumpBlendParam; |
|
if( cubeBlendParam < 0.0f ) |
|
{ |
|
cubeBlendParam = 0.0f; |
|
} |
|
for (j = 0; j < numBoxDir; j++) |
|
{ |
|
t = DotProduct( r_boxdir[j], delta ); |
|
if (t > 0) |
|
{ |
|
VectorMA( lightBoxColor[j], ratio * t * cubeBlendParam, wl->intensity, lightBoxColor[j] ); |
|
} |
|
} |
|
} |
|
} |
|
// Get the final virtual light position and color. |
|
VectorMultiply( virtualLightPosition, 1.0f / sumBumpBlendParam, virtualLightPosition ); |
|
VectorMultiply( virtualLightColor, 1.0f / sumBumpBlendParam, virtualLightColor ); |
|
} |
|
#endif |
|
|
|
|
|
// TODO: move cone calcs to position |
|
// TODO: cone clipping calc's wont work for boxlight since the player asks for a single point. Not sure what the volume is. |
|
float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector& delta, bool bNoRadiusCheck ) |
|
{ |
|
float falloff; |
|
|
|
switch (wl->type) |
|
{ |
|
case emit_surface: |
|
#if 1 |
|
// Cull out stuff that's too far |
|
if (wl->radius != 0) |
|
{ |
|
if ( DotProduct( delta, delta ) > (wl->radius * wl->radius)) |
|
return 0.0f; |
|
} |
|
|
|
return InvRSquared(delta); |
|
#else |
|
// 1/r*r |
|
falloff = DotProduct( delta, delta ); |
|
if (falloff < 1) |
|
return 1.f; |
|
else |
|
return 1.f / falloff; |
|
#endif |
|
|
|
break; |
|
|
|
case emit_skylight: |
|
return 1.f; |
|
break; |
|
|
|
case emit_quakelight: |
|
// X - r; |
|
falloff = wl->linear_attn - FastSqrt( DotProduct( delta, delta ) ); |
|
if (falloff < 0) |
|
return 0.f; |
|
|
|
return falloff; |
|
break; |
|
|
|
case emit_skyambient: |
|
return 1.f; |
|
break; |
|
|
|
case emit_point: |
|
case emit_spotlight: // directional & positional |
|
{ |
|
float dist2, dist; |
|
|
|
dist2 = DotProduct( delta, delta ); |
|
dist = FastSqrt( dist2 ); |
|
|
|
// Cull out stuff that's too far |
|
if (!bNoRadiusCheck && (wl->radius != 0) && (dist > wl->radius)) |
|
return 0.f; |
|
|
|
return 1.f / (wl->constant_attn + wl->linear_attn * dist + wl->quadratic_attn * dist2); |
|
} |
|
|
|
break; |
|
default: |
|
// Bug: need to return an error |
|
break; |
|
} |
|
return 1.f; |
|
} |
|
|
|
/* |
|
light_normal (lights normal translated to same space as other normals) |
|
surface_normal |
|
light_direction_normal | (light_pos - vertex_pos) | |
|
*/ |
|
float Engine_WorldLightAngle( const dworldlight_t *wl, const Vector& lnormal, const Vector& snormal, const Vector& delta ) |
|
{ |
|
float dot, dot2, ratio = 0; |
|
|
|
switch (wl->type) |
|
{ |
|
case emit_surface: |
|
dot = DotProduct( snormal, delta ); |
|
if (dot < 0) |
|
return 0; |
|
|
|
dot2 = -DotProduct (delta, lnormal); |
|
if (dot2 <= ON_EPSILON/10) |
|
return 0; // behind light surface |
|
|
|
return dot * dot2; |
|
|
|
case emit_point: |
|
dot = DotProduct( snormal, delta ); |
|
if (dot < 0) |
|
return 0; |
|
return dot; |
|
|
|
case emit_spotlight: |
|
// return 1.0; // !!! |
|
dot = DotProduct( snormal, delta ); |
|
if (dot < 0) |
|
return 0; |
|
|
|
dot2 = -DotProduct (delta, lnormal); |
|
if (dot2 <= wl->stopdot2) |
|
return 0; // outside light cone |
|
|
|
ratio = dot; |
|
if (dot2 >= wl->stopdot) |
|
return ratio; // inside inner cone |
|
|
|
if ((wl->exponent == 1) || (wl->exponent == 0)) |
|
{ |
|
ratio *= (dot2 - wl->stopdot2) / (wl->stopdot - wl->stopdot2); |
|
} |
|
else |
|
{ |
|
ratio *= pow((dot2 - wl->stopdot2) / (wl->stopdot - wl->stopdot2), wl->exponent ); |
|
} |
|
return ratio; |
|
|
|
case emit_skylight: |
|
dot2 = -DotProduct( snormal, lnormal ); |
|
if (dot2 < 0) |
|
return 0; |
|
return dot2; |
|
|
|
case emit_quakelight: |
|
// linear falloff |
|
dot = DotProduct( snormal, delta ); |
|
if (dot < 0) |
|
return 0; |
|
return dot; |
|
|
|
case emit_skyambient: |
|
// not supported |
|
return 1; |
|
|
|
default: |
|
// Bug: need to return an error |
|
break; |
|
} |
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Allocator for color mesh vertex buffers (for use with static props only). |
|
// It uses a trivial allocation scheme, which assumes that allocations and |
|
// deallocations are not interleaved (you do all allocs, then all deallocs). |
|
//----------------------------------------------------------------------------- |
|
class CPooledVBAllocator_ColorMesh : public IPooledVBAllocator |
|
{ |
|
public: |
|
|
|
CPooledVBAllocator_ColorMesh(); |
|
virtual ~CPooledVBAllocator_ColorMesh(); |
|
|
|
// Allocate the shared mesh (vertex buffer) |
|
virtual bool Init( VertexFormat_t format, int numVerts ); |
|
// Free the shared mesh (after Deallocate is called for all sub-allocs) |
|
virtual void Clear(); |
|
|
|
// Get the shared mesh (vertex buffer) from which sub-allocations are made |
|
virtual IMesh *GetSharedMesh() { return m_pMesh; } |
|
|
|
// Get a pointer to the start of the vertex buffer data |
|
virtual void *GetVertexBufferBase() { return m_pVertexBufferBase; } |
|
virtual int GetNumVertsAllocated() { return m_totalVerts; } |
|
|
|
// Allocate a sub-range of 'numVerts' from free space in the shared vertex buffer |
|
// (returns the byte offset from the start of the VB to the new allocation) |
|
virtual int Allocate( int numVerts ); |
|
// Deallocate an existing allocation |
|
virtual void Deallocate( int offset, int numVerts ); |
|
|
|
private: |
|
|
|
// Assert/warn that the allocator is in a clear/empty state (returns FALSE if not) |
|
bool CheckIsClear( void ); |
|
|
|
IMesh *m_pMesh; // The shared mesh (vertex buffer) from which sub-allocations are made |
|
void *m_pVertexBufferBase; // A pointer to the start of the vertex buffer data |
|
int m_totalVerts; // The number of verts in the shared vertex buffer |
|
int m_vertexSize; // The stride of the shared vertex buffer |
|
|
|
int m_numAllocations; // The number of extant allocations |
|
int m_numVertsAllocated; // The number of vertices in extant allocations |
|
int m_nextFreeOffset; // The offset to be returned by the next call to Allocate() |
|
// (incremented as a simple stack) |
|
bool m_bStartedDeallocation; // This is set when Deallocate() is called for the first time, |
|
// at which point Allocate() cannot be called again until all |
|
// extant allocations have been deallocated. |
|
}; |
|
|
|
struct colormeshparams_t |
|
{ |
|
int m_nMeshes; |
|
int m_nTotalVertexes; |
|
// Given memory alignment (VBs must be 4-KB aligned on X360, for example), it can be more efficient |
|
// to allocate many color meshes out of a single shared vertex buffer (using vertex 'stream offset') |
|
IPooledVBAllocator *m_pPooledVBAllocator; |
|
int m_nVertexes[256]; |
|
FileNameHandle_t m_fnHandle; |
|
}; |
|
|
|
class CColorMeshData |
|
{ |
|
public: |
|
void DestroyResource() |
|
{ |
|
g_pFileSystem->AsyncFinish( m_hAsyncControlVertex, true ); |
|
g_pFileSystem->AsyncRelease( m_hAsyncControlVertex ); |
|
|
|
g_pFileSystem->AsyncFinish( m_hAsyncControlTexel, true ); |
|
g_pFileSystem->AsyncRelease( m_hAsyncControlTexel ); |
|
|
|
// release the array of meshes |
|
CMatRenderContextPtr pRenderContext( materials ); |
|
|
|
for ( int i=0; i<m_nMeshes; i++ ) |
|
{ |
|
if ( m_pMeshInfos[i].m_pPooledVBAllocator ) |
|
{ |
|
// Let the pooling allocator dealloc this sub-range of the shared vertex buffer |
|
m_pMeshInfos[i].m_pPooledVBAllocator->Deallocate( m_pMeshInfos[i].m_nVertOffsetInBytes, m_pMeshInfos[i].m_nNumVerts ); |
|
} |
|
else |
|
{ |
|
// Free this standalone mesh |
|
pRenderContext->DestroyStaticMesh( m_pMeshInfos[i].m_pMesh ); |
|
} |
|
|
|
if (m_pMeshInfos[i].m_pLightmap) |
|
{ |
|
m_pMeshInfos[i].m_pLightmap->Release(); |
|
m_pMeshInfos[i].m_pLightmap = NULL; |
|
} |
|
|
|
if (m_pMeshInfos[i].m_pLightmapData) |
|
{ |
|
delete [] m_pMeshInfos[i].m_pLightmapData->m_pTexelData; |
|
delete m_pMeshInfos[i].m_pLightmapData; |
|
} |
|
} |
|
|
|
|
|
delete [] m_pMeshInfos; |
|
delete [] m_ppTargets; |
|
delete this; |
|
} |
|
|
|
CColorMeshData *GetData() |
|
{ |
|
return this; |
|
} |
|
|
|
unsigned int Size() |
|
{ |
|
// TODO: This is wrong because we don't currently account for the size of the textures we create. |
|
// However, that data isn't available until way after this query is made, so just live with |
|
// this for now I guess? |
|
return m_nTotalSize; |
|
} |
|
|
|
static CColorMeshData *CreateResource( const colormeshparams_t ¶ms ) |
|
{ |
|
CColorMeshData *data = new CColorMeshData; |
|
|
|
data->m_bHasInvalidVB = false; |
|
data->m_bColorMeshValid = false; |
|
data->m_bColorTextureValid = false; |
|
data->m_bColorTextureCreated = false; |
|
data->m_bNeedsRetry = false; |
|
data->m_hAsyncControlVertex = NULL; |
|
data->m_hAsyncControlTexel = NULL; |
|
data->m_fnHandle = params.m_fnHandle; |
|
|
|
data->m_nTotalSize = params.m_nMeshes * sizeof( IMesh* ) + params.m_nTotalVertexes * 4; |
|
data->m_nMeshes = params.m_nMeshes; |
|
data->m_pMeshInfos = new ColorMeshInfo_t[params.m_nMeshes]; |
|
Q_memset( data->m_pMeshInfos, 0, params.m_nMeshes*sizeof( ColorMeshInfo_t ) ); |
|
data->m_ppTargets = new unsigned char *[params.m_nMeshes]; |
|
|
|
CMeshBuilder meshBuilder; |
|
CMatRenderContextPtr pRenderContext( materials ); |
|
|
|
for ( int i=0; i<params.m_nMeshes; i++ ) |
|
{ |
|
VertexFormat_t vertexFormat = VERTEX_SPECULAR; |
|
|
|
data->m_pMeshInfos[i].m_pMesh = NULL; |
|
data->m_pMeshInfos[i].m_pPooledVBAllocator = params.m_pPooledVBAllocator; |
|
data->m_pMeshInfos[i].m_nVertOffsetInBytes = 0; |
|
data->m_pMeshInfos[i].m_nNumVerts = params.m_nVertexes[i]; |
|
data->m_pMeshInfos[i].m_pLightmapData = NULL; |
|
data->m_pMeshInfos[i].m_pLightmap = NULL; |
|
|
|
if ( params.m_pPooledVBAllocator != NULL ) |
|
{ |
|
// Allocate a portion of a single, shared VB for each color mesh |
|
data->m_pMeshInfos[i].m_nVertOffsetInBytes = params.m_pPooledVBAllocator->Allocate( params.m_nVertexes[i] ); |
|
|
|
if ( data->m_pMeshInfos[i].m_nVertOffsetInBytes == -1 ) |
|
{ |
|
// Failed (fall back to regular allocations) |
|
data->m_pMeshInfos[i].m_pPooledVBAllocator = NULL; |
|
data->m_pMeshInfos[i].m_nVertOffsetInBytes = 0; |
|
} |
|
else |
|
{ |
|
// Set up the mesh+data pointers |
|
data->m_pMeshInfos[i].m_pMesh = params.m_pPooledVBAllocator->GetSharedMesh(); |
|
data->m_ppTargets[i] = ( (unsigned char *)params.m_pPooledVBAllocator->GetVertexBufferBase() ) + data->m_pMeshInfos[i].m_nVertOffsetInBytes; |
|
} |
|
} |
|
|
|
if ( data->m_pMeshInfos[i].m_pMesh == NULL ) |
|
{ |
|
if ( g_VBAllocTracker ) |
|
g_VBAllocTracker->TrackMeshAllocations( "CColorMeshData::CreateResource" ); |
|
|
|
// Allocate a standalone VB per color mesh |
|
data->m_pMeshInfos[i].m_pMesh = pRenderContext->CreateStaticMesh( vertexFormat, TEXTURE_GROUP_STATIC_VERTEX_BUFFER_COLOR ); |
|
|
|
if ( g_VBAllocTracker ) |
|
g_VBAllocTracker->TrackMeshAllocations( NULL ); |
|
} |
|
|
|
Assert( data->m_pMeshInfos[i].m_pMesh ); |
|
if ( !data->m_pMeshInfos[i].m_pMesh ) |
|
{ |
|
data->DestroyResource(); |
|
data = NULL; |
|
break; |
|
} |
|
} |
|
return data; |
|
} |
|
|
|
static unsigned int EstimatedSize( const colormeshparams_t ¶ms ) |
|
{ |
|
// each vertex is a 4 byte color |
|
return params.m_nMeshes * sizeof( IMesh* ) + params.m_nTotalVertexes * 4; |
|
} |
|
|
|
int m_nMeshes; |
|
ColorMeshInfo_t *m_pMeshInfos; |
|
unsigned char **m_ppTargets; |
|
unsigned int m_nTotalSize; |
|
FSAsyncControl_t m_hAsyncControlVertex; |
|
FSAsyncControl_t m_hAsyncControlTexel; |
|
unsigned int m_bHasInvalidVB : 1; |
|
unsigned int m_bColorMeshValid : 1; |
|
unsigned int m_bColorTextureValid : 1; // Whether the texture data is valid, but not necessarily created |
|
unsigned int m_bColorTextureCreated : 1; // Whether the texture data has actually been created. |
|
unsigned int m_bNeedsRetry : 1; |
|
|
|
FileNameHandle_t m_fnHandle; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// Implementation of IVModelRender |
|
// |
|
//----------------------------------------------------------------------------- |
|
|
|
// UNDONE: Move this to hud export code, subsume previous functions |
|
class CModelRender : public IVModelRender, |
|
public CManagedDataCacheClient< CColorMeshData, colormeshparams_t > |
|
{ |
|
public: |
|
// members of the IVModelRender interface |
|
virtual void ForcedMaterialOverride( IMaterial *newMaterial, OverrideType_t nOverrideType = OVERRIDE_NORMAL ); |
|
virtual int DrawModel( |
|
int flags, IClientRenderable *cliententity, |
|
ModelInstanceHandle_t instance, int entity_index, const model_t *model, |
|
const Vector& origin, QAngle const& angles, |
|
int skin, int body, int hitboxset, |
|
const matrix3x4_t* pModelToWorld, |
|
const matrix3x4_t *pLightingOffset ); |
|
|
|
virtual void SetViewTarget( const CStudioHdr *pStudioHdr, int nBodyIndex, const Vector& target ); |
|
|
|
// Creates, destroys instance data to be associated with the model |
|
virtual ModelInstanceHandle_t CreateInstance( IClientRenderable *pRenderable, LightCacheHandle_t* pHandle ); |
|
virtual void SetStaticLighting( ModelInstanceHandle_t handle, LightCacheHandle_t* pCache ); |
|
virtual LightCacheHandle_t GetStaticLighting( ModelInstanceHandle_t handle ); |
|
virtual void DestroyInstance( ModelInstanceHandle_t handle ); |
|
virtual bool ChangeInstance( ModelInstanceHandle_t handle, IClientRenderable *pRenderable ); |
|
|
|
// Creates a decal on a model instance by doing a planar projection |
|
// along the ray. The material is the decal material, the radius is the |
|
// radius of the decal to create. |
|
virtual void AddDecal( ModelInstanceHandle_t handle, Ray_t const& ray, |
|
const Vector& decalUp, int decalIndex, int body, bool noPokethru = false, int maxLODToDecal = ADDDECAL_TO_ALL_LODS ); |
|
virtual void AddColoredDecal( ModelInstanceHandle_t handle, Ray_t const& ray, |
|
const Vector& decalUp, int decalIndex, int body, Color cColor, bool noPokethru = false, int maxLODToDecal = ADDDECAL_TO_ALL_LODS ); |
|
|
|
virtual void GetMaterialOverride( IMaterial** ppOutForcedMaterial, OverrideType_t* pOutOverrideType ); |
|
|
|
// Removes all the decals on a model instance |
|
virtual void RemoveAllDecals( ModelInstanceHandle_t handle ); |
|
|
|
// Remove all decals from all models |
|
virtual void RemoveAllDecalsFromAllModels(); |
|
|
|
// Shadow rendering (render-to-texture) |
|
virtual matrix3x4_t* DrawModelShadowSetup( IClientRenderable *pRenderable, int body, int skin, DrawModelInfo_t *pInfo, matrix3x4_t *pBoneToWorld ); |
|
virtual void DrawModelShadow( IClientRenderable *pRenderable, const DrawModelInfo_t &info, matrix3x4_t *pBoneToWorld ); |
|
|
|
// Used to allow the shadow mgr to manage a list of shadows per model |
|
unsigned short& FirstShadowOnModelInstance( ModelInstanceHandle_t handle ) { return m_ModelInstances[handle].m_FirstShadow; } |
|
|
|
// This gets called when overbright, etc gets changed to recompute static prop lighting. |
|
virtual bool RecomputeStaticLighting( ModelInstanceHandle_t handle ); |
|
|
|
// Handlers for alt-tab |
|
virtual void ReleaseAllStaticPropColorData( void ); |
|
virtual void RestoreAllStaticPropColorData( void ); |
|
|
|
// Extended version of drawmodel |
|
virtual bool DrawModelSetup( ModelRenderInfo_t &pInfo, DrawModelState_t *pState, matrix3x4_t *pBoneToWorld, matrix3x4_t** ppBoneToWorldOut ); |
|
virtual int DrawModelEx( ModelRenderInfo_t &pInfo ); |
|
virtual int DrawModelExStaticProp( ModelRenderInfo_t &pInfo ); |
|
virtual int DrawStaticPropArrayFast( StaticPropRenderInfo_t *pProps, int count, bool bShadowDepth ); |
|
|
|
// Sets up lighting context for a point in space |
|
virtual void SetupLighting( const Vector &vecCenter ); |
|
virtual void SuppressEngineLighting( bool bSuppress ); |
|
|
|
inline vertexFileHeader_t *CacheVertexData() { return g_pMDLCache->GetVertexData( VoidPtrToMDLHandle( m_pStudioHdr->VirtualModel() ) ); } |
|
|
|
bool Init(); |
|
void Shutdown(); |
|
|
|
bool GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen ); |
|
|
|
struct staticPropAsyncContext_t |
|
{ |
|
DataCacheHandle_t m_ColorMeshHandle; |
|
CColorMeshData *m_pColorMeshData; |
|
int m_nMeshes; |
|
unsigned int m_nRootLOD; |
|
char m_szFilenameVertex[MAX_PATH]; |
|
char m_szFilenameTexel[MAX_PATH]; |
|
}; |
|
|
|
|
|
void StaticPropColorMeshCallback( void *pContext, const void *pData, int numReadBytes, FSAsyncStatus_t asyncStatus ); |
|
void StaticPropColorTexelCallback(void *pContext, const void *pData, int numReadBytes, FSAsyncStatus_t asyncStatus); |
|
|
|
|
|
// 360 holds onto static prop color meshes during same map transitions |
|
void PurgeCachedStaticPropColorData(); |
|
bool IsStaticPropColorDataCached( const char *pName ); |
|
DataCacheHandle_t GetCachedStaticPropColorData( const char *pName ); |
|
|
|
virtual void SetupColorMeshes( int nTotalVerts ); |
|
|
|
private: |
|
enum |
|
{ |
|
CURRENT_LIGHTING_UNINITIALIZED = -999999 |
|
}; |
|
|
|
enum ModelInstanceFlags_t |
|
{ |
|
MODEL_INSTANCE_HAS_STATIC_LIGHTING = 0x1, |
|
MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR = 0x2, |
|
MODEL_INSTANCE_DISKCOMPILED_COLOR_BAD = 0x4, |
|
MODEL_INSTANCE_HAS_COLOR_DATA = 0x8 |
|
}; |
|
|
|
struct ModelInstance_t |
|
{ |
|
IClientRenderable* m_pRenderable; |
|
|
|
// Need to store off the model. When it changes, we lose all instance data.. |
|
model_t* m_pModel; |
|
StudioDecalHandle_t m_DecalHandle; |
|
|
|
// Stores off the current lighting state |
|
LightingState_t m_CurrentLightingState; |
|
LightingState_t m_AmbientLightingState; |
|
Vector m_flLightIntensity[MAXLOCALLIGHTS]; |
|
float m_flLightingTime; |
|
|
|
// First shadow projected onto the model |
|
unsigned short m_FirstShadow; |
|
unsigned short m_nFlags; |
|
|
|
// Static lighting |
|
LightCacheHandle_t m_LightCacheHandle; |
|
|
|
// Color mesh managed by cache |
|
DataCacheHandle_t m_ColorMeshHandle; |
|
}; |
|
|
|
// Sets up the render state for a model |
|
matrix3x4_t* SetupModelState( IClientRenderable *pRenderable ); |
|
|
|
int ComputeLOD( const ModelRenderInfo_t &info, studiohwdata_t *pStudioHWData ); |
|
|
|
void DrawModelExecute( const DrawModelState_t &state, const ModelRenderInfo_t &pInfo, matrix3x4_t *pCustomBoneToWorld = NULL ); |
|
|
|
void InitColormeshParams( ModelInstance_t &instance, studiohwdata_t *pStudioHWData, colormeshparams_t *pColorMeshParams ); |
|
CColorMeshData *FindOrCreateStaticPropColorData( ModelInstanceHandle_t handle ); |
|
void DestroyStaticPropColorData( ModelInstanceHandle_t handle ); |
|
bool UpdateStaticPropColorData( IHandleEntity *pEnt, ModelInstanceHandle_t handle ); |
|
void ProtectColorDataIfQueued( DataCacheHandle_t ); |
|
|
|
void ValidateStaticPropColorData( ModelInstanceHandle_t handle ); |
|
bool LoadStaticPropColorData( IHandleEntity *pProp, DataCacheHandle_t colorMeshHandle, studiohwdata_t *pStudioHWData ); |
|
|
|
// Returns true if the model instance is valid |
|
bool IsModelInstanceValid( ModelInstanceHandle_t handle ); |
|
|
|
void DebugDrawLightingOrigin( const DrawModelState_t& state, const ModelRenderInfo_t &pInfo ); |
|
|
|
LightingState_t *TimeAverageLightingState( ModelInstanceHandle_t handle, |
|
LightingState_t *pLightingState, int nEntIndex, const Vector *pLightingOrigin ); |
|
|
|
// Cause the current lighting state to match the given one |
|
void SnapCurrentLightingState( ModelInstance_t &inst, LightingState_t *pLightingState ); |
|
|
|
// Sets up lighting state for rendering |
|
void StudioSetupLighting( const DrawModelState_t &state, const Vector& absEntCenter, |
|
LightCacheHandle_t* pLightcache, bool bVertexLit, bool bNeedsEnvCubemap, bool &bStaticLighting, |
|
DrawModelInfo_t &drawInfo, const ModelRenderInfo_t &pInfo, int drawFlags ); |
|
|
|
// Time average the ambient term |
|
void TimeAverageAmbientLight( LightingState_t &actualLightingState, ModelInstance_t &inst, |
|
float flAttenFactor, LightingState_t *pLightingState, const Vector *pLightingOrigin ); |
|
|
|
// Old-style computation of vertex lighting |
|
void ComputeModelVertexLightingOld( mstudiomodel_t *pModel, |
|
matrix3x4_t& matrix, const LightingState_t &lightingState, color24 *pLighting, |
|
bool bUseConstDirLighting, float flConstDirLightAmount ); |
|
|
|
// New-style computation of vertex lighting |
|
void ComputeModelVertexLighting( IHandleEntity *pProp, |
|
mstudiomodel_t *pModel, OptimizedModel::ModelLODHeader_t *pVtxLOD, |
|
matrix3x4_t& matrix, Vector4D *pTempMem, color24 *pLighting ); |
|
|
|
// Internal Decal |
|
void AddDecalInternal( ModelInstanceHandle_t handle, Ray_t const& ray, const Vector& decalUp, int decalIndex, int body, bool bUseColor, Color cColor, bool noPokeThru, int maxLODToDecal); |
|
|
|
// Model instance data |
|
CUtlLinkedList< ModelInstance_t, ModelInstanceHandle_t > m_ModelInstances; |
|
|
|
// current active model |
|
studiohdr_t *m_pStudioHdr; |
|
|
|
bool m_bSuppressEngineLighting; |
|
|
|
CUtlDict< DataCacheHandle_t, int > m_CachedStaticPropColorData; |
|
CThreadFastMutex m_CachedStaticPropMutex; |
|
|
|
// Allocator for static prop color mesh vertex buffers (all are pooled into one VB) |
|
CPooledVBAllocator_ColorMesh m_colorMeshVBAllocator; |
|
}; |
|
|
|
|
|
static CModelRender s_ModelRender; |
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CModelRender, IVModelRender, VENGINE_HUDMODEL_INTERFACE_VERSION, s_ModelRender ); |
|
IVModelRender* modelrender = &s_ModelRender; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Resource loading for static prop lighting |
|
//----------------------------------------------------------------------------- |
|
class CResourcePreloadPropLighting : public CResourcePreload |
|
{ |
|
virtual bool CreateResource( const char *pName ) |
|
{ |
|
if ( !r_proplightingfromdisk.GetBool() ) |
|
{ |
|
// do nothing, not an error |
|
return true; |
|
} |
|
|
|
char szBasename[MAX_PATH]; |
|
char szFilename[MAX_PATH]; |
|
V_FileBase( pName, szBasename, sizeof( szBasename ) ); |
|
V_snprintf( szFilename, sizeof( szFilename ), "%s%s.vhv", szBasename, GetPlatformExt() ); |
|
|
|
// static props have the same name across maps |
|
// can check if loading the same map and early out if data present |
|
if ( g_pQueuedLoader->IsSameMapLoading() && s_ModelRender.IsStaticPropColorDataCached( szFilename ) ) |
|
{ |
|
// same map is loading, all disk prop lighting was left in the cache |
|
// otherwise the pre-purge operation below will do the cleanup |
|
return true; |
|
} |
|
|
|
// create an anonymous job to get the lighting data in memory, claim during static prop instancing |
|
LoaderJob_t loaderJob; |
|
loaderJob.m_pFilename = szFilename; |
|
loaderJob.m_pPathID = "GAME"; |
|
loaderJob.m_Priority = LOADERPRIORITY_DURINGPRELOAD; |
|
g_pQueuedLoader->AddJob( &loaderJob ); |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Pre purge operation before i/o commences |
|
//----------------------------------------------------------------------------- |
|
virtual void PurgeUnreferencedResources() |
|
{ |
|
if ( g_pQueuedLoader->IsSameMapLoading() ) |
|
{ |
|
// do nothing, same map is loading, correct disk prop lighting will still be in data cache |
|
return; |
|
} |
|
|
|
// Map is different, need to purge any existing disk prop lighting |
|
// before anonymous i/o commences, otherwise 2x memory usage |
|
s_ModelRender.PurgeCachedStaticPropColorData(); |
|
} |
|
|
|
virtual void PurgeAll() |
|
{ |
|
s_ModelRender.PurgeCachedStaticPropColorData(); |
|
} |
|
}; |
|
static CResourcePreloadPropLighting s_ResourcePreloadPropLighting; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Init, shutdown studiorender |
|
//----------------------------------------------------------------------------- |
|
void InitStudioRender( void ) |
|
{ |
|
UpdateStudioRenderConfig(); |
|
s_ModelRender.Init(); |
|
} |
|
|
|
void ShutdownStudioRender( void ) |
|
{ |
|
s_ModelRender.Shutdown(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Hook needed for shadows to work |
|
//----------------------------------------------------------------------------- |
|
unsigned short& FirstShadowOnModelInstance( ModelInstanceHandle_t handle ) |
|
{ |
|
return s_ModelRender.FirstShadowOnModelInstance( handle ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void R_RemoveAllDecalsFromAllModels() |
|
{ |
|
s_ModelRender.RemoveAllDecalsFromAllModels(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
bool CModelRender::Init() |
|
{ |
|
// start a managed section in the cache |
|
CCacheClientBaseClass::Init( g_pDataCache, "ColorMesh" ); |
|
|
|
if ( IsX360() ) |
|
{ |
|
g_pQueuedLoader->InstallLoader( RESOURCEPRELOAD_STATICPROPLIGHTING, &s_ResourcePreloadPropLighting ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::Shutdown() |
|
{ |
|
// end the managed section |
|
CCacheClientBaseClass::Shutdown(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used by the client to allow it to set lighting state instead of this code |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::SuppressEngineLighting( bool bSuppress ) |
|
{ |
|
m_bSuppressEngineLighting = bSuppress; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
bool CModelRender::GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen ) |
|
{ |
|
CColorMeshData *pColorMeshData = (CColorMeshData *)pItem; |
|
g_pFileSystem->String( pColorMeshData->m_fnHandle, pDest, nMaxLen ); |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Cause the current lighting state to match the given one |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::SnapCurrentLightingState( ModelInstance_t &inst, LightingState_t *pLightingState ) |
|
{ |
|
inst.m_CurrentLightingState = *pLightingState; |
|
for ( int i = 0; i < MAXLOCALLIGHTS; ++i ) |
|
{ |
|
if ( i < pLightingState->numlights ) |
|
{ |
|
inst.m_flLightIntensity[i] = pLightingState->locallight[i]->intensity; |
|
} |
|
else |
|
{ |
|
inst.m_flLightIntensity[i].Init( 0.0f, 0.0f, 0.0f ); |
|
} |
|
} |
|
|
|
#ifndef SWDS |
|
inst.m_flLightingTime = cl.GetTime(); |
|
#endif |
|
} |
|
|
|
|
|
#define AMBIENT_MAX 8.0f |
|
|
|
//----------------------------------------------------------------------------- |
|
// Time average the ambient term |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::TimeAverageAmbientLight( LightingState_t &actualLightingState, |
|
ModelInstance_t &inst, float flAttenFactor, LightingState_t *pLightingState, const Vector *pLightingOrigin ) |
|
{ |
|
flAttenFactor = clamp( flAttenFactor, 0.f, 1.f ); // don't need this but alex is a coward |
|
Vector vecDelta; |
|
for ( int i = 0; i < 6; ++i ) |
|
{ |
|
VectorSubtract( pLightingState->r_boxcolor[i], inst.m_CurrentLightingState.r_boxcolor[i], vecDelta ); |
|
vecDelta *= flAttenFactor; |
|
inst.m_CurrentLightingState.r_boxcolor[i] = pLightingState->r_boxcolor[i] - vecDelta; |
|
|
|
#if defined( VISUALIZE_TIME_AVERAGE ) && !defined( SWDS ) |
|
if ( pLightingOrigin ) |
|
{ |
|
Vector vecDir = vec3_origin; |
|
vecDir[ i >> 1 ] = (i & 0x1) ? -1.0f : 1.0f; |
|
CDebugOverlay::AddLineOverlay( *pLightingOrigin, *pLightingOrigin + vecDir * 20, |
|
255 * inst.m_CurrentLightingState.r_boxcolor[i].x, |
|
255 * inst.m_CurrentLightingState.r_boxcolor[i].y, |
|
255 * inst.m_CurrentLightingState.r_boxcolor[i].z, 255, false, 5.0f ); |
|
|
|
CDebugOverlay::AddLineOverlay( *pLightingOrigin + Vector(5, 5, 5), *pLightingOrigin + vecDir * 50, |
|
255 * pLightingState->r_boxcolor[i].x, |
|
255 * pLightingState->r_boxcolor[i].y, |
|
255 * pLightingState->r_boxcolor[i].z, 255, true, 5.0f ); |
|
} |
|
#endif |
|
// haven't been able to find this rare bug which results in ambient light getting "stuck" |
|
// on the viewmodel extremely rarely , presumably with infinities. So, mask the bug |
|
// (hopefully) and warn by clamping. |
|
#ifndef NDEBUG |
|
Assert( inst.m_CurrentLightingState.r_boxcolor[i].IsValid() ); |
|
for( int nComp = 0 ; nComp < 3; nComp++ ) |
|
{ |
|
Assert( inst.m_CurrentLightingState.r_boxcolor[i][nComp] >= 0.0 ); |
|
Assert( inst.m_CurrentLightingState.r_boxcolor[i][nComp] <= AMBIENT_MAX ); |
|
} |
|
#endif |
|
inst.m_CurrentLightingState.r_boxcolor[i].x = clamp( inst.m_CurrentLightingState.r_boxcolor[i].x, 0.f, AMBIENT_MAX ); |
|
inst.m_CurrentLightingState.r_boxcolor[i].y = clamp( inst.m_CurrentLightingState.r_boxcolor[i].y, 0.f, AMBIENT_MAX ); |
|
inst.m_CurrentLightingState.r_boxcolor[i].z = clamp( inst.m_CurrentLightingState.r_boxcolor[i].z, 0.f, AMBIENT_MAX ); |
|
} |
|
memcpy( &actualLightingState.r_boxcolor, &inst.m_CurrentLightingState.r_boxcolor, sizeof(inst.m_CurrentLightingState.r_boxcolor) ); |
|
} |
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Do time averaging of the lighting state to avoid popping... |
|
//----------------------------------------------------------------------------- |
|
LightingState_t *CModelRender::TimeAverageLightingState( ModelInstanceHandle_t handle, LightingState_t *pLightingState, int nEntIndex, const Vector *pLightingOrigin ) |
|
{ |
|
if ( r_lightaverage.GetInt() == 0 ) |
|
return pLightingState; |
|
|
|
#ifndef SWDS |
|
|
|
float flInterpFactor = r_lightinterp.GetFloat(); |
|
if ( flInterpFactor == 0 ) |
|
return pLightingState; |
|
|
|
if ( handle == MODEL_INSTANCE_INVALID) |
|
return pLightingState; |
|
|
|
ModelInstance_t &inst = m_ModelInstances[handle]; |
|
if ( inst.m_flLightingTime == CURRENT_LIGHTING_UNINITIALIZED ) |
|
{ |
|
SnapCurrentLightingState( inst, pLightingState ); |
|
return pLightingState; |
|
} |
|
|
|
float dt = (cl.GetTime() - inst.m_flLightingTime); |
|
if ( dt <= 0.0f ) |
|
{ |
|
dt = 0.0f; |
|
} |
|
else |
|
{ |
|
inst.m_flLightingTime = cl.GetTime(); |
|
} |
|
|
|
static LightingState_t actualLightingState; |
|
static dworldlight_t s_WorldLights[MAXLOCALLIGHTS]; |
|
|
|
// I'm creating the equation v = vf - (vf-vi)e^-at |
|
// where vf = this frame's lighting value, vi = current time averaged lighting value |
|
int i; |
|
Vector vecDelta; |
|
float flAttenFactor = exp( -flInterpFactor * dt ); |
|
TimeAverageAmbientLight( actualLightingState, inst, flAttenFactor, pLightingState, pLightingOrigin ); |
|
|
|
// Max # of lights... |
|
int nWorldLights; |
|
if ( !g_pMaterialSystemConfig->bSoftwareLighting ) |
|
{ |
|
nWorldLights = min( g_pMaterialSystemHardwareConfig->MaxNumLights(), r_worldlights.GetInt() ); |
|
} |
|
else |
|
{ |
|
nWorldLights = r_worldlights.GetInt(); |
|
} |
|
|
|
// Create a mapping of identical lights |
|
int nMatchCount = 0; |
|
bool pMatch[MAXLOCALLIGHTS]; |
|
Vector pLight[MAXLOCALLIGHTS]; |
|
dworldlight_t *pSourceLight[MAXLOCALLIGHTS]; |
|
|
|
memset( pMatch, 0, sizeof(pMatch) ); |
|
for ( i = 0; i < pLightingState->numlights; ++i ) |
|
{ |
|
// By default, assume the light doesn't match an existing light, so blend up from 0 |
|
pLight[i].Init( 0.0f, 0.0f, 0.0f ); |
|
int j; |
|
for ( j = 0; j < inst.m_CurrentLightingState.numlights; ++j ) |
|
{ |
|
if ( pLightingState->locallight[i] == inst.m_CurrentLightingState.locallight[j] ) |
|
{ |
|
// Ok, we found a matching light, so use the intensity of that light at the moment |
|
++nMatchCount; |
|
pMatch[j] = true; |
|
pLight[i] = inst.m_flLightIntensity[j]; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// For the lights in the current lighting state, attenuate them toward their actual value |
|
for ( i = 0; i < pLightingState->numlights; ++i ) |
|
{ |
|
actualLightingState.locallight[i] = &s_WorldLights[i]; |
|
memcpy( &s_WorldLights[i], pLightingState->locallight[i], sizeof(dworldlight_t) ); |
|
|
|
// Light already exists? Attenuate to it... |
|
VectorSubtract( pLightingState->locallight[i]->intensity, pLight[i], vecDelta ); |
|
vecDelta *= flAttenFactor; |
|
|
|
s_WorldLights[i].intensity = pLightingState->locallight[i]->intensity - vecDelta; |
|
pSourceLight[i] = pLightingState->locallight[i]; |
|
} |
|
|
|
// Ramp down any light we can; we may not be able to ramp them all down |
|
int nCurrLight = pLightingState->numlights; |
|
for ( i = 0; i < inst.m_CurrentLightingState.numlights; ++i ) |
|
{ |
|
if ( pMatch[i] ) |
|
continue; |
|
|
|
// Has it faded out to black? Then remove it. |
|
if ( inst.m_flLightIntensity[i].LengthSqr() < 1 ) |
|
continue; |
|
|
|
if ( nCurrLight >= MAXLOCALLIGHTS ) |
|
break; |
|
|
|
actualLightingState.locallight[nCurrLight] = &s_WorldLights[nCurrLight]; |
|
memcpy( &s_WorldLights[nCurrLight], inst.m_CurrentLightingState.locallight[i], sizeof(dworldlight_t) ); |
|
|
|
// Attenuate to black (fade out) |
|
VectorMultiply( inst.m_flLightIntensity[i], flAttenFactor, vecDelta ); |
|
|
|
s_WorldLights[nCurrLight].intensity = vecDelta; |
|
pSourceLight[nCurrLight] = inst.m_CurrentLightingState.locallight[i]; |
|
|
|
if (( nCurrLight >= nWorldLights ) && pLightingOrigin) |
|
{ |
|
AddWorldLightToAmbientCube( &s_WorldLights[nCurrLight], *pLightingOrigin, actualLightingState.r_boxcolor ); |
|
} |
|
|
|
++nCurrLight; |
|
} |
|
|
|
actualLightingState.numlights = min( nCurrLight, nWorldLights ); |
|
inst.m_CurrentLightingState.numlights = nCurrLight; |
|
|
|
for ( i = 0; i < nCurrLight; ++i ) |
|
{ |
|
inst.m_CurrentLightingState.locallight[i] = pSourceLight[i]; |
|
inst.m_flLightIntensity[i] = s_WorldLights[i].intensity; |
|
|
|
#if defined( VISUALIZE_TIME_AVERAGE ) && !defined( SWDS ) |
|
Vector vecColor = pSourceLight[i]->intensity; |
|
float flMax = max( vecColor.x, vecColor.y ); |
|
flMax = max( flMax, vecColor.z ); |
|
if ( flMax == 0.0f ) |
|
{ |
|
flMax = 1.0f; |
|
} |
|
vecColor *= 255.0f / flMax; |
|
float flRatio = inst.m_flLightIntensity[i].Length() / pSourceLight[i]->intensity.Length(); |
|
vecColor *= flRatio; |
|
CDebugOverlay::AddLineOverlay( *pLightingOrigin, pSourceLight[i]->origin, |
|
vecColor.x, vecColor.y, vecColor.z, 255, false, 5.0f ); |
|
#endif |
|
} |
|
|
|
return &actualLightingState; |
|
#else |
|
return pLightingState; |
|
#endif |
|
} |
|
|
|
// Ambient boost settings |
|
static ConVar r_ambientboost( "r_ambientboost", "1", FCVAR_ARCHIVE, "Set to boost ambient term if it is totally swamped by local lights" ); |
|
static ConVar r_ambientmin( "r_ambientmin", "0.3", FCVAR_ARCHIVE, "Threshold above which ambient cube will not boost (i.e. it's already sufficiently bright" ); |
|
static ConVar r_ambientfraction( "r_ambientfraction", "0.1", FCVAR_CHEAT, "Fraction of direct lighting that ambient cube must be below to trigger boosting" ); |
|
static ConVar r_ambientfactor( "r_ambientfactor", "5", FCVAR_ARCHIVE, "Boost ambient cube by no more than this factor" ); |
|
static ConVar r_lightcachemodel ( "r_lightcachemodel", "-1", FCVAR_CHEAT, "" ); |
|
static ConVar r_drawlightcache ("r_drawlightcache", "0", FCVAR_CHEAT, "0: off\n1: draw light cache entries\n2: draw rays\n"); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets up lighting state for rendering |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::StudioSetupLighting( const DrawModelState_t &state, const Vector& absEntCenter, |
|
LightCacheHandle_t* pLightcache, bool bVertexLit, bool bNeedsEnvCubemap, bool &bStaticLighting, |
|
DrawModelInfo_t &drawInfo, const ModelRenderInfo_t &pInfo, int drawFlags ) |
|
{ |
|
if ( m_bSuppressEngineLighting ) |
|
return; |
|
|
|
#ifndef SWDS |
|
ITexture *pEnvCubemapTexture = NULL; |
|
LightingState_t lightingState; |
|
|
|
Vector pSaveLightPos[MAXLOCALLIGHTS]; |
|
|
|
Vector *pDebugLightingOrigin = NULL; |
|
Vector vecDebugLightingOrigin = vec3_origin; |
|
|
|
// Cache off lighting data for rendering decals - only on dx8/dx9. |
|
LightingState_t lightingDecalState; |
|
|
|
drawInfo.m_bStaticLighting = bStaticLighting && g_pMaterialSystemHardwareConfig->SupportsStaticPlusDynamicLighting(); |
|
drawInfo.m_nLocalLightCount = 0; |
|
|
|
// Compute lighting origin from input |
|
Vector vLightingOrigin( 0.0f, 0.0f, 0.0f ); |
|
CMatRenderContextPtr pRenderContext( materials ); |
|
if ( pInfo.pLightingOrigin ) |
|
{ |
|
vLightingOrigin = *pInfo.pLightingOrigin; |
|
} |
|
else |
|
{ |
|
vLightingOrigin = absEntCenter; |
|
if ( pInfo.pLightingOffset ) |
|
{ |
|
VectorTransform( absEntCenter, *pInfo.pLightingOffset, vLightingOrigin ); |
|
} |
|
} |
|
|
|
// Set the lighting origin state |
|
pRenderContext->SetLightingOrigin( vLightingOrigin ); |
|
|
|
ModelInstance_t *pModelInst = NULL; |
|
bool bHasDecals = false; |
|
if ( pInfo.instance != m_ModelInstances.InvalidIndex() ) |
|
{ |
|
pModelInst = &m_ModelInstances[pInfo.instance]; |
|
if ( pModelInst ) |
|
{ |
|
bHasDecals = ( pModelInst->m_DecalHandle != STUDIORENDER_DECAL_INVALID ); |
|
} |
|
} |
|
|
|
if ( pLightcache ) |
|
{ |
|
// static prop case. |
|
if ( bStaticLighting ) |
|
{ |
|
if ( g_pMaterialSystemHardwareConfig->SupportsStaticPlusDynamicLighting() ) |
|
{ |
|
LightingState_t *pLightingState = NULL; |
|
// dx8 and dx9 case. . .hardware can do baked lighting plus other dynamic lighting |
|
// We already have the static part baked into a color mesh, so just get the dynamic stuff. |
|
if ( StaticLightCacheAffectedByDynamicLight( *pLightcache ) ) |
|
{ |
|
pLightingState = LightcacheGetStatic( *pLightcache, &pEnvCubemapTexture ); |
|
Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); |
|
} |
|
else |
|
{ |
|
pLightingState = LightcacheGetStatic( *pLightcache, &pEnvCubemapTexture, LIGHTCACHEFLAGS_DYNAMIC | LIGHTCACHEFLAGS_LIGHTSTYLE ); |
|
Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); |
|
} |
|
lightingState = *pLightingState; |
|
} |
|
else |
|
{ |
|
// dx6 and dx7 case. . .hardware can either do software lighting or baked lighting only. |
|
if ( StaticLightCacheAffectedByDynamicLight( *pLightcache ) || |
|
StaticLightCacheAffectedByAnimatedLightStyle( *pLightcache ) ) |
|
{ |
|
bStaticLighting = false; |
|
} |
|
else if ( StaticLightCacheNeedsSwitchableLightUpdate( *pLightcache ) ) |
|
{ |
|
// Need to rebake lighting since a switch has turned off/on. |
|
UpdateStaticPropColorData( state.m_pRenderable->GetIClientUnknown(), pInfo.instance ); |
|
} |
|
} |
|
} |
|
|
|
if ( !bStaticLighting ) |
|
{ |
|
lightingState = *(LightcacheGetStatic( *pLightcache, &pEnvCubemapTexture )); |
|
Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); |
|
} |
|
|
|
if ( r_decalstaticprops.GetBool() && pModelInst && drawInfo.m_bStaticLighting && bHasDecals ) |
|
{ |
|
for ( int iCube = 0; iCube < 6; ++iCube ) |
|
{ |
|
drawInfo.m_vecAmbientCube[iCube] = pModelInst->m_AmbientLightingState.r_boxcolor[iCube] + lightingState.r_boxcolor[iCube]; |
|
} |
|
|
|
lightingDecalState.CopyLocalLights( pModelInst->m_AmbientLightingState ); |
|
lightingDecalState.AddAllLocalLights( lightingState ); |
|
|
|
Assert( lightingDecalState.numlights >= 0 && lightingDecalState.numlights <= MAXLOCALLIGHTS ); |
|
} |
|
} |
|
else // !pLightcache |
|
{ |
|
vecDebugLightingOrigin = vLightingOrigin; |
|
pDebugLightingOrigin = &vecDebugLightingOrigin; |
|
|
|
// If we don't have a lightcache entry, but we have bStaticLighting, that means |
|
// that we are a prop_physics that has fallen asleep. |
|
if ( bStaticLighting ) |
|
{ |
|
LightcacheGetDynamic_Stats stats; |
|
pEnvCubemapTexture = LightcacheGetDynamic( vLightingOrigin, lightingState, |
|
stats, LIGHTCACHEFLAGS_DYNAMIC | LIGHTCACHEFLAGS_LIGHTSTYLE ); |
|
Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); |
|
|
|
// Deal with all the dx6/dx7 issues (ie. can't do anything besides either baked lighting |
|
// or software dynamic lighting. |
|
if ( !g_pMaterialSystemHardwareConfig->SupportsStaticPlusDynamicLighting() ) |
|
{ |
|
if ( ( stats.m_bHasDLights || stats.m_bHasNonSwitchableLightStyles ) ) |
|
{ |
|
// We either have a light switch, or a dynamic light. . do it in software. |
|
// We'll reget the cache entry with different flags below. |
|
bStaticLighting = false; |
|
} |
|
else if ( stats.m_bNeedsSwitchableLightStyleUpdate ) |
|
{ |
|
// Need to rebake lighting since a switch has turned off/on. |
|
UpdateStaticPropColorData( state.m_pRenderable->GetIClientUnknown(), pInfo.instance ); |
|
} |
|
} |
|
} |
|
|
|
if ( !bStaticLighting ) |
|
{ |
|
LightcacheGetDynamic_Stats stats; |
|
|
|
// For special r_drawlightcache mode, we only draw models containing the substring set in r_lightcachemodel |
|
bool bDebugModel = false; |
|
if( r_drawlightcache.GetInt() == 5 ) |
|
{ |
|
if ( pModelInst && pModelInst->m_pModel && !pModelInst->m_pModel->strName.IsEmpty() ) |
|
{ |
|
const char *szModelName = r_lightcachemodel.GetString(); |
|
bDebugModel = V_stristr( pModelInst->m_pModel->strName, szModelName ) != NULL; |
|
} |
|
} |
|
|
|
pEnvCubemapTexture = LightcacheGetDynamic( vLightingOrigin, lightingState, stats, |
|
LIGHTCACHEFLAGS_STATIC|LIGHTCACHEFLAGS_DYNAMIC|LIGHTCACHEFLAGS_LIGHTSTYLE|LIGHTCACHEFLAGS_ALLOWFAST, bDebugModel ); |
|
|
|
Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); |
|
} |
|
|
|
if ( pInfo.pLightingOffset && !pInfo.pLightingOrigin ) |
|
{ |
|
for ( int i = 0; i < lightingState.numlights; ++i ) |
|
{ |
|
pSaveLightPos[i] = lightingState.locallight[i]->origin; |
|
VectorITransform( pSaveLightPos[i], *pInfo.pLightingOffset, lightingState.locallight[i]->origin ); |
|
} |
|
} |
|
|
|
// Cache lighting for decals. |
|
if ( pModelInst && drawInfo.m_bStaticLighting && bHasDecals ) |
|
{ |
|
// Only do this on dx8/dx9. |
|
LightcacheGetDynamic_Stats stats; |
|
LightcacheGetDynamic( vLightingOrigin, lightingDecalState, stats, |
|
LIGHTCACHEFLAGS_STATIC|LIGHTCACHEFLAGS_DYNAMIC|LIGHTCACHEFLAGS_LIGHTSTYLE|LIGHTCACHEFLAGS_ALLOWFAST ); |
|
|
|
Assert( lightingDecalState.numlights >= 0 && lightingDecalState.numlights <= MAXLOCALLIGHTS); |
|
|
|
for ( int iCube = 0; iCube < 6; ++iCube ) |
|
{ |
|
VectorCopy( lightingDecalState.r_boxcolor[iCube], drawInfo.m_vecAmbientCube[iCube] ); |
|
} |
|
|
|
if ( pInfo.pLightingOffset && !pInfo.pLightingOrigin ) |
|
{ |
|
for ( int i = 0; i < lightingDecalState.numlights; ++i ) |
|
{ |
|
pSaveLightPos[i] = lightingDecalState.locallight[i]->origin; |
|
VectorITransform( pSaveLightPos[i], *pInfo.pLightingOffset, lightingDecalState.locallight[i]->origin ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); |
|
|
|
// Do time averaging of the lighting state to avoid popping... |
|
LightingState_t *pState; |
|
if ( !bStaticLighting && !pLightcache ) |
|
{ |
|
pState = TimeAverageLightingState( pInfo.instance, &lightingState, pInfo.entity_index, pDebugLightingOrigin ); |
|
} |
|
else |
|
{ |
|
pState = &lightingState; |
|
} |
|
|
|
if ( bNeedsEnvCubemap && pEnvCubemapTexture ) |
|
{ |
|
pRenderContext->BindLocalCubemap( pEnvCubemapTexture ); |
|
} |
|
|
|
if ( g_pMaterialSystemConfig->nFullbright == 1 ) |
|
{ |
|
pRenderContext->SetAmbientLight( 1.0, 1.0, 1.0 ); |
|
|
|
static Vector white[6] = |
|
{ |
|
Vector( 1.0, 1.0, 1.0 ), |
|
Vector( 1.0, 1.0, 1.0 ), |
|
Vector( 1.0, 1.0, 1.0 ), |
|
Vector( 1.0, 1.0, 1.0 ), |
|
Vector( 1.0, 1.0, 1.0 ), |
|
Vector( 1.0, 1.0, 1.0 ), |
|
}; |
|
|
|
g_pStudioRender->SetAmbientLightColors( white ); |
|
|
|
// Disable all the lights.. |
|
pRenderContext->DisableAllLocalLights(); |
|
} |
|
else if ( bVertexLit ) |
|
{ |
|
if( drawFlags & STUDIORENDER_DRAW_ITEM_BLINK ) |
|
{ |
|
float add = r_itemblinkmax.GetFloat() * ( FastCos( r_itemblinkrate.GetFloat() * Sys_FloatTime() ) + 1.0f ); |
|
Vector additiveColor( add, add, add ); |
|
static Vector temp[6]; |
|
int i; |
|
for( i = 0; i < 6; i++ ) |
|
{ |
|
temp[i] = pState->r_boxcolor[i] + additiveColor; |
|
} |
|
g_pStudioRender->SetAmbientLightColors( temp ); |
|
} |
|
else |
|
{ |
|
// If we have any lights and want to do ambient boost on this model |
|
if ( (pState->numlights > 0) && (pInfo.pModel->flags & MODELFLAG_STUDIOHDR_AMBIENT_BOOST) && r_ambientboost.GetBool() ) |
|
{ |
|
Vector lumCoeff( 0.3f, 0.59f, 0.11f ); |
|
float avgCubeLuminance = 0.0f; |
|
float minCubeLuminance = FLT_MAX; |
|
float maxCubeLuminance = 0.0f; |
|
|
|
// Compute average luminance of ambient cube |
|
for( int i = 0; i < 6; i++ ) |
|
{ |
|
float luminance = DotProduct( pState->r_boxcolor[i], lumCoeff ); // compute luminance |
|
minCubeLuminance = fpmin(minCubeLuminance, luminance); // min luminance |
|
maxCubeLuminance = fpmax(maxCubeLuminance, luminance); // max luminance |
|
avgCubeLuminance += luminance; // accumulate luminance |
|
} |
|
avgCubeLuminance /= 6.0f; // average luminance |
|
|
|
// Compute the amount of direct light reaching the center of the model (attenuated by distance) |
|
float fDirectLight = 0.0f; |
|
for( int i = 0; i < pState->numlights; i++ ) |
|
{ |
|
Vector vLight = pState->locallight[i]->origin - vLightingOrigin; |
|
float d2 = DotProduct( vLight, vLight ); |
|
float d = sqrtf( d2 ); |
|
float fAtten = 1.0f; |
|
|
|
float denom = pState->locallight[i]->constant_attn + |
|
pState->locallight[i]->linear_attn * d + |
|
pState->locallight[i]->quadratic_attn * d2; |
|
|
|
if ( denom > 0.00001f ) |
|
{ |
|
fAtten = 1.0f / denom; |
|
} |
|
|
|
Vector vLit = pState->locallight[i]->intensity * fAtten; |
|
fDirectLight += DotProduct( vLit, lumCoeff ); |
|
} |
|
|
|
// If ambient cube is sufficiently dim in absolute terms and ambient cube is swamped by direct lights |
|
if ( avgCubeLuminance < r_ambientmin.GetFloat() && (avgCubeLuminance < (fDirectLight * r_ambientfraction.GetFloat())) ) |
|
{ |
|
Vector vFinalAmbientCube[6]; |
|
float fBoostFactor = min( (fDirectLight * r_ambientfraction.GetFloat()) / maxCubeLuminance, 5.f ); // boost no more than a certain factor |
|
for( int i = 0; i < 6; i++ ) |
|
{ |
|
vFinalAmbientCube[i] = pState->r_boxcolor[i] * fBoostFactor; |
|
} |
|
g_pStudioRender->SetAmbientLightColors( vFinalAmbientCube ); // Boost |
|
} |
|
else |
|
{ |
|
g_pStudioRender->SetAmbientLightColors( pState->r_boxcolor ); // No Boost |
|
} |
|
} |
|
else // Don't bother with ambient boost, just use the ambient cube as is |
|
{ |
|
g_pStudioRender->SetAmbientLightColors( pState->r_boxcolor ); // No Boost |
|
} |
|
} |
|
|
|
pRenderContext->SetAmbientLight( 0.0, 0.0, 0.0 ); |
|
R_SetNonAmbientLightingState( pState->numlights, pState->locallight, |
|
&drawInfo.m_nLocalLightCount, &drawInfo.m_LocalLightDescs[0], true ); |
|
|
|
// Cache lighting for decals. |
|
if( pModelInst && drawInfo.m_bStaticLighting && bHasDecals ) |
|
{ |
|
R_SetNonAmbientLightingState( lightingDecalState.numlights, lightingDecalState.locallight, |
|
&drawInfo.m_nLocalLightCount, &drawInfo.m_LocalLightDescs[0], false ); |
|
} |
|
} |
|
|
|
if ( pInfo.pLightingOffset && !pInfo.pLightingOrigin ) |
|
{ |
|
for ( int i = 0; i < lightingState.numlights; ++i ) |
|
{ |
|
lightingState.locallight[i]->origin = pSaveLightPos[i]; |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Uses this material instead of the one the model was compiled with |
|
//----------------------------------------------------------------------------- |
|
|
|
// FIXME: a duplicate of what's in CEngineTool::GetLightingConditions |
|
int GetLightingConditions( const Vector &vecLightingOrigin, Vector *pColors, int nMaxLocalLights, LightDesc_t *pLocalLights, ITexture *&pEnvCubemapTexture ) |
|
{ |
|
#ifndef SWDS |
|
LightcacheGetDynamic_Stats stats; |
|
LightingState_t state; |
|
pEnvCubemapTexture = NULL; |
|
pEnvCubemapTexture = LightcacheGetDynamic( vecLightingOrigin, state, stats ); |
|
Assert( state.numlights >= 0 && state.numlights <= MAXLOCALLIGHTS ); |
|
memcpy( pColors, state.r_boxcolor, sizeof(state.r_boxcolor) ); |
|
|
|
int nLightCount = 0; |
|
for ( int i = 0; i < state.numlights; ++i ) |
|
{ |
|
LightDesc_t *pLightDesc = &pLocalLights[nLightCount]; |
|
if (!WorldLightToMaterialLight( state.locallight[i], *pLightDesc )) |
|
continue; |
|
|
|
// Apply lightstyle |
|
float bias = LightStyleValue( state.locallight[i]->style ); |
|
|
|
// Deal with overbrighting + bias |
|
pLightDesc->m_Color[0] *= bias; |
|
pLightDesc->m_Color[1] *= bias; |
|
pLightDesc->m_Color[2] *= bias; |
|
|
|
if ( ++nLightCount >= nMaxLocalLights ) |
|
break; |
|
} |
|
return nLightCount; |
|
#endif |
|
return 0; |
|
} |
|
|
|
// FIXME: a duplicate of what's in CCDmeMdlRenderable<T>::SetUpLighting and CDmeEmitter::SetUpLighting |
|
void CModelRender::SetupLighting( const Vector &vecCenter ) |
|
{ |
|
#ifndef SWDS |
|
// Set up lighting conditions |
|
Vector vecAmbient[6]; |
|
Vector4D vecAmbient4D[6]; |
|
LightDesc_t desc[2]; |
|
ITexture *pEnvCubemapTexture = NULL; |
|
int nLightCount = GetLightingConditions( vecCenter, vecAmbient, 2, desc, pEnvCubemapTexture ); |
|
int nMaxLights = g_pMaterialSystemHardwareConfig->MaxNumLights(); |
|
if( nLightCount > nMaxLights ) |
|
{ |
|
nLightCount = nMaxLights; |
|
} |
|
|
|
int i; |
|
for( i = 0; i < 6; i++ ) |
|
{ |
|
VectorCopy( vecAmbient[i], vecAmbient4D[i].AsVector3D() ); |
|
vecAmbient4D[i][3] = 1.0f; |
|
} |
|
CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); |
|
pRenderContext->SetAmbientLightCube( vecAmbient4D ); |
|
|
|
if ( pEnvCubemapTexture ) |
|
{ |
|
pRenderContext->BindLocalCubemap( pEnvCubemapTexture ); |
|
} |
|
|
|
for( i = 0; i < nLightCount; i++ ) |
|
{ |
|
LightDesc_t *pLight = &desc[i]; |
|
pLight->m_Flags = 0; |
|
if( pLight->m_Attenuation0 != 0.0f ) |
|
{ |
|
pLight->m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION0; |
|
} |
|
if( pLight->m_Attenuation1 != 0.0f ) |
|
{ |
|
pLight->m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION1; |
|
} |
|
if( pLight->m_Attenuation2 != 0.0f ) |
|
{ |
|
pLight->m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION2; |
|
} |
|
|
|
pRenderContext->SetLight( i, desc[i] ); |
|
} |
|
|
|
for( ; i < nMaxLights; i++ ) |
|
{ |
|
LightDesc_t disableDesc; |
|
disableDesc.m_Type = MATERIAL_LIGHT_DISABLE; |
|
pRenderContext->SetLight( i, disableDesc ); |
|
} |
|
#endif |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Uses this material instead of the one the model was compiled with |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::ForcedMaterialOverride( IMaterial *newMaterial, OverrideType_t nOverrideType ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
g_pStudioRender->ForcedMaterialOverride( newMaterial, nOverrideType ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets up the render state for a model |
|
//----------------------------------------------------------------------------- |
|
matrix3x4_t* CModelRender::SetupModelState( IClientRenderable *pRenderable ) |
|
{ |
|
const model_t *pModel = pRenderable->GetModel(); |
|
if ( !pModel ) |
|
return NULL; |
|
|
|
studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( const_cast<model_t*>(pModel) ); |
|
if ( pStudioHdr->numbodyparts == 0 ) |
|
return NULL; |
|
|
|
matrix3x4_t *pBoneMatrices = NULL; |
|
#ifndef SWDS |
|
// Set up skinning state |
|
Assert ( pRenderable ); |
|
{ |
|
int nBoneCount = pStudioHdr->numbones; |
|
pBoneMatrices = g_pStudioRender->LockBoneMatrices( pStudioHdr->numbones ); |
|
pRenderable->SetupBones( pBoneMatrices, nBoneCount, BONE_USED_BY_ANYTHING, cl.GetTime() ); // hack hack |
|
g_pStudioRender->UnlockBoneMatrices(); |
|
} |
|
#endif |
|
|
|
return pBoneMatrices; |
|
} |
|
|
|
|
|
struct ModelDebugOverlayData_t |
|
{ |
|
DrawModelInfo_t m_ModelInfo; |
|
DrawModelResults_t m_ModelResults; |
|
Vector m_Origin; |
|
|
|
ModelDebugOverlayData_t() = default; |
|
|
|
private: |
|
ModelDebugOverlayData_t( const ModelDebugOverlayData_t &vOther ); |
|
}; |
|
|
|
static CUtlVector<ModelDebugOverlayData_t> s_SavedModelInfo; |
|
|
|
void DrawModelDebugOverlay( const DrawModelInfo_t& info, const DrawModelResults_t &results, const Vector &origin, float r = 1.0f, float g = 1.0f, float b = 1.0f ) |
|
{ |
|
#ifndef SWDS |
|
float alpha = 1; |
|
if( r_drawmodelstatsoverlaydistance.GetFloat() == 1 ) |
|
{ |
|
alpha = 1.f - clamp( CurrentViewOrigin().DistTo( origin ) / r_drawmodelstatsoverlaydistance.GetFloat(), 0.f, 1.f ); |
|
} |
|
else |
|
{ |
|
float flDistance = CurrentViewOrigin().DistTo( origin ); |
|
|
|
// The view model keeps throwing up its data and it looks like garbage, so I am trying to get rid of it. |
|
if ( flDistance < 36.0f ) |
|
return; |
|
|
|
if ( flDistance > r_drawmodelstatsoverlaydistance.GetFloat() ) |
|
return; |
|
} |
|
|
|
Assert( info.m_pStudioHdr ); |
|
Assert( info.m_pStudioHdr->pszName() ); |
|
Assert( info.m_pHardwareData ); |
|
float duration = 0.0f; |
|
int lineOffset = 0; |
|
if( !info.m_pStudioHdr || !info.m_pStudioHdr->pszName() || !info.m_pHardwareData ) |
|
{ |
|
CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, "This model has problems. . see a programmer." ); |
|
return; |
|
} |
|
|
|
char buf[1024]; |
|
CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, info.m_pStudioHdr->pszName() ); |
|
Q_snprintf( buf, sizeof( buf ), "lod: %d/%d\n", results.m_nLODUsed+1, ( int )info.m_pHardwareData->m_NumLODs ); |
|
CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); |
|
Q_snprintf( buf, sizeof( buf ), "tris: %d\n", results.m_ActualTriCount ); |
|
CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); |
|
Q_snprintf( buf, sizeof( buf ), "hardware bones: %d\n", results.m_NumHardwareBones ); |
|
CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); |
|
Q_snprintf( buf, sizeof( buf ), "num batches: %d\n", results.m_NumBatches ); |
|
CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); |
|
Q_snprintf( buf, sizeof( buf ), "has shadow lod: %s\n", ( info.m_pStudioHdr->flags & STUDIOHDR_FLAGS_HASSHADOWLOD ) ? "true" : "false" ); |
|
CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); |
|
Q_snprintf( buf, sizeof( buf ), "num materials: %d\n", results.m_NumMaterials ); |
|
CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); |
|
|
|
int i; |
|
for( i = 0; i < results.m_Materials.Count(); i++ ) |
|
{ |
|
IMaterial *pMaterial = results.m_Materials[i]; |
|
if( pMaterial ) |
|
{ |
|
int numPasses = pMaterial->GetNumPasses(); |
|
Q_snprintf( buf, sizeof( buf ), "\t%s (%d %s)\n", results.m_Materials[i]->GetName(), numPasses, numPasses > 1 ? "passes" : "pass" ); |
|
CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); |
|
} |
|
} |
|
if( results.m_Materials.Count() > results.m_NumMaterials ) |
|
{ |
|
CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, "(Remaining materials not shown)\n" ); |
|
} |
|
if( r_drawmodelstatsoverlay.GetInt() == 2 ) |
|
{ |
|
Q_snprintf( buf, sizeof( buf ), "Render Time: %0.1f ms\n", results.m_RenderTime.GetDuration().GetMillisecondsF()); |
|
CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); |
|
} |
|
|
|
//Q_snprintf( buf, sizeof( buf ), "Render Time: %0.1f ms\n", info.m_pClientEntity |
|
#endif |
|
} |
|
|
|
void AddModelDebugOverlay( const DrawModelInfo_t& info, const DrawModelResults_t &results, const Vector& origin ) |
|
{ |
|
ModelDebugOverlayData_t &tmp = s_SavedModelInfo[s_SavedModelInfo.AddToTail()]; |
|
tmp.m_ModelInfo = info; |
|
tmp.m_ModelResults = results; |
|
tmp.m_Origin = origin; |
|
} |
|
|
|
void ClearSaveModelDebugOverlays( void ) |
|
{ |
|
s_SavedModelInfo.RemoveAll(); |
|
} |
|
|
|
int SavedModelInfo_Compare_f( const void *l, const void *r ) |
|
{ |
|
ModelDebugOverlayData_t *left = ( ModelDebugOverlayData_t * )l; |
|
ModelDebugOverlayData_t *right = ( ModelDebugOverlayData_t * )r; |
|
return left->m_ModelResults.m_RenderTime.GetDuration().GetSeconds() < right->m_ModelResults.m_RenderTime.GetDuration().GetSeconds(); |
|
} |
|
|
|
static ConVar r_drawmodelstatsoverlaymin( "r_drawmodelstatsoverlaymin", "0.1", FCVAR_ARCHIVE, "time in milliseconds that a model must take to render before showing an overlay in r_drawmodelstatsoverlay 2" ); |
|
static ConVar r_drawmodelstatsoverlaymax( "r_drawmodelstatsoverlaymax", "1.5", FCVAR_ARCHIVE, "time in milliseconds beyond which a model overlay is fully red in r_drawmodelstatsoverlay 2" ); |
|
|
|
void DrawSavedModelDebugOverlays( void ) |
|
{ |
|
if( s_SavedModelInfo.Count() == 0 ) |
|
{ |
|
return; |
|
} |
|
float min = r_drawmodelstatsoverlaymin.GetFloat(); |
|
float max = r_drawmodelstatsoverlaymax.GetFloat(); |
|
float ooRange = 1.0f / ( max - min ); |
|
|
|
int i; |
|
for( i = 0; i < s_SavedModelInfo.Count(); i++ ) |
|
{ |
|
float r, g, b; |
|
float t = s_SavedModelInfo[i].m_ModelResults.m_RenderTime.GetDuration().GetMillisecondsF(); |
|
if( t > min ) |
|
{ |
|
if( t >= max ) |
|
{ |
|
r = 1.0f; g = 0.0f; b = 0.0f; |
|
} |
|
else |
|
{ |
|
r = ( t - min ) * ooRange; |
|
g = 1.0f - r; |
|
b = 0.0f; |
|
} |
|
DrawModelDebugOverlay( s_SavedModelInfo[i].m_ModelInfo, s_SavedModelInfo[i].m_ModelResults, s_SavedModelInfo[i].m_Origin, r, g, b ); |
|
} |
|
} |
|
ClearSaveModelDebugOverlays(); |
|
} |
|
|
|
void CModelRender::DebugDrawLightingOrigin( const DrawModelState_t& state, const ModelRenderInfo_t &pInfo ) |
|
{ |
|
#ifndef SWDS |
|
// determine light origin in world space |
|
Vector illumPosition; |
|
Vector lightOrigin; |
|
if ( pInfo.pLightingOrigin ) |
|
{ |
|
illumPosition = *pInfo.pLightingOrigin; |
|
lightOrigin = illumPosition; |
|
} |
|
else |
|
{ |
|
R_ComputeLightingOrigin( state.m_pRenderable, state.m_pStudioHdr, *state.m_pModelToWorld, illumPosition ); |
|
lightOrigin = illumPosition; |
|
if ( pInfo.pLightingOffset ) |
|
{ |
|
VectorTransform( illumPosition, *pInfo.pLightingOffset, lightOrigin ); |
|
} |
|
} |
|
|
|
// draw z planar cross at lighting origin |
|
Vector pt0; |
|
Vector pt1; |
|
pt0 = lightOrigin; |
|
pt1 = lightOrigin; |
|
pt0.x -= 4; |
|
pt1.x += 4; |
|
CDebugOverlay::AddLineOverlay( pt0, pt1, 0, 255, 0, 255, true, 0.0f ); |
|
pt0 = lightOrigin; |
|
pt1 = lightOrigin; |
|
pt0.y -= 4; |
|
pt1.y += 4; |
|
CDebugOverlay::AddLineOverlay( pt0, pt1, 0, 255, 0, 255, true, 0.0f ); |
|
|
|
// draw lines from the light origin to the hull boundaries to identify model |
|
Vector pt; |
|
pt0.x = state.m_pStudioHdr->hull_min.x; |
|
pt0.y = state.m_pStudioHdr->hull_min.y; |
|
pt0.z = state.m_pStudioHdr->hull_min.z; |
|
VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); |
|
CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); |
|
pt0.x = state.m_pStudioHdr->hull_min.x; |
|
pt0.y = state.m_pStudioHdr->hull_max.y; |
|
pt0.z = state.m_pStudioHdr->hull_min.z; |
|
VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); |
|
CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); |
|
pt0.x = state.m_pStudioHdr->hull_max.x; |
|
pt0.y = state.m_pStudioHdr->hull_max.y; |
|
pt0.z = state.m_pStudioHdr->hull_min.z; |
|
VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); |
|
CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); |
|
pt0.x = state.m_pStudioHdr->hull_max.x; |
|
pt0.y = state.m_pStudioHdr->hull_min.y; |
|
pt0.z = state.m_pStudioHdr->hull_min.z; |
|
VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); |
|
CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); |
|
|
|
pt0.x = state.m_pStudioHdr->hull_min.x; |
|
pt0.y = state.m_pStudioHdr->hull_min.y; |
|
pt0.z = state.m_pStudioHdr->hull_max.z; |
|
VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); |
|
CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); |
|
pt0.x = state.m_pStudioHdr->hull_min.x; |
|
pt0.y = state.m_pStudioHdr->hull_max.y; |
|
pt0.z = state.m_pStudioHdr->hull_max.z; |
|
VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); |
|
CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); |
|
pt0.x = state.m_pStudioHdr->hull_max.x; |
|
pt0.y = state.m_pStudioHdr->hull_max.y; |
|
pt0.z = state.m_pStudioHdr->hull_max.z; |
|
VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); |
|
CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); |
|
pt0.x = state.m_pStudioHdr->hull_max.x; |
|
pt0.y = state.m_pStudioHdr->hull_min.y; |
|
pt0.z = state.m_pStudioHdr->hull_max.z; |
|
VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); |
|
CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); |
|
#endif |
|
} |
|
//----------------------------------------------------------------------------- |
|
// Actually renders the model |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::DrawModelExecute( const DrawModelState_t &state, const ModelRenderInfo_t &pInfo, matrix3x4_t *pBoneToWorld ) |
|
{ |
|
#ifndef SWDS |
|
bool bShadowDepth = (pInfo.flags & STUDIO_SHADOWDEPTHTEXTURE) != 0; |
|
bool bSSAODepth = ( pInfo.flags & STUDIO_SSAODEPTHTEXTURE ) != 0; |
|
|
|
// Bail if we're rendering into shadow depth map and this model doesn't cast shadows |
|
if ( bShadowDepth && ( ( pInfo.pModel->flags & MODELFLAG_STUDIOHDR_DO_NOT_CAST_SHADOWS ) != 0 ) ) |
|
return; |
|
|
|
// Shadow state... |
|
g_pShadowMgr->SetModelShadowState( pInfo.instance ); |
|
|
|
if ( g_bTextMode ) |
|
return; |
|
|
|
// Sets up flexes |
|
float *pFlexWeights = NULL; |
|
float *pFlexDelayedWeights = NULL; |
|
int nFlexCount = state.m_pStudioHdr->numflexdesc; |
|
if ( nFlexCount > 0 ) |
|
{ |
|
// Does setup for flexes |
|
Assert( pBoneToWorld ); |
|
bool bUsesDelayedWeights = state.m_pRenderable->UsesFlexDelayedWeights(); |
|
g_pStudioRender->LockFlexWeights( nFlexCount, &pFlexWeights, bUsesDelayedWeights ? &pFlexDelayedWeights : NULL ); |
|
state.m_pRenderable->SetupWeights( pBoneToWorld, nFlexCount, pFlexWeights, pFlexDelayedWeights ); |
|
g_pStudioRender->UnlockFlexWeights(); |
|
} |
|
|
|
// OPTIMIZE: Try to precompute part of this mess once a frame at the very least. |
|
bool bUsesBumpmapping = ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 80 ) && ( pInfo.pModel->flags & MODELFLAG_STUDIOHDR_USES_BUMPMAPPING ); |
|
|
|
bool bStaticLighting = ( state.m_drawFlags & STUDIORENDER_DRAW_STATIC_LIGHTING ) && |
|
( state.m_pStudioHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP ) && |
|
( !bUsesBumpmapping ) && |
|
( pInfo.instance != MODEL_INSTANCE_INVALID ) && |
|
g_pMaterialSystemHardwareConfig->SupportsColorOnSecondStream(); |
|
|
|
bool bVertexLit = ( pInfo.pModel->flags & MODELFLAG_VERTEXLIT ) != 0; |
|
|
|
bool bNeedsEnvCubemap = r_showenvcubemap.GetInt() || ( pInfo.pModel->flags & MODELFLAG_STUDIOHDR_USES_ENV_CUBEMAP ); |
|
|
|
if ( r_drawmodellightorigin.GetBool() && !bShadowDepth && !bSSAODepth ) |
|
{ |
|
DebugDrawLightingOrigin( state, pInfo ); |
|
} |
|
|
|
ColorMeshInfo_t *pColorMeshes = NULL; |
|
DataCacheHandle_t hColorMeshData = DC_INVALID_HANDLE; |
|
if ( bStaticLighting ) |
|
{ |
|
// have static lighting, get from cache |
|
hColorMeshData = m_ModelInstances[pInfo.instance].m_ColorMeshHandle; |
|
CColorMeshData *pColorMeshData = CacheGet( hColorMeshData ); |
|
if ( !pColorMeshData || pColorMeshData->m_bNeedsRetry ) |
|
{ |
|
// color meshes are not present, try to re-establish |
|
if ( RecomputeStaticLighting( pInfo.instance ) ) |
|
{ |
|
pColorMeshData = CacheGet( hColorMeshData ); |
|
} |
|
else if ( !pColorMeshData || !pColorMeshData->m_bNeedsRetry ) |
|
{ |
|
// can't draw |
|
return; |
|
} |
|
} |
|
|
|
if ( pColorMeshData && ( pColorMeshData->m_bColorMeshValid || pColorMeshData->m_bColorTextureValid ) ) |
|
{ |
|
pColorMeshes = pColorMeshData->m_pMeshInfos; |
|
if (pColorMeshData->m_bColorTextureValid && !pColorMeshData->m_bColorTextureCreated) |
|
{ |
|
CreateLightmapsFromData(pColorMeshData); |
|
} |
|
} |
|
else |
|
{ |
|
// failed, draw without static lighting |
|
bStaticLighting = false; |
|
} |
|
} |
|
|
|
DrawModelInfo_t info; |
|
info.m_bStaticLighting = false; |
|
|
|
// get lighting from ambient light sources and radiosity bounces |
|
// also set up the env_cubemap from the light cache if necessary. |
|
if ( ( bVertexLit || bNeedsEnvCubemap ) && !bShadowDepth && !bSSAODepth ) |
|
{ |
|
// See if we're using static lighting |
|
LightCacheHandle_t* pLightCache = NULL; |
|
if ( pInfo.instance != MODEL_INSTANCE_INVALID ) |
|
{ |
|
if ( ( m_ModelInstances[pInfo.instance].m_nFlags & MODEL_INSTANCE_HAS_STATIC_LIGHTING ) && m_ModelInstances[pInfo.instance].m_LightCacheHandle ) |
|
{ |
|
pLightCache = &m_ModelInstances[pInfo.instance].m_LightCacheHandle; |
|
} |
|
} |
|
|
|
// Choose the lighting origin |
|
Vector entOrigin; |
|
R_ComputeLightingOrigin( state.m_pRenderable, state.m_pStudioHdr, *state.m_pModelToWorld, entOrigin ); |
|
|
|
// Set up lighting based on the lighting origin |
|
StudioSetupLighting( state, entOrigin, pLightCache, bVertexLit, bNeedsEnvCubemap, bStaticLighting, info, pInfo, state.m_drawFlags ); |
|
} |
|
|
|
// Set up the camera state |
|
g_pStudioRender->SetViewState( CurrentViewOrigin(), CurrentViewRight(), CurrentViewUp(), CurrentViewForward() ); |
|
|
|
// Color + alpha modulation |
|
g_pStudioRender->SetColorModulation( r_colormod ); |
|
g_pStudioRender->SetAlphaModulation( r_blend ); |
|
|
|
Assert( modelloader->IsLoaded( pInfo.pModel ) ); |
|
info.m_pStudioHdr = state.m_pStudioHdr; |
|
info.m_pHardwareData = state.m_pStudioHWData; |
|
info.m_Skin = pInfo.skin; |
|
info.m_Body = pInfo.body; |
|
info.m_HitboxSet = pInfo.hitboxset; |
|
info.m_pClientEntity = (void*)state.m_pRenderable; |
|
info.m_Lod = state.m_lod; |
|
info.m_pColorMeshes = pColorMeshes; |
|
|
|
// Don't do decals if shadow depth mapping... |
|
info.m_Decals = ( bShadowDepth || bSSAODepth ) ? STUDIORENDER_DECAL_INVALID : state.m_decals; |
|
|
|
// Get perf stats if we are going to use them. |
|
int overlayVal = r_drawmodelstatsoverlay.GetInt(); |
|
int drawFlags = state.m_drawFlags; |
|
|
|
if ( bShadowDepth ) |
|
{ |
|
drawFlags |= STUDIORENDER_DRAW_OPAQUE_ONLY; |
|
drawFlags |= STUDIORENDER_SHADOWDEPTHTEXTURE; |
|
} |
|
|
|
if ( bSSAODepth == true ) |
|
{ |
|
drawFlags |= STUDIORENDER_DRAW_OPAQUE_ONLY; |
|
drawFlags |= STUDIORENDER_SSAODEPTHTEXTURE; |
|
} |
|
|
|
if ( overlayVal && !bShadowDepth && !bSSAODepth ) |
|
{ |
|
drawFlags |= STUDIORENDER_DRAW_GET_PERF_STATS; |
|
} |
|
|
|
if ( ( pInfo.flags & STUDIO_GENERATE_STATS ) != 0 ) |
|
{ |
|
drawFlags |= STUDIORENDER_GENERATE_STATS; |
|
} |
|
|
|
DrawModelResults_t results; |
|
g_pStudioRender->DrawModel( &results, info, pBoneToWorld, pFlexWeights, |
|
pFlexDelayedWeights, pInfo.origin, drawFlags ); |
|
info.m_Lod = results.m_nLODUsed; |
|
|
|
if ( overlayVal && !bShadowDepth && !bSSAODepth ) |
|
{ |
|
if ( overlayVal != 2 ) |
|
{ |
|
DrawModelDebugOverlay( info, results, pInfo.origin ); |
|
} |
|
else |
|
{ |
|
AddModelDebugOverlay( info, results, pInfo.origin ); |
|
} |
|
} |
|
|
|
if ( pColorMeshes) |
|
{ |
|
ProtectColorDataIfQueued( hColorMeshData ); |
|
} |
|
|
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Main entry point for model rendering in the engine |
|
//----------------------------------------------------------------------------- |
|
int CModelRender::DrawModel( |
|
int flags, |
|
IClientRenderable *pRenderable, |
|
ModelInstanceHandle_t instance, |
|
int entity_index, |
|
const model_t *pModel, |
|
const Vector &origin, |
|
const QAngle &angles, |
|
int skin, |
|
int body, |
|
int hitboxset, |
|
const matrix3x4_t* pModelToWorld, |
|
const matrix3x4_t *pLightingOffset ) |
|
{ |
|
ModelRenderInfo_t sInfo; |
|
sInfo.flags = flags; |
|
sInfo.pRenderable = pRenderable; |
|
sInfo.instance = instance; |
|
sInfo.entity_index = entity_index; |
|
sInfo.pModel = pModel; |
|
sInfo.origin = origin; |
|
sInfo.angles = angles; |
|
sInfo.skin = skin; |
|
sInfo.body = body; |
|
sInfo.hitboxset = hitboxset; |
|
sInfo.pModelToWorld = pModelToWorld; |
|
sInfo.pLightingOffset = pLightingOffset; |
|
|
|
if ( (r_entity.GetInt() == -1) || (r_entity.GetInt() == entity_index) ) |
|
{ |
|
return DrawModelEx( sInfo ); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int CModelRender::ComputeLOD( const ModelRenderInfo_t &info, studiohwdata_t *pStudioHWData ) |
|
{ |
|
int lod = r_lod.GetInt(); |
|
// FIXME!!! This calc should be in studiorender, not here!!!!! But since the bone setup |
|
// is done here, and we need the bone mask, we'll do it here for now. |
|
if ( lod == -1 ) |
|
{ |
|
CMatRenderContextPtr pRenderContext( materials ); |
|
float screenSize = pRenderContext->ComputePixelWidthOfSphere(info.pRenderable->GetRenderOrigin(), 0.5f ); |
|
float metric = pStudioHWData->LODMetric(screenSize); |
|
lod = pStudioHWData->GetLODForMetric(metric); |
|
} |
|
else |
|
{ |
|
if ( ( info.flags & STUDIOHDR_FLAGS_HASSHADOWLOD ) && ( lod > pStudioHWData->m_NumLODs - 2 ) ) |
|
{ |
|
lod = pStudioHWData->m_NumLODs - 2; |
|
} |
|
else if ( lod > pStudioHWData->m_NumLODs - 1 ) |
|
{ |
|
lod = pStudioHWData->m_NumLODs - 1; |
|
} |
|
else if( lod < 0 ) |
|
{ |
|
lod = 0; |
|
} |
|
} |
|
|
|
if ( lod < 0 ) |
|
{ |
|
lod = 0; |
|
} |
|
else if ( lod >= pStudioHWData->m_NumLODs ) |
|
{ |
|
lod = pStudioHWData->m_NumLODs - 1; |
|
} |
|
|
|
// clamp to root lod |
|
if (lod < pStudioHWData->m_RootLOD) |
|
{ |
|
lod = pStudioHWData->m_RootLOD; |
|
} |
|
|
|
Assert( lod >= 0 && lod < pStudioHWData->m_NumLODs ); |
|
return lod; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &pInfo - |
|
//----------------------------------------------------------------------------- |
|
bool CModelRender::DrawModelSetup( ModelRenderInfo_t &pInfo, DrawModelState_t *pState, matrix3x4_t *pCustomBoneToWorld, matrix3x4_t** ppBoneToWorldOut ) |
|
{ |
|
*ppBoneToWorldOut = NULL; |
|
|
|
#ifdef SWDS |
|
return false; |
|
#endif |
|
|
|
#if _DEBUG |
|
if ( (char*)pInfo.pRenderable < (char*)1024 ) |
|
{ |
|
Error( "CModelRender::DrawModel: pRenderable == 0x%p", pInfo.pRenderable ); |
|
} |
|
#endif |
|
|
|
// Can only deal with studio models |
|
Assert( pInfo.pModel->type == mod_studio ); |
|
Assert( modelloader->IsLoaded( pInfo.pModel ) ); |
|
|
|
DrawModelState_t &state = *pState; |
|
state.m_pStudioHdr = g_pMDLCache->GetStudioHdr( pInfo.pModel->studio ); |
|
state.m_pRenderable = pInfo.pRenderable; |
|
|
|
// Quick exit if we're just supposed to draw a specific model... |
|
if ( (r_entity.GetInt() != -1) && (r_entity.GetInt() != pInfo.entity_index) ) |
|
return false; |
|
|
|
// quick exit |
|
if (state.m_pStudioHdr->numbodyparts == 0) |
|
return false; |
|
|
|
if ( !pInfo.pModelToWorld ) |
|
{ |
|
Assert( 0 ); |
|
return false; |
|
} |
|
|
|
state.m_pModelToWorld = pInfo.pModelToWorld; |
|
|
|
Assert ( pInfo.pRenderable ); |
|
|
|
state.m_pStudioHWData = g_pMDLCache->GetHardwareData( pInfo.pModel->studio ); |
|
if ( !state.m_pStudioHWData ) |
|
return false; |
|
|
|
state.m_lod = ComputeLOD( pInfo, state.m_pStudioHWData ); |
|
|
|
int boneMask = BONE_USED_BY_VERTEX_AT_LOD( state.m_lod ); |
|
// Why isn't this always set?!? |
|
|
|
if ( ( pInfo.flags & STUDIO_RENDER ) == 0 ) |
|
{ |
|
// no rendering, just force a bone setup. Don't copy the bones |
|
bool bOk = pInfo.pRenderable->SetupBones( NULL, MAXSTUDIOBONES, boneMask, cl.GetTime() ); |
|
return bOk; |
|
} |
|
|
|
int nBoneCount = state.m_pStudioHdr->numbones; |
|
matrix3x4_t *pBoneToWorld = pCustomBoneToWorld; |
|
if ( !pCustomBoneToWorld ) |
|
{ |
|
pBoneToWorld = g_pStudioRender->LockBoneMatrices( nBoneCount ); |
|
} |
|
const bool bOk = pInfo.pRenderable->SetupBones( pBoneToWorld, nBoneCount, boneMask, cl.GetTime() ); |
|
if ( !pCustomBoneToWorld ) |
|
{ |
|
g_pStudioRender->UnlockBoneMatrices(); |
|
} |
|
if ( !bOk ) |
|
return false; |
|
|
|
*ppBoneToWorldOut = pBoneToWorld; |
|
|
|
// Convert the instance to a decal handle. |
|
state.m_decals = STUDIORENDER_DECAL_INVALID; |
|
if (pInfo.instance != MODEL_INSTANCE_INVALID) |
|
{ |
|
state.m_decals = m_ModelInstances[pInfo.instance].m_DecalHandle; |
|
} |
|
|
|
state.m_drawFlags = STUDIORENDER_DRAW_ENTIRE_MODEL; |
|
if ( pInfo.flags & STUDIO_TWOPASS ) |
|
{ |
|
if (pInfo.flags & STUDIO_TRANSPARENCY) |
|
{ |
|
state.m_drawFlags = STUDIORENDER_DRAW_TRANSLUCENT_ONLY; |
|
} |
|
else |
|
{ |
|
state.m_drawFlags = STUDIORENDER_DRAW_OPAQUE_ONLY; |
|
} |
|
} |
|
if ( pInfo.flags & STUDIO_STATIC_LIGHTING ) |
|
{ |
|
state.m_drawFlags |= STUDIORENDER_DRAW_STATIC_LIGHTING; |
|
} |
|
|
|
if( pInfo.flags & STUDIO_ITEM_BLINK ) |
|
{ |
|
state.m_drawFlags |= STUDIORENDER_DRAW_ITEM_BLINK; |
|
} |
|
|
|
if ( pInfo.flags & STUDIO_WIREFRAME ) |
|
{ |
|
state.m_drawFlags |= STUDIORENDER_DRAW_WIREFRAME; |
|
} |
|
|
|
if ( pInfo.flags & STUDIO_NOSHADOWS ) |
|
{ |
|
state.m_drawFlags |= STUDIORENDER_DRAW_NO_SHADOWS; |
|
} |
|
|
|
if ( r_drawmodelstatsoverlay.GetInt() == 2) |
|
{ |
|
state.m_drawFlags |= STUDIORENDER_DRAW_ACCURATETIME; |
|
} |
|
|
|
if ( pInfo.flags & STUDIO_SHADOWDEPTHTEXTURE ) |
|
{ |
|
state.m_drawFlags |= STUDIORENDER_SHADOWDEPTHTEXTURE; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
int CModelRender::DrawModelEx( ModelRenderInfo_t &pInfo ) |
|
{ |
|
#ifndef SWDS |
|
DrawModelState_t state; |
|
|
|
matrix3x4_t tmpmat; |
|
if ( !pInfo.pModelToWorld ) |
|
{ |
|
pInfo.pModelToWorld = &tmpmat; |
|
|
|
// Turns the origin + angles into a matrix |
|
AngleMatrix( pInfo.angles, pInfo.origin, tmpmat ); |
|
} |
|
|
|
matrix3x4_t *pBoneToWorld; |
|
if ( !DrawModelSetup( pInfo, &state, NULL, &pBoneToWorld ) ) |
|
return 0; |
|
|
|
if ( pInfo.flags & STUDIO_RENDER ) |
|
{ |
|
DrawModelExecute( state, pInfo, pBoneToWorld ); |
|
} |
|
|
|
return 1; |
|
#else |
|
return 0; |
|
#endif |
|
} |
|
|
|
int CModelRender::DrawModelExStaticProp( ModelRenderInfo_t &pInfo ) |
|
{ |
|
#ifndef SWDS |
|
bool bShadowDepth = ( pInfo.flags & STUDIO_SHADOWDEPTHTEXTURE ) != 0; |
|
|
|
#if _DEBUG |
|
if ( (char*)pInfo.pRenderable < (char*)1024 ) |
|
{ |
|
Error( "CModelRender::DrawModel: pRenderable == %p", pInfo.pRenderable ); |
|
} |
|
|
|
// Can only deal with studio models |
|
if ( pInfo.pModel->type != mod_studio ) |
|
return 0; |
|
#endif |
|
Assert( modelloader->IsLoaded( pInfo.pModel ) ); |
|
|
|
DrawModelState_t state; |
|
state.m_pStudioHdr = g_pMDLCache->GetStudioHdr( pInfo.pModel->studio ); |
|
state.m_pRenderable = pInfo.pRenderable; |
|
|
|
// quick exit |
|
if ( state.m_pStudioHdr->numbodyparts == 0 || g_bTextMode ) |
|
return 1; |
|
|
|
state.m_pStudioHWData = g_pMDLCache->GetHardwareData( pInfo.pModel->studio ); |
|
if ( !state.m_pStudioHWData ) |
|
return 0; |
|
|
|
Assert( pInfo.pModelToWorld ); |
|
state.m_pModelToWorld = pInfo.pModelToWorld; |
|
Assert ( pInfo.pRenderable ); |
|
|
|
int lod = ComputeLOD( pInfo, state.m_pStudioHWData ); |
|
// int boneMask = BONE_USED_BY_VERTEX_AT_LOD( lod ); |
|
// Why isn't this always set?!? |
|
if ( !(pInfo.flags & STUDIO_RENDER) ) |
|
return 0; |
|
|
|
// Convert the instance to a decal handle. |
|
StudioDecalHandle_t decalHandle = STUDIORENDER_DECAL_INVALID; |
|
if ( (pInfo.instance != MODEL_INSTANCE_INVALID) && !(pInfo.flags & STUDIO_SHADOWDEPTHTEXTURE) ) |
|
{ |
|
decalHandle = m_ModelInstances[pInfo.instance].m_DecalHandle; |
|
} |
|
|
|
int drawFlags = STUDIORENDER_DRAW_ENTIRE_MODEL; |
|
if ( pInfo.flags & STUDIO_TWOPASS ) |
|
{ |
|
if ( pInfo.flags & STUDIO_TRANSPARENCY ) |
|
{ |
|
drawFlags = STUDIORENDER_DRAW_TRANSLUCENT_ONLY; |
|
} |
|
else |
|
{ |
|
drawFlags = STUDIORENDER_DRAW_OPAQUE_ONLY; |
|
} |
|
} |
|
|
|
if ( pInfo.flags & STUDIO_STATIC_LIGHTING ) |
|
{ |
|
drawFlags |= STUDIORENDER_DRAW_STATIC_LIGHTING; |
|
} |
|
|
|
if ( pInfo.flags & STUDIO_WIREFRAME ) |
|
{ |
|
drawFlags |= STUDIORENDER_DRAW_WIREFRAME; |
|
} |
|
|
|
if ( pInfo.flags & STUDIO_GENERATE_STATS ) |
|
{ |
|
drawFlags |= STUDIORENDER_GENERATE_STATS; |
|
} |
|
|
|
// Shadow state... |
|
g_pShadowMgr->SetModelShadowState( pInfo.instance ); |
|
|
|
// OPTIMIZE: Try to precompute part of this mess once a frame at the very least. |
|
bool bUsesBumpmapping = ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 80 ) && ( pInfo.pModel->flags & MODELFLAG_STUDIOHDR_USES_BUMPMAPPING ); |
|
|
|
bool bStaticLighting = (( drawFlags & STUDIORENDER_DRAW_STATIC_LIGHTING ) && |
|
( state.m_pStudioHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP ) && |
|
( !bUsesBumpmapping ) && |
|
( pInfo.instance != MODEL_INSTANCE_INVALID ) && |
|
g_pMaterialSystemHardwareConfig->SupportsColorOnSecondStream() ); |
|
|
|
bool bVertexLit = ( pInfo.pModel->flags & MODELFLAG_VERTEXLIT ) != 0; |
|
bool bNeedsEnvCubemap = r_showenvcubemap.GetInt() || ( pInfo.pModel->flags & MODELFLAG_STUDIOHDR_USES_ENV_CUBEMAP ); |
|
|
|
if ( r_drawmodellightorigin.GetBool() ) |
|
{ |
|
DebugDrawLightingOrigin( state, pInfo ); |
|
} |
|
|
|
ColorMeshInfo_t *pColorMeshes = NULL; |
|
DataCacheHandle_t hColorMeshData = DC_INVALID_HANDLE; |
|
if ( bStaticLighting ) |
|
{ |
|
// have static lighting, get from cache |
|
hColorMeshData = m_ModelInstances[pInfo.instance].m_ColorMeshHandle; |
|
CColorMeshData *pColorMeshData = CacheGet( hColorMeshData ); |
|
if ( !pColorMeshData || pColorMeshData->m_bNeedsRetry ) |
|
{ |
|
// color meshes are not present, try to re-establish |
|
if ( RecomputeStaticLighting( pInfo.instance ) ) |
|
{ |
|
pColorMeshData = CacheGet( hColorMeshData ); |
|
} |
|
else if ( !pColorMeshData || !pColorMeshData->m_bNeedsRetry ) |
|
{ |
|
// can't draw |
|
return 0; |
|
} |
|
} |
|
|
|
if ( pColorMeshData && ( pColorMeshData->m_bColorMeshValid || pColorMeshData->m_bColorTextureValid ) ) |
|
{ |
|
pColorMeshes = pColorMeshData->m_pMeshInfos; |
|
if (pColorMeshData->m_bColorTextureValid && !pColorMeshData->m_bColorTextureCreated) |
|
{ |
|
CreateLightmapsFromData(pColorMeshData); |
|
} |
|
} |
|
else |
|
{ |
|
// failed, draw without static lighting |
|
bStaticLighting = false; |
|
} |
|
} |
|
|
|
DrawModelInfo_t info; |
|
info.m_bStaticLighting = false; |
|
|
|
// Get lighting from ambient light sources and radiosity bounces |
|
// also set up the env_cubemap from the light cache if necessary. |
|
// Don't bother if we're rendering to shadow depth texture |
|
if ( ( bVertexLit || bNeedsEnvCubemap ) && !bShadowDepth ) |
|
{ |
|
// See if we're using static lighting |
|
LightCacheHandle_t* pLightCache = NULL; |
|
if ( pInfo.instance != MODEL_INSTANCE_INVALID ) |
|
{ |
|
if ( ( m_ModelInstances[pInfo.instance].m_nFlags & MODEL_INSTANCE_HAS_STATIC_LIGHTING ) && m_ModelInstances[pInfo.instance].m_LightCacheHandle ) |
|
{ |
|
pLightCache = &m_ModelInstances[pInfo.instance].m_LightCacheHandle; |
|
} |
|
} |
|
|
|
// Choose the lighting origin |
|
Vector entOrigin; |
|
if ( !pLightCache ) |
|
{ |
|
R_ComputeLightingOrigin( state.m_pRenderable, state.m_pStudioHdr, *state.m_pModelToWorld, entOrigin ); |
|
} |
|
|
|
// Set up lighting based on the lighting origin |
|
StudioSetupLighting( state, entOrigin, pLightCache, bVertexLit, bNeedsEnvCubemap, bStaticLighting, info, pInfo, drawFlags ); |
|
} |
|
|
|
Assert( modelloader->IsLoaded( pInfo.pModel ) ); |
|
info.m_pStudioHdr = state.m_pStudioHdr; |
|
info.m_pHardwareData = state.m_pStudioHWData; |
|
info.m_Decals = decalHandle; |
|
info.m_Skin = pInfo.skin; |
|
info.m_Body = pInfo.body; |
|
info.m_HitboxSet = pInfo.hitboxset; |
|
info.m_pClientEntity = (void*)state.m_pRenderable; |
|
info.m_Lod = lod; |
|
info.m_pColorMeshes = pColorMeshes; |
|
|
|
if ( bShadowDepth ) |
|
{ |
|
drawFlags |= STUDIORENDER_SHADOWDEPTHTEXTURE; |
|
} |
|
|
|
#ifdef _DEBUG |
|
Vector tmp; |
|
MatrixGetColumn( *pInfo.pModelToWorld, 3, &tmp ); |
|
Assert( VectorsAreEqual( pInfo.origin, tmp, 1e-3 ) ); |
|
#endif |
|
|
|
g_pStudioRender->DrawModelStaticProp( info, *pInfo.pModelToWorld, drawFlags ); |
|
|
|
if ( pColorMeshes) |
|
{ |
|
ProtectColorDataIfQueued( hColorMeshData ); |
|
} |
|
|
|
return 1; |
|
#else |
|
return 0; |
|
#endif |
|
} |
|
|
|
struct robject_t |
|
{ |
|
const matrix3x4_t *pMatrix; |
|
IClientRenderable *pRenderable; |
|
ColorMeshInfo_t *pColorMeshes; |
|
ITexture *pEnvCubeMap; |
|
Vector *pLightingOrigin; |
|
short modelIndex; |
|
short lod; |
|
ModelInstanceHandle_t instance; |
|
short skin; |
|
short lightIndex; |
|
short pad0; |
|
}; |
|
|
|
struct rmodel_t |
|
{ |
|
const model_t * pModel; |
|
studiohdr_t* pStudioHdr; |
|
studiohwdata_t* pStudioHWData; |
|
float maxArea; |
|
short lodStart; |
|
byte lodCount; |
|
byte bVertexLit : 1; |
|
byte bNeedsCubemap : 1; |
|
byte bStaticLighting : 1; |
|
}; |
|
|
|
class CRobjectLess |
|
{ |
|
public: |
|
bool Less( const robject_t& lhs, const robject_t& rhs, void *pContext ) |
|
{ |
|
rmodel_t *pModels = static_cast<rmodel_t *>(pContext); |
|
if ( lhs.modelIndex == rhs.modelIndex ) |
|
{ |
|
if ( lhs.skin != rhs.skin ) |
|
return lhs.skin < rhs.skin; |
|
return lhs.lod < rhs.lod; |
|
} |
|
if ( pModels[lhs.modelIndex].maxArea == pModels[rhs.modelIndex].maxArea ) |
|
return lhs.modelIndex < rhs.modelIndex; |
|
return pModels[lhs.modelIndex].maxArea > pModels[rhs.modelIndex].maxArea; |
|
} |
|
}; |
|
|
|
struct rdecalmodel_t |
|
{ |
|
short objectIndex; |
|
short lightIndex; |
|
}; |
|
/* |
|
// ---------------------------------------- |
|
// not yet implemented |
|
|
|
struct rlod_t |
|
{ |
|
short groupCount; |
|
short groupStart; |
|
}; |
|
|
|
struct rgroup_t |
|
{ |
|
IMesh *pMesh; |
|
short batchCount; |
|
short batchStart; |
|
short colorMeshIndex; |
|
short pad0; |
|
}; |
|
|
|
struct rbatch_t |
|
{ |
|
IMaterial *pMaterial; |
|
short primitiveType; |
|
short pad0; |
|
unsigned short indexOffset; |
|
unsigned short indexCount; |
|
}; |
|
// ---------------------------------------- |
|
*/ |
|
|
|
inline int FindModel( const CUtlVector<rmodel_t> &list, const model_t *pModel ) |
|
{ |
|
for ( int j = list.Count(); --j >= 0 ; ) |
|
{ |
|
if ( list[j].pModel == pModel ) |
|
return j; |
|
} |
|
return -1; |
|
} |
|
|
|
|
|
// NOTE: UNDONE: This is a work in progress of a new static prop rendering pipeline |
|
// UNDONE: Expose drawing commands from studiorender and draw here |
|
// UNDONE: Build a similar pipeline for non-static prop models |
|
// UNDONE: Split this into several functions in a sub-object |
|
ConVar r_staticprop_lod("r_staticprop_lod", "-1"); |
|
int CModelRender::DrawStaticPropArrayFast( StaticPropRenderInfo_t *pProps, int count, bool bShadowDepth ) |
|
{ |
|
#ifndef SWDS |
|
MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); |
|
CMatRenderContextPtr pRenderContext( materials ); |
|
const int MAX_OBJECTS = 1024; |
|
CUtlSortVector<robject_t, CRobjectLess> objectList(0, MAX_OBJECTS); |
|
CUtlVector<rmodel_t> modelList(0,256); |
|
CUtlVector<short> lightObjects(0,256); |
|
CUtlVector<short> shadowObjects(0,64); |
|
CUtlVector<rdecalmodel_t> decalObjects(0,64); |
|
CUtlVector<LightingState_t> lightStates(0,256); |
|
bool bForceCubemap = r_showenvcubemap.GetBool(); |
|
int drawnCount = 0; |
|
int forcedLodSetting = r_lod.GetInt(); |
|
if ( r_staticprop_lod.GetInt() >= 0 ) |
|
{ |
|
forcedLodSetting = r_staticprop_lod.GetInt(); |
|
} |
|
|
|
// build list of objects and unique models |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
drawnCount++; |
|
// UNDONE: This is a perf hit in some scenes! Use a hash? |
|
int modelIndex = FindModel( modelList, pProps[i].pModel ); |
|
if ( modelIndex < 0 ) |
|
{ |
|
modelIndex = modelList.AddToTail(); |
|
modelList[modelIndex].pModel = pProps[i].pModel; |
|
modelList[modelIndex].pStudioHWData = g_pMDLCache->GetHardwareData( modelList[modelIndex].pModel->studio ); |
|
} |
|
if ( modelList[modelIndex].pStudioHWData ) |
|
{ |
|
robject_t obj; |
|
obj.pMatrix = pProps[i].pModelToWorld; |
|
obj.pRenderable = pProps[i].pRenderable; |
|
obj.modelIndex = modelIndex; |
|
obj.instance = pProps[i].instance; |
|
obj.skin = pProps[i].skin; |
|
obj.lod = 0; |
|
obj.pColorMeshes = NULL; |
|
obj.pEnvCubeMap = NULL; |
|
obj.lightIndex = -1; |
|
obj.pLightingOrigin = pProps[i].pLightingOrigin; |
|
objectList.InsertNoSort(obj); |
|
} |
|
} |
|
|
|
// process list of unique models |
|
int lodStart = 0; |
|
for ( int i = 0; i < modelList.Count(); i++ ) |
|
{ |
|
const model_t *pModel = modelList[i].pModel; |
|
Assert( modelloader->IsLoaded( pModel ) ); |
|
unsigned int flags = pModel->flags; |
|
modelList[i].pStudioHdr = g_pMDLCache->GetStudioHdr( pModel->studio ); |
|
modelList[i].maxArea = 1.0f; |
|
modelList[i].lodStart = lodStart; |
|
modelList[i].lodCount = modelList[i].pStudioHWData ? modelList[i].pStudioHWData->m_NumLODs : 0; |
|
bool bBumpMapped = (flags & MODELFLAG_STUDIOHDR_USES_BUMPMAPPING) != 0; |
|
modelList[i].bStaticLighting = (( modelList[i].pStudioHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP ) != 0) && !bBumpMapped; |
|
modelList[i].bVertexLit = ( flags & MODELFLAG_VERTEXLIT ) != 0; |
|
modelList[i].bNeedsCubemap = ( flags & MODELFLAG_STUDIOHDR_USES_ENV_CUBEMAP ) != 0; |
|
|
|
lodStart += modelList[i].lodCount; |
|
} |
|
|
|
// -1 is automatic lod |
|
if ( forcedLodSetting < 0 ) |
|
{ |
|
// compute the lod of each object |
|
for ( int i = 0; i < objectList.Count(); i++ ) |
|
{ |
|
Vector org; |
|
MatrixGetColumn( *objectList[i].pMatrix, 3, org ); |
|
float screenSize = pRenderContext->ComputePixelWidthOfSphere(org, 0.5f ); |
|
const rmodel_t &model = modelList[objectList[i].modelIndex]; |
|
float metric = model.pStudioHWData->LODMetric(screenSize); |
|
objectList[i].lod = model.pStudioHWData->GetLODForMetric(metric); |
|
if ( objectList[i].lod < model.pStudioHWData->m_RootLOD ) |
|
{ |
|
objectList[i].lod = model.pStudioHWData->m_RootLOD; |
|
} |
|
modelList[objectList[i].modelIndex].maxArea = max(modelList[objectList[i].modelIndex].maxArea, screenSize); |
|
} |
|
} |
|
else |
|
{ |
|
// force the lod of each object |
|
for ( int i = 0; i < objectList.Count(); i++ ) |
|
{ |
|
const rmodel_t &model = modelList[objectList[i].modelIndex]; |
|
objectList[i].lod = clamp(forcedLodSetting, model.pStudioHWData->m_RootLOD, model.lodCount-1); |
|
} |
|
} |
|
// UNDONE: Don't sort if rendering transparent objects - for now this isn't called in the transparent case |
|
// sort by model, then by lod |
|
objectList.SetLessContext( static_cast<void *>(modelList.Base()) ); |
|
objectList.RedoSort(true); |
|
|
|
ICallQueue *pCallQueue = pRenderContext->GetCallQueue(); |
|
|
|
// now build out the lighting states |
|
if ( !bShadowDepth ) |
|
{ |
|
for ( int i = 0; i < objectList.Count(); i++ ) |
|
{ |
|
robject_t &obj = objectList[i]; |
|
rmodel_t &model = modelList[obj.modelIndex]; |
|
bool bStaticLighting = (model.bStaticLighting && obj.instance != MODEL_INSTANCE_INVALID); |
|
bool bVertexLit = model.bVertexLit; |
|
bool bNeedsEnvCubemap = bForceCubemap || model.bNeedsCubemap; |
|
bool bHasDecals = ( m_ModelInstances[obj.instance].m_DecalHandle != STUDIORENDER_DECAL_INVALID ) ? true : false; |
|
LightingState_t *pDecalLightState = NULL; |
|
if ( bHasDecals ) |
|
{ |
|
rdecalmodel_t decalModel; |
|
decalModel.lightIndex = lightStates.AddToTail(); |
|
pDecalLightState = &lightStates[decalModel.lightIndex]; |
|
decalModel.objectIndex = i; |
|
decalObjects.AddToTail( decalModel ); |
|
} |
|
// for now we skip models that have shadows - will update later to include them in a post-pass |
|
if ( g_pShadowMgr->ModelHasShadows( obj.instance ) ) |
|
{ |
|
shadowObjects.AddToTail(i); |
|
} |
|
|
|
// get the static lighting from the cache |
|
DataCacheHandle_t hColorMeshData = DC_INVALID_HANDLE; |
|
if ( bStaticLighting ) |
|
{ |
|
// have static lighting, get from cache |
|
hColorMeshData = m_ModelInstances[obj.instance].m_ColorMeshHandle; |
|
CColorMeshData *pColorMeshData = CacheGet( hColorMeshData ); |
|
if ( !pColorMeshData || pColorMeshData->m_bNeedsRetry ) |
|
{ |
|
// color meshes are not present, try to re-establish |
|
|
|
if ( UpdateStaticPropColorData( obj.pRenderable->GetIClientUnknown(), obj.instance ) ) |
|
{ |
|
pColorMeshData = CacheGet( hColorMeshData ); |
|
} |
|
else if ( !pColorMeshData || !pColorMeshData->m_bNeedsRetry ) |
|
{ |
|
// can't draw |
|
continue; |
|
} |
|
} |
|
|
|
if ( pColorMeshData && ( pColorMeshData->m_bColorMeshValid || pColorMeshData->m_bColorTextureValid ) ) |
|
{ |
|
obj.pColorMeshes = pColorMeshData->m_pMeshInfos; |
|
if (pColorMeshData->m_bColorTextureValid && !pColorMeshData->m_bColorTextureCreated) |
|
{ |
|
CreateLightmapsFromData(pColorMeshData); |
|
} |
|
|
|
if ( pCallQueue ) |
|
{ |
|
if ( CacheLock( hColorMeshData ) ) // CacheCreate above will call functions that won't take place until later. If color mesh isn't used right away, it could get dumped |
|
{ |
|
pCallQueue->QueueCall( this, &CModelRender::CacheUnlock, hColorMeshData ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// failed, draw without static lighting |
|
bStaticLighting = false; |
|
} |
|
} |
|
|
|
// Get lighting from ambient light sources and radiosity bounces |
|
// also set up the env_cubemap from the light cache if necessary. |
|
if ( ( bVertexLit || bNeedsEnvCubemap ) ) |
|
{ |
|
// See if we're using static lighting |
|
LightCacheHandle_t* pLightCache = NULL; |
|
ITexture *pEnvCubemapTexture = NULL; |
|
if ( obj.instance != MODEL_INSTANCE_INVALID ) |
|
{ |
|
if ( ( m_ModelInstances[obj.instance].m_nFlags & MODEL_INSTANCE_HAS_STATIC_LIGHTING ) && m_ModelInstances[obj.instance].m_LightCacheHandle ) |
|
{ |
|
pLightCache = &m_ModelInstances[obj.instance].m_LightCacheHandle; |
|
} |
|
} |
|
|
|
Assert(pLightCache); |
|
LightingState_t lightingState; |
|
LightingState_t *pState = &lightingState; |
|
if ( pLightCache ) |
|
{ |
|
// dx8 and dx9 case. . .hardware can do baked lighting plus other dynamic lighting |
|
// We already have the static part baked into a color mesh, so just get the dynamic stuff. |
|
if ( !bStaticLighting || StaticLightCacheAffectedByDynamicLight( *pLightCache ) ) |
|
{ |
|
pState = LightcacheGetStatic( *pLightCache, &pEnvCubemapTexture, LIGHTCACHEFLAGS_STATIC | LIGHTCACHEFLAGS_DYNAMIC | LIGHTCACHEFLAGS_LIGHTSTYLE ); |
|
Assert( pState->numlights >= 0 && pState->numlights <= MAXLOCALLIGHTS ); |
|
} |
|
else |
|
{ |
|
pState = LightcacheGetStatic( *pLightCache, &pEnvCubemapTexture, LIGHTCACHEFLAGS_DYNAMIC | LIGHTCACHEFLAGS_LIGHTSTYLE ); |
|
Assert( pState->numlights >= 0 && pState->numlights <= MAXLOCALLIGHTS ); |
|
} |
|
if ( bHasDecals ) |
|
{ |
|
for ( int iCube = 0; iCube < 6; ++iCube ) |
|
{ |
|
pDecalLightState->r_boxcolor[iCube] = m_ModelInstances[obj.instance].m_AmbientLightingState.r_boxcolor[iCube] + pState->r_boxcolor[iCube]; |
|
} |
|
pDecalLightState->CopyLocalLights( m_ModelInstances[obj.instance].m_AmbientLightingState ); |
|
pDecalLightState->AddAllLocalLights( *pState ); |
|
} |
|
} |
|
else // !pLightcache |
|
{ |
|
// UNDONE: is it possible to end up here in the static prop case? |
|
Vector vLightingOrigin = *obj.pLightingOrigin; |
|
int lightCacheFlags = bStaticLighting ? (LIGHTCACHEFLAGS_DYNAMIC | LIGHTCACHEFLAGS_LIGHTSTYLE) |
|
: (LIGHTCACHEFLAGS_STATIC|LIGHTCACHEFLAGS_DYNAMIC|LIGHTCACHEFLAGS_LIGHTSTYLE|LIGHTCACHEFLAGS_ALLOWFAST); |
|
LightcacheGetDynamic_Stats stats; |
|
pEnvCubemapTexture = LightcacheGetDynamic( vLightingOrigin, lightingState, |
|
stats, lightCacheFlags, false ); |
|
Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); |
|
pState = &lightingState; |
|
|
|
if ( bHasDecals ) |
|
{ |
|
LightcacheGetDynamic_Stats tempStats; |
|
LightcacheGetDynamic( vLightingOrigin, *pDecalLightState, tempStats, |
|
LIGHTCACHEFLAGS_STATIC|LIGHTCACHEFLAGS_DYNAMIC|LIGHTCACHEFLAGS_LIGHTSTYLE|LIGHTCACHEFLAGS_ALLOWFAST ); |
|
} |
|
} |
|
|
|
if ( bNeedsEnvCubemap && pEnvCubemapTexture ) |
|
{ |
|
obj.pEnvCubeMap = pEnvCubemapTexture; |
|
} |
|
|
|
if ( bVertexLit ) |
|
{ |
|
// if we have any real lighting state we need to save it for this object |
|
if ( pState->numlights || pState->HasAmbientColors() ) |
|
{ |
|
obj.lightIndex = lightStates.AddToTail(*pState); |
|
lightObjects.AddToTail( i ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
// now render the baked lighting props with no lighting state |
|
float color[3]; |
|
color[0] = color[1] = color[2] = 1.0f; |
|
g_pStudioRender->SetColorModulation(color); |
|
g_pStudioRender->SetAlphaModulation(1.0f); |
|
g_pStudioRender->SetViewState( CurrentViewOrigin(), CurrentViewRight(), CurrentViewUp(), CurrentViewForward() ); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_MODEL ); |
|
pRenderContext->PushMatrix(); |
|
pRenderContext->LoadIdentity(); |
|
g_pStudioRender->ClearAllShadows(); |
|
pRenderContext->DisableAllLocalLights(); |
|
DrawModelInfo_t info; |
|
for ( int i = 0; i < 6; i++ ) |
|
info.m_vecAmbientCube[i].Init(); |
|
g_pStudioRender->SetAmbientLightColors( info.m_vecAmbientCube ); |
|
pRenderContext->SetAmbientLight( 0.0, 0.0, 0.0 ); |
|
info.m_nLocalLightCount = 0; |
|
info.m_bStaticLighting = false; |
|
|
|
int drawFlags = STUDIORENDER_DRAW_ENTIRE_MODEL | STUDIORENDER_DRAW_STATIC_LIGHTING; |
|
if (bShadowDepth) |
|
drawFlags |= STUDIO_SHADOWDEPTHTEXTURE; |
|
info.m_Decals = STUDIORENDER_DECAL_INVALID; |
|
info.m_Body = 0; |
|
info.m_HitboxSet = 0; |
|
for ( int i = 0; i < objectList.Count(); i++ ) |
|
{ |
|
robject_t &obj = objectList[i]; |
|
if ( obj.lightIndex >= 0 ) |
|
continue; |
|
rmodel_t &model = modelList[obj.modelIndex]; |
|
if ( obj.pEnvCubeMap ) |
|
{ |
|
pRenderContext->BindLocalCubemap( obj.pEnvCubeMap ); |
|
} |
|
|
|
info.m_pStudioHdr = model.pStudioHdr; |
|
info.m_pHardwareData = model.pStudioHWData; |
|
info.m_Skin = obj.skin; |
|
info.m_pClientEntity = static_cast<void*>(obj.pRenderable); |
|
info.m_Lod = obj.lod; |
|
info.m_pColorMeshes = obj.pColorMeshes; |
|
g_pStudioRender->DrawModelStaticProp( info, *obj.pMatrix, drawFlags ); |
|
} |
|
|
|
// now render the vertex lit props |
|
int nLocalLightCount = 0; |
|
LightDesc_t localLightDescs[4]; |
|
drawFlags = STUDIORENDER_DRAW_ENTIRE_MODEL | STUDIORENDER_DRAW_STATIC_LIGHTING; |
|
if ( lightObjects.Count() ) |
|
{ |
|
for ( int i = 0; i < lightObjects.Count(); i++ ) |
|
{ |
|
robject_t &obj = objectList[lightObjects[i]]; |
|
rmodel_t &model = modelList[obj.modelIndex]; |
|
|
|
if ( obj.pEnvCubeMap ) |
|
{ |
|
pRenderContext->BindLocalCubemap( obj.pEnvCubeMap ); |
|
} |
|
|
|
LightingState_t *pState = &lightStates[obj.lightIndex]; |
|
g_pStudioRender->SetAmbientLightColors( pState->r_boxcolor ); |
|
pRenderContext->SetLightingOrigin( *obj.pLightingOrigin ); |
|
R_SetNonAmbientLightingState( pState->numlights, pState->locallight, &nLocalLightCount, localLightDescs, true ); |
|
info.m_pStudioHdr = model.pStudioHdr; |
|
info.m_pHardwareData = model.pStudioHWData; |
|
info.m_Skin = obj.skin; |
|
info.m_pClientEntity = static_cast<void*>(obj.pRenderable); |
|
info.m_Lod = obj.lod; |
|
info.m_pColorMeshes = obj.pColorMeshes; |
|
g_pStudioRender->DrawModelStaticProp( info, *obj.pMatrix, drawFlags ); |
|
} |
|
} |
|
|
|
if ( !IsX360() && ( r_flashlight_version2.GetInt() == 0 ) && shadowObjects.Count() ) |
|
{ |
|
drawFlags = STUDIORENDER_DRAW_ENTIRE_MODEL; |
|
for ( int i = 0; i < shadowObjects.Count(); i++ ) |
|
{ |
|
// draw just the shadows! |
|
robject_t &obj = objectList[shadowObjects[i]]; |
|
rmodel_t &model = modelList[obj.modelIndex]; |
|
g_pShadowMgr->SetModelShadowState( obj.instance ); |
|
|
|
info.m_pStudioHdr = model.pStudioHdr; |
|
info.m_pHardwareData = model.pStudioHWData; |
|
info.m_Skin = obj.skin; |
|
info.m_pClientEntity = static_cast<void*>(obj.pRenderable); |
|
info.m_Lod = obj.lod; |
|
info.m_pColorMeshes = obj.pColorMeshes; |
|
g_pStudioRender->DrawStaticPropShadows( info, *obj.pMatrix, drawFlags ); |
|
} |
|
g_pStudioRender->ClearAllShadows(); |
|
} |
|
|
|
for ( int i = 0; i < decalObjects.Count(); i++ ) |
|
{ |
|
// draw just the decals! |
|
robject_t &obj = objectList[decalObjects[i].objectIndex]; |
|
rmodel_t &model = modelList[obj.modelIndex]; |
|
LightingState_t *pState = &lightStates[decalObjects[i].lightIndex]; |
|
g_pStudioRender->SetAmbientLightColors( pState->r_boxcolor ); |
|
pRenderContext->SetLightingOrigin( *obj.pLightingOrigin ); |
|
R_SetNonAmbientLightingState( pState->numlights, pState->locallight, &nLocalLightCount, localLightDescs, true ); |
|
info.m_pStudioHdr = model.pStudioHdr; |
|
info.m_pHardwareData = model.pStudioHWData; |
|
info.m_Decals = m_ModelInstances[obj.instance].m_DecalHandle; |
|
info.m_Skin = obj.skin; |
|
info.m_pClientEntity = static_cast<void*>(obj.pRenderable); |
|
info.m_Lod = obj.lod; |
|
info.m_pColorMeshes = obj.pColorMeshes; |
|
g_pStudioRender->DrawStaticPropDecals( info, *obj.pMatrix ); |
|
} |
|
|
|
// Restore the matrices if we're skinning |
|
pRenderContext->MatrixMode( MATERIAL_MODEL ); |
|
pRenderContext->PopMatrix(); |
|
return drawnCount; |
|
#else // SWDS |
|
return 0; |
|
#endif // SWDS |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Shadow rendering |
|
//----------------------------------------------------------------------------- |
|
matrix3x4_t* CModelRender::DrawModelShadowSetup( IClientRenderable *pRenderable, int body, int skin, DrawModelInfo_t *pInfo, matrix3x4_t *pCustomBoneToWorld ) |
|
{ |
|
#ifndef SWDS |
|
DrawModelInfo_t &info = *pInfo; |
|
static ConVar r_shadowlod("r_shadowlod", "-1"); |
|
static ConVar r_shadowlodbias("r_shadowlodbias", "2"); |
|
|
|
model_t const* pModel = pRenderable->GetModel(); |
|
if ( !pModel ) |
|
return NULL; |
|
|
|
// FIXME: Make brush shadows work |
|
if ( pModel->type != mod_studio ) |
|
return NULL; |
|
|
|
Assert( modelloader->IsLoaded( pModel ) && ( pModel->type == mod_studio ) ); |
|
|
|
info.m_pStudioHdr = g_pMDLCache->GetStudioHdr( pModel->studio ); |
|
info.m_pColorMeshes = NULL; |
|
|
|
// quick exit |
|
if (info.m_pStudioHdr->numbodyparts == 0) |
|
return NULL; |
|
|
|
Assert ( pRenderable ); |
|
info.m_pHardwareData = g_pMDLCache->GetHardwareData( pModel->studio ); |
|
if ( !info.m_pHardwareData ) |
|
return NULL; |
|
|
|
info.m_Decals = STUDIORENDER_DECAL_INVALID; |
|
info.m_Skin = skin; |
|
info.m_Body = body; |
|
info.m_pClientEntity = (void*)pRenderable; |
|
info.m_HitboxSet = 0; |
|
|
|
info.m_Lod = r_shadowlod.GetInt(); |
|
// If the .mdl has a shadowlod, force the use of that one instead |
|
if ( info.m_pStudioHdr->flags & STUDIOHDR_FLAGS_HASSHADOWLOD ) |
|
{ |
|
info.m_Lod = info.m_pHardwareData->m_NumLODs-1; |
|
} |
|
else if ( info.m_Lod == USESHADOWLOD ) |
|
{ |
|
int lastlod = info.m_pHardwareData->m_NumLODs - 1; |
|
info.m_Lod = lastlod; |
|
} |
|
else if ( info.m_Lod < 0 ) |
|
{ |
|
CMatRenderContextPtr pRenderContext( materials ); |
|
// Compute the shadow LOD... |
|
float factor = r_shadowlodbias.GetFloat() > 0.0f ? 1.0f / r_shadowlodbias.GetFloat() : 1.0f; |
|
float screenSize = factor * pRenderContext->ComputePixelWidthOfSphere( pRenderable->GetRenderOrigin(), 0.5f ); |
|
info.m_Lod = g_pStudioRender->ComputeModelLod( info.m_pHardwareData, screenSize ); |
|
info.m_Lod = info.m_pHardwareData->m_NumLODs-2; |
|
if ( info.m_Lod < 0 ) |
|
{ |
|
info.m_Lod = 0; |
|
} |
|
} |
|
|
|
// clamp to root lod |
|
if (info.m_Lod < info.m_pHardwareData->m_RootLOD) |
|
{ |
|
info.m_Lod = info.m_pHardwareData->m_RootLOD; |
|
} |
|
|
|
matrix3x4_t *pBoneToWorld = pCustomBoneToWorld; |
|
if ( !pBoneToWorld ) |
|
{ |
|
pBoneToWorld = g_pStudioRender->LockBoneMatrices( info.m_pStudioHdr->numbones ); |
|
} |
|
const bool bOk = pRenderable->SetupBones( pBoneToWorld, info.m_pStudioHdr->numbones, BONE_USED_BY_VERTEX_AT_LOD(info.m_Lod), cl.GetTime() ); |
|
g_pStudioRender->UnlockBoneMatrices(); |
|
if ( !bOk ) |
|
return NULL; |
|
return pBoneToWorld; |
|
#else |
|
return NULL; |
|
#endif |
|
} |
|
|
|
void CModelRender::DrawModelShadow( IClientRenderable *pRenderable, const DrawModelInfo_t &info, matrix3x4_t *pBoneToWorld ) |
|
{ |
|
#ifndef SWDS |
|
// Needed because we don't call SetupWeights |
|
g_pStudioRender->SetEyeViewTarget( info.m_pStudioHdr, info.m_Body, vec3_origin ); |
|
|
|
// Color + alpha modulation |
|
Vector white( 1, 1, 1 ); |
|
g_pStudioRender->SetColorModulation( white.Base() ); |
|
g_pStudioRender->SetAlphaModulation( 1.0f ); |
|
|
|
if ((info.m_pStudioHdr->flags & STUDIOHDR_FLAGS_USE_SHADOWLOD_MATERIALS) == 0) |
|
{ |
|
g_pStudioRender->ForcedMaterialOverride( g_pMaterialShadowBuild, OVERRIDE_BUILD_SHADOWS ); |
|
} |
|
|
|
g_pStudioRender->DrawModel( NULL, info, pBoneToWorld, NULL, NULL, pRenderable->GetRenderOrigin(), |
|
STUDIORENDER_DRAW_NO_SHADOWS | STUDIORENDER_DRAW_ENTIRE_MODEL | STUDIORENDER_DRAW_NO_FLEXES ); |
|
g_pStudioRender->ForcedMaterialOverride( 0 ); |
|
#endif |
|
} |
|
|
|
void CModelRender::SetViewTarget( const CStudioHdr *pStudioHdr, int nBodyIndex, const Vector& target ) |
|
{ |
|
g_pStudioRender->SetEyeViewTarget( pStudioHdr->GetRenderHdr(), nBodyIndex, target ); |
|
} |
|
|
|
void CModelRender::InitColormeshParams( ModelInstance_t &instance, studiohwdata_t *pStudioHWData, colormeshparams_t *pColorMeshParams ) |
|
{ |
|
pColorMeshParams->m_nMeshes = 0; |
|
pColorMeshParams->m_nTotalVertexes = 0; |
|
pColorMeshParams->m_pPooledVBAllocator = NULL; |
|
|
|
if ( ( instance.m_nFlags & MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR ) && |
|
g_pMaterialSystemHardwareConfig->SupportsStreamOffset() && |
|
( r_proplightingpooling.GetInt() == 1 ) ) |
|
{ |
|
// Color meshes can be allocated in a shared pool for static props |
|
// (saves memory on X360 due to 4-KB VB alignment) |
|
pColorMeshParams->m_pPooledVBAllocator = (IPooledVBAllocator *)&m_colorMeshVBAllocator; |
|
} |
|
|
|
for ( int lodID = pStudioHWData->m_RootLOD; lodID < pStudioHWData->m_NumLODs; lodID++ ) |
|
{ |
|
studioloddata_t *pLOD = &pStudioHWData->m_pLODs[lodID]; |
|
for ( int meshID = 0; meshID < pStudioHWData->m_NumStudioMeshes; meshID++ ) |
|
{ |
|
studiomeshdata_t *pMesh = &pLOD->m_pMeshData[meshID]; |
|
for ( int groupID = 0; groupID < pMesh->m_NumGroup; groupID++ ) |
|
{ |
|
pColorMeshParams->m_nVertexes[pColorMeshParams->m_nMeshes++] = pMesh->m_pMeshGroup[groupID].m_NumVertices; |
|
Assert( pColorMeshParams->m_nMeshes <= ARRAYSIZE( pColorMeshParams->m_nVertexes ) ); |
|
|
|
pColorMeshParams->m_nTotalVertexes += pMesh->m_pMeshGroup[groupID].m_NumVertices; |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Allocates the static prop color data meshes |
|
//----------------------------------------------------------------------------- |
|
// FIXME? : Move this to StudioRender? |
|
CColorMeshData *CModelRender::FindOrCreateStaticPropColorData( ModelInstanceHandle_t handle ) |
|
{ |
|
if ( handle == MODEL_INSTANCE_INVALID || !g_pMaterialSystemHardwareConfig->SupportsColorOnSecondStream() ) |
|
{ |
|
// the card can't support it |
|
return NULL; |
|
} |
|
|
|
ModelInstance_t& instance = m_ModelInstances[handle]; |
|
CColorMeshData *pColorMeshData = CacheGet( instance.m_ColorMeshHandle ); |
|
if ( pColorMeshData ) |
|
{ |
|
// found in cache |
|
return pColorMeshData; |
|
} |
|
|
|
if ( !instance.m_pModel ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
Assert( modelloader->IsLoaded( instance.m_pModel ) && ( instance.m_pModel->type == mod_studio ) ); |
|
studiohwdata_t *pStudioHWData = g_pMDLCache->GetHardwareData( instance.m_pModel->studio ); |
|
Assert( pStudioHWData ); |
|
if ( !pStudioHWData ) |
|
return NULL; |
|
|
|
colormeshparams_t params; |
|
InitColormeshParams( instance, pStudioHWData, ¶ms ); |
|
if ( params.m_nMeshes <= 0 ) |
|
{ |
|
// nothing to create |
|
return NULL; |
|
} |
|
|
|
// create the meshes |
|
params.m_fnHandle = instance.m_pModel->fnHandle; |
|
instance.m_ColorMeshHandle = CacheCreate( params ); |
|
ProtectColorDataIfQueued( instance.m_ColorMeshHandle ); |
|
pColorMeshData = CacheGet( instance.m_ColorMeshHandle ); |
|
|
|
return pColorMeshData; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Allocates the static prop color data meshes |
|
//----------------------------------------------------------------------------- |
|
// FIXME? : Move this to StudioRender? |
|
void CModelRender::ProtectColorDataIfQueued( DataCacheHandle_t hColorMesh ) |
|
{ |
|
if ( hColorMesh != DC_INVALID_HANDLE) |
|
{ |
|
CMatRenderContextPtr pRenderContext( materials ); |
|
ICallQueue *pCallQueue = pRenderContext->GetCallQueue(); |
|
if ( pCallQueue ) |
|
{ |
|
if ( CacheLock( hColorMesh ) ) // CacheCreate above will call functions that won't take place until later. If color mesh isn't used right away, it could get dumped |
|
{ |
|
pCallQueue->QueueCall( this, &CModelRender::CacheUnlock, hColorMesh ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Old-style computation of vertex lighting ( Currently In Use ) |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::ComputeModelVertexLightingOld( mstudiomodel_t *pModel, |
|
matrix3x4_t& matrix, const LightingState_t &lightingState, color24 *pLighting, |
|
bool bUseConstDirLighting, float flConstDirLightAmount ) |
|
{ |
|
Vector worldPos, worldNormal, destColor; |
|
int nNumLightDesc; |
|
LightDesc_t lightDesc[MAXLOCALLIGHTS]; |
|
LightingState_t *pLightingState; |
|
|
|
pLightingState = (LightingState_t*)&lightingState; |
|
|
|
// build the lighting descriptors |
|
R_SetNonAmbientLightingState( pLightingState->numlights, pLightingState->locallight, &nNumLightDesc, lightDesc, false ); |
|
|
|
const thinModelVertices_t *thinVertData = NULL; |
|
const mstudio_modelvertexdata_t *vertData = pModel->GetVertexData(); |
|
mstudiovertex_t *pFatVerts = NULL; |
|
if ( vertData ) |
|
{ |
|
pFatVerts = vertData->Vertex( 0 ); |
|
} |
|
else |
|
{ |
|
thinVertData = pModel->GetThinVertexData(); |
|
if ( !thinVertData ) |
|
return; |
|
} |
|
|
|
bool bHasSSE = MathLib_SSEEnabled(); |
|
|
|
// light all vertexes |
|
for ( int i = 0; i < pModel->numvertices; ++i ) |
|
{ |
|
if ( vertData ) |
|
{ |
|
#ifdef _WIN32 |
|
if (bHasSSE) |
|
{ |
|
// hint the next vertex |
|
// data is loaded with one extra vertex for read past |
|
#if !defined( _X360 ) // X360TBD |
|
_mm_prefetch( (char*)&pFatVerts[i+1], _MM_HINT_T0 ); |
|
#endif |
|
} |
|
#endif |
|
|
|
VectorTransform( pFatVerts[i].m_vecPosition, matrix, worldPos ); |
|
VectorRotate( pFatVerts[i].m_vecNormal, matrix, worldNormal ); |
|
} |
|
else |
|
{ |
|
Vector position; |
|
Vector normal; |
|
thinVertData->GetModelPosition( pModel, i, &position ); |
|
thinVertData->GetModelNormal( pModel, i, &normal ); |
|
VectorTransform( position, matrix, worldPos ); |
|
VectorRotate( normal, matrix, worldNormal ); |
|
} |
|
|
|
if ( bUseConstDirLighting ) |
|
{ |
|
g_pStudioRender->ComputeLightingConstDirectional( pLightingState->r_boxcolor, |
|
nNumLightDesc, lightDesc, worldPos, worldNormal, destColor, flConstDirLightAmount ); |
|
} |
|
else |
|
{ |
|
g_pStudioRender->ComputeLighting( pLightingState->r_boxcolor, |
|
nNumLightDesc, lightDesc, worldPos, worldNormal, destColor ); |
|
} |
|
|
|
// to gamma space |
|
destColor[0] = LinearToVertexLight( destColor[0] ); |
|
destColor[1] = LinearToVertexLight( destColor[1] ); |
|
destColor[2] = LinearToVertexLight( destColor[2] ); |
|
|
|
Assert( (destColor[0] >= 0.0f) && (destColor[0] <= 1.0f) ); |
|
Assert( (destColor[1] >= 0.0f) && (destColor[1] <= 1.0f) ); |
|
Assert( (destColor[2] >= 0.0f) && (destColor[2] <= 1.0f) ); |
|
|
|
pLighting[i].r = FastFToC(destColor[0]); |
|
pLighting[i].g = FastFToC(destColor[1]); |
|
pLighting[i].b = FastFToC(destColor[2]); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// New-style computation of vertex lighting ( Not Used Yet ) |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::ComputeModelVertexLighting( IHandleEntity *pProp, |
|
mstudiomodel_t *pModel, OptimizedModel::ModelLODHeader_t *pVtxLOD, |
|
matrix3x4_t& matrix, Vector4D *pTempMem, color24 *pLighting ) |
|
{ |
|
#ifndef SWDS |
|
if ( IsX360() ) |
|
return; |
|
|
|
int i; |
|
unsigned char *pInSolid = (unsigned char*)stackalloc( ((pModel->numvertices + 7) >> 3) * sizeof(unsigned char) ); |
|
Vector worldPos, worldNormal; |
|
|
|
const mstudio_modelvertexdata_t *vertData = pModel->GetVertexData(); |
|
Assert( vertData ); |
|
if ( !vertData ) |
|
return; |
|
|
|
for ( i = 0; i < pModel->numvertices; ++i ) |
|
{ |
|
const Vector &pos = *vertData->Position( i ); |
|
const Vector &normal = *vertData->Normal( i ); |
|
VectorTransform( pos, matrix, worldPos ); |
|
VectorRotate( normal, matrix, worldNormal ); |
|
bool bNonSolid = ComputeVertexLightingFromSphericalSamples( worldPos, worldNormal, pProp, &(pTempMem[i].AsVector3D()) ); |
|
|
|
int nByte = i >> 3; |
|
int nBit = i & 0x7; |
|
|
|
if ( bNonSolid ) |
|
{ |
|
pTempMem[i].w = 1.0f; |
|
pInSolid[ nByte ] &= ~(1 << nBit); |
|
} |
|
else |
|
{ |
|
pTempMem[i].Init( ); |
|
pInSolid[ nByte ] |= (1 << nBit); |
|
} |
|
} |
|
|
|
// Must iterate over each triangle to average out the colors for those |
|
// vertices in solid. |
|
// Iterate over all the meshes.... |
|
for (int meshID = 0; meshID < pModel->nummeshes; ++meshID) |
|
{ |
|
Assert( pModel->nummeshes == pVtxLOD->numMeshes ); |
|
mstudiomesh_t* pMesh = pModel->pMesh(meshID); |
|
OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh(meshID); |
|
|
|
// Iterate over all strip groups. |
|
for( int stripGroupID = 0; stripGroupID < pVtxMesh->numStripGroups; ++stripGroupID ) |
|
{ |
|
OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup(stripGroupID); |
|
|
|
// Iterate over all indices |
|
Assert( pStripGroup->numIndices % 3 == 0 ); |
|
for (i = 0; i < pStripGroup->numIndices; i += 3) |
|
{ |
|
unsigned short nIndex1 = *pStripGroup->pIndex( i ); |
|
unsigned short nIndex2 = *pStripGroup->pIndex( i + 1 ); |
|
unsigned short nIndex3 = *pStripGroup->pIndex( i + 2 ); |
|
|
|
int v[3]; |
|
v[0] = pStripGroup->pVertex( nIndex1 )->origMeshVertID + pMesh->vertexoffset; |
|
v[1] = pStripGroup->pVertex( nIndex2 )->origMeshVertID + pMesh->vertexoffset; |
|
v[2] = pStripGroup->pVertex( nIndex3 )->origMeshVertID + pMesh->vertexoffset; |
|
|
|
Assert( v[0] < pModel->numvertices ); |
|
Assert( v[1] < pModel->numvertices ); |
|
Assert( v[2] < pModel->numvertices ); |
|
|
|
bool bSolid[3]; |
|
bSolid[0] = ( pInSolid[ v[0] >> 3 ] & ( 1 << ( v[0] & 0x7 ) ) ) != 0; |
|
bSolid[1] = ( pInSolid[ v[1] >> 3 ] & ( 1 << ( v[1] & 0x7 ) ) ) != 0; |
|
bSolid[2] = ( pInSolid[ v[2] >> 3 ] & ( 1 << ( v[2] & 0x7 ) ) ) != 0; |
|
|
|
int nValidCount = 0; |
|
int nAverage[3]; |
|
if ( !bSolid[0] ) { nAverage[nValidCount++] = v[0]; } |
|
if ( !bSolid[1] ) { nAverage[nValidCount++] = v[1]; } |
|
if ( !bSolid[2] ) { nAverage[nValidCount++] = v[2]; } |
|
|
|
if ( nValidCount == 3 ) |
|
continue; |
|
|
|
Vector vecAverage( 0, 0, 0 ); |
|
for ( int j = 0; j < nValidCount; ++j ) |
|
{ |
|
vecAverage += pTempMem[nAverage[j]].AsVector3D(); |
|
} |
|
|
|
if (nValidCount != 0) |
|
{ |
|
vecAverage /= nValidCount; |
|
} |
|
|
|
if ( bSolid[0] ) { pTempMem[ v[0] ].AsVector3D() += vecAverage; pTempMem[ v[0] ].w += 1.0f; } |
|
if ( bSolid[1] ) { pTempMem[ v[1] ].AsVector3D() += vecAverage; pTempMem[ v[1] ].w += 1.0f; } |
|
if ( bSolid[2] ) { pTempMem[ v[2] ].AsVector3D() += vecAverage; pTempMem[ v[2] ].w += 1.0f; } |
|
} |
|
} |
|
} |
|
|
|
Vector destColor; |
|
for ( i = 0; i < pModel->numvertices; ++i ) |
|
{ |
|
if ( pTempMem[i].w != 0.0f ) |
|
{ |
|
pTempMem[i] /= pTempMem[i].w; |
|
} |
|
|
|
destColor[0] = LinearToVertexLight( pTempMem[i][0] ); |
|
destColor[1] = LinearToVertexLight( pTempMem[i][1] ); |
|
destColor[2] = LinearToVertexLight( pTempMem[i][2] ); |
|
|
|
ColorClampTruncate( destColor ); |
|
|
|
pLighting[i].r = FastFToC(destColor[0]); |
|
pLighting[i].g = FastFToC(destColor[1]); |
|
pLighting[i].b = FastFToC(destColor[2]); |
|
} |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Sanity check and setup the compiled color mesh for an optimal async load |
|
// during runtime. |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::ValidateStaticPropColorData( ModelInstanceHandle_t handle ) |
|
{ |
|
if ( !r_proplightingfromdisk.GetBool() ) |
|
{ |
|
return; |
|
} |
|
|
|
ModelInstance_t *pInstance = &m_ModelInstances[handle]; |
|
IHandleEntity* pProp = pInstance->m_pRenderable->GetIClientUnknown(); |
|
|
|
if ( !g_pMaterialSystemHardwareConfig->SupportsColorOnSecondStream() || !StaticPropMgr()->IsStaticProp( pProp ) ) |
|
{ |
|
// can't support it or not a static prop |
|
return; |
|
} |
|
|
|
if ( !g_bLoadedMapHasBakedPropLighting || StaticPropMgr()->PropHasBakedLightingDisabled( pProp ) ) |
|
{ |
|
return; |
|
} |
|
|
|
MEM_ALLOC_CREDIT(); |
|
|
|
// fetch the header |
|
CUtlBuffer utlBuf; |
|
char fileName[MAX_PATH]; |
|
if ( !g_pMaterialSystemHardwareConfig->GetHDREnabled() || g_bBakedPropLightingNoSeparateHDR ) |
|
{ |
|
Q_snprintf( fileName, sizeof( fileName ), "sp_%d%s.vhv", StaticPropMgr()->GetStaticPropIndex( pProp ), GetPlatformExt() ); |
|
} |
|
else |
|
{ |
|
Q_snprintf( fileName, sizeof( fileName ), "sp_hdr_%d%s.vhv", StaticPropMgr()->GetStaticPropIndex( pProp ), GetPlatformExt() ); |
|
} |
|
|
|
if ( IsX360() ) |
|
{ |
|
DataCacheHandle_t hColorMesh = GetCachedStaticPropColorData( fileName ); |
|
if ( hColorMesh != DC_INVALID_HANDLE ) |
|
{ |
|
// already have it |
|
pInstance->m_ColorMeshHandle = hColorMesh; |
|
pInstance->m_nFlags &= ~MODEL_INSTANCE_DISKCOMPILED_COLOR_BAD; |
|
pInstance->m_nFlags |= MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR; |
|
return; |
|
} |
|
} |
|
|
|
if ( !g_pFileSystem->ReadFile( fileName, "GAME", utlBuf, sizeof( HardwareVerts::FileHeader_t ), 0 ) ) |
|
{ |
|
// not available |
|
return; |
|
} |
|
|
|
studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( pInstance->m_pModel->studio ); |
|
|
|
HardwareVerts::FileHeader_t *pVhvHdr = (HardwareVerts::FileHeader_t *)utlBuf.Base(); |
|
if ( pVhvHdr->m_nVersion != VHV_VERSION || |
|
pVhvHdr->m_nChecksum != (unsigned int)pStudioHdr->checksum || |
|
pVhvHdr->m_nVertexSize != 4 ) |
|
{ |
|
// out of sync |
|
// mark for debug visualization |
|
pInstance->m_nFlags |= MODEL_INSTANCE_DISKCOMPILED_COLOR_BAD; |
|
return; |
|
} |
|
|
|
// async callback can safely stream data into targets |
|
pInstance->m_nFlags &= ~MODEL_INSTANCE_DISKCOMPILED_COLOR_BAD; |
|
pInstance->m_nFlags |= MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Async loader callback |
|
// Called from async i/o thread - must spend minimal cycles in this context |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::StaticPropColorMeshCallback( void *pContext, const void *pData, int numReadBytes, FSAsyncStatus_t asyncStatus ) |
|
{ |
|
// get our preserved data |
|
Assert( pContext ); |
|
staticPropAsyncContext_t *pStaticPropContext = (staticPropAsyncContext_t *)pContext; |
|
|
|
HardwareVerts::FileHeader_t *pVhvHdr; |
|
byte *pOriginalData = NULL; |
|
int numLightingComponents = 1; |
|
|
|
if ( asyncStatus != FSASYNC_OK ) |
|
{ |
|
// any i/o error |
|
goto cleanUp; |
|
} |
|
|
|
if ( IsX360() ) |
|
{ |
|
// only the 360 has compressed VHV data |
|
// the compressed data is after the header |
|
byte *pCompressedData = (byte *)pData + sizeof( HardwareVerts::FileHeader_t ); |
|
if ( CLZMA::IsCompressed( pCompressedData ) ) |
|
{ |
|
// create a buffer that matches the original |
|
int actualSize = CLZMA::GetActualSize( pCompressedData ); |
|
pOriginalData = (byte *)malloc( sizeof( HardwareVerts::FileHeader_t ) + actualSize ); |
|
|
|
// place the header, then uncompress directly after it |
|
V_memcpy( pOriginalData, pData, sizeof( HardwareVerts::FileHeader_t ) ); |
|
int outputLength = CLZMA::Uncompress( pCompressedData, pOriginalData + sizeof( HardwareVerts::FileHeader_t ) ); |
|
if ( outputLength != actualSize ) |
|
{ |
|
goto cleanUp; |
|
} |
|
pData = pOriginalData; |
|
} |
|
} |
|
|
|
pVhvHdr = (HardwareVerts::FileHeader_t *)pData; |
|
|
|
int startMesh; |
|
for ( startMesh=0; startMesh<pVhvHdr->m_nMeshes; startMesh++ ) |
|
{ |
|
// skip past higher detail lod meshes that must be ignored |
|
// find first mesh that matches desired lod |
|
if ( pVhvHdr->pMesh( startMesh )->m_nLod == pStaticPropContext->m_nRootLOD ) |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
int meshID; |
|
for ( meshID = startMesh; meshID<pVhvHdr->m_nMeshes; meshID++ ) |
|
{ |
|
int numVertexes = pVhvHdr->pMesh( meshID )->m_nVertexes; |
|
if ( numVertexes != pStaticPropContext->m_pColorMeshData->m_pMeshInfos[meshID-startMesh].m_nNumVerts ) |
|
{ |
|
// meshes are out of sync, discard data |
|
break; |
|
} |
|
|
|
int nID = meshID-startMesh; |
|
|
|
unsigned char *pIn = (unsigned char *) pVhvHdr->pVertexBase( meshID ); |
|
unsigned char *pOut = NULL; |
|
|
|
CMeshBuilder meshBuilder; |
|
meshBuilder.Begin( pStaticPropContext->m_pColorMeshData->m_pMeshInfos[ nID ].m_pMesh, MATERIAL_HETEROGENOUS, numVertexes, 0 ); |
|
if ( numLightingComponents > 1 ) |
|
{ |
|
pOut = reinterpret_cast< unsigned char * >( const_cast< float * >( meshBuilder.Normal() ) ); |
|
} |
|
else |
|
{ |
|
pOut = meshBuilder.Specular(); |
|
} |
|
|
|
#ifdef DX_TO_GL_ABSTRACTION |
|
// OPENGL_SWAP_COLORS |
|
for ( int i=0; i < (numVertexes * numLightingComponents ); i++ ) |
|
{ |
|
unsigned char red = *pIn++; |
|
unsigned char green = *pIn++; |
|
unsigned char blue = *pIn++; |
|
*pOut++ = blue; |
|
*pOut++ = green; |
|
*pOut++ = red; |
|
*pOut++ = *pIn++; // Alpha goes straight across |
|
} |
|
#else |
|
V_memcpy( pOut, pIn, numVertexes * 4 * numLightingComponents ); |
|
#endif |
|
meshBuilder.End(); |
|
} |
|
cleanUp: |
|
if ( IsX360() ) |
|
{ |
|
AUTO_LOCK( m_CachedStaticPropMutex ); |
|
// track the color mesh's datacache handle so that we can find it long after the model instance's are gone |
|
// the static prop filenames are guaranteed uniquely decorated |
|
m_CachedStaticPropColorData.Insert( pStaticPropContext->m_szFilenameVertex, pStaticPropContext->m_ColorMeshHandle ); |
|
|
|
// No support for lightmap textures on X360. |
|
} |
|
|
|
// mark as completed in single atomic operation |
|
pStaticPropContext->m_pColorMeshData->m_bColorMeshValid = true; |
|
CacheUnlock( pStaticPropContext->m_ColorMeshHandle ); |
|
delete pStaticPropContext; |
|
|
|
if ( pOriginalData ) |
|
{ |
|
free( pOriginalData ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Async loader callback |
|
// Called from async i/o thread - must spend minimal cycles in this context |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::StaticPropColorTexelCallback(void *pContext, const void *pData, int numReadBytes, FSAsyncStatus_t asyncStatus) |
|
{ |
|
// get our preserved data |
|
Assert(pContext); |
|
staticPropAsyncContext_t *pStaticPropContext = (staticPropAsyncContext_t *)pContext; |
|
|
|
HardwareTexels::FileHeader_t *pVhtHdr; |
|
|
|
// This needs to be above the goto or clang complains "goto into protected scope." |
|
bool anyTextures = false; |
|
|
|
if (asyncStatus != FSASYNC_OK) |
|
{ |
|
// any i/o error |
|
goto cleanUp; |
|
} |
|
|
|
pVhtHdr = (HardwareTexels::FileHeader_t *)pData; |
|
|
|
int startMesh; |
|
for (startMesh = 0; startMesh < pVhtHdr->m_nMeshes; startMesh++) |
|
{ |
|
// skip past higher detail lod meshes that must be ignored |
|
// find first mesh that matches desired lod |
|
if (pVhtHdr->pMesh(startMesh)->m_nLod == pStaticPropContext->m_nRootLOD) |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
|
|
|
|
int meshID; |
|
for ( meshID = startMesh; meshID < pVhtHdr->m_nMeshes; meshID++ ) |
|
{ |
|
const HardwareTexels::MeshHeader_t* pMeshData = pVhtHdr->pMesh( meshID ); |
|
|
|
// We can't create the real texture here because that's just how the material system works. |
|
// So instead, squirrel away what we need for later. |
|
ColorTexelsInfo_t* newCTI = new ColorTexelsInfo_t; |
|
newCTI->m_nWidth = pMeshData->m_nWidth; |
|
newCTI->m_nHeight = pMeshData->m_nHeight; |
|
newCTI->m_nMipmapCount = ImageLoader::GetNumMipMapLevels( newCTI->m_nWidth, newCTI->m_nHeight ); |
|
newCTI->m_ImageFormat = ( ImageFormat ) pVhtHdr->m_nTexelFormat; |
|
newCTI->m_nByteCount = pVhtHdr->pMesh( meshID )->m_nBytes; |
|
newCTI->m_pTexelData = new byte[ newCTI->m_nByteCount ]; |
|
Q_memcpy( newCTI->m_pTexelData, pVhtHdr->pTexelBase( meshID ), newCTI->m_nByteCount ); |
|
|
|
pStaticPropContext->m_pColorMeshData->m_pMeshInfos[ meshID - startMesh ].m_pLightmapData = newCTI; |
|
Assert( pStaticPropContext->m_pColorMeshData->m_pMeshInfos[ meshID - startMesh ].m_pLightmap == NULL ); |
|
anyTextures = true; |
|
} |
|
|
|
// This only gets set if we actually have texel data. Otherwise, it remains false. |
|
pStaticPropContext->m_pColorMeshData->m_bColorTextureValid = anyTextures; |
|
|
|
cleanUp: |
|
// mark as completed in single atomic operation |
|
CacheUnlock( pStaticPropContext->m_ColorMeshHandle ); |
|
delete pStaticPropContext; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Async loader callback |
|
// Called from async i/o thread - must spend minimal cycles in this context |
|
//----------------------------------------------------------------------------- |
|
static void StaticPropColorMeshCallback( const FileAsyncRequest_t &request, int numReadBytes, FSAsyncStatus_t asyncStatus ) |
|
{ |
|
s_ModelRender.StaticPropColorMeshCallback( request.pContext, request.pData, numReadBytes, asyncStatus ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Async loader callback |
|
// Called from async i/o thread - must spend minimal cycles in this context |
|
//----------------------------------------------------------------------------- |
|
static void StaticPropColorTexelCallback( const FileAsyncRequest_t &request, int numReadBytes, FSAsyncStatus_t asyncStatus ) |
|
{ |
|
s_ModelRender.StaticPropColorTexelCallback( request.pContext, request.pData, numReadBytes, asyncStatus ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Queued loader callback |
|
// Called from async i/o thread - must spend minimal cycles in this context |
|
//----------------------------------------------------------------------------- |
|
static void QueuedLoaderCallback_PropLighting( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError ) |
|
{ |
|
// translate error |
|
FSAsyncStatus_t asyncStatus = ( loaderError == LOADERERROR_NONE ? FSASYNC_OK : FSASYNC_ERR_READING ); |
|
|
|
// mimic async i/o completion |
|
s_ModelRender.StaticPropColorMeshCallback( pContext, pData, nSize, asyncStatus ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Loads the serialized static prop color data. |
|
// Returns false if legacy path should be used. |
|
//----------------------------------------------------------------------------- |
|
bool CModelRender::LoadStaticPropColorData( IHandleEntity *pProp, DataCacheHandle_t colorMeshHandle, studiohwdata_t *pStudioHWData ) |
|
{ |
|
if ( !g_bLoadedMapHasBakedPropLighting || !r_proplightingfromdisk.GetBool() ) |
|
{ |
|
return false; |
|
} |
|
|
|
// lock the mesh memory during async transfer |
|
// the color meshes should already have low quality data to be used during rendering |
|
CColorMeshData *pColorMeshData = CacheLock( colorMeshHandle ); |
|
if ( !pColorMeshData ) |
|
{ |
|
return false; |
|
} |
|
|
|
if ( pColorMeshData->m_hAsyncControlVertex || pColorMeshData->m_hAsyncControlTexel ) |
|
{ |
|
// load in progress, ignore additional request |
|
// or already loaded, ignore until discarded from cache |
|
CacheUnlock( colorMeshHandle ); |
|
return true; |
|
} |
|
|
|
// each static prop has its own compiled color mesh |
|
char fileName[MAX_PATH]; |
|
if ( !g_pMaterialSystemHardwareConfig->GetHDREnabled() || g_bBakedPropLightingNoSeparateHDR ) |
|
{ |
|
Q_snprintf( fileName, sizeof( fileName ), "sp_%d%s.vhv", StaticPropMgr()->GetStaticPropIndex( pProp ), GetPlatformExt() ); |
|
} |
|
else |
|
{ |
|
Q_snprintf( fileName, sizeof( fileName ), "sp_hdr_%d%s.vhv", StaticPropMgr()->GetStaticPropIndex( pProp ), GetPlatformExt() ); |
|
} |
|
|
|
// mark as invalid, async callback will set upon completion |
|
// prevents rendering during async transfer into locked mesh, otherwise d3drip |
|
pColorMeshData->m_bColorMeshValid = false; |
|
pColorMeshData->m_bColorTextureValid = false; |
|
pColorMeshData->m_bColorTextureCreated = false; |
|
|
|
|
|
// async load high quality lighting from file |
|
// can't optimal async yet, because need flat ppColorMesh[], so use callback to distribute |
|
// create our private context of data for the callback |
|
staticPropAsyncContext_t *pContextVertex = new staticPropAsyncContext_t; |
|
pContextVertex->m_nRootLOD = pStudioHWData->m_RootLOD; |
|
pContextVertex->m_nMeshes = pColorMeshData->m_nMeshes; |
|
pContextVertex->m_ColorMeshHandle = colorMeshHandle; |
|
pContextVertex->m_pColorMeshData = pColorMeshData; |
|
V_strncpy( pContextVertex->m_szFilenameVertex, fileName, sizeof( pContextVertex->m_szFilenameVertex ) ); |
|
|
|
if ( IsX360() && g_pQueuedLoader->IsMapLoading() ) |
|
{ |
|
if ( !g_pQueuedLoader->ClaimAnonymousJob( fileName, QueuedLoaderCallback_PropLighting, (void *)pContextVertex ) ) |
|
{ |
|
// not there as expected |
|
// as a less optimal fallback during loading, issue as a standard queued loader job |
|
LoaderJob_t loaderJob; |
|
loaderJob.m_pFilename = fileName; |
|
loaderJob.m_pPathID = "GAME"; |
|
loaderJob.m_pCallback = QueuedLoaderCallback_PropLighting; |
|
loaderJob.m_pContext = (void *)pContextVertex; |
|
loaderJob.m_Priority = LOADERPRIORITY_BEFOREPLAY; |
|
g_pQueuedLoader->AddJob( &loaderJob ); |
|
} |
|
return true; |
|
} |
|
|
|
// async load the file |
|
FileAsyncRequest_t fileRequest; |
|
fileRequest.pContext = (void *)pContextVertex; |
|
fileRequest.pfnCallback = ::StaticPropColorMeshCallback; |
|
fileRequest.pData = NULL; |
|
fileRequest.pszFilename = fileName; |
|
fileRequest.nOffset = 0; |
|
fileRequest.flags = 0; // FSASYNC_FLAGS_SYNC; |
|
fileRequest.nBytes = 0; |
|
fileRequest.priority = -1; |
|
fileRequest.pszPathID = "GAME"; |
|
|
|
// This must be done before sending pContextVertex down |
|
staticPropAsyncContext_t* pContextTexel = new staticPropAsyncContext_t( *pContextVertex ); |
|
|
|
// queue vertex data for async load |
|
{ |
|
MEM_ALLOC_CREDIT(); |
|
g_pFileSystem->AsyncRead( fileRequest, &pColorMeshData->m_hAsyncControlVertex ); |
|
} |
|
|
|
Q_snprintf( fileName, sizeof( fileName ), "texelslighting_%d.ppl", StaticPropMgr()->GetStaticPropIndex( pProp ) ); |
|
V_strncpy( pContextTexel->m_szFilenameTexel, fileName, sizeof( pContextTexel->m_szFilenameTexel ) ); |
|
|
|
|
|
// We are already locked, but we will unlock twice--so lock once more for the texel processing. |
|
CacheLock( colorMeshHandle ); |
|
|
|
// queue texel data for async load |
|
fileRequest.pContext = pContextTexel; |
|
fileRequest.pfnCallback = ::StaticPropColorTexelCallback; |
|
fileRequest.pData = NULL; |
|
fileRequest.pszFilename = fileName; // This doesn't need to happen, but included for clarity. |
|
|
|
{ |
|
MEM_ALLOC_CREDIT(); |
|
g_pFileSystem->AsyncRead( fileRequest, &pColorMeshData->m_hAsyncControlTexel ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the static prop color data. |
|
// Data calculation may be delayed if data is disk based. |
|
// Returns FALSE if data not available or error. For retry polling pattern. |
|
// Resturns TRUE if operation succesful or in progress (succeeds later). |
|
//----------------------------------------------------------------------------- |
|
bool CModelRender::UpdateStaticPropColorData( IHandleEntity *pProp, ModelInstanceHandle_t handle ) |
|
{ |
|
MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); |
|
|
|
#ifndef SWDS |
|
// find or allocate color meshes |
|
CColorMeshData *pColorMeshData = FindOrCreateStaticPropColorData( handle ); |
|
if ( !pColorMeshData ) |
|
{ |
|
return false; |
|
} |
|
|
|
// HACK: on PC, VB creation can fail due to device loss |
|
if ( IsPC() && pColorMeshData->m_bHasInvalidVB ) |
|
{ |
|
// Don't retry until color data is flushed by device restore |
|
pColorMeshData->m_bColorMeshValid = false; |
|
pColorMeshData->m_bNeedsRetry = false; |
|
return false; |
|
} |
|
|
|
unsigned char debugColor[3] = {0}; |
|
bool bDebugColor = false; |
|
if ( r_debugrandomstaticlighting.GetBool() ) |
|
{ |
|
// randomize with bright colors, skip black and white |
|
// purposely not deterministic to catch bugs with excessive re-baking (i.e. disco) |
|
Vector fRandomColor; |
|
int nColor = RandomInt(1,6); |
|
fRandomColor.x = (nColor>>2) & 1; |
|
fRandomColor.y = (nColor>>1) & 1; |
|
fRandomColor.z = nColor & 1; |
|
VectorNormalize( fRandomColor ); |
|
debugColor[0] = fRandomColor[0] * 255.0f; |
|
debugColor[1] = fRandomColor[1] * 255.0f; |
|
debugColor[2] = fRandomColor[2] * 255.0f; |
|
bDebugColor = true; |
|
} |
|
|
|
// FIXME? : Move this to StudioRender? |
|
ModelInstance_t &inst = m_ModelInstances[handle]; |
|
Assert( inst.m_pModel ); |
|
Assert( modelloader->IsLoaded( inst.m_pModel ) && ( inst.m_pModel->type == mod_studio ) ); |
|
|
|
if ( r_proplightingfromdisk.GetInt() == 2 ) |
|
{ |
|
// This visualization debug mode is strictly to debug which static prop models have valid disk |
|
// based lighting. There should be no red models, only green or yellow. Yellow models denote the legacy |
|
// lower quality runtime baked lighting. |
|
if ( inst.m_nFlags & MODEL_INSTANCE_DISKCOMPILED_COLOR_BAD ) |
|
{ |
|
// prop was compiled for static prop lighting, but out of sync |
|
// bad disk data for model, show as red |
|
debugColor[0] = 255.0f; |
|
debugColor[1] = 0; |
|
debugColor[2] = 0; |
|
} |
|
else if ( inst.m_nFlags & MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR ) |
|
{ |
|
// valid disk data, show as green |
|
debugColor[0] = 0; |
|
debugColor[1] = 255.0f; |
|
debugColor[2] = 0; |
|
} |
|
else |
|
{ |
|
// no disk based data, using runtime method, show as yellow |
|
// identifies a prop that wasn't compiled for static prop lighting |
|
debugColor[0] = 255.0f; |
|
debugColor[1] = 255.0f; |
|
debugColor[2] = 0; |
|
} |
|
bDebugColor = true; |
|
} |
|
|
|
studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( inst.m_pModel->studio ); |
|
studiohwdata_t *pStudioHWData = g_pMDLCache->GetHardwareData( inst.m_pModel->studio ); |
|
Assert( pStudioHdr && pStudioHWData ); |
|
|
|
if ( !bDebugColor && ( inst.m_nFlags & MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR ) ) |
|
{ |
|
// start an async load on available higher quality disc based data |
|
if ( LoadStaticPropColorData( pProp, inst.m_ColorMeshHandle, pStudioHWData ) ) |
|
{ |
|
// async in progress, operation expected to succeed |
|
// async callback handles finalization |
|
return true; |
|
} |
|
} |
|
|
|
// lighting calculation path |
|
// calculation may abort due to lack of async requested data, caller should retry |
|
pColorMeshData->m_bColorMeshValid = false; |
|
pColorMeshData->m_bColorTextureValid = false; |
|
pColorMeshData->m_bColorTextureCreated = false; |
|
pColorMeshData->m_bNeedsRetry = true; |
|
|
|
if ( !bDebugColor ) |
|
{ |
|
// vertexes must be available for lighting calculation |
|
vertexFileHeader_t *pVertexHdr = g_pMDLCache->GetVertexData( VoidPtrToMDLHandle( pStudioHdr->VirtualModel() ) ); |
|
if ( !pVertexHdr ) |
|
{ |
|
// data not available yet |
|
return false; |
|
} |
|
} |
|
|
|
inst.m_nFlags |= MODEL_INSTANCE_HAS_COLOR_DATA; |
|
|
|
// calculate lighting, set for access to verts |
|
m_pStudioHdr = pStudioHdr; |
|
|
|
// Sets the model transform state in g_pStudioRender |
|
matrix3x4_t matrix; |
|
AngleMatrix( inst.m_pRenderable->GetRenderAngles(), inst.m_pRenderable->GetRenderOrigin(), matrix ); |
|
|
|
// Get static lighting only!! We'll add dynamic and lightstyles in in the vertex shader. . . |
|
unsigned int lightCacheFlags = LIGHTCACHEFLAGS_STATIC; |
|
if ( !g_pMaterialSystemHardwareConfig->SupportsStaticPlusDynamicLighting() ) |
|
{ |
|
// . . . unless we can't do anything but static or dynamic simulaneously. . then |
|
// we'll bake the lightstyle info here. |
|
lightCacheFlags |= LIGHTCACHEFLAGS_LIGHTSTYLE; |
|
} |
|
|
|
LightingState_t lightingState; |
|
if ( (inst.m_nFlags & MODEL_INSTANCE_HAS_STATIC_LIGHTING) && inst.m_LightCacheHandle ) |
|
{ |
|
lightingState = *(LightcacheGetStatic( inst.m_LightCacheHandle, NULL, lightCacheFlags )); |
|
} |
|
else |
|
{ |
|
// Choose the lighting origin |
|
Vector entOrigin; |
|
R_ComputeLightingOrigin( inst.m_pRenderable, pStudioHdr, matrix, entOrigin ); |
|
LightcacheGetDynamic_Stats stats; |
|
LightcacheGetDynamic( entOrigin, lightingState, stats, lightCacheFlags ); |
|
} |
|
|
|
// See if the studiohdr wants to use constant directional light, ie |
|
// the surface normal plays no part in determining light intensity |
|
bool bUseConstDirLighting = false; |
|
float flConstDirLightingAmount = 0.0; |
|
if ( pStudioHdr->flags & STUDIOHDR_FLAGS_CONSTANT_DIRECTIONAL_LIGHT_DOT ) |
|
{ |
|
bUseConstDirLighting = true; |
|
flConstDirLightingAmount = (float)( pStudioHdr->constdirectionallightdot ) / 255.0; |
|
} |
|
|
|
CUtlMemory< color24 > tmpLightingMem; |
|
|
|
// Iterate over every body part... |
|
for ( int bodyPartID = 0; bodyPartID < pStudioHdr->numbodyparts; ++bodyPartID ) |
|
{ |
|
mstudiobodyparts_t* pBodyPart = pStudioHdr->pBodypart( bodyPartID ); |
|
|
|
// Iterate over every submodel... |
|
for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) |
|
{ |
|
mstudiomodel_t* pModel = pBodyPart->pModel(modelID); |
|
|
|
if ( pModel->numvertices == 0 ) |
|
continue; |
|
|
|
// Make sure we've got enough space allocated |
|
tmpLightingMem.EnsureCapacity( pModel->numvertices ); |
|
|
|
if ( !bDebugColor ) |
|
{ |
|
// Compute lighting for each unique vertex in the model exactly once |
|
ComputeModelVertexLightingOld( pModel, matrix, lightingState, tmpLightingMem.Base(), bUseConstDirLighting, flConstDirLightingAmount ); |
|
} |
|
else |
|
{ |
|
for ( int i=0; i<pModel->numvertices; i++ ) |
|
{ |
|
tmpLightingMem[i].r = debugColor[0]; |
|
tmpLightingMem[i].g = debugColor[1]; |
|
tmpLightingMem[i].b = debugColor[2]; |
|
} |
|
} |
|
|
|
// distribute the lighting results to the mesh's vertexes |
|
for ( int lodID = pStudioHWData->m_RootLOD; lodID < pStudioHWData->m_NumLODs; ++lodID ) |
|
{ |
|
studioloddata_t *pStudioLODData = &pStudioHWData->m_pLODs[lodID]; |
|
studiomeshdata_t *pStudioMeshData = pStudioLODData->m_pMeshData; |
|
|
|
// Iterate over all the meshes.... |
|
for ( int meshID = 0; meshID < pModel->nummeshes; ++meshID) |
|
{ |
|
mstudiomesh_t* pMesh = pModel->pMesh( meshID ); |
|
|
|
// Iterate over all strip groups. |
|
for ( int stripGroupID = 0; stripGroupID < pStudioMeshData[pMesh->meshid].m_NumGroup; ++stripGroupID ) |
|
{ |
|
studiomeshgroup_t* pMeshGroup = &pStudioMeshData[pMesh->meshid].m_pMeshGroup[stripGroupID]; |
|
ColorMeshInfo_t* pColorMeshInfo = &pColorMeshData->m_pMeshInfos[pMeshGroup->m_ColorMeshID]; |
|
|
|
CMeshBuilder meshBuilder; |
|
meshBuilder.Begin( pColorMeshInfo->m_pMesh, MATERIAL_HETEROGENOUS, pMeshGroup->m_NumVertices, 0 ); |
|
|
|
if ( !meshBuilder.VertexSize() ) |
|
{ |
|
meshBuilder.End(); |
|
return false; // Aborting processing, since something was wrong with D3D |
|
} |
|
|
|
// We need to account for the stream offset used by pool-allocated (static-lit) color meshes: |
|
int streamOffset = pColorMeshInfo->m_nVertOffsetInBytes / meshBuilder.VertexSize(); |
|
meshBuilder.AdvanceVertices( streamOffset ); |
|
|
|
// Iterate over all vertices |
|
for ( int i = 0; i < pMeshGroup->m_NumVertices; ++i) |
|
{ |
|
int nVertIndex = pMesh->vertexoffset + pMeshGroup->m_pGroupIndexToMeshIndex[i]; |
|
Assert( nVertIndex < pModel->numvertices ); |
|
meshBuilder.Specular3ub( tmpLightingMem[nVertIndex].r, tmpLightingMem[nVertIndex].g, tmpLightingMem[nVertIndex].b ); |
|
meshBuilder.AdvanceVertex(); |
|
} |
|
|
|
meshBuilder.End(); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
pColorMeshData->m_bColorMeshValid = true; |
|
pColorMeshData->m_bNeedsRetry = false; |
|
#endif |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// FIXME? : Move this to StudioRender? |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::DestroyStaticPropColorData( ModelInstanceHandle_t handle ) |
|
{ |
|
#ifndef SWDS |
|
if ( handle == MODEL_INSTANCE_INVALID ) |
|
return; |
|
|
|
if ( m_ModelInstances[handle].m_ColorMeshHandle != DC_INVALID_HANDLE ) |
|
{ |
|
CacheRemove( m_ModelInstances[handle].m_ColorMeshHandle ); |
|
m_ModelInstances[handle].m_ColorMeshHandle = DC_INVALID_HANDLE; |
|
} |
|
#endif |
|
} |
|
|
|
|
|
void CModelRender::ReleaseAllStaticPropColorData( void ) |
|
{ |
|
FOR_EACH_LL( m_ModelInstances, i ) |
|
{ |
|
DestroyStaticPropColorData( i ); |
|
} |
|
if ( IsX360() ) |
|
{ |
|
PurgeCachedStaticPropColorData(); |
|
} |
|
} |
|
|
|
|
|
void CModelRender::RestoreAllStaticPropColorData( void ) |
|
{ |
|
#if !defined( SWDS ) |
|
if ( !host_state.worldmodel ) |
|
return; |
|
|
|
// invalidate all static lighting cache data |
|
InvalidateStaticLightingCache(); |
|
|
|
// rebake |
|
FOR_EACH_LL( m_ModelInstances, i ) |
|
{ |
|
UpdateStaticPropColorData( m_ModelInstances[i].m_pRenderable->GetIClientUnknown(), i ); |
|
} |
|
#endif |
|
} |
|
|
|
void RestoreAllStaticPropColorData( void ) |
|
{ |
|
s_ModelRender.RestoreAllStaticPropColorData(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates, destroys instance data to be associated with the model |
|
//----------------------------------------------------------------------------- |
|
ModelInstanceHandle_t CModelRender::CreateInstance( IClientRenderable *pRenderable, LightCacheHandle_t *pCache ) |
|
{ |
|
Assert( pRenderable ); |
|
|
|
// ensure all components are available |
|
model_t *pModel = (model_t*)pRenderable->GetModel(); |
|
|
|
// We're ok, allocate a new instance handle |
|
ModelInstanceHandle_t handle = m_ModelInstances.AddToTail(); |
|
ModelInstance_t& instance = m_ModelInstances[handle]; |
|
|
|
instance.m_pRenderable = pRenderable; |
|
instance.m_DecalHandle = STUDIORENDER_DECAL_INVALID; |
|
instance.m_pModel = (model_t*)pModel; |
|
instance.m_ColorMeshHandle = DC_INVALID_HANDLE; |
|
instance.m_flLightingTime = CURRENT_LIGHTING_UNINITIALIZED; |
|
instance.m_nFlags = 0; |
|
instance.m_LightCacheHandle = 0; |
|
|
|
instance.m_AmbientLightingState.ZeroLightingState(); |
|
for ( int i = 0; i < 6; ++i ) |
|
{ |
|
// To catch errors with uninitialized m_AmbientLightingState... |
|
// force to pure red |
|
instance.m_AmbientLightingState.r_boxcolor[i].x = 1.0; |
|
} |
|
|
|
#ifndef SWDS |
|
instance.m_FirstShadow = g_pShadowMgr->InvalidShadowIndex(); |
|
#endif |
|
|
|
// Static props use baked lighting for performance reasons |
|
if ( pCache ) |
|
{ |
|
SetStaticLighting( handle, pCache ); |
|
|
|
// validate static color meshes once, now at load/create time |
|
ValidateStaticPropColorData( handle ); |
|
|
|
// 360 persists the color meshes across same map loads |
|
#ifdef _X360 |
|
if ( r_decalstaticprops.GetBool() && instance.m_LightCacheHandle ) |
|
instance.m_AmbientLightingState = *(LightcacheGetStatic( *pCache, NULL, LIGHTCACHEFLAGS_STATIC )); |
|
#else |
|
if ( instance.m_ColorMeshHandle == DC_INVALID_HANDLE ) |
|
{ |
|
// builds out color meshes or loads disk colors, now at load/create time |
|
RecomputeStaticLighting( handle ); |
|
} |
|
#endif |
|
} |
|
|
|
return handle; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Assigns static lighting to the model instance |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::SetStaticLighting( ModelInstanceHandle_t handle, LightCacheHandle_t *pCache ) |
|
{ |
|
// FIXME: If we make static lighting available for client-side props, |
|
// we must clean up the lightcache handles as the model instances are removed. |
|
// At the moment, since only the static prop manager uses this, it cleans up all LightCacheHandles |
|
// at level shutdown. |
|
|
|
// The reason I moved the lightcache handles into here is because this place needs |
|
// to know about lighting overrides when restoring meshes for alt-tab reasons |
|
// It was a real pain to do this from within the static prop mgr, where the |
|
// lightcache handle used to reside |
|
if (handle != MODEL_INSTANCE_INVALID) |
|
{ |
|
ModelInstance_t& instance = m_ModelInstances[handle]; |
|
if ( pCache ) |
|
{ |
|
instance.m_LightCacheHandle = *pCache; |
|
instance.m_nFlags |= MODEL_INSTANCE_HAS_STATIC_LIGHTING; |
|
} |
|
else |
|
{ |
|
instance.m_LightCacheHandle = 0; |
|
instance.m_nFlags &= ~MODEL_INSTANCE_HAS_STATIC_LIGHTING; |
|
} |
|
} |
|
} |
|
|
|
LightCacheHandle_t CModelRender::GetStaticLighting( ModelInstanceHandle_t handle ) |
|
{ |
|
if (handle != MODEL_INSTANCE_INVALID) |
|
{ |
|
ModelInstance_t& instance = m_ModelInstances[handle]; |
|
if ( instance.m_nFlags & MODEL_INSTANCE_HAS_STATIC_LIGHTING ) |
|
return instance.m_LightCacheHandle; |
|
return 0; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// This gets called when overbright, etc gets changed to recompute static prop lighting. |
|
// Returns FALSE if needed async data not available to complete computation or an error (don't draw). |
|
// Returns TRUE if operation succeeded or computation skipped (ok to draw). |
|
// Callers use this to track state in a retry pattern, so the expensive computation |
|
// only happens once as needed or can continue to be polled until success. |
|
//----------------------------------------------------------------------------- |
|
bool CModelRender::RecomputeStaticLighting( ModelInstanceHandle_t handle ) |
|
{ |
|
#ifndef SWDS |
|
if ( handle == MODEL_INSTANCE_INVALID ) |
|
{ |
|
return false; |
|
} |
|
|
|
if ( !g_pMaterialSystemHardwareConfig->SupportsColorOnSecondStream() ) |
|
{ |
|
// static lighting not supported, but callers can proceed |
|
return true; |
|
} |
|
|
|
ModelInstance_t& instance = m_ModelInstances[handle]; |
|
Assert( modelloader->IsLoaded( instance.m_pModel ) && ( instance.m_pModel->type == mod_studio ) ); |
|
|
|
// get data, possibly delayed due to async |
|
studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( instance.m_pModel->studio ); |
|
if ( !pStudioHdr ) |
|
{ |
|
// data not available |
|
return false; |
|
} |
|
|
|
if ( pStudioHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP ) |
|
{ |
|
// get data, possibly delayed due to async |
|
studiohwdata_t *pStudioHWData = g_pMDLCache->GetHardwareData( instance.m_pModel->studio ); |
|
if ( !pStudioHWData ) |
|
{ |
|
// data not available |
|
return false; |
|
} |
|
|
|
if ( r_decalstaticprops.GetBool() && instance.m_LightCacheHandle ) |
|
{ |
|
instance.m_AmbientLightingState = *(LightcacheGetStatic( instance.m_LightCacheHandle, NULL, LIGHTCACHEFLAGS_STATIC )); |
|
} |
|
|
|
return UpdateStaticPropColorData( instance.m_pRenderable->GetIClientUnknown(), handle ); |
|
} |
|
|
|
#endif |
|
// success |
|
return true; |
|
} |
|
|
|
void CModelRender::PurgeCachedStaticPropColorData( void ) |
|
{ |
|
// valid for 360 only |
|
Assert( IsX360() ); |
|
if ( IsPC() ) |
|
{ |
|
return; |
|
} |
|
|
|
// flush all the color mesh data |
|
GetCacheSection()->Flush( true, true ); |
|
DataCacheStatus_t status; |
|
GetCacheSection()->GetStatus( &status ); |
|
if ( status.nBytes ) |
|
{ |
|
DevWarning( "CModelRender: ColorMesh %d bytes failed to flush!\n", status.nBytes ); |
|
} |
|
|
|
m_colorMeshVBAllocator.Clear(); |
|
m_CachedStaticPropColorData.Purge(); |
|
} |
|
|
|
bool CModelRender::IsStaticPropColorDataCached( const char *pName ) |
|
{ |
|
// valid for 360 only |
|
Assert( IsX360() ); |
|
if ( IsPC() ) |
|
{ |
|
return false; |
|
} |
|
|
|
DataCacheHandle_t hColorMesh = DC_INVALID_HANDLE; |
|
{ |
|
AUTO_LOCK( m_CachedStaticPropMutex ); |
|
int iIndex = m_CachedStaticPropColorData.Find( pName ); |
|
if ( m_CachedStaticPropColorData.IsValidIndex( iIndex ) ) |
|
{ |
|
hColorMesh = m_CachedStaticPropColorData[iIndex]; |
|
} |
|
} |
|
|
|
CColorMeshData *pColorMeshData = CacheGetNoTouch( hColorMesh ); |
|
if ( pColorMeshData ) |
|
{ |
|
// color mesh data is in cache |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
DataCacheHandle_t CModelRender::GetCachedStaticPropColorData( const char *pName ) |
|
{ |
|
// valid for 360 only |
|
Assert( IsX360() ); |
|
if ( IsPC() ) |
|
{ |
|
return DC_INVALID_HANDLE; |
|
} |
|
|
|
DataCacheHandle_t hColorMesh = DC_INVALID_HANDLE; |
|
{ |
|
AUTO_LOCK( m_CachedStaticPropMutex ); |
|
int iIndex = m_CachedStaticPropColorData.Find( pName ); |
|
if ( m_CachedStaticPropColorData.IsValidIndex( iIndex ) ) |
|
{ |
|
hColorMesh = m_CachedStaticPropColorData[iIndex]; |
|
} |
|
} |
|
|
|
return hColorMesh; |
|
} |
|
|
|
void CModelRender::SetupColorMeshes( int nTotalVerts ) |
|
{ |
|
Assert( IsX360() ); |
|
if ( IsPC() ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( !g_pQueuedLoader->IsMapLoading() ) |
|
{ |
|
// oops, the queued loader didn't run which does the pre-purge cleanup |
|
// do the cleanup now |
|
PurgeCachedStaticPropColorData(); |
|
} |
|
|
|
// Set up the appropriate default value for color mesh pooling |
|
if ( r_proplightingpooling.GetInt() == -1 ) |
|
{ |
|
// This is useful on X360 because VBs are 4-KB aligned, so using a shared VB saves tons of memory |
|
r_proplightingpooling.SetValue( true ); |
|
} |
|
|
|
if ( r_proplightingpooling.GetInt() == 1 ) |
|
{ |
|
if ( m_colorMeshVBAllocator.GetNumVertsAllocated() == 0 ) |
|
{ |
|
if ( nTotalVerts ) |
|
{ |
|
// Allocate a mesh (vertex buffer) big enough to accommodate all static prop color meshes |
|
// (which are allocated inside CModelRender::FindOrCreateStaticPropColorData() ): |
|
m_colorMeshVBAllocator.Init( VERTEX_SPECULAR, nTotalVerts ); |
|
} |
|
} |
|
else |
|
{ |
|
// already allocated |
|
// 360 keeps the color meshes during same map loads |
|
// vb allocator already allocated, needs to match |
|
Assert( m_colorMeshVBAllocator.GetNumVertsAllocated() == nTotalVerts ); |
|
} |
|
} |
|
} |
|
|
|
void CModelRender::DestroyInstance( ModelInstanceHandle_t handle ) |
|
{ |
|
if ( handle == MODEL_INSTANCE_INVALID ) |
|
return; |
|
|
|
g_pStudioRender->DestroyDecalList( m_ModelInstances[handle].m_DecalHandle ); |
|
#ifndef SWDS |
|
g_pShadowMgr->RemoveAllShadowsFromModel( handle ); |
|
#endif |
|
|
|
// 360 holds onto static prop disk color data only, to avoid redundant work during same map load |
|
// can only persist props with disk based lighting |
|
// check for dvd mode as a reasonable assurance that the queued loader will be responsible for a possible purge |
|
// if the queued loader doesn't run, the purge will get caught later than intended |
|
bool bPersistLighting = IsX360() && |
|
( m_ModelInstances[handle].m_nFlags & MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR ) && |
|
( g_pFullFileSystem->GetDVDMode() == DVDMODE_STRICT ); |
|
if ( !bPersistLighting ) |
|
{ |
|
DestroyStaticPropColorData( handle ); |
|
} |
|
|
|
m_ModelInstances.Remove( handle ); |
|
} |
|
|
|
bool CModelRender::ChangeInstance( ModelInstanceHandle_t handle, IClientRenderable *pRenderable ) |
|
{ |
|
if ( handle == MODEL_INSTANCE_INVALID || !pRenderable ) |
|
return false; |
|
|
|
ModelInstance_t& instance = m_ModelInstances[handle]; |
|
|
|
if ( instance.m_pModel != pRenderable->GetModel() ) |
|
{ |
|
DevMsg("MoveInstanceHandle: models are different!\n"); |
|
return false; |
|
} |
|
|
|
// ok, models are the same, change renderable pointer |
|
instance.m_pRenderable = pRenderable; |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// It's not valid if the model index changed + we have non-zero instance data |
|
//----------------------------------------------------------------------------- |
|
bool CModelRender::IsModelInstanceValid( ModelInstanceHandle_t handle ) |
|
{ |
|
if ( handle == MODEL_INSTANCE_INVALID ) |
|
return false; |
|
|
|
ModelInstance_t& inst = m_ModelInstances[handle]; |
|
if ( inst.m_DecalHandle == STUDIORENDER_DECAL_INVALID ) |
|
return false; |
|
|
|
model_t const* pModel = inst.m_pRenderable->GetModel(); |
|
return inst.m_pModel == pModel; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a decal on a model instance by doing a planar projection |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::AddDecal( ModelInstanceHandle_t handle, Ray_t const& ray, |
|
const Vector& decalUp, int decalIndex, int body, bool noPokeThru, int maxLODToDecal ) |
|
{ |
|
Color cColorTemp; |
|
AddDecalInternal( handle, ray, decalUp, decalIndex, body, false, cColorTemp, noPokeThru, maxLODToDecal ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CModelRender::AddColoredDecal( ModelInstanceHandle_t handle, Ray_t const& ray, |
|
const Vector& decalUp, int decalIndex, int body, Color cColor, bool noPokeThru, int maxLODToDecal ) |
|
{ |
|
AddDecalInternal( handle, ray, decalUp, decalIndex, body, true, cColor, noPokeThru, maxLODToDecal ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CModelRender::GetMaterialOverride( IMaterial** ppOutForcedMaterial, OverrideType_t* pOutOverrideType ) |
|
{ |
|
g_pStudioRender->GetMaterialOverride( ppOutForcedMaterial, pOutOverrideType ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CModelRender::AddDecalInternal( ModelInstanceHandle_t handle, Ray_t const& ray, |
|
const Vector& decalUp, int decalIndex, int body, bool bUseColor, Color cColor, bool noPokeThru, int maxLODToDecal) |
|
{ |
|
if (handle == MODEL_INSTANCE_INVALID) |
|
return; |
|
|
|
// Get the decal material + radius |
|
IMaterial* pDecalMaterial; |
|
float w, h; |
|
R_DecalGetMaterialAndSize( decalIndex, pDecalMaterial, w, h ); |
|
if ( !pDecalMaterial ) |
|
{ |
|
DevWarning("Bad decal index %d\n", decalIndex ); |
|
return; |
|
} |
|
w *= 0.5f; |
|
h *= 0.5f; |
|
|
|
// FIXME: For now, don't render fading decals on props... |
|
bool found = false; |
|
pDecalMaterial->FindVar( "$decalFadeDuration", &found, false ); |
|
if ( found ) |
|
return; |
|
|
|
if ( bUseColor ) |
|
{ |
|
IMaterialVar *pColor = pDecalMaterial->FindVar( "$color2", &found, false ); |
|
if ( found ) |
|
{ |
|
// expects a 0..1 value. Input is 0 to 255 |
|
pColor->SetVecValue( cColor.r() / 255.0f, cColor.g() / 255.0f, cColor.b() / 255.0f ); |
|
} |
|
} |
|
|
|
// FIXME: Pass w and h into AddDecal |
|
float radius = (w > h) ? w : h; |
|
|
|
ModelInstance_t& inst = m_ModelInstances[handle]; |
|
if (!IsModelInstanceValid(handle)) |
|
{ |
|
g_pStudioRender->DestroyDecalList(inst.m_DecalHandle); |
|
inst.m_DecalHandle = STUDIORENDER_DECAL_INVALID; |
|
} |
|
|
|
Assert( modelloader->IsLoaded( inst.m_pModel ) && ( inst.m_pModel->type == mod_studio ) ); |
|
if ( inst.m_DecalHandle == STUDIORENDER_DECAL_INVALID ) |
|
{ |
|
studiohwdata_t *pStudioHWData = g_pMDLCache->GetHardwareData( inst.m_pModel->studio ); |
|
inst.m_DecalHandle = g_pStudioRender->CreateDecalList( pStudioHWData ); |
|
} |
|
|
|
matrix3x4_t *pBoneToWorld = SetupModelState( inst.m_pRenderable ); |
|
g_pStudioRender->AddDecal( inst.m_DecalHandle, g_pMDLCache->GetStudioHdr( inst.m_pModel->studio ), |
|
pBoneToWorld, ray, decalUp, pDecalMaterial, radius, body, noPokeThru, maxLODToDecal ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes all the decals on a model instance |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::RemoveAllDecals( ModelInstanceHandle_t handle ) |
|
{ |
|
if (handle == MODEL_INSTANCE_INVALID) |
|
return; |
|
|
|
ModelInstance_t& inst = m_ModelInstances[handle]; |
|
if (!IsModelInstanceValid(handle)) |
|
return; |
|
|
|
g_pStudioRender->DestroyDecalList( inst.m_DecalHandle ); |
|
inst.m_DecalHandle = STUDIORENDER_DECAL_INVALID; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CModelRender::RemoveAllDecalsFromAllModels() |
|
{ |
|
for ( ModelInstanceHandle_t i = m_ModelInstances.Head(); |
|
i != m_ModelInstances.InvalidIndex(); |
|
i = m_ModelInstances.Next( i ) ) |
|
{ |
|
RemoveAllDecals( i ); |
|
} |
|
} |
|
|
|
const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void *pModelData ) |
|
{ |
|
// make requested data resident |
|
Assert( pModelData == NULL ); |
|
return s_ModelRender.CacheVertexData(); |
|
} |
|
|
|
bool CheckVarRange_r_rootlod() |
|
{ |
|
return CheckVarRange_Generic( &r_rootlod, 0, 2 ); |
|
} |
|
|
|
bool CheckVarRange_r_lod() |
|
{ |
|
return CheckVarRange_Generic( &r_lod, -1, 2 ); |
|
} |
|
|
|
|
|
// Convar callback to change lod |
|
//----------------------------------------------------------------------------- |
|
void r_lod_f( IConVar *var, const char *pOldValue, float flOldValue ) |
|
{ |
|
CheckVarRange_r_lod(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Convar callback to change root lod |
|
//----------------------------------------------------------------------------- |
|
void SetRootLOD_f( IConVar *pConVar, const char *pOldString, float flOldValue ) |
|
{ |
|
// Make sure the variable is in range. |
|
if ( CheckVarRange_r_rootlod() ) |
|
return; // was called recursively. |
|
|
|
ConVarRef var( pConVar ); |
|
UpdateStudioRenderConfig(); |
|
if ( !g_LostVideoMemory && Q_strcmp( var.GetString(), pOldString ) ) |
|
{ |
|
// reload only the necessary models to desired lod |
|
modelloader->Studio_ReloadModels( IModelLoader::RELOAD_LOD_CHANGED ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Discard and reload (rebuild, rebake, etc) models to the current lod |
|
//----------------------------------------------------------------------------- |
|
void FlushLOD_f() |
|
{ |
|
UpdateStudioRenderConfig(); |
|
if ( !g_LostVideoMemory ) |
|
{ |
|
// force a full discard and rebuild of all loaded models |
|
modelloader->Studio_ReloadModels( IModelLoader::RELOAD_EVERYTHING ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// CPooledVBAllocator_ColorMesh implementation |
|
// |
|
//----------------------------------------------------------------------------- |
|
|
|
//----------------------------------------------------------------------------- |
|
// CPooledVBAllocator_ColorMesh constructor |
|
//----------------------------------------------------------------------------- |
|
CPooledVBAllocator_ColorMesh::CPooledVBAllocator_ColorMesh() |
|
: m_pMesh( NULL ) |
|
{ |
|
Clear(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// CPooledVBAllocator_ColorMesh destructor |
|
// - Clear should have been called |
|
//----------------------------------------------------------------------------- |
|
CPooledVBAllocator_ColorMesh::~CPooledVBAllocator_ColorMesh() |
|
{ |
|
CheckIsClear(); |
|
|
|
// Clean up, if it hadn't been done already |
|
Clear(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Init |
|
// - Allocate the internal shared mesh (vertex buffer) |
|
//----------------------------------------------------------------------------- |
|
bool CPooledVBAllocator_ColorMesh::Init( VertexFormat_t format, int numVerts ) |
|
{ |
|
if ( !CheckIsClear() ) |
|
return false; |
|
|
|
if ( g_VBAllocTracker ) |
|
g_VBAllocTracker->TrackMeshAllocations( "CPooledVBAllocator_ColorMesh::Init" ); |
|
|
|
CMatRenderContextPtr pRenderContext( materials ); |
|
m_pMesh = pRenderContext->CreateStaticMesh( format, TEXTURE_GROUP_STATIC_VERTEX_BUFFER_COLOR ); |
|
if ( m_pMesh ) |
|
{ |
|
// Build out the underlying vertex buffer |
|
CMeshBuilder meshBuilder; |
|
int numIndices = 0; |
|
meshBuilder.Begin( m_pMesh, MATERIAL_HETEROGENOUS, numVerts, numIndices ); |
|
{ |
|
m_pVertexBufferBase = meshBuilder.Specular(); |
|
m_totalVerts = numVerts; |
|
m_vertexSize = meshBuilder.VertexSize(); |
|
// Probably good to catch any change to vertex size... there may be assumptions based on it: |
|
Assert( m_vertexSize == 4 ); |
|
// Start at the bottom of the VB and work your way up like a simple stack |
|
m_nextFreeOffset = 0; |
|
} |
|
meshBuilder.End(); |
|
} |
|
|
|
if ( g_VBAllocTracker ) |
|
g_VBAllocTracker->TrackMeshAllocations( NULL ); |
|
|
|
return ( m_pMesh != NULL ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Clear |
|
// - frees the shared mesh (vertex buffer), resets member variables |
|
//----------------------------------------------------------------------------- |
|
void CPooledVBAllocator_ColorMesh::Clear( void ) |
|
{ |
|
if ( m_pMesh != NULL ) |
|
{ |
|
if ( m_numAllocations > 0 ) |
|
{ |
|
Warning( "ERROR: CPooledVBAllocator_ColorMesh::Clear should not be called until all allocations released!" ); |
|
Assert( m_numAllocations == 0 ); |
|
} |
|
CMatRenderContextPtr pRenderContext( materials ); |
|
pRenderContext->DestroyStaticMesh( m_pMesh ); |
|
m_pMesh = NULL; |
|
} |
|
|
|
m_pVertexBufferBase = NULL; |
|
m_totalVerts = 0; |
|
m_vertexSize = 0; |
|
|
|
m_numAllocations = 0; |
|
m_numVertsAllocated = 0; |
|
m_nextFreeOffset = -1; |
|
m_bStartedDeallocation = false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// CheckIsClear |
|
// - assert/warn if the allocator isn't in a clear state |
|
// (no extant allocations, no internal mesh) |
|
//----------------------------------------------------------------------------- |
|
bool CPooledVBAllocator_ColorMesh::CheckIsClear( void ) |
|
{ |
|
if ( m_pMesh ) |
|
{ |
|
Warning( "ERROR: CPooledVBAllocator_ColorMesh's internal mesh (vertex buffer) should have been freed!" ); |
|
Assert( m_pMesh == NULL ); |
|
return false; |
|
} |
|
|
|
if ( m_numAllocations > 0 ) |
|
{ |
|
Warning( "ERROR: CPooledVBAllocator_ColorMesh has unfreed allocations!" ); |
|
Assert( m_numAllocations == 0 ); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Allocate |
|
// - Allocate a sub-range of 'numVerts' from free space in the shared vertex buffer |
|
// (returns the byte offset from the start of the VB to the new allocation) |
|
// - returns -1 on failure |
|
//----------------------------------------------------------------------------- |
|
int CPooledVBAllocator_ColorMesh::Allocate( int numVerts ) |
|
{ |
|
if ( m_pMesh == NULL ) |
|
{ |
|
Warning( "ERROR: CPooledVBAllocator_ColorMesh::Allocate cannot be called before Init (expect a crash)" ); |
|
Assert( m_pMesh ); |
|
return -1; |
|
} |
|
|
|
// Once we start deallocating, we have to keep going until everything has been freed |
|
if ( m_bStartedDeallocation ) |
|
{ |
|
Warning( "ERROR: CPooledVBAllocator_ColorMesh::Allocate being called after some (but not all) calls to Deallocate have been called - invalid! (expect visual artifacts)" ); |
|
Assert( !m_bStartedDeallocation ); |
|
return -1; |
|
} |
|
|
|
if ( numVerts > ( m_totalVerts - m_numVertsAllocated ) ) |
|
{ |
|
Warning( "ERROR: CPooledVBAllocator_ColorMesh::Allocate failing - not enough space left in the vertex buffer!" ); |
|
Assert( numVerts <= ( m_totalVerts - m_numVertsAllocated ) ); |
|
return -1; |
|
} |
|
|
|
int result = m_nextFreeOffset; |
|
|
|
m_numAllocations += 1; |
|
m_numVertsAllocated += numVerts; |
|
m_nextFreeOffset += numVerts*m_vertexSize; |
|
|
|
return result; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Deallocate |
|
// - Deallocate an existing allocation |
|
//----------------------------------------------------------------------------- |
|
void CPooledVBAllocator_ColorMesh::Deallocate( int offset, int numVerts ) |
|
{ |
|
if ( m_pMesh == NULL ) |
|
{ |
|
Warning( "ERROR: CPooledVBAllocator_ColorMesh::Deallocate cannot be called before Init" ); |
|
Assert( m_pMesh != NULL ); |
|
return; |
|
} |
|
|
|
if ( m_numAllocations == 0 ) |
|
{ |
|
Warning( "ERROR: CPooledVBAllocator_ColorMesh::Deallocate called too many times! (bug in calling code)" ); |
|
Assert( m_numAllocations > 0 ); |
|
return; |
|
} |
|
|
|
if ( numVerts > m_numVertsAllocated ) |
|
{ |
|
Warning( "ERROR: CPooledVBAllocator_ColorMesh::Deallocate called with too many verts, trying to free more than were allocated (bug in calling code)" ); |
|
Assert( numVerts <= m_numVertsAllocated ); |
|
numVerts = m_numVertsAllocated; // Hack (avoid counters ever going below zero) |
|
} |
|
|
|
// Now all extant allocations must be freed before we make any new allocations |
|
m_bStartedDeallocation = true; |
|
|
|
m_numAllocations -= 1; |
|
m_numVertsAllocated -= numVerts; |
|
m_nextFreeOffset = 0; // (we shouldn't be returning this until everything's free, at which point 0 is valid) |
|
|
|
// Are we empty? |
|
if ( m_numAllocations == 0 ) |
|
{ |
|
if ( m_numVertsAllocated != 0 ) |
|
{ |
|
Warning( "ERROR: CPooledVBAllocator_ColorMesh::Deallocate, after all allocations have been freed too few verts total have been deallocated (bug in calling code)" ); |
|
Assert( m_numVertsAllocated == 0 ); |
|
} |
|
|
|
// We can start allocating again, now |
|
m_bStartedDeallocation = false; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// CreateLightmapsFromData |
|
// - Creates Lightmap Textures from data that was squirreled away during ASYNC load. |
|
// This is necessary because the material system doesn't like us creating things from ASYNC loaders. |
|
//----------------------------------------------------------------------------- |
|
static void CreateLightmapsFromData(CColorMeshData* _colorMeshData) |
|
{ |
|
Assert(_colorMeshData->m_bColorTextureValid); |
|
Assert(!_colorMeshData->m_bColorTextureCreated); |
|
|
|
for (int mesh = 0; mesh < _colorMeshData->m_nMeshes; ++mesh) |
|
{ |
|
ColorMeshInfo_t* meshInfo = &_colorMeshData->m_pMeshInfos[mesh]; |
|
|
|
// Ensure that we haven't somehow already messed with these. |
|
Assert(meshInfo->m_pLightmapData); |
|
Assert(!meshInfo->m_pLightmap); |
|
|
|
ColorTexelsInfo_t* cti = meshInfo->m_pLightmapData; |
|
|
|
Assert(cti->m_pTexelData); |
|
|
|
meshInfo->m_pLightmap = g_pMaterialSystem->CreateTextureFromBits(cti->m_nWidth, cti->m_nHeight, cti->m_nMipmapCount, cti->m_ImageFormat, cti->m_nByteCount, cti->m_pTexelData); |
|
|
|
// If this triggers, we need to figure out if it's reasonable to fail. If it is, then we should figure out how to signal back |
|
// that we shouldn't try to create this again (probably by clearing _colorMeshData->m_bColoTextureValid) |
|
Assert(meshInfo->m_pLightmap); |
|
|
|
// Cleanup after ourselves. |
|
delete [] cti->m_pTexelData; |
|
delete cti; |
|
|
|
meshInfo->m_pLightmapData = NULL; |
|
} |
|
|
|
_colorMeshData->m_bColorTextureCreated = true; |
|
}
|
|
|