Parcourir la source

Removed support for integration sub steps for PhysicsSystem::Update (#593)

The inIntegrationSubSteps parameter of PhysicsSystem::Update is getting more and more in the way. Originally it was meant to run the physics simulation at a higher frequency without paying the cost of a collision detection step. I found it of limited use and it creates a lot of complications in the code, cost memory and CPU cycles (also if it's not being used). It didn't work with the large island splitter, it didn't work when overriding the masses in contact callbacks and it caused artifacts when heavy objects rest on light objects.
Jorrit Rouwe il y a 2 ans
Parent
commit
8fcc7a78ec

+ 9 - 17
Docs/Architecture.md

@@ -321,18 +321,14 @@ The filter functions are listed in the order they're called. To avoid work, try
 
 The simulation step [PhysicsSystem::Update](@ref PhysicsSystem::Update) uses jobs ([JobSystem](@ref JobSystem)) to perform the needed work. This allows spreading the workload across multiple CPU's. We use a Sequential Impulse solver with warm starting as described in [Modeling and Solving Constraints - Erin Catto](https://box2d.org/files/ErinCatto_ModelingAndSolvingConstraints_GDC2009.pdf)
 
-Each physics step can be divided into multiple sub steps. There are collision and integration steps. For each collision step we run X integration steps, so if you run the simulation at 60 Hz with 2 collision steps and 3 integration steps we run:
+Each physics step can be divided into multiple collision steps. So if you run the simulation at 60 Hz with 2 collision steps we run:
 
-* Collision
-	* Integration (1/360s)
-	* Integration (1/360s)
-	* Integration (1/360s)
-* Collision
-	* Integration (1/360s)
-	* Integration (1/360s)
-	* Integration (1/360s)
+* Collision (1/120s)
+* Integration (1/120s)
+* Collision (1/120s)
+* Integration (1/120s)
 
-In general, the system is stable when running at 60 Hz with 1 collision and 1 integration step.
+In general, the system is stable when running at 60 Hz with 1 collision step.
 
 ## Conventions and limits
 
@@ -483,9 +479,9 @@ This job does some housekeeping work that can be executed concurrent to the solv
 
 ### Solve Velocity Constraints
 
-A number of these jobs will run in parallel. Each job takes the next unprocessed island and will run the iterative constraint solver for that island. It will first apply the impulses applied from the previous simulation step (which are stored in the contact cache) to warm start the solver. It will then repeatedly iterate over all contact and non-contact constraints until either the applied impulses are too small or a max iteration count is reached ([PhysicsSettings::mNumVelocitySteps](@ref PhysicsSettings::mNumVelocitySteps)). The result will be that the new velocities are known for all active bodies. In the last integration step, the applied impulses are stored in the contact cache for the next step. 
+A number of these jobs will run in parallel. Each job takes the next unprocessed island and will run the iterative constraint solver for that island. It will first apply the impulses applied from the previous simulation step (which are stored in the contact cache) to warm start the solver. It will then repeatedly iterate over all contact and non-contact constraints until either the applied impulses are too small or a max iteration count is reached ([PhysicsSettings::mNumVelocitySteps](@ref PhysicsSettings::mNumVelocitySteps)). The result will be that the new velocities are known for all active bodies. The applied impulses are stored in the contact cache for the next step.
 
-When an island consists of more than LargeIslandSplitter::cLargeIslandTreshold contacts plus constraints it is considered a large island. In order to not do all work on a single thread, this island will be split up by the LargeIslandSplitter. This follows an algorithm described in High-Performance Physical Simulations on Next-Generation Architecture with Many Cores by Chen et al. This is basically a greedy algorithm that tries to group contacts and constraints into groups where no contact or constraint affects the same body. Within a group, the order of execution does not matter since every memory location is only read/written once, so we can parallelize the update. At the end of each group, we need to synchronize the CPU cores before starting on the next group. When the number of groups becomes too large, a final group is created that contains all other contacts and constraints and these are solved on a single thread. The groups are processed PhysicsSettings::mNumVelocitySteps times so the end result is almost the same as an island that was not split up (only the evalutation order changes in a consistent way). Note that the large island splitter can only be used when the number of integration sub steps passed to PhysicsSettings::Update is 1.
+When an island consists of more than LargeIslandSplitter::cLargeIslandTreshold contacts plus constraints it is considered a large island. In order to not do all work on a single thread, this island will be split up by the LargeIslandSplitter. This follows an algorithm described in High-Performance Physical Simulations on Next-Generation Architecture with Many Cores by Chen et al. This is basically a greedy algorithm that tries to group contacts and constraints into groups where no contact or constraint affects the same body. Within a group, the order of execution does not matter since every memory location is only read/written once, so we can parallelize the update. At the end of each group, we need to synchronize the CPU cores before starting on the next group. When the number of groups becomes too large, a final group is created that contains all other contacts and constraints and these are solved on a single thread. The groups are processed PhysicsSettings::mNumVelocitySteps times so the end result is almost the same as an island that was not split up (only the evalutation order changes in a consistent way).
 
 ### Pre Integrate
 
@@ -522,8 +518,4 @@ A number of these jobs will run in parallel. Each job takes the next unprocessed
 
 It will also notify the broad phase of the new body positions / AABBs. 
 
-When objects move too little the body will be put to sleep. This is detected by taking the biggest two axis of the local space bounding box of the shape together with the center of mass of the shape (all points in world space) and keep track of 3 bounding spheres for those points over time. If the bounding spheres become too big, the bounding spheres are reset and the timer restarted. When the timer reaches a certain time, the object has is considered non-moving and is put to sleep.  
-
-### Apply Gravity, Setup/Solve Velocity Constraints
-
-In the second and later integration sub steps, we combine the Apply Gravity, Setup Velocity Constraints and the Solve Velocity Constraints jobs. There are multiple of these jobs and they each process simulation islands until there are no more.
+When objects move too little the body will be put to sleep. This is detected by taking the biggest two axis of the local space bounding box of the shape together with the center of mass of the shape (all points in world space) and keep track of 3 bounding spheres for those points over time. If the bounding spheres become too big, the bounding spheres are reset and the timer restarted. When the timer reaches a certain time, the object has is considered non-moving and is put to sleep.

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
Docs/PhysicsSystemUpdate.drawio


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
Docs/PhysicsSystemUpdate.gliffy


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
Docs/PhysicsSystemUpdate.svg


+ 1 - 4
HelloWorld/HelloWorld.cpp

@@ -335,11 +335,8 @@ int main(int argc, char** argv)
 		// If you take larger steps than 1 / 60th of a second you need to do multiple collision steps in order to keep the simulation stable. Do 1 collision step per 1 / 60th of a second (round up).
 		const int cCollisionSteps = 1;
 
-		// If you want more accurate step results you can do multiple sub steps within a collision step. Usually you would set this to 1.
-		const int cIntegrationSubSteps = 1;
-
 		// Step the world
-		physics_system.Update(cDeltaTime, cCollisionSteps, cIntegrationSubSteps, &temp_allocator, &job_system);
+		physics_system.Update(cDeltaTime, cCollisionSteps, &temp_allocator, &job_system);
 	}
 
 	// Remove the sphere from the physics system. Note that the sphere itself keeps all of its state and can be re-added at any time.

+ 0 - 11
Jolt/Physics/Constraints/ConstraintManager.cpp

@@ -114,17 +114,6 @@ void ConstraintManager::sSetupVelocityConstraints(Constraint **inActiveConstrain
 		(*c)->SetupVelocityConstraint(inDeltaTime);
 }
 
-void ConstraintManager::sSetupVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inDeltaTime)
-{
-	JPH_PROFILE_FUNCTION();
-
-	for (const uint32 *constraint_idx = inConstraintIdxBegin; constraint_idx < inConstraintIdxEnd; ++constraint_idx)
-	{
-		Constraint *c = inActiveConstraints[*constraint_idx];
-		c->SetupVelocityConstraint(inDeltaTime);
-	}
-}
-
 void ConstraintManager::sWarmStartVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio)
 {
 	JPH_PROFILE_FUNCTION();

+ 0 - 3
Jolt/Physics/Constraints/ConstraintManager.h

@@ -56,9 +56,6 @@ public:
 	/// Prior to solving the velocity constraints, you must call SetupVelocityConstraints once to precalculate values that are independent of velocity
 	static void				sSetupVelocityConstraints(Constraint **inActiveConstraints, uint32 inNumActiveConstraints, float inDeltaTime);
 
-	/// Same as above, but applies to a limited amount of constraints only
-	static void				sSetupVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inDeltaTime);
-
 	/// Apply last frame's impulses, must be called prior to SolveVelocityConstraints
 	static void				sWarmStartVelocityConstraints(Constraint **inActiveConstraints, const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio);
 

+ 2 - 26
Jolt/Physics/Constraints/ContactConstraintManager.cpp

@@ -789,7 +789,7 @@ void ContactConstraintManager::GetContactsFromCache(ContactAllocator &ioContactA
 	RMat44 transform_body2 = body2->GetCenterOfMassTransform();
 
 	// Get time step
-	float delta_time = mUpdateContext->mSubStepDeltaTime;
+	float delta_time = mUpdateContext->mStepDeltaTime;
 
 	// Copy manifolds
 	uint32 output_handle = ManifoldMap::cInvalidHandle;
@@ -1076,7 +1076,7 @@ bool ContactConstraintManager::TemplatedAddContactConstraint(ContactAllocator &i
 		mUpdateContext->mIslandBuilder->LinkContact(constraint_idx, inBody1.GetIndexInActiveBodiesInternal(), inBody2.GetIndexInActiveBodiesInternal());
 
 		// Get time step
-		float delta_time = mUpdateContext->mSubStepDeltaTime;
+		float delta_time = mUpdateContext->mStepDeltaTime;
 
 		// Calculate scaled mass and inertia
 		float inv_m1;
@@ -1413,30 +1413,6 @@ bool ContactConstraintManager::WereBodiesInContact(const BodyID &inBody1ID, cons
 	return kv != nullptr && kv->GetValue().mFirstCachedManifold != ManifoldMap::cInvalidHandle;
 }
 
-void ContactConstraintManager::SetupVelocityConstraints(const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inDeltaTime)
-{
-	JPH_PROFILE_FUNCTION();
-
-	// Note: We don't have the settings anymore here, mass/inertia scaling doesn't work with multiple integration substeps!
-	ContactSettings dummy_settings;
-
-	for (const uint32 *constraint_idx = inConstraintIdxBegin; constraint_idx < inConstraintIdxEnd; ++constraint_idx)
-	{
-		ContactConstraint &constraint = mConstraints[*constraint_idx];
-
-		// Fetch bodies
-		const Body &body1 = *constraint.mBody1;
-		const Body &body2 = *constraint.mBody2;
-		
-		// Get body transforms
-		RMat44 transform_body1 = body1.GetCenterOfMassTransform();
-		RMat44 transform_body2 = body2.GetCenterOfMassTransform();
-
-		// Calculate friction and non-penetration constraint properties for all contact points
-		CalculateFrictionAndNonPenetrationConstraintProperties(constraint, dummy_settings, inDeltaTime, transform_body1, transform_body2, body1, body2);
-	}
-}
-
 template <EMotionType Type1, EMotionType Type2>
 JPH_INLINE void ContactConstraintManager::sWarmStartConstraint(ContactConstraint &ioConstraint, MotionProperties *ioMotionProperties1, MotionProperties *ioMotionProperties2, float inWarmStartImpulseRatio)
 {

+ 0 - 3
Jolt/Physics/Constraints/ContactConstraintManager.h

@@ -166,9 +166,6 @@ public:
 		outBody2 = constraint.mBody2;
 	}
 
-	/// AddContactConstraint will also setup the velocity constraints for the first sub step. For subsequent sub steps this function must be called prior to warm starting the constraint.
-	void						SetupVelocityConstraints(const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inDeltaTime);
-
 	/// Apply last frame's impulses as an initial guess for this frame's impulses
 	void						WarmStartVelocityConstraints(const uint32 *inConstraintIdxBegin, const uint32 *inConstraintIdxEnd, float inWarmStartImpulseRatio);
 

+ 185 - 266
Jolt/Physics/PhysicsSystem.cpp

@@ -55,9 +55,8 @@ static const Color cColorIntegrateVelocity = Color::sGetDistinctColor(14);
 static const Color cColorPostIntegrateVelocity = Color::sGetDistinctColor(15);
 static const Color cColorResolveCCDContacts = Color::sGetDistinctColor(16);
 static const Color cColorSolvePositionConstraints = Color::sGetDistinctColor(17);
-static const Color cColorStartNextSubStep = Color::sGetDistinctColor(18);
-static const Color cColorFindCCDContacts = Color::sGetDistinctColor(19);
-static const Color cColorStepListeners = Color::sGetDistinctColor(20);
+static const Color cColorFindCCDContacts = Color::sGetDistinctColor(18);
+static const Color cColorStepListeners = Color::sGetDistinctColor(19);
 
 PhysicsSystem::~PhysicsSystem()
 {
@@ -115,14 +114,14 @@ void PhysicsSystem::RemoveStepListener(PhysicsStepListener *inListener)
 	mStepListeners.pop_back();
 }
 
-EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionSteps, int inIntegrationSubSteps, TempAllocator *inTempAllocator, JobSystem *inJobSystem)
+EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionSteps, TempAllocator *inTempAllocator, JobSystem *inJobSystem)
 {	
 	JPH_PROFILE_FUNCTION();
 
-	JPH_DET_LOG("PhysicsSystem::Update: dt: " << inDeltaTime << " steps: " << inCollisionSteps << " substeps: " << inIntegrationSubSteps);
+	JPH_DET_LOG("PhysicsSystem::Update: dt: " << inDeltaTime << " steps: " << inCollisionSteps);
 
+	JPH_ASSERT(inCollisionSteps > 0);
 	JPH_ASSERT(inDeltaTime >= 0.0f);
-	JPH_ASSERT(inIntegrationSubSteps <= PhysicsUpdateContext::cMaxSubSteps);
 
 	// Sync point for the broadphase. This will allow it to do clean up operations without having any mutexes locked yet.
 	mBroadPhase->FrameSync();
@@ -147,9 +146,9 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 	}
 
 	// Calculate ratio between current and previous frame delta time to scale initial constraint forces
