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