Add debug minimap

This commit is contained in:
tovjemam 2025-09-09 01:01:03 +02:00
parent 0b82d1b428
commit 5029aad8f2
14 changed files with 444 additions and 4 deletions

View File

@ -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<float>(viewport_size_.x) / static_cast<float>(viewport_size_.y);
renderer_.Begin(viewport_size_.x, viewport_size_.y);

View File

@ -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;

View File

@ -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<CapsuleVertex> verts;
std::vector<uint32_t> 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<float>() / 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::VertexArray>(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);
}

View File

@ -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<gfx::VertexArray> 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<EntityOccurrence> CreateOccurrence(const CreateOccurrenceParams& params)
{
return std::make_unique<EntityOccurrence>(this, params);
}
virtual void CreateDebugWireVA();
private:
void CreateOtherOccurence(const Portal& portal);

View File

@ -1,6 +1,10 @@
#include "sector.hpp"
#include <stdexcept>
#include <utility>
#include <span>
#include <map>
#include <set>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/matrix_transform.hpp>
@ -13,7 +17,9 @@ game::Sector::Sector(World* world, size_t idx, std::shared_ptr<assets::SectorDef
mesh_(def_->GetMesh()),
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<MinimapVertex> mm_verts;
std::vector<uint32_t> mm_indices;
std::map<size_t, size_t> vert_map; // Maps original vertex index to minimap vertex index
std::set<std::pair<size_t, size_t>> 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<uint32_t>(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()

View File

@ -3,6 +3,7 @@
#include "assets/sectordef.hpp"
#include <vector>
#include <map>
#include <set>
#include <btBulletCollisionCommon.h>
namespace game
@ -46,6 +47,8 @@ namespace game
std::unique_ptr<btCollisionShape> bt_col_shape; // Bullet collision shape
std::unique_ptr<btCollisionObject> 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<EntityOccurrence*> occus_;
public:
Sector(World* world, size_t idx, std::shared_ptr<assets::SectorDef> 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<EntityOccurrence*>& GetEntityOccurrences() const { return occus_; }
private:
void GenerateAllLights();
void BakeLightmap();
void CreateMinimapVA();
};

View File

@ -13,6 +13,23 @@ size_t game::World::AddSector(std::shared_ptr<assets::SectorDef> 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_)

View File

@ -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]; }

View File

@ -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;
}
}

View File

@ -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<Shader> solid_shader_;
std::shared_ptr<VertexArray> 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);
};
}

View File

@ -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

View File

@ -21,6 +21,7 @@ namespace gfx
SU_LIGHT_POSITIONS,
SU_LIGHT_COLORS_RS,
SU_AMBIENT_LIGHT,
SU_COLOR,
SU_COUNT
};

View File

@ -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

View File

@ -19,6 +19,9 @@ namespace gfx
SS_SKEL_MESH_VERT,
SS_SKEL_MESH_FRAG,
SS_SOLID_VERT,
SS_SOLID_FRAG,
};
class ShaderSources