-	float sub_step_delta_time = inDeltaTime / (inCollisionSteps * inIntegrationSubSteps);
-	float warm_start_impulse_ratio = mPhysicsSettings.mConstraintWarmStart && mPreviousSubStepDeltaTime > 0.0f? sub_step_delta_time / mPreviousSubStepDeltaTime : 0.0f;
-	mPreviousSubStepDeltaTime = sub_step_delta_time;
+	float step_delta_time = inDeltaTime / inCollisionSteps;
+	float warm_start_impulse_ratio = mPhysicsSettings.mConstraintWarmStart && mPreviousStepDeltaTime > 0.0f? step_delta_time / mPreviousStepDeltaTime : 0.0f;
+	mPreviousStepDeltaTime = step_delta_time;
 
 	// Create the context used for passing information between jobs
 	PhysicsUpdateContext context(*inTempAllocator);
@@ -157,10 +156,8 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 	context.mJobSystem = inJobSystem;
 	context.mBarrier = inJobSystem->CreateBarrier();
 	context.mIslandBuilder = &mIslandBuilder;
-	context.mStepDeltaTime = inDeltaTime / inCollisionSteps;
-	context.mSubStepDeltaTime = sub_step_delta_time;
+	context.mStepDeltaTime = step_delta_time;
 	context.mWarmStartImpulseRatio = warm_start_impulse_ratio;
-	context.mUseLargeIslandSplitter = mPhysicsSettings.mUseLargeIslandSplitter && inIntegrationSubSteps == 1; // Only use large island splitter if we don't have sub steps, not yet supported
 	context.mSteps.resize(inCollisionSteps);
 
 	// Allocate space for body pairs
@@ -206,7 +203,8 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 
 			PhysicsUpdateContext::Step &step = context.mSteps[step_idx];
 			step.mContext = &context;
-			step.mSubSteps.resize(inIntegrationSubSteps);
+			step.mIsFirst = is_first_step;
+			step.mIsLast = is_last_step;
 
 			// Create job to do broadphase finalization
 			// This job must finish before integrating velocities. Until then the positions will not be updated neither will bodies be added / removed.
@@ -219,7 +217,7 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 					context.mPhysicsSystem->mBroadPhase->UpdateFinalize(step.mBroadPhaseUpdateState);
 
 					// Signal that it is done
-					step.mSubSteps[0].mPreIntegrateVelocity.RemoveDependency(); 
+					step.mPreIntegrateVelocity.RemoveDependency(); 
 				}, num_find_collisions_jobs + 2); // depends on: find collisions, broadphase prepare update, finish building jobs
 
 			// The immediate jobs below are only immediate for the first step, the all finished job will kick them for the next step
@@ -289,9 +287,9 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 			// This job will setup velocity constraints for non-collision constraints
 			step.mSetupVelocityConstraints = inJobSystem->CreateJob("SetupVelocityConstraints", cColorSetupVelocityConstraints, [&context, &step]() 
 				{ 
-					context.mPhysicsSystem->JobSetupVelocityConstraints(context.mSubStepDeltaTime, &step);
+					context.mPhysicsSystem->JobSetupVelocityConstraints(context.mStepDeltaTime, &step);
 
-					JobHandle::sRemoveDependencies(step.mSubSteps[0].mSolveVelocityConstraints);
+					JobHandle::sRemoveDependencies(step.mSolveVelocityConstraints);
 				}, num_determine_active_constraints_jobs + 1); // depends on: determine active constraints, finish building jobs
 
 			// This job will build islands from constraints
@@ -342,7 +340,7 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 
 					context.mPhysicsSystem->JobFinalizeIslands(&context); 
 
-					JobHandle::sRemoveDependencies(step.mSubSteps[0].mSolveVelocityConstraints);
+					JobHandle::sRemoveDependencies(step.mSolveVelocityConstraints);
 					step.mBodySetIslandIndex.RemoveDependency();
 				}, num_find_collisions_jobs + 2); // depends on: find collisions, build islands from constraints, finish building jobs
 
@@ -357,7 +355,7 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 
 					if (step.mStartNextStep.IsValid())
 						step.mStartNextStep.RemoveDependency();
-				}, 1); // depends on the find ccd contacts of the last sub step
+				}, 1); // depends on the find ccd contacts
 
 			// This job will set the island index on each body (only used for debug drawing purposes)
 			// It will also delete any bodies that have been destroyed in the last frame
@@ -412,125 +410,87 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 					}, max_concurrency + 3); // depends on: solve position constraints of the last step, body set island index, contact removed callbacks, finish building the previous step
 			}
 
-			// Create solve jobs for each of the integration sub steps
-			for (int sub_step_idx = 0; sub_step_idx < inIntegrationSubSteps; ++sub_step_idx)
-			{
-				bool is_first_sub_step = sub_step_idx == 0;
-				bool is_last_sub_step = sub_step_idx == inIntegrationSubSteps - 1;
-
-				PhysicsUpdateContext::SubStep &sub_step = step.mSubSteps[sub_step_idx];
-				sub_step.mStep = &step;
-				sub_step.mIsFirst = is_first_sub_step;
-				sub_step.mIsLast = is_last_sub_step;
-				sub_step.mIsFirstOfAll = is_first_step && is_first_sub_step;
-				sub_step.mIsLastOfAll = is_last_step && is_last_sub_step;
-
-				// This job will solve the velocity constraints 
-				int num_dependencies_solve_velocity_constraints = is_first_sub_step? 3 : 2; // in first sub step depends on: finalize islands, setup velocity constraints, in later sub steps depends on: previous sub step finished. For both: finish building jobs.
-				sub_step.mSolveVelocityConstraints.resize(max_concurrency);
-				for (int i = 0; i < max_concurrency; ++i)
-					sub_step.mSolveVelocityConstraints[i] = inJobSystem->CreateJob("SolveVelocityConstraints", cColorSolveVelocityConstraints, [&context, &sub_step]() 
-						{ 
-							context.mPhysicsSystem->JobSolveVelocityConstraints(&context, &sub_step); 
+			// This job will solve the velocity constraints 
+			step.mSolveVelocityConstraints.resize(max_concurrency);
+			for (int i = 0; i < max_concurrency; ++i)
+				step.mSolveVelocityConstraints[i] = inJobSystem->CreateJob("SolveVelocityConstraints", cColorSolveVelocityConstraints, [&context, &step]() 
+					{ 
+						context.mPhysicsSystem->JobSolveVelocityConstraints(&context, &step); 
 
-							sub_step.mPreIntegrateVelocity.RemoveDependency();
-						}, num_dependencies_solve_velocity_constraints); 
+						step.mPreIntegrateVelocity.RemoveDependency();
+					}, 3); // depends on: finalize islands, setup velocity constraints, finish building jobs.
 
-				// Unblock previous jobs
-				if (is_first_sub_step)
-				{
-					// Kick find collisions after setup velocity constraints because the former job will use up all CPU cores
-					step.mSetupVelocityConstraints.RemoveDependency();
-					JobHandle::sRemoveDependencies(step.mFindCollisions);
+			// Kick find collisions after setup velocity constraints because the former job will use up all CPU cores
+			step.mSetupVelocityConstraints.RemoveDependency();
+			JobHandle::sRemoveDependencies(step.mFindCollisions);
 
-					// Finalize islands is a dependency on find collisions so it can go last
-					step.mFinalizeIslands.RemoveDependency();
-				}
-				else
-				{
-					step.mSubSteps[sub_step_idx - 1].mStartNextSubStep.RemoveDependency();
-				}
+			// Finalize islands is a dependency on find collisions so it can go last
+			step.mFinalizeIslands.RemoveDependency();
 
-				// This job will prepare the position update of all active bodies
-				int num_dependencies_integrate_velocity = is_first_sub_step? 2 + max_concurrency : 1 + max_concurrency;  // depends on: broadphase update finalize in first step, solve velocity constraints in all steps. For both: finish building jobs.
-				sub_step.mPreIntegrateVelocity = inJobSystem->CreateJob("PreIntegrateVelocity", cColorPreIntegrateVelocity, [&context, &sub_step]() 
-					{ 
-						context.mPhysicsSystem->JobPreIntegrateVelocity(&context, &sub_step);
+			// This job will prepare the position update of all active bodies
+			step.mPreIntegrateVelocity = inJobSystem->CreateJob("PreIntegrateVelocity", cColorPreIntegrateVelocity, [&context, &step]() 
+				{ 
+					context.mPhysicsSystem->JobPreIntegrateVelocity(&context, &step);
 
-						JobHandle::sRemoveDependencies(sub_step.mIntegrateVelocity);
-					}, num_dependencies_integrate_velocity);
+					JobHandle::sRemoveDependencies(step.mIntegrateVelocity);
+				}, 2 + max_concurrency); // depends on: broadphase update finalize, solve velocity constraints, finish building jobs.
 
-				// Unblock previous jobs
-				if (is_first_sub_step)
-					step.mUpdateBroadphaseFinalize.RemoveDependency();
-				JobHandle::sRemoveDependencies(sub_step.mSolveVelocityConstraints);
+			// Unblock previous jobs
+			step.mUpdateBroadphaseFinalize.RemoveDependency();
+			JobHandle::sRemoveDependencies(step.mSolveVelocityConstraints);
 
-				// This job will update the positions of all active bodies
-				sub_step.mIntegrateVelocity.resize(num_integrate_velocity_jobs);
-				for (int i = 0; i < num_integrate_velocity_jobs; ++i)
-					sub_step.mIntegrateVelocity[i] = inJobSystem->CreateJob("IntegrateVelocity", cColorIntegrateVelocity, [&context, &sub_step]() 
-						{ 
-							context.mPhysicsSystem->JobIntegrateVelocity(&context, &sub_step);
+			// This job will update the positions of all active bodies
+			step.mIntegrateVelocity.resize(num_integrate_velocity_jobs);
+			for (int i = 0; i < num_integrate_velocity_jobs; ++i)
+				step.mIntegrateVelocity[i] = inJobSystem->CreateJob("IntegrateVelocity", cColorIntegrateVelocity, [&context, &step]() 
+					{ 
+						context.mPhysicsSystem->JobIntegrateVelocity(&context, &step);
 
-							sub_step.mPostIntegrateVelocity.RemoveDependency();
-						}, 2); // depends on: pre integrate velocity, finish building jobs.
+						step.mPostIntegrateVelocity.RemoveDependency();
+					}, 2); // depends on: pre integrate velocity, finish building jobs.
 
-				// Unblock previous job
-				sub_step.mPreIntegrateVelocity.RemoveDependency();
+			// Unblock previous job
+			step.mPreIntegrateVelocity.RemoveDependency();
 
-				// This job will finish the position update of all active bodies
-				sub_step.mPostIntegrateVelocity = inJobSystem->CreateJob("PostIntegrateVelocity", cColorPostIntegrateVelocity, [&context, &sub_step]() 
-					{ 
-						context.mPhysicsSystem->JobPostIntegrateVelocity(&context, &sub_step);
+			// This job will finish the position update of all active bodies
+			step.mPostIntegrateVelocity = inJobSystem->CreateJob("PostIntegrateVelocity", cColorPostIntegrateVelocity, [&context, &step]() 
+				{ 
+					context.mPhysicsSystem->JobPostIntegrateVelocity(&context, &step);
 
-						sub_step.mResolveCCDContacts.RemoveDependency();
-					}, num_integrate_velocity_jobs + 1); // depends on: integrate velocity, finish building jobs
+					step.mResolveCCDContacts.RemoveDependency();
+				}, num_integrate_velocity_jobs + 1); // depends on: integrate velocity, finish building jobs
 
-				// Unblock previous jobs
-				JobHandle::sRemoveDependencies(sub_step.mIntegrateVelocity);
+			// Unblock previous jobs
+			JobHandle::sRemoveDependencies(step.mIntegrateVelocity);
 
-				// This job will update the positions and velocities for all bodies that need continuous collision detection
-				sub_step.mResolveCCDContacts = inJobSystem->CreateJob("ResolveCCDContacts", cColorResolveCCDContacts, [&context, &sub_step]()
-					{
-						context.mPhysicsSystem->JobResolveCCDContacts(&context, &sub_step);
+			// This job will update the positions and velocities for all bodies that need continuous collision detection
+			step.mResolveCCDContacts = inJobSystem->CreateJob("ResolveCCDContacts", cColorResolveCCDContacts, [&context, &step]()
+				{
+					context.mPhysicsSystem->JobResolveCCDContacts(&context, &step);
 
-						JobHandle::sRemoveDependencies(sub_step.mSolvePositionConstraints);
-					}, 2); // depends on: integrate velocities, detect ccd contacts (added dynamically), finish building jobs.
+					JobHandle::sRemoveDependencies(step.mSolvePositionConstraints);
+				}, 2); // depends on: integrate velocities, detect ccd contacts (added dynamically), finish building jobs.
 
-				// Unblock previous job
-				sub_step.mPostIntegrateVelocity.RemoveDependency();
+			// Unblock previous job
+			step.mPostIntegrateVelocity.RemoveDependency();
 
-				// Fixes up drift in positions and updates the broadphase with new body positions
-				sub_step.mSolvePositionConstraints.resize(max_concurrency);
-				for (int i = 0; i < max_concurrency; ++i)
-					sub_step.mSolvePositionConstraints[i] = inJobSystem->CreateJob("SolvePositionConstraints", cColorSolvePositionConstraints, [&context, &sub_step]() 
-						{ 
-							context.mPhysicsSystem->JobSolvePositionConstraints(&context, &sub_step); 
+			// Fixes up drift in positions and updates the broadphase with new body positions
+			step.mSolvePositionConstraints.resize(max_concurrency);
+			for (int i = 0; i < max_concurrency; ++i)
+				step.mSolvePositionConstraints[i] = inJobSystem->CreateJob("SolvePositionConstraints", cColorSolvePositionConstraints, [&context, &step]() 
+					{ 
+						context.mPhysicsSystem->JobSolvePositionConstraints(&context, &step); 
 			
-							// Kick the next sub step
-							if (sub_step.mStartNextSubStep.IsValid())
-								sub_step.mStartNextSubStep.RemoveDependency();
-						}, 2); // depends on: resolve ccd contacts, finish building jobs.
-
-				// Unblock previous job.
-				sub_step.mResolveCCDContacts.RemoveDependency();
+						// Kick the next step
+						if (step.mStartNextStep.IsValid())
+							step.mStartNextStep.RemoveDependency();
+					}, 2); // depends on: resolve ccd contacts, finish building jobs.
 
-				// This job starts the next sub step
-				if (!is_last_sub_step)
-				{
-					PhysicsUpdateContext::SubStep &next_sub_step = step.mSubSteps[sub_step_idx + 1];
-					sub_step.mStartNextSubStep = inJobSystem->CreateJob("StartNextSubStep", cColorStartNextSubStep, [&next_sub_step]() 
-						{ 			
-							// Kick velocity constraint solving for the next sub step
-							JobHandle::sRemoveDependencies(next_sub_step.mSolveVelocityConstraints);
-						}, max_concurrency + 1); // depends on: solve position constraints, finish building jobs.
-				}
-				else
-					sub_step.mStartNextSubStep = step.mStartNextStep;
+			// Unblock previous job.
+			step.mResolveCCDContacts.RemoveDependency();
 
-				// Unblock previous jobs
-				JobHandle::sRemoveDependencies(sub_step.mSolvePositionConstraints);
-			}
+			// Unblock previous jobs
+			JobHandle::sRemoveDependencies(step.mSolvePositionConstraints);
 		}
 	}
 	
