Counter Strike : Global Offensive Source Code
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.

259 lines
8.2 KiB

  1. //========= Copyright � Valve Corporation, All rights reserved. ============//
  2. #include "capsule.h"
  3. #include "trace.h"
  4. //#include "body.h"
  5. //#include "mass.h"
  6. //#include "distance.h"
  7. //#include "gjk.h"
  8. //#include "sat.h"
  9. #define NUM_STACKS 8
  10. #define NUM_SLICES 16
  11. //--------------------------------------------------------------------------------------------------
  12. // Local utilities
  13. //--------------------------------------------------------------------------------------------------
  14. struct CapsuleCast2D_t
  15. {
  16. float m_flCapsule, m_flRay;
  17. };
  18. //--------------------------------------------------------------------------------------------------
  19. static void CastCapsuleRay2DCoaxialInternal( CapsuleCast2D_t &out, float mx, float dx, float h, float e )
  20. {
  21. Assert( e >= 0 );
  22. float mxProj = mx + e; // m.x - (-e)
  23. if( mxProj < 0 )
  24. {
  25. // ray starts before the capsule cap
  26. out.m_flCapsule = 0;
  27. if( dx >= -mxProj ) // otherwise, ending before capsule starts: FLT_MAX
  28. {
  29. out.m_flRay = -mxProj / dx;
  30. }
  31. }
  32. else if( mx < h + e ) // otherwise, starting after capsule ends : FLT_MAX
  33. {
  34. out.m_flCapsule = Clamp( mx, 0.0f, h );
  35. out.m_flRay = 0;
  36. }
  37. else
  38. {
  39. // ray starts after the capsule cap
  40. out.m_flCapsule = h;
  41. float mxEnd = mx - ( h + e );
  42. if( -dx >= mxEnd )
  43. {
  44. out.m_flRay = mxEnd / -dx;
  45. }
  46. }
  47. }
  48. //--------------------------------------------------------------------------------------------------
  49. static void CastCapsuleRay2DParallelInternal( CapsuleCast2D_t &out, const Vector2D &m, float dx, float h, float rr )
  50. {
  51. float e2 = rr - Sqr( m.y );
  52. if( e2 > 0 ) // otherwise, going parallel and outside : FLT_MAX
  53. {
  54. // going parallel and inside the infinite slab at level m.y, left to right
  55. float e = sqrtf( e2 ); // -e..h+e is the extent
  56. CastCapsuleRay2DCoaxialInternal( out, m.x, dx, h, e );
  57. }
  58. }
  59. //--------------------------------------------------------------------------------------------------
  60. // Intersect 2D ray with 2D capsule; capsule has radius r, length h, it starts at (0,0) and ends at (h,0)
  61. // ray goes from m, delta d
  62. // return: time of hit
  63. static void CastCapsuleRay2DInternal( CapsuleCast2D_t &out, const Vector2D &m, const Vector2D &d, float h, float rr )
  64. {
  65. Assert( rr >= 0 );
  66. Assert( d.y > -FLT_EPSILON );
  67. Assert( d.y != 0.0f ); // otherwise it's going parallel
  68. float my2 = Sqr( m.y );
  69. out.m_flCapsule = Clamp( m.x, 0.0f, h );
  70. // Easy case we'll have to check a few times if we delay: are we starting in solid?
  71. // same idea as with box-box distance: cut out x=0..h, capsule becomes a circle, find distance to circle
  72. // I'm sure there's more elegant way to handle it
  73. if( Sqr( m.x - out.m_flCapsule ) + my2 < rr )
  74. {
  75. out.m_flRay = 0; // start-in-solid
  76. return;
  77. }
  78. // well, we don't start inside the capsule. Good to know
  79. float r = sqrtf( rr ), dd = Sqr( d.x ) + Sqr( d.y ), ddInv = 1.0f / dd, dymy = d.y * m.y;
  80. // first, intersect the ray with the rectangle
  81. float t = ( -r - m.y ) / d.y, t0 = fpmax( 0, t ), s0 = m.x + d.x * t0;
  82. // solutions: -b0�sqrt(b0^2-c0) , -b1�sqrt(b1^2-c1) with � controlled by d.x sign
  83. // since we know we go left-bottom to right-top, we can just choose the circle we wanna hit
  84. // since we know we don't start-in-solid, we know the first root (if any) will be t>=0
  85. float mxh;
  86. if( s0 < 0 )
  87. {
  88. // we're entering through the left cap
  89. // if we hit, we hit left circle
  90. out.m_flCapsule = 0;
  91. mxh = m.x;
  92. }
  93. else if( s0 < h )
  94. {
  95. // we're entering through the side of the capsule
  96. out.m_flCapsule = s0;
  97. if( t >= 0 ) // only if we didn't enter before ray started; otherwise, since we didn't start-in-solid, we don't hit capsule at all
  98. {
  99. out.m_flRay = t; // the caller will sort out if it's >1 or not
  100. }
  101. return;
  102. }
  103. else
  104. {
  105. out.m_flCapsule = h;
  106. mxh = m.x - h;
  107. }
  108. float b = ( d.x * mxh + dymy ) * ddInv, c = ( mxh * mxh + my2 - rr ) * ddInv, D = b * b - c;
  109. if( D >= 0 )
  110. {
  111. float tc = -b - sqrtf( D );
  112. Assert( tc - t >= -1e-4f ); // the ray should really enter the circle after it entered the stripe of halfspaces
  113. // if tc < 0, we entered capsule before ray began; since we didn't start-in-solid, it means we don't hit the capsule at all
  114. if( tc >= 0 )
  115. {
  116. out.m_flRay = tc;
  117. }
  118. }
  119. }
  120. static void CastCapsuleShortRay( CShapeCastResult &out, const Vector &sUnit, float sLen, const Vector &m, const Vector &vRayStart, const Vector vCenter[], float flRadius )
  121. {
  122. // the ray is too short, just compute the distance to the capsule and compare with radius
  123. // if we really need both high precision and stability, we need to compute distance to capsule from both ends of the ray: the capsule curvature is very low in the vicinity of the ray and is o(d^2) effect
  124. float flProjOnCapsule = DotProduct( sUnit, m );
  125. Vector vDistance;
  126. if( flProjOnCapsule < 0 )
  127. {
  128. vDistance = m;
  129. }
  130. else if( flProjOnCapsule > sLen )
  131. {
  132. vDistance = vRayStart - vCenter[ 1 ];
  133. }
  134. else
  135. {
  136. vDistance = m - vCenter[ 0 ] * flProjOnCapsule ;
  137. }
  138. float flDistFromCapsuleSqr = vDistance.LengthSqr();
  139. if( flDistFromCapsuleSqr > flRadius )
  140. {
  141. // the ray is outside of the capsule
  142. out.m_bStartInSolid = false;
  143. out.m_flHitTime = 1.0f;
  144. }
  145. else
  146. {
  147. out.m_bStartInSolid = true;
  148. out.m_flHitTime = 0;
  149. out.m_vHitNormal = flDistFromCapsuleSqr > 1e-8f ? vDistance / sqrtf( flDistFromCapsuleSqr ) : VectorPerpendicularToVector( sUnit );
  150. out.m_vHitPoint = vRayStart;
  151. }
  152. }
  153. void CastSphereRay( CShapeCastResult& out, const Vector &m, const Vector& p, const Vector& d, float flRadius );
  154. //--------------------------------------------------------------------------------------------------
  155. void CastCapsuleRay( CShapeCastResult& out, const Vector& vRayStart, const Vector& vRayDelta, const Vector vCenter[], float flRadius )
  156. {
  157. Vector m = vRayStart - vCenter[0], s = vCenter[1] - vCenter[0];
  158. float sLen = s.Length();
  159. if( flRadius < 1e-5f )
  160. {
  161. return;
  162. }
  163. if( sLen < 1e-3f ) // note: we should filter out 0-length capsules somewhere outside of this function
  164. {
  165. CastSphereRay( out, m, vRayStart, vRayDelta, flRadius );
  166. return;
  167. }
  168. Vector sUnit = s / sLen;
  169. float dLen = vRayDelta.Length();
  170. if( dLen > 1e-4f )
  171. {
  172. Vector dUnit = vRayDelta / dLen;
  173. Vector z = CrossProduct( sUnit, dUnit );
  174. float zLenSqr = z.LengthSqr();
  175. float dsUnit = DotProduct( vRayDelta, sUnit );
  176. CapsuleCast2D_t cast;
  177. cast.m_flRay = FLT_MAX;
  178. if( zLenSqr > 256*256 * FLT_EPSILON * FLT_EPSILON ) // the tolerance here is found experimentally, with the target of achieving minimal orthogonality of 1e-3 between z^s and z^d
  179. {
  180. float zLen = sqrtf( zLenSqr );
  181. Vector zUnit = z / zLen;
  182. #ifdef _DEBUG
  183. // z must be orthogonal to capsule and ray (it's a cross product of the two); if it's not, we need to handle this case as parallel
  184. float flOrthogonality[2] = { DotProduct( zUnit, s ), DotProduct( zUnit, vRayDelta ) };
  185. Assert( fabsf( flOrthogonality[0] ) < 1e-3f * MAX( 1, MAX( zLen, sLen ) ) && fabsf( flOrthogonality[1] ) < 1e-3f * MAX( 1, MAX( zLen, dLen ) ) );
  186. #endif
  187. float mzUnit = DotProduct( m, zUnit ), rr = Sqr( flRadius ) - Sqr( mzUnit );
  188. if( rr <= 0 )
  189. {
  190. out.m_flHitTime = FLT_MAX;
  191. return;
  192. }
  193. else
  194. {
  195. Vector yUnit = CrossProduct( zUnit, sUnit );
  196. Vector2D mProj( DotProduct( m, sUnit ), DotProduct( m, yUnit ) );
  197. float dyUnit = DotProduct( vRayDelta, yUnit );
  198. CastCapsuleRay2DInternal( cast, mProj, Vector2D( dsUnit, dyUnit ), sLen, rr );
  199. }
  200. }
  201. else
  202. {
  203. // they're parallel..
  204. float msUnit = DotProduct( m, sUnit );
  205. Vector zAlt = m - sUnit * msUnit;
  206. float zAltLenSqr = zAlt.LengthSqr();
  207. if( zAltLenSqr < FLT_EPSILON * FLT_EPSILON )
  208. {
  209. // ray and capsule are coaxial...
  210. CastCapsuleRay2DCoaxialInternal( cast, msUnit, dsUnit, sLen, flRadius ); // note: we're passing radius!
  211. }
  212. else
  213. {
  214. // ray and capsule are parallel
  215. Vector zUnit = zAlt / sqrtf( zAltLenSqr ), yUnit = CrossProduct( zUnit, sUnit );
  216. CastCapsuleRay2DParallelInternal( cast, Vector2D( DotProduct( m, sUnit ), DotProduct( m, yUnit ) ), dsUnit, sLen, Sqr( flRadius ) - zAltLenSqr ); // r^2 may be negative here - it'll just return no hit
  217. }
  218. }
  219. Assert( cast.m_flRay >= 0 );
  220. out.m_flHitTime = cast.m_flRay;
  221. out.m_vHitPoint = vRayStart + vRayDelta * cast.m_flRay;
  222. out.m_vHitNormal = ( out.m_vHitPoint - ( vCenter[0] + sUnit * cast.m_flCapsule ) ).Normalized();
  223. }
  224. else
  225. {
  226. CastCapsuleShortRay( out, sUnit, sLen, m, vRayStart, vCenter, flRadius );
  227. }
  228. }