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