VehicleCollisionTester.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  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/Vehicle/VehicleCollisionTester.h>
  6. #include <Jolt/Physics/Vehicle/VehicleConstraint.h>
  7. #include <Jolt/Physics/Collision/RayCast.h>
  8. #include <Jolt/Physics/Collision/ShapeCast.h>
  9. #include <Jolt/Physics/Collision/CastResult.h>
  10. #include <Jolt/Physics/Collision/Shape/SphereShape.h>
  11. #include <Jolt/Physics/Collision/Shape/CylinderShape.h>
  12. #include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
  13. #include <Jolt/Physics/PhysicsSystem.h>
  14. JPH_NAMESPACE_BEGIN
  15. bool VehicleCollisionTesterRay::Collide(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&outBody, SubShapeID &outSubShapeID, RVec3 &outContactPosition, Vec3 &outContactNormal, float &outSuspensionLength) const
  16. {
  17. const DefaultBroadPhaseLayerFilter default_broadphase_layer_filter = inPhysicsSystem.GetDefaultBroadPhaseLayerFilter(mObjectLayer);
  18. const BroadPhaseLayerFilter &broadphase_layer_filter = mBroadPhaseLayerFilter != nullptr? *mBroadPhaseLayerFilter : default_broadphase_layer_filter;
  19. const DefaultObjectLayerFilter default_object_layer_filter = inPhysicsSystem.GetDefaultLayerFilter(mObjectLayer);
  20. const ObjectLayerFilter &object_layer_filter = mObjectLayerFilter != nullptr? *mObjectLayerFilter : default_object_layer_filter;
  21. const IgnoreSingleBodyFilter default_body_filter(inVehicleBodyID);
  22. const BodyFilter &body_filter = mBodyFilter != nullptr? *mBodyFilter : default_body_filter;
  23. const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings();
  24. float wheel_radius = wheel_settings->mRadius;
  25. float ray_length = wheel_settings->mSuspensionMaxLength + wheel_radius;
  26. RRayCast ray { inOrigin, ray_length * inDirection };
  27. class MyCollector : public CastRayCollector
  28. {
  29. public:
  30. MyCollector(PhysicsSystem &inPhysicsSystem, const RRayCast &inRay, Vec3Arg inUpDirection, float inCosMaxSlopeAngle) :
  31. mPhysicsSystem(inPhysicsSystem),
  32. mRay(inRay),
  33. mUpDirection(inUpDirection),
  34. mCosMaxSlopeAngle(inCosMaxSlopeAngle)
  35. {
  36. }
  37. virtual void AddHit(const RayCastResult &inResult) override
  38. {
  39. // Test if this collision is closer than the previous one
  40. if (inResult.mFraction < GetEarlyOutFraction())
  41. {
  42. // Lock the body
  43. BodyLockRead lock(mPhysicsSystem.GetBodyLockInterfaceNoLock(), inResult.mBodyID);
  44. JPH_ASSERT(lock.Succeeded()); // When this runs all bodies are locked so this should not fail
  45. const Body *body = &lock.GetBody();
  46. if (body->IsSensor())
  47. return;
  48. // Test that we're not hitting a vertical wall
  49. RVec3 contact_pos = mRay.GetPointOnRay(inResult.mFraction);
  50. Vec3 normal = body->GetWorldSpaceSurfaceNormal(inResult.mSubShapeID2, contact_pos);
  51. if (normal.Dot(mUpDirection) > mCosMaxSlopeAngle)
  52. {
  53. // Update early out fraction to this hit
  54. UpdateEarlyOutFraction(inResult.mFraction);
  55. // Get the contact properties
  56. mBody = body;
  57. mSubShapeID2 = inResult.mSubShapeID2;
  58. mContactPosition = contact_pos;
  59. mContactNormal = normal;
  60. }
  61. }
  62. }
  63. // Configuration
  64. PhysicsSystem & mPhysicsSystem;
  65. RRayCast mRay;
  66. Vec3 mUpDirection;
  67. float mCosMaxSlopeAngle;
  68. // Resulting closest collision
  69. const Body * mBody = nullptr;
  70. SubShapeID mSubShapeID2;
  71. RVec3 mContactPosition;
  72. Vec3 mContactNormal;
  73. };
  74. RayCastSettings settings;
  75. MyCollector collector(inPhysicsSystem, ray, mUp, mCosMaxSlopeAngle);
  76. inPhysicsSystem.GetNarrowPhaseQueryNoLock().CastRay(ray, settings, collector, broadphase_layer_filter, object_layer_filter, body_filter);
  77. if (collector.mBody == nullptr)
  78. return false;
  79. outBody = const_cast<Body *>(collector.mBody);
  80. outSubShapeID = collector.mSubShapeID2;
  81. outContactPosition = collector.mContactPosition;
  82. outContactNormal = collector.mContactNormal;
  83. outSuspensionLength = max(0.0f, ray_length * collector.GetEarlyOutFraction() - wheel_radius);
  84. return true;
  85. }
  86. void VehicleCollisionTesterRay::PredictContactProperties(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&ioBody, SubShapeID &ioSubShapeID, RVec3 &ioContactPosition, Vec3 &ioContactNormal, float &ioSuspensionLength) const
  87. {
  88. // Recalculate the contact points assuming the contact point is on an infinite plane
  89. const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings();
  90. float d_dot_n = inDirection.Dot(ioContactNormal);
  91. if (d_dot_n < -1.0e-6f)
  92. {
  93. // Reproject the contact position using the suspension ray and the plane formed by the contact position and normal
  94. ioContactPosition = inOrigin + Vec3(ioContactPosition - inOrigin).Dot(ioContactNormal) / d_dot_n * inDirection;
  95. // The suspension length is simply the distance between the contact position and the suspension origin excluding the wheel radius
  96. ioSuspensionLength = Clamp(Vec3(ioContactPosition - inOrigin).Dot(inDirection) - wheel_settings->mRadius, 0.0f, wheel_settings->mSuspensionMaxLength);
  97. }
  98. else
  99. {
  100. // If the normal is pointing away we assume there's no collision anymore
  101. ioSuspensionLength = wheel_settings->mSuspensionMaxLength;
  102. }
  103. }
  104. bool VehicleCollisionTesterCastSphere::Collide(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&outBody, SubShapeID &outSubShapeID, RVec3 &outContactPosition, Vec3 &outContactNormal, float &outSuspensionLength) const
  105. {
  106. const DefaultBroadPhaseLayerFilter default_broadphase_layer_filter = inPhysicsSystem.GetDefaultBroadPhaseLayerFilter(mObjectLayer);
  107. const BroadPhaseLayerFilter &broadphase_layer_filter = mBroadPhaseLayerFilter != nullptr? *mBroadPhaseLayerFilter : default_broadphase_layer_filter;
  108. const DefaultObjectLayerFilter default_object_layer_filter = inPhysicsSystem.GetDefaultLayerFilter(mObjectLayer);
  109. const ObjectLayerFilter &object_layer_filter = mObjectLayerFilter != nullptr? *mObjectLayerFilter : default_object_layer_filter;
  110. const IgnoreSingleBodyFilter default_body_filter(inVehicleBodyID);
  111. const BodyFilter &body_filter = mBodyFilter != nullptr? *mBodyFilter : default_body_filter;
  112. SphereShape sphere(mRadius);
  113. sphere.SetEmbedded();
  114. const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings();
  115. float wheel_radius = wheel_settings->mRadius;
  116. float shape_cast_length = wheel_settings->mSuspensionMaxLength + wheel_radius - mRadius;
  117. RShapeCast shape_cast(&sphere, Vec3::sReplicate(1.0f), RMat44::sTranslation(inOrigin), inDirection * shape_cast_length);
  118. ShapeCastSettings settings;
  119. settings.mUseShrunkenShapeAndConvexRadius = true;
  120. settings.mReturnDeepestPoint = true;
  121. class MyCollector : public CastShapeCollector
  122. {
  123. public:
  124. MyCollector(PhysicsSystem &inPhysicsSystem, const RShapeCast &inShapeCast, Vec3Arg inUpDirection, float inCosMaxSlopeAngle) :
  125. mPhysicsSystem(inPhysicsSystem),
  126. mShapeCast(inShapeCast),
  127. mUpDirection(inUpDirection),
  128. mCosMaxSlopeAngle(inCosMaxSlopeAngle)
  129. {
  130. }
  131. virtual void AddHit(const ShapeCastResult &inResult) override
  132. {
  133. // Test if this collision is closer/deeper than the previous one
  134. float early_out = inResult.GetEarlyOutFraction();
  135. if (early_out < GetEarlyOutFraction())
  136. {
  137. // Lock the body
  138. BodyLockRead lock(mPhysicsSystem.GetBodyLockInterfaceNoLock(), inResult.mBodyID2);
  139. JPH_ASSERT(lock.Succeeded()); // When this runs all bodies are locked so this should not fail
  140. const Body *body = &lock.GetBody();
  141. if (body->IsSensor())
  142. return;
  143. // Test that we're not hitting a vertical wall
  144. Vec3 normal = -inResult.mPenetrationAxis.Normalized();
  145. if (normal.Dot(mUpDirection) > mCosMaxSlopeAngle)
  146. {
  147. // Update early out fraction to this hit
  148. UpdateEarlyOutFraction(early_out);
  149. // Get the contact properties
  150. mBody = body;
  151. mSubShapeID2 = inResult.mSubShapeID2;
  152. mContactPosition = mShapeCast.mCenterOfMassStart.GetTranslation() + inResult.mContactPointOn2;
  153. mContactNormal = normal;
  154. mFraction = inResult.mFraction;
  155. }
  156. }
  157. }
  158. // Configuration
  159. PhysicsSystem & mPhysicsSystem;
  160. const RShapeCast & mShapeCast;
  161. Vec3 mUpDirection;
  162. float mCosMaxSlopeAngle;
  163. // Resulting closest collision
  164. const Body * mBody = nullptr;
  165. SubShapeID mSubShapeID2;
  166. RVec3 mContactPosition;
  167. Vec3 mContactNormal;
  168. float mFraction;
  169. };
  170. MyCollector collector(inPhysicsSystem, shape_cast, mUp, mCosMaxSlopeAngle);
  171. inPhysicsSystem.GetNarrowPhaseQueryNoLock().CastShape(shape_cast, settings, shape_cast.mCenterOfMassStart.GetTranslation(), collector, broadphase_layer_filter, object_layer_filter, body_filter);
  172. if (collector.mBody == nullptr)
  173. return false;
  174. outBody = const_cast<Body *>(collector.mBody);
  175. outSubShapeID = collector.mSubShapeID2;
  176. outContactPosition = collector.mContactPosition;
  177. outContactNormal = collector.mContactNormal;
  178. outSuspensionLength = max(0.0f, shape_cast_length * collector.mFraction + mRadius - wheel_radius);
  179. return true;
  180. }
  181. void VehicleCollisionTesterCastSphere::PredictContactProperties(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&ioBody, SubShapeID &ioSubShapeID, RVec3 &ioContactPosition, Vec3 &ioContactNormal, float &ioSuspensionLength) const
  182. {
  183. // Recalculate the contact points assuming the contact point is on an infinite plane
  184. const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings();
  185. float d_dot_n = inDirection.Dot(ioContactNormal);
  186. if (d_dot_n < -1.0e-6f)
  187. {
  188. // Reproject the contact position using the suspension cast sphere and the plane formed by the contact position and normal
  189. // This solves x = inOrigin + fraction * inDirection and (x - ioContactPosition) . ioContactNormal = mRadius for fraction
  190. float oc_dot_n = Vec3(ioContactPosition - inOrigin).Dot(ioContactNormal);
  191. float fraction = (mRadius + oc_dot_n) / d_dot_n;
  192. ioContactPosition = inOrigin + fraction * inDirection - mRadius * ioContactNormal;
  193. // Calculate the new suspension length in the same way as the cast sphere normally does
  194. ioSuspensionLength = Clamp(fraction + mRadius - wheel_settings->mRadius, 0.0f, wheel_settings->mSuspensionMaxLength);
  195. }
  196. else
  197. {
  198. // If the normal is pointing away we assume there's no collision anymore
  199. ioSuspensionLength = wheel_settings->mSuspensionMaxLength;
  200. }
  201. }
  202. bool VehicleCollisionTesterCastCylinder::Collide(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&outBody, SubShapeID &outSubShapeID, RVec3 &outContactPosition, Vec3 &outContactNormal, float &outSuspensionLength) const
  203. {
  204. const DefaultBroadPhaseLayerFilter default_broadphase_layer_filter = inPhysicsSystem.GetDefaultBroadPhaseLayerFilter(mObjectLayer);
  205. const BroadPhaseLayerFilter &broadphase_layer_filter = mBroadPhaseLayerFilter != nullptr? *mBroadPhaseLayerFilter : default_broadphase_layer_filter;
  206. const DefaultObjectLayerFilter default_object_layer_filter = inPhysicsSystem.GetDefaultLayerFilter(mObjectLayer);
  207. const ObjectLayerFilter &object_layer_filter = mObjectLayerFilter != nullptr? *mObjectLayerFilter : default_object_layer_filter;
  208. const IgnoreSingleBodyFilter default_body_filter(inVehicleBodyID);
  209. const BodyFilter &body_filter = mBodyFilter != nullptr? *mBodyFilter : default_body_filter;
  210. const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings();
  211. float max_suspension_length = wheel_settings->mSuspensionMaxLength;
  212. // Get the wheel transform given that the cylinder rotates around the Y axis
  213. RMat44 shape_cast_start = inVehicleConstraint.GetWheelWorldTransform(inWheelIndex, Vec3::sAxisY(), Vec3::sAxisX());
  214. shape_cast_start.SetTranslation(inOrigin);
  215. // Construct a cylinder with the dimensions of the wheel
  216. float wheel_half_width = 0.5f * wheel_settings->mWidth;
  217. CylinderShape cylinder(wheel_half_width, wheel_settings->mRadius, min(wheel_half_width, wheel_settings->mRadius) * mConvexRadiusFraction);
  218. cylinder.SetEmbedded();
  219. RShapeCast shape_cast(&cylinder, Vec3::sReplicate(1.0f), shape_cast_start, inDirection * max_suspension_length);
  220. ShapeCastSettings settings;
  221. settings.mUseShrunkenShapeAndConvexRadius = true;
  222. settings.mReturnDeepestPoint = true;
  223. class MyCollector : public CastShapeCollector
  224. {
  225. public:
  226. MyCollector(PhysicsSystem &inPhysicsSystem, const RShapeCast &inShapeCast) :
  227. mPhysicsSystem(inPhysicsSystem),
  228. mShapeCast(inShapeCast)
  229. {
  230. }
  231. virtual void AddHit(const ShapeCastResult &inResult) override
  232. {
  233. // Test if this collision is closer/deeper than the previous one
  234. float early_out = inResult.GetEarlyOutFraction();
  235. if (early_out < GetEarlyOutFraction())
  236. {
  237. // Lock the body
  238. BodyLockRead lock(mPhysicsSystem.GetBodyLockInterfaceNoLock(), inResult.mBodyID2);
  239. JPH_ASSERT(lock.Succeeded()); // When this runs all bodies are locked so this should not fail
  240. const Body *body = &lock.GetBody();
  241. if (body->IsSensor())
  242. return;
  243. // Update early out fraction to this hit
  244. UpdateEarlyOutFraction(early_out);
  245. // Get the contact properties
  246. mBody = body;
  247. mSubShapeID2 = inResult.mSubShapeID2;
  248. mContactPosition = mShapeCast.mCenterOfMassStart.GetTranslation() + inResult.mContactPointOn2;
  249. mContactNormal = -inResult.mPenetrationAxis.Normalized();
  250. mFraction = inResult.mFraction;
  251. }
  252. }
  253. // Configuration
  254. PhysicsSystem & mPhysicsSystem;
  255. const RShapeCast & mShapeCast;
  256. // Resulting closest collision
  257. const Body * mBody = nullptr;
  258. SubShapeID mSubShapeID2;
  259. RVec3 mContactPosition;
  260. Vec3 mContactNormal;
  261. float mFraction;
  262. };
  263. MyCollector collector(inPhysicsSystem, shape_cast);
  264. inPhysicsSystem.GetNarrowPhaseQueryNoLock().CastShape(shape_cast, settings, shape_cast.mCenterOfMassStart.GetTranslation(), collector, broadphase_layer_filter, object_layer_filter, body_filter);
  265. if (collector.mBody == nullptr)
  266. return false;
  267. outBody = const_cast<Body *>(collector.mBody);
  268. outSubShapeID = collector.mSubShapeID2;
  269. outContactPosition = collector.mContactPosition;
  270. outContactNormal = collector.mContactNormal;
  271. outSuspensionLength = max_suspension_length * collector.mFraction;
  272. return true;
  273. }
  274. void VehicleCollisionTesterCastCylinder::PredictContactProperties(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&ioBody, SubShapeID &ioSubShapeID, RVec3 &ioContactPosition, Vec3 &ioContactNormal, float &ioSuspensionLength) const
  275. {
  276. // Recalculate the contact points assuming the contact point is on an infinite plane
  277. const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings();
  278. float d_dot_n = inDirection.Dot(ioContactNormal);
  279. if (d_dot_n < -1.0e-6f)
  280. {
  281. // Wheel size
  282. float half_width = 0.5f * wheel_settings->mWidth;
  283. float radius = wheel_settings->mRadius;
  284. // Get the inverse local space contact normal for a cylinder pointing along Y
  285. RMat44 wheel_transform = inVehicleConstraint.GetWheelWorldTransform(inWheelIndex, Vec3::sAxisY(), Vec3::sAxisX());
  286. Vec3 inverse_local_normal = -wheel_transform.Multiply3x3Transposed(ioContactNormal);
  287. // Get the support point of this normal in local space of the cylinder
  288. // See CylinderShape::Cylinder::GetSupport
  289. float x = inverse_local_normal.GetX(), y = inverse_local_normal.GetY(), z = inverse_local_normal.GetZ();
  290. float o = sqrt(Square(x) + Square(z));
  291. Vec3 support_point;
  292. if (o > 0.0f)
  293. support_point = Vec3((radius * x) / o, Sign(y) * half_width, (radius * z) / o);
  294. else
  295. support_point = Vec3(0, Sign(y) * half_width, 0);
  296. // Rotate back to world space
  297. support_point = wheel_transform.Multiply3x3(support_point);
  298. // Now we can use inOrigin + support_point as the start of a ray of our suspension to the contact plane
  299. // as know that it is the first point on the wheel that will hit the plane
  300. RVec3 origin = inOrigin + support_point;
  301. // Calculate contact position and suspension length, the is the same as VehicleCollisionTesterRay
  302. // but we don't need to take the radius into account anymore
  303. Vec3 oc(ioContactPosition - origin);
  304. ioContactPosition = origin + oc.Dot(ioContactNormal) / d_dot_n * inDirection;
  305. ioSuspensionLength = Clamp(oc.Dot(inDirection), 0.0f, wheel_settings->mSuspensionMaxLength);
  306. }
  307. else
  308. {
  309. // If the normal is pointing away we assume there's no collision anymore
  310. ioSuspensionLength = wheel_settings->mSuspensionMaxLength;
  311. }
  312. }
  313. JPH_NAMESPACE_END