CastSphereVsTriangles.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include <Jolt/Jolt.h>
  5. #include <Jolt/Physics/Collision/CastSphereVsTriangles.h>
  6. #include <Jolt/Physics/Collision/TransformedShape.h>
  7. #include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>
  8. #include <Jolt/Physics/Collision/Shape/SphereShape.h>
  9. #include <Jolt/Physics/Collision/ActiveEdges.h>
  10. #include <Jolt/Physics/Collision/NarrowPhaseStats.h>
  11. #include <Jolt/Geometry/ClosestPoint.h>
  12. #include <Jolt/Geometry/RaySphere.h>
  13. #include <Jolt/Core/Profiler.h>
  14. JPH_NAMESPACE_BEGIN
  15. CastSphereVsTriangles::CastSphereVsTriangles(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, CastShapeCollector &ioCollector) :
  16. mStart(inShapeCast.mCenterOfMassStart.GetTranslation()),
  17. mDirection(inShapeCast.mDirection),
  18. mShapeCastSettings(inShapeCastSettings),
  19. mCenterOfMassTransform2(inCenterOfMassTransform2),
  20. mScale(inScale),
  21. mSubShapeIDCreator1(inSubShapeIDCreator1),
  22. mCollector(ioCollector)
  23. {
  24. // Cast to sphere shape
  25. JPH_ASSERT(inShapeCast.mShape->GetSubType() == EShapeSubType::Sphere);
  26. const SphereShape *sphere = static_cast<const SphereShape *>(inShapeCast.mShape);
  27. // Scale the radius
  28. mRadius = sphere->GetRadius() * abs(inShapeCast.mScale.GetX());
  29. // Determine if shape is inside out or not
  30. mScaleSign = ScaleHelpers::IsInsideOut(inScale)? -1.0f : 1.0f;
  31. }
  32. void CastSphereVsTriangles::AddHit(bool inBackFacing, const SubShapeID &inSubShapeID2, float inFraction, Vec3Arg inContactPointA, Vec3Arg inContactPointB, Vec3Arg inContactNormal)
  33. {
  34. // Convert to world space
  35. Vec3 contact_point_a = mCenterOfMassTransform2 * (mStart + inContactPointA);
  36. Vec3 contact_point_b = mCenterOfMassTransform2 * (mStart + inContactPointB);
  37. Vec3 contact_normal_world = mCenterOfMassTransform2.Multiply3x3(inContactNormal);
  38. // Its a hit, store the sub shape id's
  39. ShapeCastResult result(inFraction, contact_point_a, contact_point_b, contact_normal_world, inBackFacing, mSubShapeIDCreator1.GetID(), inSubShapeID2, TransformedShape::sGetBodyID(mCollector.GetContext()));
  40. // Note: We don't gather faces here because that's only useful if both shapes have a face. Since the sphere always has only 1 contact point, the manifold is always a point.
  41. JPH_IF_TRACK_NARROWPHASE_STATS(TrackNarrowPhaseCollector track;)
  42. mCollector.AddHit(result);
  43. }
  44. void CastSphereVsTriangles::AddHitWithActiveEdgeDetection(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, bool inBackFacing, Vec3Arg inTriangleNormal, uint8 inActiveEdges, const SubShapeID &inSubShapeID2, float inFraction, Vec3Arg inContactPointA, Vec3Arg inContactPointB, Vec3Arg inContactNormal)
  45. {
  46. // Check if we have enabled active edge detection
  47. Vec3 contact_normal = inContactNormal;
  48. if (mShapeCastSettings.mActiveEdgeMode == EActiveEdgeMode::CollideOnlyWithActive && inActiveEdges != 0b111)
  49. {
  50. // Convert the active edge velocity hint to local space
  51. Vec3 active_edge_movement_direction = mCenterOfMassTransform2.Multiply3x3Transposed(mShapeCastSettings.mActiveEdgeMovementDirection);
  52. // Update the contact normal to account for active edges
  53. // Note that we flip the triangle normal as the penetration axis is pointing towards the triangle instead of away
  54. contact_normal = ActiveEdges::FixNormal(inV0, inV1, inV2, inBackFacing? inTriangleNormal : -inTriangleNormal, inActiveEdges, inContactPointB, inContactNormal, active_edge_movement_direction);
  55. }
  56. AddHit(inBackFacing, inSubShapeID2, inFraction, inContactPointA, inContactPointB, contact_normal);
  57. }
  58. // This is a simplified version of the ray cylinder test from: Real Time Collision Detection - Christer Ericson
  59. // Chapter 5.3.7, page 194-197. Some conditions have been removed as we're not interested in hitting the caps of the cylinder.
  60. // Note that the ray origin is assumed to be the origin here.
  61. float CastSphereVsTriangles::RayCylinder(Vec3Arg inRayDirection, Vec3Arg inCylinderA, Vec3Arg inCylinderB, float inRadius) const
  62. {
  63. // Calculate cylinder axis
  64. Vec3 axis = inCylinderB - inCylinderA;
  65. // Make ray start relative to cylinder side A (moving cylinder A to the origin)
  66. Vec3 start = -inCylinderA;
  67. // Test if segment is fully on the A side of the cylinder
  68. float start_dot_axis = start.Dot(axis);
  69. float direction_dot_axis = inRayDirection.Dot(axis);
  70. float end_dot_axis = start_dot_axis + direction_dot_axis;
  71. if (start_dot_axis < 0.0f && end_dot_axis < 0.0f)
  72. return FLT_MAX;
  73. // Test if segment is fully on the B side of the cylinder
  74. float axis_len_sq = axis.LengthSq();
  75. if (start_dot_axis > axis_len_sq && end_dot_axis > axis_len_sq)
  76. return FLT_MAX;
  77. // Calculate a, b and c, the factors for quadratic equation
  78. // We're basically solving the ray: x = start + direction * t
  79. // The closest point to x on the segment A B is: w = (x . axis) * axis / (axis . axis)
  80. // The distance between x and w should be radius: (x - w) . (x - w) = radius^2
  81. // Solving this gives the following:
  82. float a = axis_len_sq * inRayDirection.LengthSq() - Square(direction_dot_axis);
  83. if (abs(a) < 1.0e-6f)
  84. return FLT_MAX; // Segment runs parallel to cylinder axis, stop processing, we will either hit at fraction = 0 or we'll hit a vertex
  85. float b = axis_len_sq * start.Dot(inRayDirection) - direction_dot_axis * start_dot_axis; // should be multiplied by 2, instead we'll divide a and c by 2 when we solve the quadratic equation
  86. float c = axis_len_sq * (start.LengthSq() - Square(inRadius)) - Square(start_dot_axis);
  87. float det = Square(b) - a * c; // normally 4 * a * c but since both a and c need to be divided by 2 we lose the 4
  88. if (det < 0.0f)
  89. return FLT_MAX; // No solution to quadractic equation
  90. // Solve fraction t where the ray hits the cylinder
  91. float t = -(b + sqrt(det)) / a; // normally divided by 2 * a but since a should be divided by 2 we lose the 2
  92. if (t < 0.0f || t > 1.0f)
  93. return FLT_MAX; // Intersection lies outside segment
  94. if (start_dot_axis + t * direction_dot_axis < 0.0f || start_dot_axis + t * direction_dot_axis > axis_len_sq)
  95. return FLT_MAX; // Intersection outside the end point of the cylinder, stop processing, we will possibly hit a vertex
  96. return t;
  97. }
  98. void CastSphereVsTriangles::Cast(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, uint8 inActiveEdges, const SubShapeID &inSubShapeID2)
  99. {
  100. JPH_PROFILE_FUNCTION();
  101. // Scale triangle and make it relative to the start of the cast
  102. Vec3 v0 = mScale * inV0 - mStart;
  103. Vec3 v1 = mScale * inV1 - mStart;
  104. Vec3 v2 = mScale * inV2 - mStart;
  105. // Calculate triangle normal
  106. Vec3 triangle_normal = mScaleSign * (v1 - v0).Cross(v2 - v0);
  107. float triangle_normal_len = triangle_normal.Length();
  108. if (triangle_normal_len == 0.0f)
  109. return; // Degenerate triangle
  110. triangle_normal /= triangle_normal_len;
  111. // Backface check
  112. float normal_dot_direction = triangle_normal.Dot(mDirection);
  113. bool back_facing = normal_dot_direction > 0.0f;
  114. if (mShapeCastSettings.mBackFaceModeTriangles == EBackFaceMode::IgnoreBackFaces && back_facing)
  115. return;
  116. // Test if distance between the sphere and plane of triangle is smaller or equal than the radius
  117. if (abs(v0.Dot(triangle_normal)) <= mRadius)
  118. {
  119. // Check if the sphere intersects at the start of the cast
  120. uint32 closest_feature;
  121. Vec3 q = ClosestPoint::GetClosestPointOnTriangle(v0, v1, v2, closest_feature);
  122. float q_len_sq = q.LengthSq();
  123. if (q_len_sq <= Square(mRadius))
  124. {
  125. // Early out if this hit is deeper than the collector's early out value
  126. float q_len = sqrt(q_len_sq);
  127. float penetration_depth = mRadius - q_len;
  128. if (-penetration_depth >= mCollector.GetEarlyOutFraction())
  129. return;
  130. // Generate contact point
  131. Vec3 contact_normal = q_len > 0.0f? q / q_len : Vec3::sAxisY();
  132. Vec3 contact_point_a = q + contact_normal * penetration_depth;
  133. Vec3 contact_point_b = q;
  134. AddHitWithActiveEdgeDetection(v0, v1, v2, back_facing, triangle_normal, inActiveEdges, inSubShapeID2, 0.0f, contact_point_a, contact_point_b, contact_normal);
  135. return;
  136. }
  137. }
  138. else
  139. {
  140. // Check if cast is not parallel to the plane of the triangle
  141. float abs_normal_dot_direction = abs(normal_dot_direction);
  142. if (abs_normal_dot_direction > 1.0e-6f)
  143. {
  144. // Calculate the point on the sphere that will hit the triangle's plane first and calculate a fraction where it will do so
  145. Vec3 d = Sign(normal_dot_direction) * mRadius * triangle_normal;
  146. float plane_intersection = (v0 - d).Dot(triangle_normal) / normal_dot_direction;
  147. // Check if sphere will hit in the interval that we're interested in
  148. if (plane_intersection * abs_normal_dot_direction < -mRadius // Sphere hits the plane before the sweep, cannot intersect
  149. || plane_intersection >= mCollector.GetEarlyOutFraction()) // Sphere hits the plane after the sweep / early out fraction, cannot intersect
  150. return;
  151. // We can only report an interior hit if we're hitting the plane during our sweep and not before
  152. if (plane_intersection >= 0.0f)
  153. {
  154. // Calculate the point of contact on the plane
  155. Vec3 p = d + plane_intersection * mDirection;
  156. // Check if this is an interior point
  157. float u, v, w;
  158. if (ClosestPoint::GetBaryCentricCoordinates(v0 - p, v1 - p, v2 - p, u, v, w)
  159. && u >= 0.0f && v >= 0.0f && w >= 0.0f)
  160. {
  161. // Interior point, we found the collision point. We don't need to check active edges.
  162. AddHit(back_facing, inSubShapeID2, plane_intersection, p, p, back_facing? triangle_normal : -triangle_normal);
  163. return;
  164. }
  165. }
  166. }
  167. }
  168. // Test 3 edges
  169. float fraction = RayCylinder(mDirection, v0, v1, mRadius);
  170. fraction = min(fraction, RayCylinder(mDirection, v1, v2, mRadius));
  171. fraction = min(fraction, RayCylinder(mDirection, v2, v0, mRadius));
  172. // Test 3 vertices
  173. fraction = min(fraction, RaySphere(Vec3::sZero(), mDirection, v0, mRadius));
  174. fraction = min(fraction, RaySphere(Vec3::sZero(), mDirection, v1, mRadius));
  175. fraction = min(fraction, RaySphere(Vec3::sZero(), mDirection, v2, mRadius));
  176. // Check if we have a collision
  177. JPH_ASSERT(fraction >= 0.0f);
  178. if (fraction < mCollector.GetEarlyOutFraction())
  179. {
  180. // Calculate the center of the sphere at the point of contact
  181. Vec3 p = fraction * mDirection;
  182. // Get contact point and normal
  183. uint32 closest_feature;
  184. Vec3 q = ClosestPoint::GetClosestPointOnTriangle(v0 - p, v1 - p, v2 - p, closest_feature);
  185. Vec3 contact_normal = q.Normalized();
  186. Vec3 contact_point_ab = p + q;
  187. AddHitWithActiveEdgeDetection(v0, v1, v2, back_facing, triangle_normal, inActiveEdges, inSubShapeID2, fraction, contact_point_ab, contact_point_ab, contact_normal);
  188. }
  189. }
  190. JPH_NAMESPACE_END