From 5029aad8f261796d5cb6c278d8bbd45886abb5c3 Mon Sep 17 00:00:00 2001 From: tovjemam Date: Tue, 9 Sep 2025 01:01:03 +0200 Subject: [PATCH] Add debug minimap --- src/app.cpp | 8 +++ src/app.hpp | 1 + src/game/entity.cpp | 102 ++++++++++++++++++++++++++++++ src/game/entity.hpp | 13 ++++ src/game/sector.cpp | 125 ++++++++++++++++++++++++++++++++++++- src/game/sector.hpp | 20 +++++- src/game/world.cpp | 22 ++++++- src/game/world.hpp | 3 + src/gfx/renderer.cpp | 113 +++++++++++++++++++++++++++++++++ src/gfx/renderer.hpp | 5 ++ src/gfx/shader.cpp | 1 + src/gfx/shader.hpp | 1 + src/gfx/shader_sources.cpp | 31 +++++++++ src/gfx/shader_sources.hpp | 3 + 14 files changed, 444 insertions(+), 4 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index d3e2d37..aaf58d7 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -92,6 +92,14 @@ void App::Frame() delta_time = 0.1f; // Cap delta time to avoid large jumps } + game::PlayerInputFlags new_input = input_ & ~prev_input_; + prev_input_ = input_; + + if (new_input & game::PI_DEBUG1) + { + renderer_.show_minimap = !renderer_.show_minimap; + } + float aspect = static_cast(viewport_size_.x) / static_cast(viewport_size_.y); renderer_.Begin(viewport_size_.x, viewport_size_.y); diff --git a/src/app.hpp b/src/app.hpp index 65f5170..a6b85ab 100644 --- a/src/app.hpp +++ b/src/app.hpp @@ -12,6 +12,7 @@ class App float time_ = 0.0f; glm::ivec2 viewport_size_ = { 800, 600 }; game::PlayerInputFlags input_ = 0; + game::PlayerInputFlags prev_input_ = 0; float prev_time_ = 0.0f; diff --git a/src/game/entity.cpp b/src/game/entity.cpp index 5199d2c..0f3852a 100644 --- a/src/game/entity.cpp +++ b/src/game/entity.cpp @@ -194,6 +194,96 @@ void game::Entity::UpdateLights() lights_valid_ = true; } +const gfx::VertexArray& game::Entity::GetDebugWireVA() +{ + if (!debug_wire_va_) + { + CreateDebugWireVA(); + } + + return *debug_wire_va_; +} + +void game::Entity::CreateDebugWireVA() +{ + const int segments = 8; + + struct CapsuleVertex + { + glm::vec3 pos; + uint32_t color; + }; + + std::vector verts; + std::vector indices; + + float half_height = capsule_.height * 0.5f; + float radius = capsule_.radius; + + const int num_half_circles = 8; + const uint32_t color = 0xFFFFFFFF; + + auto AddArc = [&](size_t axis, const glm::vec3 offset, size_t start, size_t end) + { + size_t num_verts = end - start + 1; + + for (size_t i = 0; i < num_verts; ++i) + { + size_t p = start + i; + float angle = p * glm::two_pi() / segments; + glm::vec2 circle = glm::vec2(glm::cos(angle), glm::sin(angle)) * radius; + + if (axis == 0) + verts.push_back({ glm::vec3(0.0f, circle.x, circle.y) + offset, color }); + else if (axis == 1) + verts.push_back({ glm::vec3(circle.x, 0.0f, circle.y) + offset, color }); + else + verts.push_back({ glm::vec3(circle.x, circle.y, 0.0f) + offset, color }); + + if (i > 0) + { + indices.push_back(verts.size() - 2); + indices.push_back(verts.size() - 1); + } + } + }; + + glm::vec3 offset_top(0.0f, 0.0f, half_height); + AddArc(0, offset_top, 0, 4); + AddArc(1, offset_top, 0, 4); + AddArc(2, offset_top, 0, 8); + + glm::vec3 offset_bottom(0.0f, 0.0f, -half_height); + AddArc(0, offset_bottom, 4, 8); + AddArc(1, offset_bottom, 4, 8); + AddArc(2, offset_bottom, 0, 8); + + for (size_t i = 0; i < 4; i++) + { + glm::vec3 top(0.0f, 0.0f, half_height); + + float o = ((i % 2) ? radius : -radius); + + if (i < 2) + top.y = o; + else + top.x = o; + + glm::vec3 bottom = top; bottom.z = -half_height; + + verts.push_back({ top, color }); + verts.push_back({ bottom, color }); + + indices.push_back(verts.size() - 2); + indices.push_back(verts.size() - 1); + } + + + debug_wire_va_ = std::make_unique(gfx::VA_POSITION | gfx::VA_COLOR, gfx::VF_CREATE_EBO); + debug_wire_va_->SetVBOData(verts.data(), verts.size() * sizeof(CapsuleVertex)); + debug_wire_va_->SetIndices(indices.data(), indices.size()); +} + void game::Entity::CreateOtherOccurence(const Portal& portal) { other_occu_.reset(); @@ -231,6 +321,8 @@ game::EntityOccurrence::EntityOccurrence(Entity* entity, const CreateOccurrenceP // compute inverse normalized basis inv_normal_basis_ = glm::inverse(normal_basis_); + + sector_->AddEntityOccurrence(this); } bool game::EntityOccurrence::Sweep(const glm::vec3& target_position, float& hit_fraction, glm::vec3& hit_normal, const Portal** hit_portal) @@ -260,3 +352,13 @@ const game::LightInfluences& game::EntityOccurrence::GetLights() entity_->UpdateLights(); return lights_; } + +bool game::EntityOccurrence::IsPrimary() const +{ + return &entity_->GetOccurrence() == this; +} + +game::EntityOccurrence::~EntityOccurrence() +{ + sector_->RemoveEntityOccurrence(this); +} diff --git a/src/game/entity.hpp b/src/game/entity.hpp index 30f00af..c146f12 100644 --- a/src/game/entity.hpp +++ b/src/game/entity.hpp @@ -65,6 +65,13 @@ namespace game const glm::mat3& GetBasis() const { return basis_; } const glm::mat3& GetInvBasis() const { return inv_basis_; } + + Entity& GetEntity() { return *entity_; } + const Entity& GetEntity() const { return *entity_; } + + bool IsPrimary() const; + + ~EntityOccurrence(); }; class Entity @@ -83,6 +90,8 @@ namespace game glm::vec3 light_trace_offset_; // In entity space bool lights_valid_; + std::unique_ptr debug_wire_va_; + public: Entity(World* world, size_t sector_idx, const CapsuleShape& capsule_shape, const glm::vec3& position); @@ -99,12 +108,16 @@ namespace game const CapsuleShape& GetCapsuleShape() const { return capsule_; } EntityOccurrence& GetOccurrence() { return *occu_; } + const gfx::VertexArray& GetDebugWireVA(); + protected: virtual std::unique_ptr CreateOccurrence(const CreateOccurrenceParams& params) { return std::make_unique(this, params); } + virtual void CreateDebugWireVA(); + private: void CreateOtherOccurence(const Portal& portal); diff --git a/src/game/sector.cpp b/src/game/sector.cpp index 379afd0..cf986dc 100644 --- a/src/game/sector.cpp +++ b/src/game/sector.cpp @@ -1,6 +1,10 @@ #include "sector.hpp" #include +#include +#include +#include +#include #include #include @@ -13,7 +17,9 @@ game::Sector::Sector(World* world, size_t idx, std::shared_ptrGetMesh()), bt_col_dispatcher_(&bt_col_cfg_), - bt_world_(&bt_col_dispatcher_, &bt_broad_phase_, &bt_col_cfg_) + bt_world_(&bt_col_dispatcher_, &bt_broad_phase_, &bt_col_cfg_), + + minimap_va_(gfx::VA_POSITION | gfx::VA_COLOR, gfx::VF_CREATE_EBO) { // Create Bullet collision mesh bt_mesh_col_obj_.setCollisionShape(mesh_->GetCollisionMesh()->GetShape()); @@ -132,7 +138,7 @@ static void LinkPortal(game::Portal& p1, game::Portal& p2, int flags) { p1.tr_scale = p2.scale / p1.scale; } -void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2, int flags) +void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2, int flags, uint32_t color) { Portal& p1 = s1.portals_[idx1]; Portal& p2 = s2.portals_[idx2]; @@ -143,10 +149,12 @@ void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2, } LinkPortal(p1, p2, flags); + p1.color = color; if (&p1 != &p2) { LinkPortal(p2, p1, flags); + p2.color = color; } } @@ -444,6 +452,119 @@ void game::Sector::Bake() { GenerateAllLights(); BakeLightmap(); + CreateMinimapVA(); +} + +void game::Sector::AddEntityOccurrence(EntityOccurrence* occu) +{ + occus_.insert(occu); +} + +void game::Sector::RemoveEntityOccurrence(EntityOccurrence* occu) +{ + occus_.erase(occu); +} + +struct MinimapVertex +{ + glm::vec3 position; + uint32_t color; +}; + +void game::Sector::CreateMinimapVA() +{ + auto verts = mesh_->GetVertices(); + auto tris = mesh_->GetTriangles(); + + std::vector mm_verts; + std::vector mm_indices; + std::map vert_map; // Maps original vertex index to minimap vertex index + std::set> edges; // Set of edges to avoid duplicates + + for (const assets::MeshTriangle& tri : tris) + { + for (size_t i = 0; i < 3; i++) + { + size_t v_idx[2] = { tri.vert[i], tri.vert[(i + 1) % 3] }; + + if (v_idx[0] > v_idx[1]) + { + std::swap(v_idx[0], v_idx[1]); // Smaller index first + } + + auto edge = std::make_pair(v_idx[0], v_idx[1]); + if (edges.find(edge) != edges.end()) + { + continue; // Edge already in + } + + edges.insert(edge); + + size_t mm_v_idx[2]; + for (size_t j = 0; j < 2; j++) + { + auto it = vert_map.find(v_idx[j]); + if (it != vert_map.end()) + { + mm_v_idx[j] = it->second; + } + else + { + const auto& v = verts[v_idx[j]]; + + MinimapVertex mmv; + mmv.position = v.pos; + mmv.color = 0xFF444444; + mm_v_idx[j] = mm_verts.size(); + + mm_verts.push_back(mmv); + vert_map[v_idx[j]] = mm_v_idx[j]; + } + } + + // Insert edge indices + for (size_t j = 0; j < 2; j++) + { + mm_indices.push_back(static_cast(mm_v_idx[j])); + } + } + } + + // Add portal edges + for (const Portal& portal : portals_) + { + if (!portal.link) + { + continue; + } + + size_t first = mm_verts.size(); + for (size_t i = 0; i < 4; i++) + { + MinimapVertex mmv; + mmv.position = portal.verts[i]; + mmv.color = portal.color; + mm_verts.push_back(mmv); + } + + // Add edges + for (size_t i = 0; i < 4; i++) + { + mm_indices.push_back(first + i); + mm_indices.push_back(first + ((i + 1) % 4)); + } + } + + + minimap_va_.SetVBOData(mm_verts.data(), mm_verts.size() * sizeof(MinimapVertex)); + minimap_va_.SetIndices(mm_indices.data(), mm_indices.size()); + + // Compute AABB + for (size_t i = 0; i < mm_verts.size(); i++) + { + aabb_.AddPoint(mm_verts[i].position); + } + } void game::Sector::GenerateAllLights() diff --git a/src/game/sector.hpp b/src/game/sector.hpp index f0cb62d..902eab6 100644 --- a/src/game/sector.hpp +++ b/src/game/sector.hpp @@ -3,6 +3,7 @@ #include "assets/sectordef.hpp" #include #include +#include #include namespace game @@ -46,6 +47,8 @@ namespace game std::unique_ptr bt_col_shape; // Bullet collision shape std::unique_ptr bt_col_obj; // Bullet collision object CollisionObjectData col_data; + + uint32_t color; }; enum LinkPortalFlags @@ -70,6 +73,8 @@ namespace game glm::vec3 sun_color; }; + class EntityOccurrence; + class Sector { World* world_; @@ -94,6 +99,11 @@ namespace game btDbvtBroadphase bt_broad_phase_; btCollisionWorld bt_world_; + gfx::VertexArray minimap_va_; + collision::AABB3 aabb_; + + std::set occus_; + public: Sector(World* world, size_t idx, std::shared_ptr def); @@ -114,7 +124,10 @@ namespace game const Portal& GetPortal(size_t idx) const { return portals_[idx]; } const size_t GetNumPortals() const { return portals_.size(); } - static void LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2, int flags); + const collision::AABB3& GetAABB() const { return aabb_; } + const gfx::VertexArray& GetMinimapVA() const { return minimap_va_; } + + static void LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2, int flags, uint32_t color = 0); bool SweepCapsule( const btCapsuleShapeZ& capsule, @@ -132,9 +145,14 @@ namespace game void Bake(); + void AddEntityOccurrence(EntityOccurrence* occu); + void RemoveEntityOccurrence(EntityOccurrence* occu); + const std::set& GetEntityOccurrences() const { return occus_; } + private: void GenerateAllLights(); void BakeLightmap(); + void CreateMinimapVA(); }; diff --git a/src/game/world.cpp b/src/game/world.cpp index c84338f..edc47b0 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -13,6 +13,23 @@ size_t game::World::AddSector(std::shared_ptr def) return idx; } +static const uint32_t PORTAL_COLORS[] = { + 0xFF0000FF, // Red + 0xFF00FF00, // Green + 0xFFFF0000, // Blue + 0xFF00FFFF, // Yellow + 0xFFFF00FF, // Magenta + 0xFFFFFF00, // Cyan + 0xFFFFFFFF, // White + 0xFFFFA500, // Orange + 0xFF800080, // Purple + 0xFF808080, // Gray + 0xFF008000, // Dark Green + 0xFF000080, // Dark Blue + 0xFF800000, // Dark Red + 0xFF808000, // Olive +}; + void game::World::LinkPortals(size_t sector1, const std::string& portal_name1, size_t sector2, const std::string& portal_name2, int flags) { Sector& s1 = *sectors_[sector1]; @@ -25,9 +42,12 @@ void game::World::LinkPortals(size_t sector1, const std::string& portal_name1, s throw std::runtime_error("Invalid portal names for linking"); } - Sector::LinkPortals(s1, p1, s2, p2, flags); + size_t color_idx = (next_portal_color_++) % (sizeof(PORTAL_COLORS) / sizeof(PORTAL_COLORS[0])); + + Sector::LinkPortals(s1, p1, s2, p2, flags, PORTAL_COLORS[color_idx]); } + void game::World::Bake() { for (auto& sector : sectors_) diff --git a/src/game/world.hpp b/src/game/world.hpp index 63ed4c7..1d136d4 100644 --- a/src/game/world.hpp +++ b/src/game/world.hpp @@ -16,6 +16,8 @@ namespace game float time_ = 0.0f; // Game time + size_t next_portal_color_ = 0; + public: World(); @@ -35,6 +37,7 @@ namespace game void Update(float dt); + size_t GetNumSectors() const { return sectors_.size(); } Sector& GetSector(size_t idx) { return *sectors_[idx]; } const Sector& GetSector(size_t idx) const { return *sectors_[idx]; } diff --git a/src/gfx/renderer.cpp b/src/gfx/renderer.cpp index ee44d94..c057700 100644 --- a/src/gfx/renderer.cpp +++ b/src/gfx/renderer.cpp @@ -13,6 +13,7 @@ gfx::Renderer::Renderer() ShaderSources::MakeShader(portal_shader_.shader, SS_PORTAL_VERT, SS_PORTAL_FRAG); ShaderSources::MakeShader(mesh_shader_.shader, SS_MESH_VERT, SS_MESH_FRAG); ShaderSources::MakeShader(skel_mesh_shader_.shader, SS_SKEL_MESH_VERT, SS_SKEL_MESH_FRAG); + ShaderSources::MakeShader(solid_shader_, SS_SOLID_VERT, SS_SOLID_FRAG); proj_ = glm::mat4(1.0f); // Initialize projection matrix to identity SetupPortalVAO(); @@ -36,6 +37,9 @@ void gfx::Renderer::Begin(size_t width, size_t height) glViewport(0, 0, width, height); glClearStencil(0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + screen_width_ = width; + screen_height_ = height; } void gfx::Renderer::DrawWorld( @@ -131,6 +135,10 @@ void gfx::Renderer::DrawWorld( } } + if (show_minimap) + { + DrawMinimap(world, sector_idx); + } } @@ -462,3 +470,108 @@ void gfx::Renderer::DrawMeshInstance(const DrawSectorParams& params, game::MeshI } } +void gfx::Renderer::DrawMinimap(const game::World& world, size_t current_sector_idx) +{ + const size_t MAX_SECTORS = 32; + + size_t num_sectors = world.GetNumSectors(); + + if (num_sectors > MAX_SECTORS) + { + num_sectors = MAX_SECTORS; // Limit number of sectors to draw + } + + const float margin = 1.0f; + glm::vec2 mm_size(margin, 0.0f); + + for (size_t i = 0; i < num_sectors; ++i) + { + const game::Sector& sector = world.GetSector(i); + const collision::AABB3& aabb = sector.GetAABB(); + glm::vec3 size = aabb.max - aabb.min; + + //if (size.x > size.y) + //{ + // std::swap(size.x, size.y); // Will be rotated + //} + + mm_size.y = glm::max(mm_size.y, size.y + margin * 2.0f); + mm_size.x += size.x + margin; + } + + glm::vec2 screen_size = glm::vec2((float)screen_width_, (float)screen_height_); + float mm_scale = 24.0f; // pixels / m + + glUseProgram(solid_shader_->GetId()); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + //glm::vec2 global_offset(-mm_size.x + screen_size.x * 0.5f / mm_scale, -mm_size.y + screen_size.y * 0.5f / mm_scale); + glm::vec2 global_offset = screen_size / mm_scale * 0.5f - mm_size; + + for (size_t i = 0; i < num_sectors; ++i) + { + const game::Sector& sector = world.GetSector(i); + const VertexArray& va = sector.GetMinimapVA(); + const collision::AABB3& aabb = sector.GetAABB(); + glm::vec3 size = aabb.max - aabb.min; + + glm::mat4 view(1.0f); + + glm::vec2 ortho_scale = mm_scale / screen_size * 2.0f; + + glm::vec2 ortho_offset(-aabb.min.x, -aabb.min.y); + ortho_offset.y += (mm_size.y - size.y) * 0.5f; // Center vertically + //glm::vec2 ortho_offset(10.0f); + //ortho_offset += size * 0.5f; + ortho_offset += global_offset; + ortho_offset *= ortho_scale; + + glm::mat4 proj{ + ortho_scale.x, 0.0f, 0.0f, 0.0f, + 0.0f, ortho_scale.y, 0.0f, 0.0f, + 0.0f, 0.0f, 0.01f, 0.0f, + ortho_offset.x, ortho_offset.y, 0.0f, 1.0f + }; + + glm::mat4 view_proj = proj; + + + + glBindVertexArray(va.GetVAOId()); + glUniformMatrix4fv(solid_shader_->U(gfx::SU_VIEW_PROJ), 1, GL_FALSE, &view_proj[0][0]); + glUniform4fv(solid_shader_->U(gfx::SU_COLOR), 1, &glm::vec4(1.0f)[0]); + glDrawElements(GL_LINES, va.GetNumIndices(), GL_UNSIGNED_INT, 0); + + for (const auto& occus = sector.GetEntityOccurrences(); const auto& occu : occus) + { + glm::mat4 model = occu->GetBasis(); + model[3] = glm::vec4(occu->GetPosition(), 1.0f); + + const VertexArray& va = occu->GetEntity().GetDebugWireVA(); + + glm::mat4 mvp = view_proj * model; + + glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); + if (!occu->IsPrimary()) + { + color *= 0.5f; + color.a = 1.0f; + } + + glBindVertexArray(va.GetVAOId()); + glUniformMatrix4fv(solid_shader_->U(gfx::SU_VIEW_PROJ), 1, GL_FALSE, &mvp[0][0]); + glUniform4fv(solid_shader_->U(gfx::SU_COLOR), 1, &color[0]); + glDrawElements(GL_LINES, va.GetNumIndices(), GL_UNSIGNED_INT, 0); + } + + + + global_offset.x += size.x + margin; + } + +} + diff --git a/src/gfx/renderer.hpp b/src/gfx/renderer.hpp index 906f377..7b24536 100644 --- a/src/gfx/renderer.hpp +++ b/src/gfx/renderer.hpp @@ -45,11 +45,14 @@ namespace gfx float aspect, float fov); + bool show_minimap = false; + private: SectorShader sector_shader_; SectorShader portal_shader_; SectorShader mesh_shader_; SectorShader skel_mesh_shader_; + std::unique_ptr solid_shader_; std::shared_ptr portal_vao_; void SetupPortalVAO(); @@ -60,6 +63,7 @@ namespace gfx // If the distance is smaller, portal plane is shifted to avoid clipping by near plane float min_portal_distance_; + size_t screen_width_, screen_height_; glm::mat4 proj_; size_t last_sector_id; const Shader* current_shader_; @@ -76,6 +80,7 @@ namespace gfx void DrawMeshInstance(const DrawSectorParams& params, game::MeshInstance& mesh_instance, const glm::mat4& transform, const game::LightInfluences& lights); + void DrawMinimap(const game::World& world, size_t current_sector_idx); }; } \ No newline at end of file diff --git a/src/gfx/shader.cpp b/src/gfx/shader.cpp index 2433134..2fc44f6 100644 --- a/src/gfx/shader.cpp +++ b/src/gfx/shader.cpp @@ -16,6 +16,7 @@ static const char* const s_uni_names[] = { "u_light_positions", // SU_LIGHT_POSITIONS "u_light_colors_rs", // SU_LIGHT_COLORS_RS "u_ambient_light", // SU_AMBIENT_LIGHT + "u_color" // SU_COLOR }; // Vytvori shader z daneho zdroje diff --git a/src/gfx/shader.hpp b/src/gfx/shader.hpp index 3e5d4c8..d4e7f8a 100644 --- a/src/gfx/shader.hpp +++ b/src/gfx/shader.hpp @@ -21,6 +21,7 @@ namespace gfx SU_LIGHT_POSITIONS, SU_LIGHT_COLORS_RS, SU_AMBIENT_LIGHT, + SU_COLOR, SU_COUNT }; diff --git a/src/gfx/shader_sources.cpp b/src/gfx/shader_sources.cpp index da617bc..d5e934d 100644 --- a/src/gfx/shader_sources.cpp +++ b/src/gfx/shader_sources.cpp @@ -287,6 +287,37 @@ void main() { )GLSL", +// SS_SOLID_VERT +SHADER_HEADER +R"GLSL( +layout (location = 0) in vec3 a_pos; +layout (location = 2) in vec4 a_color; + +uniform mat4 u_view_proj; +uniform vec4 u_color; + +out vec4 v_color; + +void main() { + gl_Position = u_view_proj * vec4(a_pos, 1.0); + v_color = a_color * u_color; +} +)GLSL", + +// SS_SOLID_FRAG +SHADER_HEADER +R"GLSL( +in vec4 v_color; + +layout (location = 0) out vec4 o_color; + +void main() { + o_color = v_color; +} + +)GLSL", + + }; // Vrati zdrojovy kod shaderu diff --git a/src/gfx/shader_sources.hpp b/src/gfx/shader_sources.hpp index b7f44a3..eb3c859 100644 --- a/src/gfx/shader_sources.hpp +++ b/src/gfx/shader_sources.hpp @@ -19,6 +19,9 @@ namespace gfx SS_SKEL_MESH_VERT, SS_SKEL_MESH_FRAG, + SS_SOLID_VERT, + SS_SOLID_FRAG, + }; class ShaderSources