123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
- // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
- // SPDX-License-Identifier: MIT
- #include <Jolt/Jolt.h>
- #include <Jolt/Geometry/ConvexHullBuilder2D.h>
- #ifdef JPH_CONVEX_BUILDER_2D_DEBUG
- #include <Jolt/Renderer/DebugRenderer.h>
- #endif
- JPH_NAMESPACE_BEGIN
- void ConvexHullBuilder2D::Edge::CalculateNormalAndCenter(const Vec3 *inPositions)
- {
- Vec3 p1 = inPositions[mStartIdx];
- Vec3 p2 = inPositions[mNextEdge->mStartIdx];
- // Center of edge
- mCenter = 0.5f * (p1 + p2);
- // Create outward pointing normal.
- // We have two choices for the normal (which satisfies normal . edge = 0):
- // normal1 = (-edge.y, edge.x, 0)
- // normal2 = (edge.y, -edge.x, 0)
- // We want (normal x edge).z > 0 so that the normal points out of the polygon. Only normal2 satisfies this condition.
- Vec3 edge = p2 - p1;
- mNormal = Vec3(edge.GetY(), -edge.GetX(), 0);
- }
- ConvexHullBuilder2D::ConvexHullBuilder2D(const Positions &inPositions) :
- mPositions(inPositions)
- {
- #ifdef JPH_CONVEX_BUILDER_2D_DEBUG
- // Center the drawing of the first hull around the origin and calculate the delta offset between states
- mOffset = RVec3::sZero();
- if (mPositions.empty())
- {
- // No hull will be generated
- mDelta = Vec3::sZero();
- }
- else
- {
- Vec3 maxv = Vec3::sReplicate(-FLT_MAX), minv = Vec3::sReplicate(FLT_MAX);
- for (Vec3 v : mPositions)
- {
- minv = Vec3::sMin(minv, v);
- maxv = Vec3::sMax(maxv, v);
- mOffset -= v;
- }
- mOffset /= Real(mPositions.size());
- mDelta = Vec3((maxv - minv).GetX() + 0.5f, 0, 0);
- mOffset += mDelta; // Don't start at origin, we're already drawing the final hull there
- }
- #endif
- }
- ConvexHullBuilder2D::~ConvexHullBuilder2D()
- {
- FreeEdges();
- }
- void ConvexHullBuilder2D::FreeEdges()
- {
- if (mFirstEdge == nullptr)
- return;
- Edge *edge = mFirstEdge;
- do
- {
- Edge *next = edge->mNextEdge;
- delete edge;
- edge = next;
- } while (edge != mFirstEdge);
- mFirstEdge = nullptr;
- mNumEdges = 0;
- }
- #ifdef JPH_ENABLE_ASSERTS
- void ConvexHullBuilder2D::ValidateEdges() const
- {
- if (mFirstEdge == nullptr)
- {
- JPH_ASSERT(mNumEdges == 0);
- return;
- }
- int count = 0;
- Edge *edge = mFirstEdge;
- do
- {
- // Validate connectivity
- JPH_ASSERT(edge->mNextEdge->mPrevEdge == edge);
- JPH_ASSERT(edge->mPrevEdge->mNextEdge == edge);
- ++count;
- edge = edge->mNextEdge;
- } while (edge != mFirstEdge);
- // Validate that count matches
- JPH_ASSERT(count == mNumEdges);
- }
- #endif // JPH_ENABLE_ASSERTS
- void ConvexHullBuilder2D::AssignPointToEdge(int inPositionIdx, const Array<Edge *> &inEdges) const
- {
- Vec3 point = mPositions[inPositionIdx];
- Edge *best_edge = nullptr;
- float best_dist_sq = 0.0f;
- // Test against all edges
- for (Edge *edge : inEdges)
- {
- // Determine distance to edge
- float dot = edge->mNormal.Dot(point - edge->mCenter);
- if (dot > 0.0f)
- {
- float dist_sq = dot * dot / edge->mNormal.LengthSq();
- if (dist_sq > best_dist_sq)
- {
- best_edge = edge;
- best_dist_sq = dist_sq;
- }
- }
- }
- // If this point is in front of the edge, add it to the conflict list
- if (best_edge != nullptr)
- {
- if (best_dist_sq > best_edge->mFurthestPointDistanceSq)
- {
- // This point is futher away than any others, update the distance and add point as last point
- best_edge->mFurthestPointDistanceSq = best_dist_sq;
- best_edge->mConflictList.push_back(inPositionIdx);
- }
- else
- {
- // Not the furthest point, add it as the before last point
- best_edge->mConflictList.push_back(best_edge->mConflictList.back());
- best_edge->mConflictList[best_edge->mConflictList.size() - 2] = inPositionIdx;
- }
- }
- }
- ConvexHullBuilder2D::EResult ConvexHullBuilder2D::Initialize(int inIdx1, int inIdx2, int inIdx3, int inMaxVertices, float inTolerance, Edges &outEdges)
- {
- // Clear any leftovers
- FreeEdges();
- outEdges.clear();
- // Reset flag
- EResult result = EResult::Success;
- // Determine a suitable tolerance for detecting that points are colinear
- // Formula as per: Implementing Quickhull - Dirk Gregorius.
- Vec3 vmax = Vec3::sZero();
- for (Vec3 v : mPositions)
- vmax = Vec3::sMax(vmax, v.Abs());
- float colinear_tolerance_sq = Square(2.0f * FLT_EPSILON * (vmax.GetX() + vmax.GetY()));
- // Increase desired tolerance if accuracy doesn't allow it
- float tolerance_sq = max(colinear_tolerance_sq, Square(inTolerance));
- // Start with the initial indices in counter clockwise order
- float z = (mPositions[inIdx2] - mPositions[inIdx1]).Cross(mPositions[inIdx3] - mPositions[inIdx1]).GetZ();
- if (z < 0.0f)
- swap(inIdx1, inIdx2);
- // Create and link edges
- Edge *e1 = new Edge(inIdx1);
- Edge *e2 = new Edge(inIdx2);
- Edge *e3 = new Edge(inIdx3);
- e1->mNextEdge = e2;
- e1->mPrevEdge = e3;
- e2->mNextEdge = e3;
- e2->mPrevEdge = e1;
- e3->mNextEdge = e1;
- e3->mPrevEdge = e2;
- mFirstEdge = e1;
- mNumEdges = 3;
- // Build the initial conflict lists
- Array<Edge *> edges { e1, e2, e3 };
- for (Edge *edge : edges)
- edge->CalculateNormalAndCenter(mPositions.data());
- for (int idx = 0; idx < (int)mPositions.size(); ++idx)
- if (idx != inIdx1 && idx != inIdx2 && idx != inIdx3)
- AssignPointToEdge(idx, edges);
- JPH_IF_ENABLE_ASSERTS(ValidateEdges();)
- #ifdef JPH_CONVEX_BUILDER_2D_DEBUG
- DrawState();
- #endif
- // Add the remaining points to the hull
- for (;;)
- {
- // Check if we've reached the max amount of vertices that are allowed
- if (mNumEdges >= inMaxVertices)
- {
- result = EResult::MaxVerticesReached;
- break;
- }
- // Find the edge with the furthest point on it
- Edge *edge_with_furthest_point = nullptr;
- float furthest_dist_sq = 0.0f;
- Edge *edge = mFirstEdge;
- do
- {
- if (edge->mFurthestPointDistanceSq > furthest_dist_sq)
- {
- furthest_dist_sq = edge->mFurthestPointDistanceSq;
- edge_with_furthest_point = edge;
- }
- edge = edge->mNextEdge;
- } while (edge != mFirstEdge);
- // If there is none closer than our tolerance value, we're done
- if (edge_with_furthest_point == nullptr || furthest_dist_sq < tolerance_sq)
- break;
- // Take the furthest point
- int furthest_point_idx = edge_with_furthest_point->mConflictList.back();
- edge_with_furthest_point->mConflictList.pop_back();
- Vec3 furthest_point = mPositions[furthest_point_idx];
- // Find the horizon of edges that need to be removed
- Edge *first_edge = edge_with_furthest_point;
- do
- {
- Edge *prev = first_edge->mPrevEdge;
- if (!prev->IsFacing(furthest_point))
- break;
- first_edge = prev;
- } while (first_edge != edge_with_furthest_point);
- Edge *last_edge = edge_with_furthest_point;
- do
- {
- Edge *next = last_edge->mNextEdge;
- if (!next->IsFacing(furthest_point))
- break;
- last_edge = next;
- } while (last_edge != edge_with_furthest_point);
- // Create new edges
- e1 = new Edge(first_edge->mStartIdx);
- e2 = new Edge(furthest_point_idx);
- e1->mNextEdge = e2;
- e1->mPrevEdge = first_edge->mPrevEdge;
- e2->mPrevEdge = e1;
- e2->mNextEdge = last_edge->mNextEdge;
- e1->mPrevEdge->mNextEdge = e1;
- e2->mNextEdge->mPrevEdge = e2;
- mFirstEdge = e1; // We could delete mFirstEdge so just update it to the newly created edge
- mNumEdges += 2;
- // Calculate normals
- Array<Edge *> new_edges { e1, e2 };
- for (Edge *new_edge : new_edges)
- new_edge->CalculateNormalAndCenter(mPositions.data());
- // Delete the old edges
- for (;;)
- {
- Edge *next = first_edge->mNextEdge;
- // Redistribute points in conflict list
- for (int idx : first_edge->mConflictList)
- AssignPointToEdge(idx, new_edges);
- // Delete the old edge
- delete first_edge;
- --mNumEdges;
- if (first_edge == last_edge)
- break;
- first_edge = next;
- }
- JPH_IF_ENABLE_ASSERTS(ValidateEdges();)
- #ifdef JPH_CONVEX_BUILDER_2D_DEBUG
- DrawState();
- #endif
- }
- // Convert the edge list to a list of indices
- outEdges.reserve(mNumEdges);
- Edge *edge = mFirstEdge;
- do
- {
- outEdges.push_back(edge->mStartIdx);
- edge = edge->mNextEdge;
- } while (edge != mFirstEdge);
- return result;
- }
- #ifdef JPH_CONVEX_BUILDER_2D_DEBUG
- void ConvexHullBuilder2D::DrawState()
- {
- int color_idx = 0;
- const Edge *edge = mFirstEdge;
- do
- {
- const Edge *next = edge->mNextEdge;
- // Get unique color per edge
- Color color = Color::sGetDistinctColor(color_idx++);
- // Draw edge and normal
- DebugRenderer::sInstance->DrawArrow(cDrawScale * (mOffset + mPositions[edge->mStartIdx]), cDrawScale * (mOffset + mPositions[next->mStartIdx]), color, 0.1f);
- DebugRenderer::sInstance->DrawArrow(cDrawScale * (mOffset + edge->mCenter), cDrawScale * (mOffset + edge->mCenter) + edge->mNormal.NormalizedOr(Vec3::sZero()), Color::sGreen, 0.1f);
- // Draw points that belong to this edge in the same color
- for (int idx : edge->mConflictList)
- DebugRenderer::sInstance->DrawMarker(cDrawScale * (mOffset + mPositions[idx]), color, 0.05f);
- edge = next;
- } while (edge != mFirstEdge);
- mOffset += mDelta;
- }
- #endif
- JPH_NAMESPACE_END
|