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