TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
159 / 160
99.4%
Branches:
122 / 133
91.7%
Functions:
19 / 19
100.0%

layers/region.cpp
Line Branch Exec Source
1 #include "terraingen/layers/region.h"
2
3 #include "terraingen/raster/cell_loops.h"
4 #include "terraingen/util/checks.h"
5 #include "terraingen/util/parallel.h"
6
7 #include <algorithm>
8 #include <cmath>
9 #include <iterator>
10 #include <limits>
11 #include <numbers>
12 #include <stdexcept>
13 #include <utility>
14 #include <vector>
15
16 namespace terraingen {
17
18 namespace {
19
20 /// `height` moved towards `inner` by `weight` in [0, 1].
21 1298358 float Blend(float height, float inner, double weight) {
22
2/2
✓ Branch 0 taken 1015531 times.
✓ Branch 1 taken 282827 times.
1298358 if (weight >= 1.0) {
23 1015531 return inner;
24 }
25 282827 return static_cast<float>(static_cast<double>(height) +
26 282827 weight * (static_cast<double>(inner) - static_cast<double>(height)));
27 }
28
29 /// The smallest window holding every one of `windows`; empty when there is none.
30 32 PixelWindow UnionOf(const std::vector<PixelWindow>& windows) {
31
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 29 times.
32 if (windows.empty()) {
32 3 return {};
33 }
34 29 PixelWindow joined = windows.front();
35
2/2
✓ Branch 4 taken 29 times.
✓ Branch 5 taken 29 times.
58 for (const PixelWindow& window : windows) {
36 29 joined.colBegin = std::min(joined.colBegin, window.colBegin);
37 29 joined.colEnd = std::max(joined.colEnd, window.colEnd);
38 29 joined.rowBegin = std::min(joined.rowBegin, window.rowBegin);
39 29 joined.rowEnd = std::max(joined.rowEnd, window.rowEnd);
40 }
41 29 return joined;
42 }
43
44 /// The cells of `window` of `heights` as a raster of their own, on the same lattice.
45 20 Raster<float> CopyWindow(const Raster<float>& heights, const PixelWindow& window) {
46
1/1
✓ Branch 1 taken 20 times.
20 Raster<float> copy(window.colEnd - window.colBegin, window.rowEnd - window.rowBegin);
47 20 GeoTransform transform = heights.Transform();
48 20 const Vec2d origin = transform.PixelToWorld(static_cast<double>(window.colBegin),
49 20 static_cast<double>(window.rowBegin));
50 20 transform.originX = origin.x;
51 20 transform.originY = origin.y;
52 20 copy.SetTransform(transform);
53 20 copy.SetNoData(heights.NoData());
54
2/2
✓ Branch 0 taken 4582 times.
✓ Branch 1 taken 20 times.
4602 for (std::size_t row = window.rowBegin; row < window.rowEnd; ++row) {
55
2/2
✓ Branch 0 taken 1385538 times.
✓ Branch 1 taken 4582 times.
1390120 for (std::size_t col = window.colBegin; col < window.colEnd; ++col) {
56 1385538 copy(col - window.colBegin, row - window.rowBegin) = heights(col, row);
57 }
58 }
59 40 return copy;
60 }
61
62 } // namespace
63
64 1537839 double RegionShape::DistanceOutside(double x, double y, const Surface& surface) const {
65
2/2
✓ Branch 1 taken 2478 times.
✓ Branch 2 taken 1535361 times.
1537839 if (surface.IsBody()) {
66
2/2
✓ Branch 0 taken 2248 times.
✓ Branch 1 taken 230 times.
2478 if (kind == Kind::Circle) {
67
1/1
✓ Branch 1 taken 2248 times.
2248 return std::fmax(0.0, surface.Distance(centre, {x, y}) - radius);
68 }
69 230 double best = std::numeric_limits<double>::infinity();
70
2/2
✓ Branch 2 taken 690 times.
✓ Branch 3 taken 230 times.
920 for (const double shift : {-360.0, 0.0, 360.0}) {
71 690 const double lon = x + shift;
72 690 const double dx = std::fmax(0.0, std::fmax(rectangle.minX - lon, lon - rectangle.maxX));
73 690 const double dy = std::fmax(0.0, std::fmax(rectangle.minY - y, y - rectangle.maxY));
74 690 const double cosLat = std::cos(y * std::numbers::pi / 180.0);
75 690 best = std::fmin(best, surface.UnitLength() * std::hypot(dx * cosLat, dy));
76 }
77 230 return best;
78 }
79
2/2
✓ Branch 0 taken 582143 times.
✓ Branch 1 taken 953218 times.
1535361 if (kind == Kind::Circle) {
80 582143 return std::fmax(0.0, std::hypot(x - centre.x, y - centre.y) - radius);
81 }
82 953218 const double dx = std::fmax(0.0, std::fmax(rectangle.minX - x, x - rectangle.maxX));
83 953218 const double dy = std::fmax(0.0, std::fmax(rectangle.minY - y, y - rectangle.maxY));
84 953218 return std::hypot(dx, dy);
85 }
86
87 35 Bounds2d RegionShape::Bounds(const Surface& surface) const {
88
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 19 times.
35 if (kind == Kind::Circle) {
89
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 14 times.
16 if (surface.IsBody()) {
90
1/1
✓ Branch 1 taken 2 times.
2 return surface.Expand({centre.x, centre.y, centre.x, centre.y}, radius);
91 }
92 14 return {centre.x - radius, centre.y - radius, centre.x + radius, centre.y + radius};
93 }
94 19 return rectangle;
95 }
96
97 52 void ValidateRegionShape(const RegionShape& shape) {
98
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 28 times.
52 if (shape.kind == RegionShape::Kind::Circle) {
99
5/6
✓ Branch 1 taken 23 times.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 23 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 20 times.
47 if (!(std::isfinite(shape.centre.x) && std::isfinite(shape.centre.y) &&
100
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 20 times.
23 IsNonNegative(shape.radius))) {
101
1/1
✓ Branch 2 taken 4 times.
4 throw std::invalid_argument("A circular region needs a finite centre and radius >= 0");
102 }
103 20 return;
104 }
105 28 const Bounds2d& r = shape.rectangle;
106
5/8
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 28 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 2 times.
✓ Branch 10 taken 26 times.
56 if (!(std::isfinite(r.minX) && std::isfinite(r.minY) && std::isfinite(r.maxX) &&
107
4/6
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 26 times.
28 std::isfinite(r.maxY) && r.minX <= r.maxX && r.minY <= r.maxY)) {
108
1/1
✓ Branch 2 taken 2 times.
2 throw std::invalid_argument("A rectangular region needs finite bounds with min <= max");
109 }
110 }
111
112 34 RegionLayer::RegionLayer(std::shared_ptr<const Layer> inner, RegionShape shape, double feather,
113 34 BlendShape blend, Surface surface)
114 34 : inner_(std::move(inner)), shape_(shape), feather_(feather), blend_(blend), surface_(surface) {
115
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 33 times.
34 if (!inner_) {
116
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("A region layer needs a layer to apply");
117 }
118
1/1
✓ Branch 1 taken 32 times.
33 ValidateRegionShape(shape_);
119
1/1
✓ Branch 2 taken 30 times.
32 RequireNonNegative(feather_, "Region feather");
120 50 }
121
122 1529574 double RegionLayer::Weight(double x, double y) const {
123 1529574 const double distance = shape_.DistanceOutside(x, y, surface_);
124
2/2
✓ Branch 0 taken 1020952 times.
✓ Branch 1 taken 508622 times.
1529574 if (distance <= 0.0) {
125 1020952 return 1.0;
126 }
127
2/2
✓ Branch 0 taken 220281 times.
✓ Branch 1 taken 288341 times.
508622 if (distance >= feather_) {
128 220281 return 0.0;
129 }
130 288341 return Falloff(distance, 0.0, feather_, blend_);
131 }
132
133 8253 bool RegionLayer::Holds(double x, double y) const {
134 8253 return shape_.DistanceOutside(x, y, surface_) <= feather_;
135 }
136
137 32 PixelWindow RegionLayer::InnerWindow(const RasterGrid& grid) const {
138
2/2
✓ Branch 1 taken 32 times.
✓ Branch 4 taken 32 times.
32 const Bounds2d reach = surface_.Expand(shape_.Bounds(surface_), feather_);
139
2/2
✓ Branch 1 taken 32 times.
✓ Branch 4 taken 32 times.
32 return UnionOf(PixelWindowsIn(grid, reach, surface_));
140 }
141
142 11 std::optional<RasterGrid> RegionLayer::InnerGrid(const RasterGrid& grid) const {
143
1/1
✓ Branch 1 taken 11 times.
11 const PixelWindow window = InnerWindow(grid);
144
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 9 times.
11 if (window.Empty()) {
145 2 return std::nullopt;
146 }
147 9 GeoTransform transform = grid.transform;
148 9 const Vec2d origin = transform.PixelToWorld(static_cast<double>(window.colBegin),
149 9 static_cast<double>(window.rowBegin));
150 9 transform.originX = origin.x;
151 9 transform.originY = origin.y;
152 9 return RasterGrid{window.colEnd - window.colBegin, window.rowEnd - window.rowBegin, transform};
153 }
154
155 8 CraterFeatures RegionLayer::CratersOver(const RasterGrid& grid, double spacing) const {
156
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 const auto* placer = dynamic_cast<const CraterPlacer*>(inner_.get());
157
1/1
✓ Branch 1 taken 8 times.
8 const std::optional<RasterGrid> inner = InnerGrid(grid);
158 8 CraterFeatures held;
159
6/6
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 5 times.
8 if (placer == nullptr || !inner) {
160 3 return held;
161 }
162
1/1
✓ Branch 2 taken 5 times.
5 const CraterFeatures all = placer->CratersOver(*inner, spacing);
163
2/2
✓ Branch 1 taken 8238 times.
✓ Branch 2 taken 5 times.
8243 for (std::size_t n = 0; n < all.craters.Size(); ++n) {
164
1/1
✓ Branch 1 taken 8238 times.
8238 const Crater crater = all.craters.At(n);
165
3/3
✓ Branch 1 taken 8238 times.
✓ Branch 3 taken 8130 times.
✓ Branch 4 taken 108 times.
8238 if (Holds(crater.x, crater.y)) {
166
1/1
✓ Branch 1 taken 8130 times.
8130 held.craters.Append(crater);
167
1/1
✓ Branch 2 taken 8130 times.
8130 held.attributes.push_back(all.attributes[n]);
168 }
169 }
170 5 return held;
171 5 }
172
173 1 BoulderFeatures RegionLayer::BouldersOver(const RasterGrid& grid, double spacing,
174 const Ground* ground) const {
175
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 const auto* placer = dynamic_cast<const BoulderPlacer*>(inner_.get());
176
1/1
✓ Branch 1 taken 1 time.
1 const std::optional<RasterGrid> inner = InnerGrid(grid);
177 1 BoulderFeatures held;
178
3/6
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
1 if (placer == nullptr || !inner) {
179 ✗ return held;
180 }
181
1/1
✓ Branch 2 taken 1 time.
1 const BoulderFeatures all = placer->BouldersOver(*inner, spacing, ground);
182
2/2
✓ Branch 1 taken 1 time.
✓ Branch 6 taken 1 time.
1 std::copy_if(all.boulders.begin(), all.boulders.end(), std::back_inserter(held.boulders),
183 6 [&](const Boulder& b) { return Holds(b.x, b.y); });
184
2/2
✓ Branch 1 taken 1 time.
✓ Branch 6 taken 1 time.
1 std::copy_if(all.models.begin(), all.models.end(), std::back_inserter(held.models),
185 6 [&](const BoulderModel& m) { return Holds(m.boulder.x, m.boulder.y); });
186 1 return held;
187 1 }
188
189 72039 void RegionLayer::ApplySamples(SampleBlock& samples, const LayerContext& context) const {
190
2/2
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 72018 times.
72039 if (Raster<float>* grid = samples.Grid()) {
191 21 ApplyToGrid(*grid, context);
192 } else {
193 72018 ApplyToPoints(samples, context);
194 }
195 72039 }
196
197 72018 void RegionLayer::ApplyToPoints(SampleBlock& samples, const LayerContext& context) const {
198 72018 std::vector<std::size_t> indices;
199 72018 std::vector<Vec2d> positions;
200 72018 std::vector<float> heights;
201 72018 std::vector<double> weights;
202
2/2
✓ Branch 1 taken 72018 times.
✓ Branch 2 taken 72018 times.
144036 for (std::size_t i = 0; i < samples.Size(); ++i) {
203
1/1
✓ Branch 1 taken 72018 times.
72018 const Vec2d p = samples.Position(i);
204
4/4
✓ Branch 1 taken 72018 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 72017 times.
✓ Branch 6 taken 72017 times.
72018 const double weight = samples.IsNoData(i) ? 0.0 : Weight(p.x, p.y);
205
2/2
✓ Branch 0 taken 10927 times.
✓ Branch 1 taken 61091 times.
72018 if (weight > 0.0) {
206
1/1
✓ Branch 1 taken 10927 times.
10927 indices.push_back(i);
207
1/1
✓ Branch 1 taken 10927 times.
10927 positions.push_back(p);
208
1/1
✓ Branch 2 taken 10927 times.
10927 heights.push_back(samples.Height(i));
209
1/1
✓ Branch 1 taken 10927 times.
10927 weights.push_back(weight);
210 }
211 }
212
1/1
✓ Branch 3 taken 72018 times.
72018 SampleBlock inside(positions, heights);
213
1/1
✓ Branch 2 taken 72018 times.
72018 inner_->Apply(inside, context);
214
2/2
✓ Branch 1 taken 10927 times.
✓ Branch 2 taken 72018 times.
82945 for (std::size_t k = 0; k < indices.size(); ++k) {
215 10927 float& height = samples.Height(indices[k]);
216 10927 height = Blend(height, heights[k], weights[k]);
217 }
218 72018 }
219
220 21 void RegionLayer::ApplyToGrid(Raster<float>& heights, const LayerContext& context) const {
221
2/2
✓ Branch 1 taken 21 times.
✓ Branch 4 taken 21 times.
21 const PixelWindow window = InnerWindow(RasterGrid::FromRaster(heights));
222
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 20 times.
21 if (window.Empty()) {
223 1 return;
224 }
225
1/1
✓ Branch 1 taken 20 times.
20 Raster<float> inner = CopyWindow(heights, window);
226
1/1
✓ Branch 2 taken 20 times.
20 inner_->Apply(inner, context);
227
2/2
✓ Branch 1 taken 20 times.
✓ Branch 4 taken 20 times.
20 ParallelFor(window.rowEnd - window.rowBegin, context.threads, [&](std::size_t r) {
228 4582 const std::size_t row = window.rowBegin + r;
229
2/2
✓ Branch 0 taken 1385538 times.
✓ Branch 1 taken 4582 times.
1390120 for (std::size_t col = window.colBegin; col < window.colEnd; ++col) {
230 1385538 float& cell = heights(col, row);
231
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1385537 times.
1385538 if (heights.IsNoData(cell)) {
232 1 continue;
233 }
234 1385537 const Vec2d centre = heights.CellCenter(col, row);
235
1/1
✓ Branch 1 taken 1385537 times.
1385537 const double weight = Weight(centre.x, centre.y);
236
2/2
✓ Branch 0 taken 1287431 times.
✓ Branch 1 taken 98106 times.
1385537 if (weight > 0.0) {
237 1287431 cell = Blend(cell, inner(col - window.colBegin, r), weight);
238 }
239 }
240 4582 });
241 20 }
242
243 } // namespace terraingen
244