Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/IslandBuilder.cpp
21416 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/Physics/IslandBuilder.h>
8
#include <Jolt/Physics/Body/Body.h>
9
#include <Jolt/Physics/PhysicsSettings.h>
10
#include <Jolt/Core/Profiler.h>
11
#include <Jolt/Core/Atomics.h>
12
#include <Jolt/Core/TempAllocator.h>
13
#include <Jolt/Core/QuickSort.h>
14
15
JPH_NAMESPACE_BEGIN
16
17
IslandBuilder::~IslandBuilder()
18
{
19
JPH_ASSERT(mConstraintLinks == nullptr);
20
JPH_ASSERT(mContactLinks == nullptr);
21
JPH_ASSERT(mBodyIslands == nullptr);
22
JPH_ASSERT(mBodyIslandEnds == nullptr);
23
JPH_ASSERT(mConstraintIslands == nullptr);
24
JPH_ASSERT(mConstraintIslandEnds == nullptr);
25
JPH_ASSERT(mContactIslands == nullptr);
26
JPH_ASSERT(mContactIslandEnds == nullptr);
27
JPH_ASSERT(mIslandsSorted == nullptr);
28
29
delete [] mBodyLinks;
30
}
31
32
void IslandBuilder::Init(uint32 inMaxActiveBodies)
33
{
34
mMaxActiveBodies = inMaxActiveBodies;
35
36
// Link each body to itself, BuildBodyIslands() will restore this so that we don't need to do this each step
37
JPH_ASSERT(mBodyLinks == nullptr);
38
mBodyLinks = new BodyLink [mMaxActiveBodies];
39
for (uint32 i = 0; i < mMaxActiveBodies; ++i)
40
mBodyLinks[i].mLinkedTo.store(i, memory_order_relaxed);
41
}
42
43
void IslandBuilder::PrepareContactConstraints(uint32 inMaxContacts, TempAllocator *inTempAllocator)
44
{
45
JPH_PROFILE_FUNCTION();
46
47
// Need to call Init first
48
JPH_ASSERT(mBodyLinks != nullptr);
49
50
// Check that the builder has been reset
51
JPH_ASSERT(mNumContacts == 0);
52
JPH_ASSERT(mNumIslands == 0);
53
54
// Create contact link buffer, not initialized so each contact needs to be explicitly set
55
JPH_ASSERT(mContactLinks == nullptr);
56
mContactLinks = (uint32 *)inTempAllocator->Allocate(inMaxContacts * sizeof(uint32));
57
mMaxContacts = inMaxContacts;
58
59
#ifdef JPH_VALIDATE_ISLAND_BUILDER
60
// Create validation structures
61
JPH_ASSERT(mLinkValidation == nullptr);
62
mLinkValidation = (LinkValidation *)inTempAllocator->Allocate(inMaxContacts * sizeof(LinkValidation));
63
mNumLinkValidation = 0;
64
#endif
65
}
66
67
void IslandBuilder::PrepareNonContactConstraints(uint32 inNumConstraints, TempAllocator *inTempAllocator)
68
{
69
JPH_PROFILE_FUNCTION();
70
71
// Need to call Init first
72
JPH_ASSERT(mBodyLinks != nullptr);
73
74
// Check that the builder has been reset
75
JPH_ASSERT(mNumIslands == 0);
76
77
// Store number of constraints
78
mNumConstraints = inNumConstraints;
79
80
// Create constraint link buffer, not initialized so each constraint needs to be explicitly set
81
JPH_ASSERT(mConstraintLinks == nullptr);
82
mConstraintLinks = (uint32 *)inTempAllocator->Allocate(inNumConstraints * sizeof(uint32));
83
}
84
85
uint32 IslandBuilder::GetLowestBodyIndex(uint32 inActiveBodyIndex) const
86
{
87
uint32 index = inActiveBodyIndex;
88
for (;;)
89
{
90
uint32 link_to = mBodyLinks[index].mLinkedTo.load(memory_order_relaxed);
91
if (link_to == index)
92
break;
93
index = link_to;
94
}
95
return index;
96
}
97
98
void IslandBuilder::LinkBodies(uint32 inFirst, uint32 inSecond)
99
{
100
// Both need to be active, we don't want to create an island with static objects
101
if (inFirst >= mMaxActiveBodies || inSecond >= mMaxActiveBodies)
102
return;
103
104
#ifdef JPH_VALIDATE_ISLAND_BUILDER
105
// Add link to the validation list
106
if (mNumLinkValidation < uint32(mMaxContacts))
107
mLinkValidation[mNumLinkValidation++] = { inFirst, inSecond };
108
else
109
JPH_ASSERT(false, "Out of links");
110
#endif
111
112
// Start the algorithm with the two bodies
113
uint32 first_link_to = inFirst;
114
uint32 second_link_to = inSecond;
115
116
for (;;)
117
{
118
// Follow the chain until we get to the body with lowest index
119
// If the swap compare below fails, we'll keep searching from the lowest index for the new lowest index
120
first_link_to = GetLowestBodyIndex(first_link_to);
121
second_link_to = GetLowestBodyIndex(second_link_to);
122
123
// If the targets are the same, the bodies are already connected
124
if (first_link_to != second_link_to)
125
{
126
// We always link the highest to the lowest
127
if (first_link_to < second_link_to)
128
{
129
// Attempt to link the second to the first
130
// Since we found this body to be at the end of the chain it must point to itself, and if it
131
// doesn't it has been reparented and we need to retry the algorithm
132
if (!mBodyLinks[second_link_to].mLinkedTo.compare_exchange_weak(second_link_to, first_link_to, memory_order_relaxed))
133
continue;
134
}
135
else
136
{
137
// Attempt to link the first to the second
138
// Since we found this body to be at the end of the chain it must point to itself, and if it
139
// doesn't it has been reparented and we need to retry the algorithm
140
if (!mBodyLinks[first_link_to].mLinkedTo.compare_exchange_weak(first_link_to, second_link_to, memory_order_relaxed))
141
continue;
142
}
143
}
144
145
// Linking succeeded!
146
// Chains of bodies can become really long, resulting in an O(N) loop to find the lowest body index
147
// to prevent this we attempt to update the link of the bodies that were passed in to directly point
148
// to the lowest index that we found. If the value became lower than our lowest link, some other
149
// thread must have relinked these bodies in the mean time so we won't update the value.
150
uint32 lowest_link_to = min(first_link_to, second_link_to);
151
AtomicMin(mBodyLinks[inFirst].mLinkedTo, lowest_link_to, memory_order_relaxed);
152
AtomicMin(mBodyLinks[inSecond].mLinkedTo, lowest_link_to, memory_order_relaxed);
153
break;
154
}
155
}
156
157
void IslandBuilder::LinkConstraint(uint32 inConstraintIndex, uint32 inFirst, uint32 inSecond)
158
{
159
LinkBodies(inFirst, inSecond);
160
161
JPH_ASSERT(inConstraintIndex < mNumConstraints);
162
uint32 min_value = min(inFirst, inSecond); // Use fact that invalid index is 0xffffffff, we want the active body of two
163
JPH_ASSERT(min_value != Body::cInactiveIndex); // At least one of the bodies must be active
164
mConstraintLinks[inConstraintIndex] = min_value;
165
}
166
167
void IslandBuilder::LinkContact(uint32 inContactIndex, uint32 inFirst, uint32 inSecond)
168
{
169
JPH_ASSERT(inContactIndex < mMaxContacts);
170
mContactLinks[inContactIndex] = min(inFirst, inSecond); // Use fact that invalid index is 0xffffffff, we want the active body of two
171
}
172
173
#ifdef JPH_VALIDATE_ISLAND_BUILDER
174
175
void IslandBuilder::ValidateIslands(uint32 inNumActiveBodies) const
176
{
177
JPH_PROFILE_FUNCTION();
178
179
// Go through all links so far
180
for (uint32 i = 0; i < mNumLinkValidation; ++i)
181
{
182
// If the bodies in this link ended up in different groups we have a problem
183
if (mBodyLinks[mLinkValidation[i].mFirst].mIslandIndex != mBodyLinks[mLinkValidation[i].mSecond].mIslandIndex)
184
{
185
Trace("Fail: %u, %u", mLinkValidation[i].mFirst, mLinkValidation[i].mSecond);
186
Trace("Num Active: %u", inNumActiveBodies);
187
188
for (uint32 j = 0; j < mNumLinkValidation; ++j)
189
Trace("builder.Link(%u, %u);", mLinkValidation[j].mFirst, mLinkValidation[j].mSecond);
190
191
IslandBuilder tmp;
192
tmp.Init(inNumActiveBodies);
193
for (uint32 j = 0; j < mNumLinkValidation; ++j)
194
{
195
Trace("Link %u -> %u", mLinkValidation[j].mFirst, mLinkValidation[j].mSecond);
196
tmp.LinkBodies(mLinkValidation[j].mFirst, mLinkValidation[j].mSecond);
197
for (uint32 t = 0; t < inNumActiveBodies; ++t)
198
Trace("%u -> %u", t, (uint32)tmp.mBodyLinks[t].mLinkedTo);
199
}
200
201
JPH_ASSERT(false, "IslandBuilder validation failed");
202
}
203
}
204
}
205
206
#endif
207
208
void IslandBuilder::BuildBodyIslands(const BodyID *inActiveBodies, uint32 inNumActiveBodies, TempAllocator *inTempAllocator)
209
{
210
JPH_PROFILE_FUNCTION();
211
212
// Store the amount of active bodies
213
mNumActiveBodies = inNumActiveBodies;
214
215
// Create output arrays for body ID's, don't call constructors
216
JPH_ASSERT(mBodyIslands == nullptr);
217
mBodyIslands = (BodyID *)inTempAllocator->Allocate(inNumActiveBodies * sizeof(BodyID));
218
219
// Create output array for start index of each island. At this point we don't know how many islands there will be, but we know it cannot be more than inNumActiveBodies.
220
// Note: We allocate 1 extra entry because we always increment the count of the next island.
221
uint32 *body_island_starts = (uint32 *)inTempAllocator->Allocate((inNumActiveBodies + 1) * sizeof(uint32));
222
223
// First island always starts at 0
224
body_island_starts[0] = 0;
225
226
// Calculate island index for all bodies
227
JPH_ASSERT(mNumIslands == 0);
228
for (uint32 i = 0; i < inNumActiveBodies; ++i)
229
{
230
BodyLink &link = mBodyLinks[i];
231
uint32 s = link.mLinkedTo.load(memory_order_relaxed);
232
if (s != i)
233
{
234
// Links to another body, take island index from other body (this must have been filled in already since we're looping from low to high)
235
JPH_ASSERT(s < uint32(i));
236
uint32 island_index = mBodyLinks[s].mIslandIndex;
237
link.mIslandIndex = island_index;
238
239
// Increment the start of the next island
240
body_island_starts[island_index + 1]++;
241
}
242
else
243
{
244
// Does not link to other body, this is the start of a new island
245
link.mIslandIndex = mNumIslands;
246
++mNumIslands;
247
248
// Set the start of the next island to 1
249
body_island_starts[mNumIslands] = 1;
250
}
251
}
252
253
#ifdef JPH_VALIDATE_ISLAND_BUILDER
254
ValidateIslands(inNumActiveBodies);
255
#endif
256
257
// Make the start array absolute (so far we only counted)
258
for (uint32 island = 1; island < mNumIslands; ++island)
259
body_island_starts[island] += body_island_starts[island - 1];
260
261
// Convert the to a linear list grouped by island
262
for (uint32 i = 0; i < inNumActiveBodies; ++i)
263
{
264
BodyLink &link = mBodyLinks[i];
265
266
// Copy the body to the correct location in the array and increment it
267
uint32 &start = body_island_starts[link.mIslandIndex];
268
mBodyIslands[start] = inActiveBodies[i];
269
start++;
270
271
// Reset linked to field for the next update
272
link.mLinkedTo.store(i, memory_order_relaxed);
273
}
274
275
// We should now have a full array
276
JPH_ASSERT(mNumIslands == 0 || body_island_starts[mNumIslands - 1] == inNumActiveBodies);
277
278
// We've incremented all body indices so that they now point at the end instead of the starts
279
JPH_ASSERT(mBodyIslandEnds == nullptr);
280
mBodyIslandEnds = body_island_starts;
281
}
282
283
void IslandBuilder::BuildConstraintIslands(const uint32 *inConstraintToBody, uint32 inNumConstraints, uint32 *&outConstraints, uint32 *&outConstraintsEnd, TempAllocator *inTempAllocator) const
284
{
285
JPH_PROFILE_FUNCTION();
286
287
// Check if there's anything to do
288
if (inNumConstraints == 0)
289
return;
290
291
// Create output arrays for constraints
292
// Note: For the end indices we allocate 1 extra entry so we don't have to do an if in the inner loop
293
uint32 *constraints = (uint32 *)inTempAllocator->Allocate(inNumConstraints * sizeof(uint32));
294
uint32 *constraint_ends = (uint32 *)inTempAllocator->Allocate((mNumIslands + 1) * sizeof(uint32));
295
296
// Reset sizes
297
for (uint32 island = 0; island < mNumIslands; ++island)
298
constraint_ends[island] = 0;
299
300
// Loop over array and increment start relative position for the next island
301
for (uint32 constraint = 0; constraint < inNumConstraints; ++constraint)
302
{
303
uint32 body_idx = inConstraintToBody[constraint];
304
uint32 next_island_idx = mBodyLinks[body_idx].mIslandIndex + 1;
305
JPH_ASSERT(next_island_idx <= mNumIslands);
306
constraint_ends[next_island_idx]++;
307
}
308
309
// Make start positions absolute
310
for (uint32 island = 1; island < mNumIslands; ++island)
311
constraint_ends[island] += constraint_ends[island - 1];
312
313
// Loop over array and collect constraints
314
for (uint32 constraint = 0; constraint < inNumConstraints; ++constraint)
315
{
316
uint32 body_idx = inConstraintToBody[constraint];
317
uint32 island_idx = mBodyLinks[body_idx].mIslandIndex;
318
constraints[constraint_ends[island_idx]++] = constraint;
319
}
320
321
JPH_ASSERT(outConstraints == nullptr);
322
outConstraints = constraints;
323
JPH_ASSERT(outConstraintsEnd == nullptr);
324
outConstraintsEnd = constraint_ends;
325
}
326
327
void IslandBuilder::SortIslands(TempAllocator *inTempAllocator)
328
{
329
JPH_PROFILE_FUNCTION();
330
331
if (mNumContacts > 0 || mNumConstraints > 0)
332
{
333
// Allocate mapping table
334
JPH_ASSERT(mIslandsSorted == nullptr);
335
mIslandsSorted = (uint32 *)inTempAllocator->Allocate(mNumIslands * sizeof(uint32));
336
337
// Initialize index
338
for (uint32 island = 0; island < mNumIslands; ++island)
339
mIslandsSorted[island] = island;
340
341
// Determine the sum of contact constraints / constraints per island
342
uint32 *num_constraints = (uint32 *)inTempAllocator->Allocate(mNumIslands * sizeof(uint32));
343
if (mNumContacts > 0 && mNumConstraints > 0)
344
{
345
num_constraints[0] = mConstraintIslandEnds[0] + mContactIslandEnds[0];
346
for (uint32 island = 1; island < mNumIslands; ++island)
347
num_constraints[island] = mConstraintIslandEnds[island] - mConstraintIslandEnds[island - 1]
348
+ mContactIslandEnds[island] - mContactIslandEnds[island - 1];
349
}
350
else if (mNumContacts > 0)
351
{
352
num_constraints[0] = mContactIslandEnds[0];
353
for (uint32 island = 1; island < mNumIslands; ++island)
354
num_constraints[island] = mContactIslandEnds[island] - mContactIslandEnds[island - 1];
355
}
356
else
357
{
358
num_constraints[0] = mConstraintIslandEnds[0];
359
for (uint32 island = 1; island < mNumIslands; ++island)
360
num_constraints[island] = mConstraintIslandEnds[island] - mConstraintIslandEnds[island - 1];
361
}
362
363
// Sort so the biggest islands go first, this means that the jobs that take longest will be running
364
// first which improves the chance that all jobs finish at the same time.
365
QuickSort(mIslandsSorted, mIslandsSorted + mNumIslands, [num_constraints](uint32 inLHS, uint32 inRHS) {
366
return num_constraints[inLHS] > num_constraints[inRHS];
367
});
368
369
inTempAllocator->Free(num_constraints, mNumIslands * sizeof(uint32));
370
}
371
}
372
373
void IslandBuilder::Finalize(const BodyID *inActiveBodies, uint32 inNumActiveBodies, uint32 inNumContacts, TempAllocator *inTempAllocator)
374
{
375
JPH_PROFILE_FUNCTION();
376
377
mNumContacts = inNumContacts;
378
379
BuildBodyIslands(inActiveBodies, inNumActiveBodies, inTempAllocator);
380
BuildConstraintIslands(mConstraintLinks, mNumConstraints, mConstraintIslands, mConstraintIslandEnds, inTempAllocator);
381
BuildConstraintIslands(mContactLinks, mNumContacts, mContactIslands, mContactIslandEnds, inTempAllocator);
382
SortIslands(inTempAllocator);
383
384
mNumPositionSteps = (uint8 *)inTempAllocator->Allocate(mNumIslands * sizeof(uint8));
385
}
386
387
void IslandBuilder::GetBodiesInIsland(uint32 inIslandIndex, BodyID *&outBodiesBegin, BodyID *&outBodiesEnd) const
388
{
389
JPH_ASSERT(inIslandIndex < mNumIslands);
390
uint32 sorted_index = mIslandsSorted != nullptr? mIslandsSorted[inIslandIndex] : inIslandIndex;
391
outBodiesBegin = sorted_index > 0? mBodyIslands + mBodyIslandEnds[sorted_index - 1] : mBodyIslands;
392
outBodiesEnd = mBodyIslands + mBodyIslandEnds[sorted_index];
393
}
394
395
bool IslandBuilder::GetConstraintsInIsland(uint32 inIslandIndex, uint32 *&outConstraintsBegin, uint32 *&outConstraintsEnd) const
396
{
397
JPH_ASSERT(inIslandIndex < mNumIslands);
398
if (mNumConstraints == 0)
399
{
400
outConstraintsBegin = nullptr;
401
outConstraintsEnd = nullptr;
402
return false;
403
}
404
else
405
{
406
uint32 sorted_index = mIslandsSorted[inIslandIndex];
407
outConstraintsBegin = sorted_index > 0? mConstraintIslands + mConstraintIslandEnds[sorted_index - 1] : mConstraintIslands;
408
outConstraintsEnd = mConstraintIslands + mConstraintIslandEnds[sorted_index];
409
return outConstraintsBegin != outConstraintsEnd;
410
}
411
}
412
413
bool IslandBuilder::GetContactsInIsland(uint32 inIslandIndex, uint32 *&outContactsBegin, uint32 *&outContactsEnd) const
414
{
415
JPH_ASSERT(inIslandIndex < mNumIslands);
416
if (mNumContacts == 0)
417
{
418
outContactsBegin = nullptr;
419
outContactsEnd = nullptr;
420
return false;
421
}
422
else
423
{
424
uint32 sorted_index = mIslandsSorted[inIslandIndex];
425
outContactsBegin = sorted_index > 0? mContactIslands + mContactIslandEnds[sorted_index - 1] : mContactIslands;
426
outContactsEnd = mContactIslands + mContactIslandEnds[sorted_index];
427
return outContactsBegin != outContactsEnd;
428
}
429
}
430
431
void IslandBuilder::ResetIslands(TempAllocator *inTempAllocator)
432
{
433
JPH_PROFILE_FUNCTION();
434
435
inTempAllocator->Free(mNumPositionSteps, mNumIslands * sizeof(uint8));
436
437
if (mIslandsSorted != nullptr)
438
{
439
inTempAllocator->Free(mIslandsSorted, mNumIslands * sizeof(uint32));
440
mIslandsSorted = nullptr;
441
}
442
443
if (mContactIslands != nullptr)
444
{
445
inTempAllocator->Free(mContactIslandEnds, (mNumIslands + 1) * sizeof(uint32));
446
mContactIslandEnds = nullptr;
447
inTempAllocator->Free(mContactIslands, mNumContacts * sizeof(uint32));
448
mContactIslands = nullptr;
449
}
450
451
if (mConstraintIslands != nullptr)
452
{
453
inTempAllocator->Free(mConstraintIslandEnds, (mNumIslands + 1) * sizeof(uint32));
454
mConstraintIslandEnds = nullptr;
455
inTempAllocator->Free(mConstraintIslands, mNumConstraints * sizeof(uint32));
456
mConstraintIslands = nullptr;
457
}
458
459
inTempAllocator->Free(mBodyIslandEnds, (mNumActiveBodies + 1) * sizeof(uint32));
460
mBodyIslandEnds = nullptr;
461
inTempAllocator->Free(mBodyIslands, mNumActiveBodies * sizeof(uint32));
462
mBodyIslands = nullptr;
463
464
inTempAllocator->Free(mConstraintLinks, mNumConstraints * sizeof(uint32));
465
mConstraintLinks = nullptr;
466
467
#ifdef JPH_VALIDATE_ISLAND_BUILDER
468
inTempAllocator->Free(mLinkValidation, mMaxContacts * sizeof(LinkValidation));
469
mLinkValidation = nullptr;
470
#endif
471
472
inTempAllocator->Free(mContactLinks, mMaxContacts * sizeof(uint32));
473
mContactLinks = nullptr;
474
475
mNumActiveBodies = 0;
476
mNumConstraints = 0;
477
mMaxContacts = 0;
478
mNumContacts = 0;
479
mNumIslands = 0;
480
}
481
482
JPH_NAMESPACE_END
483
484