feat: add line renderer / visualisations for box collidors as a test

This commit is contained in:
2026-08-14 03:16:04 +02:00
parent 7f65a151b5
commit 287e5c6885
12 changed files with 596 additions and 69 deletions
+2
View File
@@ -37,12 +37,14 @@ set(SRC_FILES
"src/Graphics/Managers/MemoryManager.cpp" "src/Graphics/Managers/MemoryManager.cpp"
"src/Graphics/Managers/FrameManager.cpp" "src/Graphics/Managers/FrameManager.cpp"
"src/Graphics/Managers/ImageManager.cpp" "src/Graphics/Managers/ImageManager.cpp"
"src/Graphics/Managers/LineRenderingManager.cpp"
"src/Graphics/Resources/GPUImage.cpp" "src/Graphics/Resources/GPUImage.cpp"
"src/Graphics/Resources/NBuffer.cpp" "src/Graphics/Resources/NBuffer.cpp"
"src/Graphics/Resources/Cubemap.cpp" "src/Graphics/Resources/Cubemap.cpp"
"src/Graphics/Pipelines/MeshPipeline.cpp" "src/Graphics/Pipelines/MeshPipeline.cpp"
"src/Graphics/Pipelines/LineRenderingPass.cpp"
"src/Graphics/Pipelines/SkyboxPipeline.cpp" "src/Graphics/Pipelines/SkyboxPipeline.cpp"
"src/Graphics/Pipelines/SkinningPipeline.cpp" "src/Graphics/Pipelines/SkinningPipeline.cpp"
"src/Graphics/Pipelines/ImguiPass.cpp" "src/Graphics/Pipelines/ImguiPass.cpp"
+9
View File
@@ -0,0 +1,9 @@
#version 460
layout (location = 0) in vec4 inColor;
layout (location = 0) out vec4 outFragColor;
void main()
{
outFragColor = inColor;
}
+16
View File
@@ -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,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,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
@@ -4,6 +4,7 @@
#include <glm/vec3.hpp> #include <glm/vec3.hpp>
#include <destrum/Graphics/Camera.h> #include <destrum/Graphics/Camera.h>
#include <destrum/Graphics/Pipelines/LineRenderingPass.h>
#include <destrum/Graphics/Pipelines/MeshPipeline.h> #include <destrum/Graphics/Pipelines/MeshPipeline.h>
#include <destrum/Graphics/ids.h> #include <destrum/Graphics/ids.h>
#include <destrum/Graphics/MeshDrawCommand.h> #include <destrum/Graphics/MeshDrawCommand.h>
@@ -111,6 +112,7 @@ private:
std::unique_ptr<MeshPipeline> meshPipeline; std::unique_ptr<MeshPipeline> meshPipeline;
std::unique_ptr<SkyboxPipeline> skyboxPipeline; std::unique_ptr<SkyboxPipeline> skyboxPipeline;
std::unique_ptr<LineRenderingPass> lineRenderingPass;
std::unique_ptr<SkinningPipeline> skinningPipeline; std::unique_ptr<SkinningPipeline> skinningPipeline;
bool initialized{false}; bool initialized{false};
@@ -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,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;
}
+19
View File
@@ -1,5 +1,6 @@
#include <destrum/Graphics/Renderer.h> #include <destrum/Graphics/Renderer.h>
#include <destrum/Graphics/Managers/LineRenderingManager.h>
#include <destrum/Graphics/Util.h> #include <destrum/Graphics/Util.h>
#include <algorithm> #include <algorithm>
@@ -36,6 +37,9 @@ void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::
skinningPipeline = std::make_unique<SkinningPipeline>(); skinningPipeline = std::make_unique<SkinningPipeline>();
skinningPipeline->init(gfxDevice); skinningPipeline->init(gfxDevice);
lineRenderingPass = std::make_unique<LineRenderingPass>();
lineRenderingPass->init(gfxDevice, drawImageFormat);
GameState::GetInstance().SetRenderer(this); GameState::GetInstance().SetRenderer(this);
initialized = true; initialized = true;
} catch (...) { } catch (...) {
@@ -194,6 +198,17 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
vkCmdEndRendering(cmd); vkCmdEndRendering(cmd);
} }
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "LineRenderingPass::draw");
lineRenderingPass->draw(
cmd,
gfxDevice,
drawImage,
camera,
LineRenderingManager::GetInstance());
}
// vkutil::cmdEndLabel(cmd); // vkutil::cmdEndLabel(cmd);
} }
@@ -208,6 +223,9 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
if (skinningPipeline) if (skinningPipeline)
skinningPipeline->cleanup(gfxDevice); skinningPipeline->cleanup(gfxDevice);
if (lineRenderingPass)
lineRenderingPass->cleanup(gfxDevice);
if (skyboxPipeline) if (skyboxPipeline)
skyboxPipeline->cleanup(device); skyboxPipeline->cleanup(device);
@@ -228,6 +246,7 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
meshPipeline.reset(); meshPipeline.reset();
skyboxPipeline.reset(); skyboxPipeline.reset();
skinningPipeline.reset(); skinningPipeline.reset();
lineRenderingPass.reset();
pendingMaterialUploads.clear(); pendingMaterialUploads.clear();
meshDrawCommands.clear(); meshDrawCommands.clear();
sortedMeshDrawCommands.clear(); sortedMeshDrawCommands.clear();
+8
View File
@@ -21,7 +21,14 @@ public:
void customFixedUpdate(float dt) override; void customFixedUpdate(float dt) override;
void onWindowResize(int newWidth, int newHeight) override; void onWindowResize(int newWidth, int newHeight) override;
private: 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)}; Camera camera{glm::vec3(0.f, 0.f, -5.f), glm::vec3(0, 1, 0)};
MeshID sphereMesh; MeshID sphereMesh;
@@ -34,6 +41,7 @@ private:
char scenePath[512]{"scenes/debug_scene.json"}; char scenePath[512]{"scenes/debug_scene.json"};
char newSceneName[128]{"EmptyScene"}; char newSceneName[128]{"EmptyScene"};
std::string sceneStatus; std::string sceneStatus;
bool renderBoxColliders{false};
}; };
#endif //LIGHTKEEPER_H #endif //LIGHTKEEPER_H
+181 -69
View File
@@ -1,7 +1,9 @@
#include "Lightkeeper.h" #include "Lightkeeper.h"
#include <array>
#include <destrum/FS/AssetFS.h> #include <destrum/FS/AssetFS.h>
#include <destrum/Assets/AssetManager.h> #include <destrum/Assets/AssetManager.h>
#include <destrum/Graphics/Managers/LineRenderingManager.h>
#include "glm/gtx/transform.hpp" #include "glm/gtx/transform.hpp"
#include "spdlog/spdlog.h" #include "spdlog/spdlog.h"
#include <destrum/Components/Physics/Rigidbody.h> #include <destrum/Components/Physics/Rigidbody.h>
@@ -446,90 +448,199 @@ void LightKeeper::customUpdate(float dt)
SceneManager::GetInstance().Update(dt); SceneManager::GetInstance().Update(dt);
SceneManager::GetInstance().LateUpdate(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)) if (InputManager::GetInstance().WasKeyPressed(SDL_SCANCODE_1))
{ {
renderer.setRenderWireframe(!renderer.getRenderWireframe()); renderer.setRenderWireframe(!renderer.getRenderWireframe());
} }
ImGui::Begin("Test"); if (m_params.showDebugUi) {
if (ImGui::Button("SPawn ball")) ImGui::Begin("Test");
{ if (ImGui::Button("SPawn ball"))
auto sphere = SceneManager::GetInstance().GetCurrentScene().CreateGameObject("Sphere"); {
sphere->AddComponent<SphereCollider>(1.5f); auto sphere = SceneManager::GetInstance().GetCurrentScene().CreateGameObject("Sphere");
auto rb = sphere->AddComponent<Rigidbody>(); sphere->AddComponent<SphereCollider>(1.5f);
rb->SetMass(1000); auto rb = sphere->AddComponent<Rigidbody>();
rb->SetMass(1000);
auto meshRenderComp = sphere->AddComponent<MeshRendererComponent>(); auto meshRenderComp = sphere->AddComponent<MeshRendererComponent>();
meshRenderComp->SetMaterialID(sphereMaterial); meshRenderComp->SetMaterialID(sphereMaterial);
meshRenderComp->SetMeshID(sphereMesh); meshRenderComp->SetMeshID(sphereMesh);
sphere->GetTransform().SetWorldPosition((std::rand() % 2) - 0.5f, 100, (std::rand() % 2) - 0.5f); sphere->GetTransform().SetWorldPosition((std::rand() % 2) - 0.5f, 100, (std::rand() % 2) - 0.5f);
sphere->GetTransform().SetWorldScale(glm::vec3{0.015f}); sphere->GetTransform().SetWorldScale(glm::vec3{0.015f});
SceneManager::GetInstance().GetCurrentScene().GetPhysics().RefreshGameObject(*sphere); SceneManager::GetInstance().GetCurrentScene().GetPhysics().RefreshGameObject(*sphere);
}
ImGui::End();
} }
ImGui::End(); }
ImGui::Begin("Scene Debug"); void LightKeeper::drawDebugMenuBar()
ImGui::InputText("Scene path", scenePath, sizeof(scenePath)); {
ImGui::InputText("New scene name", newSceneName, sizeof(newSceneName)); if (!ImGui::BeginMainMenuBar()) {
return;
}
if (ImGui::Button("Save Current Scene")) { if (ImGui::BeginMenu("File")) {
try { ImGui::TextDisabled("Scene");
const auto path = ResolveScenePath(scenePath, m_params.exeDir);
const auto parent = path.parent_path();
if (!parent.empty()) {
std::filesystem::create_directories(parent);
}
if (SceneSerializer::Save( ImGui::SetNextItemWidth(320.0f);
SceneManager::GetInstance().GetCurrentScene(), ImGui::InputText("Scene path", scenePath, sizeof(scenePath));
path)) {
sceneStatus = "Saved scene to " + path.string(); ImGui::SetNextItemWidth(320.0f);
} else { ImGui::InputText("New scene name", newSceneName, sizeof(newSceneName));
sceneStatus = "Failed to save scene to " + path.string();
} ImGui::Separator();
} catch (const std::exception& exception) {
sceneStatus = std::string{"Save failed: "} + exception.what(); 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::EndMenu();
}
ImGui::EndMainMenuBar();
}
void LightKeeper::saveCurrentScene()
{
try {
const auto path = ResolveScenePath(scenePath, m_params.exeDir);
const auto parent = path.parent_path();
if (!parent.empty()) {
std::filesystem::create_directories(parent);
}
if (SceneSerializer::Save(
SceneManager::GetInstance().GetCurrentScene(),
path)) {
sceneStatus = "Saved scene to " + path.string();
} else {
sceneStatus = "Failed to save scene to " + path.string();
}
} catch (const std::exception& exception) {
sceneStatus = std::string{"Save failed: "} + exception.what();
}
}
void LightKeeper::loadScene()
{
try {
const auto path = ResolveScenePath(scenePath, m_params.exeDir);
if (SceneSerializer::Load(
SceneManager::GetInstance().GetCurrentScene(),
path)) {
sceneStatus = "Loaded scene from " + path.string();
} else {
sceneStatus = "Failed to load scene from " + path.string();
}
} catch (const std::exception& exception) {
sceneStatus = std::string{"Load failed: "} + exception.what();
}
}
void LightKeeper::createEmptyScene()
{
try {
const std::string name = newSceneName[0] != '\0'
? newSceneName
: "EmptyScene";
auto& sceneManager = SceneManager::GetInstance();
const int newSceneIndex = sceneManager.GetSceneCount();
sceneManager.CreateScene(name);
sceneManager.SwitchScene(newSceneIndex);
sceneStatus = "Created and switched to scene '" + name + "'";
} catch (const std::exception& exception) {
sceneStatus = std::string{"New scene failed: "} + exception.what();
}
}
void LightKeeper::submitBoxColliderDebugLines()
{
if (!renderBoxColliders) {
return;
}
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::SameLine();
if (ImGui::Button("Load Scene")) {
try {
const auto path = ResolveScenePath(scenePath, m_params.exeDir);
if (SceneSerializer::Load(
SceneManager::GetInstance().GetCurrentScene(),
path)) {
sceneStatus = "Loaded scene from " + path.string();
} else {
sceneStatus = "Failed to load scene from " + path.string();
}
} catch (const std::exception& exception) {
sceneStatus = std::string{"Load failed: "} + exception.what();
}
}
if (ImGui::Button("New Empty Scene")) {
try {
const std::string name = newSceneName[0] != '\0'
? newSceneName
: "EmptyScene";
auto& sceneManager = SceneManager::GetInstance();
const int newSceneIndex = sceneManager.GetSceneCount();
sceneManager.CreateScene(name);
sceneManager.SwitchScene(newSceneIndex);
sceneStatus = "Created and switched to scene '" + name + "'";
} catch (const std::exception& exception) {
sceneStatus = std::string{"New scene failed: "} + exception.what();
}
}
if (!sceneStatus.empty()) {
ImGui::TextWrapped("%s", sceneStatus.c_str());
}
ImGui::End();
} }
void LightKeeper::customDraw() void LightKeeper::customDraw()
@@ -540,6 +651,7 @@ void LightKeeper::customDraw()
} }
renderer.beginDrawing(gfxDevice); renderer.beginDrawing(gfxDevice);
submitBoxColliderDebugLines();
const RenderContext ctx{ const RenderContext ctx{
.renderer = renderer, .renderer = renderer,
+25
View File
@@ -23,6 +23,7 @@
#include <destrum/Components/Animator.h> #include <destrum/Components/Animator.h>
#include <destrum/Components/Physics/Rigidbody.h> #include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/Graphics/Material.h> #include <destrum/Graphics/Material.h>
#include <destrum/Graphics/Managers/LineRenderingManager.h>
namespace { namespace {
class TestComponent final : public Component { class TestComponent final : public Component {
@@ -189,6 +190,29 @@ namespace {
"RigidBody legacy alias must not be registered"); "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() void testAssetPathValidation()
{ {
const auto root = std::filesystem::temp_directory_path() / "destrum_asset_test"; const auto root = std::filesystem::temp_directory_path() / "destrum_asset_test";
@@ -469,6 +493,7 @@ int main()
testEventRemoval(); testEventRemoval();
testComponentRemoval(); testComponentRemoval();
testEngineComponentRegistration(); testEngineComponentRegistration();
testLineRenderingManager();
testAssetPathValidation(); testAssetPathValidation();
testLegacySceneLoad(); testLegacySceneLoad();
testSceneLoadRollback(); testSceneLoadRollback();