//========= 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]; }