source-engine/game/shared/SpriteTrail.cpp

663 lines
18 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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( &currentPoint.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) : &currentPoint;
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<CBasePlayer*>( 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<ARRAYSIZE( g_spriteWhiteList ); ++i )
{
if ( FStrEq( pszModelName, g_spriteWhiteList[i] ) )
return true;
}
}
return false;
#else
return false;
#endif
*/
}
#endif