diff options
40 files changed, 302 insertions, 139 deletions
diff --git a/game/geoData.cpp b/game/geoData.cpp index 4291a64..5cea4dd 100644 --- a/game/geoData.cpp +++ b/game/geoData.cpp @@ -35,16 +35,16 @@ GeoData::loadFromAsciiGrid(const std::filesystem::path & input) std::vector<VertexHandle> vertices; vertices.reserve(ncols * nrows); GeoData mesh; - mesh.lowerExtent = {xllcorner, yllcorner, std::numeric_limits<GlobalDistance>::max()}; - mesh.upperExtent = {xllcorner + (cellsize * (ncols - 1)), yllcorner + (cellsize * (nrows - 1)), - std::numeric_limits<GlobalDistance>::min()}; + mesh.extents = {{xllcorner, yllcorner, std::numeric_limits<GlobalDistance>::max()}, + {xllcorner + (cellsize * (ncols - 1)), yllcorner + (cellsize * (nrows - 1)), + std::numeric_limits<GlobalDistance>::min()}}; for (size_t row = 0; row < nrows; ++row) { for (size_t col = 0; col < ncols; ++col) { float heightf = 0; f >> heightf; const auto height = static_cast<GlobalDistance>(std::round(heightf * 1000.F)); - mesh.upperExtent.z = std::max(mesh.upperExtent.z, height); - mesh.lowerExtent.z = std::min(mesh.lowerExtent.z, height); + mesh.extents.max.z = std::max(mesh.extents.max.z, height); + mesh.extents.min.z = std::min(mesh.extents.min.z, height); vertices.push_back(mesh.add_vertex({xllcorner + (col * cellsize), yllcorner + (row * cellsize), height})); } } @@ -78,8 +78,7 @@ GeoData::createFlat(GlobalPosition2D lower, GlobalPosition2D upper, GlobalDistan assert((upper - lower) % GRID_SIZE == GlobalPosition2D {}); GeoData mesh; - mesh.lowerExtent = {lower, h}; - mesh.upperExtent = {upper, h}; + mesh.extents = {{lower, h}, {upper, h}}; std::vector<VertexHandle> vertices; for (GlobalDistance row = lower.x; row <= upper.x; row += GRID_SIZE) { @@ -120,7 +119,7 @@ GeoData::intersectRay(const Ray<GlobalPosition3D> & ray, FaceHandle face) const { GeoData::IntersectionResult out; walkUntil(PointFace {ray.start, face}, - ray.start.xy() + (ray.direction.xy() * RelativePosition2D(upperExtent.xy() - lowerExtent.xy())), + ray.start.xy() + (ray.direction.xy() * ::difference(extents.max.xy(), extents.min.xy())), [&out, &ray, this](const auto & step) { BaryPosition bari {}; RelativeDistance dist {}; @@ -325,9 +324,9 @@ GeoData::setHeights(const std::span<const GlobalPosition3D> triangleStrip, const if (triangleStrip.size() < 3) { return {}; } - const auto stripMinMax = std::ranges::minmax(triangleStrip, {}, &GlobalPosition3D::z); - lowerExtent.z = std::min(upperExtent.z, stripMinMax.min.z); - upperExtent.z = std::max(upperExtent.z, stripMinMax.max.z); + for (const auto & vertex : triangleStrip) { + extents += vertex; + } class SetHeights { public: diff --git a/game/geoData.h b/game/geoData.h index b2a75bd..0ff0c42 100644 --- a/game/geoData.h +++ b/game/geoData.h @@ -2,6 +2,7 @@ #include "collections.h" // IWYU pragma: keep IterableCollection #include "geoDataMesh.h" +#include "gfx/aabb.h" #include "surface.h" #include <filesystem> #include <glm/vec2.hpp> @@ -57,10 +58,10 @@ public: std::set<FaceHandle> setHeights(std::span<const GlobalPosition3D> triangleStrip, const SetHeightsOpts &); - [[nodiscard]] auto + [[nodiscard]] auto & getExtents() const { - return std::tie(lowerExtent, upperExtent); + return extents; } template<typename HandleT> @@ -80,5 +81,5 @@ protected: virtual void afterChange(); private: - GlobalPosition3D lowerExtent {}, upperExtent {}; + AxisAlignedBoundingBox<GlobalDistance> extents; }; diff --git a/game/network/rail.cpp b/game/network/rail.cpp index d7de231..2a18b9a 100644 --- a/game/network/rail.cpp +++ b/game/network/rail.cpp @@ -166,7 +166,7 @@ namespace { } void -RailLinks::render(const SceneShader & shader) const +RailLinks::render(const SceneShader & shader, const Frustum &) const { if (!links.objects.empty()) { texture->bind(); diff --git a/game/network/rail.h b/game/network/rail.h index fa64eda..4aef9e3 100644 --- a/game/network/rail.h +++ b/game/network/rail.h @@ -75,7 +75,7 @@ public: RailLinks(); std::shared_ptr<RailLink> addLinksBetween(GlobalPosition3D start, GlobalPosition3D end); - void render(const SceneShader &) const override; + void render(const SceneShader &, const Frustum &) const override; [[nodiscard]] const Surface * getBaseSurface() const override; [[nodiscard]] RelativeDistance getBaseWidth() const override; diff --git a/game/scenary/foliage.cpp b/game/scenary/foliage.cpp index a0ec576..159a078 100644 --- a/game/scenary/foliage.cpp +++ b/game/scenary/foliage.cpp @@ -29,7 +29,7 @@ Foliage::updateStencil(const ShadowStenciller & ss) const } void -Foliage::render(const SceneShader & shader) const +Foliage::render(const SceneShader & shader, const Frustum &) const { if (const auto count = instances.size()) { shader.basicInst.use(); @@ -41,7 +41,7 @@ Foliage::render(const SceneShader & shader) const } void -Foliage::shadows(const ShadowMapper & mapper) const +Foliage::shadows(const ShadowMapper & mapper, const Frustum &) const { if (const auto count = instances.size()) { const auto dimensions = bodyMesh->getDimensions(); diff --git a/game/scenary/foliage.h b/game/scenary/foliage.h index 5da63f0..71bc734 100644 --- a/game/scenary/foliage.h +++ b/game/scenary/foliage.h @@ -24,8 +24,8 @@ public: }; mutable InstanceVertices<LocationVertex> instances; - void render(const SceneShader &) const override; - void shadows(const ShadowMapper &) const override; + void render(const SceneShader &, const Frustum &) const override; + void shadows(const ShadowMapper &, const Frustum &) const override; void updateStencil(const ShadowStenciller &) const override; glTexture shadowStencil = ShadowStenciller::createStencilTexture(256, 256); diff --git a/game/scenary/illuminator.cpp b/game/scenary/illuminator.cpp index e3810ec..f1a02b2 100644 --- a/game/scenary/illuminator.cpp +++ b/game/scenary/illuminator.cpp @@ -59,7 +59,7 @@ Illuminator::postLoad() } void -Illuminator::render(const SceneShader & shader) const +Illuminator::render(const SceneShader & shader, const Frustum &) const { if (const auto count = instances.size()) { shader.basicInst.use(); diff --git a/game/scenary/illuminator.h b/game/scenary/illuminator.h index 44bd583..47ce337 100644 --- a/game/scenary/illuminator.h +++ b/game/scenary/illuminator.h @@ -45,7 +45,7 @@ public: mutable InstanceVertices<LocationVertex> instances; mutable InstanceVertices<SpotLightVertex> instancesSpotLight; mutable InstanceVertices<PointLightVertex> instancesPointLight; - void render(const SceneShader &) const override; + void render(const SceneShader &, const Frustum &) const override; void lights(const SceneShader &) const override; protected: diff --git a/game/terrain.cpp b/game/terrain.cpp index f7de6fd..f10aac6 100644 --- a/game/terrain.cpp +++ b/game/terrain.cpp @@ -1,4 +1,5 @@ #include "terrain.h" +#include "gfx/frustum.h" #include <algorithm> #include <gfx/gl/sceneShader.h> #include <gfx/gl/shadowMapper.h> @@ -13,6 +14,7 @@ #include <vector> static constexpr RGB OPEN_SURFACE {-1}; +static constexpr GlobalDistance TILE_SIZE = 1024 * 1024; // ~1km, power of 2, fast divide template<> VertexArrayObject & @@ -28,21 +30,26 @@ Terrain::SurfaceKey::operator<(const SurfaceKey & other) const < std::tie(other.surface, other.basePosition.x, other.basePosition.y); } -void -Terrain::generateMeshes() +inline void +Terrain::copyVerticesToBuffer() const { - constexpr GlobalDistance TILE_SIZE = 1024 * 1024; // ~1km, power of 2, fast divide - std::ranges::transform(all_vertices(), glMappedBufferWriter<Vertex> {GL_ARRAY_BUFFER, verticesBuffer, n_vertices()}, [this](const auto & vertex) { return Vertex {point(vertex), normal(vertex)}; }); +} + +inline GlobalPosition2D +Terrain::getTile(const FaceHandle & face) const +{ + return point(*cfv_begin(face)).xy() / TILE_SIZE; +}; - std::map<SurfaceKey, std::vector<GLuint>> surfaceIndices; - const auto getTile = [this](FaceHandle face) { - return point(*fv_begin(face)).xy() / TILE_SIZE; - }; - const auto indexBySurfaceAndTile = std::views::transform([this, &getTile](const auto & faceItr) { +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) { @@ -61,7 +68,12 @@ Terrain::generateMeshes() 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()) { @@ -77,7 +89,16 @@ Terrain::generateMeshes() .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); @@ -86,6 +107,15 @@ Terrain::generateMeshes() } void +Terrain::generateMeshes() +{ + copyVerticesToBuffer(); + const auto surfaceIndices = mapSurfaceFacesToIndices(); + copyIndicesToBuffers(surfaceIndices); + pruneOrphanMeshes(surfaceIndices); +} + +void Terrain::tick(TickDuration) { } @@ -97,9 +127,10 @@ Terrain::afterChange() } void -Terrain::render(const SceneShader & shader) const +Terrain::render(const SceneShader & shader, const Frustum & frustum) const { grass->bind(); + const auto chunkBySurface = std::views::chunk_by([](const auto & itr1, const auto & itr2) { return itr1.first.surface == itr2.first.surface; }); @@ -107,20 +138,24 @@ Terrain::render(const SceneShader & shader) const const auto surface = surfaceRange.front().first.surface; shader.landmass.use(surface ? surface->colorBias : OPEN_SURFACE); for (const auto & sab : surfaceRange) { - glBindVertexArray(sab.second.vertexArray); - glDrawElements(GL_TRIANGLES, sab.second.count, GL_UNSIGNED_INT, nullptr); + 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(); for (const auto & [surface, sab] : meshes) { - glBindVertexArray(sab.vertexArray); - glDrawElements(GL_TRIANGLES, sab.count, GL_UNSIGNED_INT, nullptr); + if (frustum.shadedBy(sab.aabb)) { + glBindVertexArray(sab.vertexArray); + glDrawElements(GL_TRIANGLES, sab.count, GL_UNSIGNED_INT, nullptr); + } } glBindVertexArray(0); } diff --git a/game/terrain.h b/game/terrain.h index f5b1b32..1a63296 100644 --- a/game/terrain.h +++ b/game/terrain.h @@ -16,8 +16,8 @@ public: generateMeshes(); } - void render(const SceneShader & shader) const override; - void shadows(const ShadowMapper &) const override; + void render(const SceneShader & shader, const Frustum &) const override; + void shadows(const ShadowMapper &, const Frustum &) const override; void tick(TickDuration) override; @@ -35,14 +35,22 @@ private: glVertexArray vertexArray; glBuffer indicesBuffer; GLsizei count; + AxisAlignedBoundingBox<GlobalDistance> aabb; }; struct SurfaceKey { const Surface * surface; GlobalPosition2D basePosition; - bool operator<(const SurfaceKey &) const; + inline bool operator<(const SurfaceKey &) const; }; + using SurfaceIndices = std::map<SurfaceKey, std::vector<GLuint>>; + void copyVerticesToBuffer() const; + [[nodiscard]] SurfaceIndices mapSurfaceFacesToIndices() const; + void copyIndicesToBuffers(const SurfaceIndices &); + void pruneOrphanMeshes(const SurfaceIndices &); + [[nodiscard]] inline GlobalPosition2D getTile(const FaceHandle &) const; + glBuffer verticesBuffer; std::map<SurfaceKey, SurfaceArrayBuffer> meshes; Texture::Ptr grass = std::make_shared<Texture>("grass.png"); diff --git a/game/vehicles/railVehicleClass.cpp b/game/vehicles/railVehicleClass.cpp index 34c1359..162a29a 100644 --- a/game/vehicles/railVehicleClass.cpp +++ b/game/vehicles/railVehicleClass.cpp @@ -33,7 +33,7 @@ RailVehicleClass::postLoad() } void -RailVehicleClass::render(const SceneShader & shader) const +RailVehicleClass::render(const SceneShader & shader, const Frustum &) const { if (const auto count = static_cast<GLsizei>(instances.size())) { if (texture) { @@ -47,7 +47,7 @@ RailVehicleClass::render(const SceneShader & shader) const } void -RailVehicleClass::shadows(const ShadowMapper & mapper) const +RailVehicleClass::shadows(const ShadowMapper & mapper, const Frustum &) const { if (const auto count = static_cast<GLsizei>(instances.size())) { mapper.dynamicPointInst.use(); diff --git a/game/vehicles/railVehicleClass.h b/game/vehicles/railVehicleClass.h index 88f08c5..6eb4ca5 100644 --- a/game/vehicles/railVehicleClass.h +++ b/game/vehicles/railVehicleClass.h @@ -14,8 +14,8 @@ class Location; class RailVehicleClass : public Renderable, public Asset { public: - void render(const SceneShader & shader) const override; - void shadows(const ShadowMapper & shadowMapper) const override; + void render(const SceneShader & shader, const Frustum &) const override; + void shadows(const ShadowMapper & shadowMapper, const Frustum &) const override; struct LocationVertex { glm::mat3 body, front, back; diff --git a/game/water.cpp b/game/water.cpp index f720e3e..527e85a 100644 --- a/game/water.cpp +++ b/game/water.cpp @@ -82,7 +82,7 @@ Water::generateMeshes() const auto pos = (p * TILE_SIZE) + GlobalPosition2D {x, y}; const auto v = vertexIndex.emplace(pos, vertices.size()); if (v.second) { - const auto cpos = glm::clamp(pos, std::get<0>(extents).xy(), std::get<1>(extents).xy()); + const auto cpos = glm::clamp(pos, extents.min.xy(), extents.max.xy()); vertices.emplace_back(geoData->positionAt(cpos)); } *out++ = static_cast<unsigned int>(v.first->second); @@ -102,7 +102,7 @@ Water::tick(TickDuration dur) } void -Water::render(const SceneShader & shader) const +Water::render(const SceneShader & shader, const Frustum &) const { shader.water.use(waveCycle); water->bind(); diff --git a/game/water.h b/game/water.h index ba46703..f9fe080 100644 --- a/game/water.h +++ b/game/water.h @@ -16,7 +16,7 @@ class Water : public WorldObject, public Renderable { public: explicit Water(std::shared_ptr<GeoData>); - void render(const SceneShader & shader) const override; + void render(const SceneShader & shader, const Frustum &) const override; void tick(TickDuration) override; float waveCycle {0.F}; diff --git a/gfx/aabb.h b/gfx/aabb.h new file mode 100644 index 0000000..ce15a0f --- /dev/null +++ b/gfx/aabb.h @@ -0,0 +1,41 @@ +#pragma once + +#include "maths.h" +#include <algorithm> +#include <tuple> + +template<Arithmetic T, glm::qualifier Q = glm::defaultp> class AxisAlignedBoundingBox { +public: + using V = glm::vec<3, T, Q>; + AxisAlignedBoundingBox() = default; + + AxisAlignedBoundingBox(const V & min, const V & max) : min {min}, max {max} { } + + AxisAlignedBoundingBox & + operator+=(const V & point) + { + min = glm::min(min, point); + max = glm::max(max, point); + return *this; + } + + AxisAlignedBoundingBox + operator-(const V & viewPoint) const + { + return {min - viewPoint, max - viewPoint}; + } + + [[nodiscard]] static AxisAlignedBoundingBox + fromPoints(auto && points) + { + using Limits = std::numeric_limits<T>; + static constexpr const auto INITIAL = std::make_pair(V {Limits::max()}, V {Limits::min()}); + return std::make_from_tuple<AxisAlignedBoundingBox<T, Q>>( + std::ranges::fold_left(points, INITIAL, [](const auto & prev, const auto & point) { + auto & [min, max] = prev; + return std::make_pair(glm::min(min, point), glm::max(max, point)); + })); + } + + V min {}, max {}; +}; diff --git a/gfx/camera.cpp b/gfx/camera.cpp index cc6a2dd..f01054a 100644 --- a/gfx/camera.cpp +++ b/gfx/camera.cpp @@ -12,7 +12,7 @@ Camera::Camera(GlobalPosition3D pos, Angle fov, Angle aspect, GlobalDistance nea Camera::Camera(GlobalPosition3D pos, GlobalDistance near, GlobalDistance far, const glm::mat4 & view, const glm::mat4 & projection) : - Frustum {view, projection}, position {pos}, forward {::north}, up {::up}, near {near}, far {far} + Frustum {pos, view, projection}, forward {::north}, up {::up}, near {near}, far {far} { } diff --git a/gfx/camera.h b/gfx/camera.h index a52ec8b..d1bfc82 100644 --- a/gfx/camera.h +++ b/gfx/camera.h @@ -59,12 +59,6 @@ public: return forward; } - [[nodiscard]] auto - getPosition() const - { - return position; - } - [[nodiscard]] std::array<GlobalPosition4D, 4> extentsAtDist(GlobalDistance) const; [[nodiscard]] static Direction3D upFromForward(const Direction3D & forward); @@ -74,7 +68,6 @@ private: const glm::mat4 & projection); void updateView(); - GlobalPosition3D position; Direction3D forward; Direction3D up; GlobalDistance near, far; diff --git a/gfx/frustum.cpp b/gfx/frustum.cpp index 9e046f6..faa676d 100644 --- a/gfx/frustum.cpp +++ b/gfx/frustum.cpp @@ -3,10 +3,10 @@ #include <collections.h> #include <glm/ext/matrix_transform.hpp> -static constexpr auto PLANES = std::array {0, 1, 2} * std::array {1.F, -1.F}; +static constexpr auto PLANES = std::array {0, 1, 2} * std::array {-1.F, 1.F}; -Frustum::Frustum(const glm::mat4 & view, const glm::mat4 & projection) : - view {view}, projection {projection}, viewProjection {}, inverseViewProjection {}, planes {} +Frustum::Frustum(const GlobalPosition3D & pos, const glm::mat4 & view, const glm::mat4 & projection) : + position {pos}, view {view}, projection {projection}, viewProjection {}, inverseViewProjection {}, planes {} { updateCache(); } @@ -18,6 +18,43 @@ Frustum::updateView(const glm::mat4 & newView) updateCache(); } +bool +Frustum::contains(const BoundingBox & aabb) const +{ + return boundByPlanes(aabb, FACES); +} + +bool +Frustum::shadedBy(const BoundingBox & aabb) const +{ + return boundByPlanes(aabb, FACES - 1); +} + +bool +Frustum::boundByPlanes(const BoundingBox & aabb, size_t nplanes) const +{ + static constexpr auto EXTENT_CORNER_IDXS = [] { + using Extent = GlobalPosition3D BoundingBox::*; + constexpr auto EXTENTS = std::array {&BoundingBox::min, &BoundingBox::max}; + std::array<glm::vec<3, Extent>, 2ZU * 2ZU * 2ZU> out {}; + std::ranges::copy(std::views::cartesian_product(EXTENTS, EXTENTS, EXTENTS) + | std::views::transform( + std::make_from_tuple<glm::vec<3, Extent>, std::tuple<Extent, Extent, Extent>>), + out.begin()); + return out; + }(); + + const std::array<RelativePosition4D, 8> corners + = EXTENT_CORNER_IDXS * [relativeAabb = aabb - position](auto idxs) -> glm::vec4 { + return {(relativeAabb.*(idxs.x)).x, (relativeAabb.*(idxs.y)).y, (relativeAabb.*(idxs.z)).z, 1.F}; + }; + return std::ranges::none_of(std::span(planes).subspan(0, nplanes), [&corners](const auto & frustumPlane) { + return (std::ranges::all_of(corners, [&frustumPlane](const auto & corner) { + return glm::dot(frustumPlane, corner) < 0.F; + })); + }); +} + void Frustum::updateCache() { diff --git a/gfx/frustum.h b/gfx/frustum.h index 25dcc18..a2d90e9 100644 --- a/gfx/frustum.h +++ b/gfx/frustum.h @@ -1,11 +1,13 @@ #pragma once +#include "aabb.h" +#include "config/types.h" #include <array> #include <glm/mat4x4.hpp> class Frustum { public: - Frustum(const glm::mat4 & view, const glm::mat4 & projection); + Frustum(const GlobalPosition3D & pos, const glm::mat4 & view, const glm::mat4 & projection); [[nodiscard]] auto & getFrustumPlanes() const @@ -19,12 +21,24 @@ public: return viewProjection; } + [[nodiscard]] auto + getPosition() const + { + return position; + } + void updateView(const glm::mat4 & view); + using BoundingBox = AxisAlignedBoundingBox<GlobalDistance>; + [[nodiscard]] bool contains(const BoundingBox &) const; + [[nodiscard]] bool shadedBy(const BoundingBox &) const; + protected: static constexpr size_t FACES = 6; void updateCache(); + [[nodiscard]] bool boundByPlanes(const BoundingBox &, size_t nplanes) const; + GlobalPosition3D position; glm::mat4 view, projection; glm::mat4 viewProjection, inverseViewProjection; std::array<glm::vec4, FACES> planes; diff --git a/gfx/gl/sceneProvider.cpp b/gfx/gl/sceneProvider.cpp index 4e271db..e01532e 100644 --- a/gfx/gl/sceneProvider.cpp +++ b/gfx/gl/sceneProvider.cpp @@ -9,6 +9,6 @@ SceneProvider::environment(const SceneShader &, const SceneRenderer & renderer) } void -SceneProvider::shadows(const ShadowMapper &) const +SceneProvider::shadows(const ShadowMapper &, const Frustum &) const { } diff --git a/gfx/gl/sceneProvider.h b/gfx/gl/sceneProvider.h index f5e8e99..f6b7009 100644 --- a/gfx/gl/sceneProvider.h +++ b/gfx/gl/sceneProvider.h @@ -5,6 +5,7 @@ class SceneRenderer; class ShadowMapper; class SceneShader; +class Frustum; class SceneProvider { public: @@ -12,8 +13,8 @@ public: virtual ~SceneProvider() = default; DEFAULT_MOVE_COPY(SceneProvider); - virtual void content(const SceneShader &) const = 0; + virtual void content(const SceneShader &, const Frustum &) const = 0; virtual void environment(const SceneShader &, const SceneRenderer &) const; virtual void lights(const SceneShader &) const = 0; - virtual void shadows(const ShadowMapper &) const; + virtual void shadows(const ShadowMapper &, const Frustum &) const; }; diff --git a/gfx/gl/sceneRenderer.cpp b/gfx/gl/sceneRenderer.cpp index b2a7d78..188c4fd 100644 --- a/gfx/gl/sceneRenderer.cpp +++ b/gfx/gl/sceneRenderer.cpp @@ -71,7 +71,7 @@ SceneRenderer::render(const SceneProvider & scene) const glEnable(GL_DEPTH_TEST); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - scene.content(shader); + scene.content(shader, camera); // Environment pass - // * ambient - clears illumination texture - see setAmbientLight diff --git a/gfx/gl/shadowMapper.cpp b/gfx/gl/shadowMapper.cpp index 908dbdb..6525f76 100644 --- a/gfx/gl/shadowMapper.cpp +++ b/gfx/gl/shadowMapper.cpp @@ -1,6 +1,7 @@ #include "shadowMapper.h" #include "collections.h" #include "game/gamestate.h" +#include "gfx/aabb.h" #include "gfx/gl/shaders/fs-shadowDynamicPointInstWithTextures.h" #include "gfx/gl/shaders/fs-shadowDynamicPointStencil.h" #include "gfx/gl/shaders/gs-commonShadowPoint.h" @@ -19,7 +20,6 @@ #include "maths.h" #include "sceneProvider.h" #include "sceneShader.h" -#include "sorting.h" #include <gfx/camera.h> #include <glm/gtc/type_ptr.hpp> #include <glm/gtx/transform.hpp> @@ -56,7 +56,7 @@ constexpr auto shadowBands = []<GlobalDistance... ints>(const float scaleFactor, std::integer_sequence<GlobalDistance, ints...>) { const auto base = 10'000'000 / pow(scaleFactor, sizeof...(ints) - 1); return std::array {1, static_cast<GlobalDistance>((base * pow(scaleFactor, ints)))...}; - }(6.6F, std::make_integer_sequence<GlobalDistance, ShadowMapper::SHADOW_BANDS>()); + }(4.6F, std::make_integer_sequence<GlobalDistance, ShadowMapper::SHADOW_BANDS>()); static_assert(shadowBands.front() == 1); static_assert(shadowBands.back() == 10'000'000); @@ -102,30 +102,30 @@ ShadowMapper::update(const SceneProvider & scene, const LightDirection & dir, co const auto bandViewExtents = getBandViewExtents(camera, lightViewDir); Definitions out; Sizes sizes; - std::transform(bandViewExtents.begin(), std::prev(bandViewExtents.end()), std::next(bandViewExtents.begin()), - std::back_inserter(out), - [bands = bandViewExtents.size() - 2, &lightViewDir, &sizes](const auto & near, const auto & far) mutable { - const auto extents_minmax - = [extents = std::span {near.begin(), far.end()}](auto && comp, RelativeDistance extra) { - const auto mm = std::minmax_element(extents.begin(), extents.end(), comp); - return std::make_pair(comp.get(*mm.first) - extra, comp.get(*mm.second) + extra); - }; - const std::array extents = {extents_minmax(CompareBy {0}, 0), extents_minmax(CompareBy {1}, 0), - extents_minmax(CompareBy {2}, 10'000)}; - - const auto lightProjection = [](const auto & x, const auto & y, const auto & z) { - return glm::ortho(x.first, x.second, y.first, y.second, -z.second, -z.first); - }(extents[0], extents[1], extents[2]); - - sizes.emplace_back(extents[0].second - extents[0].first, extents[1].second - extents[1].first, - extents[2].second - extents[2].first); + using ExtentsBoundingBox = AxisAlignedBoundingBox<RelativeDistance>; + std::ranges::transform(bandViewExtents | std::views::pairwise, std::back_inserter(out), + [&lightViewDir, &sizes](const auto & band) mutable { + const auto & [near, far] = band; + auto extents = ExtentsBoundingBox::fromPoints(std::span {near.begin(), far.end()}); + extents.min.z -= 10'000.F; + extents.max.z += 10'000.F; + const auto lightProjection = glm::ortho( + extents.min.x, extents.max.x, extents.min.y, extents.max.y, -extents.max.z, -extents.min.z); + sizes.emplace_back(extents.max - extents.min); return lightProjection * lightViewDir; }); for (const auto p : std::initializer_list<const ShadowProgram *> { &landmess, &dynamicPoint, &dynamicPointInst, &dynamicPointInstWithTextures, &stencilShadowProgram}) { p->setView(out, sizes, lightViewPoint); } - scene.shadows(*this); + ExtentsBoundingBox extents {lightViewPoint, lightViewPoint}; + for (const auto & point : bandViewExtents.back()) { + extents += point; + } + const auto lightProjection + = glm::ortho(extents.min.x, extents.max.x, extents.min.y, extents.max.y, -extents.max.z, -extents.min.z); + Frustum frustum {lightViewPoint, lightViewDir, lightProjection}; + scene.shadows(*this, frustum); glCullFace(GL_BACK); diff --git a/gfx/renderable.cpp b/gfx/renderable.cpp index 3594968..27f2459 100644 --- a/gfx/renderable.cpp +++ b/gfx/renderable.cpp @@ -6,7 +6,7 @@ Renderable::lights(const SceneShader &) const } void -Renderable::shadows(const ShadowMapper &) const +Renderable::shadows(const ShadowMapper &, const Frustum &) const { } diff --git a/gfx/renderable.h b/gfx/renderable.h index 83522e3..140c570 100644 --- a/gfx/renderable.h +++ b/gfx/renderable.h @@ -3,6 +3,7 @@ #include <special_members.h> class SceneShader; +class Frustum; class ShadowMapper; class ShadowStenciller; @@ -12,9 +13,9 @@ public: virtual ~Renderable() = default; DEFAULT_MOVE_COPY(Renderable); - virtual void render(const SceneShader & shader) const = 0; + virtual void render(const SceneShader & shader, const Frustum &) const = 0; virtual void lights(const SceneShader & shader) const; - virtual void shadows(const ShadowMapper & shadowMapper) const; + virtual void shadows(const ShadowMapper & shadowMapper, const Frustum &) const; virtual void updateStencil(const ShadowStenciller & lightDir) const; }; diff --git a/lib/collections.h b/lib/collections.h index b921424..e182af5 100644 --- a/lib/collections.h +++ b/lib/collections.h @@ -140,16 +140,16 @@ vectorOfN(std::integral auto N, T start = {}, T step = 1) template<template<typename...> typename Rtn = std::vector, typename In> [[nodiscard]] auto -materializeRange(const In begin, const In end) +materializeRange(In && begin, In && end) { - return Rtn(begin, end); + return Rtn(std::forward<In>(begin), std::forward<In>(end)); } template<template<typename...> typename Rtn = std::vector, IterableCollection In> [[nodiscard]] auto -materializeRange(const In & in) +materializeRange(In && in) { - return materializeRange<Rtn>(in.begin(), in.end()); + return materializeRange<Rtn>(std::forward<In>(in).begin(), std::forward<In>(in).end()); } template<template<typename...> typename Rtn = std::vector, typename In> diff --git a/test/Jamfile.jam b/test/Jamfile.jam index 8219398..bedc2ad 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -71,5 +71,6 @@ explicit perf-assetFactory ; explicit perf-persistence ; explicit perf-geoData ; explicit perf-instancing ; -alias perf : perf-assetFactory perf-persistence perf-geoData perf-instancing ; +explicit perf-terrain ; +alias perf : perf-assetFactory perf-persistence perf-geoData perf-instancing perf-terrain ; explicit perf ; diff --git a/test/perf-terrain.cpp b/test/perf-terrain.cpp index 81cb16c..e75f80b 100644 --- a/test/perf-terrain.cpp +++ b/test/perf-terrain.cpp @@ -1,4 +1,7 @@ #include "game/terrain.h" +#include "gfx/camera.h" +#include "gfx/frustum.h" +#include "gfx/gl/sceneShader.h" #include "testMainWindow.h" #include <benchmark/benchmark.h> @@ -14,8 +17,22 @@ namespace { terrain.generateMeshes(); } } + + void + terrainRender(benchmark::State & state) + { + Terrain terrain {GeoData::loadFromAsciiGrid(FIXTURESDIR "height/SD19.asc")}; + SceneShader shader; + Camera cam {terrain.getExtents().min + GlobalPosition3D {0, 0, 10000}, 45.F, 1.F, 1, 10000}; + cam.setForward(::north + ::east); + + for (auto _ : state) { + terrain.render(shader, cam); + } + } } BENCHMARK(terrainMeshgen); +BENCHMARK(terrainRender); BENCHMARK_MAIN(); diff --git a/test/test-assetFactory.cpp b/test/test-assetFactory.cpp index 6036721..9bade82 100644 --- a/test/test-assetFactory.cpp +++ b/test/test-assetFactory.cpp @@ -38,10 +38,10 @@ public: } void - content(const SceneShader & shader) const override + content(const SceneShader & shader, const Frustum & frustum) const override { shader.basic.use(Location {{0, 0, 0}, {0, 0, 0}}); - objects.apply(&Renderable::render, shader); + objects.apply(&Renderable::render, shader, frustum); } void @@ -58,10 +58,10 @@ public: } void - shadows(const ShadowMapper & mapper) const override + shadows(const ShadowMapper & mapper, const Frustum & frustum) const override { mapper.dynamicPoint.use(Location {{0, 0, 0}, {0, 0, 0}}); - objects.apply(&Renderable::shadows, mapper); + objects.apply(&Renderable::shadows, mapper, frustum); } void diff --git a/test/test-geoData.cpp b/test/test-geoData.cpp index 2332513..e3ef9ad 100644 --- a/test/test-geoData.cpp +++ b/test/test-geoData.cpp @@ -255,9 +255,9 @@ BOOST_DATA_TEST_CASE(deform, loadFixtureJson<DeformTerrainData>("geoData/deform/ const Terrain terrain; void - content(const SceneShader & shader) const override + content(const SceneShader & shader, const Frustum & frustum) const override { - terrain.render(shader); + terrain.render(shader, frustum); } void @@ -273,9 +273,9 @@ BOOST_DATA_TEST_CASE(deform, loadFixtureJson<DeformTerrainData>("geoData/deform/ } void - shadows(const ShadowMapper & shadowMapper) const override + shadows(const ShadowMapper & shadowMapper, const Frustum & frustum) const override { - terrain.shadows(shadowMapper); + terrain.shadows(shadowMapper, frustum); } }; diff --git a/test/test-lib.cpp b/test/test-lib.cpp index 17c0f63..ec91f6e 100644 --- a/test/test-lib.cpp +++ b/test/test-lib.cpp @@ -6,6 +6,7 @@ #include <stream_support.h> #include <collections.h> +#include <gfx/aabb.h> #include <glArrays.h> #include <glad/gl.h> #include <set> @@ -110,3 +111,14 @@ BOOST_DATA_TEST_CASE(mergeCloseInts, tolerance))); BOOST_CHECK_EQUAL_COLCOL(mutableCollection, expected); } + +BOOST_AUTO_TEST_CASE(aabb_from_points) +{ + const auto aabb = AxisAlignedBoundingBox<GlobalDistance>::fromPoints(std::vector<GlobalPosition3D> { + {1, 2, 3}, + {4, 2, 1}, + {9, 1, 7}, + }); + BOOST_CHECK_EQUAL(aabb.min, GlobalPosition3D(1, 1, 1)); + BOOST_CHECK_EQUAL(aabb.max, GlobalPosition3D(9, 2, 7)); +} diff --git a/test/test-network.cpp b/test/test-network.cpp index e7419b5..51fea8b 100644 --- a/test/test-network.cpp +++ b/test/test-network.cpp @@ -70,7 +70,7 @@ struct TestNetwork : public NetworkOf<TestLink, TestLinkS> { } void - render(const SceneShader &) const override + render(const SceneShader &, const Frustum &) const override { } diff --git a/test/test-render.cpp b/test/test-render.cpp index 3c453bd..8390d25 100644 --- a/test/test-render.cpp +++ b/test/test-render.cpp @@ -38,6 +38,8 @@ class TestScene : public SceneProvider { public: TestScene() { + terrain->point(GeoData::VertexHandle {517}).z = 100'000; + terrain->generateMeshes(); gameState->assets = AssetFactory::loadAll(RESDIR); brush47rvc = std::dynamic_pointer_cast<RailVehicleClass>(gameState->assets.at("brush-47")); std::random_device randomdev {}; @@ -68,14 +70,14 @@ public: } void - content(const SceneShader & shader) const override + content(const SceneShader & shader, const Frustum & frustum) const override { - terrain->render(shader); - water.render(shader); - rail.render(shader); - std::ranges::for_each(gameState->assets, [&shader](const auto & asset) { + terrain->render(shader, frustum); + water.render(shader, frustum); + rail.render(shader, frustum); + std::ranges::for_each(gameState->assets, [&shader, &frustum](const auto & asset) { if (const auto renderable = std::dynamic_pointer_cast<const Renderable>(asset.second)) { - renderable->render(shader); + renderable->render(shader, frustum); } }); } @@ -92,12 +94,12 @@ public: } void - shadows(const ShadowMapper & shadowMapper) const override + shadows(const ShadowMapper & shadowMapper, const Frustum & frustum) const override { - terrain->shadows(shadowMapper); - std::ranges::for_each(gameState->assets, [&shadowMapper](const auto & asset) { + terrain->shadows(shadowMapper, frustum); + std::ranges::for_each(gameState->assets, [&shadowMapper, &frustum](const auto & asset) { if (const auto renderable = std::dynamic_pointer_cast<const Renderable>(asset.second)) { - renderable->shadows(shadowMapper); + renderable->shadows(shadowMapper, frustum); } }); } @@ -171,10 +173,10 @@ BOOST_AUTO_TEST_CASE(terrain) Water water {terrain}; void - content(const SceneShader & shader) const override + content(const SceneShader & shader, const Frustum & frustum) const override { - terrain->render(shader); - water.render(shader); + terrain->render(shader, frustum); + water.render(shader, frustum); } void @@ -190,9 +192,9 @@ BOOST_AUTO_TEST_CASE(terrain) } void - shadows(const ShadowMapper & shadowMapper) const override + shadows(const ShadowMapper & shadowMapper, const Frustum & frustum) const override { - terrain->shadows(shadowMapper); + terrain->shadows(shadowMapper, frustum); } }; @@ -219,9 +221,9 @@ BOOST_AUTO_TEST_CASE(railnet) } void - content(const SceneShader & shader) const override + content(const SceneShader & shader, const Frustum & frustum) const override { - net.render(shader); + net.render(shader, frustum); } void @@ -237,7 +239,7 @@ BOOST_AUTO_TEST_CASE(railnet) } void - shadows(const ShadowMapper &) const override + shadows(const ShadowMapper &, const Frustum &) const override { } }; diff --git a/ui/editNetwork.cpp b/ui/editNetwork.cpp index c4c0297..2887491 100644 --- a/ui/editNetwork.cpp +++ b/ui/editNetwork.cpp @@ -48,19 +48,19 @@ EditNetwork::handleInput(const SDL_Event & e, const UIComponent::Position & pare } void -EditNetwork::render(const SceneShader & shader) const +EditNetwork::render(const SceneShader & shader, const Frustum & frustum) const { if (builder) { blue.bind(); shader.absolute.use(); - builder->render(shader); + builder->render(shader, frustum); } } void -EditNetwork::Builder::render(const SceneShader & shader) const +EditNetwork::Builder::render(const SceneShader & shader, const Frustum & frustum) const { - candidateLinks.apply<const Renderable>(&Renderable::render, shader); + candidateLinks.apply<const Renderable>(&Renderable::render, shader, frustum); } void diff --git a/ui/editNetwork.h b/ui/editNetwork.h index 2ae467d..ae887bd 100644 --- a/ui/editNetwork.h +++ b/ui/editNetwork.h @@ -18,7 +18,7 @@ public: bool click(const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> &) override; bool move(const SDL_MouseMotionEvent & e, const Ray<GlobalPosition3D> &) override; bool handleInput(const SDL_Event & e, const UIComponent::Position &) override; - void render(const SceneShader &) const override; + void render(const SceneShader &, const Frustum &) const override; void render(const UIShader & shader, const UIComponent::Position & pos) const override; using NetworkClickPos = std::variant<GlobalPosition3D, Node::Ptr>; @@ -26,7 +26,7 @@ public: class Builder { public: virtual ~Builder() = default; - virtual void render(const SceneShader & shader) const; + virtual void render(const SceneShader & shader, const Frustum &) const; virtual std::string hint() const = 0; virtual void click(Network *, const GeoData *, const SDL_MouseButtonEvent &, const Ray<GlobalPosition3D> &) = 0; virtual void move(Network *, const GeoData *, const SDL_MouseMotionEvent &, const Ray<GlobalPosition3D> &) = 0; diff --git a/ui/gameMainSelector.cpp b/ui/gameMainSelector.cpp index a817f69..23ae8c0 100644 --- a/ui/gameMainSelector.cpp +++ b/ui/gameMainSelector.cpp @@ -33,10 +33,10 @@ GameMainSelector::render(const UIShader & shader, const Position & parentPos) co } void -GameMainSelector::render(const SceneShader & shader) const +GameMainSelector::render(const SceneShader & shader, const Frustum & frustum) const { if (target) { - target->render(shader); + target->render(shader, frustum); } } @@ -115,6 +115,6 @@ GameMainSelector::Component::render(const UIShader &, const UIComponent::Positio } void -GameMainSelector::Component::render(const SceneShader &) const +GameMainSelector::Component::render(const SceneShader &, const Frustum &) const { } diff --git a/ui/gameMainSelector.h b/ui/gameMainSelector.h index ccf0fa0..e715823 100644 --- a/ui/gameMainSelector.h +++ b/ui/gameMainSelector.h @@ -24,13 +24,13 @@ public: virtual bool move(const SDL_MouseMotionEvent &, const Ray<GlobalPosition3D> &); virtual bool handleInput(const SDL_Event &, const Position & pos); virtual void render(const UIShader & shader, const Position & pos) const; - virtual void render(const SceneShader &) const; + virtual void render(const SceneShader &, const Frustum &) const; }; GameMainSelector(const Camera * c, ScreenAbsCoord size); void render(const UIShader & shader, const Position & pos) const override; - void render(const SceneShader & shader) const override; + void render(const SceneShader & shader, const Frustum &) const override; bool handleInput(const SDL_Event & e, const Position &) override; diff --git a/ui/gameMainWindow.cpp b/ui/gameMainWindow.cpp index c53300b..f63137c 100644 --- a/ui/gameMainWindow.cpp +++ b/ui/gameMainWindow.cpp @@ -54,15 +54,15 @@ GameMainWindow::render() const } void -GameMainWindow::content(const SceneShader & shader) const +GameMainWindow::content(const SceneShader & shader, const Frustum & frustum) const { for (const auto & [id, asset] : gameState->assets) { if (const auto r = std::dynamic_pointer_cast<const Renderable>(asset)) { - r->render(shader); + r->render(shader, frustum); } } - gameState->world.apply<Renderable>(&Renderable::render, shader); - uiComponents.apply<WorldOverlay>(&WorldOverlay::render, shader); + gameState->world.apply<Renderable>(&Renderable::render, shader, frustum); + uiComponents.apply<WorldOverlay>(&WorldOverlay::render, shader, frustum); } void @@ -78,12 +78,12 @@ GameMainWindow::lights(const SceneShader & shader) const } void -GameMainWindow::shadows(const ShadowMapper & shadowMapper) const +GameMainWindow::shadows(const ShadowMapper & shadowMapper, const Frustum & frustum) const { for (const auto & [id, asset] : gameState->assets) { if (const auto r = std::dynamic_pointer_cast<const Renderable>(asset)) { - r->shadows(shadowMapper); + r->shadows(shadowMapper, frustum); } } - gameState->world.apply<Renderable>(&Renderable::shadows, shadowMapper); + gameState->world.apply<Renderable>(&Renderable::shadows, shadowMapper, frustum); } diff --git a/ui/gameMainWindow.h b/ui/gameMainWindow.h index fcbd135..43980e8 100644 --- a/ui/gameMainWindow.h +++ b/ui/gameMainWindow.h @@ -17,8 +17,8 @@ public: void render() const override; private: - void content(const SceneShader &) const override; + void content(const SceneShader &, const Frustum &) const override; void environment(const SceneShader &, const SceneRenderer &) const override; void lights(const SceneShader &) const override; - void shadows(const ShadowMapper &) const override; + void shadows(const ShadowMapper &, const Frustum &) const override; }; diff --git a/ui/worldOverlay.h b/ui/worldOverlay.h index 18fab3f..a0f3b65 100644 --- a/ui/worldOverlay.h +++ b/ui/worldOverlay.h @@ -1,9 +1,10 @@ #pragma once class SceneShader; +class Frustum; class WorldOverlay { public: virtual ~WorldOverlay() = default; - virtual void render(const SceneShader &) const = 0; + virtual void render(const SceneShader &, const Frustum &) const = 0; }; |