Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/PhysicsSystem.cpp
20852 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
#ifdef JPH_INTERNAL_EDGE_REMOVING_COLLECTOR_DEBUG
978
, inBody1.GetCenterOfMassPosition() // Query is done relative to the position of body 1
979
#endif // JPH_INTERNAL_EDGE_REMOVING_COLLECTOR_DEBUG
980
);
981
}
982
else
983
{
984
// Regular collide
985
CollisionDispatch::sCollideShapeVsShape(inBody1.GetShape(), inBody2.GetShape(), Vec3::sOne(), Vec3::sOne(), inCenterOfMassTransform1, inCenterOfMassTransform2, part1, part2, ioCollideShapeSettings, ioCollector, inShapeFilter);
986
}
987
}
988
989
void PhysicsSystem::ProcessBodyPair(ContactAllocator &ioContactAllocator, const BodyPair &inBodyPair)
990
{
991
JPH_PROFILE_FUNCTION();
992
993
// Fetch body pair
994
Body *body1 = &mBodyManager.GetBody(inBodyPair.mBodyA);
995
Body *body2 = &mBodyManager.GetBody(inBodyPair.mBodyB);
996
JPH_ASSERT(body1->IsActive());
997
998
JPH_DET_LOG("ProcessBodyPair: id1: " << inBodyPair.mBodyA << " id2: " << inBodyPair.mBodyB << " p1: " << body1->GetCenterOfMassPosition() << " p2: " << body2->GetCenterOfMassPosition() << " r1: " << body1->GetRotation() << " r2: " << body2->GetRotation());
999
1000
// Check for soft bodies
1001
if (body2->IsSoftBody())
1002
{
1003
// If the 2nd body is a soft body and not active, we activate it now
1004
if (!body2->IsActive())
1005
mBodyManager.ActivateBodies(&inBodyPair.mBodyB, 1);
1006
1007
// Soft body processing is done later in the pipeline
1008
return;
1009
}
1010
1011
// 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,
1012
// which avoids accuracy problems when testing a very large static object against a small dynamic object
1013
// Ensure that body1 id < body2 id when motion types are the same.
1014
if (body1->GetMotionType() < body2->GetMotionType()
1015
|| (body1->GetMotionType() == body2->GetMotionType() && inBodyPair.mBodyB < inBodyPair.mBodyA))
1016
std::swap(body1, body2);
1017
1018
// Check if the contact points from the previous frame are reusable and if so copy them
1019
bool pair_handled = false, constraint_created = false;
1020
if (mPhysicsSettings.mUseBodyPairContactCache && !(body1->IsCollisionCacheInvalid() || body2->IsCollisionCacheInvalid()))
1021
mContactManager.GetContactsFromCache(ioContactAllocator, *body1, *body2, pair_handled, constraint_created);
1022
1023
// If the cache hasn't handled this body pair do actual collision detection
1024
if (!pair_handled)
1025
{
1026
// Create entry in the cache for this body pair
1027
// Needs to happen irrespective if we found a collision or not (we want to remember that no collision was found too)
1028
ContactConstraintManager::BodyPairHandle body_pair_handle = mContactManager.AddBodyPair(ioContactAllocator, *body1, *body2);
1029
if (body_pair_handle == nullptr)
1030
return; // Out of cache space
1031
1032
// Create the query settings
1033
CollideShapeSettings settings;
1034
settings.mCollectFacesMode = ECollectFacesMode::CollectFaces;
1035
settings.mActiveEdgeMode = mPhysicsSettings.mCheckActiveEdges? EActiveEdgeMode::CollideOnlyWithActive : EActiveEdgeMode::CollideWithAll;
1036
settings.mMaxSeparationDistance = body1->IsSensor() || body2->IsSensor()? 0.0f : mPhysicsSettings.mSpeculativeContactDistance;
1037
settings.mActiveEdgeMovementDirection = body1->GetLinearVelocity() - body2->GetLinearVelocity();
1038
1039
// Create shape filter
1040
SimShapeFilterWrapper shape_filter(mSimShapeFilter, body1);
1041
shape_filter.SetBody2(body2);
1042
1043
// Get transforms relative to body1
1044
RVec3 offset = body1->GetCenterOfMassPosition();
1045
Mat44 transform1 = Mat44::sRotation(body1->GetRotation());
1046
Mat44 transform2 = body2->GetCenterOfMassTransform().PostTranslated(-offset).ToMat44();
1047
1048
if (mPhysicsSettings.mUseManifoldReduction // Check global flag
1049
&& body1->GetUseManifoldReductionWithBody(*body2)) // Check body flag
1050
{
1051
// Version WITH contact manifold reduction
1052
1053
class MyManifold : public ContactManifold
1054
{
1055
public:
1056
Vec3 mFirstWorldSpaceNormal;
1057
};
1058
1059
// A temporary structure that allows us to keep track of the all manifolds between this body pair
1060
using Manifolds = StaticArray<MyManifold, 32>;
1061
1062
// Create collector
1063
class ReductionCollideShapeCollector : public CollideShapeCollector
1064
{
1065
public:
1066
ReductionCollideShapeCollector(PhysicsSystem *inSystem, const Body *inBody1, const Body *inBody2) :
1067
mSystem(inSystem),
1068
mBody1(inBody1),
1069
mBody2(inBody2)
1070
{
1071
}
1072
1073
virtual void AddHit(const CollideShapeResult &inResult) override
1074
{
1075
// The first body should be the one with the highest motion type
1076
JPH_ASSERT(mBody1->GetMotionType() >= mBody2->GetMotionType());
1077
JPH_ASSERT(!ShouldEarlyOut());
1078
1079
// Test if we want to accept this hit
1080
if (mValidateBodyPair)
1081
{
1082
switch (mSystem->mContactManager.ValidateContactPoint(*mBody1, *mBody2, mBody1->GetCenterOfMassPosition(), inResult))
1083
{
1084
case ValidateResult::AcceptContact:
1085
// We're just accepting this one, nothing to do
1086
break;
1087
1088
case ValidateResult::AcceptAllContactsForThisBodyPair:
1089
// Accept and stop calling the validate callback
1090
mValidateBodyPair = false;
1091
break;
1092
1093
case ValidateResult::RejectContact:
1094
// Skip this contact
1095
return;
1096
1097
case ValidateResult::RejectAllContactsForThisBodyPair:
1098
// Skip this and early out
1099
ForceEarlyOut();
1100
return;
1101
}
1102
}
1103
1104
// Calculate normal
1105
Vec3 world_space_normal = inResult.mPenetrationAxis.Normalized();
1106
1107
// Check if we can add it to an existing manifold
1108
Manifolds::iterator manifold;
1109
float contact_normal_cos_max_delta_rot = mSystem->mPhysicsSettings.mContactNormalCosMaxDeltaRotation;
1110
for (manifold = mManifolds.begin(); manifold != mManifolds.end(); ++manifold)
1111
if (world_space_normal.Dot(manifold->mFirstWorldSpaceNormal) >= contact_normal_cos_max_delta_rot)
1112
{
1113
// Update average normal
1114
manifold->mWorldSpaceNormal += world_space_normal;
1115
manifold->mPenetrationDepth = max(manifold->mPenetrationDepth, inResult.mPenetrationDepth);
1116
break;
1117
}
1118
if (manifold == mManifolds.end())
1119
{
1120
// Check if array is full
1121
if (mManifolds.size() == mManifolds.capacity())
1122
{
1123
// Full, find manifold with least amount of penetration
1124
manifold = mManifolds.begin();
1125
for (Manifolds::iterator m = mManifolds.begin() + 1; m < mManifolds.end(); ++m)
1126
if (m->mPenetrationDepth < manifold->mPenetrationDepth)
1127
manifold = m;
1128
1129
// If this contacts penetration is smaller than the smallest manifold, we skip this contact
1130
if (inResult.mPenetrationDepth < manifold->mPenetrationDepth)
1131
return;
1132
1133
// Replace the manifold
1134
*manifold = { { mBody1->GetCenterOfMassPosition(), world_space_normal, inResult.mPenetrationDepth, inResult.mSubShapeID1, inResult.mSubShapeID2, { }, { } }, world_space_normal };
1135
}
1136
else
1137
{
1138
// Not full, create new manifold
1139
mManifolds.push_back({ { mBody1->GetCenterOfMassPosition(), world_space_normal, inResult.mPenetrationDepth, inResult.mSubShapeID1, inResult.mSubShapeID2, { }, { } }, world_space_normal });
1140
manifold = mManifolds.end() - 1;
1141
}
1142
}
1143
1144
// Determine contact points
1145
const PhysicsSettings &settings = mSystem->mPhysicsSettings;
1146
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()));
1147
1148
// Prune if we have more than 32 points (this means we could run out of space in the next iteration)
1149
if (manifold->mRelativeContactPointsOn1.size() > 32)
1150
PruneContactPoints(manifold->mFirstWorldSpaceNormal, manifold->mRelativeContactPointsOn1, manifold->mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, manifold->mBaseOffset));
1151
}
1152
1153
PhysicsSystem * mSystem;
1154
const Body * mBody1;
1155
const Body * mBody2;
1156
bool mValidateBodyPair = true;
1157
Manifolds mManifolds;
1158
};
1159
ReductionCollideShapeCollector collector(this, body1, body2);
1160
1161
// Perform collision detection between the two shapes
1162
mSimCollideBodyVsBody(*body1, *body2, transform1, transform2, settings, collector, shape_filter.GetFilter());
1163
1164
// Add the contacts
1165
for (ContactManifold &manifold : collector.mManifolds)
1166
{
1167
// Normalize the normal (is a sum of all normals from merged manifolds)
1168
manifold.mWorldSpaceNormal = manifold.mWorldSpaceNormal.Normalized();
1169
1170
// If we still have too many points, prune them now
1171
if (manifold.mRelativeContactPointsOn1.size() > 4)
1172
PruneContactPoints(manifold.mWorldSpaceNormal, manifold.mRelativeContactPointsOn1, manifold.mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, manifold.mBaseOffset));
1173
1174
// Actually add the contact points to the manager
1175
constraint_created |= mContactManager.AddContactConstraint(ioContactAllocator, body_pair_handle, *body1, *body2, manifold);
1176
}
1177
}
1178
else
1179
{
1180
// Version WITHOUT contact manifold reduction
1181
1182
// Create collector
1183
class NonReductionCollideShapeCollector : public CollideShapeCollector
1184
{
1185
public:
1186
NonReductionCollideShapeCollector(PhysicsSystem *inSystem, ContactAllocator &ioContactAllocator, Body *inBody1, Body *inBody2, const ContactConstraintManager::BodyPairHandle &inPairHandle) :
1187
mSystem(inSystem),
1188
mContactAllocator(ioContactAllocator),
1189
mBody1(inBody1),
1190
mBody2(inBody2),
1191
mBodyPairHandle(inPairHandle)
1192
{
1193
}
1194
1195
virtual void AddHit(const CollideShapeResult &inResult) override
1196
{
1197
// The first body should be the one with the highest motion type
1198
JPH_ASSERT(mBody1->GetMotionType() >= mBody2->GetMotionType());
1199
JPH_ASSERT(!ShouldEarlyOut());
1200
1201
// Test if we want to accept this hit
1202
if (mValidateBodyPair)
1203
{
1204
switch (mSystem->mContactManager.ValidateContactPoint(*mBody1, *mBody2, mBody1->GetCenterOfMassPosition(), inResult))
1205
{
1206
case ValidateResult::AcceptContact:
1207
// We're just accepting this one, nothing to do
1208
break;
1209
1210
case ValidateResult::AcceptAllContactsForThisBodyPair:
1211
// Accept and stop calling the validate callback
1212
mValidateBodyPair = false;
1213
break;
1214
1215
case ValidateResult::RejectContact:
1216
// Skip this contact
1217
return;
1218
1219
case ValidateResult::RejectAllContactsForThisBodyPair:
1220
// Skip this and early out
1221
ForceEarlyOut();
1222
return;
1223
}
1224
}
1225
1226
// Determine contact points
1227
ContactManifold manifold;
1228
manifold.mBaseOffset = mBody1->GetCenterOfMassPosition();
1229
const PhysicsSettings &settings = mSystem->mPhysicsSettings;
1230
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));
1231
1232
// Calculate normal
1233
manifold.mWorldSpaceNormal = inResult.mPenetrationAxis.Normalized();
1234
1235
// Store penetration depth
1236
manifold.mPenetrationDepth = inResult.mPenetrationDepth;
1237
1238
// Prune if we have more than 4 points
1239
if (manifold.mRelativeContactPointsOn1.size() > 4)
1240
PruneContactPoints(manifold.mWorldSpaceNormal, manifold.mRelativeContactPointsOn1, manifold.mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, manifold.mBaseOffset));
1241
1242
// Set other properties
1243
manifold.mSubShapeID1 = inResult.mSubShapeID1;
1244
manifold.mSubShapeID2 = inResult.mSubShapeID2;
1245
1246
// Actually add the contact points to the manager
1247
mConstraintCreated |= mSystem->mContactManager.AddContactConstraint(mContactAllocator, mBodyPairHandle, *mBody1, *mBody2, manifold);
1248
}
1249
1250
PhysicsSystem * mSystem;
1251
ContactAllocator & mContactAllocator;
1252
Body * mBody1;
1253
Body * mBody2;
1254
ContactConstraintManager::BodyPairHandle mBodyPairHandle;
1255
bool mValidateBodyPair = true;
1256
bool mConstraintCreated = false;
1257
};
1258
NonReductionCollideShapeCollector collector(this, ioContactAllocator, body1, body2, body_pair_handle);
1259
1260
// Perform collision detection between the two shapes
1261
mSimCollideBodyVsBody(*body1, *body2, transform1, transform2, settings, collector, shape_filter.GetFilter());
1262
1263
constraint_created = collector.mConstraintCreated;
1264
}
1265
}
1266
1267
// If a contact constraint was created, we need to do some extra work
1268
if (constraint_created)
1269
{
1270
// Wake up sleeping bodies
1271
BodyID body_ids[2];
1272
int num_bodies = 0;
1273
if (body1->IsDynamic() && !body1->IsActive())
1274
body_ids[num_bodies++] = body1->GetID();
1275
if (body2->IsDynamic() && !body2->IsActive())
1276
body_ids[num_bodies++] = body2->GetID();
1277
if (num_bodies > 0)
1278
mBodyManager.ActivateBodies(body_ids, num_bodies);
1279
1280
// Link the two bodies
1281
mIslandBuilder.LinkBodies(body1->GetIndexInActiveBodiesInternal(), body2->GetIndexInActiveBodiesInternal());
1282
}
1283
}
1284
1285
void PhysicsSystem::JobFinalizeIslands(PhysicsUpdateContext *ioContext)
1286
{
1287
#ifdef JPH_ENABLE_ASSERTS
1288
// We only touch island data
1289
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::None);
1290
#endif
1291
1292
// Finish collecting the islands, at this point the active body list doesn't change so it's safe to access
1293
mIslandBuilder.Finalize(mBodyManager.GetActiveBodiesUnsafe(EBodyType::RigidBody), mBodyManager.GetNumActiveBodies(EBodyType::RigidBody), mContactManager.GetNumConstraints(), ioContext->mTempAllocator);
1294
1295
// Prepare the large island splitter
1296
if (mPhysicsSettings.mUseLargeIslandSplitter)
1297
mLargeIslandSplitter.Prepare(mIslandBuilder, mBodyManager.GetNumActiveBodies(EBodyType::RigidBody), ioContext->mTempAllocator);
1298
}
1299
1300
void PhysicsSystem::JobBodySetIslandIndex()
1301
{
1302
#ifdef JPH_ENABLE_ASSERTS
1303
// We only touch island data
1304
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::None);
1305
#endif
1306
1307
// Loop through the result and tag all bodies with an island index
1308
for (uint32 island_idx = 0, n = mIslandBuilder.GetNumIslands(); island_idx < n; ++island_idx)
1309
{
1310
BodyID *body_start, *body_end;
1311
mIslandBuilder.GetBodiesInIsland(island_idx, body_start, body_end);
1312
for (const BodyID *body = body_start; body < body_end; ++body)
1313
mBodyManager.GetBody(*body).GetMotionProperties()->SetIslandIndexInternal(island_idx);
1314
}
1315
}
1316
1317
JPH_SUPPRESS_WARNING_PUSH
1318
JPH_CLANG_SUPPRESS_WARNING("-Wundefined-func-template") // ConstraintManager::sWarmStartVelocityConstraints / ContactConstraintManager::WarmStartVelocityConstraints is instantiated in the cpp file
1319
1320
void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
1321
{
1322
#ifdef JPH_ENABLE_ASSERTS
1323
// We update velocities and need to read positions to do so
1324
BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::Read);
1325
#endif
1326
1327
float delta_time = ioContext->mStepDeltaTime;
1328
Constraint **active_constraints = ioContext->mActiveConstraints;
1329
1330
// Only the first step to correct for the delta time difference in the previous update
1331
float warm_start_impulse_ratio = ioStep->mIsFirst? ioContext->mWarmStartImpulseRatio : 1.0f;
1332
1333
bool check_islands = true, check_split_islands = mPhysicsSettings.mUseLargeIslandSplitter;
1334
for (;;)
1335
{
1336
// First try to get work from large islands
1337
if (check_split_islands)
1338
{
1339
bool first_iteration;
1340
uint split_island_index;
1341
uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;
1342
switch (mLargeIslandSplitter.FetchNextBatch(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, first_iteration))
1343
{
1344
case LargeIslandSplitter::EStatus::BatchRetrieved:
1345
{
1346
if (first_iteration)
1347
{
1348
// Iteration 0 is used to warm start the batch (we added 1 to the number of iterations in LargeIslandSplitter::SplitIsland)
1349
DummyCalculateSolverSteps dummy;
1350
ConstraintManager::sWarmStartVelocityConstraints(active_constraints, constraints_begin, constraints_end, warm_start_impulse_ratio, dummy);
1351
mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio, dummy);
1352
}
1353
else
1354
{
1355
// Solve velocity constraints
1356
ConstraintManager::sSolveVelocityConstraints(active_constraints, constraints_begin, constraints_end, delta_time);
1357
mContactManager.SolveVelocityConstraints(contacts_begin, contacts_end);
1358
}
1359
1360
// Mark the batch as processed
1361
bool last_iteration, final_batch;
1362
mLargeIslandSplitter.MarkBatchProcessed(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, last_iteration, final_batch);
1363
1364
// Save back the lambdas in the contact cache for the warm start of the next physics update
1365
if (last_iteration)
1366
mContactManager.StoreAppliedImpulses(contacts_begin, contacts_end);
1367
1368
// We processed work, loop again
1369
continue;
1370
}
1371
1372
case LargeIslandSplitter::EStatus::WaitingForBatch:
1373
break;
1374
1375
case LargeIslandSplitter::EStatus::AllBatchesDone:
1376
check_split_islands = false;
1377
break;
1378
}
1379
}
1380
1381
// If that didn't succeed try to process an island
1382
if (check_islands)
1383
{
1384
// Next island
1385
uint32 island_idx = ioStep->mSolveVelocityConstraintsNextIsland++;
1386
if (island_idx >= mIslandBuilder.GetNumIslands())
1387
{
1388
// We processed all islands, stop checking islands
1389
check_islands = false;
1390
continue;
1391
}
1392
1393
JPH_PROFILE("Island");
1394
1395
// Get iterators for this island
1396
uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;
1397
bool has_constraints = mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end);
1398
bool has_contacts = mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end);
1399
1400
// If we don't have any contacts or constraints, we know that none of the following islands have any contacts or constraints
1401
// (because they're sorted by most constraints first). This means we're done.
1402
if (!has_contacts && !has_constraints)
1403
{
1404
#ifdef JPH_ENABLE_ASSERTS
1405
// Validate our assumption that the next islands don't have any constraints or contacts
1406
for (; island_idx < mIslandBuilder.GetNumIslands(); ++island_idx)
1407
{
1408
JPH_ASSERT(!mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end));
1409
JPH_ASSERT(!mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end));
1410
}
1411
#endif // JPH_ENABLE_ASSERTS
1412
1413
check_islands = false;
1414
continue;
1415
}
1416
1417
// Sorting is costly but needed for a deterministic simulation, allow the user to turn this off
1418
if (mPhysicsSettings.mDeterministicSimulation)
1419
{
1420
// Sort constraints to give a deterministic simulation
1421
ConstraintManager::sSortConstraints(active_constraints, constraints_begin, constraints_end);
1422
1423
// Sort contacts to give a deterministic simulation
1424
mContactManager.SortContacts(contacts_begin, contacts_end);
1425
}
1426
1427
// Split up large islands
1428
CalculateSolverSteps steps_calculator(mPhysicsSettings);
1429
if (mPhysicsSettings.mUseLargeIslandSplitter
1430
&& mLargeIslandSplitter.SplitIsland(island_idx, mIslandBuilder, mBodyManager, mContactManager, active_constraints, steps_calculator))
1431
continue; // Loop again to try to fetch the newly split island
1432
1433
// We didn't create a split, just run the solver now for this entire island. Begin by warm starting.
1434
ConstraintManager::sWarmStartVelocityConstraints(active_constraints, constraints_begin, constraints_end, warm_start_impulse_ratio, steps_calculator);
1435
mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio, steps_calculator);
1436
steps_calculator.Finalize();
1437
1438
// Store the number of position steps for later
1439
mIslandBuilder.SetNumPositionSteps(island_idx, steps_calculator.GetNumPositionSteps());
1440
1441
// Solve velocity constraints
1442
for (uint velocity_step = 0; velocity_step < steps_calculator.GetNumVelocitySteps(); ++velocity_step)
1443
{
1444
bool applied_impulse = ConstraintManager::sSolveVelocityConstraints(active_constraints, constraints_begin, constraints_end, delta_time);
1445
applied_impulse |= mContactManager.SolveVelocityConstraints(contacts_begin, contacts_end);
1446
if (!applied_impulse)
1447
break;
1448
}
1449
1450
// Save back the lambdas in the contact cache for the warm start of the next physics update
1451
mContactManager.StoreAppliedImpulses(contacts_begin, contacts_end);
1452
1453
// We processed work, loop again
1454
continue;
1455
}
1456
1457
if (check_islands)
1458
{
1459
// If there are islands, we don't need to wait and can pick up new work
1460
continue;
1461
}
1462
else if (check_split_islands)
1463
{
1464
// If there are split islands, but we didn't do any work, give up a time slice
1465
std::this_thread::yield();
1466
}
1467
else
1468
{
1469
// No more work
1470
break;
1471
}
1472
}
1473
}
1474
1475
JPH_SUPPRESS_WARNING_POP
1476
1477
void PhysicsSystem::JobPreIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
1478
{
1479
// Reserve enough space for all bodies that may need a cast
1480
TempAllocator *temp_allocator = ioContext->mTempAllocator;
1481
JPH_ASSERT(ioStep->mCCDBodies == nullptr);
1482
ioStep->mCCDBodiesCapacity = mBodyManager.GetNumActiveCCDBodies();
1483
ioStep->mCCDBodies = (CCDBody *)temp_allocator->Allocate(ioStep->mCCDBodiesCapacity * sizeof(CCDBody));
1484
1485
// Initialize the mapping table between active body and CCD body
1486
JPH_ASSERT(ioStep->mActiveBodyToCCDBody == nullptr);
1487
ioStep->mNumActiveBodyToCCDBody = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);
1488
ioStep->mActiveBodyToCCDBody = (int *)temp_allocator->Allocate(ioStep->mNumActiveBodyToCCDBody * sizeof(int));
1489
1490
// Prepare the split island builder for solving the position constraints
1491
mLargeIslandSplitter.PrepareForSolvePositions();
1492
}
1493
1494
void PhysicsSystem::JobIntegrateVelocity(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
1495
{
1496
#ifdef JPH_ENABLE_ASSERTS
1497
// We update positions and need velocity to do so, we also clamp velocities so need to write to them
1498
BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::ReadWrite);
1499
#endif
1500
1501
float delta_time = ioContext->mStepDeltaTime;
1502
const BodyID *active_bodies = mBodyManager.GetActiveBodiesUnsafe(EBodyType::RigidBody);
1503
uint32 num_active_bodies = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);
1504
uint32 num_active_bodies_after_find_collisions = ioStep->mActiveBodyReadIdx;
1505
1506
// We can move bodies that are not part of an island. In this case we need to notify the broadphase of the movement.
1507
static constexpr int cBodiesBatch = 64;
1508
BodyID *bodies_to_update_bounds = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));
1509
int num_bodies_to_update_bounds = 0;
1510
1511
for (;;)
1512
{
1513
// Atomically fetch a batch of bodies
1514
uint32 active_body_idx = ioStep->mIntegrateVelocityReadIdx.fetch_add(cIntegrateVelocityBatchSize);
1515
if (active_body_idx >= num_active_bodies)
1516
break;
1517
1518
// Calculate the end of the batch
1519
uint32 active_body_idx_end = min(num_active_bodies, active_body_idx + cIntegrateVelocityBatchSize);
1520
1521
// Process the batch
1522
while (active_body_idx < active_body_idx_end)
1523
{
1524
// Update the positions using an Symplectic Euler step (which integrates using the updated velocity v1' rather
1525
// than the original velocity v1):
1526
// x1' = x1 + h * v1'
1527
// At this point the active bodies list does not change, so it is safe to access the array.
1528
BodyID body_id = active_bodies[active_body_idx];
1529
Body &body = mBodyManager.GetBody(body_id);
1530
MotionProperties *mp = body.GetMotionProperties();
1531
1532
JPH_DET_LOG("JobIntegrateVelocity: id: " << body_id << " v: " << body.GetLinearVelocity() << " w: " << body.GetAngularVelocity());
1533
1534
// Clamp velocities (not for kinematic bodies)
1535
if (body.IsDynamic())
1536
{
1537
mp->ClampLinearVelocity();
1538
mp->ClampAngularVelocity();
1539
}
1540
1541
// Update the rotation of the body according to the angular velocity
1542
// For motion type discrete we need to do this anyway, for motion type linear cast we have multiple choices
1543
// 1. Rotate the body first and then sweep
1544
// 2. First sweep and then rotate the body at the end
1545
// 3. Pick some in between rotation (e.g. half way), then sweep and finally rotate the remainder
1546
// (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.
1547
// 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
1548
// 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
1549
// 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
1550
// 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
1551
// 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).
1552
body.AddRotationStep(body.GetAngularVelocity() * delta_time);
1553
1554
// Get delta position
1555
Vec3 delta_pos = body.GetLinearVelocity() * delta_time;
1556
1557
// If the position should be updated (or if it is delayed because of CCD)
1558
bool update_position = true;
1559
1560
switch (mp->GetMotionQuality())
1561
{
1562
case EMotionQuality::Discrete:
1563
// No additional collision checking to be done
1564
break;
1565
1566
case EMotionQuality::LinearCast:
1567
if (body.IsDynamic() // Kinematic bodies cannot be stopped
1568
&& !body.IsSensor()) // We don't support CCD sensors
1569
{
1570
// Determine inner radius (the smallest sphere that fits into the shape)
1571
float inner_radius = body.GetShape()->GetInnerRadius();
1572
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.");
1573
1574
// Measure translation in this step and check if it above the threshold to perform a linear cast
1575
float linear_cast_threshold_sq = Square(mPhysicsSettings.mLinearCastThreshold * inner_radius);
1576
if (delta_pos.LengthSq() > linear_cast_threshold_sq)
1577
{
1578
// This body needs a cast
1579
uint32 ccd_body_idx = ioStep->mNumCCDBodies++;
1580
JPH_ASSERT(active_body_idx < ioStep->mNumActiveBodyToCCDBody);
1581
ioStep->mActiveBodyToCCDBody[active_body_idx] = ccd_body_idx;
1582
new (&ioStep->mCCDBodies[ccd_body_idx]) CCDBody(body_id, delta_pos, linear_cast_threshold_sq, min(mPhysicsSettings.mPenetrationSlop, mPhysicsSettings.mLinearCastMaxPenetration * inner_radius));
1583
1584
update_position = false;
1585
}
1586
}
1587
break;
1588
}
1589
1590
if (update_position)
1591
{
1592
// Move the body now
1593
body.AddPositionStep(delta_pos);
1594
1595
// If the body was activated due to an earlier CCD step it will have an index in the active
1596
// body list that it higher than the highest one we processed during FindCollisions
1597
// which means it hasn't been assigned an island and will not be updated by an island
1598
// this means that we need to update its bounds manually
1599
if (mp->GetIndexInActiveBodiesInternal() >= num_active_bodies_after_find_collisions)
1600
{
1601
body.CalculateWorldSpaceBoundsInternal();
1602
bodies_to_update_bounds[num_bodies_to_update_bounds++] = body.GetID();
1603
if (num_bodies_to_update_bounds == cBodiesBatch)
1604
{
1605
// Buffer full, flush now
1606
mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
1607
num_bodies_to_update_bounds = 0;
1608
}
1609
}
1610
1611
// We did not create a CCD body
1612
ioStep->mActiveBodyToCCDBody[active_body_idx] = -1;
1613
}
1614
1615
active_body_idx++;
1616
}
1617
}
1618
1619
// Notify change bounds on requested bodies
1620
if (num_bodies_to_update_bounds > 0)
1621
mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
1622
}
1623
1624
void PhysicsSystem::JobPostIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep) const
1625
{
1626
// Validate that our reservations were correct
1627
JPH_ASSERT(ioStep->mNumCCDBodies <= mBodyManager.GetNumActiveCCDBodies());
1628
1629
if (ioStep->mNumCCDBodies == 0)
1630
{
1631
// No continuous collision detection jobs -> kick the next job ourselves
1632
ioStep->mContactRemovedCallbacks.RemoveDependency();
1633
}
1634
else
1635
{
1636
// Run the continuous collision detection jobs
1637
int num_continuous_collision_jobs = min(int(ioStep->mNumCCDBodies + cNumCCDBodiesPerJob - 1) / cNumCCDBodiesPerJob, ioContext->GetMaxConcurrency());
1638
ioStep->mResolveCCDContacts.AddDependency(num_continuous_collision_jobs);
1639
ioStep->mContactRemovedCallbacks.AddDependency(num_continuous_collision_jobs - 1); // Already had 1 dependency
1640
for (int i = 0; i < num_continuous_collision_jobs; ++i)
1641
{
1642
JobHandle job = ioContext->mJobSystem->CreateJob("FindCCDContacts", cColorFindCCDContacts, [ioContext, ioStep]()
1643
{
1644
ioContext->mPhysicsSystem->JobFindCCDContacts(ioContext, ioStep);
1645
1646
ioStep->mResolveCCDContacts.RemoveDependency();
1647
ioStep->mContactRemovedCallbacks.RemoveDependency();
1648
});
1649
ioContext->mBarrier->AddJob(job);
1650
}
1651
}
1652
}
1653
1654
// Helper function to calculate the motion of a body during this CCD step
1655
inline static Vec3 sCalculateBodyMotion(const Body &inBody, float inDeltaTime)
1656
{
1657
// If the body is linear casting, the body has not yet moved so we need to calculate its motion
1658
if (inBody.IsDynamic() && inBody.GetMotionProperties()->GetMotionQuality() == EMotionQuality::LinearCast)
1659
return inDeltaTime * inBody.GetLinearVelocity();
1660
1661
// Body has already moved, so we don't need to correct for anything
1662
return Vec3::sZero();
1663
}
1664
1665
// Helper function that finds the CCD body corresponding to a body (if it exists)
1666
inline static PhysicsUpdateContext::Step::CCDBody *sGetCCDBody(const Body &inBody, PhysicsUpdateContext::Step *inStep)
1667
{
1668
// Only rigid bodies can have a CCD body
1669
if (!inBody.IsRigidBody())
1670
return nullptr;
1671
1672
// If the body has no motion properties it cannot have a CCD body
1673
const MotionProperties *motion_properties = inBody.GetMotionPropertiesUnchecked();
1674
if (motion_properties == nullptr)
1675
return nullptr;
1676
1677
// If it is not active it cannot have a CCD body
1678
uint32 active_index = motion_properties->GetIndexInActiveBodiesInternal();
1679
if (active_index == Body::cInactiveIndex)
1680
return nullptr;
1681
1682
// Check if the active body has a corresponding CCD body
1683
JPH_ASSERT(active_index < inStep->mNumActiveBodyToCCDBody); // Ensure that the body has a mapping to CCD body
1684
int ccd_index = inStep->mActiveBodyToCCDBody[active_index];
1685
if (ccd_index < 0)
1686
return nullptr;
1687
1688
PhysicsUpdateContext::Step::CCDBody *ccd_body = &inStep->mCCDBodies[ccd_index];
1689
JPH_ASSERT(ccd_body->mBodyID1 == inBody.GetID(), "We found the wrong CCD body!");
1690
return ccd_body;
1691
}
1692
1693
void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
1694
{
1695
#ifdef JPH_ENABLE_ASSERTS
1696
// We only read positions, but the validate callback may read body positions and velocities
1697
BodyAccess::Grant grant(BodyAccess::EAccess::Read, BodyAccess::EAccess::Read);
1698
#endif
1699
1700
// Allocation context for allocating new contact points
1701
ContactAllocator contact_allocator(mContactManager.GetContactAllocator());
1702
1703
// Settings
1704
ShapeCastSettings settings;
1705
settings.mUseShrunkenShapeAndConvexRadius = true;
1706
settings.mBackFaceModeTriangles = EBackFaceMode::IgnoreBackFaces;
1707
settings.mBackFaceModeConvex = EBackFaceMode::IgnoreBackFaces;
1708
settings.mReturnDeepestPoint = true;
1709
settings.mCollectFacesMode = ECollectFacesMode::CollectFaces;
1710
settings.mActiveEdgeMode = mPhysicsSettings.mCheckActiveEdges? EActiveEdgeMode::CollideOnlyWithActive : EActiveEdgeMode::CollideWithAll;
1711
1712
for (;;)
1713
{
1714
// Fetch the next body to cast
1715
uint32 idx = ioStep->mNextCCDBody++;
1716
if (idx >= ioStep->mNumCCDBodies)
1717
break;
1718
CCDBody &ccd_body = ioStep->mCCDBodies[idx];
1719
const Body &body = mBodyManager.GetBody(ccd_body.mBodyID1);
1720
1721
// Filter out layers
1722
DefaultBroadPhaseLayerFilter broadphase_layer_filter = GetDefaultBroadPhaseLayerFilter(body.GetObjectLayer());
1723
DefaultObjectLayerFilter object_layer_filter = GetDefaultLayerFilter(body.GetObjectLayer());
1724
1725
#ifdef JPH_DEBUG_RENDERER
1726
// Draw start and end shape of cast
1727
if (sDrawMotionQualityLinearCast)
1728
{
1729
RMat44 com = body.GetCenterOfMassTransform();
1730
body.GetShape()->Draw(DebugRenderer::sInstance, com, Vec3::sOne(), Color::sGreen, false, true);
1731
DebugRenderer::sInstance->DrawArrow(com.GetTranslation(), com.GetTranslation() + ccd_body.mDeltaPosition, Color::sGreen, 0.1f);
1732
body.GetShape()->Draw(DebugRenderer::sInstance, com.PostTranslated(ccd_body.mDeltaPosition), Vec3::sOne(), Color::sRed, false, true);
1733
}
1734
#endif // JPH_DEBUG_RENDERER
1735
1736
// Create a collector that will find the maximum distance allowed to travel while not penetrating more than 'max penetration'
1737
class CCDNarrowPhaseCollector : public CastShapeCollector
1738
{
1739
public:
1740
CCDNarrowPhaseCollector(const BodyManager &inBodyManager, ContactConstraintManager &inContactConstraintManager, CCDBody &inCCDBody, ShapeCastResult &inResult, float inDeltaTime) :
1741
mBodyManager(inBodyManager),
1742
mContactConstraintManager(inContactConstraintManager),
1743
mCCDBody(inCCDBody),
1744
mResult(inResult),
1745
mDeltaTime(inDeltaTime)
1746
{
1747
}
1748
1749
virtual void AddHit(const ShapeCastResult &inResult) override
1750
{
1751
JPH_PROFILE_FUNCTION();
1752
1753
// Check if this is a possible earlier hit than the one before
1754
float fraction = inResult.mFraction;
1755
if (fraction < mCCDBody.mFractionPlusSlop)
1756
{
1757
// Normalize normal
1758
Vec3 normal = inResult.mPenetrationAxis.Normalized();
1759
1760
// Calculate how much we can add to the fraction to penetrate the collision point by mMaxPenetration.
1761
// Note that the normal is pointing towards body 2!
1762
// 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|
1763
// <=> dist = mMaxPenetration * |delta_pos| / normal . delta_pos
1764
// Converting to a faction: delta_fraction = dist / |delta_pos| = mLinearCastTreshold / normal . delta_pos
1765
float denominator = normal.Dot(mCCDBody.mDeltaPosition);
1766
if (denominator > mCCDBody.mMaxPenetration) // Avoid dividing by zero, if extra hit fraction > 1 there's also no point in continuing
1767
{
1768
float fraction_plus_slop = fraction + mCCDBody.mMaxPenetration / denominator;
1769
if (fraction_plus_slop < mCCDBody.mFractionPlusSlop)
1770
{
1771
const Body &body2 = mBodyManager.GetBody(inResult.mBodyID2);
1772
1773
// Check if we've already accepted all hits from this body
1774
if (mValidateBodyPair)
1775
{
1776
// Validate the contact result
1777
const Body &body1 = mBodyManager.GetBody(mCCDBody.mBodyID1);
1778
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
1779
switch (validate_result)
1780
{
1781
case ValidateResult::AcceptContact:
1782
// Just continue
1783
break;
1784
1785
case ValidateResult::AcceptAllContactsForThisBodyPair:
1786
// Accept this and all following contacts from this body
1787
mValidateBodyPair = false;
1788
break;
1789
1790
case ValidateResult::RejectContact:
1791
return;
1792
1793
case ValidateResult::RejectAllContactsForThisBodyPair:
1794
// Reject this and all following contacts from this body
1795
mRejectAll = true;
1796
ForceEarlyOut();
1797
return;
1798
}
1799
}
1800
1801
// This is the earliest hit so far, store it
1802
mCCDBody.mContactNormal = normal;
1803
mCCDBody.mBodyID2 = inResult.mBodyID2;
1804
mCCDBody.mSubShapeID2 = inResult.mSubShapeID2;
1805
mCCDBody.mFraction = fraction;
1806
mCCDBody.mFractionPlusSlop = fraction_plus_slop;
1807
mResult = inResult;
1808
1809
// Result was assuming body 2 is not moving, but it is, so we need to correct for it
1810
Vec3 movement2 = fraction * sCalculateBodyMotion(body2, mDeltaTime);
1811
if (!movement2.IsNearZero())
1812
{
1813
mResult.mContactPointOn1 += movement2;
1814
mResult.mContactPointOn2 += movement2;
1815
for (Vec3 &v : mResult.mShape1Face)
1816
v += movement2;
1817
for (Vec3 &v : mResult.mShape2Face)
1818
v += movement2;
1819
}
1820
1821
// Update early out fraction
1822
UpdateEarlyOutFraction(fraction_plus_slop);
1823
}
1824
}
1825
}
1826
}
1827
1828
bool mValidateBodyPair; ///< If we still have to call the ValidateContactPoint for this body pair
1829
bool mRejectAll; ///< Reject all further contacts between this body pair
1830
1831
private:
1832
const BodyManager & mBodyManager;
1833
ContactConstraintManager & mContactConstraintManager;
1834
CCDBody & mCCDBody;
1835
ShapeCastResult & mResult;
1836
float mDeltaTime;
1837
BodyID mAcceptedBodyID;
1838
};
1839
1840
// Narrowphase collector
1841
ShapeCastResult cast_shape_result;
1842
CCDNarrowPhaseCollector np_collector(mBodyManager, mContactManager, ccd_body, cast_shape_result, ioContext->mStepDeltaTime);
1843
1844
// This collector wraps the narrowphase collector and collects the closest hit
1845
class CCDBroadPhaseCollector : public CastShapeBodyCollector
1846
{
1847
public:
1848
CCDBroadPhaseCollector(const CCDBody &inCCDBody, const Body &inBody1, const RShapeCast &inShapeCast, ShapeCastSettings &inShapeCastSettings, SimShapeFilterWrapper &inShapeFilter, CCDNarrowPhaseCollector &ioCollector, const BodyManager &inBodyManager, PhysicsUpdateContext::Step *inStep, float inDeltaTime) :
1849
mCCDBody(inCCDBody),
1850
mBody1(inBody1),
1851
mBody1Extent(inShapeCast.mShapeWorldBounds.GetExtent()),
1852
mShapeCast(inShapeCast),
1853
mShapeCastSettings(inShapeCastSettings),
1854
mShapeFilter(inShapeFilter),
1855
mCollector(ioCollector),
1856
mBodyManager(inBodyManager),
1857
mStep(inStep),
1858
mDeltaTime(inDeltaTime)
1859
{
1860
}
1861
1862
virtual void AddHit(const BroadPhaseCastResult &inResult) override
1863
{
1864
JPH_PROFILE_FUNCTION();
1865
1866
JPH_ASSERT(inResult.mFraction <= GetEarlyOutFraction(), "This hit should not have been passed on to the collector");
1867
1868
// Test if we're colliding with ourselves
1869
if (mBody1.GetID() == inResult.mBodyID)
1870
return;
1871
1872
// Avoid treating duplicates, if both bodies are doing CCD then only consider collision if body ID < other body ID
1873
const Body &body2 = mBodyManager.GetBody(inResult.mBodyID);
1874
const CCDBody *ccd_body2 = sGetCCDBody(body2, mStep);
1875
if (ccd_body2 != nullptr && mCCDBody.mBodyID1 > ccd_body2->mBodyID1)
1876
return;
1877
1878
// Test group filter
1879
if (!mBody1.GetCollisionGroup().CanCollide(body2.GetCollisionGroup()))
1880
return;
1881
1882
// TODO: For now we ignore sensors
1883
if (body2.IsSensor())
1884
return;
1885
1886
// Get relative movement of these two bodies
1887
Vec3 direction = mShapeCast.mDirection - sCalculateBodyMotion(body2, mDeltaTime);
1888
1889
// Test if the remaining movement is less than our movement threshold
1890
if (direction.LengthSq() < mCCDBody.mLinearCastThresholdSq)
1891
return;
1892
1893
// 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
1894
AABox bounds = body2.GetWorldSpaceBounds();
1895
bounds.mMin -= mBody1Extent;
1896
bounds.mMax += mBody1Extent;
1897
float hit_fraction = RayAABox(Vec3(mShapeCast.mCenterOfMassStart.GetTranslation()), RayInvDirection(direction), bounds.mMin, bounds.mMax);
1898
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
1899
return;
1900
1901
// Reset collector (this is a new body pair)
1902
mCollector.ResetEarlyOutFraction(GetEarlyOutFraction());
1903
mCollector.mValidateBodyPair = true;
1904
mCollector.mRejectAll = false;
1905
1906
// Set body ID on shape filter
1907
mShapeFilter.SetBody2(&body2);
1908
1909
// Provide direction as hint for the active edges algorithm
1910
mShapeCastSettings.mActiveEdgeMovementDirection = direction;
1911
1912
// Do narrow phase collision check
1913
RShapeCast relative_cast(mShapeCast.mShape, mShapeCast.mScale, mShapeCast.mCenterOfMassStart, direction, mShapeCast.mShapeWorldBounds);
1914
body2.GetTransformedShape().CastShape(relative_cast, mShapeCastSettings, mShapeCast.mCenterOfMassStart.GetTranslation(), mCollector, mShapeFilter.GetFilter());
1915
1916
// Update early out fraction based on narrow phase collector
1917
if (!mCollector.mRejectAll)
1918
UpdateEarlyOutFraction(mCollector.GetEarlyOutFraction());
1919
}
1920
1921
const CCDBody & mCCDBody;
1922
const Body & mBody1;
1923
Vec3 mBody1Extent;
1924
RShapeCast mShapeCast;
1925
ShapeCastSettings & mShapeCastSettings;
1926
SimShapeFilterWrapper & mShapeFilter;
1927
CCDNarrowPhaseCollector & mCollector;
1928
const BodyManager & mBodyManager;
1929
PhysicsUpdateContext::Step *mStep;
1930
float mDeltaTime;
1931
};
1932
1933
// Create shape filter
1934
SimShapeFilterWrapper shape_filter(mSimShapeFilter, &body);
1935
1936
// 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.
1937
RShapeCast shape_cast(body.GetShape(), Vec3::sOne(), body.GetCenterOfMassTransform(), ccd_body.mDeltaPosition);
1938
CCDBroadPhaseCollector bp_collector(ccd_body, body, shape_cast, settings, shape_filter, np_collector, mBodyManager, ioStep, ioContext->mStepDeltaTime);
1939
mBroadPhase->CastAABoxNoLock({ shape_cast.mShapeWorldBounds, shape_cast.mDirection }, bp_collector, broadphase_layer_filter, object_layer_filter);
1940
1941
// Check if there was a hit
1942
if (ccd_body.mFractionPlusSlop < 1.0f)
1943
{
1944
const Body &body2 = mBodyManager.GetBody(ccd_body.mBodyID2);
1945
1946
// Determine contact manifold
1947
ContactManifold manifold;
1948
manifold.mBaseOffset = shape_cast.mCenterOfMassStart.GetTranslation();
1949
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));
1950
manifold.mSubShapeID1 = cast_shape_result.mSubShapeID1;
1951
manifold.mSubShapeID2 = cast_shape_result.mSubShapeID2;
1952
manifold.mPenetrationDepth = cast_shape_result.mPenetrationDepth;
1953
manifold.mWorldSpaceNormal = ccd_body.mContactNormal;
1954
1955
// Call contact point callbacks
1956
mContactManager.OnCCDContactAdded(contact_allocator, body, body2, manifold, ccd_body.mContactSettings);
1957
1958
if (ccd_body.mContactSettings.mIsSensor)
1959
{
1960
// If this is a sensor, we don't want to solve the contact
1961
ccd_body.mFractionPlusSlop = 1.0f;
1962
ccd_body.mBodyID2 = BodyID();
1963
}
1964
else
1965
{
1966
// Calculate the average position from the manifold (this will result in the same impulse applied as when we apply impulses to all contact points)
1967
if (manifold.mRelativeContactPointsOn2.size() > 1)
1968
{
1969
Vec3 average_contact_point = Vec3::sZero();
1970
for (const Vec3 &v : manifold.mRelativeContactPointsOn2)
1971
average_contact_point += v;
1972
average_contact_point /= (float)manifold.mRelativeContactPointsOn2.size();
1973
ccd_body.mContactPointOn2 = manifold.mBaseOffset + average_contact_point;
1974
}
1975
else
1976
ccd_body.mContactPointOn2 = manifold.mBaseOffset + cast_shape_result.mContactPointOn2;
1977
}
1978
}
1979
}
1980
1981
// Collect information from the contact allocator and accumulate it in the step.
1982
sFinalizeContactAllocator(*ioStep, contact_allocator);
1983
}
1984
1985
void PhysicsSystem::JobResolveCCDContacts(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
1986
{
1987
#ifdef JPH_ENABLE_ASSERTS
1988
// Read/write body access
1989
BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::ReadWrite);
1990
1991
// We activate bodies that we collide with
1992
BodyManager::GrantActiveBodiesAccess grant_active(true, false);
1993
#endif
1994
1995
uint32 num_active_bodies_after_find_collisions = ioStep->mActiveBodyReadIdx;
1996
TempAllocator *temp_allocator = ioContext->mTempAllocator;
1997
1998
// Check if there's anything to do
1999
uint num_ccd_bodies = ioStep->mNumCCDBodies;
2000
if (num_ccd_bodies > 0)
2001
{
2002
// Sort on fraction so that we process earliest collisions first
2003
// This is needed to make the simulation deterministic and also to be able to stop contact processing
2004
// between body pairs if an earlier hit was found involving the body by another CCD body
2005
// (if it's body ID < this CCD body's body ID - see filtering logic in CCDBroadPhaseCollector)
2006
CCDBody **sorted_ccd_bodies = (CCDBody **)temp_allocator->Allocate(num_ccd_bodies * sizeof(CCDBody *));
2007
JPH_SCOPE_EXIT([temp_allocator, sorted_ccd_bodies, num_ccd_bodies]{ temp_allocator->Free(sorted_ccd_bodies, num_ccd_bodies * sizeof(CCDBody *)); });
2008
{
2009
JPH_PROFILE("Sort");
2010
2011
// We don't want to copy the entire struct (it's quite big), so we create a pointer array first
2012
CCDBody *src_ccd_bodies = ioStep->mCCDBodies;
2013
CCDBody **dst_ccd_bodies = sorted_ccd_bodies;
2014
CCDBody **dst_ccd_bodies_end = dst_ccd_bodies + num_ccd_bodies;
2015
while (dst_ccd_bodies < dst_ccd_bodies_end)
2016
*(dst_ccd_bodies++) = src_ccd_bodies++;
2017
2018
// Which we then sort
2019
QuickSort(sorted_ccd_bodies, sorted_ccd_bodies + num_ccd_bodies, [](const CCDBody *inBody1, const CCDBody *inBody2)
2020
{
2021
if (inBody1->mFractionPlusSlop != inBody2->mFractionPlusSlop)
2022
return inBody1->mFractionPlusSlop < inBody2->mFractionPlusSlop;
2023
2024
return inBody1->mBodyID1 < inBody2->mBodyID1;
2025
});
2026
}
2027
2028
// We can collide with bodies that are not active, we track them here so we can activate them in one go at the end.
2029
// This is also needed because we can't modify the active body array while we iterate it.
2030
static constexpr int cBodiesBatch = 64;
2031
BodyID *bodies_to_activate = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));
2032
int num_bodies_to_activate = 0;
2033
2034
// We can move bodies that are not part of an island. In this case we need to notify the broadphase of the movement.
2035
BodyID *bodies_to_update_bounds = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));
2036
int num_bodies_to_update_bounds = 0;
2037
2038
for (uint i = 0; i < num_ccd_bodies; ++i)
2039
{
2040
const CCDBody *ccd_body = sorted_ccd_bodies[i];
2041
Body &body1 = mBodyManager.GetBody(ccd_body->mBodyID1);
2042
MotionProperties *body_mp = body1.GetMotionProperties();
2043
2044
// If there was a hit
2045
if (!ccd_body->mBodyID2.IsInvalid())
2046
{
2047
Body &body2 = mBodyManager.GetBody(ccd_body->mBodyID2);
2048
2049
// Determine if the other body has a CCD body
2050
CCDBody *ccd_body2 = sGetCCDBody(body2, ioStep);
2051
if (ccd_body2 != nullptr)
2052
{
2053
JPH_ASSERT(ccd_body2->mBodyID2 != ccd_body->mBodyID1, "If we collided with another body, that other body should have ignored collisions with us!");
2054
2055
// Check if the other body found a hit that is further away
2056
if (ccd_body2->mFraction > ccd_body->mFraction)
2057
{
2058
// 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).
2059
// This means that at this point we have triggered a contact point add/persist for our further hit by accident for the other body.
2060
// 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.
2061
ccd_body2->mBodyID2 = BodyID();
2062
ccd_body2->mFractionPlusSlop = ccd_body->mFraction;
2063
}
2064
}
2065
2066
// 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.
2067
// 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.
2068
if (ccd_body2 == nullptr || ccd_body2->mFraction >= ccd_body->mFraction)
2069
{
2070
const ContactSettings &contact_settings = ccd_body->mContactSettings;
2071
2072
// Calculate contact point velocity for body 1
2073
Vec3 r1_plus_u = Vec3(ccd_body->mContactPointOn2 - (body1.GetCenterOfMassPosition() + ccd_body->mFraction * ccd_body->mDeltaPosition));
2074
Vec3 v1 = body1.GetPointVelocityCOM(r1_plus_u);
2075
2076
// Calculate inverse mass for body 1
2077
float inv_m1 = contact_settings.mInvMassScale1 * body_mp->GetInverseMass();
2078
2079
if (body2.IsRigidBody())
2080
{
2081
// Calculate contact point velocity for body 2
2082
Vec3 r2 = Vec3(ccd_body->mContactPointOn2 - body2.GetCenterOfMassPosition());
2083
Vec3 v2 = body2.GetPointVelocityCOM(r2);
2084
2085
// Calculate relative contact velocity
2086
Vec3 relative_velocity = v2 - v1;
2087
float normal_velocity = relative_velocity.Dot(ccd_body->mContactNormal);
2088
2089
// Calculate velocity bias due to restitution
2090
float normal_velocity_bias;
2091
if (contact_settings.mCombinedRestitution > 0.0f && normal_velocity < -mPhysicsSettings.mMinVelocityForRestitution)
2092
normal_velocity_bias = contact_settings.mCombinedRestitution * normal_velocity;
2093
else
2094
normal_velocity_bias = 0.0f;
2095
2096
// Get inverse mass of body 2
2097
float inv_m2 = body2.GetMotionPropertiesUnchecked() != nullptr? contact_settings.mInvMassScale2 * body2.GetMotionPropertiesUnchecked()->GetInverseMassUnchecked() : 0.0f;
2098
2099
// Solve contact constraint
2100
AxisConstraintPart contact_constraint;
2101
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);
2102
contact_constraint.SolveVelocityConstraintWithMassOverride(body1, inv_m1, body2, inv_m2, ccd_body->mContactNormal, -FLT_MAX, FLT_MAX);
2103
2104
// Apply friction
2105
if (contact_settings.mCombinedFriction > 0.0f)
2106
{
2107
// Calculate friction direction by removing normal velocity from the relative velocity
2108
Vec3 friction_direction = relative_velocity - normal_velocity * ccd_body->mContactNormal;
2109
float friction_direction_len_sq = friction_direction.LengthSq();
2110
if (friction_direction_len_sq > 1.0e-12f)
2111
{
2112
// Normalize friction direction
2113
friction_direction /= sqrt(friction_direction_len_sq);
2114
2115
// Calculate max friction impulse
2116
float max_lambda_f = contact_settings.mCombinedFriction * contact_constraint.GetTotalLambda();
2117
2118
AxisConstraintPart friction;
2119
friction.CalculateConstraintPropertiesWithMassOverride(body1, inv_m1, contact_settings.mInvInertiaScale1, r1_plus_u, body2, inv_m2, contact_settings.mInvInertiaScale2, r2, friction_direction);
2120
friction.SolveVelocityConstraintWithMassOverride(body1, inv_m1, body2, inv_m2, friction_direction, -max_lambda_f, max_lambda_f);
2121
}
2122
}
2123
2124
// Clamp velocity of body 2
2125
if (body2.IsDynamic())
2126
{
2127
MotionProperties *body2_mp = body2.GetMotionProperties();
2128
body2_mp->ClampLinearVelocity();
2129
body2_mp->ClampAngularVelocity();
2130
}
2131
}
2132
else
2133
{
2134
SoftBodyMotionProperties *soft_mp = static_cast<SoftBodyMotionProperties *>(body2.GetMotionProperties());
2135
const SoftBodyShape *soft_shape = static_cast<const SoftBodyShape *>(body2.GetShape());
2136
2137
// Convert the sub shape ID of the soft body to a face
2138
uint32 face_idx = soft_shape->GetFaceIndex(ccd_body->mSubShapeID2);
2139
const SoftBodyMotionProperties::Face &face = soft_mp->GetFace(face_idx);
2140
2141
// Get vertices of the face
2142
SoftBodyMotionProperties::Vertex &vtx0 = soft_mp->GetVertex(face.mVertex[0]);
2143
SoftBodyMotionProperties::Vertex &vtx1 = soft_mp->GetVertex(face.mVertex[1]);
2144
SoftBodyMotionProperties::Vertex &vtx2 = soft_mp->GetVertex(face.mVertex[2]);
2145
2146
// Inverse mass of the face
2147
float vtx0_mass = vtx0.mInvMass > 0.0f? 1.0f / vtx0.mInvMass : 1.0e10f;
2148
float vtx1_mass = vtx1.mInvMass > 0.0f? 1.0f / vtx1.mInvMass : 1.0e10f;
2149
float vtx2_mass = vtx2.mInvMass > 0.0f? 1.0f / vtx2.mInvMass : 1.0e10f;
2150
float inv_m2 = 1.0f / (vtx0_mass + vtx1_mass + vtx2_mass);
2151
2152
// Calculate barycentric coordinates of the contact point on the soft body's face
2153
float u, v, w;
2154
RMat44 inv_body2_transform = body2.GetInverseCenterOfMassTransform();
2155
Vec3 local_contact = Vec3(inv_body2_transform * ccd_body->mContactPointOn2);
2156
ClosestPoint::GetBaryCentricCoordinates(vtx0.mPosition - local_contact, vtx1.mPosition - local_contact, vtx2.mPosition - local_contact, u, v, w);
2157
2158
// Calculate contact point velocity for the face
2159
Vec3 v2 = inv_body2_transform.Multiply3x3Transposed(u * vtx0.mVelocity + v * vtx1.mVelocity + w * vtx2.mVelocity);
2160
float normal_velocity = (v2 - v1).Dot(ccd_body->mContactNormal);
2161
2162
// Calculate velocity bias due to restitution
2163
float normal_velocity_bias;
2164
if (contact_settings.mCombinedRestitution > 0.0f && normal_velocity < -mPhysicsSettings.mMinVelocityForRestitution)
2165
normal_velocity_bias = contact_settings.mCombinedRestitution * normal_velocity;
2166
else
2167
normal_velocity_bias = 0.0f;
2168
2169
// 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)
2170
Vec3 r1_plus_u_x_n = r1_plus_u.Cross(ccd_body->mContactNormal);
2171
Vec3 invi1_r1_plus_u_x_n = contact_settings.mInvInertiaScale1 * body1.GetInverseInertia().Multiply3x3(r1_plus_u_x_n);
2172
float jv = r1_plus_u_x_n.Dot(body_mp->GetAngularVelocity()) - normal_velocity - normal_velocity_bias;
2173
float inv_effective_mass = inv_m1 + inv_m2 + invi1_r1_plus_u_x_n.Dot(r1_plus_u_x_n);
2174
float lambda = jv / inv_effective_mass;
2175
body_mp->SubLinearVelocityStep((lambda * inv_m1) * ccd_body->mContactNormal);
2176
body_mp->SubAngularVelocityStep(lambda * invi1_r1_plus_u_x_n);
2177
Vec3 delta_v2 = inv_body2_transform.Multiply3x3(lambda * ccd_body->mContactNormal);
2178
vtx0.mVelocity += delta_v2 * vtx0.mInvMass;
2179
vtx1.mVelocity += delta_v2 * vtx1.mInvMass;
2180
vtx2.mVelocity += delta_v2 * vtx2.mInvMass;
2181
}
2182
2183
// Clamp velocity of body 1
2184
body_mp->ClampLinearVelocity();
2185
body_mp->ClampAngularVelocity();
2186
2187
// Activate the 2nd body if it is not already active
2188
if (body2.IsDynamic() && !body2.IsActive())
2189
{
2190
bodies_to_activate[num_bodies_to_activate++] = ccd_body->mBodyID2;
2191
if (num_bodies_to_activate == cBodiesBatch)
2192
{
2193
// Batch is full, activate now
2194
mBodyManager.ActivateBodies(bodies_to_activate, num_bodies_to_activate);
2195
num_bodies_to_activate = 0;
2196
}
2197
}
2198
2199
#ifdef JPH_DEBUG_RENDERER
2200
if (sDrawMotionQualityLinearCast)
2201
{
2202
// Draw the collision location
2203
RMat44 collision_transform = body1.GetCenterOfMassTransform().PostTranslated(ccd_body->mFraction * ccd_body->mDeltaPosition);
2204
body1.GetShape()->Draw(DebugRenderer::sInstance, collision_transform, Vec3::sOne(), Color::sYellow, false, true);
2205
2206
// Draw the collision location + slop
2207
RMat44 collision_transform_plus_slop = body1.GetCenterOfMassTransform().PostTranslated(ccd_body->mFractionPlusSlop * ccd_body->mDeltaPosition);
2208
body1.GetShape()->Draw(DebugRenderer::sInstance, collision_transform_plus_slop, Vec3::sOne(), Color::sOrange, false, true);
2209
2210
// Draw contact normal
2211
DebugRenderer::sInstance->DrawArrow(ccd_body->mContactPointOn2, ccd_body->mContactPointOn2 - ccd_body->mContactNormal, Color::sYellow, 0.1f);
2212
2213
// Draw post contact velocity
2214
DebugRenderer::sInstance->DrawArrow(collision_transform.GetTranslation(), collision_transform.GetTranslation() + body1.GetLinearVelocity(), Color::sOrange, 0.1f);
2215
DebugRenderer::sInstance->DrawArrow(collision_transform.GetTranslation(), collision_transform.GetTranslation() + body1.GetAngularVelocity(), Color::sPurple, 0.1f);
2216
}
2217
#endif // JPH_DEBUG_RENDERER
2218
}
2219
}
2220
2221
// Update body position
2222
body1.AddPositionStep(ccd_body->mDeltaPosition * ccd_body->mFractionPlusSlop);
2223
2224
// If the body was activated due to an earlier CCD step it will have an index in the active
2225
// body list that it higher than the highest one we processed during FindCollisions
2226
// which means it hasn't been assigned an island and will not be updated by an island
2227
// this means that we need to update its bounds manually
2228
if (body_mp->GetIndexInActiveBodiesInternal() >= num_active_bodies_after_find_collisions)
2229
{
2230
body1.CalculateWorldSpaceBoundsInternal();
2231
bodies_to_update_bounds[num_bodies_to_update_bounds++] = body1.GetID();
2232
if (num_bodies_to_update_bounds == cBodiesBatch)
2233
{
2234
// Buffer full, flush now
2235
mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
2236
num_bodies_to_update_bounds = 0;
2237
}
2238
}
2239
}
2240
2241
// Activate the requested bodies
2242
if (num_bodies_to_activate > 0)
2243
mBodyManager.ActivateBodies(bodies_to_activate, num_bodies_to_activate);
2244
2245
// Notify change bounds on requested bodies
2246
if (num_bodies_to_update_bounds > 0)
2247
mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
2248
}
2249
2250
// Ensure we free the CCD bodies array now, will not call the destructor!
2251
temp_allocator->Free(ioStep->mActiveBodyToCCDBody, ioStep->mNumActiveBodyToCCDBody * sizeof(int));
2252
ioStep->mActiveBodyToCCDBody = nullptr;
2253
ioStep->mNumActiveBodyToCCDBody = 0;
2254
temp_allocator->Free(ioStep->mCCDBodies, ioStep->mCCDBodiesCapacity * sizeof(CCDBody));
2255
ioStep->mCCDBodies = nullptr;
2256
ioStep->mCCDBodiesCapacity = 0;
2257
}
2258
2259
void PhysicsSystem::JobContactRemovedCallbacks(const PhysicsUpdateContext::Step *ioStep)
2260
{
2261
#ifdef JPH_ENABLE_ASSERTS
2262
// We don't touch any bodies
2263
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::None);
2264
#endif
2265
2266
// Reset the Body::EFlags::InvalidateContactCache flag for all bodies
2267
mBodyManager.ValidateContactCacheForAllBodies();
2268
2269
// Finalize the contact cache (this swaps the read and write versions of the contact cache)
2270
// Trigger all contact removed callbacks by looking at last step contact points that have not been flagged as reused
2271
mContactManager.FinalizeContactCacheAndCallContactPointRemovedCallbacks(ioStep->mNumBodyPairs, ioStep->mNumManifolds);
2272
}
2273
2274
class PhysicsSystem::BodiesToSleep : public NonCopyable
2275
{
2276
public:
2277
static constexpr int cBodiesToSleepSize = 512;
2278
static constexpr int cMaxBodiesToPutInBuffer = 128;
2279
2280
inline BodiesToSleep(BodyManager &inBodyManager, BodyID *inBodiesToSleepBuffer) : mBodyManager(inBodyManager), mBodiesToSleepBuffer(inBodiesToSleepBuffer), mBodiesToSleepCur(inBodiesToSleepBuffer) { }
2281
2282
inline ~BodiesToSleep()
2283
{
2284
// Flush the bodies to sleep buffer
2285
int num_bodies_in_buffer = int(mBodiesToSleepCur - mBodiesToSleepBuffer);
2286
if (num_bodies_in_buffer > 0)
2287
mBodyManager.DeactivateBodies(mBodiesToSleepBuffer, num_bodies_in_buffer);
2288
}
2289
2290
inline void PutToSleep(const BodyID *inBegin, const BodyID *inEnd)
2291
{
2292
int num_bodies_to_sleep = int(inEnd - inBegin);
2293
if (num_bodies_to_sleep > cMaxBodiesToPutInBuffer)
2294
{
2295
// Too many bodies, deactivate immediately
2296
mBodyManager.DeactivateBodies(inBegin, num_bodies_to_sleep);
2297
}
2298
else
2299
{
2300
// Check if there's enough space in the bodies to sleep buffer
2301
int num_bodies_in_buffer = int(mBodiesToSleepCur - mBodiesToSleepBuffer);
2302
if (num_bodies_in_buffer + num_bodies_to_sleep > cBodiesToSleepSize)
2303
{
2304
// Flush the bodies to sleep buffer
2305
mBodyManager.DeactivateBodies(mBodiesToSleepBuffer, num_bodies_in_buffer);
2306
mBodiesToSleepCur = mBodiesToSleepBuffer;
2307
}
2308
2309
// Copy the bodies in the buffer
2310
memcpy(mBodiesToSleepCur, inBegin, num_bodies_to_sleep * sizeof(BodyID));
2311
mBodiesToSleepCur += num_bodies_to_sleep;
2312
}
2313
}
2314
2315
private:
2316
BodyManager & mBodyManager;
2317
BodyID * mBodiesToSleepBuffer;
2318
BodyID * mBodiesToSleepCur;
2319
};
2320
2321
void PhysicsSystem::CheckSleepAndUpdateBounds(uint32 inIslandIndex, const PhysicsUpdateContext *ioContext, const PhysicsUpdateContext::Step *ioStep, BodiesToSleep &ioBodiesToSleep)
2322
{
2323
// Get the bodies that belong to this island
2324
BodyID *bodies_begin, *bodies_end;
2325
mIslandBuilder.GetBodiesInIsland(inIslandIndex, bodies_begin, bodies_end);
2326
2327
// Only check sleeping in the last step
2328
// Also resets force and torque used during the apply gravity phase
2329
if (ioStep->mIsLast)
2330
{
2331
JPH_PROFILE("Check Sleeping");
2332
2333
static_assert(int(ECanSleep::CannotSleep) == 0 && int(ECanSleep::CanSleep) == 1, "Loop below makes this assumption");
2334
int all_can_sleep = mPhysicsSettings.mAllowSleeping? int(ECanSleep::CanSleep) : int(ECanSleep::CannotSleep);
2335
2336
float time_before_sleep = mPhysicsSettings.mTimeBeforeSleep;
2337
float max_movement = mPhysicsSettings.mPointVelocitySleepThreshold * time_before_sleep;
2338
2339
for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
2340
{
2341
Body &body = mBodyManager.GetBody(*body_id);
2342
2343
// Update bounding box
2344
body.CalculateWorldSpaceBoundsInternal();
2345
2346
// Update sleeping
2347
all_can_sleep &= int(body.UpdateSleepStateInternal(ioContext->mStepDeltaTime, max_movement, time_before_sleep));
2348
2349
// Reset force and torque
2350
MotionProperties *mp = body.GetMotionProperties();
2351
mp->ResetForce();
2352
mp->ResetTorque();
2353
}
2354
2355
// If all bodies indicate they can sleep we can deactivate them
2356
if (all_can_sleep == int(ECanSleep::CanSleep))
2357
ioBodiesToSleep.PutToSleep(bodies_begin, bodies_end);
2358
}
2359
else
2360
{
2361
JPH_PROFILE("Update Bounds");
2362
2363
// Update bounding box only for all other steps
2364
for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
2365
{
2366
Body &body = mBodyManager.GetBody(*body_id);
2367
body.CalculateWorldSpaceBoundsInternal();
2368
}
2369
}
2370
2371
// Notify broadphase of changed objects (find ccd contacts can do linear casts in the next step, so we need to do this every step)
2372
// Note: Shuffles the BodyID's around!!!
2373
mBroadPhase->NotifyBodiesAABBChanged(bodies_begin, int(bodies_end - bodies_begin), false);
2374
}
2375
2376
void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
2377
{
2378
#ifdef JPH_ENABLE_ASSERTS
2379
// We fix up position errors
2380
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::ReadWrite);
2381
2382
// Can only deactivate bodies
2383
BodyManager::GrantActiveBodiesAccess grant_active(false, true);
2384
#endif
2385
2386
float delta_time = ioContext->mStepDeltaTime;
2387
float baumgarte = mPhysicsSettings.mBaumgarte;
2388
Constraint **active_constraints = ioContext->mActiveConstraints;
2389
2390
// 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
2391
BodiesToSleep bodies_to_sleep(mBodyManager, (BodyID *)JPH_STACK_ALLOC(BodiesToSleep::cBodiesToSleepSize * sizeof(BodyID)));
2392
2393
bool check_islands = true, check_split_islands = mPhysicsSettings.mUseLargeIslandSplitter;
2394
for (;;)
2395
{
2396
// First try to get work from large islands
2397
if (check_split_islands)
2398
{
2399
bool first_iteration;
2400
uint split_island_index;
2401
uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;
2402
switch (mLargeIslandSplitter.FetchNextBatch(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, first_iteration))
2403
{
2404
case LargeIslandSplitter::EStatus::BatchRetrieved:
2405
// Solve the batch
2406
ConstraintManager::sSolvePositionConstraints(active_constraints, constraints_begin, constraints_end, delta_time, baumgarte);
2407
mContactManager.SolvePositionConstraints(contacts_begin, contacts_end);
2408
2409
// Mark the batch as processed
2410
bool last_iteration, final_batch;
2411
mLargeIslandSplitter.MarkBatchProcessed(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, last_iteration, final_batch);
2412
2413
// The final batch will update all bounds and check sleeping
2414
if (final_batch)
2415
CheckSleepAndUpdateBounds(mLargeIslandSplitter.GetIslandIndex(split_island_index), ioContext, ioStep, bodies_to_sleep);
2416
2417
// We processed work, loop again
2418
continue;
2419
2420
case LargeIslandSplitter::EStatus::WaitingForBatch:
2421
break;
2422
2423
case LargeIslandSplitter::EStatus::AllBatchesDone:
2424
check_split_islands = false;
2425
break;
2426
}
2427
}
2428
2429
// If that didn't succeed try to process an island
2430
if (check_islands)
2431
{
2432
// Next island
2433
uint32 island_idx = ioStep->mSolvePositionConstraintsNextIsland++;
2434
if (island_idx >= mIslandBuilder.GetNumIslands())
2435
{
2436
// We processed all islands, stop checking islands
2437
check_islands = false;
2438
continue;
2439
}
2440
2441
JPH_PROFILE("Island");
2442
2443
// Get iterators for this island
2444
uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;
2445
mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end);
2446
mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end);
2447
2448
// If this island is a large island, it will be picked up as a batch and we don't need to do anything here
2449
uint num_items = uint(constraints_end - constraints_begin) + uint(contacts_end - contacts_begin);
2450
if (mPhysicsSettings.mUseLargeIslandSplitter
2451
&& num_items >= LargeIslandSplitter::cLargeIslandTreshold)
2452
continue;
2453
2454
// Check if this island needs solving
2455
if (num_items > 0)
2456
{
2457
// Iterate
2458
uint num_position_steps = mIslandBuilder.GetNumPositionSteps(island_idx);
2459
for (uint position_step = 0; position_step < num_position_steps; ++position_step)
2460
{
2461
bool applied_impulse = ConstraintManager::sSolvePositionConstraints(active_constraints, constraints_begin, constraints_end, delta_time, baumgarte);
2462
applied_impulse |= mContactManager.SolvePositionConstraints(contacts_begin, contacts_end);
2463
if (!applied_impulse)
2464
break;
2465
}
2466
}
2467
2468
// After solving we will update all bounds and check sleeping
2469
CheckSleepAndUpdateBounds(island_idx, ioContext, ioStep, bodies_to_sleep);
2470
2471
// We processed work, loop again
2472
continue;
2473
}
2474
2475
if (check_islands)
2476
{
2477
// If there are islands, we don't need to wait and can pick up new work
2478
continue;
2479
}
2480
else if (check_split_islands)
2481
{
2482
// If there are split islands, but we didn't do any work, give up a time slice
2483
std::this_thread::yield();
2484
}
2485
else
2486
{
2487
// No more work
2488
break;
2489
}
2490
}
2491
}
2492
2493
void PhysicsSystem::JobSoftBodyPrepare(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
2494
{
2495
JPH_PROFILE_FUNCTION();
2496
2497
{
2498
#ifdef JPH_ENABLE_ASSERTS
2499
// Reading soft body positions
2500
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::Read);
2501
#endif
2502
2503
// Get the active soft bodies
2504
BodyIDVector active_bodies;
2505
mBodyManager.GetActiveBodies(EBodyType::SoftBody, active_bodies);
2506
2507
// Quit if there are no active soft bodies
2508
if (active_bodies.empty())
2509
{
2510
// Kick the next step
2511
if (ioStep->mStartNextStep.IsValid())
2512
ioStep->mStartNextStep.RemoveDependency();
2513
return;
2514
}
2515
2516
// Sort to get a deterministic update order
2517
QuickSort(active_bodies.begin(), active_bodies.end());
2518
2519
// Allocate soft body contexts
2520
ioContext->mNumSoftBodies = (uint)active_bodies.size();
2521
ioContext->mSoftBodyUpdateContexts = (SoftBodyUpdateContext *)ioContext->mTempAllocator->Allocate(ioContext->mNumSoftBodies * sizeof(SoftBodyUpdateContext));
2522
2523
// Initialize soft body contexts
2524
for (SoftBodyUpdateContext *sb_ctx = ioContext->mSoftBodyUpdateContexts, *sb_ctx_end = ioContext->mSoftBodyUpdateContexts + ioContext->mNumSoftBodies; sb_ctx < sb_ctx_end; ++sb_ctx)
2525
{
2526
new (sb_ctx) SoftBodyUpdateContext;
2527
Body &body = mBodyManager.GetBody(active_bodies[sb_ctx - ioContext->mSoftBodyUpdateContexts]);
2528
SoftBodyMotionProperties *mp = static_cast<SoftBodyMotionProperties *>(body.GetMotionProperties());
2529
mp->InitializeUpdateContext(ioContext->mStepDeltaTime, body, *this, *sb_ctx);
2530
}
2531
}
2532
2533
// We're ready to collide the first soft body
2534
ioContext->mSoftBodyToCollide.store(0, memory_order_release);
2535
2536
// Determine number of jobs to spawn
2537
int num_soft_body_jobs = ioContext->GetMaxConcurrency();
2538
2539
// Create finalize job
2540
ioStep->mSoftBodyFinalize = ioContext->mJobSystem->CreateJob("SoftBodyFinalize", cColorSoftBodyFinalize, [ioContext, ioStep]()
2541
{
2542
ioContext->mPhysicsSystem->JobSoftBodyFinalize(ioContext);
2543
2544
// Kick the next step
2545
if (ioStep->mStartNextStep.IsValid())
2546
ioStep->mStartNextStep.RemoveDependency();
2547
}, num_soft_body_jobs); // depends on: soft body simulate
2548
ioContext->mBarrier->AddJob(ioStep->mSoftBodyFinalize);
2549
2550
// Create simulate jobs
2551
ioStep->mSoftBodySimulate.resize(num_soft_body_jobs);
2552
for (int i = 0; i < num_soft_body_jobs; ++i)
2553
ioStep->mSoftBodySimulate[i] = ioContext->mJobSystem->CreateJob("SoftBodySimulate", cColorSoftBodySimulate, [ioStep, i]()
2554
{
2555
ioStep->mContext->mPhysicsSystem->JobSoftBodySimulate(ioStep->mContext, i);
2556
2557
ioStep->mSoftBodyFinalize.RemoveDependency();
2558
}, num_soft_body_jobs); // depends on: soft body collide
2559
ioContext->mBarrier->AddJobs(ioStep->mSoftBodySimulate.data(), ioStep->mSoftBodySimulate.size());
2560
2561
// Create collision jobs
2562
ioStep->mSoftBodyCollide.resize(num_soft_body_jobs);
2563
for (int i = 0; i < num_soft_body_jobs; ++i)
2564
ioStep->mSoftBodyCollide[i] = ioContext->mJobSystem->CreateJob("SoftBodyCollide", cColorSoftBodyCollide, [ioContext, ioStep]()
2565
{
2566
ioContext->mPhysicsSystem->JobSoftBodyCollide(ioContext);
2567
2568
for (const JobHandle &h : ioStep->mSoftBodySimulate)
2569
h.RemoveDependency();
2570
}); // depends on: nothing
2571
ioContext->mBarrier->AddJobs(ioStep->mSoftBodyCollide.data(), ioStep->mSoftBodyCollide.size());
2572
}
2573
2574
void PhysicsSystem::JobSoftBodyCollide(PhysicsUpdateContext *ioContext) const
2575
{
2576
#ifdef JPH_ENABLE_ASSERTS
2577
// Reading rigid body positions and velocities
2578
BodyAccess::Grant grant(BodyAccess::EAccess::Read, BodyAccess::EAccess::Read);
2579
#endif
2580
2581
for (;;)
2582
{
2583
// Fetch the next soft body
2584
uint sb_idx = ioContext->mSoftBodyToCollide.fetch_add(1, std::memory_order_acquire);
2585
if (sb_idx >= ioContext->mNumSoftBodies)
2586
break;
2587
2588
// Do a broadphase check
2589
SoftBodyUpdateContext &sb_ctx = ioContext->mSoftBodyUpdateContexts[sb_idx];
2590
sb_ctx.mMotionProperties->DetermineCollidingShapes(sb_ctx, *this, GetBodyLockInterfaceNoLock());
2591
}
2592
}
2593
2594
void PhysicsSystem::JobSoftBodySimulate(PhysicsUpdateContext *ioContext, uint inThreadIndex) const
2595
{
2596
#ifdef JPH_ENABLE_ASSERTS
2597
// Updating velocities of soft bodies, allow the contact listener to read the soft body state
2598
BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::Read);
2599
#endif
2600
2601
// Calculate at which body we start to distribute the workload across the threads
2602
uint num_soft_bodies = ioContext->mNumSoftBodies;
2603
uint start_idx = inThreadIndex * num_soft_bodies / ioContext->GetMaxConcurrency();
2604
2605
// Keep running partial updates until everything has been updated
2606
uint status;
2607
do
2608
{
2609
// Reset status
2610
status = 0;
2611
2612
// Update all soft bodies
2613
for (uint i = 0; i < num_soft_bodies; ++i)
2614
{
2615
// Fetch the soft body context
2616
SoftBodyUpdateContext &sb_ctx = ioContext->mSoftBodyUpdateContexts[(start_idx + i) % num_soft_bodies];
2617
2618
// To avoid trashing the cache too much, we prefer to stick to one soft body until we cannot progress it any further
2619
uint sb_status;
2620
do
2621
{
2622
sb_status = (uint)sb_ctx.mMotionProperties->ParallelUpdate(sb_ctx, mPhysicsSettings);
2623
status |= sb_status;
2624
} while (sb_status == (uint)SoftBodyMotionProperties::EStatus::DidWork);
2625
}
2626
2627
// If we didn't perform any work, yield the thread so that something else can run
2628
if (!(status & (uint)SoftBodyMotionProperties::EStatus::DidWork))
2629
std::this_thread::yield();
2630
}
2631
while (status != (uint)SoftBodyMotionProperties::EStatus::Done);
2632
}
2633
2634
void PhysicsSystem::JobSoftBodyFinalize(PhysicsUpdateContext *ioContext)
2635
{
2636
#ifdef JPH_ENABLE_ASSERTS
2637
// Updating rigid body velocities and soft body positions / velocities
2638
BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::ReadWrite);
2639
2640
// Can activate and deactivate bodies
2641
BodyManager::GrantActiveBodiesAccess grant_active(true, true);
2642
#endif
2643
2644
static constexpr int cBodiesBatch = 64;
2645
BodyID *bodies_to_update_bounds = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));
2646
int num_bodies_to_update_bounds = 0;
2647
BodyID *bodies_to_put_to_sleep = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));
2648
int num_bodies_to_put_to_sleep = 0;
2649
2650
for (SoftBodyUpdateContext *sb_ctx = ioContext->mSoftBodyUpdateContexts, *sb_ctx_end = ioContext->mSoftBodyUpdateContexts + ioContext->mNumSoftBodies; sb_ctx < sb_ctx_end; ++sb_ctx)
2651
{
2652
// Apply the rigid body velocity deltas
2653
sb_ctx->mMotionProperties->UpdateRigidBodyVelocities(*sb_ctx, GetBodyInterfaceNoLock());
2654
2655
// Update the position
2656
sb_ctx->mBody->SetPositionAndRotationInternal(sb_ctx->mBody->GetPosition() + sb_ctx->mDeltaPosition, sb_ctx->mBody->GetRotation(), false);
2657
2658
BodyID id = sb_ctx->mBody->GetID();
2659
bodies_to_update_bounds[num_bodies_to_update_bounds++] = id;
2660
if (num_bodies_to_update_bounds == cBodiesBatch)
2661
{
2662
// Buffer full, flush now
2663
mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
2664
num_bodies_to_update_bounds = 0;
2665
}
2666
2667
if (sb_ctx->mCanSleep == ECanSleep::CanSleep)
2668
{
2669
// This body should go to sleep
2670
bodies_to_put_to_sleep[num_bodies_to_put_to_sleep++] = id;
2671
if (num_bodies_to_put_to_sleep == cBodiesBatch)
2672
{
2673
mBodyManager.DeactivateBodies(bodies_to_put_to_sleep, num_bodies_to_put_to_sleep);
2674
num_bodies_to_put_to_sleep = 0;
2675
}
2676
}
2677
}
2678
2679
// Notify change bounds on requested bodies
2680
if (num_bodies_to_update_bounds > 0)
2681
mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
2682
2683
// Notify bodies to go to sleep
2684
if (num_bodies_to_put_to_sleep > 0)
2685
mBodyManager.DeactivateBodies(bodies_to_put_to_sleep, num_bodies_to_put_to_sleep);
2686
2687
// Free soft body contexts
2688
ioContext->mTempAllocator->Free(ioContext->mSoftBodyUpdateContexts, ioContext->mNumSoftBodies * sizeof(SoftBodyUpdateContext));
2689
}
2690
2691
void PhysicsSystem::SaveState(StateRecorder &inStream, EStateRecorderState inState, const StateRecorderFilter *inFilter) const
2692
{
2693
JPH_PROFILE_FUNCTION();
2694
2695
inStream.Write(inState);
2696
2697
if (uint8(inState) & uint8(EStateRecorderState::Global))
2698
{
2699
inStream.Write(mPreviousStepDeltaTime);
2700
inStream.Write(mGravity);
2701
}
2702
2703
if (uint8(inState) & uint8(EStateRecorderState::Bodies))
2704
mBodyManager.SaveState(inStream, inFilter);
2705
2706
if (uint8(inState) & uint8(EStateRecorderState::Contacts))
2707
mContactManager.SaveState(inStream, inFilter);
2708
2709
if (uint8(inState) & uint8(EStateRecorderState::Constraints))
2710
mConstraintManager.SaveState(inStream, inFilter);
2711
}
2712
2713
bool PhysicsSystem::RestoreState(StateRecorder &inStream, const StateRecorderFilter *inFilter)
2714
{
2715
JPH_PROFILE_FUNCTION();
2716
2717
EStateRecorderState state = EStateRecorderState::All; // Set this value for validation. If a partial state is saved, validation will not work anyway.
2718
inStream.Read(state);
2719
2720
if (uint8(state) & uint8(EStateRecorderState::Global))
2721
{
2722
inStream.Read(mPreviousStepDeltaTime);
2723
inStream.Read(mGravity);
2724
}
2725
2726
if (uint8(state) & uint8(EStateRecorderState::Bodies))
2727
{
2728
if (!mBodyManager.RestoreState(inStream))
2729
return false;
2730
2731
// Update bounding boxes for all bodies in the broadphase
2732
if (inStream.IsLastPart())
2733
{
2734
Array<BodyID> bodies;
2735
for (const Body *b : mBodyManager.GetBodies())
2736
if (BodyManager::sIsValidBodyPointer(b) && b->IsInBroadPhase())
2737
bodies.push_back(b->GetID());
2738
if (!bodies.empty())
2739
mBroadPhase->NotifyBodiesAABBChanged(&bodies[0], (int)bodies.size());
2740
}
2741
}
2742
2743
if (uint8(state) & uint8(EStateRecorderState::Contacts))
2744
{
2745
if (!mContactManager.RestoreState(inStream, inFilter))
2746
return false;
2747
}
2748
2749
if (uint8(state) & uint8(EStateRecorderState::Constraints))
2750
{
2751
if (!mConstraintManager.RestoreState(inStream))
2752
return false;
2753
}
2754
2755
return true;
2756
}
2757
2758
void PhysicsSystem::SaveBodyState(const Body &inBody, StateRecorder &inStream) const
2759
{
2760
mBodyManager.SaveBodyState(inBody, inStream);
2761
}
2762
2763
void PhysicsSystem::RestoreBodyState(Body &ioBody, StateRecorder &inStream)
2764
{
2765
mBodyManager.RestoreBodyState(ioBody, inStream);
2766
2767
BodyID id = ioBody.GetID();
2768
mBroadPhase->NotifyBodiesAABBChanged(&id, 1);
2769
}
2770
2771
JPH_NAMESPACE_END
2772
2773