Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Geometry/ConvexHullBuilder2D.cpp
9913 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#include <Jolt/Jolt.h>
6
7
#include <Jolt/Geometry/ConvexHullBuilder2D.h>
8
9
#ifdef JPH_CONVEX_BUILDER_2D_DEBUG
10
#include <Jolt/Renderer/DebugRenderer.h>
11
#endif
12
13
JPH_NAMESPACE_BEGIN
14
15
void ConvexHullBuilder2D::Edge::CalculateNormalAndCenter(const Vec3 *inPositions)
16
{
17
Vec3 p1 = inPositions[mStartIdx];
18
Vec3 p2 = inPositions[mNextEdge->mStartIdx];
19
20
// Center of edge
21
mCenter = 0.5f * (p1 + p2);
22
23
// Create outward pointing normal.
24
// We have two choices for the normal (which satisfies normal . edge = 0):
25
// normal1 = (-edge.y, edge.x, 0)
26
// normal2 = (edge.y, -edge.x, 0)
27
// We want (normal x edge).z > 0 so that the normal points out of the polygon. Only normal2 satisfies this condition.
28
Vec3 edge = p2 - p1;
29
mNormal = Vec3(edge.GetY(), -edge.GetX(), 0);
30
}
31
32
ConvexHullBuilder2D::ConvexHullBuilder2D(const Positions &inPositions) :
33
mPositions(inPositions)
34
{
35
#ifdef JPH_CONVEX_BUILDER_2D_DEBUG
36
// Center the drawing of the first hull around the origin and calculate the delta offset between states
37
mOffset = RVec3::sZero();
38
if (mPositions.empty())
39
{
40
// No hull will be generated
41
mDelta = Vec3::sZero();
42
}
43
else
44
{
45
Vec3 maxv = Vec3::sReplicate(-FLT_MAX), minv = Vec3::sReplicate(FLT_MAX);
46
for (Vec3 v : mPositions)
47
{
48
minv = Vec3::sMin(minv, v);
49
maxv = Vec3::sMax(maxv, v);
50
mOffset -= v;
51
}
52
mOffset /= Real(mPositions.size());
53
mDelta = Vec3((maxv - minv).GetX() + 0.5f, 0, 0);
54
mOffset += mDelta; // Don't start at origin, we're already drawing the final hull there
55
}
56
#endif
57
}
58
59
ConvexHullBuilder2D::~ConvexHullBuilder2D()
60
{
61
FreeEdges();
62
}
63
64
void ConvexHullBuilder2D::FreeEdges()
65
{
66
if (mFirstEdge == nullptr)
67
return;
68
69
Edge *edge = mFirstEdge;
70
do
71
{
72
Edge *next = edge->mNextEdge;
73
delete edge;
74
edge = next;
75
} while (edge != mFirstEdge);
76
77
mFirstEdge = nullptr;
78
mNumEdges = 0;
79
}
80
81
#ifdef JPH_ENABLE_ASSERTS
82
83
void ConvexHullBuilder2D::ValidateEdges() const
84
{
85
if (mFirstEdge == nullptr)
86
{
87
JPH_ASSERT(mNumEdges == 0);
88
return;
89
}
90
91
int count = 0;
92
93
Edge *edge = mFirstEdge;
94
do
95
{
96
// Validate connectivity
97
JPH_ASSERT(edge->mNextEdge->mPrevEdge == edge);
98
JPH_ASSERT(edge->mPrevEdge->mNextEdge == edge);
99
100
++count;
101
edge = edge->mNextEdge;
102
} while (edge != mFirstEdge);
103
104
// Validate that count matches
105
JPH_ASSERT(count == mNumEdges);
106
}
107
108
#endif // JPH_ENABLE_ASSERTS
109
110
void ConvexHullBuilder2D::AssignPointToEdge(int inPositionIdx, const Array<Edge *> &inEdges) const
111
{
112
Vec3 point = mPositions[inPositionIdx];
113
114
Edge *best_edge = nullptr;
115
float best_dist_sq = 0.0f;
116
117
// Test against all edges
118
for (Edge *edge : inEdges)
119
{
120
// Determine distance to edge
121
float dot = edge->mNormal.Dot(point - edge->mCenter);
122
if (dot > 0.0f)
123
{
124
float dist_sq = dot * dot / edge->mNormal.LengthSq();
125
if (dist_sq > best_dist_sq)
126
{
127
best_edge = edge;
128
best_dist_sq = dist_sq;
129
}
130
}
131
}
132
133
// If this point is in front of the edge, add it to the conflict list
134
if (best_edge != nullptr)
135
{
136
if (best_dist_sq > best_edge->mFurthestPointDistanceSq)
137
{
138
// This point is further away than any others, update the distance and add point as last point
139
best_edge->mFurthestPointDistanceSq = best_dist_sq;
140
best_edge->mConflictList.push_back(inPositionIdx);
141
}
142
else
143
{
144
// Not the furthest point, add it as the before last point
145
best_edge->mConflictList.insert(best_edge->mConflictList.begin() + best_edge->mConflictList.size() - 1, inPositionIdx);
146
}
147
}
148
}
149
150
ConvexHullBuilder2D::EResult ConvexHullBuilder2D::Initialize(int inIdx1, int inIdx2, int inIdx3, int inMaxVertices, float inTolerance, Edges &outEdges)
151
{
152
// Clear any leftovers
153
FreeEdges();
154
outEdges.clear();
155
156
// Reset flag
157
EResult result = EResult::Success;
158
159
// Determine a suitable tolerance for detecting that points are colinear
160
// Formula as per: Implementing Quickhull - Dirk Gregorius.
161
Vec3 vmax = Vec3::sZero();
162
for (Vec3 v : mPositions)
163
vmax = Vec3::sMax(vmax, v.Abs());
164
float colinear_tolerance_sq = Square(2.0f * FLT_EPSILON * (vmax.GetX() + vmax.GetY()));
165
166
// Increase desired tolerance if accuracy doesn't allow it
167
float tolerance_sq = max(colinear_tolerance_sq, Square(inTolerance));
168
169
// Start with the initial indices in counter clockwise order
170
float z = (mPositions[inIdx2] - mPositions[inIdx1]).Cross(mPositions[inIdx3] - mPositions[inIdx1]).GetZ();
171
if (z < 0.0f)
172
std::swap(inIdx1, inIdx2);
173
174
// Create and link edges
175
Edge *e1 = new Edge(inIdx1);
176
Edge *e2 = new Edge(inIdx2);
177
Edge *e3 = new Edge(inIdx3);
178
e1->mNextEdge = e2;
179
e1->mPrevEdge = e3;
180
e2->mNextEdge = e3;
181
e2->mPrevEdge = e1;
182
e3->mNextEdge = e1;
183
e3->mPrevEdge = e2;
184
mFirstEdge = e1;
185
mNumEdges = 3;
186
187
// Build the initial conflict lists
188
Array<Edge *> edges { e1, e2, e3 };
189
for (Edge *edge : edges)
190
edge->CalculateNormalAndCenter(mPositions.data());
191
for (int idx = 0; idx < (int)mPositions.size(); ++idx)
192
if (idx != inIdx1 && idx != inIdx2 && idx != inIdx3)
193
AssignPointToEdge(idx, edges);
194
195
JPH_IF_ENABLE_ASSERTS(ValidateEdges();)
196
#ifdef JPH_CONVEX_BUILDER_2D_DEBUG
197
DrawState();
198
#endif
199
200
// Add the remaining points to the hull
201
for (;;)
202
{
203
// Check if we've reached the max amount of vertices that are allowed
204
if (mNumEdges >= inMaxVertices)
205
{
206
result = EResult::MaxVerticesReached;
207
break;
208
}
209
210
// Find the edge with the furthest point on it
211
Edge *edge_with_furthest_point = nullptr;
212
float furthest_dist_sq = 0.0f;
213
Edge *edge = mFirstEdge;
214
do
215
{
216
if (edge->mFurthestPointDistanceSq > furthest_dist_sq)
217
{
218
furthest_dist_sq = edge->mFurthestPointDistanceSq;
219
edge_with_furthest_point = edge;
220
}
221
edge = edge->mNextEdge;
222
} while (edge != mFirstEdge);
223
224
// If there is none closer than our tolerance value, we're done
225
if (edge_with_furthest_point == nullptr || furthest_dist_sq < tolerance_sq)
226
break;
227
228
// Take the furthest point
229
int furthest_point_idx = edge_with_furthest_point->mConflictList.back();
230
edge_with_furthest_point->mConflictList.pop_back();
231
Vec3 furthest_point = mPositions[furthest_point_idx];
232
233
// Find the horizon of edges that need to be removed
234
Edge *first_edge = edge_with_furthest_point;
235
do
236
{
237
Edge *prev = first_edge->mPrevEdge;
238
if (!prev->IsFacing(furthest_point))
239
break;
240
first_edge = prev;
241
} while (first_edge != edge_with_furthest_point);
242
243
Edge *last_edge = edge_with_furthest_point;
244
do
245
{
246
Edge *next = last_edge->mNextEdge;
247
if (!next->IsFacing(furthest_point))
248
break;
249
last_edge = next;
250
} while (last_edge != edge_with_furthest_point);
251
252
// Create new edges
253
e1 = new Edge(first_edge->mStartIdx);
254
e2 = new Edge(furthest_point_idx);
255
e1->mNextEdge = e2;
256
e1->mPrevEdge = first_edge->mPrevEdge;
257
e2->mPrevEdge = e1;
258
e2->mNextEdge = last_edge->mNextEdge;
259
e1->mPrevEdge->mNextEdge = e1;
260
e2->mNextEdge->mPrevEdge = e2;
261
mFirstEdge = e1; // We could delete mFirstEdge so just update it to the newly created edge
262
mNumEdges += 2;
263
264
// Calculate normals
265
Array<Edge *> new_edges { e1, e2 };
266
for (Edge *new_edge : new_edges)
267
new_edge->CalculateNormalAndCenter(mPositions.data());
268
269
// Delete the old edges
270
for (;;)
271
{
272
Edge *next = first_edge->mNextEdge;
273
274
// Redistribute points in conflict list
275
for (int idx : first_edge->mConflictList)
276
AssignPointToEdge(idx, new_edges);
277
278
// Delete the old edge
279
delete first_edge;
280
--mNumEdges;
281
282
if (first_edge == last_edge)
283
break;
284
first_edge = next;
285
}
286
287
JPH_IF_ENABLE_ASSERTS(ValidateEdges();)
288
#ifdef JPH_CONVEX_BUILDER_2D_DEBUG
289
DrawState();
290
#endif
291
}
292
293
// Convert the edge list to a list of indices
294
outEdges.reserve(mNumEdges);
295
Edge *edge = mFirstEdge;
296
do
297
{
298
outEdges.push_back(edge->mStartIdx);
299
edge = edge->mNextEdge;
300
} while (edge != mFirstEdge);
301
302
return result;
303
}
304
305
#ifdef JPH_CONVEX_BUILDER_2D_DEBUG
306
307
void ConvexHullBuilder2D::DrawState()
308
{
309
int color_idx = 0;
310
311
const Edge *edge = mFirstEdge;
312
do
313
{
314
const Edge *next = edge->mNextEdge;
315
316
// Get unique color per edge
317
Color color = Color::sGetDistinctColor(color_idx++);
318
319
// Draw edge and normal
320
DebugRenderer::sInstance->DrawArrow(cDrawScale * (mOffset + mPositions[edge->mStartIdx]), cDrawScale * (mOffset + mPositions[next->mStartIdx]), color, 0.1f);
321
DebugRenderer::sInstance->DrawArrow(cDrawScale * (mOffset + edge->mCenter), cDrawScale * (mOffset + edge->mCenter) + edge->mNormal.NormalizedOr(Vec3::sZero()), Color::sGreen, 0.1f);
322
323
// Draw points that belong to this edge in the same color
324
for (int idx : edge->mConflictList)
325
DebugRenderer::sInstance->DrawMarker(cDrawScale * (mOffset + mPositions[idx]), color, 0.05f);
326
327
edge = next;
328
} while (edge != mFirstEdge);
329
330
mOffset += mDelta;
331
}
332
333
#endif
334
335
JPH_NAMESPACE_END
336
337