Ark Server API (ASA) - Wiki
Loading...
Searching...
No Matches
ConvexHull2d.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "CoreTypes.h"
6#include "Math/Vector2D.h"
7#include "Math/Vector.h"
8
9namespace ConvexHull2D
10{
11 /**
12 * Andrew's monotone chain convex hull algorithm for 2-dimensional points. O(N log N).
13 *
14 * Not the fastest algorithm out there, but definitely the simplest one to understand.
15 *
16 * 1 - Sort O(N log N)
17 * 2 - Scan sorted vertex from left to right to compute lower hull O(N)
18 * 3 - Scan sorted vertex from right to left to compute upper hull. O(N)
19 *
20 * If this is slow for some reason, O(N log H) also exists where H is the number of outputted
21 * hull vertices which is normally a lot lower than N and helps reduce the overall complexity.
22 */
23 template<typename VectorType, typename Allocator>
24 void ComputeConvexHull(const TArray<VectorType, Allocator>& Points, TArray<int32, Allocator>& OutIndices)
25 {
26 // Handle case too simple for the algorithm to handle
27 int32 PointsNum = Points.Num();
28 if (PointsNum <= 3)
29 {
30 for (int32 Index = 0; Index < PointsNum; ++Index)
31 {
32 OutIndices.Add(Index);
33 }
34
35 return;
36 }
37
38 // Simple sorted index lookup table into immutable Points array.
39 TArray<uint32, Allocator> SortedIndices;
40 SortedIndices.SetNumUninitialized(PointsNum);
41 for (int32 Index = 0; Index < PointsNum; ++Index)
42 {
43 SortedIndices[Index] = Index;
44 }
45
46 // Get rid of costly RangeCheck during sort by using pointer directly
47 Algo::Sort(
48 SortedIndices,
49 [P = Points.GetData()](uint32 a, uint32 b)
50 {
51 // Sort in Y if X are equal
52 return P[a].X == P[b].X ? P[a].Y < P[b].Y : P[a].X < P[b].X;
53 }
54 );
55
56 auto IsClockwise =
57 [](const VectorType& O, const VectorType& A, const VectorType& B)
58 {
59 return ((A.X - O.X) * (B.Y - O.Y) - (A.Y - O.Y) * (B.X - O.X)) <= 0;
60 };
61
62 int32 HullIndex = 0;
63 OutIndices.SetNum(PointsNum*2);
64
65 // Build lower hull
66 for (int32 Index = 0; Index < PointsNum; ++Index)
67 {
68 const VectorType& B = Points[SortedIndices[Index]];
69 while (HullIndex >= 2 && IsClockwise(Points[OutIndices[HullIndex - 2]], Points[OutIndices[HullIndex - 1]], B))
70 {
71 --HullIndex;
72 }
73
74 OutIndices[HullIndex++] = SortedIndices[Index];
75 }
76
77 // Build upper hull
78 for (int32 Index = PointsNum - 1, StartIndex = HullIndex + 1; Index > 0; --Index)
79 {
80 const VectorType& B = Points[SortedIndices[Index - 1]];
81 while (HullIndex >= StartIndex && IsClockwise(Points[OutIndices[HullIndex - 2]], Points[OutIndices[HullIndex - 1]], B))
82 {
83 --HullIndex;
84 }
85
86 OutIndices[HullIndex++] = SortedIndices[Index - 1];
87 }
88
89 OutIndices.SetNum(HullIndex - 1, false);
90 }
91
92 /** Returns <0 if C is left of A-B */
93 inline FVector::FReal ComputeDeterminant(const FVector& A, const FVector& B, const FVector& C)
94 {
95 const FVector::FReal u1 = B.X - A.X;
96 const FVector::FReal v1 = B.Y - A.Y;
97 const FVector::FReal u2 = C.X - A.X;
98 const FVector::FReal v2 = C.Y - A.Y;
99
100 return u1 * v2 - v1 * u2;
101 }
102
103 /** Returns true if 'a' is more lower-left than 'b'. */
104 inline bool ComparePoints(const FVector& A, const FVector& B)
105 {
106 if (A.X < B.X)
107 {
108 return true;
109 }
110
111 if (A.X > B.X)
112 {
113 return false;
114 }
115
116 if (A.Y < B.Y)
117 {
118 return true;
119 }
120
121 if (A.Y > B.Y)
122 {
123 return false;
124 }
125
126 return false;
127 }
128
129 /**
130 * Calculates convex hull on xy-plane of points on 'Points' and stores the indices of the resulting hull in 'OutIndices'.
131 * This code was fixed to work with duplicated vertices and precision issues.
132 *
133 * Should be replaced by ComputeConvexHull and tested properly. Keep for backward compatibility until then.
134 */
135 template<typename Allocator>
136 void ComputeConvexHullLegacy(const TArray<FVector, Allocator>& Points, TArray<int32, Allocator>& OutIndices)
137 {
138 if (Points.Num() == 0)
139 {
140 // Early exit here, otherwise an invalid index will be added to the output.
141 return;
142 }
143
144 // Find lower-leftmost point.
145 int32 HullStart = 0;
146 int32 HullEnd = 0;
147
148 for (int32 i = 1; i < Points.Num(); ++i)
149 {
150 if (ComparePoints(Points[i], Points[HullStart]))
151 {
152 HullStart = i;
153 }
154 if (ComparePoints(Points[HullEnd], Points[i]))
155 {
156 HullEnd = i;
157 }
158 }
159
160 OutIndices.Add(HullStart);
161
162 if(HullStart == HullEnd)
163 {
164 // convex hull degenerated to a single point
165 return;
166 }
167
168 // Gift wrap Hull.
169 int32 Hull = HullStart;
170 int LocalEnd = HullEnd;
171 bool bGoRight = true;
172 bool bFinished = false;
173
174 // sometimes it hangs on infinite loop, repeating sequence of indices (e.g. 4,9,8,9,8,...)
175 while (OutIndices.Num() <= Points.Num())
176 {
177 int32 NextPoint = LocalEnd;
178
179 for (int j = 0; j < Points.Num(); ++j)
180 {
181 if(j == NextPoint || j == Hull)
182 {
183 continue;
184 }
185
186 const FVector & A = Points.GetData()[Hull];
187 const FVector & B = Points.GetData()[NextPoint];
188 const FVector & C = Points.GetData()[j];
189 FVector::FReal Deter = ComputeDeterminant(A, B, C);
190
191 // 0.001 Bias is to stop floating point errors, when comparing points on a straight line; KINDA_SMALL_NUMBER was slightly too small to use.
192 if(Deter < -0.001)
193 {
194 // C is left of AB, take it
195 NextPoint = j;
196 }
197 else if(Deter < 0.001)
198 {
199 if(bGoRight)
200 {
201 if(ComparePoints(B, C))
202 {
203 // we go right, take it
204 NextPoint = j;
205 }
206 }
207 else
208 {
209 if(ComparePoints(C, B))
210 {
211 // we go left, take it
212 NextPoint = j;
213 }
214 }
215 }
216 else
217 {
218 // C is right of AB, don't take it
219 }
220 }
221
222 if(NextPoint == HullEnd)
223 {
224 // turn around
225 bGoRight = false;
226 LocalEnd = HullStart;
227 }
228
229 if(NextPoint == HullStart)
230 {
231 // finish
232 bFinished = true;
233 break;
234 }
235
236 OutIndices.Add(NextPoint);
237
238 Hull = NextPoint;
239 }
240
241 // clear all indices if main loop was left without finishing shape
242 if (!bFinished)
243 {
244 OutIndices.Reset();
245 }
246 }
247
248 /** Returns <0 if C is left of A-B */
249 inline FVector2D::FReal ComputeDeterminant2D(const FVector2D& A, const FVector2D& B, const FVector2D& C)
250 {
251 const FVector2D::FReal u1 = B.X - A.X;
252 const FVector2D::FReal v1 = B.Y - A.Y;
253 const FVector2D::FReal u2 = C.X - A.X;
254 const FVector2D::FReal v2 = C.Y - A.Y;
255
256 return u1 * v2 - v1 * u2;
257 }
258
259 /**
260 * Alternate simple implementation that was found to work correctly for points that are very close together (inside the 0-1 range).
261 *
262 * Should be replaced by ComputeConvexHull and tested properly. Keep for backward compatibility until then.
263 */
264 template<typename Allocator>
265 void ComputeConvexHullLegacy2(const TArray<FVector2D, Allocator>& Points, TArray<int32, Allocator>& OutIndices)
266 {
267 if (Points.Num() == 0)
268 {
269 return;
270 }
271
272 // Jarvis march implementation
273 int32 LeftmostIndex = -1;
274 FVector2D Leftmost(FLT_MAX, FLT_MAX);
275
276 for (int32 PointIndex = 0; PointIndex < Points.Num(); PointIndex++)
277 {
278 if (Points[PointIndex].X < Leftmost.X
279 || (Points[PointIndex].X == Leftmost.X && Points[PointIndex].Y < Leftmost.Y))
280 {
281 LeftmostIndex = PointIndex;
282 Leftmost = Points[PointIndex];
283 }
284 }
285
286 int32 PointOnHullIndex = LeftmostIndex;
287 int32 EndPointIndex;
288
289 do
290 {
291 OutIndices.Add(PointOnHullIndex);
292 EndPointIndex = 0;
293
294 // Find the 'leftmost' point to the line from the last hull vertex to a candidate
295 for (int32 j = 1; j < Points.Num(); j++)
296 {
297 if (EndPointIndex == PointOnHullIndex
298 || ComputeDeterminant2D(Points[EndPointIndex], Points[OutIndices.Last()], Points[j]) < 0)
299 {
300 EndPointIndex = j;
301 }
302 }
303
304 PointOnHullIndex = EndPointIndex;
305 }
306 while (EndPointIndex != LeftmostIndex);
307 }
308
309/*
310 static void Test()
311 {
312 {
313 TArray<FVector, TInlineAllocator<8> > In;
314 In.Empty(8);
315
316 In.Add(FVector(2, 0, 0));
317 In.Add(FVector(0, 0, 0));
318 In.Add(FVector(1, 0, 0));
319 In.Add(FVector(3, 0, 0));
320
321 TArray<int32, TInlineAllocator<8>> Out;
322 Out.Empty(8);
323
324 // Compute the 2d convex hull of the frustum vertices in light space
325 ConvexHull2D::ComputeConvexHull(In, Out);
326 check(Out.Num() == 2);
327 check(Out[0] == 1);
328 check(Out[1] == 3);
329 }
330
331 {
332 TArray<FVector, TInlineAllocator<8> > In;
333 In.Empty(8);
334
335 In.Add(FVector(2, 1, 0));
336
337 TArray<int32, TInlineAllocator<8>> Out;
338 Out.Empty(8);
339
340 // Compute the 2d convex hull of the frustum vertices in light space
341 ConvexHull2D::ComputeConvexHull(In, Out);
342 check(Out.Num() == 1);
343 check(Out[0] == 0);
344 }
345
346 {
347 TArray<FVector, TInlineAllocator<8> > In;
348 In.Empty(8);
349
350 In.Add(FVector(0, 0, 0));
351 In.Add(FVector(1, 0, 0));
352 In.Add(FVector(0, 1, 0));
353 In.Add(FVector(1, 1, 0));
354
355 TArray<int32, TInlineAllocator<8>> Out;
356 Out.Empty(8);
357
358 // Compute the 2d convex hull of the frustum vertices in light space
359 ConvexHull2D::ComputeConvexHull(In, Out);
360 check(Out.Num() == 4);
361 check(Out[0] == 0);
362 check(Out[1] == 1);
363 check(Out[2] == 3);
364 check(Out[3] == 2);
365 }
366
367 {
368 TArray<FVector, TInlineAllocator<8> > In;
369 In.Empty(8);
370
371 In.Add(FVector(0, 0, 0));
372 In.Add(FVector(1, 0, 0));
373 In.Add(FVector(2, 0, 0));
374 In.Add(FVector(0, 1, 0));
375 In.Add(FVector(1, 1, 0));
376 In.Add(FVector(0, 2, 0));
377 In.Add(FVector(2, 2, 0));
378 In.Add(FVector(2, 2, 0));
379
380 TArray<int32, TInlineAllocator<8>> Out;
381 Out.Empty(8);
382
383 // Compute the 2d convex hull of the frustum vertices in light space
384 ConvexHull2D::ComputeConvexHull(In, Out);
385 check(Out.Num() == 4);
386 check(Out[0] == 0);
387 check(Out[1] == 2);
388 check(Out[2] == 6);
389 check(Out[3] == 5);
390 }
391
392 {
393 TArray<FVector, TInlineAllocator<8> > In;
394 In.Empty(8);
395
396 In.Add(FVector(2, 0, 0));
397 In.Add(FVector(3, 1, 0));
398 In.Add(FVector(4, 2, 0));
399 In.Add(FVector(0, 2, 0));
400 In.Add(FVector(1, 3, 0));
401 In.Add(FVector(2, 4, 0));
402 In.Add(FVector(1, 1, 0));
403 In.Add(FVector(3, 3, 0));
404
405 TArray<int32, TInlineAllocator<8>> Out;
406 Out.Empty(8);
407
408 // Compute the 2d convex hull of the frustum vertices in light space
409 ConvexHull2D::ComputeConvexHull(In, Out);
410 check(Out.Num() == 4);
411 check(Out[0] == 3);
412 check(Out[1] == 0);
413 check(Out[2] == 2);
414 check(Out[3] == 5);
415 }
416 }
417*/
418}