Path: blob/master/thirdparty/jolt_physics/Jolt/Geometry/ConvexHullBuilder2D.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/ConvexHullBuilder2D.h>78#ifdef JPH_CONVEX_BUILDER_2D_DEBUG9#include <Jolt/Renderer/DebugRenderer.h>10#endif1112JPH_NAMESPACE_BEGIN1314void ConvexHullBuilder2D::Edge::CalculateNormalAndCenter(const Vec3 *inPositions)15{16Vec3 p1 = inPositions[mStartIdx];17Vec3 p2 = inPositions[mNextEdge->mStartIdx];1819// Center of edge20mCenter = 0.5f * (p1 + p2);2122// Create outward pointing normal.23// We have two choices for the normal (which satisfies normal . edge = 0):24// normal1 = (-edge.y, edge.x, 0)25// normal2 = (edge.y, -edge.x, 0)26// We want (normal x edge).z > 0 so that the normal points out of the polygon. Only normal2 satisfies this condition.27Vec3 edge = p2 - p1;28mNormal = Vec3(edge.GetY(), -edge.GetX(), 0);29}3031ConvexHullBuilder2D::ConvexHullBuilder2D(const Positions &inPositions) :32mPositions(inPositions)33{34#ifdef JPH_CONVEX_BUILDER_2D_DEBUG35// Center the drawing of the first hull around the origin and calculate the delta offset between states36mOffset = RVec3::sZero();37if (mPositions.empty())38{39// No hull will be generated40mDelta = Vec3::sZero();41}42else43{44Vec3 maxv = Vec3::sReplicate(-FLT_MAX), minv = Vec3::sReplicate(FLT_MAX);45for (Vec3 v : mPositions)46{47minv = Vec3::sMin(minv, v);48maxv = Vec3::sMax(maxv, v);49mOffset -= v;50}51mOffset /= Real(mPositions.size());52mDelta = Vec3((maxv - minv).GetX() + 0.5f, 0, 0);53mOffset += mDelta; // Don't start at origin, we're already drawing the final hull there54}55#endif56}5758ConvexHullBuilder2D::~ConvexHullBuilder2D()59{60FreeEdges();61}6263void ConvexHullBuilder2D::FreeEdges()64{65if (mFirstEdge == nullptr)66return;6768Edge *edge = mFirstEdge;69do70{71Edge *next = edge->mNextEdge;72delete edge;73edge = next;74} while (edge != mFirstEdge);7576mFirstEdge = nullptr;77mNumEdges = 0;78}7980#ifdef JPH_ENABLE_ASSERTS8182void ConvexHullBuilder2D::ValidateEdges() const83{84if (mFirstEdge == nullptr)85{86JPH_ASSERT(mNumEdges == 0);87return;88}8990int count = 0;9192Edge *edge = mFirstEdge;93do94{95// Validate connectivity96JPH_ASSERT(edge->mNextEdge->mPrevEdge == edge);97JPH_ASSERT(edge->mPrevEdge->mNextEdge == edge);9899++count;100edge = edge->mNextEdge;101} while (edge != mFirstEdge);102103// Validate that count matches104JPH_ASSERT(count == mNumEdges);105}106107#endif // JPH_ENABLE_ASSERTS108109void ConvexHullBuilder2D::AssignPointToEdge(int inPositionIdx, const Array<Edge *> &inEdges) const110{111Vec3 point = mPositions[inPositionIdx];112113Edge *best_edge = nullptr;114float best_dist_sq = 0.0f;115116// Test against all edges117for (Edge *edge : inEdges)118{119// Determine distance to edge120float dot = edge->mNormal.Dot(point - edge->mCenter);121if (dot > 0.0f)122{123float dist_sq = dot * dot / edge->mNormal.LengthSq();124if (dist_sq > best_dist_sq)125{126best_edge = edge;127best_dist_sq = dist_sq;128}129}130}131132// If this point is in front of the edge, add it to the conflict list133if (best_edge != nullptr)134{135if (best_dist_sq > best_edge->mFurthestPointDistanceSq)136{137// This point is further away than any others, update the distance and add point as last point138best_edge->mFurthestPointDistanceSq = best_dist_sq;139best_edge->mConflictList.push_back(inPositionIdx);140}141else142{143// Not the furthest point, add it as the before last point144best_edge->mConflictList.insert(best_edge->mConflictList.begin() + best_edge->mConflictList.size() - 1, inPositionIdx);145}146}147}148149ConvexHullBuilder2D::EResult ConvexHullBuilder2D::Initialize(int inIdx1, int inIdx2, int inIdx3, int inMaxVertices, float inTolerance, Edges &outEdges)150{151// Clear any leftovers152FreeEdges();153outEdges.clear();154155// Reset flag156EResult result = EResult::Success;157158// Determine a suitable tolerance for detecting that points are colinear159// Formula as per: Implementing Quickhull - Dirk Gregorius.160Vec3 vmax = Vec3::sZero();161for (Vec3 v : mPositions)162vmax = Vec3::sMax(vmax, v.Abs());163float colinear_tolerance_sq = Square(2.0f * FLT_EPSILON * (vmax.GetX() + vmax.GetY()));164165// Increase desired tolerance if accuracy doesn't allow it166float tolerance_sq = max(colinear_tolerance_sq, Square(inTolerance));167168// Start with the initial indices in counter clockwise order169float z = (mPositions[inIdx2] - mPositions[inIdx1]).Cross(mPositions[inIdx3] - mPositions[inIdx1]).GetZ();170if (z < 0.0f)171std::swap(inIdx1, inIdx2);172173// Create and link edges174Edge *e1 = new Edge(inIdx1);175Edge *e2 = new Edge(inIdx2);176Edge *e3 = new Edge(inIdx3);177e1->mNextEdge = e2;178e1->mPrevEdge = e3;179e2->mNextEdge = e3;180e2->mPrevEdge = e1;181e3->mNextEdge = e1;182e3->mPrevEdge = e2;183mFirstEdge = e1;184mNumEdges = 3;185186// Build the initial conflict lists187Array<Edge *> edges { e1, e2, e3 };188for (Edge *edge : edges)189edge->CalculateNormalAndCenter(mPositions.data());190for (int idx = 0; idx < (int)mPositions.size(); ++idx)191if (idx != inIdx1 && idx != inIdx2 && idx != inIdx3)192AssignPointToEdge(idx, edges);193194JPH_IF_ENABLE_ASSERTS(ValidateEdges();)195#ifdef JPH_CONVEX_BUILDER_2D_DEBUG196DrawState();197#endif198199// Add the remaining points to the hull200for (;;)201{202// Check if we've reached the max amount of vertices that are allowed203if (mNumEdges >= inMaxVertices)204{205result = EResult::MaxVerticesReached;206break;207}208209// Find the edge with the furthest point on it210Edge *edge_with_furthest_point = nullptr;211float furthest_dist_sq = 0.0f;212Edge *edge = mFirstEdge;213do214{215if (edge->mFurthestPointDistanceSq > furthest_dist_sq)216{217furthest_dist_sq = edge->mFurthestPointDistanceSq;218edge_with_furthest_point = edge;219}220edge = edge->mNextEdge;221} while (edge != mFirstEdge);222223// If there is none closer than our tolerance value, we're done224if (edge_with_furthest_point == nullptr || furthest_dist_sq < tolerance_sq)225break;226227// Take the furthest point228int furthest_point_idx = edge_with_furthest_point->mConflictList.back();229edge_with_furthest_point->mConflictList.pop_back();230Vec3 furthest_point = mPositions[furthest_point_idx];231232// Find the horizon of edges that need to be removed233Edge *first_edge = edge_with_furthest_point;234do235{236Edge *prev = first_edge->mPrevEdge;237if (!prev->IsFacing(furthest_point))238break;239first_edge = prev;240} while (first_edge != edge_with_furthest_point);241242Edge *last_edge = edge_with_furthest_point;243do244{245Edge *next = last_edge->mNextEdge;246if (!next->IsFacing(furthest_point))247break;248last_edge = next;249} while (last_edge != edge_with_furthest_point);250251// Create new edges252e1 = new Edge(first_edge->mStartIdx);253e2 = new Edge(furthest_point_idx);254e1->mNextEdge = e2;255e1->mPrevEdge = first_edge->mPrevEdge;256e2->mPrevEdge = e1;257e2->mNextEdge = last_edge->mNextEdge;258e1->mPrevEdge->mNextEdge = e1;259e2->mNextEdge->mPrevEdge = e2;260mFirstEdge = e1; // We could delete mFirstEdge so just update it to the newly created edge261mNumEdges += 2;262263// Calculate normals264Array<Edge *> new_edges { e1, e2 };265for (Edge *new_edge : new_edges)266new_edge->CalculateNormalAndCenter(mPositions.data());267268// Delete the old edges269for (;;)270{271Edge *next = first_edge->mNextEdge;272273// Redistribute points in conflict list274for (int idx : first_edge->mConflictList)275AssignPointToEdge(idx, new_edges);276277// Delete the old edge278delete first_edge;279--mNumEdges;280281if (first_edge == last_edge)282break;283first_edge = next;284}285286JPH_IF_ENABLE_ASSERTS(ValidateEdges();)287#ifdef JPH_CONVEX_BUILDER_2D_DEBUG288DrawState();289#endif290}291292// Convert the edge list to a list of indices293outEdges.reserve(mNumEdges);294Edge *edge = mFirstEdge;295do296{297outEdges.push_back(edge->mStartIdx);298edge = edge->mNextEdge;299} while (edge != mFirstEdge);300301return result;302}303304#ifdef JPH_CONVEX_BUILDER_2D_DEBUG305306void ConvexHullBuilder2D::DrawState()307{308int color_idx = 0;309310const Edge *edge = mFirstEdge;311do312{313const Edge *next = edge->mNextEdge;314315// Get unique color per edge316Color color = Color::sGetDistinctColor(color_idx++);317318// Draw edge and normal319DebugRenderer::sInstance->DrawArrow(cDrawScale * (mOffset + mPositions[edge->mStartIdx]), cDrawScale * (mOffset + mPositions[next->mStartIdx]), color, 0.1f);320DebugRenderer::sInstance->DrawArrow(cDrawScale * (mOffset + edge->mCenter), cDrawScale * (mOffset + edge->mCenter) + edge->mNormal.NormalizedOr(Vec3::sZero()), Color::sGreen, 0.1f);321322// Draw points that belong to this edge in the same color323for (int idx : edge->mConflictList)324DebugRenderer::sInstance->DrawMarker(cDrawScale * (mOffset + mPositions[idx]), color, 0.05f);325326edge = next;327} while (edge != mFirstEdge);328329mOffset += mDelta;330}331332#endif333334JPH_NAMESPACE_END335336337