diff --git a/destrum/CMakeLists.txt b/destrum/CMakeLists.txt index 71fb46f..eec0721 100644 --- a/destrum/CMakeLists.txt +++ b/destrum/CMakeLists.txt @@ -10,6 +10,9 @@ set(SRC_FILES "src/Components/Spinner.cpp" "src/Components/OrbitAndSpin.cpp" + "src/Components/Physics/Rigidbody.cpp" + "src/Components/Physics/BoxCollider.cpp" + "src/Graphics/BindlessSetManager.cpp" "src/Graphics/Camera.cpp" "src/Graphics/ComputePipeline.cpp" @@ -45,11 +48,15 @@ set(SRC_FILES "src/Scene/Scene.cpp" "src/Scene/SceneManager.cpp" - "src/FS/AssetFS.cpp" "src/FS/Manifest.cpp" "src/Util/DeltaTime.cpp" + + "src/Physics/PhysicsWorld.cpp" + "src/Physics/SimplePhysicsWorld.cpp" + "src/Physics/PhysicsSceneBridge.cpp" + src/Components/Physics/BoxCollider.cpp ) add_library(destrum ${SRC_FILES}) diff --git a/destrum/assets_src/cube.fbx b/destrum/assets_src/cube.fbx new file mode 100644 index 0000000..891c16c Binary files /dev/null and b/destrum/assets_src/cube.fbx differ diff --git a/destrum/include/destrum/App.h b/destrum/include/destrum/App.h index 27e1abc..4577718 100644 --- a/destrum/include/destrum/App.h +++ b/destrum/include/destrum/App.h @@ -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) {}; diff --git a/destrum/include/destrum/Components/Physics/BoxCollider.h b/destrum/include/destrum/Components/Physics/BoxCollider.h new file mode 100644 index 0000000..8f724ea --- /dev/null +++ b/destrum/include/destrum/Components/Physics/BoxCollider.h @@ -0,0 +1,31 @@ +#ifndef BOXCOLLIDER_H +#define BOXCOLLIDER_H + +#include + +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 \ No newline at end of file diff --git a/destrum/include/destrum/Components/Physics/CapsuleCollider.h b/destrum/include/destrum/Components/Physics/CapsuleCollider.h new file mode 100644 index 0000000..71e2acf --- /dev/null +++ b/destrum/include/destrum/Components/Physics/CapsuleCollider.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +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}; +}; diff --git a/destrum/include/destrum/Components/Physics/Collider.h b/destrum/include/destrum/Components/Physics/Collider.h new file mode 100644 index 0000000..a37325f --- /dev/null +++ b/destrum/include/destrum/Components/Physics/Collider.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include +#include + +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}; +}; diff --git a/destrum/include/destrum/Components/Physics/Rigidbody.h b/destrum/include/destrum/Components/Physics/Rigidbody.h new file mode 100644 index 0000000..136a004 --- /dev/null +++ b/destrum/include/destrum/Components/Physics/Rigidbody.h @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include +#include + +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}; +}; diff --git a/destrum/include/destrum/Components/Physics/SphereCollider.h b/destrum/include/destrum/Components/Physics/SphereCollider.h new file mode 100644 index 0000000..7831fb4 --- /dev/null +++ b/destrum/include/destrum/Components/Physics/SphereCollider.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include + +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}; +}; diff --git a/destrum/include/destrum/ObjectModel/GameObject.h b/destrum/include/destrum/ObjectModel/GameObject.h index 7783242..025382f 100644 --- a/destrum/include/destrum/ObjectModel/GameObject.h +++ b/destrum/include/destrum/ObjectModel/GameObject.h @@ -47,13 +47,14 @@ public: GameObject& operator=(const GameObject& other) = delete; GameObject& operator=(GameObject&& other) = delete; - template - requires std::constructible_from - Component *AddComponent(Args&&... args) { + template + requires std::derived_from && + std::constructible_from + TComponent* AddComponent(Args&&... args) { auto& addedComponent = m_Components.emplace_back( - std::make_unique(*this, std::forward(args)...)); + std::make_unique(*this, std::forward(args)...)); - return reinterpret_cast(addedComponent.get()); + return static_cast(addedComponent.get()); } template diff --git a/destrum/include/destrum/Physics/PhysicsSceneBridge.h b/destrum/include/destrum/Physics/PhysicsSceneBridge.h new file mode 100644 index 0000000..9047b7e --- /dev/null +++ b/destrum/include/destrum/Physics/PhysicsSceneBridge.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include +#include + +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()}; +// }; +class PhysicsSceneBridge final { +public: + explicit PhysicsSceneBridge(std::unique_ptr 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 m_World; +}; diff --git a/destrum/include/destrum/Physics/PhysicsTypes.h b/destrum/include/destrum/Physics/PhysicsTypes.h new file mode 100644 index 0000000..88a6cc2 --- /dev/null +++ b/destrum/include/destrum/Physics/PhysicsTypes.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include +#include + +class GameObject; + +enum class RigidbodyType { + Static, + Dynamic, + Kinematic +}; + +enum class PhysicsShapeType { + None, + Box, + Sphere, + Capsule +}; + +struct PhysicsBodyHandle { + std::uint32_t id{std::numeric_limits::max()}; + + [[nodiscard]] bool IsValid() const { + return id != std::numeric_limits::max(); + } + + void Reset() { + id = std::numeric_limits::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}; +}; diff --git a/destrum/include/destrum/Physics/PhysicsWorld.h b/destrum/include/destrum/Physics/PhysicsWorld.h new file mode 100644 index 0000000..2dd84e9 --- /dev/null +++ b/destrum/include/destrum/Physics/PhysicsWorld.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +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; +}; diff --git a/destrum/include/destrum/Physics/SimplePhysicsWorld.h b/destrum/include/destrum/Physics/SimplePhysicsWorld.h new file mode 100644 index 0000000..a727f28 --- /dev/null +++ b/destrum/include/destrum/Physics/SimplePhysicsWorld.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include + +#include + +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 m_Bodies; + std::uint32_t m_NextId{0}; +}; diff --git a/destrum/include/destrum/Scene/Scene.h b/destrum/include/destrum/Scene/Scene.h index 3574adc..6c491d1 100644 --- a/destrum/include/destrum/Scene/Scene.h +++ b/destrum/include/destrum/Scene/Scene.h @@ -6,6 +6,8 @@ #include #include +#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()}; + + std::string m_name; std::vector> m_objects{}; std::vector> m_pendingAdditions{}; diff --git a/destrum/include/destrum/Scene/SceneManager.h b/destrum/include/destrum/Scene/SceneManager.h index 11e3e40..b63c811 100644 --- a/destrum/include/destrum/Scene/SceneManager.h +++ b/destrum/include/destrum/Scene/SceneManager.h @@ -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); diff --git a/destrum/src/App.cpp b/destrum/src/App.cpp index 5ba01ab..cde4d64 100644 --- a/destrum/src/App.cpp +++ b/destrum/src/App.cpp @@ -12,10 +12,12 @@ #include -App::App() { +App::App() +{ } -void App::init(const AppParams ¶ms) { +void App::init(const AppParams& params) +{ m_params = params; AssetFS::GetInstance().Init(params.exeDir); @@ -33,7 +35,8 @@ void App::init(const AppParams ¶ms) { SDL_WINDOW_VULKAN); SDL_SetWindowResizable(window, SDL_TRUE); - if (!window) { + if (!window) + { spdlog::error("Failed to create window. SDL Error: {}", SDL_GetError()); std::exit(1); } @@ -48,7 +51,8 @@ void App::init(const AppParams ¶ms) { customInit(); } -void App::run() { +void App::run() +{ Time::GetInstance().Update(); // initialize delta timing const float fixedDt = static_cast(Time::GetInstance().FixedDeltaTime()); @@ -56,14 +60,16 @@ void App::run() { float accumulator = 0.0f; isRunning = true; - while (isRunning) { + while (isRunning) + { // ---- Update timing --- Time::GetInstance().Update(); float dt = static_cast(Time::GetInstance().DeltaTime()); if (dt > 0.25f) dt = 0.25f; - if (dt > 0.0f) { + if (dt > 0.0f) + { float newFPS = 1.0f / dt; avgFPS = std::lerp(avgFPS, newFPS, 0.1f); } @@ -75,16 +81,21 @@ void App::run() { SDL_Event event; - while (SDL_PollEvent(&event)) { + while (SDL_PollEvent(&event)) + { imguiPass.handleEvent(event); - if (event.type == SDL_QUIT) { + if (event.type == SDL_QUIT) + { isRunning = false; break; } - if (event.type == SDL_WINDOWEVENT) { - switch (event.window.event) { - case SDL_WINDOWEVENT_SIZE_CHANGED: - case SDL_WINDOWEVENT_RESIZED: { + if (event.type == SDL_WINDOWEVENT) + { + switch (event.window.event) + { + case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_WINDOWEVENT_RESIZED: + { resizePending = true; lastResizeTime = std::chrono::steady_clock::now(); break; @@ -92,22 +103,24 @@ void App::run() { } } const bool mouseEvent = - event.type == SDL_MOUSEBUTTONDOWN || - event.type == SDL_MOUSEBUTTONUP || - event.type == SDL_MOUSEMOTION || - event.type == SDL_MOUSEWHEEL; + event.type == SDL_MOUSEBUTTONDOWN || + event.type == SDL_MOUSEBUTTONUP || + event.type == SDL_MOUSEMOTION || + event.type == SDL_MOUSEWHEEL; const bool keyboardEvent = - event.type == SDL_KEYDOWN || - event.type == SDL_KEYUP || - event.type == SDL_TEXTINPUT; + event.type == SDL_KEYDOWN || + event.type == SDL_KEYUP || + event.type == SDL_TEXTINPUT; const bool capturedByImgui = - (mouseEvent && imguiPass.wantsMouse()) || - (keyboardEvent && imguiPass.wantsKeyboard()); + (mouseEvent && imguiPass.wantsMouse()) || + (keyboardEvent && imguiPass.wantsKeyboard()); - if (!capturedByImgui) { - if (InputManager::GetInstance().ProcessEvent(event)) { + if (!capturedByImgui) + { + if (InputManager::GetInstance().ProcessEvent(event)) + { isRunning = false; } } @@ -123,13 +136,12 @@ void App::run() { imguiPass.endFrame(); int steps = 0; - while (accumulator >= fixedDt && steps < maxSteps) { + while (accumulator >= fixedDt && steps < maxSteps) + { // physics.Update(fixedDt); - - SDL_SetWindowTitle( - window, - fmt::format("{} - FPS: {:.2f}", m_params.windowTitle, avgFPS).c_str() - ); + customFixedUpdate(fixedDt); + // physics.Step(fixedDt); + // physics.SyncTransforms(); accumulator -= fixedDt; steps++; @@ -138,11 +150,13 @@ void App::run() { const float alpha = accumulator / fixedDt; - if (gfxDevice.needsSwapchainRecreate() || resizePending) { + if (gfxDevice.needsSwapchainRecreate() || resizePending) + { auto now = std::chrono::steady_clock::now(); if (resizePending && - now - lastResizeTime < std::chrono::milliseconds(100)) { + now - lastResizeTime < std::chrono::milliseconds(100)) + { continue; } @@ -150,7 +164,8 @@ void App::run() { int h = 0; SDL_Vulkan_GetDrawableSize(window, &w, &h); - if (w == 0 || h == 0) { + if (w == 0 || h == 0) + { continue; } @@ -166,9 +181,11 @@ void App::run() { customDraw(); - if (frameLimit) { + if (frameLimit) + { auto sleepTime = Time::GetInstance().SleepDuration(); - if (sleepTime.count() > 0) { + if (sleepTime.count() > 0) + { std::this_thread::sleep_for(sleepTime); } } @@ -177,7 +194,8 @@ void App::run() { gfxDevice.waitIdle(); } -void App::cleanup() { +void App::cleanup() +{ spdlog::info("Cleaning up"); customCleanup(); } diff --git a/destrum/src/Components/Physics/BoxCollider.cpp b/destrum/src/Components/Physics/BoxCollider.cpp new file mode 100644 index 0000000..d91a30e --- /dev/null +++ b/destrum/src/Components/Physics/BoxCollider.cpp @@ -0,0 +1 @@ +#include \ No newline at end of file diff --git a/destrum/src/Components/Physics/Rigidbody.cpp b/destrum/src/Components/Physics/Rigidbody.cpp new file mode 100644 index 0000000..47353d9 --- /dev/null +++ b/destrum/src/Components/Physics/Rigidbody.cpp @@ -0,0 +1,45 @@ +#include + +#include + +void Rigidbody::AttachPhysicsBody(PhysicsWorld* world, PhysicsBodyHandle body) { + m_World = world; + m_Body = body; +} + +void Rigidbody::DetachPhysicsBody() { + m_World = nullptr; + m_Body.Reset(); +} + +void Rigidbody::AddForce(const glm::vec3& force) { + if (!HasPhysicsBody()) { + return; + } + + m_World->AddForce(m_Body, force); +} + +void Rigidbody::AddImpulse(const glm::vec3& impulse) { + if (!HasPhysicsBody()) { + return; + } + + m_World->AddImpulse(m_Body, impulse); +} + +void Rigidbody::SetLinearVelocity(const glm::vec3& velocity) { + if (!HasPhysicsBody()) { + return; + } + + m_World->SetLinearVelocity(m_Body, velocity); +} + +glm::vec3 Rigidbody::GetLinearVelocity() const { + if (!HasPhysicsBody()) { + return glm::vec3{0.0f}; + } + + return m_World->GetLinearVelocity(m_Body); +} diff --git a/destrum/src/Physics/PhysicsSceneBridge.cpp b/destrum/src/Physics/PhysicsSceneBridge.cpp new file mode 100644 index 0000000..47ac84f --- /dev/null +++ b/destrum/src/Physics/PhysicsSceneBridge.cpp @@ -0,0 +1,35 @@ +#include + +#include +#include + +PhysicsSceneBridge::PhysicsSceneBridge(std::unique_ptr world) + : m_World(std::move(world)) { +} + +void PhysicsSceneBridge::RegisterGameObject(GameObject& object) { + if (auto* rb = object.GetComponent()) { + if (!rb->HasPhysicsBody()) { + m_World->RegisterRigidbody(*rb); + } + } +} + +void PhysicsSceneBridge::UnregisterGameObject(GameObject& object) { + if (auto* rb = object.GetComponent()) { + if (rb->HasPhysicsBody()) { + m_World->UnregisterRigidbody(*rb); + } + } +} + +void PhysicsSceneBridge::FixedUpdate(float fixedDt) { + if (auto* simple = dynamic_cast(m_World.get())) { + simple->SyncKinematicBodiesToPhysics(); + simple->Step(fixedDt); + simple->SyncDynamicBodiesToTransforms(); + return; + } + + m_World->Step(fixedDt); +} diff --git a/destrum/src/Physics/PhysicsWorld.cpp b/destrum/src/Physics/PhysicsWorld.cpp new file mode 100644 index 0000000..4915e49 --- /dev/null +++ b/destrum/src/Physics/PhysicsWorld.cpp @@ -0,0 +1,62 @@ +#include + +#include + +#include +#include +#include +#include +#include + +void PhysicsWorld::RegisterRigidbody(Rigidbody& rigidbody) { + GameObject* owner = rigidbody.GetGameObject(); + Transform& transform = owner->GetTransform(); + + auto* collider = owner->GetComponent(); + if (!collider) { + throw std::runtime_error("Rigidbody requires a Collider on the same GameObject for now."); + } + + PhysicsBodyDesc desc{}; + desc.owner = owner; + desc.transform.position = transform.GetWorldPosition(); + desc.transform.rotation = transform.GetWorldRotation(); + desc.shape = collider->BuildPhysicsShape(); + + desc.type = rigidbody.GetType(); + desc.mass = rigidbody.GetMass(); + desc.useGravity = rigidbody.UsesGravity(); + desc.allowSleep = rigidbody.AllowsSleep(); + desc.material.friction = rigidbody.GetFriction(); + desc.material.restitution = rigidbody.GetRestitution(); + + PhysicsBodyHandle handle = CreateBody(desc); + rigidbody.AttachPhysicsBody(this, handle); +} + +void PhysicsWorld::UnregisterRigidbody(Rigidbody& rigidbody) { + PhysicsBodyHandle handle = rigidbody.GetBody(); + + if (handle.IsValid()) { + DestroyBody(handle); + } + + rigidbody.DetachPhysicsBody(); +} + +void PhysicsWorld::SyncKinematicBodiesToPhysics() { + // Backend-independent sync is intentionally not possible here because + // PhysicsWorld does not own the list of active rigidbodies. + // + // Use SimplePhysicsWorld as a reference implementation. + // + // If you use Jolt/PhysX/Bullet later, keep a body table in the backend: + // handle -> { GameObject*, Rigidbody* }. +} + +void PhysicsWorld::SyncDynamicBodiesToTransforms() { + // Backend-independent sync is intentionally not possible here because + // PhysicsWorld does not own the list of active rigidbodies. + // + // Use SimplePhysicsWorld as a reference implementation. +} diff --git a/destrum/src/Physics/SimplePhysicsWorld.cpp b/destrum/src/Physics/SimplePhysicsWorld.cpp new file mode 100644 index 0000000..7bbcb43 --- /dev/null +++ b/destrum/src/Physics/SimplePhysicsWorld.cpp @@ -0,0 +1,277 @@ +#include + +#include +#include +#include + +#include +#include +#include + +SimplePhysicsWorld::SimplePhysicsWorld(const glm::vec3& gravity) + : m_Gravity(gravity) { +} + +PhysicsBodyHandle SimplePhysicsWorld::CreateBody(const PhysicsBodyDesc& desc) { + BodyRecord record{}; + record.alive = true; + record.handle = PhysicsBodyHandle{m_NextId++}; + record.desc = desc; + record.previousTransform = desc.transform; + record.currentTransform = desc.transform; + + if (desc.owner) { + record.rigidbody = desc.owner->GetComponent(); + } + + m_Bodies.emplace_back(record); + return record.handle; +} + +void SimplePhysicsWorld::DestroyBody(PhysicsBodyHandle body) { + if (BodyRecord* record = FindBody(body)) { + record->alive = false; + record->rigidbody = nullptr; + } +} + +void SimplePhysicsWorld::SetBodyTransform(PhysicsBodyHandle body, const PhysicsTransform& transform) { + BodyRecord* record = FindBody(body); + if (!record) { + return; + } + + record->previousTransform = record->currentTransform; + record->currentTransform = transform; +} + +PhysicsTransform SimplePhysicsWorld::GetBodyTransform(PhysicsBodyHandle body) const { + const BodyRecord* record = FindBody(body); + if (!record) { + return {}; + } + + return record->currentTransform; +} + +void SimplePhysicsWorld::SetLinearVelocity(PhysicsBodyHandle body, const glm::vec3& velocity) { + BodyRecord* record = FindBody(body); + if (!record) { + return; + } + + record->linearVelocity = velocity; +} + +glm::vec3 SimplePhysicsWorld::GetLinearVelocity(PhysicsBodyHandle body) const { + const BodyRecord* record = FindBody(body); + if (!record) { + return glm::vec3{0.0f}; + } + + return record->linearVelocity; +} + +void SimplePhysicsWorld::AddForce(PhysicsBodyHandle body, const glm::vec3& force) { + BodyRecord* record = FindBody(body); + if (!record || record->desc.type != RigidbodyType::Dynamic) { + return; + } + + record->accumulatedForce += force; +} + +void SimplePhysicsWorld::AddImpulse(PhysicsBodyHandle body, const glm::vec3& impulse) { + BodyRecord* record = FindBody(body); + if (!record || record->desc.type != RigidbodyType::Dynamic) { + return; + } + + const float invMass = record->desc.mass > 0.0f ? 1.0f / record->desc.mass : 0.0f; + record->linearVelocity += impulse * invMass; +} + +void SimplePhysicsWorld::Step(float fixedDt) { + if (fixedDt <= 0.0f) { + return; + } + + for (BodyRecord& body : m_Bodies) { + if (!body.alive) { + continue; + } + + body.previousTransform = body.currentTransform; + + if (body.desc.type != RigidbodyType::Dynamic) { + continue; + } + + const float invMass = body.desc.mass > 0.0f ? 1.0f / body.desc.mass : 0.0f; + + glm::vec3 acceleration{0.0f}; + + if (body.desc.useGravity) { + acceleration += m_Gravity; + } + + acceleration += body.accumulatedForce * invMass; + + // Semi-implicit Euler. + body.linearVelocity += acceleration * fixedDt; + body.currentTransform.position += body.linearVelocity * fixedDt; + + body.accumulatedForce = glm::vec3{0.0f}; + } + + // Compact dead records occasionally. + m_Bodies.erase( + std::remove_if(m_Bodies.begin(), m_Bodies.end(), + [](const BodyRecord& body) { return !body.alive; }), + m_Bodies.end()); +} + +void SimplePhysicsWorld::SyncKinematicBodiesToPhysics() { + for (BodyRecord& body : m_Bodies) { + if (!body.alive || body.desc.type != RigidbodyType::Kinematic || !body.desc.owner) { + continue; + } + + Transform& transform = body.desc.owner->GetTransform(); + + body.previousTransform = body.currentTransform; + body.currentTransform.position = transform.GetWorldPosition(); + body.currentTransform.rotation = transform.GetWorldRotation(); + } +} + +void SimplePhysicsWorld::SyncDynamicBodiesToTransforms() { + for (BodyRecord& body : m_Bodies) { + if (!body.alive || body.desc.type != RigidbodyType::Dynamic || !body.desc.owner) { + continue; + } + + Transform& transform = body.desc.owner->GetTransform(); + transform.SetWorldPosition(body.currentTransform.position); + transform.SetWorldRotation(body.currentTransform.rotation); + } +} + +bool SimplePhysicsWorld::Raycast(const glm::vec3& origin, + const glm::vec3& direction, + float maxDistance, + PhysicsRaycastHit& hit) const { + if (maxDistance <= 0.0f) { + return false; + } + + const float len = glm::length(direction); + if (len <= 0.00001f) { + return false; + } + + const glm::vec3 dir = direction / len; + + bool found = false; + float bestDistance = std::numeric_limits::max(); + + for (const BodyRecord& body : m_Bodies) { + if (!body.alive || body.desc.shape.type == PhysicsShapeType::None) { + continue; + } + + float distance = 0.0f; + const float radius = GetApproxBoundingRadius(body); + const glm::vec3 center = body.currentTransform.position + body.desc.shape.centerOffset; + + if (RaySphere(origin, dir, center, radius, maxDistance, distance)) { + if (distance < bestDistance) { + bestDistance = distance; + found = true; + + hit.object = body.desc.owner; + hit.body = body.handle; + hit.distance = distance; + hit.point = origin + dir * distance; + + const glm::vec3 n = hit.point - center; + hit.normal = glm::length(n) > 0.00001f ? glm::normalize(n) : glm::vec3{0.0f, 1.0f, 0.0f}; + } + } + } + + return found; +} + +SimplePhysicsWorld::BodyRecord* SimplePhysicsWorld::FindBody(PhysicsBodyHandle body) { + for (BodyRecord& record : m_Bodies) { + if (record.alive && record.handle == body) { + return &record; + } + } + + return nullptr; +} + +const SimplePhysicsWorld::BodyRecord* SimplePhysicsWorld::FindBody(PhysicsBodyHandle body) const { + for (const BodyRecord& record : m_Bodies) { + if (record.alive && record.handle == body) { + return &record; + } + } + + return nullptr; +} + +float SimplePhysicsWorld::GetApproxBoundingRadius(const BodyRecord& body) const { + const PhysicsShapeDesc& shape = body.desc.shape; + + switch (shape.type) { + case PhysicsShapeType::Box: + return glm::length(shape.halfExtents); + + case PhysicsShapeType::Sphere: + return shape.radius; + + case PhysicsShapeType::Capsule: + return shape.height * 0.5f; + + case PhysicsShapeType::None: + default: + return 0.0f; + } +} + +bool SimplePhysicsWorld::RaySphere(const glm::vec3& origin, + const glm::vec3& dirNormalized, + const glm::vec3& center, + float radius, + float maxDistance, + float& outDistance) { + const glm::vec3 oc = origin - center; + + const float a = glm::dot(dirNormalized, dirNormalized); + const float b = 2.0f * glm::dot(oc, dirNormalized); + const float c = glm::dot(oc, oc) - radius * radius; + + const float discriminant = b * b - 4.0f * a * c; + if (discriminant < 0.0f) { + return false; + } + + const float sqrtDisc = std::sqrt(discriminant); + const float t0 = (-b - sqrtDisc) / (2.0f * a); + const float t1 = (-b + sqrtDisc) / (2.0f * a); + + float t = t0; + if (t < 0.0f) { + t = t1; + } + + if (t < 0.0f || t > maxDistance) { + return false; + } + + outDistance = t; + return true; +} diff --git a/destrum/src/Scene/Scene.cpp b/destrum/src/Scene/Scene.cpp index 2257de7..4f87ba3 100644 --- a/destrum/src/Scene/Scene.cpp +++ b/destrum/src/Scene/Scene.cpp @@ -57,12 +57,13 @@ void Scene::Update() { } } -void Scene::FixedUpdate() { +void Scene::FixedUpdate(float dt) { for (const auto& object: m_objects) { if (object->IsActiveInHierarchy()) { object->FixedUpdate(); } } + m_Physics.FixedUpdate(dt); } void Scene::LateUpdate() { diff --git a/destrum/src/Scene/SceneManager.cpp b/destrum/src/Scene/SceneManager.cpp index b46b2ee..123ac09 100644 --- a/destrum/src/Scene/SceneManager.cpp +++ b/destrum/src/Scene/SceneManager.cpp @@ -8,8 +8,8 @@ void SceneManager::Update() { m_scenes[m_ActiveSceneIndex]->Update(); } -void SceneManager::FixedUpdate() { - m_scenes[m_ActiveSceneIndex]->FixedUpdate(); +void SceneManager::FixedUpdate(float dt) { + m_scenes[m_ActiveSceneIndex]->FixedUpdate(dt); } void SceneManager::LateUpdate() { diff --git a/lightkeeper/include/Lightkeeper.h b/lightkeeper/include/Lightkeeper.h index 16bbb4e..dbb88db 100644 --- a/lightkeeper/include/Lightkeeper.h +++ b/lightkeeper/include/Lightkeeper.h @@ -15,6 +15,7 @@ public: void customUpdate(float dt) override; void customDraw() override; void customCleanup() override; + void customFixedUpdate(float dt) override; void onWindowResize(int newWidth, int newHeight) override; private: diff --git a/lightkeeper/src/Lightkeeper.cpp b/lightkeeper/src/Lightkeeper.cpp index 8eb3f20..d8a3b87 100644 --- a/lightkeeper/src/Lightkeeper.cpp +++ b/lightkeeper/src/Lightkeeper.cpp @@ -3,6 +3,8 @@ #include #include "glm/gtx/transform.hpp" #include "spdlog/spdlog.h" +#include +#include #include #include "destrum/Components/MeshRendererComponent.h" @@ -13,12 +15,14 @@ #include "destrum/Util/ModelDoc.h" #include "destrum/Components/Animator.h" + #include #include #include "imgui.h" #include "destrum/Util/ModelDocUtils.h" + LightKeeper::LightKeeper() : App(), renderer(meshCache, materialCache) { } @@ -226,125 +230,153 @@ void LightKeeper::customInit() { // CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f); scene.Add(CharObj); - ModelDoc::LoadOptions options{}; - options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode; + // ModelDoc::LoadOptions options{}; + // options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode; + // + // auto model = ModelDoc::LoadModel( + // AssetFS::GetInstance() + // .GetFullPath("engine://multiple_object_test.fbx") + // .generic_string(), + // options + // ); + // + // ModelDocUtils::LogModelDocSummary(model, "engine://multiple_object_test.fbx"); + // + // auto root = std::make_shared("MultiMeshModel"); + // root->GetTransform().SetWorldPosition(glm::vec3(0.1f, 0.1f, 0.1f)); + // scene.Add(root); + // + // for (std::size_t i = 0; i < model.primitives.size(); ++i) { + // const auto &primitive = model.primitives[i]; + // + // const auto meshID = meshCache.addMesh(gfxDevice, primitive.mesh); + // + // const auto texturePath = ModelDocUtils::PickTexturePath( + // model, + // primitive, + // AssetFS::GetInstance().GetFullPath("engine://textures/white.png") + // ); + // + // const auto textureID = gfxDevice.loadImageFromFile(texturePath); + // + // const auto materialID = materialCache.addMaterial(gfxDevice, { + // .baseColor = ModelDocUtils::GetImportedBaseColor( + // model, primitive), + // .diffuseTexture = textureID, + // .name = ModelDocUtils::GetImportedMaterialName( + // model, + // primitive, + // primitive.mesh.name + "_Material" + // ), + // }); + // + // auto part = std::make_shared( + // primitive.mesh.name.empty() + // ? "Primitive_" + std::to_string(i) + // : primitive.mesh.name + // ); + // + // auto meshComp = part->AddComponent(); + // meshComp->SetMeshID(meshID); + // meshComp->SetMaterialID(materialID); + // + // scene.Add(part); + // } - auto model = ModelDoc::LoadModel( - AssetFS::GetInstance() - .GetFullPath("engine://multiple_object_test.fbx") - .generic_string(), - options - ); - - ModelDocUtils::LogModelDocSummary(model, "engine://multiple_object_test.fbx"); - - auto root = std::make_shared("MultiMeshModel"); - root->GetTransform().SetWorldPosition(glm::vec3(0.1f, 0.1f, 0.1f)); - scene.Add(root); - - for (std::size_t i = 0; i < model.primitives.size(); ++i) { - const auto &primitive = model.primitives[i]; - - const auto meshID = meshCache.addMesh(gfxDevice, primitive.mesh); - - const auto texturePath = ModelDocUtils::PickTexturePath( - model, - primitive, - AssetFS::GetInstance().GetFullPath("engine://textures/white.png") - ); - - const auto textureID = gfxDevice.loadImageFromFile(texturePath); - - const auto materialID = materialCache.addMaterial(gfxDevice, { - .baseColor = ModelDocUtils::GetImportedBaseColor( - model, primitive), - .diffuseTexture = textureID, - .name = ModelDocUtils::GetImportedMaterialName( - model, - primitive, - primitive.mesh.name + "_Material" - ), - }); - - auto part = std::make_shared( - primitive.mesh.name.empty() - ? "Primitive_" + std::to_string(i) - : primitive.mesh.name - ); - - auto meshComp = part->AddComponent(); - meshComp->SetMeshID(meshID); - meshComp->SetMaterialID(materialID); - - scene.Add(part); - } + // { + // const auto CharObj = std::make_shared("Character"); + // + // ModelDoc::LoadOptions characterOptions{}; + // characterOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode; + // characterOptions.loadMaterials = true; + // characterOptions.loadSkeleton = true; + // characterOptions.loadAnimations = false; + // + // auto charModel = ModelDoc::LoadModel( + // AssetFS::GetInstance() + // .GetFullPath("engine://characterMedium.fbx") + // .generic_string(), + // characterOptions + // ); + // + // const auto &charPrimitive = + // ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow( + // charModel, + // "engine://characterMedium.fbx" + // ); + // + // const auto charMeshID = meshCache.addMesh(gfxDevice, charPrimitive.mesh); + // + // const auto charTextureID = gfxDevice.loadImageFromFile( + // AssetFS::GetInstance().GetFullPath("engine://textures/criminalMaleA.png") + // ); + // + // const auto charMaterialID = materialCache.addMaterial(gfxDevice, { + // .baseColor = ModelDocUtils::GetImportedBaseColor( + // charModel, charPrimitive), + // .diffuseTexture = charTextureID, + // .name = ModelDocUtils::GetImportedMaterialName( + // charModel, + // charPrimitive, + // "CharacterMaterial" + // ), + // }); + // + // const auto charMeshComp = CharObj->AddComponent(); + // charMeshComp->SetMeshID(charMeshID); + // charMeshComp->SetMaterialID(charMaterialID); + // + // const auto animator = CharObj->AddComponent(); + // animator->setSkeleton(charModel.skeleton); + // + // auto runClips = ModelDoc::LoadAnimationClips( + // AssetFS::GetInstance() + // .GetFullPath("engine://run.fbx") + // .generic_string(), + // charModel.skeleton + // ); + // + // for (auto &clip: runClips) { + // spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration); + // animator->addClip(std::make_shared(std::move(clip))); + // } + // + // if (!runClips.empty()) { + // animator->play("Root|Run"); + // } + // + // CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f)); + // CharObj->GetTransform().SetWorldPosition(glm::vec3(5, 0, 0)); + // scene.Add(CharObj); + // } { - const auto CharObj = std::make_shared("Character"); - - ModelDoc::LoadOptions characterOptions{}; - characterOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode; - characterOptions.loadMaterials = true; - characterOptions.loadSkeleton = true; - characterOptions.loadAnimations = false; - - auto charModel = ModelDoc::LoadModel( + auto cubeModel = ModelDoc::LoadModel( AssetFS::GetInstance() - .GetFullPath("engine://characterMedium.fbx") + .GetFullPath("engine://cube.fbx") .generic_string(), - characterOptions + staticModelOptions ); + ModelDocUtils::LogModelDocSummary(cubeModel, "cube.fbx"); - const auto &charPrimitive = - ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow( - charModel, - "engine://characterMedium.fbx" - ); + const auto &cubePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(cubeModel, "game://cube.fbx"); + const auto cubeMeshID = meshCache.addMesh(gfxDevice, cubePrimitive.mesh); - const auto charMeshID = meshCache.addMesh(gfxDevice, charPrimitive.mesh); + auto cube = std::make_shared("Cube"); - const auto charTextureID = gfxDevice.loadImageFromFile( - AssetFS::GetInstance().GetFullPath("engine://textures/criminalMaleA.png") - ); + cube->AddComponent(glm::vec3{0.5f}); + cube->AddComponent(); - const auto charMaterialID = materialCache.addMaterial(gfxDevice, { - .baseColor = ModelDocUtils::GetImportedBaseColor( - charModel, charPrimitive), - .diffuseTexture = charTextureID, - .name = ModelDocUtils::GetImportedMaterialName( - charModel, - charPrimitive, - "CharacterMaterial" - ), - }); + auto meshComp = cube->AddComponent(); + meshComp->SetMeshID(cubeMeshID); + meshComp->SetMaterialID(charMaterialID); - const auto charMeshComp = CharObj->AddComponent(); - charMeshComp->SetMeshID(charMeshID); - charMeshComp->SetMaterialID(charMaterialID); + cube->GetTransform().SetWorldPosition(glm::vec3(0.0f, 2.0f, 0.0f)); + cube->GetTransform().SetWorldScale(glm::vec3(0.005f)); - const auto animator = CharObj->AddComponent(); - animator->setSkeleton(charModel.skeleton); - - auto runClips = ModelDoc::LoadAnimationClips( - AssetFS::GetInstance() - .GetFullPath("engine://run.fbx") - .generic_string(), - charModel.skeleton - ); - - for (auto &clip: runClips) { - spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration); - animator->addClip(std::make_shared(std::move(clip))); - } - - if (!runClips.empty()) { - animator->play("Root|Run"); - } - - CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f)); - CharObj->GetTransform().SetWorldPosition(glm::vec3(5, 0, 0)); - scene.Add(CharObj); + scene.Add(cube); + scene.GetPhysics().RegisterGameObject(*cube); } } @@ -408,6 +440,11 @@ void LightKeeper::customCleanup() { meshCache.cleanup(gfxDevice); } +void LightKeeper::customFixedUpdate(float dt) +{ + SceneManager::GetInstance().FixedUpdate(dt); +} + void LightKeeper::onWindowResize(int newWidth, int newHeight) { renderer.resize(gfxDevice, glm::ivec2{newWidth, newHeight}); const float aspectRatio = static_cast(newWidth) / static_cast(newHeight);