Skip to content

Commit 409722c

Browse files
committed
Added a Jolt 'hello world'.
Basic copy of the hello world Jolt supplies to test the simulation running and linking into the engine.
1 parent e7b0059 commit 409722c

File tree

3 files changed

+272
-9
lines changed

3 files changed

+272
-9
lines changed

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ PUBLIC ECS #SceneSystem.hpp uses ECS storage
182182
PUBLIC Utility
183183
PRIVATE Platform
184184
PRIVATE ImGui
185+
PRIVATE Jolt
185186
)
186187
target_compile_options(System PRIVATE ${WARNING_COMPILE_FLAGS})
187188
# System end ------------------------------------------------------------------------------------------------------------------------------
@@ -449,6 +450,11 @@ target_compile_options(Utility PRIVATE ${WARNING_COMPILE_FLAGS})
449450
# Tracy Profiler -----------------------------------------------------------------
450451
add_subdirectory(source/External/Tracy source/External/Tracy SYSTEM)
451452
# Tracy end ----------------------------------------------------------------------
453+
454+
# JoltPhysics ----------------------------------------------------------------------
455+
set(USE_STATIC_MSVC_RUNTIME_LIBRARY OFF CACHE BOOL "" FORCE)
456+
add_subdirectory(source/External/JoltPhysics/Build ${CMAKE_BINARY_DIR}/External/JoltPhysics SYSTEM)
457+
# JoltPhysics ----------------------------------------------------------------------
452458
# EXTERNAL LIBRARIES ***********************************************************************************************************************
453459

454460
add_custom_target(All DEPENDS Spirit Test)

source/System/PhysicsSystem.cpp

Lines changed: 257 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@
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+
1428
namespace 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

source/System/PhysicsSystem.hpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,21 @@ namespace System
1313
// The system is force based and numerically integrates
1414
class PhysicsSystem
1515
{
16-
public:
17-
PhysicsSystem(SceneSystem& scene_system, CollisionSystem& collision_system);
18-
void integrate(const DeltaTime& delta_time);
16+
SceneSystem& m_scene_system;
17+
CollisionSystem& m_collision_system;
18+
19+
DeltaTime m_total_simulation_time; // Total time simulated using the integrate function.
20+
glm::vec3 m_gravity; // The acceleration due to gravity.
1921

22+
public:
2023
size_t m_update_count;
2124
float m_restitution; // Coefficient of restitution applied in collision response.
2225
bool m_apply_collision_response; // Whether to apply collision response or not.
2326
bool m_bool_apply_kinematic; // Whether to apply kinematic equations or not.
2427

28+
PhysicsSystem(SceneSystem& scene_system, CollisionSystem& collision_system);
29+
void integrate(const DeltaTime& delta_time);
2530
private:
26-
SceneSystem& m_scene_system;
27-
CollisionSystem& m_collision_system;
28-
29-
DeltaTime m_total_simulation_time; // Total time simulated using the integrate function.
30-
glm::vec3 m_gravity; // The acceleration due to gravity.
31+
void init_jolt();
3132
};
3233
} // namespace System

0 commit comments

Comments
 (0)