Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/SphereShape.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/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/Collision/CollideSoftBodyVertexIterator.h>14#include <Jolt/Geometry/RaySphere.h>15#include <Jolt/Geometry/Plane.h>16#include <Jolt/Core/StreamIn.h>17#include <Jolt/Core/StreamOut.h>18#include <Jolt/ObjectStream/TypeDeclarations.h>19#ifdef JPH_DEBUG_RENDERER20#include <Jolt/Renderer/DebugRenderer.h>21#endif // JPH_DEBUG_RENDERER2223JPH_NAMESPACE_BEGIN2425JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(SphereShapeSettings)26{27JPH_ADD_BASE_CLASS(SphereShapeSettings, ConvexShapeSettings)2829JPH_ADD_ATTRIBUTE(SphereShapeSettings, mRadius)30}3132ShapeSettings::ShapeResult SphereShapeSettings::Create() const33{34if (mCachedResult.IsEmpty())35Ref<Shape> shape = new SphereShape(*this, mCachedResult);36return mCachedResult;37}3839SphereShape::SphereShape(const SphereShapeSettings &inSettings, ShapeResult &outResult) :40ConvexShape(EShapeSubType::Sphere, inSettings, outResult),41mRadius(inSettings.mRadius)42{43if (inSettings.mRadius <= 0.0f)44{45outResult.SetError("Invalid radius");46return;47}4849outResult.Set(this);50}5152float SphereShape::GetScaledRadius(Vec3Arg inScale) const53{54JPH_ASSERT(IsValidScale(inScale));5556Vec3 abs_scale = inScale.Abs();57return abs_scale.GetX() * mRadius;58}5960AABox SphereShape::GetLocalBounds() const61{62Vec3 half_extent = Vec3::sReplicate(mRadius);63return AABox(-half_extent, half_extent);64}6566AABox SphereShape::GetWorldSpaceBounds(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale) const67{68float scaled_radius = GetScaledRadius(inScale);69Vec3 half_extent = Vec3::sReplicate(scaled_radius);70AABox bounds(-half_extent, half_extent);71bounds.Translate(inCenterOfMassTransform.GetTranslation());72return bounds;73}7475class SphereShape::SphereNoConvex final : public Support76{77public:78explicit SphereNoConvex(float inRadius) :79mRadius(inRadius)80{81static_assert(sizeof(SphereNoConvex) <= sizeof(SupportBuffer), "Buffer size too small");82JPH_ASSERT(IsAligned(this, alignof(SphereNoConvex)));83}8485virtual Vec3 GetSupport(Vec3Arg inDirection) const override86{87return Vec3::sZero();88}8990virtual float GetConvexRadius() const override91{92return mRadius;93}9495private:96float mRadius;97};9899class SphereShape::SphereWithConvex final : public Support100{101public:102explicit SphereWithConvex(float inRadius) :103mRadius(inRadius)104{105static_assert(sizeof(SphereWithConvex) <= sizeof(SupportBuffer), "Buffer size too small");106JPH_ASSERT(IsAligned(this, alignof(SphereWithConvex)));107}108109virtual Vec3 GetSupport(Vec3Arg inDirection) const override110{111float len = inDirection.Length();112return len > 0.0f? (mRadius / len) * inDirection : Vec3::sZero();113}114115virtual float GetConvexRadius() const override116{117return 0.0f;118}119120private:121float mRadius;122};123124const ConvexShape::Support *SphereShape::GetSupportFunction(ESupportMode inMode, SupportBuffer &inBuffer, Vec3Arg inScale) const125{126float scaled_radius = GetScaledRadius(inScale);127128switch (inMode)129{130case ESupportMode::IncludeConvexRadius:131return new (&inBuffer) SphereWithConvex(scaled_radius);132133case ESupportMode::ExcludeConvexRadius:134case ESupportMode::Default:135return new (&inBuffer) SphereNoConvex(scaled_radius);136}137138JPH_ASSERT(false);139return nullptr;140}141142MassProperties SphereShape::GetMassProperties() const143{144MassProperties p;145146// Calculate mass147float r2 = mRadius * mRadius;148p.mMass = (4.0f / 3.0f * JPH_PI) * mRadius * r2 * GetDensity();149150// Calculate inertia151float inertia = (2.0f / 5.0f) * p.mMass * r2;152p.mInertia = Mat44::sScale(inertia);153154return p;155}156157Vec3 SphereShape::GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const158{159JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID");160161float len = inLocalSurfacePosition.Length();162return len != 0.0f? inLocalSurfacePosition / len : Vec3::sAxisY();163}164165void SphereShape::GetSubmergedVolume(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const Plane &inSurface, float &outTotalVolume, float &outSubmergedVolume, Vec3 &outCenterOfBuoyancy JPH_IF_DEBUG_RENDERER(, RVec3Arg inBaseOffset)) const166{167float scaled_radius = GetScaledRadius(inScale);168outTotalVolume = (4.0f / 3.0f * JPH_PI) * Cubed(scaled_radius);169170float distance_to_surface = inSurface.SignedDistance(inCenterOfMassTransform.GetTranslation());171if (distance_to_surface >= scaled_radius)172{173// Above surface174outSubmergedVolume = 0.0f;175outCenterOfBuoyancy = Vec3::sZero();176}177else if (distance_to_surface <= -scaled_radius)178{179// Under surface180outSubmergedVolume = outTotalVolume;181outCenterOfBuoyancy = inCenterOfMassTransform.GetTranslation();182}183else184{185// Intersecting surface186187// Calculate submerged volume, see: https://en.wikipedia.org/wiki/Spherical_cap188float h = scaled_radius - distance_to_surface;189outSubmergedVolume = (JPH_PI / 3.0f) * Square(h) * (3.0f * scaled_radius - h);190191// Calculate center of buoyancy, see: http://mathworld.wolfram.com/SphericalCap.html (eq 10)192float z = (3.0f / 4.0f) * Square(2.0f * scaled_radius - h) / (3.0f * scaled_radius - h);193outCenterOfBuoyancy = inCenterOfMassTransform.GetTranslation() - z * inSurface.GetNormal(); // Negative normal since we want the portion under the water194195#ifdef JPH_DEBUG_RENDERER196// Draw intersection between sphere and water plane197if (sDrawSubmergedVolumes)198{199Vec3 circle_center = inCenterOfMassTransform.GetTranslation() - distance_to_surface * inSurface.GetNormal();200float circle_radius = sqrt(Square(scaled_radius) - Square(distance_to_surface));201DebugRenderer::sInstance->DrawPie(inBaseOffset + circle_center, circle_radius, inSurface.GetNormal(), inSurface.GetNormal().GetNormalizedPerpendicular(), -JPH_PI, JPH_PI, Color::sGreen, DebugRenderer::ECastShadow::Off);202}203#endif // JPH_DEBUG_RENDERER204}205206#ifdef JPH_DEBUG_RENDERER207// Draw center of buoyancy208if (sDrawSubmergedVolumes)209DebugRenderer::sInstance->DrawWireSphere(inBaseOffset + outCenterOfBuoyancy, 0.05f, Color::sRed, 1);210#endif // JPH_DEBUG_RENDERER211}212213#ifdef JPH_DEBUG_RENDERER214void SphereShape::Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const215{216DebugRenderer::EDrawMode draw_mode = inDrawWireframe? DebugRenderer::EDrawMode::Wireframe : DebugRenderer::EDrawMode::Solid;217inRenderer->DrawUnitSphere(inCenterOfMassTransform * Mat44::sScale(mRadius * inScale.Abs().GetX()), inUseMaterialColors? GetMaterial()->GetDebugColor() : inColor, DebugRenderer::ECastShadow::On, draw_mode);218}219#endif // JPH_DEBUG_RENDERER220221bool SphereShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const222{223float fraction = RaySphere(inRay.mOrigin, inRay.mDirection, Vec3::sZero(), mRadius);224if (fraction < ioHit.mFraction)225{226ioHit.mFraction = fraction;227ioHit.mSubShapeID2 = inSubShapeIDCreator.GetID();228return true;229}230return false;231}232233void SphereShape::CastRay(const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector, const ShapeFilter &inShapeFilter) const234{235// Test shape filter236if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))237return;238239float min_fraction, max_fraction;240int num_results = RaySphere(inRay.mOrigin, inRay.mDirection, Vec3::sZero(), mRadius, min_fraction, max_fraction);241if (num_results > 0 // Ray should intersect242&& max_fraction >= 0.0f // End of ray should be inside sphere243&& min_fraction < ioCollector.GetEarlyOutFraction()) // Start of ray should be before early out fraction244{245// Better hit than the current hit246RayCastResult hit;247hit.mBodyID = TransformedShape::sGetBodyID(ioCollector.GetContext());248hit.mSubShapeID2 = inSubShapeIDCreator.GetID();249250// Check front side hit251if (inRayCastSettings.mTreatConvexAsSolid || min_fraction > 0.0f)252{253hit.mFraction = max(0.0f, min_fraction);254ioCollector.AddHit(hit);255}256257// Check back side hit258if (inRayCastSettings.mBackFaceModeConvex == EBackFaceMode::CollideWithBackFaces259&& num_results > 1 // Ray should have 2 intersections260&& max_fraction < ioCollector.GetEarlyOutFraction()) // End of ray should be before early out fraction261{262hit.mFraction = max_fraction;263ioCollector.AddHit(hit);264}265}266}267268void SphereShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter) const269{270// Test shape filter271if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))272return;273274if (inPoint.LengthSq() <= Square(mRadius))275ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });276}277278void SphereShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const279{280Vec3 center = inCenterOfMassTransform.GetTranslation();281float radius = GetScaledRadius(inScale);282283for (CollideSoftBodyVertexIterator v = inVertices, sbv_end = inVertices + inNumVertices; v != sbv_end; ++v)284if (v.GetInvMass() > 0.0f)285{286// Calculate penetration287Vec3 delta = v.GetPosition() - center;288float distance = delta.Length();289float penetration = radius - distance;290if (v.UpdatePenetration(penetration))291{292// Calculate contact point and normal293Vec3 normal = distance > 0.0f? delta / distance : Vec3::sAxisY();294Vec3 point = center + radius * normal;295296// Store collision297v.SetCollision(Plane::sFromPointAndNormal(point, normal), inCollidingShapeIndex);298}299}300}301302void SphereShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const303{304float scaled_radius = GetScaledRadius(inScale);305new (&ioContext) GetTrianglesContextVertexList(inPositionCOM, inRotation, Vec3::sOne(), Mat44::sScale(scaled_radius), sUnitSphereTriangles.data(), sUnitSphereTriangles.size(), GetMaterial());306}307308int SphereShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const309{310return ((GetTrianglesContextVertexList &)ioContext).GetTrianglesNext(inMaxTrianglesRequested, outTriangleVertices, outMaterials);311}312313void SphereShape::SaveBinaryState(StreamOut &inStream) const314{315ConvexShape::SaveBinaryState(inStream);316317inStream.Write(mRadius);318}319320void SphereShape::RestoreBinaryState(StreamIn &inStream)321{322ConvexShape::RestoreBinaryState(inStream);323324inStream.Read(mRadius);325}326327bool SphereShape::IsValidScale(Vec3Arg inScale) const328{329return ConvexShape::IsValidScale(inScale) && ScaleHelpers::IsUniformScale(inScale.Abs());330}331332Vec3 SphereShape::MakeScaleValid(Vec3Arg inScale) const333{334Vec3 scale = ScaleHelpers::MakeNonZeroScale(inScale);335336return scale.GetSign() * ScaleHelpers::MakeUniformScale(scale.Abs());337}338339void SphereShape::sRegister()340{341ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::Sphere);342f.mConstruct = []() -> Shape * { return new SphereShape; };343f.mColor = Color::sGreen;344}345346JPH_NAMESPACE_END347348349