//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "SpriteTrail.h" #ifdef CLIENT_DLL #include "clientsideeffects.h" #include "materialsystem/imaterialsystem.h" #include "materialsystem/imesh.h" #include "mathlib/vmatrix.h" #include "view.h" #include "beamdraw.h" #include "enginesprite.h" #include "tier0/vprof.h" extern CEngineSprite *Draw_SetSpriteTexture( const model_t *pSpriteModel, int frame, int rendermode ); #endif // CLIENT_DLL // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // constants //----------------------------------------------------------------------------- #define SCREEN_SPACE_TRAILS 0 //----------------------------------------------------------------------------- // Save/Restore //----------------------------------------------------------------------------- #if defined( CLIENT_DLL ) BEGIN_SIMPLE_DATADESC( TrailPoint_t ) #if SCREEN_SPACE_TRAILS DEFINE_FIELD( m_vecScreenPos, FIELD_VECTOR ), #else DEFINE_FIELD( m_vecScreenPos, FIELD_POSITION_VECTOR ), #endif DEFINE_FIELD( m_flDieTime, FIELD_TIME ), DEFINE_FIELD( m_flTexCoord, FIELD_FLOAT ), DEFINE_FIELD( m_flWidthVariance,FIELD_FLOAT ), END_DATADESC() #endif BEGIN_DATADESC( CSpriteTrail ) DEFINE_KEYFIELD( m_flLifeTime, FIELD_FLOAT, "lifetime" ), DEFINE_KEYFIELD( m_flStartWidth, FIELD_FLOAT, "startwidth" ), DEFINE_KEYFIELD( m_flEndWidth, FIELD_FLOAT, "endwidth" ), DEFINE_KEYFIELD( m_iszSpriteName, FIELD_STRING, "spritename" ), DEFINE_KEYFIELD( m_bAnimate, FIELD_BOOLEAN, "animate" ), DEFINE_FIELD( m_flStartWidthVariance, FIELD_FLOAT ), DEFINE_FIELD( m_flTextureRes, FIELD_FLOAT ), DEFINE_FIELD( m_flMinFadeLength, FIELD_FLOAT ), DEFINE_FIELD( m_vecSkyboxOrigin, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_flSkyboxScale, FIELD_FLOAT ), // These are client-only #if defined( CLIENT_DLL ) DEFINE_EMBEDDED_AUTO_ARRAY( m_vecSteps ), DEFINE_FIELD( m_nFirstStep, FIELD_INTEGER ), DEFINE_FIELD( m_nStepCount, FIELD_INTEGER ), DEFINE_FIELD( m_flUpdateTime, FIELD_TIME ), DEFINE_FIELD( m_vecPrevSkyboxOrigin, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_flPrevSkyboxScale, FIELD_FLOAT ), DEFINE_FIELD( m_vecRenderMins, FIELD_VECTOR ), DEFINE_FIELD( m_vecRenderMaxs, FIELD_VECTOR ), #endif END_DATADESC() LINK_ENTITY_TO_CLASS( env_spritetrail, CSpriteTrail ); //----------------------------------------------------------------------------- // Networking //----------------------------------------------------------------------------- IMPLEMENT_NETWORKCLASS_ALIASED( SpriteTrail, DT_SpriteTrail ); BEGIN_NETWORK_TABLE( CSpriteTrail, DT_SpriteTrail ) #if !defined( CLIENT_DLL ) SendPropFloat( SENDINFO(m_flLifeTime), 0, SPROP_NOSCALE ), SendPropFloat( SENDINFO(m_flStartWidth), 0, SPROP_NOSCALE ), SendPropFloat( SENDINFO(m_flEndWidth), 0, SPROP_NOSCALE ), SendPropFloat( SENDINFO(m_flStartWidthVariance), 0, SPROP_NOSCALE ), SendPropFloat( SENDINFO(m_flTextureRes), 0, SPROP_NOSCALE ), SendPropFloat( SENDINFO(m_flMinFadeLength), 0, SPROP_NOSCALE ), SendPropVector( SENDINFO(m_vecSkyboxOrigin),0, SPROP_NOSCALE ), SendPropFloat( SENDINFO(m_flSkyboxScale), 0, SPROP_NOSCALE ), #else RecvPropFloat( RECVINFO(m_flLifeTime)), RecvPropFloat( RECVINFO(m_flStartWidth)), RecvPropFloat( RECVINFO(m_flEndWidth)), RecvPropFloat( RECVINFO(m_flStartWidthVariance)), RecvPropFloat( RECVINFO(m_flTextureRes)), RecvPropFloat( RECVINFO(m_flMinFadeLength)), RecvPropVector( RECVINFO(m_vecSkyboxOrigin)), RecvPropFloat( RECVINFO(m_flSkyboxScale)), #endif END_NETWORK_TABLE() //----------------------------------------------------------------------------- // Prediction //----------------------------------------------------------------------------- BEGIN_PREDICTION_DATA( CSpriteTrail ) END_PREDICTION_DATA() //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CSpriteTrail::CSpriteTrail( void ) { #ifdef CLIENT_DLL m_nFirstStep = 0; m_nStepCount = 0; #endif m_flStartWidthVariance = 0; m_vecSkyboxOrigin.Init( 0, 0, 0 ); m_flSkyboxScale = 1.0f; m_flEndWidth = -1.0f; m_bDrawForMoveParent = true; } void CSpriteTrail::Spawn( void ) { #ifdef CLIENT_DLL BaseClass::Spawn(); #else if ( GetModelName() != NULL_STRING ) { BaseClass::Spawn(); return; } SetModelName( m_iszSpriteName ); BaseClass::Spawn(); SetSolid( SOLID_NONE ); SetMoveType( MOVETYPE_NOCLIP ); SetCollisionBounds( vec3_origin, vec3_origin ); TurnOn(); #endif } //----------------------------------------------------------------------------- // Sets parameters of the sprite trail //----------------------------------------------------------------------------- void CSpriteTrail::Precache( void ) { BaseClass::Precache(); if ( m_iszSpriteName != NULL_STRING ) { PrecacheModel( STRING(m_iszSpriteName) ); } } //----------------------------------------------------------------------------- // Sets parameters of the sprite trail //----------------------------------------------------------------------------- void CSpriteTrail::SetLifeTime( float time ) { m_flLifeTime = time; } void CSpriteTrail::SetStartWidth( float flStartWidth ) { m_flStartWidth = flStartWidth; m_flStartWidth /= m_flSkyboxScale; } void CSpriteTrail::SetStartWidthVariance( float flStartWidthVariance ) { m_flStartWidthVariance = flStartWidthVariance; m_flStartWidthVariance /= m_flSkyboxScale; } void CSpriteTrail::SetEndWidth( float flEndWidth ) { m_flEndWidth = flEndWidth; m_flEndWidth /= m_flSkyboxScale; } void CSpriteTrail::SetTextureResolution( float flTexelsPerInch ) { m_flTextureRes = flTexelsPerInch; m_flTextureRes *= m_flSkyboxScale; } void CSpriteTrail::SetMinFadeLength( float flMinFadeLength ) { m_flMinFadeLength = flMinFadeLength; m_flMinFadeLength /= m_flSkyboxScale; } void CSpriteTrail::SetSkybox( const Vector &vecSkyboxOrigin, float flSkyboxScale ) { m_flTextureRes /= m_flSkyboxScale; m_flMinFadeLength *= m_flSkyboxScale; m_flStartWidth *= m_flSkyboxScale; m_flEndWidth *= m_flSkyboxScale; m_flStartWidthVariance *= m_flSkyboxScale; m_vecSkyboxOrigin = vecSkyboxOrigin; m_flSkyboxScale = flSkyboxScale; m_flTextureRes *= m_flSkyboxScale; m_flMinFadeLength /= m_flSkyboxScale; m_flStartWidth /= m_flSkyboxScale; m_flEndWidth /= m_flSkyboxScale; m_flStartWidthVariance /= m_flSkyboxScale; if ( IsInSkybox() ) { AddEFlags( EFL_IN_SKYBOX ); } else { RemoveEFlags( EFL_IN_SKYBOX ); } } //----------------------------------------------------------------------------- // Is the trail in the skybox? //----------------------------------------------------------------------------- bool CSpriteTrail::IsInSkybox() const { return (m_flSkyboxScale != 1.0f) || (m_vecSkyboxOrigin != vec3_origin); } #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // On data update //----------------------------------------------------------------------------- void CSpriteTrail::OnPreDataChanged( DataUpdateType_t updateType ) { BaseClass::OnPreDataChanged( updateType ); m_vecPrevSkyboxOrigin = m_vecSkyboxOrigin; m_flPrevSkyboxScale = m_flSkyboxScale; } void CSpriteTrail::OnDataChanged( DataUpdateType_t updateType ) { BaseClass::OnDataChanged( updateType ); if ( updateType == DATA_UPDATE_CREATED ) { SetNextClientThink( CLIENT_THINK_ALWAYS ); } else { if ((m_flPrevSkyboxScale != m_flSkyboxScale) || (m_vecPrevSkyboxOrigin != m_vecSkyboxOrigin)) { ConvertSkybox(); } } } //----------------------------------------------------------------------------- // Compute position + bounding box //----------------------------------------------------------------------------- void CSpriteTrail::ClientThink() { // Update the trail + bounding box UpdateTrail(); UpdateBoundingBox(); } //----------------------------------------------------------------------------- // Render bounds //----------------------------------------------------------------------------- void CSpriteTrail::GetRenderBounds( Vector& mins, Vector& maxs ) { mins = m_vecRenderMins; maxs = m_vecRenderMaxs; } //----------------------------------------------------------------------------- // Converts the trail when it changes skyboxes //----------------------------------------------------------------------------- void CSpriteTrail::ConvertSkybox() { for ( int i = 0; i < m_nStepCount; ++i ) { // This makes it so that we're always drawing to the current location TrailPoint_t *pPoint = GetTrailPoint(i); VectorSubtract( pPoint->m_vecScreenPos, m_vecPrevSkyboxOrigin, pPoint->m_vecScreenPos ); pPoint->m_vecScreenPos *= m_flPrevSkyboxScale / m_flSkyboxScale; VectorSubtract( pPoint->m_vecScreenPos, m_vecSkyboxOrigin, pPoint->m_vecScreenPos ); pPoint->m_flWidthVariance *= m_flPrevSkyboxScale / m_flSkyboxScale; } } //----------------------------------------------------------------------------- // Gets at the nth item in the list //----------------------------------------------------------------------------- TrailPoint_t *CSpriteTrail::GetTrailPoint( int n ) { Assert( n < MAX_SPRITE_TRAIL_POINTS ); COMPILE_TIME_ASSERT( (MAX_SPRITE_TRAIL_POINTS & (MAX_SPRITE_TRAIL_POINTS-1)) == 0 ); int nIndex = (n + m_nFirstStep) & MAX_SPRITE_TRAIL_MASK; return &m_vecSteps[nIndex]; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSpriteTrail::ComputeScreenPosition( Vector *pScreenPos ) { #if SCREEN_SPACE_TRAILS VMatrix viewMatrix; materials->GetMatrix( MATERIAL_VIEW, &viewMatrix ); *pScreenPos = viewMatrix * GetRenderOrigin(); #else *pScreenPos = GetRenderOrigin(); #endif } //----------------------------------------------------------------------------- // Compute position + bounding box //----------------------------------------------------------------------------- void CSpriteTrail::UpdateBoundingBox( void ) { Vector vecRenderOrigin = GetRenderOrigin(); m_vecRenderMins = vecRenderOrigin; m_vecRenderMaxs = vecRenderOrigin; float flMaxWidth = m_flStartWidth; if (( m_flEndWidth >= 0.0f ) && ( m_flEndWidth > m_flStartWidth )) { flMaxWidth = m_flEndWidth; } Vector mins, maxs; for ( int i = 0; i < m_nStepCount; ++i ) { TrailPoint_t *pPoint = GetTrailPoint(i); float flActualWidth = (flMaxWidth + pPoint->m_flWidthVariance) * 0.5f; Vector size( flActualWidth, flActualWidth, flActualWidth ); VectorSubtract( pPoint->m_vecScreenPos, size, mins ); VectorAdd( pPoint->m_vecScreenPos, size, maxs ); VectorMin( m_vecRenderMins, mins, m_vecRenderMins ); VectorMax( m_vecRenderMaxs, maxs, m_vecRenderMaxs ); } m_vecRenderMins -= vecRenderOrigin; m_vecRenderMaxs -= vecRenderOrigin; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSpriteTrail::UpdateTrail( void ) { // Can't update too quickly if ( m_flUpdateTime > gpGlobals->curtime ) return; Vector screenPos; ComputeScreenPosition( &screenPos ); TrailPoint_t *pLast = m_nStepCount ? GetTrailPoint( m_nStepCount-1 ) : NULL; if ( ( pLast == NULL ) || ( pLast->m_vecScreenPos.DistToSqr( screenPos ) > 4.0f ) ) { // If we're over our limit, steal the last point and put it up front if ( m_nStepCount >= MAX_SPRITE_TRAIL_POINTS ) { --m_nStepCount; ++m_nFirstStep; } // Save off its screen position, not its world position TrailPoint_t *pNewPoint = GetTrailPoint( m_nStepCount ); pNewPoint->m_vecScreenPos = screenPos; pNewPoint->m_flDieTime = gpGlobals->curtime + m_flLifeTime; pNewPoint->m_flWidthVariance = random->RandomFloat( -m_flStartWidthVariance, m_flStartWidthVariance ); if (pLast) { pNewPoint->m_flTexCoord = pLast->m_flTexCoord + pLast->m_vecScreenPos.DistTo( screenPos ) * m_flTextureRes; } else { pNewPoint->m_flTexCoord = 0.0f; } ++m_nStepCount; } // Don't update again for a bit m_flUpdateTime = gpGlobals->curtime + ( m_flLifeTime / (float) MAX_SPRITE_TRAIL_POINTS ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CSpriteTrail::DrawModel( int flags ) { VPROF_BUDGET( "CSpriteTrail::DrawModel", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); // Must have at least one point if ( m_nStepCount < 1 ) return 1; //See if we should draw if ( !IsVisible() || ( m_bReadyToDraw == false ) ) return 0; CEngineSprite *pSprite = Draw_SetSpriteTexture( GetModel(), m_flFrame, GetRenderMode() ); if ( pSprite == NULL ) return 0; // Specify all the segments. CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); CBeamSegDraw segDraw; segDraw.Start( pRenderContext, m_nStepCount + 1, pSprite->GetMaterial( GetRenderMode() ) ); // Setup the first point, always emanating from the attachment point TrailPoint_t *pLast = GetTrailPoint( m_nStepCount-1 ); TrailPoint_t currentPoint; currentPoint.m_flDieTime = gpGlobals->curtime + m_flLifeTime; ComputeScreenPosition( ¤tPoint.m_vecScreenPos ); currentPoint.m_flTexCoord = pLast->m_flTexCoord + currentPoint.m_vecScreenPos.DistTo(pLast->m_vecScreenPos) * m_flTextureRes; currentPoint.m_flWidthVariance = 0.0f; #if SCREEN_SPACE_TRAILS VMatrix viewMatrix; materials->GetMatrix( MATERIAL_VIEW, &viewMatrix ); viewMatrix = viewMatrix.InverseTR(); #endif TrailPoint_t *pPrevPoint = NULL; float flTailAlphaDist = m_flMinFadeLength; for ( int i = 0; i <= m_nStepCount; ++i ) { // This makes it so that we're always drawing to the current location TrailPoint_t *pPoint = (i != m_nStepCount) ? GetTrailPoint(i) : ¤tPoint; float flLifePerc = (pPoint->m_flDieTime - gpGlobals->curtime) / m_flLifeTime; flLifePerc = clamp( flLifePerc, 0.0f, 1.0f ); BeamSeg_t curSeg; curSeg.m_vColor.x = (float) m_clrRender->r / 255.0f; curSeg.m_vColor.y = (float) m_clrRender->g / 255.0f; curSeg.m_vColor.z = (float) m_clrRender->b / 255.0f; float flAlphaFade = flLifePerc; if ( flTailAlphaDist > 0.0f ) { if ( pPrevPoint ) { float flDist = pPoint->m_vecScreenPos.DistTo( pPrevPoint->m_vecScreenPos ); flTailAlphaDist -= flDist; } if ( flTailAlphaDist > 0.0f ) { float flTailFade = Lerp( (m_flMinFadeLength - flTailAlphaDist) / m_flMinFadeLength, 0.0f, 1.0f ); if ( flTailFade < flAlphaFade ) { flAlphaFade = flTailFade; } } } curSeg.m_flAlpha = ( (float) GetRenderBrightness() / 255.0f ) * flAlphaFade; #if SCREEN_SPACE_TRAILS curSeg.m_vPos = viewMatrix * pPoint->m_vecScreenPos; #else curSeg.m_vPos = pPoint->m_vecScreenPos; #endif if ( m_flEndWidth >= 0.0f ) { curSeg.m_flWidth = Lerp( flLifePerc, m_flEndWidth.Get(), m_flStartWidth.Get() ); } else { curSeg.m_flWidth = m_flStartWidth.Get(); } curSeg.m_flWidth += pPoint->m_flWidthVariance; if ( curSeg.m_flWidth < 0.0f ) { curSeg.m_flWidth = 0.0f; } curSeg.m_flTexCoord = pPoint->m_flTexCoord; segDraw.NextSeg( &curSeg ); // See if we're done with this bad boy if ( pPoint->m_flDieTime <= gpGlobals->curtime ) { // Push this back onto the top for use ++m_nFirstStep; --i; --m_nStepCount; } pPrevPoint = pPoint; } segDraw.End(); return 1; } //----------------------------------------------------------------------------- // Purpose: // Output : Vector const& //----------------------------------------------------------------------------- const Vector &CSpriteTrail::GetRenderOrigin( void ) { static Vector vOrigin; vOrigin = GetAbsOrigin(); if ( m_hAttachedToEntity ) { C_BaseEntity *ent = m_hAttachedToEntity->GetBaseEntity(); if ( ent ) { QAngle dummyAngles; ent->GetAttachment( m_nAttachment, vOrigin, dummyAngles ); } } return vOrigin; } const QAngle &CSpriteTrail::GetRenderAngles( void ) { return vec3_angle; } #endif //CLIENT_DLL #if !defined( CLIENT_DLL ) //----------------------------------------------------------------------------- // Purpose: // Input : *pSpriteName - // &origin - // animate - // Output : CSpriteTrail //----------------------------------------------------------------------------- CSpriteTrail *CSpriteTrail::SpriteTrailCreate( const char *pSpriteName, const Vector &origin, bool animate ) { CSpriteTrail *pSprite = CREATE_ENTITY( CSpriteTrail, "env_spritetrail" ); pSprite->SpriteInit( pSpriteName, origin ); pSprite->SetSolid( SOLID_NONE ); pSprite->SetMoveType( MOVETYPE_NOCLIP ); UTIL_SetSize( pSprite, vec3_origin, vec3_origin ); if ( animate ) { pSprite->TurnOn(); } return pSprite; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- int CSpriteTrail::ShouldTransmit( const CCheckTransmitInfo *pInfo ) { CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); Assert( pRecipientEntity->IsPlayer() ); CBasePlayer *pRecipientPlayer = static_cast( pRecipientEntity ); if ( !m_bDrawForMoveParent ) { if ( GetMoveParent() && !GetMoveParent()->IsPlayer() ) { if ( GetMoveParent()->GetMoveParent() == pRecipientPlayer ) { return FL_EDICT_DONTSEND; } } else if ( GetMoveParent() == pRecipientPlayer ) { return FL_EDICT_DONTSEND; } } return BaseClass::ShouldTransmit( pInfo ); } #endif //CLIENT_DLL == false #if defined( CLIENT_DLL ) // It's okay to draw attached entities with these sprites. const char* g_spriteWhiteList[] = { "effects/beam001_white.vmt", "effects/beam001_red.vmt", "effects/beam001_blu.vmt", }; //----------------------------------------------------------------------------- // Purpose: TF prevents drawing of any entity attached to players that aren't items in the inventory of the player. // This is to prevent servers creating fake cosmetic items and attaching them to players. //----------------------------------------------------------------------------- bool CSpriteTrail::ValidateEntityAttachedToPlayer( bool &bShouldRetry ) { bShouldRetry = false; return true; /* #if defined( TF_CLIENT_DLL ) const char *pszModelName = modelinfo->GetModelName( GetModel() ); if ( pszModelName && pszModelName[0] ) { // We attach sprites directly to players in some cases, such as phase trails on an evading scout for ( int i=0; i