Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/PhysicsSystem.cpp
9906 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/PhysicsSystem.h>
8
#include <Jolt/Physics/PhysicsSettings.h>
9
#include <Jolt/Physics/PhysicsUpdateContext.h>
10
#include <Jolt/Physics/PhysicsStepListener.h>
11
#include <Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.h>
12
#include <Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.h>
13
#include <Jolt/Physics/Collision/CollisionDispatch.h>
14
#include <Jolt/Physics/Collision/AABoxCast.h>
15
#include <Jolt/Physics/Collision/ShapeCast.h>
16
#include <Jolt/Physics/Collision/CollideShape.h>
17
#include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
18
#include <Jolt/Physics/Collision/CastResult.h>
19
#include <Jolt/Physics/Collision/CollideConvexVsTriangles.h>
20
#include <Jolt/Physics/Collision/ManifoldBetweenTwoFaces.h>
21
#include <Jolt/Physics/Collision/Shape/ConvexShape.h>
22
#include <Jolt/Physics/Collision/SimShapeFilterWrapper.h>
23
#include <Jolt/Physics/Collision/InternalEdgeRemovingCollector.h>
24
#include <Jolt/Physics/Constraints/CalculateSolverSteps.h>
25
#include <Jolt/Physics/Constraints/ConstraintPart/AxisConstraintPart.h>
26
#include <Jolt/Physics/DeterminismLog.h>
27
#include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
28
#include <Jolt/Physics/SoftBody/SoftBodyShape.h>
29
#include <Jolt/Geometry/RayAABox.h>
30
#include <Jolt/Geometry/ClosestPoint.h>
31
#include <Jolt/Core/JobSystem.h>
32
#include <Jolt/Core/TempAllocator.h>
33
#include <Jolt/Core/QuickSort.h>
34
#include <Jolt/Core/ScopeExit.h>
35
#ifdef JPH_DEBUG_RENDERER
36
#include <Jolt/Renderer/DebugRenderer.h>
37
#endif // JPH_DEBUG_RENDERER
38
39
JPH_NAMESPACE_BEGIN
40
41
#ifdef JPH_DEBUG_RENDERER
42
bool PhysicsSystem::sDrawMotionQualityLinearCast = false;
43
#endif // JPH_DEBUG_RENDERER
44
45
//#define BROAD_PHASE BroadPhaseBruteForce
46
#define BROAD_PHASE BroadPhaseQuadTree
47
48
static const Color cColorUpdateBroadPhaseFinalize = Color::sGetDistinctColor(1);
49
static const Color cColorUpdateBroadPhasePrepare = Color::sGetDistinctColor(2);
50
static const Color cColorFindCollisions = Color::sGetDistinctColor(3);
51
static const Color cColorApplyGravity = Color::sGetDistinctColor(4);
52
static const Color cColorSetupVelocityConstraints = Color::sGetDistinctColor(5);
53
static const Color cColorBuildIslandsFromConstraints = Color::sGetDistinctColor(6);
54
static const Color cColorDetermineActiveConstraints = Color::sGetDistinctColor(7);
55
static const Color cColorFinalizeIslands = Color::sGetDistinctColor(8);
56
static const Color cColorContactRemovedCallbacks = Color::sGetDistinctColor(9);
57
static const Color cColorBodySetIslandIndex = Color::sGetDistinctColor(10);
58
static const Color cColorStartNextStep = Color::sGetDistinctColor(11);
59
static const Color cColorSolveVelocityConstraints = Color::sGetDistinctColor(12);
60
static const Color cColorPreIntegrateVelocity = Color::sGetDistinctColor(13);
61
static const Color cColorIntegrateVelocity = Color::sGetDistinctColor(14);
62
static const Color cColorPostIntegrateVelocity = Color::sGetDistinctColor(15);
63
static const Color cColorResolveCCDContacts = Color::sGetDistinctColor(16);
64
static const Color cColorSolvePositionConstraints = Color::sGetDistinctColor(17);
65
static const Color cColorFindCCDContacts = Color::sGetDistinctColor(18);
66
static const Color cColorStepListeners = Color::sGetDistinctColor(19);
67
static const Color cColorSoftBodyPrepare = Color::sGetDistinctColor(20);
68
static const Color cColorSoftBodyCollide = Color::sGetDistinctColor(21);
69
static const Color cColorSoftBodySimulate = Color::sGetDistinctColor(22);
70
static const Color cColorSoftBodyFinalize = Color::sGetDistinctColor(23);
71
72
PhysicsSystem::~PhysicsSystem()
73
{
74
// Remove broadphase
75
delete mBroadPhase;
76
}
77
78
void PhysicsSystem::Init(uint inMaxBodies, uint inNumBodyMutexes, uint inMaxBodyPairs, uint inMaxContactConstraints, const BroadPhaseLayerInterface &inBroadPhaseLayerInterface, const ObjectVsBroadPhaseLayerFilter &inObjectVsBroadPhaseLayerFilter, const ObjectLayerPairFilter &inObjectLayerPairFilter)
79
{
80
// Clamp max bodies
81
uint max_bodies = min(inMaxBodies, cMaxBodiesLimit);
82
JPH_ASSERT(max_bodies == inMaxBodies, "Cannot support this many bodies!");
83
84
mObjectVsBroadPhaseLayerFilter = &inObjectVsBroadPhaseLayerFilter;
85
mObjectLayerPairFilter = &inObjectLayerPairFilter;
86
87
// Initialize body manager
88
mBodyManager.Init(max_bodies, inNumBodyMutexes, inBroadPhaseLayerInterface);
89
90
// Create broadphase
91
mBroadPhase = new BROAD_PHASE();
92
mBroadPhase->Init(&mBodyManager, inBroadPhaseLayerInterface);
93
94
// Init contact constraint manager
95
mContactManager.Init(inMaxBodyPairs, inMaxContactConstraints);
96
97
// Init islands builder
98
mIslandBuilder.Init(max_bodies);
99
100
// Initialize body interface
101
mBodyInterfaceLocking.Init(mBodyLockInterfaceLocking, mBodyManager, *mBroadPhase);
102
mBodyInterfaceNoLock.Init(mBodyLockInterfaceNoLock, mBodyManager, *mBroadPhase);
103
104
// Initialize narrow phase query
105
mNarrowPhaseQueryLocking.Init(mBodyLockInterfaceLocking, *mBroadPhase);
106
mNarrowPhaseQueryNoLock.Init(mBodyLockInterfaceNoLock, *mBroadPhase);
107
}
108
109
void PhysicsSystem::OptimizeBroadPhase()
110
{
111
mBroadPhase->Optimize();
112
}
113
114
void PhysicsSystem::AddStepListener(PhysicsStepListener *inListener)
115
{
116
lock_guard lock(mStepListenersMutex);
117
118
JPH_ASSERT(std::find(mStepListeners.begin(), mStepListeners.end(), inListener) == mStepListeners.end());
119
mStepListeners.push_back(inListener);
120
}
121
122
void PhysicsSystem::RemoveStepListener(PhysicsStepListener *inListener)
123
{
124
lock_guard lock(mStepListenersMutex);
125
126
StepListeners::iterator i = std::find(mStepListeners.begin(), mStepListeners.end(), inListener);
127
JPH_ASSERT(i != mStepListeners.end());
128
*i = mStepListeners.back();
129
mStepListeners.pop_back();
130
}
131
132
EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionSteps, TempAllocator *inTempAllocator, JobSystem *inJobSystem)
133
{
134
JPH_PROFILE_FUNCTION();
135
136
JPH_DET_LOG("PhysicsSystem::Update: dt: " << inDeltaTime << " steps: " << inCollisionSteps);
137
138
JPH_ASSERT(inCollisionSteps > 0);
139
JPH_ASSERT(inDeltaTime >= 0.0f);
140
141
// Sync point for the broadphase. This will allow it to do clean up operations without having any mutexes locked yet.
142
mBroadPhase->FrameSync();
143
144
// If there are no active bodies (and no step listener to wake them up) or there's no time delta
145
uint32 num_active_rigid_bodies = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);
146
uint32 num_active_soft_bodies = mBodyManager.GetNumActiveBodies(EBodyType::SoftBody);
147
if ((num_active_rigid_bodies == 0 && num_active_soft_bodies == 0 && mStepListeners.empty()) || inDeltaTime <= 0.0f)
148
{
149
mBodyManager.LockAllBodies();
150
151
// Update broadphase
152
mBroadPhase->LockModifications();
153
BroadPhase::UpdateState update_state = mBroadPhase->UpdatePrepare();
154
mBroadPhase->UpdateFinalize(update_state);
155
mBroadPhase->UnlockModifications();
156
157
// If time has passed, call contact removal callbacks from contacts that existed in the previous update
158
if (inDeltaTime > 0.0f)
159
mContactManager.FinalizeContactCacheAndCallContactPointRemovedCallbacks(0, 0);
160
161
mBodyManager.UnlockAllBodies();
162
return EPhysicsUpdateError::None;
163
}
164
165
// Calculate ratio between current and previous frame delta time to scale initial constraint forces
166
float step_delta_time = inDeltaTime / inCollisionSteps;
167
float warm_start_impulse_ratio = mPhysicsSettings.mConstraintWarmStart && mPreviousStepDeltaTime > 0.0f? step_delta_time / mPreviousStepDeltaTime : 0.0f;
168
mPreviousStepDeltaTime = step_delta_time;
169
170
// Create the context used for passing information between jobs
171
PhysicsUpdateContext context(*inTempAllocator);
172
context.mPhysicsSystem = this;
173
context.mJobSystem = inJobSystem;
174
context.mBarrier = inJobSystem->CreateBarrier();
175
context.mIslandBuilder = &mIslandBuilder;
176
context.mStepDeltaTime = step_delta_time;
177
context.mWarmStartImpulseRatio = warm_start_impulse_ratio;
178
context.mSteps.resize(inCollisionSteps);
179
180
// Allocate space for body pairs
181
JPH_ASSERT(context.mBodyPairs == nullptr);
182
context.mBodyPairs = static_cast<BodyPair *>(inTempAllocator->Allocate(sizeof(BodyPair) * mPhysicsSettings.mMaxInFlightBodyPairs));
183
184
// Lock all bodies for write so that we can freely touch them
185
mStepListenersMutex.lock();
186
mBodyManager.LockAllBodies();
187
mBroadPhase->LockModifications();
188
189
// Get max number of concurrent jobs
190
int max_concurrency = context.GetMaxConcurrency();
191
192
// Calculate how many step listener jobs we spawn
193
int num_step_listener_jobs = mStepListeners.empty()? 0 : max(1, min((int)mStepListeners.size() / mPhysicsSettings.mStepListenersBatchSize / mPhysicsSettings.mStepListenerBatchesPerJob, max_concurrency));
194
195
// Number of gravity jobs depends on the amount of active bodies.
196
// Launch max 1 job per batch of active bodies
197
// Leave 1 thread for update broadphase prepare and 1 for determine active constraints
198
int num_apply_gravity_jobs = max(1, min(((int)num_active_rigid_bodies + cApplyGravityBatchSize - 1) / cApplyGravityBatchSize, max_concurrency - 2));
199
200
// Number of determine active constraints jobs to run depends on number of constraints.
201
// Leave 1 thread for update broadphase prepare and 1 for apply gravity
202
int num_determine_active_constraints_jobs = max(1, min(((int)mConstraintManager.GetNumConstraints() + cDetermineActiveConstraintsBatchSize - 1) / cDetermineActiveConstraintsBatchSize, max_concurrency - 2));
203
204
// Number of setup velocity constraints jobs to run depends on number of constraints.
205
int num_setup_velocity_constraints_jobs = max(1, min(((int)mConstraintManager.GetNumConstraints() + cSetupVelocityConstraintsBatchSize - 1) / cSetupVelocityConstraintsBatchSize, max_concurrency));
206
207
// Number of find collisions jobs to run depends on number of active bodies.
208
// Note that when we have more than 1 thread, we always spawn at least 2 find collisions jobs so that the first job can wait for build islands from constraints
209
// (which may activate additional bodies that need to be processed) while the second job can start processing collision work.
210
int num_find_collisions_jobs = max(max_concurrency == 1? 1 : 2, min(((int)num_active_rigid_bodies + cActiveBodiesBatchSize - 1) / cActiveBodiesBatchSize, max_concurrency));
211
212
// Number of integrate velocity jobs depends on number of active bodies.
213
int num_integrate_velocity_jobs = max(1, min(((int)num_active_rigid_bodies + cIntegrateVelocityBatchSize - 1) / cIntegrateVelocityBatchSize, max_concurrency));
214
215
{
216
JPH_PROFILE("Build Jobs");
217
218
// Iterate over collision steps
219
for (int step_idx = 0; step_idx < inCollisionSteps; ++step_idx)
220
{
221
bool is_first_step = step_idx == 0;
222
bool is_last_step = step_idx == inCollisionSteps - 1;
223
224
PhysicsUpdateContext::Step &step = context.mSteps[step_idx];
225
step.mContext = &context;
226
step.mIsFirst = is_first_step;
227
step.mIsLast = is_last_step;
228
229
// Create job to do broadphase finalization
230
// This job must finish before integrating velocities. Until then the positions will not be updated neither will bodies be added / removed.
231
step.mUpdateBroadphaseFinalize = inJobSystem->CreateJob("UpdateBroadPhaseFinalize", cColorUpdateBroadPhaseFinalize, [&context, &step]()
232
{
233
// Validate that all find collision jobs have stopped
234
JPH_ASSERT(step.mActiveFindCollisionJobs.load(memory_order_relaxed) == 0);
235
236
// Finalize the broadphase update
237
context.mPhysicsSystem->mBroadPhase->UpdateFinalize(step.mBroadPhaseUpdateState);
238
239
// Signal that it is done
240
step.mPreIntegrateVelocity.RemoveDependency();
241
}, num_find_collisions_jobs + 2); // depends on: find collisions, broadphase prepare update, finish building jobs
242
243
// The immediate jobs below are only immediate for the first step, the all finished job will kick them for the next step
244
int previous_step_dependency_count = is_first_step? 0 : 1;
245
246
// Start job immediately: Start the prepare broadphase
247
// Must be done under body lock protection since the order is body locks then broadphase mutex
248
// If this is turned around the RemoveBody call will hang since it locks in that order
249
step.mBroadPhasePrepare = inJobSystem->CreateJob("UpdateBroadPhasePrepare", cColorUpdateBroadPhasePrepare, [&context, &step]()
250
{
251
// Prepare the broadphase update
252
step.mBroadPhaseUpdateState = context.mPhysicsSystem->mBroadPhase->UpdatePrepare();
253
254
// Now the finalize can run (if other dependencies are met too)
255
step.mUpdateBroadphaseFinalize.RemoveDependency();
256
}, previous_step_dependency_count);
257
258
// This job will find all collisions
259
step.mBodyPairQueues.resize(max_concurrency);
260
step.mMaxBodyPairsPerQueue = mPhysicsSettings.mMaxInFlightBodyPairs / max_concurrency;
261
step.mActiveFindCollisionJobs.store(~PhysicsUpdateContext::JobMask(0) >> (sizeof(PhysicsUpdateContext::JobMask) * 8 - num_find_collisions_jobs), memory_order_release);
262
step.mFindCollisions.resize(num_find_collisions_jobs);
263
for (int i = 0; i < num_find_collisions_jobs; ++i)
264
{
265
// Build islands from constraints may activate additional bodies, so the first job will wait for this to finish in order to not miss any active bodies
266
int num_dep_build_islands_from_constraints = i == 0? 1 : 0;
267
step.mFindCollisions[i] = inJobSystem->CreateJob("FindCollisions", cColorFindCollisions, [&step, i]()
268
{
269
step.mContext->mPhysicsSystem->JobFindCollisions(&step, i);
270
}, num_apply_gravity_jobs + num_determine_active_constraints_jobs + 1 + num_dep_build_islands_from_constraints); // depends on: apply gravity, determine active constraints, finish building jobs, build islands from constraints
271
}
272
273
if (is_first_step)
274
{
275
#ifdef JPH_ENABLE_ASSERTS
276
// Don't allow write operations to the active bodies list
277
mBodyManager.SetActiveBodiesLocked(true);
278
#endif
279
280
// Store the number of active bodies at the start of the step
281
step.mNumActiveBodiesAtStepStart = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);
282
283
// Lock all constraints
284
mConstraintManager.LockAllConstraints();
285
286
// Allocate memory for storing the active constraints
287
JPH_ASSERT(context.mActiveConstraints == nullptr);
288
context.mActiveConstraints = static_cast<Constraint **>(inTempAllocator->Allocate(mConstraintManager.GetNumConstraints() * sizeof(Constraint *)));
289
290
// Prepare contact buffer
291
mContactManager.PrepareConstraintBuffer(&context);
292
293
// Setup island builder
294
mIslandBuilder.PrepareContactConstraints(mContactManager.GetMaxConstraints(), context.mTempAllocator);
295
}
296
297
// This job applies gravity to all active bodies
298
step.mApplyGravity.resize(num_apply_gravity_jobs);
299
for (int i = 0; i < num_apply_gravity_jobs; ++i)
300
step.mApplyGravity[i] = inJobSystem->CreateJob("ApplyGravity", cColorApplyGravity, [&context, &step]()
301
{
302
context.mPhysicsSystem->JobApplyGravity(&context, &step);
303
304
JobHandle::sRemoveDependencies(step.mFindCollisions);
305
}, num_step_listener_jobs > 0? num_step_listener_jobs : previous_step_dependency_count); // depends on: step listeners (or previous step if no step listeners)
306
307
// This job will setup velocity constraints for non-collision constraints
308
step.mSetupVelocityConstraints.resize(num_setup_velocity_constraints_jobs);
309
for (int i = 0; i < num_setup_velocity_constraints_jobs; ++i)
310
step.mSetupVelocityConstraints[i] = inJobSystem->CreateJob("SetupVelocityConstraints", cColorSetupVelocityConstraints, [&context, &step]()
311
{
312
context.mPhysicsSystem->JobSetupVelocityConstraints(context.mStepDeltaTime, &step);
313
314
JobHandle::sRemoveDependencies(step.mSolveVelocityConstraints);
315
}, num_determine_active_constraints_jobs + 1); // depends on: determine active constraints, finish building jobs
316
317
// This job will build islands from constraints
318
step.mBuildIslandsFromConstraints = inJobSystem->CreateJob("BuildIslandsFromConstraints", cColorBuildIslandsFromConstraints, [&context, &step]()
319
{
320
context.mPhysicsSystem->JobBuildIslandsFromConstraints(&context, &step);
321
322
step.mFindCollisions[0].RemoveDependency(); // The first collisions job cannot start running until we've finished building islands and activated all bodies
323
step.mFinalizeIslands.RemoveDependency();
324
}, num_determine_active_constraints_jobs + 1); // depends on: determine active constraints, finish building jobs
325
326
// This job determines active constraints
327
step.mDetermineActiveConstraints.resize(num_determine_active_constraints_jobs);
328
for (int i = 0; i < num_determine_active_constraints_jobs; ++i)
329
step.mDetermineActiveConstraints[i] = inJobSystem->CreateJob("DetermineActiveConstraints", cColorDetermineActiveConstraints, [&context, &step]()
330
{
331
context.mPhysicsSystem->JobDetermineActiveConstraints(&step);
332
333
step.mBuildIslandsFromConstraints.RemoveDependency();
334
335
// Kick these jobs last as they will use up all CPU cores leaving no space for the previous job, we prefer setup velocity constraints to finish first so we kick it first
336
JobHandle::sRemoveDependencies(step.mSetupVelocityConstraints);
337
JobHandle::sRemoveDependencies(step.mFindCollisions);
338
}, num_step_listener_jobs > 0? num_step_listener_jobs : previous_step_dependency_count); // depends on: step listeners (or previous step if no step listeners)
339
340
// This job calls the step listeners
341
step.mStepListeners.resize(num_step_listener_jobs);
342
for (int i = 0; i < num_step_listener_jobs; ++i)
343
step.mStepListeners[i] = inJobSystem->CreateJob("StepListeners", cColorStepListeners, [&context, &step]()
344
{
345
// Call the step listeners
346
context.mPhysicsSystem->JobStepListeners(&step);
347
348
// Kick apply gravity and determine active constraint jobs
349
JobHandle::sRemoveDependencies(step.mApplyGravity);
350
JobHandle::sRemoveDependencies(step.mDetermineActiveConstraints);
351
}, previous_step_dependency_count);
352
353
// Unblock the previous step
354
if (!is_first_step)
355
context.mSteps[step_idx - 1].mStartNextStep.RemoveDependency();
356
357
// This job will finalize the simulation islands
358
step.mFinalizeIslands = inJobSystem->CreateJob("FinalizeIslands", cColorFinalizeIslands, [&context, &step]()
359
{
360
// Validate that all find collision jobs have stopped
361
JPH_ASSERT(step.mActiveFindCollisionJobs.load(memory_order_relaxed) == 0);
362
363
context.mPhysicsSystem->JobFinalizeIslands(&context);
364
365
JobHandle::sRemoveDependencies(step.mSolveVelocityConstraints);
366
step.mBodySetIslandIndex.RemoveDependency();
367
}, num_find_collisions_jobs + 2); // depends on: find collisions, build islands from constraints, finish building jobs
368
369
// Unblock previous job
370
// Note: technically we could release find collisions here but we don't want to because that could make them run before 'setup velocity constraints' which means that job won't have a thread left
371
step.mBuildIslandsFromConstraints.RemoveDependency();
372
373
// This job will call the contact removed callbacks
374
step.mContactRemovedCallbacks = inJobSystem->CreateJob("ContactRemovedCallbacks", cColorContactRemovedCallbacks, [&context, &step]()
375
{
376
context.mPhysicsSystem->JobContactRemovedCallbacks(&step);
377
378
if (step.mStartNextStep.IsValid())
379
step.mStartNextStep.RemoveDependency();
380
}, 1); // depends on the find ccd contacts
381
382
// This job will set the island index on each body (only used for debug drawing purposes)
383
// It will also delete any bodies that have been destroyed in the last frame
384
step.mBodySetIslandIndex = inJobSystem->CreateJob("BodySetIslandIndex", cColorBodySetIslandIndex, [&context, &step]()
385
{
386
context.mPhysicsSystem->JobBodySetIslandIndex();
387
388
JobHandle::sRemoveDependencies(step.mSolvePositionConstraints);
389
}, 2); // depends on: finalize islands, finish building jobs
390
391
// Job to start the next collision step
392
if (!is_last_step)
393
{
394
PhysicsUpdateContext::Step *next_step = &context.mSteps[step_idx + 1];
395
step.mStartNextStep = inJobSystem->CreateJob("StartNextStep", cColorStartNextStep, [this, next_step]()
396
{
397
#ifdef JPH_DEBUG
398
// Validate that the cached bounds are correct
399
mBodyManager.ValidateActiveBodyBounds();
400
#endif // JPH_DEBUG
401
402
// Store the number of active bodies at the start of the step
403
next_step->mNumActiveBodiesAtStepStart = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);
404
405
// Clear the large island splitter
406
TempAllocator *temp_allocator = next_step->mContext->mTempAllocator;
407
mLargeIslandSplitter.Reset(temp_allocator);
408
409
// Clear the island builder
410
mIslandBuilder.ResetIslands(temp_allocator);
411
412
// Setup island builder
413
mIslandBuilder.PrepareContactConstraints(mContactManager.GetMaxConstraints(), temp_allocator);
414
415
// Restart the contact manager
416
mContactManager.RecycleConstraintBuffer();
417
418
// Kick the jobs of the next step (in the same order as the first step)
419
next_step->mBroadPhasePrepare.RemoveDependency();
420
if (next_step->mStepListeners.empty())
421
{
422
// Kick the gravity and active constraints jobs immediately
423
JobHandle::sRemoveDependencies(next_step->mApplyGravity);
424
JobHandle::sRemoveDependencies(next_step->mDetermineActiveConstraints);
425
}
426
else
427
{
428
// Kick the step listeners job first
429
JobHandle::sRemoveDependencies(next_step->mStepListeners);
430
}
431
}, 3); // depends on: update soft bodies, contact removed callbacks, finish building the previous step
432
}
433
434
// This job will solve the velocity constraints
435
step.mSolveVelocityConstraints.resize(max_concurrency);
436
for (int i = 0; i < max_concurrency; ++i)
437
step.mSolveVelocityConstraints[i] = inJobSystem->CreateJob("SolveVelocityConstraints", cColorSolveVelocityConstraints, [&context, &step]()
438
{
439
context.mPhysicsSystem->JobSolveVelocityConstraints(&context, &step);
440
441
step.mPreIntegrateVelocity.RemoveDependency();
442
}, num_setup_velocity_constraints_jobs + 2); // depends on: finalize islands, setup velocity constraints, finish building jobs.
443
444
// We prefer setup velocity constraints to finish first so we kick it first
445
JobHandle::sRemoveDependencies(step.mSetupVelocityConstraints);
446
JobHandle::sRemoveDependencies(step.mFindCollisions);
447
448
// Finalize islands is a dependency on find collisions so it can go last
449
step.mFinalizeIslands.RemoveDependency();
450
451
// This job will prepare the position update of all active bodies
452
step.mPreIntegrateVelocity = inJobSystem->CreateJob("PreIntegrateVelocity", cColorPreIntegrateVelocity, [&context, &step]()
453
{
454
context.mPhysicsSystem->JobPreIntegrateVelocity(&context, &step);
455
456
JobHandle::sRemoveDependencies(step.mIntegrateVelocity);
457
}, 2 + max_concurrency); // depends on: broadphase update finalize, solve velocity constraints, finish building jobs.
458
459
// Unblock previous jobs
460
step.mUpdateBroadphaseFinalize.RemoveDependency();
461
JobHandle::sRemoveDependencies(step.mSolveVelocityConstraints);
462
463
// This job will update the positions of all active bodies
464
step.mIntegrateVelocity.resize(num_integrate_velocity_jobs);
465
for (int i = 0; i < num_integrate_velocity_jobs; ++i)
466
step.mIntegrateVelocity[i] = inJobSystem->CreateJob("IntegrateVelocity", cColorIntegrateVelocity, [&context, &step]()
467
{
468
context.mPhysicsSystem->JobIntegrateVelocity(&context, &step);
469
470
step.mPostIntegrateVelocity.RemoveDependency();
471
}, 2); // depends on: pre integrate velocity, finish building jobs.
472
473
// Unblock previous job
474
step.mPreIntegrateVelocity.RemoveDependency();
475
476
// This job will finish the position update of all active bodies
477
step.mPostIntegrateVelocity = inJobSystem->CreateJob("PostIntegrateVelocity", cColorPostIntegrateVelocity, [&context, &step]()
478
{
479
context.mPhysicsSystem->JobPostIntegrateVelocity(&context, &step);
480
481
step.mResolveCCDContacts.RemoveDependency();
482
}, num_integrate_velocity_jobs + 1); // depends on: integrate velocity, finish building jobs
483
484
// Unblock previous jobs
485
JobHandle::sRemoveDependencies(step.mIntegrateVelocity);
486
487
// This job will update the positions and velocities for all bodies that need continuous collision detection
488
step.mResolveCCDContacts = inJobSystem->CreateJob("ResolveCCDContacts", cColorResolveCCDContacts, [&context, &step]()
489
{
490
context.mPhysicsSystem->JobResolveCCDContacts(&context, &step);
491
492
JobHandle::sRemoveDependencies(step.mSolvePositionConstraints);
493
}, 2); // depends on: integrate velocities, detect ccd contacts (added dynamically), finish building jobs.
494
495
// Unblock previous job
496
step.mPostIntegrateVelocity.RemoveDependency();
497
498
// Fixes up drift in positions and updates the broadphase with new body positions
499
step.mSolvePositionConstraints.resize(max_concurrency);
500
for (int i = 0; i < max_concurrency; ++i)
501
step.mSolvePositionConstraints[i] = inJobSystem->CreateJob("SolvePositionConstraints", cColorSolvePositionConstraints, [&context, &step]()
502
{
503
context.mPhysicsSystem->JobSolvePositionConstraints(&context, &step);
504
505
// Kick the next step
506
if (step.mSoftBodyPrepare.IsValid())
507
step.mSoftBodyPrepare.RemoveDependency();
508
}, 3); // depends on: resolve ccd contacts, body set island index, finish building jobs.
509
510
// Unblock previous jobs.
511
step.mResolveCCDContacts.RemoveDependency();
512
step.mBodySetIslandIndex.RemoveDependency();
513
514
// The soft body prepare job will create other jobs if needed
515
step.mSoftBodyPrepare = inJobSystem->CreateJob("SoftBodyPrepare", cColorSoftBodyPrepare, [&context, &step]()
516
{
517
context.mPhysicsSystem->JobSoftBodyPrepare(&context, &step);
518
}, max_concurrency); // depends on: solve position constraints.
519
520
// Unblock previous jobs
521
JobHandle::sRemoveDependencies(step.mSolvePositionConstraints);
522
}
523
}
524
525
// Build the list of jobs to wait for
526
JobSystem::Barrier *barrier = context.mBarrier;
527
{
528
JPH_PROFILE("Build job barrier");
529
530
StaticArray<JobHandle, cMaxPhysicsJobs> handles;
531
for (const PhysicsUpdateContext::Step &step : context.mSteps)
532
{
533
if (step.mBroadPhasePrepare.IsValid())
534
handles.push_back(step.mBroadPhasePrepare);
535
for (const JobHandle &h : step.mStepListeners)
536
handles.push_back(h);
537
for (const JobHandle &h : step.mDetermineActiveConstraints)
538
handles.push_back(h);
539
for (const JobHandle &h : step.mApplyGravity)
540
handles.push_back(h);
541
for (const JobHandle &h : step.mFindCollisions)
542
handles.push_back(h);
543
if (step.mUpdateBroadphaseFinalize.IsValid())
544
handles.push_back(step.mUpdateBroadphaseFinalize);
545
for (const JobHandle &h : step.mSetupVelocityConstraints)
546
handles.push_back(h);
547
handles.push_back(step.mBuildIslandsFromConstraints);
548
handles.push_back(step.mFinalizeIslands);
549
handles.push_back(step.mBodySetIslandIndex);
550
for (const JobHandle &h : step.mSolveVelocityConstraints)
551
handles.push_back(h);
552
handles.push_back(step.mPreIntegrateVelocity);
553
for (const JobHandle &h : step.mIntegrateVelocity)
554
handles.push_back(h);
555
handles.push_back(step.mPostIntegrateVelocity);
556
handles.push_back(step.mResolveCCDContacts);
557
for (const JobHandle &h : step.mSolvePositionConstraints)
558
handles.push_back(h);
559
handles.push_back(step.mContactRemovedCallbacks);
560
if (step.mSoftBodyPrepare.IsValid())
561
handles.push_back(step.mSoftBodyPrepare);
562
if (step.mStartNextStep.IsValid())
563
handles.push_back(step.mStartNextStep);
564
}
565
barrier->AddJobs(handles.data(), handles.size());
566
}
567
568
// Wait until all jobs finish
569
// Note we don't just wait for the last job. If we would and another job
570
// would be scheduled in between there is the possibility of a deadlock.
571
// The other job could try to e.g. add/remove a body which would try to
572
// lock a body mutex while this thread has already locked the mutex
573
inJobSystem->WaitForJobs(barrier);
574
575
// We're done with the barrier for this update
576
inJobSystem->DestroyBarrier(barrier);
577
578
#ifdef JPH_DEBUG
579
// Validate that the cached bounds are correct
580
mBodyManager.ValidateActiveBodyBounds();
581
#endif // JPH_DEBUG
582
583
// Clear the large island splitter
584
mLargeIslandSplitter.Reset(inTempAllocator);
585
586
// Clear the island builder
587
mIslandBuilder.ResetIslands(inTempAllocator);
588
589
// Clear the contact manager
590
mContactManager.FinishConstraintBuffer();
591
592
// Free active constraints
593
inTempAllocator->Free(context.mActiveConstraints, mConstraintManager.GetNumConstraints() * sizeof(Constraint *));
594
context.mActiveConstraints = nullptr;
595
596
// Free body pairs
597
inTempAllocator->Free(context.mBodyPairs, sizeof(BodyPair) * mPhysicsSettings.mMaxInFlightBodyPairs);
598
context.mBodyPairs = nullptr;
599
600
// Unlock the broadphase
601
mBroadPhase->UnlockModifications();
602
603
// Unlock all constraints
604
mConstraintManager.UnlockAllConstraints();
605
606
#ifdef JPH_ENABLE_ASSERTS
607
// Allow write operations to the active bodies list
608
mBodyManager.SetActiveBodiesLocked(false);
609
#endif
610
611
// Unlock all bodies
612
mBodyManager.UnlockAllBodies();
613
614
// Unlock step listeners
615
mStepListenersMutex.unlock();
616
617
// Return any errors
618
EPhysicsUpdateError errors = static_cast<EPhysicsUpdateError>(context.mErrors.load(memory_order_acquire));
619
JPH_ASSERT(errors == EPhysicsUpdateError::None, "An error occurred during the physics update, see EPhysicsUpdateError for more information");
620
return errors;
621
}
622
623
void PhysicsSystem::JobStepListeners(PhysicsUpdateContext::Step *ioStep)
624
{
625
#ifdef JPH_ENABLE_ASSERTS
626
// Read positions (broadphase updates concurrently so we can't write), read/write velocities
627
BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::Read);
628
629
// Can activate bodies only (we cache the amount of active bodies at the beginning of the step in mNumActiveBodiesAtStepStart so we cannot deactivate here)
630
BodyManager::GrantActiveBodiesAccess grant_active(true, false);
631
#endif
632
633
PhysicsStepListenerContext context;
634
context.mDeltaTime = ioStep->mContext->mStepDeltaTime;
635
context.mIsFirstStep = ioStep->mIsFirst;
636
context.mIsLastStep = ioStep->mIsLast;
637
context.mPhysicsSystem = this;
638
639
uint32 batch_size = mPhysicsSettings.mStepListenersBatchSize;
640
for (;;)
641
{
642
// Get the start of a new batch
643
uint32 batch = ioStep->mStepListenerReadIdx.fetch_add(batch_size);
644
if (batch >= mStepListeners.size())
645
break;
646
647
// Call the listeners
648
for (uint32 i = batch, i_end = min((uint32)mStepListeners.size(), batch + batch_size); i < i_end; ++i)
649
mStepListeners[i]->OnStep(context);
650
}
651
}
652
653
void PhysicsSystem::JobDetermineActiveConstraints(PhysicsUpdateContext::Step *ioStep) const
654
{
655
#ifdef JPH_ENABLE_ASSERTS
656
// No body access
657
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::None);
658
#endif
659
660
uint32 num_constraints = mConstraintManager.GetNumConstraints();
661
uint32 num_active_constraints;
662
Constraint **active_constraints = (Constraint **)JPH_STACK_ALLOC(cDetermineActiveConstraintsBatchSize * sizeof(Constraint *));
663
664
for (;;)
665
{
666
// Atomically fetch a batch of constraints
667
uint32 constraint_idx = ioStep->mDetermineActiveConstraintReadIdx.fetch_add(cDetermineActiveConstraintsBatchSize);
668
if (constraint_idx >= num_constraints)
669
break;
670
671
// Calculate the end of the batch
672
uint32 constraint_idx_end = min(num_constraints, constraint_idx + cDetermineActiveConstraintsBatchSize);
673
674
// Store the active constraints at the start of the step (bodies get activated during the step which in turn may activate constraints leading to an inconsistent shapshot)
675
mConstraintManager.GetActiveConstraints(constraint_idx, constraint_idx_end, active_constraints, num_active_constraints);
676
677
// Copy the block of active constraints to the global list of active constraints
678
if (num_active_constraints > 0)
679
{
680
uint32 active_constraint_idx = ioStep->mNumActiveConstraints.fetch_add(num_active_constraints);
681
memcpy(ioStep->mContext->mActiveConstraints + active_constraint_idx, active_constraints, num_active_constraints * sizeof(Constraint *));
682
}
683
}
684
}
685
686
void PhysicsSystem::JobApplyGravity(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
687
{
688
#ifdef JPH_ENABLE_ASSERTS
689
// We update velocities and need the rotation to do so
690
BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::Read);
691
#endif
692
693
// Get list of active bodies that we had at the start of the physics update.
694
// Any body that is activated as part of the simulation step does not receive gravity this frame.
695
// Note that bodies may be activated during this job but not deactivated, this means that only elements
696
// will be added to the array. Since the array is made to not reallocate, this is a safe operation.
697
const BodyID *active_bodies = mBodyManager.GetActiveBodiesUnsafe(EBodyType::RigidBody);
698
uint32 num_active_bodies_at_step_start = ioStep->mNumActiveBodiesAtStepStart;
699
700
// Fetch delta time once outside the loop
701
float delta_time = ioContext->mStepDeltaTime;
702
703
// Update velocities from forces
704
for (;;)
705
{
706
// Atomically fetch a batch of bodies
707
uint32 active_body_idx = ioStep->mApplyGravityReadIdx.fetch_add(cApplyGravityBatchSize);
708
if (active_body_idx >= num_active_bodies_at_step_start)
709
break;
710
711
// Calculate the end of the batch
712
uint32 active_body_idx_end = min(num_active_bodies_at_step_start, active_body_idx + cApplyGravityBatchSize);
713
714
// Process the batch
715
while (active_body_idx < active_body_idx_end)
716
{
717
Body &body = mBodyManager.GetBody(active_bodies[active_body_idx]);
718
if (body.IsDynamic())
719
{
720
MotionProperties *mp = body.GetMotionProperties();
721
Quat rotation = body.GetRotation();
722
723
if (body.GetApplyGyroscopicForce())
724
mp->ApplyGyroscopicForceInternal(rotation, delta_time);
725
726
mp->ApplyForceTorqueAndDragInternal(rotation, mGravity, delta_time);
727
}
728
active_body_idx++;
729
}
730
}
731
}
732
733
void PhysicsSystem::JobSetupVelocityConstraints(float inDeltaTime, PhysicsUpdateContext::Step *ioStep) const
734
{
735
#ifdef JPH_ENABLE_ASSERTS
736
// We only read positions
737
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::Read);
738
#endif
739
740
uint32 num_constraints = ioStep->mNumActiveConstraints;
741
742
for (;;)
743
{
744
// Atomically fetch a batch of constraints
745
uint32 constraint_idx = ioStep->mSetupVelocityConstraintsReadIdx.fetch_add(cSetupVelocityConstraintsBatchSize);
746
if (constraint_idx >= num_constraints)
747
break;
748
749
ConstraintManager::sSetupVelocityConstraints(ioStep->mContext->mActiveConstraints + constraint_idx, min<uint32>(cSetupVelocityConstraintsBatchSize, num_constraints - constraint_idx), inDeltaTime);
750
}
751
}
752
753
void PhysicsSystem::JobBuildIslandsFromConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
754
{
755
#ifdef JPH_ENABLE_ASSERTS
756
// We read constraints and positions
757
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::Read);
758
759
// Can only activate bodies
760
BodyManager::GrantActiveBodiesAccess grant_active(true, false);
761
#endif
762
763
// Prepare the island builder
764
mIslandBuilder.PrepareNonContactConstraints(ioStep->mNumActiveConstraints, ioContext->mTempAllocator);
765
766
// Build the islands
767
ConstraintManager::sBuildIslands(ioStep->mContext->mActiveConstraints, ioStep->mNumActiveConstraints, mIslandBuilder, mBodyManager);
768
}
769
770
void PhysicsSystem::TrySpawnJobFindCollisions(PhysicsUpdateContext::Step *ioStep) const
771
{
772
// Get how many jobs we can spawn and check if we can spawn more
773
uint max_jobs = ioStep->mBodyPairQueues.size();
774
if (CountBits(ioStep->mActiveFindCollisionJobs.load(memory_order_relaxed)) >= max_jobs)
775
return;
776
777
// Count how many body pairs we have waiting
778
uint32 num_body_pairs = 0;
779
for (const PhysicsUpdateContext::BodyPairQueue &queue : ioStep->mBodyPairQueues)
780
num_body_pairs += queue.mWriteIdx - queue.mReadIdx;
781
782
// Count how many active bodies we have waiting
783
uint32 num_active_bodies = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody) - ioStep->mActiveBodyReadIdx;
784
785
// Calculate how many jobs we would like
786
uint desired_num_jobs = min((num_body_pairs + cNarrowPhaseBatchSize - 1) / cNarrowPhaseBatchSize + (num_active_bodies + cActiveBodiesBatchSize - 1) / cActiveBodiesBatchSize, max_jobs);
787
788
for (;;)
789
{
790
// Get the bit mask of active jobs and see if we can spawn more
791
PhysicsUpdateContext::JobMask current_active_jobs = ioStep->mActiveFindCollisionJobs.load(memory_order_relaxed);
792
uint job_index = CountTrailingZeros(~current_active_jobs);
793
if (job_index >= desired_num_jobs)
794
break;
795
796
// Try to claim the job index
797
PhysicsUpdateContext::JobMask job_mask = PhysicsUpdateContext::JobMask(1) << job_index;
798
PhysicsUpdateContext::JobMask prev_value = ioStep->mActiveFindCollisionJobs.fetch_or(job_mask, memory_order_acquire);
799
if ((prev_value & job_mask) == 0)
800
{
801
// Add dependencies from the find collisions job to the next jobs
802
ioStep->mUpdateBroadphaseFinalize.AddDependency();
803
ioStep->mFinalizeIslands.AddDependency();
804
805
// Start the job
806
JobHandle job = ioStep->mContext->mJobSystem->CreateJob("FindCollisions", cColorFindCollisions, [step = ioStep, job_index]()
807
{
808
step->mContext->mPhysicsSystem->JobFindCollisions(step, job_index);
809
});
810
811
// Add the job to the job barrier so the main updating thread can execute the job too
812
ioStep->mContext->mBarrier->AddJob(job);
813
814
// Spawn only 1 extra job at a time
815
return;
816
}
817
}
818
}
819
820
static void sFinalizeContactAllocator(PhysicsUpdateContext::Step &ioStep, const ContactConstraintManager::ContactAllocator &inAllocator)
821
{
822
// Atomically accumulate the number of found manifolds and body pairs
823
ioStep.mNumBodyPairs.fetch_add(inAllocator.mNumBodyPairs, memory_order_relaxed);
824
ioStep.mNumManifolds.fetch_add(inAllocator.mNumManifolds, memory_order_relaxed);
825
826
// Combine update errors
827
ioStep.mContext->mErrors.fetch_or((uint32)inAllocator.mErrors, memory_order_relaxed);
828
}
829
830
// Disable TSAN for this function. It detects a false positive race condition on mBodyPairs.
831
// We have written mBodyPairs before doing mWriteIdx++ and we check mWriteIdx before reading mBodyPairs, so this should be safe.
832
JPH_TSAN_NO_SANITIZE
833
void PhysicsSystem::JobFindCollisions(PhysicsUpdateContext::Step *ioStep, int inJobIndex)
834
{
835
#ifdef JPH_ENABLE_ASSERTS
836
// We read positions and read velocities (for elastic collisions)
837
BodyAccess::Grant grant(BodyAccess::EAccess::Read, BodyAccess::EAccess::Read);
838
839
// Can only activate bodies
840
BodyManager::GrantActiveBodiesAccess grant_active(true, false);
841
#endif
842
843
// Allocation context for allocating new contact points
844
ContactAllocator contact_allocator(mContactManager.GetContactAllocator());
845
846
// Determine initial queue to read pairs from if no broadphase work can be done
847
// (always start looking at results from the next job)
848
int read_queue_idx = (inJobIndex + 1) % ioStep->mBodyPairQueues.size();
849
850
// Allocate space to temporarily store a batch of active bodies
851
BodyID *active_bodies = (BodyID *)JPH_STACK_ALLOC(cActiveBodiesBatchSize * sizeof(BodyID));
852
853
for (;;)
854
{
855
// Check if there are active bodies to be processed
856
uint32 active_bodies_read_idx = ioStep->mActiveBodyReadIdx;
857
uint32 num_active_bodies = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);
858
if (active_bodies_read_idx < num_active_bodies)
859
{
860
// Take a batch of active bodies
861
uint32 active_bodies_read_idx_end = min(num_active_bodies, active_bodies_read_idx + cActiveBodiesBatchSize);
862
if (ioStep->mActiveBodyReadIdx.compare_exchange_strong(active_bodies_read_idx, active_bodies_read_idx_end))
863
{
864
// Callback when a new body pair is found
865
class MyBodyPairCallback : public BodyPairCollector
866
{
867
public:
868
// Constructor
869
MyBodyPairCallback(PhysicsUpdateContext::Step *inStep, ContactAllocator &ioContactAllocator, int inJobIndex) :
870
mStep(inStep),
871
mContactAllocator(ioContactAllocator),
872
mJobIndex(inJobIndex)
873
{
874
}
875
876
// Callback function when a body pair is found
877
virtual void AddHit(const BodyPair &inPair) override
878
{
879
// Check if we have space in our write queue
880
PhysicsUpdateContext::BodyPairQueue &queue = mStep->mBodyPairQueues[mJobIndex];
881
uint32 body_pairs_in_queue = queue.mWriteIdx - queue.mReadIdx;
882
if (body_pairs_in_queue >= mStep->mMaxBodyPairsPerQueue)
883
{
884
// Buffer full, process the pair now
885
mStep->mContext->mPhysicsSystem->ProcessBodyPair(mContactAllocator, inPair);
886
}
887
else
888
{
889
// Store the pair in our own queue
890
mStep->mContext->mBodyPairs[mJobIndex * mStep->mMaxBodyPairsPerQueue + queue.mWriteIdx % mStep->mMaxBodyPairsPerQueue] = inPair;
891
++queue.mWriteIdx;
892
}
893
}
894
895
private:
896
PhysicsUpdateContext::Step * mStep;
897
ContactAllocator & mContactAllocator;
898
int mJobIndex;
899
};
900
MyBodyPairCallback add_pair(ioStep, contact_allocator, inJobIndex);
901
902
// Copy active bodies to temporary array, broadphase will reorder them
903
uint32 batch_size = active_bodies_read_idx_end - active_bodies_read_idx;
904
memcpy(active_bodies, mBodyManager.GetActiveBodiesUnsafe(EBodyType::RigidBody) + active_bodies_read_idx, batch_size * sizeof(BodyID));
905
906
// Find pairs in the broadphase
907
mBroadPhase->FindCollidingPairs(active_bodies, batch_size, mPhysicsSettings.mSpeculativeContactDistance, *mObjectVsBroadPhaseLayerFilter, *mObjectLayerPairFilter, add_pair);
908
909
// Check if we have enough pairs in the buffer to start a new job
910
const PhysicsUpdateContext::BodyPairQueue &queue = ioStep->mBodyPairQueues[inJobIndex];
911
uint32 body_pairs_in_queue = queue.mWriteIdx - queue.mReadIdx;
912
if (body_pairs_in_queue >= cNarrowPhaseBatchSize)
913
TrySpawnJobFindCollisions(ioStep);
914
}
915
}
916
else
917
{
918
// Lockless loop to get the next body pair from the pairs buffer
919
const PhysicsUpdateContext *context = ioStep->mContext;
920
int first_read_queue_idx = read_queue_idx;
921
for (;;)
922
{
923
PhysicsUpdateContext::BodyPairQueue &queue = ioStep->mBodyPairQueues[read_queue_idx];
924
925
// Get the next pair to process
926
uint32 pair_idx = queue.mReadIdx;
927
928
// If the pair hasn't been written yet
929
if (pair_idx >= queue.mWriteIdx)
930
{
931
// Go to the next queue
932
read_queue_idx = (read_queue_idx + 1) % ioStep->mBodyPairQueues.size();
933
934
// If we're back at the first queue, we've looked at all of them and found nothing
935
if (read_queue_idx == first_read_queue_idx)
936
{
937
// Collect information from the contact allocator and accumulate it in the step.
938
sFinalizeContactAllocator(*ioStep, contact_allocator);
939
940
// Mark this job as inactive
941
ioStep->mActiveFindCollisionJobs.fetch_and(~PhysicsUpdateContext::JobMask(1 << inJobIndex), memory_order_release);
942
943
// Trigger the next jobs
944
ioStep->mUpdateBroadphaseFinalize.RemoveDependency();
945
ioStep->mFinalizeIslands.RemoveDependency();
946
return;
947
}
948
949
// Try again reading from the next queue
950
continue;
951
}
952
953
// Copy the body pair out of the buffer
954
const BodyPair bp = context->mBodyPairs[read_queue_idx * ioStep->mMaxBodyPairsPerQueue + pair_idx % ioStep->mMaxBodyPairsPerQueue];
955
956
// Mark this pair as taken
957
if (queue.mReadIdx.compare_exchange_strong(pair_idx, pair_idx + 1))
958
{
959
// Process the actual body pair
960
ProcessBodyPair(contact_allocator, bp);
961
break;
962
}
963
}
964
}
965
}
966
}
967
968
void PhysicsSystem::sDefaultSimCollideBodyVsBody(const Body &inBody1, const Body &inBody2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, CollideShapeSettings &ioCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter)
969
{
970
SubShapeIDCreator part1, part2;
971
972
if (inBody1.GetEnhancedInternalEdgeRemovalWithBody(inBody2))
973
{
974
// Collide with enhanced internal edge removal
975
ioCollideShapeSettings.mActiveEdgeMode = EActiveEdgeMode::CollideWithAll;
976
InternalEdgeRemovingCollector::sCollideShapeVsShape(inBody1.GetShape(), inBody2.GetShape(), Vec3::sOne(), Vec3::sOne(), inCenterOfMassTransform1, inCenterOfMassTransform2, part1, part2, ioCollideShapeSettings, ioCollector, inShapeFilter);
977
}
978
else
979
{
980
// Regular collide
981
CollisionDispatch::sCollideShapeVsShape(inBody1.GetShape(), inBody2.GetShape(), Vec3::sOne(), Vec3::sOne(), inCenterOfMassTransform1, inCenterOfMassTransform2, part1, part2, ioCollideShapeSettings, ioCollector, inShapeFilter);
982
}
983
}
984
985
void PhysicsSystem::ProcessBodyPair(ContactAllocator &ioContactAllocator, const BodyPair &inBodyPair)
986
{
987
JPH_PROFILE_FUNCTION();
988
989
// Fetch body pair
990
Body *body1 = &mBodyManager.GetBody(inBodyPair.mBodyA);
991
Body *body2 = &mBodyManager.GetBody(inBodyPair.mBodyB);
992
JPH_ASSERT(body1->IsActive());
993
994
JPH_DET_LOG("ProcessBodyPair: id1: " << inBodyPair.mBodyA << " id2: " << inBodyPair.mBodyB << " p1: " << body1->GetCenterOfMassPosition() << " p2: " << body2->GetCenterOfMassPosition() << " r1: " << body1->GetRotation() << " r2: " << body2->GetRotation());
995
996
// Check for soft bodies
997
if (body2->IsSoftBody())
998
{
999
// If the 2nd body is a soft body and not active, we activate it now
1000
if (!body2->IsActive())
1001
mBodyManager.ActivateBodies(&inBodyPair.mBodyB, 1);
1002
1003
// Soft body processing is done later in the pipeline
1004
return;
1005
}
1006
1007
// Ensure that body1 has the higher motion type (i.e. dynamic trumps kinematic), this ensures that we do the collision detection in the space of a moving body,
1008
// which avoids accuracy problems when testing a very large static object against a small dynamic object
1009
// Ensure that body1 id < body2 id when motion types are the same.
1010
if (body1->GetMotionType() < body2->GetMotionType()
1011
|| (body1->GetMotionType() == body2->GetMotionType() && inBodyPair.mBodyB < inBodyPair.mBodyA))
1012
std::swap(body1, body2);
1013
1014
// Check if the contact points from the previous frame are reusable and if so copy them
1015
bool pair_handled = false, constraint_created = false;
1016
if (mPhysicsSettings.mUseBodyPairContactCache && !(body1->IsCollisionCacheInvalid() || body2->IsCollisionCacheInvalid()))
1017
mContactManager.GetContactsFromCache(ioContactAllocator, *body1, *body2, pair_handled, constraint_created);
1018
1019
// If the cache hasn't handled this body pair do actual collision detection
1020
if (!pair_handled)
1021
{
1022
// Create entry in the cache for this body pair
1023
// Needs to happen irrespective if we found a collision or not (we want to remember that no collision was found too)
1024
ContactConstraintManager::BodyPairHandle body_pair_handle = mContactManager.AddBodyPair(ioContactAllocator, *body1, *body2);
1025
if (body_pair_handle == nullptr)
1026
return; // Out of cache space
1027
1028
// Create the query settings
1029
CollideShapeSettings settings;
1030
settings.mCollectFacesMode = ECollectFacesMode::CollectFaces;
1031
settings.mActiveEdgeMode = mPhysicsSettings.mCheckActiveEdges? EActiveEdgeMode::CollideOnlyWithActive : EActiveEdgeMode::CollideWithAll;
1032
settings.mMaxSeparationDistance = body1->IsSensor() || body2->IsSensor()? 0.0f : mPhysicsSettings.mSpeculativeContactDistance;
1033
settings.mActiveEdgeMovementDirection = body1->GetLinearVelocity() - body2->GetLinearVelocity();
1034
1035
// Create shape filter
1036
SimShapeFilterWrapperUnion shape_filter_union(mSimShapeFilter, body1);
1037
SimShapeFilterWrapper &shape_filter = shape_filter_union.GetSimShapeFilterWrapper();
1038
shape_filter.SetBody2(body2);
1039
1040
// Get transforms relative to body1
1041
RVec3 offset = body1->GetCenterOfMassPosition();
1042
Mat44 transform1 = Mat44::sRotation(body1->GetRotation());
1043
Mat44 transform2 = body2->GetCenterOfMassTransform().PostTranslated(-offset).ToMat44();
1044
1045
if (mPhysicsSettings.mUseManifoldReduction // Check global flag
1046
&& body1->GetUseManifoldReductionWithBody(*body2)) // Check body flag
1047
{
1048
// Version WITH contact manifold reduction
1049
1050
class MyManifold : public ContactManifold
1051
{
1052
public:
1053
Vec3 mFirstWorldSpaceNormal;
1054
};
1055
1056
// A temporary structure that allows us to keep track of the all manifolds between this body pair
1057
using Manifolds = StaticArray<MyManifold, 32>;
1058
1059
// Create collector
1060
class ReductionCollideShapeCollector : public CollideShapeCollector
1061
{
1062
public:
1063
ReductionCollideShapeCollector(PhysicsSystem *inSystem, const Body *inBody1, const Body *inBody2) :
1064
mSystem(inSystem),
1065
mBody1(inBody1),
1066
mBody2(inBody2)
1067
{
1068
}
1069
1070
virtual void AddHit(const CollideShapeResult &inResult) override
1071
{
1072
// The first body should be the one with the highest motion type
1073
JPH_ASSERT(mBody1->GetMotionType() >= mBody2->GetMotionType());
1074
JPH_ASSERT(!ShouldEarlyOut());
1075
1076
// Test if we want to accept this hit
1077
if (mValidateBodyPair)
1078
{
1079
switch (mSystem->mContactManager.ValidateContactPoint(*mBody1, *mBody2, mBody1->GetCenterOfMassPosition(), inResult))
1080
{
1081
case ValidateResult::AcceptContact:
1082
// We're just accepting this one, nothing to do
1083
break;
1084
1085
case ValidateResult::AcceptAllContactsForThisBodyPair:
1086
// Accept and stop calling the validate callback
1087
mValidateBodyPair = false;
1088
break;
1089
1090
case ValidateResult::RejectContact:
1091
// Skip this contact
1092
return;
1093
1094
case ValidateResult::RejectAllContactsForThisBodyPair:
1095
// Skip this and early out
1096
ForceEarlyOut();
1097
return;
1098
}
1099
}
1100
1101
// Calculate normal
1102
Vec3 world_space_normal = inResult.mPenetrationAxis.Normalized();
1103
1104
// Check if we can add it to an existing manifold
1105
Manifolds::iterator manifold;
1106
float contact_normal_cos_max_delta_rot = mSystem->mPhysicsSettings.mContactNormalCosMaxDeltaRotation;
1107
for (manifold = mManifolds.begin(); manifold != mManifolds.end(); ++manifold)
1108
if (world_space_normal.Dot(manifold->mFirstWorldSpaceNormal) >= contact_normal_cos_max_delta_rot)
1109
{
1110
// Update average normal
1111
manifold->mWorldSpaceNormal += world_space_normal;
1112
manifold->mPenetrationDepth = max(manifold->mPenetrationDepth, inResult.mPenetrationDepth);
1113
break;
1114
}
1115
if (manifold == mManifolds.end())
1116
{
1117
// Check if array is full
1118
if (mManifolds.size() == mManifolds.capacity())
1119
{
1120
// Full, find manifold with least amount of penetration
1121
manifold = mManifolds.begin();
1122
for (Manifolds::iterator m = mManifolds.begin() + 1; m < mManifolds.end(); ++m)
1123
if (m->mPenetrationDepth < manifold->mPenetrationDepth)
1124
manifold = m;
1125
1126
// If this contacts penetration is smaller than the smallest manifold, we skip this contact
1127
if (inResult.mPenetrationDepth < manifold->mPenetrationDepth)
1128
return;
1129
1130
// Replace the manifold
1131
*manifold = { { mBody1->GetCenterOfMassPosition(), world_space_normal, inResult.mPenetrationDepth, inResult.mSubShapeID1, inResult.mSubShapeID2, { }, { } }, world_space_normal };
1132
}
1133
else
1134
{
1135
// Not full, create new manifold
1136
mManifolds.push_back({ { mBody1->GetCenterOfMassPosition(), world_space_normal, inResult.mPenetrationDepth, inResult.mSubShapeID1, inResult.mSubShapeID2, { }, { } }, world_space_normal });
1137
manifold = mManifolds.end() - 1;
1138
}
1139
}
1140
1141
// Determine contact points
1142
const PhysicsSettings &settings = mSystem->mPhysicsSettings;
1143
ManifoldBetweenTwoFaces(inResult.mContactPointOn1, inResult.mContactPointOn2, inResult.mPenetrationAxis, settings.mSpeculativeContactDistance + settings.mManifoldTolerance, inResult.mShape1Face, inResult.mShape2Face, manifold->mRelativeContactPointsOn1, manifold->mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, mBody1->GetCenterOfMassPosition()));
1144
1145
// Prune if we have more than 32 points (this means we could run out of space in the next iteration)
1146
if (manifold->mRelativeContactPointsOn1.size() > 32)
1147
PruneContactPoints(manifold->mFirstWorldSpaceNormal, manifold->mRelativeContactPointsOn1, manifold->mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, manifold->mBaseOffset));
1148
}
1149
1150
PhysicsSystem * mSystem;
1151
const Body * mBody1;
1152
const Body * mBody2;
1153
bool mValidateBodyPair = true;
1154
Manifolds mManifolds;
1155
};
1156
ReductionCollideShapeCollector collector(this, body1, body2);
1157
1158
// Perform collision detection between the two shapes
1159
mSimCollideBodyVsBody(*body1, *body2, transform1, transform2, settings, collector, shape_filter);
1160
1161
// Add the contacts
1162
for (ContactManifold &manifold : collector.mManifolds)
1163
{
1164
// Normalize the normal (is a sum of all normals from merged manifolds)
1165
manifold.mWorldSpaceNormal = manifold.mWorldSpaceNormal.Normalized();
1166
1167
// If we still have too many points, prune them now
1168
if (manifold.mRelativeContactPointsOn1.size() > 4)
1169
PruneContactPoints(manifold.mWorldSpaceNormal, manifold.mRelativeContactPointsOn1, manifold.mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, manifold.mBaseOffset));
1170
1171
// Actually add the contact points to the manager
1172
constraint_created |= mContactManager.AddContactConstraint(ioContactAllocator, body_pair_handle, *body1, *body2, manifold);
1173
}
1174
}
1175
else
1176
{
1177
// Version WITHOUT contact manifold reduction
1178
1179
// Create collector
1180
class NonReductionCollideShapeCollector : public CollideShapeCollector
1181
{
1182
public:
1183
NonReductionCollideShapeCollector(PhysicsSystem *inSystem, ContactAllocator &ioContactAllocator, Body *inBody1, Body *inBody2, const ContactConstraintManager::BodyPairHandle &inPairHandle) :
1184
mSystem(inSystem),
1185
mContactAllocator(ioContactAllocator),
1186
mBody1(inBody1),
1187
mBody2(inBody2),
1188
mBodyPairHandle(inPairHandle)
1189
{
1190
}
1191
1192
virtual void AddHit(const CollideShapeResult &inResult) override
1193
{
1194
// The first body should be the one with the highest motion type
1195
JPH_ASSERT(mBody1->GetMotionType() >= mBody2->GetMotionType());
1196
JPH_ASSERT(!ShouldEarlyOut());
1197
1198
// Test if we want to accept this hit
1199
if (mValidateBodyPair)
1200
{
1201
switch (mSystem->mContactManager.ValidateContactPoint(*mBody1, *mBody2, mBody1->GetCenterOfMassPosition(), inResult))
1202
{
1203
case ValidateResult::AcceptContact:
1204
// We're just accepting this one, nothing to do
1205
break;
1206
1207
case ValidateResult::AcceptAllContactsForThisBodyPair:
1208
// Accept and stop calling the validate callback
1209
mValidateBodyPair = false;
1210
break;
1211
1212
case ValidateResult::RejectContact:
1213
// Skip this contact
1214
return;
1215
1216
case ValidateResult::RejectAllContactsForThisBodyPair:
1217
// Skip this and early out
1218
ForceEarlyOut();
1219
return;
1220
}
1221
}
1222
1223
// Determine contact points
1224
ContactManifold manifold;
1225
manifold.mBaseOffset = mBody1->GetCenterOfMassPosition();
1226
const PhysicsSettings &settings = mSystem->mPhysicsSettings;
1227
ManifoldBetweenTwoFaces(inResult.mContactPointOn1, inResult.mContactPointOn2, inResult.mPenetrationAxis, settings.mSpeculativeContactDistance + settings.mManifoldTolerance, inResult.mShape1Face, inResult.mShape2Face, manifold.mRelativeContactPointsOn1, manifold.mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, manifold.mBaseOffset));
1228
1229
// Calculate normal
1230
manifold.mWorldSpaceNormal = inResult.mPenetrationAxis.Normalized();
1231
1232
// Store penetration depth
1233
manifold.mPenetrationDepth = inResult.mPenetrationDepth;
1234
1235
// Prune if we have more than 4 points
1236
if (manifold.mRelativeContactPointsOn1.size() > 4)
1237
PruneContactPoints(manifold.mWorldSpaceNormal, manifold.mRelativeContactPointsOn1, manifold.mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, manifold.mBaseOffset));
1238
1239
// Set other properties
1240
manifold.mSubShapeID1 = inResult.mSubShapeID1;
1241
manifold.mSubShapeID2 = inResult.mSubShapeID2;
1242
1243
// Actually add the contact points to the manager
1244
mConstraintCreated |= mSystem->mContactManager.AddContactConstraint(mContactAllocator, mBodyPairHandle, *mBody1, *mBody2, manifold);
1245
}
1246
1247
PhysicsSystem * mSystem;
1248
ContactAllocator & mContactAllocator;
1249
Body * mBody1;
1250
Body * mBody2;
1251
ContactConstraintManager::BodyPairHandle mBodyPairHandle;
1252
bool mValidateBodyPair = true;
1253
bool mConstraintCreated = false;
1254
};
1255
NonReductionCollideShapeCollector collector(this, ioContactAllocator, body1, body2, body_pair_handle);
1256
1257
// Perform collision detection between the two shapes
1258
mSimCollideBodyVsBody(*body1, *body2, transform1, transform2, settings, collector, shape_filter);
1259
1260
constraint_created = collector.mConstraintCreated;
1261
}
1262
}
1263
1264
// If a contact constraint was created, we need to do some extra work
1265
if (constraint_created)
1266
{
1267
// Wake up sleeping bodies
1268
BodyID body_ids[2];
1269
int num_bodies = 0;
1270
if (body1->IsDynamic() && !body1->IsActive())
1271
body_ids[num_bodies++] = body1->GetID();
1272
if (body2->IsDynamic() && !body2->IsActive())
1273
body_ids[num_bodies++] = body2->GetID();
1274
if (num_bodies > 0)
1275
mBodyManager.ActivateBodies(body_ids, num_bodies);
1276
1277
// Link the two bodies
1278
mIslandBuilder.LinkBodies(body1->GetIndexInActiveBodiesInternal(), body2->GetIndexInActiveBodiesInternal());
1279
}
1280
}
1281
1282
void PhysicsSystem::JobFinalizeIslands(PhysicsUpdateContext *ioContext)
1283
{
1284
#ifdef JPH_ENABLE_ASSERTS
1285
// We only touch island data
1286
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::None);
1287
#endif
1288
1289
// Finish collecting the islands, at this point the active body list doesn't change so it's safe to access
1290
mIslandBuilder.Finalize(mBodyManager.GetActiveBodiesUnsafe(EBodyType::RigidBody), mBodyManager.GetNumActiveBodies(EBodyType::RigidBody), mContactManager.GetNumConstraints(), ioContext->mTempAllocator);
1291
1292
// Prepare the large island splitter
1293
if (mPhysicsSettings.mUseLargeIslandSplitter)
1294
mLargeIslandSplitter.Prepare(mIslandBuilder, mBodyManager.GetNumActiveBodies(EBodyType::RigidBody), ioContext->mTempAllocator);
1295
}
1296
1297
void PhysicsSystem::JobBodySetIslandIndex()
1298
{
1299
#ifdef JPH_ENABLE_ASSERTS
1300
// We only touch island data
1301
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::None);
1302
#endif
1303
1304
// Loop through the result and tag all bodies with an island index
1305
for (uint32 island_idx = 0, n = mIslandBuilder.GetNumIslands(); island_idx < n; ++island_idx)
1306
{
1307
BodyID *body_start, *body_end;
1308
mIslandBuilder.GetBodiesInIsland(island_idx, body_start, body_end);
1309
for (const BodyID *body = body_start; body < body_end; ++body)
1310
mBodyManager.GetBody(*body).GetMotionProperties()->SetIslandIndexInternal(island_idx);
1311
}
1312
}
1313
1314
JPH_SUPPRESS_WARNING_PUSH
1315
JPH_CLANG_SUPPRESS_WARNING("-Wundefined-func-template") // ConstraintManager::sWarmStartVelocityConstraints / ContactConstraintManager::WarmStartVelocityConstraints is instantiated in the cpp file
1316
1317
void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
1318
{
1319
#ifdef JPH_ENABLE_ASSERTS
1320
// We update velocities and need to read positions to do so
1321
BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::Read);
1322
#endif
1323
1324
float delta_time = ioContext->mStepDeltaTime;
1325
Constraint **active_constraints = ioContext->mActiveConstraints;
1326
1327
// Only the first step to correct for the delta time difference in the previous update
1328
float warm_start_impulse_ratio = ioStep->mIsFirst? ioContext->mWarmStartImpulseRatio : 1.0f;
1329
1330
bool check_islands = true, check_split_islands = mPhysicsSettings.mUseLargeIslandSplitter;
1331
for (;;)
1332
{
1333
// First try to get work from large islands
1334
if (check_split_islands)
1335
{
1336
bool first_iteration;
1337
uint split_island_index;
1338
uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;
1339
switch (mLargeIslandSplitter.FetchNextBatch(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, first_iteration))
1340
{
1341
case LargeIslandSplitter::EStatus::BatchRetrieved:
1342
{
1343
if (first_iteration)
1344
{
1345
// Iteration 0 is used to warm start the batch (we added 1 to the number of iterations in LargeIslandSplitter::SplitIsland)
1346
DummyCalculateSolverSteps dummy;
1347
ConstraintManager::sWarmStartVelocityConstraints(active_constraints, constraints_begin, constraints_end, warm_start_impulse_ratio, dummy);
1348
mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio, dummy);
1349
}
1350
else
1351
{
1352
// Solve velocity constraints
1353
ConstraintManager::sSolveVelocityConstraints(active_constraints, constraints_begin, constraints_end, delta_time);
1354
mContactManager.SolveVelocityConstraints(contacts_begin, contacts_end);
1355
}
1356
1357
// Mark the batch as processed
1358
bool last_iteration, final_batch;
1359
mLargeIslandSplitter.MarkBatchProcessed(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, last_iteration, final_batch);
1360
1361
// Save back the lambdas in the contact cache for the warm start of the next physics update
1362
if (last_iteration)
1363
mContactManager.StoreAppliedImpulses(contacts_begin, contacts_end);
1364
1365
// We processed work, loop again
1366
continue;
1367
}
1368
1369
case LargeIslandSplitter::EStatus::WaitingForBatch:
1370
break;
1371
1372
case LargeIslandSplitter::EStatus::AllBatchesDone:
1373
check_split_islands = false;
1374
break;
1375
}
1376
}
1377
1378
// If that didn't succeed try to process an island
1379
if (check_islands)
1380
{
1381
// Next island
1382
uint32 island_idx = ioStep->mSolveVelocityConstraintsNextIsland++;
1383
if (island_idx >= mIslandBuilder.GetNumIslands())
1384
{
1385
// We processed all islands, stop checking islands
1386
check_islands = false;
1387
continue;
1388
}
1389
1390
JPH_PROFILE("Island");
1391
1392
// Get iterators for this island
1393
uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;
1394
bool has_constraints = mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end);
1395
bool has_contacts = mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end);
1396
1397
// If we don't have any contacts or constraints, we know that none of the following islands have any contacts or constraints
1398
// (because they're sorted by most constraints first). This means we're done.
1399
if (!has_contacts && !has_constraints)
1400
{
1401
#ifdef JPH_ENABLE_ASSERTS
1402
// Validate our assumption that the next islands don't have any constraints or contacts
1403
for (; island_idx < mIslandBuilder.GetNumIslands(); ++island_idx)
1404
{
1405
JPH_ASSERT(!mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end));
1406
JPH_ASSERT(!mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end));
1407
}
1408
#endif // JPH_ENABLE_ASSERTS
1409
1410
check_islands = false;
1411
continue;
1412
}
1413
1414
// Sorting is costly but needed for a deterministic simulation, allow the user to turn this off
1415
if (mPhysicsSettings.mDeterministicSimulation)
1416
{
1417
// Sort constraints to give a deterministic simulation
1418
ConstraintManager::sSortConstraints(active_constraints, constraints_begin, constraints_end);
1419
1420
// Sort contacts to give a deterministic simulation
1421
mContactManager.SortContacts(contacts_begin, contacts_end);
1422
}
1423
1424
// Split up large islands
1425
CalculateSolverSteps steps_calculator(mPhysicsSettings);
1426
if (mPhysicsSettings.mUseLargeIslandSplitter
1427
&& mLargeIslandSplitter.SplitIsland(island_idx, mIslandBuilder, mBodyManager, mContactManager, active_constraints, steps_calculator))
1428
continue; // Loop again to try to fetch the newly split island
1429
1430
// We didn't create a split, just run the solver now for this entire island. Begin by warm starting.
1431
ConstraintManager::sWarmStartVelocityConstraints(active_constraints, constraints_begin, constraints_end, warm_start_impulse_ratio, steps_calculator);
1432
mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio, steps_calculator);
1433
steps_calculator.Finalize();
1434
1435
// Store the number of position steps for later
1436
mIslandBuilder.SetNumPositionSteps(island_idx, steps_calculator.GetNumPositionSteps());
1437
1438
// Solve velocity constraints
1439
for (uint velocity_step = 0; velocity_step < steps_calculator.GetNumVelocitySteps(); ++velocity_step)
1440
{
1441
bool applied_impulse = ConstraintManager::sSolveVelocityConstraints(active_constraints, constraints_begin, constraints_end, delta_time);
1442
applied_impulse |= mContactManager.SolveVelocityConstraints(contacts_begin, contacts_end);
1443
if (!applied_impulse)
1444
break;
1445
}
1446
1447
// Save back the lambdas in the contact cache for the warm start of the next physics update
1448
mContactManager.StoreAppliedImpulses(contacts_begin, contacts_end);
1449
1450
// We processed work, loop again
1451
continue;
1452
}
1453
1454
if (check_islands)
1455
{
1456
// If there are islands, we don't need to wait and can pick up new work
1457
continue;
1458
}
1459
else if (check_split_islands)
1460
{
1461
// If there are split islands, but we didn't do any work, give up a time slice
1462
std::this_thread::yield();
1463
}
1464
else
1465
{
1466
// No more work
1467
break;
1468
}
1469
}
1470
}
1471
1472
JPH_SUPPRESS_WARNING_POP
1473
1474
void PhysicsSystem::JobPreIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
1475
{
1476
// Reserve enough space for all bodies that may need a cast
1477
TempAllocator *temp_allocator = ioContext->mTempAllocator;
1478
JPH_ASSERT(ioStep->mCCDBodies == nullptr);
1479
ioStep->mCCDBodiesCapacity = mBodyManager.GetNumActiveCCDBodies();
1480
ioStep->mCCDBodies = (CCDBody *)temp_allocator->Allocate(ioStep->mCCDBodiesCapacity * sizeof(CCDBody));
1481
1482
// Initialize the mapping table between active body and CCD body
1483
JPH_ASSERT(ioStep->mActiveBodyToCCDBody == nullptr);
1484
ioStep->mNumActiveBodyToCCDBody = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);
1485
ioStep->mActiveBodyToCCDBody = (int *)temp_allocator->Allocate(ioStep->mNumActiveBodyToCCDBody * sizeof(int));
1486
1487
// Prepare the split island builder for solving the position constraints
1488
mLargeIslandSplitter.PrepareForSolvePositions();
1489
}
1490
1491
void PhysicsSystem::JobIntegrateVelocity(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
1492
{
1493
#ifdef JPH_ENABLE_ASSERTS
1494
// We update positions and need velocity to do so, we also clamp velocities so need to write to them
1495
BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::ReadWrite);
1496
#endif
1497
1498
float delta_time = ioContext->mStepDeltaTime;
1499
const BodyID *active_bodies = mBodyManager.GetActiveBodiesUnsafe(EBodyType::RigidBody);
1500
uint32 num_active_bodies = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);
1501
uint32 num_active_bodies_after_find_collisions = ioStep->mActiveBodyReadIdx;
1502
1503
// We can move bodies that are not part of an island. In this case we need to notify the broadphase of the movement.
1504
static constexpr int cBodiesBatch = 64;
1505
BodyID *bodies_to_update_bounds = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));
1506
int num_bodies_to_update_bounds = 0;
1507
1508
for (;;)
1509
{
1510
// Atomically fetch a batch of bodies
1511
uint32 active_body_idx = ioStep->mIntegrateVelocityReadIdx.fetch_add(cIntegrateVelocityBatchSize);
1512
if (active_body_idx >= num_active_bodies)
1513
break;
1514
1515
// Calculate the end of the batch
1516
uint32 active_body_idx_end = min(num_active_bodies, active_body_idx + cIntegrateVelocityBatchSize);
1517
1518
// Process the batch
1519
while (active_body_idx < active_body_idx_end)
1520
{
1521
// Update the positions using an Symplectic Euler step (which integrates using the updated velocity v1' rather
1522
// than the original velocity v1):
1523
// x1' = x1 + h * v1'
1524
// At this point the active bodies list does not change, so it is safe to access the array.
1525
BodyID body_id = active_bodies[active_body_idx];
1526
Body &body = mBodyManager.GetBody(body_id);
1527
MotionProperties *mp = body.GetMotionProperties();
1528
1529
JPH_DET_LOG("JobIntegrateVelocity: id: " << body_id << " v: " << body.GetLinearVelocity() << " w: " << body.GetAngularVelocity());
1530
1531
// Clamp velocities (not for kinematic bodies)
1532
if (body.IsDynamic())
1533
{
1534
mp->ClampLinearVelocity();
1535
mp->ClampAngularVelocity();
1536
}
1537
1538
// Update the rotation of the body according to the angular velocity
1539
// For motion type discrete we need to do this anyway, for motion type linear cast we have multiple choices
1540
// 1. Rotate the body first and then sweep
1541
// 2. First sweep and then rotate the body at the end
1542
// 3. Pick some in between rotation (e.g. half way), then sweep and finally rotate the remainder
1543
// (1) has some clear advantages as when a long thin body hits a surface away from the center of mass, this will result in a large angular velocity and a limited reduction in linear velocity.
1544
// When simulation the rotation first before doing the translation, the body will be able to rotate away from the contact point allowing the center of mass to approach the surface. When using
1545
// approach (2) in this case what will happen is that we will immediately detect the same collision again (the body has not rotated and the body was already colliding at the end of the previous
1546
// time step) resulting in a lot of stolen time and the body appearing to be frozen in an unnatural pose (like it is glued at an angle to the surface). (2) obviously has some negative side effects
1547
// too as simulating the rotation first may cause it to tunnel through a small object that the linear cast might have otherwise detected. In any case a linear cast is not good for detecting
1548
// tunneling due to angular rotation, so we don't care about that too much (you'd need a full cast to take angular effects into account).
1549
body.AddRotationStep(body.GetAngularVelocity() * delta_time);
1550
1551
// Get delta position
1552
Vec3 delta_pos = body.GetLinearVelocity() * delta_time;
1553
1554
// If the position should be updated (or if it is delayed because of CCD)
1555
bool update_position = true;
1556
1557
switch (mp->GetMotionQuality())
1558
{
1559
case EMotionQuality::Discrete:
1560
// No additional collision checking to be done
1561
break;
1562
1563
case EMotionQuality::LinearCast:
1564
if (body.IsDynamic() // Kinematic bodies cannot be stopped
1565
&& !body.IsSensor()) // We don't support CCD sensors
1566
{
1567
// Determine inner radius (the smallest sphere that fits into the shape)
1568
float inner_radius = body.GetShape()->GetInnerRadius();
1569
JPH_ASSERT(inner_radius > 0.0f, "The shape has no inner radius, this makes the shape unsuitable for the linear cast motion quality as we cannot move it without risking tunneling.");
1570
1571
// Measure translation in this step and check if it above the threshold to perform a linear cast
1572
float linear_cast_threshold_sq = Square(mPhysicsSettings.mLinearCastThreshold * inner_radius);
1573
if (delta_pos.LengthSq() > linear_cast_threshold_sq)
1574
{
1575
// This body needs a cast
1576
uint32 ccd_body_idx = ioStep->mNumCCDBodies++;
1577
JPH_ASSERT(active_body_idx < ioStep->mNumActiveBodyToCCDBody);
1578
ioStep->mActiveBodyToCCDBody[active_body_idx] = ccd_body_idx;
1579
new (&ioStep->mCCDBodies[ccd_body_idx]) CCDBody(body_id, delta_pos, linear_cast_threshold_sq, min(mPhysicsSettings.mPenetrationSlop, mPhysicsSettings.mLinearCastMaxPenetration * inner_radius));
1580
1581
update_position = false;
1582
}
1583
}
1584
break;
1585
}
1586
1587
if (update_position)
1588
{
1589
// Move the body now
1590
body.AddPositionStep(delta_pos);
1591
1592
// If the body was activated due to an earlier CCD step it will have an index in the active
1593
// body list that it higher than the highest one we processed during FindCollisions
1594
// which means it hasn't been assigned an island and will not be updated by an island
1595
// this means that we need to update its bounds manually
1596
if (mp->GetIndexInActiveBodiesInternal() >= num_active_bodies_after_find_collisions)
1597
{
1598
body.CalculateWorldSpaceBoundsInternal();
1599
bodies_to_update_bounds[num_bodies_to_update_bounds++] = body.GetID();
1600
if (num_bodies_to_update_bounds == cBodiesBatch)
1601
{
1602
// Buffer full, flush now
1603
mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
1604
num_bodies_to_update_bounds = 0;
1605
}
1606
}
1607
1608
// We did not create a CCD body
1609
ioStep->mActiveBodyToCCDBody[active_body_idx] = -1;
1610
}
1611
1612
active_body_idx++;
1613
}
1614
}
1615
1616
// Notify change bounds on requested bodies
1617
if (num_bodies_to_update_bounds > 0)
1618
mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
1619
}
1620
1621
void PhysicsSystem::JobPostIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep) const
1622
{
1623
// Validate that our reservations were correct
1624
JPH_ASSERT(ioStep->mNumCCDBodies <= mBodyManager.GetNumActiveCCDBodies());
1625
1626
if (ioStep->mNumCCDBodies == 0)
1627
{
1628
// No continuous collision detection jobs -> kick the next job ourselves
1629
ioStep->mContactRemovedCallbacks.RemoveDependency();
1630
}
1631
else
1632
{
1633
// Run the continuous collision detection jobs
1634
int num_continuous_collision_jobs = min(int(ioStep->mNumCCDBodies + cNumCCDBodiesPerJob - 1) / cNumCCDBodiesPerJob, ioContext->GetMaxConcurrency());
1635
ioStep->mResolveCCDContacts.AddDependency(num_continuous_collision_jobs);
1636
ioStep->mContactRemovedCallbacks.AddDependency(num_continuous_collision_jobs - 1); // Already had 1 dependency
1637
for (int i = 0; i < num_continuous_collision_jobs; ++i)
1638
{
1639
JobHandle job = ioContext->mJobSystem->CreateJob("FindCCDContacts", cColorFindCCDContacts, [ioContext, ioStep]()
1640
{
1641
ioContext->mPhysicsSystem->JobFindCCDContacts(ioContext, ioStep);
1642
1643
ioStep->mResolveCCDContacts.RemoveDependency();
1644
ioStep->mContactRemovedCallbacks.RemoveDependency();
1645
});
1646
ioContext->mBarrier->AddJob(job);
1647
}
1648
}
1649
}
1650
1651
// Helper function to calculate the motion of a body during this CCD step
1652
inline static Vec3 sCalculateBodyMotion(const Body &inBody, float inDeltaTime)
1653
{
1654
// If the body is linear casting, the body has not yet moved so we need to calculate its motion
1655
if (inBody.IsDynamic() && inBody.GetMotionProperties()->GetMotionQuality() == EMotionQuality::LinearCast)
1656
return inDeltaTime * inBody.GetLinearVelocity();
1657
1658
// Body has already moved, so we don't need to correct for anything
1659
return Vec3::sZero();
1660
}
1661
1662
// Helper function that finds the CCD body corresponding to a body (if it exists)
1663
inline static PhysicsUpdateContext::Step::CCDBody *sGetCCDBody(const Body &inBody, PhysicsUpdateContext::Step *inStep)
1664
{
1665
// Only rigid bodies can have a CCD body
1666
if (!inBody.IsRigidBody())
1667
return nullptr;
1668
1669
// If the body has no motion properties it cannot have a CCD body
1670
const MotionProperties *motion_properties = inBody.GetMotionPropertiesUnchecked();
1671
if (motion_properties == nullptr)
1672
return nullptr;
1673
1674
// If it is not active it cannot have a CCD body
1675
uint32 active_index = motion_properties->GetIndexInActiveBodiesInternal();
1676
if (active_index == Body::cInactiveIndex)
1677
return nullptr;
1678
1679
// Check if the active body has a corresponding CCD body
1680
JPH_ASSERT(active_index < inStep->mNumActiveBodyToCCDBody); // Ensure that the body has a mapping to CCD body
1681
int ccd_index = inStep->mActiveBodyToCCDBody[active_index];
1682
if (ccd_index < 0)
1683
return nullptr;
1684
1685
PhysicsUpdateContext::Step::CCDBody *ccd_body = &inStep->mCCDBodies[ccd_index];
1686
JPH_ASSERT(ccd_body->mBodyID1 == inBody.GetID(), "We found the wrong CCD body!");
1687
return ccd_body;
1688
}
1689
1690
void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
1691
{
1692
#ifdef JPH_ENABLE_ASSERTS
1693
// We only read positions, but the validate callback may read body positions and velocities
1694
BodyAccess::Grant grant(BodyAccess::EAccess::Read, BodyAccess::EAccess::Read);
1695
#endif
1696
1697
// Allocation context for allocating new contact points
1698
ContactAllocator contact_allocator(mContactManager.GetContactAllocator());
1699
1700
// Settings
1701
ShapeCastSettings settings;
1702
settings.mUseShrunkenShapeAndConvexRadius = true;
1703
settings.mBackFaceModeTriangles = EBackFaceMode::IgnoreBackFaces;
1704
settings.mBackFaceModeConvex = EBackFaceMode::IgnoreBackFaces;
1705
settings.mReturnDeepestPoint = true;
1706
settings.mCollectFacesMode = ECollectFacesMode::CollectFaces;
1707
settings.mActiveEdgeMode = mPhysicsSettings.mCheckActiveEdges? EActiveEdgeMode::CollideOnlyWithActive : EActiveEdgeMode::CollideWithAll;
1708
1709
for (;;)
1710
{
1711
// Fetch the next body to cast
1712
uint32 idx = ioStep->mNextCCDBody++;
1713
if (idx >= ioStep->mNumCCDBodies)
1714
break;
1715
CCDBody &ccd_body = ioStep->mCCDBodies[idx];
1716
const Body &body = mBodyManager.GetBody(ccd_body.mBodyID1);
1717
1718
// Filter out layers
1719
DefaultBroadPhaseLayerFilter broadphase_layer_filter = GetDefaultBroadPhaseLayerFilter(body.GetObjectLayer());
1720
DefaultObjectLayerFilter object_layer_filter = GetDefaultLayerFilter(body.GetObjectLayer());
1721
1722
#ifdef JPH_DEBUG_RENDERER
1723
// Draw start and end shape of cast
1724
if (sDrawMotionQualityLinearCast)
1725
{
1726
RMat44 com = body.GetCenterOfMassTransform();
1727
body.GetShape()->Draw(DebugRenderer::sInstance, com, Vec3::sOne(), Color::sGreen, false, true);
1728
DebugRenderer::sInstance->DrawArrow(com.GetTranslation(), com.GetTranslation() + ccd_body.mDeltaPosition, Color::sGreen, 0.1f);
1729
body.GetShape()->Draw(DebugRenderer::sInstance, com.PostTranslated(ccd_body.mDeltaPosition), Vec3::sOne(), Color::sRed, false, true);
1730
}
1731
#endif // JPH_DEBUG_RENDERER
1732
1733
// Create a collector that will find the maximum distance allowed to travel while not penetrating more than 'max penetration'
1734
class CCDNarrowPhaseCollector : public CastShapeCollector
1735
{
1736
public:
1737
CCDNarrowPhaseCollector(const BodyManager &inBodyManager, ContactConstraintManager &inContactConstraintManager, CCDBody &inCCDBody, ShapeCastResult &inResult, float inDeltaTime) :
1738
mBodyManager(inBodyManager),
1739
mContactConstraintManager(inContactConstraintManager),
1740
mCCDBody(inCCDBody),
1741
mResult(inResult),
1742
mDeltaTime(inDeltaTime)
1743
{
1744
}
1745
1746
virtual void AddHit(const ShapeCastResult &inResult) override
1747
{
1748
JPH_PROFILE_FUNCTION();
1749
1750
// Check if this is a possible earlier hit than the one before
1751
float fraction = inResult.mFraction;
1752
if (fraction < mCCDBody.mFractionPlusSlop)
1753
{
1754
// Normalize normal
1755
Vec3 normal = inResult.mPenetrationAxis.Normalized();
1756
1757
// Calculate how much we can add to the fraction to penetrate the collision point by mMaxPenetration.
1758
// Note that the normal is pointing towards body 2!
1759
// Let the extra distance that we can travel along delta_pos be 'dist': mMaxPenetration / dist = cos(angle between normal and delta_pos) = normal . delta_pos / |delta_pos|
1760
// <=> dist = mMaxPenetration * |delta_pos| / normal . delta_pos
1761
// Converting to a faction: delta_fraction = dist / |delta_pos| = mLinearCastTreshold / normal . delta_pos
1762
float denominator = normal.Dot(mCCDBody.mDeltaPosition);
1763
if (denominator > mCCDBody.mMaxPenetration) // Avoid dividing by zero, if extra hit fraction > 1 there's also no point in continuing
1764
{
1765
float fraction_plus_slop = fraction + mCCDBody.mMaxPenetration / denominator;
1766
if (fraction_plus_slop < mCCDBody.mFractionPlusSlop)
1767
{
1768
const Body &body2 = mBodyManager.GetBody(inResult.mBodyID2);
1769
1770
// Check if we've already accepted all hits from this body
1771
if (mValidateBodyPair)
1772
{
1773
// Validate the contact result
1774
const Body &body1 = mBodyManager.GetBody(mCCDBody.mBodyID1);
1775
ValidateResult validate_result = mContactConstraintManager.ValidateContactPoint(body1, body2, body1.GetCenterOfMassPosition(), inResult); // Note that the center of mass of body 1 is the start of the sweep and is used as base offset below
1776
switch (validate_result)
1777
{
1778
case ValidateResult::AcceptContact:
1779
// Just continue
1780
break;
1781
1782
case ValidateResult::AcceptAllContactsForThisBodyPair:
1783
// Accept this and all following contacts from this body
1784
mValidateBodyPair = false;
1785
break;
1786
1787
case ValidateResult::RejectContact:
1788
return;
1789
1790
case ValidateResult::RejectAllContactsForThisBodyPair:
1791
// Reject this and all following contacts from this body
1792
mRejectAll = true;
1793
ForceEarlyOut();
1794
return;
1795
}
1796
}
1797
1798
// This is the earliest hit so far, store it
1799
mCCDBody.mContactNormal = normal;
1800
mCCDBody.mBodyID2 = inResult.mBodyID2;
1801
mCCDBody.mSubShapeID2 = inResult.mSubShapeID2;
1802
mCCDBody.mFraction = fraction;
1803
mCCDBody.mFractionPlusSlop = fraction_plus_slop;
1804
mResult = inResult;
1805
1806
// Result was assuming body 2 is not moving, but it is, so we need to correct for it
1807
Vec3 movement2 = fraction * sCalculateBodyMotion(body2, mDeltaTime);
1808
if (!movement2.IsNearZero())
1809
{
1810
mResult.mContactPointOn1 += movement2;
1811
mResult.mContactPointOn2 += movement2;
1812
for (Vec3 &v : mResult.mShape1Face)
1813
v += movement2;
1814
for (Vec3 &v : mResult.mShape2Face)
1815
v += movement2;
1816
}
1817
1818
// Update early out fraction
1819
UpdateEarlyOutFraction(fraction_plus_slop);
1820
}
1821
}
1822
}
1823
}
1824
1825
bool mValidateBodyPair; ///< If we still have to call the ValidateContactPoint for this body pair
1826
bool mRejectAll; ///< Reject all further contacts between this body pair
1827
1828
private:
1829
const BodyManager & mBodyManager;
1830
ContactConstraintManager & mContactConstraintManager;
1831
CCDBody & mCCDBody;
1832
ShapeCastResult & mResult;
1833
float mDeltaTime;
1834
BodyID mAcceptedBodyID;
1835
};
1836
1837
// Narrowphase collector
1838
ShapeCastResult cast_shape_result;
1839
CCDNarrowPhaseCollector np_collector(mBodyManager, mContactManager, ccd_body, cast_shape_result, ioContext->mStepDeltaTime);
1840
1841
// This collector wraps the narrowphase collector and collects the closest hit
1842
class CCDBroadPhaseCollector : public CastShapeBodyCollector
1843
{
1844
public:
1845
CCDBroadPhaseCollector(const CCDBody &inCCDBody, const Body &inBody1, const RShapeCast &inShapeCast, ShapeCastSettings &inShapeCastSettings, SimShapeFilterWrapper &inShapeFilter, CCDNarrowPhaseCollector &ioCollector, const BodyManager &inBodyManager, PhysicsUpdateContext::Step *inStep, float inDeltaTime) :
1846
mCCDBody(inCCDBody),
1847
mBody1(inBody1),
1848
mBody1Extent(inShapeCast.mShapeWorldBounds.GetExtent()),
1849
mShapeCast(inShapeCast),
1850
mShapeCastSettings(inShapeCastSettings),
1851
mShapeFilter(inShapeFilter),
1852
mCollector(ioCollector),
1853
mBodyManager(inBodyManager),
1854
mStep(inStep),
1855
mDeltaTime(inDeltaTime)
1856
{
1857
}
1858
1859
virtual void AddHit(const BroadPhaseCastResult &inResult) override
1860
{
1861
JPH_PROFILE_FUNCTION();
1862
1863
JPH_ASSERT(inResult.mFraction <= GetEarlyOutFraction(), "This hit should not have been passed on to the collector");
1864
1865
// Test if we're colliding with ourselves
1866
if (mBody1.GetID() == inResult.mBodyID)
1867
return;
1868
1869
// Avoid treating duplicates, if both bodies are doing CCD then only consider collision if body ID < other body ID
1870
const Body &body2 = mBodyManager.GetBody(inResult.mBodyID);
1871
const CCDBody *ccd_body2 = sGetCCDBody(body2, mStep);
1872
if (ccd_body2 != nullptr && mCCDBody.mBodyID1 > ccd_body2->mBodyID1)
1873
return;
1874
1875
// Test group filter
1876
if (!mBody1.GetCollisionGroup().CanCollide(body2.GetCollisionGroup()))
1877
return;
1878
1879
// TODO: For now we ignore sensors
1880
if (body2.IsSensor())
1881
return;
1882
1883
// Get relative movement of these two bodies
1884
Vec3 direction = mShapeCast.mDirection - sCalculateBodyMotion(body2, mDeltaTime);
1885
1886
// Test if the remaining movement is less than our movement threshold
1887
if (direction.LengthSq() < mCCDBody.mLinearCastThresholdSq)
1888
return;
1889
1890
// Get the bounds of 2, widen it by the extent of 1 and test a ray to see if it hits earlier than the current early out fraction
1891
AABox bounds = body2.GetWorldSpaceBounds();
1892
bounds.mMin -= mBody1Extent;
1893
bounds.mMax += mBody1Extent;
1894
float hit_fraction = RayAABox(Vec3(mShapeCast.mCenterOfMassStart.GetTranslation()), RayInvDirection(direction), bounds.mMin, bounds.mMax);
1895
if (hit_fraction > GetPositiveEarlyOutFraction()) // If early out fraction <= 0, we have the possibility of finding a deeper hit so we need to clamp the early out fraction
1896
return;
1897
1898
// Reset collector (this is a new body pair)
1899
mCollector.ResetEarlyOutFraction(GetEarlyOutFraction());
1900
mCollector.mValidateBodyPair = true;
1901
mCollector.mRejectAll = false;
1902
1903
// Set body ID on shape filter
1904
mShapeFilter.SetBody2(&body2);
1905
1906
// Provide direction as hint for the active edges algorithm
1907
mShapeCastSettings.mActiveEdgeMovementDirection = direction;
1908
1909
// Do narrow phase collision check
1910
RShapeCast relative_cast(mShapeCast.mShape, mShapeCast.mScale, mShapeCast.mCenterOfMassStart, direction, mShapeCast.mShapeWorldBounds);
1911
body2.GetTransformedShape().CastShape(relative_cast, mShapeCastSettings, mShapeCast.mCenterOfMassStart.GetTranslation(), mCollector, mShapeFilter);
1912
1913
// Update early out fraction based on narrow phase collector
1914
if (!mCollector.mRejectAll)
1915
UpdateEarlyOutFraction(mCollector.GetEarlyOutFraction());
1916
}
1917
1918
const CCDBody & mCCDBody;
1919
const Body & mBody1;
1920
Vec3 mBody1Extent;
1921
RShapeCast mShapeCast;
1922
ShapeCastSettings & mShapeCastSettings;
1923
SimShapeFilterWrapper & mShapeFilter;
1924
CCDNarrowPhaseCollector & mCollector;
1925
const BodyManager & mBodyManager;
1926
PhysicsUpdateContext::Step *mStep;
1927
float mDeltaTime;
1928
};
1929
1930
// Create shape filter
1931
SimShapeFilterWrapperUnion shape_filter_union(mSimShapeFilter, &body);
1932
SimShapeFilterWrapper &shape_filter = shape_filter_union.GetSimShapeFilterWrapper();
1933
1934
// Check if we collide with any other body. Note that we use the non-locking interface as we know the broadphase cannot be modified at this point.
1935
RShapeCast shape_cast(body.GetShape(), Vec3::sOne(), body.GetCenterOfMassTransform(), ccd_body.mDeltaPosition);
1936
CCDBroadPhaseCollector bp_collector(ccd_body, body, shape_cast, settings, shape_filter, np_collector, mBodyManager, ioStep, ioContext->mStepDeltaTime);
1937
mBroadPhase->CastAABoxNoLock({ shape_cast.mShapeWorldBounds, shape_cast.mDirection }, bp_collector, broadphase_layer_filter, object_layer_filter);
1938
1939
// Check if there was a hit
1940
if (ccd_body.mFractionPlusSlop < 1.0f)
1941
{
1942
const Body &body2 = mBodyManager.GetBody(ccd_body.mBodyID2);
1943
1944
// Determine contact manifold
1945
ContactManifold manifold;
1946
manifold.mBaseOffset = shape_cast.mCenterOfMassStart.GetTranslation();
1947
ManifoldBetweenTwoFaces(cast_shape_result.mContactPointOn1, cast_shape_result.mContactPointOn2, cast_shape_result.mPenetrationAxis, mPhysicsSettings.mManifoldTolerance, cast_shape_result.mShape1Face, cast_shape_result.mShape2Face, manifold.mRelativeContactPointsOn1, manifold.mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, manifold.mBaseOffset));
1948
manifold.mSubShapeID1 = cast_shape_result.mSubShapeID1;
1949
manifold.mSubShapeID2 = cast_shape_result.mSubShapeID2;
1950
manifold.mPenetrationDepth = cast_shape_result.mPenetrationDepth;
1951
manifold.mWorldSpaceNormal = ccd_body.mContactNormal;
1952
1953
// Call contact point callbacks
1954
mContactManager.OnCCDContactAdded(contact_allocator, body, body2, manifold, ccd_body.mContactSettings);
1955
1956
if (ccd_body.mContactSettings.mIsSensor)
1957
{
1958
// If this is a sensor, we don't want to solve the contact
1959
ccd_body.mFractionPlusSlop = 1.0f;
1960
ccd_body.mBodyID2 = BodyID();
1961
}
1962
else
1963
{
1964
// Calculate the average position from the manifold (this will result in the same impulse applied as when we apply impulses to all contact points)
1965
if (manifold.mRelativeContactPointsOn2.size() > 1)
1966
{
1967
Vec3 average_contact_point = Vec3::sZero();
1968
for (const Vec3 &v : manifold.mRelativeContactPointsOn2)
1969
average_contact_point += v;
1970
average_contact_point /= (float)manifold.mRelativeContactPointsOn2.size();
1971
ccd_body.mContactPointOn2 = manifold.mBaseOffset + average_contact_point;
1972
}
1973
else
1974
ccd_body.mContactPointOn2 = manifold.mBaseOffset + cast_shape_result.mContactPointOn2;
1975
}
1976
}
1977
}
1978
1979
// Collect information from the contact allocator and accumulate it in the step.
1980
sFinalizeContactAllocator(*ioStep, contact_allocator);
1981
}
1982
1983
void PhysicsSystem::JobResolveCCDContacts(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
1984
{
1985
#ifdef JPH_ENABLE_ASSERTS
1986
// Read/write body access
1987
BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::ReadWrite);
1988
1989
// We activate bodies that we collide with
1990
BodyManager::GrantActiveBodiesAccess grant_active(true, false);
1991
#endif
1992
1993
uint32 num_active_bodies_after_find_collisions = ioStep->mActiveBodyReadIdx;
1994
TempAllocator *temp_allocator = ioContext->mTempAllocator;
1995
1996
// Check if there's anything to do
1997
uint num_ccd_bodies = ioStep->mNumCCDBodies;
1998
if (num_ccd_bodies > 0)
1999
{
2000
// Sort on fraction so that we process earliest collisions first
2001
// This is needed to make the simulation deterministic and also to be able to stop contact processing
2002
// between body pairs if an earlier hit was found involving the body by another CCD body
2003
// (if it's body ID < this CCD body's body ID - see filtering logic in CCDBroadPhaseCollector)
2004
CCDBody **sorted_ccd_bodies = (CCDBody **)temp_allocator->Allocate(num_ccd_bodies * sizeof(CCDBody *));
2005
JPH_SCOPE_EXIT([temp_allocator, sorted_ccd_bodies, num_ccd_bodies]{ temp_allocator->Free(sorted_ccd_bodies, num_ccd_bodies * sizeof(CCDBody *)); });
2006
{
2007
JPH_PROFILE("Sort");
2008
2009
// We don't want to copy the entire struct (it's quite big), so we create a pointer array first
2010
CCDBody *src_ccd_bodies = ioStep->mCCDBodies;
2011
CCDBody **dst_ccd_bodies = sorted_ccd_bodies;
2012
CCDBody **dst_ccd_bodies_end = dst_ccd_bodies + num_ccd_bodies;
2013
while (dst_ccd_bodies < dst_ccd_bodies_end)
2014
*(dst_ccd_bodies++) = src_ccd_bodies++;
2015
2016
// Which we then sort
2017
QuickSort(sorted_ccd_bodies, sorted_ccd_bodies + num_ccd_bodies, [](const CCDBody *inBody1, const CCDBody *inBody2)
2018
{
2019
if (inBody1->mFractionPlusSlop != inBody2->mFractionPlusSlop)
2020
return inBody1->mFractionPlusSlop < inBody2->mFractionPlusSlop;
2021
2022
return inBody1->mBodyID1 < inBody2->mBodyID1;
2023
});
2024
}
2025
2026
// We can collide with bodies that are not active, we track them here so we can activate them in one go at the end.
2027
// This is also needed because we can't modify the active body array while we iterate it.
2028
static constexpr int cBodiesBatch = 64;
2029
BodyID *bodies_to_activate = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));
2030
int num_bodies_to_activate = 0;
2031
2032
// We can move bodies that are not part of an island. In this case we need to notify the broadphase of the movement.
2033
BodyID *bodies_to_update_bounds = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));
2034
int num_bodies_to_update_bounds = 0;
2035
2036
for (uint i = 0; i < num_ccd_bodies; ++i)
2037
{
2038
const CCDBody *ccd_body = sorted_ccd_bodies[i];
2039
Body &body1 = mBodyManager.GetBody(ccd_body->mBodyID1);
2040
MotionProperties *body_mp = body1.GetMotionProperties();
2041
2042
// If there was a hit
2043
if (!ccd_body->mBodyID2.IsInvalid())
2044
{
2045
Body &body2 = mBodyManager.GetBody(ccd_body->mBodyID2);
2046
2047
// Determine if the other body has a CCD body
2048
CCDBody *ccd_body2 = sGetCCDBody(body2, ioStep);
2049
if (ccd_body2 != nullptr)
2050
{
2051
JPH_ASSERT(ccd_body2->mBodyID2 != ccd_body->mBodyID1, "If we collided with another body, that other body should have ignored collisions with us!");
2052
2053
// Check if the other body found a hit that is further away
2054
if (ccd_body2->mFraction > ccd_body->mFraction)
2055
{
2056
// Reset the colliding body of the other CCD body. The other body will shorten its distance traveled and will not do any collision response (we'll do that).
2057
// This means that at this point we have triggered a contact point add/persist for our further hit by accident for the other body.
2058
// We accept this as calling the contact point callbacks here would require persisting the manifolds up to this point and doing the callbacks single threaded.
2059
ccd_body2->mBodyID2 = BodyID();
2060
ccd_body2->mFractionPlusSlop = ccd_body->mFraction;
2061
}
2062
}
2063
2064
// If the other body moved less than us before hitting something, we're not colliding with it so we again have triggered contact point add/persist callbacks by accident.
2065
// We'll just move to the collision position anyway (as that's the last position we know is good), but we won't do any collision response.
2066
if (ccd_body2 == nullptr || ccd_body2->mFraction >= ccd_body->mFraction)
2067
{
2068
const ContactSettings &contact_settings = ccd_body->mContactSettings;
2069
2070
// Calculate contact point velocity for body 1
2071
Vec3 r1_plus_u = Vec3(ccd_body->mContactPointOn2 - (body1.GetCenterOfMassPosition() + ccd_body->mFraction * ccd_body->mDeltaPosition));
2072
Vec3 v1 = body1.GetPointVelocityCOM(r1_plus_u);
2073
2074
// Calculate inverse mass for body 1
2075
float inv_m1 = contact_settings.mInvMassScale1 * body_mp->GetInverseMass();
2076
2077
if (body2.IsRigidBody())
2078
{
2079
// Calculate contact point velocity for body 2
2080
Vec3 r2 = Vec3(ccd_body->mContactPointOn2 - body2.GetCenterOfMassPosition());
2081
Vec3 v2 = body2.GetPointVelocityCOM(r2);
2082
2083
// Calculate relative contact velocity
2084
Vec3 relative_velocity = v2 - v1;
2085
float normal_velocity = relative_velocity.Dot(ccd_body->mContactNormal);
2086
2087
// Calculate velocity bias due to restitution
2088
float normal_velocity_bias;
2089
if (contact_settings.mCombinedRestitution > 0.0f && normal_velocity < -mPhysicsSettings.mMinVelocityForRestitution)
2090
normal_velocity_bias = contact_settings.mCombinedRestitution * normal_velocity;
2091
else
2092
normal_velocity_bias = 0.0f;
2093
2094
// Get inverse mass of body 2
2095
float inv_m2 = body2.GetMotionPropertiesUnchecked() != nullptr? contact_settings.mInvMassScale2 * body2.GetMotionPropertiesUnchecked()->GetInverseMassUnchecked() : 0.0f;
2096
2097
// Solve contact constraint
2098
AxisConstraintPart contact_constraint;
2099
contact_constraint.CalculateConstraintPropertiesWithMassOverride(body1, inv_m1, contact_settings.mInvInertiaScale1, r1_plus_u, body2, inv_m2, contact_settings.mInvInertiaScale2, r2, ccd_body->mContactNormal, normal_velocity_bias);
2100
contact_constraint.SolveVelocityConstraintWithMassOverride(body1, inv_m1, body2, inv_m2, ccd_body->mContactNormal, -FLT_MAX, FLT_MAX);
2101
2102
// Apply friction
2103
if (contact_settings.mCombinedFriction > 0.0f)
2104
{
2105
// Calculate friction direction by removing normal velocity from the relative velocity
2106
Vec3 friction_direction = relative_velocity - normal_velocity * ccd_body->mContactNormal;
2107
float friction_direction_len_sq = friction_direction.LengthSq();
2108
if (friction_direction_len_sq > 1.0e-12f)
2109
{
2110
// Normalize friction direction
2111
friction_direction /= sqrt(friction_direction_len_sq);
2112
2113
// Calculate max friction impulse
2114
float max_lambda_f = contact_settings.mCombinedFriction * contact_constraint.GetTotalLambda();
2115
2116
AxisConstraintPart friction;
2117
friction.CalculateConstraintPropertiesWithMassOverride(body1, inv_m1, contact_settings.mInvInertiaScale1, r1_plus_u, body2, inv_m2, contact_settings.mInvInertiaScale2, r2, friction_direction);
2118
friction.SolveVelocityConstraintWithMassOverride(body1, inv_m1, body2, inv_m2, friction_direction, -max_lambda_f, max_lambda_f);
2119
}
2120
}
2121
2122
// Clamp velocity of body 2
2123
if (body2.IsDynamic())
2124
{
2125
MotionProperties *body2_mp = body2.GetMotionProperties();
2126
body2_mp->ClampLinearVelocity();
2127
body2_mp->ClampAngularVelocity();
2128
}
2129
}
2130
else
2131
{
2132
SoftBodyMotionProperties *soft_mp = static_cast<SoftBodyMotionProperties *>(body2.GetMotionProperties());
2133
const SoftBodyShape *soft_shape = static_cast<const SoftBodyShape *>(body2.GetShape());
2134
2135
// Convert the sub shape ID of the soft body to a face
2136
uint32 face_idx = soft_shape->GetFaceIndex(ccd_body->mSubShapeID2);
2137
const SoftBodyMotionProperties::Face &face = soft_mp->GetFace(face_idx);
2138
2139
// Get vertices of the face
2140
SoftBodyMotionProperties::Vertex &vtx0 = soft_mp->GetVertex(face.mVertex[0]);
2141
SoftBodyMotionProperties::Vertex &vtx1 = soft_mp->GetVertex(face.mVertex[1]);
2142
SoftBodyMotionProperties::Vertex &vtx2 = soft_mp->GetVertex(face.mVertex[2]);
2143
2144
// Inverse mass of the face
2145
float vtx0_mass = vtx0.mInvMass > 0.0f? 1.0f / vtx0.mInvMass : 1.0e10f;
2146
float vtx1_mass = vtx1.mInvMass > 0.0f? 1.0f / vtx1.mInvMass : 1.0e10f;
2147
float vtx2_mass = vtx2.mInvMass > 0.0f? 1.0f / vtx2.mInvMass : 1.0e10f;
2148
float inv_m2 = 1.0f / (vtx0_mass + vtx1_mass + vtx2_mass);
2149
2150
// Calculate barycentric coordinates of the contact point on the soft body's face
2151
float u, v, w;
2152
RMat44 inv_body2_transform = body2.GetInverseCenterOfMassTransform();
2153
Vec3 local_contact = Vec3(inv_body2_transform * ccd_body->mContactPointOn2);
2154
ClosestPoint::GetBaryCentricCoordinates(vtx0.mPosition - local_contact, vtx1.mPosition - local_contact, vtx2.mPosition - local_contact, u, v, w);
2155
2156
// Calculate contact point velocity for the face
2157
Vec3 v2 = inv_body2_transform.Multiply3x3Transposed(u * vtx0.mVelocity + v * vtx1.mVelocity + w * vtx2.mVelocity);
2158
float normal_velocity = (v2 - v1).Dot(ccd_body->mContactNormal);
2159
2160
// Calculate velocity bias due to restitution
2161
float normal_velocity_bias;
2162
if (contact_settings.mCombinedRestitution > 0.0f && normal_velocity < -mPhysicsSettings.mMinVelocityForRestitution)
2163
normal_velocity_bias = contact_settings.mCombinedRestitution * normal_velocity;
2164
else
2165
normal_velocity_bias = 0.0f;
2166
2167
// Calculate resulting velocity change (the math here is similar to AxisConstraintPart but without an inertia term for body 2 as we treat it as a point mass)
2168
Vec3 r1_plus_u_x_n = r1_plus_u.Cross(ccd_body->mContactNormal);
2169
Vec3 invi1_r1_plus_u_x_n = contact_settings.mInvInertiaScale1 * body1.GetInverseInertia().Multiply3x3(r1_plus_u_x_n);
2170
float jv = r1_plus_u_x_n.Dot(body_mp->GetAngularVelocity()) - normal_velocity - normal_velocity_bias;
2171
float inv_effective_mass = inv_m1 + inv_m2 + invi1_r1_plus_u_x_n.Dot(r1_plus_u_x_n);
2172
float lambda = jv / inv_effective_mass;
2173
body_mp->SubLinearVelocityStep((lambda * inv_m1) * ccd_body->mContactNormal);
2174
body_mp->SubAngularVelocityStep(lambda * invi1_r1_plus_u_x_n);
2175
Vec3 delta_v2 = inv_body2_transform.Multiply3x3(lambda * ccd_body->mContactNormal);
2176
vtx0.mVelocity += delta_v2 * vtx0.mInvMass;
2177
vtx1.mVelocity += delta_v2 * vtx1.mInvMass;
2178
vtx2.mVelocity += delta_v2 * vtx2.mInvMass;
2179
}
2180
2181
// Clamp velocity of body 1
2182
body_mp->ClampLinearVelocity();
2183
body_mp->ClampAngularVelocity();
2184
2185
// Activate the 2nd body if it is not already active
2186
if (body2.IsDynamic() && !body2.IsActive())
2187
{
2188
bodies_to_activate[num_bodies_to_activate++] = ccd_body->mBodyID2;
2189
if (num_bodies_to_activate == cBodiesBatch)
2190
{
2191
// Batch is full, activate now
2192
mBodyManager.ActivateBodies(bodies_to_activate, num_bodies_to_activate);
2193
num_bodies_to_activate = 0;
2194
}
2195
}
2196
2197
#ifdef JPH_DEBUG_RENDERER
2198
if (sDrawMotionQualityLinearCast)
2199
{
2200
// Draw the collision location
2201
RMat44 collision_transform = body1.GetCenterOfMassTransform().PostTranslated(ccd_body->mFraction * ccd_body->mDeltaPosition);
2202
body1.GetShape()->Draw(DebugRenderer::sInstance, collision_transform, Vec3::sOne(), Color::sYellow, false, true);
2203
2204
// Draw the collision location + slop
2205
RMat44 collision_transform_plus_slop = body1.GetCenterOfMassTransform().PostTranslated(ccd_body->mFractionPlusSlop * ccd_body->mDeltaPosition);
2206
body1.GetShape()->Draw(DebugRenderer::sInstance, collision_transform_plus_slop, Vec3::sOne(), Color::sOrange, false, true);
2207
2208
// Draw contact normal
2209
DebugRenderer::sInstance->DrawArrow(ccd_body->mContactPointOn2, ccd_body->mContactPointOn2 - ccd_body->mContactNormal, Color::sYellow, 0.1f);
2210
2211
// Draw post contact velocity
2212
DebugRenderer::sInstance->DrawArrow(collision_transform.GetTranslation(), collision_transform.GetTranslation() + body1.GetLinearVelocity(), Color::sOrange, 0.1f);
2213
DebugRenderer::sInstance->DrawArrow(collision_transform.GetTranslation(), collision_transform.GetTranslation() + body1.GetAngularVelocity(), Color::sPurple, 0.1f);
2214
}
2215
#endif // JPH_DEBUG_RENDERER
2216
}
2217
}
2218
2219
// Update body position
2220
body1.AddPositionStep(ccd_body->mDeltaPosition * ccd_body->mFractionPlusSlop);
2221
2222
// If the body was activated due to an earlier CCD step it will have an index in the active
2223
// body list that it higher than the highest one we processed during FindCollisions
2224
// which means it hasn't been assigned an island and will not be updated by an island
2225
// this means that we need to update its bounds manually
2226
if (body_mp->GetIndexInActiveBodiesInternal() >= num_active_bodies_after_find_collisions)
2227
{
2228
body1.CalculateWorldSpaceBoundsInternal();
2229
bodies_to_update_bounds[num_bodies_to_update_bounds++] = body1.GetID();
2230
if (num_bodies_to_update_bounds == cBodiesBatch)
2231
{
2232
// Buffer full, flush now
2233
mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
2234
num_bodies_to_update_bounds = 0;
2235
}
2236
}
2237
}
2238
2239
// Activate the requested bodies
2240
if (num_bodies_to_activate > 0)
2241
mBodyManager.ActivateBodies(bodies_to_activate, num_bodies_to_activate);
2242
2243
// Notify change bounds on requested bodies
2244
if (num_bodies_to_update_bounds > 0)
2245
mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
2246
}
2247
2248
// Ensure we free the CCD bodies array now, will not call the destructor!
2249
temp_allocator->Free(ioStep->mActiveBodyToCCDBody, ioStep->mNumActiveBodyToCCDBody * sizeof(int));
2250
ioStep->mActiveBodyToCCDBody = nullptr;
2251
ioStep->mNumActiveBodyToCCDBody = 0;
2252
temp_allocator->Free(ioStep->mCCDBodies, ioStep->mCCDBodiesCapacity * sizeof(CCDBody));
2253
ioStep->mCCDBodies = nullptr;
2254
ioStep->mCCDBodiesCapacity = 0;
2255
}
2256
2257
void PhysicsSystem::JobContactRemovedCallbacks(const PhysicsUpdateContext::Step *ioStep)
2258
{
2259
#ifdef JPH_ENABLE_ASSERTS
2260
// We don't touch any bodies
2261
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::None);
2262
#endif
2263
2264
// Reset the Body::EFlags::InvalidateContactCache flag for all bodies
2265
mBodyManager.ValidateContactCacheForAllBodies();
2266
2267
// Finalize the contact cache (this swaps the read and write versions of the contact cache)
2268
// Trigger all contact removed callbacks by looking at last step contact points that have not been flagged as reused
2269
mContactManager.FinalizeContactCacheAndCallContactPointRemovedCallbacks(ioStep->mNumBodyPairs, ioStep->mNumManifolds);
2270
}
2271
2272
class PhysicsSystem::BodiesToSleep : public NonCopyable
2273
{
2274
public:
2275
static constexpr int cBodiesToSleepSize = 512;
2276
static constexpr int cMaxBodiesToPutInBuffer = 128;
2277
2278
inline BodiesToSleep(BodyManager &inBodyManager, BodyID *inBodiesToSleepBuffer) : mBodyManager(inBodyManager), mBodiesToSleepBuffer(inBodiesToSleepBuffer), mBodiesToSleepCur(inBodiesToSleepBuffer) { }
2279
2280
inline ~BodiesToSleep()
2281
{
2282
// Flush the bodies to sleep buffer
2283
int num_bodies_in_buffer = int(mBodiesToSleepCur - mBodiesToSleepBuffer);
2284
if (num_bodies_in_buffer > 0)
2285
mBodyManager.DeactivateBodies(mBodiesToSleepBuffer, num_bodies_in_buffer);
2286
}
2287
2288
inline void PutToSleep(const BodyID *inBegin, const BodyID *inEnd)
2289
{
2290
int num_bodies_to_sleep = int(inEnd - inBegin);
2291
if (num_bodies_to_sleep > cMaxBodiesToPutInBuffer)
2292
{
2293
// Too many bodies, deactivate immediately
2294
mBodyManager.DeactivateBodies(inBegin, num_bodies_to_sleep);
2295
}
2296
else
2297
{
2298
// Check if there's enough space in the bodies to sleep buffer
2299
int num_bodies_in_buffer = int(mBodiesToSleepCur - mBodiesToSleepBuffer);
2300
if (num_bodies_in_buffer + num_bodies_to_sleep > cBodiesToSleepSize)
2301
{
2302
// Flush the bodies to sleep buffer
2303
mBodyManager.DeactivateBodies(mBodiesToSleepBuffer, num_bodies_in_buffer);
2304
mBodiesToSleepCur = mBodiesToSleepBuffer;
2305
}
2306
2307
// Copy the bodies in the buffer
2308
memcpy(mBodiesToSleepCur, inBegin, num_bodies_to_sleep * sizeof(BodyID));
2309
mBodiesToSleepCur += num_bodies_to_sleep;
2310
}
2311
}
2312
2313
private:
2314
BodyManager & mBodyManager;
2315
BodyID * mBodiesToSleepBuffer;
2316
BodyID * mBodiesToSleepCur;
2317
};
2318
2319
void PhysicsSystem::CheckSleepAndUpdateBounds(uint32 inIslandIndex, const PhysicsUpdateContext *ioContext, const PhysicsUpdateContext::Step *ioStep, BodiesToSleep &ioBodiesToSleep)
2320
{
2321
// Get the bodies that belong to this island
2322
BodyID *bodies_begin, *bodies_end;
2323
mIslandBuilder.GetBodiesInIsland(inIslandIndex, bodies_begin, bodies_end);
2324
2325
// Only check sleeping in the last step
2326
// Also resets force and torque used during the apply gravity phase
2327
if (ioStep->mIsLast)
2328
{
2329
JPH_PROFILE("Check Sleeping");
2330
2331
static_assert(int(ECanSleep::CannotSleep) == 0 && int(ECanSleep::CanSleep) == 1, "Loop below makes this assumption");
2332
int all_can_sleep = mPhysicsSettings.mAllowSleeping? int(ECanSleep::CanSleep) : int(ECanSleep::CannotSleep);
2333
2334
float time_before_sleep = mPhysicsSettings.mTimeBeforeSleep;
2335
float max_movement = mPhysicsSettings.mPointVelocitySleepThreshold * time_before_sleep;
2336
2337
for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
2338
{
2339
Body &body = mBodyManager.GetBody(*body_id);
2340
2341
// Update bounding box
2342
body.CalculateWorldSpaceBoundsInternal();
2343
2344
// Update sleeping
2345
all_can_sleep &= int(body.UpdateSleepStateInternal(ioContext->mStepDeltaTime, max_movement, time_before_sleep));
2346
2347
// Reset force and torque
2348
MotionProperties *mp = body.GetMotionProperties();
2349
mp->ResetForce();
2350
mp->ResetTorque();
2351
}
2352
2353
// If all bodies indicate they can sleep we can deactivate them
2354
if (all_can_sleep == int(ECanSleep::CanSleep))
2355
ioBodiesToSleep.PutToSleep(bodies_begin, bodies_end);
2356
}
2357
else
2358
{
2359
JPH_PROFILE("Update Bounds");
2360
2361
// Update bounding box only for all other steps
2362
for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
2363
{
2364
Body &body = mBodyManager.GetBody(*body_id);
2365
body.CalculateWorldSpaceBoundsInternal();
2366
}
2367
}
2368
2369
// Notify broadphase of changed objects (find ccd contacts can do linear casts in the next step, so we need to do this every step)
2370
// Note: Shuffles the BodyID's around!!!
2371
mBroadPhase->NotifyBodiesAABBChanged(bodies_begin, int(bodies_end - bodies_begin), false);
2372
}
2373
2374
void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
2375
{
2376
#ifdef JPH_ENABLE_ASSERTS
2377
// We fix up position errors
2378
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::ReadWrite);
2379
2380
// Can only deactivate bodies
2381
BodyManager::GrantActiveBodiesAccess grant_active(false, true);
2382
#endif
2383
2384
float delta_time = ioContext->mStepDeltaTime;
2385
float baumgarte = mPhysicsSettings.mBaumgarte;
2386
Constraint **active_constraints = ioContext->mActiveConstraints;
2387
2388
// Keep a buffer of bodies that need to go to sleep in order to not constantly lock the active bodies mutex and create contention between all solving threads
2389
BodiesToSleep bodies_to_sleep(mBodyManager, (BodyID *)JPH_STACK_ALLOC(BodiesToSleep::cBodiesToSleepSize * sizeof(BodyID)));
2390
2391
bool check_islands = true, check_split_islands = mPhysicsSettings.mUseLargeIslandSplitter;
2392
for (;;)
2393
{
2394
// First try to get work from large islands
2395
if (check_split_islands)
2396
{
2397
bool first_iteration;
2398
uint split_island_index;
2399
uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;
2400
switch (mLargeIslandSplitter.FetchNextBatch(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, first_iteration))
2401
{
2402
case LargeIslandSplitter::EStatus::BatchRetrieved:
2403
// Solve the batch
2404
ConstraintManager::sSolvePositionConstraints(active_constraints, constraints_begin, constraints_end, delta_time, baumgarte);
2405
mContactManager.SolvePositionConstraints(contacts_begin, contacts_end);
2406
2407
// Mark the batch as processed
2408
bool last_iteration, final_batch;
2409
mLargeIslandSplitter.MarkBatchProcessed(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, last_iteration, final_batch);
2410
2411
// The final batch will update all bounds and check sleeping
2412
if (final_batch)
2413
CheckSleepAndUpdateBounds(mLargeIslandSplitter.GetIslandIndex(split_island_index), ioContext, ioStep, bodies_to_sleep);
2414
2415
// We processed work, loop again
2416
continue;
2417
2418
case LargeIslandSplitter::EStatus::WaitingForBatch:
2419
break;
2420
2421
case LargeIslandSplitter::EStatus::AllBatchesDone:
2422
check_split_islands = false;
2423
break;
2424
}
2425
}
2426
2427
// If that didn't succeed try to process an island
2428
if (check_islands)
2429
{
2430
// Next island
2431
uint32 island_idx = ioStep->mSolvePositionConstraintsNextIsland++;
2432
if (island_idx >= mIslandBuilder.GetNumIslands())
2433
{
2434
// We processed all islands, stop checking islands
2435
check_islands = false;
2436
continue;
2437
}
2438
2439
JPH_PROFILE("Island");
2440
2441
// Get iterators for this island
2442
uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;
2443
mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end);
2444
mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end);
2445
2446
// If this island is a large island, it will be picked up as a batch and we don't need to do anything here
2447
uint num_items = uint(constraints_end - constraints_begin) + uint(contacts_end - contacts_begin);
2448
if (mPhysicsSettings.mUseLargeIslandSplitter
2449
&& num_items >= LargeIslandSplitter::cLargeIslandTreshold)
2450
continue;
2451
2452
// Check if this island needs solving
2453
if (num_items > 0)
2454
{
2455
// Iterate
2456
uint num_position_steps = mIslandBuilder.GetNumPositionSteps(island_idx);
2457
for (uint position_step = 0; position_step < num_position_steps; ++position_step)
2458
{
2459
bool applied_impulse = ConstraintManager::sSolvePositionConstraints(active_constraints, constraints_begin, constraints_end, delta_time, baumgarte);
2460
applied_impulse |= mContactManager.SolvePositionConstraints(contacts_begin, contacts_end);
2461
if (!applied_impulse)
2462
break;
2463
}
2464
}
2465
2466
// After solving we will update all bounds and check sleeping
2467
CheckSleepAndUpdateBounds(island_idx, ioContext, ioStep, bodies_to_sleep);
2468
2469
// We processed work, loop again
2470
continue;
2471
}
2472
2473
if (check_islands)
2474
{
2475
// If there are islands, we don't need to wait and can pick up new work
2476
continue;
2477
}
2478
else if (check_split_islands)
2479
{
2480
// If there are split islands, but we didn't do any work, give up a time slice
2481
std::this_thread::yield();
2482
}
2483
else
2484
{
2485
// No more work
2486
break;
2487
}
2488
}
2489
}
2490
2491
void PhysicsSystem::JobSoftBodyPrepare(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
2492
{
2493
JPH_PROFILE_FUNCTION();
2494
2495
{
2496
#ifdef JPH_ENABLE_ASSERTS
2497
// Reading soft body positions
2498
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::Read);
2499
#endif
2500
2501
// Get the active soft bodies
2502
BodyIDVector active_bodies;
2503
mBodyManager.GetActiveBodies(EBodyType::SoftBody, active_bodies);
2504
2505
// Quit if there are no active soft bodies
2506
if (active_bodies.empty())
2507
{
2508
// Kick the next step
2509
if (ioStep->mStartNextStep.IsValid())
2510
ioStep->mStartNextStep.RemoveDependency();
2511
return;
2512
}
2513
2514
// Sort to get a deterministic update order
2515
QuickSort(active_bodies.begin(), active_bodies.end());
2516
2517
// Allocate soft body contexts
2518
ioContext->mNumSoftBodies = (uint)active_bodies.size();
2519
ioContext->mSoftBodyUpdateContexts = (SoftBodyUpdateContext *)ioContext->mTempAllocator->Allocate(ioContext->mNumSoftBodies * sizeof(SoftBodyUpdateContext));
2520
2521
// Initialize soft body contexts
2522
for (SoftBodyUpdateContext *sb_ctx = ioContext->mSoftBodyUpdateContexts, *sb_ctx_end = ioContext->mSoftBodyUpdateContexts + ioContext->mNumSoftBodies; sb_ctx < sb_ctx_end; ++sb_ctx)
2523
{
2524
new (sb_ctx) SoftBodyUpdateContext;
2525
Body &body = mBodyManager.GetBody(active_bodies[sb_ctx - ioContext->mSoftBodyUpdateContexts]);
2526
SoftBodyMotionProperties *mp = static_cast<SoftBodyMotionProperties *>(body.GetMotionProperties());
2527
mp->InitializeUpdateContext(ioContext->mStepDeltaTime, body, *this, *sb_ctx);
2528
}
2529
}
2530
2531
// We're ready to collide the first soft body
2532
ioContext->mSoftBodyToCollide.store(0, memory_order_release);
2533
2534
// Determine number of jobs to spawn
2535
int num_soft_body_jobs = ioContext->GetMaxConcurrency();
2536
2537
// Create finalize job
2538
ioStep->mSoftBodyFinalize = ioContext->mJobSystem->CreateJob("SoftBodyFinalize", cColorSoftBodyFinalize, [ioContext, ioStep]()
2539
{
2540
ioContext->mPhysicsSystem->JobSoftBodyFinalize(ioContext);
2541
2542
// Kick the next step
2543
if (ioStep->mStartNextStep.IsValid())
2544
ioStep->mStartNextStep.RemoveDependency();
2545
}, num_soft_body_jobs); // depends on: soft body simulate
2546
ioContext->mBarrier->AddJob(ioStep->mSoftBodyFinalize);
2547
2548
// Create simulate jobs
2549
ioStep->mSoftBodySimulate.resize(num_soft_body_jobs);
2550
for (int i = 0; i < num_soft_body_jobs; ++i)
2551
ioStep->mSoftBodySimulate[i] = ioContext->mJobSystem->CreateJob("SoftBodySimulate", cColorSoftBodySimulate, [ioStep, i]()
2552
{
2553
ioStep->mContext->mPhysicsSystem->JobSoftBodySimulate(ioStep->mContext, i);
2554
2555
ioStep->mSoftBodyFinalize.RemoveDependency();
2556
}, num_soft_body_jobs); // depends on: soft body collide
2557
ioContext->mBarrier->AddJobs(ioStep->mSoftBodySimulate.data(), ioStep->mSoftBodySimulate.size());
2558
2559
// Create collision jobs
2560
ioStep->mSoftBodyCollide.resize(num_soft_body_jobs);
2561
for (int i = 0; i < num_soft_body_jobs; ++i)
2562
ioStep->mSoftBodyCollide[i] = ioContext->mJobSystem->CreateJob("SoftBodyCollide", cColorSoftBodyCollide, [ioContext, ioStep]()
2563
{
2564
ioContext->mPhysicsSystem->JobSoftBodyCollide(ioContext);
2565
2566
for (const JobHandle &h : ioStep->mSoftBodySimulate)
2567
h.RemoveDependency();
2568
}); // depends on: nothing
2569
ioContext->mBarrier->AddJobs(ioStep->mSoftBodyCollide.data(), ioStep->mSoftBodyCollide.size());
2570
}
2571
2572
void PhysicsSystem::JobSoftBodyCollide(PhysicsUpdateContext *ioContext) const
2573
{
2574
#ifdef JPH_ENABLE_ASSERTS
2575
// Reading rigid body positions and velocities
2576
BodyAccess::Grant grant(BodyAccess::EAccess::Read, BodyAccess::EAccess::Read);
2577
#endif
2578
2579
for (;;)
2580
{
2581
// Fetch the next soft body
2582
uint sb_idx = ioContext->mSoftBodyToCollide.fetch_add(1, std::memory_order_acquire);
2583
if (sb_idx >= ioContext->mNumSoftBodies)
2584
break;
2585
2586
// Do a broadphase check
2587
SoftBodyUpdateContext &sb_ctx = ioContext->mSoftBodyUpdateContexts[sb_idx];
2588
sb_ctx.mMotionProperties->DetermineCollidingShapes(sb_ctx, *this, GetBodyLockInterfaceNoLock());
2589
}
2590
}
2591
2592
void PhysicsSystem::JobSoftBodySimulate(PhysicsUpdateContext *ioContext, uint inThreadIndex) const
2593
{
2594
#ifdef JPH_ENABLE_ASSERTS
2595
// Updating velocities of soft bodies, allow the contact listener to read the soft body state
2596
BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::Read);
2597
#endif
2598
2599
// Calculate at which body we start to distribute the workload across the threads
2600
uint num_soft_bodies = ioContext->mNumSoftBodies;
2601
uint start_idx = inThreadIndex * num_soft_bodies / ioContext->GetMaxConcurrency();
2602
2603
// Keep running partial updates until everything has been updated
2604
uint status;
2605
do
2606
{
2607
// Reset status
2608
status = 0;
2609
2610
// Update all soft bodies
2611
for (uint i = 0; i < num_soft_bodies; ++i)
2612
{
2613
// Fetch the soft body context
2614
SoftBodyUpdateContext &sb_ctx = ioContext->mSoftBodyUpdateContexts[(start_idx + i) % num_soft_bodies];
2615
2616
// To avoid trashing the cache too much, we prefer to stick to one soft body until we cannot progress it any further
2617
uint sb_status;
2618
do
2619
{
2620
sb_status = (uint)sb_ctx.mMotionProperties->ParallelUpdate(sb_ctx, mPhysicsSettings);
2621
status |= sb_status;
2622
} while (sb_status == (uint)SoftBodyMotionProperties::EStatus::DidWork);
2623
}
2624
2625
// If we didn't perform any work, yield the thread so that something else can run
2626
if (!(status & (uint)SoftBodyMotionProperties::EStatus::DidWork))
2627
std::this_thread::yield();
2628
}
2629
while (status != (uint)SoftBodyMotionProperties::EStatus::Done);
2630
}
2631
2632
void PhysicsSystem::JobSoftBodyFinalize(PhysicsUpdateContext *ioContext)
2633
{
2634
#ifdef JPH_ENABLE_ASSERTS
2635
// Updating rigid body velocities and soft body positions / velocities
2636
BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::ReadWrite);
2637
2638
// Can activate and deactivate bodies
2639
BodyManager::GrantActiveBodiesAccess grant_active(true, true);
2640
#endif
2641
2642
static constexpr int cBodiesBatch = 64;
2643
BodyID *bodies_to_update_bounds = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));
2644
int num_bodies_to_update_bounds = 0;
2645
BodyID *bodies_to_put_to_sleep = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));
2646
int num_bodies_to_put_to_sleep = 0;
2647
2648
for (SoftBodyUpdateContext *sb_ctx = ioContext->mSoftBodyUpdateContexts, *sb_ctx_end = ioContext->mSoftBodyUpdateContexts + ioContext->mNumSoftBodies; sb_ctx < sb_ctx_end; ++sb_ctx)
2649
{
2650
// Apply the rigid body velocity deltas
2651
sb_ctx->mMotionProperties->UpdateRigidBodyVelocities(*sb_ctx, GetBodyInterfaceNoLock());
2652
2653
// Update the position
2654
sb_ctx->mBody->SetPositionAndRotationInternal(sb_ctx->mBody->GetPosition() + sb_ctx->mDeltaPosition, sb_ctx->mBody->GetRotation(), false);
2655
2656
BodyID id = sb_ctx->mBody->GetID();
2657
bodies_to_update_bounds[num_bodies_to_update_bounds++] = id;
2658
if (num_bodies_to_update_bounds == cBodiesBatch)
2659
{
2660
// Buffer full, flush now
2661
mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
2662
num_bodies_to_update_bounds = 0;
2663
}
2664
2665
if (sb_ctx->mCanSleep == ECanSleep::CanSleep)
2666
{
2667
// This body should go to sleep
2668
bodies_to_put_to_sleep[num_bodies_to_put_to_sleep++] = id;
2669
if (num_bodies_to_put_to_sleep == cBodiesBatch)
2670
{
2671
mBodyManager.DeactivateBodies(bodies_to_put_to_sleep, num_bodies_to_put_to_sleep);
2672
num_bodies_to_put_to_sleep = 0;
2673
}
2674
}
2675
}
2676
2677
// Notify change bounds on requested bodies
2678
if (num_bodies_to_update_bounds > 0)
2679
mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
2680
2681
// Notify bodies to go to sleep
2682
if (num_bodies_to_put_to_sleep > 0)
2683
mBodyManager.DeactivateBodies(bodies_to_put_to_sleep, num_bodies_to_put_to_sleep);
2684
2685
// Free soft body contexts
2686
ioContext->mTempAllocator->Free(ioContext->mSoftBodyUpdateContexts, ioContext->mNumSoftBodies * sizeof(SoftBodyUpdateContext));
2687
}
2688
2689
void PhysicsSystem::SaveState(StateRecorder &inStream, EStateRecorderState inState, const StateRecorderFilter *inFilter) const
2690
{
2691
JPH_PROFILE_FUNCTION();
2692
2693
inStream.Write(inState);
2694
2695
if (uint8(inState) & uint8(EStateRecorderState::Global))
2696
{
2697
inStream.Write(mPreviousStepDeltaTime);
2698
inStream.Write(mGravity);
2699
}
2700
2701
if (uint8(inState) & uint8(EStateRecorderState::Bodies))
2702
mBodyManager.SaveState(inStream, inFilter);
2703
2704
if (uint8(inState) & uint8(EStateRecorderState::Contacts))
2705
mContactManager.SaveState(inStream, inFilter);
2706
2707
if (uint8(inState) & uint8(EStateRecorderState::Constraints))
2708
mConstraintManager.SaveState(inStream, inFilter);
2709
}
2710
2711
bool PhysicsSystem::RestoreState(StateRecorder &inStream, const StateRecorderFilter *inFilter)
2712
{
2713
JPH_PROFILE_FUNCTION();
2714
2715
EStateRecorderState state = EStateRecorderState::All; // Set this value for validation. If a partial state is saved, validation will not work anyway.
2716
inStream.Read(state);
2717
2718
if (uint8(state) & uint8(EStateRecorderState::Global))
2719
{
2720
inStream.Read(mPreviousStepDeltaTime);
2721
inStream.Read(mGravity);
2722
}
2723
2724
if (uint8(state) & uint8(EStateRecorderState::Bodies))
2725
{
2726
if (!mBodyManager.RestoreState(inStream))
2727
return false;
2728
2729
// Update bounding boxes for all bodies in the broadphase
2730
if (inStream.IsLastPart())
2731
{
2732
Array<BodyID> bodies;
2733
for (const Body *b : mBodyManager.GetBodies())
2734
if (BodyManager::sIsValidBodyPointer(b) && b->IsInBroadPhase())
2735
bodies.push_back(b->GetID());
2736
if (!bodies.empty())
2737
mBroadPhase->NotifyBodiesAABBChanged(&bodies[0], (int)bodies.size());
2738
}
2739
}
2740
2741
if (uint8(state) & uint8(EStateRecorderState::Contacts))
2742
{
2743
if (!mContactManager.RestoreState(inStream, inFilter))
2744
return false;
2745
}
2746
2747
if (uint8(state) & uint8(EStateRecorderState::Constraints))
2748
{
2749
if (!mConstraintManager.RestoreState(inStream))
2750
return false;
2751
}
2752
2753
return true;
2754
}
2755
2756
void PhysicsSystem::SaveBodyState(const Body &inBody, StateRecorder &inStream) const
2757
{
2758
mBodyManager.SaveBodyState(inBody, inStream);
2759
}
2760
2761
void PhysicsSystem::RestoreBodyState(Body &ioBody, StateRecorder &inStream)
2762
{
2763
mBodyManager.RestoreBodyState(ioBody, inStream);
2764
2765
BodyID id = ioBody.GetID();
2766
mBroadPhase->NotifyBodiesAABBChanged(&id, 1);
2767
}
2768
2769
JPH_NAMESPACE_END
2770
2771