Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.

580 lines
16 KiB

5 years ago
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: The Escort's Shield weapon effect
//
// $Workfile: $
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "tf_shieldshared.h"
#include "edict.h"
#include "mathlib/vmatrix.h"
#include "engine/IEngineTrace.h"
#ifdef CLIENT_DLL
#include "cdll_client_int.h"
#else
#include "gameinterface.h"
#endif
BEGIN_PREDICTION_DATA_NO_BASE( CShieldEffect )
DEFINE_FIELD( m_TestPoint, FIELD_INTEGER ),
DEFINE_FIELD( m_Position, FIELD_VECTOR ),
DEFINE_FIELD( m_Velocity, FIELD_VECTOR ),
DEFINE_FIELD( m_CurrentAngles, FIELD_VECTOR ),
DEFINE_FIELD( m_Theta, FIELD_FLOAT ),
DEFINE_FIELD( m_Phi, FIELD_FLOAT ),
DEFINE_FIELD( m_ThetaVelocity, FIELD_FLOAT ),
DEFINE_FIELD( m_PhiVelocity, FIELD_FLOAT ),
DEFINE_FIELD( m_vecDesiredOrigin, FIELD_VECTOR ),
DEFINE_FIELD( m_angDesiredAngles, FIELD_VECTOR ),
DEFINE_FIELD( m_ShieldTheta, FIELD_FLOAT ),
DEFINE_FIELD( m_ShieldPhi, FIELD_FLOAT ),
END_PREDICTION_DATA()
//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
CShieldEffect::CShieldEffect( )
{
}
//-----------------------------------------------------------------------------
// compute rest positions of the springs
//-----------------------------------------------------------------------------
void CShieldEffect::ComputeRestPositions()
{
int i;
m_vecRenderMins.Init( FLT_MAX, FLT_MAX, FLT_MAX );
m_vecRenderMaxs.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX );
// Set the initial directions and distances (in shield space)...
for ( i = 0; i < SHIELD_NUM_VERTICAL_POINTS; ++i)
{
// Choose phi centered at pi/2
float phi = (M_PI - m_ShieldPhi) * 0.5f + m_ShieldPhi *
(float)i / (float)(SHIELD_NUM_VERTICAL_POINTS - 1);
for (int j = 0; j < SHIELD_NUM_HORIZONTAL_POINTS; ++j)
{
// Choose theta centered at pi/2 also (y, or forward axis)
float theta = (M_PI - m_ShieldTheta) * 0.5f + m_ShieldTheta *
(float)j / (float)(SHIELD_NUM_HORIZONTAL_POINTS - 1);
int idx = i * SHIELD_NUM_HORIZONTAL_POINTS + j;
m_pFixedDirection[idx].x = cos(theta) * sin(phi);
m_pFixedDirection[idx].y = sin(theta) * sin(phi);
m_pFixedDirection[idx].z = cos(phi);
m_pFixedDirection[idx] *= m_RestLength;
VectorMin( m_vecRenderMins, m_pFixedDirection[idx], m_vecRenderMins );
VectorMax( m_vecRenderMaxs, m_pFixedDirection[idx], m_vecRenderMaxs );
}
}
// Compute box for fake volume testing
Vector dist = m_pFixedDirection[0] - m_pFixedDirection[1];
float l = dist.Length(); // * m_RestLength;
SetShieldPanelSize( Vector( -l * 0.25f, -l * 0.25f, -l * 0.25f),
Vector( l * 0.25f, l * 0.25f, l * 0.25f) );
}
//-----------------------------------------------------------------------------
// Sets orientation + position
//-----------------------------------------------------------------------------
void CShieldEffect::SetDesiredOrigin( const Vector& origin )
{
VectorCopy( origin, m_vecDesiredOrigin );
}
void CShieldEffect::SetDesiredAngles( const QAngle& angles )
{
VectorCopy( angles, m_angDesiredAngles );
}
const QAngle& CShieldEffect::GetDesiredAngles() const
{
return m_angDesiredAngles;
}
//-----------------------------------------------------------------------------
// Gets a point...
//-----------------------------------------------------------------------------
const Vector& CShieldEffect::GetPoint( int x, int y ) const
{
return m_pControlPoint[ x + y * SHIELD_NUM_HORIZONTAL_POINTS ];
}
const Vector& CShieldEffect::GetPoint( int i ) const
{
return m_pControlPoint[ i ];
}
Vector& CShieldEffect::GetPoint( int i )
{
return m_pControlPoint[ i ];
}
//-----------------------------------------------------------------------------
// Sets the collision group
//-----------------------------------------------------------------------------
void CShieldEffect::SetCollisionGroup( int group )
{
m_CollisionGroup = group;
}
//-----------------------------------------------------------------------------
// Hooks in active bits...
//-----------------------------------------------------------------------------
void CShieldEffect::SetActiveVertexList( IActiveVertList *pActiveVerts )
{
m_pActiveVerts = pActiveVerts;
// No points are visible initially
for ( int i=0; i < SHIELD_VERTEX_BYTES*8; i++ )
m_pActiveVerts->SetActiveVertState( i, 0 );
}
//-----------------------------------------------------------------------------
// Is a particular vertex active?
//-----------------------------------------------------------------------------
bool CShieldEffect::IsVertexActive( int x, int y ) const
{
if ((x < 0) || (y < 0) || (x >= SHIELD_NUM_HORIZONTAL_POINTS) ||
(y >= SHIELD_NUM_VERTICAL_POINTS))
{
return false;
}
int idx = x + (SHIELD_NUM_HORIZONTAL_POINTS) * y;
return m_pActiveVerts->GetActiveVertState( idx ) != 0;
}
//-----------------------------------------------------------------------------
// Is a particular panel active?
//-----------------------------------------------------------------------------
bool CShieldEffect::IsPanelActive( int x, int y ) const
{
if ((x < 0) || (y < 0) || (x >= SHIELD_HORIZONTAL_PANEL_COUNT) ||
(y >= SHIELD_VERTICAL_PANEL_COUNT))
{
return false;
}
int idx = x + (SHIELD_HORIZONTAL_PANEL_COUNT) * y;
return m_pActivePanels[idx];
}
//-----------------------------------------------------------------------------
// Recompute whether the panels are active or not
//-----------------------------------------------------------------------------
void CShieldEffect::ComputePanelActivity()
{
// Check neighbors to see how many squares we've got
for ( int i = 0; i < SHIELD_NUM_HORIZONTAL_POINTS - 1; ++i)
{
for ( int j = 0; j < SHIELD_NUM_VERTICAL_POINTS - 1; ++j)
{
int idx = i + j * (SHIELD_NUM_HORIZONTAL_POINTS - 1);
// Test the neighbors
m_pActivePanels[idx] =
IsVertexActive( i, j ) ||
IsVertexActive( i+1, j ) ||
IsVertexActive( i, j+1 ) ||
IsVertexActive( i+1, j+1 );
}
}
}
//-----------------------------------------------------------------------------
// Compute vertex activity
//-----------------------------------------------------------------------------
enum
{
SHIELD_TESTS_PER_FRAME = 4
};
void CShieldEffect::ComputeVertexActivity()
{
int i;
for ( i = 0; i < SHIELD_TESTS_PER_FRAME; ++i )
{
// Visit points in random order...
int pt = m_PointList[m_TestPoint];
// Collision test...
// Check a line that goes farther out than our current point...
// This will let us check for resting contact
trace_t tr;
CTraceFilterWorldOnly traceFilter;
UTIL_TraceHull( m_Position, m_pControlPoint[pt],
m_PanelBoxMin, m_PanelBoxMax, MASK_SOLID_BRUSHONLY, &traceFilter, &tr );
bool isActive = (!tr.allsolid) && ( (tr.fraction - 1.0f) >= 0.0f );
m_pActiveVerts->SetActiveVertState( pt, isActive );
if (++m_TestPoint >= SHIELD_NUM_CONTROL_POINTS)
m_TestPoint = 0;
}
ComputePanelActivity();
}
//-----------------------------------------------------------------------------
// bounding box for collision
//-----------------------------------------------------------------------------
void CShieldEffect::SetShieldPanelSize( Vector& mins, Vector& maxs )
{
m_PanelBoxMin = mins;
m_PanelBoxMax = maxs;
}
//-----------------------------------------------------------------------------
// bounding box for collision
//-----------------------------------------------------------------------------
void CShieldEffect::ComputeBounds( Vector& mins, Vector& maxs )
{
VectorCopy( m_pControlPoint[0], mins );
VectorCopy( m_pControlPoint[0], maxs );
for (int i = 1; i < SHIELD_NUM_CONTROL_POINTS; ++i)
{
VectorMin( mins, m_pControlPoint[i], mins );
VectorMax( maxs, m_pControlPoint[i], maxs );
}
// Bounds are in local coords
mins -= m_Position;
maxs -= m_Position;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CShieldEffect::Precache( void )
{
m_RestLength = 200.0;
m_SpringConstant = 30.0f;
m_DampConstant = 4.0f;
m_ViscousDrag = 4.0f;
m_Mass = 1.0f;
m_AngularSpringConstant = 2.0f;
m_AngularViscousDrag = 4.0f;
SetThetaPhi( SHIELD_INITIAL_THETA, SHIELD_INITIAL_PHI );
}
//-----------------------------------------------------------------------------
// Compute orientation matrix:
//-----------------------------------------------------------------------------
void CShieldEffect::ComputeOrientationMatrix()
{
// Generate the orientation matrix from theta and phi...
// X = forward direction, Y - left direction
Vector forward, left, up;
forward.x = cos(m_Theta) * sin(m_Phi);
forward.y = sin(m_Theta) * sin(m_Phi);
forward.z = cos(m_Phi);
left.x = -forward.y;
left.y = forward.x;
left.z = 0;
if ( VectorNormalize(left) == 0.0f )
left.Init( 0.0f, 1.0f, 0.0f );
CrossProduct( forward, left, up );
m_Orientation.SetBasisVectors( forward, left, up );
// Turn the current matrix into angles...
MatrixToAngles( m_Orientation, m_CurrentAngles );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CShieldEffect::Spawn( const Vector& currentPosition, const QAngle& currentAngles )
{
Precache();
VectorCopy( currentPosition, m_Position );
m_Velocity.Init();
Vector forward;
AngleVectors( currentAngles, &forward, 0, 0 );
m_Phi = acos( forward.z );
m_Theta = atan2( forward.y, forward.x );
m_PhiVelocity = 0.0f;
m_ThetaVelocity = 0.0f;
ComputeOrientationMatrix();
VectorCopy( currentAngles, m_CurrentAngles );
VectorCopy( currentAngles, m_angDesiredAngles );
// No points are visible initially
memset( m_pActivePanels, 0, SHIELD_PANELS_COUNT );
m_TestPoint = 0;
// Choose random order to visit shield verts
int i;
for ( i = 0; i < SHIELD_NUM_CONTROL_POINTS; ++i )
{
m_PointList[i] = i;
}
for ( i = 0; i < SHIELD_NUM_CONTROL_POINTS; ++i )
{
int j = rand() % SHIELD_NUM_CONTROL_POINTS;
swap( m_PointList[i], m_PointList[j] );
}
}
//-----------------------------------------------------------------------------
// Computes the opacity....
//-----------------------------------------------------------------------------
float CShieldEffect::ComputeOpacity( const Vector& pt, const Vector& center ) const
{
float dist = pt.DistTo( center ) / m_RestLength;
if (dist > 1.0)
dist = 1.0f;
return 32 + (1.0 - dist) * 192;
}
//-----------------------------------------------------------------------------
// Computes control points
//-----------------------------------------------------------------------------
void CShieldEffect::ComputeControlPoints()
{
Vector forward, right, up;
AngleVectors(m_CurrentAngles, &forward, &right, &up);
for ( int i = 0; i < SHIELD_NUM_CONTROL_POINTS; ++i )
{
// Compute the world space position...
VectorCopy( m_Position, m_pControlPoint[i] );
m_pControlPoint[i] += right * m_pFixedDirection[i].x;
m_pControlPoint[i] += up * m_pFixedDirection[i].z;
m_pControlPoint[i] += forward * m_pFixedDirection[i].y;
}
}
//-----------------------------------------------------------------------------
// Gets the frustum size
//-----------------------------------------------------------------------------
void CShieldEffect::GetPanelSize( Vector& mins, Vector& maxs ) const
{
VectorCopy( m_PanelBoxMin, mins );
VectorCopy( m_PanelBoxMax, maxs );
}
void CShieldEffect::SetAngularSpringConstant( float flConstant )
{
m_AngularSpringConstant = flConstant;
}
//-----------------------------------------------------------------------------
// Set the shield theta & phi
//-----------------------------------------------------------------------------
void CShieldEffect::SetThetaPhi( float flTheta, float flPhi )
{
m_ShieldTheta = M_PI * flTheta / 180.0f;
m_ShieldPhi = M_PI * flPhi / 180.0f;
// Computes the rest positions
ComputeRestPositions();
}
//-----------------------------------------------------------------------------
// The current position (computed by Simulate on the server)
//-----------------------------------------------------------------------------
const Vector& CShieldEffect::GetCurrentPosition()
{
return m_Position;
}
void CShieldEffect::SetCurrentPosition( const Vector& pos )
{
m_Position = pos;
}
void CShieldEffect::SetCurrentAngles( const QAngle& angles )
{
VectorCopy( angles, m_CurrentAngles );
}
//-----------------------------------------------------------------------------
// Simulate the center of mass
//-----------------------------------------------------------------------------
void CShieldEffect::SimulateTranslation( float dt )
{
// Hook's law for a damped spring:
// got two particles, a and b with positions xa and xb and velocities va and vb
// and l = xa - xb
// fa = -( ks * (|l| - r) + kd * (va - vb) dot (l) / |l|) * l/|l|
Vector dx, force;
// Case where we're connected to a control point
dx = m_Position - m_vecDesiredOrigin;
// rest condition
float length = dx.Length();
float speedSq = m_Velocity.LengthSqr();
if ((length < 1e-3) && (speedSq < 1e-6))
return;
// Compute force
if (length > 1e-3)
dx /= length;
else
dx.Init( 0, 0, 0 );
float springfactor = m_SpringConstant * length;
float dampfactor = m_DampConstant * DotProduct( m_Velocity, dx );
force = dx * -( springfactor + dampfactor );
assert( force.IsValid( ) );
Vector drag = m_Velocity * m_ViscousDrag;
force -= drag;
// Update position and velocity
m_Position += m_Velocity * dt;
m_Velocity += force * dt / m_Mass;
assert( m_Velocity.IsValid( ) );
// clamp for stability
if (speedSq > 1e6)
{
m_Velocity *= 1e3 / sqrt(speedSq);
}
}
void CShieldEffect::SimulateRotation( float dt, const Vector& forward )
{
// Here's a torsional spring for the angular component...
// A little tricky: We need to actually think about 2 torsional springs,
// one in thetha (x-y plane), and one in phi (z-plane)
float phi2 = acos( forward.z );
float dPhi = m_Phi - phi2;
float theta2 = atan2( forward.y, forward.x );
float dTheta = (m_Theta - theta2);
if (dTheta > M_PI)
dTheta -= 2 * M_PI;
else if (dTheta < -M_PI)
dTheta += 2 * M_PI;
// rest condition...
if ((fabs(dTheta) < 1e-3) && (fabs(m_ThetaVelocity) < 1e-6) &&
(fabs(dPhi) < 1e-3) && (fabs(m_PhiVelocity) < 1e-6))
{
return;
}
float springfactor = m_AngularSpringConstant * dTheta;
float torqueTheta = -springfactor; // + dampfactor);
torqueTheta -= m_ThetaVelocity * m_AngularViscousDrag;
springfactor = m_AngularSpringConstant * dPhi;
float torqueTPhi = -springfactor; // + dampfactor);
torqueTPhi -= m_PhiVelocity * m_AngularViscousDrag;
// Update position and velocity
m_Theta += m_ThetaVelocity * dt;
m_ThetaVelocity += torqueTheta * dt;
m_Phi += m_PhiVelocity * dt;
m_PhiVelocity += torqueTPhi * dt;
// clamp for stability
if (fabs(m_ThetaVelocity) > 1e2)
{
m_ThetaVelocity *= 1e2 / m_ThetaVelocity;
}
if (fabs(m_PhiVelocity) > 1e2)
{
m_PhiVelocity *= 1e2 / m_PhiVelocity;
}
ComputeOrientationMatrix();
}
//-----------------------------------------------------------------------------
// Update the shield position:
//-----------------------------------------------------------------------------
void CShieldEffect::Simulate( float dt )
{
// We're gonna basically assume a spring connected to the center control point
Vector forward;
AngleVectors(m_angDesiredAngles, &forward, 0, 0);
// We've got two springs: a spring connected to the origin
// and a torsional spring connected to the view direction.
// Stiff spring, subdivide time....
dt /= SHIELD_TIME_SUBVISIBIONS;
for (int i = 0; i < SHIELD_TIME_SUBVISIBIONS; ++i)
{
SimulateTranslation( dt );
SimulateRotation( dt, forward );
}
ComputeControlPoints();
}
//-----------------------------------------------------------------------------
// Determines shield obstructions
//-----------------------------------------------------------------------------
static inline bool IsPointValid( bool* pActivePoints, int i, int j )
{
// Here's the control point we're checking
int idx = j * SHIELD_NUM_HORIZONTAL_POINTS + i;
return pActivePoints[idx];
}