UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
CollisionConvexMesh.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "CoreMinimal.h"
6#include "Chaos/Box.h"
7#include "TriangleMesh.h"
8#include "Particles.h"
9#include "ChaosLog.h"
12
13#define DEBUG_HULL_GENERATION 0
14
15// Those flags allow to output geometric data in OBJ compatible format
16// INSTRUCTIONS:
17// Get the data form the log, remove the log header part and save it to a .obj file,
18// in a 3D viewer or DCC ( Windows 3D Viewer, Maya, Blender ... ), open/import the .obj file
19// WARNING:
20// - this needs DEBUG_HULL_GENERATION to also be enabled
21// - this may produce a lot of data and slow down levels have assets with a lot of convexes
22#define DEBUG_HULL_GENERATION_HULLSTEPS_TO_OBJ 0
23#define DEBUG_HULL_GENERATION_BUILDHORIZON_TO_OBJ 0
24
25namespace Chaos
26{
27 // When encountering a triangle or quad in hull generation (3-points or 4 coplanar points) we will instead generate
28 // a prism with a small thickness to emulate the desired result as a hull. Otherwise hull generation will fail on
29 // these cases. Verbose logging on LogChaos will point out when this path is taken for further scrutiny about
30 // the geometry
31 static constexpr FRealSingle TriQuadPrismInflation() { return 0.1f; }
32 static constexpr FRealSingle DefaultHorizonEpsilon() { return 0.1f; }
33
35 {
36 public:
37
42
44 {
45 Default = 0, // use what is set as default from cvars
46 Original = 1, // original method, fast but has issues
47 ConvexHull3 = 2,// new method, slower but less issues
48 ConvexHull3Simplified = 3, // same as above new method, plus new simplification method
49 };
50
51 class Params
52 {
53 public:
55 : HorizonEpsilon(DefaultHorizonEpsilon())
56 {}
57
59 };
60
62 {
64 {
65 // legacy path, return the hardcoded default value
66 return DefaultHorizonEpsilon();
67 }
68
69 // Create a scaled epsilon for our input data set. FLT_EPSILON is the distance between 1.0 and the next value
70 // above 1.0 such that 1.0 + FLT_EPSILON != 1.0. Floats aren't equally disanced though so big or small numbers
71 // don't work well with it. Here we take the max absolute of each axis and scale that for a wider margin and
72 // use that to scale FLT_EPSILON to get a more relevant value.
74 const int32 NumVertices = InVertices.Num();
75
76 if (NumVertices <= 1)
77 {
78 return FLT_EPSILON;
79 }
80
81 for (int32 Index = 0; Index < NumVertices; ++Index)
82 {
84
85 MaxAxes[0] = FMath::Max(MaxAxes[0], PositionAbs[0]);
86 MaxAxes[1] = FMath::Max(MaxAxes[1], PositionAbs[1]);
87 MaxAxes[2] = FMath::Max(MaxAxes[2], PositionAbs[2]);
88 }
89
90 return 3.0f * (MaxAxes[0] + MaxAxes[1] + MaxAxes[2]) * FLT_EPSILON;
91 }
92
93 static bool IsValidTriangle(const FVec3Type& A, const FVec3Type& B, const FVec3Type& C, FVec3Type& OutNormal)
94 {
95 const FVec3Type BA = B - A;
96 const FVec3Type CA = C - A;
97 const FVec3Type Cross = FVec3Type::CrossProduct(BA, CA);
98 OutNormal = Cross.GetUnsafeNormal();
99 return Cross.Size() > 1e-4;
100 }
101
102 static bool IsValidTriangle(const FVec3Type& A, const FVec3Type& B, const FVec3Type& C)
103 {
104 FVec3Type Normal(0);
105 return IsValidTriangle(A, B, C, Normal);
106 }
107
108 static bool IsValidQuad(const FVec3Type& A, const FVec3Type& B, const FVec3Type& C, const FVec3Type& D, FVec3Type& OutNormal)
109 {
111 const FRealType DPointDistance = FMath::Abs(TriPlane.PlaneDot(D));
112 OutNormal = FVec3Type(TriPlane.X, TriPlane.Y, TriPlane.Z);
114 }
115
116 static bool IsPlanarShape(const TArray<FVec3Type>& InVertices, FVec3Type& OutNormal)
117 {
118 bool bResult = false;
119 const int32 NumVertices = InVertices.Num();
120
121 if (NumVertices <= 3)
122 {
123 // Nothing, point, line or triangle, not a planar set
124 return false;
125 }
126 else // > 3 points
127 {
129 OutNormal = FVec3Type(TriPlane.X, TriPlane.Y, TriPlane.Z);
130
131 for (int32 Index = 3; Index < NumVertices; ++Index)
132 {
133 const FRealType PointPlaneDot = FMath::Abs(TriPlane.PlaneDot(InVertices[Index]));
135 {
136 return false;
137 }
138 }
139 }
140
141 return true;
142 }
143
144 private:
145 class FMemPool;
146 struct FHalfEdge;
147 struct FConvexFace
148 {
149 FHalfEdge* FirstEdge;
150 FHalfEdge* ConflictList; //Note that these half edges are really just free verts grouped together
151 FPlaneType Plane;
152 FConvexFace* Prev;
153 FConvexFace* Next; //these have no geometric meaning, just used for book keeping
154 int32 PoolIdx;
155
156 private:
157 FConvexFace(int32 InPoolIdx, const FPlaneType& FacePlane)
158 : PoolIdx(InPoolIdx)
159 {
161 }
162
163 void Reset(const FPlaneType& FacePlane)
164 {
165 ConflictList = nullptr;
166 Plane = FacePlane;
167 }
168
169 // Required for TChunkedArray
170 FConvexFace() = default;
171 ~FConvexFace() = default;
172
173 friend FMemPool;
175 };
176
177 struct FHalfEdge
178 {
182 FHalfEdge* Twin;
183 FConvexFace* Face;
184 int32 PoolIdx;
185
186 private:
188 : PoolIdx(InPoolIdx)
189 {
191 }
192
193 void Reset(int32 InVertex)
194 {
196 }
197
198 // Required for TChunkedArray
199 FHalfEdge() = default;
200 ~FHalfEdge() = default;
201
202 friend FMemPool;
204 };
205
206 class FMemPool
207 {
208 public:
209 FHalfEdge* AllocHalfEdge(int32 InVertex =-1)
210 {
211 if(HalfEdgesFreeIndices.Num())
212 {
213 const uint32 Idx = HalfEdgesFreeIndices.Pop(EAllowShrinking::No);
214 FHalfEdge* FreeHalfEdge = &HalfEdges[Idx];
215 FreeHalfEdge->Reset(InVertex);
216 ensure(FreeHalfEdge->PoolIdx == Idx);
217 return FreeHalfEdge;
218 }
219 else
220 {
221 int32 Idx = HalfEdges.AddElement(FHalfEdge(HalfEdges.Num(), InVertex));
222 return &HalfEdges[Idx];
223 }
224 }
225
226 FConvexFace* AllocConvexFace(const FPlaneType& FacePlane)
227 {
228 if(FacesFreeIndices.Num())
229 {
230 const uint32 Idx = FacesFreeIndices.Pop(EAllowShrinking::No);
231 FConvexFace* FreeFace = &Faces[Idx];
232 FreeFace->Reset(FacePlane);
233 ensure(FreeFace->PoolIdx == Idx);
234 return FreeFace;
235 }
236 else
237 {
238 int32 Idx = Faces.AddElement(FConvexFace(Faces.Num(), FacePlane));
239 return &Faces[Idx];
240 }
241 }
242
243 void FreeHalfEdge(FHalfEdge* HalfEdge)
244 {
245 HalfEdgesFreeIndices.Add(HalfEdge->PoolIdx);
246 }
247
248 void FreeConvexFace(FConvexFace* Face)
249 {
250 FacesFreeIndices.Add(Face->PoolIdx);
251 }
252
253 private:
254 TArray<int32> HalfEdgesFreeIndices;
255 TChunkedArray<FHalfEdge> HalfEdges;
256
257 TArray<int32> FacesFreeIndices;
259 };
260
261 public:
262
264
266
268
270
272 {
273 TRACE_CPUPROFILER_EVENT_SCOPE(Chaos::BuildConvexHull);
274
275 OutIndices.Reset();
276 FMemPool Pool;
277 FConvexFace* Faces = BuildInitialHull(Pool, InVertices);
278 if(Faces == nullptr)
279 {
280 return;
281 }
282
283#if DEBUG_HULL_GENERATION
284 FString InitialFacesString(TEXT("Generated Initial Hull: "));
285 FConvexFace* Current = Faces;
286 while(Current)
287 {
288 InitialFacesString += FString::Printf(TEXT("(%d %d %d) "), Current->FirstEdge->Vertex, Current->FirstEdge->Next->Vertex, Current->FirstEdge->Prev->Vertex);
289 Current = Current->Next;
290 }
291 UE_LOG(LogChaos, VeryVerbose, TEXT("%s"), *InitialFacesString);
292#endif
293
294 FConvexFace* DummyFace = Pool.AllocConvexFace(Faces->Plane);
295 DummyFace->Prev = nullptr;
296 DummyFace->Next = Faces;
297 Faces->Prev = DummyFace;
298
299 FHalfEdge* ConflictV = FindConflictVertex(InVertices, DummyFace->Next);
300 while(ConflictV)
301 {
302
303#if DEBUG_HULL_GENERATION
304#if DEBUG_HULL_GENERATION_HULLSTEPS_TO_OBJ
305 UE_LOG(LogChaos, VeryVerbose, TEXT("# ======================================================"));
306 const FVec3Type ConflictPos = InVertices[ConflictV->Vertex];
307 UE_LOG(LogChaos, VeryVerbose, TEXT("# GENERATED HULL before adding Vtx %d (%f %f %f)"), ConflictV->Vertex, ConflictPos.X, ConflictPos.Y, ConflictPos.Z);
308 UE_LOG(LogChaos, VeryVerbose, TEXT("# ------------------------------------------------------"));
309 FConvexFace* Face = DummyFace->Next;
310 while (Face)
311 {
312 const FVector P1 = InVertices[Face->FirstEdge->Prev->Vertex];
313 const FVector P2 = InVertices[Face->FirstEdge->Next->Vertex];
314 const FVector P3 = InVertices[Face->FirstEdge->Vertex];
315 UE_LOG(LogChaos, VeryVerbose, TEXT("v %f %f %f"), P1.X, P1.Y, P1.Z);
316 UE_LOG(LogChaos, VeryVerbose, TEXT("v %f %f %f"), P2.X, P2.Y, P2.Z);
317 UE_LOG(LogChaos, VeryVerbose, TEXT("v %f %f %f"), P3.X, P3.Y, P3.Z);
318 UE_LOG(LogChaos, VeryVerbose, TEXT("f -3 -2 -1"));
319 Face = Face->Next;
320 }
321#endif
322#endif
323
324 if (!AddVertex(Pool, InVertices, ConflictV, InParams))
325 {
326 // AddVertex failed to process the conflict vertex --
327 // subsequent calls to FindConflictVertex will just keep finding the same one,
328 // so all we can do here is fail to build a convex hull at all
329 return;
330 }
331 ConflictV = FindConflictVertex(InVertices, DummyFace->Next);
332 }
333
334 FConvexFace* Cur = DummyFace->Next;
335 while(Cur)
336 {
337 //todo(ocohen): this assumes faces are triangles, not true once face merging is added
338 OutIndices.Add(TVec3<int32>(Cur->FirstEdge->Vertex, Cur->FirstEdge->Next->Vertex, Cur->FirstEdge->Next->Next->Vertex));
339 FConvexFace* Next = Cur->Next;
340 Cur = Next;
341 }
342 }
343
345 {
346 TArray<TVec3<int32>> Indices;
347 BuildConvexHull(InVertices, Indices);
348 return FTriangleMesh(MoveTemp(Indices));
349 }
350
351 static bool IsPerformanceWarning(int32 NumPlanes, int32 NumVertices)
352 {
354 {
355 return false;
356 }
357
358 return (NumVertices > VerticesThreshold);
359 }
360
362 {
363 return (PerformGeometryReduction>0)?true:false;
364 }
365
366 static FString PerformanceWarningString(int32 NumPlanes, int32 NumVertices)
367 {
368 return FString::Printf(TEXT("Planes %d, Vertices %d"), NumPlanes, NumVertices);
369 }
370
372 {
373 struct TPair
374 {
375 TPair() : A(-1), B(-1) {}
376 uint32 A;
377 uint32 B;
378 };
379
383
384 int32 Size = InOutVertices.Num();
386
389 IsDeleted.Init(false, Size);
390
391 if (NumToDelete > 0)
392 {
393 for (uint32 Iteration = 0; Iteration < (uint32)NumToDelete; Iteration++)
394 {
397
398 for (int32 A = 0; A < (Size - 1); A++)
399 {
400 if (!IsDeleted[A])
401 {
402 for (int32 B = A + 1; B < Size; B++)
403 {
404 if (!IsDeleted[B])
405 {
406 FVec3Type Vec = Vertices[A] - Vertices[B];
407 FRealType LengthSqr = Vec.SizeSquared();
409 {
411 ClosestPair.A = A;
412 ClosestPair.B = B;
413 }
414 }
415 }
416 }
417 }
418
419 if (ClosestPair.A != -1)
420 {
421 // merge to mid point
422 Vertices[ClosestPair.A] = Vertices[ClosestPair.A] + (Vertices[ClosestPair.B] - Vertices[ClosestPair.A]) * 0.5f;
423 IsDeleted[ClosestPair.B] = true;
424 }
425 }
426 }
427
429 for (int Idx = 0; Idx < Vertices.Num(); Idx++)
430 {
431 // Only add vertices that have not been merged away
432 if (!IsDeleted[Idx])
433 {
434 TmpVertices.Add(Vertices[Idx]);
435 }
436 }
437
439 check(InOutVertices.Num() > 3);
440 }
441
442 static bool IsFaceOutlineConvex(const FPlaneType& Plane, const TArray<int32>& Indices, const TArray<FVec3Type>& Vertices)
443 {
445 Signs.SetNum(Indices.Num());
446
447 if (Indices.Num() < 4)
448 {
449 return true;
450 }
451
452 for (int32 PointIndex = 0; PointIndex < Indices.Num(); PointIndex++)
453 {
454 const int32 Index0 = Indices[PointIndex];
455 const int32 Index1 = Indices[(PointIndex + 1) % Indices.Num()];
456 const int32 Index2 = Indices[(PointIndex + 2) % Indices.Num()];
457
458 const FVec3Type Point0 = Vertices[Index0];
459 const FVec3Type Point1 = Vertices[Index1];
460 const FVec3Type Point2 = Vertices[Index2];
461
462 const FVec3Type Segment0(Point1 - Point0);
463 const FVec3Type Segment1(Point2 - Point1);
464
465 const FVec3Type Cross = FVec3Type::CrossProduct(Segment0, Segment1);
466 const FRealType Dot = FVec3Type::DotProduct(Cross, Plane.Normal());
467
468 Signs[PointIndex] = static_cast<int8>(FMath::Sign(Dot));
469 }
470
471 int8 RefSign = 0;
472 for (const int8 Sign : Signs)
473 {
474 if (RefSign == 0)
475 {
476 RefSign = Sign;
477 }
478 if (Sign != 0 && RefSign != Sign)
479 {
480 return false;
481 }
482 }
483
484 return true;
485 }
486
487 // remove any invalid faces ( less than 3 vertices )
489 {
490 int32 PlaneIndex = 0;
491 while (PlaneIndex < InOutPlanes.Num())
492 {
493 if (InOutFaceVertexIndices[PlaneIndex].Num() < 3)
494 {
495 InOutFaceVertexIndices.RemoveAtSwap(PlaneIndex);
496 InOutPlanes.RemoveAtSwap(PlaneIndex);
497 }
498 else
499 {
500 ++PlaneIndex;
501 }
502 }
503 }
504
505 // Convert multi-triangle faces to single n-gons
507 {
508 const FRealType NormalThreshold = 1.e-4f;
509
510 // Find planes with equal normal within the threshold and merge them
512 {
515
517 {
520
521 // First similarity test: normals are close - this will reject all very dissimilar faces
522 const FRealType PlaneNormalDot = FVec3Type::DotProduct(Plane0.Normal(), Plane1.Normal());
523 if (PlaneNormalDot > 1.0f - NormalThreshold)
524 {
525 // Second similarity test: vertices of one plane are within threshold distance of the other. This is slower but more accurate
526 bool bWithinDistanceThreshold = true;
528 {
529 const FVec3Type Plane0Vertex = Vertices[Plane0VertexIndex];
530 const FRealType Plane0VertexDistance = FMath::Abs(FVec3Type::DotProduct(Plane1.X() - Plane0Vertex, Plane1.Normal()));
531 if (Plane0VertexDistance > DistanceThreshold)
532 {
534 break;
535 }
536 }
538 {
540 {
541 const FVec3Type Plane1Vertex = Vertices[Plane1VertexIndex];
542 const FRealType Plane1VertexDistance = FMath::Abs(FVec3Type::DotProduct(Plane0.X() - Plane1Vertex, Plane0.Normal()));
543 if (Plane1VertexDistance > DistanceThreshold)
544 {
546 break;
547 }
548 }
549 }
550
552 {
553 // Merge the verts from the second plane into the first
554 for (int32 VertexIndex1 = 0; VertexIndex1 < Vertices1.Num(); ++VertexIndex1)
555 {
556 Vertices0.AddUnique(Vertices1[VertexIndex1]);
557 }
558
559 // Erase the second plane
562 --PlaneIndex1;
563 }
564 }
565 }
566 }
567
568 // Re-order the face vertices to form the face half-edges
570 {
572#if DEBUG_HULL_GENERATION
574#endif
575 }
576
578 }
579
580 // Find edge pairs that are colinear and remove the unnecessary vertex to make a single edge.
581 // If we are left with an invalid face (2 verts or less), remove it.
582 // NOTE: a vertex in the middle of two colinear edges on one face may still be required by some other face,
583 // although if we are lucky those faces would have been merged by the MergeFaces() function, depending on the tolerances used
585 {
587
588 // This array maps from the input vertex to the output vertex array. INDEX_NONE means removed.
591 for (int32& VertexIndex : VertexIndexMap)
592 {
593 VertexIndex = INDEX_NONE;
594 }
595
596 // See if we have any co-linear edges in any faces, where the center vertex
597 // is not required by some other face. Assume we already removed coincident vertices.
598 // NOTE: after this loop the vertex index map contains INDEX_NONE for items to remove
599 // and other vertex has its original index. We pack and re-index later.
601 {
603 if (FaceVertexIndices.Num() > 2)
604 {
605 // Visit all set of 3-vertex chains in the face
606 int32 VertexIndex0 = FaceVertexIndices[FaceVertexIndices.Num() - 2];
607 int32 VertexIndex1 = FaceVertexIndices[FaceVertexIndices.Num() - 1];
609 {
611
612 // Calculate the sine of the angle between the two edges formed by the 3 vertices
613 const FVec3 Edge0 = (InOutVertices[VertexIndex1] - InOutVertices[VertexIndex0]).GetSafeNormal();
614 const FVec3 Edge1 = (InOutVertices[VertexIndex2] - InOutVertices[VertexIndex1]).GetSafeNormal();
615 const FReal CosAngle = FVec3::DotProduct(Edge0, Edge1);
616
617 // See if we need the vertex.
618 if (CosAngle < (FReal(1) - AngleToleranceCos))
619 {
620 VertexIndexMap[VertexIndex1] = VertexIndex1;
621 }
622
623 // Move to next edge pair
624 VertexIndex0 = VertexIndex1;
625 VertexIndex1 = VertexIndex2;
626 }
627 }
628 }
629
630 // Remove unused vertices from all faces and update the index map to account for removals
632 for (int32 VertexIndex = 0; VertexIndex < InOutVertices.Num(); ++VertexIndex)
633 {
634 if (VertexIndexMap[VertexIndex] == INDEX_NONE)
635 {
636 // Remove vertices we don't need from faces that use them
637 // If we end up with less than 3 verts, remove the face
639 {
640 InOutFaceVertexIndices[PlaneIndex0].Remove(VertexIndex);
642 {
644 InOutPlanes.RemoveAt(PlaneIndex0);
645 }
646 }
648 }
649 else
650 {
651 VertexIndexMap[VertexIndex] = VertexIndex - NumVerticesRemoved;
652 }
653 }
654
655 if (NumVerticesRemoved > 0)
656 {
657 // Remove unused verts from the array
658 for (int32 VertexIndex = InOutVertices.Num() - 1; VertexIndex >= 0; --VertexIndex)
659 {
660 if (VertexIndexMap[VertexIndex] == INDEX_NONE)
661 {
662 InOutVertices.RemoveAt(VertexIndex);
663 }
664 }
665
666 // Remap vertex indices in all faces
668 {
671 {
673 }
674 }
675 }
676 }
677
678 // IMPORTANT : vertices are assumed to be sorted CCW
680 {
681 // we let 3 points faces being processed as there may be colinear cases that will result n invalid faces out of this function?
682 if (InOutFaceVertexIndices.Num() < 3)
683 {
684 return;
685 }
686 // find furthest point from centroid as it is garanteed to be part of the convex hull
687 int32 StartIndex = 0;
689 for (int32 Index = 0; Index < InOutFaceVertexIndices.Num(); ++Index)
690 {
691 const FRealType SquaredDistance = (Vertices[InOutFaceVertexIndices[Index]] - Centroid).SquaredLength();
692 if (SquaredDistance > FurthestSquaredDistance)
693 {
694 StartIndex = Index;
695 FurthestSquaredDistance = SquaredDistance;
696 }
697 }
698
700
701 struct FPointSegment
702 {
705 };
706
707 const int32 VtxStartIndex = InOutFaceVertexIndices[StartIndex];
709 int32 VtxIndex1 = InOutFaceVertexIndices[(StartIndex + 1) % VtxCount];
710
712 Stack.Push({ VtxIndex0, FVec3Type{0} });
713 Stack.Push({ VtxIndex1, Vertices[VtxIndex1] - Vertices[VtxIndex0] });
714
715 int32 Step = 2; // we already processed the two first
716 while (Step <= VtxCount)
717 {
718 const int32 NextIndex = (StartIndex + Step) % VtxCount;
719
720 const FPointSegment& LastOnStack = Stack.Last();
721 VtxIndex0 = LastOnStack.VtxIndex;
723 const FVec3Type Segment = Vertices[VtxIndex1] - Vertices[VtxIndex0];
724
725 const FVec3 Cross = FVec3Type::CrossProduct(LastOnStack.Segment, Segment);
726 if (FVec3Type::DotProduct(Cross, Face.Normal()) >= 0)
727 {
729 {
730 Stack.Push({ VtxIndex1, Segment });
731 }
732 Step++;
733 }
734 else
735 {
736 Stack.Pop();
737 }
738 }
739
740 // faces where all points are on the same line may produce 2 points faces after removal
741 // in that case let's keep the entire face empty as a sign this should be trimmed
743 if (Stack.Num() >= 3)
744 {
745 for (const FPointSegment& PointSegment : Stack)
746 {
748 }
749 }
750 }
751
752 // Reorder the vertices to be counter-clockwise about the normal
754 {
755 const FMatrix33 FaceMatrix = (FMatrix44f)FRotationMatrix::MakeFromZ(FVector(Face.Normal()));
756
757 // [2, -2] based on clockwise angle about the normal
758 auto VertexScore = [&Centroid, &FaceMatrix, &Vertices](int32 VertexIndex)
759 {
760 const FVec3Type CentroidOffsetDir = (Vertices[VertexIndex] - Centroid).GetSafeNormal();
761 const FRealType DotX = FVec3Type::DotProduct(CentroidOffsetDir, FaceMatrix.GetAxis(0));
762 const FRealType DotY = FVec3Type::DotProduct(CentroidOffsetDir, FaceMatrix.GetAxis(1));
763 const FRealType Score = (DotX >= 0.0f) ? 1.0f + DotY : -1.0f - DotY;
764 return Score;
765 };
766
768 {
770 };
771
773 }
774
775 // make sure faces have CCW winding, remove inside points
777 {
778 // compute centroid as this is needed for both sorting and removing inside points
779 FVec3Type Centroid(0);
780 for (int32 VertexIndex = 0; VertexIndex < InOutFaceVertexIndices.Num(); ++VertexIndex)
781 {
782 Centroid += Vertices[InOutFaceVertexIndices[VertexIndex]];
783 }
784 Centroid /= FRealType(InOutFaceVertexIndices.Num());
785
786 SortFaceVerticesCCW(Face, InOutFaceVertexIndices, Vertices, Centroid);
787
789 }
790
791 // Generate the vertex indices for all planes in CCW order (used to serialize old data that did not have structure data)
793 {
794 OutFaceVertexIndices.Reset(InPlanes.Num());
795 for (int32 PlaneIndex = 0; PlaneIndex < InPlanes.Num(); ++PlaneIndex)
796 {
797 for (int32 VertexIndex = 0; VertexIndex < Vertices.Num(); ++VertexIndex)
798 {
799 const FRealType PlaneVertexDistance = FVec3Type::DotProduct(InPlanes[PlaneIndex].Normal(), Vertices[VertexIndex] - InPlanes[PlaneIndex].X());
800 if (FMath::Abs(PlaneVertexDistance) < DistanceTolerance)
801 {
802 OutFaceVertexIndices[PlaneIndex].Add(VertexIndex);
803 }
804 }
805
806 FinalizeFaces(InPlanes[PlaneIndex], OutFaceVertexIndices[PlaneIndex], Vertices);
807 }
809 }
810
811 // CVars variables for controlling geometry complexity checking and simplification
818
819 private:
820
821 static FVec3Type ComputeFaceNormal(const FVec3Type& A, const FVec3Type& B, const FVec3Type& C)
822 {
823 return FVec3Type::CrossProduct((B - A), (C - A));
824 }
825
826 static FConvexFace* CreateFace(FMemPool& Pool, const TArray<FVec3Type>& InVertices, FHalfEdge* RS, FHalfEdge* ST, FHalfEdge* TR)
827 {
828 RS->Prev = TR;
829 RS->Next = ST;
830 ST->Prev = RS;
831 ST->Next = TR;
832 TR->Prev = ST;
833 TR->Next = RS;
834 FVec3Type RSTNormal = ComputeFaceNormal(InVertices[RS->Vertex], InVertices[ST->Vertex], InVertices[TR->Vertex]);
835 const FRealType RSTNormalSize = RSTNormal.Size();
836 if (RSTNormalSize > 0)
837 {
839 }
840 else
841 {
842 // degenerated face, use a valid neighbor face normal
843 RSTNormal = RS->Twin->Face->Plane.Normal();
844 }
845 FConvexFace* RST = Pool.AllocConvexFace(FPlaneType(InVertices[RS->Vertex], RSTNormal));
846 RST->FirstEdge = RS;
847 RS->Face = RST;
848 ST->Face = RST;
849 TR->Face = RST;
850 return RST;
851 }
852
853 static void StealConflictList(FMemPool& Pool, const TArray<FVec3Type>& InVertices, FHalfEdge* OldList, FConvexFace** Faces, int32 NumFaces)
854 {
855 FHalfEdge* Cur = OldList;
856 while(Cur)
857 {
858 FRealType MaxD = 1e-4f;
859 int32 MaxIdx = -1;
860 for(int32 Idx = 0; Idx < NumFaces; ++Idx)
861 {
862 FRealType Distance = Faces[Idx]->Plane.SignedDistance(InVertices[Cur->Vertex]);
863 if(Distance > MaxD)
864 {
865 MaxD = Distance;
866 MaxIdx = Idx;
867 }
868 }
869
870 bool bDeleteVertex = MaxIdx == -1;
871 if(!bDeleteVertex)
872 {
873 //let's make sure faces created with this new conflict vertex will be valid. The plane check above is not sufficient because long thin triangles will have a plane with its point at one of these. Combined with normal and precision we can have errors
875 return FVec3Type::CrossProduct(InVertices[B->Vertex] - InVertices[A->Vertex], InVertices[C->Vertex] - InVertices[A->Vertex]).SizeSquared();
876 };
877 FHalfEdge* Edge = Faces[MaxIdx]->FirstEdge;
878 do
879 {
880 if(PretendNormal(Edge->Prev, Edge, Cur) < 1e-4)
881 {
882 bDeleteVertex = true;
883 break;
884 }
885 Edge = Edge->Next;
886 } while(Edge != Faces[MaxIdx]->FirstEdge);
887 }
888
889 if(!bDeleteVertex)
890 {
891 FHalfEdge* Next = Cur->Next;
892 FHalfEdge*& ConflictList = Faces[MaxIdx]->ConflictList;
893 if(ConflictList)
894 {
895 ConflictList->Prev = Cur;
896 }
897 Cur->Next = ConflictList;
898 ConflictList = Cur;
899 Cur->Prev = nullptr;
900 Cur = Next;
901 }
902 else
903 {
904 //point is contained, we can delete it
905 FHalfEdge* Next = Cur->Next;
906 Pool.FreeHalfEdge(Cur);
907 Cur = Next;
908 }
909 }
910 }
911
912 static FConvexFace* BuildInitialHull(FMemPool& Pool, const TArray<FVec3Type>& InVertices)
913 {
914 if (InVertices.Num() < 4) //not enough points
915 {
916 return nullptr;
917 }
918
919 constexpr FRealType Epsilon = 1e-4f;
920
921 const int32 NumVertices = InVertices.Num();
922
923 //We store the vertex directly in the half-edge. We use its next to group free vertices by context list
924 //create a starting triangle by finding min/max on X and max on Y
927 FHalfEdge* A = nullptr; //min x
928 FHalfEdge* B = nullptr; //max x
929 FHalfEdge* DummyHalfEdge = Pool.AllocHalfEdge(-1);
930 DummyHalfEdge->Prev = nullptr;
931 DummyHalfEdge->Next = nullptr;
933
934 for (int32 i = 0; i < NumVertices; ++i)
935 {
936 FHalfEdge* VHalf = Pool.AllocHalfEdge(i); //todo(ocohen): preallocate these
937 Prev->Next = VHalf;
938 VHalf->Prev = Prev;
939 VHalf->Next = nullptr;
940 const FVec3Type& V = InVertices[i];
941
942 if(V[0] < MinX)
943 {
944 MinX = V[0];
945 A = VHalf;
946 }
947 if(V[0] > MaxX)
948 {
949 MaxX = V[0];
950 B = VHalf;
951 }
952
953 Prev = VHalf;
954 }
955
956 check(A && B);
957 if (A == B || (InVertices[A->Vertex] - InVertices[B->Vertex]).SizeSquared() < Epsilon) //infinitely thin
958 {
959 return nullptr;
960 }
961
962 //remove A and B from conflict list
963 A->Prev->Next = A->Next;
964 if(A->Next)
965 {
966 A->Next->Prev = A->Prev;
967 }
968 B->Prev->Next = B->Next;
969 if(B->Next)
970 {
971 B->Next->Prev = B->Prev;
972 }
973
974 //find C so that we get the biggest base triangle
976 const FVec3Type AToB = InVertices[B->Vertex] - InVertices[A->Vertex];
977 FHalfEdge* C = nullptr;
978 for(FHalfEdge* V = DummyHalfEdge->Next; V; V = V->Next)
979 {
980 FRealType TriSize = FVec3Type::CrossProduct(AToB, InVertices[V->Vertex] - InVertices[A->Vertex]).SizeSquared();
981 if(TriSize > MaxTriSize)
982 {
984 C = V;
985 }
986 }
987
988 if(C == nullptr) //biggest triangle is tiny
989 {
990 return nullptr;
991 }
992
993 //remove C from conflict list
994 C->Prev->Next = C->Next;
995 if(C->Next)
996 {
997 C->Next->Prev = C->Prev;
998 }
999
1000 //find farthest D along normal
1001 const FVec3Type AToC = InVertices[C->Vertex] - InVertices[A->Vertex];
1002 const FVec3Type Normal = FVec3Type::CrossProduct(AToB, AToC).GetSafeNormal();
1003
1006 FHalfEdge* PosD = nullptr;
1007 FHalfEdge* NegD = nullptr;
1008 for(FHalfEdge* V = DummyHalfEdge->Next; V; V = V->Next)
1009 {
1010 FRealType Dot = FVec3Type::DotProduct(InVertices[V->Vertex] - InVertices[A->Vertex], Normal);
1011 if(Dot > MaxPosDistance)
1012 {
1014 PosD = V;
1015 }
1016 if(-Dot > MaxNegDistance)
1017 {
1019 NegD = V;
1020 }
1021 }
1022
1023 if(MaxNegDistance == Epsilon && MaxPosDistance == Epsilon)
1024 {
1025 return nullptr; //plane
1026 }
1027
1029 FHalfEdge* D = bPositive ? PosD : NegD;
1030 check(D);
1031
1032 //remove D from conflict list
1033 D->Prev->Next = D->Next;
1034 if(D->Next)
1035 {
1036 D->Next->Prev = D->Prev;
1037 }
1038
1039 //we must now create the 3 faces. Face must be oriented CCW around normal and positive normal should face out
1040 //Note we are now using A,B,C,D as edges. We can only use one edge per face so once they're used we'll need new ones
1041 FHalfEdge* Edges[4] = {A, B, C, D};
1042
1043 //The base is a plane with Edges[0], Edges[1], Edges[2]. The order depends on which side D is on
1044 if(bPositive)
1045 {
1046 //D is on the positive side of Edges[0], Edges[1], Edges[2] so we must reorder it
1047 FHalfEdge* Tmp = Edges[0];
1048 Edges[0] = Edges[1];
1049 Edges[1] = Tmp;
1050 }
1051
1052 FConvexFace* Faces[4];
1053 Faces[0] = CreateFace(Pool, InVertices, Edges[0], Edges[1], Edges[2]); //base
1054 Faces[1] = CreateFace(Pool, InVertices, Pool.AllocHalfEdge(Edges[1]->Vertex), Pool.AllocHalfEdge(Edges[0]->Vertex), Edges[3]);
1055 Faces[2] = CreateFace(Pool, InVertices, Pool.AllocHalfEdge(Edges[0]->Vertex), Pool.AllocHalfEdge(Edges[2]->Vertex), Pool.AllocHalfEdge(Edges[3]->Vertex));
1056 Faces[3] = CreateFace(Pool, InVertices, Pool.AllocHalfEdge(Edges[2]->Vertex), Pool.AllocHalfEdge(Edges[1]->Vertex), Pool.AllocHalfEdge(Edges[3]->Vertex));
1057
1058 auto MakeTwins = [](FHalfEdge* E1, FHalfEdge* E2) {
1059 E1->Twin = E2;
1060 E2->Twin = E1;
1061 };
1062 //mark twins so half edge can cross faces
1063 MakeTwins(Edges[0], Faces[1]->FirstEdge); //0-1 1-0
1064 MakeTwins(Edges[1], Faces[3]->FirstEdge); //1-2 2-1
1065 MakeTwins(Edges[2], Faces[2]->FirstEdge); //2-0 0-2
1066 MakeTwins(Faces[1]->FirstEdge->Next, Faces[2]->FirstEdge->Prev); //0-3 3-0
1067 MakeTwins(Faces[1]->FirstEdge->Prev, Faces[3]->FirstEdge->Next); //3-1 1-3
1068 MakeTwins(Faces[2]->FirstEdge->Next, Faces[3]->FirstEdge->Prev); //2-3 3-2
1069
1070 Faces[0]->Prev = nullptr;
1071 for(int i = 1; i < 4; ++i)
1072 {
1073 Faces[i - 1]->Next = Faces[i];
1074 Faces[i]->Prev = Faces[i - 1];
1075 }
1076 Faces[3]->Next = nullptr;
1077
1078 //split up the conflict list
1079 StealConflictList(Pool, InVertices, DummyHalfEdge->Next, Faces, 4);
1080 return Faces[0];
1081 }
1082
1083 static FHalfEdge* FindConflictVertex(const TArray<FVec3Type>& InVertices, FConvexFace* FaceList)
1084 {
1085 UE_CLOG(DEBUG_HULL_GENERATION, LogChaos, VeryVerbose, TEXT("Finding conflict vertex"));
1086
1087 for(FConvexFace* CurFace = FaceList; CurFace; CurFace = CurFace->Next)
1088 {
1089 UE_CLOG(DEBUG_HULL_GENERATION, LogChaos, VeryVerbose, TEXT("\tTesting Face (%d %d %d)"), CurFace->FirstEdge->Vertex, CurFace->FirstEdge->Next->Vertex, CurFace->FirstEdge->Prev->Vertex);
1090
1092 FHalfEdge* MaxV = nullptr;
1094 {
1095 //is it faster to cache this from stealing stage?
1096 FRealType Dist = FVec3Type::DotProduct(InVertices[CurFaceVertex->Vertex], CurFace->Plane.Normal());
1097 if(Dist > MaxD)
1098 {
1099 MaxD = Dist;
1101 }
1102 }
1103
1104 UE_CLOG((DEBUG_HULL_GENERATION && !MaxV), LogChaos, VeryVerbose, TEXT("\t\tNo Conflict List"));
1105 UE_CLOG((DEBUG_HULL_GENERATION && MaxV), LogChaos, VeryVerbose, TEXT("\t\tFound %d at distance %f"), MaxV->Vertex, MaxD);
1106
1107 check(CurFace->ConflictList == nullptr || MaxV);
1108 if(MaxV)
1109 {
1110 if(MaxV->Prev)
1111 {
1112 MaxV->Prev->Next = MaxV->Next;
1113 }
1114 if(MaxV->Next)
1115 {
1116 MaxV->Next->Prev = MaxV->Prev;
1117 }
1118 if(MaxV == CurFace->ConflictList)
1119 {
1120 CurFace->ConflictList = MaxV->Next;
1121 }
1122 MaxV->Face = CurFace;
1123 return MaxV;
1124 }
1125 }
1126
1127 return nullptr;
1128 }
1129
1131 {
1132#if DEBUG_HULL_GENERATION
1133 UE_LOG(LogChaos, VeryVerbose, TEXT("Generate horizon - START"));
1134#endif
1135 //We must flood fill from the initial face and mark edges of faces the conflict vertex cannot see
1136 //In order to return a CCW ordering we must traverse each face in CCW order from the edge we crossed over
1137 //This should already be the ordering in the half edge
1138 const FRealType Epsilon = InParams.HorizonEpsilon;
1139 const FVec3Type V = InVertices[ConflictV->Vertex];
1140 TSet<FConvexFace*> Processed;
1141 TArray<FHalfEdge*> Queue;
1142 check(ConflictV->Face);
1143 Queue.Add(ConflictV->Face->FirstEdge->Prev); //stack pops so reverse order
1144 Queue.Add(ConflictV->Face->FirstEdge->Next);
1145 Queue.Add(ConflictV->Face->FirstEdge);
1146 FacesToDelete.Add(ConflictV->Face);
1147 while(Queue.Num())
1148 {
1149#if DEBUG_HULL_GENERATION
1150 FString QueueString(TEXT("\t Queue ("));
1151 for (const FHalfEdge* QueuedEdge : Queue)
1152 {
1153 QueueString += FString::Printf(TEXT(" [%d - %d] "), QueuedEdge->Vertex, QueuedEdge->Next->Vertex);
1154 }
1155 QueueString += TEXT(")");
1156 UE_LOG(LogChaos, VeryVerbose, TEXT("%s"), *QueueString);
1157#endif
1158
1160 Processed.Add(Edge->Face);
1161 FHalfEdge* Twin = Edge->Twin;
1162 FConvexFace* NextFace = Twin->Face;
1163
1164#if DEBUG_HULL_GENERATION
1165 UE_LOG(LogChaos, VeryVerbose, TEXT("\tPop edge [%d - %d] from queue"), Edge->Vertex, Edge->Next->Vertex);
1166#endif
1167
1168 if(Processed.Contains(NextFace))
1169 {
1170#if DEBUG_HULL_GENERATION
1171 UE_LOG(LogChaos, VeryVerbose, TEXT("\tTwin Face [%d] already processed - skip"), NextFace);
1172#endif
1173 continue;
1174 }
1175 const FRealType Distance = NextFace->Plane.SignedDistance(V);
1176 if(Distance > Epsilon)
1177 {
1178#if DEBUG_HULL_GENERATION
1179 UE_LOG(LogChaos, VeryVerbose, TEXT("\tDistance [%f] > Epsilon [%f] - add to queue"), Distance, Epsilon);
1180#endif
1181 Queue.Add(Twin->Prev); //stack pops so reverse order
1182 Queue.Add(Twin->Next);
1184 }
1185 else
1186 {
1187#if DEBUG_HULL_GENERATION
1188 UE_LOG(LogChaos, VeryVerbose, TEXT("\tAdd [%d - %d] to horizon "), Edge->Vertex, Edge->Next->Vertex);
1189#endif
1190 // we need to ensure that the horizon is a continous edge loop
1191 // this may get the wrong edge path, but way more roibust than not testing this
1192 if (HorizonEdges.Num() == 0 || Edge->Vertex == HorizonEdges[HorizonEdges.Num()-1]->Next->Vertex)
1193 {
1194 HorizonEdges.Add(Edge);
1195 }
1196 else
1197 {
1198#if DEBUG_HULL_GENERATION
1199 UE_LOG(LogChaos, VeryVerbose, TEXT("\tNON VALID EDGE LOOP - internal horizon edge detected [%d - %d] - skipping "), Edge->Vertex, Edge->Next->Vertex);
1200#endif
1201 }
1202 }
1203 }
1204
1205#if DEBUG_HULL_GENERATION
1206#if DEBUG_HULL_GENERATION_BUILDHORIZON_TO_OBJ
1207 UE_LOG(LogChaos, VeryVerbose, TEXT("# ======================================================"));
1208 const FVec3Type ConflictPos = InVertices[ConflictV->Vertex];
1209 UE_LOG(LogChaos, VeryVerbose, TEXT("# BUILD_HORIZON - Conflict Vertex = %d (%f %f %f)"), ConflictV->Vertex, ConflictPos.X, ConflictPos.Y, ConflictPos.Z);
1210 UE_LOG(LogChaos, VeryVerbose, TEXT("# ------------------------------------------------------"));
1211 for (TSet<FConvexFace*>::TConstIterator SetIt(Processed); SetIt; ++SetIt)
1212 {
1213 const FConvexFace* Face = *SetIt;
1214 const FVector P1 = InVertices[Face->FirstEdge->Prev->Vertex];
1215 const FVector P2 = InVertices[Face->FirstEdge->Next->Vertex];
1216 const FVector P3 = InVertices[Face->FirstEdge->Vertex];
1217 UE_LOG(LogChaos, VeryVerbose, TEXT("v %f %f %f"), P1.X, P1.Y, P1.Z);
1218 UE_LOG(LogChaos, VeryVerbose, TEXT("v %f %f %f"), P2.X, P2.Y, P2.Z);
1219 UE_LOG(LogChaos, VeryVerbose, TEXT("v %f %f %f"), P3.X, P3.Y, P3.Z);
1220 UE_LOG(LogChaos, VeryVerbose, TEXT("f -3 -2 -1"));
1221 }
1222#endif
1223
1224 FString HorizonString(TEXT("> Final Horizon: ("));
1225 for (const FHalfEdge* HorizonEdge : HorizonEdges)
1226 {
1227 HorizonString += FString::Printf(TEXT("%d (%d)"), HorizonEdge->Vertex, HorizonEdge->Next->Vertex);
1228 }
1229 HorizonString += TEXT(")");
1230 UE_LOG(LogChaos, VeryVerbose, TEXT("%s"), *HorizonString);
1231
1232 UE_LOG(LogChaos, VeryVerbose, TEXT("Generate horizon - END"));
1233#endif
1234
1235 }
1236
1237 static bool BuildFaces(FMemPool& Pool, const TArray<FVec3Type>& InVertices, const FHalfEdge* ConflictV, const TArray<FHalfEdge*>& HorizonEdges, const TArray<FConvexFace*>& OldFaces, TArray<FConvexFace*>& NewFaces)
1238 {
1239 //The HorizonEdges are in CCW order. We must make new faces and edges to join from ConflictV to these edges
1240 if (!(HorizonEdges.Num() >= 3)) // TODO: previously this was a check(), but it sometimes failed; can we fix things so this always holds?
1241 {
1242 return false;
1243 }
1244 NewFaces.Reserve(HorizonEdges.Num());
1245 FHalfEdge* PrevEdge = nullptr;
1247 {
1248 FHalfEdge* OriginalEdge = HorizonEdges[HorizonIdx];
1249 FHalfEdge* NewHorizonEdge = Pool.AllocHalfEdge(OriginalEdge->Vertex);
1250 NewHorizonEdge->Twin = OriginalEdge->Twin; //swap edges
1251 NewHorizonEdge->Twin->Twin = NewHorizonEdge;
1252 FHalfEdge* HorizonNext = Pool.AllocHalfEdge(OriginalEdge->Next->Vertex);
1253 // TODO: previously this was a check(), but it sometimes failed; can we fix things so this always holds?
1254 if (!(HorizonNext->Vertex == HorizonEdges[(HorizonIdx + 1) % HorizonEdges.Num()]->Vertex)) //should be ordered properly
1255 {
1256 return false;
1257 }
1258 FHalfEdge* V = Pool.AllocHalfEdge(ConflictV->Vertex);
1259 V->Twin = PrevEdge;
1260 if(PrevEdge)
1261 {
1262 PrevEdge->Twin = V;
1263 }
1264 PrevEdge = HorizonNext;
1265
1266 //link new faces together
1267 FConvexFace* NewFace = CreateFace(Pool, InVertices, NewHorizonEdge, HorizonNext, V);
1268 if(NewFaces.Num() > 0)
1269 {
1270 NewFace->Prev = NewFaces[NewFaces.Num() - 1];
1271 NewFaces[NewFaces.Num() - 1]->Next = NewFace;
1272 }
1273 else
1274 {
1275 NewFace->Prev = nullptr;
1276 }
1277 NewFaces.Add(NewFace);
1278 }
1279
1280 // TODO: previously this was a check(); can we determine if this always hold and switch it back if so?
1281 if (!PrevEdge)
1282 {
1283 return false;
1284 }
1285 NewFaces[0]->FirstEdge->Prev->Twin = PrevEdge;
1286 PrevEdge->Twin = NewFaces[0]->FirstEdge->Prev;
1287 NewFaces[NewFaces.Num() - 1]->Next = nullptr;
1288
1289 //redistribute conflict list
1290 for(FConvexFace* OldFace : OldFaces)
1291 {
1292 StealConflictList(Pool, InVertices, OldFace->ConflictList, &NewFaces[0], NewFaces.Num());
1293 }
1294
1295 //insert all new faces after conflict vertex face
1296 FConvexFace* OldFace = ConflictV->Face;
1297 FConvexFace* StartFace = NewFaces[0];
1298 FConvexFace* EndFace = NewFaces[NewFaces.Num() - 1];
1299 if(OldFace->Next)
1300 {
1301 OldFace->Next->Prev = EndFace;
1302 }
1303 EndFace->Next = OldFace->Next;
1304 OldFace->Next = StartFace;
1305 StartFace->Prev = OldFace;
1306
1307 return true;
1308 }
1309
1310 static bool AddVertex(FMemPool& Pool, const TArray<FVec3Type>& InVertices, FHalfEdge* ConflictV, const Params& InParams)
1311 {
1312 UE_CLOG(DEBUG_HULL_GENERATION, LogChaos, VeryVerbose, TEXT("Adding Vertex %d"), ConflictV->Vertex);
1313
1317
1319 if (!ensure(BuildFaces(Pool, InVertices, ConflictV, HorizonEdges, FacesToDelete, NewFaces)))
1320 {
1321 return false;
1322 }
1323
1324#if DEBUG_HULL_GENERATION
1325 FString NewFaceString(TEXT("\tNew Faces: "));
1326 for(const FConvexFace* Face : NewFaces)
1327 {
1328 NewFaceString += FString::Printf(TEXT("(%d %d %d) "), Face->FirstEdge->Vertex, Face->FirstEdge->Next->Vertex, Face->FirstEdge->Prev->Vertex);
1329 }
1330 UE_LOG(LogChaos, VeryVerbose, TEXT("%s"), *NewFaceString);
1331
1332 FString DeleteFaceString(TEXT("\tDelete Faces: "));
1333 for(const FConvexFace* Face : FacesToDelete)
1334 {
1335 DeleteFaceString += FString::Printf(TEXT("(%d %d %d) "), Face->FirstEdge->Vertex, Face->FirstEdge->Next->Vertex, Face->FirstEdge->Prev->Vertex);
1336 }
1337 UE_LOG(LogChaos, VeryVerbose, TEXT("%s"), *DeleteFaceString);
1338#endif
1339
1340 for(FConvexFace* Face : FacesToDelete)
1341 {
1342 FHalfEdge* Edge = Face->FirstEdge;
1343 do
1344 {
1345 FHalfEdge* Next = Edge->Next;
1346 Pool.FreeHalfEdge(Next);
1347 Edge = Next;
1348 } while(Edge != Face->FirstEdge);
1349 if(Face->Prev)
1350 {
1351 Face->Prev->Next = Face->Next;
1352 }
1353 if(Face->Next)
1354 {
1355 Face->Next->Prev = Face->Prev;
1356 }
1357 Pool.FreeConvexFace(Face);
1358 }
1359
1360 //todo(ocohen): need to explicitly test for merge failures. Coplaner, nonconvex, etc...
1361 //getting this in as is for now to unblock other systems
1362
1363 return true;
1364 }
1365
1366 };
1367}
1368
#define check(expr)
Definition AssertionMacros.h:314
#define ensure( InExpression)
Definition AssertionMacros.h:464
#define DEBUG_HULL_GENERATION
Definition CollisionConvexMesh.h:13
@ INDEX_NONE
Definition CoreMiscDefines.h:150
FPlatformTypes::int8 int8
An 8-bit signed integer.
Definition Platform.h:1121
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
#define TRACE_CPUPROFILER_EVENT_SCOPE(Name)
Definition CpuProfilerTrace.h:528
#define FVector
Definition IOSSystemIncludes.h:8
#define UE_CLOG(Condition, CategoryName, Verbosity, Format,...)
Definition LogMacros.h:298
#define UE_LOG(CategoryName, Verbosity, Format,...)
Definition LogMacros.h:270
UE::Math::TMatrix< float > FMatrix44f
Definition MathFwd.h:77
@ Num
Definition MetalRHIPrivate.h:234
@ Vertex
Definition MetalRHIPrivate.h:223
TTuple< KeyType, ValueType > TPair
Definition Tuple.h:55
#define UE_KINDA_SMALL_NUMBER
Definition UnrealMathUtility.h:131
UE_INTRINSIC_CAST UE_REWRITE constexpr std::remove_reference_t< T > && MoveTemp(T &&Obj) noexcept
Definition UnrealTemplate.h:520
uint32 Size
Definition VulkanMemory.cpp:4034
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition CollisionConvexMesh.h:52
Params()
Definition CollisionConvexMesh.h:54
FRealType HorizonEpsilon
Definition CollisionConvexMesh.h:58
Definition CollisionConvexMesh.h:35
static void FinalizeFaces(const FPlaneType &Face, TArray< int32 > &InOutFaceVertexIndices, const TArray< FVec3Type > &Vertices)
Definition CollisionConvexMesh.h:776
static void RemoveInvalidFaces(TArray< FPlaneType > &InOutPlanes, TArray< TArray< int32 > > &InOutFaceVertexIndices)
Definition CollisionConvexMesh.h:488
static bool IsPlanarShape(const TArray< FVec3Type > &InVertices, FVec3Type &OutNormal)
Definition CollisionConvexMesh.h:116
static void BuildPlaneVertexIndices(TArray< FPlaneType > &InPlanes, const TArray< FVec3Type > &Vertices, TArray< TArray< int32 > > &OutFaceVertexIndices, const FRealType DistanceTolerance=1.e-3f)
Definition CollisionConvexMesh.h:792
static void RemoveInsideFaceVertices(const FPlaneType &Face, TArray< int32 > &InOutFaceVertexIndices, const TArray< FVec3Type > &Vertices, const FVec3Type &Centroid)
Definition CollisionConvexMesh.h:679
static bool IsValidTriangle(const FVec3Type &A, const FVec3Type &B, const FVec3Type &C)
Definition CollisionConvexMesh.h:102
static bool IsFaceOutlineConvex(const FPlaneType &Plane, const TArray< int32 > &Indices, const TArray< FVec3Type > &Vertices)
Definition CollisionConvexMesh.h:442
static bool IsGeometryReductionEnabled()
Definition CollisionConvexMesh.h:361
static CHAOS_API int32 PerformGeometryCheck
Definition CollisionConvexMesh.h:812
FRealSingle FRealType
Definition CollisionConvexMesh.h:38
static CHAOS_API void BuildIndices(const TArray< FVec3Type > &InVertices, TArray< int32 > &OutResultIndexData, EBuildMethod BuildMethod=EBuildMethod::Default)
Definition CollisionConvexMesh.cpp:44
static FString PerformanceWarningString(int32 NumPlanes, int32 NumVertices)
Definition CollisionConvexMesh.h:366
EBuildMethod
Definition CollisionConvexMesh.h:44
@ Default
Definition CollisionConvexMesh.h:45
@ Original
Definition CollisionConvexMesh.h:46
@ ConvexHull3
Definition CollisionConvexMesh.h:47
@ ConvexHull3Simplified
Definition CollisionConvexMesh.h:48
static FTriangleMesh BuildConvexHullTriMesh(const TArray< FVec3Type > &InVertices)
Definition CollisionConvexMesh.h:344
static CHAOS_API int32 ComputeHorizonEpsilonFromMeshExtends
Definition CollisionConvexMesh.h:815
static CHAOS_API int32 PerformGeometryReduction
Definition CollisionConvexMesh.h:813
TPlaneConcrete< FRealType, 3 > FPlaneType
Definition CollisionConvexMesh.h:40
static bool UseConvexHull3Simplifier(EBuildMethod BuildMethod)
Definition CollisionConvexMesh.cpp:37
static void SortFaceVerticesCCW(const FPlaneType &Face, TArray< int32 > &InOutFaceVertexIndices, const TArray< FVec3Type > &Vertices, const FVec3Type &Centroid)
Definition CollisionConvexMesh.h:753
TVec3< FRealType > FVec3Type
Definition CollisionConvexMesh.h:39
static void BuildConvexHull(const TArray< FVec3Type > &InVertices, TArray< TVec3< int32 > > &OutIndices, const Params &InParams=Params())
Definition CollisionConvexMesh.h:271
TAABB< FRealType, 3 > FAABB3Type
Definition CollisionConvexMesh.h:41
static void Simplify(TArray< FPlaneType > &InOutPlanes, TArray< TArray< int32 > > &InOutFaces, TArray< FVec3Type > &InOutVertices, FAABB3Type &InOutLocalBounds)
Definition CollisionConvexMesh.h:371
static void MergeFaces(TArray< FPlaneType > &InOutPlanes, TArray< TArray< int32 > > &InOutFaceVertexIndices, const TArray< FVec3Type > &Vertices, FRealType DistanceThreshold)
Definition CollisionConvexMesh.h:506
static bool UseConvexHull3(EBuildMethod BuildMethod)
Definition CollisionConvexMesh.cpp:31
static bool IsValidTriangle(const FVec3Type &A, const FVec3Type &B, const FVec3Type &C, FVec3Type &OutNormal)
Definition CollisionConvexMesh.h:93
static CHAOS_API bool bUseSimplifierForTConvexHull3
Definition CollisionConvexMesh.h:817
static CHAOS_API bool bUseGeometryTConvexHull3
Definition CollisionConvexMesh.h:816
static bool IsValidQuad(const FVec3Type &A, const FVec3Type &B, const FVec3Type &C, const FVec3Type &D, FVec3Type &OutNormal)
Definition CollisionConvexMesh.h:108
static CHAOS_API int32 VerticesThreshold
Definition CollisionConvexMesh.h:814
static void MergeColinearEdges(TArray< FPlaneType > &InOutPlanes, TArray< TArray< int32 > > &InOutFaceVertexIndices, TArray< FVec3Type > &InOutVertices, FRealType AngleToleranceCos)
Definition CollisionConvexMesh.h:584
static FRealType SuggestEpsilon(const TArray< FVec3Type > &InVertices)
Definition CollisionConvexMesh.h:61
static bool IsPerformanceWarning(int32 NumPlanes, int32 NumVertices)
Definition CollisionConvexMesh.h:351
Definition TriangleMesh.h:24
Definition CorePlane.h:12
Definition Vector.h:1000
FORCEINLINE T Size() const
Definition Vector.h:1055
Definition Array.h:670
UE_REWRITE SizeType Num() const
Definition Array.h:1144
UE_NODEBUG UE_FORCEINLINE_HINT void Push(ElementType &&Item)
Definition Array.h:1224
void Reset(SizeType NewSize=0)
Definition Array.h:2246
void SetNum(SizeType NewNum, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2308
UE_NODEBUG UE_FORCEINLINE_HINT SizeType Add(ElementType &&Item)
Definition Array.h:2696
ElementType Pop(EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:1196
Definition ChunkedArray.h:56
Definition SkeletalMeshComponent.h:307
@ X
Definition SimulationModuleBase.h:152
FRealDouble FReal
Definition Real.h:22
float FRealSingle
Definition Real.h:14
constexpr double Epsilon
Definition SlopeUtils.h:114
U16 Index
Definition radfft.cpp:71
Definition BlendSpaceHelpers.h:61
static UE_FORCEINLINE_HINT bool IsNearlyEqual(float A, float B, float ErrorTolerance=UE_SMALL_NUMBER)
Definition UnrealMathUtility.h:388
Definition NumericLimits.h:41
Definition Tuple.h:652
Definition Plane.h:35
T Z
Definition Vector.h:68
T Y
Definition Vector.h:65
T X
Definition Vector.h:62