aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/client/c_rope.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/client/c_rope.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/client/c_rope.cpp')
-rw-r--r--mp/src/game/client/c_rope.cpp4152
1 files changed, 2076 insertions, 2076 deletions
diff --git a/mp/src/game/client/c_rope.cpp b/mp/src/game/client/c_rope.cpp
index 77389bd0..605f4750 100644
--- a/mp/src/game/client/c_rope.cpp
+++ b/mp/src/game/client/c_rope.cpp
@@ -1,2076 +1,2076 @@
-//========= 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();
- Warning( "Overflowed rope queued rendering memory stack. Needed %d, have %d/%d\n", bytes, iMaxSize - m_QueuedRopeMemory[m_nCurrentStack].GetUsed(), iMaxSize );
- 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 )
- {
- int iRenderCache = m_aRenderCache.AddToTail();
- 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
-void Catmull_Rom_Spline_Matrix( const Vector &p1, const Vector &p2, const Vector &p3, const Vector &p4, catmull_t &output )
-{
- 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
-}
-
-// 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 )
- {
- data.m_nMaterial = reinterpret_cast< int >( this );
- 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;
-
- int iAttachment = pModel->LookupAttachment( "buff_attach" );
- if ( pAngles )
- return pModel->GetAttachment( iAttachment, vPos, *pAngles );
- return pModel->GetAttachment( iAttachment, vPos );
- }
- }
-
- 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();
-}
+//========= 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();
+ Warning( "Overflowed rope queued rendering memory stack. Needed %d, have %d/%d\n", bytes, iMaxSize - m_QueuedRopeMemory[m_nCurrentStack].GetUsed(), iMaxSize );
+ 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 )
+ {
+ int iRenderCache = m_aRenderCache.AddToTail();
+ 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
+void Catmull_Rom_Spline_Matrix( const Vector &p1, const Vector &p2, const Vector &p3, const Vector &p4, catmull_t &output )
+{
+ 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
+}
+
+// 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 )
+ {
+ data.m_nMaterial = reinterpret_cast< int >( this );
+ 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;
+
+ int iAttachment = pModel->LookupAttachment( "buff_attach" );
+ if ( pAngles )
+ return pModel->GetAttachment( iAttachment, vPos, *pAngles );
+ return pModel->GetAttachment( iAttachment, vPos );
+ }
+ }
+
+ 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();
+}