@@ -558,21 +518,18 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 			handles.push_back(step.mBuildIslandsFromConstraints);
 			handles.push_back(step.mFinalizeIslands);
 			handles.push_back(step.mBodySetIslandIndex);
-			for (const PhysicsUpdateContext::SubStep &sub_step : step.mSubSteps)
-			{
-				for (const JobHandle &h : sub_step.mSolveVelocityConstraints)
-					handles.push_back(h);
-				handles.push_back(sub_step.mPreIntegrateVelocity);
-				for (const JobHandle &h : sub_step.mIntegrateVelocity)
-					handles.push_back(h);
-				handles.push_back(sub_step.mPostIntegrateVelocity);
-				handles.push_back(sub_step.mResolveCCDContacts);
-				for (const JobHandle &h : sub_step.mSolvePositionConstraints)
-					handles.push_back(h);
-				if (sub_step.mStartNextSubStep.IsValid())
-					handles.push_back(sub_step.mStartNextSubStep);
-			}
+			for (const JobHandle &h : step.mSolveVelocityConstraints)
+				handles.push_back(h);
+			handles.push_back(step.mPreIntegrateVelocity);
+			for (const JobHandle &h : step.mIntegrateVelocity)
+				handles.push_back(h);
+			handles.push_back(step.mPostIntegrateVelocity);
+			handles.push_back(step.mResolveCCDContacts);
+			for (const JobHandle &h : step.mSolvePositionConstraints)
+				handles.push_back(h);
 			handles.push_back(step.mContactRemovedCallbacks);
+			if (step.mStartNextStep.IsValid())
+				handles.push_back(step.mStartNextStep);
 		}
 		barrier->AddJobs(handles.data(), handles.size());
 	}
@@ -705,7 +662,7 @@ void PhysicsSystem::JobApplyGravity(const PhysicsUpdateContext *ioContext, Physi
 	uint32 num_active_bodies_at_step_start = ioStep->mNumActiveBodiesAtStepStart;
 
 	// Fetch delta time once outside the loop
-	float delta_time = ioContext->mSubStepDeltaTime;
+	float delta_time = ioContext->mStepDeltaTime;
 
 	// Update velocities from forces
 	for (;;)
@@ -1255,7 +1212,7 @@ void PhysicsSystem::JobFinalizeIslands(PhysicsUpdateContext *ioContext)
 	mIslandBuilder.Finalize(mBodyManager.GetActiveBodiesUnsafe(), mBodyManager.GetNumActiveBodies(), mContactManager.GetNumConstraints(), ioContext->mTempAllocator);
 
 	// Prepare the large island splitter
-	if (ioContext->mUseLargeIslandSplitter)
+	if (mPhysicsSettings.mUseLargeIslandSplitter)
 		mLargeIslandSplitter.Prepare(mIslandBuilder, mBodyManager.GetNumActiveBodies(), ioContext->mTempAllocator);
 }
 
@@ -1276,23 +1233,20 @@ void PhysicsSystem::JobBodySetIslandIndex()
 	}
 }
 
-void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep)
+void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
 {
 #ifdef JPH_ENABLE_ASSERTS
 	// We update velocities and need to read positions to do so
 	BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::Read);
 #endif
 	
-	float delta_time = ioContext->mSubStepDeltaTime;
+	float delta_time = ioContext->mStepDeltaTime;
 	Constraint **active_constraints = ioContext->mActiveConstraints;
 
-	bool first_sub_step = ioSubStep->mIsFirst;
-	bool last_sub_step = ioSubStep->mIsLast;
-
-	// Only the first sub step of the first step needs to correct for the delta time difference in the previous update
-	float warm_start_impulse_ratio = ioSubStep->mIsFirstOfAll? ioContext->mWarmStartImpulseRatio : 1.0f; 
+	// Only the first step to correct for the delta time difference in the previous update
+	float warm_start_impulse_ratio = ioStep->mIsFirst? ioContext->mWarmStartImpulseRatio : 1.0f; 
 
-	bool check_islands = true, check_split_islands = ioContext->mUseLargeIslandSplitter;
+	bool check_islands = true, check_split_islands = mPhysicsSettings.mUseLargeIslandSplitter;
 	do
 	{
 		// First try to get work from large islands
@@ -1323,7 +1277,7 @@ void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext,
 					mLargeIslandSplitter.MarkBatchProcessed(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, last_iteration, final_batch);
 
 					// Save back the lambdas in the contact cache for the warm start of the next physics update
-					if (last_sub_step && last_iteration)
+					if (last_iteration)
 						mContactManager.StoreAppliedImpulses(contacts_begin, contacts_end);
 
 					// We processed work, loop again
@@ -1341,7 +1295,7 @@ void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext,
 		if (check_islands)
 		{
 			// Next island
-			uint32 island_idx = ioSubStep->mSolveVelocityConstraintsNextIsland++;
+			uint32 island_idx = ioStep->mSolveVelocityConstraintsNextIsland++;
 			if (island_idx >= mIslandBuilder.GetNumIslands())
 			{
 				// We processed all islands, stop checking islands
@@ -1356,66 +1310,36 @@ void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext,
 			bool has_constraints = mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end);
 			bool has_contacts = mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end);
 
-			if (first_sub_step)
+			// If we don't have any contacts or constraints, we know that none of the following islands have any contacts or constraints
+			// (because they're sorted by most constraints first). This means we're done.
+			if (!has_contacts && !has_constraints)
 			{
-				// If we don't have any contacts or constraints, we know that none of the following islands have any contacts or constraints
-				// (because they're sorted by most constraints first). This means we're done.
-				if (!has_contacts && !has_constraints)
+			#ifdef JPH_ENABLE_ASSERTS
+				// Validate our assumption that the next islands don't have any constraints or contacts
+				for (; island_idx < mIslandBuilder.GetNumIslands(); ++island_idx)
 				{
-				#ifdef JPH_ENABLE_ASSERTS
-					// Validate our assumption that the next islands don't have any constraints or contacts
-					for (; island_idx < mIslandBuilder.GetNumIslands(); ++island_idx)
-					{
-						JPH_ASSERT(!mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end));
-						JPH_ASSERT(!mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end));
-					}
-				#endif // JPH_ENABLE_ASSERTS
-
-					check_islands = false;
-					continue;
+					JPH_ASSERT(!mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end));
+					JPH_ASSERT(!mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end));
 				}
+			#endif // JPH_ENABLE_ASSERTS
 
-				// Sorting is costly but needed for a deterministic simulation, allow the user to turn this off
-				if (mPhysicsSettings.mDeterministicSimulation)
-				{
-					// Sort constraints to give a deterministic simulation
-					ConstraintManager::sSortConstraints(active_constraints, constraints_begin, constraints_end);
-
-					// Sort contacts to give a deterministic simulation
-					mContactManager.SortContacts(contacts_begin, contacts_end);
-				}
+				check_islands = false;
+				continue;
 			}
-			else
-			{
-				{
-					JPH_PROFILE("Apply Gravity");
-
-					// Get bodies in this island
-					BodyID *bodies_begin, *bodies_end;
-					mIslandBuilder.GetBodiesInIsland(island_idx, bodies_begin, bodies_end);
 
-					// Apply gravity. In the first step this is done in a separate job.
-					for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
-					{
-						Body &body = mBodyManager.GetBody(*body_id);
-						if (body.IsDynamic())
-							body.GetMotionProperties()->ApplyForceTorqueAndDragInternal(body.GetRotation(), mGravity, delta_time);
-					}
-				}
-
-				// If we don't have any contacts or constraints, we don't need to run the solver, but we do need to process
-				// the next island in order to apply gravity
-				if (!has_contacts && !has_constraints)
-					continue;
+			// Sorting is costly but needed for a deterministic simulation, allow the user to turn this off
+			if (mPhysicsSettings.mDeterministicSimulation)
+			{
+				// Sort constraints to give a deterministic simulation
+				ConstraintManager::sSortConstraints(active_constraints, constraints_begin, constraints_end);
 
-				// Prepare velocity constraints. In the first step this is done when adding the contact constraints.
-				ConstraintManager::sSetupVelocityConstraints(active_constraints, constraints_begin, constraints_end, delta_time);
-				mContactManager.SetupVelocityConstraints(contacts_begin, contacts_end, delta_time);
+				// Sort contacts to give a deterministic simulation
+				mContactManager.SortContacts(contacts_begin, contacts_end);
 			}
 
 			// Split up large islands
 			int num_velocity_steps = mPhysicsSettings.mNumVelocitySteps;
-			if (ioContext->mUseLargeIslandSplitter
+			if (mPhysicsSettings.mUseLargeIslandSplitter
 				&& mLargeIslandSplitter.SplitIsland(island_idx, mIslandBuilder, mBodyManager, mContactManager, active_constraints, num_velocity_steps, mPhysicsSettings.mNumPositionSteps))
 				continue; // Loop again to try to fetch the newly split island
 
@@ -1433,8 +1357,7 @@ void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext,
 			}
 
 			// Save back the lambdas in the contact cache for the warm start of the next physics update
-			if (last_sub_step)
-				mContactManager.StoreAppliedImpulses(contacts_begin, contacts_end);
+			mContactManager.StoreAppliedImpulses(contacts_begin, contacts_end);
 
 			// We processed work, loop again
 			continue;
@@ -1446,34 +1369,34 @@ void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext,
 	while (check_islands || check_split_islands);
 }
 
-void PhysicsSystem::JobPreIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep)
+void PhysicsSystem::JobPreIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
 {
 	// Reserve enough space for all bodies that may need a cast
 	TempAllocator *temp_allocator = ioContext->mTempAllocator;
-	JPH_ASSERT(ioSubStep->mCCDBodies == nullptr);
-	ioSubStep->mCCDBodiesCapacity = mBodyManager.GetNumActiveCCDBodies();
-	ioSubStep->mCCDBodies = (CCDBody *)temp_allocator->Allocate(ioSubStep->mCCDBodiesCapacity * sizeof(CCDBody));
+	JPH_ASSERT(ioStep->mCCDBodies == nullptr);
+	ioStep->mCCDBodiesCapacity = mBodyManager.GetNumActiveCCDBodies();
+	ioStep->mCCDBodies = (CCDBody *)temp_allocator->Allocate(ioStep->mCCDBodiesCapacity * sizeof(CCDBody));
 
 	// Initialize the mapping table between active body and CCD body
-	JPH_ASSERT(ioSubStep->mActiveBodyToCCDBody == nullptr);
-	ioSubStep->mNumActiveBodyToCCDBody = mBodyManager.GetNumActiveBodies();
-	ioSubStep->mActiveBodyToCCDBody = (int *)temp_allocator->Allocate(ioSubStep->mNumActiveBodyToCCDBody * sizeof(int));
+	JPH_ASSERT(ioStep->mActiveBodyToCCDBody == nullptr);
+	ioStep->mNumActiveBodyToCCDBody = mBodyManager.GetNumActiveBodies();
+	ioStep->mActiveBodyToCCDBody = (int *)temp_allocator->Allocate(ioStep->mNumActiveBodyToCCDBody * sizeof(int));
 
 	// Prepare the split island builder for solving the position constraints
 	mLargeIslandSplitter.PrepareForSolvePositions();
 }
 
