add basic ah physicsworld

This commit is contained in:
2026-06-23 05:04:17 +02:00
parent 85fbf2c1ff
commit 067caf5fe6
25 changed files with 1115 additions and 152 deletions
+1
View File
@@ -34,6 +34,7 @@ public:
virtual void customUpdate(float dt) = 0;
virtual void customDraw() = 0;
virtual void customCleanup() = 0;
virtual void customFixedUpdate(float dt) = 0;
virtual void onWindowResize(int newWidth, int newHeight) {};
@@ -0,0 +1,31 @@
#ifndef BOXCOLLIDER_H
#define BOXCOLLIDER_H
#include <destrum/Components/Physics/Collider.h>
class BoxCollider final : public Collider {
public:
explicit BoxCollider(GameObject& owner, const glm::vec3& halfExtents = glm::vec3{0.5f})
: Collider(owner)
, m_HalfExtents(halfExtents) {}
void Update() override {}
[[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override {
return PhysicsShapeDesc::Box(m_HalfExtents, m_CenterOffset, m_IsTrigger);
}
[[nodiscard]] const glm::vec3& GetHalfExtents() const {
return m_HalfExtents;
}
void SetHalfExtents(const glm::vec3& halfExtents) {
m_HalfExtents = halfExtents;
}
private:
glm::vec3 m_HalfExtents{0.5f};
};
#endif
@@ -0,0 +1,37 @@
#pragma once
#include <algorithm>
#include <destrum/Components/Collider.h>
class CapsuleCollider final : public Collider {
public:
explicit CapsuleCollider(GameObject& owner, float radius = 0.5f, float height = 2.0f)
: Collider(owner)
, m_Radius(radius)
, m_Height(height) {}
[[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override {
return PhysicsShapeDesc::Capsule(m_Radius, m_Height, m_CenterOffset, m_IsTrigger);
}
[[nodiscard]] float GetRadius() const {
return m_Radius;
}
void SetRadius(float radius) {
m_Radius = std::max(radius, 0.0f);
}
[[nodiscard]] float GetHeight() const {
return m_Height;
}
void SetHeight(float height) {
m_Height = std::max(height, m_Radius * 2.0f);
}
private:
float m_Radius{0.5f};
float m_Height{2.0f};
};
@@ -0,0 +1,26 @@
#pragma once
#include <glm/glm.hpp>
#include <destrum/ObjectModel/Component.h>
#include <destrum/Physics/PhysicsTypes.h>
class Collider : public Component {
public:
explicit Collider(GameObject& owner)
: Component(owner) {}
~Collider() override = default;
[[nodiscard]] virtual PhysicsShapeDesc BuildPhysicsShape() const = 0;
[[nodiscard]] bool IsTrigger() const { return m_IsTrigger; }
void SetTrigger(bool trigger) { m_IsTrigger = trigger; }
[[nodiscard]] const glm::vec3& GetCenterOffset() const { return m_CenterOffset; }
void SetCenterOffset(const glm::vec3& offset) { m_CenterOffset = offset; }
protected:
bool m_IsTrigger{false};
glm::vec3 m_CenterOffset{0.0f};
};
@@ -0,0 +1,63 @@
#pragma once
#include <glm/glm.hpp>
#include <destrum/ObjectModel/Component.h>
#include <destrum/Physics/PhysicsTypes.h>
class PhysicsWorld;
class Rigidbody final : public Component {
public:
explicit Rigidbody(GameObject& owner)
: Component(owner) {}
~Rigidbody() override = default;
void Update() override {}
void AttachPhysicsBody(PhysicsWorld* world, PhysicsBodyHandle body);
void DetachPhysicsBody();
[[nodiscard]] PhysicsBodyHandle GetBody() const { return m_Body; }
[[nodiscard]] bool HasPhysicsBody() const { return m_Body.IsValid() && m_World != nullptr; }
void AddForce(const glm::vec3& force);
void AddImpulse(const glm::vec3& impulse);
void SetLinearVelocity(const glm::vec3& velocity);
[[nodiscard]] glm::vec3 GetLinearVelocity() const;
[[nodiscard]] RigidbodyType GetType() const { return m_Type; }
void SetType(RigidbodyType type) { m_Type = type; }
[[nodiscard]] float GetMass() const { return m_Mass; }
void SetMass(float mass) { m_Mass = mass > 0.0f ? mass : 0.0001f; }
[[nodiscard]] float GetFriction() const { return m_Friction; }
void SetFriction(float friction) { m_Friction = friction; }
[[nodiscard]] float GetRestitution() const { return m_Restitution; }
void SetRestitution(float restitution) { m_Restitution = restitution; }
[[nodiscard]] bool UsesGravity() const { return m_UseGravity; }
void SetUseGravity(bool useGravity) { m_UseGravity = useGravity; }
[[nodiscard]] bool AllowsSleep() const { return m_AllowSleep; }
void SetAllowSleep(bool allowSleep) { m_AllowSleep = allowSleep; }
[[nodiscard]] PhysicsWorld* GetPhysicsWorld() const { return m_World; }
private:
PhysicsWorld* m_World{nullptr};
PhysicsBodyHandle m_Body{};
RigidbodyType m_Type{RigidbodyType::Dynamic};
float m_Mass{1.0f};
float m_Friction{0.5f};
float m_Restitution{0.0f};
bool m_UseGravity{true};
bool m_AllowSleep{true};
};
@@ -0,0 +1,27 @@
#pragma once
#include <algorithm>
#include <destrum/Components/Collider.h>
class SphereCollider final : public Collider {
public:
explicit SphereCollider(GameObject& owner, float radius = 0.5f)
: Collider(owner)
, m_Radius(radius) {}
[[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override {
return PhysicsShapeDesc::Sphere(m_Radius, m_CenterOffset, m_IsTrigger);
}
[[nodiscard]] float GetRadius() const {
return m_Radius;
}
void SetRadius(float radius) {
m_Radius = std::max(radius, 0.0f);
}
private:
float m_Radius{0.5f};
};
@@ -47,13 +47,14 @@ public:
GameObject& operator=(const GameObject& other) = delete;
GameObject& operator=(GameObject&& other) = delete;
template <typename Component, typename... Args>
requires std::constructible_from<Component, GameObject&, Args...>
Component *AddComponent(Args&&... args) {
template <typename TComponent, typename... Args>
requires std::derived_from<TComponent, Component> &&
std::constructible_from<TComponent, GameObject&, Args&&...>
TComponent* AddComponent(Args&&... args) {
auto& addedComponent = m_Components.emplace_back(
std::make_unique<Component>(*this, std::forward<Args>(args)...));
std::make_unique<TComponent>(*this, std::forward<Args>(args)...));
return reinterpret_cast<Component*>(addedComponent.get());
return static_cast<TComponent*>(addedComponent.get());
}
template <typename Component>
@@ -0,0 +1,34 @@
#pragma once
#include <memory>
#include <vector>
#include <destrum/Physics/PhysicsWorld.h>
#include <destrum/Physics/SimplePhysicsWorld.h>
class GameObject;
// Small helper you can store inside Scene.
//
// Example:
// class Scene {
// public:
// PhysicsSceneBridge& GetPhysics() { return m_Physics; }
// private:
// PhysicsSceneBridge m_Physics{std::make_unique<SimplePhysicsWorld>()};
// };
class PhysicsSceneBridge final {
public:
explicit PhysicsSceneBridge(std::unique_ptr<PhysicsWorld> world);
[[nodiscard]] PhysicsWorld& GetWorld() { return *m_World; }
[[nodiscard]] const PhysicsWorld& GetWorld() const { return *m_World; }
void RegisterGameObject(GameObject& object);
void UnregisterGameObject(GameObject& object);
void FixedUpdate(float fixedDt);
private:
std::unique_ptr<PhysicsWorld> m_World;
};
@@ -0,0 +1,118 @@
#pragma once
#include <cstdint>
#include <limits>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
class GameObject;
enum class RigidbodyType {
Static,
Dynamic,
Kinematic
};
enum class PhysicsShapeType {
None,
Box,
Sphere,
Capsule
};
struct PhysicsBodyHandle {
std::uint32_t id{std::numeric_limits<std::uint32_t>::max()};
[[nodiscard]] bool IsValid() const {
return id != std::numeric_limits<std::uint32_t>::max();
}
void Reset() {
id = std::numeric_limits<std::uint32_t>::max();
}
friend bool operator==(const PhysicsBodyHandle& a, const PhysicsBodyHandle& b) {
return a.id == b.id;
}
friend bool operator!=(const PhysicsBodyHandle& a, const PhysicsBodyHandle& b) {
return !(a == b);
}
};
struct PhysicsTransform {
glm::vec3 position{0.0f};
glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f};
};
struct PhysicsMaterialDesc {
float friction{0.5f};
float restitution{0.0f};
};
struct PhysicsShapeDesc {
PhysicsShapeType type{PhysicsShapeType::None};
// Box
glm::vec3 halfExtents{0.5f};
// Sphere / capsule
float radius{0.5f};
// Capsule full height, including the two hemispheres.
float height{2.0f};
// Local offset from the rigidbody transform.
glm::vec3 centerOffset{0.0f};
bool isTrigger{false};
[[nodiscard]] static PhysicsShapeDesc Box(const glm::vec3& halfExtents,
const glm::vec3& centerOffset = glm::vec3{0.0f},
bool isTrigger = false) {
PhysicsShapeDesc desc{};
desc.type = PhysicsShapeType::Box;
desc.halfExtents = halfExtents;
desc.centerOffset = centerOffset;
desc.isTrigger = isTrigger;
return desc;
}
[[nodiscard]] static PhysicsShapeDesc Sphere(float radius,
const glm::vec3& centerOffset = glm::vec3{0.0f},
bool isTrigger = false) {
PhysicsShapeDesc desc{};
desc.type = PhysicsShapeType::Sphere;
desc.radius = radius;
desc.centerOffset = centerOffset;
desc.isTrigger = isTrigger;
return desc;
}
[[nodiscard]] static PhysicsShapeDesc Capsule(float radius,
float height,
const glm::vec3& centerOffset = glm::vec3{0.0f},
bool isTrigger = false) {
PhysicsShapeDesc desc{};
desc.type = PhysicsShapeType::Capsule;
desc.radius = radius;
desc.height = height;
desc.centerOffset = centerOffset;
desc.isTrigger = isTrigger;
return desc;
}
};
struct PhysicsBodyDesc {
GameObject* owner{nullptr};
PhysicsTransform transform{};
PhysicsShapeDesc shape{};
PhysicsMaterialDesc material{};
RigidbodyType type{RigidbodyType::Dynamic};
float mass{1.0f};
bool useGravity{true};
bool allowSleep{true};
};
@@ -0,0 +1,47 @@
#pragma once
#include <glm/glm.hpp>
#include <destrum/Physics/PhysicsTypes.h>
#include <destrum/Components/Physics/Rigidbody.h>
class GameObject;
struct PhysicsRaycastHit {
GameObject* object{nullptr};
PhysicsBodyHandle body{};
glm::vec3 point{0.0f};
glm::vec3 normal{0.0f, 1.0f, 0.0f};
float distance{0.0f};
};
class PhysicsWorld {
public:
virtual ~PhysicsWorld() = default;
void RegisterRigidbody(Rigidbody& rigidbody);
void UnregisterRigidbody(Rigidbody& rigidbody);
virtual void Step(float fixedDt) = 0;
// - Kinematic: Transform -> physics body before Step()
// - Dynamic: physics body -> Transform after Step()
void SyncKinematicBodiesToPhysics();
void SyncDynamicBodiesToTransforms();
virtual PhysicsBodyHandle CreateBody(const PhysicsBodyDesc& desc) = 0;
virtual void DestroyBody(PhysicsBodyHandle body) = 0;
virtual void SetBodyTransform(PhysicsBodyHandle body, const PhysicsTransform& transform) = 0;
virtual PhysicsTransform GetBodyTransform(PhysicsBodyHandle body) const = 0;
virtual void SetLinearVelocity(PhysicsBodyHandle body, const glm::vec3& velocity) = 0;
virtual glm::vec3 GetLinearVelocity(PhysicsBodyHandle body) const = 0;
virtual void AddForce(PhysicsBodyHandle body, const glm::vec3& force) = 0;
virtual void AddImpulse(PhysicsBodyHandle body, const glm::vec3& impulse) = 0;
virtual bool Raycast(const glm::vec3& origin,
const glm::vec3& direction,
float maxDistance,
PhysicsRaycastHit& hit) const = 0;
};
@@ -0,0 +1,88 @@
#pragma once
#include <vector>
#include <optional>
#include <destrum/Physics/PhysicsWorld.h>
class Rigidbody;
// Simple CPU placeholder backend.
//
// This is NOT a replacement for Jolt/PhysX/Bullet.
// It exists so your component/scene/render sync can be implemented and tested
// before the real backend is added.
//
// It supports:
// - static, dynamic, kinematic bodies
// - gravity
// - force/impulse integration
// - very simple sphere/box/capsule raycasts using bounding spheres
//
// It does NOT support:
// - collision response
// - contacts
// - friction
// - constraints
// - real character controllers
class SimplePhysicsWorld final : public PhysicsWorld {
public:
explicit SimplePhysicsWorld(const glm::vec3& gravity = glm::vec3{0.0f, -9.81f, 0.0f});
void Step(float fixedDt) override;
PhysicsBodyHandle CreateBody(const PhysicsBodyDesc& desc) override;
void DestroyBody(PhysicsBodyHandle body) override;
void SetBodyTransform(PhysicsBodyHandle body, const PhysicsTransform& transform) override;
PhysicsTransform GetBodyTransform(PhysicsBodyHandle body) const override;
void SetLinearVelocity(PhysicsBodyHandle body, const glm::vec3& velocity) override;
glm::vec3 GetLinearVelocity(PhysicsBodyHandle body) const override;
void AddForce(PhysicsBodyHandle body, const glm::vec3& force) override;
void AddImpulse(PhysicsBodyHandle body, const glm::vec3& impulse) override;
bool Raycast(const glm::vec3& origin,
const glm::vec3& direction,
float maxDistance,
PhysicsRaycastHit& hit) const override;
void SyncKinematicBodiesToPhysics();
void SyncDynamicBodiesToTransforms();
[[nodiscard]] const glm::vec3& GetGravity() const { return m_Gravity; }
void SetGravity(const glm::vec3& gravity) { m_Gravity = gravity; }
private:
struct BodyRecord {
bool alive{false};
PhysicsBodyHandle handle{};
PhysicsBodyDesc desc{};
PhysicsTransform previousTransform{};
PhysicsTransform currentTransform{};
glm::vec3 linearVelocity{0.0f};
glm::vec3 accumulatedForce{0.0f};
Rigidbody* rigidbody{nullptr};
};
[[nodiscard]] BodyRecord* FindBody(PhysicsBodyHandle body);
[[nodiscard]] const BodyRecord* FindBody(PhysicsBodyHandle body) const;
[[nodiscard]] float GetApproxBoundingRadius(const BodyRecord& body) const;
[[nodiscard]] static bool RaySphere(const glm::vec3& origin,
const glm::vec3& dirNormalized,
const glm::vec3& center,
float radius,
float maxDistance,
float& outDistance);
glm::vec3 m_Gravity{0.0f, -9.81f, 0.0f};
std::vector<BodyRecord> m_Bodies;
std::uint32_t m_NextId{0};
};
+7 -1
View File
@@ -6,6 +6,8 @@
#include <destrum/Event.h>
#include <destrum/Scene/SceneManager.h>
#include "destrum/Physics/PhysicsSceneBridge.h"
class GameObject;
class Scene final {
@@ -19,7 +21,7 @@ public:
void Load();
void Update();
void FixedUpdate();
void FixedUpdate(float dt);
void LateUpdate();
void Render(const RenderContext& ctx) const;
void RenderImgui();
@@ -62,9 +64,13 @@ public:
Event<> OnSceneLoaded;
PhysicsSceneBridge& GetPhysics() { return m_Physics; }
private:
explicit Scene(const std::string& name);
PhysicsSceneBridge m_Physics{std::make_unique<SimplePhysicsWorld>()};
std::string m_name;
std::vector<std::shared_ptr<GameObject>> m_objects{};
std::vector<std::shared_ptr<GameObject>> m_pendingAdditions{};
+1 -1
View File
@@ -19,7 +19,7 @@ public:
Scene& GetCurrentScene() const { return *m_scenes[m_ActiveSceneIndex]; }
void Update();
void FixedUpdate();
void FixedUpdate(float dt);
void LateUpdate();
void Render(const RenderContext& ctx);