#include "engine.hpp" #include #include #include #include #include "glm/ext/matrix_clip_space.hpp" #include namespace { [[nodiscard]] auto load_scene_to_renderer(trb::render::renderer &renderer, trb::game::scene &scene) { const auto is_mesh_node = [](const auto &v) { return std::holds_alternative(v); }; const auto upload_mesh_node = [&](const auto &v) { return renderer.register_mesh(std::get(v)); }; return scene.nodes | std::views::filter(is_mesh_node) | std::views::transform(upload_mesh_node) | std::ranges::to>(); } [[nodiscard]] std::pair bounds_of(const trb::game::scene &scene) { auto min = glm::vec3(std::numeric_limits::infinity()); auto max = glm::vec3(-std::numeric_limits::infinity()); const auto is_mesh_node = [](const auto &v) { return std::holds_alternative(v); }; for (const auto &v : scene.nodes | std::views::filter(is_mesh_node)) { const auto &mesh = std::get(v); for (const auto [position, normal, uv] : mesh.vertices) { min = glm::min(min, position); max = glm::max(max, 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) { // 1. Calculate the X/Z center. We ignore the Y center per your requirement. glm::vec3 center = glm::vec3((min.x + max.x) * 0.5f, 0.0f, (min.z + max.z) * 0.5f); // 2. Position the camera. // For 45 degrees in Y-up: we move 'distance' up (Y) and 'distance' back (Z). float distance = 100.0f; glm::vec3 cameraPos = center + glm::vec3(0.0f, distance, distance); // 3. View Matrix // Up is (0, 1, 0). Camera looks at the X/Z center. glm::mat4 view = glm::lookAt(cameraPos, center, glm::vec3(0.0f, 1.0f, 0.0f)); // 4. Determine Ortho Bounds // We must check all corners because at 45 degrees, the Y-height // of objects affects their projected X/Y position on screen. 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); } // 5. Apply Zoom and Aspect Ratio 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::engine() : display(&input, {1920, 1080}, "Tanks Reborn"), renderer(&display), level_border(load_glb("../assets/models/level_scene.glb")), level_border_render_ids( ::load_scene_to_renderer(renderer, level_border)) {} void engine::run() { while (!display.should_close()) { try { const auto [map_min, map_max] = ::bounds_of(level_border); const auto [view, projection] = ::calculate_camera_matrices(map_min, map_max, 1920.0f / 1080.0f); renderer.render(level_border_render_ids, view, projection); } catch (const vuk::VkException &ex) { if (ex.what() != std::string("Out of date.")) { std::rethrow_exception(std::current_exception()); } } display.poll_events(); } } } // namespace trb::game