Files
tanks-reborn/source/game/engine.cpp
2026-04-01 13:59:04 -03:00

160 lines
4.9 KiB
C++

#include "engine.hpp"
#include <exception>
#include <game/components.hpp>
#include <glm/trigonometric.hpp>
#include <limits>
#include <ranges>
#include "glm/ext/matrix_clip_space.hpp"
#include "glm/ext/matrix_transform.hpp"
#include <glm/gtc/matrix_transform.hpp>
namespace {
[[nodiscard]] auto load_scene_to_renderer(trb::render::renderer &renderer,
trb::game::scene &scene) {
const auto upload_mesh_node = [&](const auto &mesh) {
return renderer.register_mesh(
mesh,
scene.textures[scene.materials[mesh.material_index].albedo_texture]);
};
return scene.meshes | std::views::transform(upload_mesh_node) |
std::ranges::to<std::vector<std::uint32_t>>();
}
[[nodiscard]] std::pair<glm::vec3, glm::vec3>
bounds_of(const trb::game::scene &scene) {
auto min = glm::vec3(std::numeric_limits<float>::infinity());
auto max = glm::vec3(-std::numeric_limits<float>::infinity());
for (const auto &mesh : scene.meshes) {
const auto transform = [mesh](glm::vec3 p) {
return glm::vec3(mesh.transform * glm::vec4(p, 1.0f));
};
for (const auto [position, normal, uv] : mesh.vertices) {
min = glm::min(min, transform(position));
max = glm::max(max, transform(position));
}
}
return std::make_pair(min, max);
}
struct level_view {
glm::mat4 view;
glm::mat4 projection;
};
[[nodiscard]] level_view calculate_camera_matrices(glm::vec3 min, glm::vec3 max,
float aspectRatio,
float zoom = 1.0f) {
glm::vec3 center =
glm::vec3((min.x + max.x) * 0.5f, 0.0f, (min.z + max.z) * 0.5f);
float distance = 100.0f;
glm::vec3 cameraPos = center + glm::vec3(0.0f, distance, distance);
glm::mat4 view = glm::lookAt(cameraPos, center, glm::vec3(0.0f, 1.0f, 0.0f));
glm::vec3 corners[8] = {{min.x, min.y, min.z}, {max.x, min.y, min.z},
{min.x, max.y, min.z}, {max.x, max.y, min.z},
{min.x, min.y, max.z}, {max.x, min.y, max.z},
{min.x, max.y, max.z}, {max.x, max.y, max.z}};
float vMinX = FLT_MAX, vMaxX = -FLT_MAX;
float vMinY = FLT_MAX, vMaxY = -FLT_MAX;
for (const auto &corner : corners) {
glm::vec4 vSpace = view * glm::vec4(corner, 1.0f);
vMinX = glm::min(vMinX, vSpace.x);
vMaxX = glm::max(vMaxX, vSpace.x);
vMinY = glm::min(vMinY, vSpace.y);
vMaxY = glm::max(vMaxY, vSpace.y);
}
float width = (vMaxX - vMinX) / zoom;
float height = (vMaxY - vMinY) / zoom;
float midX = (vMinX + vMaxX) * 0.5f;
float midY = (vMinY + vMaxY) * 0.5f;
if (aspectRatio > (width / height)) {
width = height * aspectRatio;
} else {
height = width / aspectRatio;
}
glm::mat4 projection =
glm::ortho(midX - width * 0.5f, midX + width * 0.5f, midY - height * 0.5f,
midY + height * 0.5f, -1000.0f, 1000.0f);
return {view, projection};
}
} // namespace
namespace trb::game {
engine::game_models engine::load_models(std::filesystem::path model_path) {
return {
.boundary = load_glb(model_path / "level_scene.glb"),
.boxes = load_glb(model_path / "boxes.glb"),
};
}
std::vector<std::uint32_t> engine::load_scene_to_ecs(game::scene &scene) {
const auto load_mesh =
[this, &scene](const game::mesh_node &mesh) -> std::uint32_t {
const auto entity = entities.create();
const auto render_id = renderer.register_mesh(
mesh,
scene.textures[scene.materials[mesh.material_index].albedo_texture]);
entities.emplace<components::render_id>(entity,
components::render_id(render_id));
return entity;
};
return scene.meshes | std::views::transform(load_mesh) |
std::ranges::to<std::vector<std::uint32_t>>();
}
render::request
engine::add_level_bounds_to_render_request(render::request request) const {
for (const auto eid : level_border_entity_ids) {
const auto render_id = entities.get<components::render_id>(eid);
request = request.add_mesh(static_cast<std::uint32_t>(render_id)).commit();
}
return request;
}
engine::engine()
: display(&input, {1920, 1080}, "Tanks Reborn"), renderer(&display),
models(load_models("../assets/models")),
level_border_entity_ids(load_scene_to_ecs(models.boundary)) {}
void engine::run() {
while (!display.should_close()) {
display.poll_events();
try {
const auto [map_min, map_max] = ::bounds_of(models.boundary);
const auto [view, projection] = ::calculate_camera_matrices(
map_min, map_max, 1920.0f / 1080.0f, 3.0f);
auto request = render::request();
request = add_level_bounds_to_render_request(request);
renderer.render(request, view, projection);
} catch (const vuk::VkException &ex) {
if (ex.what() != std::string("Out of date.")) {
std::rethrow_exception(std::current_exception());
}
}
}
}
} // namespace trb::game