JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
ConvexHullBuilder.h
Go to the documentation of this file.
1//
2// ██╗██████╗ ██╗ ██╗██████╗ ███████╗
3// ██║██╔══██╗ ██║ ██║██╔══██╗██╔════╝ ** JPLSpatial **
4// ██║██████╔╝ ██║ ██║██████╔╝███████╗
5// ██ ██║██╔═══╝ ██║ ██║██╔══██╗╚════██║ https://github.com/Jaytheway/JPLSpatial
6// ╚█████╔╝██║ ███████╗██║██████╔╝███████║
7// ╚════╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚══════╝
8//
9// Copyright 2024 Jaroslav Pevno, JPLSpatial is offered under the terms of the ISC license:
10//
11// Permission to use, copy, modify, and/or distribute this software for any purpose with or
12// without fee is hereby granted, provided that the above copyright notice and this permission
13// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
14// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
16// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
17// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
18// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#pragma once
21
23
29
30#include <cstdlib>
31#include <vector>
32#include <memory>
33#include <limits>
34#include <span>
35#include <unordered_set>
36
37//#ifndef JPL_ENABLE_VALIDATION
38//#define JPL_ENABLE_VALIDATION 0
39//#endif
40
41#if defined(JPL_ENABLE_VALIDATION)// && JPL_ENABLE_VALIDATION
42#include <format>
43#include <iostream>
44#endif // JPL_ENABLE_VALIDATION
45
46namespace JPL
47{
48
49 //==========================================================================
55 template<CVec3 Vec3Type>
57 {
58 using Vec3 = Vec3Type;
59
60 template<class T>
61 using Array = std::pmr::vector<T>;
62
63 ConvexHullBuilder(const ConvexHullBuilder&) = delete;
64 ConvexHullBuilder& operator=(const ConvexHullBuilder&) = delete;
65 public:
66 // Forward declare
67 class Face;
68
70 class Edge
71 {
72 Edge(const Edge&) = delete;
73 Edge& operator =(const Edge&) = delete;
74 public:
75
76 Edge(Face* inFace, int inStartIdx)
77 : mFace(inFace), mStartIdx(inStartIdx)
78 {
79 }
80
83
85 Edge* mNextEdge = nullptr;
86 Edge* mNeighbourEdge = nullptr;
88 };
89
90 public:
91 using ConflictList = Array<int>;
92
94 class Face final
95 {
96 Face(const Face&) = delete;
97 Face& operator=(const Face&) = delete;
98 public:
99 Face() = default;
100 ~Face();
101
103 void Initialize(int inIdx0, int inIdx1, int inIdx2, std::span<const Vec3> inPositions);
104
106 void CalculateNormalAndCentroid(std::span<const Vec3> inPositions);
107
108 template<class Vec3i>
109 bool Triangulate(std::span<const Vec3> inPositions, Array<Vec3i>& outTris) const;
110
112 bool IsFacing(const Vec3& inPosition) const;
113
114 Vec3 mNormal;
117 Edge* mFirstEdge = nullptr;
119 bool mRemoved = false;
120#ifdef JPL_CONVEX_BUILDER_DEBUG
121 int mIteration;
122#endif
123
124 private:
125 Edge* AllocateEdge(int inStartIdx);
126 void FreeEdge(Edge* edge);
127 };
128
129 // Typedefs
130 using Positions = std::span<const Vec3>;
131 using Faces = Array<Face*>;
132
133 ConvexHullBuilder(Positions inPositions);
135
137 enum class EResult
138 {
139 Success,
143 Degenerate,
144 };
145
152 EResult Initialize(int inMaxVertices, float inTolerance, const char*& outError);
153
155 int GetNumVerticesUsed() const;
156
158 bool ContainsFace(const Array<int>& inIndices) const;
159
160#if 0
166 void DetermineMaxError(Face*& outFaceWithMaxError, float& outMaxError, int& outMaxErrorPositionIdx, float& outCoplanarDistance) const;
167#endif
168
170 const Faces& GetFaces() const { return mFaces; }
171
174 template<class Vec3i>
175 void GetTriangles(Array<Vec3i>& outTris);
176
177 private:
179 static constexpr float cMinTriangleAreaSq = 1.0e-12f;
180
181#ifdef JPL_CONVEX_BUILDER_DEBUG
183 static constexpr Real cDrawScale = 10;
184#endif
185
187 class FullEdge
188 {
189 public:
190 Edge* mNeighbourEdge;
191 int mStartIdx;
192 int mEndIdx;
193 };
194
195 // Private typedefs
196 using FullEdges = Array<FullEdge>;
197
199 float DetermineCoplanarDistance() const;
200
206 void GetFaceForPoint(const Vec3& inPoint, const Faces& inFaces, Face*& outFace, float& outDistSq) const;
207
212 float GetDistanceToEdgeSq(const Vec3& inPoint, const Face* inFace) const;
213
219 bool AssignPointToFace(int inPositionIdx, const Faces& inFaces, float inToleranceSq);
220
222 void AddPoint(Face* inFacingFace, int inIdx, float inCoplanarToleranceSq, Faces& outNewFaces);
223
225 void GarbageCollectFaces();
226
228 Face* CreateFace();
229
231 Face* CreateTriangle(int inIdx1, int inIdx2, int inIdx3);
232
234 void FreeFace(Face* inFace);
235
237 void FreeFaces();
239 static void sLinkFace(Edge* inEdge1, Edge* inEdge2);
240
242 static void sUnlinkFace(Face* inFace);
243
246 void FindEdge(Face* inFacingFace, const Vec3& inVertex, FullEdges& outEdges) const;
247
249 void MergeFaces(Edge* inEdge);
250
252 void MergeDegenerateFace(Face* inFace, Faces& ioAffectedFaces);
253
256 void MergeCoplanarOrConcaveFaces(Face* inFace, float inCoplanarToleranceSq, Faces& ioAffectedFaces);
257
259 static void sMarkAffected(Face* inFace, Faces& ioAffectedFaces);
260
265 void RemoveInvalidEdges(Face* inFace, Faces& ioAffectedFaces);
266
270 bool RemoveTwoEdgeFace(Face* inFace, Faces& ioAffectedFaces) const;
271
272#ifdef JPL_ENABLE_VALIDATION
274 void DumpFace(const Face* inFace) const;
275
277 void ValidateFace(const Face* inFace) const;
278
280 void ValidateFaces() const;
281#endif
282
283#ifdef JPL_CONVEX_BUILDER_DEBUG
285 void DrawState(bool inDrawConflictList = false) const;
286
288 void DrawWireFace(const Face* inFace, ColorArg inColor) const;
289
291 void DrawEdge(const Edge* inEdge, ColorArg inColor) const;
292#endif
293
294#ifdef JPL_CONVEX_BUILDER_DUMP_SHAPE
295 void DumpShape() const;
296#endif
297
298 const Positions mPositions;
299 Faces mFaces;
300
301 struct Coplanar
302 {
303 int mPositionIdx;
304 float mDistanceSq;
305 };
306 using CoplanarList = Array<Coplanar>;
307
308 CoplanarList mCoplanarList;
309
310#ifdef JPL_CONVEX_BUILDER_DEBUG
311 int mIteration;
312 mutable RVec3 mOffset;
313 Vec3 mDelta;
314#endif
315 };
316} // namespace JPL
317
318//==============================================================================
319//
320// Code beyond this point is implementation detail...
321//
322//==============================================================================
323
324namespace JPL
325{
326 template<CVec3 Vec3Type>
328 {
329 Edge* prev_edge = this;
330 while (prev_edge->mNextEdge != this)
331 prev_edge = prev_edge->mNextEdge;
332 return prev_edge;
333 }
334
335 template<CVec3 Vec3Type>
337 {
338 // Free all edges
339 if (Edge* e = mFirstEdge)
340 {
341 do
342 {
343 Edge* next = e->mNextEdge;
344 FreeEdge(e);
345 e = next;
346 } while (e != mFirstEdge);
347 }
348 }
349
350 //==========================================================================
351 template<CVec3 Vec3Type>
352 inline void ConvexHullBuilder<Vec3Type>::Face::Initialize(int inIdx0, int inIdx1, int inIdx2, std::span<const Vec3> inPositions)
353 {
354 JPL_ASSERT(mFirstEdge == nullptr);
355 JPL_ASSERT(inIdx0 != inIdx1 && inIdx0 != inIdx2 && inIdx1 != inIdx2);
356
357 // Create 3 edges
358 auto* e0 = AllocateEdge(inIdx0);
359 auto* e1 = AllocateEdge(inIdx1);
360 auto* e2 = AllocateEdge(inIdx2);
361
362 // Link edges
363 e0->mNextEdge = e1;
364 e1->mNextEdge = e2;
365 e2->mNextEdge = e0;
366 mFirstEdge = e0;
367
368 CalculateNormalAndCentroid(inPositions);
369 }
370
371 template<CVec3 Vec3Type>
372 inline auto ConvexHullBuilder<Vec3Type>::Face::AllocateEdge(int inStartIdx) -> Edge*
373 {
374 return DefaultNew<Edge>(this, inStartIdx);
375 }
376
377 template<CVec3 Vec3Type>
378 inline void ConvexHullBuilder<Vec3Type>::Face::FreeEdge(Edge* edge)
379 {
380 DefaultDelete(edge);
381 }
382
383 template<CVec3 Vec3Type>
384 inline void ConvexHullBuilder<Vec3Type>::Face::CalculateNormalAndCentroid(std::span<const Vec3> inPositions)
385 {
386 // Get point that we use to construct a triangle fan
387 Edge* e = mFirstEdge;
388 Vec3 y0 = inPositions[e->mStartIdx];
389
390 // Get the 2nd point
391 e = e->mNextEdge;
392 Vec3 y1 = inPositions[e->mStartIdx];
393
394 // Start accumulating the centroid
395 mCentroid = y0 + y1;
396 int n = 2;
397
398 // Start accumulating the normal
399 mNormal = Vec3{ 0.0f, 0.0f, 0.0f };
400
401 // Loop over remaining edges accumulating normals in a triangle fan fashion
402 for (e = e->mNextEdge; e != mFirstEdge; e = e->mNextEdge)
403 {
404 // Get the 3rd point
405 Vec3 y2 = inPositions[e->mStartIdx];
406
407 // Calculate edges (counter clockwise)
408 Vec3 e0 = y1 - y0;
409 Vec3 e1 = y2 - y1;
410 Vec3 e2 = y0 - y2;
411
412 // The best normal is calculated by using the two shortest edges
413 // See: https://box2d.org/posts/2014/01/troublesome-triangle/
414 // 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).
415 // Therefore we can suffice by just picking the shortest from 2 edges and use that with the 3rd edge to calculate the normal.
416 mNormal += LengthSquared(e1) < LengthSquared(e2) ? CrossProduct(e0, e1) : CrossProduct(e2, e0);
417
418 // Accumulate centroid
419 mCentroid += y2;
420 n++;
421
422 // Update y1 for next triangle
423 y1 = y2;
424 }
425
426 // Finalize centroid
427 mCentroid /= float(n);
428 }
429
430 template<CVec3 Vec3Type>
431 template<class Vec3i>
432 inline bool ConvexHullBuilder<Vec3Type>::Face::Triangulate(std::span<const Vec3> inPositions, Array<Vec3i>& outTris) const
433 {
434 using IndexType = std::remove_cvref_t<decltype(std::declval<Vec3i>()[0])>;
435
436 Array<int> poly;
437 poly.reserve(32);
438
439 // Collect boundary indices
440 poly.push_back(mFirstEdge->mStartIdx);
441 for (Edge* e = mFirstEdge->mNextEdge; e != mFirstEdge; e = e->mNextEdge)
442 poly.push_back(e->mStartIdx);
443
444 if (poly.size() < 3)
445 return false; // degenerate
446
447 if (poly.size() == 3)
448 {
449 // Single triangle face
450 outTris.push_back(Vec3i{
451 static_cast<IndexType>(poly[0]),
452 static_cast<IndexType>(poly[1]),
453 static_cast<IndexType>(poly[2])
454 });
455 }
456 else
457 {
458 // Polygon face, needs triangulation
459 Triangulation::TriangulatePoly(inPositions, poly, outTris);
460 }
461
462 return true;
463 }
464
465 template<CVec3 Vec3Type>
466 inline bool ConvexHullBuilder<Vec3Type>::Face::IsFacing(const Vec3& inPosition) const
467 {
468 JPL_ASSERT(!mRemoved);
469 return DotProduct(mNormal, (inPosition - mCentroid)) > 0.0f;
470 }
471
472 //==========================================================================
473 template<CVec3 Vec3Type>
475 : mPositions(inPositions)
476 , mFaces(GetDefaultMemoryResource())
477 , mCoplanarList(GetDefaultMemoryResource())
478 {
479#ifdef JPL_CONVEX_BUILDER_DEBUG
480 mIteration = 0;
481
482 // Center the drawing of the first hull around the origin and calculate the delta offset between states
483 mOffset = RVec3::sZero();
484 if (mPositions.empty())
485 {
486 // No hull will be generated
487 mDelta = Vec3::sZero();
488 }
489 else
490 {
491 Vec3 maxv = Vec3::sReplicate(-std::numeric_limits<float>::max()), minv = Vec3::sReplicate(std::numeric_limits<float>::max());
492 for (Vec3 v : mPositions)
493 {
494 minv = Vec3::sMin(minv, v);
495 maxv = Vec3::sMax(maxv, v);
496 mOffset -= v;
497 }
498 mOffset /= Real(mPositions.size());
499 mDelta = Vec3((maxv - minv).GetX() + 0.5f, 0, 0);
500 mOffset += mDelta; // Don't start at origin, we're already drawing the final hull there
501 }
502#endif
503 }
504
505 template<CVec3 Vec3Type>
507 {
508 FreeFaces();
509 }
510
511 template<CVec3 Vec3Type>
512 inline ConvexHullBuilder<Vec3Type>::EResult ConvexHullBuilder<Vec3Type>::Initialize(int inMaxVertices, float inTolerance, const char*& outError)
513 {
514 // Free the faces possibly left over from an earlier hull
515 FreeFaces();
516
517 // Test that we have at least 3 points
518 if (mPositions.size() < 3)
519 {
520 outError = "Need at least 3 points to make a hull";
521 return EResult::TooFewPoints;
522 }
523
524 // Determine a suitable tolerance for detecting that points are coplanar
525 float coplanar_tolerance_sq = DetermineCoplanarDistance();
526 coplanar_tolerance_sq *= coplanar_tolerance_sq;
527
528 // Increase desired tolerance if accuracy doesn't allow it
529 const auto sqrTolerance = inTolerance * inTolerance;
530 float tolerance_sq = std::max(coplanar_tolerance_sq, sqrTolerance);
531
532 // Find point furthest from the origin
533 int idx1 = -1;
534 float max_dist_sq = -1.0f;
535 for (int i = 0; i < (int)mPositions.size(); ++i)
536 {
537 float dist_sq = LengthSquared(mPositions[i]);
538 if (dist_sq > max_dist_sq)
539 {
540 max_dist_sq = dist_sq;
541 idx1 = i;
542 }
543 }
544 JPL_ASSERT(idx1 >= 0);
545
546 // Find point that is furthest away from this point
547 int idx2 = -1;
548 max_dist_sq = -1.0f;
549 for (int i = 0; i < (int)mPositions.size(); ++i)
550 {
551 if (i != idx1)
552 {
553 float dist_sq = LengthSquared(mPositions[i] - mPositions[idx1]);
554 if (dist_sq > max_dist_sq)
555 {
556 max_dist_sq = dist_sq;
557 idx2 = i;
558 }
559 }
560 }
561 JPL_ASSERT(idx2 >= 0);
562
563 // Find point that forms the biggest triangle
564 // TODO: we may not need this, since we actually want small triangles, just not narrow
565 int idx3 = -1;
566 float best_triangle_area_sq = -1.0f;
567 for (int i = 0; i < (int)mPositions.size(); ++i)
568 {
569 if (i != idx1 && i != idx2)
570 {
571 float triangle_area_sq = LengthSquared(CrossProduct(mPositions[idx1] - mPositions[i], mPositions[idx2] - mPositions[i]));
572 if (triangle_area_sq > best_triangle_area_sq)
573 {
574 best_triangle_area_sq = triangle_area_sq;
575 idx3 = i;
576 }
577 }
578 }
579
580 JPL_ASSERT(idx3 >= 0);
581 if (best_triangle_area_sq < cMinTriangleAreaSq)
582 {
583 outError = "Could not find a suitable initial triangle because its area was too small";
584 return EResult::Degenerate;
585 }
586
587 // Check if we have only 3 vertices
588 if (mPositions.size() == 3)
589 {
590 // Create two triangles (back to back)
591 // TODO: we may only want a single triangle, instead of two faces of same triangle
592 Face* t1 = CreateTriangle(idx1, idx2, idx3);
593 Face* t2 = CreateTriangle(idx1, idx3, idx2);
594
595 // Link faces edges
596 // TODO: we may not need to link faces
597 sLinkFace(t1->mFirstEdge, t2->mFirstEdge->mNextEdge->mNextEdge);
598 sLinkFace(t1->mFirstEdge->mNextEdge, t2->mFirstEdge->mNextEdge);
599 sLinkFace(t1->mFirstEdge->mNextEdge->mNextEdge, t2->mFirstEdge);
600
601#ifdef JPL_CONVEX_BUILDER_DEBUG
602 // Draw current state
603 DrawState();
604#endif
605
606 return EResult::Success;
607 }
608
609 // Find point that forms the biggest tetrahedron
610 Vec3 initial_plane_normal = Normalized(CrossProduct(mPositions[idx2] - mPositions[idx1], mPositions[idx3] - mPositions[idx1]));
611 Vec3 initial_plane_centroid = (mPositions[idx1] + mPositions[idx2] + mPositions[idx3]) / 3.0f;
612 int idx4 = -1;
613 float max_dist = 0.0f;
614 for (int i = 0; i < (int)mPositions.size(); ++i)
615 {
616 if (i != idx1 && i != idx2 && i != idx3)
617 {
618 float dist = DotProduct(mPositions[i] - initial_plane_centroid, initial_plane_normal);
619 if (std::abs(dist) > std::abs(max_dist))
620 {
621 max_dist = dist;
622 idx4 = i;
623 }
624 }
625 }
626
627 // Check if the hull is coplanar
628 if ((max_dist * max_dist) <= 25.0f * coplanar_tolerance_sq)
629 {
631 // TODO: (may be still useful if we reuse this hull builder for other stuff
632#if 1
633 JPL_ASSERT(false);
634 return EResult::Degenerate;
635#else
636 // First project all points in 2D space
637 Vec3 base1 = GetNormalizedPerpendicular(initial_plane_normal);
638 Vec3 base2 = CrossProduct(initial_plane_normal, base1);
639 Array<Vec3> positions_2d;
640 positions_2d.reserve(mPositions.size());
641 for (Vec3 v : mPositions)
642 positions_2d.emplace_back(DotProduct(base1, v), DotProduct(base2, v), 0.0f);
643
644 // Build hull
645 Array<int> edges_2d;
646 ConvexHullBuilder2D builder_2d(positions_2d);
647 ConvexHullBuilder2D::EResult result = builder_2d.Initialize(idx1, idx2, idx3, inMaxVertices, inTolerance, edges_2d);
648
649 // Create faces (back to back)
650 Face* f1 = CreateFace();
651 Face* f2 = CreateFace();
652
653 // Create edges for face 1
654 Array<Edge*> edges_f1;
655 edges_f1.reserve(edges_2d.size());
656 for (int start_idx : edges_2d)
657 {
658 Edge* edge = new Edge(f1, start_idx);
659 if (edges_f1.empty())
660 f1->mFirstEdge = edge;
661 else
662 edges_f1.back()->mNextEdge = edge;
663 edges_f1.push_back(edge);
664 }
665 edges_f1.back()->mNextEdge = f1->mFirstEdge;
666
667 // Create edges for face 2
668 Array<Edge*> edges_f2;
669 edges_f2.reserve(edges_2d.size());
670 for (int i = (int)edges_2d.size() - 1; i >= 0; --i)
671 {
672 Edge* edge = new Edge(f2, edges_2d[i]);
673 if (edges_f2.empty())
674 f2->mFirstEdge = edge;
675 else
676 edges_f2.back()->mNextEdge = edge;
677 edges_f2.push_back(edge);
678 }
679 edges_f2.back()->mNextEdge = f2->mFirstEdge;
680
681 // Link edges
682 for (size_t i = 0; i < edges_2d.size(); ++i)
683 sLinkFace(edges_f1[i], edges_f2[(2 * edges_2d.size() - 2 - i) % edges_2d.size()]);
684
685 // Calculate the plane for both faces
686 f1->CalculateNormalAndCentroid(mPositions.data());
687 f2->mNormal = -f1->mNormal;
688 f2->mCentroid = f1->mCentroid;
689
690#ifdef JPL_CONVEX_BUILDER_DEBUG
691 // Draw current state
692 DrawState();
693#endif
694
695 return result == ConvexHullBuilder2D::EResult::MaxVerticesReached ? EResult::MaxVerticesReached : EResult::Success;
696#endif
697 }
698
699 // Ensure the planes are facing outwards
700 if (max_dist < 0.0f)
701 std::swap(idx2, idx3);
702
703 // Create tetrahedron
704 Face* t1 = CreateTriangle(idx1, idx2, idx4);
705 Face* t2 = CreateTriangle(idx2, idx3, idx4);
706 Face* t3 = CreateTriangle(idx3, idx1, idx4);
707 Face* t4 = CreateTriangle(idx1, idx3, idx2);
708
709 // Link face edges
710 sLinkFace(t1->mFirstEdge, t4->mFirstEdge->mNextEdge->mNextEdge);
711 sLinkFace(t1->mFirstEdge->mNextEdge, t2->mFirstEdge->mNextEdge->mNextEdge);
712 sLinkFace(t1->mFirstEdge->mNextEdge->mNextEdge, t3->mFirstEdge->mNextEdge);
713 sLinkFace(t2->mFirstEdge, t4->mFirstEdge->mNextEdge);
714 sLinkFace(t2->mFirstEdge->mNextEdge, t3->mFirstEdge->mNextEdge->mNextEdge);
715 sLinkFace(t3->mFirstEdge, t4->mFirstEdge);
716
717 // Build the initial conflict lists
718 Faces faces({ t1, t2, t3, t4 }, GetDefaultMemoryResource());
719 for (int idx = 0; idx < (int)mPositions.size(); ++idx)
720 if (idx != idx1 && idx != idx2 && idx != idx3 && idx != idx4)
721 AssignPointToFace(idx, faces, tolerance_sq);
722
723#ifdef JPL_CONVEX_BUILDER_DEBUG
724 // Draw current state including conflict list
725 DrawState(true);
726
727 // Increment iteration counter
728 ++mIteration;
729#endif
730
731 // Overestimate of the actual amount of vertices we use, for limiting the amount of vertices in the hull
732 int num_vertices_used = 4;
733
734 // Loop through the remainder of the points and add them
735 for (;;)
736 {
737 // Find the face with the furthest point on it
738 Face* face_with_furthest_point = nullptr;
739 float furthest_dist_sq = 0.0f;
740 for (Face* f : mFaces)
741 {
742 if (f->mFurthestPointDistanceSq > furthest_dist_sq)
743 {
744 furthest_dist_sq = f->mFurthestPointDistanceSq;
745 face_with_furthest_point = f;
746 }
747 }
748
749 int furthest_point_idx;
750 if (face_with_furthest_point != nullptr)
751 {
752 // Take the furthest point
753 furthest_point_idx = face_with_furthest_point->mConflictList.back();
754 face_with_furthest_point->mConflictList.pop_back();
755 }
756 else if (!mCoplanarList.empty())
757 {
758 // Try to assign points to faces (this also recalculates the distance to the hull for the coplanar vertices)
759 CoplanarList coplanar(GetDefaultMemoryResource());
760 mCoplanarList.swap(coplanar);
761 bool added = false;
762 for (const Coplanar& c : coplanar)
763 added |= AssignPointToFace(c.mPositionIdx, mFaces, tolerance_sq);
764
765 // If we were able to assign a point, loop again to pick it up
766 if (added)
767 continue;
768
769 // If the coplanar list is empty, there are no points left and we're done
770 if (mCoplanarList.empty())
771 break;
772
773 do
774 {
775 // Find the vertex that is furthest from the hull
776 typename CoplanarList::size_type best_idx = 0;
777 float best_dist_sq = mCoplanarList.front().mDistanceSq;
778 for (typename CoplanarList::size_type idx = 1; idx < mCoplanarList.size(); ++idx)
779 {
780 const Coplanar& c = mCoplanarList[idx];
781 if (c.mDistanceSq > best_dist_sq)
782 {
783 best_idx = idx;
784 best_dist_sq = c.mDistanceSq;
785 }
786 }
787
788 // Swap it to the end
789 std::swap(mCoplanarList[best_idx], mCoplanarList.back());
790
791 // Remove it
792 furthest_point_idx = mCoplanarList.back().mPositionIdx;
793 mCoplanarList.pop_back();
794
795 // Find the face for which the point is furthest away
796 GetFaceForPoint(mPositions[furthest_point_idx], mFaces, face_with_furthest_point, best_dist_sq);
797 } while (!mCoplanarList.empty() && face_with_furthest_point == nullptr);
798
799 if (face_with_furthest_point == nullptr)
800 break;
801 }
802 else
803 {
804 // If there are no more vertices, we're done
805 break;
806 }
807
808 // Check if we have a limit on the max vertices that we should produce
809 if (num_vertices_used >= inMaxVertices)
810 {
811 // Count the actual amount of used vertices (we did not take the removal of any vertices into account)
812 num_vertices_used = GetNumVerticesUsed();
813
814 // Check if there are too many
815 if (num_vertices_used >= inMaxVertices)
816 return EResult::MaxVerticesReached;
817 }
818
819 // We're about to add another vertex
820 ++num_vertices_used;
821
822 // Add the point to the hull
823 Faces new_faces(GetDefaultMemoryResource());
824 AddPoint(face_with_furthest_point, furthest_point_idx, coplanar_tolerance_sq, new_faces);
825
826 // Redistribute points on conflict lists belonging to removed faces
827 for (const Face* face : mFaces)
828 if (face->mRemoved)
829 for (int idx : face->mConflictList)
830 AssignPointToFace(idx, new_faces, tolerance_sq);
831
832 // Permanently delete faces that we removed in AddPoint()
833 GarbageCollectFaces();
834
835#ifdef JPL_CONVEX_BUILDER_DEBUG
836 // Draw state at the end of this step including conflict list
837 DrawState(true);
838
839 // Increment iteration counter
840 ++mIteration;
841#endif
842 }
843
844 // Check if we are left with a hull. It is possible that hull building fails if the points are nearly coplanar.
845 if (mFaces.size() < 2)
846 {
847 outError = "Too few faces in hull";
848 return EResult::TooFewFaces;
849 }
850
851 return EResult::Success;
852 }
853
854 template<CVec3 Vec3Type>
856 {
857 std::unordered_set<int> used_verts;
858 for (const Face* f : mFaces)
859 {
860 const Edge* e = f->mFirstEdge;
861 do
862 {
863 used_verts.insert(e->mStartIdx);
864 e = e->mNextEdge;
865 } while (e != f->mFirstEdge);
866 }
867 return (int)used_verts.size();
868 }
869
870 template<CVec3 Vec3Type>
871 inline bool ConvexHullBuilder<Vec3Type>::ContainsFace(const Array<int>& inIndices) const
872 {
873 for (Face* f : mFaces)
874 {
875 Edge* e = f->mFirstEdge;
876 Array<int>::const_iterator index = std::find(inIndices.begin(), inIndices.end(), e->mStartIdx);
877 if (index != inIndices.end())
878 {
879 size_t matches = 0;
880
881 do
882 {
883 // Check if index matches
884 if (*index != e->mStartIdx)
885 break;
886
887 // Increment number of matches
888 matches++;
889
890 // Next index in list of inIndices
891 index++;
892 if (index == inIndices.end())
893 index = inIndices.begin();
894
895 // Next edge
896 e = e->mNextEdge;
897 } while (e != f->mFirstEdge);
898
899 if (matches == inIndices.size())
900 return true;
901 }
902 }
903
904 return false;
905 }
906
907 template<CVec3 Vec3Type>
908 template<class Vec3i>
909 inline void ConvexHullBuilder<Vec3Type>::GetTriangles(Array<Vec3i>& outTris)
910 {
911 outTris.clear();
912 outTris.reserve(mFaces.size());
913
914 for (const Face* f : mFaces)
915 f->Triangulate(mPositions, outTris);
916 }
917
918 template<CVec3 Vec3Type>
920 {
921 auto vec3max = [](const Vec3& a, const Vec3& b)
922 {
923 return Vec3{ std::max(GetX(a), GetX(b)), std::max(GetY(a), GetY(b)), std::max(GetZ(a), GetZ(b)) };
924 };
925 auto vec3abs = [](const Vec3& v)
926 {
927 return Vec3{ std::abs(GetX(v)), std::abs(GetY(v)), std::abs(GetZ(v)) };
928 };
929
930 // Formula as per: Implementing Quickhull - Dirk Gregorius.
931 Vec3 vmax{ 0.0f, 0.0f, 0.0f };
932 for (Vec3 v : mPositions)
933 vmax = vec3max(vmax, vec3abs(v));
934 return 3.0f * std::numeric_limits<float>::epsilon() * (GetX(vmax) + GetY(vmax) + GetZ(vmax));
935 }
936
937 template<CVec3 Vec3Type>
938 inline void ConvexHullBuilder<Vec3Type>::GetFaceForPoint(const Vec3& inPoint, const Faces& inFaces, Face*& outFace, float& outDistSq) const
939 {
940 outFace = nullptr;
941 outDistSq = 0.0f;
942
943 for (Face* f : inFaces)
944 {
945 if (!f->mRemoved)
946 {
947 // Determine distance to face
948 const float dot = DotProduct(f->mNormal, inPoint - f->mCentroid);
949 if (dot > 0.0f)
950 {
951 const float dist_sq = dot * dot / LengthSquared(f->mNormal);
952 if (dist_sq > outDistSq)
953 {
954 outFace = f;
955 outDistSq = dist_sq;
956 }
957 }
958 }
959 }
960 }
961
962 template<CVec3 Vec3Type>
963 inline float ConvexHullBuilder<Vec3Type>::GetDistanceToEdgeSq(const Vec3& inPoint, const Face* inFace) const
964 {
965 bool all_inside = true;
966 float edge_dist_sq = std::numeric_limits<float>::max();
967
968 // Test if it is inside the edges of the polygon
969 Edge* edge = inFace->mFirstEdge;
970 Vec3 p1 = mPositions[edge->GetPreviousEdge()->mStartIdx];
971 do
972 {
973 Vec3 p2 = mPositions[edge->mStartIdx];
974 if (DotProduct(CrossProduct(p2 - p1, inPoint - p1), inFace->mNormal) < 0.0f)
975 {
976 // It is outside
977 all_inside = false;
978
979 // Measure distance to this edge
980 uint32 s;
981 edge_dist_sq = std::min(edge_dist_sq, LengthSquared(GetClosestPointOnLine(p1 - inPoint, p2 - inPoint, s)));
982 }
983 p1 = p2;
984 edge = edge->mNextEdge;
985 } while (edge != inFace->mFirstEdge);
986
987 return all_inside ? 0.0f : edge_dist_sq;
988 }
989
990 template<CVec3 Vec3Type>
991 inline bool ConvexHullBuilder<Vec3Type>::AssignPointToFace(int inPositionIdx, const Faces& inFaces, float inToleranceSq)
992 {
993 Vec3 point = mPositions[inPositionIdx];
994
995 // Find the face for which the point is furthest away
996 Face* best_face;
997 float best_dist_sq;
998 GetFaceForPoint(point, inFaces, best_face, best_dist_sq);
999
1000 if (best_face != nullptr)
1001 {
1002 // Check if this point is within the tolerance margin to the plane
1003 if (best_dist_sq <= inToleranceSq)
1004 {
1005 // Check distance to edges
1006 float dist_to_edge_sq = GetDistanceToEdgeSq(point, best_face);
1007 if (dist_to_edge_sq > inToleranceSq)
1008 {
1009 // Point is outside of the face and too far away to discard
1010 mCoplanarList.push_back({ inPositionIdx, dist_to_edge_sq });
1011 }
1012 }
1013 else
1014 {
1015 // This point is in front of the face, add it to the conflict list
1016 if (best_dist_sq > best_face->mFurthestPointDistanceSq)
1017 {
1018 // This point is further away than any others, update the distance and add point as last point
1019 best_face->mFurthestPointDistanceSq = best_dist_sq;
1020 best_face->mConflictList.push_back(inPositionIdx);
1021 }
1022 else
1023 {
1024 // Not the furthest point, add it as the before last point
1025 best_face->mConflictList.insert(best_face->mConflictList.begin() + best_face->mConflictList.size() - 1, inPositionIdx);
1026 }
1027
1028 return true;
1029 }
1030 }
1031
1032 return false;
1033 }
1034
1035 template<CVec3 Vec3Type>
1036 inline void ConvexHullBuilder<Vec3Type>::AddPoint(Face* inFacingFace, int inIdx, float inCoplanarToleranceSq, Faces& outNewFaces)
1037 {
1038 // Get position
1039 Vec3 pos = mPositions[inIdx];
1040
1041#ifdef JPL_CONVEX_BUILDER_DEBUG
1042 // Draw point to be added
1043 DebugRenderer::sInstance->DrawMarker(cDrawScale * (mOffset + pos), Color::sYellow, 0.1f);
1044 DebugRenderer::sInstance->DrawText3D(cDrawScale * (mOffset + pos), ConvertToString(inIdx), Color::sWhite);
1045#endif
1046
1047#ifdef JPL_ENABLE_VALIDATION
1048 // Check if structure is intact
1049 ValidateFaces();
1050#endif
1051
1052 // Find edge of convex hull of faces that are not facing the new vertex
1053 FullEdges edges(GetDefaultMemoryResource());
1054 FindEdge(inFacingFace, pos, edges);
1055 JPL_ASSERT(edges.size() >= 3);
1056
1057 // Create new faces
1058 outNewFaces.reserve(edges.size());
1059 for (const FullEdge& e : edges)
1060 {
1061 JPL_ASSERT(e.mStartIdx != e.mEndIdx);
1062 Face* f = CreateTriangle(e.mStartIdx, e.mEndIdx, inIdx);
1063 outNewFaces.push_back(f);
1064 }
1065
1066 // Link edges
1067 for (typename Faces::size_type i = 0; i < outNewFaces.size(); ++i)
1068 {
1069 sLinkFace(outNewFaces[i]->mFirstEdge, edges[i].mNeighbourEdge);
1070 sLinkFace(outNewFaces[i]->mFirstEdge->mNextEdge, outNewFaces[(i + 1) % outNewFaces.size()]->mFirstEdge->mNextEdge->mNextEdge);
1071 }
1072
1073 // Loop on faces that were modified until nothing needs to be checked anymore
1074 Faces affected_faces = outNewFaces;
1075 while (!affected_faces.empty())
1076 {
1077 // Take the next face
1078 Face* face = affected_faces.back();
1079 affected_faces.pop_back();
1080
1081 if (!face->mRemoved)
1082 {
1083 // Merge with neighbour if this is a degenerate face
1084 MergeDegenerateFace(face, affected_faces);
1085
1086 // Merge with coplanar neighbours (or when the neighbour forms a concave edge)
1089#if 0
1090 if (!face->mRemoved)
1091 MergeCoplanarOrConcaveFaces(face, inCoplanarToleranceSq, affected_faces);
1092#endif
1093 }
1094 }
1095
1096#ifdef JPL_ENABLE_VALIDATION
1097 // Check if structure is intact
1098 ValidateFaces();
1099#endif
1100 }
1101
1102 template<CVec3 Vec3Type>
1103 inline void ConvexHullBuilder<Vec3Type>::GarbageCollectFaces()
1104 {
1105 for (int i = (int)mFaces.size() - 1; i >= 0; --i)
1106 {
1107 Face* f = mFaces[i];
1108 if (f->mRemoved)
1109 {
1110 FreeFace(f);
1111 mFaces.erase(mFaces.begin() + i);
1112 }
1113 }
1114 }
1115
1116 template<CVec3 Vec3Type>
1117 inline ConvexHullBuilder<Vec3Type>::Face* ConvexHullBuilder<Vec3Type>::CreateFace()
1118 {
1119 // Call provider to create face
1120 Face* f = DefaultNew<Face>();
1121
1122#ifdef JPL_CONVEX_BUILDER_DEBUG
1123 // Remember iteration counter
1124 f->mIteration = mIteration;
1125#endif
1126
1127 // Add to list
1128 mFaces.push_back(f);
1129 return f;
1130 }
1131
1132 template<CVec3 Vec3Type>
1133 inline ConvexHullBuilder<Vec3Type>::Face* ConvexHullBuilder<Vec3Type>::CreateTriangle(int inIdx1, int inIdx2, int inIdx3)
1134 {
1135 Face* f = CreateFace();
1136 f->Initialize(inIdx1, inIdx2, inIdx3, mPositions);
1137 return f;
1138 }
1139
1140 template<CVec3 Vec3Type>
1141 inline void ConvexHullBuilder<Vec3Type>::FreeFace(Face* inFace)
1142 {
1143 JPL_ASSERT(inFace->mRemoved);
1144
1145#ifdef JPL_ENABLE_VALIDATION
1146 // Make sure that this face is not connected
1147 if (Edge* e = inFace->mFirstEdge)
1148 {
1149 do
1150 {
1151 JPL_ASSERT(e->mNeighbourEdge == nullptr);
1152 e = e->mNextEdge;
1153 } while (e != inFace->mFirstEdge);
1154 }
1155#endif
1156
1157 // Free the face
1158 DefaultDelete(inFace);
1159 }
1160
1161 template<CVec3 Vec3Type>
1162 inline void ConvexHullBuilder<Vec3Type>::FreeFaces()
1163 {
1164 for (Face* f : mFaces)
1165 {
1166 DefaultDelete(f);
1167 }
1168 mFaces.clear();
1169 }
1170
1171 template<CVec3 Vec3Type>
1172 inline void ConvexHullBuilder<Vec3Type>::sLinkFace(Edge* inEdge1, Edge* inEdge2)
1173 {
1174 // Check not connected yet
1175 JPL_ASSERT(inEdge1->mNeighbourEdge == nullptr);
1176 JPL_ASSERT(inEdge2->mNeighbourEdge == nullptr);
1177 JPL_ASSERT(inEdge1->mFace != inEdge2->mFace);
1178
1179 // Check vertices match
1180 JPL_ASSERT(inEdge1->mStartIdx == inEdge2->mNextEdge->mStartIdx);
1181 JPL_ASSERT(inEdge2->mStartIdx == inEdge1->mNextEdge->mStartIdx);
1182
1183 // Link up
1184 inEdge1->mNeighbourEdge = inEdge2;
1185 inEdge2->mNeighbourEdge = inEdge1;
1186 }
1187
1188 template<CVec3 Vec3Type>
1189 inline void ConvexHullBuilder<Vec3Type>::sUnlinkFace(Face* inFace)
1190 {
1191 // Unlink from neighbours
1192 Edge* e = inFace->mFirstEdge;
1193 do
1194 {
1195 if (e->mNeighbourEdge != nullptr)
1196 {
1197 // Validate that neighbour points to us
1198 JPL_ASSERT(e->mNeighbourEdge->mNeighbourEdge == e);
1199
1200 // Unlink
1201 e->mNeighbourEdge->mNeighbourEdge = nullptr;
1202 e->mNeighbourEdge = nullptr;
1203 }
1204 e = e->mNextEdge;
1205 } while (e != inFace->mFirstEdge);
1206 }
1207
1208 template<CVec3 Vec3Type>
1209 inline void ConvexHullBuilder<Vec3Type>::FindEdge(Face* inFacingFace, const Vec3& inVertex, FullEdges& outEdges) const
1210 {
1211 // Assert that we were given an empty array
1212 JPL_ASSERT(outEdges.empty());
1213
1214 // Should start with a facing face
1215 JPL_ASSERT(inFacingFace->IsFacing(inVertex));
1216
1217 // Flag as removed
1218 inFacingFace->mRemoved = true;
1219
1220 // Instead of recursing, we build our own stack with the information we need
1221 struct StackEntry
1222 {
1223 Edge* mFirstEdge;
1224 Edge* mCurrentEdge;
1225 };
1226 constexpr int cMaxEdgeLength = 128;
1227 StackEntry stack[cMaxEdgeLength];
1228 int cur_stack_pos = 0;
1229
1230 static_assert(alignof(Edge) >= 2, "Need lowest bit to indicate to tell if we completed the loop");
1231
1232 // Start with the face / edge provided
1233 stack[0].mFirstEdge = inFacingFace->mFirstEdge;
1234 stack[0].mCurrentEdge = reinterpret_cast<Edge*>(reinterpret_cast<uintptr_t>(inFacingFace->mFirstEdge) | 1); // Set lowest bit of pointer to make it different from the first edge
1235
1236 for (;;)
1237 {
1238 StackEntry& cur_entry = stack[cur_stack_pos];
1239
1240 // Next edge
1241 Edge* raw_e = cur_entry.mCurrentEdge;
1242 Edge* 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 testing
1243 cur_entry.mCurrentEdge = e->mNextEdge;
1244
1245 // If we're back at the first edge we've completed the face and we're done
1246 if (raw_e == cur_entry.mFirstEdge)
1247 {
1248 // This face needs to be removed, unlink it now, caller will free
1249 sUnlinkFace(e->mFace);
1250
1251 // Pop from stack
1252 if (--cur_stack_pos < 0)
1253 break;
1254 }
1255 else
1256 {
1257 // Visit neighbour face
1258 Edge* ne = e->mNeighbourEdge;
1259 if (ne != nullptr)
1260 {
1261 Face* n = ne->mFace;
1262 if (!n->mRemoved)
1263 {
1264 // Check if vertex is on the front side of this face
1265 if (n->IsFacing(inVertex))
1266 {
1267 // Vertex on front, this face needs to be removed
1268 n->mRemoved = true;
1269
1270 // Add element to the stack of elements to visit
1271 cur_stack_pos++;
1272 JPL_ASSERT(cur_stack_pos < cMaxEdgeLength);
1273 StackEntry& new_entry = stack[cur_stack_pos];
1274 new_entry.mFirstEdge = ne;
1275 new_entry.mCurrentEdge = ne->mNextEdge; // We don't need to test this edge again since we came from it
1276 }
1277 else
1278 {
1279 // Vertex behind, keep edge
1280 FullEdge full;
1281 full.mNeighbourEdge = ne;
1282 full.mStartIdx = e->mStartIdx;
1283 full.mEndIdx = ne->mStartIdx;
1284 outEdges.push_back(full);
1285 }
1286 }
1287 }
1288 }
1289 }
1290
1291 // Assert that we have a fully connected loop
1292#ifdef JPL_ENABLE_VALIDATION
1293 for (int i = 0; i < (int)outEdges.size(); ++i)
1294 JPL_ASSERT(outEdges[i].mEndIdx == outEdges[(i + 1) % outEdges.size()].mStartIdx);
1295#endif
1296
1297#ifdef JPL_CONVEX_BUILDER_DEBUG
1298 // Draw edge of facing faces
1299 for (int i = 0; i < (int)outEdges.size(); ++i)
1300 DebugRenderer::sInstance->DrawArrow(cDrawScale * (mOffset + mPositions[outEdges[i].mStartIdx]), cDrawScale * (mOffset + mPositions[outEdges[i].mEndIdx]), Color::sWhite, 0.01f);
1301 DrawState();
1302#endif
1303 }
1304
1305 template<CVec3 Vec3Type>
1306 inline void ConvexHullBuilder<Vec3Type>::MergeFaces(Edge* inEdge)
1307 {
1308 // Get the face
1309 Face* face = inEdge->mFace;
1310
1311 // Find the previous and next edge
1312 Edge* next_edge = inEdge->mNextEdge;
1313 Edge* prev_edge = inEdge->GetPreviousEdge();
1314
1315 // Get the other face
1316 Edge* other_edge = inEdge->mNeighbourEdge;
1317 Face* other_face = other_edge->mFace;
1318
1319 // Check if attempting to merge with self
1320 JPL_ASSERT(face != other_face);
1321
1322#ifdef JPL_CONVEX_BUILDER_DEBUG
1323 DrawWireFace(face, Color::sGreen);
1324 DrawWireFace(other_face, Color::sRed);
1325 DrawState();
1326#endif
1327
1328 // Loop over the edges of the other face and make them belong to inFace
1329 Edge* edge = other_edge->mNextEdge;
1330 prev_edge->mNextEdge = edge;
1331 for (;;)
1332 {
1333 edge->mFace = face;
1334 if (edge->mNextEdge == other_edge)
1335 {
1336 // Terminate when we are back at other_edge
1337 edge->mNextEdge = next_edge;
1338 break;
1339 }
1340 edge = edge->mNextEdge;
1341 }
1342
1343 // If the first edge happens to be inEdge we need to fix it because this edge is no longer part of the face.
1344 // Note that we replace it with the first edge of the merged face so that if the MergeFace function is called
1345 // from a loop that loops around the face that it will still terminate after visiting all edges once.
1346 if (face->mFirstEdge == inEdge)
1347 face->mFirstEdge = prev_edge->mNextEdge;
1348
1349 // Free the edges
1350 delete inEdge;
1351 delete other_edge;
1352
1353 // Mark the other face as removed
1354 other_face->mFirstEdge = nullptr;
1355 other_face->mRemoved = true;
1356
1357 // Recalculate plane
1358 face->CalculateNormalAndCentroid(mPositions);
1359
1360 // Merge conflict lists
1361 if (face->mFurthestPointDistanceSq > other_face->mFurthestPointDistanceSq)
1362 {
1363 // 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 list
1364 face->mConflictList.insert(face->mConflictList.end() - 1, other_face->mConflictList.begin(), other_face->mConflictList.end());
1365 }
1366 else
1367 {
1368 // The other face has a point that's furthest away, add that list at the end.
1369 face->mConflictList.insert(face->mConflictList.end(), other_face->mConflictList.begin(), other_face->mConflictList.end());
1370 face->mFurthestPointDistanceSq = other_face->mFurthestPointDistanceSq;
1371 }
1372 other_face->mConflictList.clear();
1373
1374#ifdef JPL_CONVEX_BUILDER_DEBUG
1375 DrawWireFace(face, Color::sWhite);
1376 DrawState();
1377#endif
1378 }
1379
1380 template<CVec3 Vec3Type>
1381 inline void ConvexHullBuilder<Vec3Type>::MergeDegenerateFace(Face* inFace, Faces& ioAffectedFaces)
1382 {
1383 // Check area of face
1384 if (LengthSquared(inFace->mNormal) < cMinTriangleAreaSq)
1385 {
1386 // Find longest edge, since this face is a sliver this should keep the face convex
1387 float max_length_sq = 0.0f;
1388 Edge* longest_edge = nullptr;
1389 Edge* e = inFace->mFirstEdge;
1390 Vec3 p1 = mPositions[e->mStartIdx];
1391 do
1392 {
1393 Edge* next = e->mNextEdge;
1394 Vec3 p2 = mPositions[next->mStartIdx];
1395 float length_sq = LengthSquared(p2 - p1);
1396 if (length_sq >= max_length_sq)
1397 {
1398 max_length_sq = length_sq;
1399 longest_edge = e;
1400 }
1401 p1 = p2;
1402 e = next;
1403 } while (e != inFace->mFirstEdge);
1404
1405 // Merge with face on longest edge
1406 MergeFaces(longest_edge);
1407
1408 // Remove any invalid edges
1409 RemoveInvalidEdges(inFace, ioAffectedFaces);
1410 }
1411 }
1412
1413 template<CVec3 Vec3Type>
1414 inline void ConvexHullBuilder<Vec3Type>::MergeCoplanarOrConcaveFaces(Face* inFace, float inCoplanarToleranceSq, Faces& ioAffectedFaces)
1415 {
1416 bool merged = false;
1417
1418 Edge* edge = inFace->mFirstEdge;
1419 do
1420 {
1421 // Store next edge since this edge can be removed
1422 Edge* next_edge = edge->mNextEdge;
1423
1424 // Test if centroid of one face is above plane of the other face by inCoplanarToleranceSq.
1425 // If so we need to merge other face into inFace.
1426 const Face* other_face = edge->mNeighbourEdge->mFace;
1427 const Vec3 delta_centroid = other_face->mCentroid - inFace->mCentroid;
1428 const float dist_other_face_centroid = DotProduct(inFace->mNormal, delta_centroid);
1429 const float signed_dist_other_face_centroid_sq = std::abs(dist_other_face_centroid) * dist_other_face_centroid;
1430 const float dist_face_centroid = -DotProduct(other_face->mNormal, delta_centroid);
1431 const float signed_dist_face_centroid_sq = std::abs(dist_face_centroid) * dist_face_centroid;
1432 const float face_normal_len_sq = LengthSquared(inFace->mNormal);
1433 const float other_face_normal_len_sq = LengthSquared(other_face->mNormal);
1434 if ((signed_dist_other_face_centroid_sq > -inCoplanarToleranceSq * face_normal_len_sq
1435 || signed_dist_face_centroid_sq > -inCoplanarToleranceSq * other_face_normal_len_sq)
1436 && DotProduct(inFace->mNormal, other_face->mNormal) > 0.0f) // Never merge faces that are back to back
1437 {
1438 MergeFaces(edge);
1439 merged = true;
1440 }
1441
1442 edge = next_edge;
1443 } while (edge != inFace->mFirstEdge);
1444
1445 if (merged)
1446 RemoveInvalidEdges(inFace, ioAffectedFaces);
1447 }
1448
1449 template<CVec3 Vec3Type>
1450 inline void ConvexHullBuilder<Vec3Type>::sMarkAffected(Face* inFace, Faces& ioAffectedFaces)
1451 {
1452 if (std::find(ioAffectedFaces.begin(), ioAffectedFaces.end(), inFace) == ioAffectedFaces.end())
1453 ioAffectedFaces.push_back(inFace);
1454 }
1455
1456 template<CVec3 Vec3Type>
1457 inline void ConvexHullBuilder<Vec3Type>::RemoveInvalidEdges(Face* inFace, Faces& ioAffectedFaces)
1458 {
1459 // This marks that the plane needs to be recalculated (we delay this until the end of the
1460 // function since we don't use the plane and we want to avoid calculating it multiple times)
1461 bool recalculate_plane = false;
1462
1463 // We keep going through this loop until no more edges were removed
1464 bool removed;
1465 do
1466 {
1467 removed = false;
1468
1469 // Loop over all edges in this face
1470 Edge* edge = inFace->mFirstEdge;
1471 Face* neighbour_face = edge->mNeighbourEdge->mFace;
1472 do
1473 {
1474 Edge* next_edge = edge->mNextEdge;
1475 Face* next_neighbour_face = next_edge->mNeighbourEdge->mFace;
1476
1477 if (neighbour_face == inFace)
1478 {
1479 // We only remove 1 edge at a time, check if this edge's next edge is our neighbour.
1480 // If this check fails, we will continue to scan along the edge until we find an edge where this is the case.
1481 if (edge->mNeighbourEdge == next_edge)
1482 {
1483 // This edge leads back to the starting point, this means the edge is interior and needs to be removed
1484#ifdef JPL_CONVEX_BUILDER_DEBUG
1485 DrawWireFace(inFace, Color::sBlue);
1486 DrawState();
1487#endif
1488
1489 // Remove edge
1490 Edge* prev_edge = edge->GetPreviousEdge();
1491 prev_edge->mNextEdge = next_edge->mNextEdge;
1492 if (inFace->mFirstEdge == edge || inFace->mFirstEdge == next_edge)
1493 inFace->mFirstEdge = prev_edge;
1494 delete edge;
1495 delete next_edge;
1496
1497#ifdef JPL_CONVEX_BUILDER_DEBUG
1498 DrawWireFace(inFace, Color::sGreen);
1499 DrawState();
1500#endif
1501
1502 // Check if inFace now has only 2 edges left
1503 if (RemoveTwoEdgeFace(inFace, ioAffectedFaces))
1504 return; // Bail if face no longer exists
1505
1506 // Restart the loop
1507 recalculate_plane = true;
1508 removed = true;
1509 break;
1510 }
1511 }
1512 else if (neighbour_face == next_neighbour_face)
1513 {
1514 // There are two edges that connect to the same face, we will remove the second one
1515#ifdef JPL_CONVEX_BUILDER_DEBUG
1516 DrawWireFace(inFace, Color::sYellow);
1517 DrawWireFace(neighbour_face, Color::sRed);
1518 DrawState();
1519#endif
1520
1521 // First merge the neighbours edges
1522 Edge* neighbour_edge = next_edge->mNeighbourEdge;
1523 Edge* next_neighbour_edge = neighbour_edge->mNextEdge;
1524 if (neighbour_face->mFirstEdge == next_neighbour_edge)
1525 neighbour_face->mFirstEdge = neighbour_edge;
1526 neighbour_edge->mNextEdge = next_neighbour_edge->mNextEdge;
1527 neighbour_edge->mNeighbourEdge = edge;
1528 delete next_neighbour_edge;
1529
1530 // Then merge my own edges
1531 if (inFace->mFirstEdge == next_edge)
1532 inFace->mFirstEdge = edge;
1533 edge->mNextEdge = next_edge->mNextEdge;
1534 edge->mNeighbourEdge = neighbour_edge;
1535 delete next_edge;
1536
1537#ifdef JPL_CONVEX_BUILDER_DEBUG
1538 DrawWireFace(inFace, Color::sYellow);
1539 DrawWireFace(neighbour_face, Color::sGreen);
1540 DrawState();
1541#endif
1542
1543 // Check if neighbour has only 2 edges left
1544 if (!RemoveTwoEdgeFace(neighbour_face, ioAffectedFaces))
1545 {
1546 // No, we need to recalculate its plane
1547 neighbour_face->CalculateNormalAndCentroid(mPositions);
1548
1549 // Mark neighbour face as affected
1550 sMarkAffected(neighbour_face, ioAffectedFaces);
1551 }
1552
1553 // Check if inFace now has only 2 edges left
1554 if (RemoveTwoEdgeFace(inFace, ioAffectedFaces))
1555 return; // Bail if face no longer exists
1556
1557 // Restart loop
1558 recalculate_plane = true;
1559 removed = true;
1560 break;
1561 }
1562
1563 // This edge is ok, go to the next edge
1564 edge = next_edge;
1565 neighbour_face = next_neighbour_face;
1566
1567 } while (edge != inFace->mFirstEdge);
1568 } while (removed);
1569
1570 // Recalculate plane?
1571 if (recalculate_plane)
1572 inFace->CalculateNormalAndCentroid(mPositions);
1573 }
1574
1575 template<CVec3 Vec3Type>
1576 inline bool ConvexHullBuilder<Vec3Type>::RemoveTwoEdgeFace(Face* inFace, Faces& ioAffectedFaces) const
1577 {
1578 // Check if this face contains only 2 edges
1579 Edge* edge = inFace->mFirstEdge;
1580 Edge* next_edge = edge->mNextEdge;
1581 JPL_ASSERT(edge != next_edge); // 1 edge faces should not exist
1582 if (next_edge->mNextEdge == edge)
1583 {
1584#ifdef JPL_CONVEX_BUILDER_DEBUG
1585 DrawWireFace(inFace, Color::sRed);
1586 DrawState();
1587#endif
1588
1589 // Schedule both neighbours for re-checking
1590 Edge* neighbour_edge = edge->mNeighbourEdge;
1591 Face* neighbour_face = neighbour_edge->mFace;
1592 Edge* next_neighbour_edge = next_edge->mNeighbourEdge;
1593 Face* next_neighbour_face = next_neighbour_edge->mFace;
1594 sMarkAffected(neighbour_face, ioAffectedFaces);
1595 sMarkAffected(next_neighbour_face, ioAffectedFaces);
1596
1597 // Link my neighbours to each other
1598 neighbour_edge->mNeighbourEdge = next_neighbour_edge;
1599 next_neighbour_edge->mNeighbourEdge = neighbour_edge;
1600
1601 // Unlink my edges
1602 edge->mNeighbourEdge = nullptr;
1603 next_edge->mNeighbourEdge = nullptr;
1604
1605 // Mark this face as removed
1606 inFace->mRemoved = true;
1607
1608 return true;
1609 }
1610
1611 return false;
1612 }
1613
1614#ifdef JPL_ENABLE_VALIDATION
1615 template<CVec3 Vec3Type>
1616 inline void ConvexHullBuilder<Vec3Type>::DumpFace(const Face* inFace) const
1617 {
1618 std::cout << std::format("f:{}", std::uintptr_t(inFace)) << '\n';
1619
1620 const Edge* e = inFace->mFirstEdge;
1621 do
1622 {
1623 std::cout << std::format("e:{} || i:{} e:{} f:{} ",
1624 std::uintptr_t(e),
1625 e->mStartIdx,
1626 std::uintptr_t(e->mNeighbourEdge),
1627 std::uintptr_t(e->mNeighbourEdge->mFace)) << '\n';
1628 e = e->mNextEdge;
1629 } while (e != inFace->mFirstEdge);
1630 }
1631
1632 template<CVec3 Vec3Type>
1633 inline void ConvexHullBuilder<Vec3Type>::DumpFaces() const
1634 {
1635 std::cout << std::format("Dump Faces:") << '\n';
1636
1637 for (const Face* f : mFaces)
1638 if (!f->mRemoved)
1639 DumpFace(f);
1640 }
1641
1642 template<CVec3 Vec3Type>
1643 inline void ConvexHullBuilder<Vec3Type>::ValidateFace(const Face* inFace) const
1644 {
1645 if (inFace->mRemoved)
1646 {
1647 const Edge* e = inFace->mFirstEdge;
1648 if (e != nullptr)
1649 do
1650 {
1651 JPL_ASSERT(e->mNeighbourEdge == nullptr);
1652 e = e->mNextEdge;
1653 } while (e != inFace->mFirstEdge);
1654 }
1655 else
1656 {
1657 int edge_count = 0;
1658
1659 const Edge* e = inFace->mFirstEdge;
1660 do
1661 {
1662 // Count edge
1663 ++edge_count;
1664
1665 // Validate that adjacent faces are all different
1666 if (mFaces.size() > 2)
1667 for (const Edge* other_edge = e->mNextEdge; other_edge != inFace->mFirstEdge; other_edge = other_edge->mNextEdge)
1668 JPL_ASSERT(e->mNeighbourEdge->mFace != other_edge->mNeighbourEdge->mFace);
1669
1670 // Assert that the face is correct
1671 JPL_ASSERT(e->mFace == inFace);
1672
1673 // Assert that we have a neighbour
1674 const Edge* nb_edge = e->mNeighbourEdge;
1675 JPL_ASSERT(nb_edge != nullptr);
1676 if (nb_edge != nullptr)
1677 {
1678 // Assert that our neighbours edge points to us
1679 JPL_ASSERT(nb_edge->mNeighbourEdge == e);
1680
1681 // Assert that it belongs to a different face
1682 JPL_ASSERT(nb_edge->mFace != inFace);
1683
1684 // Assert that the next edge of the neighbour points to the same vertex as this edge's vertex
1685 JPL_ASSERT(nb_edge->mNextEdge->mStartIdx == e->mStartIdx);
1686
1687 // Assert that my next edge points to the same vertex as my neighbours vertex
1688 JPL_ASSERT(e->mNextEdge->mStartIdx == nb_edge->mStartIdx);
1689 }
1690 e = e->mNextEdge;
1691 } while (e != inFace->mFirstEdge);
1692
1693 // Assert that we have 3 or more edges
1694 JPL_ASSERT(edge_count >= 3);
1695 }
1696 }
1697
1698 template<CVec3 Vec3Type>
1699 inline void ConvexHullBuilder<Vec3Type>::ValidateFaces() const
1700 {
1701 for (const Face* f : mFaces)
1702 ValidateFace(f);
1703 }
1704#endif
1705
1706} // namespace JPL
#define JPL_ASSERT(inExpression,...)
Main assert macro, usage: JPL_ASSERT(condition, message) or JPL_ASSERT(condition)
Definition ErrorReporting.h:80
Class that holds the information of an edge.
Definition ConvexHullBuilder.h:71
Edge(Face *inFace, int inStartIdx)
Definition ConvexHullBuilder.h:76
int mStartIdx
Vertex index in mPositions that indicates the start vertex of this edge.
Definition ConvexHullBuilder.h:87
Face * mFace
Face that this edge belongs to.
Definition ConvexHullBuilder.h:84
Edge * mNextEdge
Next edge of this face.
Definition ConvexHullBuilder.h:85
Edge * GetPreviousEdge()
Get the previous edge.
Definition ConvexHullBuilder.h:327
Edge * mNeighbourEdge
Edge that this edge is connected to.
Definition ConvexHullBuilder.h:86
Class that holds the information of one face.
Definition ConvexHullBuilder.h:95
bool Triangulate(std::span< const Vec3 > inPositions, Array< Vec3i > &outTris) const
Definition ConvexHullBuilder.h:432
~Face()
Definition ConvexHullBuilder.h:336
void Initialize(int inIdx0, int inIdx1, int inIdx2, std::span< const Vec3 > inPositions)
Initialize a face with three indices.
Definition ConvexHullBuilder.h:352
ConflictList mConflictList
Positions associated with this edge (that are closest to this edge). The last position in the list is...
Definition ConvexHullBuilder.h:116
Vec3 mCentroid
Center of the face.
Definition ConvexHullBuilder.h:115
Vec3 mNormal
Normal of this face, length is 2 times area of face.
Definition ConvexHullBuilder.h:114
void CalculateNormalAndCentroid(std::span< const Vec3 > inPositions)
Calculates the centroid and normal for this face.
Definition ConvexHullBuilder.h:384
bool mRemoved
Flag that indicates that face has been removed (face will be freed later)
Definition ConvexHullBuilder.h:119
float mFurthestPointDistanceSq
Squared distance of furthest point from the conflict list to the face.
Definition ConvexHullBuilder.h:118
Edge * mFirstEdge
First edge of this face.
Definition ConvexHullBuilder.h:117
bool IsFacing(const Vec3 &inPosition) const
Check if face inFace is facing inPosition.
Definition ConvexHullBuilder.h:466
Definition ConvexHullBuilder.h:57
~ConvexHullBuilder()
Definition ConvexHullBuilder.h:506
EResult
Result enum that indicates how the hull got created.
Definition ConvexHullBuilder.h:138
@ TooFewFaces
Too few faces in the created hull (signifies precision errors during building)
@ Degenerate
Degenerate hull detected.
@ Success
Hull building finished successfully.
@ TooFewPoints
Too few points to create a hull.
@ MaxVerticesReached
Hull building finished successfully, but the desired accuracy was not reached because the max vertice...
int GetNumVerticesUsed() const
Returns the amount of vertices that are currently used by the hull.
Definition ConvexHullBuilder.h:855
std::span< const Vec3 > Positions
Definition ConvexHullBuilder.h:130
Array< int > ConflictList
Definition ConvexHullBuilder.h:91
bool ContainsFace(const Array< int > &inIndices) const
Returns true if the hull contains a polygon with inIndices (counter clockwise indices in mPositions)
Definition ConvexHullBuilder.h:871
void GetTriangles(Array< Vec3i > &outTris)
Definition ConvexHullBuilder.h:909
Array< Face * > Faces
Definition ConvexHullBuilder.h:131
EResult Initialize(int inMaxVertices, float inTolerance, const char *&outError)
Definition ConvexHullBuilder.h:512
const Faces & GetFaces() const
Access to the created faces. Memory is owned by the convex hull builder.
Definition ConvexHullBuilder.h:170
void TriangulatePoly(std::span< const Vec3 > inPositions, std::span< int > poly, ContainerType< Vec3i, Args... > &outTris)
Definition Triangulation.h:80
Definition AcousticMaterial.h:36
JPL_INLINE T * DefaultNew(Args &&...args)
Allocate new object from deafult global memory resource used in JPLSpatial.
Definition Memory.h:191
std::uint32_t uint32
Definition Core.h:311
JPL_INLINE auto GetX(const Vec3Type &v) noexcept
Definition Vec3Traits.h:35
JPL_INLINE auto GetZ(const Vec3Type &v) noexcept
Definition Vec3Traits.h:37
JPL_INLINE void DefaultDelete(T *object)
Delete object allcoated from deafult global memory resource. The object must had been allocated by ca...
Definition Memory.h:198
std::pmr::memory_resource * GetDefaultMemoryResource() noexcept
Definition Memory.h:42
Vec3 GetClosestPointOnLine(const Vec3 &inA, const Vec3 &inB, uint32_t &outSet)
Definition ClosestPoint.h:66
JPL_INLINE auto GetY(const Vec3Type &v) noexcept
Definition Vec3Traits.h:36