Path: blob/master/thirdparty/jolt_physics/Jolt/Geometry/EPAConvexHullBuilder.h
9913 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#pragma once56// Define to validate the integrity of the hull structure7//#define JPH_EPA_CONVEX_BUILDER_VALIDATE89// Define to draw the building of the hull for debugging purposes10//#define JPH_EPA_CONVEX_BUILDER_DRAW1112#include <Jolt/Core/NonCopyable.h>13#include <Jolt/Core/BinaryHeap.h>1415#ifdef JPH_EPA_CONVEX_BUILDER_DRAW16#include <Jolt/Renderer/DebugRenderer.h>17#include <Jolt/Core/StringTools.h>18#endif1920JPH_NAMESPACE_BEGIN2122/// A convex hull builder specifically made for the EPA penetration depth calculation. It trades accuracy for speed and will simply abort of the hull forms defects due to numerical precision problems.23class EPAConvexHullBuilder : public NonCopyable24{25private:26#ifdef JPH_EPA_CONVEX_BUILDER_DRAW27/// Factor to scale convex hull when debug drawing the construction process28static constexpr Real cDrawScale = 10;29#endif3031public:32// Due to the Euler characteristic (https://en.wikipedia.org/wiki/Euler_characteristic) we know that Vertices - Edges + Faces = 233// In our case we only have triangles and they are always fully connected, so each edge is shared exactly between 2 faces: Edges = Faces * 3 / 234// Substituting: Vertices = Faces / 2 + 2 which is approximately Faces / 2.35static constexpr int cMaxTriangles = 256; ///< Max triangles in hull36static constexpr int cMaxPoints = cMaxTriangles / 2; ///< Max number of points in hull3738// Constants39static constexpr int cMaxEdgeLength = 128; ///< Max number of edges in FindEdge40static constexpr float cMinTriangleArea = 1.0e-10f; ///< Minimum area of a triangle before, if smaller than this it will not be added to the priority queue41static constexpr float cBarycentricEpsilon = 1.0e-3f; ///< Epsilon value used to determine if a point is in the interior of a triangle4243// Forward declare44class Triangle;4546/// Class that holds the information of an edge47class Edge48{49public:50/// Information about neighbouring triangle51Triangle * mNeighbourTriangle; ///< Triangle that neighbours this triangle52int mNeighbourEdge; ///< Index in mEdge that specifies edge that this Edge is connected to5354int mStartIdx; ///< Vertex index in mPositions that indicates the start vertex of this edge55};5657using Edges = StaticArray<Edge, cMaxEdgeLength>;58using NewTriangles = StaticArray<Triangle *, cMaxEdgeLength>;5960/// Class that holds the information of one triangle61class Triangle : public NonCopyable62{63public:64/// Constructor65inline Triangle(int inIdx0, int inIdx1, int inIdx2, const Vec3 *inPositions);6667/// Check if triangle is facing inPosition68inline bool IsFacing(Vec3Arg inPosition) const69{70JPH_ASSERT(!mRemoved);71return mNormal.Dot(inPosition - mCentroid) > 0.0f;72}7374/// Check if triangle is facing the origin75inline bool IsFacingOrigin() const76{77JPH_ASSERT(!mRemoved);78return mNormal.Dot(mCentroid) < 0.0f;79}8081/// Get the next edge of edge inIndex82inline const Edge & GetNextEdge(int inIndex) const83{84return mEdge[(inIndex + 1) % 3];85}8687Edge mEdge[3]; ///< 3 edges of this triangle88Vec3 mNormal; ///< Normal of this triangle, length is 2 times area of triangle89Vec3 mCentroid; ///< Center of the triangle90float mClosestLenSq = FLT_MAX; ///< Closest distance^2 from origin to triangle91float mLambda[2]; ///< Barycentric coordinates of closest point to origin on triangle92bool mLambdaRelativeTo0; ///< How to calculate the closest point, true: y0 + l0 * (y1 - y0) + l1 * (y2 - y0), false: y1 + l0 * (y0 - y1) + l1 * (y2 - y1)93bool mClosestPointInterior = false; ///< Flag that indicates that the closest point from this triangle to the origin is an interior point94bool mRemoved = false; ///< Flag that indicates that triangle has been removed95bool mInQueue = false; ///< Flag that indicates that this triangle was placed in the sorted heap (stays true after it is popped because the triangle is freed by the main EPA algorithm loop)96#ifdef JPH_EPA_CONVEX_BUILDER_DRAW97int mIteration; ///< Iteration that this triangle was created98#endif99};100101/// Factory that creates triangles in a fixed size buffer102class TriangleFactory : public NonCopyable103{104private:105/// Struct that stores both a triangle or a next pointer in case the triangle is unused106union alignas(Triangle) Block107{108uint8 mTriangle[sizeof(Triangle)];109Block * mNextFree;110};111112/// Storage for triangle data113Block mTriangles[cMaxTriangles]; ///< Storage for triangles114Block * mNextFree = nullptr; ///< List of free triangles115int mHighWatermark = 0; ///< High water mark for used triangles (if mNextFree == nullptr we can take one from here)116117public:118/// Return all triangles to the free pool119void Clear()120{121mNextFree = nullptr;122mHighWatermark = 0;123}124125/// Allocate a new triangle with 3 indexes126Triangle * CreateTriangle(int inIdx0, int inIdx1, int inIdx2, const Vec3 *inPositions)127{128Triangle *t;129if (mNextFree != nullptr)130{131// Entry available from the free list132t = reinterpret_cast<Triangle *>(&mNextFree->mTriangle);133mNextFree = mNextFree->mNextFree;134}135else136{137// Allocate from never used before triangle store138if (mHighWatermark >= cMaxTriangles)139return nullptr; // Buffer full140t = reinterpret_cast<Triangle *>(&mTriangles[mHighWatermark].mTriangle);141++mHighWatermark;142}143144// Call constructor145new (t) Triangle(inIdx0, inIdx1, inIdx2, inPositions);146147return t;148}149150/// Free a triangle151void FreeTriangle(Triangle *inT)152{153// Destruct triangle154inT->~Triangle();155#ifdef JPH_DEBUG156memset(inT, 0xcd, sizeof(Triangle));157#endif158159// Add triangle to the free list160Block *tu = reinterpret_cast<Block *>(inT);161tu->mNextFree = mNextFree;162mNextFree = tu;163}164};165166// Typedefs167using PointsBase = StaticArray<Vec3, cMaxPoints>;168using Triangles = StaticArray<Triangle *, cMaxTriangles>;169170/// Specialized points list that allows direct access to the size171class Points : public PointsBase172{173public:174size_type & GetSizeRef()175{176return mSize;177}178};179180/// Specialized triangles list that keeps them sorted on closest distance to origin181class TriangleQueue : public Triangles182{183public:184/// Function to sort triangles on closest distance to origin185static bool sTriangleSorter(const Triangle *inT1, const Triangle *inT2)186{187return inT1->mClosestLenSq > inT2->mClosestLenSq;188}189190/// Add triangle to the list191void push_back(Triangle *inT)192{193// Add to base194Triangles::push_back(inT);195196// Mark in queue197inT->mInQueue = true;198199// Resort heap200BinaryHeapPush(begin(), end(), sTriangleSorter);201}202203/// Peek the next closest triangle without removing it204Triangle * PeekClosest()205{206return front();207}208209/// Get next closest triangle210Triangle * PopClosest()211{212// Move closest to end213BinaryHeapPop(begin(), end(), sTriangleSorter);214215// Remove last triangle216Triangle *t = back();217pop_back();218return t;219}220};221222/// Constructor223explicit EPAConvexHullBuilder(const Points &inPositions) :224mPositions(inPositions)225{226#ifdef JPH_EPA_CONVEX_BUILDER_DRAW227mIteration = 0;228mOffset = RVec3::sZero();229#endif230}231232/// Initialize the hull with 3 points233void Initialize(int inIdx1, int inIdx2, int inIdx3)234{235// Release triangles236mFactory.Clear();237238// Create triangles (back to back)239Triangle *t1 = CreateTriangle(inIdx1, inIdx2, inIdx3);240Triangle *t2 = CreateTriangle(inIdx1, inIdx3, inIdx2);241242// Link triangles edges243sLinkTriangle(t1, 0, t2, 2);244sLinkTriangle(t1, 1, t2, 1);245sLinkTriangle(t1, 2, t2, 0);246247// Always add both triangles to the priority queue248mTriangleQueue.push_back(t1);249mTriangleQueue.push_back(t2);250251#ifdef JPH_EPA_CONVEX_BUILDER_DRAW252// Draw current state253DrawState();254255// Increment iteration counter256++mIteration;257#endif258}259260/// Check if there's another triangle to process from the queue261bool HasNextTriangle() const262{263return !mTriangleQueue.empty();264}265266/// Access to the next closest triangle to the origin (won't remove it from the queue).267Triangle * PeekClosestTriangleInQueue()268{269return mTriangleQueue.PeekClosest();270}271272/// Access to the next closest triangle to the origin and remove it from the queue.273Triangle * PopClosestTriangleFromQueue()274{275return mTriangleQueue.PopClosest();276}277278/// Find the triangle on which inPosition is the furthest to the front279/// Note this function works as long as all points added have been added with AddPoint(..., FLT_MAX).280Triangle * FindFacingTriangle(Vec3Arg inPosition, float &outBestDistSq)281{282Triangle *best = nullptr;283float best_dist_sq = 0.0f;284285for (Triangle *t : mTriangleQueue)286if (!t->mRemoved)287{288float dot = t->mNormal.Dot(inPosition - t->mCentroid);289if (dot > 0.0f)290{291float dist_sq = dot * dot / t->mNormal.LengthSq();292if (dist_sq > best_dist_sq)293{294best = t;295best_dist_sq = dist_sq;296}297}298}299300outBestDistSq = best_dist_sq;301return best;302}303304/// Add a new point to the convex hull305bool AddPoint(Triangle *inFacingTriangle, int inIdx, float inClosestDistSq, NewTriangles &outTriangles)306{307// Get position308Vec3 pos = mPositions[inIdx];309310#ifdef JPH_EPA_CONVEX_BUILDER_DRAW311// Draw new support point312DrawMarker(pos, Color::sYellow, 1.0f);313#endif314315#ifdef JPH_EPA_CONVEX_BUILDER_VALIDATE316// Check if structure is intact317ValidateTriangles();318#endif319320// Find edge of convex hull of triangles that are not facing the new vertex w321Edges edges;322if (!FindEdge(inFacingTriangle, pos, edges))323return false;324325// Create new triangles326int num_edges = edges.size();327for (int i = 0; i < num_edges; ++i)328{329// Create new triangle330Triangle *nt = CreateTriangle(edges[i].mStartIdx, edges[(i + 1) % num_edges].mStartIdx, inIdx);331if (nt == nullptr)332return false;333outTriangles.push_back(nt);334335// Check if we need to put this triangle in the priority queue336if ((nt->mClosestPointInterior && nt->mClosestLenSq < inClosestDistSq) // For the main algorithm337|| nt->mClosestLenSq < 0.0f) // For when the origin is not inside the hull yet338mTriangleQueue.push_back(nt);339}340341// Link edges342for (int i = 0; i < num_edges; ++i)343{344sLinkTriangle(outTriangles[i], 0, edges[i].mNeighbourTriangle, edges[i].mNeighbourEdge);345sLinkTriangle(outTriangles[i], 1, outTriangles[(i + 1) % num_edges], 2);346}347348#ifdef JPH_EPA_CONVEX_BUILDER_VALIDATE349// Check if structure is intact350ValidateTriangles();351#endif352353#ifdef JPH_EPA_CONVEX_BUILDER_DRAW354// Draw state of the hull355DrawState();356357// Increment iteration counter358++mIteration;359#endif360361return true;362}363364/// Free a triangle365void FreeTriangle(Triangle *inT)366{367#ifdef JPH_ENABLE_ASSERTS368// Make sure that this triangle is not connected369JPH_ASSERT(inT->mRemoved);370for (const Edge &e : inT->mEdge)371JPH_ASSERT(e.mNeighbourTriangle == nullptr);372#endif373374#if defined(JPH_EPA_CONVEX_BUILDER_VALIDATE) || defined(JPH_EPA_CONVEX_BUILDER_DRAW)375// Remove from list of all triangles376Triangles::iterator i = std::find(mTriangles.begin(), mTriangles.end(), inT);377JPH_ASSERT(i != mTriangles.end());378mTriangles.erase(i);379#endif380381mFactory.FreeTriangle(inT);382}383384private:385/// Create a new triangle386Triangle * CreateTriangle(int inIdx1, int inIdx2, int inIdx3)387{388// Call provider to create triangle389Triangle *t = mFactory.CreateTriangle(inIdx1, inIdx2, inIdx3, mPositions.data());390if (t == nullptr)391return nullptr;392393#ifdef JPH_EPA_CONVEX_BUILDER_DRAW394// Remember iteration counter395t->mIteration = mIteration;396#endif397398#if defined(JPH_EPA_CONVEX_BUILDER_VALIDATE) || defined(JPH_EPA_CONVEX_BUILDER_DRAW)399// Add to list of triangles for debugging purposes400mTriangles.push_back(t);401#endif402403return t;404}405406/// Link triangle edge to other triangle edge407static void sLinkTriangle(Triangle *inT1, int inEdge1, Triangle *inT2, int inEdge2)408{409JPH_ASSERT(inEdge1 >= 0 && inEdge1 < 3);410JPH_ASSERT(inEdge2 >= 0 && inEdge2 < 3);411Edge &e1 = inT1->mEdge[inEdge1];412Edge &e2 = inT2->mEdge[inEdge2];413414// Check not connected yet415JPH_ASSERT(e1.mNeighbourTriangle == nullptr);416JPH_ASSERT(e2.mNeighbourTriangle == nullptr);417418// Check vertices match419JPH_ASSERT(e1.mStartIdx == inT2->GetNextEdge(inEdge2).mStartIdx);420JPH_ASSERT(e2.mStartIdx == inT1->GetNextEdge(inEdge1).mStartIdx);421422// Link up423e1.mNeighbourTriangle = inT2;424e1.mNeighbourEdge = inEdge2;425e2.mNeighbourTriangle = inT1;426e2.mNeighbourEdge = inEdge1;427}428429/// Unlink this triangle430void UnlinkTriangle(Triangle *inT)431{432// Unlink from neighbours433for (int i = 0; i < 3; ++i)434{435Edge &edge = inT->mEdge[i];436if (edge.mNeighbourTriangle != nullptr)437{438Edge &neighbour_edge = edge.mNeighbourTriangle->mEdge[edge.mNeighbourEdge];439440// Validate that neighbour points to us441JPH_ASSERT(neighbour_edge.mNeighbourTriangle == inT);442JPH_ASSERT(neighbour_edge.mNeighbourEdge == i);443444// Unlink445neighbour_edge.mNeighbourTriangle = nullptr;446edge.mNeighbourTriangle = nullptr;447}448}449450// If this triangle is not in the priority queue, we can delete it now451if (!inT->mInQueue)452FreeTriangle(inT);453}454455/// Given one triangle that faces inVertex, find the edges of the triangles that are not facing inVertex.456/// Will flag all those triangles for removal.457bool FindEdge(Triangle *inFacingTriangle, Vec3Arg inVertex, Edges &outEdges)458{459// Assert that we were given an empty array460JPH_ASSERT(outEdges.empty());461462// Should start with a facing triangle463JPH_ASSERT(inFacingTriangle->IsFacing(inVertex));464465// Flag as removed466inFacingTriangle->mRemoved = true;467468// Instead of recursing, we build our own stack with the information we need469struct StackEntry470{471Triangle * mTriangle;472int mEdge;473int mIter;474};475StackEntry stack[cMaxEdgeLength];476int cur_stack_pos = 0;477478// Start with the triangle / edge provided479stack[0].mTriangle = inFacingTriangle;480stack[0].mEdge = 0;481stack[0].mIter = -1; // Start with edge 0 (is incremented below before use)482483// Next index that we expect to find, if we don't then there are 'islands'484int next_expected_start_idx = -1;485486for (;;)487{488StackEntry &cur_entry = stack[cur_stack_pos];489490// Next iteration491if (++cur_entry.mIter >= 3)492{493// This triangle needs to be removed, unlink it now494UnlinkTriangle(cur_entry.mTriangle);495496// Pop from stack497if (--cur_stack_pos < 0)498break;499}500else501{502// Visit neighbour503Edge &e = cur_entry.mTriangle->mEdge[(cur_entry.mEdge + cur_entry.mIter) % 3];504Triangle *n = e.mNeighbourTriangle;505if (n != nullptr && !n->mRemoved)506{507// Check if vertex is on the front side of this triangle508if (n->IsFacing(inVertex))509{510// Vertex on front, this triangle needs to be removed511n->mRemoved = true;512513// Add element to the stack of elements to visit514cur_stack_pos++;515JPH_ASSERT(cur_stack_pos < cMaxEdgeLength);516StackEntry &new_entry = stack[cur_stack_pos];517new_entry.mTriangle = n;518new_entry.mEdge = e.mNeighbourEdge;519new_entry.mIter = 0; // Is incremented before use, we don't need to test this edge again since we came from it520}521else522{523// Detect if edge doesn't connect to previous edge, if this happens we have found and 'island' which means524// the newly added point is so close to the triangles of the hull that we classified some (nearly) coplanar525// triangles as before and some behind the point. At this point we just abort adding the point because526// we've reached numerical precision.527// Note that we do not need to test if the first and last edge connect, since when there are islands528// there should be at least 2 disconnects.529if (e.mStartIdx != next_expected_start_idx && next_expected_start_idx != -1)530return false;531532// Next expected index is the start index of our neighbour's edge533next_expected_start_idx = n->mEdge[e.mNeighbourEdge].mStartIdx;534535// Vertex behind, keep edge536outEdges.push_back(e);537}538}539}540}541542// Assert that we have a fully connected loop543JPH_ASSERT(outEdges.empty() || outEdges[0].mStartIdx == next_expected_start_idx);544545#ifdef JPH_EPA_CONVEX_BUILDER_DRAW546// Draw edge of facing triangles547for (int i = 0; i < (int)outEdges.size(); ++i)548{549RVec3 edge_start = cDrawScale * (mOffset + mPositions[outEdges[i].mStartIdx]);550DebugRenderer::sInstance->DrawArrow(edge_start, cDrawScale * (mOffset + mPositions[outEdges[(i + 1) % outEdges.size()].mStartIdx]), Color::sYellow, 0.01f);551DebugRenderer::sInstance->DrawText3D(edge_start, ConvertToString(outEdges[i].mStartIdx), Color::sWhite);552}553554// Draw the state with the facing triangles removed555DrawState();556#endif557558// When we start with two triangles facing away from each other and adding a point that is on the plane,559// sometimes we consider the point in front of both causing both triangles to be removed resulting in an empty edge list.560// In this case we fail to add the point which will result in no collision reported (the shapes are contacting in 1 point so there's 0 penetration)561return outEdges.size() >= 3;562}563564#ifdef JPH_EPA_CONVEX_BUILDER_VALIDATE565/// Check consistency of 1 triangle566void ValidateTriangle(const Triangle *inT) const567{568if (inT->mRemoved)569{570// Validate that removed triangles are not connected to anything571for (const Edge &my_edge : inT->mEdge)572JPH_ASSERT(my_edge.mNeighbourTriangle == nullptr);573}574else575{576for (int i = 0; i < 3; ++i)577{578const Edge &my_edge = inT->mEdge[i];579580// Assert that we have a neighbour581const Triangle *nb = my_edge.mNeighbourTriangle;582JPH_ASSERT(nb != nullptr);583584if (nb != nullptr)585{586// Assert that our neighbours edge points to us587const Edge &nb_edge = nb->mEdge[my_edge.mNeighbourEdge];588JPH_ASSERT(nb_edge.mNeighbourTriangle == inT);589JPH_ASSERT(nb_edge.mNeighbourEdge == i);590591// Assert that the next edge of the neighbour points to the same vertex as this edge's vertex592const Edge &nb_next_edge = nb->GetNextEdge(my_edge.mNeighbourEdge);593JPH_ASSERT(nb_next_edge.mStartIdx == my_edge.mStartIdx);594595// Assert that my next edge points to the same vertex as my neighbours vertex596const Edge &my_next_edge = inT->GetNextEdge(i);597JPH_ASSERT(my_next_edge.mStartIdx == nb_edge.mStartIdx);598}599}600}601}602603/// Check consistency of all triangles604void ValidateTriangles() const605{606for (const Triangle *t : mTriangles)607ValidateTriangle(t);608}609#endif610611#ifdef JPH_EPA_CONVEX_BUILDER_DRAW612public:613/// Draw state of algorithm614void DrawState()615{616// Draw origin617DebugRenderer::sInstance->DrawCoordinateSystem(RMat44::sTranslation(cDrawScale * mOffset), 1.0f);618619// Draw triangles620for (const Triangle *t : mTriangles)621if (!t->mRemoved)622{623// Calculate the triangle vertices624RVec3 p1 = cDrawScale * (mOffset + mPositions[t->mEdge[0].mStartIdx]);625RVec3 p2 = cDrawScale * (mOffset + mPositions[t->mEdge[1].mStartIdx]);626RVec3 p3 = cDrawScale * (mOffset + mPositions[t->mEdge[2].mStartIdx]);627628// Draw triangle629DebugRenderer::sInstance->DrawTriangle(p1, p2, p3, Color::sGetDistinctColor(t->mIteration));630DebugRenderer::sInstance->DrawWireTriangle(p1, p2, p3, Color::sGrey);631632// Draw normal633RVec3 centroid = cDrawScale * (mOffset + t->mCentroid);634float len = t->mNormal.Length();635if (len > 0.0f)636DebugRenderer::sInstance->DrawArrow(centroid, centroid + t->mNormal / len, Color::sDarkGreen, 0.01f);637}638639// Determine max position640float min_x = FLT_MAX;641float max_x = -FLT_MAX;642for (Vec3 p : mPositions)643{644min_x = min(min_x, p.GetX());645max_x = max(max_x, p.GetX());646}647648// Offset to the right649mOffset += Vec3(max_x - min_x + 0.5f, 0.0f, 0.0f);650}651652/// Draw a label to indicate the next stage in the algorithm653void DrawLabel(const string_view &inText)654{655DebugRenderer::sInstance->DrawText3D(cDrawScale * mOffset, inText, Color::sWhite, 0.1f * cDrawScale);656657mOffset += Vec3(5.0f, 0.0f, 0.0f);658}659660/// Draw geometry for debugging purposes661void DrawGeometry(const DebugRenderer::GeometryRef &inGeometry, ColorArg inColor)662{663RMat44 origin = RMat44::sScale(Vec3::sReplicate(cDrawScale)) * RMat44::sTranslation(mOffset);664DebugRenderer::sInstance->DrawGeometry(origin, inGeometry->mBounds.Transformed(origin), inGeometry->mBounds.GetExtent().LengthSq(), inColor, inGeometry);665666mOffset += Vec3(inGeometry->mBounds.GetSize().GetX(), 0, 0);667}668669/// Draw a triangle for debugging purposes670void DrawWireTriangle(const Triangle &inTriangle, ColorArg inColor)671{672RVec3 prev = cDrawScale * (mOffset + mPositions[inTriangle.mEdge[2].mStartIdx]);673for (const Edge &edge : inTriangle.mEdge)674{675RVec3 cur = cDrawScale * (mOffset + mPositions[edge.mStartIdx]);676DebugRenderer::sInstance->DrawArrow(prev, cur, inColor, 0.01f);677prev = cur;678}679}680681/// Draw a marker for debugging purposes682void DrawMarker(Vec3Arg inPosition, ColorArg inColor, float inSize)683{684DebugRenderer::sInstance->DrawMarker(cDrawScale * (mOffset + inPosition), inColor, inSize);685}686687/// Draw an arrow for debugging purposes688void DrawArrow(Vec3Arg inFrom, Vec3Arg inTo, ColorArg inColor, float inArrowSize)689{690DebugRenderer::sInstance->DrawArrow(cDrawScale * (mOffset + inFrom), cDrawScale * (mOffset + inTo), inColor, inArrowSize);691}692#endif693694private:695TriangleFactory mFactory; ///< Factory to create new triangles and remove old ones696const Points & mPositions; ///< List of positions (some of them are part of the hull)697TriangleQueue mTriangleQueue; ///< List of triangles that are part of the hull that still need to be checked (if !mRemoved)698699#if defined(JPH_EPA_CONVEX_BUILDER_VALIDATE) || defined(JPH_EPA_CONVEX_BUILDER_DRAW)700Triangles mTriangles; ///< The list of all triangles in this hull (for debug purposes)701#endif702703#ifdef JPH_EPA_CONVEX_BUILDER_DRAW704int mIteration; ///< Number of iterations we've had so far (for debug purposes)705RVec3 mOffset; ///< Offset to use for state drawing706#endif707};708709// The determinant that is calculated in the Triangle constructor is really sensitive710// to numerical round off, disable the fmadd instructions to maintain precision.711JPH_PRECISE_MATH_ON712713EPAConvexHullBuilder::Triangle::Triangle(int inIdx0, int inIdx1, int inIdx2, const Vec3 *inPositions)714{715// Fill in indexes716JPH_ASSERT(inIdx0 != inIdx1 && inIdx0 != inIdx2 && inIdx1 != inIdx2);717mEdge[0].mStartIdx = inIdx0;718mEdge[1].mStartIdx = inIdx1;719mEdge[2].mStartIdx = inIdx2;720721// Clear links722mEdge[0].mNeighbourTriangle = nullptr;723mEdge[1].mNeighbourTriangle = nullptr;724mEdge[2].mNeighbourTriangle = nullptr;725726// Get vertex positions727Vec3 y0 = inPositions[inIdx0];728Vec3 y1 = inPositions[inIdx1];729Vec3 y2 = inPositions[inIdx2];730731// Calculate centroid732mCentroid = (y0 + y1 + y2) / 3.0f;733734// Calculate edges735Vec3 y10 = y1 - y0;736Vec3 y20 = y2 - y0;737Vec3 y21 = y2 - y1;738739// The most accurate normal is calculated by using the two shortest edges740// See: https://box2d.org/posts/2014/01/troublesome-triangle/741// The difference in normals is most pronounced when one edge is much smaller than the others (in which case the other 2 must have roughly the same length).742// Therefore we can suffice by just picking the shortest from 2 edges and use that with the 3rd edge to calculate the normal.743// We first check which of the edges is shorter.744float y20_dot_y20 = y20.Dot(y20);745float y21_dot_y21 = y21.Dot(y21);746if (y20_dot_y20 < y21_dot_y21)747{748// We select the edges y10 and y20749mNormal = y10.Cross(y20);750751// Check if triangle is degenerate752float normal_len_sq = mNormal.LengthSq();753if (normal_len_sq > cMinTriangleArea)754{755// Determine distance between triangle and origin: distance = (centroid - origin) . normal / |normal|756// Note that this way of calculating the closest point is much more accurate than first calculating barycentric coordinates and then calculating the closest757// point based on those coordinates. Note that we preserve the sign of the distance to check on which side the origin is.758float c_dot_n = mCentroid.Dot(mNormal);759mClosestLenSq = abs(c_dot_n) * c_dot_n / normal_len_sq;760761// Calculate closest point to origin using barycentric coordinates:762//763// v = y0 + l0 * (y1 - y0) + l1 * (y2 - y0)764// v . (y1 - y0) = 0765// v . (y2 - y0) = 0766//767// Written in matrix form:768//769// | y10.y10 y20.y10 | | l0 | = | -y0.y10 |770// | y10.y20 y20.y20 | | l1 | | -y0.y20 |771//772// (y10 = y1 - y0 etc.)773//774// Cramers rule to invert matrix:775float y10_dot_y10 = y10.LengthSq();776float y10_dot_y20 = y10.Dot(y20);777float determinant = y10_dot_y10 * y20_dot_y20 - y10_dot_y20 * y10_dot_y20;778if (determinant > 0.0f) // If determinant == 0 then the system is linearly dependent and the triangle is degenerate, since y10.10 * y20.y20 > y10.y20^2 it should also be > 0779{780float y0_dot_y10 = y0.Dot(y10);781float y0_dot_y20 = y0.Dot(y20);782float l0 = (y10_dot_y20 * y0_dot_y20 - y20_dot_y20 * y0_dot_y10) / determinant;783float l1 = (y10_dot_y20 * y0_dot_y10 - y10_dot_y10 * y0_dot_y20) / determinant;784mLambda[0] = l0;785mLambda[1] = l1;786mLambdaRelativeTo0 = true;787788// Check if closest point is interior to the triangle. For a convex hull which contains the origin each face must contain the origin, but because789// our faces are triangles, we can have multiple coplanar triangles and only 1 will have the origin as an interior point. We want to use this triangle790// to calculate the contact points because it gives the most accurate results, so we will only add these triangles to the priority queue.791if (l0 > -cBarycentricEpsilon && l1 > -cBarycentricEpsilon && l0 + l1 < 1.0f + cBarycentricEpsilon)792mClosestPointInterior = true;793}794}795}796else797{798// We select the edges y10 and y21799mNormal = y10.Cross(y21);800801// Check if triangle is degenerate802float normal_len_sq = mNormal.LengthSq();803if (normal_len_sq > cMinTriangleArea)804{805// Again calculate distance between triangle and origin806float c_dot_n = mCentroid.Dot(mNormal);807mClosestLenSq = abs(c_dot_n) * c_dot_n / normal_len_sq;808809// Calculate closest point to origin using barycentric coordinates but this time using y1 as the reference vertex810//811// v = y1 + l0 * (y0 - y1) + l1 * (y2 - y1)812// v . (y0 - y1) = 0813// v . (y2 - y1) = 0814//815// Written in matrix form:816//817// | y10.y10 -y21.y10 | | l0 | = | y1.y10 |818// | -y10.y21 y21.y21 | | l1 | | -y1.y21 |819//820// Cramers rule to invert matrix:821float y10_dot_y10 = y10.LengthSq();822float y10_dot_y21 = y10.Dot(y21);823float determinant = y10_dot_y10 * y21_dot_y21 - y10_dot_y21 * y10_dot_y21;824if (determinant > 0.0f)825{826float y1_dot_y10 = y1.Dot(y10);827float y1_dot_y21 = y1.Dot(y21);828float l0 = (y21_dot_y21 * y1_dot_y10 - y10_dot_y21 * y1_dot_y21) / determinant;829float l1 = (y10_dot_y21 * y1_dot_y10 - y10_dot_y10 * y1_dot_y21) / determinant;830mLambda[0] = l0;831mLambda[1] = l1;832mLambdaRelativeTo0 = false;833834// Again check if the closest point is inside the triangle835if (l0 > -cBarycentricEpsilon && l1 > -cBarycentricEpsilon && l0 + l1 < 1.0f + cBarycentricEpsilon)836mClosestPointInterior = true;837}838}839}840}841842JPH_PRECISE_MATH_OFF843844JPH_NAMESPACE_END845846847