CapsuleShape.cpp 13 KB

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