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
+8 -1
View File
@@ -10,6 +10,9 @@ set(SRC_FILES
"src/Components/Spinner.cpp" "src/Components/Spinner.cpp"
"src/Components/OrbitAndSpin.cpp" "src/Components/OrbitAndSpin.cpp"
"src/Components/Physics/Rigidbody.cpp"
"src/Components/Physics/BoxCollider.cpp"
"src/Graphics/BindlessSetManager.cpp" "src/Graphics/BindlessSetManager.cpp"
"src/Graphics/Camera.cpp" "src/Graphics/Camera.cpp"
"src/Graphics/ComputePipeline.cpp" "src/Graphics/ComputePipeline.cpp"
@@ -45,11 +48,15 @@ set(SRC_FILES
"src/Scene/Scene.cpp" "src/Scene/Scene.cpp"
"src/Scene/SceneManager.cpp" "src/Scene/SceneManager.cpp"
"src/FS/AssetFS.cpp" "src/FS/AssetFS.cpp"
"src/FS/Manifest.cpp" "src/FS/Manifest.cpp"
"src/Util/DeltaTime.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}) add_library(destrum ${SRC_FILES})
Binary file not shown.
+1
View File
@@ -34,6 +34,7 @@ public:
virtual void customUpdate(float dt) = 0; virtual void customUpdate(float dt) = 0;
virtual void customDraw() = 0; virtual void customDraw() = 0;
virtual void customCleanup() = 0; virtual void customCleanup() = 0;
virtual void customFixedUpdate(float dt) = 0;
virtual void onWindowResize(int newWidth, int newHeight) {}; 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=(const GameObject& other) = delete;
GameObject& operator=(GameObject&& other) = delete; GameObject& operator=(GameObject&& other) = delete;
template <typename Component, typename... Args> template <typename TComponent, typename... Args>
requires std::constructible_from<Component, GameObject&, Args...> requires std::derived_from<TComponent, Component> &&
Component *AddComponent(Args&&... args) { std::constructible_from<TComponent, GameObject&, Args&&...>
TComponent* AddComponent(Args&&... args) {
auto& addedComponent = m_Components.emplace_back( 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> 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/Event.h>
#include <destrum/Scene/SceneManager.h> #include <destrum/Scene/SceneManager.h>
#include "destrum/Physics/PhysicsSceneBridge.h"
class GameObject; class GameObject;
class Scene final { class Scene final {
@@ -19,7 +21,7 @@ public:
void Load(); void Load();
void Update(); void Update();
void FixedUpdate(); void FixedUpdate(float dt);
void LateUpdate(); void LateUpdate();
void Render(const RenderContext& ctx) const; void Render(const RenderContext& ctx) const;
void RenderImgui(); void RenderImgui();
@@ -62,9 +64,13 @@ public:
Event<> OnSceneLoaded; Event<> OnSceneLoaded;
PhysicsSceneBridge& GetPhysics() { return m_Physics; }
private: private:
explicit Scene(const std::string& name); explicit Scene(const std::string& name);
PhysicsSceneBridge m_Physics{std::make_unique<SimplePhysicsWorld>()};
std::string m_name; std::string m_name;
std::vector<std::shared_ptr<GameObject>> m_objects{}; std::vector<std::shared_ptr<GameObject>> m_objects{};
std::vector<std::shared_ptr<GameObject>> m_pendingAdditions{}; 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]; } Scene& GetCurrentScene() const { return *m_scenes[m_ActiveSceneIndex]; }
void Update(); void Update();
void FixedUpdate(); void FixedUpdate(float dt);
void LateUpdate(); void LateUpdate();
void Render(const RenderContext& ctx); void Render(const RenderContext& ctx);
+53 -35
View File
@@ -12,10 +12,12 @@
#include <Jolt/Jolt.h> #include <Jolt/Jolt.h>
App::App() { App::App()
{
} }
void App::init(const AppParams &params) { void App::init(const AppParams& params)
{
m_params = params; m_params = params;
AssetFS::GetInstance().Init(params.exeDir); AssetFS::GetInstance().Init(params.exeDir);
@@ -33,7 +35,8 @@ void App::init(const AppParams &params) {
SDL_WINDOW_VULKAN); SDL_WINDOW_VULKAN);
SDL_SetWindowResizable(window, SDL_TRUE); SDL_SetWindowResizable(window, SDL_TRUE);
if (!window) { if (!window)
{
spdlog::error("Failed to create window. SDL Error: {}", SDL_GetError()); spdlog::error("Failed to create window. SDL Error: {}", SDL_GetError());
std::exit(1); std::exit(1);
} }
@@ -48,7 +51,8 @@ void App::init(const AppParams &params) {
customInit(); customInit();
} }
void App::run() { void App::run()
{
Time::GetInstance().Update(); // initialize delta timing Time::GetInstance().Update(); // initialize delta timing
const float fixedDt = static_cast<float>(Time::GetInstance().FixedDeltaTime()); const float fixedDt = static_cast<float>(Time::GetInstance().FixedDeltaTime());
@@ -56,14 +60,16 @@ void App::run() {
float accumulator = 0.0f; float accumulator = 0.0f;
isRunning = true; isRunning = true;
while (isRunning) { while (isRunning)
{
// ---- Update timing --- // ---- Update timing ---
Time::GetInstance().Update(); Time::GetInstance().Update();
float dt = static_cast<float>(Time::GetInstance().DeltaTime()); float dt = static_cast<float>(Time::GetInstance().DeltaTime());
if (dt > 0.25f) dt = 0.25f; if (dt > 0.25f) dt = 0.25f;
if (dt > 0.0f) { if (dt > 0.0f)
{
float newFPS = 1.0f / dt; float newFPS = 1.0f / dt;
avgFPS = std::lerp(avgFPS, newFPS, 0.1f); avgFPS = std::lerp(avgFPS, newFPS, 0.1f);
} }
@@ -75,16 +81,21 @@ void App::run() {
SDL_Event event; SDL_Event event;
while (SDL_PollEvent(&event)) { while (SDL_PollEvent(&event))
{
imguiPass.handleEvent(event); imguiPass.handleEvent(event);
if (event.type == SDL_QUIT) { if (event.type == SDL_QUIT)
{
isRunning = false; isRunning = false;
break; break;
} }
if (event.type == SDL_WINDOWEVENT) { if (event.type == SDL_WINDOWEVENT)
switch (event.window.event) { {
case SDL_WINDOWEVENT_SIZE_CHANGED: switch (event.window.event)
case SDL_WINDOWEVENT_RESIZED: { {
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
{
resizePending = true; resizePending = true;
lastResizeTime = std::chrono::steady_clock::now(); lastResizeTime = std::chrono::steady_clock::now();
break; break;
@@ -92,22 +103,24 @@ void App::run() {
} }
} }
const bool mouseEvent = const bool mouseEvent =
event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONDOWN ||
event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONUP ||
event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEMOTION ||
event.type == SDL_MOUSEWHEEL; event.type == SDL_MOUSEWHEEL;
const bool keyboardEvent = const bool keyboardEvent =
event.type == SDL_KEYDOWN || event.type == SDL_KEYDOWN ||
event.type == SDL_KEYUP || event.type == SDL_KEYUP ||
event.type == SDL_TEXTINPUT; event.type == SDL_TEXTINPUT;
const bool capturedByImgui = const bool capturedByImgui =
(mouseEvent && imguiPass.wantsMouse()) || (mouseEvent && imguiPass.wantsMouse()) ||
(keyboardEvent && imguiPass.wantsKeyboard()); (keyboardEvent && imguiPass.wantsKeyboard());
if (!capturedByImgui) { if (!capturedByImgui)
if (InputManager::GetInstance().ProcessEvent(event)) { {
if (InputManager::GetInstance().ProcessEvent(event))
{
isRunning = false; isRunning = false;
} }
} }
@@ -123,13 +136,12 @@ void App::run() {
imguiPass.endFrame(); imguiPass.endFrame();
int steps = 0; int steps = 0;
while (accumulator >= fixedDt && steps < maxSteps) { while (accumulator >= fixedDt && steps < maxSteps)
{
// physics.Update(fixedDt); // physics.Update(fixedDt);
customFixedUpdate(fixedDt);
SDL_SetWindowTitle( // physics.Step(fixedDt);
window, // physics.SyncTransforms();
fmt::format("{} - FPS: {:.2f}", m_params.windowTitle, avgFPS).c_str()
);
accumulator -= fixedDt; accumulator -= fixedDt;
steps++; steps++;
@@ -138,11 +150,13 @@ void App::run() {
const float alpha = accumulator / fixedDt; const float alpha = accumulator / fixedDt;
if (gfxDevice.needsSwapchainRecreate() || resizePending) { if (gfxDevice.needsSwapchainRecreate() || resizePending)
{
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
if (resizePending && if (resizePending &&
now - lastResizeTime < std::chrono::milliseconds(100)) { now - lastResizeTime < std::chrono::milliseconds(100))
{
continue; continue;
} }
@@ -150,7 +164,8 @@ void App::run() {
int h = 0; int h = 0;
SDL_Vulkan_GetDrawableSize(window, &w, &h); SDL_Vulkan_GetDrawableSize(window, &w, &h);
if (w == 0 || h == 0) { if (w == 0 || h == 0)
{
continue; continue;
} }
@@ -166,9 +181,11 @@ void App::run() {
customDraw(); customDraw();
if (frameLimit) { if (frameLimit)
{
auto sleepTime = Time::GetInstance().SleepDuration(); auto sleepTime = Time::GetInstance().SleepDuration();
if (sleepTime.count() > 0) { if (sleepTime.count() > 0)
{
std::this_thread::sleep_for(sleepTime); std::this_thread::sleep_for(sleepTime);
} }
} }
@@ -177,7 +194,8 @@ void App::run() {
gfxDevice.waitIdle(); gfxDevice.waitIdle();
} }
void App::cleanup() { void App::cleanup()
{
spdlog::info("Cleaning up"); spdlog::info("Cleaning up");
customCleanup(); customCleanup();
} }
@@ -0,0 +1 @@
#include <destrum/Components/Physics/BoxCollider.h>
@@ -0,0 +1,45 @@
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/Physics/PhysicsWorld.h>
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);
}
@@ -0,0 +1,35 @@
#include <destrum/Physics/PhysicsSceneBridge.h>
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/ObjectModel/GameObject.h>
PhysicsSceneBridge::PhysicsSceneBridge(std::unique_ptr<PhysicsWorld> world)
: m_World(std::move(world)) {
}
void PhysicsSceneBridge::RegisterGameObject(GameObject& object) {
if (auto* rb = object.GetComponent<Rigidbody>()) {
if (!rb->HasPhysicsBody()) {
m_World->RegisterRigidbody(*rb);
}
}
}
void PhysicsSceneBridge::UnregisterGameObject(GameObject& object) {
if (auto* rb = object.GetComponent<Rigidbody>()) {
if (rb->HasPhysicsBody()) {
m_World->UnregisterRigidbody(*rb);
}
}
}
void PhysicsSceneBridge::FixedUpdate(float fixedDt) {
if (auto* simple = dynamic_cast<SimplePhysicsWorld*>(m_World.get())) {
simple->SyncKinematicBodiesToPhysics();
simple->Step(fixedDt);
simple->SyncDynamicBodiesToTransforms();
return;
}
m_World->Step(fixedDt);
}
+62
View File
@@ -0,0 +1,62 @@
#include <destrum/Physics/PhysicsWorld.h>
#include <stdexcept>
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/Components/Physics/Collider.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/ObjectModel/Transform.h>
#include <destrum/Components/Physics/Rigidbody.h>
void PhysicsWorld::RegisterRigidbody(Rigidbody& rigidbody) {
GameObject* owner = rigidbody.GetGameObject();
Transform& transform = owner->GetTransform();
auto* collider = owner->GetComponent<Collider>();
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.
}
+277
View File
@@ -0,0 +1,277 @@
#include <destrum/Physics/SimplePhysicsWorld.h>
#include <algorithm>
#include <cmath>
#include <limits>
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/ObjectModel/Transform.h>
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<Rigidbody>();
}
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<float>::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;
}
+2 -1
View File
@@ -57,12 +57,13 @@ void Scene::Update() {
} }
} }
void Scene::FixedUpdate() { void Scene::FixedUpdate(float dt) {
for (const auto& object: m_objects) { for (const auto& object: m_objects) {
if (object->IsActiveInHierarchy()) { if (object->IsActiveInHierarchy()) {
object->FixedUpdate(); object->FixedUpdate();
} }
} }
m_Physics.FixedUpdate(dt);
} }
void Scene::LateUpdate() { void Scene::LateUpdate() {
+2 -2
View File
@@ -8,8 +8,8 @@ void SceneManager::Update() {
m_scenes[m_ActiveSceneIndex]->Update(); m_scenes[m_ActiveSceneIndex]->Update();
} }
void SceneManager::FixedUpdate() { void SceneManager::FixedUpdate(float dt) {
m_scenes[m_ActiveSceneIndex]->FixedUpdate(); m_scenes[m_ActiveSceneIndex]->FixedUpdate(dt);
} }
void SceneManager::LateUpdate() { void SceneManager::LateUpdate() {
+1
View File
@@ -15,6 +15,7 @@ public:
void customUpdate(float dt) override; void customUpdate(float dt) override;
void customDraw() override; void customDraw() override;
void customCleanup() override; void customCleanup() override;
void customFixedUpdate(float dt) override;
void onWindowResize(int newWidth, int newHeight) override; void onWindowResize(int newWidth, int newHeight) override;
private: private:
+143 -106
View File
@@ -3,6 +3,8 @@
#include <destrum/FS/AssetFS.h> #include <destrum/FS/AssetFS.h>
#include "glm/gtx/transform.hpp" #include "glm/gtx/transform.hpp"
#include "spdlog/spdlog.h" #include "spdlog/spdlog.h"
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/Components/Physics/BoxCollider.h>
#include <destrum/Scene/Scene.h> #include <destrum/Scene/Scene.h>
#include "destrum/Components/MeshRendererComponent.h" #include "destrum/Components/MeshRendererComponent.h"
@@ -13,12 +15,14 @@
#include "destrum/Util/ModelDoc.h" #include "destrum/Util/ModelDoc.h"
#include "destrum/Components/Animator.h" #include "destrum/Components/Animator.h"
#include <filesystem> #include <filesystem>
#include <string> #include <string>
#include "imgui.h" #include "imgui.h"
#include "destrum/Util/ModelDocUtils.h" #include "destrum/Util/ModelDocUtils.h"
LightKeeper::LightKeeper() : App(), renderer(meshCache, materialCache) { LightKeeper::LightKeeper() : App(), renderer(meshCache, materialCache) {
} }
@@ -226,125 +230,153 @@ void LightKeeper::customInit() {
// CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f); // CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f);
scene.Add(CharObj); scene.Add(CharObj);
ModelDoc::LoadOptions options{}; // ModelDoc::LoadOptions options{};
options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode; // 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<GameObject>("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<GameObject>(
// primitive.mesh.name.empty()
// ? "Primitive_" + std::to_string(i)
// : primitive.mesh.name
// );
//
// auto meshComp = part->AddComponent<MeshRendererComponent>();
// 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<GameObject>("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<GameObject>(
primitive.mesh.name.empty()
? "Primitive_" + std::to_string(i)
: primitive.mesh.name
);
auto meshComp = part->AddComponent<MeshRendererComponent>();
meshComp->SetMeshID(meshID);
meshComp->SetMaterialID(materialID);
scene.Add(part);
}
// {
// const auto CharObj = std::make_shared<GameObject>("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<MeshRendererComponent>();
// charMeshComp->SetMeshID(charMeshID);
// charMeshComp->SetMaterialID(charMaterialID);
//
// const auto animator = CharObj->AddComponent<Animator>();
// 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<SkeletalAnimation>(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<GameObject>("Character"); auto cubeModel = ModelDoc::LoadModel(
ModelDoc::LoadOptions characterOptions{};
characterOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
characterOptions.loadMaterials = true;
characterOptions.loadSkeleton = true;
characterOptions.loadAnimations = false;
auto charModel = ModelDoc::LoadModel(
AssetFS::GetInstance() AssetFS::GetInstance()
.GetFullPath("engine://characterMedium.fbx") .GetFullPath("engine://cube.fbx")
.generic_string(), .generic_string(),
characterOptions staticModelOptions
); );
ModelDocUtils::LogModelDocSummary(cubeModel, "cube.fbx");
const auto &charPrimitive = const auto &cubePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(cubeModel, "game://cube.fbx");
ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow( const auto cubeMeshID = meshCache.addMesh(gfxDevice, cubePrimitive.mesh);
charModel,
"engine://characterMedium.fbx"
);
const auto charMeshID = meshCache.addMesh(gfxDevice, charPrimitive.mesh); auto cube = std::make_shared<GameObject>("Cube");
const auto charTextureID = gfxDevice.loadImageFromFile( cube->AddComponent<BoxCollider>(glm::vec3{0.5f});
AssetFS::GetInstance().GetFullPath("engine://textures/criminalMaleA.png") cube->AddComponent<Rigidbody>();
);
const auto charMaterialID = materialCache.addMaterial(gfxDevice, { auto meshComp = cube->AddComponent<MeshRendererComponent>();
.baseColor = ModelDocUtils::GetImportedBaseColor( meshComp->SetMeshID(cubeMeshID);
charModel, charPrimitive), meshComp->SetMaterialID(charMaterialID);
.diffuseTexture = charTextureID,
.name = ModelDocUtils::GetImportedMaterialName(
charModel,
charPrimitive,
"CharacterMaterial"
),
});
const auto charMeshComp = CharObj->AddComponent<MeshRendererComponent>(); cube->GetTransform().SetWorldPosition(glm::vec3(0.0f, 2.0f, 0.0f));
charMeshComp->SetMeshID(charMeshID); cube->GetTransform().SetWorldScale(glm::vec3(0.005f));
charMeshComp->SetMaterialID(charMaterialID);
const auto animator = CharObj->AddComponent<Animator>(); scene.Add(cube);
animator->setSkeleton(charModel.skeleton); scene.GetPhysics().RegisterGameObject(*cube);
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<SkeletalAnimation>(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);
} }
} }
@@ -408,6 +440,11 @@ void LightKeeper::customCleanup() {
meshCache.cleanup(gfxDevice); meshCache.cleanup(gfxDevice);
} }
void LightKeeper::customFixedUpdate(float dt)
{
SceneManager::GetInstance().FixedUpdate(dt);
}
void LightKeeper::onWindowResize(int newWidth, int newHeight) { void LightKeeper::onWindowResize(int newWidth, int newHeight) {
renderer.resize(gfxDevice, glm::ivec2{newWidth, newHeight}); renderer.resize(gfxDevice, glm::ivec2{newWidth, newHeight});
const float aspectRatio = static_cast<float>(newWidth) / static_cast<float>(newHeight); const float aspectRatio = static_cast<float>(newWidth) / static_cast<float>(newHeight);