354 lines
13 KiB
C++
354 lines
13 KiB
C++
//========= Copyright © 1996-2007, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "blob_networkbypass.h"
|
|
#include "ispsharedmemory.h"
|
|
|
|
#ifndef CLIENT_DLL
|
|
#include "npc_surface.h"
|
|
#endif
|
|
|
|
|
|
#include "tier0/memdbgon.h"
|
|
|
|
BlobNetworkBypass_t *g_pBlobNetworkBypass;
|
|
|
|
#ifdef CLIENT_DLL
|
|
CInterpolatedVar< Vector > s_PositionInterpolators[BLOB_MAX_LEVEL_PARTICLES];
|
|
CInterpolatedVar< float > s_RadiusInterpolators[BLOB_MAX_LEVEL_PARTICLES];
|
|
CInterpolatedVar< Vector > s_ClosestSurfDirInterpolators[BLOB_MAX_LEVEL_PARTICLES];
|
|
BlobParticleInterpolation_t g_BlobParticleInterpolation;
|
|
void BlobNetworkBypass_CustomDemoDataCallback( uint8 *pData, size_t iSize );
|
|
#endif
|
|
|
|
class CBlobParticleNetworkBypassAutoGame : public CAutoGameSystemPerFrame
|
|
{
|
|
public:
|
|
virtual bool Init()
|
|
{
|
|
m_pSharedMemory = engine->GetSinglePlayerSharedMemorySpace( "BlobParticleNetworkBypass" );
|
|
m_pSharedMemory->Init( sizeof( BlobNetworkBypass_t ) );
|
|
g_pBlobNetworkBypass = (BlobNetworkBypass_t *)m_pSharedMemory->Base();
|
|
|
|
#ifdef CLIENT_DLL
|
|
float fInterpAmount = TICK_INTERVAL * (C_BaseEntity::IsSimulatingOnAlternateTicks()?2:1);
|
|
|
|
for( int i = 0; i != BLOB_MAX_LEVEL_PARTICLES; ++i )
|
|
{
|
|
s_PositionInterpolators[i].Setup( &g_BlobParticleInterpolation.vInterpolatedPositions[i], LATCH_ANIMATION_VAR ); //LATCH_SIMULATION_VAR, LATCH_ANIMATION_VAR
|
|
s_PositionInterpolators[i].SetInterpolationAmount( fInterpAmount ); //fInterpAmount
|
|
s_RadiusInterpolators[i].Setup( &g_BlobParticleInterpolation.vInterpolatedRadii[i], LATCH_ANIMATION_VAR ); //LATCH_SIMULATION_VAR, LATCH_ANIMATION_VAR
|
|
s_RadiusInterpolators[i].SetInterpolationAmount( fInterpAmount ); //fInterpAmount
|
|
s_ClosestSurfDirInterpolators[i].Setup( &g_BlobParticleInterpolation.vInterpolatedClosestSurfDir[i], LATCH_ANIMATION_VAR ); //LATCH_SIMULATION_VAR, LATCH_ANIMATION_VAR
|
|
s_ClosestSurfDirInterpolators[i].SetInterpolationAmount( fInterpAmount ); //fInterpAmount
|
|
}
|
|
|
|
m_iOldHighestIndexUsed = 0;
|
|
memset( &m_bOldInUse, 0, sizeof( m_bOldInUse ) );
|
|
|
|
engine->RegisterDemoCustomDataCallback( MAKE_STRING( "BlobNetworkBypass_CustomDemoDataCallback" ), BlobNetworkBypass_CustomDemoDataCallback );
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
virtual void Shutdown()
|
|
{
|
|
m_pSharedMemory->Release();
|
|
m_pSharedMemory = NULL;
|
|
g_pBlobNetworkBypass = NULL;
|
|
}
|
|
|
|
#ifdef CLIENT_DLL
|
|
virtual void PreRender( void );
|
|
unsigned int m_iOldHighestIndexUsed;
|
|
CBitVec<BLOB_MAX_LEVEL_PARTICLES> m_bOldInUse;
|
|
#else
|
|
virtual void PreClientUpdate()
|
|
{
|
|
CNPC_Surface::UpdateBypassParticleData();
|
|
}
|
|
#endif
|
|
|
|
ISPSharedMemory *m_pSharedMemory;
|
|
};
|
|
|
|
static CBlobParticleNetworkBypassAutoGame s_CBPNBAG;
|
|
|
|
|
|
|
|
|
|
#ifndef CLIENT_DLL
|
|
int AllocateBlobNetworkBypassIndex( void )
|
|
{
|
|
int retval;
|
|
if( g_pBlobNetworkBypass->iNumParticlesAllocated == g_pBlobNetworkBypass->iHighestIndexUsed )
|
|
{
|
|
//no holes in the allocations, allocate from the end
|
|
retval = g_pBlobNetworkBypass->iHighestIndexUsed;
|
|
++g_pBlobNetworkBypass->iHighestIndexUsed;
|
|
}
|
|
else
|
|
{
|
|
CBitVec<BLOB_MAX_LEVEL_PARTICLES> notUsed;
|
|
g_pBlobNetworkBypass->bCurrentlyInUse.Not( ¬Used );
|
|
retval = notUsed.FindNextSetBit( 0 );
|
|
Assert( retval < (int)g_pBlobNetworkBypass->iHighestIndexUsed );
|
|
}
|
|
|
|
++g_pBlobNetworkBypass->iNumParticlesAllocated;
|
|
|
|
g_pBlobNetworkBypass->bCurrentlyInUse.Set( retval );
|
|
return retval;
|
|
}
|
|
|
|
void ReleaseBlobNetworkBypassIndex( int iIndex )
|
|
{
|
|
Assert( g_pBlobNetworkBypass->bCurrentlyInUse.IsBitSet( iIndex ) );
|
|
g_pBlobNetworkBypass->bCurrentlyInUse.Clear( iIndex );
|
|
g_pBlobNetworkBypass->vParticlePositions[iIndex] = vec3_origin;
|
|
g_pBlobNetworkBypass->vParticleRadii[iIndex] = 1.0f;
|
|
g_pBlobNetworkBypass->vParticleClosestSurfDir[iIndex] = vec3_origin;
|
|
--g_pBlobNetworkBypass->iNumParticlesAllocated;
|
|
Assert( iIndex < (int)g_pBlobNetworkBypass->iHighestIndexUsed );
|
|
if( iIndex == ((int)g_pBlobNetworkBypass->iHighestIndexUsed - 1) )
|
|
{
|
|
//search for newest high index
|
|
int iOldHighestIntUsed = g_pBlobNetworkBypass->iHighestIndexUsed / BITS_PER_INT;
|
|
for( int i = iOldHighestIntUsed; i >= 0; --i )
|
|
{
|
|
if( (g_pBlobNetworkBypass->bCurrentlyInUse.GetDWord( i ) & (-1)) != 0 )
|
|
{
|
|
int iLowBit = i * BITS_PER_INT;
|
|
int iHighBit = iLowBit + BITS_PER_INT;
|
|
for( int j = iHighBit; --j >= iLowBit; )
|
|
{
|
|
if( g_pBlobNetworkBypass->bCurrentlyInUse.IsBitSet( j ) )
|
|
{
|
|
g_pBlobNetworkBypass->iHighestIndexUsed = (uint32)j + 1;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Assert( g_pBlobNetworkBypass->iHighestIndexUsed >= g_pBlobNetworkBypass->iNumParticlesAllocated );
|
|
}
|
|
|
|
#else
|
|
|
|
void CBlobParticleNetworkBypassAutoGame::PreRender( void )
|
|
{
|
|
if( engine->IsRecordingDemo() && g_pBlobNetworkBypass->bDataUpdated )
|
|
{
|
|
//record the update, TODO: compress the data by omitting the holes
|
|
|
|
int iMaxIndex = MAX(g_pBlobNetworkBypass->iHighestIndexUsed, m_iOldHighestIndexUsed);
|
|
int iBitMax = (iMaxIndex / BITS_PER_INT) + 1;
|
|
|
|
size_t iDataSize = sizeof( int ) + sizeof( float ) + sizeof( int ) + sizeof( int ) + (sizeof( int ) * iBitMax) +
|
|
iMaxIndex*( sizeof( Vector ) + sizeof( float ) + sizeof( Vector ) );
|
|
uint8 *pData = new uint8 [iDataSize];
|
|
uint8 *pWrite = pData;
|
|
|
|
//let the receiver know how much of each array to expect
|
|
*(int *)pWrite = LittleDWord( iMaxIndex );
|
|
pWrite += sizeof( int );
|
|
|
|
//write the update timestamp
|
|
*(float *)pWrite = g_pBlobNetworkBypass->fTimeDataUpdated;
|
|
pWrite += sizeof( float );
|
|
|
|
//record usage information, also helps us effectively compress the subsequent data by omitting the holes.
|
|
*(int *)pWrite = LittleDWord( g_pBlobNetworkBypass->iHighestIndexUsed );
|
|
pWrite += sizeof( int );
|
|
|
|
*(int *)pWrite = LittleDWord( g_pBlobNetworkBypass->iNumParticlesAllocated );
|
|
pWrite += sizeof( int );
|
|
|
|
int *pIntParser = (int *)&g_pBlobNetworkBypass->bCurrentlyInUse;
|
|
for( int i = 0; i != iBitMax; ++i )
|
|
{
|
|
//convert and write the bitfield integers
|
|
*(int *)pWrite = LittleDWord( *pIntParser );
|
|
pWrite += sizeof( int );
|
|
++pIntParser;
|
|
}
|
|
|
|
//write positions
|
|
memcpy( pWrite, g_pBlobNetworkBypass->vParticlePositions, sizeof( Vector ) * iMaxIndex );
|
|
pWrite += sizeof( Vector ) * iMaxIndex;
|
|
|
|
//write radii
|
|
memcpy( pWrite, g_pBlobNetworkBypass->vParticleRadii, sizeof( float ) * iMaxIndex );
|
|
pWrite += sizeof( float ) * iMaxIndex;
|
|
|
|
//write closest surface direction
|
|
memcpy( pWrite, g_pBlobNetworkBypass->vParticleClosestSurfDir, sizeof( Vector ) * iMaxIndex );
|
|
pWrite += sizeof( Vector ) * iMaxIndex;
|
|
|
|
engine->RecordDemoCustomData( BlobNetworkBypass_CustomDemoDataCallback, pData, iDataSize );
|
|
|
|
Assert( pWrite == (pData + iDataSize) );
|
|
|
|
delete []pData;
|
|
}
|
|
|
|
//invalidate interpolation on freed indices, do a quick update for brand new indices
|
|
{
|
|
//operate on smaller chunks based on the assumption that LARGE portions of the end of the bitvecs are empty
|
|
CBitVec<BITS_PER_INT> *pCurrentlyInUse = (CBitVec<BITS_PER_INT> *)&g_pBlobNetworkBypass->bCurrentlyInUse;
|
|
CBitVec<BITS_PER_INT> *pOldInUse = (CBitVec<BITS_PER_INT> *)&m_bOldInUse;
|
|
int iStop = (MAX(g_pBlobNetworkBypass->iHighestIndexUsed, m_iOldHighestIndexUsed) / BITS_PER_INT) + 1;
|
|
int iBaseIndex = 0;
|
|
|
|
//float fNewIndicesUpdateTime = g_pBlobNetworkBypass->bPositionsUpdated ? g_pBlobNetworkBypass->fTimeDataUpdated : gpGlobals->curtime;
|
|
|
|
for( int i = 0; i != iStop; ++i )
|
|
{
|
|
CBitVec<BITS_PER_INT> bInUseXOR;
|
|
pCurrentlyInUse->Xor( *pOldInUse, &bInUseXOR ); //find bits that changed
|
|
|
|
int j = 0;
|
|
while( (j = bInUseXOR.FindNextSetBit( j )) != -1 )
|
|
{
|
|
int iChangedUsageIndex = iBaseIndex + j;
|
|
|
|
if( pOldInUse->IsBitSet( iChangedUsageIndex ) )
|
|
{
|
|
//index no longer used
|
|
g_BlobParticleInterpolation.vInterpolatedPositions[iChangedUsageIndex] = vec3_origin;
|
|
s_PositionInterpolators[iChangedUsageIndex].ClearHistory();
|
|
g_BlobParticleInterpolation.vInterpolatedRadii[iChangedUsageIndex] = 1.0f;
|
|
s_RadiusInterpolators[iChangedUsageIndex].ClearHistory();
|
|
g_BlobParticleInterpolation.vInterpolatedClosestSurfDir[iChangedUsageIndex] = vec3_origin;
|
|
s_ClosestSurfDirInterpolators[iChangedUsageIndex].ClearHistory();
|
|
}
|
|
else
|
|
{
|
|
//index just started being used. Assume we got an out of band update to the position
|
|
g_BlobParticleInterpolation.vInterpolatedPositions[iChangedUsageIndex] = g_pBlobNetworkBypass->vParticlePositions[iChangedUsageIndex];
|
|
s_PositionInterpolators[iChangedUsageIndex].Reset( gpGlobals->curtime );
|
|
g_BlobParticleInterpolation.vInterpolatedRadii[iChangedUsageIndex] = g_pBlobNetworkBypass->vParticleRadii[iChangedUsageIndex];
|
|
s_RadiusInterpolators[iChangedUsageIndex].Reset( gpGlobals->curtime );
|
|
g_BlobParticleInterpolation.vInterpolatedClosestSurfDir[iChangedUsageIndex] = g_pBlobNetworkBypass->vParticleClosestSurfDir[iChangedUsageIndex];
|
|
s_ClosestSurfDirInterpolators[iChangedUsageIndex].Reset( gpGlobals->curtime );
|
|
//s_PositionInterpolators[iChangedUsageIndex].NoteChanged( gpGlobals->curtime, fNewIndicesUpdateTime, true );
|
|
}
|
|
|
|
++j;
|
|
if( j == BITS_PER_INT )
|
|
break;
|
|
}
|
|
iBaseIndex += BITS_PER_INT;
|
|
++pCurrentlyInUse;
|
|
++pOldInUse;
|
|
}
|
|
|
|
memcpy( &m_bOldInUse, &g_pBlobNetworkBypass->bCurrentlyInUse, sizeof( m_bOldInUse ) );
|
|
m_iOldHighestIndexUsed = g_pBlobNetworkBypass->iHighestIndexUsed;
|
|
}
|
|
|
|
if( g_pBlobNetworkBypass->iHighestIndexUsed == 0 )
|
|
return;
|
|
|
|
static ConVarRef cl_interpREF( "cl_interp" );
|
|
//now do the interpolation of positions still in use
|
|
{
|
|
float fInterpTime = gpGlobals->curtime - cl_interpREF.GetFloat();
|
|
|
|
CBitVec<BITS_PER_INT> *pIntParser = (CBitVec<BITS_PER_INT> *)&g_pBlobNetworkBypass->bCurrentlyInUse;
|
|
int iStop = (g_pBlobNetworkBypass->iHighestIndexUsed / BITS_PER_INT) + 1;
|
|
int iBaseIndex = 0;
|
|
for( int i = 0; i != iStop; ++i )
|
|
{
|
|
int j = 0;
|
|
while( (j = pIntParser->FindNextSetBit( j )) != -1 )
|
|
{
|
|
int iUpdateIndex = iBaseIndex + j;
|
|
|
|
if( g_pBlobNetworkBypass->bDataUpdated )
|
|
{
|
|
g_BlobParticleInterpolation.vInterpolatedPositions[iUpdateIndex] = g_pBlobNetworkBypass->vParticlePositions[iUpdateIndex];
|
|
s_PositionInterpolators[iUpdateIndex].NoteChanged( gpGlobals->curtime, g_pBlobNetworkBypass->fTimeDataUpdated, true );
|
|
g_BlobParticleInterpolation.vInterpolatedRadii[iUpdateIndex] = g_pBlobNetworkBypass->vParticleRadii[iUpdateIndex];
|
|
s_RadiusInterpolators[iUpdateIndex].NoteChanged( gpGlobals->curtime, g_pBlobNetworkBypass->fTimeDataUpdated, true );
|
|
g_BlobParticleInterpolation.vInterpolatedClosestSurfDir[iUpdateIndex] = g_pBlobNetworkBypass->vParticleClosestSurfDir[iUpdateIndex];
|
|
s_ClosestSurfDirInterpolators[iUpdateIndex].NoteChanged( gpGlobals->curtime, g_pBlobNetworkBypass->fTimeDataUpdated, true );
|
|
//s_PositionInterpolators[iUpdateIndex].AddToHead( gpGlobals->curtime, &g_pBlobNetworkBypass->vParticlePositions[iUpdateIndex], false );
|
|
}
|
|
|
|
s_PositionInterpolators[iUpdateIndex].Interpolate( fInterpTime );
|
|
s_RadiusInterpolators[iUpdateIndex].Interpolate( fInterpTime );
|
|
s_ClosestSurfDirInterpolators[iUpdateIndex].Interpolate( fInterpTime );
|
|
|
|
++j;
|
|
if( j == BITS_PER_INT )
|
|
break;
|
|
}
|
|
iBaseIndex += BITS_PER_INT;
|
|
++pIntParser;
|
|
}
|
|
|
|
g_pBlobNetworkBypass->bDataUpdated = false;
|
|
}
|
|
}
|
|
|
|
|
|
void BlobNetworkBypass_CustomDemoDataCallback( uint8 *pData, size_t iSize )
|
|
{
|
|
// FIXME: need a version number!
|
|
|
|
uint8 *pParse = pData;
|
|
int iMaxIndex = LittleDWord( *(int *)pParse );
|
|
pParse += sizeof( int );
|
|
|
|
int iBitMax = (iMaxIndex / BITS_PER_INT) + 1;
|
|
|
|
Assert( iSize == (sizeof( int ) + sizeof( float ) + sizeof( int ) + sizeof( int ) + (sizeof( int ) * iBitMax) +
|
|
iMaxIndex*( sizeof( Vector ) + sizeof( float ) + sizeof( Vector ) )) );
|
|
|
|
g_pBlobNetworkBypass->fTimeDataUpdated = *(float *)pParse;
|
|
pParse += sizeof( float );
|
|
|
|
g_pBlobNetworkBypass->iHighestIndexUsed = LittleDWord( *(int *)pParse );
|
|
pParse += sizeof( int );
|
|
|
|
g_pBlobNetworkBypass->iNumParticlesAllocated = LittleDWord( *(int *)pParse );
|
|
pParse += sizeof( int );
|
|
|
|
int *pIntParser = (int *)&g_pBlobNetworkBypass->bCurrentlyInUse;
|
|
for( int i = 0; i != iBitMax; ++i )
|
|
{
|
|
//read and convert the bitfield integers
|
|
*pIntParser = LittleDWord( *(int *)pParse );
|
|
pParse += sizeof( int );
|
|
++pIntParser;
|
|
}
|
|
|
|
//read positions
|
|
memcpy( g_pBlobNetworkBypass->vParticlePositions, pParse, sizeof( Vector ) * iMaxIndex );
|
|
pParse += sizeof( Vector ) * iMaxIndex;
|
|
|
|
//read radii
|
|
memcpy( g_pBlobNetworkBypass->vParticleRadii, pParse, sizeof( float ) * iMaxIndex );
|
|
pParse += sizeof( float ) * iMaxIndex;
|
|
|
|
//read closest surface direction
|
|
memcpy( g_pBlobNetworkBypass->vParticleClosestSurfDir, pParse, sizeof( Vector ) * iMaxIndex );
|
|
pParse += sizeof( Vector ) * iMaxIndex;
|
|
|
|
g_pBlobNetworkBypass->bDataUpdated = true;
|
|
|
|
Assert( pParse == (pData + iSize) );
|
|
}
|
|
|
|
#endif
|
|
|