Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/PlaneShape.cpp
9913 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2024 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/Collision/Shape/PlaneShape.h>7#include <Jolt/Physics/Collision/Shape/ConvexShape.h>8#include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>9#include <Jolt/Physics/Collision/RayCast.h>10#include <Jolt/Physics/Collision/ShapeCast.h>11#include <Jolt/Physics/Collision/ShapeFilter.h>12#include <Jolt/Physics/Collision/CastResult.h>13#include <Jolt/Physics/Collision/CollisionDispatch.h>14#include <Jolt/Physics/Collision/TransformedShape.h>15#include <Jolt/Physics/Collision/CollidePointResult.h>16#include <Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h>17#include <Jolt/Core/Profiler.h>18#include <Jolt/Core/StreamIn.h>19#include <Jolt/Core/StreamOut.h>20#include <Jolt/Geometry/Plane.h>21#include <Jolt/ObjectStream/TypeDeclarations.h>22#ifdef JPH_DEBUG_RENDERER23#include <Jolt/Renderer/DebugRenderer.h>24#endif // JPH_DEBUG_RENDERER2526JPH_NAMESPACE_BEGIN2728JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(PlaneShapeSettings)29{30JPH_ADD_BASE_CLASS(PlaneShapeSettings, ShapeSettings)3132JPH_ADD_ATTRIBUTE(PlaneShapeSettings, mPlane)33JPH_ADD_ATTRIBUTE(PlaneShapeSettings, mMaterial)34JPH_ADD_ATTRIBUTE(PlaneShapeSettings, mHalfExtent)35}3637ShapeSettings::ShapeResult PlaneShapeSettings::Create() const38{39if (mCachedResult.IsEmpty())40Ref<Shape> shape = new PlaneShape(*this, mCachedResult);41return mCachedResult;42}4344inline static void sPlaneGetOrthogonalBasis(Vec3Arg inNormal, Vec3 &outPerp1, Vec3 &outPerp2)45{46outPerp1 = inNormal.Cross(Vec3::sAxisY()).NormalizedOr(Vec3::sAxisX());47outPerp2 = outPerp1.Cross(inNormal).Normalized();48outPerp1 = inNormal.Cross(outPerp2);49}5051void PlaneShape::GetVertices(Vec3 *outVertices) const52{53// Create orthogonal basis54Vec3 normal = mPlane.GetNormal();55Vec3 perp1, perp2;56sPlaneGetOrthogonalBasis(normal, perp1, perp2);5758// Scale basis59perp1 *= mHalfExtent;60perp2 *= mHalfExtent;6162// Calculate corners63Vec3 point = -normal * mPlane.GetConstant();64outVertices[0] = point + perp1 + perp2;65outVertices[1] = point + perp1 - perp2;66outVertices[2] = point - perp1 - perp2;67outVertices[3] = point - perp1 + perp2;68}6970void PlaneShape::CalculateLocalBounds()71{72// Get the vertices of the plane73Vec3 vertices[4];74GetVertices(vertices);7576// Encapsulate the vertices and a point mHalfExtent behind the plane77mLocalBounds = AABox();78Vec3 normal = mPlane.GetNormal();79for (const Vec3 &v : vertices)80{81mLocalBounds.Encapsulate(v);82mLocalBounds.Encapsulate(v - mHalfExtent * normal);83}84}8586PlaneShape::PlaneShape(const PlaneShapeSettings &inSettings, ShapeResult &outResult) :87Shape(EShapeType::Plane, EShapeSubType::Plane, inSettings, outResult),88mPlane(inSettings.mPlane),89mMaterial(inSettings.mMaterial),90mHalfExtent(inSettings.mHalfExtent)91{92if (!mPlane.GetNormal().IsNormalized())93{94outResult.SetError("Plane normal needs to be normalized!");95return;96}9798CalculateLocalBounds();99100outResult.Set(this);101}102103MassProperties PlaneShape::GetMassProperties() const104{105// Object should always be static, return default mass properties106return MassProperties();107}108109void PlaneShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg inDirection, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform, SupportingFace &outVertices) const110{111// Get the vertices of the plane112Vec3 vertices[4];113GetVertices(vertices);114115// Reverse if scale is inside out116if (ScaleHelpers::IsInsideOut(inScale))117{118std::swap(vertices[0], vertices[3]);119std::swap(vertices[1], vertices[2]);120}121122// Transform them to world space123outVertices.clear();124Mat44 com = inCenterOfMassTransform.PreScaled(inScale);125for (const Vec3 &v : vertices)126outVertices.push_back(com * v);127}128129#ifdef JPH_DEBUG_RENDERER130void PlaneShape::Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const131{132// Get the vertices of the plane133Vec3 local_vertices[4];134GetVertices(local_vertices);135136// Reverse if scale is inside out137if (ScaleHelpers::IsInsideOut(inScale))138{139std::swap(local_vertices[0], local_vertices[3]);140std::swap(local_vertices[1], local_vertices[2]);141}142143// Transform them to world space144RMat44 com = inCenterOfMassTransform.PreScaled(inScale);145RVec3 vertices[4];146for (uint i = 0; i < 4; ++i)147vertices[i] = com * local_vertices[i];148149// Determine the color150Color color = inUseMaterialColors? GetMaterial(SubShapeID())->GetDebugColor() : inColor;151152// Draw the plane153if (inDrawWireframe)154{155inRenderer->DrawWireTriangle(vertices[0], vertices[1], vertices[2], color);156inRenderer->DrawWireTriangle(vertices[0], vertices[2], vertices[3], color);157}158else159{160inRenderer->DrawTriangle(vertices[0], vertices[1], vertices[2], color, DebugRenderer::ECastShadow::On);161inRenderer->DrawTriangle(vertices[0], vertices[2], vertices[3], color, DebugRenderer::ECastShadow::On);162}163}164#endif // JPH_DEBUG_RENDERER165166bool PlaneShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const167{168JPH_PROFILE_FUNCTION();169170// Test starting inside of negative half space171float distance = mPlane.SignedDistance(inRay.mOrigin);172if (distance <= 0.0f)173{174ioHit.mFraction = 0.0f;175ioHit.mSubShapeID2 = inSubShapeIDCreator.GetID();176return true;177}178179// Test ray parallel to plane180float dot = inRay.mDirection.Dot(mPlane.GetNormal());181if (dot == 0.0f)182return false;183184// Calculate hit fraction185float fraction = -distance / dot;186if (fraction >= 0.0f && fraction < ioHit.mFraction)187{188ioHit.mFraction = fraction;189ioHit.mSubShapeID2 = inSubShapeIDCreator.GetID();190return true;191}192193return false;194}195196void PlaneShape::CastRay(const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector, const ShapeFilter &inShapeFilter) const197{198JPH_PROFILE_FUNCTION();199200// Test shape filter201if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))202return;203204// Inside solid half space?205float distance = mPlane.SignedDistance(inRay.mOrigin);206if (inRayCastSettings.mTreatConvexAsSolid207&& distance <= 0.0f // Inside plane208&& ioCollector.GetEarlyOutFraction() > 0.0f) // Willing to accept hits at fraction 0209{210// Hit at fraction 0211RayCastResult hit;212hit.mBodyID = TransformedShape::sGetBodyID(ioCollector.GetContext());213hit.mFraction = 0.0f;214hit.mSubShapeID2 = inSubShapeIDCreator.GetID();215ioCollector.AddHit(hit);216}217218float dot = inRay.mDirection.Dot(mPlane.GetNormal());219if (dot != 0.0f // Parallel ray will not hit plane220&& (inRayCastSettings.mBackFaceModeConvex == EBackFaceMode::CollideWithBackFaces || dot < 0.0f)) // Back face culling221{222// Calculate hit with plane223float fraction = -distance / dot;224if (fraction >= 0.0f && fraction < ioCollector.GetEarlyOutFraction())225{226RayCastResult hit;227hit.mBodyID = TransformedShape::sGetBodyID(ioCollector.GetContext());228hit.mFraction = fraction;229hit.mSubShapeID2 = inSubShapeIDCreator.GetID();230ioCollector.AddHit(hit);231}232}233}234235void PlaneShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter) const236{237JPH_PROFILE_FUNCTION();238239// Test shape filter240if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))241return;242243// Check if the point is inside the plane244if (mPlane.SignedDistance(inPoint) < 0.0f)245ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });246}247248void PlaneShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const249{250JPH_PROFILE_FUNCTION();251252// Convert plane to world space253Plane plane = mPlane.Scaled(inScale).GetTransformed(inCenterOfMassTransform);254255for (CollideSoftBodyVertexIterator v = inVertices, sbv_end = inVertices + inNumVertices; v != sbv_end; ++v)256if (v.GetInvMass() > 0.0f)257{258// Calculate penetration259float penetration = -plane.SignedDistance(v.GetPosition());260if (v.UpdatePenetration(penetration))261v.SetCollision(plane, inCollidingShapeIndex);262}263}264265// This is a version of GetSupportingFace that returns a face that is large enough to cover the shape we're colliding with but not as large as the regular GetSupportedFace to avoid numerical precision issues266inline static void sGetSupportingFace(const ConvexShape *inShape, Vec3Arg inShapeCOM, const Plane &inPlane, Mat44Arg inPlaneToWorld, ConvexShape::SupportingFace &outPlaneFace)267{268// Project COM of shape onto plane269Plane world_plane = inPlane.GetTransformed(inPlaneToWorld);270Vec3 center = world_plane.ProjectPointOnPlane(inShapeCOM);271272// Create orthogonal basis for the plane273Vec3 normal = world_plane.GetNormal();274Vec3 perp1, perp2;275sPlaneGetOrthogonalBasis(normal, perp1, perp2);276277// Base the size of the face on the bounding box of the shape, ensuring that it is large enough to cover the entire shape278float size = inShape->GetLocalBounds().GetSize().Length();279perp1 *= size;280perp2 *= size;281282// Emit the vertices283outPlaneFace.resize(4);284outPlaneFace[0] = center + perp1 + perp2;285outPlaneFace[1] = center + perp1 - perp2;286outPlaneFace[2] = center - perp1 - perp2;287outPlaneFace[3] = center - perp1 + perp2;288}289290void PlaneShape::sCastConvexVsPlane(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, [[maybe_unused]] const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector)291{292JPH_PROFILE_FUNCTION();293294// Get the shapes295JPH_ASSERT(inShapeCast.mShape->GetType() == EShapeType::Convex);296JPH_ASSERT(inShape->GetType() == EShapeType::Plane);297const ConvexShape *convex_shape = static_cast<const ConvexShape *>(inShapeCast.mShape);298const PlaneShape *plane_shape = static_cast<const PlaneShape *>(inShape);299300// Shape cast is provided relative to COM of inShape, so all we need to do is transform our plane with inScale301Plane plane = plane_shape->mPlane.Scaled(inScale);302Vec3 normal = plane.GetNormal();303304// Get support function305ConvexShape::SupportBuffer shape1_support_buffer;306const ConvexShape::Support *shape1_support = convex_shape->GetSupportFunction(ConvexShape::ESupportMode::Default, shape1_support_buffer, inShapeCast.mScale);307308// Get the support point of the convex shape in the opposite direction of the plane normal in our local space309Vec3 normal_in_convex_shape_space = inShapeCast.mCenterOfMassStart.Multiply3x3Transposed(normal);310Vec3 support_point = inShapeCast.mCenterOfMassStart * shape1_support->GetSupport(-normal_in_convex_shape_space);311float signed_distance = plane.SignedDistance(support_point);312float convex_radius = shape1_support->GetConvexRadius();313float penetration_depth = -signed_distance + convex_radius;314float dot = inShapeCast.mDirection.Dot(normal);315316// Collision output317Mat44 com_hit;318Vec3 point1, point2;319float fraction;320321// Do we start in collision?322if (penetration_depth > 0.0f)323{324// Back face culling?325if (inShapeCastSettings.mBackFaceModeConvex == EBackFaceMode::IgnoreBackFaces && dot > 0.0f)326return;327328// Shallower hit?329if (penetration_depth <= -ioCollector.GetEarlyOutFraction())330return;331332// We're hitting at fraction 0333fraction = 0.0f;334335// Get contact point336com_hit = inCenterOfMassTransform2;337point1 = inCenterOfMassTransform2 * (support_point - normal * convex_radius);338point2 = inCenterOfMassTransform2 * (support_point - normal * signed_distance);339}340else if (dot < 0.0f) // Moving towards the plane?341{342// Calculate hit fraction343fraction = penetration_depth / dot;344JPH_ASSERT(fraction >= 0.0f);345346// Further than early out fraction?347if (fraction >= ioCollector.GetEarlyOutFraction())348return;349350// Get contact point351com_hit = inCenterOfMassTransform2.PostTranslated(fraction * inShapeCast.mDirection);352point1 = point2 = com_hit * (support_point - normal * convex_radius);353}354else355{356// Moving away from the plane357return;358}359360// Create cast result361Vec3 penetration_axis_world = com_hit.Multiply3x3(-normal);362bool back_facing = dot > 0.0f;363ShapeCastResult result(fraction, point1, point2, penetration_axis_world, back_facing, inSubShapeIDCreator1.GetID(), inSubShapeIDCreator2.GetID(), TransformedShape::sGetBodyID(ioCollector.GetContext()));364365// Gather faces366if (inShapeCastSettings.mCollectFacesMode == ECollectFacesMode::CollectFaces)367{368// Get supporting face of convex shape369Mat44 shape_to_world = com_hit * inShapeCast.mCenterOfMassStart;370convex_shape->GetSupportingFace(SubShapeID(), normal_in_convex_shape_space, inShapeCast.mScale, shape_to_world, result.mShape1Face);371372// Get supporting face of plane373if (!result.mShape1Face.empty())374sGetSupportingFace(convex_shape, shape_to_world.GetTranslation(), plane, inCenterOfMassTransform2, result.mShape2Face);375}376377// Notify the collector378JPH_IF_TRACK_NARROWPHASE_STATS(TrackNarrowPhaseCollector track;)379ioCollector.AddHit(result);380}381382struct PlaneShape::PSGetTrianglesContext383{384Float3 mVertices[4];385bool mDone = false;386};387388void PlaneShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const389{390static_assert(sizeof(PSGetTrianglesContext) <= sizeof(GetTrianglesContext), "GetTrianglesContext too small");391JPH_ASSERT(IsAligned(&ioContext, alignof(PSGetTrianglesContext)));392393PSGetTrianglesContext *context = new (&ioContext) PSGetTrianglesContext();394395// Get the vertices of the plane396Vec3 vertices[4];397GetVertices(vertices);398399// Reverse if scale is inside out400if (ScaleHelpers::IsInsideOut(inScale))401{402std::swap(vertices[0], vertices[3]);403std::swap(vertices[1], vertices[2]);404}405406// Transform them to world space407Mat44 com = Mat44::sRotationTranslation(inRotation, inPositionCOM).PreScaled(inScale);408for (uint i = 0; i < 4; ++i)409(com * vertices[i]).StoreFloat3(&context->mVertices[i]);410}411412int PlaneShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const413{414static_assert(cGetTrianglesMinTrianglesRequested >= 2, "cGetTrianglesMinTrianglesRequested is too small");415JPH_ASSERT(inMaxTrianglesRequested >= cGetTrianglesMinTrianglesRequested);416417// Check if we're done418PSGetTrianglesContext &context = (PSGetTrianglesContext &)ioContext;419if (context.mDone)420return 0;421context.mDone = true;422423// 1st triangle424outTriangleVertices[0] = context.mVertices[0];425outTriangleVertices[1] = context.mVertices[1];426outTriangleVertices[2] = context.mVertices[2];427428// 2nd triangle429outTriangleVertices[3] = context.mVertices[0];430outTriangleVertices[4] = context.mVertices[2];431outTriangleVertices[5] = context.mVertices[3];432433if (outMaterials != nullptr)434{435// Get material436const PhysicsMaterial *material = GetMaterial(SubShapeID());437outMaterials[0] = material;438outMaterials[1] = material;439}440441return 2;442}443444void PlaneShape::sCollideConvexVsPlane(const Shape *inShape1, const Shape *inShape2, Vec3Arg inScale1, Vec3Arg inScale2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, const CollideShapeSettings &inCollideShapeSettings, CollideShapeCollector &ioCollector, [[maybe_unused]] const ShapeFilter &inShapeFilter)445{446JPH_PROFILE_FUNCTION();447448// Get the shapes449JPH_ASSERT(inShape1->GetType() == EShapeType::Convex);450JPH_ASSERT(inShape2->GetType() == EShapeType::Plane);451const ConvexShape *shape1 = static_cast<const ConvexShape *>(inShape1);452const PlaneShape *shape2 = static_cast<const PlaneShape *>(inShape2);453454// Transform the plane to the space of the convex shape455Plane scaled_plane = shape2->mPlane.Scaled(inScale2);456Plane plane = scaled_plane.GetTransformed(inCenterOfMassTransform1.InversedRotationTranslation() * inCenterOfMassTransform2);457Vec3 normal = plane.GetNormal();458459// Get support function460ConvexShape::SupportBuffer shape1_support_buffer;461const ConvexShape::Support *shape1_support = shape1->GetSupportFunction(ConvexShape::ESupportMode::Default, shape1_support_buffer, inScale1);462463// Get the support point of the convex shape in the opposite direction of the plane normal464Vec3 support_point = shape1_support->GetSupport(-normal);465float signed_distance = plane.SignedDistance(support_point);466float convex_radius = shape1_support->GetConvexRadius();467float penetration_depth = -signed_distance + convex_radius;468if (penetration_depth > -inCollideShapeSettings.mMaxSeparationDistance)469{470// Get contact point471Vec3 point1 = inCenterOfMassTransform1 * (support_point - normal * convex_radius);472Vec3 point2 = inCenterOfMassTransform1 * (support_point - normal * signed_distance);473Vec3 penetration_axis_world = inCenterOfMassTransform1.Multiply3x3(-normal);474475// Create collision result476CollideShapeResult result(point1, point2, penetration_axis_world, penetration_depth, inSubShapeIDCreator1.GetID(), inSubShapeIDCreator2.GetID(), TransformedShape::sGetBodyID(ioCollector.GetContext()));477478// Gather faces479if (inCollideShapeSettings.mCollectFacesMode == ECollectFacesMode::CollectFaces)480{481// Get supporting face of shape 1482shape1->GetSupportingFace(SubShapeID(), normal, inScale1, inCenterOfMassTransform1, result.mShape1Face);483484// Get supporting face of shape 2485if (!result.mShape1Face.empty())486sGetSupportingFace(shape1, inCenterOfMassTransform1.GetTranslation(), scaled_plane, inCenterOfMassTransform2, result.mShape2Face);487}488489// Notify the collector490JPH_IF_TRACK_NARROWPHASE_STATS(TrackNarrowPhaseCollector track;)491ioCollector.AddHit(result);492}493}494495void PlaneShape::SaveBinaryState(StreamOut &inStream) const496{497Shape::SaveBinaryState(inStream);498499inStream.Write(mPlane);500inStream.Write(mHalfExtent);501}502503void PlaneShape::RestoreBinaryState(StreamIn &inStream)504{505Shape::RestoreBinaryState(inStream);506507inStream.Read(mPlane);508inStream.Read(mHalfExtent);509510CalculateLocalBounds();511}512513void PlaneShape::SaveMaterialState(PhysicsMaterialList &outMaterials) const514{515outMaterials = { mMaterial };516}517518void PlaneShape::RestoreMaterialState(const PhysicsMaterialRefC *inMaterials, uint inNumMaterials)519{520JPH_ASSERT(inNumMaterials == 1);521mMaterial = inMaterials[0];522}523524void PlaneShape::sRegister()525{526ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::Plane);527f.mConstruct = []() -> Shape * { return new PlaneShape; };528f.mColor = Color::sDarkRed;529530for (EShapeSubType s : sConvexSubShapeTypes)531{532CollisionDispatch::sRegisterCollideShape(s, EShapeSubType::Plane, sCollideConvexVsPlane);533CollisionDispatch::sRegisterCastShape(s, EShapeSubType::Plane, sCastConvexVsPlane);534535CollisionDispatch::sRegisterCastShape(EShapeSubType::Plane, s, CollisionDispatch::sReversedCastShape);536CollisionDispatch::sRegisterCollideShape(EShapeSubType::Plane, s, CollisionDispatch::sReversedCollideShape);537}538}539540JPH_NAMESPACE_END541542543