Compare commits
6
Commits
star-catcher
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c96b2282fb | ||
|
|
e8a56405df | ||
|
|
c9fb6e991a | ||
|
|
d515a36403 | ||
|
|
287e5c6885 | ||
|
|
7f65a151b5 |
@@ -44,6 +44,7 @@
|
||||
[submodule "destrum/third_party/imgui"]
|
||||
path = destrum/third_party/imgui
|
||||
url = https://github.com/ocornut/imgui.git
|
||||
branch = docking
|
||||
[submodule "destrum/third_party/jolt"]
|
||||
path = destrum/third_party/jolt
|
||||
url = https://github.com/jrouwe/JoltPhysics.git
|
||||
|
||||
+1
-1
Submodule TheChef updated: 14f7dd9423...38230f5092
@@ -37,12 +37,16 @@ set(SRC_FILES
|
||||
"src/Graphics/Managers/MemoryManager.cpp"
|
||||
"src/Graphics/Managers/FrameManager.cpp"
|
||||
"src/Graphics/Managers/ImageManager.cpp"
|
||||
"src/Graphics/Managers/LineRenderingManager.cpp"
|
||||
"src/Graphics/Managers/QuadRenderingManager.cpp"
|
||||
|
||||
"src/Graphics/Resources/GPUImage.cpp"
|
||||
"src/Graphics/Resources/NBuffer.cpp"
|
||||
"src/Graphics/Resources/Cubemap.cpp"
|
||||
|
||||
"src/Graphics/Pipelines/MeshPipeline.cpp"
|
||||
"src/Graphics/Pipelines/LineRenderingPass.cpp"
|
||||
"src/Graphics/Pipelines/QuadRendererPass.cpp"
|
||||
"src/Graphics/Pipelines/SkyboxPipeline.cpp"
|
||||
"src/Graphics/Pipelines/SkinningPipeline.cpp"
|
||||
"src/Graphics/Pipelines/ImguiPass.cpp"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#version 460
|
||||
|
||||
layout (location = 0) in vec4 inColor;
|
||||
layout (location = 0) out vec4 outFragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
outFragColor = inColor;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#version 460
|
||||
|
||||
layout (location = 0) in vec4 inPosition;
|
||||
layout (location = 1) in vec4 inColor;
|
||||
|
||||
layout (location = 0) out vec4 outColor;
|
||||
|
||||
layout (push_constant) uniform LinePushConstants {
|
||||
mat4 viewProjection;
|
||||
} pcs;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = pcs.viewProjection * inPosition;
|
||||
outColor = inColor;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#version 460
|
||||
#extension GL_GOOGLE_include_directive : require
|
||||
#extension GL_EXT_nonuniform_qualifier : enable
|
||||
|
||||
#include "bindless.glsl"
|
||||
|
||||
layout(location = 0) in vec2 inUV;
|
||||
layout(location = 1) in vec4 inColor;
|
||||
layout(location = 2) flat in uint inTexId;
|
||||
|
||||
layout(location = 0) out vec4 outFragColor;
|
||||
|
||||
void main() {
|
||||
vec4 texColor = sampleTexture2DLinear(inTexId, inUV);
|
||||
outFragColor = texColor * inColor;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#version 460
|
||||
#extension GL_GOOGLE_include_directive : require
|
||||
|
||||
layout(location = 0) in vec2 inPosition;
|
||||
layout(location = 1) in vec2 inUV;
|
||||
layout(location = 2) in vec4 inColor;
|
||||
layout(location = 3) in uint inTexId;
|
||||
|
||||
layout(location = 0) out vec2 outUV;
|
||||
layout(location = 1) out vec4 outColor;
|
||||
layout(location = 2) flat out uint outTexId;
|
||||
|
||||
layout(push_constant) uniform QuadPushConstants {
|
||||
mat4 viewProjection;
|
||||
} pcs;
|
||||
|
||||
void main() {
|
||||
gl_Position = pcs.viewProjection * vec4(inPosition, 0.0, 1.0);
|
||||
outUV = inUV;
|
||||
outColor = inColor;
|
||||
outTexId = inTexId;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef DESTRUM_LINERENDERINGMANAGER_H
|
||||
#define DESTRUM_LINERENDERINGMANAGER_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
#include <destrum/Singleton.h>
|
||||
|
||||
struct Line {
|
||||
glm::vec3 start{};
|
||||
glm::vec3 end{};
|
||||
glm::vec4 color{1.0f};
|
||||
};
|
||||
|
||||
class LineRenderingManager final : public Singleton<LineRenderingManager> {
|
||||
public:
|
||||
friend class Singleton<LineRenderingManager>;
|
||||
|
||||
void SubmitLine(const Line& line);
|
||||
void SubmitLine(
|
||||
const glm::vec3& start,
|
||||
const glm::vec3& end,
|
||||
const glm::vec4& color = glm::vec4{1.0f});
|
||||
|
||||
[[nodiscard]] const std::vector<Line>& GetLines() const { return lines; }
|
||||
|
||||
void ClearLines();
|
||||
|
||||
private:
|
||||
LineRenderingManager() = default;
|
||||
|
||||
std::vector<Line> lines;
|
||||
};
|
||||
|
||||
#endif // DESTRUM_LINERENDERINGMANAGER_H
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef DESTRUM_QUADRENDERINGMANAGER_H
|
||||
#define DESTRUM_QUADRENDERINGMANAGER_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <destrum/Graphics/ids.h>
|
||||
#include <destrum/Singleton.h>
|
||||
|
||||
struct QuadSubmission {
|
||||
glm::vec2 position;
|
||||
glm::vec2 size;
|
||||
float rotation;
|
||||
glm::vec4 color;
|
||||
ImageID textureId;
|
||||
};
|
||||
|
||||
class QuadRenderingManager final : public Singleton<QuadRenderingManager> {
|
||||
public:
|
||||
friend class Singleton<QuadRenderingManager>;
|
||||
|
||||
void SubmitQuad(
|
||||
const glm::vec2& position,
|
||||
const glm::vec2& size,
|
||||
const glm::vec4& color = glm::vec4{1.0f},
|
||||
ImageID textureId = NULL_IMAGE_ID);
|
||||
|
||||
void SubmitQuad(
|
||||
const glm::vec2& position,
|
||||
const glm::vec2& size,
|
||||
float rotation,
|
||||
const glm::vec4& color = glm::vec4{1.0f},
|
||||
ImageID textureId = NULL_IMAGE_ID);
|
||||
|
||||
[[nodiscard]] const std::vector<QuadSubmission>& GetQuads() const { return quads; }
|
||||
void ClearQuads();
|
||||
|
||||
private:
|
||||
QuadRenderingManager() = default;
|
||||
|
||||
std::vector<QuadSubmission> quads;
|
||||
};
|
||||
|
||||
#endif // DESTRUM_QUADRENDERINGMANAGER_H
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef DESTRUM_LINERENDERINGPASS_H
|
||||
#define DESTRUM_LINERENDERINGPASS_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
||||
#include <destrum/Graphics/Resources/NBuffer.h>
|
||||
|
||||
class Camera;
|
||||
class GfxDevice;
|
||||
struct GPUImage;
|
||||
class Pipeline;
|
||||
|
||||
class LineRenderingPass final {
|
||||
public:
|
||||
void init(GfxDevice& gfxDevice, VkFormat drawImageFormat);
|
||||
void draw(
|
||||
VkCommandBuffer cmd,
|
||||
GfxDevice& gfxDevice,
|
||||
const GPUImage& target,
|
||||
const Camera& camera,
|
||||
LineRenderingManager& lineManager);
|
||||
void cleanup(GfxDevice& gfxDevice);
|
||||
|
||||
private:
|
||||
struct LineVertex {
|
||||
glm::vec4 position;
|
||||
glm::vec4 color;
|
||||
};
|
||||
|
||||
void ensureVertexCapacity(GfxDevice& gfxDevice, std::size_t requiredVertices);
|
||||
|
||||
VkPipelineLayout pipelineLayout{VK_NULL_HANDLE};
|
||||
std::unique_ptr<Pipeline> pipeline;
|
||||
NBuffer vertexBuffer;
|
||||
std::size_t vertexCapacity{0};
|
||||
};
|
||||
|
||||
#endif // DESTRUM_LINERENDERINGPASS_H
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef DESTRUM_QUADRENDERERPASS_H
|
||||
#define DESTRUM_QUADRENDERERPASS_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include <destrum/Graphics/Resources/Buffer.h>
|
||||
|
||||
|
||||
class GfxDevice;
|
||||
class Pipeline;
|
||||
struct GPUImage;
|
||||
class RenderResources;
|
||||
|
||||
class QuadRendererPass final {
|
||||
public:
|
||||
void init(GfxDevice& gfxDevice, RenderResources& resources, VkFormat drawImageFormat);
|
||||
void draw(
|
||||
VkCommandBuffer cmd,
|
||||
GfxDevice& gfxDevice,
|
||||
RenderResources& resources,
|
||||
const GPUImage& target,
|
||||
const glm::mat4& projection);
|
||||
void cleanup(GfxDevice& gfxDevice);
|
||||
|
||||
private:
|
||||
struct QuadVertex {
|
||||
glm::vec2 position;
|
||||
glm::vec2 uv;
|
||||
glm::vec4 color;
|
||||
std::uint32_t textureId;
|
||||
};
|
||||
|
||||
void ensureCapacity(GfxDevice& gfxDevice, std::size_t requiredQuads);
|
||||
|
||||
VkPipelineLayout pipelineLayout{VK_NULL_HANDLE};
|
||||
std::unique_ptr<Pipeline> pipeline;
|
||||
GPUBuffer vertexBuffer{};
|
||||
GPUBuffer indexBuffer{};
|
||||
std::size_t quadCapacity{0};
|
||||
};
|
||||
|
||||
#endif // DESTRUM_QUADRENDERERPASS_H
|
||||
@@ -1,10 +1,13 @@
|
||||
#ifndef RENDERER_H
|
||||
#define RENDERER_H
|
||||
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <destrum/Graphics/Camera.h>
|
||||
#include <destrum/Graphics/Pipelines/LineRenderingPass.h>
|
||||
#include <destrum/Graphics/Pipelines/MeshPipeline.h>
|
||||
#include <destrum/Graphics/Pipelines/QuadRendererPass.h>
|
||||
#include <destrum/Graphics/ids.h>
|
||||
#include <destrum/Graphics/MeshDrawCommand.h>
|
||||
#include <destrum/Graphics/Resources/NBuffer.h>
|
||||
@@ -68,6 +71,11 @@ public:
|
||||
return resources;
|
||||
}
|
||||
|
||||
void drawQuads(
|
||||
VkCommandBuffer cmd,
|
||||
GfxDevice& gfxDevice,
|
||||
const glm::mat4& projection);
|
||||
|
||||
private:
|
||||
void createDrawImage(GfxDevice& gfxDevice, const glm::ivec2& drawImageSize, bool firstCreate);
|
||||
|
||||
@@ -111,6 +119,8 @@ private:
|
||||
|
||||
std::unique_ptr<MeshPipeline> meshPipeline;
|
||||
std::unique_ptr<SkyboxPipeline> skyboxPipeline;
|
||||
std::unique_ptr<LineRenderingPass> lineRenderingPass;
|
||||
std::unique_ptr<QuadRendererPass> quadRendererPass;
|
||||
|
||||
std::unique_ptr<SkinningPipeline> skinningPipeline;
|
||||
bool initialized{false};
|
||||
|
||||
@@ -30,8 +30,8 @@ public:
|
||||
|
||||
int MouseX() const { return m_mouseX; }
|
||||
int MouseY() const { return m_mouseY; }
|
||||
int MouseDeltaX() const { return m_mouseDX; }
|
||||
int MouseDeltaY() const { return m_mouseDY; }
|
||||
int MouseDeltaX() const { return m_mouseRelDX; }
|
||||
int MouseDeltaY() const { return m_mouseRelDY; }
|
||||
|
||||
int WheelX() const { return m_wheelX; }
|
||||
int WheelY() const { return m_wheelY; }
|
||||
@@ -81,6 +81,9 @@ public:
|
||||
|
||||
void SetAxisDeadzone(int deadzone) { m_axisDeadzone = deadzone; } // 0..32767
|
||||
|
||||
void SetMouseCaptured(bool captured);
|
||||
[[nodiscard]] bool IsMouseCaptured() const { return m_MouseCaptured; }
|
||||
|
||||
private:
|
||||
// Key states
|
||||
std::unordered_set<SDL_Scancode> m_keysDown;
|
||||
@@ -92,10 +95,9 @@ private:
|
||||
std::unordered_set<Uint8> m_mousePressed;
|
||||
std::unordered_set<Uint8> m_mouseReleased;
|
||||
|
||||
// Mouse position + delta
|
||||
// Mouse position + relative delta (accumulated from SDL xrel/yrel)
|
||||
int m_mouseX = 0, m_mouseY = 0;
|
||||
int m_prevMouseX = 0, m_prevMouseY = 0;
|
||||
int m_mouseDX = 0, m_mouseDY = 0;
|
||||
int m_mouseRelDX = 0, m_mouseRelDY = 0;
|
||||
|
||||
int m_wheelX = 0, m_wheelY = 0;
|
||||
|
||||
@@ -125,6 +127,8 @@ private:
|
||||
|
||||
int m_axisDeadzone = 8000; // typical deadzone
|
||||
|
||||
bool m_MouseCaptured = false;
|
||||
|
||||
private:
|
||||
bool QueryBinding(const Binding& b, ButtonState state) const;
|
||||
|
||||
|
||||
@@ -59,6 +59,16 @@ public:
|
||||
virtual void ResolveReferences(const ObjectMap&) {
|
||||
}
|
||||
|
||||
// Trigger callbacks called when this component's owner overlaps a sensor.
|
||||
// Only called when the owner has a Collider set as a trigger (or overlaps one).
|
||||
virtual void OnTriggerEnter(GameObject* other) {
|
||||
(void)other;
|
||||
}
|
||||
|
||||
virtual void OnTriggerExit(GameObject* other) {
|
||||
(void)other;
|
||||
}
|
||||
|
||||
bool HasStarted{false};
|
||||
|
||||
protected:
|
||||
|
||||
@@ -55,6 +55,8 @@ public:
|
||||
float maxDistance,
|
||||
PhysicsRaycastHit& hit) const override;
|
||||
|
||||
std::vector<TriggerEvent> ConsumeTriggerEvents() override;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> m_Impl;
|
||||
|
||||
@@ -21,6 +21,7 @@ class PhysicsSceneBridge final {
|
||||
public:
|
||||
explicit PhysicsSceneBridge(std::unique_ptr<PhysicsWorld> world);
|
||||
|
||||
[[nodiscard]] bool IsValid() const { return m_World != nullptr; }
|
||||
[[nodiscard]] PhysicsWorld& GetWorld() { return *m_World; }
|
||||
[[nodiscard]] const PhysicsWorld& GetWorld() const { return *m_World; }
|
||||
|
||||
|
||||
@@ -116,3 +116,9 @@ struct PhysicsBodyDesc {
|
||||
bool useGravity{true};
|
||||
bool allowSleep{true};
|
||||
};
|
||||
|
||||
struct TriggerEvent {
|
||||
GameObject* owner{nullptr};
|
||||
GameObject* other{nullptr};
|
||||
bool entered{true};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <unordered_set>
|
||||
@@ -49,6 +51,9 @@ public:
|
||||
float maxDistance,
|
||||
PhysicsRaycastHit& hit) const = 0;
|
||||
|
||||
// Trigger events accumulated during the last Step().
|
||||
virtual std::vector<TriggerEvent> ConsumeTriggerEvents() { return {}; }
|
||||
|
||||
private:
|
||||
std::unordered_set<Rigidbody*> m_RegisteredRigidbodies;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <destrum/Event.h>
|
||||
#include <destrum/ObjectModel/ObjectId.h>
|
||||
#include <destrum/Scene/SceneManager.h>
|
||||
@@ -12,6 +14,7 @@
|
||||
|
||||
class GameObject;
|
||||
class SceneSerializer;
|
||||
class PhysicsWorld;
|
||||
|
||||
class Scene final {
|
||||
friend Scene& SceneManager::CreateScene(const std::string& name);
|
||||
@@ -104,6 +107,7 @@ public:
|
||||
PhysicsSceneBridge& GetPhysics() { return m_Physics; }
|
||||
private:
|
||||
explicit Scene(const std::string& name);
|
||||
explicit Scene(const std::string& name, std::unique_ptr<PhysicsWorld> physicsWorld);
|
||||
|
||||
PhysicsSceneBridge m_Physics{std::make_unique<JoltPhysicsWorld>()};
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ public:
|
||||
Scene& CreateScene(const std::string& name);
|
||||
|
||||
Scene& GetCurrentScene() const;
|
||||
const std::vector<std::shared_ptr<Scene>>& GetActiveScenes() const { return m_activeScenes; }
|
||||
|
||||
void Update(float dt);
|
||||
void FixedUpdate(float dt);
|
||||
@@ -34,7 +35,9 @@ public:
|
||||
void Destroy();
|
||||
|
||||
void SwitchScene(int index);
|
||||
int GetActiveSceneId() const { return m_scenes.empty() ? -1 : m_ActiveSceneIndex; }
|
||||
void AddActiveScene(int index);
|
||||
void RemoveActiveScene(int index);
|
||||
int GetActiveSceneId() const;
|
||||
|
||||
[[nodiscard]] const std::vector<std::shared_ptr<Scene>>& GetScenes() const { return m_scenes; }
|
||||
|
||||
@@ -47,8 +50,7 @@ private:
|
||||
|
||||
SceneManager() = default;
|
||||
|
||||
int m_ActiveSceneIndex{0};
|
||||
|
||||
std::vector<std::shared_ptr<Scene>> m_activeScenes;
|
||||
std::vector<std::shared_ptr<Scene>> m_scenes;
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,6 @@
|
||||
X(Rigidbody) \
|
||||
X(BoxCollider) \
|
||||
X(SphereCollider) \
|
||||
X(CapsuleCollider)
|
||||
X(CapsuleCollider) \
|
||||
|
||||
#endif // DESTRUM_ENGINECOMPONENTLIST_H
|
||||
|
||||
@@ -2,13 +2,37 @@
|
||||
#define DESTRUM_SCENESERIALIZER_H
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
class Scene;
|
||||
|
||||
class SceneSerializer final {
|
||||
public:
|
||||
struct LoadResult {
|
||||
bool success{false};
|
||||
std::string errorMessage;
|
||||
nlohmann::json root;
|
||||
};
|
||||
|
||||
static bool Save(Scene& scene, const std::filesystem::path& path);
|
||||
static bool Load(Scene& scene, const std::filesystem::path& path);
|
||||
|
||||
static bool Load(Scene& scene, const std::filesystem::path& path,
|
||||
std::function<void(float)> progressCallback = nullptr);
|
||||
|
||||
// Thread-safe: parse and validate a scene file on any thread.
|
||||
// No engine state is touched during parsing.
|
||||
static LoadResult LoadSceneFile(const std::filesystem::path& path);
|
||||
|
||||
// Main-thread-only: construct a scene from pre-validated JSON.
|
||||
static bool ConstructScene(Scene& scene, LoadResult& result,
|
||||
std::function<void(float)> progressCallback = nullptr);
|
||||
|
||||
// Parse the scene file on a background thread. Call LoadFromJson on the
|
||||
// main thread once the future is ready.
|
||||
static std::future<LoadResult> LoadSceneFileAsync(const std::filesystem::path& path);
|
||||
};
|
||||
|
||||
#endif //DESTRUM_SCENESERIALIZER_H
|
||||
|
||||
+8
-8
@@ -131,6 +131,12 @@ void App::run()
|
||||
|
||||
imguiPass.handleEvent(event);
|
||||
|
||||
const bool mouseEvent =
|
||||
event.type == SDL_MOUSEBUTTONDOWN ||
|
||||
event.type == SDL_MOUSEBUTTONUP ||
|
||||
event.type == SDL_MOUSEMOTION ||
|
||||
event.type == SDL_MOUSEWHEEL;
|
||||
|
||||
if (event.type == SDL_QUIT)
|
||||
{
|
||||
isRunning = false;
|
||||
@@ -149,19 +155,13 @@ void App::run()
|
||||
}
|
||||
}
|
||||
|
||||
const bool mouseEvent =
|
||||
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;
|
||||
|
||||
const bool capturedByImgui =
|
||||
(mouseEvent && imguiPass.wantsMouse()) ||
|
||||
(mouseEvent && !InputManager::GetInstance().IsMouseCaptured() && imguiPass.wantsMouse()) ||
|
||||
(keyboardEvent && imguiPass.wantsKeyboard());
|
||||
|
||||
if (!capturedByImgui)
|
||||
@@ -176,7 +176,7 @@ void App::run()
|
||||
}
|
||||
}
|
||||
|
||||
if (!isRunning) break;
|
||||
if (!isRunning) break;
|
||||
|
||||
// Consume SDL events before updating the base camera so held and
|
||||
// newly pressed inputs are applied in the same frame.
|
||||
|
||||
@@ -114,7 +114,7 @@ void Camera::SetRotation(const glm::vec2& yawPitchRadians) {
|
||||
SetRotation(yawPitchRadians.x, yawPitchRadians.y);
|
||||
}
|
||||
|
||||
void Camera::SetTarget(const glm::vec3& target) {
|
||||
void Camera::SetTarget(const glm::vec3&) {
|
||||
}
|
||||
|
||||
void Camera::ClearTarget() {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
||||
|
||||
void LineRenderingManager::SubmitLine(const Line& line)
|
||||
{
|
||||
lines.push_back(line);
|
||||
}
|
||||
|
||||
void LineRenderingManager::SubmitLine(
|
||||
const glm::vec3& start,
|
||||
const glm::vec3& end,
|
||||
const glm::vec4& color)
|
||||
{
|
||||
SubmitLine(Line{
|
||||
.start = start,
|
||||
.end = end,
|
||||
.color = color,
|
||||
});
|
||||
}
|
||||
|
||||
void LineRenderingManager::ClearLines()
|
||||
{
|
||||
lines.clear();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
|
||||
|
||||
void QuadRenderingManager::SubmitQuad(
|
||||
const glm::vec2& position,
|
||||
const glm::vec2& size,
|
||||
const glm::vec4& color,
|
||||
ImageID textureId)
|
||||
{
|
||||
SubmitQuad(position, size, 0.0f, color, textureId);
|
||||
}
|
||||
|
||||
void QuadRenderingManager::SubmitQuad(
|
||||
const glm::vec2& position,
|
||||
const glm::vec2& size,
|
||||
float rotation,
|
||||
const glm::vec4& color,
|
||||
ImageID textureId)
|
||||
{
|
||||
quads.push_back(QuadSubmission{
|
||||
.position = position,
|
||||
.size = size,
|
||||
.rotation = rotation,
|
||||
.color = color,
|
||||
.textureId = textureId,
|
||||
});
|
||||
}
|
||||
|
||||
void QuadRenderingManager::ClearQuads()
|
||||
{
|
||||
quads.clear();
|
||||
}
|
||||
@@ -124,6 +124,11 @@ void ImguiPass::beginFrame() {
|
||||
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
|
||||
if (SDL_GetRelativeMouseMode() == SDL_TRUE) {
|
||||
ImGui::GetIO().MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
|
||||
}
|
||||
|
||||
ImGui::NewFrame();
|
||||
|
||||
frameBegun = true;
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
#include <destrum/Graphics/Pipelines/LineRenderingPass.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
#include <destrum/Graphics/Camera.h>
|
||||
#include <destrum/Graphics/GPUImage.h>
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/Pipeline.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include "volk.h"
|
||||
|
||||
namespace {
|
||||
constexpr std::size_t InitialVertexCapacity = 256;
|
||||
}
|
||||
|
||||
void LineRenderingPass::init(GfxDevice& gfxDevice, VkFormat drawImageFormat)
|
||||
{
|
||||
try {
|
||||
vertexBuffer.init(
|
||||
gfxDevice,
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
InitialVertexCapacity * sizeof(LineVertex),
|
||||
"line vertices");
|
||||
vertexCapacity = InitialVertexCapacity;
|
||||
|
||||
const auto vertexShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/line.vert");
|
||||
const auto fragmentShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/line.frag");
|
||||
|
||||
const auto pushConstantRange = VkPushConstantRange{
|
||||
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
|
||||
.offset = 0,
|
||||
.size = sizeof(glm::mat4),
|
||||
};
|
||||
|
||||
const auto pipelineLayoutInfo = VkPipelineLayoutCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||
.pushConstantRangeCount = 1,
|
||||
.pPushConstantRanges = &pushConstantRange,
|
||||
};
|
||||
|
||||
VK_CHECK(vkCreatePipelineLayout(
|
||||
gfxDevice.getDevice(),
|
||||
&pipelineLayoutInfo,
|
||||
nullptr,
|
||||
&pipelineLayout));
|
||||
|
||||
PipelineConfigInfo pipelineConfig{};
|
||||
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
|
||||
pipelineConfig.name = "Line Rendering Pipeline";
|
||||
pipelineConfig.pipelineLayout = pipelineLayout;
|
||||
pipelineConfig.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||
pipelineConfig.rasterizationInfo.cullMode = VK_CULL_MODE_NONE;
|
||||
pipelineConfig.depthStencilInfo.depthTestEnable = VK_FALSE;
|
||||
pipelineConfig.depthStencilInfo.depthWriteEnable = VK_FALSE;
|
||||
pipelineConfig.depthStencilInfo.depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||
pipelineConfig.colorAttachments = {drawImageFormat};
|
||||
pipelineConfig.depthAttachment = VK_FORMAT_UNDEFINED;
|
||||
|
||||
pipelineConfig.vertexBindingDescriptions = {
|
||||
VkVertexInputBindingDescription{
|
||||
.binding = 0,
|
||||
.stride = sizeof(LineVertex),
|
||||
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
|
||||
}
|
||||
};
|
||||
pipelineConfig.vertexAttributeDescriptions = {
|
||||
VkVertexInputAttributeDescription{
|
||||
.location = 0,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32B32A32_SFLOAT,
|
||||
.offset = offsetof(LineVertex, position),
|
||||
},
|
||||
VkVertexInputAttributeDescription{
|
||||
.location = 1,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32B32A32_SFLOAT,
|
||||
.offset = offsetof(LineVertex, color),
|
||||
},
|
||||
};
|
||||
|
||||
pipelineConfig.colorBlendAttachment.blendEnable = VK_TRUE;
|
||||
pipelineConfig.colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
pipelineConfig.colorBlendAttachment.dstColorBlendFactor =
|
||||
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
pipelineConfig.colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
pipelineConfig.colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
pipelineConfig.colorBlendAttachment.dstAlphaBlendFactor =
|
||||
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
pipelineConfig.colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
|
||||
pipeline = std::make_unique<Pipeline>(
|
||||
gfxDevice,
|
||||
vertexShader.string(),
|
||||
fragmentShader.string(),
|
||||
pipelineConfig);
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void LineRenderingPass::draw(
|
||||
VkCommandBuffer cmd,
|
||||
GfxDevice& gfxDevice,
|
||||
const GPUImage& target,
|
||||
const Camera& camera,
|
||||
LineRenderingManager& lineManager)
|
||||
{
|
||||
const auto& lines = lineManager.GetLines();
|
||||
if (!pipeline || lines.empty()) {
|
||||
lineManager.ClearLines();
|
||||
return;
|
||||
}
|
||||
|
||||
if (lines.size() > std::numeric_limits<std::size_t>::max() / 2) {
|
||||
throw std::overflow_error("Too many lines submitted for rendering");
|
||||
}
|
||||
|
||||
std::vector<LineVertex> vertices;
|
||||
vertices.reserve(lines.size() * 2);
|
||||
for (const Line& line : lines) {
|
||||
vertices.push_back(LineVertex{
|
||||
.position = glm::vec4{line.start, 1.0f},
|
||||
.color = line.color,
|
||||
});
|
||||
vertices.push_back(LineVertex{
|
||||
.position = glm::vec4{line.end, 1.0f},
|
||||
.color = line.color,
|
||||
});
|
||||
}
|
||||
|
||||
if (vertices.size() > std::numeric_limits<std::uint32_t>::max()) {
|
||||
throw std::overflow_error("Too many line vertices submitted for rendering");
|
||||
}
|
||||
|
||||
ensureVertexCapacity(gfxDevice, vertices.size());
|
||||
vertexBuffer.uploadNewData(
|
||||
cmd,
|
||||
gfxDevice.getCurrentFrameIndex(),
|
||||
vertices.data(),
|
||||
vertices.size() * sizeof(LineVertex));
|
||||
|
||||
auto renderInfo = vkutil::createRenderingInfo({
|
||||
.renderExtent = target.getExtent2D(),
|
||||
.colorImageView = target.imageView,
|
||||
});
|
||||
|
||||
vkCmdBeginRendering(cmd, &renderInfo.renderingInfo);
|
||||
|
||||
pipeline->bind(cmd);
|
||||
|
||||
const auto viewport = VkViewport{
|
||||
.x = 0.0f,
|
||||
.y = 0.0f,
|
||||
.width = static_cast<float>(target.extent.width),
|
||||
.height = static_cast<float>(target.extent.height),
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 1.0f,
|
||||
};
|
||||
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
||||
|
||||
const auto scissor = VkRect2D{
|
||||
.offset = {},
|
||||
.extent = target.getExtent2D(),
|
||||
};
|
||||
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||
vkCmdSetPolygonModeEXT(cmd, VK_POLYGON_MODE_FILL);
|
||||
|
||||
const glm::mat4 viewProjection = camera.GetViewProjectionMatrix();
|
||||
vkCmdPushConstants(
|
||||
cmd,
|
||||
pipelineLayout,
|
||||
VK_SHADER_STAGE_VERTEX_BIT,
|
||||
0,
|
||||
sizeof(viewProjection),
|
||||
&viewProjection);
|
||||
|
||||
const VkBuffer vertexBufferHandle = vertexBuffer.getBuffer().buffer;
|
||||
const VkDeviceSize vertexBufferOffset = 0;
|
||||
vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBufferHandle, &vertexBufferOffset);
|
||||
vkCmdDraw(cmd, static_cast<std::uint32_t>(vertices.size()), 1, 0, 0);
|
||||
|
||||
vkCmdEndRendering(cmd);
|
||||
lineManager.ClearLines();
|
||||
}
|
||||
|
||||
void LineRenderingPass::cleanup(GfxDevice& gfxDevice)
|
||||
{
|
||||
pipeline.reset();
|
||||
|
||||
if (pipelineLayout != VK_NULL_HANDLE && gfxDevice.getDevice() != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(gfxDevice.getDevice(), pipelineLayout, nullptr);
|
||||
}
|
||||
pipelineLayout = VK_NULL_HANDLE;
|
||||
|
||||
vertexBuffer.cleanup(gfxDevice);
|
||||
vertexCapacity = 0;
|
||||
}
|
||||
|
||||
void LineRenderingPass::ensureVertexCapacity(
|
||||
GfxDevice& gfxDevice,
|
||||
std::size_t requiredVertices)
|
||||
{
|
||||
if (requiredVertices <= vertexCapacity) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t newCapacity = std::max(vertexCapacity, InitialVertexCapacity);
|
||||
while (newCapacity < requiredVertices) {
|
||||
if (newCapacity > std::numeric_limits<std::size_t>::max() / 2) {
|
||||
newCapacity = requiredVertices;
|
||||
break;
|
||||
}
|
||||
newCapacity *= 2;
|
||||
}
|
||||
|
||||
gfxDevice.waitIdle();
|
||||
vertexBuffer.cleanup(gfxDevice);
|
||||
vertexBuffer.init(
|
||||
gfxDevice,
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
newCapacity * sizeof(LineVertex),
|
||||
"line vertices");
|
||||
vertexCapacity = newCapacity;
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
#include <destrum/Graphics/Pipelines/QuadRendererPass.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/GPUImage.h>
|
||||
#include <destrum/Graphics/Pipeline.h>
|
||||
#include <destrum/Graphics/RenderResources.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
|
||||
|
||||
#include "volk.h"
|
||||
|
||||
namespace {
|
||||
constexpr std::size_t InitialQuadCapacity = 256;
|
||||
constexpr std::size_t VerticesPerQuad = 4;
|
||||
constexpr std::size_t IndicesPerQuad = 6;
|
||||
|
||||
glm::vec2 rotatePoint(const glm::vec2& point, float cosA, float sinA) {
|
||||
return glm::vec2{
|
||||
point.x * cosA - point.y * sinA,
|
||||
point.x * sinA + point.y * cosA,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void QuadRendererPass::init(GfxDevice& gfxDevice, RenderResources& resources, VkFormat drawImageFormat)
|
||||
{
|
||||
try {
|
||||
const std::size_t vertexBytes = InitialQuadCapacity * VerticesPerQuad * sizeof(QuadVertex);
|
||||
vertexBuffer = gfxDevice.createBuffer(
|
||||
vertexBytes,
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
|
||||
|
||||
const std::size_t indexBytes = InitialQuadCapacity * IndicesPerQuad * sizeof(std::uint32_t);
|
||||
indexBuffer = gfxDevice.createBuffer(
|
||||
indexBytes,
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
|
||||
|
||||
quadCapacity = InitialQuadCapacity;
|
||||
|
||||
const auto vertexShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/quad.vert");
|
||||
const auto fragmentShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/quad.frag");
|
||||
|
||||
const auto pushConstantRange = VkPushConstantRange{
|
||||
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
|
||||
.offset = 0,
|
||||
.size = sizeof(glm::mat4),
|
||||
};
|
||||
|
||||
const auto layouts = std::array{
|
||||
resources.getBindlessDescSetLayout()
|
||||
};
|
||||
|
||||
const auto pipelineLayoutInfo = VkPipelineLayoutCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||
.setLayoutCount = static_cast<std::uint32_t>(layouts.size()),
|
||||
.pSetLayouts = layouts.data(),
|
||||
.pushConstantRangeCount = 1,
|
||||
.pPushConstantRanges = &pushConstantRange,
|
||||
};
|
||||
|
||||
VK_CHECK(vkCreatePipelineLayout(
|
||||
gfxDevice.getDevice(),
|
||||
&pipelineLayoutInfo,
|
||||
nullptr,
|
||||
&pipelineLayout));
|
||||
|
||||
PipelineConfigInfo pipelineConfig{};
|
||||
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
|
||||
pipelineConfig.name = "Quad Rendering Pipeline";
|
||||
pipelineConfig.pipelineLayout = pipelineLayout;
|
||||
pipelineConfig.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
pipelineConfig.rasterizationInfo.cullMode = VK_CULL_MODE_NONE;
|
||||
pipelineConfig.depthStencilInfo.depthTestEnable = VK_FALSE;
|
||||
pipelineConfig.depthStencilInfo.depthWriteEnable = VK_FALSE;
|
||||
pipelineConfig.depthStencilInfo.depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||
pipelineConfig.colorAttachments = {drawImageFormat};
|
||||
pipelineConfig.depthAttachment = VK_FORMAT_UNDEFINED;
|
||||
|
||||
pipelineConfig.vertexBindingDescriptions = {
|
||||
VkVertexInputBindingDescription{
|
||||
.binding = 0,
|
||||
.stride = sizeof(QuadVertex),
|
||||
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
|
||||
}
|
||||
};
|
||||
pipelineConfig.vertexAttributeDescriptions = {
|
||||
VkVertexInputAttributeDescription{
|
||||
.location = 0,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32_SFLOAT,
|
||||
.offset = offsetof(QuadVertex, position),
|
||||
},
|
||||
VkVertexInputAttributeDescription{
|
||||
.location = 1,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32_SFLOAT,
|
||||
.offset = offsetof(QuadVertex, uv),
|
||||
},
|
||||
VkVertexInputAttributeDescription{
|
||||
.location = 2,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32B32A32_SFLOAT,
|
||||
.offset = offsetof(QuadVertex, color),
|
||||
},
|
||||
VkVertexInputAttributeDescription{
|
||||
.location = 3,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32_UINT,
|
||||
.offset = offsetof(QuadVertex, textureId),
|
||||
},
|
||||
};
|
||||
|
||||
pipelineConfig.colorBlendAttachment.blendEnable = VK_TRUE;
|
||||
pipelineConfig.colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
pipelineConfig.colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
pipelineConfig.colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
pipelineConfig.colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
pipelineConfig.colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
pipelineConfig.colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
|
||||
pipeline = std::make_unique<Pipeline>(
|
||||
gfxDevice,
|
||||
vertexShader.string(),
|
||||
fragmentShader.string(),
|
||||
pipelineConfig);
|
||||
} catch (...) {
|
||||
cleanup(gfxDevice);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void QuadRendererPass::draw(
|
||||
VkCommandBuffer cmd,
|
||||
GfxDevice& gfxDevice,
|
||||
RenderResources& resources,
|
||||
const GPUImage& target,
|
||||
const glm::mat4& projection)
|
||||
{
|
||||
auto& quadManager = QuadRenderingManager::GetInstance();
|
||||
const auto& quads = quadManager.GetQuads();
|
||||
if (!pipeline || quads.empty()) {
|
||||
quadManager.ClearQuads();
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t numQuads = quads.size();
|
||||
if (numQuads > std::numeric_limits<std::size_t>::max() / VerticesPerQuad) {
|
||||
throw std::overflow_error("Too many quads submitted for rendering");
|
||||
}
|
||||
|
||||
const std::size_t numVertices = numQuads * VerticesPerQuad;
|
||||
const std::size_t numIndices = numQuads * IndicesPerQuad;
|
||||
|
||||
if (numVertices > std::numeric_limits<std::uint32_t>::max()) {
|
||||
throw std::overflow_error("Too many quad vertices submitted for rendering");
|
||||
}
|
||||
|
||||
ensureCapacity(gfxDevice, numQuads);
|
||||
|
||||
const ImageID whiteTextureId = resources.getWhiteTextureID();
|
||||
|
||||
auto* vertexMapped = reinterpret_cast<std::uint8_t*>(vertexBuffer.info.pMappedData);
|
||||
auto* indexMapped = reinterpret_cast<std::uint8_t*>(indexBuffer.info.pMappedData);
|
||||
|
||||
constexpr std::array<glm::vec2, 4> localCorners = {{
|
||||
glm::vec2{-0.5f, -0.5f},
|
||||
glm::vec2{ 0.5f, -0.5f},
|
||||
glm::vec2{ 0.5f, 0.5f},
|
||||
glm::vec2{-0.5f, 0.5f},
|
||||
}};
|
||||
|
||||
constexpr std::array<glm::vec2, 4> uvs = {{
|
||||
glm::vec2{0.0f, 1.0f},
|
||||
glm::vec2{1.0f, 1.0f},
|
||||
glm::vec2{1.0f, 0.0f},
|
||||
glm::vec2{0.0f, 0.0f},
|
||||
}};
|
||||
|
||||
auto* vertWrite = reinterpret_cast<QuadVertex*>(vertexMapped);
|
||||
auto* idxWrite = reinterpret_cast<std::uint32_t*>(indexMapped);
|
||||
std::uint32_t vertexBase = 0;
|
||||
|
||||
for (const auto& quad : quads) {
|
||||
const float cosA = std::cos(quad.rotation);
|
||||
const float sinA = std::sin(quad.rotation);
|
||||
const glm::vec2 halfSize = quad.size * 0.5f;
|
||||
const ImageID texId = (quad.textureId == NULL_IMAGE_ID) ? whiteTextureId : quad.textureId;
|
||||
|
||||
for (std::size_t v = 0; v < VerticesPerQuad; ++v) {
|
||||
glm::vec2 localPos = localCorners[v] * halfSize;
|
||||
glm::vec2 rotatedPos = rotatePoint(localPos, cosA, sinA);
|
||||
glm::vec2 worldPos = quad.position + rotatedPos;
|
||||
|
||||
vertWrite[vertexBase + v] = QuadVertex{
|
||||
.position = worldPos,
|
||||
.uv = uvs[v],
|
||||
.color = quad.color,
|
||||
.textureId = static_cast<std::uint32_t>(texId),
|
||||
};
|
||||
}
|
||||
|
||||
const std::uint32_t idxOffset = vertexBase / 4 * 6;
|
||||
idxWrite[idxOffset + 0] = vertexBase + 0;
|
||||
idxWrite[idxOffset + 1] = vertexBase + 1;
|
||||
idxWrite[idxOffset + 2] = vertexBase + 2;
|
||||
idxWrite[idxOffset + 3] = vertexBase + 0;
|
||||
idxWrite[idxOffset + 4] = vertexBase + 2;
|
||||
idxWrite[idxOffset + 5] = vertexBase + 3;
|
||||
vertexBase += VerticesPerQuad;
|
||||
}
|
||||
|
||||
auto& memMgr = gfxDevice.getMemoryManager();
|
||||
memMgr.flushAllocation(vertexBuffer, 0, numVertices * sizeof(QuadVertex));
|
||||
memMgr.flushAllocation(indexBuffer, 0, numIndices * sizeof(std::uint32_t));
|
||||
|
||||
auto renderInfo = vkutil::createRenderingInfo({
|
||||
.renderExtent = target.getExtent2D(),
|
||||
.colorImageView = target.imageView,
|
||||
});
|
||||
|
||||
vkCmdBeginRendering(cmd, &renderInfo.renderingInfo);
|
||||
|
||||
pipeline->bind(cmd);
|
||||
resources.bindBindlessDescSet(cmd, pipelineLayout);
|
||||
|
||||
const auto viewport = VkViewport{
|
||||
.x = 0.0f,
|
||||
.y = 0.0f,
|
||||
.width = static_cast<float>(target.extent.width),
|
||||
.height = static_cast<float>(target.extent.height),
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 1.0f,
|
||||
};
|
||||
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
||||
|
||||
const auto scissor = VkRect2D{
|
||||
.offset = {},
|
||||
.extent = target.getExtent2D(),
|
||||
};
|
||||
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||
vkCmdSetPolygonModeEXT(cmd, VK_POLYGON_MODE_FILL);
|
||||
|
||||
vkCmdPushConstants(
|
||||
cmd,
|
||||
pipelineLayout,
|
||||
VK_SHADER_STAGE_VERTEX_BIT,
|
||||
0,
|
||||
sizeof(glm::mat4),
|
||||
&projection);
|
||||
|
||||
const VkBuffer vertexBufferHandle = vertexBuffer.buffer;
|
||||
const VkDeviceSize vertexBufferOffset = 0;
|
||||
vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBufferHandle, &vertexBufferOffset);
|
||||
|
||||
const VkBuffer indexBufferHandle = indexBuffer.buffer;
|
||||
vkCmdBindIndexBuffer(cmd, indexBufferHandle, 0, VK_INDEX_TYPE_UINT32);
|
||||
|
||||
vkCmdDrawIndexed(cmd, static_cast<std::uint32_t>(numIndices), 1, 0, 0, 0);
|
||||
|
||||
vkCmdEndRendering(cmd);
|
||||
quadManager.ClearQuads();
|
||||
}
|
||||
|
||||
void QuadRendererPass::cleanup(GfxDevice& gfxDevice)
|
||||
{
|
||||
pipeline.reset();
|
||||
|
||||
if (pipelineLayout != VK_NULL_HANDLE && gfxDevice.getDevice() != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(gfxDevice.getDevice(), pipelineLayout, nullptr);
|
||||
}
|
||||
pipelineLayout = VK_NULL_HANDLE;
|
||||
|
||||
if (vertexBuffer.buffer != VK_NULL_HANDLE) {
|
||||
gfxDevice.destroyBuffer(vertexBuffer);
|
||||
}
|
||||
if (indexBuffer.buffer != VK_NULL_HANDLE) {
|
||||
gfxDevice.destroyBuffer(indexBuffer);
|
||||
}
|
||||
quadCapacity = 0;
|
||||
}
|
||||
|
||||
void QuadRendererPass::ensureCapacity(
|
||||
GfxDevice& gfxDevice,
|
||||
std::size_t requiredQuads)
|
||||
{
|
||||
if (requiredQuads <= quadCapacity) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t newCapacity = std::max(quadCapacity, InitialQuadCapacity);
|
||||
while (newCapacity < requiredQuads) {
|
||||
if (newCapacity > std::numeric_limits<std::size_t>::max() / 2) {
|
||||
newCapacity = requiredQuads;
|
||||
break;
|
||||
}
|
||||
newCapacity *= 2;
|
||||
}
|
||||
|
||||
gfxDevice.waitIdle();
|
||||
|
||||
if (vertexBuffer.buffer != VK_NULL_HANDLE) {
|
||||
gfxDevice.destroyBuffer(vertexBuffer);
|
||||
}
|
||||
vertexBuffer = gfxDevice.createBuffer(
|
||||
newCapacity * VerticesPerQuad * sizeof(QuadVertex),
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
|
||||
|
||||
if (indexBuffer.buffer != VK_NULL_HANDLE) {
|
||||
gfxDevice.destroyBuffer(indexBuffer);
|
||||
}
|
||||
indexBuffer = gfxDevice.createBuffer(
|
||||
newCapacity * IndicesPerQuad * sizeof(std::uint32_t),
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
|
||||
|
||||
quadCapacity = newCapacity;
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
#include <destrum/Graphics/Renderer.h>
|
||||
|
||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
||||
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "volk.h"
|
||||
#include "destrum/Util/GameState.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
@@ -30,12 +34,18 @@ void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::
|
||||
meshPipeline = std::make_unique<MeshPipeline>();
|
||||
meshPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
|
||||
skyboxPipeline = std::make_unique<SkyboxPipeline>();
|
||||
skyboxPipeline = std::make_unique<SkyboxPipeline>();
|
||||
skyboxPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
|
||||
skinningPipeline = std::make_unique<SkinningPipeline>();
|
||||
skinningPipeline->init(gfxDevice);
|
||||
|
||||
lineRenderingPass = std::make_unique<LineRenderingPass>();
|
||||
lineRenderingPass->init(gfxDevice, drawImageFormat);
|
||||
|
||||
quadRendererPass = std::make_unique<QuadRendererPass>();
|
||||
quadRendererPass->init(gfxDevice, _resources, drawImageFormat);
|
||||
|
||||
GameState::GetInstance().SetRenderer(this);
|
||||
initialized = true;
|
||||
} catch (...) {
|
||||
@@ -49,6 +59,7 @@ void GameRenderer::beginDrawing(GfxDevice& gfxDevice)
|
||||
flushMaterialUpdates(gfxDevice);
|
||||
meshDrawCommands.clear();
|
||||
skinningPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
||||
QuadRenderingManager::GetInstance().ClearQuads();
|
||||
}
|
||||
|
||||
void GameRenderer::endDrawing()
|
||||
@@ -194,7 +205,41 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
|
||||
vkCmdEndRendering(cmd);
|
||||
}
|
||||
// vkutil::cmdEndLabel(cmd);
|
||||
|
||||
{
|
||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "LineRenderingPass::draw");
|
||||
|
||||
lineRenderingPass->draw(
|
||||
cmd,
|
||||
gfxDevice,
|
||||
drawImage,
|
||||
camera,
|
||||
LineRenderingManager::GetInstance());
|
||||
}
|
||||
|
||||
{
|
||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "QuadRendererPass::draw");
|
||||
|
||||
quadRendererPass->draw(
|
||||
cmd,
|
||||
gfxDevice,
|
||||
*resources,
|
||||
drawImage,
|
||||
glm::ortho(0.0f,
|
||||
static_cast<float>(drawImage.extent.width),
|
||||
static_cast<float>(drawImage.extent.height),
|
||||
0.0f, -1.0f, 1.0f));
|
||||
}
|
||||
// vkutil::cmdEndLabel(cmd);
|
||||
}
|
||||
|
||||
void GameRenderer::drawQuads(
|
||||
VkCommandBuffer cmd,
|
||||
GfxDevice& gfxDevice,
|
||||
const glm::mat4& projection)
|
||||
{
|
||||
const auto& drawImage = resources->getImage(drawImageId);
|
||||
quadRendererPass->draw(cmd, gfxDevice, *resources, drawImage, projection);
|
||||
}
|
||||
|
||||
void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
@@ -205,9 +250,15 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
vkDeviceWaitIdle(device);
|
||||
}
|
||||
|
||||
if (skinningPipeline)
|
||||
if (skinningPipeline)
|
||||
skinningPipeline->cleanup(gfxDevice);
|
||||
|
||||
if (lineRenderingPass)
|
||||
lineRenderingPass->cleanup(gfxDevice);
|
||||
|
||||
if (quadRendererPass)
|
||||
quadRendererPass->cleanup(gfxDevice);
|
||||
|
||||
if (skyboxPipeline)
|
||||
skyboxPipeline->cleanup(device);
|
||||
|
||||
@@ -228,6 +279,8 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
meshPipeline.reset();
|
||||
skyboxPipeline.reset();
|
||||
skinningPipeline.reset();
|
||||
lineRenderingPass.reset();
|
||||
quadRendererPass.reset();
|
||||
pendingMaterialUploads.clear();
|
||||
meshDrawCommands.clear();
|
||||
sortedMeshDrawCommands.clear();
|
||||
|
||||
@@ -46,11 +46,11 @@ void InputManager::BeginFrame() {
|
||||
|
||||
m_textInput.clear();
|
||||
|
||||
// Update mouse delta based on last known position
|
||||
m_mouseDX = m_mouseX - m_prevMouseX;
|
||||
m_mouseDY = m_mouseY - m_prevMouseY;
|
||||
m_prevMouseX = m_mouseX;
|
||||
m_prevMouseY = m_mouseY;
|
||||
// Relative mouse accumulators are filled during SDL event processing
|
||||
// and consumed by game code each frame. Reset them here (before events
|
||||
// are processed) so this frame accumulates fresh deltas.
|
||||
m_mouseRelDX = 0;
|
||||
m_mouseRelDY = 0;
|
||||
|
||||
// Clear controller "edge" sets + snapshot axes
|
||||
for (auto& [id, pad]: m_pads) {
|
||||
@@ -60,6 +60,12 @@ void InputManager::BeginFrame() {
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::SetMouseCaptured(bool captured) {
|
||||
if (captured == m_MouseCaptured) return;
|
||||
m_MouseCaptured = captured;
|
||||
SDL_SetRelativeMouseMode(captured ? SDL_TRUE : SDL_FALSE);
|
||||
}
|
||||
|
||||
void InputManager::AddController(int deviceIndex) {
|
||||
if (!SDL_IsGameController(deviceIndex)) return;
|
||||
|
||||
@@ -134,6 +140,8 @@ bool InputManager::ProcessEvent(const SDL_Event& e) {
|
||||
case SDL_MOUSEMOTION: {
|
||||
m_mouseX = e.motion.x;
|
||||
m_mouseY = e.motion.y;
|
||||
m_mouseRelDX += e.motion.xrel;
|
||||
m_mouseRelDY += e.motion.yrel;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -26,9 +26,12 @@
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
||||
@@ -276,6 +279,84 @@ namespace
|
||||
throw std::runtime_error("Cannot create Jolt body without a valid shape.");
|
||||
}
|
||||
}
|
||||
|
||||
class SensorContactListener final : public ContactListener
|
||||
{
|
||||
public:
|
||||
using BodyPair = std::pair<BodyID, BodyID>;
|
||||
|
||||
struct PairHash {
|
||||
std::size_t operator()(const BodyPair& p) const {
|
||||
return std::hash<uint32_t>{}(
|
||||
p.first.GetIndexAndSequenceNumber() ^
|
||||
(p.second.GetIndexAndSequenceNumber() << 7));
|
||||
}
|
||||
};
|
||||
|
||||
struct PairData {
|
||||
GameObject* objA{nullptr};
|
||||
GameObject* objB{nullptr};
|
||||
bool sensorA{false};
|
||||
bool sensorB{false};
|
||||
};
|
||||
|
||||
ValidateResult OnContactValidate(const Body&, const Body&, RVec3Arg,
|
||||
const CollideShapeResult&) override
|
||||
{
|
||||
return ValidateResult::AcceptAllContactsForThisBodyPair;
|
||||
}
|
||||
|
||||
void OnContactAdded(const Body& body1, const Body& body2,
|
||||
const ContactManifold&, ContactSettings& ioSettings) override
|
||||
{
|
||||
if (body1.IsSensor() || body2.IsSensor())
|
||||
{
|
||||
PairData data;
|
||||
data.objA = reinterpret_cast<GameObject*>(body1.GetUserData());
|
||||
data.objB = reinterpret_cast<GameObject*>(body2.GetUserData());
|
||||
data.sensorA = body1.IsSensor();
|
||||
data.sensorB = body2.IsSensor();
|
||||
std::lock_guard<std::mutex> lock(m_Mutex);
|
||||
m_Overlaps.insert_or_assign(MakePair(body1.GetID(), body2.GetID()), data);
|
||||
}
|
||||
}
|
||||
|
||||
void OnContactPersisted(const Body& body1, const Body& body2,
|
||||
const ContactManifold&, ContactSettings&) override
|
||||
{
|
||||
}
|
||||
|
||||
void OnContactRemoved(const SubShapeIDPair& subShapePair) override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_Mutex);
|
||||
m_Overlaps.erase(MakePair(subShapePair.GetBody1ID(), subShapePair.GetBody2ID()));
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_Mutex);
|
||||
m_Overlaps.clear();
|
||||
}
|
||||
|
||||
std::unordered_map<BodyPair, PairData, PairHash> SwapOverlaps()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_Mutex);
|
||||
return std::move(m_Overlaps);
|
||||
}
|
||||
|
||||
private:
|
||||
static BodyPair MakePair(BodyID a, BodyID b)
|
||||
{
|
||||
if (a.GetIndexAndSequenceNumber() < b.GetIndexAndSequenceNumber())
|
||||
{
|
||||
return {a, b};
|
||||
}
|
||||
return {b, a};
|
||||
}
|
||||
|
||||
std::mutex m_Mutex;
|
||||
std::unordered_map<BodyPair, PairData, PairHash> m_Overlaps;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
class JoltPhysicsWorld::Impl
|
||||
@@ -307,6 +388,7 @@ public:
|
||||
m_ObjectVsBroadPhaseLayerFilter,
|
||||
m_ObjectLayerPairFilter);
|
||||
|
||||
m_PhysicsSystem.SetContactListener(&m_SensorListener);
|
||||
m_PhysicsSystem.SetGravity(ToJoltVec3(settings.gravity));
|
||||
}
|
||||
|
||||
@@ -426,6 +508,15 @@ public:
|
||||
collisionSteps,
|
||||
m_TempAllocator.get(),
|
||||
m_JobSystem.get());
|
||||
|
||||
ProcessSensorOverlaps();
|
||||
}
|
||||
|
||||
std::vector<TriggerEvent> ConsumeTriggerEvents()
|
||||
{
|
||||
std::vector<TriggerEvent> events;
|
||||
m_TriggerEvents.swap(events);
|
||||
return events;
|
||||
}
|
||||
|
||||
void SyncKinematicBodiesToPhysics()
|
||||
@@ -626,6 +717,43 @@ private:
|
||||
return {};
|
||||
}
|
||||
|
||||
void ProcessSensorOverlaps()
|
||||
{
|
||||
const auto currentOverlaps = m_SensorListener.SwapOverlaps();
|
||||
|
||||
for (const auto& [pair, data] : currentOverlaps)
|
||||
{
|
||||
if (m_PreviousSensorOverlaps.find(pair) == m_PreviousSensorOverlaps.end())
|
||||
{
|
||||
if (data.sensorA && data.objA)
|
||||
{
|
||||
m_TriggerEvents.push_back({data.objA, data.objB, true});
|
||||
}
|
||||
if (data.sensorB && data.objB)
|
||||
{
|
||||
m_TriggerEvents.push_back({data.objB, data.objA, true});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [pair, data] : m_PreviousSensorOverlaps)
|
||||
{
|
||||
if (currentOverlaps.find(pair) == currentOverlaps.end())
|
||||
{
|
||||
if (data.sensorA && data.objA)
|
||||
{
|
||||
m_TriggerEvents.push_back({data.objA, data.objB, false});
|
||||
}
|
||||
if (data.sensorB && data.objB)
|
||||
{
|
||||
m_TriggerEvents.push_back({data.objB, data.objA, false});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_PreviousSensorOverlaps = std::move(currentOverlaps);
|
||||
}
|
||||
|
||||
Settings m_Settings{};
|
||||
|
||||
BPLayerInterfaceImpl m_BPLayerInterface{};
|
||||
@@ -639,6 +767,10 @@ private:
|
||||
|
||||
std::unordered_map<std::uint32_t, BodyRecord> m_Bodies;
|
||||
std::uint32_t m_NextHandle{0};
|
||||
|
||||
SensorContactListener m_SensorListener;
|
||||
std::unordered_map<SensorContactListener::BodyPair, SensorContactListener::PairData, SensorContactListener::PairHash> m_PreviousSensorOverlaps;
|
||||
std::vector<TriggerEvent> m_TriggerEvents;
|
||||
};
|
||||
|
||||
JoltPhysicsWorld::JoltPhysicsWorld(const Settings& settings)
|
||||
@@ -710,3 +842,8 @@ bool JoltPhysicsWorld::Raycast(const glm::vec3& origin,
|
||||
{
|
||||
return m_Impl->Raycast(origin, direction, maxDistance, hit);
|
||||
}
|
||||
|
||||
std::vector<TriggerEvent> JoltPhysicsWorld::ConsumeTriggerEvents()
|
||||
{
|
||||
return m_Impl->ConsumeTriggerEvents();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <destrum/Physics/PhysicsSceneBridge.h>
|
||||
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/ObjectModel/Component.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
|
||||
PhysicsSceneBridge::PhysicsSceneBridge(std::unique_ptr<PhysicsWorld> world)
|
||||
@@ -8,14 +9,16 @@ PhysicsSceneBridge::PhysicsSceneBridge(std::unique_ptr<PhysicsWorld> world)
|
||||
}
|
||||
|
||||
void PhysicsSceneBridge::RegisterGameObject(GameObject& object) {
|
||||
if (!m_World) return;
|
||||
if (auto* rb = object.GetComponent<Rigidbody>()) {
|
||||
if (!rb->HasPhysicsBody() || rb->GetPhysicsWorld() != &GetWorld()) {
|
||||
if (!rb->HasPhysicsBody() || rb->GetPhysicsWorld() != m_World.get()) {
|
||||
m_World->RegisterRigidbody(*rb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSceneBridge::UnregisterGameObject(GameObject& object) {
|
||||
if (!m_World) return;
|
||||
if (auto* rb = object.GetComponent<Rigidbody>()) {
|
||||
if (rb->GetPhysicsWorld() == m_World.get()) {
|
||||
m_World->UnregisterRigidbody(*rb);
|
||||
@@ -24,13 +27,29 @@ void PhysicsSceneBridge::UnregisterGameObject(GameObject& object) {
|
||||
}
|
||||
|
||||
void PhysicsSceneBridge::RefreshGameObject(GameObject& object) {
|
||||
if (!m_World) return;
|
||||
if (auto* rb = object.GetComponent<Rigidbody>()) {
|
||||
m_World->RefreshRigidbody(*rb);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSceneBridge::FixedUpdate(float fixedDt) {
|
||||
if (!m_World) return;
|
||||
m_World->SyncKinematicBodiesToPhysics();
|
||||
m_World->Step(fixedDt);
|
||||
|
||||
for (const auto& event : m_World->ConsumeTriggerEvents()) {
|
||||
if (event.owner == nullptr) continue;
|
||||
for (const auto& component : event.owner->GetComponents()) {
|
||||
if (component && !component->IsBeingDestroyed() && component->isEnabled()) {
|
||||
if (event.entered) {
|
||||
component->OnTriggerEnter(event.other);
|
||||
} else {
|
||||
component->OnTriggerExit(event.other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_World->SyncDynamicBodiesToTransforms();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,12 @@ namespace {
|
||||
}
|
||||
|
||||
Scene::Scene(const std::string& name)
|
||||
: m_name(name),
|
||||
: Scene(name, std::make_unique<JoltPhysicsWorld>()) {
|
||||
}
|
||||
|
||||
Scene::Scene(const std::string& name, std::unique_ptr<PhysicsWorld> physicsWorld)
|
||||
: m_Physics(std::move(physicsWorld)),
|
||||
m_name(name),
|
||||
m_id(++m_idCounter) {
|
||||
}
|
||||
|
||||
|
||||
@@ -8,50 +8,40 @@
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
Scene& SceneManager::GetCurrentScene() const {
|
||||
if (m_scenes.empty()) {
|
||||
throw std::out_of_range("No scenes are available");
|
||||
if (m_activeScenes.empty()) {
|
||||
throw std::out_of_range("No active scenes are available");
|
||||
}
|
||||
|
||||
if (m_ActiveSceneIndex < 0 || m_ActiveSceneIndex >= static_cast<int>(m_scenes.size())) {
|
||||
throw std::out_of_range("Active scene index is invalid");
|
||||
}
|
||||
|
||||
return *m_scenes[static_cast<std::size_t>(m_ActiveSceneIndex)];
|
||||
return *m_activeScenes.front();
|
||||
}
|
||||
|
||||
void SceneManager::Update(float dt) {
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
for (const auto& scene : m_activeScenes) {
|
||||
scene->Update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::FixedUpdate(float dt) {
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
for (const auto& scene : m_activeScenes) {
|
||||
scene->FixedUpdate(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::LateUpdate(float dt) {
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
for (const auto& scene : m_activeScenes) {
|
||||
scene->LateUpdate(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::Render(const RenderContext& ctx) {
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
for (const auto& scene : m_activeScenes) {
|
||||
scene->Render(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::RenderImgui() {
|
||||
if (m_scenes.empty()) return;
|
||||
(void)GetCurrentScene();
|
||||
const auto scene = m_scenes.at(static_cast<std::size_t>(m_ActiveSceneIndex));
|
||||
for (const auto& scene : m_activeScenes) {
|
||||
scene->RenderImgui();
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::HandleGameObjectDestroy() {
|
||||
@@ -61,7 +51,7 @@ void SceneManager::HandleGameObjectDestroy() {
|
||||
}
|
||||
|
||||
void SceneManager::DestroyGameObjects() {
|
||||
for (const auto& scene: m_scenes) {
|
||||
for (const auto& scene : m_scenes) {
|
||||
scene->DestroyGameObjects();
|
||||
}
|
||||
}
|
||||
@@ -73,37 +63,22 @@ void SceneManager::UnloadAllScenes() {
|
||||
}
|
||||
|
||||
void SceneManager::HandleSceneDestroy() {
|
||||
const std::shared_ptr<Scene> activeScene =
|
||||
m_ActiveSceneIndex >= 0 &&
|
||||
m_ActiveSceneIndex < static_cast<int>(m_scenes.size())
|
||||
? m_scenes[static_cast<std::size_t>(m_ActiveSceneIndex)]
|
||||
: nullptr;
|
||||
|
||||
for (auto it = m_scenes.begin(); it != m_scenes.end();) {
|
||||
if ((*it)->IsBeingUnloaded()) {
|
||||
const auto activeIt = std::find(m_activeScenes.begin(), m_activeScenes.end(), *it);
|
||||
if (activeIt != m_activeScenes.end()) {
|
||||
m_activeScenes.erase(activeIt);
|
||||
}
|
||||
it = m_scenes.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_scenes.empty()) {
|
||||
m_ActiveSceneIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeScene != nullptr) {
|
||||
const auto activeIt = std::find(m_scenes.begin(), m_scenes.end(), activeScene);
|
||||
if (activeIt != m_scenes.end()) {
|
||||
m_ActiveSceneIndex = static_cast<int>(std::distance(m_scenes.begin(), activeIt));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_ActiveSceneIndex = std::clamp(
|
||||
m_ActiveSceneIndex,
|
||||
0,
|
||||
static_cast<int>(m_scenes.size()) - 1);
|
||||
// Remove any stale active scenes that are no longer in the scene list.
|
||||
std::erase_if(m_activeScenes, [this](const auto& scene) {
|
||||
return std::find(m_scenes.begin(), m_scenes.end(), scene) == m_scenes.end();
|
||||
});
|
||||
}
|
||||
|
||||
void SceneManager::HandleScene() {
|
||||
@@ -113,7 +88,6 @@ void SceneManager::HandleScene() {
|
||||
|
||||
void SceneManager::Destroy() {
|
||||
if (m_scenes.empty()) {
|
||||
m_ActiveSceneIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,25 +98,60 @@ void SceneManager::Destroy() {
|
||||
}
|
||||
|
||||
void SceneManager::SwitchScene(int index) {
|
||||
// InputManager::GetInstance().RemoveAllBindings();
|
||||
|
||||
if (index < 0 || index >= static_cast<int>(m_scenes.size())) {
|
||||
throw std::out_of_range("Scene index out of range");
|
||||
}
|
||||
if (index == m_ActiveSceneIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_scenes[static_cast<std::size_t>(m_ActiveSceneIndex)]->UnloadBindings();
|
||||
m_ActiveSceneIndex = index;
|
||||
m_scenes[static_cast<std::size_t>(m_ActiveSceneIndex)]->LoadBindings();
|
||||
for (const auto& scene : m_activeScenes) {
|
||||
scene->UnloadBindings();
|
||||
}
|
||||
m_activeScenes.clear();
|
||||
m_activeScenes.push_back(m_scenes[static_cast<std::size_t>(index)]);
|
||||
m_activeScenes.back()->LoadBindings();
|
||||
}
|
||||
|
||||
Scene &SceneManager::CreateScene(const std::string &name) {
|
||||
void SceneManager::AddActiveScene(int index) {
|
||||
if (index < 0 || index >= static_cast<int>(m_scenes.size())) {
|
||||
throw std::out_of_range("Scene index out of range");
|
||||
}
|
||||
|
||||
const auto& scene = m_scenes[static_cast<std::size_t>(index)];
|
||||
if (std::find(m_activeScenes.begin(), m_activeScenes.end(), scene) == m_activeScenes.end()) {
|
||||
scene->LoadBindings();
|
||||
m_activeScenes.push_back(scene);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::RemoveActiveScene(int index) {
|
||||
if (index < 0 || index >= static_cast<int>(m_scenes.size())) {
|
||||
throw std::out_of_range("Scene index out of range");
|
||||
}
|
||||
|
||||
const auto& scene = m_scenes[static_cast<std::size_t>(index)];
|
||||
const auto it = std::find(m_activeScenes.begin(), m_activeScenes.end(), scene);
|
||||
if (it != m_activeScenes.end()) {
|
||||
(*it)->UnloadBindings();
|
||||
m_activeScenes.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
int SceneManager::GetActiveSceneId() const {
|
||||
if (m_scenes.empty() || m_activeScenes.empty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto it = std::find(m_scenes.begin(), m_scenes.end(), m_activeScenes.front());
|
||||
if (it == m_scenes.end()) {
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int>(std::distance(m_scenes.begin(), it));
|
||||
}
|
||||
|
||||
Scene& SceneManager::CreateScene(const std::string& name) {
|
||||
const auto scene = std::shared_ptr<Scene>(new Scene(name));
|
||||
m_scenes.push_back(scene);
|
||||
if (m_scenes.size() == 1) {
|
||||
m_ActiveSceneIndex = 0;
|
||||
m_activeScenes.push_back(scene);
|
||||
}
|
||||
return *scene;
|
||||
}
|
||||
@@ -16,8 +16,10 @@
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/ObjectModel/ObjectId.h>
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
#include <destrum/Assets/AssetReference.h>
|
||||
#include <destrum/Components/Physics/Collider.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
#include <destrum/Serialization/ComponentFactory.h>
|
||||
#include <destrum/Serialization/ComponentRegistry.h>
|
||||
|
||||
@@ -201,6 +203,78 @@ namespace {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool AssetFileExists(const std::string& path) {
|
||||
if (path.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::filesystem::path fullPath;
|
||||
if (path.find("://") != std::string::npos) {
|
||||
try {
|
||||
fullPath = AssetFS::GetInstance().GetFullPath(path);
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
fullPath = path;
|
||||
}
|
||||
|
||||
return std::filesystem::exists(fullPath);
|
||||
}
|
||||
|
||||
void ValidateAssetReferences(const json& root, const std::filesystem::path& scenePath) {
|
||||
const auto report = [&](const std::string& objectName, const std::string& detail) {
|
||||
std::cerr << "Scene asset warning (" << scenePath << "): " << objectName
|
||||
<< " references \"" << detail << "\" which could not be found\n";
|
||||
};
|
||||
|
||||
for (const auto& objectJson : root.at("objects")) {
|
||||
const std::string objectName = objectJson.value("name", "GameObject");
|
||||
if (!objectJson.contains("components")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& componentJson : objectJson.at("components")) {
|
||||
if (!componentJson.contains("data")) {
|
||||
continue;
|
||||
}
|
||||
const auto& data = componentJson.at("data");
|
||||
|
||||
// MeshRendererComponent: meshKey / materialKey are asset cache keys.
|
||||
for (const char* key : {"meshKey", "materialKey"}) {
|
||||
if (data.contains(key) && data.at(key).is_string()) {
|
||||
const std::string cacheKey = data.at(key).get<std::string>();
|
||||
if (cacheKey.empty()) continue;
|
||||
const auto asset = AssetReference::fromCacheKey(cacheKey);
|
||||
if (asset && !AssetFileExists(asset->path)) {
|
||||
report(objectName, cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generic: any nested object with a "path" field is an AssetReference.
|
||||
std::function<void(const json&)> walk = [&](const json& node) {
|
||||
if (node.is_object()) {
|
||||
if (node.contains("path") && node.at("path").is_string()) {
|
||||
const std::string path = node.at("path").get<std::string>();
|
||||
if (!path.empty() && !AssetFileExists(path)) {
|
||||
report(objectName, path);
|
||||
}
|
||||
}
|
||||
for (auto it = node.begin(); it != node.end(); ++it) {
|
||||
walk(it.value());
|
||||
}
|
||||
} else if (node.is_array()) {
|
||||
for (const auto& element : node) {
|
||||
walk(element);
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SceneSerializer::Save(Scene& scene, const std::filesystem::path& path) {
|
||||
@@ -290,30 +364,45 @@ bool SceneSerializer::Save(Scene& scene, const std::filesystem::path& path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
SceneSerializer::LoadResult SceneSerializer::LoadSceneFile(const std::filesystem::path& path) {
|
||||
LoadResult result;
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) {
|
||||
result.errorMessage = "Failed to open scene file for reading: " + path.string();
|
||||
std::cerr << result.errorMessage << '\n';
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
file >> result.root;
|
||||
if (!ValidateSceneJson(result.root)) {
|
||||
result.errorMessage = "Invalid scene data: " + path.string();
|
||||
std::cerr << result.errorMessage << '\n';
|
||||
return result;
|
||||
}
|
||||
} catch (const std::exception& exception) {
|
||||
result.errorMessage = "Failed to parse scene file: " + std::string(exception.what());
|
||||
std::cerr << result.errorMessage << '\n';
|
||||
return result;
|
||||
}
|
||||
|
||||
ValidateAssetReferences(result.root, path);
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SceneSerializer::ConstructScene(Scene& scene, LoadResult& result,
|
||||
std::function<void(float)> progressCallback) {
|
||||
if (scene.IsIterating()) {
|
||||
std::cerr << "Cannot load a scene during an update or render phase: "
|
||||
<< scene.GetName() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
RegisterEngineComponents();
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Failed to open scene file for reading: " << path << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
json root;
|
||||
try {
|
||||
file >> root;
|
||||
if (!ValidateSceneJson(root)) {
|
||||
std::cerr << "Invalid scene data: " << path << '\n';
|
||||
return false;
|
||||
}
|
||||
} catch (const std::exception& exception) {
|
||||
std::cerr << "Failed to parse scene file: " << exception.what() << '\n';
|
||||
if (!result.success) {
|
||||
std::cerr << "Cannot construct scene from a failed load result: "
|
||||
<< result.errorMessage << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -322,9 +411,16 @@ bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RegisterEngineComponents();
|
||||
if (progressCallback) progressCallback(0.35f);
|
||||
|
||||
const json& root = result.root;
|
||||
|
||||
// Build the replacement separately. The current scene is not touched
|
||||
// until all objects, transforms, and components have loaded successfully.
|
||||
Scene stagingScene(scene.GetName());
|
||||
// No physics world is created for the staging scene - physics bodies are
|
||||
// registered directly in the live scene's world once loading succeeds.
|
||||
Scene stagingScene(scene.GetName(), nullptr);
|
||||
std::unordered_map<ObjectId, GameObject*> idMap;
|
||||
std::vector<const json*> objectJsonList;
|
||||
std::vector<GameObject*> registeredObjects;
|
||||
@@ -343,7 +439,16 @@ bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
objectJsonList.push_back(&objectJson);
|
||||
}
|
||||
|
||||
stagingScene.CommitPendingAdditions();
|
||||
if (progressCallback) progressCallback(0.4f);
|
||||
|
||||
// Move objects from pending to objects list without physics registration
|
||||
// (staging scene has no physics world).
|
||||
for (auto& pending : stagingScene.m_pendingAdditions) {
|
||||
if (pending) {
|
||||
stagingScene.m_objects.emplace_back(std::move(pending));
|
||||
}
|
||||
}
|
||||
stagingScene.m_pendingAdditions.clear();
|
||||
|
||||
for (const json* objectJson : objectJsonList) {
|
||||
const ObjectId id = objectJson->at("id").get<ObjectId>();
|
||||
@@ -392,6 +497,8 @@ bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
}
|
||||
}
|
||||
|
||||
if (progressCallback) progressCallback(0.5f);
|
||||
|
||||
for (const json* objectJson : objectJsonList) {
|
||||
GameObject* object = idMap.at(objectJson->at("id").get<ObjectId>());
|
||||
if (!objectJson->contains("components")) {
|
||||
@@ -412,6 +519,8 @@ bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
}
|
||||
}
|
||||
|
||||
if (progressCallback) progressCallback(0.6f);
|
||||
|
||||
for (const auto& objectPtr : stagingScene.GetObjects()) {
|
||||
if (!objectPtr) {
|
||||
continue;
|
||||
@@ -424,6 +533,8 @@ bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
}
|
||||
}
|
||||
|
||||
if (progressCallback) progressCallback(0.7f);
|
||||
|
||||
// Register the replacement bodies in the live physics world before
|
||||
// touching the current scene. If any body fails, the old scene and
|
||||
// its physics state can remain intact.
|
||||
@@ -438,6 +549,8 @@ bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
registeredObjects.push_back(objectPtr.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (progressCallback) progressCallback(0.8f);
|
||||
} catch (const std::exception& exception) {
|
||||
std::cerr << "Failed to load scene: " << exception.what() << '\n';
|
||||
for (GameObject* object : registeredObjects) {
|
||||
@@ -449,6 +562,7 @@ bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
scene.UnloadBindings();
|
||||
scene.RemoveAll();
|
||||
scene.m_objects = std::move(stagingScene.m_objects);
|
||||
scene.m_pendingAdditions = std::move(stagingScene.m_pendingAdditions);
|
||||
@@ -456,6 +570,8 @@ bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
scene.m_name = root.at("name").get<std::string>();
|
||||
}
|
||||
|
||||
if (progressCallback) progressCallback(0.9f);
|
||||
|
||||
for (const auto& object : scene.m_objects) {
|
||||
if (object) {
|
||||
object->SetScene(&scene);
|
||||
@@ -467,5 +583,27 @@ bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
|
||||
}
|
||||
}
|
||||
|
||||
if (progressCallback) progressCallback(1.0f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path,
|
||||
std::function<void(float)> progressCallback) {
|
||||
if (progressCallback) progressCallback(0.0f);
|
||||
|
||||
auto result = LoadSceneFile(path);
|
||||
if (!result.success) {
|
||||
if (progressCallback) progressCallback(1.0f);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (progressCallback) progressCallback(0.3f);
|
||||
return ConstructScene(scene, result, std::move(progressCallback));
|
||||
}
|
||||
|
||||
std::future<SceneSerializer::LoadResult> SceneSerializer::LoadSceneFileAsync(
|
||||
const std::filesystem::path& path) {
|
||||
return std::async(std::launch::async, [path]() {
|
||||
return LoadSceneFile(path);
|
||||
});
|
||||
}
|
||||
|
||||
Vendored
+1
-1
Submodule destrum/third_party/imgui updated: d15966ff6c...7e1b65d26d
@@ -6,6 +6,11 @@ set(GAME_SRC
|
||||
src/main.cpp
|
||||
|
||||
src/Lightkeeper.cpp
|
||||
|
||||
src/components/GameComponentRegistry.cpp
|
||||
src/components/FirstPersonController.cpp
|
||||
src/components/PrintComponent.cpp
|
||||
src/components/TriggerLoggerComponent.cpp
|
||||
)
|
||||
|
||||
add_executable(lightkeeper ${GAME_SRC})
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "destrum/Graphics/Resources/Cubemap.h"
|
||||
#include "destrum/ObjectModel/GameObject.h"
|
||||
|
||||
class FirstPersonController;
|
||||
class LightKeeper final : public App {
|
||||
public:
|
||||
LightKeeper();
|
||||
@@ -21,19 +22,33 @@ public:
|
||||
void customFixedUpdate(float dt) override;
|
||||
|
||||
void onWindowResize(int newWidth, int newHeight) override;
|
||||
|
||||
private:
|
||||
void drawDebugMenuBar();
|
||||
void saveCurrentScene();
|
||||
void loadScene();
|
||||
void createEmptyScene();
|
||||
void submitBoxColliderDebugLines();
|
||||
|
||||
Camera camera{glm::vec3(0.f, 0.f, -5.f), glm::vec3(0, 1, 0)};
|
||||
|
||||
MeshID sphereMesh;
|
||||
MaterialID sphereMaterial;
|
||||
|
||||
ImageID texID;
|
||||
|
||||
std::unique_ptr<CubeMap> skyboxCubemap;
|
||||
|
||||
GameObject* capybara = nullptr;
|
||||
GameObject* m_Player = nullptr;
|
||||
FirstPersonController* m_FPSController = nullptr;
|
||||
|
||||
char scenePath[512]{"scenes/debug_scene.json"};
|
||||
char newSceneName[128]{"EmptyScene"};
|
||||
std::string sceneStatus;
|
||||
bool renderBoxColliders{false};
|
||||
bool renderQuads{true};
|
||||
bool renderDebugLines{true};
|
||||
};
|
||||
|
||||
#endif //LIGHTKEEPER_H
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#ifndef LIGHTKEEPER_FIRSTPERSONCONTROLLER_H
|
||||
#define LIGHTKEEPER_FIRSTPERSONCONTROLLER_H
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <destrum/Input/InputManager.h>
|
||||
#include <destrum/ObjectModel/Component.h>
|
||||
|
||||
class Rigidbody;
|
||||
class CapsuleCollider;
|
||||
|
||||
class FirstPersonController final : public Component {
|
||||
public:
|
||||
explicit FirstPersonController(GameObject& parent);
|
||||
|
||||
std::string GetTypeName() const override { return "FirstPersonController"; }
|
||||
|
||||
void Start() override;
|
||||
void Update(float dt) override;
|
||||
void FixedUpdate(float fixedDt) override;
|
||||
void ImGuiInspector() override;
|
||||
nlohmann::json Serialize() const override;
|
||||
void Deserialize(const nlohmann::json& data) override;
|
||||
|
||||
[[nodiscard]] float GetYaw() const { return m_Yaw; }
|
||||
[[nodiscard]] float GetPitch() const { return m_Pitch; }
|
||||
[[nodiscard]] float GetEyeHeight() const { return m_EyeHeight; }
|
||||
|
||||
void SetMovementSpeed(float speed) { m_MovementSpeed = speed; }
|
||||
[[nodiscard]] float GetMovementSpeed() const { return m_MovementSpeed; }
|
||||
|
||||
void SetMouseSensitivity(float sens) { m_MouseSensitivity = sens; }
|
||||
[[nodiscard]] float GetMouseSensitivity() const { return m_MouseSensitivity; }
|
||||
|
||||
[[nodiscard]] bool IsMouseCaptured() const { return InputManager::GetInstance().IsMouseCaptured(); }
|
||||
void SetMouseCaptured(bool captured);
|
||||
|
||||
private:
|
||||
Rigidbody* m_Rigidbody{nullptr};
|
||||
CapsuleCollider* m_Capsule{nullptr};
|
||||
|
||||
float m_Yaw{0.0f};
|
||||
float m_Pitch{0.0f};
|
||||
float m_MovementSpeed{5.0f};
|
||||
float m_MouseSensitivity{0.002f};
|
||||
float m_EyeHeight{1.6f};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef LIGHTKEEPER_GAMECOMPONENTLIST_H
|
||||
#define LIGHTKEEPER_GAMECOMPONENTLIST_H
|
||||
|
||||
#include <components/FirstPersonController.h>
|
||||
#include <components/PrintComponent.h>
|
||||
#include <components/TriggerLoggerComponent.h>
|
||||
|
||||
#define LIGHTKEEPER_GAME_COMPONENTS(X) \
|
||||
X(FirstPersonController) \
|
||||
X(PrintComponent) \
|
||||
X(TriggerLoggerComponent)
|
||||
|
||||
#endif // LIGHTKEEPER_GAMECOMPONENTLIST_H
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef LIGHTKEEPER_GAMECOMPONENTREGISTRY_H
|
||||
#define LIGHTKEEPER_GAMECOMPONENTREGISTRY_H
|
||||
|
||||
void RegisterGameComponents();
|
||||
|
||||
#endif // LIGHTKEEPER_GAMECOMPONENTREGISTRY_H
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef DESTRUM_PRINTCOMPONENT_H
|
||||
#define DESTRUM_PRINTCOMPONENT_H
|
||||
#include "destrum/ObjectModel/Component.h"
|
||||
|
||||
class PrintComponent: public Component {
|
||||
public:
|
||||
|
||||
PrintComponent(GameObject& parent);
|
||||
|
||||
std::string GetTypeName() const override { return "PrintComponent"; }
|
||||
void Start() override;
|
||||
void Update(float dt) override;
|
||||
void ImGuiInspector() override;
|
||||
nlohmann::json Serialize() const override;
|
||||
void Deserialize(const nlohmann::json&) override;
|
||||
|
||||
void SetMessage(std::string_view message)
|
||||
{
|
||||
m_message = message;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::string m_message{};
|
||||
|
||||
};
|
||||
|
||||
#endif //DESTRUM_PRINTCOMPONENT_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef DESTRUM_TRIGGERLOGGERCOMPONENT_H
|
||||
#define DESTRUM_TRIGGERLOGGERCOMPONENT_H
|
||||
#include "destrum/ObjectModel/Component.h"
|
||||
|
||||
class TriggerLoggerComponent: public Component {
|
||||
public:
|
||||
TriggerLoggerComponent(GameObject& parent);
|
||||
|
||||
std::string GetTypeName() const override { return "TriggerLoggerComponent"; }
|
||||
|
||||
void OnTriggerEnter(GameObject* other) override;
|
||||
void OnTriggerExit(GameObject* other) override;
|
||||
|
||||
nlohmann::json Serialize() const override;
|
||||
void Deserialize(const nlohmann::json&) override;
|
||||
|
||||
int triggerEnterCount{0};
|
||||
int triggerExitCount{0};
|
||||
std::string lastOtherName;
|
||||
};
|
||||
|
||||
#endif //DESTRUM_TRIGGERLOGGERCOMPONENT_H
|
||||
+170
-10
@@ -1,7 +1,11 @@
|
||||
#include "Lightkeeper.h"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
#include <destrum/Assets/AssetManager.h>
|
||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
||||
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
|
||||
#include "glm/gtx/transform.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
@@ -16,6 +20,8 @@
|
||||
#include "destrum/ObjectModel/GameObject.h"
|
||||
#include "destrum/Util/ModelDoc.h"
|
||||
#include "destrum/Components/Animator.h"
|
||||
#include <components/FirstPersonController.h>
|
||||
#include <components/TriggerLoggerComponent.h>
|
||||
|
||||
|
||||
#include <filesystem>
|
||||
@@ -24,6 +30,8 @@
|
||||
#include <string_view>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "components/GameComponentRegistry.h"
|
||||
#include "components/PrintComponent.h"
|
||||
#include "destrum/Components/Physics/SphereCollider.h"
|
||||
#include "destrum/Util/ModelDocUtils.h"
|
||||
|
||||
@@ -60,6 +68,8 @@ LightKeeper::~LightKeeper()
|
||||
|
||||
void LightKeeper::customInit()
|
||||
{
|
||||
RegisterGameComponents();
|
||||
|
||||
resources.init(gfxDevice);
|
||||
renderer.init(gfxDevice, resources, m_params.renderSize);
|
||||
|
||||
@@ -426,21 +436,61 @@ void LightKeeper::customInit()
|
||||
|
||||
sphereMaterial = resources.materials().addSimpleColorMaterial({0.5f, 0.3f, 0.8f}, "Blue");
|
||||
|
||||
|
||||
// Trigger zone: a static box with a TriggerLoggerComponent.
|
||||
// Spheres spawned via the "Spawn ball" button fall through it.
|
||||
auto triggerObj = scene.CreateGameObject("TriggerBox");
|
||||
auto triggerBox = triggerObj->AddComponent<BoxCollider>(glm::vec3{3.0f, 3.0f, 3.0f});
|
||||
triggerBox->SetTrigger(true);
|
||||
auto triggerRb = triggerObj->AddComponent<Rigidbody>();
|
||||
triggerRb->SetType(RigidbodyType::Static);
|
||||
triggerObj->AddComponent<TriggerLoggerComponent>();
|
||||
triggerObj->GetTransform().SetWorldPosition({0.0f, 0.0f, 0.0f});
|
||||
|
||||
auto playerObj = scene.CreateGameObject("Player");
|
||||
playerObj->GetTransform().SetWorldPosition(glm::vec3(0.0f, 2.0f, -5.0f));
|
||||
auto* fps = playerObj->AddComponent<FirstPersonController>();
|
||||
m_Player = playerObj;
|
||||
m_FPSController = fps;
|
||||
scene.GetPhysics().RegisterGameObject(*playerObj);
|
||||
|
||||
const auto texPath = AssetFS::GetInstance().GetFullPath("engine://textures/kobe.png");
|
||||
texID = resources.loadImageFromFile(gfxDevice, texPath);
|
||||
|
||||
}
|
||||
|
||||
void LightKeeper::customUpdate(float dt)
|
||||
{
|
||||
// LightKeeper owns a camera that hides App::camera; update this instance
|
||||
// after SDL events have been consumed.
|
||||
if (m_FPSController != nullptr && m_FPSController->IsMouseCaptured())
|
||||
{
|
||||
auto& playerTransform = m_Player->GetTransform();
|
||||
const glm::vec3 playerPos = playerTransform.GetWorldPosition();
|
||||
camera.SetRotation(m_FPSController->GetYaw(), m_FPSController->GetPitch());
|
||||
camera.m_position = playerPos + glm::vec3(0.0f, m_FPSController->GetEyeHeight(), 0.0f);
|
||||
camera.CalculateViewMatrix();
|
||||
camera.CalculateProjectionMatrix();
|
||||
}
|
||||
else
|
||||
{
|
||||
camera.Update(dt);
|
||||
}
|
||||
SceneManager::GetInstance().Update(dt);
|
||||
SceneManager::GetInstance().LateUpdate(dt);
|
||||
|
||||
if (m_params.showDebugUi) {
|
||||
drawDebugMenuBar();
|
||||
}
|
||||
|
||||
LineRenderingManager::GetInstance().SubmitLine({0, 0, 0}, {0, 10, 0}, {255, 0, 0, 255});
|
||||
|
||||
if (InputManager::GetInstance().WasKeyPressed(SDL_SCANCODE_1))
|
||||
{
|
||||
renderer.setRenderWireframe(!renderer.getRenderWireframe());
|
||||
}
|
||||
|
||||
if (m_params.showDebugUi) {
|
||||
ImGui::Begin("Test");
|
||||
if (ImGui::Button("SPawn ball"))
|
||||
{
|
||||
@@ -460,12 +510,57 @@ void LightKeeper::customUpdate(float dt)
|
||||
SceneManager::GetInstance().GetCurrentScene().GetPhysics().RefreshGameObject(*sphere);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
ImGui::Begin("Scene Debug");
|
||||
}
|
||||
|
||||
void LightKeeper::drawDebugMenuBar()
|
||||
{
|
||||
if (!ImGui::BeginMainMenuBar()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
ImGui::TextDisabled("Scene");
|
||||
|
||||
ImGui::SetNextItemWidth(320.0f);
|
||||
ImGui::InputText("Scene path", scenePath, sizeof(scenePath));
|
||||
|
||||
ImGui::SetNextItemWidth(320.0f);
|
||||
ImGui::InputText("New scene name", newSceneName, sizeof(newSceneName));
|
||||
|
||||
if (ImGui::Button("Save Current Scene")) {
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem("New Empty Scene")) {
|
||||
createEmptyScene();
|
||||
}
|
||||
if (ImGui::MenuItem("Load")) {
|
||||
loadScene();
|
||||
}
|
||||
if (ImGui::MenuItem("Save")) {
|
||||
saveCurrentScene();
|
||||
}
|
||||
|
||||
if (!sceneStatus.empty()) {
|
||||
ImGui::Separator();
|
||||
ImGui::TextWrapped("%s", sceneStatus.c_str());
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Render")) {
|
||||
ImGui::MenuItem("Box Colliders", nullptr, &renderBoxColliders);
|
||||
ImGui::MenuItem("2D Quads", nullptr, &renderQuads);
|
||||
ImGui::MenuItem("Debug Lines", nullptr, &renderDebugLines);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
|
||||
void LightKeeper::saveCurrentScene()
|
||||
{
|
||||
try {
|
||||
const auto path = ResolveScenePath(scenePath, m_params.exeDir);
|
||||
const auto parent = path.parent_path();
|
||||
@@ -483,10 +578,10 @@ void LightKeeper::customUpdate(float dt)
|
||||
} catch (const std::exception& exception) {
|
||||
sceneStatus = std::string{"Save failed: "} + exception.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Load Scene")) {
|
||||
void LightKeeper::loadScene()
|
||||
{
|
||||
try {
|
||||
const auto path = ResolveScenePath(scenePath, m_params.exeDir);
|
||||
if (SceneSerializer::Load(
|
||||
@@ -499,9 +594,10 @@ void LightKeeper::customUpdate(float dt)
|
||||
} catch (const std::exception& exception) {
|
||||
sceneStatus = std::string{"Load failed: "} + exception.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Button("New Empty Scene")) {
|
||||
void LightKeeper::createEmptyScene()
|
||||
{
|
||||
try {
|
||||
const std::string name = newSceneName[0] != '\0'
|
||||
? newSceneName
|
||||
@@ -514,12 +610,71 @@ void LightKeeper::customUpdate(float dt)
|
||||
} catch (const std::exception& exception) {
|
||||
sceneStatus = std::string{"New scene failed: "} + exception.what();
|
||||
}
|
||||
}
|
||||
|
||||
void LightKeeper::submitBoxColliderDebugLines()
|
||||
{
|
||||
if (!renderBoxColliders) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sceneStatus.empty()) {
|
||||
ImGui::TextWrapped("%s", sceneStatus.c_str());
|
||||
auto& sceneManager = SceneManager::GetInstance();
|
||||
if (sceneManager.GetSceneCount() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Scene& scene = sceneManager.GetCurrentScene();
|
||||
auto& lineManager = LineRenderingManager::GetInstance();
|
||||
constexpr std::array<std::array<std::size_t, 2>, 12> edges{{
|
||||
{{0, 1}}, {{1, 3}}, {{3, 2}}, {{2, 0}},
|
||||
{{4, 5}}, {{5, 7}}, {{7, 6}}, {{6, 4}},
|
||||
{{0, 4}}, {{1, 5}}, {{2, 6}}, {{3, 7}},
|
||||
}};
|
||||
|
||||
for (const auto& objectPtr : scene.GetObjects()) {
|
||||
if (!objectPtr || objectPtr->IsBeingDestroyed() ||
|
||||
!objectPtr->IsActiveInHierarchy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto* boxCollider = objectPtr->GetComponent<BoxCollider>();
|
||||
if (boxCollider == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collider dimensions are already in world units. Physics does not
|
||||
// apply the GameObject scale when constructing the shape.
|
||||
const glm::vec3 center = boxCollider->GetCenterOffset();
|
||||
const glm::vec3 halfExtents = boxCollider->GetHalfExtents();
|
||||
const std::array<glm::vec3, 8> localCorners{
|
||||
center + glm::vec3{-halfExtents.x, -halfExtents.y, -halfExtents.z},
|
||||
center + glm::vec3{ halfExtents.x, -halfExtents.y, -halfExtents.z},
|
||||
center + glm::vec3{-halfExtents.x, halfExtents.y, -halfExtents.z},
|
||||
center + glm::vec3{ halfExtents.x, halfExtents.y, -halfExtents.z},
|
||||
center + glm::vec3{-halfExtents.x, -halfExtents.y, halfExtents.z},
|
||||
center + glm::vec3{ halfExtents.x, -halfExtents.y, halfExtents.z},
|
||||
center + glm::vec3{-halfExtents.x, halfExtents.y, halfExtents.z},
|
||||
center + glm::vec3{ halfExtents.x, halfExtents.y, halfExtents.z},
|
||||
};
|
||||
|
||||
Transform& transform = objectPtr->GetTransform();
|
||||
const glm::vec3 worldPosition = transform.GetWorldPosition();
|
||||
const glm::quat worldRotation = transform.GetWorldRotation();
|
||||
std::array<glm::vec3, 8> worldCorners{};
|
||||
for (std::size_t index = 0; index < localCorners.size(); ++index) {
|
||||
worldCorners[index] = worldPosition + worldRotation * localCorners[index];
|
||||
}
|
||||
|
||||
const glm::vec4 color = boxCollider->IsTrigger()
|
||||
? glm::vec4{1.0f, 0.75f, 0.1f, 1.0f}
|
||||
: glm::vec4{0.1f, 0.8f, 1.0f, 1.0f};
|
||||
for (const auto& edge : edges) {
|
||||
lineManager.SubmitLine(
|
||||
worldCorners[edge[0]],
|
||||
worldCorners[edge[1]],
|
||||
color);
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void LightKeeper::customDraw()
|
||||
@@ -530,6 +685,11 @@ void LightKeeper::customDraw()
|
||||
}
|
||||
|
||||
renderer.beginDrawing(gfxDevice);
|
||||
submitBoxColliderDebugLines();
|
||||
|
||||
QuadRenderingManager::GetInstance().SubmitQuad(
|
||||
glm::vec2{100.0f, 100.0f}, glm::vec2{200.0f, 200.0f}, 0.0f,
|
||||
glm::vec4{1.0f}, texID);
|
||||
|
||||
const RenderContext ctx{
|
||||
.renderer = renderer,
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
#include <components/FirstPersonController.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
||||
#include <destrum/Components/Physics/CapsuleCollider.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Input/InputManager.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/Physics/PhysicsWorld.h>
|
||||
#include <destrum/Scene/Scene.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
FirstPersonController::FirstPersonController(GameObject& parent)
|
||||
: Component(parent, "FirstPersonController")
|
||||
{
|
||||
}
|
||||
|
||||
void FirstPersonController::SetMouseCaptured(bool captured)
|
||||
{
|
||||
InputManager::GetInstance().SetMouseCaptured(captured);
|
||||
}
|
||||
|
||||
void FirstPersonController::Start()
|
||||
{
|
||||
auto* obj = GetGameObject();
|
||||
m_Capsule = obj->AddComponent<CapsuleCollider>(0.5f, 2.0f);
|
||||
m_Rigidbody = obj->AddComponent<Rigidbody>();
|
||||
m_Rigidbody->SetMass(80.0f);
|
||||
m_Rigidbody->SetAllowSleep(false);
|
||||
m_Rigidbody->SetUseGravity(true);
|
||||
}
|
||||
|
||||
void FirstPersonController::Update(float)
|
||||
{
|
||||
const auto& input = InputManager::GetInstance();
|
||||
|
||||
if (input.WasKeyPressed(SDL_SCANCODE_F5))
|
||||
{
|
||||
SetMouseCaptured(!IsMouseCaptured());
|
||||
}
|
||||
|
||||
if (!IsMouseCaptured())
|
||||
{
|
||||
if (input.WasMousePressed(SDL_BUTTON_LEFT) && !ImGui::GetIO().WantCaptureMouse)
|
||||
{
|
||||
SetMouseCaptured(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.WasKeyPressed(SDL_SCANCODE_ESCAPE))
|
||||
{
|
||||
SetMouseCaptured(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.WasKeyPressed(SDL_SCANCODE_SPACE) && m_Rigidbody->HasPhysicsBody())
|
||||
{
|
||||
m_Rigidbody->AddImpulse(glm::vec3(0.0f, 400.0f, 0.0f));
|
||||
}
|
||||
|
||||
m_Yaw += static_cast<float>(input.MouseDeltaX()) * m_MouseSensitivity;
|
||||
m_Pitch -= static_cast<float>(input.MouseDeltaY()) * m_MouseSensitivity;
|
||||
m_Pitch = glm::clamp(m_Pitch, glm::radians(-89.0f), glm::radians(89.0f));
|
||||
}
|
||||
|
||||
void FirstPersonController::FixedUpdate(float)
|
||||
{
|
||||
if (!IsMouseCaptured() || m_Rigidbody == nullptr || !m_Rigidbody->HasPhysicsBody())
|
||||
return;
|
||||
|
||||
const auto& input = InputManager::GetInstance();
|
||||
|
||||
const float cosY = glm::cos(m_Yaw);
|
||||
const float sinY = glm::sin(m_Yaw);
|
||||
const glm::vec3 forward{cosY, 0.0f, sinY};
|
||||
const glm::vec3 right{-sinY, 0.0f, cosY};
|
||||
|
||||
glm::vec3 moveDir(0.0f);
|
||||
if (input.IsKeyDown(SDL_SCANCODE_W)) moveDir += forward;
|
||||
if (input.IsKeyDown(SDL_SCANCODE_S)) moveDir -= forward;
|
||||
if (input.IsKeyDown(SDL_SCANCODE_D)) moveDir += right;
|
||||
if (input.IsKeyDown(SDL_SCANCODE_A)) moveDir -= right;
|
||||
|
||||
glm::vec3 velocity(0.0f);
|
||||
if (glm::length2(moveDir) > 0.0f)
|
||||
{
|
||||
moveDir = glm::normalize(moveDir);
|
||||
velocity = moveDir * m_MovementSpeed;
|
||||
}
|
||||
|
||||
velocity.y = m_Rigidbody->GetLinearVelocity().y;
|
||||
m_Rigidbody->SetLinearVelocity(velocity);
|
||||
|
||||
auto* world = m_Rigidbody->GetPhysicsWorld();
|
||||
if (world != nullptr)
|
||||
{
|
||||
PhysicsTransform xform = world->GetBodyTransform(m_Rigidbody->GetBody());
|
||||
xform.rotation = glm::quat(glm::vec3(0.0f, m_Yaw, 0.0f));
|
||||
world->SetBodyTransform(m_Rigidbody->GetBody(), xform);
|
||||
}
|
||||
}
|
||||
|
||||
void FirstPersonController::ImGuiInspector()
|
||||
{
|
||||
Component::ImGuiInspector();
|
||||
ImGui::DragFloat("Movement Speed", &m_MovementSpeed, 0.1f, 0.0f, 100.0f);
|
||||
ImGui::DragFloat("Mouse Sensitivity", &m_MouseSensitivity, 0.0001f, 0.0f, 0.1f);
|
||||
ImGui::DragFloat("Eye Height", &m_EyeHeight, 0.01f, 0.0f, 5.0f);
|
||||
ImGui::Text("Yaw: %.1f deg", glm::degrees(m_Yaw));
|
||||
ImGui::Text("Pitch: %.1f deg", glm::degrees(m_Pitch));
|
||||
bool captured = IsMouseCaptured();
|
||||
ImGui::Checkbox("Mouse Captured", &captured);
|
||||
if (captured != IsMouseCaptured()) {
|
||||
SetMouseCaptured(captured);
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json FirstPersonController::Serialize() const
|
||||
{
|
||||
return {
|
||||
{"movementSpeed", m_MovementSpeed},
|
||||
{"mouseSensitivity", m_MouseSensitivity},
|
||||
{"eyeHeight", m_EyeHeight},
|
||||
{"yaw", m_Yaw},
|
||||
{"pitch", m_Pitch},
|
||||
};
|
||||
}
|
||||
|
||||
void FirstPersonController::Deserialize(const nlohmann::json& data)
|
||||
{
|
||||
if (data.contains("movementSpeed")) m_MovementSpeed = data.at("movementSpeed").get<float>();
|
||||
if (data.contains("mouseSensitivity")) m_MouseSensitivity = data.at("mouseSensitivity").get<float>();
|
||||
if (data.contains("eyeHeight")) m_EyeHeight = data.at("eyeHeight").get<float>();
|
||||
if (data.contains("yaw")) m_Yaw = data.at("yaw").get<float>();
|
||||
if (data.contains("pitch")) m_Pitch = data.at("pitch").get<float>();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#include <components/GameComponentRegistry.h>
|
||||
|
||||
#include <destrum/Serialization/ComponentFactory.h>
|
||||
|
||||
#include <components/GameComponentList.h>
|
||||
|
||||
void RegisterGameComponents()
|
||||
{
|
||||
static bool registered = false;
|
||||
if (registered) {
|
||||
return;
|
||||
}
|
||||
|
||||
registered = true;
|
||||
|
||||
#define LIGHTKEEPER_REGISTER_COMPONENT(ComponentType) \
|
||||
ComponentFactory::Register<ComponentType>(#ComponentType);
|
||||
LIGHTKEEPER_GAME_COMPONENTS(LIGHTKEEPER_REGISTER_COMPONENT)
|
||||
#undef LIGHTKEEPER_REGISTER_COMPONENT
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#include <components/PrintComponent.h>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
PrintComponent::PrintComponent(GameObject& parent) : Component(parent, "PrintComponent")
|
||||
{
|
||||
}
|
||||
|
||||
void PrintComponent::Start()
|
||||
{
|
||||
}
|
||||
|
||||
void PrintComponent::Update(float dt)
|
||||
{
|
||||
spdlog::debug(m_message);
|
||||
}
|
||||
|
||||
void PrintComponent::ImGuiInspector()
|
||||
{
|
||||
Component::ImGuiInspector();
|
||||
}
|
||||
|
||||
nlohmann::json PrintComponent::Serialize() const
|
||||
{
|
||||
return {"message", m_message};
|
||||
}
|
||||
|
||||
void PrintComponent::Deserialize(const nlohmann::json& basic_jsons)
|
||||
{
|
||||
if (basic_jsons.contains("message"))
|
||||
{
|
||||
m_message = basic_jsons.at("message").get<std::string>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#include <components/TriggerLoggerComponent.h>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
|
||||
TriggerLoggerComponent::TriggerLoggerComponent(GameObject& parent)
|
||||
: Component(parent, "TriggerLoggerComponent") {}
|
||||
|
||||
void TriggerLoggerComponent::OnTriggerEnter(GameObject* other) {
|
||||
++triggerEnterCount;
|
||||
lastOtherName = other ? other->GetName() : "null";
|
||||
spdlog::info(
|
||||
"TriggerLogger: ENTER {} -> {} (enters: {}, exits: {})",
|
||||
GetGameObject()->GetName(),
|
||||
lastOtherName,
|
||||
triggerEnterCount,
|
||||
triggerExitCount);
|
||||
}
|
||||
|
||||
void TriggerLoggerComponent::OnTriggerExit(GameObject* other) {
|
||||
++triggerExitCount;
|
||||
lastOtherName = other ? other->GetName() : "null";
|
||||
spdlog::info(
|
||||
"TriggerLogger: EXIT {} -> {} (enters: {}, exits: {})",
|
||||
GetGameObject()->GetName(),
|
||||
lastOtherName,
|
||||
triggerEnterCount,
|
||||
triggerExitCount);
|
||||
}
|
||||
|
||||
nlohmann::json TriggerLoggerComponent::Serialize() const {
|
||||
return {
|
||||
{"triggerEnterCount", triggerEnterCount},
|
||||
{"triggerExitCount", triggerExitCount}
|
||||
};
|
||||
}
|
||||
|
||||
void TriggerLoggerComponent::Deserialize(const nlohmann::json& data) {
|
||||
if (data.contains("triggerEnterCount")) {
|
||||
triggerEnterCount = data.at("triggerEnterCount").get<int>();
|
||||
}
|
||||
if (data.contains("triggerExitCount")) {
|
||||
triggerExitCount = data.at("triggerExitCount").get<int>();
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,11 @@
|
||||
#include <destrum/Physics/JoltPhysicsWorld.h>
|
||||
#include <destrum/Components/Physics/SphereCollider.h>
|
||||
#include <destrum/Components/Physics/BoxCollider.h>
|
||||
#include <destrum/Components/Physics/Collider.h>
|
||||
#include <destrum/Components/Animator.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Graphics/Material.h>
|
||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
||||
|
||||
namespace {
|
||||
class TestComponent final : public Component {
|
||||
@@ -189,6 +191,29 @@ namespace {
|
||||
"RigidBody legacy alias must not be registered");
|
||||
}
|
||||
|
||||
void testLineRenderingManager()
|
||||
{
|
||||
auto& lineManager = LineRenderingManager::GetInstance();
|
||||
lineManager.ClearLines();
|
||||
|
||||
lineManager.SubmitLine(
|
||||
glm::vec3{0.0f},
|
||||
glm::vec3{1.0f, 0.0f, 0.0f},
|
||||
glm::vec4{1.0f, 0.0f, 0.0f, 1.0f});
|
||||
lineManager.SubmitLine(Line{
|
||||
.start = glm::vec3{1.0f},
|
||||
.end = glm::vec3{2.0f},
|
||||
.color = glm::vec4{0.0f, 1.0f, 0.0f, 1.0f},
|
||||
});
|
||||
|
||||
check(lineManager.GetLines().size() == 2,
|
||||
"line rendering manager must retain submitted lines");
|
||||
|
||||
lineManager.ClearLines();
|
||||
check(lineManager.GetLines().empty(),
|
||||
"line rendering manager must clear transient lines");
|
||||
}
|
||||
|
||||
void testAssetPathValidation()
|
||||
{
|
||||
const auto root = std::filesystem::temp_directory_path() / "destrum_asset_test";
|
||||
@@ -459,6 +484,65 @@ namespace {
|
||||
check(!AssetReference::fromCacheKey("CharacterMedium.fbx").has_value(),
|
||||
"plain cache names must not be treated as file-backed assets");
|
||||
}
|
||||
|
||||
class TriggerTracker final : public Component {
|
||||
public:
|
||||
explicit TriggerTracker(GameObject& owner)
|
||||
: Component(owner, "TriggerTracker") {}
|
||||
|
||||
void Update(float) override {}
|
||||
std::string GetTypeName() const override { return "TriggerTracker"; }
|
||||
|
||||
void OnTriggerEnter(GameObject* other) override {
|
||||
++enterCount;
|
||||
lastOther = other;
|
||||
enterObjectName = other ? other->GetName() : "null";
|
||||
}
|
||||
|
||||
void OnTriggerExit(GameObject* other) override {
|
||||
++exitCount;
|
||||
}
|
||||
|
||||
int enterCount{0};
|
||||
int exitCount{0};
|
||||
GameObject* lastOther{nullptr};
|
||||
std::string enterObjectName;
|
||||
};
|
||||
|
||||
void testTriggerZone()
|
||||
{
|
||||
Scene& scene = SceneManager::GetInstance().CreateScene("trigger-test");
|
||||
|
||||
// Trigger box at origin (Static rigidbody + BoxCollider as trigger).
|
||||
GameObject* triggerObj = scene.CreateGameObject("TriggerZone");
|
||||
triggerObj->AddComponent<BoxCollider>(glm::vec3{3.0f, 3.0f, 3.0f});
|
||||
triggerObj->GetComponent<BoxCollider>()->SetTrigger(true);
|
||||
auto* triggerRb = triggerObj->AddComponent<Rigidbody>();
|
||||
triggerRb->SetType(RigidbodyType::Static);
|
||||
auto* tracker = triggerObj->AddComponent<TriggerTracker>();
|
||||
|
||||
// Falling sphere above the trigger.
|
||||
GameObject* sphere = scene.CreateGameObject("FallingSphere");
|
||||
sphere->AddComponent<SphereCollider>(0.5f);
|
||||
sphere->AddComponent<Rigidbody>();
|
||||
sphere->GetTransform().SetWorldPosition({0.0f, 8.0f, 0.0f});
|
||||
|
||||
scene.CommitPendingAdditions();
|
||||
|
||||
// Run physics for enough steps that the sphere falls into the trigger.
|
||||
for (int step = 0; step < 120; ++step) {
|
||||
scene.FixedUpdate(1.0f / 60.0f);
|
||||
}
|
||||
|
||||
check(tracker->enterCount >= 1,
|
||||
"falling sphere must trigger OnTriggerEnter at least once");
|
||||
check(tracker->exitCount >= 1,
|
||||
"falling sphere passing through trigger must exit");
|
||||
check(tracker->enterObjectName == "FallingSphere",
|
||||
"OnTriggerEnter must report the correct entering object");
|
||||
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
@@ -469,6 +553,7 @@ int main()
|
||||
testEventRemoval();
|
||||
testComponentRemoval();
|
||||
testEngineComponentRegistration();
|
||||
testLineRenderingManager();
|
||||
testAssetPathValidation();
|
||||
testLegacySceneLoad();
|
||||
testSceneLoadRollback();
|
||||
@@ -478,6 +563,7 @@ int main()
|
||||
testAnimatorAssetReferences();
|
||||
testSimpleColorMaterial();
|
||||
testAssetReferenceCacheKeys();
|
||||
testTriggerZone();
|
||||
std::cout << "destrum tests passed\n";
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user