source-engine/game/client/c_rope.cpp

2077 lines
61 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=====================================================================================//
#include "cbase.h"
#include "c_rope.h"
#include "beamdraw.h"
#include "view.h"
#include "env_wind_shared.h"
#include "input.h"
#ifdef TF_CLIENT_DLL
#include "cdll_util.h"
#endif
#include "rope_helpers.h"
#include "engine/ivmodelinfo.h"
#include "tier0/vprof.h"
#include "c_te_effect_dispatch.h"
#include "collisionutils.h"
#include <KeyValues.h>
#include <bitbuf.h>
#include "utllinkedlist.h"
#include "materialsystem/imaterialsystemhardwareconfig.h"
#include "tier1/callqueue.h"
#include "tier1/memstack.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
void RecvProxy_RecomputeSprings( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
// Have the regular proxy store the data.
RecvProxy_Int32ToInt32( pData, pStruct, pOut );
C_RopeKeyframe *pRope = (C_RopeKeyframe*)pStruct;
pRope->RecomputeSprings();
}
IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_RopeKeyframe, DT_RopeKeyframe, CRopeKeyframe )
RecvPropInt( RECVINFO(m_iRopeMaterialModelIndex) ),
RecvPropEHandle( RECVINFO(m_hStartPoint) ),
RecvPropEHandle( RECVINFO(m_hEndPoint) ),
RecvPropInt( RECVINFO(m_iStartAttachment) ),
RecvPropInt( RECVINFO(m_iEndAttachment) ),
RecvPropInt( RECVINFO(m_fLockedPoints) ),
RecvPropInt( RECVINFO(m_Slack), 0, RecvProxy_RecomputeSprings ),
RecvPropInt( RECVINFO(m_RopeLength), 0, RecvProxy_RecomputeSprings ),
RecvPropInt( RECVINFO(m_RopeFlags) ),
RecvPropFloat( RECVINFO(m_TextureScale) ),
RecvPropInt( RECVINFO(m_nSegments) ),
RecvPropBool( RECVINFO(m_bConstrainBetweenEndpoints) ),
RecvPropInt( RECVINFO(m_Subdiv) ),
RecvPropFloat( RECVINFO(m_Width) ),
RecvPropFloat( RECVINFO(m_flScrollSpeed) ),
RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ),
RecvPropInt( RECVINFO_NAME(m_hNetworkMoveParent, moveparent), 0, RecvProxy_IntToMoveParent ),
RecvPropInt( RECVINFO( m_iParentAttachment ) ),
END_RECV_TABLE()
#define ROPE_IMPULSE_SCALE 20
#define ROPE_IMPULSE_DECAY 0.95
static ConVar rope_shake( "rope_shake", "0" );
static ConVar rope_subdiv( "rope_subdiv", "2", 0, "Rope subdivision amount", true, 0, true, MAX_ROPE_SUBDIVS );
static ConVar rope_collide( "rope_collide", "1", 0, "Collide rope with the world" );
static ConVar rope_smooth( "rope_smooth", "1", 0, "Do an antialiasing effect on ropes" );
static ConVar rope_smooth_enlarge( "rope_smooth_enlarge", "1.4", 0, "How much to enlarge ropes in screen space for antialiasing effect" );
static ConVar rope_smooth_minwidth( "rope_smooth_minwidth", "0.3", 0, "When using smoothing, this is the min screenspace width it lets a rope shrink to" );
static ConVar rope_smooth_minalpha( "rope_smooth_minalpha", "0.2", 0, "Alpha for rope antialiasing effect" );
static ConVar rope_smooth_maxalphawidth( "rope_smooth_maxalphawidth", "1.75" );
static ConVar rope_smooth_maxalpha( "rope_smooth_maxalpha", "0.5", 0, "Alpha for rope antialiasing effect" );
static ConVar mat_fullbright( "mat_fullbright", "0", FCVAR_CHEAT ); // get it from the engine
static ConVar r_drawropes( "r_drawropes", "1", FCVAR_CHEAT );
static ConVar r_queued_ropes( "r_queued_ropes", "1" );
static ConVar r_ropetranslucent( "r_ropetranslucent", "1");
static ConVar r_rope_holiday_light_scale( "r_rope_holiday_light_scale", "0.055", FCVAR_DEVELOPMENTONLY );
static ConVar r_ropes_holiday_lights_allowed( "r_ropes_holiday_lights_allowed", "1", FCVAR_DEVELOPMENTONLY );
static ConVar rope_wind_dist( "rope_wind_dist", "1000", 0, "Don't use CPU applying small wind gusts to ropes when they're past this distance." );
static ConVar rope_averagelight( "rope_averagelight", "1", 0, "Makes ropes use average of cubemap lighting instead of max intensity." );
static ConVar rope_rendersolid( "rope_rendersolid", "1" );
static ConVar rope_solid_minwidth( "rope_solid_minwidth", "0.3" );
static ConVar rope_solid_maxwidth( "rope_solid_maxwidth", "1" );
static ConVar rope_solid_minalpha( "rope_solid_minalpha", "0.0" );
static ConVar rope_solid_maxalpha( "rope_solid_maxalpha", "1" );
static CCycleCount g_RopeCollideTicks;
static CCycleCount g_RopeDrawTicks;
static CCycleCount g_RopeSimulateTicks;
static int g_nRopePointsSimulated;
// Active ropes.
CUtlLinkedList<C_RopeKeyframe*, int> g_Ropes;
static Vector g_FullBright_LightValues[ROPE_MAX_SEGMENTS];
class CFullBrightLightValuesInit
{
public:
CFullBrightLightValuesInit()
{
for( int i=0; i < ROPE_MAX_SEGMENTS; i++ )
g_FullBright_LightValues[i].Init( 1, 1, 1 );
}
} g_FullBrightLightValuesInit;
// Precalculated info for rope subdivision.
static Vector g_RopeSubdivs[MAX_ROPE_SUBDIVS][MAX_ROPE_SUBDIVS];
class CSubdivInit
{
public:
CSubdivInit()
{
for ( int iSubdiv=0; iSubdiv < MAX_ROPE_SUBDIVS; iSubdiv++ )
{
for( int i=0; i <= iSubdiv; i++ )
{
float t = (float)(i+1) / (iSubdiv+1);
g_RopeSubdivs[iSubdiv][i].Init( t, t*t, t*t*t );
}
}
}
} g_SubdivInit;
//interesting barbed-wire-looking effect
static int g_nBarbedSubdivs = 3;
static Vector g_BarbedSubdivs[MAX_ROPE_SUBDIVS] = { Vector(1.5, 1.5*1.5, 1.5*1.5*1.5),
Vector(-0.5, -0.5 * -0.5, -0.5*-0.5*-0.5),
Vector(0.5, 0.5*0.5, 0.5*0.5*0.5) };
// This can be exposed through the entity if we ever care.
static float g_flLockAmount = 0.1;
static float g_flLockFalloff = 0.3;
class CQueuedRopeMemoryManager
{
public:
CQueuedRopeMemoryManager( void )
{
m_nCurrentStack = 0;
MEM_ALLOC_CREDIT();
m_QueuedRopeMemory[0].Init( 131072, 0, 16384 );
m_QueuedRopeMemory[1].Init( 131072, 0, 16384 );
}
~CQueuedRopeMemoryManager( void )
{
m_QueuedRopeMemory[0].FreeAll( true );
m_QueuedRopeMemory[1].FreeAll( true );
for( int i = 0; i != 2; ++i )
{
for( int j = m_DeleteOnSwitch[i].Count(); --j >= 0; )
{
free( m_DeleteOnSwitch[i].Element(j) );
}
m_DeleteOnSwitch[i].RemoveAll();
}
}
void SwitchStack( void )
{
m_nCurrentStack = 1 - m_nCurrentStack;
m_QueuedRopeMemory[m_nCurrentStack].FreeAll( false );
for( int i = m_DeleteOnSwitch[m_nCurrentStack].Count(); --i >= 0; )
{
free( m_DeleteOnSwitch[m_nCurrentStack].Element(i) );
}
m_DeleteOnSwitch[m_nCurrentStack].RemoveAll();
}
inline void *Alloc( size_t bytes )
{
MEM_ALLOC_CREDIT();
void *pReturn = m_QueuedRopeMemory[m_nCurrentStack].Alloc( bytes, false );
if( pReturn == NULL )
{
int iMaxSize = m_QueuedRopeMemory[m_nCurrentStack].GetMaxSize();
2022-03-01 20:00:42 +00:00
Warning( "Overflowed rope queued rendering memory stack. Needed %d, have %d/%d\n", bytes, iMaxSize - m_QueuedRopeMemory[m_nCurrentStack].GetUsed(), iMaxSize );
2020-04-22 16:56:21 +00:00
pReturn = malloc( bytes );
m_DeleteOnSwitch[m_nCurrentStack].AddToTail( pReturn );
}
return pReturn;
}
CMemoryStack m_QueuedRopeMemory[2];
int m_nCurrentStack;
CUtlVector<void *> m_DeleteOnSwitch[2]; //when we overflow the stack, we do new/delete
};
//=============================================================================
//
// Rope mananger.
//
struct RopeSegData_t
{
int m_nSegmentCount;
BeamSeg_t m_Segments[MAX_ROPE_SEGMENTS];
float m_BackWidths[MAX_ROPE_SEGMENTS];
// If this is less than rope_solid_minwidth and rope_solid_minalpha is 0, then we can avoid drawing..
float m_flMaxBackWidth;
};
class CRopeManager : public IRopeManager
{
public:
CRopeManager();
~CRopeManager();
void ResetRenderCache( void );
void AddToRenderCache( C_RopeKeyframe *pRope );
void DrawRenderCache( bool bShadowDepth );
void OnRenderStart( void )
{
m_QueuedModeMemory.SwitchStack();
}
void SetHolidayLightMode( bool bHoliday ) { m_bDrawHolidayLights = bHoliday; }
bool IsHolidayLightMode( void );
int GetHolidayLightStyle( void );
private:
struct RopeRenderData_t;
public:
void DrawRenderCache_NonQueued( bool bShadowDepth, RopeRenderData_t *pRenderCache, int nRenderCacheCount, const Vector &vCurrentViewForward, const Vector &vCurrentViewOrigin, C_RopeKeyframe::BuildRopeQueuedData_t *pBuildRopeQueuedData );
void ResetSegmentCache( int nMaxSegments );
RopeSegData_t *GetNextSegmentFromCache( void );
enum { MAX_ROPE_RENDERCACHE = 128 };
void RemoveRopeFromQueuedRenderCaches( C_RopeKeyframe *pRope );
private:
void RenderNonSolidRopes( IMatRenderContext *pRenderContext, IMaterial *pMaterial, int nVertCount, int nIndexCount );
void RenderSolidRopes( IMatRenderContext *pRenderContext, IMaterial *pMaterial, int nVertCount, int nIndexCount, bool bRenderNonSolid );
private:
struct RopeRenderData_t
{
IMaterial *m_pSolidMaterial;
IMaterial *m_pBackMaterial;
int m_nCacheCount;
C_RopeKeyframe *m_aCache[MAX_ROPE_RENDERCACHE];
};
CUtlVector<RopeRenderData_t> m_aRenderCache;
int m_nSegmentCacheCount;
CUtlVector<RopeSegData_t> m_aSegmentCache;
CThreadFastMutex m_RenderCacheMutex; //there's only any contention during the switch from r_queued_ropes on to off
//in queued material system mode we need to store off data for later use.
CQueuedRopeMemoryManager m_QueuedModeMemory;
IMaterial* m_pDepthWriteMaterial;
struct RopeQueuedRenderCache_t
{
RopeRenderData_t *pCaches;
int iCacheCount;
RopeQueuedRenderCache_t( void ) : pCaches(NULL), iCacheCount(0) { };
};
CUtlLinkedList<RopeQueuedRenderCache_t> m_RopeQueuedRenderCaches;
bool m_bDrawHolidayLights;
bool m_bHolidayInitialized;
int m_nHolidayLightsStyle;
};
static CRopeManager s_RopeManager;
IRopeManager *RopeManager()
{
return &s_RopeManager;
}
inline bool ShouldUseFakeAA( IMaterial *pBackMaterial )
{
return pBackMaterial && rope_smooth.GetInt() && engine->GetDXSupportLevel() > 70 && !g_pMaterialSystemHardwareConfig->IsAAEnabled();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CRopeManager::CRopeManager()
{
m_aRenderCache.Purge();
m_aSegmentCache.Purge();
m_nSegmentCacheCount = 0;
m_pDepthWriteMaterial = NULL;
m_bDrawHolidayLights = false;
m_bHolidayInitialized = false;
m_nHolidayLightsStyle = 0;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CRopeManager::~CRopeManager()
{
int nRenderCacheCount = m_aRenderCache.Count();
for ( int iRenderCache = 0; iRenderCache < nRenderCacheCount; ++iRenderCache )
{
if ( m_aRenderCache[iRenderCache].m_pSolidMaterial )
{
m_aRenderCache[iRenderCache].m_pSolidMaterial->DecrementReferenceCount();
}
if ( m_aRenderCache[iRenderCache].m_pBackMaterial )
{
m_aRenderCache[iRenderCache].m_pBackMaterial->DecrementReferenceCount();
}
}
m_aRenderCache.Purge();
m_aSegmentCache.Purge();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CRopeManager::ResetRenderCache( void )
{
int nRenderCacheCount = m_aRenderCache.Count();
for ( int iRenderCache = 0; iRenderCache < nRenderCacheCount; ++iRenderCache )
{
m_aRenderCache[iRenderCache].m_nCacheCount = 0;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CRopeManager::AddToRenderCache( C_RopeKeyframe *pRope )
{
if( !pRope->GetSolidMaterial() )
{
return;
}
// Find the current rope list.
int iRenderCache = 0;
int nRenderCacheCount = m_aRenderCache.Count();
for ( ; iRenderCache < nRenderCacheCount; ++iRenderCache )
{
if ( ( pRope->GetSolidMaterial() == m_aRenderCache[iRenderCache].m_pSolidMaterial ) &&
( pRope->GetBackMaterial() == m_aRenderCache[iRenderCache].m_pBackMaterial ) )
break;
}
// A full rope list should have been generate in CreateRenderCache
// If we didn't find one, then allocate the mofo.
if ( iRenderCache == nRenderCacheCount )
{
2022-03-01 20:00:42 +00:00
int iRenderCache = m_aRenderCache.AddToTail();
2020-04-22 16:56:21 +00:00
m_aRenderCache[iRenderCache].m_pSolidMaterial = pRope->GetSolidMaterial();
if ( m_aRenderCache[iRenderCache].m_pSolidMaterial )
{
m_aRenderCache[iRenderCache].m_pSolidMaterial->IncrementReferenceCount();
}
m_aRenderCache[iRenderCache].m_pBackMaterial = pRope->GetBackMaterial();
if ( m_aRenderCache[iRenderCache].m_pBackMaterial )
{
m_aRenderCache[iRenderCache].m_pBackMaterial->IncrementReferenceCount();
}
m_aRenderCache[iRenderCache].m_nCacheCount = 0;
}
if ( m_aRenderCache[iRenderCache].m_nCacheCount >= MAX_ROPE_RENDERCACHE )
{
Warning( "CRopeManager::AddToRenderCache count to large for cache!\n" );
return;
}
m_aRenderCache[iRenderCache].m_aCache[m_aRenderCache[iRenderCache].m_nCacheCount] = pRope;
++m_aRenderCache[iRenderCache].m_nCacheCount;
}
void CRopeManager::DrawRenderCache_NonQueued( bool bShadowDepth, RopeRenderData_t *pRenderCache, int nRenderCacheCount, const Vector &vCurrentViewForward, const Vector &vCurrentViewOrigin, C_RopeKeyframe::BuildRopeQueuedData_t *pBuildRopeQueuedData )
{
VPROF_BUDGET( "CRopeManager::DrawRenderCache", VPROF_BUDGETGROUP_ROPES );
AUTO_LOCK( m_RenderCacheMutex ); //contention cases: Toggling from queued mode on to off. Rope deletion from the cache.
// Check to see if we want to render the ropes.
if( !r_drawropes.GetBool() )
{
if( pBuildRopeQueuedData && (m_RopeQueuedRenderCaches.Count() != 0) )
{
m_RopeQueuedRenderCaches.Remove( m_RopeQueuedRenderCaches.Head() );
}
return;
}
if ( bShadowDepth && !m_pDepthWriteMaterial && g_pMaterialSystem )
{
KeyValues *pVMTKeyValues = new KeyValues( "DepthWrite" );
pVMTKeyValues->SetInt( "$no_fullbright", 1 );
pVMTKeyValues->SetInt( "$alphatest", 0 );
pVMTKeyValues->SetInt( "$nocull", 1 );
m_pDepthWriteMaterial = g_pMaterialSystem->FindProceduralMaterial( "__DepthWrite01", TEXTURE_GROUP_OTHER, pVMTKeyValues );
m_pDepthWriteMaterial->IncrementReferenceCount();
}
CMatRenderContextPtr pRenderContext( materials );
C_RopeKeyframe::BuildRopeQueuedData_t stackQueuedData;
Vector vStackPredictedPositions[MAX_ROPE_SEGMENTS];
for ( int iRenderCache = 0; iRenderCache < nRenderCacheCount; ++iRenderCache )
{
int nCacheCount = pRenderCache[iRenderCache].m_nCacheCount;
if ( nCacheCount == 0 )
continue;
ResetSegmentCache( nCacheCount );
for ( int iCache = 0; iCache < nCacheCount; ++iCache )
{
C_RopeKeyframe *pRope = pRenderCache[iRenderCache].m_aCache[iCache];
if ( pRope )
{
RopeSegData_t *pRopeSegment = GetNextSegmentFromCache();
if( pBuildRopeQueuedData )
{
pRope->BuildRope( pRopeSegment, vCurrentViewForward, vCurrentViewOrigin, pBuildRopeQueuedData, true );
++pBuildRopeQueuedData;
}
else
{
//to unify the BuildRope code, emulate the queued data
stackQueuedData.m_iNodeCount = pRope->m_RopePhysics.NumNodes();
stackQueuedData.m_pLightValues = pRope->m_LightValues;
stackQueuedData.m_vColorMod = pRope->m_vColorMod;
stackQueuedData.m_pPredictedPositions = vStackPredictedPositions;
stackQueuedData.m_RopeLength = pRope->m_RopeLength;
stackQueuedData.m_Slack = pRope->m_Slack;
for( int i = 0; i != stackQueuedData.m_iNodeCount; ++i )
{
vStackPredictedPositions[i] = pRope->m_RopePhysics.GetNode( i )->m_vPredicted;
}
pRope->BuildRope( pRopeSegment, vCurrentViewForward, vCurrentViewOrigin, &stackQueuedData, false );
}
}
else
{
if( pBuildRopeQueuedData )
{
//we should only be here if a rope was in the queue and then deleted. We still have it's relevant data (and need to skip over it).
++pBuildRopeQueuedData;
}
}
}
if ( materials->GetRenderContext()->GetCallQueue() != NULL && pBuildRopeQueuedData == NULL )
{
// We build ropes outside of queued mode for holidy lights
// But we don't want to render them
continue;
}
int nVertCount = 0;
int nIndexCount = 0;
for ( int iSegmentCache = 0; iSegmentCache < m_nSegmentCacheCount; ++iSegmentCache )
{
nVertCount += ( m_aSegmentCache[iSegmentCache].m_nSegmentCount * 2 );
nIndexCount += ( ( m_aSegmentCache[iSegmentCache].m_nSegmentCount - 1 ) * 6 );
}
// Render the non-solid portion of the ropes.
bool bRenderNonSolid = !bShadowDepth && ShouldUseFakeAA( pRenderCache[iRenderCache].m_pBackMaterial );
if ( bRenderNonSolid )
{
RenderNonSolidRopes( pRenderContext, pRenderCache[iRenderCache].m_pBackMaterial, nVertCount, nIndexCount );
}
// Render the solid portion of the ropes.
if ( rope_rendersolid.GetInt() )
{
if ( bShadowDepth )
RenderSolidRopes( pRenderContext, m_pDepthWriteMaterial, nVertCount, nIndexCount, bRenderNonSolid );
else
RenderSolidRopes( pRenderContext, pRenderCache[iRenderCache].m_pSolidMaterial, nVertCount, nIndexCount, bRenderNonSolid );
}
}
ResetSegmentCache( 0 );
if( pBuildRopeQueuedData && (m_RopeQueuedRenderCaches.Count() != 0) )
{
m_RopeQueuedRenderCaches.Remove( m_RopeQueuedRenderCaches.Head() );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CRopeManager::DrawRenderCache( bool bShadowDepth )
{
int iRenderCacheCount = m_aRenderCache.Count();
if( iRenderCacheCount == 0 )
return;
Vector vForward = CurrentViewForward();
Vector vOrigin = CurrentViewOrigin();
ICallQueue *pCallQueue;
if( r_queued_ropes.GetBool() && (pCallQueue = materials->GetRenderContext()->GetCallQueue()) != NULL )
{
//material queue available and desired
CRopeManager::RopeRenderData_t *pRenderCache = m_aRenderCache.Base();
AUTO_LOCK( m_RenderCacheMutex );
int iRopeCount = 0;
int iNodeCount = 0;
for( int i = 0; i != iRenderCacheCount; ++i )
{
CRopeManager::RopeRenderData_t *pCache = &pRenderCache[i];
int iCacheCount = pCache->m_nCacheCount;
iRopeCount += iCacheCount;
for( int j = 0; j != iCacheCount; ++j )
{
C_RopeKeyframe *pRope = pCache->m_aCache[j];
if( pRope )
iNodeCount += pRope->m_RopePhysics.NumNodes();
else
--iRopeCount;
}
}
if( iRopeCount == 0 )
return; //nothing to draw
size_t iMemoryNeeded = (iRenderCacheCount * sizeof(CRopeManager::RopeRenderData_t)) +
(iRopeCount * sizeof(C_RopeKeyframe::BuildRopeQueuedData_t)) +
(iNodeCount * (sizeof(Vector) * 2));
void *pMemory = m_QueuedModeMemory.Alloc( iMemoryNeeded );
CRopeManager::RopeRenderData_t *pRenderCachesStart = (CRopeManager::RopeRenderData_t *)pMemory;
C_RopeKeyframe::BuildRopeQueuedData_t *pBuildRopeQueuedDataStart = (C_RopeKeyframe::BuildRopeQueuedData_t *)(pRenderCachesStart + iRenderCacheCount);
Vector *pVectorDataStart = (Vector *)(pBuildRopeQueuedDataStart + iRopeCount);
//memcpy( pRenderCachesStart, m_aRenderCache.Base(), iRenderCacheCount * sizeof( CRopeManager::RopeRenderData_t ) );
RopeQueuedRenderCache_t cache;
cache.pCaches = pRenderCachesStart;
cache.iCacheCount = iRenderCacheCount;
m_RopeQueuedRenderCaches.AddToTail( cache );
C_RopeKeyframe::BuildRopeQueuedData_t *pWriteRopeQueuedData = pBuildRopeQueuedDataStart;
Vector *pVectorWrite = (Vector *)pVectorDataStart;
//Setup the rest of our data. This writes to two separate areas of memory at the same time. One area for the C_RopeKeyframe::BuildRopeQueuedData_t array, the other for mini-arrays of vector data
for( int i = 0; i != iRenderCacheCount; ++i )
{
CRopeManager::RopeRenderData_t *pReadCache = &pRenderCache[i];
CRopeManager::RopeRenderData_t *pWriteCache = &pRenderCachesStart[i];
int iCacheCount = pReadCache->m_nCacheCount;
pWriteCache->m_nCacheCount = 0;
pWriteCache->m_pSolidMaterial = pReadCache->m_pSolidMaterial;
pWriteCache->m_pBackMaterial = pReadCache->m_pBackMaterial;
for( int j = 0; j != iCacheCount; ++j )
{
C_RopeKeyframe *pRope = pReadCache->m_aCache[j];
if( pRope == NULL )
continue;
pWriteCache->m_aCache[pWriteCache->m_nCacheCount] = pRope;
++pWriteCache->m_nCacheCount;
int iNodes = pRope->m_RopePhysics.NumNodes();
//setup the C_RopeKeyframe::BuildRopeQueuedData_t struct
pWriteRopeQueuedData->m_iNodeCount = pRope->m_RopePhysics.NumNodes();
pWriteRopeQueuedData->m_vColorMod = pRope->m_vColorMod;
pWriteRopeQueuedData->m_RopeLength = pRope->m_RopeLength;
pWriteRopeQueuedData->m_Slack = pRope->m_Slack;
pWriteRopeQueuedData->m_pPredictedPositions = pVectorWrite;
pWriteRopeQueuedData->m_pLightValues = pVectorWrite + iNodes;
++pWriteRopeQueuedData;
//make two arrays, one of predicted positions followed immediately by light values
for( int k = 0; k != iNodes; ++k )
{
pVectorWrite[0] = pRope->m_RopePhysics.GetNode( k )->m_vPredicted;
pVectorWrite[iNodes] = pRope->m_LightValues[k];
++pVectorWrite;
}
pVectorWrite += iNodes; //so we don't overwrite the light values with the next rope's predicted positions
}
}
Assert( ((void *)pVectorWrite == (void *)(((uint8 *)pMemory) + iMemoryNeeded)) && ((void *)pWriteRopeQueuedData == (void *)pVectorDataStart));
pCallQueue->QueueCall( this, &CRopeManager::DrawRenderCache_NonQueued, bShadowDepth, pRenderCachesStart, iRenderCacheCount, vForward, vOrigin, pBuildRopeQueuedDataStart );
if ( IsHolidayLightMode() )
{
// With holiday lights we need to also build the ropes non-queued without rendering them
DrawRenderCache_NonQueued( bShadowDepth, m_aRenderCache.Base(), iRenderCacheCount, vForward, vOrigin, NULL );
}
}
else
{
DrawRenderCache_NonQueued( bShadowDepth, m_aRenderCache.Base(), iRenderCacheCount, vForward, vOrigin, NULL );
}
}
bool CRopeManager::IsHolidayLightMode( void )
{
if ( !r_ropes_holiday_lights_allowed.GetBool() )
{
return false;
}
bool bDrawHolidayLights = false;
#ifdef USES_ECON_ITEMS
if ( !m_bHolidayInitialized && GameRules() )
{
m_bHolidayInitialized = true;
m_bDrawHolidayLights = GameRules()->IsHolidayActive( kHoliday_Christmas );
}
bDrawHolidayLights = m_bDrawHolidayLights;
m_nHolidayLightsStyle = 0;
#ifdef TF_CLIENT_DLL
// Turn them on in Pyro-vision too
if ( IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_PYRO ) )
{
bDrawHolidayLights = true;
m_nHolidayLightsStyle = 1;
}
#endif // TF_CLIENT_DLL
#endif // USES_ECON_ITEMS
return bDrawHolidayLights;
}
int CRopeManager::GetHolidayLightStyle( void )
{
return m_nHolidayLightsStyle;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CRopeManager::RenderNonSolidRopes( IMatRenderContext *pRenderContext, IMaterial *pMaterial, int nVertCount, int nIndexCount )
{
// Render the solid portion of the ropes.
CMeshBuilder meshBuilder;
IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial );
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nVertCount, nIndexCount );
CBeamSegDraw beamSegment;
int nVerts = 0;
for ( int iSegmentCache = 0; iSegmentCache < m_nSegmentCacheCount; ++iSegmentCache )
{
int nSegmentCount = m_aSegmentCache[iSegmentCache].m_nSegmentCount;
beamSegment.Start( pRenderContext, nSegmentCount, pMaterial, &meshBuilder, nVerts );
for ( int iSegment = 0; iSegment < nSegmentCount; ++iSegment )
{
beamSegment.NextSeg( &m_aSegmentCache[iSegmentCache].m_Segments[iSegment] );
}
beamSegment.End();
nVerts += ( m_aSegmentCache[iSegmentCache].m_nSegmentCount * 2 );
}
meshBuilder.End();
pMesh->Draw();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CRopeManager::RenderSolidRopes( IMatRenderContext *pRenderContext, IMaterial *pMaterial, int nVertCount, int nIndexCount, bool bRenderNonSolid )
{
// Render the solid portion of the ropes.
CMeshBuilder meshBuilder;
IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial );
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nVertCount, nIndexCount );
CBeamSegDraw beamSegment;
if ( bRenderNonSolid )
{
int nVerts = 0;
for ( int iSegmentCache = 0; iSegmentCache < m_nSegmentCacheCount; ++iSegmentCache )
{
RopeSegData_t *pSegData = &m_aSegmentCache[iSegmentCache];
// If it's all going to be 0 alpha, then just skip drawing this one.
if ( rope_solid_minalpha.GetFloat() == 0.0 && pSegData->m_flMaxBackWidth <= rope_solid_minwidth.GetFloat() )
continue;
int nSegmentCount = m_aSegmentCache[iSegmentCache].m_nSegmentCount;
beamSegment.Start( pRenderContext, nSegmentCount, pMaterial, &meshBuilder, nVerts );
for ( int iSegment = 0; iSegment < nSegmentCount; ++iSegment )
{
BeamSeg_t *pSeg = &m_aSegmentCache[iSegmentCache].m_Segments[iSegment];
pSeg->m_flWidth = m_aSegmentCache[iSegmentCache].m_BackWidths[iSegment];
// To avoid aliasing, the "solid" version of the rope on xbox is just "more solid",
// and it has its own values controlling its alpha.
pSeg->m_flAlpha = RemapVal( pSeg->m_flWidth,
rope_solid_minwidth.GetFloat(),
rope_solid_maxwidth.GetFloat(),
rope_solid_minalpha.GetFloat(),
rope_solid_maxalpha.GetFloat() );
pSeg->m_flAlpha = clamp( pSeg->m_flAlpha, 0.0f, 1.0f );
beamSegment.NextSeg( &m_aSegmentCache[iSegmentCache].m_Segments[iSegment] );
}
beamSegment.End();
nVerts += ( m_aSegmentCache[iSegmentCache].m_nSegmentCount * 2 );
}
}
else
{
int nVerts = 0;
for ( int iSegmentCache = 0; iSegmentCache < m_nSegmentCacheCount; ++iSegmentCache )
{
int nSegmentCount = m_aSegmentCache[iSegmentCache].m_nSegmentCount;
beamSegment.Start( pRenderContext, nSegmentCount, pMaterial, &meshBuilder, nVerts );
for ( int iSegment = 0; iSegment < nSegmentCount; ++iSegment )
{
beamSegment.NextSeg( &m_aSegmentCache[iSegmentCache].m_Segments[iSegment] );
}
beamSegment.End();
nVerts += ( m_aSegmentCache[iSegmentCache].m_nSegmentCount * 2 );
}
}
meshBuilder.End();
pMesh->Draw();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CRopeManager::ResetSegmentCache( int nMaxSegments )
{
MEM_ALLOC_CREDIT();
m_nSegmentCacheCount = 0;
if ( nMaxSegments )
m_aSegmentCache.EnsureCount( nMaxSegments );
else
m_aSegmentCache.Purge();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
RopeSegData_t *CRopeManager::GetNextSegmentFromCache( void )
{
if ( m_nSegmentCacheCount >= m_aSegmentCache.Count() )
{
Warning( "CRopeManager::GetNextSegmentFromCache too many segments for cache!\n" );
return NULL;
}
++m_nSegmentCacheCount;
return &m_aSegmentCache[m_nSegmentCacheCount-1];
}
void CRopeManager::RemoveRopeFromQueuedRenderCaches( C_RopeKeyframe *pRope )
{
//remove this rope from queued render caches
AUTO_LOCK( m_RenderCacheMutex );
int index = m_RopeQueuedRenderCaches.Head();
while( m_RopeQueuedRenderCaches.IsValidIndex( index ) )
{
RopeQueuedRenderCache_t &RenderCacheData = m_RopeQueuedRenderCaches[index];
for( int i = 0; i != RenderCacheData.iCacheCount; ++i )
{
RopeRenderData_t *pCache = &RenderCacheData.pCaches[i];
for( int j = 0; j != pCache->m_nCacheCount; ++j )
{
if( pCache->m_aCache[j] == pRope )
{
pCache->m_aCache[j] = NULL;
}
}
}
index = m_RopeQueuedRenderCaches.Next( index );
}
}
//=============================================================================
// ------------------------------------------------------------------------------------ //
// Global functions.
// ------------------------------------------------------------------------------------ //
void Rope_ResetCounters()
{
g_RopeCollideTicks.Init();
g_RopeDrawTicks.Init();
g_RopeSimulateTicks.Init();
g_nRopePointsSimulated = 0;
}
// ------------------------------------------------------------------------------------ //
// This handles the rope shake command.
// ------------------------------------------------------------------------------------ //
void ShakeRopesCallback( const CEffectData &data )
{
Vector vCenter = data.m_vOrigin;
float flRadius = data.m_flRadius;
float flMagnitude = data.m_flMagnitude;
// Now find any nearby ropes and shake them.
FOR_EACH_LL( g_Ropes, i )
{
C_RopeKeyframe *pRope = g_Ropes[i];
pRope->ShakeRope( vCenter, flRadius, flMagnitude );
}
}
DECLARE_CLIENT_EFFECT( "ShakeRopes", ShakeRopesCallback );
// ------------------------------------------------------------------------------------ //
// C_RopeKeyframe::CPhysicsDelegate
// ------------------------------------------------------------------------------------ //
#define WIND_FORCE_FACTOR 10
void C_RopeKeyframe::CPhysicsDelegate::GetNodeForces( CSimplePhysics::CNode *pNodes, int iNode, Vector *pAccel )
{
// Gravity.
if ( !( m_pKeyframe->GetRopeFlags() & ROPE_NO_GRAVITY ) )
{
pAccel->Init( ROPE_GRAVITY );
}
if( !m_pKeyframe->m_LinksTouchingSomething[iNode] && m_pKeyframe->m_bApplyWind)
{
Vector vecWindVel;
GetWindspeedAtTime(gpGlobals->curtime, vecWindVel);
if ( vecWindVel.LengthSqr() > 0 )
{
Vector vecWindAccel;
VectorMA( *pAccel, WIND_FORCE_FACTOR, vecWindVel, *pAccel );
}
else
{
if (m_pKeyframe->m_flCurrentGustTimer < m_pKeyframe->m_flCurrentGustLifetime )
{
float div = m_pKeyframe->m_flCurrentGustTimer / m_pKeyframe->m_flCurrentGustLifetime;
float scale = 1 - cos( div * M_PI );
*pAccel += m_pKeyframe->m_vWindDir * scale;
}
}
}
// HACK.. shake the rope around.
static float scale=15000;
if( rope_shake.GetInt() )
{
*pAccel += RandomVector( -scale, scale );
}
// Apply any instananeous forces and reset
*pAccel += ROPE_IMPULSE_SCALE * m_pKeyframe->m_flImpulse;
m_pKeyframe->m_flImpulse *= ROPE_IMPULSE_DECAY;
}
void LockNodeDirection(
CSimplePhysics::CNode *pNodes,
int parity,
int nFalloffNodes,
float flLockAmount,
float flLockFalloff,
const Vector &vIdealDir )
{
for ( int i=0; i < nFalloffNodes; i++ )
{
Vector &v0 = pNodes[i*parity].m_vPos;
Vector &v1 = pNodes[(i+1)*parity].m_vPos;
Vector vDir = v1 - v0;
float len = vDir.Length();
if ( len > 0.0001f )
{
vDir /= len;
Vector vActual;
VectorLerp( vDir, vIdealDir, flLockAmount, vActual );
v1 = v0 + vActual * len;
flLockAmount *= flLockFalloff;
}
}
}
void C_RopeKeyframe::CPhysicsDelegate::ApplyConstraints( CSimplePhysics::CNode *pNodes, int nNodes )
{
VPROF( "CPhysicsDelegate::ApplyConstraints" );
CTraceFilterWorldOnly traceFilter;
// Collide with the world.
if( ((m_pKeyframe->m_RopeFlags & ROPE_COLLIDE) &&
rope_collide.GetInt()) ||
(rope_collide.GetInt() == 2) )
{
CTimeAdder adder( &g_RopeCollideTicks );
for( int i=0; i < nNodes; i++ )
{
CSimplePhysics::CNode *pNode = &pNodes[i];
int iIteration;
int nIterations = 10;
for( iIteration=0; iIteration < nIterations; iIteration++ )
{
trace_t trace;
UTIL_TraceHull( pNode->m_vPrevPos, pNode->m_vPos,
Vector(-2,-2,-2), Vector(2,2,2), MASK_SOLID_BRUSHONLY, &traceFilter, &trace );
if( trace.fraction == 1 )
break;
if( trace.fraction == 0 || trace.allsolid || trace.startsolid )
{
m_pKeyframe->m_LinksTouchingSomething[i] = true;
pNode->m_vPos = pNode->m_vPrevPos;
break;
}
// Apply some friction.
static float flSlowFactor = 0.3f;
pNode->m_vPos -= (pNode->m_vPos - pNode->m_vPrevPos) * flSlowFactor;
// Move it out along the face normal.
float distBehind = trace.plane.normal.Dot( pNode->m_vPos ) - trace.plane.dist;
pNode->m_vPos += trace.plane.normal * (-distBehind + 2.2);
m_pKeyframe->m_LinksTouchingSomething[i] = true;
}
if( iIteration == nIterations )
pNodes[i].m_vPos = pNodes[i].m_vPrevPos;
}
}
// Lock the endpoints.
QAngle angles;
if( m_pKeyframe->m_fLockedPoints & ROPE_LOCK_START_POINT )
{
m_pKeyframe->GetEndPointAttachment( 0, pNodes[0].m_vPos, angles );
if (( m_pKeyframe->m_fLockedPoints & ROPE_LOCK_START_DIRECTION ) && (nNodes > 3))
{
Vector forward;
AngleVectors( angles, &forward );
int parity = 1;
int nFalloffNodes = MIN( 2, nNodes - 2 );
LockNodeDirection( pNodes, parity, nFalloffNodes, g_flLockAmount, g_flLockFalloff, forward );
}
}
if( m_pKeyframe->m_fLockedPoints & ROPE_LOCK_END_POINT )
{
m_pKeyframe->GetEndPointAttachment( 1, pNodes[nNodes-1].m_vPos, angles );
if( m_pKeyframe->m_fLockedPoints & ROPE_LOCK_END_DIRECTION && (nNodes > 3))
{
Vector forward;
AngleVectors( angles, &forward );
int parity = -1;
int nFalloffNodes = MIN( 2, nNodes - 2 );
LockNodeDirection( &pNodes[nNodes-1], parity, nFalloffNodes, g_flLockAmount, g_flLockFalloff, forward );
}
}
}
// ------------------------------------------------------------------------------------ //
// C_RopeKeyframe
// ------------------------------------------------------------------------------------ //
C_RopeKeyframe::C_RopeKeyframe()
{
m_bEndPointAttachmentPositionsDirty = true;
m_bEndPointAttachmentAnglesDirty = true;
m_PhysicsDelegate.m_pKeyframe = this;
m_pMaterial = NULL;
m_bPhysicsInitted = false;
m_RopeFlags = 0;
m_TextureHeight = 1;
m_hStartPoint = m_hEndPoint = NULL;
m_iStartAttachment = m_iEndAttachment = 0;
m_vColorMod.Init( 1, 1, 1 );
m_nLinksTouchingSomething = 0;
m_Subdiv = 255; // default to using the cvar
m_fLockedPoints = 0;
m_fPrevLockedPoints = 0;
m_iForcePointMoveCounter = 0;
m_flCurScroll = m_flScrollSpeed = 0;
m_TextureScale = 4; // 4:1
m_flImpulse.Init();
g_Ropes.AddToTail( this );
}
C_RopeKeyframe::~C_RopeKeyframe()
{
s_RopeManager.RemoveRopeFromQueuedRenderCaches( this );
g_Ropes.FindAndRemove( this );
if ( m_pBackMaterial )
{
m_pBackMaterial->DecrementReferenceCount();
m_pBackMaterial = NULL;
}
}
C_RopeKeyframe* C_RopeKeyframe::Create(
C_BaseEntity *pStartEnt,
C_BaseEntity *pEndEnt,
int iStartAttachment,
int iEndAttachment,
float ropeWidth,
const char *pMaterialName,
int numSegments,
int ropeFlags
)
{
C_RopeKeyframe *pRope = new C_RopeKeyframe;
pRope->InitializeAsClientEntity( NULL, RENDER_GROUP_OPAQUE_ENTITY );
if ( pStartEnt )
{
pRope->m_hStartPoint = pStartEnt;
pRope->m_fLockedPoints |= ROPE_LOCK_START_POINT;
}
if ( pEndEnt )
{
pRope->m_hEndPoint = pEndEnt;
pRope->m_fLockedPoints |= ROPE_LOCK_END_POINT;
}
pRope->m_iStartAttachment = iStartAttachment;
pRope->m_iEndAttachment = iEndAttachment;
pRope->m_Width = ropeWidth;
pRope->m_nSegments = clamp( numSegments, 2, ROPE_MAX_SEGMENTS );
pRope->m_RopeFlags = ropeFlags;
pRope->FinishInit( pMaterialName );
return pRope;
}
C_RopeKeyframe* C_RopeKeyframe::CreateFromKeyValues( C_BaseAnimating *pEnt, KeyValues *pValues )
{
C_RopeKeyframe *pRope = C_RopeKeyframe::Create(
pEnt,
pEnt,
pEnt->LookupAttachment( pValues->GetString( "StartAttachment" ) ),
pEnt->LookupAttachment( pValues->GetString( "EndAttachment" ) ),
pValues->GetFloat( "Width", 0.5 ),
pValues->GetString( "Material" ),
pValues->GetInt( "NumSegments" ),
0 );
if ( pRope )
{
if ( pValues->GetInt( "Gravity", 1 ) == 0 )
{
pRope->m_RopeFlags |= ROPE_NO_GRAVITY;
}
pRope->m_RopeLength = pValues->GetInt( "Length" );
pRope->m_TextureScale = pValues->GetFloat( "TextureScale", pRope->m_TextureScale );
pRope->m_Slack = 0;
pRope->m_RopeFlags |= ROPE_SIMULATE;
}
return pRope;
}
int C_RopeKeyframe::GetRopesIntersectingAABB( C_RopeKeyframe **pRopes, int nMaxRopes, const Vector &vAbsMin, const Vector &vAbsMax )
{
if ( nMaxRopes == 0 )
return 0;
int nRopes = 0;
FOR_EACH_LL( g_Ropes, i )
{
C_RopeKeyframe *pRope = g_Ropes[i];
Vector v1, v2;
if ( pRope->GetEndPointPos( 0, v1 ) && pRope->GetEndPointPos( 1, v2 ) )
{
if ( IsBoxIntersectingRay( v1, v2-v1, vAbsMin, vAbsMax, 0.1f ) )
{
pRopes[nRopes++] = pRope;
if ( nRopes == nMaxRopes )
break;
}
}
}
return nRopes;
}
void C_RopeKeyframe::SetSlack( int slack )
{
m_Slack = slack;
RecomputeSprings();
}
void C_RopeKeyframe::SetRopeFlags( int flags )
{
m_RopeFlags = flags;
UpdateVisibility();
}
int C_RopeKeyframe::GetRopeFlags() const
{
return m_RopeFlags;
}
void C_RopeKeyframe::SetupHangDistance( float flHangDist )
{
C_BaseEntity *pEnt1 = m_hStartPoint;
C_BaseEntity *pEnt2 = m_hEndPoint;
if ( !pEnt1 || !pEnt2 )
return;
QAngle dummyAngles;
// Calculate starting conditions so we can force it to hang down N inches.
Vector v1 = pEnt1->GetAbsOrigin();
pEnt1->GetAttachment( m_iStartAttachment, v1, dummyAngles );
Vector v2 = pEnt2->GetAbsOrigin();
pEnt2->GetAttachment( m_iEndAttachment, v2, dummyAngles );
float flSlack, flLen;
CalcRopeStartingConditions( v1, v2, ROPE_MAX_SEGMENTS, flHangDist, &flLen, &flSlack );
m_RopeLength = (int)flLen;
m_Slack = (int)flSlack;
RecomputeSprings();
}
void C_RopeKeyframe::SetStartEntity( C_BaseEntity *pEnt )
{
m_hStartPoint = pEnt;
}
void C_RopeKeyframe::SetEndEntity( C_BaseEntity *pEnt )
{
m_hEndPoint = pEnt;
}
C_BaseEntity* C_RopeKeyframe::GetStartEntity() const
{
return m_hStartPoint;
}
C_BaseEntity* C_RopeKeyframe::GetEndEntity() const
{
return m_hEndPoint;
}
CSimplePhysics::IHelper* C_RopeKeyframe::HookPhysics( CSimplePhysics::IHelper *pHook )
{
m_RopePhysics.SetDelegate( pHook );
return &m_PhysicsDelegate;
}
void C_RopeKeyframe::SetColorMod( const Vector &vColorMod )
{
m_vColorMod = vColorMod;
}
void C_RopeKeyframe::RecomputeSprings()
{
m_RopePhysics.ResetSpringLength(
(m_RopeLength + m_Slack + ROPESLACK_FUDGEFACTOR) / (m_RopePhysics.NumNodes() - 1) );
}
void C_RopeKeyframe::ShakeRope( const Vector &vCenter, float flRadius, float flMagnitude )
{
// Sum up whatever it would apply to all of our points.
for ( int i=0; i < m_nSegments; i++ )
{
CSimplePhysics::CNode *pNode = m_RopePhysics.GetNode( i );
float flDist = (pNode->m_vPos - vCenter).Length();
float flShakeAmount = 1.0f - flDist / flRadius;
if ( flShakeAmount >= 0 )
{
m_flImpulse.z += flShakeAmount * flMagnitude;
}
}
}
void C_RopeKeyframe::OnDataChanged( DataUpdateType_t updateType )
{
BaseClass::OnDataChanged( updateType );
m_bNewDataThisFrame = true;
if( updateType != DATA_UPDATE_CREATED )
return;
// Figure out the material name.
char str[512];
const model_t *pModel = modelinfo->GetModel( m_iRopeMaterialModelIndex );
if ( pModel )
{
Q_strncpy( str, modelinfo->GetModelName( pModel ), sizeof( str ) );
// Get rid of the extension because the material system doesn't want it.
char *pExt = Q_stristr( str, ".vmt" );
if ( pExt )
pExt[0] = 0;
}
else
{
Q_strncpy( str, "asdf", sizeof( str ) );
}
FinishInit( str );
}
void C_RopeKeyframe::FinishInit( const char *pMaterialName )
{
// Get the material from the material system.
m_pMaterial = materials->FindMaterial( pMaterialName, TEXTURE_GROUP_OTHER );
if( m_pMaterial )
m_TextureHeight = m_pMaterial->GetMappingHeight();
else
m_TextureHeight = 1;
char backName[512];
Q_snprintf( backName, sizeof( backName ), "%s_back", pMaterialName );
m_pBackMaterial = materials->FindMaterial( backName, TEXTURE_GROUP_OTHER, false );
if ( IsErrorMaterial( m_pBackMaterial ) )
m_pBackMaterial = NULL;
if ( m_pBackMaterial )
{
m_pBackMaterial->IncrementReferenceCount();
m_pBackMaterial->GetMappingWidth();
}
// Init rope physics.
m_nSegments = clamp( m_nSegments, 2, ROPE_MAX_SEGMENTS );
m_RopePhysics.SetNumNodes( m_nSegments );
SetCollisionBounds( Vector( -10, -10, -10 ), Vector( 10, 10, 10 ) );
// We want to think every frame.
SetNextClientThink( CLIENT_THINK_ALWAYS );
}
void C_RopeKeyframe::RunRopeSimulation( float flSeconds )
{
// First, forget about links touching things.
for ( int i=0; i < m_nSegments; i++ )
m_LinksTouchingSomething[i] = false;
// Simulate, and it will mark which links touched things.
m_RopePhysics.Simulate( flSeconds );
// Now count how many links touched something.
m_nLinksTouchingSomething = 0;
for ( int i=0; i < m_nSegments; i++ )
{
if ( m_LinksTouchingSomething[i] )
++m_nLinksTouchingSomething;
}
}
Vector C_RopeKeyframe::ConstrainNode( const Vector &vNormal, const Vector &vNodePosition, const Vector &vMidpiont, float fNormalLength )
{
// Get triangle edges formed
Vector vMidpointToNode = vNodePosition - vMidpiont;
Vector vMidpointToNodeProjected = vMidpointToNode.Dot( vNormal ) * vNormal;
float fMidpointToNodeLengh = VectorNormalize( vMidpointToNode );
float fMidpointToNodeProjectedLengh = VectorNormalize( vMidpointToNodeProjected );
// See if it's past an endpoint
if ( fMidpointToNodeProjectedLengh < fNormalLength + 1.0f )
return vNodePosition;
// Apply the ratio between the triangles
return vMidpiont + vMidpointToNode * fMidpointToNodeLengh * ( fNormalLength / fMidpointToNodeProjectedLengh );
}
void C_RopeKeyframe::ConstrainNodesBetweenEndpoints( void )
{
if ( !m_bConstrainBetweenEndpoints )
return;
// Get midpoint and normals
Vector vMidpiont = ( m_vCachedEndPointAttachmentPos[ 0 ] + m_vCachedEndPointAttachmentPos[ 1 ] ) / 2.0f;
Vector vNormal = vMidpiont - m_vCachedEndPointAttachmentPos[ 0 ];
float fNormalLength = VectorNormalize( vNormal );
// Loop through all the middle segments and ensure their positions are constrained between the endpoints
for ( int i = 1; i < m_RopePhysics.NumNodes() - 1; ++i )
{
// Fix the current position
m_RopePhysics.GetNode( i )->m_vPos = ConstrainNode( vNormal, m_RopePhysics.GetNode( i )->m_vPos, vMidpiont, fNormalLength );
// Fix the predicted position
m_RopePhysics.GetNode( i )->m_vPredicted = ConstrainNode( vNormal, m_RopePhysics.GetNode( i )->m_vPredicted, vMidpiont, fNormalLength );
}
}
void C_RopeKeyframe::ClientThink()
{
// Only recalculate the endpoint attachments once per frame.
m_bEndPointAttachmentPositionsDirty = true;
m_bEndPointAttachmentAnglesDirty = true;
if( !r_drawropes.GetBool() )
return;
if( !InitRopePhysics() ) // init if not already
return;
if( !DetectRestingState( m_bApplyWind ) )
{
// Update the simulation.
CTimeAdder adder( &g_RopeSimulateTicks );
RunRopeSimulation( gpGlobals->frametime );
g_nRopePointsSimulated += m_RopePhysics.NumNodes();
m_bNewDataThisFrame = false;
// Setup a new wind gust?
m_flCurrentGustTimer += gpGlobals->frametime;
m_flTimeToNextGust -= gpGlobals->frametime;
if( m_flTimeToNextGust <= 0 )
{
m_vWindDir = RandomVector( -1, 1 );
VectorNormalize( m_vWindDir );
static float basicScale = 50;
m_vWindDir *= basicScale;
m_vWindDir *= RandomFloat( -1.0f, 1.0f );
m_flCurrentGustTimer = 0;
m_flCurrentGustLifetime = RandomFloat( 2.0f, 3.0f );
m_flTimeToNextGust = RandomFloat( 3.0f, 4.0f );
}
UpdateBBox();
}
}
int C_RopeKeyframe::DrawModel( int flags )
{
VPROF_BUDGET( "C_RopeKeyframe::DrawModel", VPROF_BUDGETGROUP_ROPES );
if( !InitRopePhysics() )
return 0;
if ( !m_bReadyToDraw )
return 0;
// Resize the rope
if( m_RopeFlags & ROPE_RESIZE )
{
RecomputeSprings();
}
// If our start & end entities have models, but are nodraw, then we don't draw
if ( m_hStartPoint && m_hStartPoint->IsDormant() && m_hEndPoint && m_hEndPoint->IsDormant() )
{
// Check models because rope endpoints are point entities
if ( m_hStartPoint->GetModelIndex() && m_hEndPoint->GetModelIndex() )
return 0;
}
ConstrainNodesBetweenEndpoints();
RopeManager()->AddToRenderCache( this );
return 1;
}
bool C_RopeKeyframe::ShouldDraw()
{
if( !r_ropetranslucent.GetBool() )
return false;
if( !(m_RopeFlags & ROPE_SIMULATE) )
return false;
return true;
}
const Vector& C_RopeKeyframe::WorldSpaceCenter( ) const
{
return GetAbsOrigin();
}
bool C_RopeKeyframe::GetAttachment( int number, matrix3x4_t &matrix )
{
int nNodes = m_RopePhysics.NumNodes();
if ( (number != ROPE_ATTACHMENT_START_POINT && number != ROPE_ATTACHMENT_END_POINT) || nNodes < 2 )
return false;
// Now setup the orientation based on the last segment.
Vector vForward, origin;
if ( number == ROPE_ATTACHMENT_START_POINT )
{
origin = m_RopePhysics.GetNode( 0 )->m_vPredicted;
vForward = m_RopePhysics.GetNode( 0 )->m_vPredicted - m_RopePhysics.GetNode( 1 )->m_vPredicted;
}
else
{
origin = m_RopePhysics.GetNode( nNodes-1 )->m_vPredicted;
vForward = m_RopePhysics.GetNode( nNodes-1 )->m_vPredicted - m_RopePhysics.GetNode( nNodes-2 )->m_vPredicted;
}
VectorMatrix( vForward, matrix );
PositionMatrix( origin, matrix );
return true;
}
bool C_RopeKeyframe::GetAttachment( int number, Vector &origin )
{
int nNodes = m_RopePhysics.NumNodes();
if ( (number != ROPE_ATTACHMENT_START_POINT && number != ROPE_ATTACHMENT_END_POINT) || nNodes < 2 )
return false;
// Now setup the orientation based on the last segment.
if ( number == ROPE_ATTACHMENT_START_POINT )
{
origin = m_RopePhysics.GetNode( 0 )->m_vPredicted;
}
else
{
origin = m_RopePhysics.GetNode( nNodes-1 )->m_vPredicted;
}
return true;
}
bool C_RopeKeyframe::GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel )
{
Assert(0);
return false;
}
bool C_RopeKeyframe::GetAttachment( int number, Vector &origin, QAngle &angles )
{
int nNodes = m_RopePhysics.NumNodes();
if ( (number == ROPE_ATTACHMENT_START_POINT || number == ROPE_ATTACHMENT_END_POINT) && nNodes >= 2 )
{
// Now setup the orientation based on the last segment.
Vector vForward;
if ( number == ROPE_ATTACHMENT_START_POINT )
{
origin = m_RopePhysics.GetNode( 0 )->m_vPredicted;
vForward = m_RopePhysics.GetNode( 0 )->m_vPredicted - m_RopePhysics.GetNode( 1 )->m_vPredicted;
}
else
{
origin = m_RopePhysics.GetNode( nNodes-1 )->m_vPredicted;
vForward = m_RopePhysics.GetNode( nNodes-1 )->m_vPredicted - m_RopePhysics.GetNode( nNodes-2 )->m_vPredicted;
}
VectorAngles( vForward, angles );
return true;
}
return false;
}
bool C_RopeKeyframe::AnyPointsMoved()
{
for( int i=0; i < m_RopePhysics.NumNodes(); i++ )
{
CSimplePhysics::CNode *pNode = m_RopePhysics.GetNode( i );
float flMoveDistSqr = (pNode->m_vPos - pNode->m_vPrevPos).LengthSqr();
if( flMoveDistSqr > 0.03f )
return true;
}
if( --m_iForcePointMoveCounter > 0 )
return true;
return false;
}
inline bool C_RopeKeyframe::DidEndPointMove( int iPt )
{
// If this point isn't locked anyway, just break out.
if( !( m_fLockedPoints & (1 << iPt) ) )
return false;
bool bOld = m_bPrevEndPointPos[iPt];
Vector vOld = m_vPrevEndPointPos[iPt];
m_bPrevEndPointPos[iPt] = GetEndPointPos( iPt, m_vPrevEndPointPos[iPt] );
// If it wasn't and isn't attached to anything, don't register a change.
if( !bOld && !m_bPrevEndPointPos[iPt] )
return true;
// Register a change if the endpoint moves.
if( !VectorsAreEqual( vOld, m_vPrevEndPointPos[iPt], 0.1 ) )
return true;
return false;
}
bool C_RopeKeyframe::DetectRestingState( bool &bApplyWind )
{
bApplyWind = false;
if( m_fPrevLockedPoints != m_fLockedPoints )
{
// Force it to move the points for some number of frames when they get detached or
// after we get new data. This allows them to accelerate from gravity.
m_iForcePointMoveCounter = 10;
m_fPrevLockedPoints = m_fLockedPoints;
return false;
}
if( m_bNewDataThisFrame )
{
// Simulate if anything about us changed this frame, such as our position due to hierarchy.
// FIXME: this won't work when hierarchy is client side
return false;
}
// Make sure our attachment points haven't moved.
if( DidEndPointMove( 0 ) || DidEndPointMove( 1 ) )
return false;
// See how close we are to the line.
Vector &vEnd1 = m_RopePhysics.GetFirstNode()->m_vPos;
Vector &vEnd2 = m_RopePhysics.GetLastNode()->m_vPos;
if ( !( m_RopeFlags & ROPE_NO_WIND ) )
{
// Don't apply wind if more than half of the nodes are touching something.
float flDist1 = CalcDistanceToLineSegment( MainViewOrigin(), vEnd1, vEnd2 );
if( m_nLinksTouchingSomething < (m_RopePhysics.NumNodes() >> 1) )
bApplyWind = flDist1 < rope_wind_dist.GetFloat();
}
if ( m_flPreviousImpulse != m_flImpulse )
{
m_flPreviousImpulse = m_flImpulse;
return false;
}
return !AnyPointsMoved() && !bApplyWind && !rope_shake.GetInt();
}
// simple struct to precompute basis for catmull rom splines for faster evaluation
struct catmull_t
{
Vector t3;
Vector t2;
Vector t;
Vector c;
};
// bake out the terms of the catmull rom spline
2022-03-01 20:00:42 +00:00
void Catmull_Rom_Spline_Matrix( const Vector &p1, const Vector &p2, const Vector &p3, const Vector &p4, catmull_t &output )
2020-04-22 16:56:21 +00:00
{
2022-03-01 20:00:42 +00:00
output.t3 = 0.5f * ((-1*p1) + (3*p2) + (-3*p3) + p4); // 0.5 t^3 * [ (-1*p1) + ( 3*p2) + (-3*p3) + p4 ]
output.t2 = 0.5f * ((2*p1) + (-5*p2) + (4*p3) - p4); // 0.5 t^2 * [ ( 2*p1) + (-5*p2) + ( 4*p3) - p4 ]
output.t = 0.5f * ((-1*p1) + p3); // 0.5 t * [ (-1*p1) + p3 ]
output.c = p2; // p2
2020-04-22 16:56:21 +00:00
}
// evaluate one point on the spline, t is a vector of (t, t^2, t^3)
inline void Catmull_Rom_Eval( const catmull_t &spline, const Vector &t, Vector &output )
{
Assert(spline.c.IsValid());
Assert(spline.t.IsValid());
Assert(spline.t2.IsValid());
Assert(spline.t3.IsValid());
output = spline.c + (t.x * spline.t) + (t.y*spline.t2) + (t.z * spline.t3);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_RopeKeyframe::BuildRope( RopeSegData_t *pSegmentData, const Vector &vCurrentViewForward, const Vector &vCurrentViewOrigin, C_RopeKeyframe::BuildRopeQueuedData_t *pQueuedData, bool bQueued )
{
if ( !pSegmentData )
return;
// Get the lighting values.
Vector *pLightValues = ( mat_fullbright.GetInt() == 1 ) ? g_FullBright_LightValues : pQueuedData->m_pLightValues;
// Update the rope subdivisions if necessary.
int nSubdivCount;
Vector *pSubdivVecList = GetRopeSubdivVectors( &nSubdivCount );
int nSegmentCount = 0;
int iPrevNode = 0;
const float subdivScale = 1.0f / (nSubdivCount+1);
const int nodeCount = pQueuedData->m_iNodeCount;
const int lastNode = nodeCount-1;
catmull_t spline;
Vector *pPredictedPositions = pQueuedData->m_pPredictedPositions;
Vector vColorMod = pQueuedData->m_vColorMod;
for( int iNode = 0; iNode < nodeCount; ++iNode )
{
pSegmentData->m_Segments[nSegmentCount].m_vPos = pPredictedPositions[iNode];
pSegmentData->m_Segments[nSegmentCount].m_vColor = pLightValues[iNode] * vColorMod;
CEffectData data;
if ( !bQueued && RopeManager()->IsHolidayLightMode() && r_rope_holiday_light_scale.GetFloat() > 0.0f )
{
2022-02-23 11:50:30 +00:00
data.m_nMaterial = (intp)this;
2020-04-22 16:56:21 +00:00
data.m_nHitBox = ( iNode << 8 );
data.m_flScale = r_rope_holiday_light_scale.GetFloat();
data.m_vOrigin = pSegmentData->m_Segments[nSegmentCount].m_vPos;
DispatchEffect( "TF_HolidayLight", data );
}
++nSegmentCount;
if ( iNode < lastNode )
{
// Draw a midpoint to the next segment.
int iNext = iNode + 1;
int iNextNext = iNode + 2;
if ( iNext >= nodeCount )
{
iNext = iNextNext = lastNode;
}
else if ( iNextNext >= nodeCount )
{
iNextNext = lastNode;
}
Vector vecColorInc = subdivScale * ( ( pLightValues[iNode+1] - pLightValues[iNode] ) * vColorMod );
// precompute spline basis
Catmull_Rom_Spline_Matrix( pPredictedPositions[iPrevNode], pPredictedPositions[iNode],
pPredictedPositions[iNext], pPredictedPositions[iNextNext], spline );
for( int iSubdiv = 0; iSubdiv < nSubdivCount; ++iSubdiv )
{
pSegmentData->m_Segments[nSegmentCount].m_vColor = pSegmentData->m_Segments[nSegmentCount-1].m_vColor + vecColorInc;
// simple eval using precomputed basis
Catmull_Rom_Eval( spline, pSubdivVecList[iSubdiv], pSegmentData->m_Segments[nSegmentCount].m_vPos );
if ( !bQueued && RopeManager()->IsHolidayLightMode() && r_rope_holiday_light_scale.GetFloat() > 0.0f )
{
data.m_nHitBox++;
data.m_flScale = r_rope_holiday_light_scale.GetFloat();
data.m_vOrigin = pSegmentData->m_Segments[nSegmentCount].m_vPos;
DispatchEffect( "TF_HolidayLight", data );
}
++nSegmentCount;
Assert( nSegmentCount <= MAX_ROPE_SEGMENTS );
}
iPrevNode = iNode;
}
}
pSegmentData->m_nSegmentCount = nSegmentCount;
pSegmentData->m_flMaxBackWidth = 0;
// Figure out texture scale.
float flPixelsPerInch = 4.0f / m_TextureScale;
float flTotalTexCoord = flPixelsPerInch * ( pQueuedData->m_RopeLength + pQueuedData->m_Slack + ROPESLACK_FUDGEFACTOR );
int nTotalPoints = ( nodeCount - 1 ) * nSubdivCount + 1;
float flActualInc = ( flTotalTexCoord / nTotalPoints ) / ( float )m_TextureHeight;
// First draw a translucent rope underneath the solid rope for an antialiasing effect.
if ( ShouldUseFakeAA( m_pBackMaterial ) )
{
// Compute screen width
float flScreenWidth = ScreenWidth();
float flHalfScreenWidth = flScreenWidth / 2.0f;
float flExtraScreenSpaceWidth = rope_smooth_enlarge.GetFloat();
float flMinAlpha = rope_smooth_minalpha.GetFloat();
float flMaxAlpha = rope_smooth_maxalpha.GetFloat();
float flMinScreenSpaceWidth = rope_smooth_minwidth.GetFloat();
float flMaxAlphaScreenSpaceWidth = rope_smooth_maxalphawidth.GetFloat();
float flTexCoord = m_flCurScroll;
for ( int iSegment = 0; iSegment < nSegmentCount; ++iSegment )
{
pSegmentData->m_Segments[iSegment].m_flTexCoord = flTexCoord;
// Right here, we need to specify a width that will be 1 pixel larger in screen space.
float zCoord = vCurrentViewForward.Dot( pSegmentData->m_Segments[iSegment].m_vPos - vCurrentViewOrigin );
zCoord = MAX( zCoord, 0.1f );
float flScreenSpaceWidth = m_Width * flHalfScreenWidth / zCoord;
if ( flScreenSpaceWidth < flMinScreenSpaceWidth )
{
pSegmentData->m_Segments[iSegment].m_flAlpha = flMinAlpha;
pSegmentData->m_Segments[iSegment].m_flWidth = flMinScreenSpaceWidth * zCoord / flHalfScreenWidth;
pSegmentData->m_BackWidths[iSegment] = 0.0f;
}
else
{
if ( flScreenSpaceWidth > flMaxAlphaScreenSpaceWidth )
{
pSegmentData->m_Segments[iSegment].m_flAlpha = flMaxAlpha;
}
else
{
pSegmentData->m_Segments[iSegment].m_flAlpha = RemapVal( flScreenSpaceWidth, flMinScreenSpaceWidth, flMaxAlphaScreenSpaceWidth, flMinAlpha, flMaxAlpha );
}
pSegmentData->m_Segments[iSegment].m_flWidth = m_Width;
pSegmentData->m_BackWidths[iSegment] = m_Width - ( zCoord * flExtraScreenSpaceWidth ) / flScreenWidth;
if ( pSegmentData->m_BackWidths[iSegment] < 0.0f )
{
pSegmentData->m_BackWidths[iSegment] = 0.0f;
}
else
{
pSegmentData->m_flMaxBackWidth = MAX( pSegmentData->m_flMaxBackWidth, pSegmentData->m_BackWidths[iSegment] );
}
}
// Get the next texture coordinate.
flTexCoord += flActualInc;
}
}
else
{
float flTexCoord = m_flCurScroll;
// Build the data with no smoothing.
for ( int iSegment = 0; iSegment < nSegmentCount; ++iSegment )
{
pSegmentData->m_Segments[iSegment].m_flTexCoord = flTexCoord;
pSegmentData->m_Segments[iSegment].m_flAlpha = 0.3f;
pSegmentData->m_Segments[iSegment].m_flWidth = m_Width;
pSegmentData->m_BackWidths[iSegment] = -1.0f;
// Get the next texture coordinate.
flTexCoord += flActualInc;
}
}
}
void C_RopeKeyframe::UpdateBBox()
{
Vector &vStart = m_RopePhysics.GetFirstNode()->m_vPos;
Vector &vEnd = m_RopePhysics.GetLastNode()->m_vPos;
Vector mins, maxs;
VectorMin( vStart, vEnd, mins );
VectorMax( vStart, vEnd, maxs );
for( int i=1; i < m_RopePhysics.NumNodes()-1; i++ )
{
const Vector &vPos = m_RopePhysics.GetNode(i)->m_vPos;
AddPointToBounds( vPos, mins, maxs );
}
mins -= GetAbsOrigin();
maxs -= GetAbsOrigin();
SetCollisionBounds( mins, maxs );
}
bool C_RopeKeyframe::InitRopePhysics()
{
if( !(m_RopeFlags & ROPE_SIMULATE) )
return 0;
if( m_bPhysicsInitted )
{
return true;
}
// Must have both entities to work.
m_bPrevEndPointPos[0] = GetEndPointPos( 0, m_vPrevEndPointPos[0] );
if( !m_bPrevEndPointPos[0] )
return false;
// They're allowed to not have an end attachment point so the rope can dangle.
m_bPrevEndPointPos[1] = GetEndPointPos( 1, m_vPrevEndPointPos[1] );
if( !m_bPrevEndPointPos[1] )
m_vPrevEndPointPos[1] = m_vPrevEndPointPos[0];
const Vector &vStart = m_vPrevEndPointPos[0];
const Vector &vAttached = m_vPrevEndPointPos[1];
m_RopePhysics.SetupSimulation( 0, &m_PhysicsDelegate );
RecomputeSprings();
m_RopePhysics.Restart();
// Initialize the positions of the nodes.
for( int i=0; i < m_RopePhysics.NumNodes(); i++ )
{
CSimplePhysics::CNode *pNode = m_RopePhysics.GetNode( i );
float t = (float)i / (m_RopePhysics.NumNodes() - 1);
VectorLerp( vStart, vAttached, t, pNode->m_vPos );
pNode->m_vPrevPos = pNode->m_vPos;
}
// Simulate for a bit to let it sag.
if ( m_RopeFlags & ROPE_INITIAL_HANG )
{
RunRopeSimulation( 5 );
}
CalcLightValues();
// Set our bounds for visibility.
UpdateBBox();
m_flTimeToNextGust = RandomFloat( 1.0f, 3.0f );
m_bPhysicsInitted = true;
return true;
}
bool C_RopeKeyframe::CalculateEndPointAttachment( C_BaseEntity *pEnt, int iAttachment, Vector &vPos, QAngle *pAngles )
{
VPROF_BUDGET( "C_RopeKeyframe::CalculateEndPointAttachment", VPROF_BUDGETGROUP_ROPES );
if( !pEnt )
return false;
if ( m_RopeFlags & ROPE_PLAYER_WPN_ATTACH )
{
C_BasePlayer *pPlayer = dynamic_cast< C_BasePlayer* >( pEnt );
if ( pPlayer )
{
C_BaseAnimating *pModel = pPlayer->GetRenderedWeaponModel();
if ( !pModel )
return false;
2022-03-01 20:00:42 +00:00
int iAttachment = pModel->LookupAttachment( "buff_attach" );
2020-04-22 16:56:21 +00:00
if ( pAngles )
2022-03-01 20:00:42 +00:00
return pModel->GetAttachment( iAttachment, vPos, *pAngles );
return pModel->GetAttachment( iAttachment, vPos );
2020-04-22 16:56:21 +00:00
}
}
if( iAttachment > 0 )
{
bool bOk;
if ( pAngles )
{
bOk = pEnt->GetAttachment( iAttachment, vPos, *pAngles );
}
else
{
bOk = pEnt->GetAttachment( iAttachment, vPos );
}
if ( bOk )
return true;
}
vPos = pEnt->WorldSpaceCenter( );
if ( pAngles )
{
*pAngles = pEnt->GetAbsAngles();
}
return true;
}
bool C_RopeKeyframe::GetEndPointPos( int iPt, Vector &vPos )
{
// By caching the results here, we avoid doing this a bunch of times per frame.
if ( m_bEndPointAttachmentPositionsDirty )
{
CalculateEndPointAttachment( m_hStartPoint, m_iStartAttachment, m_vCachedEndPointAttachmentPos[0], NULL );
CalculateEndPointAttachment( m_hEndPoint, m_iEndAttachment, m_vCachedEndPointAttachmentPos[1], NULL );
m_bEndPointAttachmentPositionsDirty = false;
}
Assert( iPt == 0 || iPt == 1 );
vPos = m_vCachedEndPointAttachmentPos[iPt];
return true;
}
IMaterial* C_RopeKeyframe::GetSolidMaterial( void )
{
#ifdef TF_CLIENT_DLL
if ( RopeManager()->IsHolidayLightMode() )
{
if ( RopeManager()->GetHolidayLightStyle() == 1 )
{
return materials->FindMaterial( "cable/pure_white", TEXTURE_GROUP_OTHER );
}
}
#endif
return m_pMaterial;
}
IMaterial* C_RopeKeyframe::GetBackMaterial( void )
{
return m_pBackMaterial;
}
bool C_RopeKeyframe::GetEndPointAttachment( int iPt, Vector &vPos, QAngle &angle )
{
// By caching the results here, we avoid doing this a bunch of times per frame.
if ( m_bEndPointAttachmentPositionsDirty || m_bEndPointAttachmentAnglesDirty )
{
CalculateEndPointAttachment( m_hStartPoint, m_iStartAttachment, m_vCachedEndPointAttachmentPos[0], &m_vCachedEndPointAttachmentAngle[0] );
CalculateEndPointAttachment( m_hEndPoint, m_iEndAttachment, m_vCachedEndPointAttachmentPos[1], &m_vCachedEndPointAttachmentAngle[1] );
m_bEndPointAttachmentPositionsDirty = false;
m_bEndPointAttachmentAnglesDirty = false;
}
Assert( iPt == 0 || iPt == 1 );
vPos = m_vCachedEndPointAttachmentPos[iPt];
angle = m_vCachedEndPointAttachmentAngle[iPt];
return true;
}
// Look at the global cvar and recalculate rope subdivision data if necessary.
Vector *C_RopeKeyframe::GetRopeSubdivVectors( int *nSubdivs )
{
if( m_RopeFlags & ROPE_BARBED )
{
*nSubdivs = g_nBarbedSubdivs;
return g_BarbedSubdivs;
}
else
{
int subdiv = m_Subdiv;
if ( subdiv == 255 )
{
subdiv = rope_subdiv.GetInt();
}
if ( subdiv >= MAX_ROPE_SUBDIVS )
subdiv = MAX_ROPE_SUBDIVS-1;
*nSubdivs = subdiv;
return g_RopeSubdivs[subdiv];
}
}
void C_RopeKeyframe::CalcLightValues()
{
Vector boxColors[6];
for( int i=0; i < m_RopePhysics.NumNodes(); i++ )
{
const Vector &vPos = m_RopePhysics.GetNode(i)->m_vPredicted;
engine->ComputeLighting( vPos, NULL, true, m_LightValues[i], boxColors );
if ( !rope_averagelight.GetInt() )
{
// The engine averages the lighting across the 6 box faces, but we would rather just get the MAX intensity
// since we do our own half-lambert lighting in the rope shader to simulate directionality.
//
// So here, we take the average of all the incoming light, and scale it to use the max intensity of all the box sides.
float flMaxIntensity = 0;
for ( int iSide=0; iSide < 6; iSide++ )
{
float flLen = boxColors[iSide].Length();
flMaxIntensity = MAX( flMaxIntensity, flLen );
}
VectorNormalize( m_LightValues[i] );
m_LightValues[i] *= flMaxIntensity;
float flMax = MAX( m_LightValues[i].x, MAX( m_LightValues[i].y, m_LightValues[i].z ) );
if ( flMax > 1 )
m_LightValues[i] /= flMax;
}
}
}
//------------------------------------------------------------------------------
// Purpose :
// Input :
// Output :
//------------------------------------------------------------------------------
void C_RopeKeyframe::ReceiveMessage( int classID, bf_read &msg )
{
if ( classID != GetClientClass()->m_ClassID )
{
// message is for subclass
BaseClass::ReceiveMessage( classID, msg );
return;
}
// Read instantaneous fore data
m_flImpulse.x = msg.ReadFloat();
m_flImpulse.y = msg.ReadFloat();
m_flImpulse.z = msg.ReadFloat();
}