implement basic level editing

This commit is contained in:
2026-04-11 08:24:32 -03:00
parent 343de8a22b
commit 15a58dd66d
5 changed files with 104 additions and 12 deletions

View File

@@ -10,8 +10,8 @@ struct position final : strong::alias<glm::vec2, position> {
using strong::alias<glm::vec2, position>::alias;
};
struct tile_position final : strong::alias<glm::vec2, tile_position> {
using strong::alias<glm::vec2, tile_position>::alias;
struct tile_position final : strong::alias<glm::uvec2, tile_position> {
using strong::alias<glm::uvec2, tile_position>::alias;
};
struct render_id : strong::alias<std::uint32_t, render_id> {

View File

@@ -290,6 +290,50 @@ engine::add_single_tile_to_render_request(render::request request,
return request.add_lines(lines, glm::vec3(1.0f, 1.0f, 0.0f));
}
std::optional<glm::uvec2>
engine::calculate_intersected_tile(glm::mat4 view, glm::mat4 projection,
glm::vec2 mouse) const {
const float width = static_cast<float>(display.extent().x);
const float height = static_cast<float>(display.extent().y);
float x = (2.0f * mouse.x) / width - 1.0f;
float y = 1.0f - (2.0f * mouse.y) / height;
glm::vec4 near_clip = glm::vec4(x, y, -1.0f, 1.0f);
glm::vec4 far_clip = glm::vec4(x, y, 1.0f, 1.0f);
glm::vec4 near_world = glm::inverse(projection * view) * near_clip;
glm::vec4 far_world = glm::inverse(projection * view) * far_clip;
near_world /= near_world.w;
far_world /= far_world.w;
glm::vec3 ray_world = glm::normalize(glm::vec3(far_world - near_world));
glm::vec3 cam_pos = glm::vec3(near_world);
if (std::abs(ray_world.y) < 1e-5f) {
return std::nullopt;
}
float t = -cam_pos.y / ray_world.y;
glm::vec3 hit = cam_pos + ray_world * t;
const auto full_dimensions = area_size * 2.0f;
const float cell_width = full_dimensions.x / 22.0f;
const float cell_depth = full_dimensions.z / 16.0f;
const auto start = area_origin - area_size;
int tile_x = static_cast<int>((hit.x - start.x) / cell_width);
int tile_y = static_cast<int>((hit.z - start.z) / cell_depth);
tile_x = std::clamp(tile_x, 0, 21);
tile_y = std::clamp(tile_y, 0, 15);
return glm::uvec2(tile_x, tile_y);
}
engine::mesh_bounds engine::compute_size_of_mesh(const mesh_node &mesh) {
auto bounds = mesh_bounds();
@@ -441,6 +485,28 @@ void engine::do_level_editor_ui() {
ImGui::Checkbox("Enabled", &is_editing_level);
static int wall_type = 0;
ImGui::Combo("Wall Type", &wall_type, "cork\0solid\0\0");
level_editing_data.type_to_write =
static_cast<components::wall_type>(wall_type);
static int wall_height = 0;
ImGui::Combo("Wall Height", &wall_height, "half\0full\0\0");
level_editing_data.height_to_write =
static_cast<components::wall_height>(wall_height);
if (level_editing_data.intersected_tile.has_value()) {
// Todo: Check for validity based on same types
if (ImGui::Button("Add Wall To Tile")) {
auto entity = current_level->get_or_create_entity_at(
*level_editing_data.intersected_tile);
auto &wall = current_level->get_entities()
->get_or_emplace<components::wall_definition>(entity);
wall.type = level_editing_data.type_to_write;
wall.heights[wall.wall_count++] = level_editing_data.height_to_write;
}
}
ImGui::End();
}
@@ -479,12 +545,6 @@ void engine::run() {
auto request = render::request();
if (!ImGui::GetIO().WantCaptureMouse) {
const auto mx = ImGui::GetIO().MousePos;
std::println("mouse {}, {}", mx.x, mx.y);
// request = request.add_circle({mx.x, mx.y}, 1.0f);
}
request = add_level_bounds_to_render_request(request);
if (current_level.has_value()) {
request = add_level_to_render_request(request, &(*current_level));
@@ -498,10 +558,18 @@ void engine::run() {
request = add_tiles_to_render_request(request);
}
level_editing_data.intersected_tile = glm::uvec2(5, 5);
if (level_editing_data.intersected_tile.has_value()) {
request = add_single_tile_to_render_request(
request, *level_editing_data.intersected_tile);
if (debug_drawing.level_editor) {
if (!ImGui::GetIO().WantCaptureMouse) {
const auto mx = ImGui::GetIO().MousePos;
if (ImGui::GetIO().MouseDown[0]) {
level_editing_data.intersected_tile =
calculate_intersected_tile(view, projection, {mx.x, mx.y});
}
if (level_editing_data.intersected_tile.has_value()) {
request = add_single_tile_to_render_request(
request, *level_editing_data.intersected_tile);
}
}
}
renderer.render(request, view, projection);

View File

@@ -1,6 +1,7 @@
#ifndef TANKS_ENGINE_HPP
#define TANKS_ENGINE_HPP
#include "game/components.hpp"
#include <cstdint>
#include <entt/entt.hpp>
@@ -47,6 +48,9 @@ private:
std::optional<level> current_level = level();
struct {
std::optional<glm::uvec2> intersected_tile;
components::wall_height height_to_write;
components::wall_type type_to_write;
} level_editing_data;
bool is_editing_level = false;
struct {
@@ -81,6 +85,10 @@ private:
add_single_tile_to_render_request(render::request request,
glm::uvec2 tile) const;
[[nodiscard]] std::optional<glm::uvec2>
calculate_intersected_tile(glm::mat4 view, glm::mat4 projection,
glm::vec2 mouse) const;
struct mesh_bounds {
glm::vec3 min;
glm::vec3 max;

View File

@@ -44,4 +44,18 @@ entt::basic_registry<std::uint32_t> *level::get_entities() { return &entities; }
glm::uvec2 level::get_bounds() const noexcept { return {WIDTH, HEIGHT}; }
std::uint32_t level::get_or_create_entity_at(glm::uvec2 tile) {
for (const auto [entity, e_tile] :
entities.view<components::tile_position>()->each()) {
if (static_cast<glm::uvec2>(e_tile) == tile) {
return entity;
}
}
auto created = entities.create();
entities.emplace<components::tile_position>(created, tile);
return created;
}
} // namespace trb::game

View File

@@ -22,6 +22,8 @@ public:
[[nodiscard]] glm::uvec2 get_bounds() const noexcept;
[[nodiscard]] entt::basic_registry<std::uint32_t> *get_entities();
[[nodiscard]] std::uint32_t get_or_create_entity_at(glm::uvec2 tile);
};
} // namespace trb::game