Path: blob/master/thirdparty/jolt_physics/Jolt/Geometry/ConvexHullBuilder.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/Geometry/ConvexHullBuilder.h>7#include <Jolt/Geometry/ConvexHullBuilder2D.h>8#include <Jolt/Geometry/ClosestPoint.h>9#include <Jolt/Core/StringTools.h>10#include <Jolt/Core/UnorderedSet.h>1112#ifdef JPH_CONVEX_BUILDER_DUMP_SHAPE13JPH_SUPPRESS_WARNINGS_STD_BEGIN14#include <fstream>15JPH_SUPPRESS_WARNINGS_STD_END16#endif // JPH_CONVEX_BUILDER_DUMP_SHAPE1718#ifdef JPH_CONVEX_BUILDER_DEBUG19#include <Jolt/Renderer/DebugRenderer.h>20#endif2122JPH_NAMESPACE_BEGIN2324ConvexHullBuilder::Face::~Face()25{26// Free all edges27Edge *e = mFirstEdge;28if (e != nullptr)29{30do31{32Edge *next = e->mNextEdge;33delete e;34e = next;35} while (e != mFirstEdge);36}37}3839void ConvexHullBuilder::Face::CalculateNormalAndCentroid(const Vec3 *inPositions)40{41// Get point that we use to construct a triangle fan42Edge *e = mFirstEdge;43Vec3 y0 = inPositions[e->mStartIdx];4445// Get the 2nd point46e = e->mNextEdge;47Vec3 y1 = inPositions[e->mStartIdx];4849// Start accumulating the centroid50mCentroid = y0 + y1;51int n = 2;5253// Start accumulating the normal54mNormal = Vec3::sZero();5556// Loop over remaining edges accumulating normals in a triangle fan fashion57for (e = e->mNextEdge; e != mFirstEdge; e = e->mNextEdge)58{59// Get the 3rd point60Vec3 y2 = inPositions[e->mStartIdx];6162// Calculate edges (counter clockwise)63Vec3 e0 = y1 - y0;64Vec3 e1 = y2 - y1;65Vec3 e2 = y0 - y2;6667// The best normal is calculated by using the two shortest edges68// See: https://box2d.org/posts/2014/01/troublesome-triangle/69// The difference in normals is most pronounced when one edge is much smaller than the others (in which case the others must have roughly the same length).70// Therefore we can suffice by just picking the shortest from 2 edges and use that with the 3rd edge to calculate the normal.71// We first check which of the edges is shorter: e1 or e272UVec4 e1_shorter_than_e2 = Vec4::sLess(e1.DotV4(e1), e2.DotV4(e2));7374// We calculate both normals and then select the one that had the shortest edge for our normal (this avoids branching)75Vec3 normal_e01 = e0.Cross(e1);76Vec3 normal_e02 = e2.Cross(e0);77mNormal += Vec3::sSelect(normal_e02, normal_e01, e1_shorter_than_e2);7879// Accumulate centroid80mCentroid += y2;81n++;8283// Update y1 for next triangle84y1 = y2;85}8687// Finalize centroid88mCentroid /= float(n);89}9091void ConvexHullBuilder::Face::Initialize(int inIdx0, int inIdx1, int inIdx2, const Vec3 *inPositions)92{93JPH_ASSERT(mFirstEdge == nullptr);94JPH_ASSERT(inIdx0 != inIdx1 && inIdx0 != inIdx2 && inIdx1 != inIdx2);9596// Create 3 edges97Edge *e0 = new Edge(this, inIdx0);98Edge *e1 = new Edge(this, inIdx1);99Edge *e2 = new Edge(this, inIdx2);100101// Link edges102e0->mNextEdge = e1;103e1->mNextEdge = e2;104e2->mNextEdge = e0;105mFirstEdge = e0;106107CalculateNormalAndCentroid(inPositions);108}109110ConvexHullBuilder::ConvexHullBuilder(const Positions &inPositions) :111mPositions(inPositions)112{113#ifdef JPH_CONVEX_BUILDER_DEBUG114mIteration = 0;115116// Center the drawing of the first hull around the origin and calculate the delta offset between states117mOffset = RVec3::sZero();118if (mPositions.empty())119{120// No hull will be generated121mDelta = Vec3::sZero();122}123else124{125Vec3 maxv = Vec3::sReplicate(-FLT_MAX), minv = Vec3::sReplicate(FLT_MAX);126for (Vec3 v : mPositions)127{128minv = Vec3::sMin(minv, v);129maxv = Vec3::sMax(maxv, v);130mOffset -= v;131}132mOffset /= Real(mPositions.size());133mDelta = Vec3((maxv - minv).GetX() + 0.5f, 0, 0);134mOffset += mDelta; // Don't start at origin, we're already drawing the final hull there135}136#endif137}138139void ConvexHullBuilder::FreeFaces()140{141for (Face *f : mFaces)142delete f;143mFaces.clear();144}145146void ConvexHullBuilder::GetFaceForPoint(Vec3Arg inPoint, const Faces &inFaces, Face *&outFace, float &outDistSq) const147{148outFace = nullptr;149outDistSq = 0.0f;150151for (Face *f : inFaces)152if (!f->mRemoved)153{154// Determine distance to face155float dot = f->mNormal.Dot(inPoint - f->mCentroid);156if (dot > 0.0f)157{158float dist_sq = dot * dot / f->mNormal.LengthSq();159if (dist_sq > outDistSq)160{161outFace = f;162outDistSq = dist_sq;163}164}165}166}167168float ConvexHullBuilder::GetDistanceToEdgeSq(Vec3Arg inPoint, const Face *inFace) const169{170bool all_inside = true;171float edge_dist_sq = FLT_MAX;172173// Test if it is inside the edges of the polygon174Edge *edge = inFace->mFirstEdge;175Vec3 p1 = mPositions[edge->GetPreviousEdge()->mStartIdx];176do177{178Vec3 p2 = mPositions[edge->mStartIdx];179if ((p2 - p1).Cross(inPoint - p1).Dot(inFace->mNormal) < 0.0f)180{181// It is outside182all_inside = false;183184// Measure distance to this edge185uint32 s;186edge_dist_sq = min(edge_dist_sq, ClosestPoint::GetClosestPointOnLine(p1 - inPoint, p2 - inPoint, s).LengthSq());187}188p1 = p2;189edge = edge->mNextEdge;190} while (edge != inFace->mFirstEdge);191192return all_inside? 0.0f : edge_dist_sq;193}194195bool ConvexHullBuilder::AssignPointToFace(int inPositionIdx, const Faces &inFaces, float inToleranceSq)196{197Vec3 point = mPositions[inPositionIdx];198199// Find the face for which the point is furthest away200Face *best_face;201float best_dist_sq;202GetFaceForPoint(point, inFaces, best_face, best_dist_sq);203204if (best_face != nullptr)205{206// Check if this point is within the tolerance margin to the plane207if (best_dist_sq <= inToleranceSq)208{209// Check distance to edges210float dist_to_edge_sq = GetDistanceToEdgeSq(point, best_face);211if (dist_to_edge_sq > inToleranceSq)212{213// Point is outside of the face and too far away to discard214mCoplanarList.push_back({ inPositionIdx, dist_to_edge_sq });215}216}217else218{219// This point is in front of the face, add it to the conflict list220if (best_dist_sq > best_face->mFurthestPointDistanceSq)221{222// This point is further away than any others, update the distance and add point as last point223best_face->mFurthestPointDistanceSq = best_dist_sq;224best_face->mConflictList.push_back(inPositionIdx);225}226else227{228// Not the furthest point, add it as the before last point229best_face->mConflictList.insert(best_face->mConflictList.begin() + best_face->mConflictList.size() - 1, inPositionIdx);230}231232return true;233}234}235236return false;237}238239float ConvexHullBuilder::DetermineCoplanarDistance() const240{241// Formula as per: Implementing Quickhull - Dirk Gregorius.242Vec3 vmax = Vec3::sZero();243for (Vec3 v : mPositions)244vmax = Vec3::sMax(vmax, v.Abs());245return 3.0f * FLT_EPSILON * (vmax.GetX() + vmax.GetY() + vmax.GetZ());246}247248int ConvexHullBuilder::GetNumVerticesUsed() const249{250UnorderedSet<int> used_verts;251used_verts.reserve(UnorderedSet<int>::size_type(mPositions.size()));252for (Face *f : mFaces)253{254Edge *e = f->mFirstEdge;255do256{257used_verts.insert(e->mStartIdx);258e = e->mNextEdge;259} while (e != f->mFirstEdge);260}261return (int)used_verts.size();262}263264bool ConvexHullBuilder::ContainsFace(const Array<int> &inIndices) const265{266for (Face *f : mFaces)267{268Edge *e = f->mFirstEdge;269Array<int>::const_iterator index = std::find(inIndices.begin(), inIndices.end(), e->mStartIdx);270if (index != inIndices.end())271{272size_t matches = 0;273274do275{276// Check if index matches277if (*index != e->mStartIdx)278break;279280// Increment number of matches281matches++;282283// Next index in list of inIndices284index++;285if (index == inIndices.end())286index = inIndices.begin();287288// Next edge289e = e->mNextEdge;290} while (e != f->mFirstEdge);291292if (matches == inIndices.size())293return true;294}295}296297return false;298}299300ConvexHullBuilder::EResult ConvexHullBuilder::Initialize(int inMaxVertices, float inTolerance, const char *&outError)301{302// Free the faces possibly left over from an earlier hull303FreeFaces();304305// Test that we have at least 3 points306if (mPositions.size() < 3)307{308outError = "Need at least 3 points to make a hull";309return EResult::TooFewPoints;310}311312// Determine a suitable tolerance for detecting that points are coplanar313float coplanar_tolerance_sq = Square(DetermineCoplanarDistance());314315// Increase desired tolerance if accuracy doesn't allow it316float tolerance_sq = max(coplanar_tolerance_sq, Square(inTolerance));317318// Find point furthest from the origin319int idx1 = -1;320float max_dist_sq = -1.0f;321for (int i = 0; i < (int)mPositions.size(); ++i)322{323float dist_sq = mPositions[i].LengthSq();324if (dist_sq > max_dist_sq)325{326max_dist_sq = dist_sq;327idx1 = i;328}329}330JPH_ASSERT(idx1 >= 0);331332// Find point that is furthest away from this point333int idx2 = -1;334max_dist_sq = -1.0f;335for (int i = 0; i < (int)mPositions.size(); ++i)336if (i != idx1)337{338float dist_sq = (mPositions[i] - mPositions[idx1]).LengthSq();339if (dist_sq > max_dist_sq)340{341max_dist_sq = dist_sq;342idx2 = i;343}344}345JPH_ASSERT(idx2 >= 0);346347// Find point that forms the biggest triangle348int idx3 = -1;349float best_triangle_area_sq = -1.0f;350for (int i = 0; i < (int)mPositions.size(); ++i)351if (i != idx1 && i != idx2)352{353float triangle_area_sq = (mPositions[idx1] - mPositions[i]).Cross(mPositions[idx2] - mPositions[i]).LengthSq();354if (triangle_area_sq > best_triangle_area_sq)355{356best_triangle_area_sq = triangle_area_sq;357idx3 = i;358}359}360JPH_ASSERT(idx3 >= 0);361if (best_triangle_area_sq < cMinTriangleAreaSq)362{363outError = "Could not find a suitable initial triangle because its area was too small";364return EResult::Degenerate;365}366367// Check if we have only 3 vertices368if (mPositions.size() == 3)369{370// Create two triangles (back to back)371Face *t1 = CreateTriangle(idx1, idx2, idx3);372Face *t2 = CreateTriangle(idx1, idx3, idx2);373374// Link faces edges375sLinkFace(t1->mFirstEdge, t2->mFirstEdge->mNextEdge->mNextEdge);376sLinkFace(t1->mFirstEdge->mNextEdge, t2->mFirstEdge->mNextEdge);377sLinkFace(t1->mFirstEdge->mNextEdge->mNextEdge, t2->mFirstEdge);378379#ifdef JPH_CONVEX_BUILDER_DEBUG380// Draw current state381DrawState();382#endif383384return EResult::Success;385}386387// Find point that forms the biggest tetrahedron388Vec3 initial_plane_normal = (mPositions[idx2] - mPositions[idx1]).Cross(mPositions[idx3] - mPositions[idx1]).Normalized();389Vec3 initial_plane_centroid = (mPositions[idx1] + mPositions[idx2] + mPositions[idx3]) / 3.0f;390int idx4 = -1;391float max_dist = 0.0f;392for (int i = 0; i < (int)mPositions.size(); ++i)393if (i != idx1 && i != idx2 && i != idx3)394{395float dist = (mPositions[i] - initial_plane_centroid).Dot(initial_plane_normal);396if (abs(dist) > abs(max_dist))397{398max_dist = dist;399idx4 = i;400}401}402403// Check if the hull is coplanar404if (Square(max_dist) <= 25.0f * coplanar_tolerance_sq)405{406// First project all points in 2D space407Vec3 base1 = initial_plane_normal.GetNormalizedPerpendicular();408Vec3 base2 = initial_plane_normal.Cross(base1);409Array<Vec3> positions_2d;410positions_2d.reserve(mPositions.size());411for (Vec3 v : mPositions)412positions_2d.emplace_back(base1.Dot(v), base2.Dot(v), 0.0f);413414// Build hull415Array<int> edges_2d;416ConvexHullBuilder2D builder_2d(positions_2d);417ConvexHullBuilder2D::EResult result = builder_2d.Initialize(idx1, idx2, idx3, inMaxVertices, inTolerance, edges_2d);418419// Create faces (back to back)420Face *f1 = CreateFace();421Face *f2 = CreateFace();422423// Create edges for face 1424Array<Edge *> edges_f1;425edges_f1.reserve(edges_2d.size());426for (int start_idx : edges_2d)427{428Edge *edge = new Edge(f1, start_idx);429if (edges_f1.empty())430f1->mFirstEdge = edge;431else432edges_f1.back()->mNextEdge = edge;433edges_f1.push_back(edge);434}435edges_f1.back()->mNextEdge = f1->mFirstEdge;436437// Create edges for face 2438Array<Edge *> edges_f2;439edges_f2.reserve(edges_2d.size());440for (int i = (int)edges_2d.size() - 1; i >= 0; --i)441{442Edge *edge = new Edge(f2, edges_2d[i]);443if (edges_f2.empty())444f2->mFirstEdge = edge;445else446edges_f2.back()->mNextEdge = edge;447edges_f2.push_back(edge);448}449edges_f2.back()->mNextEdge = f2->mFirstEdge;450451// Link edges452for (size_t i = 0; i < edges_2d.size(); ++i)453sLinkFace(edges_f1[i], edges_f2[(2 * edges_2d.size() - 2 - i) % edges_2d.size()]);454455// Calculate the plane for both faces456f1->CalculateNormalAndCentroid(mPositions.data());457f2->mNormal = -f1->mNormal;458f2->mCentroid = f1->mCentroid;459460#ifdef JPH_CONVEX_BUILDER_DEBUG461// Draw current state462DrawState();463#endif464465return result == ConvexHullBuilder2D::EResult::MaxVerticesReached? EResult::MaxVerticesReached : EResult::Success;466}467468// Ensure the planes are facing outwards469if (max_dist < 0.0f)470std::swap(idx2, idx3);471472// Create tetrahedron473Face *t1 = CreateTriangle(idx1, idx2, idx4);474Face *t2 = CreateTriangle(idx2, idx3, idx4);475Face *t3 = CreateTriangle(idx3, idx1, idx4);476Face *t4 = CreateTriangle(idx1, idx3, idx2);477478// Link face edges479sLinkFace(t1->mFirstEdge, t4->mFirstEdge->mNextEdge->mNextEdge);480sLinkFace(t1->mFirstEdge->mNextEdge, t2->mFirstEdge->mNextEdge->mNextEdge);481sLinkFace(t1->mFirstEdge->mNextEdge->mNextEdge, t3->mFirstEdge->mNextEdge);482sLinkFace(t2->mFirstEdge, t4->mFirstEdge->mNextEdge);483sLinkFace(t2->mFirstEdge->mNextEdge, t3->mFirstEdge->mNextEdge->mNextEdge);484sLinkFace(t3->mFirstEdge, t4->mFirstEdge);485486// Build the initial conflict lists487Faces faces { t1, t2, t3, t4 };488for (int idx = 0; idx < (int)mPositions.size(); ++idx)489if (idx != idx1 && idx != idx2 && idx != idx3 && idx != idx4)490AssignPointToFace(idx, faces, tolerance_sq);491492#ifdef JPH_CONVEX_BUILDER_DEBUG493// Draw current state including conflict list494DrawState(true);495496// Increment iteration counter497++mIteration;498#endif499500// Overestimate of the actual amount of vertices we use, for limiting the amount of vertices in the hull501int num_vertices_used = 4;502503// Loop through the remainder of the points and add them504for (;;)505{506// Find the face with the furthest point on it507Face *face_with_furthest_point = nullptr;508float furthest_dist_sq = 0.0f;509for (Face *f : mFaces)510if (f->mFurthestPointDistanceSq > furthest_dist_sq)511{512furthest_dist_sq = f->mFurthestPointDistanceSq;513face_with_furthest_point = f;514}515516int furthest_point_idx;517if (face_with_furthest_point != nullptr)518{519// Take the furthest point520furthest_point_idx = face_with_furthest_point->mConflictList.back();521face_with_furthest_point->mConflictList.pop_back();522}523else if (!mCoplanarList.empty())524{525// Try to assign points to faces (this also recalculates the distance to the hull for the coplanar vertices)526CoplanarList coplanar;527mCoplanarList.swap(coplanar);528bool added = false;529for (const Coplanar &c : coplanar)530added |= AssignPointToFace(c.mPositionIdx, mFaces, tolerance_sq);531532// If we were able to assign a point, loop again to pick it up533if (added)534continue;535536// If the coplanar list is empty, there are no points left and we're done537if (mCoplanarList.empty())538break;539540do541{542// Find the vertex that is furthest from the hull543CoplanarList::size_type best_idx = 0;544float best_dist_sq = mCoplanarList.front().mDistanceSq;545for (CoplanarList::size_type idx = 1; idx < mCoplanarList.size(); ++idx)546{547const Coplanar &c = mCoplanarList[idx];548if (c.mDistanceSq > best_dist_sq)549{550best_idx = idx;551best_dist_sq = c.mDistanceSq;552}553}554555// Swap it to the end556std::swap(mCoplanarList[best_idx], mCoplanarList.back());557558// Remove it559furthest_point_idx = mCoplanarList.back().mPositionIdx;560mCoplanarList.pop_back();561562// Find the face for which the point is furthest away563GetFaceForPoint(mPositions[furthest_point_idx], mFaces, face_with_furthest_point, best_dist_sq);564} while (!mCoplanarList.empty() && face_with_furthest_point == nullptr);565566if (face_with_furthest_point == nullptr)567break;568}569else570{571// If there are no more vertices, we're done572break;573}574575// Check if we have a limit on the max vertices that we should produce576if (num_vertices_used >= inMaxVertices)577{578// Count the actual amount of used vertices (we did not take the removal of any vertices into account)579num_vertices_used = GetNumVerticesUsed();580581// Check if there are too many582if (num_vertices_used >= inMaxVertices)583return EResult::MaxVerticesReached;584}585586// We're about to add another vertex587++num_vertices_used;588589// Add the point to the hull590Faces new_faces;591AddPoint(face_with_furthest_point, furthest_point_idx, coplanar_tolerance_sq, new_faces);592593// Redistribute points on conflict lists belonging to removed faces594for (const Face *face : mFaces)595if (face->mRemoved)596for (int idx : face->mConflictList)597AssignPointToFace(idx, new_faces, tolerance_sq);598599// Permanently delete faces that we removed in AddPoint()600GarbageCollectFaces();601602#ifdef JPH_CONVEX_BUILDER_DEBUG603// Draw state at the end of this step including conflict list604DrawState(true);605606// Increment iteration counter607++mIteration;608#endif609}610611// Check if we are left with a hull. It is possible that hull building fails if the points are nearly coplanar.612if (mFaces.size() < 2)613{614outError = "Too few faces in hull";615return EResult::TooFewFaces;616}617618return EResult::Success;619}620621void ConvexHullBuilder::AddPoint(Face *inFacingFace, int inIdx, float inCoplanarToleranceSq, Faces &outNewFaces)622{623// Get position624Vec3 pos = mPositions[inIdx];625626#ifdef JPH_CONVEX_BUILDER_DEBUG627// Draw point to be added628DebugRenderer::sInstance->DrawMarker(cDrawScale * (mOffset + pos), Color::sYellow, 0.1f);629DebugRenderer::sInstance->DrawText3D(cDrawScale * (mOffset + pos), ConvertToString(inIdx), Color::sWhite);630#endif631632#ifdef JPH_ENABLE_ASSERTS633// Check if structure is intact634ValidateFaces();635#endif636637// Find edge of convex hull of faces that are not facing the new vertex638FullEdges edges;639FindEdge(inFacingFace, pos, edges);640JPH_ASSERT(edges.size() >= 3);641642// Create new faces643outNewFaces.reserve(edges.size());644for (const FullEdge &e : edges)645{646JPH_ASSERT(e.mStartIdx != e.mEndIdx);647Face *f = CreateTriangle(e.mStartIdx, e.mEndIdx, inIdx);648outNewFaces.push_back(f);649}650651// Link edges652for (Faces::size_type i = 0; i < outNewFaces.size(); ++i)653{654sLinkFace(outNewFaces[i]->mFirstEdge, edges[i].mNeighbourEdge);655sLinkFace(outNewFaces[i]->mFirstEdge->mNextEdge, outNewFaces[(i + 1) % outNewFaces.size()]->mFirstEdge->mNextEdge->mNextEdge);656}657658// Loop on faces that were modified until nothing needs to be checked anymore659Faces affected_faces = outNewFaces;660while (!affected_faces.empty())661{662// Take the next face663Face *face = affected_faces.back();664affected_faces.pop_back();665666if (!face->mRemoved)667{668// Merge with neighbour if this is a degenerate face669MergeDegenerateFace(face, affected_faces);670671// Merge with coplanar neighbours (or when the neighbour forms a concave edge)672if (!face->mRemoved)673MergeCoplanarOrConcaveFaces(face, inCoplanarToleranceSq, affected_faces);674}675}676677#ifdef JPH_ENABLE_ASSERTS678// Check if structure is intact679ValidateFaces();680#endif681}682683void ConvexHullBuilder::GarbageCollectFaces()684{685for (int i = (int)mFaces.size() - 1; i >= 0; --i)686{687Face *f = mFaces[i];688if (f->mRemoved)689{690FreeFace(f);691mFaces.erase(mFaces.begin() + i);692}693}694}695696ConvexHullBuilder::Face *ConvexHullBuilder::CreateFace()697{698// Call provider to create face699Face *f = new Face();700701#ifdef JPH_CONVEX_BUILDER_DEBUG702// Remember iteration counter703f->mIteration = mIteration;704#endif705706// Add to list707mFaces.push_back(f);708return f;709}710711ConvexHullBuilder::Face *ConvexHullBuilder::CreateTriangle(int inIdx1, int inIdx2, int inIdx3)712{713Face *f = CreateFace();714f->Initialize(inIdx1, inIdx2, inIdx3, mPositions.data());715return f;716}717718void ConvexHullBuilder::FreeFace(Face *inFace)719{720JPH_ASSERT(inFace->mRemoved);721722#ifdef JPH_ENABLE_ASSERTS723// Make sure that this face is not connected724Edge *e = inFace->mFirstEdge;725if (e != nullptr)726do727{728JPH_ASSERT(e->mNeighbourEdge == nullptr);729e = e->mNextEdge;730} while (e != inFace->mFirstEdge);731#endif732733// Free the face734delete inFace;735}736737void ConvexHullBuilder::sLinkFace(Edge *inEdge1, Edge *inEdge2)738{739// Check not connected yet740JPH_ASSERT(inEdge1->mNeighbourEdge == nullptr);741JPH_ASSERT(inEdge2->mNeighbourEdge == nullptr);742JPH_ASSERT(inEdge1->mFace != inEdge2->mFace);743744// Check vertices match745JPH_ASSERT(inEdge1->mStartIdx == inEdge2->mNextEdge->mStartIdx);746JPH_ASSERT(inEdge2->mStartIdx == inEdge1->mNextEdge->mStartIdx);747748// Link up749inEdge1->mNeighbourEdge = inEdge2;750inEdge2->mNeighbourEdge = inEdge1;751}752753void ConvexHullBuilder::sUnlinkFace(Face *inFace)754{755// Unlink from neighbours756Edge *e = inFace->mFirstEdge;757do758{759if (e->mNeighbourEdge != nullptr)760{761// Validate that neighbour points to us762JPH_ASSERT(e->mNeighbourEdge->mNeighbourEdge == e);763764// Unlink765e->mNeighbourEdge->mNeighbourEdge = nullptr;766e->mNeighbourEdge = nullptr;767}768e = e->mNextEdge;769} while (e != inFace->mFirstEdge);770}771772void ConvexHullBuilder::FindEdge(Face *inFacingFace, Vec3Arg inVertex, FullEdges &outEdges) const773{774// Assert that we were given an empty array775JPH_ASSERT(outEdges.empty());776777// Should start with a facing face778JPH_ASSERT(inFacingFace->IsFacing(inVertex));779780// Flag as removed781inFacingFace->mRemoved = true;782783// Instead of recursing, we build our own stack with the information we need784struct StackEntry785{786Edge * mFirstEdge;787Edge * mCurrentEdge;788};789constexpr int cMaxEdgeLength = 128;790StackEntry stack[cMaxEdgeLength];791int cur_stack_pos = 0;792793static_assert(alignof(Edge) >= 2, "Need lowest bit to indicate to tell if we completed the loop");794795// Start with the face / edge provided796stack[0].mFirstEdge = inFacingFace->mFirstEdge;797stack[0].mCurrentEdge = reinterpret_cast<Edge *>(reinterpret_cast<uintptr_t>(inFacingFace->mFirstEdge) | 1); // Set lowest bit of pointer to make it different from the first edge798799for (;;)800{801StackEntry &cur_entry = stack[cur_stack_pos];802803// Next edge804Edge *raw_e = cur_entry.mCurrentEdge;805Edge *e = reinterpret_cast<Edge *>(reinterpret_cast<uintptr_t>(raw_e) & ~uintptr_t(1)); // Remove the lowest bit which was used to indicate that this is the first edge we're testing806cur_entry.mCurrentEdge = e->mNextEdge;807808// If we're back at the first edge we've completed the face and we're done809if (raw_e == cur_entry.mFirstEdge)810{811// This face needs to be removed, unlink it now, caller will free812sUnlinkFace(e->mFace);813814// Pop from stack815if (--cur_stack_pos < 0)816break;817}818else819{820// Visit neighbour face821Edge *ne = e->mNeighbourEdge;822if (ne != nullptr)823{824Face *n = ne->mFace;825if (!n->mRemoved)826{827// Check if vertex is on the front side of this face828if (n->IsFacing(inVertex))829{830// Vertex on front, this face needs to be removed831n->mRemoved = true;832833// Add element to the stack of elements to visit834cur_stack_pos++;835JPH_ASSERT(cur_stack_pos < cMaxEdgeLength);836StackEntry &new_entry = stack[cur_stack_pos];837new_entry.mFirstEdge = ne;838new_entry.mCurrentEdge = ne->mNextEdge; // We don't need to test this edge again since we came from it839}840else841{842// Vertex behind, keep edge843FullEdge full;844full.mNeighbourEdge = ne;845full.mStartIdx = e->mStartIdx;846full.mEndIdx = ne->mStartIdx;847outEdges.push_back(full);848}849}850}851}852}853854// Assert that we have a fully connected loop855#ifdef JPH_ENABLE_ASSERTS856for (int i = 0; i < (int)outEdges.size(); ++i)857JPH_ASSERT(outEdges[i].mEndIdx == outEdges[(i + 1) % outEdges.size()].mStartIdx);858#endif859860#ifdef JPH_CONVEX_BUILDER_DEBUG861// Draw edge of facing faces862for (int i = 0; i < (int)outEdges.size(); ++i)863DebugRenderer::sInstance->DrawArrow(cDrawScale * (mOffset + mPositions[outEdges[i].mStartIdx]), cDrawScale * (mOffset + mPositions[outEdges[i].mEndIdx]), Color::sWhite, 0.01f);864DrawState();865#endif866}867868void ConvexHullBuilder::MergeFaces(Edge *inEdge)869{870// Get the face871Face *face = inEdge->mFace;872873// Find the previous and next edge874Edge *next_edge = inEdge->mNextEdge;875Edge *prev_edge = inEdge->GetPreviousEdge();876877// Get the other face878Edge *other_edge = inEdge->mNeighbourEdge;879Face *other_face = other_edge->mFace;880881// Check if attempting to merge with self882JPH_ASSERT(face != other_face);883884#ifdef JPH_CONVEX_BUILDER_DEBUG885DrawWireFace(face, Color::sGreen);886DrawWireFace(other_face, Color::sRed);887DrawState();888#endif889890// Loop over the edges of the other face and make them belong to inFace891Edge *edge = other_edge->mNextEdge;892prev_edge->mNextEdge = edge;893for (;;)894{895edge->mFace = face;896if (edge->mNextEdge == other_edge)897{898// Terminate when we are back at other_edge899edge->mNextEdge = next_edge;900break;901}902edge = edge->mNextEdge;903}904905// If the first edge happens to be inEdge we need to fix it because this edge is no longer part of the face.906// Note that we replace it with the first edge of the merged face so that if the MergeFace function is called907// from a loop that loops around the face that it will still terminate after visiting all edges once.908if (face->mFirstEdge == inEdge)909face->mFirstEdge = prev_edge->mNextEdge;910911// Free the edges912delete inEdge;913delete other_edge;914915// Mark the other face as removed916other_face->mFirstEdge = nullptr;917other_face->mRemoved = true;918919// Recalculate plane920face->CalculateNormalAndCentroid(mPositions.data());921922// Merge conflict lists923if (face->mFurthestPointDistanceSq > other_face->mFurthestPointDistanceSq)924{925// This face has a point that's further away, make sure it remains the last one as we add the other points to this faces list926face->mConflictList.insert(face->mConflictList.end() - 1, other_face->mConflictList.begin(), other_face->mConflictList.end());927}928else929{930// The other face has a point that's furthest away, add that list at the end.931face->mConflictList.insert(face->mConflictList.end(), other_face->mConflictList.begin(), other_face->mConflictList.end());932face->mFurthestPointDistanceSq = other_face->mFurthestPointDistanceSq;933}934other_face->mConflictList.clear();935936#ifdef JPH_CONVEX_BUILDER_DEBUG937DrawWireFace(face, Color::sWhite);938DrawState();939#endif940}941942void ConvexHullBuilder::MergeDegenerateFace(Face *inFace, Faces &ioAffectedFaces)943{944// Check area of face945if (inFace->mNormal.LengthSq() < cMinTriangleAreaSq)946{947// Find longest edge, since this face is a sliver this should keep the face convex948float max_length_sq = 0.0f;949Edge *longest_edge = nullptr;950Edge *e = inFace->mFirstEdge;951Vec3 p1 = mPositions[e->mStartIdx];952do953{954Edge *next = e->mNextEdge;955Vec3 p2 = mPositions[next->mStartIdx];956float length_sq = (p2 - p1).LengthSq();957if (length_sq >= max_length_sq)958{959max_length_sq = length_sq;960longest_edge = e;961}962p1 = p2;963e = next;964} while (e != inFace->mFirstEdge);965966// Merge with face on longest edge967MergeFaces(longest_edge);968969// Remove any invalid edges970RemoveInvalidEdges(inFace, ioAffectedFaces);971}972}973974void ConvexHullBuilder::MergeCoplanarOrConcaveFaces(Face *inFace, float inCoplanarToleranceSq, Faces &ioAffectedFaces)975{976bool merged = false;977978Edge *edge = inFace->mFirstEdge;979do980{981// Store next edge since this edge can be removed982Edge *next_edge = edge->mNextEdge;983984// Test if centroid of one face is above plane of the other face by inCoplanarToleranceSq.985// If so we need to merge other face into inFace.986const Face *other_face = edge->mNeighbourEdge->mFace;987Vec3 delta_centroid = other_face->mCentroid - inFace->mCentroid;988float dist_other_face_centroid = inFace->mNormal.Dot(delta_centroid);989float signed_dist_other_face_centroid_sq = abs(dist_other_face_centroid) * dist_other_face_centroid;990float dist_face_centroid = -other_face->mNormal.Dot(delta_centroid);991float signed_dist_face_centroid_sq = abs(dist_face_centroid) * dist_face_centroid;992float face_normal_len_sq = inFace->mNormal.LengthSq();993float other_face_normal_len_sq = other_face->mNormal.LengthSq();994if ((signed_dist_other_face_centroid_sq > -inCoplanarToleranceSq * face_normal_len_sq995|| signed_dist_face_centroid_sq > -inCoplanarToleranceSq * other_face_normal_len_sq)996&& inFace->mNormal.Dot(other_face->mNormal) > 0.0f) // Never merge faces that are back to back997{998MergeFaces(edge);999merged = true;1000}10011002edge = next_edge;1003} while (edge != inFace->mFirstEdge);10041005if (merged)1006RemoveInvalidEdges(inFace, ioAffectedFaces);1007}10081009void ConvexHullBuilder::sMarkAffected(Face *inFace, Faces &ioAffectedFaces)1010{1011if (std::find(ioAffectedFaces.begin(), ioAffectedFaces.end(), inFace) == ioAffectedFaces.end())1012ioAffectedFaces.push_back(inFace);1013}10141015void ConvexHullBuilder::RemoveInvalidEdges(Face *inFace, Faces &ioAffectedFaces)1016{1017// This marks that the plane needs to be recalculated (we delay this until the end of the1018// function since we don't use the plane and we want to avoid calculating it multiple times)1019bool recalculate_plane = false;10201021// We keep going through this loop until no more edges were removed1022bool removed;1023do1024{1025removed = false;10261027// Loop over all edges in this face1028Edge *edge = inFace->mFirstEdge;1029Face *neighbour_face = edge->mNeighbourEdge->mFace;1030do1031{1032Edge *next_edge = edge->mNextEdge;1033Face *next_neighbour_face = next_edge->mNeighbourEdge->mFace;10341035if (neighbour_face == inFace)1036{1037// We only remove 1 edge at a time, check if this edge's next edge is our neighbour.1038// If this check fails, we will continue to scan along the edge until we find an edge where this is the case.1039if (edge->mNeighbourEdge == next_edge)1040{1041// This edge leads back to the starting point, this means the edge is interior and needs to be removed1042#ifdef JPH_CONVEX_BUILDER_DEBUG1043DrawWireFace(inFace, Color::sBlue);1044DrawState();1045#endif10461047// Remove edge1048Edge *prev_edge = edge->GetPreviousEdge();1049prev_edge->mNextEdge = next_edge->mNextEdge;1050if (inFace->mFirstEdge == edge || inFace->mFirstEdge == next_edge)1051inFace->mFirstEdge = prev_edge;1052delete edge;1053delete next_edge;10541055#ifdef JPH_CONVEX_BUILDER_DEBUG1056DrawWireFace(inFace, Color::sGreen);1057DrawState();1058#endif10591060// Check if inFace now has only 2 edges left1061if (RemoveTwoEdgeFace(inFace, ioAffectedFaces))1062return; // Bail if face no longer exists10631064// Restart the loop1065recalculate_plane = true;1066removed = true;1067break;1068}1069}1070else if (neighbour_face == next_neighbour_face)1071{1072// There are two edges that connect to the same face, we will remove the second one1073#ifdef JPH_CONVEX_BUILDER_DEBUG1074DrawWireFace(inFace, Color::sYellow);1075DrawWireFace(neighbour_face, Color::sRed);1076DrawState();1077#endif10781079// First merge the neighbours edges1080Edge *neighbour_edge = next_edge->mNeighbourEdge;1081Edge *next_neighbour_edge = neighbour_edge->mNextEdge;1082if (neighbour_face->mFirstEdge == next_neighbour_edge)1083neighbour_face->mFirstEdge = neighbour_edge;1084neighbour_edge->mNextEdge = next_neighbour_edge->mNextEdge;1085neighbour_edge->mNeighbourEdge = edge;1086delete next_neighbour_edge;10871088// Then merge my own edges1089if (inFace->mFirstEdge == next_edge)1090inFace->mFirstEdge = edge;1091edge->mNextEdge = next_edge->mNextEdge;1092edge->mNeighbourEdge = neighbour_edge;1093delete next_edge;10941095#ifdef JPH_CONVEX_BUILDER_DEBUG1096DrawWireFace(inFace, Color::sYellow);1097DrawWireFace(neighbour_face, Color::sGreen);1098DrawState();1099#endif11001101// Check if neighbour has only 2 edges left1102if (!RemoveTwoEdgeFace(neighbour_face, ioAffectedFaces))1103{1104// No, we need to recalculate its plane1105neighbour_face->CalculateNormalAndCentroid(mPositions.data());11061107// Mark neighbour face as affected1108sMarkAffected(neighbour_face, ioAffectedFaces);1109}11101111// Check if inFace now has only 2 edges left1112if (RemoveTwoEdgeFace(inFace, ioAffectedFaces))1113return; // Bail if face no longer exists11141115// Restart loop1116recalculate_plane = true;1117removed = true;1118break;1119}11201121// This edge is ok, go to the next edge1122edge = next_edge;1123neighbour_face = next_neighbour_face;11241125} while (edge != inFace->mFirstEdge);1126} while (removed);11271128// Recalculate plane?1129if (recalculate_plane)1130inFace->CalculateNormalAndCentroid(mPositions.data());1131}11321133bool ConvexHullBuilder::RemoveTwoEdgeFace(Face *inFace, Faces &ioAffectedFaces) const1134{1135// Check if this face contains only 2 edges1136Edge *edge = inFace->mFirstEdge;1137Edge *next_edge = edge->mNextEdge;1138JPH_ASSERT(edge != next_edge); // 1 edge faces should not exist1139if (next_edge->mNextEdge == edge)1140{1141#ifdef JPH_CONVEX_BUILDER_DEBUG1142DrawWireFace(inFace, Color::sRed);1143DrawState();1144#endif11451146// Schedule both neighbours for re-checking1147Edge *neighbour_edge = edge->mNeighbourEdge;1148Face *neighbour_face = neighbour_edge->mFace;1149Edge *next_neighbour_edge = next_edge->mNeighbourEdge;1150Face *next_neighbour_face = next_neighbour_edge->mFace;1151sMarkAffected(neighbour_face, ioAffectedFaces);1152sMarkAffected(next_neighbour_face, ioAffectedFaces);11531154// Link my neighbours to each other1155neighbour_edge->mNeighbourEdge = next_neighbour_edge;1156next_neighbour_edge->mNeighbourEdge = neighbour_edge;11571158// Unlink my edges1159edge->mNeighbourEdge = nullptr;1160next_edge->mNeighbourEdge = nullptr;11611162// Mark this face as removed1163inFace->mRemoved = true;11641165return true;1166}11671168return false;1169}11701171#ifdef JPH_ENABLE_ASSERTS11721173void ConvexHullBuilder::DumpFace(const Face *inFace) const1174{1175Trace("f:0x%p", inFace);11761177const Edge *e = inFace->mFirstEdge;1178do1179{1180Trace("\te:0x%p { i:%d e:0x%p f:0x%p }", e, e->mStartIdx, e->mNeighbourEdge, e->mNeighbourEdge->mFace);1181e = e->mNextEdge;1182} while (e != inFace->mFirstEdge);1183}11841185void ConvexHullBuilder::DumpFaces() const1186{1187Trace("Dump Faces:");11881189for (const Face *f : mFaces)1190if (!f->mRemoved)1191DumpFace(f);1192}11931194void ConvexHullBuilder::ValidateFace(const Face *inFace) const1195{1196if (inFace->mRemoved)1197{1198const Edge *e = inFace->mFirstEdge;1199if (e != nullptr)1200do1201{1202JPH_ASSERT(e->mNeighbourEdge == nullptr);1203e = e->mNextEdge;1204} while (e != inFace->mFirstEdge);1205}1206else1207{1208int edge_count = 0;12091210const Edge *e = inFace->mFirstEdge;1211do1212{1213// Count edge1214++edge_count;12151216// Validate that adjacent faces are all different1217if (mFaces.size() > 2)1218for (const Edge *other_edge = e->mNextEdge; other_edge != inFace->mFirstEdge; other_edge = other_edge->mNextEdge)1219JPH_ASSERT(e->mNeighbourEdge->mFace != other_edge->mNeighbourEdge->mFace);12201221// Assert that the face is correct1222JPH_ASSERT(e->mFace == inFace);12231224// Assert that we have a neighbour1225const Edge *nb_edge = e->mNeighbourEdge;1226JPH_ASSERT(nb_edge != nullptr);1227if (nb_edge != nullptr)1228{1229// Assert that our neighbours edge points to us1230JPH_ASSERT(nb_edge->mNeighbourEdge == e);12311232// Assert that it belongs to a different face1233JPH_ASSERT(nb_edge->mFace != inFace);12341235// Assert that the next edge of the neighbour points to the same vertex as this edge's vertex1236JPH_ASSERT(nb_edge->mNextEdge->mStartIdx == e->mStartIdx);12371238// Assert that my next edge points to the same vertex as my neighbours vertex1239JPH_ASSERT(e->mNextEdge->mStartIdx == nb_edge->mStartIdx);1240}1241e = e->mNextEdge;1242} while (e != inFace->mFirstEdge);12431244// Assert that we have 3 or more edges1245JPH_ASSERT(edge_count >= 3);1246}1247}12481249void ConvexHullBuilder::ValidateFaces() const1250{1251for (const Face *f : mFaces)1252ValidateFace(f);1253}12541255#endif // JPH_ENABLE_ASSERTS12561257void ConvexHullBuilder::GetCenterOfMassAndVolume(Vec3 &outCenterOfMass, float &outVolume) const1258{1259// Fourth point is the average of all face centroids1260Vec3 v4 = Vec3::sZero();1261for (const Face *f : mFaces)1262v4 += f->mCentroid;1263v4 /= float(mFaces.size());12641265// Calculate mass and center of mass of this convex hull by summing all tetrahedrons1266outVolume = 0.0f;1267outCenterOfMass = Vec3::sZero();1268for (const Face *f : mFaces)1269{1270// Get the first vertex that we'll use to create a triangle fan1271Edge *e = f->mFirstEdge;1272Vec3 v1 = mPositions[e->mStartIdx];12731274// Get the second vertex1275e = e->mNextEdge;1276Vec3 v2 = mPositions[e->mStartIdx];12771278for (e = e->mNextEdge; e != f->mFirstEdge; e = e->mNextEdge)1279{1280// Fetch the last point of the triangle1281Vec3 v3 = mPositions[e->mStartIdx];12821283// Calculate center of mass and mass of this tetrahedron,1284// see: https://en.wikipedia.org/wiki/Tetrahedron#Volume1285float volume_tetrahedron = (v1 - v4).Dot((v2 - v4).Cross(v3 - v4)); // Needs to be divided by 6, postpone this until the end of the loop1286Vec3 center_of_mass_tetrahedron = v1 + v2 + v3 + v4; // Needs to be divided by 4, postpone this until the end of the loop12871288// Accumulate results1289outVolume += volume_tetrahedron;1290outCenterOfMass += volume_tetrahedron * center_of_mass_tetrahedron;12911292// Update v2 for next triangle1293v2 = v3;1294}1295}12961297// Calculate center of mass, fall back to average point in case there is no volume (everything is on a plane in this case)1298if (outVolume > FLT_EPSILON)1299outCenterOfMass /= 4.0f * outVolume;1300else1301outCenterOfMass = v4;13021303outVolume /= 6.0f;1304}13051306void ConvexHullBuilder::DetermineMaxError(Face *&outFaceWithMaxError, float &outMaxError, int &outMaxErrorPositionIdx, float &outCoplanarDistance) const1307{1308outCoplanarDistance = DetermineCoplanarDistance();13091310// This measures the distance from a polygon to the furthest point outside of the hull1311float max_error = 0.0f;1312Face *max_error_face = nullptr;1313int max_error_point = -1;13141315for (int i = 0; i < (int)mPositions.size(); ++i)1316{1317Vec3 v = mPositions[i];13181319// This measures the closest edge from all faces to point v1320// Note that we take the min of all faces since there may be multiple near coplanar faces so if we were to test this per face1321// we may find that a point is outside of a polygon and mark it as an error, while it is actually inside a nearly coplanar1322// polygon.1323float min_edge_dist_sq = FLT_MAX;1324Face *min_edge_dist_face = nullptr;13251326for (Face *f : mFaces)1327{1328// Check if point is on or in front of plane1329float normal_len = f->mNormal.Length();1330JPH_ASSERT(normal_len > 0.0f);1331float plane_dist = f->mNormal.Dot(v - f->mCentroid) / normal_len;1332if (plane_dist > -outCoplanarDistance)1333{1334// Check distance to the edges of this face1335float edge_dist_sq = GetDistanceToEdgeSq(v, f);1336if (edge_dist_sq < min_edge_dist_sq)1337{1338min_edge_dist_sq = edge_dist_sq;1339min_edge_dist_face = f;1340}13411342// If the point is inside the polygon and the point is in front of the plane, measure the distance1343if (edge_dist_sq == 0.0f && plane_dist > max_error)1344{1345max_error = plane_dist;1346max_error_face = f;1347max_error_point = i;1348}1349}1350}13511352// If the minimum distance to an edge is further than our current max error, we use that as max error1353float min_edge_dist = sqrt(min_edge_dist_sq);1354if (min_edge_dist_face != nullptr && min_edge_dist > max_error)1355{1356max_error = min_edge_dist;1357max_error_face = min_edge_dist_face;1358max_error_point = i;1359}1360}13611362outFaceWithMaxError = max_error_face;1363outMaxError = max_error;1364outMaxErrorPositionIdx = max_error_point;1365}13661367#ifdef JPH_CONVEX_BUILDER_DEBUG13681369void ConvexHullBuilder::DrawState(bool inDrawConflictList) const1370{1371// Draw origin1372DebugRenderer::sInstance->DrawMarker(cDrawScale * mOffset, Color::sRed, 0.2f);13731374int face_idx = 0;13751376// Draw faces1377for (const Face *f : mFaces)1378if (!f->mRemoved)1379{1380Color iteration_color = Color::sGetDistinctColor(f->mIteration);1381Color face_color = Color::sGetDistinctColor(face_idx++);13821383// First point1384const Edge *e = f->mFirstEdge;1385RVec3 p1 = cDrawScale * (mOffset + mPositions[e->mStartIdx]);13861387// Second point1388e = e->mNextEdge;1389RVec3 p2 = cDrawScale * (mOffset + mPositions[e->mStartIdx]);13901391// First line1392DebugRenderer::sInstance->DrawLine(p1, p2, Color::sGrey);13931394do1395{1396// Third point1397e = e->mNextEdge;1398RVec3 p3 = cDrawScale * (mOffset + mPositions[e->mStartIdx]);13991400DebugRenderer::sInstance->DrawTriangle(p1, p2, p3, iteration_color);14011402DebugRenderer::sInstance->DrawLine(p2, p3, Color::sGrey);14031404p2 = p3;1405}1406while (e != f->mFirstEdge);14071408// Draw normal1409RVec3 centroid = cDrawScale * (mOffset + f->mCentroid);1410DebugRenderer::sInstance->DrawArrow(centroid, centroid + f->mNormal.NormalizedOr(Vec3::sZero()), face_color, 0.01f);14111412// Draw conflict list1413if (inDrawConflictList)1414for (int idx : f->mConflictList)1415DebugRenderer::sInstance->DrawMarker(cDrawScale * (mOffset + mPositions[idx]), face_color, 0.05f);1416}14171418// Offset to the right1419mOffset += mDelta;1420}14211422void ConvexHullBuilder::DrawWireFace(const Face *inFace, ColorArg inColor) const1423{1424const Edge *e = inFace->mFirstEdge;1425RVec3 prev = cDrawScale * (mOffset + mPositions[e->mStartIdx]);1426do1427{1428const Edge *next = e->mNextEdge;1429RVec3 cur = cDrawScale * (mOffset + mPositions[next->mStartIdx]);1430DebugRenderer::sInstance->DrawArrow(prev, cur, inColor, 0.01f);1431DebugRenderer::sInstance->DrawText3D(prev, ConvertToString(e->mStartIdx), inColor);1432e = next;1433prev = cur;1434} while (e != inFace->mFirstEdge);1435}14361437void ConvexHullBuilder::DrawEdge(const Edge *inEdge, ColorArg inColor) const1438{1439RVec3 p1 = cDrawScale * (mOffset + mPositions[inEdge->mStartIdx]);1440RVec3 p2 = cDrawScale * (mOffset + mPositions[inEdge->mNextEdge->mStartIdx]);1441DebugRenderer::sInstance->DrawArrow(p1, p2, inColor, 0.01f);1442}14431444#endif // JPH_CONVEX_BUILDER_DEBUG14451446#ifdef JPH_CONVEX_BUILDER_DUMP_SHAPE14471448void ConvexHullBuilder::DumpShape() const1449{1450static atomic<int> sShapeNo = 1;1451int shape_no = sShapeNo++;14521453std::ofstream f;1454f.open(StringFormat("dumped_shape%d.cpp", shape_no).c_str(), std::ofstream::out | std::ofstream::trunc);1455if (!f.is_open())1456return;14571458f << "{\n";1459for (Vec3 v : mPositions)1460f << StringFormat("\tVec3(%.9gf, %.9gf, %.9gf),\n", (double)v.GetX(), (double)v.GetY(), (double)v.GetZ());1461f << "},\n";1462}14631464#endif // JPH_CONVEX_BUILDER_DUMP_SHAPE14651466JPH_NAMESPACE_END146714681469