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
+181 -69
View File
@@ -1,7 +1,9 @@
#include "Lightkeeper.h"
#include <array>
#include <destrum/FS/AssetFS.h>
#include <destrum/Assets/AssetManager.h>
#include <destrum/Graphics/Managers/LineRenderingManager.h>
#include "glm/gtx/transform.hpp"
#include "spdlog/spdlog.h"
#include <destrum/Components/Physics/Rigidbody.h>
@@ -446,90 +448,199 @@ void LightKeeper::customUpdate(float 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());
}
ImGui::Begin("Test");
if (ImGui::Button("SPawn ball"))
{
auto sphere = SceneManager::GetInstance().GetCurrentScene().CreateGameObject("Sphere");
sphere->AddComponent<SphereCollider>(1.5f);
auto rb = sphere->AddComponent<Rigidbody>();
rb->SetMass(1000);
if (m_params.showDebugUi) {
ImGui::Begin("Test");
if (ImGui::Button("SPawn ball"))
{
auto sphere = SceneManager::GetInstance().GetCurrentScene().CreateGameObject("Sphere");
sphere->AddComponent<SphereCollider>(1.5f);
auto rb = sphere->AddComponent<Rigidbody>();
rb->SetMass(1000);
auto meshRenderComp = sphere->AddComponent<MeshRendererComponent>();
meshRenderComp->SetMaterialID(sphereMaterial);
meshRenderComp->SetMeshID(sphereMesh);
auto meshRenderComp = sphere->AddComponent<MeshRendererComponent>();
meshRenderComp->SetMaterialID(sphereMaterial);
meshRenderComp->SetMeshID(sphereMesh);
sphere->GetTransform().SetWorldPosition((std::rand() % 2) - 0.5f, 100, (std::rand() % 2) - 0.5f);
sphere->GetTransform().SetWorldScale(glm::vec3{0.015f});
sphere->GetTransform().SetWorldPosition((std::rand() % 2) - 0.5f, 100, (std::rand() % 2) - 0.5f);
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");
ImGui::InputText("Scene path", scenePath, sizeof(scenePath));
ImGui::InputText("New scene name", newSceneName, sizeof(newSceneName));
void LightKeeper::drawDebugMenuBar()
{
if (!ImGui::BeginMainMenuBar()) {
return;
}
if (ImGui::Button("Save Current Scene")) {
try {
const auto path = ResolveScenePath(scenePath, m_params.exeDir);
const auto parent = path.parent_path();
if (!parent.empty()) {
std::filesystem::create_directories(parent);
}
if (ImGui::BeginMenu("File")) {
ImGui::TextDisabled("Scene");
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();
ImGui::SetNextItemWidth(320.0f);
ImGui::InputText("Scene path", scenePath, sizeof(scenePath));
ImGui::SetNextItemWidth(320.0f);
ImGui::InputText("New scene name", newSceneName, sizeof(newSceneName));
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::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()
@@ -540,6 +651,7 @@ void LightKeeper::customDraw()
}
renderer.beginDrawing(gfxDevice);
submitBoxColliderDebugLines();
const RenderContext ctx{
.renderer = renderer,