diff options
Diffstat (limited to 'game/terrain.cpp')
-rw-r--r-- | game/terrain.cpp | 166 |
1 files changed, 122 insertions, 44 deletions
diff --git a/game/terrain.cpp b/game/terrain.cpp index 3b16e79..f10aac6 100644 --- a/game/terrain.cpp +++ b/game/terrain.cpp @@ -1,65 +1,118 @@ #include "terrain.h" -#include "game/geoData.h" -#include "gfx/models/texture.h" +#include "gfx/frustum.h" #include <algorithm> -#include <cstddef> #include <gfx/gl/sceneShader.h> #include <gfx/gl/shadowMapper.h> #include <gfx/image.h> #include <gfx/models/mesh.h> #include <gfx/models/vertex.h> +#include <glMappedBufferWriter.h> #include <glm/glm.hpp> -#include <iterator> #include <location.h> #include <maths.h> #include <utility> #include <vector> -static constexpr RGB openSurface {-1}; - -Terrain::Terrain(std::shared_ptr<GeoData> tm) : geoData {std::move(tm)}, grass {std::make_shared<Texture>("grass.png")} -{ - generateMeshes(); -} +static constexpr RGB OPEN_SURFACE {-1}; +static constexpr GlobalDistance TILE_SIZE = 1024 * 1024; // ~1km, power of 2, fast divide template<> VertexArrayObject & VertexArrayObject::addAttribsFor<Terrain::Vertex>(const GLuint arrayBuffer, const GLuint divisor) { - return addAttribs<Terrain::Vertex, &Terrain::Vertex::pos, &Terrain::Vertex::normal, &Terrain::Vertex::colourBias>( - arrayBuffer, divisor); + return addAttribs<Terrain::Vertex, &Terrain::Vertex::pos, &Terrain::Vertex::normal>(arrayBuffer, divisor); } -void -Terrain::generateMeshes() +bool +Terrain::SurfaceKey::operator<(const SurfaceKey & other) const { - std::vector<unsigned int> indices; - indices.reserve(geoData->n_faces() * 3); - std::vector<Vertex> vertices; - vertices.reserve(geoData->n_vertices()); - std::map<std::pair<GeoData::VertexHandle, const Surface *>, size_t> vertexIndex; - std::for_each(geoData->vertices_sbegin(), geoData->vertices_end(), - [this, &vertexIndex, &vertices](const GeoData::VertexHandle v) { - std::for_each(geoData->vf_begin(v), geoData->vf_end(v), - [&vertexIndex, v, this, &vertices](const GeoData::FaceHandle f) { - const auto surface = geoData->get_surface(f); - if (const auto vertexIndexRef = vertexIndex.emplace(std::make_pair(v, surface), 0); - vertexIndexRef.second) { - vertexIndexRef.first->second = vertices.size(); + return std::tie(surface, basePosition.x, basePosition.y) + < std::tie(other.surface, other.basePosition.x, other.basePosition.y); +} - vertices.emplace_back(geoData->point(v), geoData->normal(v), - surface ? surface->colorBias : openSurface); - } - }); - }); - std::for_each( - geoData->faces_sbegin(), geoData->faces_end(), [this, &vertexIndex, &indices](const GeoData::FaceHandle f) { - std::transform(geoData->fv_begin(f), geoData->fv_end(f), std::back_inserter(indices), - [&vertexIndex, f, this](const GeoData::VertexHandle v) { - return vertexIndex[std::make_pair(v, geoData->get_surface(f))]; - }); +inline void +Terrain::copyVerticesToBuffer() const +{ + std::ranges::transform(all_vertices(), glMappedBufferWriter<Vertex> {GL_ARRAY_BUFFER, verticesBuffer, n_vertices()}, + [this](const auto & vertex) { + return Vertex {point(vertex), normal(vertex)}; }); - meshes.create<MeshT<Vertex>>(vertices, indices); +} + +inline GlobalPosition2D +Terrain::getTile(const FaceHandle & face) const +{ + return point(*cfv_begin(face)).xy() / TILE_SIZE; +}; + +Terrain::SurfaceIndices +Terrain::mapSurfaceFacesToIndices() const +{ + SurfaceIndices surfaceIndices; + const auto indexBySurfaceAndTile = std::views::transform([this](const auto & faceItr) { + return std::pair<SurfaceKey, FaceHandle> {{getSurface(*faceItr), getTile(*faceItr)}, *faceItr}; + }); + const auto chunkBySurfaceAndTile = std::views::chunk_by([](const auto & face1, const auto & face2) { + return face1.first.surface == face2.first.surface && face1.first.basePosition == face2.first.basePosition; + }); + for (const auto & faceRange : faces() | indexBySurfaceAndTile | chunkBySurfaceAndTile) { + const SurfaceKey & surfaceKey = faceRange.front().first; + auto indexItr = surfaceIndices.find(surfaceKey); + if (indexItr == surfaceIndices.end()) { + indexItr = surfaceIndices.emplace(surfaceKey, std::vector<GLuint> {}).first; + if (auto existing = meshes.find(surfaceKey); existing != meshes.end()) { + indexItr->second.reserve(static_cast<size_t>(existing->second.count)); + } + } + for (auto push = std::back_inserter(indexItr->second); const auto & [_, face] : faceRange) { + std::ranges::transform(fv_range(face), push, &OpenMesh::VertexHandle::idx); + } + } + return surfaceIndices; +} + +void +Terrain::copyIndicesToBuffers(const SurfaceIndices & surfaceIndices) +{ + for (const auto & [surfaceKey, indices] : surfaceIndices) { + auto meshItr = meshes.find(surfaceKey); + if (meshItr == meshes.end()) { + meshItr = meshes.emplace(surfaceKey, SurfaceArrayBuffer {}).first; + VertexArrayObject {meshItr->second.vertexArray} + .addAttribsFor<Vertex>(verticesBuffer) + .addIndices(meshItr->second.indicesBuffer, indices) + .data(verticesBuffer, GL_ARRAY_BUFFER); + } + else { + VertexArrayObject {meshItr->second.vertexArray} + .addIndices(meshItr->second.indicesBuffer, indices) + .data(verticesBuffer, GL_ARRAY_BUFFER); + } + meshItr->second.count = static_cast<GLsizei>(indices.size()); + meshItr->second.aabb = AxisAlignedBoundingBox<GlobalDistance>::fromPoints( + indices | std::views::transform([this](const auto vertex) { + return this->point(VertexHandle {static_cast<int>(vertex)}); + })); + } +} + +void +Terrain::pruneOrphanMeshes(const SurfaceIndices & surfaceIndices) +{ + if (meshes.size() > surfaceIndices.size()) { + std::erase_if(meshes, [&surfaceIndices](const auto & mesh) { + return !surfaceIndices.contains(mesh.first); + }); + } +} + +void +Terrain::generateMeshes() +{ + copyVerticesToBuffer(); + const auto surfaceIndices = mapSurfaceFacesToIndices(); + copyIndicesToBuffers(surfaceIndices); + pruneOrphanMeshes(surfaceIndices); } void @@ -68,16 +121,41 @@ Terrain::tick(TickDuration) } void -Terrain::render(const SceneShader & shader) const +Terrain::afterChange() +{ + generateMeshes(); +} + +void +Terrain::render(const SceneShader & shader, const Frustum & frustum) const { - shader.landmass.use(); grass->bind(); - meshes.apply(&Mesh::Draw); + + const auto chunkBySurface = std::views::chunk_by([](const auto & itr1, const auto & itr2) { + return itr1.first.surface == itr2.first.surface; + }); + for (const auto & surfaceRange : meshes | chunkBySurface) { + const auto surface = surfaceRange.front().first.surface; + shader.landmass.use(surface ? surface->colorBias : OPEN_SURFACE); + for (const auto & sab : surfaceRange) { + if (frustum.contains(sab.second.aabb)) { + glBindVertexArray(sab.second.vertexArray); + glDrawElements(GL_TRIANGLES, sab.second.count, GL_UNSIGNED_INT, nullptr); + } + } + } + glBindVertexArray(0); } void -Terrain::shadows(const ShadowMapper & shadowMapper) const +Terrain::shadows(const ShadowMapper & shadowMapper, const Frustum & frustum) const { shadowMapper.landmess.use(); - meshes.apply(&Mesh::Draw); + for (const auto & [surface, sab] : meshes) { + if (frustum.shadedBy(sab.aabb)) { + glBindVertexArray(sab.vertexArray); + glDrawElements(GL_TRIANGLES, sab.count, GL_UNSIGNED_INT, nullptr); + } + } + glBindVertexArray(0); } |