瀏覽代碼

Created BroadPhaseLayerInterface so that we don't need an array mapping ObjectLayer -> BroadPhaseLayer anymore (#65)

* Added demo that shows objects changing layer
* Added unit test for layer switching
* Overriding object layers of loaded objects now that the layer numbers have changed
Jorrit Rouwe 3 年之前
父節點
當前提交
36dd3f8c8c

+ 43 - 5
HelloWorld/HelloWorld.cpp

@@ -89,6 +89,46 @@ namespace BroadPhaseLayers
 {
 	static constexpr BroadPhaseLayer NON_MOVING(0);
 	static constexpr BroadPhaseLayer MOVING(1);
+	static constexpr uint NUM_LAYERS(2);
+};
+
+// BroadPhaseLayerInterface implementation
+// This defines a mapping between object and broadphase layers.
+class BPLayerInterfaceImpl final : public BroadPhaseLayerInterface
+{
+public:
+									BPLayerInterfaceImpl()
+	{
+		// Create a mapping table from object to broad phase layer
+		mObjectToBroadPhase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
+		mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING;
+	}
+
+	virtual uint					GetNumBroadPhaseLayers() const override
+	{
+		return BroadPhaseLayers::NUM_LAYERS;
+	}
+
+	virtual BroadPhaseLayer			GetBroadPhaseLayer(ObjectLayer inLayer) const override
+	{
+		JPH_ASSERT(inLayer < Layers::NUM_LAYERS);
+		return mObjectToBroadPhase[inLayer];
+	}
+
+#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
+	virtual const char *			GetBroadPhaseLayerName(BroadPhaseLayer inLayer) const override
+	{
+		switch ((BroadPhaseLayer::Type)inLayer)
+		{
+		case (BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING:	return "NON_MOVING";
+		case (BroadPhaseLayer::Type)BroadPhaseLayers::MOVING:		return "MOVING";
+		default:													JPH_ASSERT(false); return "INVALID";
+		}
+	}
+#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
+
+private:
+	BroadPhaseLayer					mObjectToBroadPhase[Layers::NUM_LAYERS];
 };
 
 // Function that determines if two broadphase layers can collide
@@ -191,14 +231,12 @@ int main(int argc, char** argv)
 	const uint cMaxContactConstraints = 1024;
 
 	// Create mapping table from object layer to broadphase layer
-	ObjectToBroadPhaseLayer object_to_broadphase;
-	object_to_broadphase.resize(Layers::NUM_LAYERS);
-	object_to_broadphase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
-	object_to_broadphase[Layers::MOVING] = BroadPhaseLayers::MOVING;
+	// Note: As this is an interface, PhysicsSystem will take a reference to this so this instance needs to stay alive!
+	BPLayerInterfaceImpl broad_phase_layer_interface;
 
 	// Now we can create the actual physics system.
 	PhysicsSystem physics_system;
-	physics_system.Init(cMaxBodies, cNumBodyMutexes, cMaxBodyPairs, cMaxContactConstraints, object_to_broadphase, MyBroadPhaseCanCollide, MyObjectCanCollide);
+	physics_system.Init(cMaxBodies, cNumBodyMutexes, cMaxBodyPairs, cMaxContactConstraints, broad_phase_layer_interface, MyBroadPhaseCanCollide, MyObjectCanCollide);
 
 	// A body activation listener gets notified when bodies activate and go to sleep
 	// Note that this is called from a job so whatever you do here needs to be thread safe.

+ 6 - 4
Jolt/Physics/Body/Body.h

@@ -6,6 +6,7 @@
 #include <Core/NonCopyable.h>
 #include <Geometry/AABox.h>
 #include <Physics/Collision/Shape/Shape.h>
+#include <Physics/Collision/BroadPhase/BroadPhaseLayer.h>
 #include <Physics/Collision/ObjectLayer.h>
 #include <Physics/Collision/CollisionGroup.h>
 #include <Physics/Collision/TransformedShape.h>
@@ -69,6 +70,9 @@ public:
 	inline EMotionType		GetMotionType() const											{ return mMotionType; }
 	void					SetMotionType(EMotionType inMotionType);
 
+	/// Get broadphase layer, this determines in which broad phase sub-tree the object is placed
+	inline BroadPhaseLayer	GetBroadPhaseLayer() const										{ return mBroadPhaseLayer; }	
+
 	/// Get object layer, this determines which other objects it collides with
 	inline ObjectLayer		GetObjectLayer() const											{ return mObjectLayer; }
 
@@ -225,9 +229,6 @@ public:
 	/// Updates world space bounding box (should only be called by the PhysicsSystem)
 	void					CalculateWorldSpaceBoundsInternal();
 
-	/// Function to update body's layer (should only be called by the BodyInterface since it also requires updating the broadphase)
-	void					SetObjectLayerInternal(ObjectLayer inLayer)						{ mObjectLayer = inLayer; }
-
 	/// Function to update body's position (should only be called by the BodyInterface since it also requires updating the broadphase)
 	void					SetPositionAndRotationInternal(Vec3Arg inPosition, QuatArg inRotation);
 
@@ -298,10 +299,11 @@ private:
 	ObjectLayer				mObjectLayer;													///< The collision layer this body belongs to (determines if two objects can collide)
 
 	// 1 byte aligned
+	BroadPhaseLayer			mBroadPhaseLayer;												///< The broad phase layer this body belongs to
 	EMotionType				mMotionType;													///< Type of motion (static, dynamic or kinematic)
 	atomic<uint8>			mFlags = 0;														///< See EFlags for possible flags
 	
-	// 120 bytes up to here
+	// 121 bytes up to here
 
 #ifdef _DEBUG
 	string					mDebugName;														///< Name for debugging purposes

+ 2 - 1
Jolt/Physics/Body/BodyInterface.cpp

@@ -227,7 +227,8 @@ void BodyInterface::SetObjectLayer(const BodyID &inBodyID, ObjectLayer inLayer)
 		// Check if layer actually changed, updating the broadphase is rather expensive
 		if (body.GetObjectLayer() != inLayer)
 		{
-			body.SetObjectLayerInternal(inLayer);
+			// Update the layer on the body
+			mBodyManager->SetBodyObjectLayerInternal(body, inLayer);
 
 			// Notify broadphase of change
 			if (body.IsInBroadPhase())

+ 5 - 1
Jolt/Physics/Body/BodyManager.cpp

@@ -51,7 +51,7 @@ BodyManager::~BodyManager()
 	delete [] mActiveBodies;
 }
 
-void BodyManager::Init(uint inMaxBodies, uint inNumBodyMutexes)
+void BodyManager::Init(uint inMaxBodies, uint inNumBodyMutexes, const BroadPhaseLayerInterface &inLayerInterface)
 {
 	UniqueLock lock(mBodiesMutex, EPhysicsLockTypes::BodiesList);
 
@@ -70,6 +70,9 @@ void BodyManager::Init(uint inMaxBodies, uint inNumBodyMutexes)
 
 	// Allocate space for sequence numbers
 	mBodySequenceNumbers.resize(inMaxBodies);
+
+	// Keep layer interface
+	mBroadPhaseLayerInterface = &inLayerInterface;
 }
 
 uint BodyManager::GetNumBodies() const
@@ -170,6 +173,7 @@ Body *BodyManager::CreateBody(const BodyCreationSettings &inBodyCreationSettings
 	body->mMotionType = inBodyCreationSettings.mMotionType;
 	if (inBodyCreationSettings.mIsSensor)
 		body->mFlags.fetch_or(uint8(Body::EFlags::IsSensor), memory_order_relaxed);
+	SetBodyObjectLayerInternal(*body, inBodyCreationSettings.mObjectLayer);
 	body->mObjectLayer = inBodyCreationSettings.mObjectLayer;
 	body->mCollisionGroup = inBodyCreationSettings.mCollisionGroup;
 	

+ 7 - 1
Jolt/Physics/Body/BodyManager.h

@@ -31,7 +31,7 @@ public:
 									~BodyManager();
 
 	/// Initialize the manager
-	void							Init(uint inMaxBodies, uint inNumBodyMutexes);
+	void							Init(uint inMaxBodies, uint inNumBodyMutexes, const BroadPhaseLayerInterface &inLayerInterface);
 
 	/// Gets the current amount of bodies that are in the body manager
 	uint							GetNumBodies() const;
@@ -135,6 +135,9 @@ public:
 	/// Unlock all bodies. This should only be done during PhysicsSystem::Update().
 	void							UnlockAllBodies() const;
 
+	/// Function to update body's layer (should only be called by the BodyInterface since it also requires updating the broadphase)
+	inline void						SetBodyObjectLayerInternal(Body &ioBody, ObjectLayer inLayer) const { ioBody.mObjectLayer = inLayer; ioBody.mBroadPhaseLayer = mBroadPhaseLayerInterface->GetBroadPhaseLayer(inLayer); }
+
 	/// Set the Body::EFlags::InvalidateContactCache flag for the specified body. This means that the collision cache is invalid for any body pair involving that body until the next physics step.
 	void							InvalidateContactCacheForBody(Body &ioBody);
 
@@ -269,6 +272,9 @@ private:
 	/// Listener that is notified whenever a body is activated/deactivated
 	BodyActivationListener *		mActivationListener = nullptr;
 
+	/// Cached broadphase layer interface
+	const BroadPhaseLayerInterface *mBroadPhaseLayerInterface = nullptr;
+
 #ifdef JPH_ENABLE_ASSERTS
 	/// Debug system that tries to limit changes to active bodies during the PhysicsSystem::Update()
 	bool							mActiveBodiesLocked = false;

+ 1 - 1
Jolt/Physics/Collision/BroadPhase/BroadPhase.cpp

@@ -7,7 +7,7 @@
 
 namespace JPH {
 
-void BroadPhase::Init(BodyManager *inBodyManager, const ObjectToBroadPhaseLayer &inObjectToBroadPhaseLayer)
+void BroadPhase::Init(BodyManager *inBodyManager, const BroadPhaseLayerInterface &inLayerInterface)
 {
 	mBodyManager = inBodyManager;
 }

+ 2 - 7
Jolt/Physics/Collision/BroadPhase/BroadPhase.h

@@ -26,9 +26,9 @@ class BroadPhase : public BroadPhaseQuery
 public:
 	/// Initialize the broadphase.
 	/// @param inBodyManager The body manager singleton
-	/// @param inObjectToBroadPhaseLayer Maps object layer to broadphase layer, @see ObjectToBroadPhaseLayer. 
+	/// @param inLayerInterface Interface that maps object layers to broadphase layers.
 	/// Note that the broadphase takes a pointer to the data inside inObjectToBroadPhaseLayer so this object should remain static.
-	virtual void		Init(BodyManager *inBodyManager, const ObjectToBroadPhaseLayer &inObjectToBroadPhaseLayer);
+	virtual void		Init(BodyManager *inBodyManager, const BroadPhaseLayerInterface &inLayerInterface);
 
 	/// Should be called after many objects have been inserted to make the broadphase more efficient, usually done on startup only
 	virtual void		Optimize()															{ /* Optionally overridden by implementation */ }
@@ -94,11 +94,6 @@ public:
 	/// Same as BroadPhaseQuery::CastAABox but can be implemented in a way to take no broad phase locks.
 	virtual void		CastAABoxNoLock(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter) const = 0; 
 
-#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
-	/// Set function that converts a broadphase layer to a human readable string for debugging purposes
-	virtual void		SetBroadPhaseLayerToString(BroadPhaseLayerToString inBroadPhaseLayerToString) { /* Can be implemented by derived classes */ }
-#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
-
 #ifdef JPH_TRACK_BROADPHASE_STATS
 	/// Trace the collected broadphase stats in CSV form.
 	/// This report can be used to judge and tweak the efficiency of the broadphase.

+ 18 - 7
Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h

@@ -55,17 +55,28 @@ private:
 /// Constant value used to indicate an invalid broad phase layer
 static constexpr BroadPhaseLayer cBroadPhaseLayerInvalid(0xff);
 
-/// An array whose length corresponds to the max amount of object layers that should be supported.
-/// To map these to a broadphase layer you'd do vector[BroadPhaseLayer]. The broadphase layers should be tightly 
-/// packed, i.e. the lowest value should be 0 and the amount of sub structures that are created in the broadphase is max(inObjectToBroadPhaseLayer).
-using ObjectToBroadPhaseLayer = vector<BroadPhaseLayer>;
+/// Interface that the application should implement to allow mapping object layers to broadphase layers
+class BroadPhaseLayerInterface : public NonCopyable
+{
+public:
+	/// Destructor
+	virtual							~BroadPhaseLayerInterface() = default;
+
+	/// Return the number of broadphase layers there are
+	virtual uint					GetNumBroadPhaseLayers() const = 0;
+
+	/// Convert an object layer to the corresponding broadphase layer
+	virtual BroadPhaseLayer			GetBroadPhaseLayer(ObjectLayer inLayer) const = 0;
+
+#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
+	/// Get the user readable name of a broadphase layer (debugging purposes)
+	virtual const char *			GetBroadPhaseLayerName(BroadPhaseLayer inLayer) const = 0;
+#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
+};
 
 /// Function to test if an object can collide with a broadphase layer. Used while finding collision pairs.
 using ObjectVsBroadPhaseLayerFilter = bool (*)(ObjectLayer inLayer1, BroadPhaseLayer inLayer2);
 
-/// Function to convert a broadphase layer to a string for debugging purposes
-using BroadPhaseLayerToString = const char * (*)(BroadPhaseLayer inLayer);
-
 /// Filter class for broadphase layers
 class BroadPhaseLayerFilter : public NonCopyable
 {

+ 19 - 36
Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.cpp

@@ -15,12 +15,14 @@ BroadPhaseQuadTree::~BroadPhaseQuadTree()
 	delete [] mLayers;
 }
 
-void BroadPhaseQuadTree::Init(BodyManager* inBodyManager, const ObjectToBroadPhaseLayer &inObjectToBroadPhaseLayer)
+void BroadPhaseQuadTree::Init(BodyManager *inBodyManager, const BroadPhaseLayerInterface &inLayerInterface)
 {
-	BroadPhase::Init(inBodyManager, inObjectToBroadPhaseLayer);
+	BroadPhase::Init(inBodyManager, inLayerInterface);
 
 	// Store input parameters
-	mObjectToBroadPhaseLayer = inObjectToBroadPhaseLayer;
+	mBroadPhaseLayerInterface = &inLayerInterface;
+	mNumLayers = inLayerInterface.GetNumBroadPhaseLayers();
+	JPH_ASSERT(mNumLayers < (BroadPhaseLayer::Type)cBroadPhaseLayerInvalid);
 
 	// Store max bodies
 	mMaxBodies = inBodyManager->GetMaxBodies();
@@ -34,21 +36,17 @@ void BroadPhaseQuadTree::Init(BodyManager* inBodyManager, const ObjectToBroadPha
 	uint32 num_leaves_plus_internal_nodes = num_leaves + (num_leaves + 2) / 3; // = Sum(num_leaves * 4^-i) with i = [0, Inf].
 	mAllocator.Init(2 * num_leaves_plus_internal_nodes, 256); // We use double the amount of nodes while rebuilding the tree during Update()
 
-	// Determine min and max layers
-	BroadPhaseLayer::Type min_layer = (BroadPhaseLayer::Type)cBroadPhaseLayerInvalid, max_layer = 0;
-	for (BroadPhaseLayer layer : inObjectToBroadPhaseLayer)
-	{
-		min_layer = min(min_layer, (BroadPhaseLayer::Type)layer);
-		max_layer = max(max_layer, (BroadPhaseLayer::Type)layer);
-	}
-	JPH_ASSERT(min_layer == 0); // Assume layers start at 0
-	JPH_ASSERT(max_layer != (BroadPhaseLayer::Type)cBroadPhaseLayerInvalid); // Assume the invalid layer is unused
-	mNumLayers = max_layer + 1;
-
 	// Init sub trees
 	mLayers = new QuadTree [mNumLayers];
 	for (uint l = 0; l < mNumLayers; ++l)
+	{
 		mLayers[l].Init(mAllocator);
+
+#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
+		// Set the name of the layer
+		mLayers[l].SetName(inLayerInterface.GetBroadPhaseLayerName(BroadPhaseLayer(BroadPhaseLayer::Type(l))));
+#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
+	}
 }
 
 void BroadPhaseQuadTree::FrameSync()
@@ -159,21 +157,18 @@ BroadPhase::AddState BroadPhaseQuadTree::AddBodiesPrepare(BodyID *ioBodies, int
 	LayerState *state = new LayerState [mNumLayers];
 
 	// Sort bodies on layer
-	const BroadPhaseLayer *object_to_broadphase = mObjectToBroadPhaseLayer.data();
 	Body * const * const bodies_ptr = bodies.data(); // C pointer or else sort is incredibly slow in debug mode
-	sort(ioBodies, ioBodies + inNumber, [bodies_ptr, object_to_broadphase](BodyID inLHS, BodyID inRHS) { return object_to_broadphase[bodies_ptr[inLHS.GetIndex()]->GetObjectLayer()] < object_to_broadphase[bodies_ptr[inRHS.GetIndex()]->GetObjectLayer()]; });
+	sort(ioBodies, ioBodies + inNumber, [bodies_ptr](BodyID inLHS, BodyID inRHS) { return bodies_ptr[inLHS.GetIndex()]->GetBroadPhaseLayer() < bodies_ptr[inRHS.GetIndex()]->GetBroadPhaseLayer(); });
 
 	BodyID *b_start = ioBodies, *b_end = ioBodies + inNumber;
 	while (b_start < b_end)
 	{
 		// Get broadphase layer
-		ObjectLayer first_body_object_layer = bodies[b_start->GetIndex()]->GetObjectLayer();
-		JPH_ASSERT(first_body_object_layer < mObjectToBroadPhaseLayer.size());
-		BroadPhaseLayer::Type broadphase_layer = (BroadPhaseLayer::Type)object_to_broadphase[first_body_object_layer];
+		BroadPhaseLayer::Type broadphase_layer = (BroadPhaseLayer::Type)bodies[b_start->GetIndex()]->GetBroadPhaseLayer();
 		JPH_ASSERT(broadphase_layer < mNumLayers);
 
 		// Find first body with different layer
-		BodyID *b_mid = upper_bound(b_start, b_end, broadphase_layer, [bodies_ptr, object_to_broadphase](BroadPhaseLayer::Type inLayer, BodyID inBodyID) { return inLayer < (BroadPhaseLayer::Type)object_to_broadphase[bodies_ptr[inBodyID.GetIndex()]->GetObjectLayer()]; });
+		BodyID *b_mid = upper_bound(b_start, b_end, broadphase_layer, [bodies_ptr](BroadPhaseLayer::Type inLayer, BodyID inBodyID) { return inLayer < (BroadPhaseLayer::Type)bodies_ptr[inBodyID.GetIndex()]->GetBroadPhaseLayer(); });
 
 		// Keep track of state for this layer
 		LayerState &layer_state = state[broadphase_layer];
@@ -373,16 +368,15 @@ void BroadPhaseQuadTree::NotifyBodiesLayerChanged(BodyID *ioBodies, int inNumber
 	{
 		uint32 index = body_id->GetIndex();
 		JPH_ASSERT(bodies[index]->GetID() == *body_id, "Provided BodyID doesn't match BodyID in body manager");
-		ObjectLayer object_layer = bodies[index]->GetObjectLayer();
-		JPH_ASSERT(object_layer < mObjectToBroadPhaseLayer.size());
-		BroadPhaseLayer::Type broadphase_layer = (BroadPhaseLayer::Type)mObjectToBroadPhaseLayer[object_layer];
+		const Body *body = bodies[index];
+		BroadPhaseLayer::Type broadphase_layer = (BroadPhaseLayer::Type)body->GetBroadPhaseLayer();
 		JPH_ASSERT(broadphase_layer < mNumLayers);
 		if (mTracking[index].mBroadPhaseLayer == broadphase_layer)
 		{
 			// Update tracking information
-			mTracking[index].mObjectLayer = object_layer;
+			mTracking[index].mObjectLayer = body->GetObjectLayer();
 
-			// If move the body to the end, layer didn't change
+			// Move the body to the end, layer didn't change
 			swap(*body_id, ioBodies[inNumber - 1]);
 			--inNumber;
 		}
@@ -579,17 +573,6 @@ void BroadPhaseQuadTree::FindCollidingPairs(BodyID *ioActiveBodies, int inNumAct
 	}
 }
 
-#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
-
-void BroadPhaseQuadTree::SetBroadPhaseLayerToString(BroadPhaseLayerToString inBroadPhaseLayerToString)
-{
-	// Set the name on the sub trees
-	for (BroadPhaseLayer::Type l = 0; l < mNumLayers; ++l)
-		mLayers[l].SetName(inBroadPhaseLayerToString(BroadPhaseLayer(l)));
-}
-
-#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
-
 #ifdef JPH_TRACK_BROADPHASE_STATS
 
 void BroadPhaseQuadTree::ReportStats()

+ 3 - 6
Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.h

@@ -16,7 +16,7 @@ public:
 	virtual					~BroadPhaseQuadTree() override;
 
 	// Implementing interface of BroadPhase (see BroadPhase for documentation)
-	virtual void			Init(BodyManager *inBodyManager, const ObjectToBroadPhaseLayer &inObjectToBroadPhaseLayer) override;
+	virtual void			Init(BodyManager *inBodyManager, const BroadPhaseLayerInterface &inLayerInterface) override;
 	virtual void			Optimize() override;
 	virtual void			FrameSync() override;
 	virtual void			LockModifications() override;
@@ -37,9 +37,6 @@ public:
 	virtual void			CastAABoxNoLock(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter) const override; 
 	virtual void			CastAABox(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter) const override;
 	virtual void			FindCollidingPairs(BodyID *ioActiveBodies, int inNumActiveBodies, float inSpeculativeContactDistance, ObjectVsBroadPhaseLayerFilter inObjectVsBroadPhaseLayerFilter, ObjectLayerPairFilter inObjectLayerPairFilter, BodyPairCollector &ioPairCollector) const override;
-#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
-	virtual void			SetBroadPhaseLayerToString(BroadPhaseLayerToString inBroadPhaseLayerToString) override;
-#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
 #ifdef JPH_TRACK_BROADPHASE_STATS
 	virtual void			ReportStats() override;
 #endif // JPH_TRACK_BROADPHASE_STATS
@@ -65,8 +62,8 @@ private:
 	/// Node allocator for all trees
 	QuadTree::Allocator		mAllocator;
 
-	/// Mapping table that maps from Object Layer to tree
-	ObjectToBroadPhaseLayer	mObjectToBroadPhaseLayer;
+	/// Information about broad phase layers
+	const BroadPhaseLayerInterface *mBroadPhaseLayerInterface = nullptr;
 
 	/// One tree per object layer
 	QuadTree *				mLayers;

+ 3 - 3
Jolt/Physics/PhysicsSystem.cpp

@@ -59,17 +59,17 @@ PhysicsSystem::~PhysicsSystem()
 	delete mBroadPhase;
 }
 
-void PhysicsSystem::Init(uint inMaxBodies, uint inNumBodyMutexes, uint inMaxBodyPairs, uint inMaxContactConstraints, const ObjectToBroadPhaseLayer &inObjectToBroadPhaseLayer, ObjectVsBroadPhaseLayerFilter inObjectVsBroadPhaseLayerFilter, ObjectLayerPairFilter inObjectLayerPairFilter)
+void PhysicsSystem::Init(uint inMaxBodies, uint inNumBodyMutexes, uint inMaxBodyPairs, uint inMaxContactConstraints, const BroadPhaseLayerInterface &inBroadPhaseLayerInterface, ObjectVsBroadPhaseLayerFilter inObjectVsBroadPhaseLayerFilter, ObjectLayerPairFilter inObjectLayerPairFilter)
 { 
 	mObjectVsBroadPhaseLayerFilter = inObjectVsBroadPhaseLayerFilter;
 	mObjectLayerPairFilter = inObjectLayerPairFilter;
 
 	// Initialize body manager
-	mBodyManager.Init(inMaxBodies, inNumBodyMutexes); 
+	mBodyManager.Init(inMaxBodies, inNumBodyMutexes, inBroadPhaseLayerInterface); 
 
 	// Create broadphase
 	mBroadPhase = new BROAD_PHASE();
-	mBroadPhase->Init(&mBodyManager, inObjectToBroadPhaseLayer);
+	mBroadPhase->Init(&mBodyManager, inBroadPhaseLayerInterface);
 
 	// Init contact constraint manager
 	mContactManager.Init(inMaxBodyPairs, inMaxContactConstraints);

+ 2 - 7
Jolt/Physics/PhysicsSystem.h

@@ -33,9 +33,9 @@ public:
 	/// @param inNumBodyMutexes Number of body mutexes to use. Should be a power of 2 in the range [1, 64], use 0 to auto detect.
 	/// @param inMaxBodyPairs Maximum amount of body pairs to process (anything else will fall through the world), this number should generally be much higher than the max amount of contact points as there will be lots of bodies close that are not actually touching
 	/// @param inMaxContactConstraints Maximum amount of contact constraints to process (anything else will fall through the world)
-	/// @param inObjectToBroadPhaseLayer Maps object layer to broadphase layer, see ObjectToBroadPhaseLayer.
+	/// @param inBroadPhaseLayerInterface Information on the mapping of object layers to broad phase layers, note since this is a virtual interface, the instance needs to stay alive during the lifetime of the PhysicsSystem
 	/// @param inObjectLayerPairFilter Filter callback function that is used to determine if two object layers collide.
-	void						Init(uint inMaxBodies, uint inNumBodyMutexes, uint inMaxBodyPairs, uint inMaxContactConstraints, const ObjectToBroadPhaseLayer &inObjectToBroadPhaseLayer, ObjectVsBroadPhaseLayerFilter inObjectVsBroadPhaseLayerFilter, ObjectLayerPairFilter inObjectLayerPairFilter);
+	void						Init(uint inMaxBodies, uint inNumBodyMutexes, uint inMaxBodyPairs, uint inMaxContactConstraints, const BroadPhaseLayerInterface &inBroadPhaseLayerInterface, ObjectVsBroadPhaseLayerFilter inObjectVsBroadPhaseLayerFilter, ObjectLayerPairFilter inObjectLayerPairFilter);
 	
 	/// Listener that is notified whenever a body is activated/deactivated
 	void						SetBodyActivationListener(BodyActivationListener *inListener) { mBodyManager.SetBodyActivationListener(inListener); }
@@ -57,11 +57,6 @@ public:
 	void						SetPhysicsSettings(const PhysicsSettings &inSettings)		{ mPhysicsSettings = inSettings; }
 	const PhysicsSettings &		GetPhysicsSettings() const									{ return mPhysicsSettings; }
 
-#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
-	/// Set function that converts a broadphase layer to a human readable string for debugging purposes
-	void						SetBroadPhaseLayerToString(BroadPhaseLayerToString inBroadPhaseLayerToString) { mBroadPhase->SetBroadPhaseLayerToString(inBroadPhaseLayerToString); }
-#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
-
 	/// Access to the body interface. This interface allows to to create / remove bodies and to change their properties.
 	BodyInterface &				GetBodyInterface() 											{ return mBodyInterfaceLocking; }
 	BodyInterface & 			GetBodyInterfaceNoLock()									{ return mBodyInterfaceNoLock; } ///< Version that does not lock the bodies, use with great care!

+ 39 - 10
PerformanceTest/Layers.h

@@ -34,6 +34,45 @@ namespace BroadPhaseLayers
 {
 	static constexpr BroadPhaseLayer NON_MOVING(0);
 	static constexpr BroadPhaseLayer MOVING(1);
+	static constexpr uint NUM_LAYERS(2);
+};
+
+/// BroadPhaseLayerInterface implementation
+class BPLayerInterfaceImpl final : public BroadPhaseLayerInterface
+{
+public:
+									BPLayerInterfaceImpl()
+	{
+		// Create a mapping table from object to broad phase layer
+		mObjectToBroadPhase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
+		mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING;
+	}
+
+	virtual uint					GetNumBroadPhaseLayers() const override
+	{
+		return BroadPhaseLayers::NUM_LAYERS;
+	}
+
+	virtual BroadPhaseLayer			GetBroadPhaseLayer(ObjectLayer inLayer) const override
+	{
+		JPH_ASSERT(inLayer < Layers::NUM_LAYERS);
+		return mObjectToBroadPhase[inLayer];
+	}
+
+#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
+	virtual const char *			GetBroadPhaseLayerName(BroadPhaseLayer inLayer) const override
+	{
+		switch ((BroadPhaseLayer::Type)inLayer)
+		{
+		case (BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING:	return "NON_MOVING";
+		case (BroadPhaseLayer::Type)BroadPhaseLayers::MOVING:		return "MOVING";
+		default:													JPH_ASSERT(false); return "INVALID";
+		}
+	}
+#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
+
+private:
+	BroadPhaseLayer					mObjectToBroadPhase[Layers::NUM_LAYERS];
 };
 
 /// Function that determines if two broadphase layers can collide
@@ -50,13 +89,3 @@ inline bool BroadPhaseCanCollide(ObjectLayer inLayer1, BroadPhaseLayer inLayer2)
 		return false;
 	}
 }
-
-/// Create mapping table from layer to broadphase layer
-inline ObjectToBroadPhaseLayer GetObjectToBroadPhaseLayer()
-{
-	ObjectToBroadPhaseLayer object_to_broadphase;
-	object_to_broadphase.resize(Layers::NUM_LAYERS);
-	object_to_broadphase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
-	object_to_broadphase[Layers::MOVING] = BroadPhaseLayers::MOVING;
-	return object_to_broadphase;
-}

+ 2 - 2
PerformanceTest/PerformanceTest.cpp

@@ -150,7 +150,7 @@ int main(int argc, char** argv)
 	cout << "Running scene: " << scene->GetName() << endl;
 
 	// Create mapping table from object layer to broadphase layer
-	ObjectToBroadPhaseLayer object_to_broadphase = GetObjectToBroadPhaseLayer();
+	BPLayerInterfaceImpl broad_phase_layer_interface;
 
 	// Start profiling this thread
 	JPH_PROFILE_THREAD_START("Main");
@@ -185,7 +185,7 @@ int main(int argc, char** argv)
 
 			// Create physics system
 			PhysicsSystem physics_system;
-			physics_system.Init(10240, 0, 65536, 10240, object_to_broadphase, BroadPhaseCanCollide, ObjectCanCollide);
+			physics_system.Init(10240, 0, 65536, 10240, broad_phase_layer_interface, BroadPhaseCanCollide, ObjectCanCollide);
 
 			// Start test scene
 			scene->StartTest(physics_system, motion_quality);

+ 60 - 31
Samples/Layers.h

@@ -9,12 +9,14 @@
 /// Layer that objects can be in, determines which other objects it can collide with
 namespace Layers
 {
-	static constexpr uint8 UNUSED1 = 0; // 3 unused values so that broadphase layers values don't match with object layer values (for testing purposes)
+	static constexpr uint8 UNUSED1 = 0; // 4 unused values so that broadphase layers values don't match with object layer values (for testing purposes)
 	static constexpr uint8 UNUSED2 = 1;
 	static constexpr uint8 UNUSED3 = 2;
-	static constexpr uint8 NON_MOVING = 3;
-	static constexpr uint8 MOVING = 4;
-	static constexpr uint8 NUM_LAYERS = 5;
+	static constexpr uint8 UNUSED4 = 3;
+	static constexpr uint8 NON_MOVING = 4;
+	static constexpr uint8 MOVING = 5;
+	static constexpr uint8 DEBRIS = 6; // Example: Debris collides only with NON_MOVING
+	static constexpr uint8 NUM_LAYERS = 7;
 };
 
 /// Function that determines if two object layers can collide
@@ -25,11 +27,14 @@ inline bool ObjectCanCollide(ObjectLayer inObject1, ObjectLayer inObject2)
 	case Layers::UNUSED1:
 	case Layers::UNUSED2:
 	case Layers::UNUSED3:
+	case Layers::UNUSED4:
 		return false;
 	case Layers::NON_MOVING:
-		return inObject2 == Layers::MOVING;
+		return inObject2 == Layers::MOVING || inObject2 == Layers::DEBRIS;
 	case Layers::MOVING:
 		return inObject2 == Layers::NON_MOVING || inObject2 == Layers::MOVING;
+	case Layers::DEBRIS:
+		return inObject2 == Layers::NON_MOVING;
 	default:
 		JPH_ASSERT(false);
 		return false;
@@ -41,7 +46,54 @@ namespace BroadPhaseLayers
 {
 	static constexpr BroadPhaseLayer NON_MOVING(0);
 	static constexpr BroadPhaseLayer MOVING(1);
-	static constexpr BroadPhaseLayer UNUSED(2);
+	static constexpr BroadPhaseLayer DEBRIS(2);
+	static constexpr BroadPhaseLayer UNUSED(3);
+	static constexpr uint NUM_LAYERS(4);
+};
+
+/// BroadPhaseLayerInterface implementation
+class BPLayerInterfaceImpl final : public BroadPhaseLayerInterface
+{
+public:
+									BPLayerInterfaceImpl()
+	{
+		// Create a mapping table from object to broad phase layer
+		mObjectToBroadPhase[Layers::UNUSED1] = BroadPhaseLayers::UNUSED;
+		mObjectToBroadPhase[Layers::UNUSED2] = BroadPhaseLayers::UNUSED;
+		mObjectToBroadPhase[Layers::UNUSED3] = BroadPhaseLayers::UNUSED;
+		mObjectToBroadPhase[Layers::UNUSED4] = BroadPhaseLayers::UNUSED;
+		mObjectToBroadPhase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
+		mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING;
+		mObjectToBroadPhase[Layers::DEBRIS] = BroadPhaseLayers::DEBRIS;
+	}
+
+	virtual uint					GetNumBroadPhaseLayers() const override
+	{
+		return BroadPhaseLayers::NUM_LAYERS;
+	}
+
+	virtual BroadPhaseLayer			GetBroadPhaseLayer(ObjectLayer inLayer) const override
+	{
+		JPH_ASSERT(inLayer < Layers::NUM_LAYERS);
+		return mObjectToBroadPhase[inLayer];
+	}
+
+#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
+	virtual const char *			GetBroadPhaseLayerName(BroadPhaseLayer inLayer) const override
+	{
+		switch ((BroadPhaseLayer::Type)inLayer)
+		{
+		case (BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING:	return "NON_MOVING";
+		case (BroadPhaseLayer::Type)BroadPhaseLayers::MOVING:		return "MOVING";
+		case (BroadPhaseLayer::Type)BroadPhaseLayers::DEBRIS:		return "DEBRIS";
+		case (BroadPhaseLayer::Type)BroadPhaseLayers::UNUSED:		return "UNUSED";
+		default:													JPH_ASSERT(false); return "INVALID";
+		}
+	}
+#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
+
+private:
+	BroadPhaseLayer					mObjectToBroadPhase[Layers::NUM_LAYERS];
 };
 
 /// Function that determines if two broadphase layers can collide
@@ -53,6 +105,8 @@ inline bool BroadPhaseCanCollide(ObjectLayer inLayer1, BroadPhaseLayer inLayer2)
 		return inLayer2 == BroadPhaseLayers::MOVING;
 	case Layers::MOVING:
 		return inLayer2 == BroadPhaseLayers::NON_MOVING || inLayer2 == BroadPhaseLayers::MOVING;
+	case Layers::DEBRIS:
+		return inLayer2 == BroadPhaseLayers::NON_MOVING;
 	case Layers::UNUSED1:
 	case Layers::UNUSED2:
 	case Layers::UNUSED3:
@@ -62,28 +116,3 @@ inline bool BroadPhaseCanCollide(ObjectLayer inLayer1, BroadPhaseLayer inLayer2)
 		return false;
 	}
 }
-
-/// Create mapping table from layer to broadphase layer
-inline ObjectToBroadPhaseLayer GetObjectToBroadPhaseLayer()
-{
-	ObjectToBroadPhaseLayer object_to_broadphase;
-	object_to_broadphase.resize(Layers::NUM_LAYERS);
-	object_to_broadphase[Layers::UNUSED1] = BroadPhaseLayers::UNUSED;
-	object_to_broadphase[Layers::UNUSED2] = BroadPhaseLayers::UNUSED;
-	object_to_broadphase[Layers::UNUSED3] = BroadPhaseLayers::UNUSED;
-	object_to_broadphase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
-	object_to_broadphase[Layers::MOVING] = BroadPhaseLayers::MOVING;
-	return object_to_broadphase;
-}
-
-/// Get name of broadphase layer for debugging purposes
-inline const char *GetBroadPhaseLayerName(BroadPhaseLayer inLayer)
-{
-	switch ((BroadPhaseLayer::Type)inLayer)
-	{
-	case (BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING:	return "NON_MOVING";
-	case (BroadPhaseLayer::Type)BroadPhaseLayers::MOVING:		return "MOVING";
-	case (BroadPhaseLayer::Type)BroadPhaseLayers::UNUSED:		return "UNUSED";
-	default:													JPH_ASSERT(false); return "INVALID";
-	}
-}

+ 2 - 0
Samples/Samples.cmake

@@ -69,6 +69,8 @@ set(SAMPLES_SRC_FILES
 	${SAMPLES_ROOT}/Tests/General/ChangeMotionTypeTest.h
 	${SAMPLES_ROOT}/Tests/General/ChangeShapeTest.cpp
 	${SAMPLES_ROOT}/Tests/General/ChangeShapeTest.h
+	${SAMPLES_ROOT}/Tests/General/ChangeObjectLayerTest.cpp
+	${SAMPLES_ROOT}/Tests/General/ChangeObjectLayerTest.h
 	${SAMPLES_ROOT}/Tests/General/ContactListenerTest.cpp
 	${SAMPLES_ROOT}/Tests/General/ContactListenerTest.h
 	${SAMPLES_ROOT}/Tests/General/ContactManifoldTest.cpp

+ 3 - 5
Samples/SamplesApp.cpp

@@ -31,7 +31,6 @@
 #include <Physics/Collision/Shape/ScaledShape.h>
 #include <Physics/Collision/NarrowPhaseStats.h>
 #include <Physics/Constraints/DistanceConstraint.h>
-#include <Layers.h>
 #include <Utils/Log.h>
 #include <Renderer/DebugRendererImp.h>
 
@@ -70,6 +69,7 @@ JPH_DECLARE_RTTI_FOR_FACTORY(HeavyOnLightTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(HighSpeedTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(ChangeMotionTypeTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(ChangeShapeTest)
+JPH_DECLARE_RTTI_FOR_FACTORY(ChangeObjectLayerTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(LoadSaveSceneTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(LoadSaveBinaryTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(BigVsSmallTest)
@@ -99,6 +99,7 @@ static TestNameAndRTTI sGeneralTests[] =
 	{ "High Speed",							JPH_RTTI(HighSpeedTest) },
 	{ "Change Motion Type",					JPH_RTTI(ChangeMotionTypeTest) },
 	{ "Change Shape",						JPH_RTTI(ChangeShapeTest) },
+	{ "Change Object Layer",				JPH_RTTI(ChangeObjectLayerTest) },
 	{ "Load/Save Scene",					JPH_RTTI(LoadSaveSceneTest) },
 	{ "Load/Save Binary",					JPH_RTTI(LoadSaveBinaryTest) },
 	{ "Big vs Small",						JPH_RTTI(BigVsSmallTest) },
@@ -505,11 +506,8 @@ void SamplesApp::StartTest(const RTTI *inRTTI)
 
 	// Create physics system
 	mPhysicsSystem = new PhysicsSystem();
-	mPhysicsSystem->Init(cNumBodies, cNumBodyMutexes, cMaxBodyPairs, cMaxContactConstraints, GetObjectToBroadPhaseLayer(), BroadPhaseCanCollide, ObjectCanCollide);
+	mPhysicsSystem->Init(cNumBodies, cNumBodyMutexes, cMaxBodyPairs, cMaxContactConstraints, mBroadPhaseLayerInterface, BroadPhaseCanCollide, ObjectCanCollide);
 	mPhysicsSystem->SetPhysicsSettings(mPhysicsSettings);
-#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
-	mPhysicsSystem->SetBroadPhaseLayerToString(GetBroadPhaseLayerName);
-#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
 
 	// Restore gravity
 	mPhysicsSystem->SetGravity(old_gravity);

+ 2 - 0
Samples/SamplesApp.h

@@ -12,6 +12,7 @@
 #include <Utils/ContactListenerImpl.h>
 #include <Renderer/DebugRendererImp.h>
 #include <Physics/StateRecorderImpl.h>
+#include <Layers.h>
 
 namespace JPH {
 	class JobSystem;
@@ -85,6 +86,7 @@ private:
 	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
+	BPLayerInterfaceImpl	mBroadPhaseLayerInterface;									// The broadphase layer interface that maps object layers to broadphase layers
 	PhysicsSystem *			mPhysicsSystem = nullptr;									// The physics system that simulates the world
 	ContactListenerImpl *	mContactListener = nullptr;									// Contact listener implementation
 	PhysicsSettings			mPhysicsSettings;											// Main physics simulation settings

+ 2 - 4
Samples/Tests/BroadPhase/BroadPhaseTest.cpp

@@ -8,7 +8,6 @@
 #include <Physics/Body/BodyCreationSettings.h>
 #include <Physics/Collision/BroadPhase/BroadPhaseBruteForce.h>
 #include <Physics/Collision/BroadPhase/BroadPhaseQuadTree.h>
-#include <Layers.h>
 #include <random>
 
 JPH_IMPLEMENT_RTTI_ABSTRACT(BroadPhaseTest) 
@@ -54,12 +53,11 @@ void BroadPhaseTest::Initialize()
 {
 	// Create body manager
 	mBodyManager = new BodyManager();
-	mBodyManager->Init(NUM_BODIES, 0);
+	mBodyManager->Init(NUM_BODIES, 0, mBroadPhaseLayerInterface);
 		
 	// Crate broadphase
-	mObjectToBroadPhaseLayer = GetObjectToBroadPhaseLayer();
 	mBroadPhase = new BROAD_PHASE;
-	mBroadPhase->Init(mBodyManager, mObjectToBroadPhaseLayer);
+	mBroadPhase->Init(mBodyManager, mBroadPhaseLayerInterface);
 }
 
 void BroadPhaseTest::PostPhysicsUpdate(float inDeltaTime)

+ 2 - 1
Samples/Tests/BroadPhase/BroadPhaseTest.h

@@ -6,6 +6,7 @@
 #include <Tests/Test.h>
 #include <Physics/Body/BodyManager.h>
 #include <Physics/Collision/BroadPhase/BroadPhase.h>
+#include <Layers.h>
 
 // Base class for a test involving only the broad phase
 class BroadPhaseTest : public Test
@@ -27,7 +28,7 @@ protected:
 	// http://pub.ist.ac.at/~edels/Papers/2002-J-01-FastBoxIntersection.pdf
 	void					CreateBalancedDistribution(BodyManager *inBodyManager, int inNumBodies, float inEnvironmentSize = 512.0f);
 
-	ObjectToBroadPhaseLayer	mObjectToBroadPhaseLayer;
+	BPLayerInterfaceImpl	mBroadPhaseLayerInterface;
 	BroadPhase *			mBroadPhase = nullptr;
 	BodyManager *			mBodyManager = nullptr;
 };

+ 3 - 0
Samples/Tests/Character/CharacterTest.cpp

@@ -60,7 +60,10 @@ void CharacterTest::Initialize()
 			FatalError("Failed to load scene");
 		scene->FixInvalidScales();
 		for (BodyCreationSettings &settings : scene->GetBodies())
+		{
+			settings.mObjectLayer = Layers::NON_MOVING;
 			settings.mFriction = 0.5f;
+		}
 		scene->CreateBodies(mPhysicsSystem);
 	}
 

+ 80 - 0
Samples/Tests/General/ChangeObjectLayerTest.cpp

@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#include <TestFramework.h>
+
+#include <Tests/General/ChangeObjectLayerTest.h>
+#include <Layers.h>
+#include <Physics/Collision/Shape/BoxShape.h>
+#include <Physics/Body/BodyCreationSettings.h>
+
+JPH_IMPLEMENT_RTTI_VIRTUAL(ChangeObjectLayerTest) 
+{ 
+	JPH_ADD_BASE_CLASS(ChangeObjectLayerTest, Test) 
+}
+
+void ChangeObjectLayerTest::Initialize()
+{
+	// Floor
+	CreateFloor();
+
+	// A dynamic box in the MOVING layer
+	mMoving = mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(5, 0.1f, 5)), Vec3(0, 1.5f, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
+
+	// Lots of dynamic objects in the DEBRIS layer
+	default_random_engine random;
+	uniform_real_distribution<float> position_variation(-10, 10);
+	for (int i = 0; i < 50; ++i)
+	{
+		Vec3 position(position_variation(random), 2.0f, position_variation(random));
+		Quat rotation = Quat::sRandom(random);
+		mDebris.push_back(mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(0.1f)), position, rotation, EMotionType::Dynamic, Layers::DEBRIS), EActivation::Activate));
+	}
+}
+
+void ChangeObjectLayerTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
+{ 
+	const float cSwitchTime = 2.0f;
+
+	// Increment time
+	mTime += inParams.mDeltaTime;
+
+	if (mTime >= cSwitchTime)
+	{
+		mIsDebris = !mIsDebris;
+
+		// Reposition moving object
+		mBodyInterface->SetPosition(mMoving, Vec3(0, 1.5f, 0), EActivation::Activate);
+
+		default_random_engine random;
+		uniform_real_distribution<float> position_variation(-7.5f, 7.5f);
+		for (BodyID id : mDebris)
+		{
+			// Reposition debris
+			Vec3 position(position_variation(random), 2.0f, position_variation(random));
+			Quat rotation = Quat::sRandom(random);
+			mBodyInterface->SetPositionAndRotation(id, position, rotation, EActivation::Activate);
+
+			// And update layer
+			mBodyInterface->SetObjectLayer(id, mIsDebris? Layers::DEBRIS : Layers::MOVING);
+		}
+
+		mTime = 0;
+	}
+}
+
+void ChangeObjectLayerTest::SaveState(StateRecorder &inStream) const
+{
+	inStream.Write(mTime);
+	inStream.Write(mIsDebris);
+}
+
+void ChangeObjectLayerTest::RestoreState(StateRecorder &inStream)
+{
+	inStream.Read(mTime);
+	inStream.Read(mIsDebris);
+
+	// Restore layer
+	for (BodyID id : mDebris)
+		mBodyInterface->SetObjectLayer(id, mIsDebris? Layers::DEBRIS : Layers::MOVING);
+}

+ 31 - 0
Samples/Tests/General/ChangeObjectLayerTest.h

@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Tests/Test.h>
+#include <Physics/Body/BodyID.h>
+
+// This test will demonstrates how to use layers to disable collisions between other objects and how to change them on the fly.
+// The bodies will switch between the MOVING layer and the DEBRIS layer (debris only collides with static).
+class ChangeObjectLayerTest : public Test
+{
+public:
+	JPH_DECLARE_RTTI_VIRTUAL(ChangeObjectLayerTest)
+
+	// Initialize the test
+	virtual void			Initialize() override;
+
+	// Update the test, called before the physics update
+	virtual void			PrePhysicsUpdate(const PreUpdateParams &inParams) override;
+
+	// Saving / restoring state for replay
+	virtual void			SaveState(StateRecorder &inStream) const override;
+	virtual void			RestoreState(StateRecorder &inStream) override;
+
+private:
+	BodyID					mMoving;
+	BodyIDVector			mDebris;
+	bool					mIsDebris = true;
+	float					mTime = 0.0f;
+};

+ 2 - 0
Samples/Tests/General/HighSpeedTest.cpp

@@ -351,6 +351,8 @@ void HighSpeedTest::CreateConvexOnTerrain1()
 	Ref<PhysicsScene> scene;
 	if (!ObjectStreamIn::sReadObject("Assets/terrain1.bof", scene))
 		FatalError("Failed to load scene");
+	for (BodyCreationSettings &body : scene->GetBodies())
+		body.mObjectLayer = Layers::NON_MOVING;
 	scene->FixInvalidScales();
 	scene->CreateBodies(mPhysicsSystem);
 

+ 2 - 0
Samples/Tests/Rig/RigPileTest.cpp

@@ -60,6 +60,8 @@ void RigPileTest::Initialize()
 		Ref<PhysicsScene> scene;
 		if (!ObjectStreamIn::sReadObject((string("Assets/") + sSceneName + ".bof").c_str(), scene))
 			FatalError("Failed to load scene");
+		for (BodyCreationSettings &body : scene->GetBodies())
+			body.mObjectLayer = Layers::NON_MOVING;
 		scene->FixInvalidScales();
 		scene->CreateBodies(mPhysicsSystem);
 	}

+ 2 - 0
Samples/Tests/Vehicle/VehicleTest.cpp

@@ -52,6 +52,8 @@ void VehicleTest::Initialize()
 		Ref<PhysicsScene> scene;
 		if (!ObjectStreamIn::sReadObject((string("Assets/") + sSceneName + ".bof").c_str(), scene))
 			FatalError("Failed to load scene");
+		for (BodyCreationSettings &body : scene->GetBodies())
+			body.mObjectLayer = Layers::NON_MOVING;
 		scene->FixInvalidScales();
 		scene->CreateBodies(mPhysicsSystem);
 	}

+ 71 - 20
UnitTests/Layers.h

@@ -9,12 +9,16 @@
 /// Layer that objects can be in, determines which other objects it can collide with
 namespace Layers
 {
-	static constexpr uint8 UNUSED1 = 0; // 3 unused values so that broadphase layers values don't match with object layer values (for testing purposes)
+	static constexpr uint8 UNUSED1 = 0; // 5 unused values so that broadphase layers values don't match with object layer values (for testing purposes)
 	static constexpr uint8 UNUSED2 = 1;
 	static constexpr uint8 UNUSED3 = 2;
-	static constexpr uint8 NON_MOVING = 3;
-	static constexpr uint8 MOVING = 4;
-	static constexpr uint8 NUM_LAYERS = 5;
+	static constexpr uint8 UNUSED4 = 3;
+	static constexpr uint8 UNUSED5 = 4;
+	static constexpr uint8 NON_MOVING = 5;
+	static constexpr uint8 MOVING = 6;
+	static constexpr uint8 HQ_DEBRIS = 7; // High quality debris collides with MOVING and NON_MOVING but not with any debris
+	static constexpr uint8 LQ_DEBRIS = 8; // Low quality debris only collides with NON_MOVING
+	static constexpr uint8 NUM_LAYERS = 9;
 };
 
 /// Function that determines if two object layers can collide
@@ -25,11 +29,17 @@ inline bool ObjectCanCollide(ObjectLayer inObject1, ObjectLayer inObject2)
 	case Layers::UNUSED1:
 	case Layers::UNUSED2:
 	case Layers::UNUSED3:
+	case Layers::UNUSED4:
+	case Layers::UNUSED5:
 		return false;
 	case Layers::NON_MOVING:
-		return inObject2 == Layers::MOVING;
+		return inObject2 == Layers::MOVING || inObject2 == Layers::HQ_DEBRIS || inObject2 == Layers::LQ_DEBRIS;
 	case Layers::MOVING:
+		return inObject2 == Layers::NON_MOVING || inObject2 == Layers::MOVING || inObject2 == Layers::HQ_DEBRIS;
+	case Layers::HQ_DEBRIS:
 		return inObject2 == Layers::NON_MOVING || inObject2 == Layers::MOVING;
+	case Layers::LQ_DEBRIS:
+		return inObject2 == Layers::NON_MOVING;
 	default:
 		JPH_ASSERT(false);
 		return false;
@@ -40,8 +50,57 @@ inline bool ObjectCanCollide(ObjectLayer inObject1, ObjectLayer inObject2)
 namespace BroadPhaseLayers
 {
 	static constexpr BroadPhaseLayer NON_MOVING(0);
-	static constexpr BroadPhaseLayer MOVING(1);
-	static constexpr BroadPhaseLayer UNUSED(2);
+	static constexpr BroadPhaseLayer MOVING(1); 
+	static constexpr BroadPhaseLayer LQ_DEBRIS(2);
+	static constexpr BroadPhaseLayer UNUSED(3);
+	static constexpr uint NUM_LAYERS(4);
+};
+
+/// BroadPhaseLayerInterface implementation
+class BPLayerInterfaceImpl final : public BroadPhaseLayerInterface
+{
+public:
+									BPLayerInterfaceImpl()
+	{
+		// Create a mapping table from object to broad phase layer
+		mObjectToBroadPhase[Layers::UNUSED1] = BroadPhaseLayers::UNUSED;
+		mObjectToBroadPhase[Layers::UNUSED2] = BroadPhaseLayers::UNUSED;
+		mObjectToBroadPhase[Layers::UNUSED3] = BroadPhaseLayers::UNUSED;
+		mObjectToBroadPhase[Layers::UNUSED4] = BroadPhaseLayers::UNUSED;
+		mObjectToBroadPhase[Layers::UNUSED5] = BroadPhaseLayers::UNUSED;
+		mObjectToBroadPhase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
+		mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING;
+		mObjectToBroadPhase[Layers::HQ_DEBRIS] = BroadPhaseLayers::MOVING; // HQ_DEBRIS is also in the MOVING layer as an example on how to map multiple layers onto the same broadphase layer
+		mObjectToBroadPhase[Layers::LQ_DEBRIS] = BroadPhaseLayers::LQ_DEBRIS;
+	}
+
+	virtual uint					GetNumBroadPhaseLayers() const override
+	{
+		return BroadPhaseLayers::NUM_LAYERS;
+	}
+
+	virtual BroadPhaseLayer			GetBroadPhaseLayer(ObjectLayer inLayer) const override
+	{
+		JPH_ASSERT(inLayer < Layers::NUM_LAYERS);
+		return mObjectToBroadPhase[inLayer];
+	}
+
+#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
+	virtual const char *			GetBroadPhaseLayerName(BroadPhaseLayer inLayer) const override
+	{
+		switch ((BroadPhaseLayer::Type)inLayer)
+		{
+		case (BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING:	return "NON_MOVING";
+		case (BroadPhaseLayer::Type)BroadPhaseLayers::MOVING:		return "MOVING";
+		case (BroadPhaseLayer::Type)BroadPhaseLayers::LQ_DEBRIS:	return "LQ_DEBRIS";
+		case (BroadPhaseLayer::Type)BroadPhaseLayers::UNUSED:		return "UNUSED";
+		default:													JPH_ASSERT(false); return "INVALID";
+		}
+	}
+#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
+
+private:
+	BroadPhaseLayer					mObjectToBroadPhase[Layers::NUM_LAYERS];
 };
 
 /// Function that determines if two broadphase layers can collide
@@ -52,26 +111,18 @@ inline bool BroadPhaseCanCollide(ObjectLayer inLayer1, BroadPhaseLayer inLayer2)
 	case Layers::NON_MOVING:
 		return inLayer2 == BroadPhaseLayers::MOVING;
 	case Layers::MOVING:
+	case Layers::HQ_DEBRIS:
 		return inLayer2 == BroadPhaseLayers::NON_MOVING || inLayer2 == BroadPhaseLayers::MOVING;
+	case Layers::LQ_DEBRIS:
+		return inLayer2 == BroadPhaseLayers::NON_MOVING;
 	case Layers::UNUSED1:
 	case Layers::UNUSED2:
 	case Layers::UNUSED3:
+	case Layers::UNUSED4:
+	case Layers::UNUSED5:
 		return false;			
 	default:
 		JPH_ASSERT(false);
 		return false;
 	}
 }
-
-/// Create mapping table from layer to broadphase layer
-inline ObjectToBroadPhaseLayer GetObjectToBroadPhaseLayer()
-{
-	ObjectToBroadPhaseLayer object_to_broadphase;
-	object_to_broadphase.resize(Layers::NUM_LAYERS);
-	object_to_broadphase[Layers::UNUSED1] = BroadPhaseLayers::UNUSED;
-	object_to_broadphase[Layers::UNUSED2] = BroadPhaseLayers::UNUSED;
-	object_to_broadphase[Layers::UNUSED3] = BroadPhaseLayers::UNUSED;
-	object_to_broadphase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
-	object_to_broadphase[Layers::MOVING] = BroadPhaseLayers::MOVING;
-	return object_to_broadphase;
-}

+ 4 - 3
UnitTests/Physics/BroadPhaseTests.cpp

@@ -15,14 +15,15 @@ TEST_SUITE("BroadPhaseTests")
 {
 	TEST_CASE("TestBroadPhaseOptimize")
 	{
+		BPLayerInterfaceImpl broad_phase_layer_interface; 
+
 		// Create body manager
 		BodyManager body_manager;
-		body_manager.Init(1, 0);
+		body_manager.Init(1, 0, broad_phase_layer_interface);
 		
 		// Create quad tree
 		BroadPhaseQuadTree broadphase;
-		ObjectToBroadPhaseLayer obj_to_bp = GetObjectToBroadPhaseLayer();
-		broadphase.Init(&body_manager, obj_to_bp);
+		broadphase.Init(&body_manager, broad_phase_layer_interface);
 
 		// Create a box
 		BodyCreationSettings settings(new BoxShape(Vec3::sReplicate(1.0f)), Vec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);

+ 150 - 0
UnitTests/Physics/PhysicsTests.cpp

@@ -1020,4 +1020,154 @@ TEST_SUITE("PhysicsTests")
 		PhysicsTestContext c2(1.0f / 60.0f, 1, 1);
 		TestPhysicsActivateDuringStep(c2, true);
 	}
+
+	TEST_CASE("TestPhysicsBroadPhaseLayers")
+	{
+		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		BodyInterface &bi = c.GetBodyInterface();
+
+		// Reduce slop
+		PhysicsSettings settings = c.GetSystem()->GetPhysicsSettings();
+		settings.mPenetrationSlop = 0.0f;
+		c.GetSystem()->SetPhysicsSettings(settings);
+
+		// Create static floor
+		c.CreateFloor();
+
+		// Create MOVING boxes
+		Body &moving1 = c.CreateBox(Vec3(0, 1, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f), EActivation::Activate);
+		Body &moving2 = c.CreateBox(Vec3(0, 2, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f), EActivation::Activate);
+
+		// Create HQ_DEBRIS boxes
+		Body &hq_debris1 = c.CreateBox(Vec3(0, 3, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::HQ_DEBRIS, Vec3::sReplicate(0.5f), EActivation::Activate);
+		Body &hq_debris2 = c.CreateBox(Vec3(0, 4, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::HQ_DEBRIS, Vec3::sReplicate(0.5f), EActivation::Activate);
+
+		// Create LQ_DEBRIS boxes
+		Body &lq_debris1 = c.CreateBox(Vec3(0, 5, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::LQ_DEBRIS, Vec3::sReplicate(0.5f), EActivation::Activate);
+		Body &lq_debris2 = c.CreateBox(Vec3(0, 6, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::LQ_DEBRIS, Vec3::sReplicate(0.5f), EActivation::Activate);
+
+		// Check layers
+		CHECK(moving1.GetObjectLayer() == Layers::MOVING);
+		CHECK(moving2.GetObjectLayer() == Layers::MOVING);
+		CHECK(hq_debris1.GetObjectLayer() == Layers::HQ_DEBRIS);
+		CHECK(hq_debris2.GetObjectLayer() == Layers::HQ_DEBRIS);
+		CHECK(lq_debris1.GetObjectLayer() == Layers::LQ_DEBRIS);
+		CHECK(lq_debris2.GetObjectLayer() == Layers::LQ_DEBRIS);
+		CHECK(moving1.GetBroadPhaseLayer() == BroadPhaseLayers::MOVING);
+		CHECK(moving2.GetBroadPhaseLayer() == BroadPhaseLayers::MOVING);
+		CHECK(hq_debris1.GetBroadPhaseLayer() == BroadPhaseLayers::MOVING);
+		CHECK(hq_debris2.GetBroadPhaseLayer() == BroadPhaseLayers::MOVING);
+		CHECK(lq_debris1.GetBroadPhaseLayer() == BroadPhaseLayers::LQ_DEBRIS);
+		CHECK(lq_debris2.GetBroadPhaseLayer() == BroadPhaseLayers::LQ_DEBRIS);
+
+		// Simulate the boxes falling
+		c.Simulate(5.0f);
+
+		// Everything should sleep
+		CHECK_FALSE(moving1.IsActive());
+		CHECK_FALSE(moving2.IsActive());
+		CHECK_FALSE(hq_debris1.IsActive());
+		CHECK_FALSE(hq_debris2.IsActive());
+		CHECK_FALSE(lq_debris1.IsActive());
+		CHECK_FALSE(lq_debris2.IsActive());
+
+		// MOVING boxes should have stacked
+		float slop = 0.02f;
+		CHECK_APPROX_EQUAL(moving1.GetPosition(), Vec3(0, 0.5f, 0), slop);
+		CHECK_APPROX_EQUAL(moving2.GetPosition(), Vec3(0, 1.5f, 0), slop);
+
+		// HQ_DEBRIS boxes should have stacked on MOVING boxes but don't collide with each other
+		CHECK_APPROX_EQUAL(hq_debris1.GetPosition(), Vec3(0, 2.5f, 0), slop);
+		CHECK_APPROX_EQUAL(hq_debris2.GetPosition(), Vec3(0, 2.5f, 0), slop);
+
+		// LQ_DEBRIS should have fallen through all but the floor
+		CHECK_APPROX_EQUAL(lq_debris1.GetPosition(), Vec3(0, 0.5f, 0), slop);
+		CHECK_APPROX_EQUAL(lq_debris2.GetPosition(), Vec3(0, 0.5f, 0), slop);
+
+		// Now change HQ_DEBRIS to LQ_DEBRIS
+		bi.SetObjectLayer(hq_debris1.GetID(), Layers::LQ_DEBRIS);
+		bi.SetObjectLayer(hq_debris2.GetID(), Layers::LQ_DEBRIS);
+		bi.ActivateBody(hq_debris1.GetID());
+		bi.ActivateBody(hq_debris2.GetID());
+
+		// Check layers
+		CHECK(moving1.GetObjectLayer() == Layers::MOVING);
+		CHECK(moving2.GetObjectLayer() == Layers::MOVING);
+		CHECK(hq_debris1.GetObjectLayer() == Layers::LQ_DEBRIS);
+		CHECK(hq_debris2.GetObjectLayer() == Layers::LQ_DEBRIS);
+		CHECK(lq_debris1.GetObjectLayer() == Layers::LQ_DEBRIS);
+		CHECK(lq_debris2.GetObjectLayer() == Layers::LQ_DEBRIS);
+		CHECK(moving1.GetBroadPhaseLayer() == BroadPhaseLayers::MOVING);
+		CHECK(moving2.GetBroadPhaseLayer() == BroadPhaseLayers::MOVING);
+		CHECK(hq_debris1.GetBroadPhaseLayer() == BroadPhaseLayers::LQ_DEBRIS);
+		CHECK(hq_debris2.GetBroadPhaseLayer() == BroadPhaseLayers::LQ_DEBRIS);
+		CHECK(lq_debris1.GetBroadPhaseLayer() == BroadPhaseLayers::LQ_DEBRIS);
+		CHECK(lq_debris2.GetBroadPhaseLayer() == BroadPhaseLayers::LQ_DEBRIS);
+
+		// Simulate again
+		c.Simulate(5.0f);
+
+		// Everything should sleep
+		CHECK_FALSE(moving1.IsActive());
+		CHECK_FALSE(moving2.IsActive());
+		CHECK_FALSE(hq_debris1.IsActive());
+		CHECK_FALSE(hq_debris2.IsActive());
+		CHECK_FALSE(lq_debris1.IsActive());
+		CHECK_FALSE(lq_debris2.IsActive());
+
+		// MOVING boxes should have stacked
+		CHECK_APPROX_EQUAL(moving1.GetPosition(), Vec3(0, 0.5f, 0), slop);
+		CHECK_APPROX_EQUAL(moving2.GetPosition(), Vec3(0, 1.5f, 0), slop);
+
+		// HQ_DEBRIS (now LQ_DEBRIS) boxes have fallen through all but the floor
+		CHECK_APPROX_EQUAL(hq_debris1.GetPosition(), Vec3(0, 0.5f, 0), slop);
+		CHECK_APPROX_EQUAL(hq_debris2.GetPosition(), Vec3(0, 0.5f, 0), slop);
+
+		// LQ_DEBRIS should have fallen through all but the floor
+		CHECK_APPROX_EQUAL(lq_debris1.GetPosition(), Vec3(0, 0.5f, 0), slop);
+		CHECK_APPROX_EQUAL(lq_debris2.GetPosition(), Vec3(0, 0.5f, 0), slop);
+
+		// Now change MOVING to HQ_DEBRIS (this doesn't change the broadphase layer so avoids adding/removing bodies)
+		bi.SetObjectLayer(moving1.GetID(), Layers::HQ_DEBRIS);
+		bi.SetObjectLayer(moving2.GetID(), Layers::HQ_DEBRIS);
+		bi.ActivateBody(moving1.GetID());
+		bi.ActivateBody(moving2.GetID());
+
+		// Check layers
+		CHECK(moving1.GetObjectLayer() == Layers::HQ_DEBRIS);
+		CHECK(moving2.GetObjectLayer() == Layers::HQ_DEBRIS);
+		CHECK(hq_debris1.GetObjectLayer() == Layers::LQ_DEBRIS);
+		CHECK(hq_debris2.GetObjectLayer() == Layers::LQ_DEBRIS);
+		CHECK(lq_debris1.GetObjectLayer() == Layers::LQ_DEBRIS);
+		CHECK(lq_debris2.GetObjectLayer() == Layers::LQ_DEBRIS);
+		CHECK(moving1.GetBroadPhaseLayer() == BroadPhaseLayers::MOVING); // Broadphase layer didn't change
+		CHECK(moving2.GetBroadPhaseLayer() == BroadPhaseLayers::MOVING);
+		CHECK(hq_debris1.GetBroadPhaseLayer() == BroadPhaseLayers::LQ_DEBRIS);
+		CHECK(hq_debris2.GetBroadPhaseLayer() == BroadPhaseLayers::LQ_DEBRIS);
+		CHECK(lq_debris1.GetBroadPhaseLayer() == BroadPhaseLayers::LQ_DEBRIS);
+		CHECK(lq_debris2.GetBroadPhaseLayer() == BroadPhaseLayers::LQ_DEBRIS);
+
+		// Simulate again
+		c.Simulate(5.0f);
+
+		// Everything should sleep
+		CHECK_FALSE(moving1.IsActive());
+		CHECK_FALSE(moving2.IsActive());
+		CHECK_FALSE(hq_debris1.IsActive());
+		CHECK_FALSE(hq_debris2.IsActive());
+		CHECK_FALSE(lq_debris1.IsActive());
+		CHECK_FALSE(lq_debris2.IsActive());
+
+		// MOVING boxes now also fall through
+		CHECK_APPROX_EQUAL(moving1.GetPosition(), Vec3(0, 0.5f, 0), slop);
+		CHECK_APPROX_EQUAL(moving2.GetPosition(), Vec3(0, 0.5f, 0), slop);
+
+		// HQ_DEBRIS (now LQ_DEBRIS) boxes have fallen through all but the floor
+		CHECK_APPROX_EQUAL(hq_debris1.GetPosition(), Vec3(0, 0.5f, 0), slop);
+		CHECK_APPROX_EQUAL(hq_debris2.GetPosition(), Vec3(0, 0.5f, 0), slop);
+
+		// LQ_DEBRIS should have fallen through all but the floor
+		CHECK_APPROX_EQUAL(lq_debris1.GetPosition(), Vec3(0, 0.5f, 0), slop);
+		CHECK_APPROX_EQUAL(lq_debris2.GetPosition(), Vec3(0, 0.5f, 0), slop);
+	}
 }

+ 3 - 3
UnitTests/Physics/RayShapeTests.cpp

@@ -16,6 +16,7 @@
 #include <Physics/Collision/Shape/MutableCompoundShape.h>
 #include <Physics/Body/BodyCreationSettings.h>
 #include <Physics/PhysicsSystem.h>
+#include <Layers.h>
 
 TEST_SUITE("RayShapeTests")
 {
@@ -271,10 +272,9 @@ TEST_SUITE("RayShapeTests")
 		const Mat44 cShapeMatrix = Mat44::sRotationTranslation(cShapeRotation, cShapePosition);
 
 		// Make the shape part of a body and insert it into the physics system
-		ObjectToBroadPhaseLayer object_to_broadphase;
-		object_to_broadphase.push_back(BroadPhaseLayer(0));
+		BPLayerInterfaceImpl broad_phase_layer_interface;
 		PhysicsSystem system;
-		system.Init(1, 0, 4, 4, object_to_broadphase, [](ObjectLayer, BroadPhaseLayer) { return true; }, [](ObjectLayer, ObjectLayer) { return true; });
+		system.Init(1, 0, 4, 4, broad_phase_layer_interface, [](ObjectLayer, BroadPhaseLayer) { return true; }, [](ObjectLayer, ObjectLayer) { return true; });
 		system.GetBodyInterface().CreateAndAddBody(BodyCreationSettings(inShape, cShapePosition, cShapeRotation, EMotionType::Static, 0), EActivation::DontActivate);
 			   
 

+ 1 - 2
UnitTests/PhysicsTestContext.cpp

@@ -8,7 +8,6 @@
 #include <Physics/Collision/Shape/SphereShape.h>
 #include <Core/JobSystemThreadPool.h>
 #include <Core/TempAllocator.h>
-#include "Layers.h"
 
 PhysicsTestContext::PhysicsTestContext(float inDeltaTime, int inCollisionSteps, int inIntegrationSubSteps, int inWorkerThreads) :
 #ifdef JPH_DISABLE_TEMP_ALLOCATOR
@@ -23,7 +22,7 @@ PhysicsTestContext::PhysicsTestContext(float inDeltaTime, int inCollisionSteps,
 {
 	// Create physics system
 	mSystem = new PhysicsSystem();
-	mSystem->Init(1024, 0, 4096, 1024, GetObjectToBroadPhaseLayer(), BroadPhaseCanCollide, ObjectCanCollide);
+	mSystem->Init(1024, 0, 4096, 1024, mBroadPhaseLayerInterface, BroadPhaseCanCollide, ObjectCanCollide);
 }
 
 PhysicsTestContext::~PhysicsTestContext()

+ 2 - 0
UnitTests/PhysicsTestContext.h

@@ -6,6 +6,7 @@
 #include <Physics/PhysicsSystem.h>
 #include <Physics/Body/BodyCreationSettings.h>
 #include <Physics/Constraints/TwoBodyConstraint.h>
+#include "Layers.h"
 
 namespace JPH {
 	class TempAllocator;
@@ -83,6 +84,7 @@ public:
 private:
 	TempAllocator *		mTempAllocator;
 	JobSystem *			mJobSystem;
+	BPLayerInterfaceImpl mBroadPhaseLayerInterface;
 	PhysicsSystem *		mSystem;
 	float				mDeltaTime;
 	int					mCollisionSteps;