-void PhysicsSystem::JobIntegrateVelocity(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep)
+void PhysicsSystem::JobIntegrateVelocity(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
 {
 #ifdef JPH_ENABLE_ASSERTS
 	// We update positions and need velocity to do so, we also clamp velocities so need to write to them
 	BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::ReadWrite);
 #endif
 
-	float delta_time = ioContext->mSubStepDeltaTime;
+	float delta_time = ioContext->mStepDeltaTime;
 	const BodyID *active_bodies = mBodyManager.GetActiveBodiesUnsafe();
 	uint32 num_active_bodies = mBodyManager.GetNumActiveBodies();
-	uint32 num_active_bodies_after_find_collisions = ioSubStep->mStep->mActiveBodyReadIdx;
+	uint32 num_active_bodies_after_find_collisions = ioStep->mActiveBodyReadIdx;
 
 	// We can move bodies that are not part of an island. In this case we need to notify the broadphase of the movement.
 	static constexpr int cBodiesBatch = 64;
@@ -1483,7 +1406,7 @@ void PhysicsSystem::JobIntegrateVelocity(const PhysicsUpdateContext *ioContext,
 	for (;;)
 	{
 		// Atomically fetch a batch of bodies
-		uint32 active_body_idx = ioSubStep->mIntegrateVelocityReadIdx.fetch_add(cIntegrateVelocityBatchSize);
+		uint32 active_body_idx = ioStep->mIntegrateVelocityReadIdx.fetch_add(cIntegrateVelocityBatchSize);
 		if (active_body_idx >= num_active_bodies)
 			break;
 
@@ -1548,9 +1471,9 @@ void PhysicsSystem::JobIntegrateVelocity(const PhysicsUpdateContext *ioContext,
 					if (delta_pos.LengthSq() > linear_cast_threshold_sq)
 					{
 						// This body needs a cast
-						uint32 ccd_body_idx = ioSubStep->mNumCCDBodies++;
-						ioSubStep->mActiveBodyToCCDBody[active_body_idx] = ccd_body_idx;
-						new (&ioSubStep->mCCDBodies[ccd_body_idx]) CCDBody(body_id, delta_pos, linear_cast_threshold_sq, min(mPhysicsSettings.mPenetrationSlop, mPhysicsSettings.mLinearCastMaxPenetration * inner_radius));
+						uint32 ccd_body_idx = ioStep->mNumCCDBodies++;
+						ioStep->mActiveBodyToCCDBody[active_body_idx] = ccd_body_idx;
+						new (&ioStep->mCCDBodies[ccd_body_idx]) CCDBody(body_id, delta_pos, linear_cast_threshold_sq, min(mPhysicsSettings.mPenetrationSlop, mPhysicsSettings.mLinearCastMaxPenetration * inner_radius));
 
 						update_position = false;
 					}
@@ -1580,7 +1503,7 @@ void PhysicsSystem::JobIntegrateVelocity(const PhysicsUpdateContext *ioContext,
 				}
 
 				// We did not create a CCD body
-				ioSubStep->mActiveBodyToCCDBody[active_body_idx] = -1;
+				ioStep->mActiveBodyToCCDBody[active_body_idx] = -1;
 			}
 
 			active_body_idx++;
@@ -1592,33 +1515,30 @@ void PhysicsSystem::JobIntegrateVelocity(const PhysicsUpdateContext *ioContext,
 		mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
 }
 
-void PhysicsSystem::JobPostIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep) const
+void PhysicsSystem::JobPostIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep) const
 {
 	// Validate that our reservations were correct
-	JPH_ASSERT(ioSubStep->mNumCCDBodies <= mBodyManager.GetNumActiveCCDBodies());
+	JPH_ASSERT(ioStep->mNumCCDBodies <= mBodyManager.GetNumActiveCCDBodies());
 
-	if (ioSubStep->mNumCCDBodies == 0)
+	if (ioStep->mNumCCDBodies == 0)
 	{
 		// No continous collision detection jobs -> kick the next job ourselves
-		if (ioSubStep->mIsLast)
-			ioSubStep->mStep->mContactRemovedCallbacks.RemoveDependency();
+		ioStep->mContactRemovedCallbacks.RemoveDependency();
 	}
 	else
 	{
 		// Run the continous collision detection jobs
-		int num_continuous_collision_jobs = min(int(ioSubStep->mNumCCDBodies + cNumCCDBodiesPerJob - 1) / cNumCCDBodiesPerJob, ioContext->GetMaxConcurrency());
-		ioSubStep->mResolveCCDContacts.AddDependency(num_continuous_collision_jobs);
-		if (ioSubStep->mIsLast)
-			ioSubStep->mStep->mContactRemovedCallbacks.AddDependency(num_continuous_collision_jobs - 1); // Already had 1 dependency
+		int num_continuous_collision_jobs = min(int(ioStep->mNumCCDBodies + cNumCCDBodiesPerJob - 1) / cNumCCDBodiesPerJob, ioContext->GetMaxConcurrency());
+		ioStep->mResolveCCDContacts.AddDependency(num_continuous_collision_jobs);
+		ioStep->mContactRemovedCallbacks.AddDependency(num_continuous_collision_jobs - 1); // Already had 1 dependency
 		for (int i = 0; i < num_continuous_collision_jobs; ++i)
 		{
-			JobHandle job = ioContext->mJobSystem->CreateJob("FindCCDContacts", cColorFindCCDContacts, [ioContext, ioSubStep]() 
+			JobHandle job = ioContext->mJobSystem->CreateJob("FindCCDContacts", cColorFindCCDContacts, [ioContext, ioStep]() 
 			{
-				ioContext->mPhysicsSystem->JobFindCCDContacts(ioContext, ioSubStep);
+				ioContext->mPhysicsSystem->JobFindCCDContacts(ioContext, ioStep);
 
-				ioSubStep->mResolveCCDContacts.RemoveDependency();
-				if (ioSubStep->mIsLast)
-					ioSubStep->mStep->mContactRemovedCallbacks.RemoveDependency();
+				ioStep->mResolveCCDContacts.RemoveDependency();
+				ioStep->mContactRemovedCallbacks.RemoveDependency();
 			});
 			ioContext->mBarrier->AddJob(job);
 		}
@@ -1637,7 +1557,7 @@ inline static Vec3 sCalculateBodyMotion(const Body &inBody, float inDeltaTime)
 }
 
 // Helper function that finds the CCD body corresponding to a body (if it exists)
-inline static PhysicsUpdateContext::SubStep::CCDBody *sGetCCDBody(const Body &inBody, PhysicsUpdateContext::SubStep *inSubStep)
+inline static PhysicsUpdateContext::Step::CCDBody *sGetCCDBody(const Body &inBody, PhysicsUpdateContext::Step *inStep)
 {
 	// If the body has no motion properties it cannot have a CCD body
 	const MotionProperties *motion_properties = inBody.GetMotionPropertiesUnchecked();
@@ -1650,17 +1570,17 @@ inline static PhysicsUpdateContext::SubStep::CCDBody *sGetCCDBody(const Body &in
 		return nullptr;
 
 	// Check if the active body has a corresponding CCD body
-	JPH_ASSERT(active_index < inSubStep->mNumActiveBodyToCCDBody); // Ensure that the body has a mapping to CCD body
-	int ccd_index = inSubStep->mActiveBodyToCCDBody[active_index];
+	JPH_ASSERT(active_index < inStep->mNumActiveBodyToCCDBody); // Ensure that the body has a mapping to CCD body
+	int ccd_index = inStep->mActiveBodyToCCDBody[active_index];
 	if (ccd_index < 0)
 		return nullptr;
 
-	PhysicsUpdateContext::SubStep::CCDBody *ccd_body = &inSubStep->mCCDBodies[ccd_index];
+	PhysicsUpdateContext::Step::CCDBody *ccd_body = &inStep->mCCDBodies[ccd_index];
 	JPH_ASSERT(ccd_body->mBodyID1 == inBody.GetID(), "We found the wrong CCD body!");
 	return ccd_body;
 }
 
-void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep)
+void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
 {
 #ifdef JPH_ENABLE_ASSERTS
 	// We only read positions, but the validate callback may read body positions and velocities
@@ -1682,10 +1602,10 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph
 	for (;;)
 	{
 		// Fetch the next body to cast
-		uint32 idx = ioSubStep->mNextCCDBody++;
-		if (idx >= ioSubStep->mNumCCDBodies)
+		uint32 idx = ioStep->mNextCCDBody++;
+		if (idx >= ioStep->mNumCCDBodies)
 			break;
-		CCDBody &ccd_body = ioSubStep->mCCDBodies[idx];
+		CCDBody &ccd_body = ioStep->mCCDBodies[idx];
 		const Body &body = mBodyManager.GetBody(ccd_body.mBodyID1);
 
 		// Filter out layers
@@ -1808,13 +1728,13 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph
 
 		// Narrowphase collector
 		ShapeCastResult cast_shape_result;
-		CCDNarrowPhaseCollector np_collector(mBodyManager, mContactManager, ccd_body, cast_shape_result, ioContext->mSubStepDeltaTime);
+		CCDNarrowPhaseCollector np_collector(mBodyManager, mContactManager, ccd_body, cast_shape_result, ioContext->mStepDeltaTime);
 
 		// This collector wraps the narrowphase collector and collects the closest hit
 		class CCDBroadPhaseCollector : public CastShapeBodyCollector
 		{
 		public:
-										CCDBroadPhaseCollector(const CCDBody &inCCDBody, const Body &inBody1, const RShapeCast &inShapeCast, ShapeCastSettings &inShapeCastSettings, CCDNarrowPhaseCollector &ioCollector, const BodyManager &inBodyManager, PhysicsUpdateContext::SubStep *inSubStep, float inDeltaTime) :
+										CCDBroadPhaseCollector(const CCDBody &inCCDBody, const Body &inBody1, const RShapeCast &inShapeCast, ShapeCastSettings &inShapeCastSettings, CCDNarrowPhaseCollector &ioCollector, const BodyManager &inBodyManager, PhysicsUpdateContext::Step *inStep, float inDeltaTime) :
 				mCCDBody(inCCDBody),
 				mBody1(inBody1),
 				mBody1Extent(inShapeCast.mShapeWorldBounds.GetExtent()),
@@ -1822,7 +1742,7 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph
 				mShapeCastSettings(inShapeCastSettings),
 				mCollector(ioCollector),
 				mBodyManager(inBodyManager),
-				mSubStep(inSubStep),
+				mStep(inStep),
 				mDeltaTime(inDeltaTime)
 			{
 			}
@@ -1839,7 +1759,7 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph
 
 				// Avoid treating duplicates, if both bodies are doing CCD then only consider collision if body ID < other body ID
 				const Body &body2 = mBodyManager.GetBody(inResult.mBodyID);
-				const CCDBody *ccd_body2 = sGetCCDBody(body2, mSubStep);
+				const CCDBody *ccd_body2 = sGetCCDBody(body2, mStep);
 				if (ccd_body2 != nullptr && mCCDBody.mBodyID1 > ccd_body2->mBodyID1)
 					return;
 
@@ -1890,13 +1810,13 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph
 			ShapeCastSettings &			mShapeCastSettings;
 			CCDNarrowPhaseCollector &	mCollector;
 			const BodyManager &			mBodyManager;
-			PhysicsUpdateContext::SubStep *mSubStep;
+			PhysicsUpdateContext::Step *mStep;
 			float						mDeltaTime;
 		};
 
 		// 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.
 		RShapeCast shape_cast(body.GetShape(), Vec3::sReplicate(1.0f), body.GetCenterOfMassTransform(), ccd_body.mDeltaPosition);
-		CCDBroadPhaseCollector bp_collector(ccd_body, body, shape_cast, settings, np_collector, mBodyManager, ioSubStep, ioContext->mSubStepDeltaTime);
+		CCDBroadPhaseCollector bp_collector(ccd_body, body, shape_cast, settings, np_collector, mBodyManager, ioStep, ioContext->mStepDeltaTime);
 		mBroadPhase->CastAABoxNoLock({ shape_cast.mShapeWorldBounds, shape_cast.mDirection }, bp_collector, broadphase_layer_filter, object_layer_filter);
 
 		// Check if there was a hit
@@ -1931,10 +1851,10 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph
 	}
 
 	// Collect information from the contact allocator and accumulate it in the step.
-	sFinalizeContactAllocator(*ioSubStep->mStep, contact_allocator);
+	sFinalizeContactAllocator(*ioStep, contact_allocator);
 }
 
-void PhysicsSystem::JobResolveCCDContacts(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep)
+void PhysicsSystem::JobResolveCCDContacts(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
 {
 #ifdef JPH_ENABLE_ASSERTS
 	// Read/write body access
@@ -1944,11 +1864,11 @@ void PhysicsSystem::JobResolveCCDContacts(PhysicsUpdateContext *ioContext, Physi
 	BodyManager::GrantActiveBodiesAccess grant_active(true, false);
 #endif
 
-	uint32 num_active_bodies_after_find_collisions = ioSubStep->mStep->mActiveBodyReadIdx;
+	uint32 num_active_bodies_after_find_collisions = ioStep->mActiveBodyReadIdx;
 	TempAllocator *temp_allocator = ioContext->mTempAllocator;
 
 	// Check if there's anything to do
-	uint num_ccd_bodies = ioSubStep->mNumCCDBodies;
+	uint num_ccd_bodies = ioStep->mNumCCDBodies;
 	if (num_ccd_bodies > 0)
 	{
 		// Sort on fraction so that we process earliest collisions first
@@ -1960,7 +1880,7 @@ void PhysicsSystem::JobResolveCCDContacts(PhysicsUpdateContext *ioContext, Physi
 			JPH_PROFILE("Sort");
 
 			// We don't want to copy the entire struct (it's quite big), so we create a pointer array first
-			CCDBody *src_ccd_bodies = ioSubStep->mCCDBodies;
+			CCDBody *src_ccd_bodies = ioStep->mCCDBodies;
 			CCDBody **dst_ccd_bodies = sorted_ccd_bodies;
 			CCDBody **dst_ccd_bodies_end = dst_ccd_bodies + num_ccd_bodies;
 			while (dst_ccd_bodies < dst_ccd_bodies_end)
@@ -1998,7 +1918,7 @@ void PhysicsSystem::JobResolveCCDContacts(PhysicsUpdateContext *ioContext, Physi
 				Body &body2 = mBodyManager.GetBody(ccd_body->mBodyID2);
 
 				// Determine if the other body has a CCD body
-				CCDBody *ccd_body2 = sGetCCDBody(body2, ioSubStep);
+				CCDBody *ccd_body2 = sGetCCDBody(body2, ioStep);
 				if (ccd_body2 != nullptr)
 				{
 					JPH_ASSERT(ccd_body2->mBodyID2 != ccd_body->mBodyID1, "If we collided with another body, that other body should have ignored collisions with us!");
@@ -2138,12 +2058,12 @@ void PhysicsSystem::JobResolveCCDContacts(PhysicsUpdateContext *ioContext, Physi
 	}
 
 	// Ensure we free the CCD bodies array now, will not call the destructor!
-	temp_allocator->Free(ioSubStep->mActiveBodyToCCDBody, ioSubStep->mNumActiveBodyToCCDBody * sizeof(int));
-	ioSubStep->mActiveBodyToCCDBody = nullptr;
-	ioSubStep->mNumActiveBodyToCCDBody = 0;
-	temp_allocator->Free(ioSubStep->mCCDBodies, ioSubStep->mCCDBodiesCapacity * sizeof(CCDBody));
-	ioSubStep->mCCDBodies = nullptr;
-	ioSubStep->mCCDBodiesCapacity = 0;
+	temp_allocator->Free(ioStep->mActiveBodyToCCDBody, ioStep->mNumActiveBodyToCCDBody * sizeof(int));
+	ioStep->mActiveBodyToCCDBody = nullptr;
+	ioStep->mNumActiveBodyToCCDBody = 0;
+	temp_allocator->Free(ioStep->mCCDBodies, ioStep->mCCDBodiesCapacity * sizeof(CCDBody));
+	ioStep->mCCDBodies = nullptr;
+	ioStep->mCCDBodiesCapacity = 0;
 }
 
 void PhysicsSystem::JobContactRemovedCallbacks(const PhysicsUpdateContext::Step *ioStep)
@@ -2208,15 +2128,15 @@ private:
 	BodyID *				mBodiesToSleepCur;
 };
 
-void PhysicsSystem::CheckSleepAndUpdateBounds(uint32 inIslandIndex, const PhysicsUpdateContext *ioContext, const PhysicsUpdateContext::SubStep *ioSubStep, BodiesToSleep &ioBodiesToSleep)
+void PhysicsSystem::CheckSleepAndUpdateBounds(uint32 inIslandIndex, const PhysicsUpdateContext *ioContext, const PhysicsUpdateContext::Step *ioStep, BodiesToSleep &ioBodiesToSleep)
 {
 	// Get the bodies that belong to this island
 	BodyID *bodies_begin, *bodies_end;
 	mIslandBuilder.GetBodiesInIsland(inIslandIndex, bodies_begin, bodies_end);
 
-	// Only check sleeping in the last sub step of the last step
+	// Only check sleeping in the last step
 	// Also resets force and torque used during the apply gravity phase
-	if (ioSubStep->mIsLastOfAll)
+	if (ioStep->mIsLast)
 	{
 		JPH_PROFILE("Check Sleeping");
 
@@ -2234,7 +2154,7 @@ void PhysicsSystem::CheckSleepAndUpdateBounds(uint32 inIslandIndex, const Physic
 			body.CalculateWorldSpaceBoundsInternal();
 
 			// Update sleeping
-			all_can_sleep &= int(body.UpdateSleepStateInternal(ioContext->mSubStepDeltaTime, max_movement, time_before_sleep));
+			all_can_sleep &= int(body.UpdateSleepStateInternal(ioContext->mStepDeltaTime, max_movement, time_before_sleep));
 
 			// Reset force and torque
 			MotionProperties *mp = body.GetMotionProperties();
@@ -2250,7 +2170,7 @@ void PhysicsSystem::CheckSleepAndUpdateBounds(uint32 inIslandIndex, const Physic
 	{
 		JPH_PROFILE("Update Bounds");
 
-		// Update bounding box only for all other sub steps
+		// Update bounding box only for all other steps
 		for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
 		{
 			Body &body = mBodyManager.GetBody(*body_id);
@@ -2258,13 +2178,12 @@ void PhysicsSystem::CheckSleepAndUpdateBounds(uint32 inIslandIndex, const Physic
 		}
 	}
 
-	// Notify broadphase of changed objects (find ccd contacts can do linear casts in the next step, so
-	// we need to do this every sub step)
+	// Notify broadphase of changed objects (find ccd contacts can do linear casts in the next step, so we need to do this every step)
 	// Note: Shuffles the BodyID's around!!!
 	mBroadPhase->NotifyBodiesAABBChanged(bodies_begin, int(bodies_end - bodies_begin), false);
 }
 
-void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep)
+void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
 {
 #ifdef JPH_ENABLE_ASSERTS
 	// We fix up position errors
@@ -2274,14 +2193,14 @@ void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext,
 	BodyManager::GrantActiveBodiesAccess grant_active(false, true);
 #endif
 
-	float delta_time = ioContext->mSubStepDeltaTime;
+	float delta_time = ioContext->mStepDeltaTime;
 	float baumgarte = mPhysicsSettings.mBaumgarte;
 	Constraint **active_constraints = ioContext->mActiveConstraints;
 
 	// Keep a buffer of bodies that need to go to sleep in order to not constantly lock the active bodies mutex and create contention between all solving threads
 	BodiesToSleep bodies_to_sleep(mBodyManager, (BodyID *)JPH_STACK_ALLOC(BodiesToSleep::cBodiesToSleepSize * sizeof(BodyID)));
 
-	bool check_islands = true, check_split_islands = ioContext->mUseLargeIslandSplitter;
+	bool check_islands = true, check_split_islands = mPhysicsSettings.mUseLargeIslandSplitter;
 	do
 	{
 		// First try to get work from large islands
@@ -2303,7 +2222,7 @@ void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext,
 
 				// The final batch will update all bounds and check sleeping
 				if (final_batch)
-					CheckSleepAndUpdateBounds(mLargeIslandSplitter.GetIslandIndex(split_island_index), ioContext, ioSubStep, bodies_to_sleep);
+					CheckSleepAndUpdateBounds(mLargeIslandSplitter.GetIslandIndex(split_island_index), ioContext, ioStep, bodies_to_sleep);
 
 				// We processed work, loop again
 				continue;
@@ -2319,7 +2238,7 @@ void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext,
 		if (check_islands)
 		{
 			// Next island
-			uint32 island_idx = ioSubStep->mSolvePositionConstraintsNextIsland++;
+			uint32 island_idx = ioStep->mSolvePositionConstraintsNextIsland++;
 			if (island_idx >= mIslandBuilder.GetNumIslands())
 			{
 				// We processed all islands, stop checking islands
@@ -2336,7 +2255,7 @@ void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext,
 
 			// If this island is a large island, it will be picked up as a batch and we don't need to do anything here
 			uint num_items = uint(constraints_end - constraints_begin) + uint(contacts_end - contacts_begin);
-			if (ioContext->mUseLargeIslandSplitter
+			if (mPhysicsSettings.mUseLargeIslandSplitter
 				&& num_items >= LargeIslandSplitter::cLargeIslandTreshold)
 				continue;
 
@@ -2375,7 +2294,7 @@ void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext,
 			}
 
 			// After solving we will update all bounds and check sleeping
-			CheckSleepAndUpdateBounds(island_idx, ioContext, ioSubStep, bodies_to_sleep);
+			CheckSleepAndUpdateBounds(island_idx, ioContext, ioStep, bodies_to_sleep);
 
 			// We processed work, loop again
 			continue;
@@ -2391,7 +2310,7 @@ void PhysicsSystem::SaveState(StateRecorder &inStream) const
 {
 	JPH_PROFILE_FUNCTION();
 
-	inStream.Write(mPreviousSubStepDeltaTime);
+	inStream.Write(mPreviousStepDeltaTime);
 	inStream.Write(mGravity);
 
 	mBodyManager.SaveState(inStream);
@@ -2405,7 +2324,7 @@ bool PhysicsSystem::RestoreState(StateRecorder &inStream)
 {
 	JPH_PROFILE_FUNCTION();
 
-	inStream.Read(mPreviousSubStepDeltaTime);
+	inStream.Read(mPreviousStepDeltaTime);
 	inStream.Read(mGravity);
 
 	if (!mBodyManager.RestoreState(inStream))

+ 13 - 13
Jolt/Physics/PhysicsSystem.h

@@ -101,10 +101,10 @@ public:
 	void						RemoveStepListener(PhysicsStepListener *inListener);
 
 	/// Simulate the system.
-	/// The world steps for a total of inDeltaTime seconds. This is divided in inCollisionSteps iterations. Each iteration
-	/// consists of collision detection followed by inIntegrationSubSteps integration steps.
+	/// The world steps for a total of inDeltaTime seconds. This is divided in inCollisionSteps iterations.
+	/// Each iteration consists of collision detection followed by an integration step.
 	/// This function internally spawns jobs using inJobSystem and waits for them to complete, so no jobs will be running when this function returns.
-	EPhysicsUpdateError			Update(float inDeltaTime, int inCollisionSteps, int inIntegrationSubSteps, TempAllocator *inTempAllocator, JobSystem *inJobSystem);
+	EPhysicsUpdateError			Update(float inDeltaTime, int inCollisionSteps, TempAllocator *inTempAllocator, JobSystem *inJobSystem);
 
 	/// Saving state for replay
 	void						SaveState(StateRecorder &inStream) const;
@@ -185,7 +185,7 @@ public:
 #endif // JPH_TRACK_BROADPHASE_STATS
 
 private:
-	using CCDBody = PhysicsUpdateContext::SubStep::CCDBody;
+	using CCDBody = PhysicsUpdateContext::Step::CCDBody;
 
 	// Various job entry points
 	void						JobStepListeners(PhysicsUpdateContext::Step *ioStep);
@@ -196,14 +196,14 @@ private:
 	void						JobFindCollisions(PhysicsUpdateContext::Step *ioStep, int inJobIndex);
 	void						JobFinalizeIslands(PhysicsUpdateContext *ioContext);
 	void						JobBodySetIslandIndex();
-	void						JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep);
-	void						JobPreIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep);
-	void						JobIntegrateVelocity(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep);
-	void						JobPostIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep) const;
-	void						JobFindCCDContacts(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep);
-	void						JobResolveCCDContacts(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep);
+	void						JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep);
+	void						JobPreIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep);
+	void						JobIntegrateVelocity(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep);
+	void						JobPostIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep) const;
+	void						JobFindCCDContacts(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep);
+	void						JobResolveCCDContacts(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep);
 	void						JobContactRemovedCallbacks(const PhysicsUpdateContext::Step *ioStep);
-	void						JobSolvePositionConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep);
+	void						JobSolvePositionConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep);
 
 	/// Tries to spawn a new FindCollisions job if max concurrency hasn't been reached yet
 	void						TrySpawnJobFindCollisions(PhysicsUpdateContext::Step *ioStep) const;
@@ -217,7 +217,7 @@ private:
 	class BodiesToSleep;
 
 	/// Called at the end of JobSolveVelocityConstraints to check if bodies need to go to sleep and to update their bounding box in the broadphase
-	void						CheckSleepAndUpdateBounds(uint32 inIslandIndex, const PhysicsUpdateContext *ioContext, const PhysicsUpdateContext::SubStep *ioSubStep, BodiesToSleep &ioBodiesToSleep);
+	void						CheckSleepAndUpdateBounds(uint32 inIslandIndex, const PhysicsUpdateContext *ioContext, const PhysicsUpdateContext::Step *ioStep, BodiesToSleep &ioBodiesToSleep);
 
 	/// Number of constraints to process at once in JobDetermineActiveConstraints
 	static constexpr int		cDetermineActiveConstraintsBatchSize = 64;
@@ -287,7 +287,7 @@ private:
 	Vec3						mGravity = Vec3(0, -9.81f, 0);
 
 	/// Previous frame's delta time of one sub step to allow scaling previous frame's constraint impulses
-	float						mPreviousSubStepDeltaTime = 0.0f;
+	float						mPreviousStepDeltaTime = 0.0f;
 };
 
 JPH_NAMESPACE_END

+ 36 - 52
Jolt/Physics/PhysicsUpdateContext.h

@@ -27,60 +27,11 @@ public:
 							~PhysicsUpdateContext();
 
 	static constexpr int	cMaxConcurrency = 32;									///< Maximum supported amount of concurrent jobs
-	static constexpr int	cMaxSubSteps = 4;										///< Maximum supported amount of integration sub steps
 
 	using JobHandleArray = StaticArray<JobHandle, cMaxConcurrency>;
 
 	struct Step;
 
-	/// Structure that contains job handles for each integration sub step
-	struct SubStep
-	{
-		Step *				mStep;													///< Step that this substeb belongs to
-
-		bool				mIsFirst;												///< If this is the first substep in the step
-		bool				mIsLast;												///< If this is the last substep in the step
-		bool				mIsFirstOfAll;											///< If this is the first substep of the first step
-		bool				mIsLastOfAll;											///< If this is the last substep in the last step
-
-		atomic<uint32>		mSolveVelocityConstraintsNextIsland { 0 };				///< Next island that needs to be processed for the solve velocity constraints step (doesn't need own cache line since position jobs don't run at same time)
-		atomic<uint32>		mSolvePositionConstraintsNextIsland { 0 };				///< Next island that needs to be processed for the solve position constraints step (doesn't need own cache line since velocity jobs don't run at same time)
-
-		/// Contains the information needed to cast a body through the scene to do continuous collision detection
-		struct CCDBody
-		{
-							CCDBody(BodyID inBodyID1, Vec3Arg inDeltaPosition, float inLinearCastThresholdSq, float inMaxPenetration) : mDeltaPosition(inDeltaPosition), mBodyID1(inBodyID1), mLinearCastThresholdSq(inLinearCastThresholdSq), mMaxPenetration(inMaxPenetration) { }
-
-			Vec3			mDeltaPosition;											///< Desired rotation step
-			Vec3			mContactNormal;											///< World space normal of closest hit (only valid if mFractionPlusSlop < 1)
-			RVec3			mContactPointOn2;										///< World space contact point on body 2 of closest hit (only valid if mFractionPlusSlop < 1)
-			BodyID			mBodyID1;												///< Body 1 (the body that is performing collision detection)
-			BodyID			mBodyID2;												///< Body 2 (the body of the closest hit, only valid if mFractionPlusSlop < 1)
-			float			mFraction = 1.0f;										///< Fraction at which the hit occurred
-			float			mFractionPlusSlop = 1.0f;								///< Fraction at which the hit occurred + extra delta to allow body to penetrate by mMaxPenetration
-			float			mLinearCastThresholdSq;									///< Maximum allowed squared movement before doing a linear cast (determined by inner radius of shape)
-			float			mMaxPenetration;										///< Maximum allowed penetration (determined by inner radius of shape)
-			ContactSettings	mContactSettings;										///< The contact settings for this contact
-		};
-		atomic<uint32>		mIntegrateVelocityReadIdx { 0 };						///< Next active body index to take when integrating velocities
-		CCDBody *			mCCDBodies = nullptr;									///< List of bodies that need to do continuous collision detection
-		uint32				mCCDBodiesCapacity = 0;									///< Capacity of the mCCDBodies list
-		atomic<uint32>		mNumCCDBodies = 0;										///< Number of CCD bodies in mCCDBodies
-		atomic<uint32>		mNextCCDBody { 0 };										///< Next unprocessed body index in mCCDBodies
-		int *				mActiveBodyToCCDBody = nullptr;							///< A mapping between an index in BodyManager::mActiveBodies and the index in mCCDBodies
-		uint32				mNumActiveBodyToCCDBody = 0;							///< Number of indices in mActiveBodyToCCDBody
-
-		JobHandleArray		mSolveVelocityConstraints;								///< Solve the constraints in the velocity domain
-		JobHandle			mPreIntegrateVelocity;									///< Setup integration of all body positions
-		JobHandleArray		mIntegrateVelocity;										///< Integrate all body positions
-		JobHandle			mPostIntegrateVelocity;									///< Finalize integration of all body positions
-		JobHandle			mResolveCCDContacts;									///< Updates the positions and velocities for all bodies that need continuous collision detection
-		JobHandleArray		mSolvePositionConstraints;								///< Solve all constraints in the position domain
-		JobHandle			mStartNextSubStep;										///< Trampoline job that either kicks the next sub step or the next step
-	};
-
-	using SubSteps = StaticArray<SubStep, cMaxSubSteps>;
-
 	struct BodyPairQueue
 	{
 		atomic<uint32>		mWriteIdx { 0 };										///< Next index to write in mBodyPair array (need to add thread index * mMaxBodyPairsPerQueue and modulo mMaxBodyPairsPerQueue)
@@ -103,6 +54,9 @@ public:
 
 		PhysicsUpdateContext *mContext;												///< The physics update context
 
+		bool				mIsFirst;												///< If this is the first step
+		bool				mIsLast;												///< If this is the last step
+
 		BroadPhase::UpdateState	mBroadPhaseUpdateState;								///< Handle returned by Broadphase::UpdatePrepare
 
 		uint32				mNumActiveBodiesAtStepStart;							///< Number of bodies that were active at the start of the physics update step. Only these bodies will receive gravity (they are the first N in the active body list).
@@ -131,6 +85,33 @@ public:
 		atomic<uint>		mNumBodyPairs { 0 };									///< The number of body pairs found in this step (used to size the contact cache in the next step)
 		atomic<uint>		mNumManifolds { 0 };									///< The number of manifolds found in this step (used to size the contact cache in the next step)
 
+		atomic<uint32>		mSolveVelocityConstraintsNextIsland { 0 };				///< Next island that needs to be processed for the solve velocity constraints step (doesn't need own cache line since position jobs don't run at same time)
+		atomic<uint32>		mSolvePositionConstraintsNextIsland { 0 };				///< Next island that needs to be processed for the solve position constraints step (doesn't need own cache line since velocity jobs don't run at same time)
+
+		/// Contains the information needed to cast a body through the scene to do continuous collision detection
+		struct CCDBody
+		{
+							CCDBody(BodyID inBodyID1, Vec3Arg inDeltaPosition, float inLinearCastThresholdSq, float inMaxPenetration) : mDeltaPosition(inDeltaPosition), mBodyID1(inBodyID1), mLinearCastThresholdSq(inLinearCastThresholdSq), mMaxPenetration(inMaxPenetration) { }
+
+			Vec3			mDeltaPosition;											///< Desired rotation step
+			Vec3			mContactNormal;											///< World space normal of closest hit (only valid if mFractionPlusSlop < 1)
+			RVec3			mContactPointOn2;										///< World space contact point on body 2 of closest hit (only valid if mFractionPlusSlop < 1)
+			BodyID			mBodyID1;												///< Body 1 (the body that is performing collision detection)
+			BodyID			mBodyID2;												///< Body 2 (the body of the closest hit, only valid if mFractionPlusSlop < 1)
+			float			mFraction = 1.0f;										///< Fraction at which the hit occurred
+			float			mFractionPlusSlop = 1.0f;								///< Fraction at which the hit occurred + extra delta to allow body to penetrate by mMaxPenetration
+			float			mLinearCastThresholdSq;									///< Maximum allowed squared movement before doing a linear cast (determined by inner radius of shape)
+			float			mMaxPenetration;										///< Maximum allowed penetration (determined by inner radius of shape)
+			ContactSettings	mContactSettings;										///< The contact settings for this contact
+		};
+		atomic<uint32>		mIntegrateVelocityReadIdx { 0 };						///< Next active body index to take when integrating velocities
+		CCDBody *			mCCDBodies = nullptr;									///< List of bodies that need to do continuous collision detection
+		uint32				mCCDBodiesCapacity = 0;									///< Capacity of the mCCDBodies list
+		atomic<uint32>		mNumCCDBodies = 0;										///< Number of CCD bodies in mCCDBodies
+		atomic<uint32>		mNextCCDBody { 0 };										///< Next unprocessed body index in mCCDBodies
+		int *				mActiveBodyToCCDBody = nullptr;							///< A mapping between an index in BodyManager::mActiveBodies and the index in mCCDBodies
+		uint32				mNumActiveBodyToCCDBody = 0;							///< Number of indices in mActiveBodyToCCDBody
+
 		// Jobs in order of execution (some run in parallel)
 		JobHandle			mBroadPhasePrepare;										///< Prepares the new tree in the background
 		JobHandleArray		mStepListeners;											///< Listeners to notify of the beginning of a physics step
@@ -142,7 +123,12 @@ public:
 		JobHandle			mBuildIslandsFromConstraints;							///< Go over all constraints and assign the bodies they're attached to to an island
 		JobHandle			mFinalizeIslands;										///< Finalize calculation simulation islands
 		JobHandle			mBodySetIslandIndex;									///< Set the current island index on each body (not used by the simulation, only for drawing purposes)
-		SubSteps			mSubSteps;												///< Integration sub steps
+		JobHandleArray		mSolveVelocityConstraints;								///< Solve the constraints in the velocity domain
+		JobHandle			mPreIntegrateVelocity;									///< Setup integration of all body positions
+		JobHandleArray		mIntegrateVelocity;										///< Integrate all body positions
+		JobHandle			mPostIntegrateVelocity;									///< Finalize integration of all body positions
+		JobHandle			mResolveCCDContacts;									///< Updates the positions and velocities for all bodies that need continuous collision detection
+		JobHandleArray		mSolvePositionConstraints;								///< Solve all constraints in the position domain
 		JobHandle			mContactRemovedCallbacks;								///< Calls the contact removed callbacks
 		JobHandle			mStartNextStep;											///< Job that kicks the next step (empty for the last step)
 	};
@@ -158,9 +144,7 @@ public:
 	JobSystem::Barrier *	mBarrier;												///< Barrier used to wait for all physics jobs to complete
 
 	float					mStepDeltaTime;											///< Delta time for a simulation step (collision step)
-	float					mSubStepDeltaTime;										///< Delta time for a simulation sub step (integration step)
 	float					mWarmStartImpulseRatio;									///< Ratio of this step delta time vs last step
-	bool					mUseLargeIslandSplitter;								///< If true, use large island splitting
 	atomic<uint32>			mErrors { 0 };											///< Errors that occurred during the update, actual type is EPhysicsUpdateError
 
 	Constraint **			mActiveConstraints = nullptr;							///< Constraints that were active at the start of the physics update step (activating bodies can activate constraints and we need a consistent snapshot). Only these constraints will be resolved.

+ 1 - 1
PerformanceTest/PerformanceTest.cpp

@@ -327,7 +327,7 @@ int main(int argc, char** argv)
 					chrono::high_resolution_clock::time_point clock_start = chrono::high_resolution_clock::now();
 
 					// Do a physics step
-					physics_system.Update(cDeltaTime, 1, 1, &temp_allocator, &job_system);
+					physics_system.Update(cDeltaTime, 1, &temp_allocator, &job_system);
 
 					// Stop measuring
 					chrono::high_resolution_clock::time_point clock_end = chrono::high_resolution_clock::now();

+ 1 - 2
Samples/SamplesApp.cpp

@@ -400,7 +400,6 @@ SamplesApp::SamplesApp()
 			mDebugUI->CreateSlider(phys_settings, "Gravity (m/s^2)", -mPhysicsSystem->GetGravity().GetY(), 0.0f, 20.0f, 1.0f, [this](float inValue) { mPhysicsSystem->SetGravity(Vec3(0, -inValue, 0)); });
 			mDebugUI->CreateSlider(phys_settings, "Update Frequency (Hz)", mUpdateFrequency, 7.5f, 300.0f, 2.5f, [this](float inValue) { mUpdateFrequency = inValue; });
 			mDebugUI->CreateSlider(phys_settings, "Num Collision Steps", float(mCollisionSteps), 1.0f, 4.0f, 1.0f, [this](float inValue) { mCollisionSteps = int(inValue); });
-			mDebugUI->CreateSlider(phys_settings, "Num Integration Sub Steps", float(mIntegrationSubSteps), 1.0f, 4.0f, 1.0f, [this](float inValue) { mIntegrationSubSteps = int(inValue); });
 			mDebugUI->CreateSlider(phys_settings, "Num Velocity Steps", float(mPhysicsSettings.mNumVelocitySteps), 0, 30, 1, [this](float inValue) { mPhysicsSettings.mNumVelocitySteps = int(round(inValue)); mPhysicsSystem->SetPhysicsSettings(mPhysicsSettings); });
 			mDebugUI->CreateSlider(phys_settings, "Num Position Steps", float(mPhysicsSettings.mNumPositionSteps), 0, 30, 1, [this](float inValue) { mPhysicsSettings.mNumPositionSteps = int(round(inValue)); mPhysicsSystem->SetPhysicsSettings(mPhysicsSettings); });
 			mDebugUI->CreateSlider(phys_settings, "Baumgarte Stabilization Factor", mPhysicsSettings.mBaumgarte, 0.01f, 1.0f, 0.05f, [this](float inValue) { mPhysicsSettings.mBaumgarte = inValue; mPhysicsSystem->SetPhysicsSettings(mPhysicsSettings); });
@@ -2168,7 +2167,7 @@ void SamplesApp::StepPhysics(JobSystem *inJobSystem)
 	uint64 start_tick = GetProcessorTickCount();
 
 	// Step the world (with fixed frequency)
-	mPhysicsSystem->Update(delta_time, mCollisionSteps, mIntegrationSubSteps, mTempAllocator, inJobSystem);
+	mPhysicsSystem->Update(delta_time, mCollisionSteps, mTempAllocator, inJobSystem);
 #ifndef JPH_DISABLE_TEMP_ALLOCATOR
 	JPH_ASSERT(static_cast<TempAllocatorImpl *>(mTempAllocator)->IsEmpty());
 #endif // JPH_DISABLE_TEMP_ALLOCATOR

+ 0 - 1
Samples/SamplesApp.h

@@ -89,7 +89,6 @@ private:
 	int						mMaxConcurrentJobs = thread::hardware_concurrency();		// How many jobs to run in parallel
 	float					mUpdateFrequency = 60.0f;									// Physics update frequency
 	int						mCollisionSteps = 1;										// How many collision detection steps per physics update
-	int						mIntegrationSubSteps = 1;									// How many integration steps per physics update
 	TempAllocator *			mTempAllocator = nullptr;									// Allocator for temporary allocations
 	JobSystem *				mJobSystem = nullptr;										// The job system that runs physics jobs
 	JobSystem *				mJobSystemValidating = nullptr;								// The job system to use when validating determinism

+ 2 - 2
UnitTests/Physics/ActiveEdgesTests.cpp

@@ -198,7 +198,7 @@ TEST_SUITE("ActiveEdgesTest")
 	// Tests a discrete cube sliding over a mesh / heightfield shape
 	static void sDiscreteCubeSlide(Ref<ShapeSettings> inShape, bool inCheckActiveEdges)
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 
 		const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
 
@@ -269,7 +269,7 @@ TEST_SUITE("ActiveEdgesTest")
 	// Tests a linear cast cube sliding over a mesh / heightfield shape
 	static void sLinearCastCubeSlide(Ref<ShapeSettings> inShape, bool inCheckActiveEdges)
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 
 		const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
 

+ 4 - 4
UnitTests/Physics/ContactListenerTests.cpp

@@ -21,7 +21,7 @@ TEST_SUITE("ContactListenerTests")
 	// Let a sphere bounce on the floor with restition = 1
 	TEST_CASE("TestContactListenerElastic")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 
 		const float cSimulationTime = 1.0f;
 		const RVec3 cDistanceTraveled = c.PredictPosition(RVec3::sZero(), Vec3::sZero(), cGravity, cSimulationTime);
@@ -92,7 +92,7 @@ TEST_SUITE("ContactListenerTests")
 	// Let a sphere fall on the floor with restition = 0, then give it horizontal velocity, then take it away from the floor
 	TEST_CASE("TestContactListenerInelastic")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 
 		const float cSimulationTime = 1.0f;
 		const RVec3 cDistanceTraveled = c.PredictPosition(RVec3::sZero(), Vec3::sZero(), cGravity, cSimulationTime);
@@ -270,7 +270,7 @@ TEST_SUITE("ContactListenerTests")
 	{
 		for (int sign = -1; sign <= 1; sign += 2)
 		{
-			PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+			PhysicsTestContext c;
 
 			PhysicsSystem *s = c.GetSystem();
 			BodyInterface &bi = c.GetBodyInterface();
@@ -368,7 +368,7 @@ TEST_SUITE("ContactListenerTests")
 
 	TEST_CASE("TestSurfaceVelocity")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 
 		Body &floor = c.CreateBox(RVec3(0, -1, 0), Quat::sRotation(Vec3::sAxisY(), DegreesToRadians(10.0f)), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, Vec3(100.0f, 1.0f, 100.0f));
 		floor.SetFriction(1.0f);

+ 0 - 1
UnitTests/Physics/ConvexVsTrianglesTest.cpp

@@ -12,7 +12,6 @@
 #include <Jolt/Physics/Collision/CollideConvexVsTriangles.h>
 #include <Jolt/Physics/Collision/CollideSphereVsTriangles.h>
 #include "Layers.h"
-#include "PhysicsTestContext.h"
 
 TEST_SUITE("ConvexVsTrianglesTest")
 {

+ 8 - 8
UnitTests/Physics/MotionQualityLinearCastTests.cpp

@@ -19,7 +19,7 @@ TEST_SUITE("MotionQualityLinearCastTests")
 	// Two boxes colliding in the center, each has enough velocity to tunnel though in 1 step
 	TEST_CASE("TestDiscreteBoxVsDiscreteBox")
 	{
-		PhysicsTestContext c(1.0f / cFrequency, 1, 1);
+		PhysicsTestContext c(1.0f / cFrequency, 1);
 		c.ZeroGravity();
 
 		// Register listener
@@ -50,7 +50,7 @@ TEST_SUITE("MotionQualityLinearCastTests")
 	// Two boxes colliding in the center, each has enough velocity to step over the other in 1 step, restitution = 1
 	TEST_CASE("TestLinearCastBoxVsLinearCastBoxElastic")
 	{
-		PhysicsTestContext c(1.0f / cFrequency, 1, 1);
+		PhysicsTestContext c(1.0f / cFrequency, 1);
 		c.ZeroGravity();
 
 		const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
@@ -111,7 +111,7 @@ TEST_SUITE("MotionQualityLinearCastTests")
 	// Two boxes colliding in the center, each has enough velocity to step over the other in 1 step, restitution = 0
 	TEST_CASE("TestLinearCastBoxVsLinearCastBoxInelastic")
 	{
-		PhysicsTestContext c(1.0f / cFrequency, 1, 1);
+		PhysicsTestContext c(1.0f / cFrequency, 1);
 		c.ZeroGravity();
 
 		const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
@@ -166,7 +166,7 @@ TEST_SUITE("MotionQualityLinearCastTests")
 	// Two boxes colliding in the center, linear cast vs inactive linear cast
 	TEST_CASE("TestLinearCastBoxVsInactiveLinearCastBox")
 	{
-		PhysicsTestContext c(1.0f / cFrequency, 1, 1);
+		PhysicsTestContext c(1.0f / cFrequency, 1);
 		c.ZeroGravity();
 
 		const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
@@ -217,7 +217,7 @@ TEST_SUITE("MotionQualityLinearCastTests")
 	// Two boxes colliding in the center, linear cast vs inactive discrete
 	TEST_CASE("TestLinearCastBoxVsInactiveDiscreteBox")
 	{
-		PhysicsTestContext c(1.0f / cFrequency, 1, 1);
+		PhysicsTestContext c(1.0f / cFrequency, 1);
 		c.ZeroGravity();
 
 		const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
@@ -271,7 +271,7 @@ TEST_SUITE("MotionQualityLinearCastTests")
 		const Vec3 cAngledOffset1(1, 0, -2);
 		const Vec3 cAngledVelocity = -cFrequency * 2 * cAngledOffset1;
 
-		PhysicsTestContext c(1.0f / cFrequency, 1, 1);
+		PhysicsTestContext c(1.0f / cFrequency, 1);
 		c.ZeroGravity();
 
 		const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
@@ -315,7 +315,7 @@ TEST_SUITE("MotionQualityLinearCastTests")
 	// Two boxes colliding in the center, linear cast vs fast moving discrete, should tunnel through because all discrete bodies are moved before linear cast bodies are tested
 	TEST_CASE("TestLinearCastBoxVsFastDiscreteBox")
 	{
-		PhysicsTestContext c(1.0f / cFrequency, 1, 1);
+		PhysicsTestContext c(1.0f / cFrequency, 1);
 		c.ZeroGravity();
 
 		// Register listener
@@ -343,7 +343,7 @@ TEST_SUITE("MotionQualityLinearCastTests")
 	// Two boxes colliding in the center, linear cast vs moving discrete, discrete is slow enough not to tunnel through linear cast body
 	TEST_CASE("TestLinearCastBoxVsSlowDiscreteBox")
 	{
-		PhysicsTestContext c(1.0f / cFrequency, 1, 1);
+		PhysicsTestContext c(1.0f / cFrequency, 1);
 		c.ZeroGravity();
 
 		const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;

+ 6 - 6
UnitTests/Physics/PhysicsDeterminismTests.cpp

@@ -99,10 +99,10 @@ TEST_SUITE("PhysicsDeterminismTests")
 
 	TEST_CASE("TestGridOfBoxesDiscrete")
 	{
-		PhysicsTestContext c1(1.0f / 60.0f, 1, 1, 0);
+		PhysicsTestContext c1(1.0f / 60.0f, 1, 0);
 		CreateGridOfBoxesDiscrete(c1);
 
-		PhysicsTestContext c2(1.0f / 60.0f, 1, 1, 15);
+		PhysicsTestContext c2(1.0f / 60.0f, 1, 15);
 		CreateGridOfBoxesDiscrete(c2);
 
 		CompareSimulations(c1, c2, 5.0f);
@@ -126,10 +126,10 @@ TEST_SUITE("PhysicsDeterminismTests")
 
 	TEST_CASE("TestGridOfBoxesLinearCast")
 	{
-		PhysicsTestContext c1(1.0f / 60.0f, 1, 1, 0);
+		PhysicsTestContext c1(1.0f / 60.0f, 1, 0);
 		CreateGridOfBoxesLinearCast(c1);
 
-		PhysicsTestContext c2(1.0f / 60.0f, 1, 1, 15);
+		PhysicsTestContext c2(1.0f / 60.0f, 1, 15);
 		CreateGridOfBoxesLinearCast(c2);
 
 		CompareSimulations(c1, c2, 5.0f);
@@ -184,10 +184,10 @@ TEST_SUITE("PhysicsDeterminismTests")
 
 	TEST_CASE("TestGridOfBoxesConstrained")
 	{
-		PhysicsTestContext c1(1.0f / 60.0f, 1, 1, 0);
+		PhysicsTestContext c1(1.0f / 60.0f, 1, 0);
 		CreateGridOfBoxesConstrained(c1);
 
-		PhysicsTestContext c2(1.0f / 60.0f, 1, 1, 15);
+		PhysicsTestContext c2(1.0f / 60.0f, 1, 15);
 		CreateGridOfBoxesConstrained(c2);
 
 		CompareSimulations(c1, c2, 5.0f);

+ 1 - 1
UnitTests/Physics/PhysicsStepListenerTests.cpp

@@ -27,7 +27,7 @@ TEST_SUITE("StepListenerTest")
 	// Perform the actual listener test with a variable amount of collision steps
 	static void DoTest(int inCollisionSteps)
 	{
-		PhysicsTestContext c(1.0f / 60.0f, inCollisionSteps, 1);
+		PhysicsTestContext c(1.0f / 60.0f, inCollisionSteps);
 
 		// Initialize and add listeners
 		TestStepListener listeners[10];

+ 57 - 117
UnitTests/Physics/PhysicsTests.cpp

@@ -170,7 +170,7 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsBodyIDSequenceNumber")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		BodyInterface &bi = c.GetBodyInterface();
 
 		// Create a body and check it's id
@@ -193,7 +193,7 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsBodyIDOverride")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		BodyInterface &bi = c.GetBodyInterface();
 
 		// Dummy creation settings
@@ -254,7 +254,7 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsBodyUserData")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		BodyInterface &bi = c.GetBodyInterface();
 
 		// Create a body and pass user data through the creation settings
@@ -274,7 +274,7 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsConstraintUserData")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 
 		// Create a body
 		Body &body = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(1.0f));
@@ -296,7 +296,7 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsPosition")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		BodyInterface &bi = c.GetBodyInterface();
 
 		// Translate / rotate the box
@@ -324,7 +324,7 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsOverrideMassAndInertia")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		BodyInterface &bi = c.GetBodyInterface();
 
 		const float cDensity = 1234.0f;
@@ -391,29 +391,17 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsFreeFall")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		TestPhysicsFreeFall(c);
 	}
 
-	TEST_CASE("TestPhysicsFreeFallSubStep")
+	TEST_CASE("TestPhysicsFreeFallStep")
 	{
-		PhysicsTestContext c1(2.0f / 60.0f, 1, 2);
+		PhysicsTestContext c1(2.0f / 60.0f, 2);
 		TestPhysicsFreeFall(c1);
 
-		PhysicsTestContext c2(4.0f / 60.0f, 1, 4);
+		PhysicsTestContext c2(4.0f / 60.0f, 4);
 		TestPhysicsFreeFall(c2);
-
-		PhysicsTestContext c3(4.0f / 60.0f, 2, 2);
-		TestPhysicsFreeFall(c3);
-
-		PhysicsTestContext c4(2.0f / 60.0f, 2, 1);
-		TestPhysicsFreeFall(c4);
-
-		PhysicsTestContext c5(8.0f / 60.0f, 4, 2);
-		TestPhysicsFreeFall(c5);
-
-		PhysicsTestContext c6(4.0f / 60.0f, 4, 1);
-		TestPhysicsFreeFall(c6);
 	}
 
 	// Test acceleration of a box with force applied
@@ -445,29 +433,17 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsApplyForce")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		TestPhysicsApplyForce(c);
 	}
 
-	TEST_CASE("TestPhysicsApplyForceSubStep")
+	TEST_CASE("TestPhysicsApplyForceStep")
 	{
-		PhysicsTestContext c1(2.0f / 60.0f, 1, 2);
+		PhysicsTestContext c1(2.0f / 60.0f, 2);
 		TestPhysicsApplyForce(c1);
 
-		PhysicsTestContext c2(4.0f / 60.0f, 1, 4);
+		PhysicsTestContext c2(4.0f / 60.0f, 4);
 		TestPhysicsApplyForce(c2);
-
-		PhysicsTestContext c3(4.0f / 60.0f, 2, 2);
-		TestPhysicsApplyForce(c3);
-
-		PhysicsTestContext c4(2.0f / 60.0f, 2, 1);
-		TestPhysicsApplyForce(c4);
-
-		PhysicsTestContext c5(8.0f / 60.0f, 4, 2);
-		TestPhysicsApplyForce(c5);
-
-		PhysicsTestContext c6(4.0f / 60.0f, 4, 1);
-		TestPhysicsApplyForce(c6);
 	}
 
 	// Test angular accelartion for a box by applying torque every frame
@@ -501,29 +477,17 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsApplyTorque")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		TestPhysicsApplyTorque(c);
 	}
 
-	TEST_CASE("TestPhysicsApplyTorqueSubStep")
+	TEST_CASE("TestPhysicsApplyTorqueStep")
 	{
-		PhysicsTestContext c1(2.0f / 60.0f, 1, 2);
+		PhysicsTestContext c1(2.0f / 60.0f, 2);
 		TestPhysicsApplyTorque(c1);
 
-		PhysicsTestContext c2(4.0f / 60.0f, 1, 4);
+		PhysicsTestContext c2(4.0f / 60.0f, 4);
 		TestPhysicsApplyTorque(c2);
-
-		PhysicsTestContext c3(4.0f / 60.0f, 2, 2);
-		TestPhysicsApplyTorque(c3);
-
-		PhysicsTestContext c4(2.0f / 60.0f, 2, 1);
-		TestPhysicsApplyTorque(c4);
-
-		PhysicsTestContext c5(8.0f / 60.0f, 4, 2);
-		TestPhysicsApplyTorque(c5);
-
-		PhysicsTestContext c6(4.0f / 60.0f, 4, 1);
-		TestPhysicsApplyTorque(c6);
 	}		
 
 	// Let a sphere bounce on the floor with restition = 1
@@ -552,10 +516,10 @@ TEST_SUITE("PhysicsTests")
 
 		// Assert that collision is processed and velocity is reversed (which is required for a fully elastic collision). 
 		// Note that the physics engine will first apply gravity for the time step and then do collision detection, 
-		// hence the reflected velocity is actually 1 sub-step times gravity bigger than it would be in reality
+		// hence the reflected velocity is actually 1 step times gravity bigger than it would be in reality
 		// For the remainder of cDeltaTime normal gravity will be applied
-		float sub_step_delta_time = ioContext.GetSubStepDeltaTime();
-		float remaining_step_time = ioContext.GetDeltaTime() - ioContext.GetSubStepDeltaTime();
+		float sub_step_delta_time = ioContext.GetStepDeltaTime();
+		float remaining_step_time = ioContext.GetDeltaTime() - ioContext.GetStepDeltaTime();
 		Vec3 reflected_velocity_after_sub_step = -(cSimulationTime + sub_step_delta_time) * cGravity;
 		Vec3 reflected_velocity_after_full_step = reflected_velocity_after_sub_step + remaining_step_time * cGravity;
 		CHECK_APPROX_EQUAL(reflected_velocity_after_full_step, body.GetLinearVelocity(), 1.0e-4f);
@@ -575,26 +539,17 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsCollisionElastic")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		TestPhysicsCollisionElastic(c);
 	}
 
-	TEST_CASE("TestPhysicsCollisionElasticSubStep")
+	TEST_CASE("TestPhysicsCollisionElasticStep")
 	{
-		PhysicsTestContext c1(2.0f / 60.0f, 1, 2);
+		PhysicsTestContext c1(2.0f / 60.0f, 2);
 		TestPhysicsCollisionElastic(c1);
 
-		PhysicsTestContext c2(4.0f / 60.0f, 1, 4);
+		PhysicsTestContext c2(4.0f / 60.0f, 4);
 		TestPhysicsCollisionElastic(c2);
-
-		PhysicsTestContext c3(4.0f / 60.0f, 2, 2);
-		TestPhysicsCollisionElastic(c3);
-
-		PhysicsTestContext c4(2.0f / 60.0f, 2, 1);
-		TestPhysicsCollisionElastic(c4);
-
-		PhysicsTestContext c5(4.0f / 60.0f, 4, 1);
-		TestPhysicsCollisionElastic(c5);
 	}		
 
 	// Let a sphere bounce on the floor with restitution = 0
@@ -635,26 +590,17 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsCollisionInelastic")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		TestPhysicsCollisionInelastic(c);
 	}
 
-	TEST_CASE("TestPhysicsCollisionInelasticSubStep")
+	TEST_CASE("TestPhysicsCollisionInelasticStep")
 	{
-		PhysicsTestContext c1(2.0f / 60.0f, 1, 2);
+		PhysicsTestContext c1(2.0f / 60.0f, 2);
 		TestPhysicsCollisionInelastic(c1);
 
-		PhysicsTestContext c2(4.0f / 60.0f, 1, 4);
+		PhysicsTestContext c2(4.0f / 60.0f, 4);
 		TestPhysicsCollisionInelastic(c2);
-
-		PhysicsTestContext c3(4.0f / 60.0f, 2, 2);
-		TestPhysicsCollisionInelastic(c3);
-
-		PhysicsTestContext c4(2.0f / 60.0f, 2, 1);
-		TestPhysicsCollisionInelastic(c4);
-
-		PhysicsTestContext c5(4.0f / 60.0f, 4, 1);
-		TestPhysicsCollisionInelastic(c5);
 	}		
 		
 	// Let box intersect with floor by cPenetrationSlop. It should not move, this is the maximum penetration allowed.
@@ -679,16 +625,16 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsPenetrationSlop1")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		TestPhysicsPenetrationSlop1(c);
 	}
 
-	TEST_CASE("TestPhysicsPenetrationSlop1SubStep")
+	TEST_CASE("TestPhysicsPenetrationSlop1Step")
 	{
-		PhysicsTestContext c(1.0f / 30.0f, 1, 2);
+		PhysicsTestContext c(2.0f / 60.0f, 2);
 		TestPhysicsPenetrationSlop1(c);
 
-		PhysicsTestContext c2(1.0f / 30.0f, 2, 1);
+		PhysicsTestContext c2(4.0f / 60.0f, 4);
 		TestPhysicsPenetrationSlop1(c2);
 	}		
 
@@ -715,16 +661,16 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsPenetrationSlop2")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		TestPhysicsPenetrationSlop2(c);
 	}
 
-	TEST_CASE("TestPhysicsPenetrationSlop2SubStep")
+	TEST_CASE("TestPhysicsPenetrationSlop2Step")
 	{
-		PhysicsTestContext c(1.0f / 30.0f, 1, 2);
+		PhysicsTestContext c(2.0f / 60.0f, 2);
 		TestPhysicsPenetrationSlop2(c);
 
-		PhysicsTestContext c2(1.0f / 30.0f, 2, 1);
+		PhysicsTestContext c2(4.0f / 60.0f, 4);
 		TestPhysicsPenetrationSlop2(c2);
 	}		
 
@@ -750,22 +696,22 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsPenetrationSlop3")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		TestPhysicsPenetrationSlop3(c);
 	}
 
-	TEST_CASE("TestPhysicsPenetrationSlop3SubStep")
+	TEST_CASE("TestPhysicsPenetrationSlop3Step")
 	{
-		PhysicsTestContext c(1.0f / 30.0f, 1, 2);
+		PhysicsTestContext c(2.0f / 60.0f, 2);
 		TestPhysicsPenetrationSlop3(c);
 
-		PhysicsTestContext c2(1.0f / 30.0f, 2, 1);
+		PhysicsTestContext c2(4.0f / 60.0f, 4);
 		TestPhysicsPenetrationSlop3(c2);
 	}
 
 	TEST_CASE("TestPhysicsOutsideOfSpeculativeContactDistance")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		Body &floor = c.CreateFloor();
 		c.ZeroGravity();
 
@@ -808,7 +754,7 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsInsideSpeculativeContactDistanceNoRestitution")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		Body &floor = c.CreateFloor();
 		c.ZeroGravity();
 
@@ -875,7 +821,7 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsInsideSpeculativeContactDistanceWithRestitution")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		Body &floor = c.CreateFloor();
 		c.ZeroGravity();
 
@@ -931,7 +877,7 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsInsideSpeculativeContactDistanceNoHit")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		Body &floor = c.CreateFloor();
 		floor.SetRestitution(1.0f);
 		c.ZeroGravity();
@@ -984,7 +930,7 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsInsideSpeculativeContactDistanceMovingAway")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		Body &floor = c.CreateFloor();
 		c.ZeroGravity();
 
@@ -1087,20 +1033,14 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsActivationDeactivation")
 	{
-		PhysicsTestContext c1(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c1(1.0f / 60.0f, 1);
 		TestPhysicsActivationDeactivation(c1);
 
-		PhysicsTestContext c2(2.0f / 60.0f, 1, 2);
+		PhysicsTestContext c2(2.0f / 60.0f, 2);
 		TestPhysicsActivationDeactivation(c2);
 
-		PhysicsTestContext c3(2.0f / 60.0f, 2, 1);
+		PhysicsTestContext c3(4.0f / 60.0f, 4);
 		TestPhysicsActivationDeactivation(c3);
-
-		PhysicsTestContext c4(4.0f / 60.0f, 4, 1);
-		TestPhysicsActivationDeactivation(c4);
-
-		PhysicsTestContext c5(8.0f / 60.0f, 4, 2);
-		TestPhysicsActivationDeactivation(c5);
 	}
 
 	// A test that checks that a row of penetrating boxes will all activate and handle collision in 1 frame so that active bodies cannot tunnel through inactive bodies
@@ -1186,16 +1126,16 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestPhysicsActivateDuringStep")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		TestPhysicsActivateDuringStep(c, false);
 
-		PhysicsTestContext c2(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c2;
 		TestPhysicsActivateDuringStep(c2, true);
 	}
 
 	TEST_CASE("TestPhysicsBroadPhaseLayers")
 	{
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c;
 		BodyInterface &bi = c.GetBodyInterface();
 
 		// Reduce slop
@@ -1345,9 +1285,9 @@ TEST_SUITE("PhysicsTests")
 
 	TEST_CASE("TestMultiplePhysicsSystems")
 	{
-		PhysicsTestContext c1(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c1;
 		c1.ZeroGravity();
-		PhysicsTestContext c2(1.0f / 60.0f, 1, 1);
+		PhysicsTestContext c2;
 		c2.ZeroGravity();
 
 		const RVec3 cBox1Position(1.0f, 2.0f, 3.0f);
@@ -1387,7 +1327,7 @@ TEST_SUITE("PhysicsTests")
 	TEST_CASE("TestOutOfBodies")
 	{
 		// Create a context with space for a single body
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1, 0, 1);
+		PhysicsTestContext c(1.0f / 60.0f, 1, 0, 1);
 
 		BodyInterface& bi = c.GetBodyInterface();
 
@@ -1413,7 +1353,7 @@ TEST_SUITE("PhysicsTests")
 	TEST_CASE("TestOutOfContactConstraints")
 	{
 		// Create a context with space for 8 constraints
-		PhysicsTestContext c(1.0f / 60.0f, 1, 1, 0, 1024, 4096, 8);
+		PhysicsTestContext c(1.0f / 60.0f, 1, 0, 1024, 4096, 8);
 
 		c.CreateFloor();
 
@@ -1445,7 +1385,7 @@ TEST_SUITE("PhysicsTests")
 		for (float angle = 0; angle < 360.0f; angle += 30.0f)
 		{
 			// Create a context with space for 8 constraints
-			PhysicsTestContext c(1.0f / 60.0f, 1, 1, 0, 1024, 4096, 8);
+			PhysicsTestContext c(1.0f / 60.0f, 1, 0, 1024, 4096, 8);
 
 			// Create floor
 			Body &floor = c.CreateFloor();

+ 6 - 7
UnitTests/PhysicsTestContext.cpp

@@ -10,7 +10,7 @@
 #include <Jolt/Core/JobSystemThreadPool.h>
 #include <Jolt/Core/TempAllocator.h>
 
-PhysicsTestContext::PhysicsTestContext(float inDeltaTime, int inCollisionSteps, int inIntegrationSubSteps, int inWorkerThreads, uint inMaxBodies, uint inMaxBodyPairs, uint inMaxContactConstraints) :
+PhysicsTestContext::PhysicsTestContext(float inDeltaTime, int inCollisionSteps, int inWorkerThreads, uint inMaxBodies, uint inMaxBodyPairs, uint inMaxContactConstraints) :
 #ifdef JPH_DISABLE_TEMP_ALLOCATOR
 	mTempAllocator(new TempAllocatorMalloc()),
 #else
@@ -18,8 +18,7 @@ PhysicsTestContext::PhysicsTestContext(float inDeltaTime, int inCollisionSteps,
 #endif
 	mJobSystem(new JobSystemThreadPool(cMaxPhysicsJobs, cMaxPhysicsBarriers, inWorkerThreads)),
 	mDeltaTime(inDeltaTime),
-	mCollisionSteps(inCollisionSteps),
-	mIntegrationSubSteps(inIntegrationSubSteps)
+	mCollisionSteps(inCollisionSteps)
 {
 	// Create physics system
 	mSystem = new PhysicsSystem();
@@ -86,7 +85,7 @@ EPhysicsUpdateError PhysicsTestContext::Simulate(float inTotalTime, function<voi
 	for (int s = 0; s < cNumSteps; ++s)
 	{
 		inPreStepCallback();
-		errors |= mSystem->Update(mDeltaTime, mCollisionSteps, mIntegrationSubSteps, mTempAllocator, mJobSystem);
+		errors |= mSystem->Update(mDeltaTime, mCollisionSteps, mTempAllocator, mJobSystem);
 	#ifndef JPH_DISABLE_TEMP_ALLOCATOR
 		JPH_ASSERT(static_cast<TempAllocatorImpl *>(mTempAllocator)->IsEmpty());
 	#endif // JPH_DISABLE_TEMP_ALLOCATOR
@@ -97,7 +96,7 @@ EPhysicsUpdateError PhysicsTestContext::Simulate(float inTotalTime, function<voi
 
 EPhysicsUpdateError PhysicsTestContext::SimulateSingleStep()
 {
-	EPhysicsUpdateError errors = mSystem->Update(mDeltaTime, mCollisionSteps, mIntegrationSubSteps, mTempAllocator, mJobSystem);
+	EPhysicsUpdateError errors = mSystem->Update(mDeltaTime, mCollisionSteps, mTempAllocator, mJobSystem);
 #ifndef JPH_DISABLE_TEMP_ALLOCATOR
 	JPH_ASSERT(static_cast<TempAllocatorImpl *>(mTempAllocator)->IsEmpty());
 #endif // JPH_DISABLE_TEMP_ALLOCATOR
@@ -110,7 +109,7 @@ RVec3 PhysicsTestContext::PredictPosition(RVec3Arg inPosition, Vec3Arg inVelocit
 	RVec3 pos = inPosition;
 	Vec3 vel = inVelocity;
 
-	const float delta_time = GetSubStepDeltaTime();
+	const float delta_time = GetStepDeltaTime();
 	const int cNumSteps = int(round(inTotalTime / delta_time));
 	for (int s = 0; s < cNumSteps; ++s)
 	{
@@ -127,7 +126,7 @@ Quat PhysicsTestContext::PredictOrientation(QuatArg inRotation, Vec3Arg inAngula
 	Quat rot = inRotation;
 	Vec3 vel = inAngularVelocity;
 
-	const float delta_time = GetSubStepDeltaTime();
+	const float delta_time = GetStepDeltaTime();
 	const int cNumSteps = int(round(inTotalTime / delta_time));
 	for (int s = 0; s < cNumSteps; ++s)
 	{

+ 4 - 5
UnitTests/PhysicsTestContext.h

@@ -19,7 +19,7 @@ class PhysicsTestContext
 {
 public:
 	// Constructor / destructor
-						PhysicsTestContext(float inDeltaTime = 1.0f / 60.0f, int inCollisionSteps = 1, int inIntegrationSubSteps = 1, int inWorkerThreads = 0, uint inMaxBodies = 1024, uint inMaxBodyPairs = 4096, uint inMaxContactConstraints = 1024);
+						PhysicsTestContext(float inDeltaTime = 1.0f / 60.0f, int inCollisionSteps = 1, int inWorkerThreads = 0, uint inMaxBodies = 1024, uint inMaxBodyPairs = 4096, uint inMaxContactConstraints = 1024);
 						~PhysicsTestContext();
 
 	// Set the gravity to zero
@@ -76,10 +76,10 @@ public:
 		return mDeltaTime;
 	}
 
-	// Get delta time for a simulation integration sub step
-	inline float		GetSubStepDeltaTime() const
+	// Get delta time for a simulation collision step
+	inline float		GetStepDeltaTime() const
 	{
-		return mDeltaTime / (mCollisionSteps * mIntegrationSubSteps);
+		return mDeltaTime / mCollisionSteps;
 	}
 
 private:
@@ -91,5 +91,4 @@ private:
 	PhysicsSystem *		mSystem;
 	float				mDeltaTime;
 	int					mCollisionSteps;
-	int					mIntegrationSubSteps;
 };

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff