Adding basic impl of Jolt

Update positions on 60tps, position of objects can be modified while
physic simulation is running. Position is owned by transform comps but
the truth is held by the physics engine for affected entities
This commit is contained in:
Erris
2026-03-04 10:19:46 +01:00
parent 282eeeabda
commit eaef554b10
12 changed files with 518 additions and 90 deletions

View File

@@ -0,0 +1,188 @@
#ifndef PHYSICS_HPP
#define PHYSICS_HPP
#include "logging.hpp"
#include <Jolt/Jolt.h>
#include <Jolt/Physics/Body/BodyInterface.h>
#include <Jolt/RegisterTypes.h>
#include <Jolt/Core/Factory.h>
#include <Jolt/Core/TempAllocator.h>
#include <Jolt/Core/JobSystemThreadPool.h>
#include <Jolt/Physics/PhysicsSettings.h>
#include <Jolt/Physics/PhysicsSystem.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Body/BodyActivationListener.h>
#include <cstdarg>
using namespace JPH;
using namespace JPH::literals;
// Callback for traces, connect this to your own trace function if you have one
static void TraceImpl(const char *in_fmt, ...)
{
// Format the message
va_list list;
va_start(list, in_fmt);
char buffer[1024];
vsnprintf(buffer, sizeof(buffer), in_fmt, list);
va_end(list);
OE_CORE_TRACE("Jolt: {}", buffer);
}
#ifdef JPH_ENABLE_ASSERTS
// Callback for asserts, connect this to your own assert handler if you have one
static bool AssertFailedImpl(const char *in_expression, const char *in_message, const char *in_file, uint in_line)
{
OE_CORE_ERROR("Jolt: Assert in file: {}:{} :({}) {}", in_file, in_line, in_expression, (in_message != nullptr? in_message : ""));
// Breakpoint
return true;
};
#endif // JPH_ENABLE_ASSERTS
// Layer that objects can be in, determines which other objects it can collide with
// Typically you at least want to have 1 layer for moving bodies and 1 layer for static bodies, but you can have more
// layers if you want. E.g. you could have a layer for high detail collision (which is not used by the physics simulation
// but only if you do collision testing).
namespace Layers {
static constexpr ObjectLayer NON_MOVING = 0;
static constexpr ObjectLayer MOVING = 1;
static constexpr ObjectLayer NUM_LAYERS = 2;
};
class ObjectLayerPairFilterImpl : public ObjectLayerPairFilter
{
public:
virtual bool ShouldCollide(ObjectLayer object1, ObjectLayer object2) const override;
};
// Each broadphase layer results in a separate bounding volume tree in the broad phase. You at least want to have
// a layer for non-moving and moving objects to avoid having to update a tree full of static objects every frame.
// You can have a 1-on-1 mapping between object layers and broadphase layers (like in this case) but if you have
// many object layers you'll be creating many broad phase trees, which is not efficient. If you want to fine tune
// your broadphase layers define JPH_TRACK_BROADPHASE_STATS and look at the stats reported on the TTY.
namespace BroadPhaseLayers
{
static constexpr BroadPhaseLayer NON_MOVING(0);
static constexpr BroadPhaseLayer MOVING(1);
static constexpr uint NUM_LAYERS(2);
};
class BPLayerInterfaceImpl final : public BroadPhaseLayerInterface
{
public:
BPLayerInterfaceImpl()
{
object_to_broad_phase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
object_to_broad_phase[Layers::MOVING] = BroadPhaseLayers::MOVING;
}
virtual uint GetNumBroadPhaseLayers() const override { return BroadPhaseLayers::NUM_LAYERS; };
virtual BroadPhaseLayer GetBroadPhaseLayer(ObjectLayer layer) const override
{
JPH_ASSERT(layer < Layers::NUM_LAYERS);
return object_to_broad_phase[layer];
}
private:
BroadPhaseLayer object_to_broad_phase[Layers::NUM_LAYERS];
};
class ObjectVsBroadPhaseLayerFilterImpl : public ObjectVsBroadPhaseLayerFilter
{
public:
virtual bool ShouldCollide(ObjectLayer layer1, BroadPhaseLayer layer2) const override
{
switch (layer1)
{
case Layers::NON_MOVING:
return layer2 == BroadPhaseLayers::MOVING;
case Layers::MOVING:
return true;
default:
JPH_ASSERT(false);
return false;
}
}
};
// An example contact listener
class MyContactListener : public ContactListener
{
public:
// See: ContactListener
virtual ValidateResult OnContactValidate(const Body &inBody1, const Body &inBody2, RVec3Arg inBaseOffset, const CollideShapeResult &inCollisionResult) override
{
OE_CORE_TRACE("Jolt: Contact validate callback");
// Allows you to ignore a contact before it is created (using layers to not make objects collide is cheaper!)
return ValidateResult::AcceptAllContactsForThisBodyPair;
}
virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
{
OE_CORE_TRACE("Jolt: A contact was added");
}
virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
{
OE_CORE_TRACE("Jolt: A contact was persisted");
}
virtual void OnContactRemoved(const SubShapeIDPair &inSubShapePair) override
{
OE_CORE_TRACE("Jolt: A contact was removed");
}
};
// An example activation listener
class MyBodyActivationListener : public BodyActivationListener
{
public:
virtual void OnBodyActivated(const BodyID &inBodyID, uint64 inBodyUserData) override
{
OE_CORE_TRACE("Jolt: A body got activated");
}
virtual void OnBodyDeactivated(const BodyID &inBodyID, uint64 inBodyUserData) override
{
OE_CORE_TRACE("Jolt: A body went to sleep");
}
};
namespace OpenEngine {
// Consider making this static like the renderer
class PhysicsEngine
{
public:
PhysicsEngine();
~PhysicsEngine();
// TODO: Temp, make abstract function to create a body
BodyInterface& GetBodyInterface() { return physics_system.GetBodyInterface(); };
void Update( float delta_time, int collision_steps);
private:
PhysicsSystem physics_system;
Ref<TempAllocatorImpl> tmp_allocator;
Ref<JobSystemThreadPool> job_system;
MyBodyActivationListener body_activation_listener;
MyContactListener contact_listener;
BPLayerInterfaceImpl broad_phase_layer_interface;
ObjectVsBroadPhaseLayerFilterImpl object_vs_broadphase_layer_filter;
ObjectLayerPairFilterImpl object_vs_object_layer_filter;
};
}
#endif // PHYSICS_HPP

