Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/PhysicsSystem.cpp
20852 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/PhysicsSystem.h>7#include <Jolt/Physics/PhysicsSettings.h>8#include <Jolt/Physics/PhysicsUpdateContext.h>9#include <Jolt/Physics/PhysicsStepListener.h>10#include <Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.h>11#include <Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.h>12#include <Jolt/Physics/Collision/CollisionDispatch.h>13#include <Jolt/Physics/Collision/AABoxCast.h>14#include <Jolt/Physics/Collision/ShapeCast.h>15#include <Jolt/Physics/Collision/CollideShape.h>16#include <Jolt/Physics/Collision/CollisionCollectorImpl.h>17#include <Jolt/Physics/Collision/CastResult.h>18#include <Jolt/Physics/Collision/CollideConvexVsTriangles.h>19#include <Jolt/Physics/Collision/ManifoldBetweenTwoFaces.h>20#include <Jolt/Physics/Collision/Shape/ConvexShape.h>21#include <Jolt/Physics/Collision/SimShapeFilterWrapper.h>22#include <Jolt/Physics/Collision/InternalEdgeRemovingCollector.h>23#include <Jolt/Physics/Constraints/CalculateSolverSteps.h>24#include <Jolt/Physics/Constraints/ConstraintPart/AxisConstraintPart.h>25#include <Jolt/Physics/DeterminismLog.h>26#include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>27#include <Jolt/Physics/SoftBody/SoftBodyShape.h>28#include <Jolt/Geometry/RayAABox.h>29#include <Jolt/Geometry/ClosestPoint.h>30#include <Jolt/Core/JobSystem.h>31#include <Jolt/Core/TempAllocator.h>32#include <Jolt/Core/QuickSort.h>33#include <Jolt/Core/ScopeExit.h>34#ifdef JPH_DEBUG_RENDERER35#include <Jolt/Renderer/DebugRenderer.h>36#endif // JPH_DEBUG_RENDERER3738JPH_NAMESPACE_BEGIN3940#ifdef JPH_DEBUG_RENDERER41bool PhysicsSystem::sDrawMotionQualityLinearCast = false;42#endif // JPH_DEBUG_RENDERER4344//#define BROAD_PHASE BroadPhaseBruteForce45#define BROAD_PHASE BroadPhaseQuadTree4647static const Color cColorUpdateBroadPhaseFinalize = Color::sGetDistinctColor(1);48static const Color cColorUpdateBroadPhasePrepare = Color::sGetDistinctColor(2);49static const Color cColorFindCollisions = Color::sGetDistinctColor(3);50static const Color cColorApplyGravity = Color::sGetDistinctColor(4);51static const Color cColorSetupVelocityConstraints = Color::sGetDistinctColor(5);52static const Color cColorBuildIslandsFromConstraints = Color::sGetDistinctColor(6);53static const Color cColorDetermineActiveConstraints = Color::sGetDistinctColor(7);54static const Color cColorFinalizeIslands = Color::sGetDistinctColor(8);55static const Color cColorContactRemovedCallbacks = Color::sGetDistinctColor(9);56static const Color cColorBodySetIslandIndex = Color::sGetDistinctColor(10);57static const Color cColorStartNextStep = Color::sGetDistinctColor(11);58static const Color cColorSolveVelocityConstraints = Color::sGetDistinctColor(12);59static const Color cColorPreIntegrateVelocity = Color::sGetDistinctColor(13);60static const Color cColorIntegrateVelocity = Color::sGetDistinctColor(14);61static const Color cColorPostIntegrateVelocity = Color::sGetDistinctColor(15);62static const Color cColorResolveCCDContacts = Color::sGetDistinctColor(16);63static const Color cColorSolvePositionConstraints = Color::sGetDistinctColor(17);64static const Color cColorFindCCDContacts = Color::sGetDistinctColor(18);65static const Color cColorStepListeners = Color::sGetDistinctColor(19);66static const Color cColorSoftBodyPrepare = Color::sGetDistinctColor(20);67static const Color cColorSoftBodyCollide = Color::sGetDistinctColor(21);68static const Color cColorSoftBodySimulate = Color::sGetDistinctColor(22);69static const Color cColorSoftBodyFinalize = Color::sGetDistinctColor(23);7071PhysicsSystem::~PhysicsSystem()72{73// Remove broadphase74delete mBroadPhase;75}7677void PhysicsSystem::Init(uint inMaxBodies, uint inNumBodyMutexes, uint inMaxBodyPairs, uint inMaxContactConstraints, const BroadPhaseLayerInterface &inBroadPhaseLayerInterface, const ObjectVsBroadPhaseLayerFilter &inObjectVsBroadPhaseLayerFilter, const ObjectLayerPairFilter &inObjectLayerPairFilter)78{79// Clamp max bodies80uint max_bodies = min(inMaxBodies, cMaxBodiesLimit);81JPH_ASSERT(max_bodies == inMaxBodies, "Cannot support this many bodies!");8283mObjectVsBroadPhaseLayerFilter = &inObjectVsBroadPhaseLayerFilter;84mObjectLayerPairFilter = &inObjectLayerPairFilter;8586// Initialize body manager87mBodyManager.Init(max_bodies, inNumBodyMutexes, inBroadPhaseLayerInterface);8889// Create broadphase90mBroadPhase = new BROAD_PHASE();91mBroadPhase->Init(&mBodyManager, inBroadPhaseLayerInterface);9293// Init contact constraint manager94mContactManager.Init(inMaxBodyPairs, inMaxContactConstraints);9596// Init islands builder97mIslandBuilder.Init(max_bodies);9899// Initialize body interface100mBodyInterfaceLocking.Init(mBodyLockInterfaceLocking, mBodyManager, *mBroadPhase);101mBodyInterfaceNoLock.Init(mBodyLockInterfaceNoLock, mBodyManager, *mBroadPhase);102103// Initialize narrow phase query104mNarrowPhaseQueryLocking.Init(mBodyLockInterfaceLocking, *mBroadPhase);105mNarrowPhaseQueryNoLock.Init(mBodyLockInterfaceNoLock, *mBroadPhase);106}107108void PhysicsSystem::OptimizeBroadPhase()109{110mBroadPhase->Optimize();111}112113void PhysicsSystem::AddStepListener(PhysicsStepListener *inListener)114{115lock_guard lock(mStepListenersMutex);116117JPH_ASSERT(std::find(mStepListeners.begin(), mStepListeners.end(), inListener) == mStepListeners.end());118mStepListeners.push_back(inListener);119}120121void PhysicsSystem::RemoveStepListener(PhysicsStepListener *inListener)122{123lock_guard lock(mStepListenersMutex);124125StepListeners::iterator i = std::find(mStepListeners.begin(), mStepListeners.end(), inListener);126JPH_ASSERT(i != mStepListeners.end());127*i = mStepListeners.back();128mStepListeners.pop_back();129}130131EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionSteps, TempAllocator *inTempAllocator, JobSystem *inJobSystem)132{133JPH_PROFILE_FUNCTION();134135JPH_DET_LOG("PhysicsSystem::Update: dt: " << inDeltaTime << " steps: " << inCollisionSteps);136137JPH_ASSERT(inCollisionSteps > 0);138JPH_ASSERT(inDeltaTime >= 0.0f);139140// Sync point for the broadphase. This will allow it to do clean up operations without having any mutexes locked yet.141mBroadPhase->FrameSync();142143// If there are no active bodies (and no step listener to wake them up) or there's no time delta144uint32 num_active_rigid_bodies = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);145uint32 num_active_soft_bodies = mBodyManager.GetNumActiveBodies(EBodyType::SoftBody);146if ((num_active_rigid_bodies == 0 && num_active_soft_bodies == 0 && mStepListeners.empty()) || inDeltaTime <= 0.0f)147{148mBodyManager.LockAllBodies();149150// Update broadphase151mBroadPhase->LockModifications();152BroadPhase::UpdateState update_state = mBroadPhase->UpdatePrepare();153mBroadPhase->UpdateFinalize(update_state);154mBroadPhase->UnlockModifications();155156// If time has passed, call contact removal callbacks from contacts that existed in the previous update157if (inDeltaTime > 0.0f)158mContactManager.FinalizeContactCacheAndCallContactPointRemovedCallbacks(0, 0);159160mBodyManager.UnlockAllBodies();161return EPhysicsUpdateError::None;162}163164// Calculate ratio between current and previous frame delta time to scale initial constraint forces165float step_delta_time = inDeltaTime / inCollisionSteps;166float warm_start_impulse_ratio = mPhysicsSettings.mConstraintWarmStart && mPreviousStepDeltaTime > 0.0f? step_delta_time / mPreviousStepDeltaTime : 0.0f;167mPreviousStepDeltaTime = step_delta_time;168169// Create the context used for passing information between jobs170PhysicsUpdateContext context(*inTempAllocator);171context.mPhysicsSystem = this;172context.mJobSystem = inJobSystem;173context.mBarrier = inJobSystem->CreateBarrier();174context.mIslandBuilder = &mIslandBuilder;175context.mStepDeltaTime = step_delta_time;176context.mWarmStartImpulseRatio = warm_start_impulse_ratio;177context.mSteps.resize(inCollisionSteps);178179// Allocate space for body pairs180JPH_ASSERT(context.mBodyPairs == nullptr);181context.mBodyPairs = static_cast<BodyPair *>(inTempAllocator->Allocate(sizeof(BodyPair) * mPhysicsSettings.mMaxInFlightBodyPairs));182183// Lock all bodies for write so that we can freely touch them184mStepListenersMutex.lock();185mBodyManager.LockAllBodies();186mBroadPhase->LockModifications();187188// Get max number of concurrent jobs189int max_concurrency = context.GetMaxConcurrency();190191// Calculate how many step listener jobs we spawn192int num_step_listener_jobs = mStepListeners.empty()? 0 : max(1, min((int)mStepListeners.size() / mPhysicsSettings.mStepListenersBatchSize / mPhysicsSettings.mStepListenerBatchesPerJob, max_concurrency));193194// Number of gravity jobs depends on the amount of active bodies.195// Launch max 1 job per batch of active bodies196// Leave 1 thread for update broadphase prepare and 1 for determine active constraints197int num_apply_gravity_jobs = max(1, min(((int)num_active_rigid_bodies + cApplyGravityBatchSize - 1) / cApplyGravityBatchSize, max_concurrency - 2));198199// Number of determine active constraints jobs to run depends on number of constraints.200// Leave 1 thread for update broadphase prepare and 1 for apply gravity201int num_determine_active_constraints_jobs = max(1, min(((int)mConstraintManager.GetNumConstraints() + cDetermineActiveConstraintsBatchSize - 1) / cDetermineActiveConstraintsBatchSize, max_concurrency - 2));202203// Number of setup velocity constraints jobs to run depends on number of constraints.204int num_setup_velocity_constraints_jobs = max(1, min(((int)mConstraintManager.GetNumConstraints() + cSetupVelocityConstraintsBatchSize - 1) / cSetupVelocityConstraintsBatchSize, max_concurrency));205206// Number of find collisions jobs to run depends on number of active bodies.207// 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 constraints208// (which may activate additional bodies that need to be processed) while the second job can start processing collision work.209int num_find_collisions_jobs = max(max_concurrency == 1? 1 : 2, min(((int)num_active_rigid_bodies + cActiveBodiesBatchSize - 1) / cActiveBodiesBatchSize, max_concurrency));210211// Number of integrate velocity jobs depends on number of active bodies.212int num_integrate_velocity_jobs = max(1, min(((int)num_active_rigid_bodies + cIntegrateVelocityBatchSize - 1) / cIntegrateVelocityBatchSize, max_concurrency));213214{215JPH_PROFILE("Build Jobs");216217// Iterate over collision steps218for (int step_idx = 0; step_idx < inCollisionSteps; ++step_idx)219{220bool is_first_step = step_idx == 0;221bool is_last_step = step_idx == inCollisionSteps - 1;222223PhysicsUpdateContext::Step &step = context.mSteps[step_idx];224step.mContext = &context;225step.mIsFirst = is_first_step;226step.mIsLast = is_last_step;227228// Create job to do broadphase finalization229// This job must finish before integrating velocities. Until then the positions will not be updated neither will bodies be added / removed.230step.mUpdateBroadphaseFinalize = inJobSystem->CreateJob("UpdateBroadPhaseFinalize", cColorUpdateBroadPhaseFinalize, [&context, &step]()231{232// Validate that all find collision jobs have stopped233JPH_ASSERT(step.mActiveFindCollisionJobs.load(memory_order_relaxed) == 0);234235// Finalize the broadphase update236context.mPhysicsSystem->mBroadPhase->UpdateFinalize(step.mBroadPhaseUpdateState);237238// Signal that it is done239step.mPreIntegrateVelocity.RemoveDependency();240}, num_find_collisions_jobs + 2); // depends on: find collisions, broadphase prepare update, finish building jobs241242// The immediate jobs below are only immediate for the first step, the all finished job will kick them for the next step243int previous_step_dependency_count = is_first_step? 0 : 1;244245// Start job immediately: Start the prepare broadphase246// Must be done under body lock protection since the order is body locks then broadphase mutex247// If this is turned around the RemoveBody call will hang since it locks in that order248step.mBroadPhasePrepare = inJobSystem->CreateJob("UpdateBroadPhasePrepare", cColorUpdateBroadPhasePrepare, [&context, &step]()249{250// Prepare the broadphase update251step.mBroadPhaseUpdateState = context.mPhysicsSystem->mBroadPhase->UpdatePrepare();252253// Now the finalize can run (if other dependencies are met too)254step.mUpdateBroadphaseFinalize.RemoveDependency();255}, previous_step_dependency_count);256257// This job will find all collisions258step.mBodyPairQueues.resize(max_concurrency);259step.mMaxBodyPairsPerQueue = mPhysicsSettings.mMaxInFlightBodyPairs / max_concurrency;260step.mActiveFindCollisionJobs.store(~PhysicsUpdateContext::JobMask(0) >> (sizeof(PhysicsUpdateContext::JobMask) * 8 - num_find_collisions_jobs), memory_order_release);261step.mFindCollisions.resize(num_find_collisions_jobs);262for (int i = 0; i < num_find_collisions_jobs; ++i)263{264// 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 bodies265int num_dep_build_islands_from_constraints = i == 0? 1 : 0;266step.mFindCollisions[i] = inJobSystem->CreateJob("FindCollisions", cColorFindCollisions, [&step, i]()267{268step.mContext->mPhysicsSystem->JobFindCollisions(&step, i);269}, 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 constraints270}271272if (is_first_step)273{274#ifdef JPH_ENABLE_ASSERTS275// Don't allow write operations to the active bodies list276mBodyManager.SetActiveBodiesLocked(true);277#endif278279// Store the number of active bodies at the start of the step280step.mNumActiveBodiesAtStepStart = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);281282// Lock all constraints283mConstraintManager.LockAllConstraints();284285// Allocate memory for storing the active constraints286JPH_ASSERT(context.mActiveConstraints == nullptr);287context.mActiveConstraints = static_cast<Constraint **>(inTempAllocator->Allocate(mConstraintManager.GetNumConstraints() * sizeof(Constraint *)));288289// Prepare contact buffer290mContactManager.PrepareConstraintBuffer(&context);291292// Setup island builder293mIslandBuilder.PrepareContactConstraints(mContactManager.GetMaxConstraints(), context.mTempAllocator);294}295296// This job applies gravity to all active bodies297step.mApplyGravity.resize(num_apply_gravity_jobs);298for (int i = 0; i < num_apply_gravity_jobs; ++i)299step.mApplyGravity[i] = inJobSystem->CreateJob("ApplyGravity", cColorApplyGravity, [&context, &step]()300{301context.mPhysicsSystem->JobApplyGravity(&context, &step);302303JobHandle::sRemoveDependencies(step.mFindCollisions);304}, num_step_listener_jobs > 0? num_step_listener_jobs : previous_step_dependency_count); // depends on: step listeners (or previous step if no step listeners)305306// This job will setup velocity constraints for non-collision constraints307step.mSetupVelocityConstraints.resize(num_setup_velocity_constraints_jobs);308for (int i = 0; i < num_setup_velocity_constraints_jobs; ++i)309step.mSetupVelocityConstraints[i] = inJobSystem->CreateJob("SetupVelocityConstraints", cColorSetupVelocityConstraints, [&context, &step]()310{311context.mPhysicsSystem->JobSetupVelocityConstraints(context.mStepDeltaTime, &step);312313JobHandle::sRemoveDependencies(step.mSolveVelocityConstraints);314}, num_determine_active_constraints_jobs + 1); // depends on: determine active constraints, finish building jobs315316// This job will build islands from constraints317step.mBuildIslandsFromConstraints = inJobSystem->CreateJob("BuildIslandsFromConstraints", cColorBuildIslandsFromConstraints, [&context, &step]()318{319context.mPhysicsSystem->JobBuildIslandsFromConstraints(&context, &step);320321step.mFindCollisions[0].RemoveDependency(); // The first collisions job cannot start running until we've finished building islands and activated all bodies322step.mFinalizeIslands.RemoveDependency();323}, num_determine_active_constraints_jobs + 1); // depends on: determine active constraints, finish building jobs324325// This job determines active constraints326step.mDetermineActiveConstraints.resize(num_determine_active_constraints_jobs);327for (int i = 0; i < num_determine_active_constraints_jobs; ++i)328step.mDetermineActiveConstraints[i] = inJobSystem->CreateJob("DetermineActiveConstraints", cColorDetermineActiveConstraints, [&context, &step]()329{330context.mPhysicsSystem->JobDetermineActiveConstraints(&step);331332step.mBuildIslandsFromConstraints.RemoveDependency();333334// 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 first335JobHandle::sRemoveDependencies(step.mSetupVelocityConstraints);336JobHandle::sRemoveDependencies(step.mFindCollisions);337}, num_step_listener_jobs > 0? num_step_listener_jobs : previous_step_dependency_count); // depends on: step listeners (or previous step if no step listeners)338339// This job calls the step listeners340step.mStepListeners.resize(num_step_listener_jobs);341for (int i = 0; i < num_step_listener_jobs; ++i)342step.mStepListeners[i] = inJobSystem->CreateJob("StepListeners", cColorStepListeners, [&context, &step]()343{344// Call the step listeners345context.mPhysicsSystem->JobStepListeners(&step);346347// Kick apply gravity and determine active constraint jobs348JobHandle::sRemoveDependencies(step.mApplyGravity);349JobHandle::sRemoveDependencies(step.mDetermineActiveConstraints);350}, previous_step_dependency_count);351352// Unblock the previous step353if (!is_first_step)354context.mSteps[step_idx - 1].mStartNextStep.RemoveDependency();355356// This job will finalize the simulation islands357step.mFinalizeIslands = inJobSystem->CreateJob("FinalizeIslands", cColorFinalizeIslands, [&context, &step]()358{359// Validate that all find collision jobs have stopped360JPH_ASSERT(step.mActiveFindCollisionJobs.load(memory_order_relaxed) == 0);361362context.mPhysicsSystem->JobFinalizeIslands(&context);363364JobHandle::sRemoveDependencies(step.mSolveVelocityConstraints);365step.mBodySetIslandIndex.RemoveDependency();366}, num_find_collisions_jobs + 2); // depends on: find collisions, build islands from constraints, finish building jobs367368// Unblock previous job369// 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 left370step.mBuildIslandsFromConstraints.RemoveDependency();371372// This job will call the contact removed callbacks373step.mContactRemovedCallbacks = inJobSystem->CreateJob("ContactRemovedCallbacks", cColorContactRemovedCallbacks, [&context, &step]()374{375context.mPhysicsSystem->JobContactRemovedCallbacks(&step);376377if (step.mStartNextStep.IsValid())378step.mStartNextStep.RemoveDependency();379}, 1); // depends on the find ccd contacts380381// This job will set the island index on each body (only used for debug drawing purposes)382// It will also delete any bodies that have been destroyed in the last frame383step.mBodySetIslandIndex = inJobSystem->CreateJob("BodySetIslandIndex", cColorBodySetIslandIndex, [&context, &step]()384{385context.mPhysicsSystem->JobBodySetIslandIndex();386387JobHandle::sRemoveDependencies(step.mSolvePositionConstraints);388}, 2); // depends on: finalize islands, finish building jobs389390// Job to start the next collision step391if (!is_last_step)392{393PhysicsUpdateContext::Step *next_step = &context.mSteps[step_idx + 1];394step.mStartNextStep = inJobSystem->CreateJob("StartNextStep", cColorStartNextStep, [this, next_step]()395{396#ifdef JPH_DEBUG397// Validate that the cached bounds are correct398mBodyManager.ValidateActiveBodyBounds();399#endif // JPH_DEBUG400401// Store the number of active bodies at the start of the step402next_step->mNumActiveBodiesAtStepStart = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);403404// Clear the large island splitter405TempAllocator *temp_allocator = next_step->mContext->mTempAllocator;406mLargeIslandSplitter.Reset(temp_allocator);407408// Clear the island builder409mIslandBuilder.ResetIslands(temp_allocator);410411// Setup island builder412mIslandBuilder.PrepareContactConstraints(mContactManager.GetMaxConstraints(), temp_allocator);413414// Restart the contact manager415mContactManager.RecycleConstraintBuffer();416417// Kick the jobs of the next step (in the same order as the first step)418next_step->mBroadPhasePrepare.RemoveDependency();419if (next_step->mStepListeners.empty())420{421// Kick the gravity and active constraints jobs immediately422JobHandle::sRemoveDependencies(next_step->mApplyGravity);423JobHandle::sRemoveDependencies(next_step->mDetermineActiveConstraints);424}425else426{427// Kick the step listeners job first428JobHandle::sRemoveDependencies(next_step->mStepListeners);429}430}, 3); // depends on: update soft bodies, contact removed callbacks, finish building the previous step431}432433// This job will solve the velocity constraints434step.mSolveVelocityConstraints.resize(max_concurrency);435for (int i = 0; i < max_concurrency; ++i)436step.mSolveVelocityConstraints[i] = inJobSystem->CreateJob("SolveVelocityConstraints", cColorSolveVelocityConstraints, [&context, &step]()437{438context.mPhysicsSystem->JobSolveVelocityConstraints(&context, &step);439440step.mPreIntegrateVelocity.RemoveDependency();441}, num_setup_velocity_constraints_jobs + 2); // depends on: finalize islands, setup velocity constraints, finish building jobs.442443// We prefer setup velocity constraints to finish first so we kick it first444JobHandle::sRemoveDependencies(step.mSetupVelocityConstraints);445JobHandle::sRemoveDependencies(step.mFindCollisions);446447// Finalize islands is a dependency on find collisions so it can go last448step.mFinalizeIslands.RemoveDependency();449450// This job will prepare the position update of all active bodies451step.mPreIntegrateVelocity = inJobSystem->CreateJob("PreIntegrateVelocity", cColorPreIntegrateVelocity, [&context, &step]()452{453context.mPhysicsSystem->JobPreIntegrateVelocity(&context, &step);454455JobHandle::sRemoveDependencies(step.mIntegrateVelocity);456}, 2 + max_concurrency); // depends on: broadphase update finalize, solve velocity constraints, finish building jobs.457458// Unblock previous jobs459step.mUpdateBroadphaseFinalize.RemoveDependency();460JobHandle::sRemoveDependencies(step.mSolveVelocityConstraints);461462// This job will update the positions of all active bodies463step.mIntegrateVelocity.resize(num_integrate_velocity_jobs);464for (int i = 0; i < num_integrate_velocity_jobs; ++i)465step.mIntegrateVelocity[i] = inJobSystem->CreateJob("IntegrateVelocity", cColorIntegrateVelocity, [&context, &step]()466{467context.mPhysicsSystem->JobIntegrateVelocity(&context, &step);468469step.mPostIntegrateVelocity.RemoveDependency();470}, 2); // depends on: pre integrate velocity, finish building jobs.471472// Unblock previous job473step.mPreIntegrateVelocity.RemoveDependency();474475// This job will finish the position update of all active bodies476step.mPostIntegrateVelocity = inJobSystem->CreateJob("PostIntegrateVelocity", cColorPostIntegrateVelocity, [&context, &step]()477{478context.mPhysicsSystem->JobPostIntegrateVelocity(&context, &step);479480step.mResolveCCDContacts.RemoveDependency();481}, num_integrate_velocity_jobs + 1); // depends on: integrate velocity, finish building jobs482483// Unblock previous jobs484JobHandle::sRemoveDependencies(step.mIntegrateVelocity);485486// This job will update the positions and velocities for all bodies that need continuous collision detection487step.mResolveCCDContacts = inJobSystem->CreateJob("ResolveCCDContacts", cColorResolveCCDContacts, [&context, &step]()488{489context.mPhysicsSystem->JobResolveCCDContacts(&context, &step);490491JobHandle::sRemoveDependencies(step.mSolvePositionConstraints);492}, 2); // depends on: integrate velocities, detect ccd contacts (added dynamically), finish building jobs.493494// Unblock previous job495step.mPostIntegrateVelocity.RemoveDependency();496497// Fixes up drift in positions and updates the broadphase with new body positions498step.mSolvePositionConstraints.resize(max_concurrency);499for (int i = 0; i < max_concurrency; ++i)500step.mSolvePositionConstraints[i] = inJobSystem->CreateJob("SolvePositionConstraints", cColorSolvePositionConstraints, [&context, &step]()501{502context.mPhysicsSystem->JobSolvePositionConstraints(&context, &step);503504// Kick the next step505if (step.mSoftBodyPrepare.IsValid())506step.mSoftBodyPrepare.RemoveDependency();507}, 3); // depends on: resolve ccd contacts, body set island index, finish building jobs.508509// Unblock previous jobs.510step.mResolveCCDContacts.RemoveDependency();511step.mBodySetIslandIndex.RemoveDependency();512513// The soft body prepare job will create other jobs if needed514step.mSoftBodyPrepare = inJobSystem->CreateJob("SoftBodyPrepare", cColorSoftBodyPrepare, [&context, &step]()515{516context.mPhysicsSystem->JobSoftBodyPrepare(&context, &step);517}, max_concurrency); // depends on: solve position constraints.518519// Unblock previous jobs520JobHandle::sRemoveDependencies(step.mSolvePositionConstraints);521}522}523524// Build the list of jobs to wait for525JobSystem::Barrier *barrier = context.mBarrier;526{527JPH_PROFILE("Build job barrier");528529StaticArray<JobHandle, cMaxPhysicsJobs> handles;530for (const PhysicsUpdateContext::Step &step : context.mSteps)531{532if (step.mBroadPhasePrepare.IsValid())533handles.push_back(step.mBroadPhasePrepare);534for (const JobHandle &h : step.mStepListeners)535handles.push_back(h);536for (const JobHandle &h : step.mDetermineActiveConstraints)537handles.push_back(h);538for (const JobHandle &h : step.mApplyGravity)539handles.push_back(h);540for (const JobHandle &h : step.mFindCollisions)541handles.push_back(h);542if (step.mUpdateBroadphaseFinalize.IsValid())543handles.push_back(step.mUpdateBroadphaseFinalize);544for (const JobHandle &h : step.mSetupVelocityConstraints)545handles.push_back(h);546handles.push_back(step.mBuildIslandsFromConstraints);547handles.push_back(step.mFinalizeIslands);548handles.push_back(step.mBodySetIslandIndex);549for (const JobHandle &h : step.mSolveVelocityConstraints)550handles.push_back(h);551handles.push_back(step.mPreIntegrateVelocity);552for (const JobHandle &h : step.mIntegrateVelocity)553handles.push_back(h);554handles.push_back(step.mPostIntegrateVelocity);555handles.push_back(step.mResolveCCDContacts);556for (const JobHandle &h : step.mSolvePositionConstraints)557handles.push_back(h);558handles.push_back(step.mContactRemovedCallbacks);559if (step.mSoftBodyPrepare.IsValid())560handles.push_back(step.mSoftBodyPrepare);561if (step.mStartNextStep.IsValid())562handles.push_back(step.mStartNextStep);563}564barrier->AddJobs(handles.data(), handles.size());565}566567// Wait until all jobs finish568// Note we don't just wait for the last job. If we would and another job569// would be scheduled in between there is the possibility of a deadlock.570// The other job could try to e.g. add/remove a body which would try to571// lock a body mutex while this thread has already locked the mutex572inJobSystem->WaitForJobs(barrier);573574// We're done with the barrier for this update575inJobSystem->DestroyBarrier(barrier);576577#ifdef JPH_DEBUG578// Validate that the cached bounds are correct579mBodyManager.ValidateActiveBodyBounds();580#endif // JPH_DEBUG581582// Clear the large island splitter583mLargeIslandSplitter.Reset(inTempAllocator);584585// Clear the island builder586mIslandBuilder.ResetIslands(inTempAllocator);587588// Clear the contact manager589mContactManager.FinishConstraintBuffer();590591// Free active constraints592inTempAllocator->Free(context.mActiveConstraints, mConstraintManager.GetNumConstraints() * sizeof(Constraint *));593context.mActiveConstraints = nullptr;594595// Free body pairs596inTempAllocator->Free(context.mBodyPairs, sizeof(BodyPair) * mPhysicsSettings.mMaxInFlightBodyPairs);597context.mBodyPairs = nullptr;598599// Unlock the broadphase600mBroadPhase->UnlockModifications();601602// Unlock all constraints603mConstraintManager.UnlockAllConstraints();604605#ifdef JPH_ENABLE_ASSERTS606// Allow write operations to the active bodies list607mBodyManager.SetActiveBodiesLocked(false);608#endif609610// Unlock all bodies611mBodyManager.UnlockAllBodies();612613// Unlock step listeners614mStepListenersMutex.unlock();615616// Return any errors617EPhysicsUpdateError errors = static_cast<EPhysicsUpdateError>(context.mErrors.load(memory_order_acquire));618JPH_ASSERT(errors == EPhysicsUpdateError::None, "An error occurred during the physics update, see EPhysicsUpdateError for more information");619return errors;620}621622void PhysicsSystem::JobStepListeners(PhysicsUpdateContext::Step *ioStep)623{624#ifdef JPH_ENABLE_ASSERTS625// Read positions (broadphase updates concurrently so we can't write), read/write velocities626BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::Read);627628// Can activate bodies only (we cache the amount of active bodies at the beginning of the step in mNumActiveBodiesAtStepStart so we cannot deactivate here)629BodyManager::GrantActiveBodiesAccess grant_active(true, false);630#endif631632PhysicsStepListenerContext context;633context.mDeltaTime = ioStep->mContext->mStepDeltaTime;634context.mIsFirstStep = ioStep->mIsFirst;635context.mIsLastStep = ioStep->mIsLast;636context.mPhysicsSystem = this;637638uint32 batch_size = mPhysicsSettings.mStepListenersBatchSize;639for (;;)640{641// Get the start of a new batch642uint32 batch = ioStep->mStepListenerReadIdx.fetch_add(batch_size);643if (batch >= mStepListeners.size())644break;645646// Call the listeners647for (uint32 i = batch, i_end = min((uint32)mStepListeners.size(), batch + batch_size); i < i_end; ++i)648mStepListeners[i]->OnStep(context);649}650}651652void PhysicsSystem::JobDetermineActiveConstraints(PhysicsUpdateContext::Step *ioStep) const653{654#ifdef JPH_ENABLE_ASSERTS655// No body access656BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::None);657#endif658659uint32 num_constraints = mConstraintManager.GetNumConstraints();660uint32 num_active_constraints;661Constraint **active_constraints = (Constraint **)JPH_STACK_ALLOC(cDetermineActiveConstraintsBatchSize * sizeof(Constraint *));662663for (;;)664{665// Atomically fetch a batch of constraints666uint32 constraint_idx = ioStep->mDetermineActiveConstraintReadIdx.fetch_add(cDetermineActiveConstraintsBatchSize);667if (constraint_idx >= num_constraints)668break;669670// Calculate the end of the batch671uint32 constraint_idx_end = min(num_constraints, constraint_idx + cDetermineActiveConstraintsBatchSize);672673// 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)674mConstraintManager.GetActiveConstraints(constraint_idx, constraint_idx_end, active_constraints, num_active_constraints);675676// Copy the block of active constraints to the global list of active constraints677if (num_active_constraints > 0)678{679uint32 active_constraint_idx = ioStep->mNumActiveConstraints.fetch_add(num_active_constraints);680memcpy(ioStep->mContext->mActiveConstraints + active_constraint_idx, active_constraints, num_active_constraints * sizeof(Constraint *));681}682}683}684685void PhysicsSystem::JobApplyGravity(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)686{687#ifdef JPH_ENABLE_ASSERTS688// We update velocities and need the rotation to do so689BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::Read);690#endif691692// Get list of active bodies that we had at the start of the physics update.693// Any body that is activated as part of the simulation step does not receive gravity this frame.694// Note that bodies may be activated during this job but not deactivated, this means that only elements695// will be added to the array. Since the array is made to not reallocate, this is a safe operation.696const BodyID *active_bodies = mBodyManager.GetActiveBodiesUnsafe(EBodyType::RigidBody);697uint32 num_active_bodies_at_step_start = ioStep->mNumActiveBodiesAtStepStart;698699// Fetch delta time once outside the loop700float delta_time = ioContext->mStepDeltaTime;701702// Update velocities from forces703for (;;)704{705// Atomically fetch a batch of bodies706uint32 active_body_idx = ioStep->mApplyGravityReadIdx.fetch_add(cApplyGravityBatchSize);707if (active_body_idx >= num_active_bodies_at_step_start)708break;709710// Calculate the end of the batch711uint32 active_body_idx_end = min(num_active_bodies_at_step_start, active_body_idx + cApplyGravityBatchSize);712713// Process the batch714while (active_body_idx < active_body_idx_end)715{716Body &body = mBodyManager.GetBody(active_bodies[active_body_idx]);717if (body.IsDynamic())718{719MotionProperties *mp = body.GetMotionProperties();720Quat rotation = body.GetRotation();721722if (body.GetApplyGyroscopicForce())723mp->ApplyGyroscopicForceInternal(rotation, delta_time);724725mp->ApplyForceTorqueAndDragInternal(rotation, mGravity, delta_time);726}727active_body_idx++;728}729}730}731732void PhysicsSystem::JobSetupVelocityConstraints(float inDeltaTime, PhysicsUpdateContext::Step *ioStep) const733{734#ifdef JPH_ENABLE_ASSERTS735// We only read positions736BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::Read);737#endif738739uint32 num_constraints = ioStep->mNumActiveConstraints;740741for (;;)742{743// Atomically fetch a batch of constraints744uint32 constraint_idx = ioStep->mSetupVelocityConstraintsReadIdx.fetch_add(cSetupVelocityConstraintsBatchSize);745if (constraint_idx >= num_constraints)746break;747748ConstraintManager::sSetupVelocityConstraints(ioStep->mContext->mActiveConstraints + constraint_idx, min<uint32>(cSetupVelocityConstraintsBatchSize, num_constraints - constraint_idx), inDeltaTime);749}750}751752void PhysicsSystem::JobBuildIslandsFromConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)753{754#ifdef JPH_ENABLE_ASSERTS755// We read constraints and positions756BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::Read);757758// Can only activate bodies759BodyManager::GrantActiveBodiesAccess grant_active(true, false);760#endif761762// Prepare the island builder763mIslandBuilder.PrepareNonContactConstraints(ioStep->mNumActiveConstraints, ioContext->mTempAllocator);764765// Build the islands766ConstraintManager::sBuildIslands(ioStep->mContext->mActiveConstraints, ioStep->mNumActiveConstraints, mIslandBuilder, mBodyManager);767}768769void PhysicsSystem::TrySpawnJobFindCollisions(PhysicsUpdateContext::Step *ioStep) const770{771// Get how many jobs we can spawn and check if we can spawn more772uint max_jobs = ioStep->mBodyPairQueues.size();773if (CountBits(ioStep->mActiveFindCollisionJobs.load(memory_order_relaxed)) >= max_jobs)774return;775776// Count how many body pairs we have waiting777uint32 num_body_pairs = 0;778for (const PhysicsUpdateContext::BodyPairQueue &queue : ioStep->mBodyPairQueues)779num_body_pairs += queue.mWriteIdx - queue.mReadIdx;780781// Count how many active bodies we have waiting782uint32 num_active_bodies = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody) - ioStep->mActiveBodyReadIdx;783784// Calculate how many jobs we would like785uint desired_num_jobs = min((num_body_pairs + cNarrowPhaseBatchSize - 1) / cNarrowPhaseBatchSize + (num_active_bodies + cActiveBodiesBatchSize - 1) / cActiveBodiesBatchSize, max_jobs);786787for (;;)788{789// Get the bit mask of active jobs and see if we can spawn more790PhysicsUpdateContext::JobMask current_active_jobs = ioStep->mActiveFindCollisionJobs.load(memory_order_relaxed);791uint job_index = CountTrailingZeros(~current_active_jobs);792if (job_index >= desired_num_jobs)793break;794795// Try to claim the job index796PhysicsUpdateContext::JobMask job_mask = PhysicsUpdateContext::JobMask(1) << job_index;797PhysicsUpdateContext::JobMask prev_value = ioStep->mActiveFindCollisionJobs.fetch_or(job_mask, memory_order_acquire);798if ((prev_value & job_mask) == 0)799{800// Add dependencies from the find collisions job to the next jobs801ioStep->mUpdateBroadphaseFinalize.AddDependency();802ioStep->mFinalizeIslands.AddDependency();803804// Start the job805JobHandle job = ioStep->mContext->mJobSystem->CreateJob("FindCollisions", cColorFindCollisions, [step = ioStep, job_index]()806{807step->mContext->mPhysicsSystem->JobFindCollisions(step, job_index);808});809810// Add the job to the job barrier so the main updating thread can execute the job too811ioStep->mContext->mBarrier->AddJob(job);812813// Spawn only 1 extra job at a time814return;815}816}817}818819static void sFinalizeContactAllocator(PhysicsUpdateContext::Step &ioStep, const ContactConstraintManager::ContactAllocator &inAllocator)820{821// Atomically accumulate the number of found manifolds and body pairs822ioStep.mNumBodyPairs.fetch_add(inAllocator.mNumBodyPairs, memory_order_relaxed);823ioStep.mNumManifolds.fetch_add(inAllocator.mNumManifolds, memory_order_relaxed);824825// Combine update errors826ioStep.mContext->mErrors.fetch_or((uint32)inAllocator.mErrors, memory_order_relaxed);827}828829// Disable TSAN for this function. It detects a false positive race condition on mBodyPairs.830// We have written mBodyPairs before doing mWriteIdx++ and we check mWriteIdx before reading mBodyPairs, so this should be safe.831JPH_TSAN_NO_SANITIZE832void PhysicsSystem::JobFindCollisions(PhysicsUpdateContext::Step *ioStep, int inJobIndex)833{834#ifdef JPH_ENABLE_ASSERTS835// We read positions and read velocities (for elastic collisions)836BodyAccess::Grant grant(BodyAccess::EAccess::Read, BodyAccess::EAccess::Read);837838// Can only activate bodies839BodyManager::GrantActiveBodiesAccess grant_active(true, false);840#endif841842// Allocation context for allocating new contact points843ContactAllocator contact_allocator(mContactManager.GetContactAllocator());844845// Determine initial queue to read pairs from if no broadphase work can be done846// (always start looking at results from the next job)847int read_queue_idx = (inJobIndex + 1) % ioStep->mBodyPairQueues.size();848849// Allocate space to temporarily store a batch of active bodies850BodyID *active_bodies = (BodyID *)JPH_STACK_ALLOC(cActiveBodiesBatchSize * sizeof(BodyID));851852for (;;)853{854// Check if there are active bodies to be processed855uint32 active_bodies_read_idx = ioStep->mActiveBodyReadIdx;856uint32 num_active_bodies = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);857if (active_bodies_read_idx < num_active_bodies)858{859// Take a batch of active bodies860uint32 active_bodies_read_idx_end = min(num_active_bodies, active_bodies_read_idx + cActiveBodiesBatchSize);861if (ioStep->mActiveBodyReadIdx.compare_exchange_strong(active_bodies_read_idx, active_bodies_read_idx_end))862{863// Callback when a new body pair is found864class MyBodyPairCallback : public BodyPairCollector865{866public:867// Constructor868MyBodyPairCallback(PhysicsUpdateContext::Step *inStep, ContactAllocator &ioContactAllocator, int inJobIndex) :869mStep(inStep),870mContactAllocator(ioContactAllocator),871mJobIndex(inJobIndex)872{873}874875// Callback function when a body pair is found876virtual void AddHit(const BodyPair &inPair) override877{878// Check if we have space in our write queue879PhysicsUpdateContext::BodyPairQueue &queue = mStep->mBodyPairQueues[mJobIndex];880uint32 body_pairs_in_queue = queue.mWriteIdx - queue.mReadIdx;881if (body_pairs_in_queue >= mStep->mMaxBodyPairsPerQueue)882{883// Buffer full, process the pair now884mStep->mContext->mPhysicsSystem->ProcessBodyPair(mContactAllocator, inPair);885}886else887{888// Store the pair in our own queue889mStep->mContext->mBodyPairs[mJobIndex * mStep->mMaxBodyPairsPerQueue + queue.mWriteIdx % mStep->mMaxBodyPairsPerQueue] = inPair;890++queue.mWriteIdx;891}892}893894private:895PhysicsUpdateContext::Step * mStep;896ContactAllocator & mContactAllocator;897int mJobIndex;898};899MyBodyPairCallback add_pair(ioStep, contact_allocator, inJobIndex);900901// Copy active bodies to temporary array, broadphase will reorder them902uint32 batch_size = active_bodies_read_idx_end - active_bodies_read_idx;903memcpy(active_bodies, mBodyManager.GetActiveBodiesUnsafe(EBodyType::RigidBody) + active_bodies_read_idx, batch_size * sizeof(BodyID));904905// Find pairs in the broadphase906mBroadPhase->FindCollidingPairs(active_bodies, batch_size, mPhysicsSettings.mSpeculativeContactDistance, *mObjectVsBroadPhaseLayerFilter, *mObjectLayerPairFilter, add_pair);907908// Check if we have enough pairs in the buffer to start a new job909const PhysicsUpdateContext::BodyPairQueue &queue = ioStep->mBodyPairQueues[inJobIndex];910uint32 body_pairs_in_queue = queue.mWriteIdx - queue.mReadIdx;911if (body_pairs_in_queue >= cNarrowPhaseBatchSize)912TrySpawnJobFindCollisions(ioStep);913}914}915else916{917// Lockless loop to get the next body pair from the pairs buffer918const PhysicsUpdateContext *context = ioStep->mContext;919int first_read_queue_idx = read_queue_idx;920for (;;)921{922PhysicsUpdateContext::BodyPairQueue &queue = ioStep->mBodyPairQueues[read_queue_idx];923924// Get the next pair to process925uint32 pair_idx = queue.mReadIdx;926927// If the pair hasn't been written yet928if (pair_idx >= queue.mWriteIdx)929{930// Go to the next queue931read_queue_idx = (read_queue_idx + 1) % ioStep->mBodyPairQueues.size();932933// If we're back at the first queue, we've looked at all of them and found nothing934if (read_queue_idx == first_read_queue_idx)935{936// Collect information from the contact allocator and accumulate it in the step.937sFinalizeContactAllocator(*ioStep, contact_allocator);938939// Mark this job as inactive940ioStep->mActiveFindCollisionJobs.fetch_and(~PhysicsUpdateContext::JobMask(1 << inJobIndex), memory_order_release);941942// Trigger the next jobs943ioStep->mUpdateBroadphaseFinalize.RemoveDependency();944ioStep->mFinalizeIslands.RemoveDependency();945return;946}947948// Try again reading from the next queue949continue;950}951952// Copy the body pair out of the buffer953const BodyPair bp = context->mBodyPairs[read_queue_idx * ioStep->mMaxBodyPairsPerQueue + pair_idx % ioStep->mMaxBodyPairsPerQueue];954955// Mark this pair as taken956if (queue.mReadIdx.compare_exchange_strong(pair_idx, pair_idx + 1))957{958// Process the actual body pair959ProcessBodyPair(contact_allocator, bp);960break;961}962}963}964}965}966967void PhysicsSystem::sDefaultSimCollideBodyVsBody(const Body &inBody1, const Body &inBody2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, CollideShapeSettings &ioCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter)968{969SubShapeIDCreator part1, part2;970971if (inBody1.GetEnhancedInternalEdgeRemovalWithBody(inBody2))972{973// Collide with enhanced internal edge removal974ioCollideShapeSettings.mActiveEdgeMode = EActiveEdgeMode::CollideWithAll;975InternalEdgeRemovingCollector::sCollideShapeVsShape(inBody1.GetShape(), inBody2.GetShape(), Vec3::sOne(), Vec3::sOne(), inCenterOfMassTransform1, inCenterOfMassTransform2, part1, part2, ioCollideShapeSettings, ioCollector, inShapeFilter976#ifdef JPH_INTERNAL_EDGE_REMOVING_COLLECTOR_DEBUG977, inBody1.GetCenterOfMassPosition() // Query is done relative to the position of body 1978#endif // JPH_INTERNAL_EDGE_REMOVING_COLLECTOR_DEBUG979);980}981else982{983// Regular collide984CollisionDispatch::sCollideShapeVsShape(inBody1.GetShape(), inBody2.GetShape(), Vec3::sOne(), Vec3::sOne(), inCenterOfMassTransform1, inCenterOfMassTransform2, part1, part2, ioCollideShapeSettings, ioCollector, inShapeFilter);985}986}987988void PhysicsSystem::ProcessBodyPair(ContactAllocator &ioContactAllocator, const BodyPair &inBodyPair)989{990JPH_PROFILE_FUNCTION();991992// Fetch body pair993Body *body1 = &mBodyManager.GetBody(inBodyPair.mBodyA);994Body *body2 = &mBodyManager.GetBody(inBodyPair.mBodyB);995JPH_ASSERT(body1->IsActive());996997JPH_DET_LOG("ProcessBodyPair: id1: " << inBodyPair.mBodyA << " id2: " << inBodyPair.mBodyB << " p1: " << body1->GetCenterOfMassPosition() << " p2: " << body2->GetCenterOfMassPosition() << " r1: " << body1->GetRotation() << " r2: " << body2->GetRotation());998999// Check for soft bodies1000if (body2->IsSoftBody())1001{1002// If the 2nd body is a soft body and not active, we activate it now1003if (!body2->IsActive())1004mBodyManager.ActivateBodies(&inBodyPair.mBodyB, 1);10051006// Soft body processing is done later in the pipeline1007return;1008}10091010// 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,1011// which avoids accuracy problems when testing a very large static object against a small dynamic object1012// Ensure that body1 id < body2 id when motion types are the same.1013if (body1->GetMotionType() < body2->GetMotionType()1014|| (body1->GetMotionType() == body2->GetMotionType() && inBodyPair.mBodyB < inBodyPair.mBodyA))1015std::swap(body1, body2);10161017// Check if the contact points from the previous frame are reusable and if so copy them1018bool pair_handled = false, constraint_created = false;1019if (mPhysicsSettings.mUseBodyPairContactCache && !(body1->IsCollisionCacheInvalid() || body2->IsCollisionCacheInvalid()))1020mContactManager.GetContactsFromCache(ioContactAllocator, *body1, *body2, pair_handled, constraint_created);10211022// If the cache hasn't handled this body pair do actual collision detection1023if (!pair_handled)1024{1025// Create entry in the cache for this body pair1026// Needs to happen irrespective if we found a collision or not (we want to remember that no collision was found too)1027ContactConstraintManager::BodyPairHandle body_pair_handle = mContactManager.AddBodyPair(ioContactAllocator, *body1, *body2);1028if (body_pair_handle == nullptr)1029return; // Out of cache space10301031// Create the query settings1032CollideShapeSettings settings;1033settings.mCollectFacesMode = ECollectFacesMode::CollectFaces;1034settings.mActiveEdgeMode = mPhysicsSettings.mCheckActiveEdges? EActiveEdgeMode::CollideOnlyWithActive : EActiveEdgeMode::CollideWithAll;1035settings.mMaxSeparationDistance = body1->IsSensor() || body2->IsSensor()? 0.0f : mPhysicsSettings.mSpeculativeContactDistance;1036settings.mActiveEdgeMovementDirection = body1->GetLinearVelocity() - body2->GetLinearVelocity();10371038// Create shape filter1039SimShapeFilterWrapper shape_filter(mSimShapeFilter, body1);1040shape_filter.SetBody2(body2);10411042// Get transforms relative to body11043RVec3 offset = body1->GetCenterOfMassPosition();1044Mat44 transform1 = Mat44::sRotation(body1->GetRotation());1045Mat44 transform2 = body2->GetCenterOfMassTransform().PostTranslated(-offset).ToMat44();10461047if (mPhysicsSettings.mUseManifoldReduction // Check global flag1048&& body1->GetUseManifoldReductionWithBody(*body2)) // Check body flag1049{1050// Version WITH contact manifold reduction10511052class MyManifold : public ContactManifold1053{1054public:1055Vec3 mFirstWorldSpaceNormal;1056};10571058// A temporary structure that allows us to keep track of the all manifolds between this body pair1059using Manifolds = StaticArray<MyManifold, 32>;10601061// Create collector1062class ReductionCollideShapeCollector : public CollideShapeCollector1063{1064public:1065ReductionCollideShapeCollector(PhysicsSystem *inSystem, const Body *inBody1, const Body *inBody2) :1066mSystem(inSystem),1067mBody1(inBody1),1068mBody2(inBody2)1069{1070}10711072virtual void AddHit(const CollideShapeResult &inResult) override1073{1074// The first body should be the one with the highest motion type1075JPH_ASSERT(mBody1->GetMotionType() >= mBody2->GetMotionType());1076JPH_ASSERT(!ShouldEarlyOut());10771078// Test if we want to accept this hit1079if (mValidateBodyPair)1080{1081switch (mSystem->mContactManager.ValidateContactPoint(*mBody1, *mBody2, mBody1->GetCenterOfMassPosition(), inResult))1082{1083case ValidateResult::AcceptContact:1084// We're just accepting this one, nothing to do1085break;10861087case ValidateResult::AcceptAllContactsForThisBodyPair:1088// Accept and stop calling the validate callback1089mValidateBodyPair = false;1090break;10911092case ValidateResult::RejectContact:1093// Skip this contact1094return;10951096case ValidateResult::RejectAllContactsForThisBodyPair:1097// Skip this and early out1098ForceEarlyOut();1099return;1100}1101}11021103// Calculate normal1104Vec3 world_space_normal = inResult.mPenetrationAxis.Normalized();11051106// Check if we can add it to an existing manifold1107Manifolds::iterator manifold;1108float contact_normal_cos_max_delta_rot = mSystem->mPhysicsSettings.mContactNormalCosMaxDeltaRotation;1109for (manifold = mManifolds.begin(); manifold != mManifolds.end(); ++manifold)1110if (world_space_normal.Dot(manifold->mFirstWorldSpaceNormal) >= contact_normal_cos_max_delta_rot)1111{1112// Update average normal1113manifold->mWorldSpaceNormal += world_space_normal;1114manifold->mPenetrationDepth = max(manifold->mPenetrationDepth, inResult.mPenetrationDepth);1115break;1116}1117if (manifold == mManifolds.end())1118{1119// Check if array is full1120if (mManifolds.size() == mManifolds.capacity())1121{1122// Full, find manifold with least amount of penetration1123manifold = mManifolds.begin();1124for (Manifolds::iterator m = mManifolds.begin() + 1; m < mManifolds.end(); ++m)1125if (m->mPenetrationDepth < manifold->mPenetrationDepth)1126manifold = m;11271128// If this contacts penetration is smaller than the smallest manifold, we skip this contact1129if (inResult.mPenetrationDepth < manifold->mPenetrationDepth)1130return;11311132// Replace the manifold1133*manifold = { { mBody1->GetCenterOfMassPosition(), world_space_normal, inResult.mPenetrationDepth, inResult.mSubShapeID1, inResult.mSubShapeID2, { }, { } }, world_space_normal };1134}1135else1136{1137// Not full, create new manifold1138mManifolds.push_back({ { mBody1->GetCenterOfMassPosition(), world_space_normal, inResult.mPenetrationDepth, inResult.mSubShapeID1, inResult.mSubShapeID2, { }, { } }, world_space_normal });1139manifold = mManifolds.end() - 1;1140}1141}11421143// Determine contact points1144const PhysicsSettings &settings = mSystem->mPhysicsSettings;1145ManifoldBetweenTwoFaces(inResult.mContactPointOn1, inResult.mContactPointOn2, inResult.mPenetrationAxis, settings.mSpeculativeContactDistance + settings.mManifoldTolerance, inResult.mShape1Face, inResult.mShape2Face, manifold->mRelativeContactPointsOn1, manifold->mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, mBody1->GetCenterOfMassPosition()));11461147// Prune if we have more than 32 points (this means we could run out of space in the next iteration)1148if (manifold->mRelativeContactPointsOn1.size() > 32)1149PruneContactPoints(manifold->mFirstWorldSpaceNormal, manifold->mRelativeContactPointsOn1, manifold->mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, manifold->mBaseOffset));1150}11511152PhysicsSystem * mSystem;1153const Body * mBody1;1154const Body * mBody2;1155bool mValidateBodyPair = true;1156Manifolds mManifolds;1157};1158ReductionCollideShapeCollector collector(this, body1, body2);11591160// Perform collision detection between the two shapes1161mSimCollideBodyVsBody(*body1, *body2, transform1, transform2, settings, collector, shape_filter.GetFilter());11621163// Add the contacts1164for (ContactManifold &manifold : collector.mManifolds)1165{1166// Normalize the normal (is a sum of all normals from merged manifolds)1167manifold.mWorldSpaceNormal = manifold.mWorldSpaceNormal.Normalized();11681169// If we still have too many points, prune them now1170if (manifold.mRelativeContactPointsOn1.size() > 4)1171PruneContactPoints(manifold.mWorldSpaceNormal, manifold.mRelativeContactPointsOn1, manifold.mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, manifold.mBaseOffset));11721173// Actually add the contact points to the manager1174constraint_created |= mContactManager.AddContactConstraint(ioContactAllocator, body_pair_handle, *body1, *body2, manifold);1175}1176}1177else1178{1179// Version WITHOUT contact manifold reduction11801181// Create collector1182class NonReductionCollideShapeCollector : public CollideShapeCollector1183{1184public:1185NonReductionCollideShapeCollector(PhysicsSystem *inSystem, ContactAllocator &ioContactAllocator, Body *inBody1, Body *inBody2, const ContactConstraintManager::BodyPairHandle &inPairHandle) :1186mSystem(inSystem),1187mContactAllocator(ioContactAllocator),1188mBody1(inBody1),1189mBody2(inBody2),1190mBodyPairHandle(inPairHandle)1191{1192}11931194virtual void AddHit(const CollideShapeResult &inResult) override1195{1196// The first body should be the one with the highest motion type1197JPH_ASSERT(mBody1->GetMotionType() >= mBody2->GetMotionType());1198JPH_ASSERT(!ShouldEarlyOut());11991200// Test if we want to accept this hit1201if (mValidateBodyPair)1202{1203switch (mSystem->mContactManager.ValidateContactPoint(*mBody1, *mBody2, mBody1->GetCenterOfMassPosition(), inResult))1204{1205case ValidateResult::AcceptContact:1206// We're just accepting this one, nothing to do1207break;12081209case ValidateResult::AcceptAllContactsForThisBodyPair:1210// Accept and stop calling the validate callback1211mValidateBodyPair = false;1212break;12131214case ValidateResult::RejectContact:1215// Skip this contact1216return;12171218case ValidateResult::RejectAllContactsForThisBodyPair:1219// Skip this and early out1220ForceEarlyOut();1221return;1222}1223}12241225// Determine contact points1226ContactManifold manifold;1227manifold.mBaseOffset = mBody1->GetCenterOfMassPosition();1228const PhysicsSettings &settings = mSystem->mPhysicsSettings;1229ManifoldBetweenTwoFaces(inResult.mContactPointOn1, inResult.mContactPointOn2, inResult.mPenetrationAxis, settings.mSpeculativeContactDistance + settings.mManifoldTolerance, inResult.mShape1Face, inResult.mShape2Face, manifold.mRelativeContactPointsOn1, manifold.mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, manifold.mBaseOffset));12301231// Calculate normal1232manifold.mWorldSpaceNormal = inResult.mPenetrationAxis.Normalized();12331234// Store penetration depth1235manifold.mPenetrationDepth = inResult.mPenetrationDepth;12361237// Prune if we have more than 4 points1238if (manifold.mRelativeContactPointsOn1.size() > 4)1239PruneContactPoints(manifold.mWorldSpaceNormal, manifold.mRelativeContactPointsOn1, manifold.mRelativeContactPointsOn2 JPH_IF_DEBUG_RENDERER(, manifold.mBaseOffset));12401241// Set other properties1242manifold.mSubShapeID1 = inResult.mSubShapeID1;1243manifold.mSubShapeID2 = inResult.mSubShapeID2;12441245// Actually add the contact points to the manager1246mConstraintCreated |= mSystem->mContactManager.AddContactConstraint(mContactAllocator, mBodyPairHandle, *mBody1, *mBody2, manifold);1247}12481249PhysicsSystem * mSystem;1250ContactAllocator & mContactAllocator;1251Body * mBody1;1252Body * mBody2;1253ContactConstraintManager::BodyPairHandle mBodyPairHandle;1254bool mValidateBodyPair = true;1255bool mConstraintCreated = false;1256};1257NonReductionCollideShapeCollector collector(this, ioContactAllocator, body1, body2, body_pair_handle);12581259// Perform collision detection between the two shapes1260mSimCollideBodyVsBody(*body1, *body2, transform1, transform2, settings, collector, shape_filter.GetFilter());12611262constraint_created = collector.mConstraintCreated;1263}1264}12651266// If a contact constraint was created, we need to do some extra work1267if (constraint_created)1268{1269// Wake up sleeping bodies1270BodyID body_ids[2];1271int num_bodies = 0;1272if (body1->IsDynamic() && !body1->IsActive())1273body_ids[num_bodies++] = body1->GetID();1274if (body2->IsDynamic() && !body2->IsActive())1275body_ids[num_bodies++] = body2->GetID();1276if (num_bodies > 0)1277mBodyManager.ActivateBodies(body_ids, num_bodies);12781279// Link the two bodies1280mIslandBuilder.LinkBodies(body1->GetIndexInActiveBodiesInternal(), body2->GetIndexInActiveBodiesInternal());1281}1282}12831284void PhysicsSystem::JobFinalizeIslands(PhysicsUpdateContext *ioContext)1285{1286#ifdef JPH_ENABLE_ASSERTS1287// We only touch island data1288BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::None);1289#endif12901291// Finish collecting the islands, at this point the active body list doesn't change so it's safe to access1292mIslandBuilder.Finalize(mBodyManager.GetActiveBodiesUnsafe(EBodyType::RigidBody), mBodyManager.GetNumActiveBodies(EBodyType::RigidBody), mContactManager.GetNumConstraints(), ioContext->mTempAllocator);12931294// Prepare the large island splitter1295if (mPhysicsSettings.mUseLargeIslandSplitter)1296mLargeIslandSplitter.Prepare(mIslandBuilder, mBodyManager.GetNumActiveBodies(EBodyType::RigidBody), ioContext->mTempAllocator);1297}12981299void PhysicsSystem::JobBodySetIslandIndex()1300{1301#ifdef JPH_ENABLE_ASSERTS1302// We only touch island data1303BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::None);1304#endif13051306// Loop through the result and tag all bodies with an island index1307for (uint32 island_idx = 0, n = mIslandBuilder.GetNumIslands(); island_idx < n; ++island_idx)1308{1309BodyID *body_start, *body_end;1310mIslandBuilder.GetBodiesInIsland(island_idx, body_start, body_end);1311for (const BodyID *body = body_start; body < body_end; ++body)1312mBodyManager.GetBody(*body).GetMotionProperties()->SetIslandIndexInternal(island_idx);1313}1314}13151316JPH_SUPPRESS_WARNING_PUSH1317JPH_CLANG_SUPPRESS_WARNING("-Wundefined-func-template") // ConstraintManager::sWarmStartVelocityConstraints / ContactConstraintManager::WarmStartVelocityConstraints is instantiated in the cpp file13181319void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)1320{1321#ifdef JPH_ENABLE_ASSERTS1322// We update velocities and need to read positions to do so1323BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::Read);1324#endif13251326float delta_time = ioContext->mStepDeltaTime;1327Constraint **active_constraints = ioContext->mActiveConstraints;13281329// Only the first step to correct for the delta time difference in the previous update1330float warm_start_impulse_ratio = ioStep->mIsFirst? ioContext->mWarmStartImpulseRatio : 1.0f;13311332bool check_islands = true, check_split_islands = mPhysicsSettings.mUseLargeIslandSplitter;1333for (;;)1334{1335// First try to get work from large islands1336if (check_split_islands)1337{1338bool first_iteration;1339uint split_island_index;1340uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;1341switch (mLargeIslandSplitter.FetchNextBatch(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, first_iteration))1342{1343case LargeIslandSplitter::EStatus::BatchRetrieved:1344{1345if (first_iteration)1346{1347// Iteration 0 is used to warm start the batch (we added 1 to the number of iterations in LargeIslandSplitter::SplitIsland)1348DummyCalculateSolverSteps dummy;1349ConstraintManager::sWarmStartVelocityConstraints(active_constraints, constraints_begin, constraints_end, warm_start_impulse_ratio, dummy);1350mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio, dummy);1351}1352else1353{1354// Solve velocity constraints1355ConstraintManager::sSolveVelocityConstraints(active_constraints, constraints_begin, constraints_end, delta_time);1356mContactManager.SolveVelocityConstraints(contacts_begin, contacts_end);1357}13581359// Mark the batch as processed1360bool last_iteration, final_batch;1361mLargeIslandSplitter.MarkBatchProcessed(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, last_iteration, final_batch);13621363// Save back the lambdas in the contact cache for the warm start of the next physics update1364if (last_iteration)1365mContactManager.StoreAppliedImpulses(contacts_begin, contacts_end);13661367// We processed work, loop again1368continue;1369}13701371case LargeIslandSplitter::EStatus::WaitingForBatch:1372break;13731374case LargeIslandSplitter::EStatus::AllBatchesDone:1375check_split_islands = false;1376break;1377}1378}13791380// If that didn't succeed try to process an island1381if (check_islands)1382{1383// Next island1384uint32 island_idx = ioStep->mSolveVelocityConstraintsNextIsland++;1385if (island_idx >= mIslandBuilder.GetNumIslands())1386{1387// We processed all islands, stop checking islands1388check_islands = false;1389continue;1390}13911392JPH_PROFILE("Island");13931394// Get iterators for this island1395uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;1396bool has_constraints = mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end);1397bool has_contacts = mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end);13981399// If we don't have any contacts or constraints, we know that none of the following islands have any contacts or constraints1400// (because they're sorted by most constraints first). This means we're done.1401if (!has_contacts && !has_constraints)1402{1403#ifdef JPH_ENABLE_ASSERTS1404// Validate our assumption that the next islands don't have any constraints or contacts1405for (; island_idx < mIslandBuilder.GetNumIslands(); ++island_idx)1406{1407JPH_ASSERT(!mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end));1408JPH_ASSERT(!mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end));1409}1410#endif // JPH_ENABLE_ASSERTS14111412check_islands = false;1413continue;1414}14151416// Sorting is costly but needed for a deterministic simulation, allow the user to turn this off1417if (mPhysicsSettings.mDeterministicSimulation)1418{1419// Sort constraints to give a deterministic simulation1420ConstraintManager::sSortConstraints(active_constraints, constraints_begin, constraints_end);14211422// Sort contacts to give a deterministic simulation1423mContactManager.SortContacts(contacts_begin, contacts_end);1424}14251426// Split up large islands1427CalculateSolverSteps steps_calculator(mPhysicsSettings);1428if (mPhysicsSettings.mUseLargeIslandSplitter1429&& mLargeIslandSplitter.SplitIsland(island_idx, mIslandBuilder, mBodyManager, mContactManager, active_constraints, steps_calculator))1430continue; // Loop again to try to fetch the newly split island14311432// We didn't create a split, just run the solver now for this entire island. Begin by warm starting.1433ConstraintManager::sWarmStartVelocityConstraints(active_constraints, constraints_begin, constraints_end, warm_start_impulse_ratio, steps_calculator);1434mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio, steps_calculator);1435steps_calculator.Finalize();14361437// Store the number of position steps for later1438mIslandBuilder.SetNumPositionSteps(island_idx, steps_calculator.GetNumPositionSteps());14391440// Solve velocity constraints1441for (uint velocity_step = 0; velocity_step < steps_calculator.GetNumVelocitySteps(); ++velocity_step)1442{1443bool applied_impulse = ConstraintManager::sSolveVelocityConstraints(active_constraints, constraints_begin, constraints_end, delta_time);1444applied_impulse |= mContactManager.SolveVelocityConstraints(contacts_begin, contacts_end);1445if (!applied_impulse)1446break;1447}14481449// Save back the lambdas in the contact cache for the warm start of the next physics update1450mContactManager.StoreAppliedImpulses(contacts_begin, contacts_end);14511452// We processed work, loop again1453continue;1454}14551456if (check_islands)1457{1458// If there are islands, we don't need to wait and can pick up new work1459continue;1460}1461else if (check_split_islands)1462{1463// If there are split islands, but we didn't do any work, give up a time slice1464std::this_thread::yield();1465}1466else1467{1468// No more work1469break;1470}1471}1472}14731474JPH_SUPPRESS_WARNING_POP14751476void PhysicsSystem::JobPreIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)1477{1478// Reserve enough space for all bodies that may need a cast1479TempAllocator *temp_allocator = ioContext->mTempAllocator;1480JPH_ASSERT(ioStep->mCCDBodies == nullptr);1481ioStep->mCCDBodiesCapacity = mBodyManager.GetNumActiveCCDBodies();1482ioStep->mCCDBodies = (CCDBody *)temp_allocator->Allocate(ioStep->mCCDBodiesCapacity * sizeof(CCDBody));14831484// Initialize the mapping table between active body and CCD body1485JPH_ASSERT(ioStep->mActiveBodyToCCDBody == nullptr);1486ioStep->mNumActiveBodyToCCDBody = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);1487ioStep->mActiveBodyToCCDBody = (int *)temp_allocator->Allocate(ioStep->mNumActiveBodyToCCDBody * sizeof(int));14881489// Prepare the split island builder for solving the position constraints1490mLargeIslandSplitter.PrepareForSolvePositions();1491}14921493void PhysicsSystem::JobIntegrateVelocity(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)1494{1495#ifdef JPH_ENABLE_ASSERTS1496// We update positions and need velocity to do so, we also clamp velocities so need to write to them1497BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::ReadWrite);1498#endif14991500float delta_time = ioContext->mStepDeltaTime;1501const BodyID *active_bodies = mBodyManager.GetActiveBodiesUnsafe(EBodyType::RigidBody);1502uint32 num_active_bodies = mBodyManager.GetNumActiveBodies(EBodyType::RigidBody);1503uint32 num_active_bodies_after_find_collisions = ioStep->mActiveBodyReadIdx;15041505// We can move bodies that are not part of an island. In this case we need to notify the broadphase of the movement.1506static constexpr int cBodiesBatch = 64;1507BodyID *bodies_to_update_bounds = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));1508int num_bodies_to_update_bounds = 0;15091510for (;;)1511{1512// Atomically fetch a batch of bodies1513uint32 active_body_idx = ioStep->mIntegrateVelocityReadIdx.fetch_add(cIntegrateVelocityBatchSize);1514if (active_body_idx >= num_active_bodies)1515break;15161517// Calculate the end of the batch1518uint32 active_body_idx_end = min(num_active_bodies, active_body_idx + cIntegrateVelocityBatchSize);15191520// Process the batch1521while (active_body_idx < active_body_idx_end)1522{1523// Update the positions using an Symplectic Euler step (which integrates using the updated velocity v1' rather1524// than the original velocity v1):1525// x1' = x1 + h * v1'1526// At this point the active bodies list does not change, so it is safe to access the array.1527BodyID body_id = active_bodies[active_body_idx];1528Body &body = mBodyManager.GetBody(body_id);1529MotionProperties *mp = body.GetMotionProperties();15301531JPH_DET_LOG("JobIntegrateVelocity: id: " << body_id << " v: " << body.GetLinearVelocity() << " w: " << body.GetAngularVelocity());15321533// Clamp velocities (not for kinematic bodies)1534if (body.IsDynamic())1535{1536mp->ClampLinearVelocity();1537mp->ClampAngularVelocity();1538}15391540// Update the rotation of the body according to the angular velocity1541// For motion type discrete we need to do this anyway, for motion type linear cast we have multiple choices1542// 1. Rotate the body first and then sweep1543// 2. First sweep and then rotate the body at the end1544// 3. Pick some in between rotation (e.g. half way), then sweep and finally rotate the remainder1545// (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.1546// 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 using1547// 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 previous1548// 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 effects1549// 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 detecting1550// 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).1551body.AddRotationStep(body.GetAngularVelocity() * delta_time);15521553// Get delta position1554Vec3 delta_pos = body.GetLinearVelocity() * delta_time;15551556// If the position should be updated (or if it is delayed because of CCD)1557bool update_position = true;15581559switch (mp->GetMotionQuality())1560{1561case EMotionQuality::Discrete:1562// No additional collision checking to be done1563break;15641565case EMotionQuality::LinearCast:1566if (body.IsDynamic() // Kinematic bodies cannot be stopped1567&& !body.IsSensor()) // We don't support CCD sensors1568{1569// Determine inner radius (the smallest sphere that fits into the shape)1570float inner_radius = body.GetShape()->GetInnerRadius();1571JPH_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.");15721573// Measure translation in this step and check if it above the threshold to perform a linear cast1574float linear_cast_threshold_sq = Square(mPhysicsSettings.mLinearCastThreshold * inner_radius);1575if (delta_pos.LengthSq() > linear_cast_threshold_sq)1576{1577// This body needs a cast1578uint32 ccd_body_idx = ioStep->mNumCCDBodies++;1579JPH_ASSERT(active_body_idx < ioStep->mNumActiveBodyToCCDBody);1580ioStep->mActiveBodyToCCDBody[active_body_idx] = ccd_body_idx;1581new (&ioStep->mCCDBodies[ccd_body_idx]) CCDBody(body_id, delta_pos, linear_cast_threshold_sq, min(mPhysicsSettings.mPenetrationSlop, mPhysicsSettings.mLinearCastMaxPenetration * inner_radius));15821583update_position = false;1584}1585}1586break;1587}15881589if (update_position)1590{1591// Move the body now1592body.AddPositionStep(delta_pos);15931594// If the body was activated due to an earlier CCD step it will have an index in the active1595// body list that it higher than the highest one we processed during FindCollisions1596// which means it hasn't been assigned an island and will not be updated by an island1597// this means that we need to update its bounds manually1598if (mp->GetIndexInActiveBodiesInternal() >= num_active_bodies_after_find_collisions)1599{1600body.CalculateWorldSpaceBoundsInternal();1601bodies_to_update_bounds[num_bodies_to_update_bounds++] = body.GetID();1602if (num_bodies_to_update_bounds == cBodiesBatch)1603{1604// Buffer full, flush now1605mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);1606num_bodies_to_update_bounds = 0;1607}1608}16091610// We did not create a CCD body1611ioStep->mActiveBodyToCCDBody[active_body_idx] = -1;1612}16131614active_body_idx++;1615}1616}16171618// Notify change bounds on requested bodies1619if (num_bodies_to_update_bounds > 0)1620mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);1621}16221623void PhysicsSystem::JobPostIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep) const1624{1625// Validate that our reservations were correct1626JPH_ASSERT(ioStep->mNumCCDBodies <= mBodyManager.GetNumActiveCCDBodies());16271628if (ioStep->mNumCCDBodies == 0)1629{1630// No continuous collision detection jobs -> kick the next job ourselves1631ioStep->mContactRemovedCallbacks.RemoveDependency();1632}1633else1634{1635// Run the continuous collision detection jobs1636int num_continuous_collision_jobs = min(int(ioStep->mNumCCDBodies + cNumCCDBodiesPerJob - 1) / cNumCCDBodiesPerJob, ioContext->GetMaxConcurrency());1637ioStep->mResolveCCDContacts.AddDependency(num_continuous_collision_jobs);1638ioStep->mContactRemovedCallbacks.AddDependency(num_continuous_collision_jobs - 1); // Already had 1 dependency1639for (int i = 0; i < num_continuous_collision_jobs; ++i)1640{1641JobHandle job = ioContext->mJobSystem->CreateJob("FindCCDContacts", cColorFindCCDContacts, [ioContext, ioStep]()1642{1643ioContext->mPhysicsSystem->JobFindCCDContacts(ioContext, ioStep);16441645ioStep->mResolveCCDContacts.RemoveDependency();1646ioStep->mContactRemovedCallbacks.RemoveDependency();1647});1648ioContext->mBarrier->AddJob(job);1649}1650}1651}16521653// Helper function to calculate the motion of a body during this CCD step1654inline static Vec3 sCalculateBodyMotion(const Body &inBody, float inDeltaTime)1655{1656// If the body is linear casting, the body has not yet moved so we need to calculate its motion1657if (inBody.IsDynamic() && inBody.GetMotionProperties()->GetMotionQuality() == EMotionQuality::LinearCast)1658return inDeltaTime * inBody.GetLinearVelocity();16591660// Body has already moved, so we don't need to correct for anything1661return Vec3::sZero();1662}16631664// Helper function that finds the CCD body corresponding to a body (if it exists)1665inline static PhysicsUpdateContext::Step::CCDBody *sGetCCDBody(const Body &inBody, PhysicsUpdateContext::Step *inStep)1666{1667// Only rigid bodies can have a CCD body1668if (!inBody.IsRigidBody())1669return nullptr;16701671// If the body has no motion properties it cannot have a CCD body1672const MotionProperties *motion_properties = inBody.GetMotionPropertiesUnchecked();1673if (motion_properties == nullptr)1674return nullptr;16751676// If it is not active it cannot have a CCD body1677uint32 active_index = motion_properties->GetIndexInActiveBodiesInternal();1678if (active_index == Body::cInactiveIndex)1679return nullptr;16801681// Check if the active body has a corresponding CCD body1682JPH_ASSERT(active_index < inStep->mNumActiveBodyToCCDBody); // Ensure that the body has a mapping to CCD body1683int ccd_index = inStep->mActiveBodyToCCDBody[active_index];1684if (ccd_index < 0)1685return nullptr;16861687PhysicsUpdateContext::Step::CCDBody *ccd_body = &inStep->mCCDBodies[ccd_index];1688JPH_ASSERT(ccd_body->mBodyID1 == inBody.GetID(), "We found the wrong CCD body!");1689return ccd_body;1690}16911692void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)1693{1694#ifdef JPH_ENABLE_ASSERTS1695// We only read positions, but the validate callback may read body positions and velocities1696BodyAccess::Grant grant(BodyAccess::EAccess::Read, BodyAccess::EAccess::Read);1697#endif16981699// Allocation context for allocating new contact points1700ContactAllocator contact_allocator(mContactManager.GetContactAllocator());17011702// Settings1703ShapeCastSettings settings;1704settings.mUseShrunkenShapeAndConvexRadius = true;1705settings.mBackFaceModeTriangles = EBackFaceMode::IgnoreBackFaces;1706settings.mBackFaceModeConvex = EBackFaceMode::IgnoreBackFaces;1707settings.mReturnDeepestPoint = true;1708settings.mCollectFacesMode = ECollectFacesMode::CollectFaces;1709settings.mActiveEdgeMode = mPhysicsSettings.mCheckActiveEdges? EActiveEdgeMode::CollideOnlyWithActive : EActiveEdgeMode::CollideWithAll;17101711for (;;)1712{1713// Fetch the next body to cast1714uint32 idx = ioStep->mNextCCDBody++;1715if (idx >= ioStep->mNumCCDBodies)1716break;1717CCDBody &ccd_body = ioStep->mCCDBodies[idx];1718const Body &body = mBodyManager.GetBody(ccd_body.mBodyID1);17191720// Filter out layers1721DefaultBroadPhaseLayerFilter broadphase_layer_filter = GetDefaultBroadPhaseLayerFilter(body.GetObjectLayer());1722DefaultObjectLayerFilter object_layer_filter = GetDefaultLayerFilter(body.GetObjectLayer());17231724#ifdef JPH_DEBUG_RENDERER1725// Draw start and end shape of cast1726if (sDrawMotionQualityLinearCast)1727{1728RMat44 com = body.GetCenterOfMassTransform();1729body.GetShape()->Draw(DebugRenderer::sInstance, com, Vec3::sOne(), Color::sGreen, false, true);1730DebugRenderer::sInstance->DrawArrow(com.GetTranslation(), com.GetTranslation() + ccd_body.mDeltaPosition, Color::sGreen, 0.1f);1731body.GetShape()->Draw(DebugRenderer::sInstance, com.PostTranslated(ccd_body.mDeltaPosition), Vec3::sOne(), Color::sRed, false, true);1732}1733#endif // JPH_DEBUG_RENDERER17341735// Create a collector that will find the maximum distance allowed to travel while not penetrating more than 'max penetration'1736class CCDNarrowPhaseCollector : public CastShapeCollector1737{1738public:1739CCDNarrowPhaseCollector(const BodyManager &inBodyManager, ContactConstraintManager &inContactConstraintManager, CCDBody &inCCDBody, ShapeCastResult &inResult, float inDeltaTime) :1740mBodyManager(inBodyManager),1741mContactConstraintManager(inContactConstraintManager),1742mCCDBody(inCCDBody),1743mResult(inResult),1744mDeltaTime(inDeltaTime)1745{1746}17471748virtual void AddHit(const ShapeCastResult &inResult) override1749{1750JPH_PROFILE_FUNCTION();17511752// Check if this is a possible earlier hit than the one before1753float fraction = inResult.mFraction;1754if (fraction < mCCDBody.mFractionPlusSlop)1755{1756// Normalize normal1757Vec3 normal = inResult.mPenetrationAxis.Normalized();17581759// Calculate how much we can add to the fraction to penetrate the collision point by mMaxPenetration.1760// Note that the normal is pointing towards body 2!1761// 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|1762// <=> dist = mMaxPenetration * |delta_pos| / normal . delta_pos1763// Converting to a faction: delta_fraction = dist / |delta_pos| = mLinearCastTreshold / normal . delta_pos1764float denominator = normal.Dot(mCCDBody.mDeltaPosition);1765if (denominator > mCCDBody.mMaxPenetration) // Avoid dividing by zero, if extra hit fraction > 1 there's also no point in continuing1766{1767float fraction_plus_slop = fraction + mCCDBody.mMaxPenetration / denominator;1768if (fraction_plus_slop < mCCDBody.mFractionPlusSlop)1769{1770const Body &body2 = mBodyManager.GetBody(inResult.mBodyID2);17711772// Check if we've already accepted all hits from this body1773if (mValidateBodyPair)1774{1775// Validate the contact result1776const Body &body1 = mBodyManager.GetBody(mCCDBody.mBodyID1);1777ValidateResult 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 below1778switch (validate_result)1779{1780case ValidateResult::AcceptContact:1781// Just continue1782break;17831784case ValidateResult::AcceptAllContactsForThisBodyPair:1785// Accept this and all following contacts from this body1786mValidateBodyPair = false;1787break;17881789case ValidateResult::RejectContact:1790return;17911792case ValidateResult::RejectAllContactsForThisBodyPair:1793// Reject this and all following contacts from this body1794mRejectAll = true;1795ForceEarlyOut();1796return;1797}1798}17991800// This is the earliest hit so far, store it1801mCCDBody.mContactNormal = normal;1802mCCDBody.mBodyID2 = inResult.mBodyID2;1803mCCDBody.mSubShapeID2 = inResult.mSubShapeID2;1804mCCDBody.mFraction = fraction;1805mCCDBody.mFractionPlusSlop = fraction_plus_slop;1806mResult = inResult;18071808// Result was assuming body 2 is not moving, but it is, so we need to correct for it1809Vec3 movement2 = fraction * sCalculateBodyMotion(body2, mDeltaTime);1810if (!movement2.IsNearZero())1811{1812mResult.mContactPointOn1 += movement2;1813mResult.mContactPointOn2 += movement2;1814for (Vec3 &v : mResult.mShape1Face)1815v += movement2;1816for (Vec3 &v : mResult.mShape2Face)1817v += movement2;1818}18191820// Update early out fraction1821UpdateEarlyOutFraction(fraction_plus_slop);1822}1823}1824}1825}18261827bool mValidateBodyPair; ///< If we still have to call the ValidateContactPoint for this body pair1828bool mRejectAll; ///< Reject all further contacts between this body pair18291830private:1831const BodyManager & mBodyManager;1832ContactConstraintManager & mContactConstraintManager;1833CCDBody & mCCDBody;1834ShapeCastResult & mResult;1835float mDeltaTime;1836BodyID mAcceptedBodyID;1837};18381839// Narrowphase collector1840ShapeCastResult cast_shape_result;1841CCDNarrowPhaseCollector np_collector(mBodyManager, mContactManager, ccd_body, cast_shape_result, ioContext->mStepDeltaTime);18421843// This collector wraps the narrowphase collector and collects the closest hit1844class CCDBroadPhaseCollector : public CastShapeBodyCollector1845{1846public:1847CCDBroadPhaseCollector(const CCDBody &inCCDBody, const Body &inBody1, const RShapeCast &inShapeCast, ShapeCastSettings &inShapeCastSettings, SimShapeFilterWrapper &inShapeFilter, CCDNarrowPhaseCollector &ioCollector, const BodyManager &inBodyManager, PhysicsUpdateContext::Step *inStep, float inDeltaTime) :1848mCCDBody(inCCDBody),1849mBody1(inBody1),1850mBody1Extent(inShapeCast.mShapeWorldBounds.GetExtent()),1851mShapeCast(inShapeCast),1852mShapeCastSettings(inShapeCastSettings),1853mShapeFilter(inShapeFilter),1854mCollector(ioCollector),1855mBodyManager(inBodyManager),1856mStep(inStep),1857mDeltaTime(inDeltaTime)1858{1859}18601861virtual void AddHit(const BroadPhaseCastResult &inResult) override1862{1863JPH_PROFILE_FUNCTION();18641865JPH_ASSERT(inResult.mFraction <= GetEarlyOutFraction(), "This hit should not have been passed on to the collector");18661867// Test if we're colliding with ourselves1868if (mBody1.GetID() == inResult.mBodyID)1869return;18701871// Avoid treating duplicates, if both bodies are doing CCD then only consider collision if body ID < other body ID1872const Body &body2 = mBodyManager.GetBody(inResult.mBodyID);1873const CCDBody *ccd_body2 = sGetCCDBody(body2, mStep);1874if (ccd_body2 != nullptr && mCCDBody.mBodyID1 > ccd_body2->mBodyID1)1875return;18761877// Test group filter1878if (!mBody1.GetCollisionGroup().CanCollide(body2.GetCollisionGroup()))1879return;18801881// TODO: For now we ignore sensors1882if (body2.IsSensor())1883return;18841885// Get relative movement of these two bodies1886Vec3 direction = mShapeCast.mDirection - sCalculateBodyMotion(body2, mDeltaTime);18871888// Test if the remaining movement is less than our movement threshold1889if (direction.LengthSq() < mCCDBody.mLinearCastThresholdSq)1890return;18911892// 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 fraction1893AABox bounds = body2.GetWorldSpaceBounds();1894bounds.mMin -= mBody1Extent;1895bounds.mMax += mBody1Extent;1896float hit_fraction = RayAABox(Vec3(mShapeCast.mCenterOfMassStart.GetTranslation()), RayInvDirection(direction), bounds.mMin, bounds.mMax);1897if (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 fraction1898return;18991900// Reset collector (this is a new body pair)1901mCollector.ResetEarlyOutFraction(GetEarlyOutFraction());1902mCollector.mValidateBodyPair = true;1903mCollector.mRejectAll = false;19041905// Set body ID on shape filter1906mShapeFilter.SetBody2(&body2);19071908// Provide direction as hint for the active edges algorithm1909mShapeCastSettings.mActiveEdgeMovementDirection = direction;19101911// Do narrow phase collision check1912RShapeCast relative_cast(mShapeCast.mShape, mShapeCast.mScale, mShapeCast.mCenterOfMassStart, direction, mShapeCast.mShapeWorldBounds);1913body2.GetTransformedShape().CastShape(relative_cast, mShapeCastSettings, mShapeCast.mCenterOfMassStart.GetTranslation(), mCollector, mShapeFilter.GetFilter());19141915// Update early out fraction based on narrow phase collector1916if (!mCollector.mRejectAll)1917UpdateEarlyOutFraction(mCollector.GetEarlyOutFraction());1918}19191920const CCDBody & mCCDBody;1921const Body & mBody1;1922Vec3 mBody1Extent;1923RShapeCast mShapeCast;1924ShapeCastSettings & mShapeCastSettings;1925SimShapeFilterWrapper & mShapeFilter;1926CCDNarrowPhaseCollector & mCollector;1927const BodyManager & mBodyManager;1928PhysicsUpdateContext::Step *mStep;1929float mDeltaTime;1930};19311932// Create shape filter1933SimShapeFilterWrapper shape_filter(mSimShapeFilter, &body);19341935// 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.1936RShapeCast shape_cast(body.GetShape(), Vec3::sOne(), body.GetCenterOfMassTransform(), ccd_body.mDeltaPosition);1937CCDBroadPhaseCollector bp_collector(ccd_body, body, shape_cast, settings, shape_filter, np_collector, mBodyManager, ioStep, ioContext->mStepDeltaTime);1938mBroadPhase->CastAABoxNoLock({ shape_cast.mShapeWorldBounds, shape_cast.mDirection }, bp_collector, broadphase_layer_filter, object_layer_filter);19391940// Check if there was a hit1941if (ccd_body.mFractionPlusSlop < 1.0f)1942{1943const Body &body2 = mBodyManager.GetBody(ccd_body.mBodyID2);19441945// Determine contact manifold1946ContactManifold manifold;1947manifold.mBaseOffset = shape_cast.mCenterOfMassStart.GetTranslation();1948ManifoldBetweenTwoFaces(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));1949manifold.mSubShapeID1 = cast_shape_result.mSubShapeID1;1950manifold.mSubShapeID2 = cast_shape_result.mSubShapeID2;1951manifold.mPenetrationDepth = cast_shape_result.mPenetrationDepth;1952manifold.mWorldSpaceNormal = ccd_body.mContactNormal;19531954// Call contact point callbacks1955mContactManager.OnCCDContactAdded(contact_allocator, body, body2, manifold, ccd_body.mContactSettings);19561957if (ccd_body.mContactSettings.mIsSensor)1958{1959// If this is a sensor, we don't want to solve the contact1960ccd_body.mFractionPlusSlop = 1.0f;1961ccd_body.mBodyID2 = BodyID();1962}1963else1964{1965// Calculate the average position from the manifold (this will result in the same impulse applied as when we apply impulses to all contact points)1966if (manifold.mRelativeContactPointsOn2.size() > 1)1967{1968Vec3 average_contact_point = Vec3::sZero();1969for (const Vec3 &v : manifold.mRelativeContactPointsOn2)1970average_contact_point += v;1971average_contact_point /= (float)manifold.mRelativeContactPointsOn2.size();1972ccd_body.mContactPointOn2 = manifold.mBaseOffset + average_contact_point;1973}1974else1975ccd_body.mContactPointOn2 = manifold.mBaseOffset + cast_shape_result.mContactPointOn2;1976}1977}1978}19791980// Collect information from the contact allocator and accumulate it in the step.1981sFinalizeContactAllocator(*ioStep, contact_allocator);1982}19831984void PhysicsSystem::JobResolveCCDContacts(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)1985{1986#ifdef JPH_ENABLE_ASSERTS1987// Read/write body access1988BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::ReadWrite);19891990// We activate bodies that we collide with1991BodyManager::GrantActiveBodiesAccess grant_active(true, false);1992#endif19931994uint32 num_active_bodies_after_find_collisions = ioStep->mActiveBodyReadIdx;1995TempAllocator *temp_allocator = ioContext->mTempAllocator;19961997// Check if there's anything to do1998uint num_ccd_bodies = ioStep->mNumCCDBodies;1999if (num_ccd_bodies > 0)2000{2001// Sort on fraction so that we process earliest collisions first2002// This is needed to make the simulation deterministic and also to be able to stop contact processing2003// between body pairs if an earlier hit was found involving the body by another CCD body2004// (if it's body ID < this CCD body's body ID - see filtering logic in CCDBroadPhaseCollector)2005CCDBody **sorted_ccd_bodies = (CCDBody **)temp_allocator->Allocate(num_ccd_bodies * sizeof(CCDBody *));2006JPH_SCOPE_EXIT([temp_allocator, sorted_ccd_bodies, num_ccd_bodies]{ temp_allocator->Free(sorted_ccd_bodies, num_ccd_bodies * sizeof(CCDBody *)); });2007{2008JPH_PROFILE("Sort");20092010// We don't want to copy the entire struct (it's quite big), so we create a pointer array first2011CCDBody *src_ccd_bodies = ioStep->mCCDBodies;2012CCDBody **dst_ccd_bodies = sorted_ccd_bodies;2013CCDBody **dst_ccd_bodies_end = dst_ccd_bodies + num_ccd_bodies;2014while (dst_ccd_bodies < dst_ccd_bodies_end)2015*(dst_ccd_bodies++) = src_ccd_bodies++;20162017// Which we then sort2018QuickSort(sorted_ccd_bodies, sorted_ccd_bodies + num_ccd_bodies, [](const CCDBody *inBody1, const CCDBody *inBody2)2019{2020if (inBody1->mFractionPlusSlop != inBody2->mFractionPlusSlop)2021return inBody1->mFractionPlusSlop < inBody2->mFractionPlusSlop;20222023return inBody1->mBodyID1 < inBody2->mBodyID1;2024});2025}20262027// We can collide with bodies that are not active, we track them here so we can activate them in one go at the end.2028// This is also needed because we can't modify the active body array while we iterate it.2029static constexpr int cBodiesBatch = 64;2030BodyID *bodies_to_activate = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));2031int num_bodies_to_activate = 0;20322033// We can move bodies that are not part of an island. In this case we need to notify the broadphase of the movement.2034BodyID *bodies_to_update_bounds = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));2035int num_bodies_to_update_bounds = 0;20362037for (uint i = 0; i < num_ccd_bodies; ++i)2038{2039const CCDBody *ccd_body = sorted_ccd_bodies[i];2040Body &body1 = mBodyManager.GetBody(ccd_body->mBodyID1);2041MotionProperties *body_mp = body1.GetMotionProperties();20422043// If there was a hit2044if (!ccd_body->mBodyID2.IsInvalid())2045{2046Body &body2 = mBodyManager.GetBody(ccd_body->mBodyID2);20472048// Determine if the other body has a CCD body2049CCDBody *ccd_body2 = sGetCCDBody(body2, ioStep);2050if (ccd_body2 != nullptr)2051{2052JPH_ASSERT(ccd_body2->mBodyID2 != ccd_body->mBodyID1, "If we collided with another body, that other body should have ignored collisions with us!");20532054// Check if the other body found a hit that is further away2055if (ccd_body2->mFraction > ccd_body->mFraction)2056{2057// 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).2058// This means that at this point we have triggered a contact point add/persist for our further hit by accident for the other body.2059// 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.2060ccd_body2->mBodyID2 = BodyID();2061ccd_body2->mFractionPlusSlop = ccd_body->mFraction;2062}2063}20642065// 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.2066// 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.2067if (ccd_body2 == nullptr || ccd_body2->mFraction >= ccd_body->mFraction)2068{2069const ContactSettings &contact_settings = ccd_body->mContactSettings;20702071// Calculate contact point velocity for body 12072Vec3 r1_plus_u = Vec3(ccd_body->mContactPointOn2 - (body1.GetCenterOfMassPosition() + ccd_body->mFraction * ccd_body->mDeltaPosition));2073Vec3 v1 = body1.GetPointVelocityCOM(r1_plus_u);20742075// Calculate inverse mass for body 12076float inv_m1 = contact_settings.mInvMassScale1 * body_mp->GetInverseMass();20772078if (body2.IsRigidBody())2079{2080// Calculate contact point velocity for body 22081Vec3 r2 = Vec3(ccd_body->mContactPointOn2 - body2.GetCenterOfMassPosition());2082Vec3 v2 = body2.GetPointVelocityCOM(r2);20832084// Calculate relative contact velocity2085Vec3 relative_velocity = v2 - v1;2086float normal_velocity = relative_velocity.Dot(ccd_body->mContactNormal);20872088// Calculate velocity bias due to restitution2089float normal_velocity_bias;2090if (contact_settings.mCombinedRestitution > 0.0f && normal_velocity < -mPhysicsSettings.mMinVelocityForRestitution)2091normal_velocity_bias = contact_settings.mCombinedRestitution * normal_velocity;2092else2093normal_velocity_bias = 0.0f;20942095// Get inverse mass of body 22096float inv_m2 = body2.GetMotionPropertiesUnchecked() != nullptr? contact_settings.mInvMassScale2 * body2.GetMotionPropertiesUnchecked()->GetInverseMassUnchecked() : 0.0f;20972098// Solve contact constraint2099AxisConstraintPart contact_constraint;2100contact_constraint.CalculateConstraintPropertiesWithMassOverride(body1, inv_m1, contact_settings.mInvInertiaScale1, r1_plus_u, body2, inv_m2, contact_settings.mInvInertiaScale2, r2, ccd_body->mContactNormal, normal_velocity_bias);2101contact_constraint.SolveVelocityConstraintWithMassOverride(body1, inv_m1, body2, inv_m2, ccd_body->mContactNormal, -FLT_MAX, FLT_MAX);21022103// Apply friction2104if (contact_settings.mCombinedFriction > 0.0f)2105{2106// Calculate friction direction by removing normal velocity from the relative velocity2107Vec3 friction_direction = relative_velocity - normal_velocity * ccd_body->mContactNormal;2108float friction_direction_len_sq = friction_direction.LengthSq();2109if (friction_direction_len_sq > 1.0e-12f)2110{2111// Normalize friction direction2112friction_direction /= sqrt(friction_direction_len_sq);21132114// Calculate max friction impulse2115float max_lambda_f = contact_settings.mCombinedFriction * contact_constraint.GetTotalLambda();21162117AxisConstraintPart friction;2118friction.CalculateConstraintPropertiesWithMassOverride(body1, inv_m1, contact_settings.mInvInertiaScale1, r1_plus_u, body2, inv_m2, contact_settings.mInvInertiaScale2, r2, friction_direction);2119friction.SolveVelocityConstraintWithMassOverride(body1, inv_m1, body2, inv_m2, friction_direction, -max_lambda_f, max_lambda_f);2120}2121}21222123// Clamp velocity of body 22124if (body2.IsDynamic())2125{2126MotionProperties *body2_mp = body2.GetMotionProperties();2127body2_mp->ClampLinearVelocity();2128body2_mp->ClampAngularVelocity();2129}2130}2131else2132{2133SoftBodyMotionProperties *soft_mp = static_cast<SoftBodyMotionProperties *>(body2.GetMotionProperties());2134const SoftBodyShape *soft_shape = static_cast<const SoftBodyShape *>(body2.GetShape());21352136// Convert the sub shape ID of the soft body to a face2137uint32 face_idx = soft_shape->GetFaceIndex(ccd_body->mSubShapeID2);2138const SoftBodyMotionProperties::Face &face = soft_mp->GetFace(face_idx);21392140// Get vertices of the face2141SoftBodyMotionProperties::Vertex &vtx0 = soft_mp->GetVertex(face.mVertex[0]);2142SoftBodyMotionProperties::Vertex &vtx1 = soft_mp->GetVertex(face.mVertex[1]);2143SoftBodyMotionProperties::Vertex &vtx2 = soft_mp->GetVertex(face.mVertex[2]);21442145// Inverse mass of the face2146float vtx0_mass = vtx0.mInvMass > 0.0f? 1.0f / vtx0.mInvMass : 1.0e10f;2147float vtx1_mass = vtx1.mInvMass > 0.0f? 1.0f / vtx1.mInvMass : 1.0e10f;2148float vtx2_mass = vtx2.mInvMass > 0.0f? 1.0f / vtx2.mInvMass : 1.0e10f;2149float inv_m2 = 1.0f / (vtx0_mass + vtx1_mass + vtx2_mass);21502151// Calculate barycentric coordinates of the contact point on the soft body's face2152float u, v, w;2153RMat44 inv_body2_transform = body2.GetInverseCenterOfMassTransform();2154Vec3 local_contact = Vec3(inv_body2_transform * ccd_body->mContactPointOn2);2155ClosestPoint::GetBaryCentricCoordinates(vtx0.mPosition - local_contact, vtx1.mPosition - local_contact, vtx2.mPosition - local_contact, u, v, w);21562157// Calculate contact point velocity for the face2158Vec3 v2 = inv_body2_transform.Multiply3x3Transposed(u * vtx0.mVelocity + v * vtx1.mVelocity + w * vtx2.mVelocity);2159float normal_velocity = (v2 - v1).Dot(ccd_body->mContactNormal);21602161// Calculate velocity bias due to restitution2162float normal_velocity_bias;2163if (contact_settings.mCombinedRestitution > 0.0f && normal_velocity < -mPhysicsSettings.mMinVelocityForRestitution)2164normal_velocity_bias = contact_settings.mCombinedRestitution * normal_velocity;2165else2166normal_velocity_bias = 0.0f;21672168// 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)2169Vec3 r1_plus_u_x_n = r1_plus_u.Cross(ccd_body->mContactNormal);2170Vec3 invi1_r1_plus_u_x_n = contact_settings.mInvInertiaScale1 * body1.GetInverseInertia().Multiply3x3(r1_plus_u_x_n);2171float jv = r1_plus_u_x_n.Dot(body_mp->GetAngularVelocity()) - normal_velocity - normal_velocity_bias;2172float inv_effective_mass = inv_m1 + inv_m2 + invi1_r1_plus_u_x_n.Dot(r1_plus_u_x_n);2173float lambda = jv / inv_effective_mass;2174body_mp->SubLinearVelocityStep((lambda * inv_m1) * ccd_body->mContactNormal);2175body_mp->SubAngularVelocityStep(lambda * invi1_r1_plus_u_x_n);2176Vec3 delta_v2 = inv_body2_transform.Multiply3x3(lambda * ccd_body->mContactNormal);2177vtx0.mVelocity += delta_v2 * vtx0.mInvMass;2178vtx1.mVelocity += delta_v2 * vtx1.mInvMass;2179vtx2.mVelocity += delta_v2 * vtx2.mInvMass;2180}21812182// Clamp velocity of body 12183body_mp->ClampLinearVelocity();2184body_mp->ClampAngularVelocity();21852186// Activate the 2nd body if it is not already active2187if (body2.IsDynamic() && !body2.IsActive())2188{2189bodies_to_activate[num_bodies_to_activate++] = ccd_body->mBodyID2;2190if (num_bodies_to_activate == cBodiesBatch)2191{2192// Batch is full, activate now2193mBodyManager.ActivateBodies(bodies_to_activate, num_bodies_to_activate);2194num_bodies_to_activate = 0;2195}2196}21972198#ifdef JPH_DEBUG_RENDERER2199if (sDrawMotionQualityLinearCast)2200{2201// Draw the collision location2202RMat44 collision_transform = body1.GetCenterOfMassTransform().PostTranslated(ccd_body->mFraction * ccd_body->mDeltaPosition);2203body1.GetShape()->Draw(DebugRenderer::sInstance, collision_transform, Vec3::sOne(), Color::sYellow, false, true);22042205// Draw the collision location + slop2206RMat44 collision_transform_plus_slop = body1.GetCenterOfMassTransform().PostTranslated(ccd_body->mFractionPlusSlop * ccd_body->mDeltaPosition);2207body1.GetShape()->Draw(DebugRenderer::sInstance, collision_transform_plus_slop, Vec3::sOne(), Color::sOrange, false, true);22082209// Draw contact normal2210DebugRenderer::sInstance->DrawArrow(ccd_body->mContactPointOn2, ccd_body->mContactPointOn2 - ccd_body->mContactNormal, Color::sYellow, 0.1f);22112212// Draw post contact velocity2213DebugRenderer::sInstance->DrawArrow(collision_transform.GetTranslation(), collision_transform.GetTranslation() + body1.GetLinearVelocity(), Color::sOrange, 0.1f);2214DebugRenderer::sInstance->DrawArrow(collision_transform.GetTranslation(), collision_transform.GetTranslation() + body1.GetAngularVelocity(), Color::sPurple, 0.1f);2215}2216#endif // JPH_DEBUG_RENDERER2217}2218}22192220// Update body position2221body1.AddPositionStep(ccd_body->mDeltaPosition * ccd_body->mFractionPlusSlop);22222223// If the body was activated due to an earlier CCD step it will have an index in the active2224// body list that it higher than the highest one we processed during FindCollisions2225// which means it hasn't been assigned an island and will not be updated by an island2226// this means that we need to update its bounds manually2227if (body_mp->GetIndexInActiveBodiesInternal() >= num_active_bodies_after_find_collisions)2228{2229body1.CalculateWorldSpaceBoundsInternal();2230bodies_to_update_bounds[num_bodies_to_update_bounds++] = body1.GetID();2231if (num_bodies_to_update_bounds == cBodiesBatch)2232{2233// Buffer full, flush now2234mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);2235num_bodies_to_update_bounds = 0;2236}2237}2238}22392240// Activate the requested bodies2241if (num_bodies_to_activate > 0)2242mBodyManager.ActivateBodies(bodies_to_activate, num_bodies_to_activate);22432244// Notify change bounds on requested bodies2245if (num_bodies_to_update_bounds > 0)2246mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);2247}22482249// Ensure we free the CCD bodies array now, will not call the destructor!2250temp_allocator->Free(ioStep->mActiveBodyToCCDBody, ioStep->mNumActiveBodyToCCDBody * sizeof(int));2251ioStep->mActiveBodyToCCDBody = nullptr;2252ioStep->mNumActiveBodyToCCDBody = 0;2253temp_allocator->Free(ioStep->mCCDBodies, ioStep->mCCDBodiesCapacity * sizeof(CCDBody));2254ioStep->mCCDBodies = nullptr;2255ioStep->mCCDBodiesCapacity = 0;2256}22572258void PhysicsSystem::JobContactRemovedCallbacks(const PhysicsUpdateContext::Step *ioStep)2259{2260#ifdef JPH_ENABLE_ASSERTS2261// We don't touch any bodies2262BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::None);2263#endif22642265// Reset the Body::EFlags::InvalidateContactCache flag for all bodies2266mBodyManager.ValidateContactCacheForAllBodies();22672268// Finalize the contact cache (this swaps the read and write versions of the contact cache)2269// Trigger all contact removed callbacks by looking at last step contact points that have not been flagged as reused2270mContactManager.FinalizeContactCacheAndCallContactPointRemovedCallbacks(ioStep->mNumBodyPairs, ioStep->mNumManifolds);2271}22722273class PhysicsSystem::BodiesToSleep : public NonCopyable2274{2275public:2276static constexpr int cBodiesToSleepSize = 512;2277static constexpr int cMaxBodiesToPutInBuffer = 128;22782279inline BodiesToSleep(BodyManager &inBodyManager, BodyID *inBodiesToSleepBuffer) : mBodyManager(inBodyManager), mBodiesToSleepBuffer(inBodiesToSleepBuffer), mBodiesToSleepCur(inBodiesToSleepBuffer) { }22802281inline ~BodiesToSleep()2282{2283// Flush the bodies to sleep buffer2284int num_bodies_in_buffer = int(mBodiesToSleepCur - mBodiesToSleepBuffer);2285if (num_bodies_in_buffer > 0)2286mBodyManager.DeactivateBodies(mBodiesToSleepBuffer, num_bodies_in_buffer);2287}22882289inline void PutToSleep(const BodyID *inBegin, const BodyID *inEnd)2290{2291int num_bodies_to_sleep = int(inEnd - inBegin);2292if (num_bodies_to_sleep > cMaxBodiesToPutInBuffer)2293{2294// Too many bodies, deactivate immediately2295mBodyManager.DeactivateBodies(inBegin, num_bodies_to_sleep);2296}2297else2298{2299// Check if there's enough space in the bodies to sleep buffer2300int num_bodies_in_buffer = int(mBodiesToSleepCur - mBodiesToSleepBuffer);2301if (num_bodies_in_buffer + num_bodies_to_sleep > cBodiesToSleepSize)2302{2303// Flush the bodies to sleep buffer2304mBodyManager.DeactivateBodies(mBodiesToSleepBuffer, num_bodies_in_buffer);2305mBodiesToSleepCur = mBodiesToSleepBuffer;2306}23072308// Copy the bodies in the buffer2309memcpy(mBodiesToSleepCur, inBegin, num_bodies_to_sleep * sizeof(BodyID));2310mBodiesToSleepCur += num_bodies_to_sleep;2311}2312}23132314private:2315BodyManager & mBodyManager;2316BodyID * mBodiesToSleepBuffer;2317BodyID * mBodiesToSleepCur;2318};23192320void PhysicsSystem::CheckSleepAndUpdateBounds(uint32 inIslandIndex, const PhysicsUpdateContext *ioContext, const PhysicsUpdateContext::Step *ioStep, BodiesToSleep &ioBodiesToSleep)2321{2322// Get the bodies that belong to this island2323BodyID *bodies_begin, *bodies_end;2324mIslandBuilder.GetBodiesInIsland(inIslandIndex, bodies_begin, bodies_end);23252326// Only check sleeping in the last step2327// Also resets force and torque used during the apply gravity phase2328if (ioStep->mIsLast)2329{2330JPH_PROFILE("Check Sleeping");23312332static_assert(int(ECanSleep::CannotSleep) == 0 && int(ECanSleep::CanSleep) == 1, "Loop below makes this assumption");2333int all_can_sleep = mPhysicsSettings.mAllowSleeping? int(ECanSleep::CanSleep) : int(ECanSleep::CannotSleep);23342335float time_before_sleep = mPhysicsSettings.mTimeBeforeSleep;2336float max_movement = mPhysicsSettings.mPointVelocitySleepThreshold * time_before_sleep;23372338for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)2339{2340Body &body = mBodyManager.GetBody(*body_id);23412342// Update bounding box2343body.CalculateWorldSpaceBoundsInternal();23442345// Update sleeping2346all_can_sleep &= int(body.UpdateSleepStateInternal(ioContext->mStepDeltaTime, max_movement, time_before_sleep));23472348// Reset force and torque2349MotionProperties *mp = body.GetMotionProperties();2350mp->ResetForce();2351mp->ResetTorque();2352}23532354// If all bodies indicate they can sleep we can deactivate them2355if (all_can_sleep == int(ECanSleep::CanSleep))2356ioBodiesToSleep.PutToSleep(bodies_begin, bodies_end);2357}2358else2359{2360JPH_PROFILE("Update Bounds");23612362// Update bounding box only for all other steps2363for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)2364{2365Body &body = mBodyManager.GetBody(*body_id);2366body.CalculateWorldSpaceBoundsInternal();2367}2368}23692370// Notify broadphase of changed objects (find ccd contacts can do linear casts in the next step, so we need to do this every step)2371// Note: Shuffles the BodyID's around!!!2372mBroadPhase->NotifyBodiesAABBChanged(bodies_begin, int(bodies_end - bodies_begin), false);2373}23742375void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)2376{2377#ifdef JPH_ENABLE_ASSERTS2378// We fix up position errors2379BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::ReadWrite);23802381// Can only deactivate bodies2382BodyManager::GrantActiveBodiesAccess grant_active(false, true);2383#endif23842385float delta_time = ioContext->mStepDeltaTime;2386float baumgarte = mPhysicsSettings.mBaumgarte;2387Constraint **active_constraints = ioContext->mActiveConstraints;23882389// 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 threads2390BodiesToSleep bodies_to_sleep(mBodyManager, (BodyID *)JPH_STACK_ALLOC(BodiesToSleep::cBodiesToSleepSize * sizeof(BodyID)));23912392bool check_islands = true, check_split_islands = mPhysicsSettings.mUseLargeIslandSplitter;2393for (;;)2394{2395// First try to get work from large islands2396if (check_split_islands)2397{2398bool first_iteration;2399uint split_island_index;2400uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;2401switch (mLargeIslandSplitter.FetchNextBatch(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, first_iteration))2402{2403case LargeIslandSplitter::EStatus::BatchRetrieved:2404// Solve the batch2405ConstraintManager::sSolvePositionConstraints(active_constraints, constraints_begin, constraints_end, delta_time, baumgarte);2406mContactManager.SolvePositionConstraints(contacts_begin, contacts_end);24072408// Mark the batch as processed2409bool last_iteration, final_batch;2410mLargeIslandSplitter.MarkBatchProcessed(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, last_iteration, final_batch);24112412// The final batch will update all bounds and check sleeping2413if (final_batch)2414CheckSleepAndUpdateBounds(mLargeIslandSplitter.GetIslandIndex(split_island_index), ioContext, ioStep, bodies_to_sleep);24152416// We processed work, loop again2417continue;24182419case LargeIslandSplitter::EStatus::WaitingForBatch:2420break;24212422case LargeIslandSplitter::EStatus::AllBatchesDone:2423check_split_islands = false;2424break;2425}2426}24272428// If that didn't succeed try to process an island2429if (check_islands)2430{2431// Next island2432uint32 island_idx = ioStep->mSolvePositionConstraintsNextIsland++;2433if (island_idx >= mIslandBuilder.GetNumIslands())2434{2435// We processed all islands, stop checking islands2436check_islands = false;2437continue;2438}24392440JPH_PROFILE("Island");24412442// Get iterators for this island2443uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;2444mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end);2445mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end);24462447// If this island is a large island, it will be picked up as a batch and we don't need to do anything here2448uint num_items = uint(constraints_end - constraints_begin) + uint(contacts_end - contacts_begin);2449if (mPhysicsSettings.mUseLargeIslandSplitter2450&& num_items >= LargeIslandSplitter::cLargeIslandTreshold)2451continue;24522453// Check if this island needs solving2454if (num_items > 0)2455{2456// Iterate2457uint num_position_steps = mIslandBuilder.GetNumPositionSteps(island_idx);2458for (uint position_step = 0; position_step < num_position_steps; ++position_step)2459{2460bool applied_impulse = ConstraintManager::sSolvePositionConstraints(active_constraints, constraints_begin, constraints_end, delta_time, baumgarte);2461applied_impulse |= mContactManager.SolvePositionConstraints(contacts_begin, contacts_end);2462if (!applied_impulse)2463break;2464}2465}24662467// After solving we will update all bounds and check sleeping2468CheckSleepAndUpdateBounds(island_idx, ioContext, ioStep, bodies_to_sleep);24692470// We processed work, loop again2471continue;2472}24732474if (check_islands)2475{2476// If there are islands, we don't need to wait and can pick up new work2477continue;2478}2479else if (check_split_islands)2480{2481// If there are split islands, but we didn't do any work, give up a time slice2482std::this_thread::yield();2483}2484else2485{2486// No more work2487break;2488}2489}2490}24912492void PhysicsSystem::JobSoftBodyPrepare(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)2493{2494JPH_PROFILE_FUNCTION();24952496{2497#ifdef JPH_ENABLE_ASSERTS2498// Reading soft body positions2499BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::Read);2500#endif25012502// Get the active soft bodies2503BodyIDVector active_bodies;2504mBodyManager.GetActiveBodies(EBodyType::SoftBody, active_bodies);25052506// Quit if there are no active soft bodies2507if (active_bodies.empty())2508{2509// Kick the next step2510if (ioStep->mStartNextStep.IsValid())2511ioStep->mStartNextStep.RemoveDependency();2512return;2513}25142515// Sort to get a deterministic update order2516QuickSort(active_bodies.begin(), active_bodies.end());25172518// Allocate soft body contexts2519ioContext->mNumSoftBodies = (uint)active_bodies.size();2520ioContext->mSoftBodyUpdateContexts = (SoftBodyUpdateContext *)ioContext->mTempAllocator->Allocate(ioContext->mNumSoftBodies * sizeof(SoftBodyUpdateContext));25212522// Initialize soft body contexts2523for (SoftBodyUpdateContext *sb_ctx = ioContext->mSoftBodyUpdateContexts, *sb_ctx_end = ioContext->mSoftBodyUpdateContexts + ioContext->mNumSoftBodies; sb_ctx < sb_ctx_end; ++sb_ctx)2524{2525new (sb_ctx) SoftBodyUpdateContext;2526Body &body = mBodyManager.GetBody(active_bodies[sb_ctx - ioContext->mSoftBodyUpdateContexts]);2527SoftBodyMotionProperties *mp = static_cast<SoftBodyMotionProperties *>(body.GetMotionProperties());2528mp->InitializeUpdateContext(ioContext->mStepDeltaTime, body, *this, *sb_ctx);2529}2530}25312532// We're ready to collide the first soft body2533ioContext->mSoftBodyToCollide.store(0, memory_order_release);25342535// Determine number of jobs to spawn2536int num_soft_body_jobs = ioContext->GetMaxConcurrency();25372538// Create finalize job2539ioStep->mSoftBodyFinalize = ioContext->mJobSystem->CreateJob("SoftBodyFinalize", cColorSoftBodyFinalize, [ioContext, ioStep]()2540{2541ioContext->mPhysicsSystem->JobSoftBodyFinalize(ioContext);25422543// Kick the next step2544if (ioStep->mStartNextStep.IsValid())2545ioStep->mStartNextStep.RemoveDependency();2546}, num_soft_body_jobs); // depends on: soft body simulate2547ioContext->mBarrier->AddJob(ioStep->mSoftBodyFinalize);25482549// Create simulate jobs2550ioStep->mSoftBodySimulate.resize(num_soft_body_jobs);2551for (int i = 0; i < num_soft_body_jobs; ++i)2552ioStep->mSoftBodySimulate[i] = ioContext->mJobSystem->CreateJob("SoftBodySimulate", cColorSoftBodySimulate, [ioStep, i]()2553{2554ioStep->mContext->mPhysicsSystem->JobSoftBodySimulate(ioStep->mContext, i);25552556ioStep->mSoftBodyFinalize.RemoveDependency();2557}, num_soft_body_jobs); // depends on: soft body collide2558ioContext->mBarrier->AddJobs(ioStep->mSoftBodySimulate.data(), ioStep->mSoftBodySimulate.size());25592560// Create collision jobs2561ioStep->mSoftBodyCollide.resize(num_soft_body_jobs);2562for (int i = 0; i < num_soft_body_jobs; ++i)2563ioStep->mSoftBodyCollide[i] = ioContext->mJobSystem->CreateJob("SoftBodyCollide", cColorSoftBodyCollide, [ioContext, ioStep]()2564{2565ioContext->mPhysicsSystem->JobSoftBodyCollide(ioContext);25662567for (const JobHandle &h : ioStep->mSoftBodySimulate)2568h.RemoveDependency();2569}); // depends on: nothing2570ioContext->mBarrier->AddJobs(ioStep->mSoftBodyCollide.data(), ioStep->mSoftBodyCollide.size());2571}25722573void PhysicsSystem::JobSoftBodyCollide(PhysicsUpdateContext *ioContext) const2574{2575#ifdef JPH_ENABLE_ASSERTS2576// Reading rigid body positions and velocities2577BodyAccess::Grant grant(BodyAccess::EAccess::Read, BodyAccess::EAccess::Read);2578#endif25792580for (;;)2581{2582// Fetch the next soft body2583uint sb_idx = ioContext->mSoftBodyToCollide.fetch_add(1, std::memory_order_acquire);2584if (sb_idx >= ioContext->mNumSoftBodies)2585break;25862587// Do a broadphase check2588SoftBodyUpdateContext &sb_ctx = ioContext->mSoftBodyUpdateContexts[sb_idx];2589sb_ctx.mMotionProperties->DetermineCollidingShapes(sb_ctx, *this, GetBodyLockInterfaceNoLock());2590}2591}25922593void PhysicsSystem::JobSoftBodySimulate(PhysicsUpdateContext *ioContext, uint inThreadIndex) const2594{2595#ifdef JPH_ENABLE_ASSERTS2596// Updating velocities of soft bodies, allow the contact listener to read the soft body state2597BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::Read);2598#endif25992600// Calculate at which body we start to distribute the workload across the threads2601uint num_soft_bodies = ioContext->mNumSoftBodies;2602uint start_idx = inThreadIndex * num_soft_bodies / ioContext->GetMaxConcurrency();26032604// Keep running partial updates until everything has been updated2605uint status;2606do2607{2608// Reset status2609status = 0;26102611// Update all soft bodies2612for (uint i = 0; i < num_soft_bodies; ++i)2613{2614// Fetch the soft body context2615SoftBodyUpdateContext &sb_ctx = ioContext->mSoftBodyUpdateContexts[(start_idx + i) % num_soft_bodies];26162617// To avoid trashing the cache too much, we prefer to stick to one soft body until we cannot progress it any further2618uint sb_status;2619do2620{2621sb_status = (uint)sb_ctx.mMotionProperties->ParallelUpdate(sb_ctx, mPhysicsSettings);2622status |= sb_status;2623} while (sb_status == (uint)SoftBodyMotionProperties::EStatus::DidWork);2624}26252626// If we didn't perform any work, yield the thread so that something else can run2627if (!(status & (uint)SoftBodyMotionProperties::EStatus::DidWork))2628std::this_thread::yield();2629}2630while (status != (uint)SoftBodyMotionProperties::EStatus::Done);2631}26322633void PhysicsSystem::JobSoftBodyFinalize(PhysicsUpdateContext *ioContext)2634{2635#ifdef JPH_ENABLE_ASSERTS2636// Updating rigid body velocities and soft body positions / velocities2637BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::ReadWrite);26382639// Can activate and deactivate bodies2640BodyManager::GrantActiveBodiesAccess grant_active(true, true);2641#endif26422643static constexpr int cBodiesBatch = 64;2644BodyID *bodies_to_update_bounds = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));2645int num_bodies_to_update_bounds = 0;2646BodyID *bodies_to_put_to_sleep = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));2647int num_bodies_to_put_to_sleep = 0;26482649for (SoftBodyUpdateContext *sb_ctx = ioContext->mSoftBodyUpdateContexts, *sb_ctx_end = ioContext->mSoftBodyUpdateContexts + ioContext->mNumSoftBodies; sb_ctx < sb_ctx_end; ++sb_ctx)2650{2651// Apply the rigid body velocity deltas2652sb_ctx->mMotionProperties->UpdateRigidBodyVelocities(*sb_ctx, GetBodyInterfaceNoLock());26532654// Update the position2655sb_ctx->mBody->SetPositionAndRotationInternal(sb_ctx->mBody->GetPosition() + sb_ctx->mDeltaPosition, sb_ctx->mBody->GetRotation(), false);26562657BodyID id = sb_ctx->mBody->GetID();2658bodies_to_update_bounds[num_bodies_to_update_bounds++] = id;2659if (num_bodies_to_update_bounds == cBodiesBatch)2660{2661// Buffer full, flush now2662mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);2663num_bodies_to_update_bounds = 0;2664}26652666if (sb_ctx->mCanSleep == ECanSleep::CanSleep)2667{2668// This body should go to sleep2669bodies_to_put_to_sleep[num_bodies_to_put_to_sleep++] = id;2670if (num_bodies_to_put_to_sleep == cBodiesBatch)2671{2672mBodyManager.DeactivateBodies(bodies_to_put_to_sleep, num_bodies_to_put_to_sleep);2673num_bodies_to_put_to_sleep = 0;2674}2675}2676}26772678// Notify change bounds on requested bodies2679if (num_bodies_to_update_bounds > 0)2680mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);26812682// Notify bodies to go to sleep2683if (num_bodies_to_put_to_sleep > 0)2684mBodyManager.DeactivateBodies(bodies_to_put_to_sleep, num_bodies_to_put_to_sleep);26852686// Free soft body contexts2687ioContext->mTempAllocator->Free(ioContext->mSoftBodyUpdateContexts, ioContext->mNumSoftBodies * sizeof(SoftBodyUpdateContext));2688}26892690void PhysicsSystem::SaveState(StateRecorder &inStream, EStateRecorderState inState, const StateRecorderFilter *inFilter) const2691{2692JPH_PROFILE_FUNCTION();26932694inStream.Write(inState);26952696if (uint8(inState) & uint8(EStateRecorderState::Global))2697{2698inStream.Write(mPreviousStepDeltaTime);2699inStream.Write(mGravity);2700}27012702if (uint8(inState) & uint8(EStateRecorderState::Bodies))2703mBodyManager.SaveState(inStream, inFilter);27042705if (uint8(inState) & uint8(EStateRecorderState::Contacts))2706mContactManager.SaveState(inStream, inFilter);27072708if (uint8(inState) & uint8(EStateRecorderState::Constraints))2709mConstraintManager.SaveState(inStream, inFilter);2710}27112712bool PhysicsSystem::RestoreState(StateRecorder &inStream, const StateRecorderFilter *inFilter)2713{2714JPH_PROFILE_FUNCTION();27152716EStateRecorderState state = EStateRecorderState::All; // Set this value for validation. If a partial state is saved, validation will not work anyway.2717inStream.Read(state);27182719if (uint8(state) & uint8(EStateRecorderState::Global))2720{2721inStream.Read(mPreviousStepDeltaTime);2722inStream.Read(mGravity);2723}27242725if (uint8(state) & uint8(EStateRecorderState::Bodies))2726{2727if (!mBodyManager.RestoreState(inStream))2728return false;27292730// Update bounding boxes for all bodies in the broadphase2731if (inStream.IsLastPart())2732{2733Array<BodyID> bodies;2734for (const Body *b : mBodyManager.GetBodies())2735if (BodyManager::sIsValidBodyPointer(b) && b->IsInBroadPhase())2736bodies.push_back(b->GetID());2737if (!bodies.empty())2738mBroadPhase->NotifyBodiesAABBChanged(&bodies[0], (int)bodies.size());2739}2740}27412742if (uint8(state) & uint8(EStateRecorderState::Contacts))2743{2744if (!mContactManager.RestoreState(inStream, inFilter))2745return false;2746}27472748if (uint8(state) & uint8(EStateRecorderState::Constraints))2749{2750if (!mConstraintManager.RestoreState(inStream))2751return false;2752}27532754return true;2755}27562757void PhysicsSystem::SaveBodyState(const Body &inBody, StateRecorder &inStream) const2758{2759mBodyManager.SaveBodyState(inBody, inStream);2760}27612762void PhysicsSystem::RestoreBodyState(Body &ioBody, StateRecorder &inStream)2763{2764mBodyManager.RestoreBodyState(ioBody, inStream);27652766BodyID id = ioBody.GetID();2767mBroadPhase->NotifyBodiesAABBChanged(&id, 1);2768}27692770JPH_NAMESPACE_END277127722773