132 lines
4.3 KiB
C++
132 lines
4.3 KiB
C++
#include "engine.hpp"
|
|
|
|
#include <exception>
|
|
#include <glm/trigonometric.hpp>
|
|
#include <limits>
|
|
#include <ranges>
|
|
|
|
#include "glm/ext/matrix_clip_space.hpp"
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
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<trb::game::mesh_node>(v);
|
|
};
|
|
|
|
const auto upload_mesh_node = [&](const auto &v) {
|
|
return renderer.register_mesh(std::get<trb::game::mesh_node>(v));
|
|
};
|
|
|
|
return scene.nodes | std::views::filter(is_mesh_node) |
|
|
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());
|
|
|
|
const auto is_mesh_node = [](const auto &v) {
|
|
return std::holds_alternative<trb::game::mesh_node>(v);
|
|
};
|
|
|
|
for (const auto &v : scene.nodes | std::views::filter(is_mesh_node)) {
|
|
const auto &mesh = std::get<trb::game::mesh_node>(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
|