pipeline/terrain.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/pipeline/terrain.h" | ||
| 2 | |||
| 3 | #include "terraingen/layers/evaluation_cache.h" | ||
| 4 | #include "terraingen/util/checks.h" | ||
| 5 | #include "terraingen/util/log.h" | ||
| 6 | #include "terraingen/util/parallel.h" | ||
| 7 | |||
| 8 | #include <algorithm> | ||
| 9 | #include <cmath> | ||
| 10 | #include <memory> | ||
| 11 | #include <optional> | ||
| 12 | #include <stdexcept> | ||
| 13 | |||
| 14 | namespace terraingen { | ||
| 15 | |||
| 16 | namespace { | ||
| 17 | |||
| 18 | /// Sets `heights` to the terrain's source at `points`, then applies its first `layers` layers to | ||
| 19 | /// them all at once, each on the ground beneath it. | ||
| 20 | 41957430 | void EvaluateStack(const Terrain& terrain, std::size_t layers, std::span<const Vec2d> points, | |
| 21 | std::span<float> heights, double spacing, EvaluationCache* cache = nullptr) { | ||
| 22 | 85130143 | for (std::size_t i = 0; i < points.size(); ++i) { | |
| 23 | 43172713 | heights[i] = terrain.source ? terrain.source->HeightAt(points[i].x, points[i].y) : 0.0f; | |
| 24 | } | ||
| 25 | 88523128 | for (std::size_t n = 0; n < layers; ++n) { | |
| 26 | 46565698 | const TerrainGround ground(terrain, n); | |
| 27 | 46565698 | SampleBlock samples(points, heights); | |
| 28 | 46565698 | terrain.layers[n]->Apply(samples, LayerContext{spacing, 1, &ground, Backend::Cpu, cache}); | |
| 29 | 46565698 | } | |
| 30 | 41957430 | } | |
| 31 | |||
| 32 | } // namespace | ||
| 33 | |||
| 34 | 65 | Raster<float> Evaluate(const Terrain& terrain, const RasterGrid& grid, double spacing) { | |
| 35 | 65 | return Evaluate(terrain, grid, EvaluationOptions{spacing, 1}); | |
| 36 | } | ||
| 37 | |||
| 38 | 1070 | Raster<float> Evaluate(const Terrain& terrain, const RasterGrid& grid, | |
| 39 | const EvaluationOptions& options) { | ||
| 40 | 1070 | ValidateTerrain(terrain); | |
| 41 | 1069 | RequireNonNegative(options.spacing, "Sample spacing"); | |
| 42 | 1066 | const double resolved = std::max(options.spacing, grid.Spacing(terrain.surface)); | |
| 43 | 1066 | const Backend backend = ResolveBackend(options.backend.value_or(DefaultBackend())); | |
| 44 | 1066 | Raster<float> heights = grid.MakeRaster(); | |
| 45 | 1066 | if (terrain.source) { | |
| 46 | 913 | terrain.source->Fill(heights); | |
| 47 | } | ||
| 48 | 2575 | for (std::size_t n = 0; n < terrain.layers.size(); ++n) { | |
| 49 | 1510 | const Stopwatch stopwatch; | |
| 50 | 1510 | const TerrainGround ground(terrain, n); | |
| 51 | 1510 | terrain.layers[n]->Apply(heights, | |
| 52 | 1510 | LayerContext{resolved, options.threads, &ground, backend}); | |
| 53 | 1509 | if (options.onLayer) { | |
| 54 | 300 | options.onLayer(n, stopwatch.Milliseconds()); | |
| 55 | } | ||
| 56 | 1510 | } | |
| 57 | 1065 | return heights; | |
| 58 | 1 | } | |
| 59 | |||
| 60 | 65143 | void ValidateTerrain(const Terrain& terrain) { | |
| 61 | 65143 | if (std::any_of(terrain.layers.begin(), terrain.layers.end(), | |
| 62 | 158010 | [](const auto& l) { return !l; })) { | |
| 63 | 6 | throw std::invalid_argument("A terrain's layers must not be null"); | |
| 64 | } | ||
| 65 | 65137 | } | |
| 66 | |||
| 67 | 50508 | std::vector<float> EvaluatePoints(const Terrain& terrain, std::span<const Vec2d> points, | |
| 68 | double spacing, unsigned threads) { | ||
| 69 | 50508 | ValidateTerrain(terrain); | |
| 70 | 50506 | RequirePositive(spacing, "Sample spacing"); | |
| 71 | 50503 | constexpr std::size_t kBatch = 256; | |
| 72 | 50503 | std::vector<float> heights(points.size()); | |
| 73 | 50503 | const std::size_t batches = (points.size() + kBatch - 1) / kBatch; | |
| 74 | 50503 | std::optional<EvaluationCache> cache; | |
| 75 | 50503 | if (batches > 1) { | |
| 76 | 16 | cache.emplace(); | |
| 77 | } | ||
| 78 | 50503 | ParallelFor(batches, threads, [&](std::size_t batch) { | |
| 79 | 51441 | const std::size_t begin = batch * kBatch; | |
| 80 | 51441 | const std::size_t count = std::min(kBatch, points.size() - begin); | |
| 81 | 102882 | EvaluateStack(terrain, terrain.layers.size(), points.subspan(begin, count), | |
| 82 | 51441 | std::span<float>(heights).subspan(begin, count), spacing, | |
| 83 | 52396 | cache ? &*cache : nullptr); | |
| 84 | 51441 | }); | |
| 85 | 101006 | return heights; | |
| 86 | 50503 | } | |
| 87 | |||
| 88 | 50486 | float EvaluateAt(const Terrain& terrain, double x, double y, double spacing) { | |
| 89 | 50486 | const Vec2d point{x, y}; | |
| 90 | 50486 | return EvaluatePoints(terrain, {&point, 1}, spacing, 1)[0]; | |
| 91 | } | ||
| 92 | |||
| 93 | 46591716 | TerrainGround::TerrainGround(const Terrain& terrain, std::size_t layers) | |
| 94 | 46591716 | : terrain_(terrain), layers_(layers) { | |
| 95 | 46591716 | if (layers_ > terrain_.layers.size()) { | |
| 96 | 2 | throw std::invalid_argument("The terrain has fewer layers than the ground asks for"); | |
| 97 | } | ||
| 98 | 46591716 | } | |
| 99 | |||
| 100 | 41896782 | float TerrainGround::HeightAt(double x, double y, double spacing) const { | |
| 101 | 41896782 | const Vec2d point{x, y}; | |
| 102 | 41896782 | float height = 0.0f; | |
| 103 | 41896782 | EvaluateStack(terrain_, layers_, {&point, 1}, {&height, 1}, spacing); | |
| 104 | 41896782 | return height; | |
| 105 | } | ||
| 106 | |||
| 107 | 45607 | void TerrainGround::HeightsAt(std::span<const Vec2d> points, std::span<float> heights, | |
| 108 | double spacing) const { | ||
| 109 | 45607 | constexpr std::size_t kBatch = 256; | |
| 110 | 45607 | const std::size_t batches = (points.size() + kBatch - 1) / kBatch; | |
| 111 | 45607 | std::optional<EvaluationCache> cache; | |
| 112 | 45607 | if (batches > 1) { | |
| 113 | 58 | cache.emplace(); | |
| 114 | } | ||
| 115 | 45607 | ParallelFor(batches, 0, [&](std::size_t batch) { | |
| 116 | 9207 | const std::size_t begin = batch * kBatch; | |
| 117 | 9207 | const std::size_t count = std::min(kBatch, points.size() - begin); | |
| 118 | 12373 | EvaluateStack(terrain_, layers_, points.subspan(begin, count), | |
| 119 | 12373 | heights.subspan(begin, count), spacing, cache ? &*cache : nullptr); | |
| 120 | 9207 | }); | |
| 121 | 45607 | } | |
| 122 | |||
| 123 | namespace { | ||
| 124 | |||
| 125 | /// The ground beneath the first layers of a terrain it keeps. | ||
| 126 | class OwnedGround final : public Ground { | ||
| 127 | public: | ||
| 128 | 6 | OwnedGround(const Terrain& terrain, std::size_t layers) | |
| 129 | 7 | : terrain_(std::make_shared<const Terrain>(terrain)), ground_(*terrain_, layers) {} | |
| 130 | |||
| 131 | 1 | float HeightAt(double x, double y, double spacing) const override { | |
| 132 | 1 | return ground_.HeightAt(x, y, spacing); | |
| 133 | } | ||
| 134 | 5 | void HeightsAt(std::span<const Vec2d> points, std::span<float> heights, | |
| 135 | double spacing) const override { | ||
| 136 | 5 | ground_.HeightsAt(points, heights, spacing); | |
| 137 | 5 | } | |
| 138 | |||
| 139 | private: | ||
| 140 | std::shared_ptr<const Terrain> terrain_; | ||
| 141 | TerrainGround ground_; | ||
| 142 | }; | ||
| 143 | |||
| 144 | } // namespace | ||
| 145 | |||
| 146 | 6 | std::shared_ptr<const Ground> SharedGround(const Terrain& terrain, std::size_t layers) { | |
| 147 | 6 | return std::make_shared<OwnedGround>(terrain, layers); | |
| 148 | } | ||
| 149 | |||
| 150 | 37 | Raster<float> Evaluate(const Terrain& terrain) { | |
| 151 | 37 | ValidateTerrain(terrain); | |
| 152 | 35 | if (!terrain.source) { | |
| 153 | 1 | throw std::invalid_argument("A terrain without a source needs a grid to evaluate over"); | |
| 154 | } | ||
| 155 | 34 | return Evaluate(terrain, terrain.source->Grid()); | |
| 156 | } | ||
| 157 | |||
| 158 | } // namespace terraingen | ||
| 159 |