1111#include " Utility/Performance.hpp"
1212#include " Utility/Utility.hpp"
1313
14+ #include " Jolt/Jolt.h"
15+ #include " Jolt/RegisterTypes.h"
16+ #include " Jolt/Core/Factory.h"
17+ #include " Jolt/Core/TempAllocator.h"
18+ #include " Jolt/Core/JobSystemThreadPool.h"
19+ #include " Jolt/Physics/PhysicsSettings.h"
20+ #include " Jolt/Physics/PhysicsSystem.h"
21+ #include " Jolt/Physics/Collision/Shape/BoxShape.h"
22+ #include " Jolt/Physics/Collision/Shape/SphereShape.h"
23+ #include " Jolt/Physics/Body/BodyCreationSettings.h"
24+ #include " Jolt/Physics/Body/BodyActivationListener.h"
25+
26+ #include < thread>
27+
1428namespace System
1529{
1630 PhysicsSystem::PhysicsSystem (SceneSystem& scene_system, CollisionSystem& collision_system)
@@ -22,7 +36,9 @@ namespace System
2236 , m_collision_system{collision_system}
2337 , m_total_simulation_time{DeltaTime::zero ()}
2438 , m_gravity{glm::vec3 (0 .f , -9 .81f , 0 .f )}
25- {}
39+ {
40+ init_jolt ();
41+ }
2642
2743 void PhysicsSystem::integrate (const DeltaTime& p_delta_time)
2844 {
@@ -103,4 +119,244 @@ namespace System
103119 }
104120 });
105121 }
122+
123+
124+ // Layer that objects can be in, determines which other objects it can collide with
125+ // Typically you at least want to have 1 layer for moving bodies and 1 layer for static bodies, but you can have more
126+ // layers if you want. E.g. you could have a layer for high detail collision (which is not used by the physics simulation
127+ // but only if you do collision testing).
128+ namespace Layers
129+ {
130+ static constexpr JPH::ObjectLayer NON_MOVING = 0 ;
131+ static constexpr JPH::ObjectLayer MOVING = 1 ;
132+ static constexpr JPH::ObjectLayer NUM_LAYERS = 2 ;
133+ };
134+ // Each broadphase layer results in a separate bounding volume tree in the broad phase. You at least want to have
135+ // a layer for non-moving and moving objects to avoid having to update a tree full of static objects every frame.
136+ // You can have a 1-on-1 mapping between object layers and broadphase layers (like in this case) but if you have
137+ // many object layers you'll be creating many broad phase trees, which is not efficient. If you want to fine tune
138+ // your broadphase layers define JPH_TRACK_BROADPHASE_STATS and look at the stats reported.
139+ namespace BroadPhaseLayers
140+ {
141+ static constexpr JPH::BroadPhaseLayer NON_MOVING (0 );
142+ static constexpr JPH::BroadPhaseLayer MOVING (1 );
143+ static constexpr uint32_t NUM_LAYERS (2 );
144+ };
145+
146+ // This defines a mapping between object and broadphase layers.
147+ class BPLayerInterfaceImpl final : public JPH::BroadPhaseLayerInterface
148+ {
149+ JPH::BroadPhaseLayer mObjectToBroadPhase [Layers::NUM_LAYERS];
150+ public:
151+ BPLayerInterfaceImpl ()
152+ {
153+ // Create a mapping table from object to broad phase layer
154+ mObjectToBroadPhase [Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
155+ mObjectToBroadPhase [Layers::MOVING] = BroadPhaseLayers::MOVING;
156+ }
157+
158+ virtual uint32_t GetNumBroadPhaseLayers () const override { return BroadPhaseLayers::NUM_LAYERS; }
159+
160+ virtual JPH::BroadPhaseLayer GetBroadPhaseLayer (JPH::ObjectLayer inLayer) const override
161+ {
162+ JPH_ASSERT (inLayer < Layers::NUM_LAYERS);
163+ return mObjectToBroadPhase [inLayer];
164+ }
165+
166+ #if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
167+ virtual const char * GetBroadPhaseLayerName (JPH::BroadPhaseLayer inLayer) const override
168+ {
169+ switch ((JPH::BroadPhaseLayer::Type)inLayer)
170+ {
171+ case (JPH::BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING: return " NON_MOVING" ;
172+ case (JPH::BroadPhaseLayer::Type)BroadPhaseLayers::MOVING: return " MOVING" ;
173+ default : JPH_ASSERT (false ); return " INVALID" ;
174+ }
175+ }
176+ #endif
177+ };
178+
179+
180+ // Class that determines if an object layer can collide with a broadphase layer
181+ class ObjectVsBroadPhaseLayerFilterImpl : public JPH ::ObjectVsBroadPhaseLayerFilter
182+ {
183+ public:
184+ virtual bool ShouldCollide (JPH::ObjectLayer inLayer1, JPH::BroadPhaseLayer inLayer2) const override
185+ {
186+ switch (inLayer1)
187+ {
188+ case Layers::NON_MOVING: return inLayer2 == BroadPhaseLayers::MOVING;
189+ case Layers::MOVING: return true ;
190+ default :
191+ JPH_ASSERT (false );
192+ return false ;
193+ }
194+ }
195+ };
196+ // Class that determines if two object layers can collide
197+ class ObjectLayerPairFilterImpl : public JPH ::ObjectLayerPairFilter
198+ {
199+ public:
200+ virtual bool ShouldCollide (JPH::ObjectLayer inObject1, JPH::ObjectLayer inObject2) const override
201+ {
202+ switch (inObject1)
203+ {
204+ case Layers::NON_MOVING: return inObject2 == Layers::MOVING; // Non moving only collides with moving
205+ case Layers::MOVING: return true ; // Moving collides with everything
206+ default :
207+ JPH_ASSERT (false );
208+ return false ;
209+ }
210+ }
211+ };
212+
213+ void PhysicsSystem::init_jolt ()
214+ {
215+ JPH::RegisterDefaultAllocator ();
216+
217+ JPH::Trace = [](const char *inFMT, ...) {
218+ // Format the message
219+ va_list list;
220+ va_start (list, inFMT);
221+ char buffer[1024 ];
222+ vsnprintf (buffer, sizeof (buffer), inFMT, list);
223+ va_end (list);
224+ LOG (" [JOLT] {}" , buffer);
225+ };
226+
227+ JPH_IF_ENABLE_ASSERTS (JPH::AssertFailed = [](const char *inExpression, const char *inMessage, const char *inFile, unsigned int inLine) {
228+ LOG_ERROR (false , " [JOLT] Assertion failed in file {} at line {}: ({}) {}" , inFile, inLine, inExpression, (inMessage != nullptr ? inMessage : " " ));
229+ return true ;
230+ });
231+
232+ JPH::Factory::sInstance = new JPH::Factory ();
233+ // If you have your own custom shape types you probably need to register their handlers with the CollisionDispatch before calling this function.
234+ // If you implement your own default material (PhysicsMaterial::sDefault) make sure to initialize it before this function or else this function will create one for you.
235+ JPH::RegisterTypes ();
236+ constexpr size_t temp_allocator_size = 10 * 1024 * 1024 ; // 10 MB
237+ JPH::TempAllocatorImpl temp_allocator (temp_allocator_size);
238+
239+ constexpr int cMaxPhysicsJobs = 2048 ;
240+ constexpr int cMaxPhysicsBarriers = 8 ;
241+ // We need a job system that will execute physics jobs on multiple threads.
242+ // TODO: Implement a JobSystem interface that integrates with Jolt
243+ // JobSystemThreadPool is an example implementation.
244+ JPH::JobSystemThreadPool job_system (cMaxPhysicsJobs, cMaxPhysicsBarriers, std::thread::hardware_concurrency () - 1 );
245+
246+ constexpr uint32_t cMaxBodies = 65536 ;
247+ constexpr uint32_t cNumBodyMutexes = 0 ; // Determines how many mutexes to allocate to protect rigid bodies from concurrent access default 0.
248+ // Max number of body pairs to be queued at any time, the broad phase will detect overlapping
249+ // based on bounding boxes and queue them up to be processed by the narrow phase.
250+ // If this number is too low the queue will fill up and the broad phase jobs will start to do narrow phase work (less efficient).
251+ constexpr uint32_t cMaxBodyPairs = 65536 ;
252+ // Maximum size of the contact constraint buffer. If more contacts between bodies are detected than this
253+ // then these contacts will be ignored and bodies will start interpenetrating / fall through the world.
254+ constexpr uint32_t cMaxContactConstraints = 10240 ;
255+
256+ // Note: As this is an interface, PhysicsSystem will take a reference to this so this instance needs to stay alive!
257+ // Also have a look at BroadPhaseLayerInterfaceTable or BroadPhaseLayerInterfaceMask for a simpler interface.
258+ BPLayerInterfaceImpl broad_phase_layer_interface;
259+ // Create class that filters object vs broadphase layers
260+ // Note: As this is an interface, PhysicsSystem will take a reference to this so this instance needs to stay alive!
261+ // Also have a look at ObjectVsBroadPhaseLayerFilterTable or ObjectVsBroadPhaseLayerFilterMask for a simpler interface.
262+ ObjectVsBroadPhaseLayerFilterImpl object_vs_broadphase_layer_filter;
263+
264+ // Create class that filters object vs object layers
265+ // Note: As this is an interface, PhysicsSystem will take a reference to this so this instance needs to stay alive!
266+ // Also have a look at ObjectLayerPairFilterTable or ObjectLayerPairFilterMask for a simpler interface.
267+ ObjectLayerPairFilterImpl object_vs_object_layer_filter;
268+
269+ JPH::PhysicsSystem physics_system;
270+ physics_system.Init (cMaxBodies, cNumBodyMutexes, cMaxBodyPairs, cMaxContactConstraints, broad_phase_layer_interface, object_vs_broadphase_layer_filter, object_vs_object_layer_filter);
271+
272+ // A body activation listener gets notified when bodies activate and go to sleep
273+ // Note that this is called from a job so whatever you do here needs to be thread safe.
274+ // Registering one is entirely optional.
275+ // MyBodyActivationListener body_activation_listener;
276+ // physics_system.SetBodyActivationListener(&body_activation_listener);
277+ // A contact listener gets notified when bodies (are about to) collide, and when they separate again.
278+ // Note that this is called from a job so whatever you do here needs to be thread safe.
279+ // Registering one is entirely optional.
280+ // MyContactListener contact_listener;
281+ // physics_system.SetContactListener(&contact_listener);
282+
283+ // The main way to interact with the bodies in the physics system is through the body interface. There is a locking and a non-locking
284+ // variant of this. We're going to use the locking version (even though we're not planning to access bodies from multiple threads)
285+ auto & body_interface = physics_system.GetBodyInterface ();
286+
287+ using namespace JPH ::literals; // _r
288+
289+ // Next we can create a rigid body to serve as the floor, we make a large box
290+ // Create the settings for the collision volume (the shape).
291+ // Note that for simple shapes (like boxes) you can also directly construct a BoxShape.
292+ JPH::BoxShapeSettings floor_shape_settings (JPH::Vec3 (100 .0f , 1 .0f , 100 .0f ));
293+ floor_shape_settings.SetEmbedded (); // A ref counted object on the stack (base class RefTarget) should be marked as such to prevent it from being freed when its reference count goes to 0.
294+ // Create the shape
295+ JPH::ShapeSettings::ShapeResult floor_shape_result = floor_shape_settings.Create ();
296+ JPH::ShapeRefC floor_shape = floor_shape_result.Get (); // We don't expect an error here, but you can check floor_shape_result for HasError() / GetError()
297+ // Create the settings for the body itself. Note that here you can also set other properties like the restitution / friction.
298+ JPH::BodyCreationSettings floor_settings (floor_shape, JPH::RVec3 (0 .0_r, -1 .0_r, 0 .0_r), JPH::Quat::sIdentity (), JPH::EMotionType::Static, Layers::NON_MOVING);
299+ JPH::Body* floor = body_interface.CreateBody (floor_settings); // Note that if we run out of bodies this can return nullptr
300+ // Add it to the world
301+ body_interface.AddBody (floor->GetID (), JPH::EActivation::DontActivate);
302+
303+
304+
305+ // Now create a dynamic body to bounce on the floor
306+ // Note that this uses the shorthand version of creating and adding a body to the world
307+ JPH::BodyCreationSettings sphere_settings (new JPH::SphereShape (0 .5f ), JPH::RVec3 (0 .0_r, 2 .0_r, 0 .0_r), JPH::Quat::sIdentity (), JPH::EMotionType::Dynamic, Layers::MOVING);
308+ JPH::BodyID sphere_id = body_interface.CreateAndAddBody (sphere_settings, JPH::EActivation::Activate);
309+ // Now you can interact with the dynamic body, in this case we're going to give it a velocity.
310+ // (note that if we had used CreateBody then we could have set the velocity straight on the body before adding it to the physics system)
311+ body_interface.SetLinearVelocity (sphere_id, JPH::Vec3 (0 .0f , -5 .0f , 0 .0f ));
312+
313+
314+ // Optional step: Before starting the physics simulation you can optimize the broad phase. This improves collision detection performance (it's pointless here because we only have 2 bodies).
315+ // You should definitely not call this every frame or when e.g. streaming in a new level section as it is an expensive operation.
316+ // Instead insert all new objects in batches instead of 1 at a time to keep the broad phase efficient.
317+ physics_system.OptimizeBroadPhase ();
318+
319+
320+
321+
322+ // Now we're ready to simulate the body, keep simulating until it goes to sleep
323+ uint32_t step = 0 ;
324+ const float cDeltaTime = 1 .0f / 60 .0f ;
325+ while (body_interface.IsActive (sphere_id))
326+ {
327+ // Next step
328+ ++step;
329+
330+ // Output current position and velocity of the sphere
331+ JPH::RVec3 position = body_interface.GetCenterOfMassPosition (sphere_id);
332+ JPH::Vec3 velocity = body_interface.GetLinearVelocity (sphere_id);
333+ LOG (" Step {}: Position = ({}, {}, {}), Velocity = ({}, {}, {})" , step, position.GetX (), position.GetY (), position.GetZ (), velocity.GetX (), velocity.GetY (), velocity.GetZ ());
334+
335+ // 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).
336+ const int cCollisionSteps = 1 ;
337+
338+ // Step the world
339+ physics_system.Update (cDeltaTime, cCollisionSteps, &temp_allocator, &job_system);
340+ }
341+
342+
343+ {// Cleanup
344+ // 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.
345+ body_interface.RemoveBody (sphere_id);
346+
347+ // Destroy the sphere. After this the sphere ID is no longer valid.
348+ body_interface.DestroyBody (sphere_id);
349+
350+ // Remove and destroy the floor
351+ body_interface.RemoveBody (floor->GetID ());
352+ body_interface.DestroyBody (floor->GetID ());
353+
354+ // Unregisters all types with the factory and cleans up the default material
355+ JPH::UnregisterTypes ();
356+
357+ // Destroy the factory
358+ delete JPH::Factory::sInstance ;
359+ JPH::Factory::sInstance = nullptr ;
360+ }
361+ }
106362} // namespace System
0 commit comments