Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/CapsuleShape.cpp
9913 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>7#include <Jolt/Physics/Collision/Shape/SphereShape.h>8#include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>9#include <Jolt/Physics/Collision/Shape/GetTrianglesContext.h>10#include <Jolt/Physics/Collision/RayCast.h>11#include <Jolt/Physics/Collision/CastResult.h>12#include <Jolt/Physics/Collision/CollidePointResult.h>13#include <Jolt/Physics/Collision/TransformedShape.h>14#include <Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h>15#include <Jolt/Geometry/RayCapsule.h>16#include <Jolt/ObjectStream/TypeDeclarations.h>17#include <Jolt/Core/StreamIn.h>18#include <Jolt/Core/StreamOut.h>19#ifdef JPH_DEBUG_RENDERER20#include <Jolt/Renderer/DebugRenderer.h>21#endif // JPH_DEBUG_RENDERER2223JPH_NAMESPACE_BEGIN2425JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(CapsuleShapeSettings)26{27JPH_ADD_BASE_CLASS(CapsuleShapeSettings, ConvexShapeSettings)2829JPH_ADD_ATTRIBUTE(CapsuleShapeSettings, mRadius)30JPH_ADD_ATTRIBUTE(CapsuleShapeSettings, mHalfHeightOfCylinder)31}3233static const int cCapsuleDetailLevel = 2;3435static const StaticArray<Vec3, 192> sCapsuleTopTriangles = []() {36StaticArray<Vec3, 192> verts;37GetTrianglesContextVertexList::sCreateHalfUnitSphereTop(verts, cCapsuleDetailLevel);38return verts;39}();4041static const StaticArray<Vec3, 96> sCapsuleMiddleTriangles = []() {42StaticArray<Vec3, 96> verts;43GetTrianglesContextVertexList::sCreateUnitOpenCylinder(verts, cCapsuleDetailLevel);44return verts;45}();4647static const StaticArray<Vec3, 192> sCapsuleBottomTriangles = []() {48StaticArray<Vec3, 192> verts;49GetTrianglesContextVertexList::sCreateHalfUnitSphereBottom(verts, cCapsuleDetailLevel);50return verts;51}();5253ShapeSettings::ShapeResult CapsuleShapeSettings::Create() const54{55if (mCachedResult.IsEmpty())56{57Ref<Shape> shape;58if (IsValid() && IsSphere())59{60// If the capsule has no height, use a sphere instead61shape = new SphereShape(mRadius, mMaterial);62mCachedResult.Set(shape);63}64else65shape = new CapsuleShape(*this, mCachedResult);66}67return mCachedResult;68}6970CapsuleShape::CapsuleShape(const CapsuleShapeSettings &inSettings, ShapeResult &outResult) :71ConvexShape(EShapeSubType::Capsule, inSettings, outResult),72mRadius(inSettings.mRadius),73mHalfHeightOfCylinder(inSettings.mHalfHeightOfCylinder)74{75if (inSettings.mHalfHeightOfCylinder <= 0.0f)76{77outResult.SetError("Invalid height");78return;79}8081if (inSettings.mRadius <= 0.0f)82{83outResult.SetError("Invalid radius");84return;85}8687outResult.Set(this);88}8990class CapsuleShape::CapsuleNoConvex final : public Support91{92public:93CapsuleNoConvex(Vec3Arg inHalfHeightOfCylinder, float inConvexRadius) :94mHalfHeightOfCylinder(inHalfHeightOfCylinder),95mConvexRadius(inConvexRadius)96{97static_assert(sizeof(CapsuleNoConvex) <= sizeof(SupportBuffer), "Buffer size too small");98JPH_ASSERT(IsAligned(this, alignof(CapsuleNoConvex)));99}100101virtual Vec3 GetSupport(Vec3Arg inDirection) const override102{103if (inDirection.GetY() > 0)104return mHalfHeightOfCylinder;105else106return -mHalfHeightOfCylinder;107}108109virtual float GetConvexRadius() const override110{111return mConvexRadius;112}113114private:115Vec3 mHalfHeightOfCylinder;116float mConvexRadius;117};118119class CapsuleShape::CapsuleWithConvex final : public Support120{121public:122CapsuleWithConvex(Vec3Arg inHalfHeightOfCylinder, float inRadius) :123mHalfHeightOfCylinder(inHalfHeightOfCylinder),124mRadius(inRadius)125{126static_assert(sizeof(CapsuleWithConvex) <= sizeof(SupportBuffer), "Buffer size too small");127JPH_ASSERT(IsAligned(this, alignof(CapsuleWithConvex)));128}129130virtual Vec3 GetSupport(Vec3Arg inDirection) const override131{132float len = inDirection.Length();133Vec3 radius = len > 0.0f? inDirection * (mRadius / len) : Vec3::sZero();134135if (inDirection.GetY() > 0)136return radius + mHalfHeightOfCylinder;137else138return radius - mHalfHeightOfCylinder;139}140141virtual float GetConvexRadius() const override142{143return 0.0f;144}145146private:147Vec3 mHalfHeightOfCylinder;148float mRadius;149};150151const ConvexShape::Support *CapsuleShape::GetSupportFunction(ESupportMode inMode, SupportBuffer &inBuffer, Vec3Arg inScale) const152{153JPH_ASSERT(IsValidScale(inScale));154155// Get scaled capsule156Vec3 abs_scale = inScale.Abs();157float scale = abs_scale.GetX();158Vec3 scaled_half_height_of_cylinder = Vec3(0, scale * mHalfHeightOfCylinder, 0);159float scaled_radius = scale * mRadius;160161switch (inMode)162{163case ESupportMode::IncludeConvexRadius:164return new (&inBuffer) CapsuleWithConvex(scaled_half_height_of_cylinder, scaled_radius);165166case ESupportMode::ExcludeConvexRadius:167case ESupportMode::Default:168return new (&inBuffer) CapsuleNoConvex(scaled_half_height_of_cylinder, scaled_radius);169}170171JPH_ASSERT(false);172return nullptr;173}174175void CapsuleShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg inDirection, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform, SupportingFace &outVertices) const176{177JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID");178JPH_ASSERT(IsValidScale(inScale));179180// Get direction in horizontal plane181Vec3 direction = inDirection;182direction.SetComponent(1, 0.0f);183184// Check zero vector, in this case we're hitting from top/bottom so there's no supporting face185float len = direction.Length();186if (len == 0.0f)187return;188189// Get scaled capsule190Vec3 abs_scale = inScale.Abs();191float scale = abs_scale.GetX();192Vec3 scaled_half_height_of_cylinder = Vec3(0, scale * mHalfHeightOfCylinder, 0);193float scaled_radius = scale * mRadius;194195// Get support point for top and bottom sphere in the opposite of 'direction' (including convex radius)196Vec3 support = (scaled_radius / len) * direction;197Vec3 support_top = scaled_half_height_of_cylinder - support;198Vec3 support_bottom = -scaled_half_height_of_cylinder - support;199200// Get projection on inDirection201// Note that inDirection is not normalized, so we need to divide by inDirection.Length() to get the actual projection202// We've multiplied both sides of the if below with inDirection.Length()203float proj_top = support_top.Dot(inDirection);204float proj_bottom = support_bottom.Dot(inDirection);205206// If projection is roughly equal then return line, otherwise we return nothing as there's only 1 point207if (abs(proj_top - proj_bottom) < cCapsuleProjectionSlop * inDirection.Length())208{209outVertices.push_back(inCenterOfMassTransform * support_top);210outVertices.push_back(inCenterOfMassTransform * support_bottom);211}212}213214MassProperties CapsuleShape::GetMassProperties() const215{216MassProperties p;217218float density = GetDensity();219220// Calculate inertia and mass according to:221// https://www.gamedev.net/resources/_/technical/math-and-physics/capsule-inertia-tensor-r3856222// 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 value223float radius_sq = Square(mRadius);224float height = 2.0f * mHalfHeightOfCylinder;225float cylinder_mass = JPH_PI * height * radius_sq * density;226float hemisphere_mass = (2.0f * JPH_PI / 3.0f) * radius_sq * mRadius * density;227228// From cylinder229float height_sq = Square(height);230float inertia_y = radius_sq * cylinder_mass * 0.5f;231float inertia_xz = inertia_y * 0.5f + cylinder_mass * height_sq / 12.0f;232233// From hemispheres234float temp = hemisphere_mass * 4.0f * radius_sq / 5.0f;235inertia_y += temp;236inertia_xz += temp + hemisphere_mass * (0.5f * height_sq + (3.0f / 4.0f) * height * mRadius);237238// Mass is cylinder + hemispheres239p.mMass = cylinder_mass + hemisphere_mass * 2.0f;240241// Set inertia242p.mInertia = Mat44::sScale(Vec3(inertia_xz, inertia_y, inertia_xz));243244return p;245}246247Vec3 CapsuleShape::GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const248{249JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID");250251if (inLocalSurfacePosition.GetY() > mHalfHeightOfCylinder)252return (inLocalSurfacePosition - Vec3(0, mHalfHeightOfCylinder, 0)).Normalized();253else if (inLocalSurfacePosition.GetY() < -mHalfHeightOfCylinder)254return (inLocalSurfacePosition - Vec3(0, -mHalfHeightOfCylinder, 0)).Normalized();255else256return Vec3(inLocalSurfacePosition.GetX(), 0, inLocalSurfacePosition.GetZ()).NormalizedOr(Vec3::sAxisX());257}258259AABox CapsuleShape::GetLocalBounds() const260{261Vec3 extent = Vec3::sReplicate(mRadius) + Vec3(0, mHalfHeightOfCylinder, 0);262return AABox(-extent, extent);263}264265AABox CapsuleShape::GetWorldSpaceBounds(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale) const266{267JPH_ASSERT(IsValidScale(inScale));268269Vec3 abs_scale = inScale.Abs();270float scale = abs_scale.GetX();271Vec3 extent = Vec3::sReplicate(scale * mRadius);272Vec3 height = Vec3(0, scale * mHalfHeightOfCylinder, 0);273Vec3 p1 = inCenterOfMassTransform * -height;274Vec3 p2 = inCenterOfMassTransform * height;275return AABox(Vec3::sMin(p1, p2) - extent, Vec3::sMax(p1, p2) + extent);276}277278#ifdef JPH_DEBUG_RENDERER279void CapsuleShape::Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const280{281DebugRenderer::EDrawMode draw_mode = inDrawWireframe? DebugRenderer::EDrawMode::Wireframe : DebugRenderer::EDrawMode::Solid;282inRenderer->DrawCapsule(inCenterOfMassTransform * Mat44::sScale(inScale.Abs().GetX()), mHalfHeightOfCylinder, mRadius, inUseMaterialColors? GetMaterial()->GetDebugColor() : inColor, DebugRenderer::ECastShadow::On, draw_mode);283}284#endif // JPH_DEBUG_RENDERER285286bool CapsuleShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const287{288// Test ray against capsule289float fraction = RayCapsule(inRay.mOrigin, inRay.mDirection, mHalfHeightOfCylinder, mRadius);290if (fraction < ioHit.mFraction)291{292ioHit.mFraction = fraction;293ioHit.mSubShapeID2 = inSubShapeIDCreator.GetID();294return true;295}296return false;297}298299void CapsuleShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter) const300{301// Test shape filter302if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))303return;304305float radius_sq = Square(mRadius);306307// Get vertical distance to the top/bottom sphere centers308float delta_y = abs(inPoint.GetY()) - mHalfHeightOfCylinder;309310// Get distance in horizontal plane311float xz_sq = Square(inPoint.GetX()) + Square(inPoint.GetZ());312313// Check if the point is in one of the two spheres314bool in_sphere = xz_sq + Square(delta_y) <= radius_sq;315316// Check if the point is in the cylinder in the middle317bool in_cylinder = delta_y <= 0.0f && xz_sq <= radius_sq;318319if (in_sphere || in_cylinder)320ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });321}322323void CapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const324{325JPH_ASSERT(IsValidScale(inScale));326327Mat44 inverse_transform = inCenterOfMassTransform.InversedRotationTranslation();328329// Get scaled capsule330float scale = abs(inScale.GetX());331float half_height_of_cylinder = scale * mHalfHeightOfCylinder;332float radius = scale * mRadius;333334for (CollideSoftBodyVertexIterator v = inVertices, sbv_end = inVertices + inNumVertices; v != sbv_end; ++v)335if (v.GetInvMass() > 0.0f)336{337// Calculate penetration338Vec3 local_pos = inverse_transform * v.GetPosition();339if (abs(local_pos.GetY()) <= half_height_of_cylinder)340{341// Near cylinder342Vec3 normal = local_pos;343normal.SetY(0.0f);344float normal_length = normal.Length();345float penetration = radius - normal_length;346if (v.UpdatePenetration(penetration))347{348// Calculate contact point and normal349normal = normal_length > 0.0f? normal / normal_length : Vec3::sAxisX();350Vec3 point = radius * normal;351352// Store collision353v.SetCollision(Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform), inCollidingShapeIndex);354}355}356else357{358// Near cap359Vec3 center = Vec3(0, Sign(local_pos.GetY()) * half_height_of_cylinder, 0);360Vec3 delta = local_pos - center;361float distance = delta.Length();362float penetration = radius - distance;363if (v.UpdatePenetration(penetration))364{365// Calculate contact point and normal366Vec3 normal = delta / distance;367Vec3 point = center + radius * normal;368369// Store collision370v.SetCollision(Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform), inCollidingShapeIndex);371}372}373}374}375376void CapsuleShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const377{378JPH_ASSERT(IsValidScale(inScale));379380Vec3 abs_scale = inScale.Abs();381float scale = abs_scale.GetX();382383GetTrianglesContextMultiVertexList *context = new (&ioContext) GetTrianglesContextMultiVertexList(false, GetMaterial());384385Mat44 world_matrix = Mat44::sRotationTranslation(inRotation, inPositionCOM) * Mat44::sScale(scale);386387Mat44 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));388context->AddPart(top_matrix, sCapsuleTopTriangles.data(), sCapsuleTopTriangles.size());389390Mat44 middle_matrix = world_matrix * Mat44::sScale(Vec3(mRadius, mHalfHeightOfCylinder, mRadius));391context->AddPart(middle_matrix, sCapsuleMiddleTriangles.data(), sCapsuleMiddleTriangles.size());392393Mat44 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));394context->AddPart(bottom_matrix, sCapsuleBottomTriangles.data(), sCapsuleBottomTriangles.size());395}396397int CapsuleShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const398{399return ((GetTrianglesContextMultiVertexList &)ioContext).GetTrianglesNext(inMaxTrianglesRequested, outTriangleVertices, outMaterials);400}401402void CapsuleShape::SaveBinaryState(StreamOut &inStream) const403{404ConvexShape::SaveBinaryState(inStream);405406inStream.Write(mRadius);407inStream.Write(mHalfHeightOfCylinder);408}409410void CapsuleShape::RestoreBinaryState(StreamIn &inStream)411{412ConvexShape::RestoreBinaryState(inStream);413414inStream.Read(mRadius);415inStream.Read(mHalfHeightOfCylinder);416}417418bool CapsuleShape::IsValidScale(Vec3Arg inScale) const419{420return ConvexShape::IsValidScale(inScale) && ScaleHelpers::IsUniformScale(inScale.Abs());421}422423Vec3 CapsuleShape::MakeScaleValid(Vec3Arg inScale) const424{425Vec3 scale = ScaleHelpers::MakeNonZeroScale(inScale);426427return scale.GetSign() * ScaleHelpers::MakeUniformScale(scale.Abs());428}429430void CapsuleShape::sRegister()431{432ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::Capsule);433f.mConstruct = []() -> Shape * { return new CapsuleShape; };434f.mColor = Color::sGreen;435}436437JPH_NAMESPACE_END438439440