//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: The Escort's Shield weapon effect // // $Workfile: $ // $Date: $ // //----------------------------------------------------------------------------- // $Log: $ // // $NoKeywords: $ //=============================================================================// #include "sheetsimulator.h" #include "edict.h" #include "collisionutils.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define COLLISION_PLANE_OFFSET 6.0f //----------------------------------------------------------------------------- // constructor, destructor //----------------------------------------------------------------------------- CSheetSimulator::CSheetSimulator( TraceLineFunc_t traceline, TraceHullFunc_t traceHull ) : m_pFixedPoint(0), m_ControlPoints(0), m_TraceLine(traceline), m_TraceHull(traceHull) { } CSheetSimulator::~CSheetSimulator() { if (m_pFixedPoint) { delete[] m_pFixedPoint; delete[] m_ControlPoints; delete[] m_pCollisionPlanes; delete[] m_pValidCollisionPlane; } delete[] m_Particle; } //----------------------------------------------------------------------------- // Initialization //----------------------------------------------------------------------------- void CSheetSimulator::Init( int w, int h, int fixedPointCount ) { m_ControlPointOffset.Init( 0, 0, 0 ); m_HorizontalCount = w; m_VerticalCount = h; m_Particle = new Particle_t[w * h]; m_FixedPointCount = fixedPointCount; if (fixedPointCount) { m_pFixedPoint = new Vector[fixedPointCount]; m_ControlPoints = new Vector[fixedPointCount]; m_pCollisionPlanes = new cplane_t[fixedPointCount]; m_pValidCollisionPlane = new bool[fixedPointCount]; } // Initialize distances and such m_Origin = Vector(0, 0, 0); for ( int i = 0; i < NumParticles(); ++i ) { m_Particle[i].m_Mass = 1.0f; m_Particle[i].m_Collided = false; m_Particle[i].m_Position = Vector(0,0,0); m_Particle[i].m_Velocity = Vector(0,0,0); } } //----------------------------------------------------------------------------- // adds springs //----------------------------------------------------------------------------- void CSheetSimulator::AddSpring( int p1, int p2, float restLength ) { int spring = m_Springs.AddToTail(); m_Springs[spring].m_Particle1 = p1; m_Springs[spring].m_Particle2 = p2; m_Springs[spring].m_RestLength = restLength; } void CSheetSimulator::AddFixedPointSpring( int fixedPoint, int p, float restLength ) { assert( fixedPoint < m_FixedPointCount ); int spring = m_Springs.AddToTail(); m_Springs[spring].m_Particle1 = p; m_Springs[spring].m_Particle2 = -(fixedPoint+1); m_Springs[spring].m_RestLength = restLength; } //----------------------------------------------------------------------------- // Gravity //----------------------------------------------------------------------------- void CSheetSimulator::SetGravityConstant( float g ) { m_GravityConstant = g; } void CSheetSimulator::AddGravityForce( int particle ) { m_Gravity.AddToTail( particle ); } //----------------------------------------------------------------------------- // spring constants.... //----------------------------------------------------------------------------- void CSheetSimulator::SetPointSpringConstant( float constant ) { m_PointSpringConstant = constant; } void CSheetSimulator::SetFixedSpringConstant( float constant ) { m_FixedSpringConstant = constant; } void CSheetSimulator::SetViscousDrag( float drag ) { m_ViscousDrag = drag; } void CSheetSimulator::SetSpringDampConstant( float damp ) { m_DampConstant = damp; } //----------------------------------------------------------------------------- // Sets the collision group //----------------------------------------------------------------------------- void CSheetSimulator::SetCollisionGroup( int group ) { m_CollisionGroup = group; } //----------------------------------------------------------------------------- // bounding box for collision //----------------------------------------------------------------------------- void CSheetSimulator::SetBoundingBox( Vector& mins, Vector& maxs ) { m_FrustumBoxMin = mins; m_FrustumBoxMax = maxs; } //----------------------------------------------------------------------------- // bounding box for collision //----------------------------------------------------------------------------- void CSheetSimulator::ComputeBounds( Vector& mins, Vector& maxs ) { VectorCopy( m_Particle[0].m_Position, mins ); VectorCopy( m_Particle[0].m_Position, maxs ); for (int i = 1; i < NumParticles(); ++i) { VectorMin( mins, m_Particle[i].m_Position, mins ); VectorMax( maxs, m_Particle[i].m_Position, maxs ); } mins -= m_Origin; maxs -= m_Origin; } //----------------------------------------------------------------------------- // Set the shield position //----------------------------------------------------------------------------- void CSheetSimulator::SetPosition( const Vector& origin, const QAngle& angles ) { // FIXME: Need a better metric for position reset if (m_Origin.DistToSqr(origin) > 1e3) { for ( int i = 0; i < NumParticles(); ++i ) { m_Particle[i].m_Position = origin; m_Particle[i].m_Velocity = Vector(0,0,0); } } m_Origin = origin; m_Angles = angles; ComputeControlPoints(); } //----------------------------------------------------------------------------- // get at the points //----------------------------------------------------------------------------- int CSheetSimulator::NumHorizontal() const { return m_HorizontalCount; } int CSheetSimulator::NumVertical() const { return m_VerticalCount; } int CSheetSimulator::PointCount() const { return m_HorizontalCount * m_VerticalCount; } const Vector& CSheetSimulator::GetPoint( int x, int y ) const { return m_Particle[y * NumHorizontal() + x].m_Position; } const Vector& CSheetSimulator::GetPoint( int i ) const { return m_Particle[i].m_Position; } // Fixed points Vector& CSheetSimulator::GetFixedPoint( int i ) { assert( i < m_FixedPointCount ); return m_pFixedPoint[i]; } //----------------------------------------------------------------------------- // For offseting the control points //----------------------------------------------------------------------------- void CSheetSimulator::SetControlPointOffset( const Vector& offset ) { VectorCopy( offset, m_ControlPointOffset ); } //----------------------------------------------------------------------------- // Compute the position of the fixed points //----------------------------------------------------------------------------- void CSheetSimulator::ComputeControlPoints() { //trace_t tr; Vector forward, right, up; AngleVectors(m_Angles, &forward, &right, &up); for (int i = 0; i < m_FixedPointCount; ++i) { VectorAdd( m_Origin, m_ControlPointOffset, m_ControlPoints[i] ); m_ControlPoints[i] += right * m_pFixedPoint[i].x; m_ControlPoints[i] += up * m_pFixedPoint[i].z; m_ControlPoints[i] += forward * m_pFixedPoint[i].y; } } //----------------------------------------------------------------------------- // Clear forces + velocities affecting each point //----------------------------------------------------------------------------- void CSheetSimulator::ClearForces() { int i; for ( i = 0; i < NumParticles(); ++i) { m_Particle[i].m_Force = Vector(0,0,0); } } //----------------------------------------------------------------------------- // Update the shield positions //----------------------------------------------------------------------------- void CSheetSimulator::ComputeForces() { float springConstant; int i; for ( i = 0; i < m_Springs.Size(); ++i ) { // 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, dv, force; if (m_Springs[i].m_Particle2 < 0) { // Case where we're connected to a control point dx = m_Particle[m_Springs[i].m_Particle1].m_Position - m_ControlPoints[- m_Springs[i].m_Particle2 - 1]; dv = m_Particle[m_Springs[i].m_Particle1].m_Velocity; springConstant = m_FixedSpringConstant; } else { // Case where we're connected to another part of the shield dx = m_Particle[m_Springs[i].m_Particle1].m_Position - m_Particle[m_Springs[i].m_Particle2].m_Position; dv = m_Particle[m_Springs[i].m_Particle1].m_Velocity - m_Particle[m_Springs[i].m_Particle2].m_Velocity; springConstant = m_PointSpringConstant; } float length = dx.Length(); if (length < 1e-6) continue; dx /= length; float springfactor = springConstant * ( length - m_Springs[i].m_RestLength); float dampfactor = m_DampConstant * DotProduct( dv, dx ); force = dx * -( springfactor + dampfactor ); m_Particle[m_Springs[i].m_Particle1].m_Force += force; if (m_Springs[i].m_Particle2 >= 0) m_Particle[m_Springs[i].m_Particle2].m_Force -= force; assert( IsFinite( m_Particle[m_Springs[i].m_Particle1].m_Force.x ) && IsFinite( m_Particle[m_Springs[i].m_Particle1].m_Force.y) && IsFinite( m_Particle[m_Springs[i].m_Particle1].m_Force.z) ); } // gravity term for (i = 0; i < m_Gravity.Count(); ++i) { m_Particle[m_Gravity[i]].m_Force.z -= m_Particle[m_Gravity[i]].m_Mass * m_GravityConstant; } // viscous drag term for (i = 0; i < NumParticles(); ++i) { // Factor out bad forces for surface contact // Do this before the drag term otherwise the drag will be too large if ((m_Particle[i].m_CollisionPlane) >= 0) { const Vector& planeNormal = m_pCollisionPlanes[m_Particle[i].m_CollisionPlane].normal; float perp = DotProduct( m_Particle[i].m_Force, planeNormal ); if (perp < 0) m_Particle[i].m_Force -= planeNormal * perp; } Vector drag = m_Particle[i].m_Velocity * m_ViscousDrag; m_Particle[i].m_Force -= drag; } } //----------------------------------------------------------------------------- // Used for testing neighbors against a particular plane //----------------------------------------------------------------------------- void CSheetSimulator::TestVertAgainstPlane( int vert, int plane, bool bFarTest ) { if (!m_pValidCollisionPlane[plane]) return; // Compute distance to the plane under consideration cplane_t* pPlane = &m_pCollisionPlanes[plane]; Ray_t ray; ray.Init( m_Origin, m_Particle[vert].m_Position ); float t = IntersectRayWithPlane( ray, *pPlane ); if (!bFarTest || (t <= 1.0f)) { if ((t < m_Particle[vert].m_CollisionDist) && (t >= 0.0f)) { m_Particle[vert].m_CollisionDist = t; m_Particle[vert].m_CollisionPlane = plane; } } } //----------------------------------------------------------------------------- // Collision detect //----------------------------------------------------------------------------- void CSheetSimulator::InitPosition( int i ) { // Collision test... // Check a line that goes farther out than our current point... // This will let us check for resting contact trace_t tr; m_TraceHull(m_Origin, m_ControlPoints[i], m_FrustumBoxMin, m_FrustumBoxMax, MASK_SOLID_BRUSHONLY, m_CollisionGroup, &tr ); if ( tr.fraction - 1.0 < 0 ) { memcpy( &m_pCollisionPlanes[i], &tr.plane, sizeof(cplane_t) ); m_pCollisionPlanes[i].dist += COLLISION_PLANE_OFFSET; // The trace endpos represents where the center of the box // ends up being. We actually want to choose a point which is on the // collision plane Vector delta; VectorSubtract( m_ControlPoints[i], m_Origin, delta ); int maxdist = VectorNormalize( delta ); float dist = (m_pCollisionPlanes[i].dist - DotProduct( m_Origin, m_pCollisionPlanes[i].normal )) / DotProduct( delta, m_pCollisionPlanes[i].normal ); if (dist > maxdist) dist = maxdist; VectorMA( m_Origin, dist, delta, m_Particle[i].m_Position ); m_pValidCollisionPlane[i] = true; } else if (tr.allsolid || tr.startsolid) { m_pValidCollisionPlane[i] = true; VectorSubtract( m_Origin, m_ControlPoints[i], m_pCollisionPlanes[i].normal ); VectorNormalize( m_pCollisionPlanes[i].normal ); m_pCollisionPlanes[i].dist = DotProduct( m_Origin, m_pCollisionPlanes[i].normal ) - COLLISION_PLANE_OFFSET; m_pCollisionPlanes[i].type = 3; } else { VectorCopy( m_ControlPoints[i], m_Particle[i].m_Position ); m_pValidCollisionPlane[i] = false; } } //----------------------------------------------------------------------------- // Collision detect //----------------------------------------------------------------------------- void CSheetSimulator::DetectCollision( int i, float flPlaneOffset ) { // Collision test... // Check a line that goes farther out than our current point... // This will let us check for resting contact // Vector endpt = m_Particle[i].m_Position; trace_t tr; m_TraceHull(m_Origin, m_ControlPoints[i], m_FrustumBoxMin, m_FrustumBoxMax, MASK_SOLID_BRUSHONLY, m_CollisionGroup, &tr ); if ( tr.fraction - 1.0 < 0 ) { m_pValidCollisionPlane[i] = true; memcpy( &m_pCollisionPlanes[i], &tr.plane, sizeof(cplane_t) ); m_pCollisionPlanes[i].dist += flPlaneOffset; } else if (tr.allsolid || tr.startsolid) { m_pValidCollisionPlane[i] = true; VectorSubtract( m_Origin, m_ControlPoints[i], m_pCollisionPlanes[i].normal ); VectorNormalize( m_pCollisionPlanes[i].normal ); m_pCollisionPlanes[i].dist = DotProduct( m_Origin, m_pCollisionPlanes[i].normal ) - flPlaneOffset; m_pCollisionPlanes[i].type = 3; } else { m_pValidCollisionPlane[i] = false; } } //----------------------------------------------------------------------------- // Collision plane fixup //----------------------------------------------------------------------------- void CSheetSimulator::DetermineBestCollisionPlane( bool bFarTest ) { // Check neighbors for violation of collision plane constraints for ( int i = 0; i < NumVertical(); ++i) { for ( int j = 0; j < NumHorizontal(); ++j) { // Here's the particle we're making springs for int idx = i * NumHorizontal() + j; // Now that we've seen all collisions, find the best collision plane // to use (look at myself and all neighbors). The best plane // is the one that comes closest to the origin. m_Particle[idx].m_CollisionDist = FLT_MAX; m_Particle[idx].m_CollisionPlane = -1; TestVertAgainstPlane( idx, idx, bFarTest ); if (j > 0) { TestVertAgainstPlane( idx, idx-1, bFarTest ); } if (j < NumHorizontal() - 1) { TestVertAgainstPlane( idx, idx+1, bFarTest ); } if (i > 0) TestVertAgainstPlane( idx, idx-NumHorizontal(), bFarTest ); if (i < NumVertical() - 1) TestVertAgainstPlane( idx, idx+NumHorizontal(), bFarTest ); } } } //----------------------------------------------------------------------------- // satify collision constraints //----------------------------------------------------------------------------- void CSheetSimulator::SatisfyCollisionConstraints() { // Eliminate velocity perp to a collision plane for ( int i = 0; i < NumParticles(); ++i ) { // The actual collision plane if (m_Particle[i].m_CollisionPlane >= 0) { cplane_t* pPlane = &m_pCollisionPlanes[m_Particle[i].m_CollisionPlane]; // Fix up position so it lies on the plane Vector delta = m_Particle[i].m_Position - m_Origin; m_Particle[i].m_Position = m_Origin + delta * m_Particle[i].m_CollisionDist; float perp = DotProduct( m_Particle[i].m_Velocity, pPlane->normal ); if (perp < 0) m_Particle[i].m_Velocity -= pPlane->normal * perp; } } } //----------------------------------------------------------------------------- // integrator //----------------------------------------------------------------------------- void CSheetSimulator::EulerStep( float dt ) { ClearForces(); ComputeForces(); // Update positions and velocities for ( int i = 0; i < NumParticles(); ++i) { m_Particle[i].m_Position += m_Particle[i].m_Velocity * dt; m_Particle[i].m_Velocity += m_Particle[i].m_Force * dt / m_Particle[i].m_Mass; assert( IsFinite( m_Particle[i].m_Velocity.x ) && IsFinite( m_Particle[i].m_Velocity.y) && IsFinite( m_Particle[i].m_Velocity.z) ); // clamp for stability float lensq = m_Particle[i].m_Velocity.LengthSqr(); if (lensq > 1e6) { m_Particle[i].m_Velocity *= 1e3 / sqrt(lensq); } } SatisfyCollisionConstraints(); } //----------------------------------------------------------------------------- // Update the shield position: //----------------------------------------------------------------------------- void CSheetSimulator::Simulate( float dt ) { // Initialize positions if necessary EulerStep(dt); } void CSheetSimulator::Simulate( float dt, int steps ) { ComputeControlPoints(); // Initialize positions if necessary dt /= steps; for (int i = 0; i < steps; ++i) { // Each step, we want to re-select the best collision planes to constrain // the movement by DetermineBestCollisionPlane(); EulerStep(dt); } } #define CLAMP_DIST 6.0 void CSheetSimulator::ClampPointsToCollisionPlanes() { // Find collision planes to clamp to DetermineBestCollisionPlane( false ); // Eliminate velocity perp to a collision plane for ( int i = 0; i < NumParticles(); ++i ) { // The actual collision plane if (m_Particle[i].m_CollisionPlane >= 0) { cplane_t* pPlane = &m_pCollisionPlanes[m_Particle[i].m_CollisionPlane]; // Make sure we have a close enough perpendicular distance to the plane... float flPerpDist = fabs ( DotProduct( m_Particle[i].m_Position, pPlane->normal ) - pPlane->dist ); if (flPerpDist >= CLAMP_DIST) continue; // Drop it along the perp VectorMA( m_Particle[i].m_Position, -flPerpDist, pPlane->normal, m_Particle[i].m_Position ); } } } //----------------------------------------------------------------------------- // Class to help dealing with the iterative computation //----------------------------------------------------------------------------- CIterativeSheetSimulator::CIterativeSheetSimulator( TraceLineFunc_t traceline, TraceHullFunc_t traceHull ) : CSheetSimulator( traceline, traceHull ), m_SimulationSteps(0) { } void CIterativeSheetSimulator::BeginSimulation( float dt, int steps, int substeps, int collisionCount ) { m_CurrentCollisionPt = 0; m_TimeStep = dt; m_SimulationSteps = steps; m_TotalSteps = steps; m_SubSteps = substeps; m_InitialPass = true; m_CollisionCount = collisionCount; } bool CIterativeSheetSimulator::Think( ) { assert( m_SimulationSteps >= 0 ); // Need to iteratively perform collision detection if (m_CurrentCollisionPt >= 0) { DetectCollisions(); return false; } else { // Simulate it a bunch of times Simulate(m_TimeStep, m_SubSteps); // Reset the collision point for collision detect m_CurrentCollisionPt = 0; --m_SimulationSteps; if ( m_SimulationSteps == 0 ) { ClampPointsToCollisionPlanes(); } return true; } } // Iterative collision detection void CIterativeSheetSimulator::DetectCollisions( void ) { for ( int i = 0; i < m_CollisionCount; ++i ) { if (m_InitialPass) { InitPosition( m_CurrentCollisionPt ); } else { float flOffset = COLLISION_PLANE_OFFSET * ( (float)(m_SimulationSteps - 1) / (float)(m_TotalSteps - 1) ); DetectCollision( m_CurrentCollisionPt, flOffset ); } if (++m_CurrentCollisionPt >= NumParticles()) { m_CurrentCollisionPt = -1; m_InitialPass = false; break; } } }