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.

388 lines
12 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Local fast collision system for particles
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "particle_collision.h"
  9. #include "engine/ivdebugoverlay.h"
  10. // memdbgon must be the last include file in a .cpp file!!!
  11. #include "tier0/memdbgon.h"
  12. #ifdef _XBOX
  13. #define __DEBUG_PARTICLE_COLLISION_RETEST 0
  14. #else
  15. #define __DEBUG_PARTICLE_COLLISION_RETEST 1
  16. #endif // _XBOX
  17. #define __DEBUG_PARTICLE_COLLISION_OVERLAY 0
  18. #define __DEBUG_PARTICLE_COLLISION_OVERLAY_LIFETIME 0.1f
  19. #define NUM_DISCREET_STEPS 8.0f
  20. #define NUM_SIMULATION_SECONDS 2.0f
  21. #define COLLISION_EPSILON 0.01f
  22. //-----------------------------------------------------------------------------
  23. // Purpose:
  24. //-----------------------------------------------------------------------------
  25. CBaseSimpleCollision::CBaseSimpleCollision( void )
  26. {
  27. ClearActivePlanes();
  28. }
  29. //-----------------------------------------------------------------------------
  30. // Purpose:
  31. // Input : &origin -
  32. // radius -
  33. //-----------------------------------------------------------------------------
  34. void CBaseSimpleCollision::Setup( const Vector &origin, float speed, float gravity )
  35. {
  36. TestForPlane( origin, Vector( 1, 0, 0 ), speed, gravity );
  37. TestForPlane( origin, Vector( -1, 0, 0 ), speed, gravity );
  38. TestForPlane( origin, Vector( 0, 1, 0 ), speed, gravity );
  39. TestForPlane( origin, Vector( 0, -1, 0 ), speed, gravity );
  40. TestForPlane( origin, Vector( 0, 0, 1 ), speed, gravity );
  41. TestForPlane( origin, Vector( 0, 0, -1 ), speed, gravity );
  42. }
  43. //-----------------------------------------------------------------------------
  44. // Purpose: Trace line for super-simplified traces
  45. // Input : &start - start position
  46. // &end - end position
  47. // *pTrace - trace structure to fill
  48. // coarse - tests again with a real trace unless coarse is set
  49. //-----------------------------------------------------------------------------
  50. void CBaseSimpleCollision::TraceLine( const Vector &start, const Vector &end, trace_t *pTrace, bool coarse )
  51. {
  52. //Iterate over all active planes
  53. for ( int i = 0; i < m_nActivePlanes; i++ )
  54. {
  55. //Must be a valid plane
  56. if ( m_collisionPlanes[i].m_Dist == -1.0f )
  57. continue;
  58. //Get our information about the relation to this plane
  59. float dot1 = m_collisionPlanes[i].DistTo(start);
  60. float dot2 = m_collisionPlanes[i].DistTo(end);
  61. //Don't consider particles on the backside of planes
  62. if ( dot1 < -COLLISION_EPSILON )
  63. continue;
  64. //Must be crossing the plane's boundary
  65. if ( ( dot1 > COLLISION_EPSILON ) == ( dot2 > COLLISION_EPSILON ) )
  66. continue;
  67. //Find the intersection point
  68. float t = dot1 / (dot1 - dot2);
  69. Vector vIntersection = start + (end - start) * t;
  70. //Fake the collision info
  71. pTrace->endpos = vIntersection;
  72. pTrace->fraction = t - COLLISION_EPSILON;
  73. pTrace->plane.normal = m_collisionPlanes[i].m_Normal;
  74. pTrace->plane.dist = m_collisionPlanes[i].m_Dist;
  75. //If we need an exact trace, test again on a successful hit
  76. if ( ( coarse == false ) && ( pTrace->fraction < 1.0f ) )
  77. {
  78. UTIL_TraceLine( start, end, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, pTrace );
  79. }
  80. #if __DEBUG_PARTICLE_COLLISION_OVERLAY
  81. if ( debugoverlay )
  82. {
  83. debugoverlay->AddBoxOverlay( vIntersection, Vector(-1,-1,-1), Vector(1,1,1), QAngle(0,0,0), 0, 255, 0, 16, __DEBUG_PARTICLE_COLLISION_OVERLAY_LIFETIME );
  84. }
  85. #endif //__DEBUG_PARTICLE_COLLISION_OVERLAY
  86. //Done
  87. return;
  88. }
  89. //Fell through, so clear all the fields
  90. pTrace->plane.normal[0] = 0.0f;
  91. pTrace->plane.normal[1] = 0.0f;
  92. pTrace->plane.normal[2] = 0.0f;
  93. pTrace->plane.dist = 0.0f;
  94. pTrace->fraction = 1.0f;
  95. pTrace->allsolid = false;
  96. pTrace->startsolid = false;
  97. pTrace->m_pEnt = NULL;
  98. }
  99. //-----------------------------------------------------------------------------
  100. // Purpose: Tests the planes against all others for validity
  101. // Input : *plane - plane to test
  102. //-----------------------------------------------------------------------------
  103. void CBaseSimpleCollision::ConsiderPlane( cplane_t *plane )
  104. {
  105. //Test against all other active planes
  106. for ( int i = 0; i < m_nActivePlanes; i++ )
  107. {
  108. if ( m_collisionPlanes[i].m_Dist != -1.0f )
  109. {
  110. //Test for coplanar
  111. if ( ( m_collisionPlanes[i].m_Normal == plane->normal ) && ( m_collisionPlanes[i].m_Dist == plane->dist ) )
  112. return;
  113. }
  114. }
  115. //Don't overrun
  116. if ( m_nActivePlanes >= MAX_COLLISION_PLANES )
  117. return;
  118. //Take it
  119. m_collisionPlanes[m_nActivePlanes].m_Dist = plane->dist;
  120. m_collisionPlanes[m_nActivePlanes].m_Normal = plane->normal;
  121. m_nActivePlanes++;
  122. }
  123. //-----------------------------------------------------------------------------
  124. // Purpose: Runs a simulation of the average particle's movement, looking for collisions along the way
  125. // Input : &start - start of the simulation
  126. // &dir - direction of travel
  127. // speed - speed of the particle
  128. // gravity - gravity being used
  129. //-----------------------------------------------------------------------------
  130. void CBaseSimpleCollision::TestForPlane( const Vector &start, const Vector &dir, float speed, float gravity )
  131. {
  132. trace_t tr;
  133. Vector testStart, testEnd;
  134. testStart = start;
  135. //Setup our step increments
  136. float dStepTime = (NUM_SIMULATION_SECONDS/NUM_DISCREET_STEPS);
  137. Vector vStepIncr = dir * ( speed * dStepTime );
  138. float flGravIncr = gravity*dStepTime;
  139. //Simulate collsions in discreet steps
  140. for ( int i = 1; i <= NUM_DISCREET_STEPS; i++ )
  141. {
  142. testEnd = testStart + vStepIncr;
  143. testEnd[2] -= flGravIncr * (0.5f*(dStepTime*i)*(dStepTime*i) );
  144. //Trace the line
  145. UTIL_TraceLine( testStart, testEnd, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
  146. //See if we found one
  147. if ( tr.fraction != 1.0f )
  148. {
  149. #if __DEBUG_PARTICLE_COLLISION_OVERLAY
  150. if ( debugoverlay )
  151. {
  152. debugoverlay->AddLineOverlay( testStart, tr.endpos, 255, 0, 0, true, __DEBUG_PARTICLE_COLLISION_OVERLAY_LIFETIME );
  153. QAngle angles;
  154. VectorAngles( tr.plane.normal,angles );
  155. angles[PITCH] += 90;
  156. debugoverlay->AddBoxOverlay( tr.endpos, Vector(-64,-64,0), Vector(64,64,0), angles, 255, 0, 0, 16, __DEBUG_PARTICLE_COLLISION_OVERLAY_LIFETIME );
  157. }
  158. #endif //__DEBUG_PARTICLE_COLLISION_OVERLAY
  159. //Test the plane against a set of criteria
  160. ConsiderPlane( &tr.plane );
  161. return;
  162. }
  163. //We missed
  164. #if __DEBUG_PARTICLE_COLLISION_OVERLAY
  165. if ( debugoverlay )
  166. {
  167. debugoverlay->AddLineOverlay( testStart, tr.endpos, 0, 128.0f+(128.0f*((float)i/(float)NUM_DISCREET_STEPS)), 0, true, __DEBUG_PARTICLE_COLLISION_OVERLAY_LIFETIME );
  168. }
  169. #endif //__DEBUG_PARTICLE_COLLISION_OVERLAY
  170. //Save that position for the next round
  171. testStart = testEnd;
  172. }
  173. }
  174. //-----------------------------------------------------------------------------
  175. // Purpose:
  176. //-----------------------------------------------------------------------------
  177. void CBaseSimpleCollision::ClearActivePlanes( void )
  178. {
  179. for ( int i = 0; i < MAX_COLLISION_PLANES; i++ )
  180. {
  181. m_collisionPlanes[i].m_Dist = -1.0f;
  182. m_collisionPlanes[i].m_Normal.Init();
  183. }
  184. m_nActivePlanes = 0;
  185. }
  186. //
  187. // CParticleCollision
  188. //
  189. //-----------------------------------------------------------------------------
  190. // Constructor
  191. //-----------------------------------------------------------------------------
  192. CParticleCollision::CParticleCollision( void )
  193. {
  194. m_flGravity = 800.0f;
  195. m_flCollisionDampen = 0.5f;
  196. m_flAngularCollisionDampen = 0.25f;
  197. ClearActivePlanes();
  198. }
  199. //-----------------------------------------------------------------------------
  200. // Purpose: Test for surrounding collision surfaces for quick collision testing for the particle system
  201. // Input : &origin - starting position
  202. // *dir - direction of movement (if NULL, will do a point emission test in four directions)
  203. // angularSpread - looseness of the spread
  204. // minSpeed - minimum speed
  205. // maxSpeed - maximum speed
  206. // gravity - particle gravity for the sytem
  207. // dampen - dampening amount on collisions
  208. //-----------------------------------------------------------------------------
  209. void CParticleCollision::Setup( const Vector &origin, const Vector *dir, float angularSpread, float minSpeed, float maxSpeed, float gravity, float dampen )
  210. {
  211. //Take the information for this simulation
  212. m_flGravity = gravity;
  213. m_flCollisionDampen = dampen;
  214. m_nActivePlanes = 0;
  215. //We take a rough estimation of the spray
  216. float speedAvg = (minSpeed+maxSpeed)*0.5f;
  217. //Point or directed?
  218. if ( dir == NULL )
  219. {
  220. //Test all around
  221. TestForPlane( origin, Vector( 1, 0, 0 ), speedAvg, gravity );
  222. TestForPlane( origin, Vector( -1, 0, 0 ), speedAvg, gravity );
  223. TestForPlane( origin, Vector( 0, 1, 0 ), speedAvg, gravity );
  224. TestForPlane( origin, Vector( 0, -1, 0 ), speedAvg, gravity );
  225. TestForPlane( origin, Vector( 0, 0, 1 ), speedAvg, gravity );
  226. TestForPlane( origin, Vector( 0, 0, -1 ), speedAvg, gravity );
  227. }
  228. else
  229. {
  230. Vector vSkewDir, vRight;
  231. QAngle vAngles;
  232. //FIXME: Quicker conversion?
  233. //FIXME: We need to factor in the angular spread instead
  234. VectorAngles( *dir, vAngles );
  235. AngleVectors( vAngles, NULL, &vRight, NULL );
  236. //Test straight
  237. TestForPlane( origin, *dir, speedAvg, gravity );
  238. vSkewDir = vRight;
  239. //Test right
  240. TestForPlane( origin, vSkewDir, speedAvg, gravity );
  241. vSkewDir *= -1.0f;
  242. //Test left
  243. TestForPlane( origin, vSkewDir, speedAvg, gravity );
  244. #if __DEBUG_PARTICLE_COLLISION_OVERLAY
  245. DevMsg( 1, "CParticleCollision: Found %d active plane(s)\n", m_nActivePlanes );
  246. #endif //__DEBUG_PARTICLE_COLLISION_OVERLAY
  247. }
  248. }
  249. //-----------------------------------------------------------------------------
  250. // Purpose: Simulate movement, with collision
  251. // Input : &origin - position of the particle
  252. // &velocity - velocity of the particle
  253. // &rollDelta - roll delta of the particle
  254. // timeDelta - time step
  255. //-----------------------------------------------------------------------------
  256. bool CParticleCollision::MoveParticle( Vector &origin, Vector &velocity, float *rollDelta, float timeDelta, trace_t *pTrace )
  257. {
  258. //Don't bother with non-moving particles
  259. if ( velocity == vec3_origin )
  260. return false;
  261. //Factor in gravity
  262. velocity[2] -= m_flGravity * timeDelta;
  263. //Move
  264. Vector testPosition = ( origin + ( velocity * timeDelta ) );
  265. //Only collide if we have active planes
  266. if ( m_nActivePlanes > 0 )
  267. {
  268. //Collide
  269. TraceLine( origin, testPosition, pTrace );
  270. //See if we hit something
  271. if ( pTrace->fraction != 1.0f )
  272. {
  273. #if __DEBUG_PARTICLE_COLLISION_RETEST
  274. //Retest the collision with a true trace line to avoid errant collisions
  275. UTIL_TraceLine( origin, testPosition, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, pTrace );
  276. #endif //__DEBUG_RETEST_COLLISION
  277. //Did we hit anything?
  278. if ( pTrace->fraction != 1.0f )
  279. {
  280. //See if we've settled
  281. if ( ( pTrace->plane.normal[2] >= 0.5f ) && ( fabs( velocity[2] ) <= 48.0f ) )
  282. {
  283. //Leave the particle at the collision point
  284. origin += velocity * ( (pTrace->fraction-COLLISION_EPSILON) * timeDelta );
  285. //Stop the particle
  286. velocity = vec3_origin;
  287. if ( rollDelta != NULL )
  288. {
  289. *rollDelta = 0.0f;
  290. }
  291. return false;
  292. }
  293. else
  294. {
  295. //Move the particle to the collision point
  296. origin += velocity * ( (pTrace->fraction-COLLISION_EPSILON) * timeDelta );
  297. //Find the reflection vector
  298. float proj = velocity.Dot( pTrace->plane.normal );
  299. velocity += pTrace->plane.normal * (-proj*2.0f);
  300. //Apply dampening
  301. velocity *= random->RandomFloat( (m_flCollisionDampen-0.1f), (m_flCollisionDampen+0.1f) );
  302. //Dampen the roll of the particles
  303. if ( rollDelta != NULL )
  304. {
  305. (*rollDelta) *= -0.25f;
  306. }
  307. return true;
  308. }
  309. }
  310. else
  311. {
  312. #if __DEBUG_PARTICLE_COLLISION_OVERLAY
  313. //Display a false hit
  314. if ( debugoverlay )
  315. {
  316. debugoverlay->AddBoxOverlay( pTrace->endpos, Vector(-1,-1,-1), Vector(1,1,1), QAngle(0,0,0), 255, 0, 0, 16, __DEBUG_PARTICLE_COLLISION_OVERLAY_LIFETIME );
  317. }
  318. #endif //__DEBUG_PARTICLE_COLLISION_OVERLAY
  319. }
  320. }
  321. }
  322. //Simple move, no collision
  323. origin = testPosition;
  324. return false;
  325. }