View File

@@ -6,7 +6,14 @@
#include "open_engine/scene/scene_camera.hpp"
#include "open_engine/renderer/texture.hpp"
#include "open_engine/renderer/renderer3d.hpp"
#include "open_engine/physics.hpp"
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Body/BodyInterface.h>
#include <Jolt/Physics/Body/MotionType.h>
#include <Jolt/Physics/Collision/ObjectLayer.h>
#include <Jolt/Physics/Collision/Shape/Shape.h>
#include <Jolt/Physics/EActivation.h>
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/fwd.hpp>
@@ -110,6 +117,32 @@ namespace OpenEngine {
: material(material) {};
};
struct PhysicsBodyComponent
{
enum class BodyType { Static = 0, Kinematic, Dynamic };
BodyID body;
BodyType type = BodyType::Dynamic;
float linear_damping = 0.05f;
float angular_damping = 0.05f;
float gravity_factor = 1.0f;
EActivation initial_activation_state = EActivation::Activate;
PhysicsBodyComponent() = default;
PhysicsBodyComponent(const PhysicsBodyComponent&) = default;
};
struct PhysicsShapeComponent
{
ShapeRefC shape;
float restitution = 0.8f;
float friction = 0.5f;
};
}
#endif // COMPONENTS_HPP

View File

@@ -2,7 +2,9 @@
#define SCENE_HPP
#include "open_engine/renderer/editor_camera.hpp"
#include "open_engine/physics.hpp"
#include <Jolt/Physics/Body/BodyInterface.h>
#include <entt/entity/fwd.hpp>
#include <entt/entt.hpp>
#include <cstdint>
@@ -17,6 +19,9 @@ namespace OpenEngine {
Scene() = default;
~Scene() = default;
void OnRuntimeStart();
void OnRuntimeStop();
Entity CreateEntity(const std::string& name = std::string());
void DeleteEntity(entt::entity entity);
void MarkEntityForDeletion(Entity entity);
@@ -28,20 +33,27 @@ namespace OpenEngine {
void OnViewportResize(uint32_t width, uint32_t height);
entt::registry& GetRegistry() { return registry; };
PhysicsEngine& GetPhysicsEngine() { return physics_engine; };
Entity GetPrimaryCamera();
private:
void OnUpdatePhysics();
template<typename T>
void OnComponentAdded(Entity entity, T& component);
private:
entt::registry registry;
PhysicsEngine physics_engine;
BodyInterface* body_interface;
uint32_t viewport_width = 0, viewport_height = 0;
std::vector<entt::entity> pending_deletion;
//BodyID sphere_id;
friend class SceneSerializer;
friend class Entity;
};