| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580 |
- #include "riverbank_renderer.h"
- #include "../../game/map/visibility_service.h"
- #include "../gl/mesh.h"
- #include "../gl/resources.h"
- #include "../scene_renderer.h"
- #include "ground_utils.h"
- #include "map/terrain.h"
- #include <GL/gl.h>
- #include <QVector2D>
- #include <QVector3D>
- #include <algorithm>
- #include <cmath>
- #include <cstddef>
- #include <memory>
- #include <qglobal.h>
- #include <qmatrix4x4.h>
- #include <qvectornd.h>
- #include <utility>
- #include <vector>
- namespace Render::GL {
- RiverbankRenderer::RiverbankRenderer() = default;
- RiverbankRenderer::~RiverbankRenderer() = default;
- void RiverbankRenderer::configure(
- const std::vector<Game::Map::RiverSegment> &river_segments,
- const Game::Map::TerrainHeightMap &height_map) {
- m_river_segments = river_segments;
- m_tile_size = height_map.getTileSize();
- m_grid_width = height_map.getWidth();
- m_grid_height = height_map.getHeight();
- m_heights = height_map.getHeightData();
- m_visibility_texture.reset();
- m_cached_visibility_version = 0;
- m_visibility_width = 0;
- m_visibility_height = 0;
- build_meshes();
- }
- void RiverbankRenderer::build_meshes() {
- m_meshes.clear();
- m_visibility_samples.clear();
- if (m_river_segments.empty()) {
- return;
- }
- auto noise = [](float x, float y) -> float {
- float const ix = std::floor(x);
- float const iy = std::floor(y);
- float fx = x - ix;
- float fy = y - iy;
- fx = fx * fx * (3.0F - 2.0F * fx);
- fy = fy * fy * (3.0F - 2.0F * fy);
- float const a = Ground::noise_hash(ix, iy);
- float const b = Ground::noise_hash(ix + 1.0F, iy);
- float const c = Ground::noise_hash(ix, iy + 1.0F);
- float const d = Ground::noise_hash(ix + 1.0F, iy + 1.0F);
- return a * (1.0F - fx) * (1.0F - fy) + b * fx * (1.0F - fy) +
- c * (1.0F - fx) * fy + d * fx * fy;
- };
- auto sample_height = [&](float world_x, float world_z) -> float {
- if (m_heights.empty() || m_grid_width == 0 || m_grid_height == 0) {
- return 0.0F;
- }
- float const half_width = m_grid_width * 0.5F - 0.5F;
- float const half_height = m_grid_height * 0.5F - 0.5F;
- float gx = (world_x / m_tile_size) + half_width;
- float gz = (world_z / m_tile_size) + half_height;
- gx = std::clamp(gx, 0.0F, float(m_grid_width - 1));
- gz = std::clamp(gz, 0.0F, float(m_grid_height - 1));
- int const x0 = int(std::floor(gx));
- int const z0 = int(std::floor(gz));
- int const x1 = std::min(x0 + 1, m_grid_width - 1);
- int const z1 = std::min(z0 + 1, m_grid_height - 1);
- float const tx = gx - float(x0);
- float const tz = gz - float(z0);
- float const h00 = m_heights[z0 * m_grid_width + x0];
- float const h10 = m_heights[z0 * m_grid_width + x1];
- float const h01 = m_heights[z1 * m_grid_width + x0];
- float const h11 = m_heights[z1 * m_grid_width + x1];
- float const h0 = h00 * (1.0F - tx) + h10 * tx;
- float const h1 = h01 * (1.0F - tx) + h11 * tx;
- return h0 * (1.0F - tz) + h1 * tz;
- };
- for (const auto &segment : m_river_segments) {
- QVector3D dir = segment.end - segment.start;
- float const length = dir.length();
- if (length < 0.01F) {
- m_meshes.push_back(nullptr);
- m_visibility_samples.emplace_back();
- continue;
- }
- dir.normalize();
- QVector3D const perpendicular(-dir.z(), 0.0F, dir.x());
- float const half_width = segment.width * 0.5F;
- constexpr int k_rings_per_side = 5;
- constexpr int k_total_rings = k_rings_per_side * 2;
- int length_steps =
- static_cast<int>(std::ceil(length / (m_tile_size * 0.5F))) + 1;
- length_steps = std::max(length_steps, 8);
- std::vector<Vertex> vertices;
- std::vector<unsigned int> indices;
- std::vector<QVector3D> samples;
- for (int i = 0; i < length_steps; ++i) {
- float const t =
- static_cast<float>(i) / static_cast<float>(length_steps - 1);
- QVector3D center_pos = segment.start + dir * (length * t);
- float const center_height = sample_height(center_pos.x(), center_pos.z());
- constexpr float k_edge_noise_freq_1 = 2.0F;
- constexpr float k_edge_noise_freq_2 = 5.0F;
- constexpr float k_edge_noise_freq_3 = 10.0F;
- float const edge_noise1 = noise(center_pos.x() * k_edge_noise_freq_1,
- center_pos.z() * k_edge_noise_freq_1);
- float const edge_noise2 = noise(center_pos.x() * k_edge_noise_freq_2,
- center_pos.z() * k_edge_noise_freq_2);
- float const edge_noise3 = noise(center_pos.x() * k_edge_noise_freq_3,
- center_pos.z() * k_edge_noise_freq_3);
- float combined_noise =
- edge_noise1 * 0.5F + edge_noise2 * 0.3F + edge_noise3 * 0.2F;
- combined_noise = (combined_noise - 0.5F) * 2.0F;
- float const width_variation = combined_noise * half_width * 0.35F;
- float const meander = noise(t * 3.0F, length * 0.1F) * 0.3F;
- QVector3D const center_offset = perpendicular * meander;
- center_pos += center_offset;
- struct RingProfile {
- float distance_from_water;
- float height_offset;
- };
- constexpr RingProfile k_left_rings[k_rings_per_side] = {{0.0F, 0.02F},
- {0.125F, 0.175F},
- {0.25F, 0.3F},
- {0.375F, 0.125F},
- {0.5F, -0.15F}};
- float const ring_noise =
- noise(center_pos.x() * 3.0F, center_pos.z() * 3.0F) * 0.075F;
- float const base_bank_width = 0.5F + ring_noise;
- unsigned int const ring_start_idx =
- static_cast<unsigned int>(vertices.size());
- for (int ring = 0; ring < k_rings_per_side; ++ring) {
- float const ring_dist =
- k_left_rings[ring].distance_from_water * base_bank_width;
- float const ring_height = k_left_rings[ring].height_offset;
- QVector3D const ring_pos =
- center_pos -
- perpendicular * (half_width + width_variation + ring_dist);
- float const terrain_height = sample_height(ring_pos.x(), ring_pos.z());
- float const clamped_height =
- std::min(terrain_height, center_height + 0.05F);
- if (ring == 0) {
- samples.push_back(ring_pos);
- }
- Vertex vtx{};
- vtx.position[0] = ring_pos.x();
- vtx.position[1] = clamped_height + ring_height;
- vtx.position[2] = ring_pos.z();
- QVector3D normal;
- if (ring == 0) {
- QVector3D const next_ring_pos =
- center_pos -
- perpendicular *
- (half_width + width_variation +
- k_left_rings[1].distance_from_water * base_bank_width);
- float const next_terrain =
- sample_height(next_ring_pos.x(), next_ring_pos.z());
- float const next_clamped =
- std::min(next_terrain, center_height + 0.05F);
- QVector3D slope_vec(next_ring_pos.x() - ring_pos.x(),
- (next_clamped + k_left_rings[1].height_offset) -
- (clamped_height + ring_height),
- next_ring_pos.z() - ring_pos.z());
- normal = QVector3D::crossProduct(slope_vec, dir).normalized();
- } else if (ring == k_rings_per_side - 1) {
- unsigned int prev_idx = ring_start_idx + ring - 1;
- QVector3D prev_pos(vertices[prev_idx].position[0],
- vertices[prev_idx].position[1],
- vertices[prev_idx].position[2]);
- QVector3D slope_vec(ring_pos.x() - prev_pos.x(),
- (clamped_height + ring_height) - prev_pos.y(),
- ring_pos.z() - prev_pos.z());
- normal = QVector3D::crossProduct(slope_vec, dir).normalized();
- } else {
- unsigned int prev_idx = ring_start_idx + ring - 1;
- QVector3D prev_pos(vertices[prev_idx].position[0],
- vertices[prev_idx].position[1],
- vertices[prev_idx].position[2]);
- QVector3D const next_ring_pos =
- center_pos -
- perpendicular * (half_width + width_variation +
- k_left_rings[ring + 1].distance_from_water *
- base_bank_width);
- float const next_terrain =
- sample_height(next_ring_pos.x(), next_ring_pos.z());
- float const next_clamped =
- std::min(next_terrain, center_height + 0.05F);
- QVector3D slope_from_prev(ring_pos.x() - prev_pos.x(),
- (terrain_height + ring_height) -
- prev_pos.y(),
- ring_pos.z() - prev_pos.z());
- QVector3D slope_to_next(
- next_ring_pos.x() - ring_pos.x(),
- (next_clamped + k_left_rings[ring + 1].height_offset) -
- (clamped_height + ring_height),
- next_ring_pos.z() - ring_pos.z());
- QVector3D n1 =
- QVector3D::crossProduct(slope_from_prev, dir).normalized();
- QVector3D n2 =
- QVector3D::crossProduct(slope_to_next, dir).normalized();
- normal = ((n1 + n2) * 0.5F).normalized();
- }
- vtx.normal[0] = normal.x();
- vtx.normal[1] = normal.y();
- vtx.normal[2] = normal.z();
- vtx.tex_coord[0] = static_cast<float>(ring) / (k_rings_per_side - 1);
- vtx.tex_coord[1] = t;
- vertices.push_back(vtx);
- }
- for (int ring = 0; ring < k_rings_per_side; ++ring) {
- float const ring_dist =
- k_left_rings[ring].distance_from_water * base_bank_width;
- float const ring_height = k_left_rings[ring].height_offset;
- QVector3D const ring_pos =
- center_pos +
- perpendicular * (half_width + width_variation + ring_dist);
- float const terrain_height = sample_height(ring_pos.x(), ring_pos.z());
- float const clamped_height =
- std::min(terrain_height, center_height + 0.05F);
- if (ring == 0) {
- samples.push_back(ring_pos);
- }
- Vertex vtx{};
- vtx.position[0] = ring_pos.x();
- vtx.position[1] = clamped_height + ring_height;
- vtx.position[2] = ring_pos.z();
- QVector3D normal;
- if (ring == 0) {
- QVector3D const next_ring_pos =
- center_pos +
- perpendicular *
- (half_width + width_variation +
- k_left_rings[1].distance_from_water * base_bank_width);
- float const next_terrain =
- sample_height(next_ring_pos.x(), next_ring_pos.z());
- float const next_clamped =
- std::min(next_terrain, center_height + 0.05F);
- QVector3D slope_vec(next_ring_pos.x() - ring_pos.x(),
- (next_clamped + k_left_rings[1].height_offset) -
- (clamped_height + ring_height),
- next_ring_pos.z() - ring_pos.z());
- normal = QVector3D::crossProduct(dir, slope_vec).normalized();
- } else if (ring == k_rings_per_side - 1) {
- unsigned int prev_idx = ring_start_idx + k_rings_per_side + ring - 1;
- QVector3D prev_pos(vertices[prev_idx].position[0],
- vertices[prev_idx].position[1],
- vertices[prev_idx].position[2]);
- QVector3D slope_vec(ring_pos.x() - prev_pos.x(),
- (clamped_height + ring_height) - prev_pos.y(),
- ring_pos.z() - prev_pos.z());
- normal = QVector3D::crossProduct(dir, slope_vec).normalized();
- } else {
- unsigned int prev_idx = ring_start_idx + k_rings_per_side + ring - 1;
- QVector3D prev_pos(vertices[prev_idx].position[0],
- vertices[prev_idx].position[1],
- vertices[prev_idx].position[2]);
- QVector3D const next_ring_pos =
- center_pos +
- perpendicular * (half_width + width_variation +
- k_left_rings[ring + 1].distance_from_water *
- base_bank_width);
- float const next_terrain =
- sample_height(next_ring_pos.x(), next_ring_pos.z());
- float const next_clamped =
- std::min(next_terrain, center_height + 0.05F);
- QVector3D slope_from_prev(ring_pos.x() - prev_pos.x(),
- (clamped_height + ring_height) -
- prev_pos.y(),
- ring_pos.z() - prev_pos.z());
- QVector3D slope_to_next(
- next_ring_pos.x() - ring_pos.x(),
- (next_clamped + k_left_rings[ring + 1].height_offset) -
- (clamped_height + ring_height),
- next_ring_pos.z() - ring_pos.z());
- QVector3D n1 =
- QVector3D::crossProduct(dir, slope_from_prev).normalized();
- QVector3D n2 =
- QVector3D::crossProduct(dir, slope_to_next).normalized();
- normal = ((n1 + n2) * 0.5F).normalized();
- }
- vtx.normal[0] = normal.x();
- vtx.normal[1] = normal.y();
- vtx.normal[2] = normal.z();
- vtx.tex_coord[0] = static_cast<float>(ring) / (k_rings_per_side - 1);
- vtx.tex_coord[1] = t;
- vertices.push_back(vtx);
- }
- {
- Vertex const &water_edge_left = vertices[ring_start_idx];
- Vertex skirt_vtx = water_edge_left;
- skirt_vtx.position[1] = -0.05F;
- skirt_vtx.normal[0] = -perpendicular.x();
- skirt_vtx.normal[1] = 0.0F;
- skirt_vtx.normal[2] = -perpendicular.z();
- vertices.push_back(skirt_vtx);
- }
- {
- Vertex const &water_edge_right =
- vertices[ring_start_idx + k_rings_per_side];
- Vertex skirt_vtx = water_edge_right;
- skirt_vtx.position[1] = -0.05F;
- skirt_vtx.normal[0] = perpendicular.x();
- skirt_vtx.normal[1] = 0.0F;
- skirt_vtx.normal[2] = perpendicular.z();
- vertices.push_back(skirt_vtx);
- }
- if (i < length_steps - 1) {
- unsigned int const base_idx = ring_start_idx;
- unsigned int const next_base_idx = base_idx + k_total_rings + 2;
- for (int ring = 0; ring < k_rings_per_side - 1; ++ring) {
- unsigned int idx0 = base_idx + ring;
- unsigned int idx1 = base_idx + ring + 1;
- unsigned int idx2 = next_base_idx + ring;
- unsigned int idx3 = next_base_idx + ring + 1;
- indices.push_back(idx0);
- indices.push_back(idx2);
- indices.push_back(idx1);
- indices.push_back(idx1);
- indices.push_back(idx2);
- indices.push_back(idx3);
- }
- for (int ring = 0; ring < k_rings_per_side - 1; ++ring) {
- unsigned int idx0 = base_idx + k_rings_per_side + ring;
- unsigned int idx1 = base_idx + k_rings_per_side + ring + 1;
- unsigned int idx2 = next_base_idx + k_rings_per_side + ring;
- unsigned int idx3 = next_base_idx + k_rings_per_side + ring + 1;
- indices.push_back(idx0);
- indices.push_back(idx1);
- indices.push_back(idx2);
- indices.push_back(idx1);
- indices.push_back(idx3);
- indices.push_back(idx2);
- }
- {
- unsigned int left_top = base_idx;
- unsigned int left_bottom = base_idx + k_total_rings;
- unsigned int left_top_next = next_base_idx;
- unsigned int left_bottom_next = next_base_idx + k_total_rings;
- indices.push_back(left_top);
- indices.push_back(left_bottom);
- indices.push_back(left_top_next);
- indices.push_back(left_bottom);
- indices.push_back(left_bottom_next);
- indices.push_back(left_top_next);
- unsigned int right_top = base_idx + k_rings_per_side;
- unsigned int right_bottom = base_idx + k_total_rings + 1;
- unsigned int right_top_next = next_base_idx + k_rings_per_side;
- unsigned int right_bottom_next = next_base_idx + k_total_rings + 1;
- indices.push_back(right_top);
- indices.push_back(right_top_next);
- indices.push_back(right_bottom);
- indices.push_back(right_bottom);
- indices.push_back(right_top_next);
- indices.push_back(right_bottom_next);
- }
- }
- }
- if (!vertices.empty() && !indices.empty()) {
- m_meshes.push_back(std::make_unique<Mesh>(vertices, indices));
- m_visibility_samples.push_back(std::move(samples));
- } else {
- m_meshes.push_back(nullptr);
- m_visibility_samples.emplace_back();
- }
- }
- }
- void RiverbankRenderer::submit(Renderer &renderer, ResourceManager *resources) {
- if (m_meshes.empty() || m_river_segments.empty()) {
- return;
- }
- Q_UNUSED(resources);
- auto *shader = renderer.get_shader("riverbank");
- if (shader == nullptr) {
- return;
- }
- renderer.set_current_shader(shader);
- auto *backend = renderer.backend();
- auto &visibility = Game::Map::VisibilityService::instance();
- const bool use_visibility = visibility.is_initialized();
- const std::uint64_t visibility_version =
- use_visibility ? visibility.version() : 0;
- Game::Map::VisibilityService::Snapshot visibility_snapshot;
- if (use_visibility) {
- visibility_snapshot = visibility.snapshot();
- }
- Texture *visibility_tex = nullptr;
- QVector2D visibility_size(0.0F, 0.0F);
- if (use_visibility) {
- const int vis_w = visibility_snapshot.width;
- const int vis_h = visibility_snapshot.height;
- bool const size_changed =
- (vis_w != m_visibility_width) || (vis_h != m_visibility_height);
- if (!m_visibility_texture || size_changed) {
- m_visibility_texture = std::make_unique<Texture>();
- m_visibility_texture->create_empty(vis_w, vis_h, Texture::Format::RGBA);
- m_visibility_texture->set_filter(Texture::Filter::Nearest,
- Texture::Filter::Nearest);
- m_visibility_texture->set_wrap(Texture::Wrap::ClampToEdge,
- Texture::Wrap::ClampToEdge);
- m_visibility_width = vis_w;
- m_visibility_height = vis_h;
- m_cached_visibility_version = 0;
- }
- if (visibility_version != m_cached_visibility_version || size_changed) {
- const auto &cells = visibility_snapshot.cells;
- std::vector<unsigned char> texels(
- static_cast<std::size_t>(vis_w * vis_h * 4), 0U);
- for (int z = 0; z < vis_h; ++z) {
- for (int x = 0; x < vis_w; ++x) {
- int const idx = z * vis_w + x;
- unsigned char val = 0U;
- switch (static_cast<Game::Map::VisibilityState>(cells[idx])) {
- case Game::Map::VisibilityState::Visible:
- val = 255U;
- break;
- case Game::Map::VisibilityState::Explored:
- val = 128U;
- break;
- case Game::Map::VisibilityState::Unseen:
- default:
- val = 0U;
- break;
- }
- texels[static_cast<std::size_t>(idx * 4)] = val;
- texels[static_cast<std::size_t>(idx * 4 + 3)] = 255;
- }
- }
- m_visibility_texture->bind();
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, vis_w, vis_h, GL_RGBA,
- GL_UNSIGNED_BYTE, texels.data());
- visibility_tex = m_visibility_texture.get();
- m_cached_visibility_version = visibility_version;
- } else {
- visibility_tex = m_visibility_texture.get();
- }
- visibility_size =
- QVector2D(static_cast<float>(vis_w), static_cast<float>(vis_h));
- }
- if (backend != nullptr) {
- backend->set_riverbank_visibility(
- use_visibility && visibility_tex != nullptr, visibility_tex,
- visibility_size, m_tile_size, m_explored_dim_factor);
- }
- QMatrix4x4 model;
- model.setToIdentity();
- size_t mesh_index = 0;
- for (const auto &segment : m_river_segments) {
- if (mesh_index >= m_meshes.size()) {
- break;
- }
- auto *mesh = m_meshes[mesh_index].get();
- ++mesh_index;
- if (mesh == nullptr) {
- continue;
- }
- float segment_visibility = 1.0F;
- if (use_visibility) {
- enum class SegmentState { Hidden, Explored, Visible };
- SegmentState state = SegmentState::Hidden;
- const auto &samples = m_visibility_samples[mesh_index - 1];
- if (samples.empty()) {
- state = SegmentState::Visible;
- }
- for (const auto &sample : samples) {
- if (visibility_snapshot.isVisibleWorld(sample.x(), sample.z())) {
- state = SegmentState::Visible;
- break;
- }
- if ((state == SegmentState::Hidden) &&
- visibility_snapshot.isExploredWorld(sample.x(), sample.z())) {
- state = SegmentState::Explored;
- }
- }
- if (state == SegmentState::Hidden) {
- continue;
- }
- segment_visibility =
- (state == SegmentState::Visible) ? 1.0F : m_explored_dim_factor;
- }
- renderer.mesh(mesh, model, QVector3D(1.0F, 1.0F, 1.0F), nullptr,
- segment_visibility);
- }
- renderer.set_current_shader(nullptr);
- }
- } // namespace Render::GL
|