Team Fortress 2 Source Code as on 22/4/2020
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.

675 lines
19 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: The Escort's Shield weapon effect
  4. //
  5. // $Workfile: $
  6. // $Date: $
  7. //
  8. //-----------------------------------------------------------------------------
  9. // $Log: $
  10. //
  11. // $NoKeywords: $
  12. //=============================================================================//
  13. #include "sheetsimulator.h"
  14. #include "edict.h"
  15. #include "collisionutils.h"
  16. // memdbgon must be the last include file in a .cpp file!!!
  17. #include "tier0/memdbgon.h"
  18. #define COLLISION_PLANE_OFFSET 6.0f
  19. //-----------------------------------------------------------------------------
  20. // constructor, destructor
  21. //-----------------------------------------------------------------------------
  22. CSheetSimulator::CSheetSimulator( TraceLineFunc_t traceline,
  23. TraceHullFunc_t traceHull ) :
  24. m_pFixedPoint(0), m_ControlPoints(0),
  25. m_TraceLine(traceline), m_TraceHull(traceHull)
  26. {
  27. }
  28. CSheetSimulator::~CSheetSimulator()
  29. {
  30. if (m_pFixedPoint)
  31. {
  32. delete[] m_pFixedPoint;
  33. delete[] m_ControlPoints;
  34. delete[] m_pCollisionPlanes;
  35. delete[] m_pValidCollisionPlane;
  36. }
  37. delete[] m_Particle;
  38. }
  39. //-----------------------------------------------------------------------------
  40. // Initialization
  41. //-----------------------------------------------------------------------------
  42. void CSheetSimulator::Init( int w, int h, int fixedPointCount )
  43. {
  44. m_ControlPointOffset.Init( 0, 0, 0 );
  45. m_HorizontalCount = w;
  46. m_VerticalCount = h;
  47. m_Particle = new Particle_t[w * h];
  48. m_FixedPointCount = fixedPointCount;
  49. if (fixedPointCount)
  50. {
  51. m_pFixedPoint = new Vector[fixedPointCount];
  52. m_ControlPoints = new Vector[fixedPointCount];
  53. m_pCollisionPlanes = new cplane_t[fixedPointCount];
  54. m_pValidCollisionPlane = new bool[fixedPointCount];
  55. }
  56. // Initialize distances and such
  57. m_Origin = Vector(0, 0, 0);
  58. for ( int i = 0; i < NumParticles(); ++i )
  59. {
  60. m_Particle[i].m_Mass = 1.0f;
  61. m_Particle[i].m_Collided = false;
  62. m_Particle[i].m_Position = Vector(0,0,0);
  63. m_Particle[i].m_Velocity = Vector(0,0,0);
  64. }
  65. }
  66. //-----------------------------------------------------------------------------
  67. // adds springs
  68. //-----------------------------------------------------------------------------
  69. void CSheetSimulator::AddSpring( int p1, int p2, float restLength )
  70. {
  71. int spring = m_Springs.AddToTail();
  72. m_Springs[spring].m_Particle1 = p1;
  73. m_Springs[spring].m_Particle2 = p2;
  74. m_Springs[spring].m_RestLength = restLength;
  75. }
  76. void CSheetSimulator::AddFixedPointSpring( int fixedPoint, int p, float restLength )
  77. {
  78. assert( fixedPoint < m_FixedPointCount );
  79. int spring = m_Springs.AddToTail();
  80. m_Springs[spring].m_Particle1 = p;
  81. m_Springs[spring].m_Particle2 = -(fixedPoint+1);
  82. m_Springs[spring].m_RestLength = restLength;
  83. }
  84. //-----------------------------------------------------------------------------
  85. // Gravity
  86. //-----------------------------------------------------------------------------
  87. void CSheetSimulator::SetGravityConstant( float g )
  88. {
  89. m_GravityConstant = g;
  90. }
  91. void CSheetSimulator::AddGravityForce( int particle )
  92. {
  93. m_Gravity.AddToTail( particle );
  94. }
  95. //-----------------------------------------------------------------------------
  96. // spring constants....
  97. //-----------------------------------------------------------------------------
  98. void CSheetSimulator::SetPointSpringConstant( float constant )
  99. {
  100. m_PointSpringConstant = constant;
  101. }
  102. void CSheetSimulator::SetFixedSpringConstant( float constant )
  103. {
  104. m_FixedSpringConstant = constant;
  105. }
  106. void CSheetSimulator::SetViscousDrag( float drag )
  107. {
  108. m_ViscousDrag = drag;
  109. }
  110. void CSheetSimulator::SetSpringDampConstant( float damp )
  111. {
  112. m_DampConstant = damp;
  113. }
  114. //-----------------------------------------------------------------------------
  115. // Sets the collision group
  116. //-----------------------------------------------------------------------------
  117. void CSheetSimulator::SetCollisionGroup( int group )
  118. {
  119. m_CollisionGroup = group;
  120. }
  121. //-----------------------------------------------------------------------------
  122. // bounding box for collision
  123. //-----------------------------------------------------------------------------
  124. void CSheetSimulator::SetBoundingBox( Vector& mins, Vector& maxs )
  125. {
  126. m_FrustumBoxMin = mins;
  127. m_FrustumBoxMax = maxs;
  128. }
  129. //-----------------------------------------------------------------------------
  130. // bounding box for collision
  131. //-----------------------------------------------------------------------------
  132. void CSheetSimulator::ComputeBounds( Vector& mins, Vector& maxs )
  133. {
  134. VectorCopy( m_Particle[0].m_Position, mins );
  135. VectorCopy( m_Particle[0].m_Position, maxs );
  136. for (int i = 1; i < NumParticles(); ++i)
  137. {
  138. VectorMin( mins, m_Particle[i].m_Position, mins );
  139. VectorMax( maxs, m_Particle[i].m_Position, maxs );
  140. }
  141. mins -= m_Origin;
  142. maxs -= m_Origin;
  143. }
  144. //-----------------------------------------------------------------------------
  145. // Set the shield position
  146. //-----------------------------------------------------------------------------
  147. void CSheetSimulator::SetPosition( const Vector& origin, const QAngle& angles )
  148. {
  149. // FIXME: Need a better metric for position reset
  150. if (m_Origin.DistToSqr(origin) > 1e3)
  151. {
  152. for ( int i = 0; i < NumParticles(); ++i )
  153. {
  154. m_Particle[i].m_Position = origin;
  155. m_Particle[i].m_Velocity = Vector(0,0,0);
  156. }
  157. }
  158. m_Origin = origin;
  159. m_Angles = angles;
  160. ComputeControlPoints();
  161. }
  162. //-----------------------------------------------------------------------------
  163. // get at the points
  164. //-----------------------------------------------------------------------------
  165. int CSheetSimulator::NumHorizontal() const
  166. {
  167. return m_HorizontalCount;
  168. }
  169. int CSheetSimulator::NumVertical() const
  170. {
  171. return m_VerticalCount;
  172. }
  173. int CSheetSimulator::PointCount() const
  174. {
  175. return m_HorizontalCount * m_VerticalCount;
  176. }
  177. const Vector& CSheetSimulator::GetPoint( int x, int y ) const
  178. {
  179. return m_Particle[y * NumHorizontal() + x].m_Position;
  180. }
  181. const Vector& CSheetSimulator::GetPoint( int i ) const
  182. {
  183. return m_Particle[i].m_Position;
  184. }
  185. // Fixed points
  186. Vector& CSheetSimulator::GetFixedPoint( int i )
  187. {
  188. assert( i < m_FixedPointCount );
  189. return m_pFixedPoint[i];
  190. }
  191. //-----------------------------------------------------------------------------
  192. // For offseting the control points
  193. //-----------------------------------------------------------------------------
  194. void CSheetSimulator::SetControlPointOffset( const Vector& offset )
  195. {
  196. VectorCopy( offset, m_ControlPointOffset );
  197. }
  198. //-----------------------------------------------------------------------------
  199. // Compute the position of the fixed points
  200. //-----------------------------------------------------------------------------
  201. void CSheetSimulator::ComputeControlPoints()
  202. {
  203. //trace_t tr;
  204. Vector forward, right, up;
  205. AngleVectors(m_Angles, &forward, &right, &up);
  206. for (int i = 0; i < m_FixedPointCount; ++i)
  207. {
  208. VectorAdd( m_Origin, m_ControlPointOffset, m_ControlPoints[i] );
  209. m_ControlPoints[i] += right * m_pFixedPoint[i].x;
  210. m_ControlPoints[i] += up * m_pFixedPoint[i].z;
  211. m_ControlPoints[i] += forward * m_pFixedPoint[i].y;
  212. }
  213. }
  214. //-----------------------------------------------------------------------------
  215. // Clear forces + velocities affecting each point
  216. //-----------------------------------------------------------------------------
  217. void CSheetSimulator::ClearForces()
  218. {
  219. int i;
  220. for ( i = 0; i < NumParticles(); ++i)
  221. {
  222. m_Particle[i].m_Force = Vector(0,0,0);
  223. }
  224. }
  225. //-----------------------------------------------------------------------------
  226. // Update the shield positions
  227. //-----------------------------------------------------------------------------
  228. void CSheetSimulator::ComputeForces()
  229. {
  230. float springConstant;
  231. int i;
  232. for ( i = 0; i < m_Springs.Size(); ++i )
  233. {
  234. // Hook's law for a damped spring:
  235. // got two particles, a and b with positions xa and xb and velocities va and vb
  236. // and l = xa - xb
  237. // fa = -( ks * (|l| - r) + kd * (va - vb) dot (l) / |l|) * l/|l|
  238. Vector dx, dv, force;
  239. if (m_Springs[i].m_Particle2 < 0)
  240. {
  241. // Case where we're connected to a control point
  242. dx = m_Particle[m_Springs[i].m_Particle1].m_Position -
  243. m_ControlPoints[- m_Springs[i].m_Particle2 - 1];
  244. dv = m_Particle[m_Springs[i].m_Particle1].m_Velocity;
  245. springConstant = m_FixedSpringConstant;
  246. }
  247. else
  248. {
  249. // Case where we're connected to another part of the shield
  250. dx = m_Particle[m_Springs[i].m_Particle1].m_Position -
  251. m_Particle[m_Springs[i].m_Particle2].m_Position;
  252. dv = m_Particle[m_Springs[i].m_Particle1].m_Velocity -
  253. m_Particle[m_Springs[i].m_Particle2].m_Velocity;
  254. springConstant = m_PointSpringConstant;
  255. }
  256. float length = dx.Length();
  257. if (length < 1e-6)
  258. continue;
  259. dx /= length;
  260. float springfactor = springConstant * ( length - m_Springs[i].m_RestLength);
  261. float dampfactor = m_DampConstant * DotProduct( dv, dx );
  262. force = dx * -( springfactor + dampfactor );
  263. m_Particle[m_Springs[i].m_Particle1].m_Force += force;
  264. if (m_Springs[i].m_Particle2 >= 0)
  265. m_Particle[m_Springs[i].m_Particle2].m_Force -= force;
  266. assert( IsFinite( m_Particle[m_Springs[i].m_Particle1].m_Force.x ) &&
  267. IsFinite( m_Particle[m_Springs[i].m_Particle1].m_Force.y) &&
  268. IsFinite( m_Particle[m_Springs[i].m_Particle1].m_Force.z) );
  269. }
  270. // gravity term
  271. for (i = 0; i < m_Gravity.Count(); ++i)
  272. {
  273. m_Particle[m_Gravity[i]].m_Force.z -= m_Particle[m_Gravity[i]].m_Mass * m_GravityConstant;
  274. }
  275. // viscous drag term
  276. for (i = 0; i < NumParticles(); ++i)
  277. {
  278. // Factor out bad forces for surface contact
  279. // Do this before the drag term otherwise the drag will be too large
  280. if ((m_Particle[i].m_CollisionPlane) >= 0)
  281. {
  282. const Vector& planeNormal = m_pCollisionPlanes[m_Particle[i].m_CollisionPlane].normal;
  283. float perp = DotProduct( m_Particle[i].m_Force, planeNormal );
  284. if (perp < 0)
  285. m_Particle[i].m_Force -= planeNormal * perp;
  286. }
  287. Vector drag = m_Particle[i].m_Velocity * m_ViscousDrag;
  288. m_Particle[i].m_Force -= drag;
  289. }
  290. }
  291. //-----------------------------------------------------------------------------
  292. // Used for testing neighbors against a particular plane
  293. //-----------------------------------------------------------------------------
  294. void CSheetSimulator::TestVertAgainstPlane( int vert, int plane, bool bFarTest )
  295. {
  296. if (!m_pValidCollisionPlane[plane])
  297. return;
  298. // Compute distance to the plane under consideration
  299. cplane_t* pPlane = &m_pCollisionPlanes[plane];
  300. Ray_t ray;
  301. ray.Init( m_Origin, m_Particle[vert].m_Position );
  302. float t = IntersectRayWithPlane( ray, *pPlane );
  303. if (!bFarTest || (t <= 1.0f))
  304. {
  305. if ((t < m_Particle[vert].m_CollisionDist) && (t >= 0.0f))
  306. {
  307. m_Particle[vert].m_CollisionDist = t;
  308. m_Particle[vert].m_CollisionPlane = plane;
  309. }
  310. }
  311. }
  312. //-----------------------------------------------------------------------------
  313. // Collision detect
  314. //-----------------------------------------------------------------------------
  315. void CSheetSimulator::InitPosition( int i )
  316. {
  317. // Collision test...
  318. // Check a line that goes farther out than our current point...
  319. // This will let us check for resting contact
  320. trace_t tr;
  321. m_TraceHull(m_Origin, m_ControlPoints[i], m_FrustumBoxMin, m_FrustumBoxMax,
  322. MASK_SOLID_BRUSHONLY, m_CollisionGroup, &tr );
  323. if ( tr.fraction - 1.0 < 0 )
  324. {
  325. memcpy( &m_pCollisionPlanes[i], &tr.plane, sizeof(cplane_t) );
  326. m_pCollisionPlanes[i].dist += COLLISION_PLANE_OFFSET;
  327. // The trace endpos represents where the center of the box
  328. // ends up being. We actually want to choose a point which is on the
  329. // collision plane
  330. Vector delta;
  331. VectorSubtract( m_ControlPoints[i], m_Origin, delta );
  332. int maxdist = VectorNormalize( delta );
  333. float dist = (m_pCollisionPlanes[i].dist - DotProduct( m_Origin, m_pCollisionPlanes[i].normal )) /
  334. DotProduct( delta, m_pCollisionPlanes[i].normal );
  335. if (dist > maxdist)
  336. dist = maxdist;
  337. VectorMA( m_Origin, dist, delta, m_Particle[i].m_Position );
  338. m_pValidCollisionPlane[i] = true;
  339. }
  340. else if (tr.allsolid || tr.startsolid)
  341. {
  342. m_pValidCollisionPlane[i] = true;
  343. VectorSubtract( m_Origin, m_ControlPoints[i], m_pCollisionPlanes[i].normal );
  344. VectorNormalize( m_pCollisionPlanes[i].normal );
  345. m_pCollisionPlanes[i].dist = DotProduct( m_Origin, m_pCollisionPlanes[i].normal ) - COLLISION_PLANE_OFFSET;
  346. m_pCollisionPlanes[i].type = 3;
  347. }
  348. else
  349. {
  350. VectorCopy( m_ControlPoints[i], m_Particle[i].m_Position );
  351. m_pValidCollisionPlane[i] = false;
  352. }
  353. }
  354. //-----------------------------------------------------------------------------
  355. // Collision detect
  356. //-----------------------------------------------------------------------------
  357. void CSheetSimulator::DetectCollision( int i, float flPlaneOffset )
  358. {
  359. // Collision test...
  360. // Check a line that goes farther out than our current point...
  361. // This will let us check for resting contact
  362. // Vector endpt = m_Particle[i].m_Position;
  363. trace_t tr;
  364. m_TraceHull(m_Origin, m_ControlPoints[i], m_FrustumBoxMin, m_FrustumBoxMax,
  365. MASK_SOLID_BRUSHONLY, m_CollisionGroup, &tr );
  366. if ( tr.fraction - 1.0 < 0 )
  367. {
  368. m_pValidCollisionPlane[i] = true;
  369. memcpy( &m_pCollisionPlanes[i], &tr.plane, sizeof(cplane_t) );
  370. m_pCollisionPlanes[i].dist += flPlaneOffset;
  371. }
  372. else if (tr.allsolid || tr.startsolid)
  373. {
  374. m_pValidCollisionPlane[i] = true;
  375. VectorSubtract( m_Origin, m_ControlPoints[i], m_pCollisionPlanes[i].normal );
  376. VectorNormalize( m_pCollisionPlanes[i].normal );
  377. m_pCollisionPlanes[i].dist = DotProduct( m_Origin, m_pCollisionPlanes[i].normal ) - flPlaneOffset;
  378. m_pCollisionPlanes[i].type = 3;
  379. }
  380. else
  381. {
  382. m_pValidCollisionPlane[i] = false;
  383. }
  384. }
  385. //-----------------------------------------------------------------------------
  386. // Collision plane fixup
  387. //-----------------------------------------------------------------------------
  388. void CSheetSimulator::DetermineBestCollisionPlane( bool bFarTest )
  389. {
  390. // Check neighbors for violation of collision plane constraints
  391. for ( int i = 0; i < NumVertical(); ++i)
  392. {
  393. for ( int j = 0; j < NumHorizontal(); ++j)
  394. {
  395. // Here's the particle we're making springs for
  396. int idx = i * NumHorizontal() + j;
  397. // Now that we've seen all collisions, find the best collision plane
  398. // to use (look at myself and all neighbors). The best plane
  399. // is the one that comes closest to the origin.
  400. m_Particle[idx].m_CollisionDist = FLT_MAX;
  401. m_Particle[idx].m_CollisionPlane = -1;
  402. TestVertAgainstPlane( idx, idx, bFarTest );
  403. if (j > 0)
  404. {
  405. TestVertAgainstPlane( idx, idx-1, bFarTest );
  406. }
  407. if (j < NumHorizontal() - 1)
  408. {
  409. TestVertAgainstPlane( idx, idx+1, bFarTest );
  410. }
  411. if (i > 0)
  412. TestVertAgainstPlane( idx, idx-NumHorizontal(), bFarTest );
  413. if (i < NumVertical() - 1)
  414. TestVertAgainstPlane( idx, idx+NumHorizontal(), bFarTest );
  415. }
  416. }
  417. }
  418. //-----------------------------------------------------------------------------
  419. // satify collision constraints
  420. //-----------------------------------------------------------------------------
  421. void CSheetSimulator::SatisfyCollisionConstraints()
  422. {
  423. // Eliminate velocity perp to a collision plane
  424. for ( int i = 0; i < NumParticles(); ++i )
  425. {
  426. // The actual collision plane
  427. if (m_Particle[i].m_CollisionPlane >= 0)
  428. {
  429. cplane_t* pPlane = &m_pCollisionPlanes[m_Particle[i].m_CollisionPlane];
  430. // Fix up position so it lies on the plane
  431. Vector delta = m_Particle[i].m_Position - m_Origin;
  432. m_Particle[i].m_Position = m_Origin + delta * m_Particle[i].m_CollisionDist;
  433. float perp = DotProduct( m_Particle[i].m_Velocity, pPlane->normal );
  434. if (perp < 0)
  435. m_Particle[i].m_Velocity -= pPlane->normal * perp;
  436. }
  437. }
  438. }
  439. //-----------------------------------------------------------------------------
  440. // integrator
  441. //-----------------------------------------------------------------------------
  442. void CSheetSimulator::EulerStep( float dt )
  443. {
  444. ClearForces();
  445. ComputeForces();
  446. // Update positions and velocities
  447. for ( int i = 0; i < NumParticles(); ++i)
  448. {
  449. m_Particle[i].m_Position += m_Particle[i].m_Velocity * dt;
  450. m_Particle[i].m_Velocity += m_Particle[i].m_Force * dt / m_Particle[i].m_Mass;
  451. assert( IsFinite( m_Particle[i].m_Velocity.x ) &&
  452. IsFinite( m_Particle[i].m_Velocity.y) &&
  453. IsFinite( m_Particle[i].m_Velocity.z) );
  454. // clamp for stability
  455. float lensq = m_Particle[i].m_Velocity.LengthSqr();
  456. if (lensq > 1e6)
  457. {
  458. m_Particle[i].m_Velocity *= 1e3 / sqrt(lensq);
  459. }
  460. }
  461. SatisfyCollisionConstraints();
  462. }
  463. //-----------------------------------------------------------------------------
  464. // Update the shield position:
  465. //-----------------------------------------------------------------------------
  466. void CSheetSimulator::Simulate( float dt )
  467. {
  468. // Initialize positions if necessary
  469. EulerStep(dt);
  470. }
  471. void CSheetSimulator::Simulate( float dt, int steps )
  472. {
  473. ComputeControlPoints();
  474. // Initialize positions if necessary
  475. dt /= steps;
  476. for (int i = 0; i < steps; ++i)
  477. {
  478. // Each step, we want to re-select the best collision planes to constrain
  479. // the movement by
  480. DetermineBestCollisionPlane();
  481. EulerStep(dt);
  482. }
  483. }
  484. #define CLAMP_DIST 6.0
  485. void CSheetSimulator::ClampPointsToCollisionPlanes()
  486. {
  487. // Find collision planes to clamp to
  488. DetermineBestCollisionPlane( false );
  489. // Eliminate velocity perp to a collision plane
  490. for ( int i = 0; i < NumParticles(); ++i )
  491. {
  492. // The actual collision plane
  493. if (m_Particle[i].m_CollisionPlane >= 0)
  494. {
  495. cplane_t* pPlane = &m_pCollisionPlanes[m_Particle[i].m_CollisionPlane];
  496. // Make sure we have a close enough perpendicular distance to the plane...
  497. float flPerpDist = fabs ( DotProduct( m_Particle[i].m_Position, pPlane->normal ) - pPlane->dist );
  498. if (flPerpDist >= CLAMP_DIST)
  499. continue;
  500. // Drop it along the perp
  501. VectorMA( m_Particle[i].m_Position, -flPerpDist, pPlane->normal, m_Particle[i].m_Position );
  502. }
  503. }
  504. }
  505. //-----------------------------------------------------------------------------
  506. // Class to help dealing with the iterative computation
  507. //-----------------------------------------------------------------------------
  508. CIterativeSheetSimulator::CIterativeSheetSimulator( TraceLineFunc_t traceline, TraceHullFunc_t traceHull ) :
  509. CSheetSimulator( traceline, traceHull ),
  510. m_SimulationSteps(0)
  511. {
  512. }
  513. void CIterativeSheetSimulator::BeginSimulation( float dt, int steps, int substeps, int collisionCount )
  514. {
  515. m_CurrentCollisionPt = 0;
  516. m_TimeStep = dt;
  517. m_SimulationSteps = steps;
  518. m_TotalSteps = steps;
  519. m_SubSteps = substeps;
  520. m_InitialPass = true;
  521. m_CollisionCount = collisionCount;
  522. }
  523. bool CIterativeSheetSimulator::Think( )
  524. {
  525. assert( m_SimulationSteps >= 0 );
  526. // Need to iteratively perform collision detection
  527. if (m_CurrentCollisionPt >= 0)
  528. {
  529. DetectCollisions();
  530. return false;
  531. }
  532. else
  533. {
  534. // Simulate it a bunch of times
  535. Simulate(m_TimeStep, m_SubSteps);
  536. // Reset the collision point for collision detect
  537. m_CurrentCollisionPt = 0;
  538. --m_SimulationSteps;
  539. if ( m_SimulationSteps == 0 )
  540. {
  541. ClampPointsToCollisionPlanes();
  542. }
  543. return true;
  544. }
  545. }
  546. // Iterative collision detection
  547. void CIterativeSheetSimulator::DetectCollisions( void )
  548. {
  549. for ( int i = 0; i < m_CollisionCount; ++i )
  550. {
  551. if (m_InitialPass)
  552. {
  553. InitPosition( m_CurrentCollisionPt );
  554. }
  555. else
  556. {
  557. float flOffset = COLLISION_PLANE_OFFSET * ( (float)(m_SimulationSteps - 1) / (float)(m_TotalSteps - 1) );
  558. DetectCollision( m_CurrentCollisionPt, flOffset );
  559. }
  560. if (++m_CurrentCollisionPt >= NumParticles())
  561. {
  562. m_CurrentCollisionPt = -1;
  563. m_InitialPass = false;
  564. break;
  565. }
  566. }
  567. }