Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/ConvexShape.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/ConvexShape.h>7#include <Jolt/Physics/Collision/RayCast.h>8#include <Jolt/Physics/Collision/ShapeCast.h>9#include <Jolt/Physics/Collision/CollideShape.h>10#include <Jolt/Physics/Collision/CastResult.h>11#include <Jolt/Physics/Collision/CollidePointResult.h>12#include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>13#include <Jolt/Physics/Collision/Shape/GetTrianglesContext.h>14#include <Jolt/Physics/Collision/Shape/PolyhedronSubmergedVolumeCalculator.h>15#include <Jolt/Physics/Collision/TransformedShape.h>16#include <Jolt/Physics/Collision/CollisionDispatch.h>17#include <Jolt/Physics/Collision/NarrowPhaseStats.h>18#include <Jolt/Physics/PhysicsSettings.h>19#include <Jolt/Core/StreamIn.h>20#include <Jolt/Core/StreamOut.h>21#include <Jolt/Geometry/EPAPenetrationDepth.h>22#include <Jolt/Geometry/OrientedBox.h>23#include <Jolt/ObjectStream/TypeDeclarations.h>2425JPH_NAMESPACE_BEGIN2627JPH_IMPLEMENT_SERIALIZABLE_ABSTRACT(ConvexShapeSettings)28{29JPH_ADD_BASE_CLASS(ConvexShapeSettings, ShapeSettings)3031JPH_ADD_ATTRIBUTE(ConvexShapeSettings, mDensity)32JPH_ADD_ATTRIBUTE(ConvexShapeSettings, mMaterial)33}3435const StaticArray<Vec3, 384> ConvexShape::sUnitSphereTriangles = []() {36const int level = 2;3738StaticArray<Vec3, 384> verts;39GetTrianglesContextVertexList::sCreateHalfUnitSphereTop(verts, level);40GetTrianglesContextVertexList::sCreateHalfUnitSphereBottom(verts, level);41return verts;42}();4344void ConvexShape::sCollideConvexVsConvex(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)45{46JPH_PROFILE_FUNCTION();4748// Get the shapes49JPH_ASSERT(inShape1->GetType() == EShapeType::Convex);50JPH_ASSERT(inShape2->GetType() == EShapeType::Convex);51const ConvexShape *shape1 = static_cast<const ConvexShape *>(inShape1);52const ConvexShape *shape2 = static_cast<const ConvexShape *>(inShape2);5354// Get transforms55Mat44 inverse_transform1 = inCenterOfMassTransform1.InversedRotationTranslation();56Mat44 transform_2_to_1 = inverse_transform1 * inCenterOfMassTransform2;5758// Get bounding boxes59float max_separation_distance = inCollideShapeSettings.mMaxSeparationDistance;60AABox shape1_bbox = shape1->GetLocalBounds().Scaled(inScale1);61shape1_bbox.ExpandBy(Vec3::sReplicate(max_separation_distance));62AABox shape2_bbox = shape2->GetLocalBounds().Scaled(inScale2);6364// Check if they overlap65if (!OrientedBox(transform_2_to_1, shape2_bbox).Overlaps(shape1_bbox))66return;6768// Note: As we don't remember the penetration axis from the last iteration, and it is likely that shape2 is pushed out of69// collision relative to shape1 by comparing their COM's, we use that as an initial penetration axis: shape2.com - shape1.com70// This has been seen to improve performance by approx. 1% over using a fixed axis like (1, 0, 0).71Vec3 penetration_axis = transform_2_to_1.GetTranslation();7273// Ensure that we do not pass in a near zero penetration axis74if (penetration_axis.IsNearZero())75penetration_axis = Vec3::sAxisX();7677Vec3 point1, point2;78EPAPenetrationDepth pen_depth;79EPAPenetrationDepth::EStatus status;8081// Scope to limit lifetime of SupportBuffer82{83// Create support function84SupportBuffer buffer1_excl_cvx_radius, buffer2_excl_cvx_radius;85const Support *shape1_excl_cvx_radius = shape1->GetSupportFunction(ConvexShape::ESupportMode::ExcludeConvexRadius, buffer1_excl_cvx_radius, inScale1);86const Support *shape2_excl_cvx_radius = shape2->GetSupportFunction(ConvexShape::ESupportMode::ExcludeConvexRadius, buffer2_excl_cvx_radius, inScale2);8788// Transform shape 2 in the space of shape 189TransformedConvexObject transformed2_excl_cvx_radius(transform_2_to_1, *shape2_excl_cvx_radius);9091// Perform GJK step92status = pen_depth.GetPenetrationDepthStepGJK(*shape1_excl_cvx_radius, shape1_excl_cvx_radius->GetConvexRadius() + max_separation_distance, transformed2_excl_cvx_radius, shape2_excl_cvx_radius->GetConvexRadius(), inCollideShapeSettings.mCollisionTolerance, penetration_axis, point1, point2);93}9495// Check result of collision detection96switch (status)97{98case EPAPenetrationDepth::EStatus::Colliding:99break;100101case EPAPenetrationDepth::EStatus::NotColliding:102return;103104case EPAPenetrationDepth::EStatus::Indeterminate:105{106// Need to run expensive EPA algorithm107108// We know we're overlapping at this point, so we can set the max separation distance to 0.109// Numerically it is possible that GJK finds that the shapes are overlapping but EPA finds that they're separated.110// In order to avoid this, we clamp the max separation distance to 1 so that we don't excessively inflate the shape,111// but we still inflate it enough to avoid the case where EPA misses the collision.112max_separation_distance = min(max_separation_distance, 1.0f);113114// Create support function115SupportBuffer buffer1_incl_cvx_radius, buffer2_incl_cvx_radius;116const Support *shape1_incl_cvx_radius = shape1->GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer1_incl_cvx_radius, inScale1);117const Support *shape2_incl_cvx_radius = shape2->GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer2_incl_cvx_radius, inScale2);118119// Add separation distance120AddConvexRadius shape1_add_max_separation_distance(*shape1_incl_cvx_radius, max_separation_distance);121122// Transform shape 2 in the space of shape 1123TransformedConvexObject transformed2_incl_cvx_radius(transform_2_to_1, *shape2_incl_cvx_radius);124125// Perform EPA step126if (!pen_depth.GetPenetrationDepthStepEPA(shape1_add_max_separation_distance, transformed2_incl_cvx_radius, inCollideShapeSettings.mPenetrationTolerance, penetration_axis, point1, point2))127return;128break;129}130}131132// Check if the penetration is bigger than the early out fraction133float penetration_depth = (point2 - point1).Length() - max_separation_distance;134if (-penetration_depth >= ioCollector.GetEarlyOutFraction())135return;136137// Correct point1 for the added separation distance138float penetration_axis_len = penetration_axis.Length();139if (penetration_axis_len > 0.0f)140point1 -= penetration_axis * (max_separation_distance / penetration_axis_len);141142// Convert to world space143point1 = inCenterOfMassTransform1 * point1;144point2 = inCenterOfMassTransform1 * point2;145Vec3 penetration_axis_world = inCenterOfMassTransform1.Multiply3x3(penetration_axis);146147// Create collision result148CollideShapeResult result(point1, point2, penetration_axis_world, penetration_depth, inSubShapeIDCreator1.GetID(), inSubShapeIDCreator2.GetID(), TransformedShape::sGetBodyID(ioCollector.GetContext()));149150// Gather faces151if (inCollideShapeSettings.mCollectFacesMode == ECollectFacesMode::CollectFaces)152{153// Get supporting face of shape 1154shape1->GetSupportingFace(SubShapeID(), -penetration_axis, inScale1, inCenterOfMassTransform1, result.mShape1Face);155156// Get supporting face of shape 2157shape2->GetSupportingFace(SubShapeID(), transform_2_to_1.Multiply3x3Transposed(penetration_axis), inScale2, inCenterOfMassTransform2, result.mShape2Face);158}159160// Notify the collector161JPH_IF_TRACK_NARROWPHASE_STATS(TrackNarrowPhaseCollector track;)162ioCollector.AddHit(result);163}164165bool ConvexShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const166{167// Note: This is a fallback routine, most convex shapes should implement a more performant version!168169JPH_PROFILE_FUNCTION();170171// Create support function172SupportBuffer buffer;173const Support *support = GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer, Vec3::sOne());174175// Cast ray176GJKClosestPoint gjk;177if (gjk.CastRay(inRay.mOrigin, inRay.mDirection, cDefaultCollisionTolerance, *support, ioHit.mFraction))178{179ioHit.mSubShapeID2 = inSubShapeIDCreator.GetID();180return true;181}182183return false;184}185186void ConvexShape::CastRay(const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector, const ShapeFilter &inShapeFilter) const187{188// Note: This is a fallback routine, most convex shapes should implement a more performant version!189190// Test shape filter191if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))192return;193194// First do a normal raycast, limited to the early out fraction195RayCastResult hit;196hit.mFraction = ioCollector.GetEarlyOutFraction();197if (CastRay(inRay, inSubShapeIDCreator, hit))198{199// Check front side200if (inRayCastSettings.mTreatConvexAsSolid || hit.mFraction > 0.0f)201{202hit.mBodyID = TransformedShape::sGetBodyID(ioCollector.GetContext());203ioCollector.AddHit(hit);204}205206// Check if we want back facing hits and the collector still accepts additional hits207if (inRayCastSettings.mBackFaceModeConvex == EBackFaceMode::CollideWithBackFaces && !ioCollector.ShouldEarlyOut())208{209// Invert the ray, going from the early out fraction back to the fraction where we found our forward hit210float start_fraction = min(1.0f, ioCollector.GetEarlyOutFraction());211float delta_fraction = hit.mFraction - start_fraction;212if (delta_fraction < 0.0f)213{214RayCast inverted_ray { inRay.mOrigin + start_fraction * inRay.mDirection, delta_fraction * inRay.mDirection };215216// Cast another ray217RayCastResult inverted_hit;218inverted_hit.mFraction = 1.0f;219if (CastRay(inverted_ray, inSubShapeIDCreator, inverted_hit)220&& inverted_hit.mFraction > 0.0f) // Ignore hits with fraction 0, this means the ray ends inside the object and we don't want to report it as a back facing hit221{222// Invert fraction and rescale it to the fraction of the original ray223inverted_hit.mFraction = hit.mFraction + (inverted_hit.mFraction - 1.0f) * delta_fraction;224inverted_hit.mBodyID = TransformedShape::sGetBodyID(ioCollector.GetContext());225ioCollector.AddHit(inverted_hit);226}227}228}229}230}231232void ConvexShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter) const233{234// Test shape filter235if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))236return;237238// First test bounding box239if (GetLocalBounds().Contains(inPoint))240{241// Create support function242SupportBuffer buffer;243const Support *support = GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer, Vec3::sOne());244245// Create support function for point246PointConvexSupport point { inPoint };247248// Test intersection249GJKClosestPoint gjk;250Vec3 v = inPoint;251if (gjk.Intersects(*support, point, cDefaultCollisionTolerance, v))252ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });253}254}255256void ConvexShape::sCastConvexVsConvex(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)257{258JPH_PROFILE_FUNCTION();259260// Only supported for convex shapes261JPH_ASSERT(inShapeCast.mShape->GetType() == EShapeType::Convex);262const ConvexShape *cast_shape = static_cast<const ConvexShape *>(inShapeCast.mShape);263264JPH_ASSERT(inShape->GetType() == EShapeType::Convex);265const ConvexShape *shape = static_cast<const ConvexShape *>(inShape);266267// Determine if we want to use the actual shape or a shrunken shape with convex radius268ConvexShape::ESupportMode support_mode = inShapeCastSettings.mUseShrunkenShapeAndConvexRadius? ConvexShape::ESupportMode::ExcludeConvexRadius : ConvexShape::ESupportMode::Default;269270// Create support function for shape to cast271SupportBuffer cast_buffer;272const Support *cast_support = cast_shape->GetSupportFunction(support_mode, cast_buffer, inShapeCast.mScale);273274// Create support function for target shape275SupportBuffer target_buffer;276const Support *target_support = shape->GetSupportFunction(support_mode, target_buffer, inScale);277278// Do a raycast against the result279EPAPenetrationDepth epa;280float fraction = ioCollector.GetEarlyOutFraction();281Vec3 contact_point_a, contact_point_b, contact_normal;282if (epa.CastShape(inShapeCast.mCenterOfMassStart, inShapeCast.mDirection, inShapeCastSettings.mCollisionTolerance, inShapeCastSettings.mPenetrationTolerance, *cast_support, *target_support, cast_support->GetConvexRadius(), target_support->GetConvexRadius(), inShapeCastSettings.mReturnDeepestPoint, fraction, contact_point_a, contact_point_b, contact_normal)283&& (inShapeCastSettings.mBackFaceModeConvex == EBackFaceMode::CollideWithBackFaces284|| contact_normal.Dot(inShapeCast.mDirection) > 0.0f)) // Test if backfacing285{286// Convert to world space287contact_point_a = inCenterOfMassTransform2 * contact_point_a;288contact_point_b = inCenterOfMassTransform2 * contact_point_b;289Vec3 contact_normal_world = inCenterOfMassTransform2.Multiply3x3(contact_normal);290291ShapeCastResult result(fraction, contact_point_a, contact_point_b, contact_normal_world, false, inSubShapeIDCreator1.GetID(), inSubShapeIDCreator2.GetID(), TransformedShape::sGetBodyID(ioCollector.GetContext()));292293// Early out if this hit is deeper than the collector's early out value294if (fraction == 0.0f && -result.mPenetrationDepth >= ioCollector.GetEarlyOutFraction())295return;296297// Gather faces298if (inShapeCastSettings.mCollectFacesMode == ECollectFacesMode::CollectFaces)299{300// Get supporting face of shape 1301Mat44 transform_1_to_2 = inShapeCast.mCenterOfMassStart;302transform_1_to_2.SetTranslation(transform_1_to_2.GetTranslation() + fraction * inShapeCast.mDirection);303cast_shape->GetSupportingFace(SubShapeID(), transform_1_to_2.Multiply3x3Transposed(-contact_normal), inShapeCast.mScale, inCenterOfMassTransform2 * transform_1_to_2, result.mShape1Face);304305// Get supporting face of shape 2306shape->GetSupportingFace(SubShapeID(), contact_normal, inScale, inCenterOfMassTransform2, result.mShape2Face);307}308309JPH_IF_TRACK_NARROWPHASE_STATS(TrackNarrowPhaseCollector track;)310ioCollector.AddHit(result);311}312}313314class ConvexShape::CSGetTrianglesContext315{316public:317CSGetTrianglesContext(const ConvexShape *inShape, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) :318mLocalToWorld(Mat44::sRotationTranslation(inRotation, inPositionCOM) * Mat44::sScale(inScale)),319mIsInsideOut(ScaleHelpers::IsInsideOut(inScale))320{321mSupport = inShape->GetSupportFunction(ESupportMode::IncludeConvexRadius, mSupportBuffer, Vec3::sOne());322}323324SupportBuffer mSupportBuffer;325const Support * mSupport;326Mat44 mLocalToWorld;327bool mIsInsideOut;328size_t mCurrentVertex = 0;329};330331void ConvexShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const332{333static_assert(sizeof(CSGetTrianglesContext) <= sizeof(GetTrianglesContext), "GetTrianglesContext too small");334JPH_ASSERT(IsAligned(&ioContext, alignof(CSGetTrianglesContext)));335336new (&ioContext) CSGetTrianglesContext(this, inPositionCOM, inRotation, inScale);337}338339int ConvexShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const340{341JPH_ASSERT(inMaxTrianglesRequested >= cGetTrianglesMinTrianglesRequested);342343CSGetTrianglesContext &context = (CSGetTrianglesContext &)ioContext;344345int total_num_vertices = min(inMaxTrianglesRequested * 3, int(sUnitSphereTriangles.size() - context.mCurrentVertex));346347if (context.mIsInsideOut)348{349// Store triangles flipped350for (const Vec3 *v = sUnitSphereTriangles.data() + context.mCurrentVertex, *v_end = v + total_num_vertices; v < v_end; v += 3)351{352(context.mLocalToWorld * context.mSupport->GetSupport(v[0])).StoreFloat3(outTriangleVertices++);353(context.mLocalToWorld * context.mSupport->GetSupport(v[2])).StoreFloat3(outTriangleVertices++);354(context.mLocalToWorld * context.mSupport->GetSupport(v[1])).StoreFloat3(outTriangleVertices++);355}356}357else358{359// Store triangles360for (const Vec3 *v = sUnitSphereTriangles.data() + context.mCurrentVertex, *v_end = v + total_num_vertices; v < v_end; v += 3)361{362(context.mLocalToWorld * context.mSupport->GetSupport(v[0])).StoreFloat3(outTriangleVertices++);363(context.mLocalToWorld * context.mSupport->GetSupport(v[1])).StoreFloat3(outTriangleVertices++);364(context.mLocalToWorld * context.mSupport->GetSupport(v[2])).StoreFloat3(outTriangleVertices++);365}366}367368context.mCurrentVertex += total_num_vertices;369int total_num_triangles = total_num_vertices / 3;370371// Store materials372if (outMaterials != nullptr)373{374const PhysicsMaterial *material = GetMaterial();375for (const PhysicsMaterial **m = outMaterials, **m_end = outMaterials + total_num_triangles; m < m_end; ++m)376*m = material;377}378379return total_num_triangles;380}381382void ConvexShape::GetSubmergedVolume(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const Plane &inSurface, float &outTotalVolume, float &outSubmergedVolume, Vec3 &outCenterOfBuoyancy JPH_IF_DEBUG_RENDERER(, RVec3Arg inBaseOffset)) const383{384// Calculate total volume385Vec3 abs_scale = inScale.Abs();386Vec3 extent = GetLocalBounds().GetExtent() * abs_scale;387outTotalVolume = 8.0f * extent.GetX() * extent.GetY() * extent.GetZ();388389// Points of the bounding box390Vec3 points[] =391{392Vec3(-1, -1, -1),393Vec3( 1, -1, -1),394Vec3(-1, 1, -1),395Vec3( 1, 1, -1),396Vec3(-1, -1, 1),397Vec3( 1, -1, 1),398Vec3(-1, 1, 1),399Vec3( 1, 1, 1),400};401402// Faces of the bounding box403using Face = int[5];404#define MAKE_FACE(a, b, c, d) { a, b, c, d, ((1 << a) | (1 << b) | (1 << c) | (1 << d)) } // Last int is a bit mask that indicates which indices are used405Face faces[] =406{407MAKE_FACE(0, 2, 3, 1),408MAKE_FACE(4, 6, 2, 0),409MAKE_FACE(4, 5, 7, 6),410MAKE_FACE(1, 3, 7, 5),411MAKE_FACE(2, 6, 7, 3),412MAKE_FACE(0, 1, 5, 4),413};414415PolyhedronSubmergedVolumeCalculator::Point *buffer = (PolyhedronSubmergedVolumeCalculator::Point *)JPH_STACK_ALLOC(8 * sizeof(PolyhedronSubmergedVolumeCalculator::Point));416PolyhedronSubmergedVolumeCalculator submerged_vol_calc(inCenterOfMassTransform * Mat44::sScale(extent), points, sizeof(Vec3), 8, inSurface, buffer JPH_IF_DEBUG_RENDERER(, inBaseOffset));417418if (submerged_vol_calc.AreAllAbove())419{420// We're above the water421outSubmergedVolume = 0.0f;422outCenterOfBuoyancy = Vec3::sZero();423}424else if (submerged_vol_calc.AreAllBelow())425{426// We're fully submerged427outSubmergedVolume = outTotalVolume;428outCenterOfBuoyancy = inCenterOfMassTransform.GetTranslation();429}430else431{432// Calculate submerged volume433int reference_point_bit = 1 << submerged_vol_calc.GetReferencePointIdx();434for (const Face &f : faces)435{436// Test if this face includes the reference point437if ((f[4] & reference_point_bit) == 0)438{439// Triangulate the face (a quad)440submerged_vol_calc.AddFace(f[0], f[1], f[2]);441submerged_vol_calc.AddFace(f[0], f[2], f[3]);442}443}444445submerged_vol_calc.GetResult(outSubmergedVolume, outCenterOfBuoyancy);446}447}448449#ifdef JPH_DEBUG_RENDERER450void ConvexShape::DrawGetSupportFunction(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inDrawSupportDirection) const451{452// Get the support function with convex radius453SupportBuffer buffer;454const Support *support = GetSupportFunction(ESupportMode::ExcludeConvexRadius, buffer, inScale);455AddConvexRadius add_convex(*support, support->GetConvexRadius());456457// Draw the shape458DebugRenderer::GeometryRef geometry = inRenderer->CreateTriangleGeometryForConvex([&add_convex](Vec3Arg inDirection) { return add_convex.GetSupport(inDirection); });459AABox bounds = geometry->mBounds.Transformed(inCenterOfMassTransform);460float lod_scale_sq = geometry->mBounds.GetExtent().LengthSq();461inRenderer->DrawGeometry(inCenterOfMassTransform, bounds, lod_scale_sq, inColor, geometry);462463if (inDrawSupportDirection)464{465// Iterate on all directions and draw the support point and an arrow in the direction that was sampled to test if the support points make sense466for (Vec3 v : Vec3::sUnitSphere)467{468Vec3 direction = 0.05f * v;469Vec3 pos = add_convex.GetSupport(direction);470RVec3 from = inCenterOfMassTransform * pos;471RVec3 to = inCenterOfMassTransform * (pos + direction);472inRenderer->DrawMarker(from, Color::sWhite, 0.001f);473inRenderer->DrawArrow(from, to, Color::sWhite, 0.001f);474}475}476}477478void ConvexShape::DrawGetSupportingFace(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale) const479{480// Sample directions and map which faces belong to which directions481using FaceToDirection = UnorderedMap<SupportingFace, Array<Vec3>>;482FaceToDirection faces;483for (Vec3 v : Vec3::sUnitSphere)484{485Vec3 direction = 0.05f * v;486487SupportingFace face;488GetSupportingFace(SubShapeID(), direction, inScale, Mat44::sIdentity(), face);489490if (!face.empty())491{492JPH_ASSERT(face.size() >= 2, "The GetSupportingFace function should either return nothing or at least an edge");493faces[face].push_back(direction);494}495}496497// Draw each face in a unique color and draw corresponding directions498int color_it = 0;499for (FaceToDirection::value_type &ftd : faces)500{501Color color = Color::sGetDistinctColor(color_it++);502503// Create copy of face (key in map is read only)504SupportingFace face = ftd.first;505506// Displace the face a little bit forward so it is easier to see507Vec3 normal = face.size() >= 3? (face[2] - face[1]).Cross(face[0] - face[1]).NormalizedOr(Vec3::sZero()) : Vec3::sZero();508Vec3 displacement = 0.001f * normal;509510// Transform face to world space and calculate center of mass511Vec3 com_ls = Vec3::sZero();512for (Vec3 &v : face)513{514v = inCenterOfMassTransform.Multiply3x3(v + displacement);515com_ls += v;516}517RVec3 com = inCenterOfMassTransform.GetTranslation() + com_ls / (float)face.size();518519// Draw the polygon and directions520inRenderer->DrawWirePolygon(RMat44::sTranslation(inCenterOfMassTransform.GetTranslation()), face, color, face.size() >= 3? 0.001f : 0.0f);521if (face.size() >= 3)522inRenderer->DrawArrow(com, com + inCenterOfMassTransform.Multiply3x3(normal), color, 0.01f);523for (Vec3 &v : ftd.second)524inRenderer->DrawArrow(com, com + inCenterOfMassTransform.Multiply3x3(-v), color, 0.001f);525}526}527#endif // JPH_DEBUG_RENDERER528529void ConvexShape::SaveBinaryState(StreamOut &inStream) const530{531Shape::SaveBinaryState(inStream);532533inStream.Write(mDensity);534}535536void ConvexShape::RestoreBinaryState(StreamIn &inStream)537{538Shape::RestoreBinaryState(inStream);539540inStream.Read(mDensity);541}542543void ConvexShape::SaveMaterialState(PhysicsMaterialList &outMaterials) const544{545outMaterials.clear();546outMaterials.push_back(mMaterial);547}548549void ConvexShape::RestoreMaterialState(const PhysicsMaterialRefC *inMaterials, uint inNumMaterials)550{551JPH_ASSERT(inNumMaterials == 1);552mMaterial = inMaterials[0];553}554555void ConvexShape::sRegister()556{557for (EShapeSubType s1 : sConvexSubShapeTypes)558for (EShapeSubType s2 : sConvexSubShapeTypes)559{560CollisionDispatch::sRegisterCollideShape(s1, s2, sCollideConvexVsConvex);561CollisionDispatch::sRegisterCastShape(s1, s2, sCastConvexVsConvex);562}563}564565JPH_NAMESPACE_END566567568