TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
625 / 632
98.9%
Branches:
503 / 543
92.6%
Functions:
64 / 64
100.0%

layers/boulders/boulder_layer.cpp
Line Branch Exec Source
1 #include "terraingen/layers/boulders/boulder_layer.h"
2
3 #include "terraingen/layers/craters/ages/age_model.h"
4 #include "terraingen/noise/hash.h"
5 #include "terraingen/random/philox.h"
6 #include "terraingen/random/stream.h"
7 #include "terraingen/raster/cell_loops.h"
8 #include "terraingen/raster/grid.h"
9 #include "terraingen/util/cells.h"
10 #include "terraingen/util/parallel.h"
11
12 #include <algorithm>
13 #include <bit>
14 #include <cmath>
15 #include <limits>
16 #include <map>
17 #include <numbers>
18 #include <span>
19 #include <stdexcept>
20 #include <unordered_map>
21 #include <utility>
22 #include <vector>
23
24 namespace terraingen {
25
26 namespace {
27
28 /// A ground whose heights at some points, at one spacing, are read up front in one pass; any
29 /// other point or spacing is read from the ground beneath. Gives the same heights either way.
30 class PrefetchedGround final : public Ground {
31 public:
32 45748 PrefetchedGround(const Ground& ground, std::span<const Vec2d> points, double spacing)
33 45748 : ground_(ground), spacing_(spacing) {
34
1/1
✓ Branch 2 taken 45748 times.
45748 std::vector<float> heights(points.size());
35
1/1
✓ Branch 2 taken 45748 times.
45748 ground.HeightsAt(points, heights, spacing);
36
1/1
✓ Branch 2 taken 45748 times.
45748 heights_.reserve(points.size());
37
2/2
✓ Branch 1 taken 557982 times.
✓ Branch 2 taken 45748 times.
603730 for (std::size_t n = 0; n < points.size(); ++n) {
38
1/1
✓ Branch 5 taken 557982 times.
557982 heights_.emplace(KeyOf(points[n].x, points[n].y), heights[n]);
39 }
40 45748 }
41
42 1850275 float HeightAt(double x, double y, double spacing) const override {
43
1/2
✓ Branch 0 taken 1850275 times.
✗ Branch 1 not taken.
1850275 if (spacing == spacing_) {
44
1/1
✓ Branch 2 taken 1850275 times.
1850275 const auto found = heights_.find(KeyOf(x, y));
45
2/2
✓ Branch 2 taken 550389 times.
✓ Branch 3 taken 1299886 times.
1850275 if (found != heights_.end()) {
46 550389 return found->second;
47 }
48 }
49 1299886 return ground_.HeightAt(x, y, spacing);
50 }
51
52 private:
53 using Key = std::pair<std::uint64_t, std::uint64_t>;
54 struct KeyHash {
55 2408257 std::size_t operator()(const Key& key) const {
56 2408257 return std::hash<std::uint64_t>{}(key.first * 0x9E3779B97F4A7C15ull ^ key.second);
57 }
58 };
59
60 2408257 static Key KeyOf(double x, double y) {
61 2408257 return {std::bit_cast<std::uint64_t>(x), std::bit_cast<std::uint64_t>(y)};
62 }
63
64 const Ground& ground_;
65 double spacing_;
66 std::unordered_map<Key, float, KeyHash> heights_;
67 };
68
69 constexpr std::uint32_t kBoulderSeed = 0x524F434Bu;
70 constexpr std::uint32_t kBoulderAttributes = 0x41545452u;
71 constexpr std::uint32_t kBoulderPieces = 0x50494543u;
72
73 /// A uniform in [0, 1) from two 32-bit words.
74 2880116 double Uniform(std::uint32_t high, std::uint32_t low) {
75 2880116 const std::uint64_t bits = (static_cast<std::uint64_t>(high) << 32) | low;
76 2880116 return static_cast<double>(bits >> 11) * 0x1.0p-53;
77 }
78
79 /// Cuts `mesh` flat at a plane: triangles crossing it are split along it, sharing the new points
80 /// between neighbours so the mesh stays closed, and points above it (`distance(p)` > 0, the
81 /// distance along the unit `normal`) are moved down onto it.
82 template <typename Distance>
83 136 void CutByPlane(TriangleMesh& mesh, const Vec3d& normal, const Distance& distance) {
84
1/1
✓ Branch 2 taken 136 times.
136 std::vector<double> s(mesh.positions.size());
85
2/2
✓ Branch 1 taken 69340 times.
✓ Branch 2 taken 136 times.
69476 for (std::size_t i = 0; i < s.size(); ++i) {
86 69340 s[i] = distance(mesh.positions[i]);
87 }
88 136 std::map<std::pair<std::uint32_t, std::uint32_t>, std::uint32_t> crossings;
89 17822 const auto crossing = [&](std::uint32_t a, std::uint32_t b) {
90 17686 const auto key = std::minmax(a, b);
91
1/1
✓ Branch 2 taken 17686 times.
17686 const auto found = crossings.find(key);
92
2/2
✓ Branch 2 taken 8843 times.
✓ Branch 3 taken 8843 times.
17686 if (found != crossings.end()) {
93 8843 return found->second;
94 }
95 8843 const double t = s[a] / (s[a] - s[b]);
96 8843 const Vec3d& pa = mesh.positions[a];
97
1/1
✓ Branch 5 taken 8843 times.
8843 mesh.positions.push_back(pa + (mesh.positions[b] - pa) * t);
98
1/1
✓ Branch 1 taken 8843 times.
8843 s.push_back(0.0);
99 8843 const auto index = static_cast<std::uint32_t>(mesh.positions.size() - 1);
100
1/1
✓ Branch 1 taken 8843 times.
8843 crossings.emplace(key, index);
101 8843 return index;
102 };
103 136 std::vector<std::array<std::uint32_t, 3>> triangles;
104
1/1
✓ Branch 2 taken 136 times.
136 triangles.reserve(mesh.triangles.size());
105
2/2
✓ Branch 5 taken 138136 times.
✓ Branch 6 taken 136 times.
138272 for (const auto& triangle : mesh.triangles) {
106 432180 const auto above = [&](std::uint32_t v) { return s[v] > 0.0; };
107 138136 const int count = static_cast<int>(above(triangle[0])) +
108 138136 static_cast<int>(above(triangle[1])) +
109 138136 static_cast<int>(above(triangle[2]));
110
4/4
✓ Branch 0 taken 43840 times.
✓ Branch 1 taken 94296 times.
✓ Branch 2 taken 34997 times.
✓ Branch 3 taken 8843 times.
138136 if (count == 0 || count == 3) {
111
1/1
✓ Branch 1 taken 129293 times.
129293 triangles.push_back(triangle);
112 129293 continue;
113 }
114 8843 std::size_t lone = 0;
115
2/2
✓ Branch 2 taken 8929 times.
✓ Branch 3 taken 8843 times.
17772 while (above(triangle[lone]) == (count == 2)) {
116 8929 ++lone;
117 }
118 8843 const std::uint32_t a = triangle[lone];
119 8843 const std::uint32_t b = triangle[(lone + 1) % 3];
120 8843 const std::uint32_t c = triangle[(lone + 2) % 3];
121
1/1
✓ Branch 1 taken 8843 times.
8843 const std::uint32_t ab = crossing(a, b);
122
1/1
✓ Branch 1 taken 8843 times.
8843 const std::uint32_t ac = crossing(a, c);
123
1/1
✓ Branch 1 taken 8843 times.
8843 triangles.push_back({a, ab, ac});
124
1/1
✓ Branch 1 taken 8843 times.
8843 triangles.push_back({ab, b, c});
125
1/1
✓ Branch 1 taken 8843 times.
8843 triangles.push_back({ab, c, ac});
126 }
127 136 mesh.triangles = std::move(triangles);
128
2/2
✓ Branch 1 taken 78183 times.
✓ Branch 2 taken 136 times.
78319 for (std::size_t i = 0; i < s.size(); ++i) {
129
2/2
✓ Branch 1 taken 19777 times.
✓ Branch 2 taken 58406 times.
78183 if (s[i] > 0.0) {
130 19777 mesh.positions[i] = mesh.positions[i] - normal * s[i];
131 }
132 }
133 136 }
134
135 /// The box the relief of a boulder at (`x`, `y`) reaching `reach` metres is drawn over: on a
136 /// sphere a square in degrees around the box `Surface::Expand` gives.
137 27498 Bounds2d ReliefBox(double x, double y, double reach, const Surface& surface) {
138
2/2
✓ Branch 1 taken 27456 times.
✓ Branch 2 taken 42 times.
27498 if (!surface.IsBody()) {
139 27456 return {x - reach, y - reach, x + reach, y + reach};
140 }
141
1/1
✓ Branch 1 taken 42 times.
42 const Bounds2d box = surface.Expand({x, y, x, y}, reach);
142 42 const double cx = 0.5 * (box.minX + box.maxX);
143 42 const double cy = 0.5 * (box.minY + box.maxY);
144 42 const double half = std::fmax(0.5 * (box.maxX - box.minX), 0.5 * (box.maxY - box.minY));
145 42 return {cx - half, cy - half, cx + half, cy + half};
146 }
147
148 /// How far a placed shape reaches from its boulder's centre `cx`, `cy` along either axis.
149 5899 double ShapeReach(const TriangleMesh& placed, double cx, double cy) {
150
1/1
✓ Branch 1 taken 5899 times.
5899 const Box3d box = Bounds(placed);
151 5899 return std::fmax(std::fmax(std::fabs(box.min.x - cx), std::fabs(box.max.x - cx)),
152 11798 std::fmax(std::fabs(box.min.y - cy), std::fabs(box.max.y - cy)));
153 }
154
155 /// A uniform in [`low`, `high`) from `u`.
156 808014 double Between(double low, double high, double u) {
157 808014 return low + (high - low) * u;
158 }
159
160 } // namespace
161
162 205 BoulderLayer::BoulderLayer(BoulderLayerOptions options, std::shared_ptr<const CraterLayer> craters,
163 205 Surface surface)
164 205 : options_(std::move(options)), craters_(std::move(craters)), surface_(surface) {
165
4/6
✓ Branch 1 taken 50 times.
✓ Branch 2 taken 155 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 50 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 205 times.
205 if (craters_ && !(craters_->Options().surface == surface_)) {
166
0/1
✗ Branch 2 not taken.
✗ throw std::invalid_argument("A boulder layer and its crater layer must share a surface");
167 }
168
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 199 times.
205 if (surface_.IsBody()) {
169
1/1
✓ Branch 1 taken 6 times.
6 cells_ = std::make_shared<CubeSphereCells>(surface_);
170 } else {
171
1/1
✓ Branch 1 taken 199 times.
199 cells_ = std::make_shared<PlanarCells>();
172 }
173
1/1
✓ Branch 1 taken 190 times.
205 candidates_ = BoulderCandidates(options_);
174
1/1
✓ Branch 1 taken 190 times.
190 minCraterDiameter_ = options_.ejecta.MinCraterDiameter();
175
6/6
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 160 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 29 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 189 times.
190 if (minCraterDiameter_ > 0.0 && !craters_) {
176 throw std::invalid_argument(
177
1/1
✓ Branch 2 taken 1 time.
1 "Boulder ejecta need the crater layer whose craters throw them");
178 }
179
1/1
✓ Branch 1 taken 189 times.
189 maxBackground_ = MaxBackgroundAbundance(options_);
180
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 182 times.
189 if (options_.densityMap) {
181
1/1
✓ Branch 1 taken 7 times.
7 mapAbundance_.emplace(options_);
182
1/1
✓ Branch 1 taken 7 times.
7 patchMean_ = MeanPatchAbundance(options_.patches);
183 }
184
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 185 times.
189 if (options_.slopeMap) {
185 4 referenceCount_ =
186
1/1
✓ Branch 3 taken 4 times.
4 options_.sizes.distribution->Cumulative(options_.slopeMap->referenceDiameter);
187 }
188
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 179 times.
189 if (options_.clusters.share > 0.0) {
189 10 clusterMean_ = options_.clusters.model == BoulderClusterModel::Thomas
190
3/3
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 5 times.
10 ? MeanThomasField(options_.clusters, surface_.IsBody())
191
1/1
✓ Branch 1 taken 5 times.
5 : MeanClusterBump(options_.clusters);
192 }
193
3/3
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 160 times.
✓ Branch 3 taken 29 times.
189 ejectaSizes_ = minCraterDiameter_ > 0.0 ? EjectaSizes(options_) : nullptr;
194
1/2
✓ Branch 0 taken 189 times.
✗ Branch 1 not taken.
189 if (options_.cacheBoulders > 0) {
195
1/1
✓ Branch 1 taken 189 times.
189 cache_ = std::make_shared<CraterCellCache>(options_.cacheBoulders);
196 }
197
2/2
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 175 times.
189 if (options_.bedrock.exposure) {
198
1/1
✓ Branch 2 taken 14 times.
14 exposure_.emplace(*options_.bedrock.exposure, surface_);
199 }
200
6/6
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 175 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 12 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 187 times.
189 if (exposure_ && options_.densityMap) {
201 2 constexpr std::size_t kMeanCells = 1u << 16;
202 2 const double side = options_.bedrock.meanSize / surface_.UnitLength();
203 2 meanCell_ = {side, side};
204 2 if (const auto* raster =
205
3/4
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
2 dynamic_cast<const RasterField*>(options_.densityMap->field.get())) {
206 1 const GeoTransform& transform = raster->Values().Transform();
207 1 meanOrigin_ = {transform.originX, transform.originY};
208 1 meanCell_ = {transform.pixelWidth, transform.pixelHeight};
209 }
210
1/1
✓ Branch 1 taken 2 times.
4 bedrockMeans_ = std::make_shared<LruCache<MeanCell, double, MeanCellHash>>(
211 76 kMeanCells, [](const double&) { return std::size_t{1}; });
212 }
213 189 const std::uint32_t prototypes = options_.rendering.shapePrototypes;
214 189 shapes_ = std::make_shared<LruCache<std::uint64_t, BoulderShape>>(
215
3/3
✓ Branch 0 taken 188 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 189 times.
1252 prototypes > 0 ? prototypes : 4096, [](const BoulderShape&) { return std::size_t{1}; });
216 189 fragments_ = std::make_shared<LruCache<std::uint64_t, std::vector<BoulderFragment>>>(
217
3/3
✓ Branch 0 taken 188 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 189 times.
189 prototypes > 0 ? prototypes : 4096,
218 518 [](const std::vector<BoulderFragment>&) { return std::size_t{1}; });
219 429 }
220
221 11188 std::shared_ptr<const BoulderShape> BoulderLayer::ShapeOf(std::uint64_t shapeSeed) const {
222 11188 return shapes_->Get(shapeSeed,
223
1/1
✓ Branch 3 taken 11188 times.
12251 [&] { return GenerateBoulderShape(shapeSeed, options_.rendering.shapes); });
224 }
225
226 std::shared_ptr<const std::vector<BoulderFragment>>
227 4405 BoulderLayer::FragmentsOf(std::uint64_t shapeSeed) const {
228 ✗ return fragments_->Get(shapeSeed, [&] {
229 329 const BoulderFragmentation& fragmentation = options_.fragmentation;
230 329 const auto span =
231 329 static_cast<std::uint32_t>(fragmentation.maxPieces - fragmentation.minPieces + 1);
232 const int pieces =
233 329 fragmentation.minPieces +
234 329 static_cast<int>(PcgHash(static_cast<std::uint32_t>(shapeSeed) ^ kBoulderPieces) %
235 329 span);
236 329 const std::uint32_t pick = PcgHash(static_cast<std::uint32_t>(shapeSeed >> 32) ^
237 329 kBoulderPieces ^ static_cast<std::uint32_t>(shapeSeed));
238 658 const BoulderCracks cracks = (pick >> 8) * 0x1.0p-24 < fragmentation.slabShare
239
2/2
✓ Branch 0 taken 201 times.
✓ Branch 1 taken 128 times.
329 ? BoulderCracks::Slabs
240 : BoulderCracks::Radial;
241
2/2
✓ Branch 1 taken 329 times.
✓ Branch 5 taken 329 times.
658 return FragmentBoulderShape(*ShapeOf(shapeSeed), pieces, shapeSeed, cracks);
242
1/1
✓ Branch 3 taken 4405 times.
4405 });
243 }
244
245 10960 std::shared_ptr<const BoulderShape> BoulderLayer::Shape(const Boulder& boulder) const {
246 10960 const std::uint32_t prototypes = options_.rendering.shapePrototypes;
247
2/2
✓ Branch 0 taken 10859 times.
✓ Branch 1 taken 101 times.
10960 if (boulder.parent == 0) {
248
2/2
✓ Branch 1 taken 10859 times.
✓ Branch 4 taken 10859 times.
10859 return ShapeOf(BoulderShapeSeed(boulder.id, prototypes));
249 }
250
2/2
✓ Branch 1 taken 101 times.
✓ Branch 4 taken 101 times.
101 const auto fragments = FragmentsOf(BoulderShapeSeed(boulder.parent, prototypes));
251 101 return {fragments, &(*fragments)[boulder.piece].shape};
252 101 }
253
254 18449 Boulder BoulderLayer::InLocalFrame(const Boulder& boulder) const {
255 18449 Boulder local = boulder;
256
2/2
✓ Branch 1 taken 207 times.
✓ Branch 2 taken 18242 times.
18449 if (surface_.IsBody()) {
257 207 local.x = 0.0;
258 207 local.y = 0.0;
259 }
260 18449 return local;
261 }
262
263 5899 TriangleMesh BoulderLayer::PlacedOnLevel(const Boulder& boulder) const {
264
1/1
✓ Branch 1 taken 5899 times.
5899 const auto shape = Shape(boulder);
265 const BoulderPlacement placement =
266
1/1
✓ Branch 3 taken 5899 times.
5899 PlaceBoulderModel(InLocalFrame(boulder), *shape, 0.0, options_.pose.settle);
267
1/1
✓ Branch 2 taken 5899 times.
5899 TriangleMesh mesh = shape->mesh;
268
2/2
✓ Branch 5 taken 2884910 times.
✓ Branch 6 taken 5899 times.
2890809 for (Vec3d& p : mesh.positions) {
269
1/1
✓ Branch 1 taken 2884910 times.
2884910 p = placement.Apply(p);
270 }
271
1/1
✓ Branch 2 taken 5899 times.
5899 CutFacets(mesh, InLocalFrame(boulder), 0.0);
272 11798 return mesh;
273 5899 }
274
275 402930 double BoulderLayer::Wear(double diameter, const BoulderAbundance& abundance, double density,
276 std::uint64_t id) const {
277 402930 const IdWords words = SplitId(id);
278 const U32x4 draw =
279 402930 Philox4x32({words.low, words.high, kBoulderAttributes, 2u}, {options_.seed, kBoulderSeed});
280
3/4
✓ Branch 0 taken 17764 times.
✓ Branch 1 taken 385166 times.
✓ Branch 2 taken 17764 times.
✗ Branch 3 not taken.
402930 if (abundance.ejecta > 0.0 && density > 0.0) {
281
1/1
✓ Branch 2 taken 17764 times.
17764 const double fromCrater = abundance.ejecta * ejectaSizes_->Incremental(diameter) / density;
282
2/2
✓ Branch 1 taken 15854 times.
✓ Branch 2 taken 1910 times.
17764 if (Uniform(draw.x, draw.y) < fromCrater) {
283 15854 return abundance.ejectaWear;
284 }
285 }
286 387076 return Uniform(draw.z, draw.w);
287 }
288
289 711894 float BoulderLayer::AddRelief(float height, float base, double x, double y, const Boulder& boulder,
290 const TriangleMesh* placed, double weight, bool rock,
291 double width) const {
292
1/1
✓ Branch 1 taken 711894 times.
711894 const Vec2d offset = surface_.Offset({boulder.x, boulder.y}, {x, y});
293
3/3
✓ Branch 0 taken 701314 times.
✓ Branch 1 taken 10580 times.
✓ Branch 3 taken 701314 times.
711894 const double relief = rock ? RockRelief(x, y, boulder, placed, width) : 0.0;
294 711894 double result = static_cast<double>(height);
295
2/2
✓ Branch 0 taken 326335 times.
✓ Branch 1 taken 385559 times.
711894 if (relief > 0.0) {
296 326335 result = std::fmax(result, static_cast<double>(base) + weight * relief);
297 }
298
2/2
✓ Branch 0 taken 100494 times.
✓ Branch 1 taken 611400 times.
711894 if (options_.fillet.heightShare > 0.0) {
299 const double fillet =
300
1/1
✓ Branch 1 taken 100494 times.
100494 FilletHeight(boulder, std::hypot(offset.x, offset.y), options_.fillet);
301
2/2
✓ Branch 0 taken 65233 times.
✓ Branch 1 taken 35261 times.
100494 if (fillet > 0.0) {
302 65233 result = std::fmax(result, static_cast<double>(base) + weight * fillet);
303 }
304 }
305 711894 return static_cast<float>(result);
306 }
307
308 716694 double BoulderLayer::RockRelief(double x, double y, const Boulder& boulder,
309 const TriangleMesh* placed, double width) const {
310
1/1
✓ Branch 1 taken 716694 times.
716694 const Vec2d offset = surface_.Offset({boulder.x, boulder.y}, {x, y});
311
2/2
✓ Branch 0 taken 526516 times.
✓ Branch 1 taken 190178 times.
716694 if (placed == nullptr) {
312 526516 return boulder.CutByFacets(offset,
313
1/1
✓ Branch 1 taken 526516 times.
1053032 DomeHeight(boulder, std::hypot(offset.x, offset.y), width));
314 }
315 const std::optional<double> top =
316
4/4
✓ Branch 1 taken 5692 times.
✓ Branch 2 taken 184486 times.
✓ Branch 4 taken 5692 times.
✓ Branch 7 taken 184486 times.
190178 surface_.IsBody() ? TopSurface(*placed, offset.x, offset.y) : TopSurface(*placed, x, y);
317
2/2
✓ Branch 1 taken 50335 times.
✓ Branch 2 taken 139843 times.
190178 return top ? std::fmax(*top, 0.0) : 0.0;
318 }
319
320 22419 double BoulderLayer::MaterialCoverAt(double x, double y, double spacing,
321 const Ground* ground) const {
322
2/2
✓ Branch 0 taken 19344 times.
✓ Branch 1 taken 3075 times.
22419 const double rim = options_.rendering.relief == BoulderRelief::Dome ? 0.5 * spacing : 0.0;
323 const std::vector<Boulder> boulders =
324
2/2
✓ Branch 1 taken 22419 times.
✓ Branch 4 taken 22419 times.
22419 Boulders(surface_.Expand({x, y, x, y}, rim), spacing, ground);
325 22419 double cover = 0.0;
326
3/3
✓ Branch 1 taken 22419 times.
✓ Branch 8 taken 1277 times.
✓ Branch 9 taken 22419 times.
23696 for (const Relief& relief : PrepareRelief(boulders, spacing)) {
327
2/2
✓ Branch 1 taken 534 times.
✓ Branch 2 taken 743 times.
1277 const TriangleMesh* shape = relief.placed ? &*relief.placed : nullptr;
328
6/7
✓ Branch 0 taken 1277 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1277 times.
✓ Branch 5 taken 699 times.
✓ Branch 6 taken 578 times.
✓ Branch 7 taken 699 times.
✓ Branch 8 taken 578 times.
1277 if (relief.rock && RockRelief(x, y, *relief.boulder, shape, relief.width) > 0.0) {
329 699 cover = std::fmax(cover, relief.weight);
330 }
331 22419 }
332
2/2
✓ Branch 5 taken 2746 times.
✓ Branch 6 taken 22419 times.
25165 for (const Boulder& boulder : boulders) {
333
1/1
✓ Branch 1 taken 2746 times.
2746 const Vec2d offset = surface_.Offset({boulder.x, boulder.y}, {x, y});
334
6/6
✓ Branch 0 taken 164 times.
✓ Branch 1 taken 2582 times.
✓ Branch 2 taken 89 times.
✓ Branch 3 taken 75 times.
✓ Branch 4 taken 89 times.
✓ Branch 5 taken 2657 times.
2746 if (boulder.model && std::hypot(offset.x, offset.y) < 0.5 * boulder.diameter) {
335
1/1
✓ Branch 1 taken 89 times.
89 cover = std::fmax(cover, ResolutionWeight(boulder.diameter, spacing, options_.fade));
336 }
337 }
338 22419 return cover;
339 22419 }
340
341 10 Raster<double> BoulderLayer::MaterialCoverOver(const RasterGrid& grid, double spacing,
342 const Ground* ground, unsigned threads) const {
343
1/1
✓ Branch 1 taken 10 times.
10 Raster<double> cover(grid.width, grid.height);
344 10 cover.SetTransform(grid.transform);
345
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 const double rim = options_.rendering.relief == BoulderRelief::Dome ? 0.5 * spacing : 0.0;
346 const std::vector<Boulder> boulders =
347
3/3
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 10 times.
✓ Branch 7 taken 10 times.
10 Boulders(surface_.Expand(grid.Bounds(), rim), spacing, ground);
348
1/1
✓ Branch 1 taken 10 times.
10 std::vector<Relief> reliefs = PrepareRelief(boulders, spacing);
349
1/1
✓ Branch 1 taken 10 times.
333 std::erase_if(reliefs, [](const Relief& relief) { return !relief.rock; });
350 10 std::vector<std::vector<PixelWindow>> windows;
351
2/2
✓ Branch 4 taken 323 times.
✓ Branch 5 taken 10 times.
333 for (const Relief& relief : reliefs) {
352
2/2
✓ Branch 1 taken 323 times.
✓ Branch 4 taken 323 times.
323 windows.push_back(PixelWindowsIn(grid, relief.box, surface_));
353 }
354
1/1
✓ Branch 1 taken 10 times.
10 ForEachFeatureCell(
355 cover, threads, reliefs.size(),
356 2326 [&](std::size_t n) -> const std::vector<PixelWindow>& { return windows[n]; },
357 10 [&](double& cell, const FeatureCell& at) {
358 14103 const Relief& relief = reliefs[at.feature];
359
2/2
✓ Branch 1 taken 7163 times.
✓ Branch 2 taken 6940 times.
14103 const TriangleMesh* shape = relief.placed ? &*relief.placed : nullptr;
360
2/2
✓ Branch 1 taken 6263 times.
✓ Branch 2 taken 7840 times.
14103 if (RockRelief(at.centre.x, at.centre.y, *relief.boulder, shape, relief.width) > 0.0) {
361 6263 cell = std::fmax(cell, relief.weight);
362 }
363 14103 });
364 10 std::vector<const Boulder*> models;
365 10 windows.clear();
366
2/2
✓ Branch 5 taken 628 times.
✓ Branch 6 taken 10 times.
638 for (const Boulder& boulder : boulders) {
367
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 623 times.
628 if (boulder.model) {
368
1/1
✓ Branch 1 taken 5 times.
5 models.push_back(&boulder);
369
2/2
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 5 times.
5 windows.push_back(PixelWindowsIn(
370
1/1
✓ Branch 1 taken 5 times.
10 grid, ReliefBox(boulder.x, boulder.y, 0.5 * boulder.diameter, surface_), surface_));
371 }
372 }
373
1/1
✓ Branch 1 taken 10 times.
10 ForEachFeatureCell(
374 cover, threads, models.size(),
375 20 [&](std::size_t n) -> const std::vector<PixelWindow>& { return windows[n]; },
376 10 [&](double& cell, const FeatureCell& at) {
377 163 const Boulder& boulder = *models[at.feature];
378
1/1
✓ Branch 1 taken 163 times.
163 const Vec2d offset = surface_.Offset({boulder.x, boulder.y}, at.centre);
379
2/2
✓ Branch 0 taken 88 times.
✓ Branch 1 taken 75 times.
163 if (std::hypot(offset.x, offset.y) < 0.5 * boulder.diameter) {
380
1/1
✓ Branch 1 taken 88 times.
88 cell = std::fmax(cell, ResolutionWeight(boulder.diameter, spacing, options_.fade));
381 }
382 163 });
383 20 return cover;
384 10 }
385
386 559526 double BoulderLayer::OwnMarginRadii() const {
387
2/2
✓ Branch 0 taken 184943 times.
✓ Branch 1 taken 374583 times.
559526 return options_.fillet.heightShare > 0.0 ? 1.0 + 2.0 * options_.fillet.widthRatio : 1.0;
388 }
389
390 147682 double BoulderLayer::BreakMarginRadii() const {
391 147682 const BoulderFragmentation& fragmentation = options_.fragmentation;
392
2/2
✓ Branch 1 taken 7222 times.
✓ Branch 2 taken 140460 times.
147682 return fragmentation.Enabled() ? 2.0 * (fragmentation.gapRatio + fragmentation.spreadRatio)
393 147682 : 0.0;
394 }
395
396 144071 CraterList BoulderLayer::ScatterBoulderCandidates(const Bounds2d& region, double spacing) const {
397 144071 const std::uint32_t seed = options_.seed ^ kBoulderSeed;
398 144071 const FreshAges ages;
399 144071 const double margin = OwnMarginRadii() + BreakMarginRadii();
400 144071 CraterGenerationOptions generation{.dMin = options_.sizes.minDiameter,
401 144071 .dMax = options_.sizes.maxDiameter,
402
1/11
✓ Branch 8 taken 144071 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
288142 .marginRadii = margin};
403 144071 generation.surface = surface_;
404
1/1
✓ Branch 1 taken 144071 times.
144071 generation.logName = "boulders";
405
2/2
✓ Branch 0 taken 143997 times.
✓ Branch 1 taken 74 times.
144071 if (spacing > 0.0) {
406 143997 generation.minResolvedDiameter = options_.fade.startSamples * spacing;
407 }
408
2/2
✓ Branch 5 taken 8 times.
✓ Branch 6 taken 144071 times.
144079 for (const Crater& boulder : options_.manual) {
409
1/1
✓ Branch 1 taken 8 times.
8 generation.manual.Append(boulder);
410 }
411 144071 generation.completeness = options_.completeness;
412
1/2
✓ Branch 1 taken 144071 times.
✗ Branch 2 not taken.
144071 if (cache_) {
413
1/1
✓ Branch 4 taken 144071 times.
144071 generation.cellCraters = CachedCellCraters(*cache_, *cells_, *candidates_, ages, seed);
414 }
415
1/1
✓ Branch 3 taken 144071 times.
288142 return GenerateCraters(*cells_, region, *candidates_, ages, seed, generation);
416 144071 }
417
418 144071 std::vector<Boulder> BoulderLayer::DrawBoulders(const CraterList& candidates, double spacing,
419 const Ground* ground,
420 const EjectaIndex& ejecta) const {
421 144071 std::vector<Kept> kept;
422
2/2
✓ Branch 1 taken 807116 times.
✓ Branch 2 taken 144071 times.
951187 for (std::size_t n = 0; n < candidates.Size(); ++n) {
423
4/4
✓ Branch 1 taken 807116 times.
✓ Branch 4 taken 807116 times.
✓ Branch 7 taken 402930 times.
✓ Branch 8 taken 404186 times.
807116 if (std::optional<Kept> k = Keep(candidates.At(n), ground, ejecta)) {
424
1/1
✓ Branch 2 taken 402930 times.
402930 kept.push_back(*k);
425 }
426 }
427 144071 std::optional<PrefetchedGround> fetched;
428
2/2
✓ Branch 0 taken 45748 times.
✓ Branch 1 taken 98323 times.
144071 if (ground != nullptr) {
429 45748 const BoulderPose& pose = options_.pose;
430
4/4
✓ Branch 0 taken 45745 times.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 45733 times.
45748 const bool gradient = pose.followSlope || pose.maxSlope.has_value();
431 45748 std::vector<Vec2d> points;
432
3/3
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 45733 times.
✓ Branch 4 taken 45748 times.
45748 points.reserve(kept.size() * (gradient ? 5 : 1));
433
2/2
✓ Branch 5 taken 289526 times.
✓ Branch 6 taken 45748 times.
335274 for (const Kept& k : kept) {
434 289526 const Crater& c = k.candidate;
435
1/1
✓ Branch 1 taken 289526 times.
289526 points.push_back({c.x, c.y});
436
2/2
✓ Branch 0 taken 67114 times.
✓ Branch 1 taken 222412 times.
289526 if (gradient) {
437
3/3
✓ Branch 1 taken 67114 times.
✓ Branch 3 taken 268456 times.
✓ Branch 4 taken 67114 times.
335570 for (const Vec2d& p : GradientPoints(c.x, c.y, c.diameter)) {
438
1/1
✓ Branch 1 taken 268456 times.
268456 points.push_back(p);
439 }
440 }
441 }
442
1/1
✓ Branch 1 taken 45748 times.
45748 fetched.emplace(*ground, points, options_.bedrock.exposureSpacing);
443 45748 }
444 144071 std::vector<Boulder> boulders;
445
1/1
✓ Branch 2 taken 144071 times.
144071 boulders.reserve(kept.size());
446
2/2
✓ Branch 5 taken 402930 times.
✓ Branch 6 taken 144071 times.
547001 for (const Kept& k : kept) {
447
5/5
✓ Branch 1 taken 289526 times.
✓ Branch 2 taken 113404 times.
✓ Branch 5 taken 402930 times.
✓ Branch 8 taken 401047 times.
✓ Branch 9 taken 1883 times.
402930 if (std::optional<Boulder> boulder = Place(k, spacing, fetched ? &*fetched : nullptr)) {
448
1/1
✓ Branch 2 taken 401047 times.
401047 boulders.push_back(*boulder);
449 }
450 }
451 288142 return boulders;
452 144071 }
453
454 807116 std::optional<BoulderLayer::Kept> BoulderLayer::Keep(const Crater& candidate, const Ground* ground,
455 const EjectaIndex& ejecta) const {
456 807116 const IdWords id = SplitId(candidate.id);
457 const U32x4 draw =
458 807116 Philox4x32({id.low, id.high, kBoulderAttributes, 0u}, {options_.seed, kBoulderSeed});
459
1/1
✓ Branch 1 taken 807116 times.
807116 Kept kept{candidate, Abundance(candidate.x, candidate.y, ground, ejecta)};
460
2/2
✓ Branch 0 taken 807108 times.
✓ Branch 1 taken 8 times.
807116 if (!candidate.manual) {
461
1/1
✓ Branch 1 taken 807108 times.
807108 kept.density = Density(candidate.diameter, kept.abundance);
462 807108 if (Uniform(draw.x, draw.y) >=
463
3/3
✓ Branch 2 taken 807108 times.
✓ Branch 4 taken 404186 times.
✓ Branch 5 taken 402922 times.
807108 kept.density / candidates_->Incremental(candidate.diameter)) {
464 404186 return std::nullopt;
465 }
466 }
467 402930 return kept;
468 }
469
470 402930 std::optional<Boulder> BoulderLayer::Place(const Kept& kept, double spacing,
471 const Ground* ground) const {
472 402930 const Crater& candidate = kept.candidate;
473 402930 const BoulderAbundance& abundance = kept.abundance;
474 402930 const double density = kept.density;
475 402930 const IdWords id = SplitId(candidate.id);
476 const U32x4 draw =
477 402930 Philox4x32({id.low, id.high, kBoulderAttributes, 0u}, {options_.seed, kBoulderSeed});
478 const U32x4 shape =
479 402930 Philox4x32({id.low, id.high, kBoulderAttributes, 1u}, {options_.seed, kBoulderSeed});
480 402930 const BoulderPose& pose = options_.pose;
481 const double ratio =
482 402930 Between(pose.minHeightRatio, pose.maxHeightRatio, Uniform(shape.x, shape.y));
483
1/1
✓ Branch 1 taken 402930 times.
402930 const double wear = Wear(candidate.diameter, abundance, density, candidate.id);
484 402930 double share = Uniform(draw.z, draw.w);
485
2/2
✓ Branch 0 taken 3554 times.
✓ Branch 1 taken 399376 times.
402930 if (pose.burialByWear > 0.0) {
486 3554 share = (1.0 - pose.burialByWear) * share + pose.burialByWear * wear;
487 }
488 402930 const double burial = Between(pose.minBurial, pose.maxBurial, share);
489 402930 Boulder boulder{candidate.x,
490 402930 candidate.y,
491 402930 candidate.diameter,
492 402930 ratio * candidate.diameter,
493 burial,
494 805860 2.0 * std::numbers::pi * Uniform(shape.z, shape.w),
495 805860 pose.maxLean * Uniform(shape.w, shape.x),
496 402930 candidate.id};
497 402930 boulder.wear = wear;
498
1/1
✓ Branch 1 taken 402930 times.
402930 boulder.model = options_.rendering.models.IsModel(boulder.diameter, spacing);
499
11/11
✓ Branch 1 taken 67114 times.
✓ Branch 2 taken 335816 times.
✓ Branch 3 taken 66458 times.
✓ Branch 4 taken 656 times.
✓ Branch 5 taken 66456 times.
✓ Branch 6 taken 2 times.
✓ Branch 8 taken 66456 times.
✓ Branch 10 taken 1883 times.
✓ Branch 11 taken 64573 times.
✓ Branch 12 taken 1883 times.
✓ Branch 13 taken 401047 times.
402930 if (pose.maxSlope && ground != nullptr && !candidate.manual && !Roll(boulder, *ground)) {
500 1883 return std::nullopt;
501 }
502
2/2
✓ Branch 0 taken 287643 times.
✓ Branch 1 taken 113404 times.
401047 if (ground != nullptr) {
503 const double height =
504
1/1
✓ Branch 1 taken 287643 times.
287643 ground->HeightAt(boulder.x, boulder.y, options_.bedrock.exposureSpacing);
505
1/2
✓ Branch 1 taken 287643 times.
✗ Branch 2 not taken.
287643 if (!std::isnan(height)) {
506 287643 boulder.ground = height;
507 }
508 }
509
6/8
✓ Branch 0 taken 385081 times.
✓ Branch 1 taken 15966 times.
✓ Branch 2 taken 385081 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 385081 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1516 times.
✓ Branch 7 taken 383565 times.
401047 if (pose.sideShare > 0.0 || pose.endShare > 0.0 || pose.maxTilt > 0.0 || pose.followSlope) {
510
1/1
✓ Branch 1 taken 17482 times.
17482 Settle(boulder, ground);
511 }
512
2/2
✓ Branch 0 taken 3476 times.
✓ Branch 1 taken 397571 times.
401047 if (options_.ventifacts.share > 0.0) {
513 3476 Abrade(boulder);
514 }
515 401047 return boulder;
516 }
517
518 3476 void BoulderLayer::Abrade(Boulder& boulder) const {
519 3476 const BoulderVentifacts& ventifacts = options_.ventifacts;
520 3476 const IdWords id = SplitId(boulder.id);
521 const U32x4 draw =
522 3476 Philox4x32({id.low, id.high, kBoulderAttributes, 4u}, {options_.seed, kBoulderSeed});
523
2/2
✓ Branch 1 taken 2026 times.
✓ Branch 2 taken 1450 times.
3476 if (Uniform(draw.x, draw.y) >= ventifacts.share) {
524 2026 return;
525 }
526 const U32x4 angles =
527 1450 Philox4x32({id.low, id.high, kBoulderAttributes, 5u}, {options_.seed, kBoulderSeed});
528 1450 const double reach = 0.5 * boulder.diameter * (1.0 - boulder.wear);
529 2154 const auto facet = [&](double wind, double pick) {
530 2154 return BoulderFacet{wind + std::numbers::pi,
531 2154 Between(ventifacts.minAngle, ventifacts.maxAngle, pick), reach};
532 1450 };
533 1450 boulder.facets[0] = facet(ventifacts.windAzimuth, Uniform(angles.x, angles.y));
534 1450 boulder.facetCount = 1;
535
6/6
✓ Branch 1 taken 1404 times.
✓ Branch 2 taken 46 times.
✓ Branch 4 taken 704 times.
✓ Branch 5 taken 700 times.
✓ Branch 6 taken 704 times.
✓ Branch 7 taken 746 times.
1450 if (ventifacts.secondWindAzimuth && Uniform(draw.z, draw.w) < ventifacts.keelShare) {
536 704 boulder.facets[1] = facet(*ventifacts.secondWindAzimuth, Uniform(angles.z, angles.w));
537 704 boulder.facetCount = 2;
538 }
539 }
540
541 5900 void BoulderLayer::CutFacets(TriangleMesh& placed, const Boulder& local, double base) const {
542
2/2
✓ Branch 0 taken 136 times.
✓ Branch 1 taken 5900 times.
6036 for (std::uint8_t k = 0; k < local.facetCount; ++k) {
543 136 const BoulderFacet& facet = local.facets[k];
544 136 const Vec3d normal{std::cos(facet.azimuth) * std::sin(facet.angle),
545 136 std::sin(facet.azimuth) * std::sin(facet.angle), std::cos(facet.angle)};
546
1/1
✓ Branch 1 taken 136 times.
136 CutByPlane(placed, normal, [&](const Vec3d& p) {
547 69340 return (p.z - base - facet.HeightAt({p.x - local.x, p.y - local.y})) * normal.z;
548 });
549 }
550 5900 }
551
552 1 BoulderShape BoulderLayer::Abraded(const BoulderShape& shape, const BoulderPlacement& placement,
553 const Boulder& local, double base) const {
554 1 BoulderShape cut = shape;
555
2/2
✓ Branch 5 taken 642 times.
✓ Branch 6 taken 1 time.
643 for (Vec3d& p : cut.mesh.positions) {
556
1/1
✓ Branch 1 taken 642 times.
642 p = placement.Apply(p);
557 }
558
1/1
✓ Branch 1 taken 1 time.
1 CutFacets(cut.mesh, local, base);
559 1 const std::array<double, 9>& r = placement.pose.rotation;
560
2/2
✓ Branch 4 taken 734 times.
✓ Branch 5 taken 1 time.
735 for (Vec3d& p : cut.mesh.positions) {
561 734 const Vec3d d = p - placement.pose.translation;
562 734 p = {(r[0] * d.x + r[3] * d.y + r[6] * d.z) / placement.scale.x,
563 734 (r[1] * d.x + r[4] * d.y + r[7] * d.z) / placement.scale.y,
564 734 (r[2] * d.x + r[5] * d.y + r[8] * d.z) / placement.scale.z};
565 }
566 1 return cut;
567 }
568
569 66456 bool BoulderLayer::Roll(Boulder& boulder, const Ground& ground) const {
570 66456 constexpr double kMaxSteps = 128.0;
571 66456 const BoulderPose& pose = options_.pose;
572 66456 const double limit = std::tan(*pose.maxSlope);
573 66456 const double reach = pose.rollDistance.value_or(0.0);
574 66456 const double step = std::fmax(boulder.diameter, reach / kMaxSteps);
575 66456 double rolled = 0.0;
576 for (;;) {
577
1/1
✓ Branch 1 taken 389142 times.
389142 const std::optional<Vec2d> g = GroundGradient(boulder, ground);
578
1/2
✓ Branch 1 taken 389142 times.
✗ Branch 2 not taken.
389142 const double gradient = g ? std::hypot(g->x, g->y) : 0.0;
579
2/2
✓ Branch 0 taken 64573 times.
✓ Branch 1 taken 324569 times.
389142 if (!(gradient > limit)) {
580 66456 return true;
581 }
582
2/2
✓ Branch 0 taken 1883 times.
✓ Branch 1 taken 322686 times.
324569 if (rolled + step > reach) {
583 1883 return false;
584 }
585 ✗ const Vec2d to = surface_.Locate({boulder.x, boulder.y},
586
1/1
✓ Branch 3 taken 322686 times.
322686 {-g->x / gradient * step, -g->y / gradient * step});
587 322686 boulder.x = to.x;
588 322686 boulder.y = to.y;
589 322686 rolled += step;
590 322686 }
591 }
592
593 457772 std::array<Vec2d, 4> BoulderLayer::GradientPoints(double x, double y, double diameter) const {
594 457772 const double step = 0.5 * diameter;
595
2/2
✓ Branch 1 taken 457772 times.
✓ Branch 4 taken 457772 times.
457772 return {surface_.Locate({x, y}, {step, 0.0}), surface_.Locate({x, y}, {-step, 0.0}),
596
2/2
✓ Branch 1 taken 457772 times.
✓ Branch 4 taken 457772 times.
457772 surface_.Locate({x, y}, {0.0, step}), surface_.Locate({x, y}, {0.0, -step})};
597 }
598
599 390658 std::optional<Vec2d> BoulderLayer::GroundGradient(const Boulder& boulder,
600 const Ground& ground) const {
601 390658 const double step = 0.5 * boulder.diameter;
602
1/1
✓ Branch 1 taken 390658 times.
390658 const std::array<Vec2d, 4> points = GradientPoints(boulder.x, boulder.y, boulder.diameter);
603 1562632 const auto at = [&](std::size_t n) {
604 return static_cast<double>(
605 1562632 ground.HeightAt(points[n].x, points[n].y, options_.bedrock.exposureSpacing));
606 390658 };
607
2/2
✓ Branch 1 taken 390658 times.
✓ Branch 4 taken 390658 times.
390658 const double gx = (at(0) - at(1)) / (2.0 * step);
608
2/2
✓ Branch 1 taken 390658 times.
✓ Branch 4 taken 390658 times.
390658 const double gy = (at(2) - at(3)) / (2.0 * step);
609
3/6
✓ Branch 1 taken 390658 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 390658 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 390658 times.
390658 if (!std::isfinite(gx) || !std::isfinite(gy)) {
610 ✗ return std::nullopt;
611 }
612 390658 return Vec2d{gx, gy};
613 }
614
615 17482 void BoulderLayer::Settle(Boulder& boulder, const Ground* ground) const {
616 17482 const BoulderPose& pose = options_.pose;
617 17482 const IdWords id = SplitId(boulder.id);
618 const U32x4 draw =
619 17482 Philox4x32({id.low, id.high, kBoulderAttributes, 3u}, {options_.seed, kBoulderSeed});
620 17482 const double pick = Uniform(draw.x, draw.y);
621
2/2
✓ Branch 0 taken 1599 times.
✓ Branch 1 taken 15883 times.
33365 boulder.rest = pick < pose.endShare ? BoulderRest::End
622
2/2
✓ Branch 0 taken 4914 times.
✓ Branch 1 taken 10969 times.
15883 : pick < pose.endShare + pose.sideShare ? BoulderRest::Side
623 : BoulderRest::Face;
624 17482 Vec3d up{0.0, 0.0, 1.0};
625
2/2
✓ Branch 0 taken 15966 times.
✓ Branch 1 taken 1516 times.
17482 if (pose.maxTilt > 0.0) {
626 15966 const double cosine = 1.0 - Uniform(draw.z, draw.w) * (1.0 - std::cos(pose.maxTilt));
627 15966 const double sine = std::sqrt(std::fmax(0.0, 1.0 - cosine * cosine));
628 15966 const double azimuth = 2.0 * std::numbers::pi * Uniform(draw.w, draw.x);
629 15966 up = {sine * std::cos(azimuth), sine * std::sin(azimuth), cosine};
630 }
631
3/4
✓ Branch 0 taken 1516 times.
✓ Branch 1 taken 15966 times.
✓ Branch 2 taken 1516 times.
✗ Branch 3 not taken.
17482 if (pose.followSlope && ground != nullptr) {
632
2/3
✓ Branch 1 taken 1516 times.
✓ Branch 4 taken 1516 times.
✗ Branch 5 not taken.
1516 if (const std::optional<Vec2d> g = GroundGradient(boulder, *ground)) {
633 1516 const double slope = std::atan(std::hypot(g->x, g->y));
634
2/2
✓ Branch 3 taken 1516 times.
✓ Branch 6 taken 1516 times.
1516 up = TiltRotation(slope, std::atan2(-g->y, -g->x)).Apply(up);
635 }
636 }
637
1/1
✓ Branch 1 taken 17482 times.
17482 boulder.tilt = std::acos(std::clamp(up.z, -1.0, 1.0));
638 17482 boulder.tiltAzimuth = std::atan2(up.y, up.x);
639 17482 }
640
641 144072 std::vector<Boulder> BoulderLayer::Boulders(const Bounds2d& region, double spacing,
642 const Ground* ground) const {
643
2/2
✓ Branch 1 taken 1035 times.
✓ Branch 2 taken 143037 times.
144072 if (exposure_) {
644
1/1
✓ Branch 2 taken 1034 times.
1035 RequireGround(ground, "Boulders that read the exposure field");
645 }
646
2/2
✓ Branch 0 taken 45748 times.
✓ Branch 1 taken 98323 times.
144071 const double roll = ground != nullptr ? options_.pose.rollDistance.value_or(0.0) : 0.0;
647
3/3
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 144065 times.
✓ Branch 3 taken 6 times.
144071 const Bounds2d from = roll > 0.0 ? surface_.Expand(region, roll) : region;
648 395791 const auto rests = [&](const Boulder& boulder) {
649 395791 const double own = 0.5 * boulder.diameter * OwnMarginRadii();
650
4/4
✓ Branch 0 taken 61829 times.
✓ Branch 1 taken 333962 times.
✓ Branch 2 taken 6207 times.
✓ Branch 3 taken 55622 times.
457620 return roll == 0.0 ||
651
1/1
✓ Branch 1 taken 61829 times.
457620 surface_.Contains(surface_.Expand(region, own), {boulder.x, boulder.y});
652 144071 };
653
1/1
✓ Branch 1 taken 144071 times.
144071 const CraterList candidates = ScatterBoulderCandidates(from, spacing);
654
2/2
✓ Branch 1 taken 140460 times.
✓ Branch 2 taken 3611 times.
144071 if (!options_.fragmentation.Enabled()) {
655 std::vector<Boulder> boulders =
656
2/2
✓ Branch 1 taken 140460 times.
✓ Branch 4 taken 140460 times.
140460 DrawBoulders(candidates, spacing, ground, EjectaCraters(from));
657
1/1
✓ Branch 1 taken 140460 times.
536251 std::erase_if(boulders, [&](const Boulder& boulder) { return !rests(boulder); });
658 140460 return boulders;
659 140460 }
660 3611 const double reach = 0.5 * options_.sizes.maxDiameter * (OwnMarginRadii() + BreakMarginRadii());
661
2/2
✓ Branch 1 taken 3611 times.
✓ Branch 4 taken 3611 times.
3611 const EjectaIndex ejecta = EjectaCraters(surface_.Expand(from, reach));
662 3611 std::vector<Boulder> boulders;
663
3/3
✓ Branch 1 taken 3611 times.
✓ Branch 8 taken 5256 times.
✓ Branch 9 taken 3611 times.
8867 for (const Boulder& parent : DrawBoulders(candidates, spacing, ground, ejecta)) {
664
3/3
✓ Branch 1 taken 5256 times.
✓ Branch 8 taken 16053 times.
✓ Branch 9 taken 5256 times.
21309 for (const Boulder& boulder : Break(parent, spacing, ground)) {
665 16053 const double own = 0.5 * boulder.diameter * OwnMarginRadii();
666
3/3
✓ Branch 1 taken 16053 times.
✓ Branch 4 taken 14894 times.
✓ Branch 5 taken 1159 times.
16053 if (surface_.Contains(surface_.Expand(region, own), {boulder.x, boulder.y})) {
667
1/1
✓ Branch 1 taken 14894 times.
14894 boulders.push_back(boulder);
668 }
669 5256 }
670 3611 }
671 3611 return boulders;
672 144071 }
673
674 5256 std::vector<Boulder> BoulderLayer::Break(const Boulder& parent, double spacing,
675 const Ground* ground) const {
676 5256 const BoulderFragmentation& fragmentation = options_.fragmentation;
677
3/4
✓ Branch 0 taken 4304 times.
✓ Branch 1 taken 952 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4304 times.
5256 if (parent.diameter < fragmentation.minDiameter || parent.wear < fragmentation.splitWear) {
678
1/1
✓ Branch 1 taken 952 times.
2856 return {parent};
679 }
680 4304 const bool rubble = parent.wear >= fragmentation.rubbleWear;
681
1/2
✓ Branch 0 taken 3404 times.
✗ Branch 1 not taken.
3404 const double opening = rubble || fragmentation.rubbleWear <= fragmentation.splitWear
682
2/2
✓ Branch 0 taken 3404 times.
✓ Branch 1 taken 900 times.
4304 ? 1.0
683 3404 : (parent.wear - fragmentation.splitWear) /
684 3404 (fragmentation.rubbleWear - fragmentation.splitWear);
685 4304 const double scatter =
686
2/2
✓ Branch 0 taken 900 times.
✓ Branch 1 taken 3404 times.
5204 !rubble ? 0.0
687 900 : fragmentation.rubbleWear < 1.0
688
1/2
✓ Branch 0 taken 900 times.
✗ Branch 1 not taken.
900 ? (parent.wear - fragmentation.rubbleWear) / (1.0 - fragmentation.rubbleWear)
689 : 1.0;
690
1/1
✓ Branch 1 taken 4304 times.
4304 const auto shape = Shape(parent);
691 const auto fragments =
692
2/2
✓ Branch 1 taken 4304 times.
✓ Branch 4 taken 4304 times.
4304 FragmentsOf(BoulderShapeSeed(parent.id, options_.rendering.shapePrototypes));
693 4304 Boulder origin = parent;
694 4304 origin.x = 0.0;
695 4304 origin.y = 0.0;
696
1/1
✓ Branch 2 taken 4304 times.
4304 const BoulderPlacement placement = PlaceBoulderModel(origin, *shape, 0.0, options_.pose.settle);
697 4304 const IdWords id = SplitId(parent.id);
698
1/1
✓ Branch 1 taken 4304 times.
4304 RandomStream random(options_.seed ^ kBoulderPieces, kBoulderSeed, id.low, id.high);
699
2/2
✓ Branch 0 taken 473 times.
✓ Branch 1 taken 3831 times.
4304 const double smallest = spacing > 0.0 ? options_.fade.startSamples * spacing : 0.0;
700 4304 std::vector<Boulder> pieces;
701
2/2
✓ Branch 2 taken 16249 times.
✓ Branch 3 taken 4304 times.
20553 for (std::size_t k = 0; k < fragments->size(); ++k) {
702 16249 const BoulderFragment& fragment = (*fragments)[k];
703
1/1
✓ Branch 1 taken 16249 times.
16249 const double push = random.NextDouble();
704
1/1
✓ Branch 1 taken 16249 times.
16249 const double twist = random.NextDouble();
705 16249 double low = std::numeric_limits<double>::infinity();
706 16249 double high = -low;
707
2/2
✓ Branch 4 taken 8010354 times.
✓ Branch 5 taken 16249 times.
8026603 for (const Vec3d& p : fragment.shape.mesh.positions) {
708
1/1
✓ Branch 3 taken 8010354 times.
8010354 const double z = placement.Apply(fragment.centre + p * fragment.scale).z;
709 8010354 low = std::fmin(low, z);
710 8010354 high = std::fmax(high, z);
711 }
712
1/1
✓ Branch 1 taken 16249 times.
16249 const Vec3d centre = placement.Apply(fragment.centre);
713 16249 const double out = std::hypot(centre.x, centre.y);
714 const double angle =
715
1/2
✓ Branch 0 taken 16249 times.
✗ Branch 1 not taken.
16249 out > 0.0 ? std::atan2(centre.y, centre.x) : 2.0 * std::numbers::pi * push;
716 16249 const double gap = (opening * fragmentation.gapRatio +
717 16249 scatter * fragmentation.spreadRatio * (0.5 + 0.5 * push)) *
718 16249 parent.diameter;
719 16249 Boulder piece = parent;
720 16249 piece.facetCount = 0;
721 const UVec3 key =
722 16249 Pcg3dHash({id.low, id.high, kBoulderPieces + static_cast<std::uint32_t>(k)});
723 16249 piece.id = (static_cast<std::uint64_t>(key.x) << 32) | key.y;
724 16249 piece.parent = parent.id;
725 16249 piece.piece = static_cast<std::uint32_t>(k);
726 16249 piece.diameter = parent.diameter * fragment.scale;
727 16249 piece.height = parent.height * fragment.shape.shortAxis * fragment.scale / shape->shortAxis;
728 16249 piece.yaw = parent.yaw + scatter * std::numbers::pi * (2.0 * twist - 1.0);
729 16249 piece.lean = parent.lean * (1.0 - scatter);
730 16249 piece.tilt = parent.tilt * (1.0 - scatter);
731
1/1
✓ Branch 1 taken 16249 times.
16249 const double sunk = std::clamp(-low / (high - low), 0.0, 0.95);
732 16249 piece.burial = std::fmax(sunk, sunk + scatter * (options_.pose.maxBurial - sunk));
733 ✗ const Vec2d at = surface_.Locate({parent.x, parent.y}, {centre.x + gap * std::cos(angle),
734
1/1
✓ Branch 1 taken 16249 times.
16249 centre.y + gap * std::sin(angle)});
735 16249 piece.x = at.x;
736 16249 piece.y = at.y;
737
2/2
✓ Branch 0 taken 1148 times.
✓ Branch 1 taken 15101 times.
16249 if (piece.diameter < smallest) {
738 1148 continue;
739 }
740
1/1
✓ Branch 1 taken 15101 times.
15101 piece.model = options_.rendering.models.IsModel(piece.diameter, spacing);
741 15101 piece.ground.reset();
742
2/2
✓ Branch 0 taken 706 times.
✓ Branch 1 taken 14395 times.
15101 if (ground != nullptr) {
743 const double height =
744
1/1
✓ Branch 1 taken 706 times.
706 ground->HeightAt(piece.x, piece.y, options_.bedrock.exposureSpacing);
745
1/2
✓ Branch 1 taken 706 times.
✗ Branch 2 not taken.
706 if (!std::isnan(height)) {
746 706 piece.ground = height;
747 }
748 }
749
1/1
✓ Branch 1 taken 15101 times.
15101 pieces.push_back(piece);
750 }
751 4304 return pieces;
752 4304 }
753
754 526545 double DomeHeight(const Boulder& boulder, double distance, double width) {
755 526545 const double radius = 0.5 * boulder.diameter;
756
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 526544 times.
526545 if (!(width > 0.0)) {
757 1 const double r2 = (distance * distance) / (radius * radius);
758
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 return r2 < 1.0 ? boulder.ExposedHeight() * std::sqrt(1.0 - r2) : 0.0;
759 }
760 568146 const auto area = [](double u) { return 0.5 * (u * std::sqrt(1.0 - u * u) + std::asin(u)); };
761
1/1
✓ Branch 1 taken 526544 times.
526544 const double low = std::clamp((distance - 0.5 * width) / radius, -1.0, 1.0);
762
1/1
✓ Branch 1 taken 526544 times.
526544 const double high = std::clamp((distance + 0.5 * width) / radius, -1.0, 1.0);
763
2/2
✓ Branch 0 taken 242471 times.
✓ Branch 1 taken 284073 times.
526544 if (!(high > low)) {
764 242471 return 0.0;
765 }
766 284073 return boulder.ExposedHeight() * (area(high) - area(low)) * radius / width;
767 }
768
769 6 float AddBoulder(float height, double x, double y, const Boulder& boulder, double weight,
770 const Surface& surface, double width) {
771
1/1
✓ Branch 1 taken 6 times.
6 const Vec2d offset = surface.Offset({boulder.x, boulder.y}, {x, y});
772
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (!(width > 0.0)) {
773 6 const double dx = offset.x;
774 6 const double dy = offset.y;
775 6 const double radius = 0.5 * boulder.diameter;
776 6 const double r2 = (dx * dx + dy * dy) / (radius * radius);
777
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
6 if (r2 >= 1.0) {
778 1 return height;
779 }
780 const double top =
781 5 boulder.CutByFacets(offset, boulder.ExposedHeight() * std::sqrt(1.0 - r2));
782 5 return static_cast<float>(static_cast<double>(height) + weight * top);
783 }
784 const double dome =
785
0/1
✗ Branch 1 not taken.
✗ boulder.CutByFacets(offset, DomeHeight(boulder, std::hypot(offset.x, offset.y), width));
786
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ return dome > 0.0 ? static_cast<float>(static_cast<double>(height) + weight * dome) : height;
787 }
788
789 81695 std::vector<BoulderLayer::Relief> BoulderLayer::PrepareRelief(const std::vector<Boulder>& boulders,
790 double spacing) const {
791 81695 const BoulderRelief look = options_.rendering.relief;
792 81695 const bool filleted = options_.fillet.heightShare > 0.0;
793 81695 std::vector<Relief> reliefs;
794
2/2
✓ Branch 5 taken 29538 times.
✓ Branch 6 taken 81695 times.
111233 for (const Boulder& boulder : boulders) {
795
1/1
✓ Branch 1 taken 29538 times.
29538 const double weight = ResolutionWeight(boulder.diameter, spacing, options_.fade);
796 29538 const bool drawn = options_.rendering.reliefRange.Holds(boulder.diameter, boulder.model);
797
3/4
✓ Branch 0 taken 29069 times.
✓ Branch 1 taken 469 times.
✓ Branch 2 taken 29069 times.
✗ Branch 3 not taken.
29538 const bool rock = drawn && look != BoulderRelief::None;
798
5/6
✓ Branch 0 taken 1702 times.
✓ Branch 1 taken 27836 times.
✓ Branch 2 taken 29 times.
✓ Branch 3 taken 1673 times.
✓ Branch 4 taken 29 times.
✗ Branch 5 not taken.
29538 const bool apron = filleted && (drawn || boulder.model);
799
6/6
✓ Branch 0 taken 469 times.
✓ Branch 1 taken 29069 times.
✓ Branch 2 taken 29 times.
✓ Branch 3 taken 440 times.
✓ Branch 4 taken 1605 times.
✓ Branch 5 taken 27493 times.
29538 if (!(rock || apron) || weight <= 0.0) {
800 2045 continue;
801 }
802
2/2
✓ Branch 0 taken 21565 times.
✓ Branch 1 taken 5928 times.
27493 const double width = look == BoulderRelief::Dome ? spacing : 0.0;
803 27493 Relief relief{&boulder, weight, rock, width, std::nullopt, {}};
804 27493 double reach = 0.5 * (boulder.diameter + width);
805
4/4
✓ Branch 0 taken 27464 times.
✓ Branch 1 taken 29 times.
✓ Branch 2 taken 5899 times.
✓ Branch 3 taken 21565 times.
27493 if (rock && look == BoulderRelief::Shape) {
806
1/1
✓ Branch 1 taken 5899 times.
5899 relief.placed = PlacedOnLevel(boulder);
807 5899 const Boulder local = InLocalFrame(boulder);
808
1/1
✓ Branch 2 taken 5899 times.
5899 reach = ShapeReach(*relief.placed, local.x, local.y);
809 }
810
2/2
✓ Branch 0 taken 1702 times.
✓ Branch 1 taken 25791 times.
27493 if (filleted) {
811
1/1
✓ Branch 1 taken 1702 times.
1702 reach = std::fmax(reach, FilletReach(boulder, options_.fillet));
812 }
813
1/1
✓ Branch 1 taken 27493 times.
27493 relief.box = ReliefBox(boulder.x, boulder.y, reach, surface_);
814
1/1
✓ Branch 2 taken 27493 times.
27493 reliefs.push_back(std::move(relief));
815 27493 }
816 81695 return reliefs;
817 }
818
819 59266 void BoulderLayer::DrawRelief(SampleBlock& samples, const std::vector<Relief>& reliefs,
820 unsigned threads) const {
821 711894 const auto draw = [&](float height, float base, const Vec2d& p, const Relief& relief) {
822
2/2
✓ Branch 1 taken 182481 times.
✓ Branch 2 taken 529413 times.
711894 const TriangleMesh* shape = relief.placed ? &*relief.placed : nullptr;
823 1423788 return AddRelief(height, base, p.x, p.y, *relief.boulder, shape, relief.weight, relief.rock,
824 711894 relief.width);
825 59266 };
826
2/2
✓ Branch 1 taken 81 times.
✓ Branch 2 taken 59185 times.
59266 if (Raster<float>* heights = samples.Grid()) {
827 81 const std::span<const float> values = std::as_const(*heights).Values();
828
1/1
✓ Branch 3 taken 81 times.
81 const std::vector<float> ground(values.begin(), values.end());
829 81 const std::size_t width = heights->Width();
830 81 std::vector<std::vector<PixelWindow>> windows;
831
2/2
✓ Branch 4 taken 23083 times.
✓ Branch 5 taken 81 times.
23164 for (const Relief& relief : reliefs) {
832
2/2
✓ Branch 1 taken 23083 times.
✓ Branch 4 taken 23083 times.
23083 windows.push_back(PixelWindowsIn(*heights, relief.box, surface_));
833 }
834
1/1
✓ Branch 1 taken 81 times.
81 ForEachFeatureCell(
835 *heights, threads, reliefs.size(),
836 297971 [&](std::size_t n) -> const std::vector<PixelWindow>& { return windows[n]; },
837 81 [&](float& cell, const FeatureCell& at) {
838 709096 const float base = ground[at.row * width + at.col];
839 709096 cell = draw(cell, base, at.centre, reliefs[at.feature]);
840 709096 });
841 81 return;
842 81 }
843
2/2
✓ Branch 1 taken 230484 times.
✓ Branch 2 taken 59185 times.
289669 for (std::size_t i = 0; i < samples.Size(); ++i) {
844
3/3
✓ Branch 1 taken 230484 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 230483 times.
230484 if (samples.IsNoData(i)) {
845 1 continue;
846 }
847
1/1
✓ Branch 1 taken 230483 times.
230483 const Vec2d p = samples.Position(i);
848 230483 const float base = samples.Height(i);
849
2/2
✓ Branch 5 taken 5809 times.
✓ Branch 6 taken 230483 times.
236292 for (const Relief& relief : reliefs) {
850
2/2
✓ Branch 1 taken 2798 times.
✓ Branch 2 taken 3011 times.
5809 if (surface_.Contains(relief.box, p)) {
851
1/1
✓ Branch 2 taken 2798 times.
2798 samples.Height(i) = draw(samples.Height(i), base, p, relief);
852 }
853 }
854 }
855 }
856
857 59268 void BoulderLayer::ApplySamples(SampleBlock& samples, const LayerContext& context) const {
858
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 59265 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 time.
59268 if (options_.rendering.relief == BoulderRelief::None && options_.fillet.heightShare <= 0.0) {
859 2 return;
860 }
861 59266 const Raster<float>* grid = samples.Grid();
862 const double spacing =
863
4/4
✓ Branch 0 taken 81 times.
✓ Branch 1 taken 59185 times.
✓ Branch 3 taken 81 times.
✓ Branch 6 taken 81 times.
59266 grid != nullptr ? std::max(context.spacing, RasterGrid::FromRaster(*grid).Spacing(surface_))
864 59266 : context.spacing;
865
2/2
✓ Branch 0 taken 45938 times.
✓ Branch 1 taken 13328 times.
59266 const double rim = options_.rendering.relief == BoulderRelief::Dome ? 0.5 * spacing : 0.0;
866 const std::vector<Boulder> boulders =
867
3/3
✓ Branch 1 taken 59266 times.
✓ Branch 4 taken 59266 times.
✓ Branch 7 taken 59266 times.
59266 Boulders(surface_.Expand(samples.Bounds(), rim), spacing, context.ground);
868
2/2
✓ Branch 1 taken 59266 times.
✓ Branch 4 taken 59266 times.
59266 DrawRelief(samples, PrepareRelief(boulders, spacing), context.threads);
869 59266 }
870
871 71 std::vector<BoulderModel> BoulderLayer::Models(const std::vector<Boulder>& boulders) const {
872 71 std::vector<const Boulder*> placed;
873
2/2
✓ Branch 5 taken 26829 times.
✓ Branch 6 taken 71 times.
26900 for (const Boulder& boulder : boulders) {
874
6/6
✓ Branch 0 taken 798 times.
✓ Branch 1 taken 26031 times.
✓ Branch 3 taken 752 times.
✓ Branch 4 taken 46 times.
✓ Branch 5 taken 752 times.
✓ Branch 6 taken 26077 times.
26829 if (boulder.model && boulder.ground) {
875
1/1
✓ Branch 1 taken 752 times.
752 placed.push_back(&boulder);
876 }
877 }
878
1/1
✓ Branch 2 taken 71 times.
71 std::vector<BoulderModel> models(placed.size());
879
2/2
✓ Branch 1 taken 71 times.
✓ Branch 5 taken 71 times.
71 ParallelFor(placed.size(), 0, [&](std::size_t n) {
880 752 const Boulder& boulder = *placed[n];
881
1/1
✓ Branch 1 taken 752 times.
752 std::shared_ptr<const BoulderShape> shape = Shape(boulder);
882 752 const Boulder local = InLocalFrame(boulder);
883 const BoulderPlacement placement =
884
1/1
✓ Branch 3 taken 752 times.
752 PlaceBoulderModel(local, *shape, *boulder.ground, options_.pose.settle);
885
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 751 times.
752 if (boulder.facetCount > 0) {
886
1/1
✓ Branch 1 taken 1 time.
2 shape = std::make_shared<const BoulderShape>(
887
1/1
✓ Branch 3 taken 1 time.
3 Abraded(*shape, placement, local, *boulder.ground));
888 }
889 752 models[n] = {boulder,
890
1/1
✓ Branch 1 taken 752 times.
752 BoulderShapeSeed(boulder.parent != 0 ? boulder.parent : boulder.id,
891 752 options_.rendering.shapePrototypes),
892 752 shape, placement};
893
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 746 times.
2256 });
894 142 return models;
895 71 }
896
897 68 BoulderFeatures BoulderLayer::BouldersOver(const RasterGrid& grid, double spacing,
898 const Ground* ground) const {
899 68 BoulderFeatures placed{
900
3/3
✓ Branch 1 taken 68 times.
✓ Branch 5 taken 68 times.
✓ Branch 8 taken 68 times.
68 Boulders(grid.Bounds(), std::max(spacing, grid.Spacing(surface_)), ground), {}};
901
1/1
✓ Branch 1 taken 68 times.
68 placed.models = Models(placed.boulders);
902 68 return placed;
903 }
904
905 } // namespace terraingen
906