CapsuleShape.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  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/Shape/CapsuleShape.h>
  6. #include <Jolt/Physics/Collision/Shape/SphereShape.h>
  7. #include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>
  8. #include <Jolt/Physics/Collision/Shape/GetTrianglesContext.h>
  9. #include <Jolt/Physics/Collision/RayCast.h>
  10. #include <Jolt/Physics/Collision/CastResult.h>
  11. #include <Jolt/Physics/Collision/CollidePointResult.h>
  12. #include <Jolt/Physics/Collision/TransformedShape.h>
  13. #include <Jolt/Physics/SoftBody/SoftBodyVertex.h>
  14. #include <Jolt/Geometry/RayCapsule.h>
  15. #include <Jolt/ObjectStream/TypeDeclarations.h>
  16. #include <Jolt/Core/StreamIn.h>
  17. #include <Jolt/Core/StreamOut.h>
  18. #ifdef JPH_DEBUG_RENDERER
  19. #include <Jolt/Renderer/DebugRenderer.h>
  20. #endif // JPH_DEBUG_RENDERER
  21. JPH_NAMESPACE_BEGIN
  22. JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(CapsuleShapeSettings)
  23. {
  24. JPH_ADD_BASE_CLASS(CapsuleShapeSettings, ConvexShapeSettings)
  25. JPH_ADD_ATTRIBUTE(CapsuleShapeSettings, mRadius)
  26. JPH_ADD_ATTRIBUTE(CapsuleShapeSettings, mHalfHeightOfCylinder)
  27. }
  28. static const int cCapsuleDetailLevel = 2;
  29. static const std::vector<Vec3> sCapsuleTopTriangles = []() {
  30. std::vector<Vec3> verts;
  31. GetTrianglesContextVertexList::sCreateHalfUnitSphereTop(verts, cCapsuleDetailLevel);
  32. return verts;
  33. }();
  34. static const std::vector<Vec3> sCapsuleMiddleTriangles = []() {
  35. std::vector<Vec3> verts;
  36. GetTrianglesContextVertexList::sCreateUnitOpenCylinder(verts, cCapsuleDetailLevel);
  37. return verts;
  38. }();
  39. static const std::vector<Vec3> sCapsuleBottomTriangles = []() {
  40. std::vector<Vec3> verts;
  41. GetTrianglesContextVertexList::sCreateHalfUnitSphereBottom(verts, cCapsuleDetailLevel);
  42. return verts;
  43. }();
  44. ShapeSettings::ShapeResult CapsuleShapeSettings::Create() const
  45. {
  46. if (mCachedResult.IsEmpty())
  47. {
  48. Ref<Shape> shape;
  49. if (IsValid() && IsSphere())
  50. {
  51. // If the capsule has no height, use a sphere instead
  52. shape = new SphereShape(mRadius, mMaterial);
  53. mCachedResult.Set(shape);
  54. }
  55. else
  56. shape = new CapsuleShape(*this, mCachedResult);
  57. }
  58. return mCachedResult;
  59. }
  60. CapsuleShape::CapsuleShape(const CapsuleShapeSettings &inSettings, ShapeResult &outResult) :
  61. ConvexShape(EShapeSubType::Capsule, inSettings, outResult),
  62. mRadius(inSettings.mRadius),
  63. mHalfHeightOfCylinder(inSettings.mHalfHeightOfCylinder)
  64. {
  65. if (inSettings.mHalfHeightOfCylinder <= 0.0f)
  66. {
  67. outResult.SetError("Invalid height");
  68. return;
  69. }
  70. if (inSettings.mRadius <= 0.0f)
  71. {
  72. outResult.SetError("Invalid radius");
  73. return;
  74. }
  75. outResult.Set(this);
  76. }
  77. class CapsuleShape::CapsuleNoConvex final : public Support
  78. {
  79. public:
  80. CapsuleNoConvex(Vec3Arg inHalfHeightOfCylinder, float inConvexRadius) :
  81. mHalfHeightOfCylinder(inHalfHeightOfCylinder),
  82. mConvexRadius(inConvexRadius)
  83. {
  84. static_assert(sizeof(CapsuleNoConvex) <= sizeof(SupportBuffer), "Buffer size too small");
  85. JPH_ASSERT(IsAligned(this, alignof(CapsuleNoConvex)));
  86. }
  87. virtual Vec3 GetSupport(Vec3Arg inDirection) const override
  88. {
  89. if (inDirection.GetY() > 0)
  90. return mHalfHeightOfCylinder;
  91. else
  92. return -mHalfHeightOfCylinder;
  93. }
  94. virtual float GetConvexRadius() const override
  95. {
  96. return mConvexRadius;
  97. }
  98. private:
  99. Vec3 mHalfHeightOfCylinder;
  100. float mConvexRadius;
  101. };
  102. class CapsuleShape::CapsuleWithConvex final : public Support
  103. {
  104. public:
  105. CapsuleWithConvex(Vec3Arg inHalfHeightOfCylinder, float inRadius) :
  106. mHalfHeightOfCylinder(inHalfHeightOfCylinder),
  107. mRadius(inRadius)
  108. {
  109. static_assert(sizeof(CapsuleWithConvex) <= sizeof(SupportBuffer), "Buffer size too small");
  110. JPH_ASSERT(IsAligned(this, alignof(CapsuleWithConvex)));
  111. }
  112. virtual Vec3 GetSupport(Vec3Arg inDirection) const override
  113. {
  114. float len = inDirection.Length();
  115. Vec3 radius = len > 0.0f? inDirection * (mRadius / len) : Vec3::sZero();
  116. if (inDirection.GetY() > 0)
  117. return radius + mHalfHeightOfCylinder;
  118. else
  119. return radius - mHalfHeightOfCylinder;
  120. }
  121. virtual float GetConvexRadius() const override
  122. {
  123. return 0.0f;
  124. }
  125. private:
  126. Vec3 mHalfHeightOfCylinder;
  127. float mRadius;
  128. };
  129. const ConvexShape::Support *CapsuleShape::GetSupportFunction(ESupportMode inMode, SupportBuffer &inBuffer, Vec3Arg inScale) const
  130. {
  131. JPH_ASSERT(IsValidScale(inScale));
  132. // Get scaled capsule
  133. Vec3 abs_scale = inScale.Abs();
  134. float scale = abs_scale.GetX();
  135. Vec3 scaled_half_height_of_cylinder = Vec3(0, scale * mHalfHeightOfCylinder, 0);
  136. float scaled_radius = scale * mRadius;
  137. switch (inMode)
  138. {
  139. case ESupportMode::IncludeConvexRadius:
  140. return new (&inBuffer) CapsuleWithConvex(scaled_half_height_of_cylinder, scaled_radius);
  141. case ESupportMode::ExcludeConvexRadius:
  142. case ESupportMode::Default:
  143. return new (&inBuffer) CapsuleNoConvex(scaled_half_height_of_cylinder, scaled_radius);
  144. }
  145. JPH_ASSERT(false);
  146. return nullptr;
  147. }
  148. void CapsuleShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg inDirection, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform, SupportingFace &outVertices) const
  149. {
  150. JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID");
  151. JPH_ASSERT(IsValidScale(inScale));
  152. // Get direction in horizontal plane
  153. Vec3 direction = inDirection;
  154. direction.SetComponent(1, 0.0f);
  155. // Check zero vector, in this case we're hitting from top/bottom so there's no supporting face
  156. float len = direction.Length();
  157. if (len == 0.0f)
  158. return;
  159. // Get scaled capsule
  160. Vec3 abs_scale = inScale.Abs();
  161. float scale = abs_scale.GetX();
  162. Vec3 scaled_half_height_of_cylinder = Vec3(0, scale * mHalfHeightOfCylinder, 0);
  163. float scaled_radius = scale * mRadius;
  164. // Get support point for top and bottom sphere in the opposite of 'direction' (including convex radius)
  165. Vec3 support = (scaled_radius / len) * direction;
  166. Vec3 support_top = scaled_half_height_of_cylinder - support;
  167. Vec3 support_bottom = -scaled_half_height_of_cylinder - support;
  168. // Get projection on inDirection
  169. // Note that inDirection is not normalized, so we need to divide by inDirection.Length() to get the actual projection
  170. // We've multiplied both sides of the if below with inDirection.Length()
  171. float proj_top = support_top.Dot(inDirection);
  172. float proj_bottom = support_bottom.Dot(inDirection);
  173. // If projection is roughly equal then return line, otherwise we return nothing as there's only 1 point
  174. if (abs(proj_top - proj_bottom) < cCapsuleProjectionSlop * inDirection.Length())
  175. {
  176. outVertices.push_back(inCenterOfMassTransform * support_top);
  177. outVertices.push_back(inCenterOfMassTransform * support_bottom);
  178. }
  179. }
  180. MassProperties CapsuleShape::GetMassProperties() const
  181. {
  182. MassProperties p;
  183. float density = GetDensity();
  184. // Calculate inertia and mass according to:
  185. // https://www.gamedev.net/resources/_/technical/math-and-physics/capsule-inertia-tensor-r3856
  186. // Note that there is an error in eq 14, H^2/2 should be H^2/4 in Ixx and Izz, eq 12 does contain the correct value
  187. float radius_sq = Square(mRadius);
  188. float height = 2.0f * mHalfHeightOfCylinder;
  189. float cylinder_mass = JPH_PI * height * radius_sq * density;
  190. float hemisphere_mass = (2.0f * JPH_PI / 3.0f) * radius_sq * mRadius * density;
  191. // From cylinder
  192. float height_sq = Square(height);
  193. float inertia_y = radius_sq * cylinder_mass * 0.5f;
  194. float inertia_xz = inertia_y * 0.5f + cylinder_mass * height_sq / 12.0f;
  195. // From hemispheres
  196. float temp = hemisphere_mass * 4.0f * radius_sq / 5.0f;
  197. inertia_y += temp;
  198. inertia_xz += temp + hemisphere_mass * (0.5f * height_sq + (3.0f / 4.0f) * height * mRadius);
  199. // Mass is cylinder + hemispheres
  200. p.mMass = cylinder_mass + hemisphere_mass * 2.0f;
  201. // Set inertia
  202. p.mInertia = Mat44::sScale(Vec3(inertia_xz, inertia_y, inertia_xz));
  203. return p;
  204. }
  205. Vec3 CapsuleShape::GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const
  206. {
  207. JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID");
  208. if (inLocalSurfacePosition.GetY() > mHalfHeightOfCylinder)
  209. return (inLocalSurfacePosition - Vec3(0, mHalfHeightOfCylinder, 0)).Normalized();
  210. else if (inLocalSurfacePosition.GetY() < -mHalfHeightOfCylinder)
  211. return (inLocalSurfacePosition - Vec3(0, -mHalfHeightOfCylinder, 0)).Normalized();
  212. else
  213. return Vec3(inLocalSurfacePosition.GetX(), 0, inLocalSurfacePosition.GetZ()).NormalizedOr(Vec3::sAxisX());
  214. }
  215. AABox CapsuleShape::GetLocalBounds() const
  216. {
  217. Vec3 extent = Vec3::sReplicate(mRadius) + Vec3(0, mHalfHeightOfCylinder, 0);
  218. return AABox(-extent, extent);
  219. }
  220. AABox CapsuleShape::GetWorldSpaceBounds(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale) const
  221. {
  222. JPH_ASSERT(IsValidScale(inScale));
  223. Vec3 abs_scale = inScale.Abs();
  224. float scale = abs_scale.GetX();
  225. Vec3 extent = Vec3::sReplicate(scale * mRadius);
  226. Vec3 height = Vec3(0, scale * mHalfHeightOfCylinder, 0);
  227. Vec3 p1 = inCenterOfMassTransform * -height;
  228. Vec3 p2 = inCenterOfMassTransform * height;
  229. return AABox(Vec3::sMin(p1, p2) - extent, Vec3::sMax(p1, p2) + extent);
  230. }
  231. #ifdef JPH_DEBUG_RENDERER
  232. void CapsuleShape::Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const
  233. {
  234. DebugRenderer::EDrawMode draw_mode = inDrawWireframe? DebugRenderer::EDrawMode::Wireframe : DebugRenderer::EDrawMode::Solid;
  235. inRenderer->DrawCapsule(inCenterOfMassTransform * Mat44::sScale(inScale.Abs().GetX()), mHalfHeightOfCylinder, mRadius, inUseMaterialColors? GetMaterial()->GetDebugColor() : inColor, DebugRenderer::ECastShadow::On, draw_mode);
  236. }
  237. #endif // JPH_DEBUG_RENDERER
  238. bool CapsuleShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const
  239. {
  240. // Test ray against capsule
  241. float fraction = RayCapsule(inRay.mOrigin, inRay.mDirection, mHalfHeightOfCylinder, mRadius);
  242. if (fraction < ioHit.mFraction)
  243. {
  244. ioHit.mFraction = fraction;
  245. ioHit.mSubShapeID2 = inSubShapeIDCreator.GetID();
  246. return true;
  247. }
  248. return false;
  249. }
  250. void CapsuleShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter) const
  251. {
  252. // Test shape filter
  253. if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))
  254. return;
  255. float radius_sq = Square(mRadius);
  256. // Get vertical distance to the top/bottom sphere centers
  257. float delta_y = abs(inPoint.GetY()) - mHalfHeightOfCylinder;
  258. // Get distance in horizontal plane
  259. float xz_sq = Square(inPoint.GetX()) + Square(inPoint.GetZ());
  260. // Check if the point is in one of the two spheres
  261. bool in_sphere = xz_sq + Square(delta_y) <= radius_sq;
  262. // Check if the point is in the cylinder in the middle
  263. bool in_cylinder = delta_y <= 0.0f && xz_sq <= radius_sq;
  264. if (in_sphere || in_cylinder)
  265. ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
  266. }
  267. void CapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
  268. {
  269. JPH_ASSERT(IsValidScale(inScale));
  270. Mat44 inverse_transform = inCenterOfMassTransform.InversedRotationTranslation();
  271. // Get scaled capsule
  272. float scale = abs(inScale.GetX());
  273. float half_height_of_cylinder = scale * mHalfHeightOfCylinder;
  274. float radius = scale * mRadius;
  275. for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v)
  276. if (v->mInvMass > 0.0f)
  277. {
  278. // Calculate penetration
  279. Vec3 local_pos = inverse_transform * v->mPosition;
  280. if (abs(local_pos.GetY()) <= half_height_of_cylinder)
  281. {
  282. // Near cylinder
  283. Vec3 normal = local_pos;
  284. normal.SetY(0.0f);
  285. float normal_length = normal.Length();
  286. float penetration = radius - normal_length;
  287. if (penetration > v->mLargestPenetration)
  288. {
  289. v->mLargestPenetration = penetration;
  290. // Calculate contact point and normal
  291. normal = normal_length > 0.0f? normal / normal_length : Vec3::sAxisX();
  292. Vec3 point = radius * normal;
  293. // Store collision
  294. v->mCollisionPlane = Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform);
  295. v->mCollidingShapeIndex = inCollidingShapeIndex;
  296. }
  297. }
  298. else
  299. {
  300. // Near cap
  301. Vec3 center = Vec3(0, Sign(local_pos.GetY()) * half_height_of_cylinder, 0);
  302. Vec3 delta = local_pos - center;
  303. float distance = delta.Length();
  304. float penetration = radius - distance;
  305. if (penetration > v->mLargestPenetration)
  306. {
  307. v->mLargestPenetration = penetration;
  308. // Calculate contact point and normal
  309. Vec3 normal = delta / distance;
  310. Vec3 point = center + radius * normal;
  311. // Store collision
  312. v->mCollisionPlane = Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform);
  313. v->mCollidingShapeIndex = inCollidingShapeIndex;
  314. }
  315. }
  316. }
  317. }
  318. void CapsuleShape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const
  319. {
  320. Vec3 scale;
  321. Mat44 transform = inCenterOfMassTransform.Decompose(scale);
  322. TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetQuaternion(), this, BodyID(), SubShapeIDCreator());
  323. ts.SetShapeScale(ScaleHelpers::MakeUniformScale(scale.Abs()));
  324. ioCollector.AddHit(ts);
  325. }
  326. void CapsuleShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const
  327. {
  328. JPH_ASSERT(IsValidScale(inScale));
  329. Vec3 abs_scale = inScale.Abs();
  330. float scale = abs_scale.GetX();
  331. GetTrianglesContextMultiVertexList *context = new (&ioContext) GetTrianglesContextMultiVertexList(false, GetMaterial());
  332. Mat44 world_matrix = Mat44::sRotationTranslation(inRotation, inPositionCOM) * Mat44::sScale(scale);
  333. Mat44 top_matrix = world_matrix * Mat44(Vec4(mRadius, 0, 0, 0), Vec4(0, mRadius, 0, 0), Vec4(0, 0, mRadius, 0), Vec4(0, mHalfHeightOfCylinder, 0, 1));
  334. context->AddPart(top_matrix, sCapsuleTopTriangles.data(), sCapsuleTopTriangles.size());
  335. Mat44 middle_matrix = world_matrix * Mat44::sScale(Vec3(mRadius, mHalfHeightOfCylinder, mRadius));
  336. context->AddPart(middle_matrix, sCapsuleMiddleTriangles.data(), sCapsuleMiddleTriangles.size());
  337. Mat44 bottom_matrix = world_matrix * Mat44(Vec4(mRadius, 0, 0, 0), Vec4(0, mRadius, 0, 0), Vec4(0, 0, mRadius, 0), Vec4(0, -mHalfHeightOfCylinder, 0, 1));
  338. context->AddPart(bottom_matrix, sCapsuleBottomTriangles.data(), sCapsuleBottomTriangles.size());
  339. }
  340. int CapsuleShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const
  341. {
  342. return ((GetTrianglesContextMultiVertexList &)ioContext).GetTrianglesNext(inMaxTrianglesRequested, outTriangleVertices, outMaterials);
  343. }
  344. void CapsuleShape::SaveBinaryState(StreamOut &inStream) const
  345. {
  346. ConvexShape::SaveBinaryState(inStream);
  347. inStream.Write(mRadius);
  348. inStream.Write(mHalfHeightOfCylinder);
  349. }
  350. void CapsuleShape::RestoreBinaryState(StreamIn &inStream)
  351. {
  352. ConvexShape::RestoreBinaryState(inStream);
  353. inStream.Read(mRadius);
  354. inStream.Read(mHalfHeightOfCylinder);
  355. }
  356. bool CapsuleShape::IsValidScale(Vec3Arg inScale) const
  357. {
  358. return ConvexShape::IsValidScale(inScale) && ScaleHelpers::IsUniformScale(inScale.Abs());
  359. }
  360. void CapsuleShape::sRegister()
  361. {
  362. ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::Capsule);
  363. f.mConstruct = []() -> Shape * { return new CapsuleShape; };
  364. f.mColor = Color::sGreen;
  365. }
  366. JPH_NAMESPACE_END