From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/client/c_rope.cpp | 4152 ++++++++++++++++++++--------------------- 1 file changed, 2076 insertions(+), 2076 deletions(-) (limited to 'mp/src/game/client/c_rope.cpp') 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 -#include -#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 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 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 m_aRenderCache; - int m_nSegmentCacheCount; - CUtlVector 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 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 +#include +#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 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 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 m_aRenderCache; + int m_nSegmentCacheCount; + CUtlVector 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 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(); +} -- cgit v1.2.3