TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
13 / 13
100.0%
Branches:
5 / 10
50.0%
Functions:
5 / 5, 2 excl
100.0%

layers/boulders/boulder_layer.h
Line Branch Exec Source
1 /// @file
2 /// @brief `BoulderLayer`, boulders scattered over the ground and around fresh craters.
3 #pragma once
4
5 #include "terraingen/distribution/size_frequency.h"
6 #include "terraingen/layers/boulders/boulder.h"
7 #include "terraingen/layers/boulders/boulder_fragments.h"
8 #include "terraingen/layers/boulders/boulder_models.h"
9 #include "terraingen/layers/boulders/boulder_options.h"
10 #include "terraingen/layers/boulders/boulder_placer.h"
11 #include "terraingen/layers/boulders/ejecta_index.h"
12 #include "terraingen/layers/craters/crater_layer.h"
13 #include "terraingen/layers/craters/generation/cell_cache.h"
14 #include "terraingen/layers/exposure.h"
15 #include "terraingen/layers/layer.h"
16 #include "terraingen/layers/material.h"
17 #include "terraingen/util/lru_cache.h"
18
19 #include <array>
20 #include <cstdint>
21 #include <memory>
22 #include <optional>
23 #include <vector>
24
25 namespace terraingen {
26
27 /// How rich the boulders are at a point.
28 struct BoulderAbundance {
29 /// Density of the boulders between craters over its mean, from patches and bedrock.
30 double background = 1.0;
31 /// Share of fresh crater blocks, from the profile and age of the craters around.
32 double ejecta = 0.0;
33 /// Wear of the crater the ejecta share comes from, from 0 (fresh) to 1 (its blocks gone).
34 double ejectaWear = 0.0;
35 /// The size exponent a size slope map gives here (`SizeSlopeAt`); none keeps the sizes' shape.
36 std::optional<double> exponent;
37
38 bool operator==(const BoulderAbundance&) const = default;
39 };
40
41 /// The share of a sample's area covered by boulders too small to resolve in it.
42 struct UnresolvedCover {
43 35 double mean = 0.0; ///< Expected share.
44 35 double variance = 0.0; ///< Variance of the share from the boulders' Poisson scatter.
45
46
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 35 times.
35 bool operator==(const UnresolvedCover&) const = default;
47 };
48
49 /// Boulders scattered over the ground: between craters, in patches and richer on bedrock,
50 /// plus blocks around fresh craters (which need `craters`).
51 ///
52 /// Candidates are drawn per cell in size bands and thinned by a uniform keyed by each ID, so
53 /// boulders depend only on position and agree on every grid, tile and point.
54 class BoulderLayer final : public Layer, public BoulderPlacer, public MaterialSource {
55 public:
56 /// On a sphere, candidates are drawn in `CubeSphereCells`, distances are great-circle, and
57 /// relief and model placements are in each boulder's local east, north and up frame, its
58 /// origin on the datum below the boulder.
59 /// @param craters The crater layer whose craters throw blocks; may be null without ejecta.
60 /// @param surface The crater layer's surface.
61 /// @throws std::invalid_argument for invalid options, ejecta without `craters`, or a crater
62 /// layer on another surface.
63 BoulderLayer(BoulderLayerOptions options, std::shared_ptr<const CraterLayer> craters = nullptr,
64 Surface surface = {});
65
66 /// The boulders centred in `region` (surface coordinates) or reaching it with their radius or
67 /// fillet, in a fixed order.
68 /// @param spacing Sample spacing in metres; boulders too small to resolve at it are left out
69 /// and models are marked at it; 0 keeps every boulder.
70 /// @param ground Read under each boulder; needed with the exposure field.
71 /// @throws std::invalid_argument if the exposure field is enabled and `ground` is null.
72 std::vector<Boulder> Boulders(const Bounds2d& region, double spacing = 0.0,
73 const Ground* ground = nullptr) const;
74
75 /// The background and ejecta abundance at surface (x, y).
76 /// @param ground Needed with the exposure field.
77 /// @throws std::invalid_argument if the exposure field is enabled and `ground` is null.
78 BoulderAbundance AbundanceAt(double x, double y, const Ground* ground = nullptr) const;
79
80 /// The density -dN/dD of boulders of `diameter` at surface (x, y), per m^2 per metre of
81 /// diameter.
82 /// @param diameter In metres, within the layer's sizes.
83 /// @param ground Needed with the exposure field.
84 /// @throws std::invalid_argument if the exposure field is enabled and `ground` is null.
85 double DensityAt(double diameter, double x, double y, const Ground* ground = nullptr) const;
86
87 /// The share of a sample `spacing` metres across at surface (x, y) that the layer's boulders
88 /// cover where they are too small to resolve, each weighted by one minus its
89 /// `ResolutionWeight`: the area their density integrates to, and the variance of that share
90 /// over the sample's area for Poisson-scattered boulders. Visible plan areas, as the
91 /// Golombek-Rapp coverage.
92 /// @param spacing Positive.
93 /// @param ground Needed with the exposure field.
94 /// @throws std::invalid_argument if the exposure field is enabled and `ground` is null.
95 UnresolvedCover UnresolvedCoverAt(double x, double y, double spacing,
96 const Ground* ground = nullptr) const;
97 /// `UnresolvedCoverAt` at every cell centre of `grid`, row-major, the ejecta craters found
98 /// once.
99 /// @throws std::invalid_argument as `UnresolvedCoverAt`.
100 std::vector<UnresolvedCover> UnresolvedCoverOver(const RasterGrid& grid, double spacing,
101 const Ground* ground, unsigned threads) const;
102
103 /// `sizes.maxDiameter`.
104 62233 double MaxFeatureDiameter() const {
105 62233 return options_.sizes.maxDiameter;
106 }
107
108 /// The options the layer was made with.
109 1 const BoulderLayerOptions& Options() const {
110 1 return options_;
111 }
112
113 /// The 3D shape of a boulder, shared between boulders with the same shape seed; for a
114 /// fragment, its piece of its parent's shape.
115 std::shared_ptr<const BoulderShape> Shape(const Boulder& boulder) const;
116
117 /// The boulders of `boulders` to place as their own 3D models: those marked `model` whose
118 /// ground is known, placed on it.
119 std::vector<BoulderModel> Models(const std::vector<Boulder>& boulders) const;
120
121 BoulderFeatures BouldersOver(const RasterGrid& grid, double spacing,
122 const Ground* ground) const override;
123
124 3077 MaterialClass Material() const override {
125 3077 return MaterialClass::Rock;
126 }
127 /// The weight at `spacing` of the boulder whose rock stands at surface (x, y), a model's over
128 /// its footprint; 0 between boulders and on fillets.
129 /// @throws std::invalid_argument if the exposure field is enabled and `ground` is null.
130 double MaterialCoverAt(double x, double y, double spacing, const Ground* ground) const override;
131 /// `MaterialCoverAt` over `grid`, the boulders found once for the whole grid.
132 /// @throws std::invalid_argument if the exposure field is enabled and `ground` is null.
133 Raster<double> MaterialCoverOver(const RasterGrid& grid, double spacing, const Ground* ground,
134 unsigned threads) const override;
135
136 protected:
137 /// Draws the exposed parts of non-model boulders into the samples.
138 /// @throws std::invalid_argument if the exposure field is enabled and `context.ground` is null.
139 void ApplySamples(SampleBlock& samples, const LayerContext& context) const override;
140
141 private:
142 /// The candidates of `region`, before thinning.
143 CraterList ScatterBoulderCandidates(const Bounds2d& region, double spacing) const;
144 /// A candidate thinning keeps, with what it was kept by.
145 struct Kept {
146 Crater candidate;
147 BoulderAbundance abundance;
148 double density = 0.0;
149 };
150 /// `candidate` with its abundance and density, or nothing when thinning drops it.
151 std::optional<Kept> Keep(const Crater& candidate, const Ground* ground,
152 const EjectaIndex& ejecta) const;
153 /// A kept candidate as a boulder, or nothing when it is left out on steep ground.
154 std::optional<Boulder> Place(const Kept& kept, double spacing, const Ground* ground) const;
155 /// The boulders `candidates` give, in their order: those thinning keeps, their ground read in
156 /// one pass (`Ground::HeightsAt`) and then placed.
157 std::vector<Boulder> DrawBoulders(const CraterList& candidates, double spacing,
158 const Ground* ground, const EjectaIndex& ejecta) const;
159 /// The points `GroundGradient` reads around (`x`, `y`) for a boulder `diameter` metres across:
160 /// east, west, north and south.
161 std::array<Vec2d, 4> GradientPoints(double x, double y, double diameter) const;
162 /// Margin in radii a boulder's own relief and fillet reach beyond its centre.
163 double OwnMarginRadii() const;
164 /// Margin in radii its pieces may scatter beyond that when boulders break.
165 double BreakMarginRadii() const;
166 /// Sets `boulder`'s resting axis and tilt from the pose options, on the slope of `ground` when
167 /// they follow it.
168 void Settle(Boulder& boulder, const Ground* ground) const;
169 /// The rise east and north (m/m) of `ground` across `boulder`'s diameter; none where the
170 /// ground is missing.
171 std::optional<Vec2d> GroundGradient(const Boulder& boulder, const Ground& ground) const;
172 /// Rolls `boulder` down ground steeper than `maxSlope` until it rests, as `rollDistance`
173 /// allows; false when it would roll farther, or with no roll distance on steep ground.
174 bool Roll(Boulder& boulder, const Ground& ground) const;
175 /// The pieces `parent` breaks into at its wear, those resolved at `spacing`; `parent` alone
176 /// when it is too small or too fresh to break.
177 std::vector<Boulder> Break(const Boulder& parent, double spacing, const Ground* ground) const;
178 /// The pieces of the shape of seed `shapeSeed`, shared by every boulder of that shape.
179 std::shared_ptr<const std::vector<BoulderFragment>> FragmentsOf(std::uint64_t shapeSeed) const;
180 /// The shape of seed `shapeSeed`.
181 std::shared_ptr<const BoulderShape> ShapeOf(std::uint64_t shapeSeed) const;
182 /// The wear (0 fresh to 1 worn) of a boulder of `diameter` keyed by `id`: that of the crater
183 /// its block came from, drawn by each source's share of `density`, or uniform between craters.
184 double Wear(double diameter, const BoulderAbundance& abundance, double density,
185 std::uint64_t id) const;
186 /// `height` at (`x`, `y`) raised to at least `base` (the height before any boulder) plus the
187 /// boulder's exposed part and its fillet, so boulders and fillets form a union instead of
188 /// stacking; `placed` is its shape placed on level ground at 0 for `BoulderRelief::Shape`, a
189 /// dome is averaged over `width` metres, and without `rock` only the fillet is drawn.
190 float AddRelief(float height, float base, double x, double y, const Boulder& boulder,
191 const TriangleMesh* placed, double weight, bool rock, double width) const;
192 /// The height of `boulder`'s exposed rock above the ground at (`x`, `y`), 0 off it; `placed`
193 /// and `width` as in `AddRelief`.
194 double RockRelief(double x, double y, const Boulder& boulder, const TriangleMesh* placed,
195 double width) const;
196 /// `boulder` in its local frame: centred at the origin on a sphere, unchanged on a plane.
197 Boulder InLocalFrame(const Boulder& boulder) const;
198 /// `boulder`'s shape placed on level ground at 0, cut by its facets.
199 TriangleMesh PlacedOnLevel(const Boulder& boulder) const;
200 /// Gives `boulder` its ventifact facets, drawn by its ID.
201 void Abrade(Boulder& boulder) const;
202 /// Moves the points of `placed` (a mesh in `local`'s frame, on ground at height `base`) above
203 /// `local`'s facets onto them.
204 void CutFacets(TriangleMesh& placed, const Boulder& local, double base) const;
205 /// `shape` cut by `local`'s facets where `placement` puts it, still in the shape's own frame.
206 BoulderShape Abraded(const BoulderShape& shape, const BoulderPlacement& placement,
207 const Boulder& local, double base) const;
208 /// A boulder drawn into the heights: its weight at the spacing, whether its rock is drawn or
209 /// only its fillet, its shape placed on level ground (for `BoulderRelief::Shape`) and the box
210 /// its relief covers.
211 struct Relief {
212 const Boulder* boulder;
213 double weight;
214 bool rock;
215 double width; ///< Width a dome is averaged over: the spacing, or 0 for shapes.
216 std::optional<TriangleMesh> placed;
217 Bounds2d box;
218 };
219 /// The boulders of `boulders` resolved at `spacing` with relief or a fillet to draw, in order.
220 std::vector<Relief> PrepareRelief(const std::vector<Boulder>& boulders, double spacing) const;
221 /// Draws `reliefs` into every sample their boxes hold.
222 void DrawRelief(SampleBlock& samples, const std::vector<Relief>& reliefs,
223 unsigned threads) const;
224
225 /// The ejecta craters that can add blocks in `region`, indexed by how far their blocks reach.
226 EjectaIndex EjectaCraters(const Bounds2d& region) const;
227 /// The abundance at (`x`, `y`) with the ejecta craters `ejecta` around.
228 BoulderAbundance Abundance(double x, double y, const Ground* ground,
229 const EjectaIndex& ejecta) const;
230 /// The distance (m) from surface (x, y) to the nearest cluster centre.
231 double NearestClusterDistance(double x, double y) const;
232 /// The sum at surface (x, y) of the Gaussians of the `Thomas` cluster centres around it.
233 double ThomasClusterField(double x, double y) const;
234 /// The abundance between craters at (`x`, `y`), where the crater blocks hold `ejecta` of
235 /// theirs.
236 double BackgroundAbundance(double x, double y, const Ground* ground, double ejecta) const;
237 /// Sets `abundance`'s ejecta share and wear at (`x`, `y`) from the craters `ejecta`.
238 void EjectaAbundance(double x, double y, const EjectaIndex& ejecta,
239 BoulderAbundance& abundance) const;
240 double Density(double diameter, const BoulderAbundance& abundance) const;
241 /// A diameter `UnresolvedCoverAt` integrates over, its plan area, and its quadrature weight
242 /// times the share of it left unresolved.
243 struct UnresolvedNode {
244 double diameter;
245 double footprint;
246 double weight;
247 };
248 /// The nodes of `UnresolvedCoverAt` at `spacing`: Simpson's rule in log diameter, split where
249 /// the resolution fade begins.
250 std::vector<UnresolvedNode> UnresolvedNodes(double spacing) const;
251 /// `UnresolvedCoverAt` with the abundance there.
252 UnresolvedCover UnresolvedCoverWith(const BoulderAbundance& abundance,
253 const std::vector<UnresolvedNode>& nodes,
254 double spacing) const;
255 /// The mean bedrock factor around (`x`, `y`) on `ground` over the cells of the density map,
256 /// or squares of `BoulderBedrock::meanSize`, blended between the four nearest cell centres.
257 double BedrockMean(double x, double y, const Ground& ground) const;
258
259 /// A cell of the lattice bedrock means are taken over, on one ground.
260 struct MeanCell {
261 319936 const Ground* ground = nullptr;
262 319936 std::int64_t i = 0;
263 319936 std::int64_t j = 0;
264
3/6
✗ Branch 0 not taken.
✓ Branch 1 taken 319936 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 319936 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 319936 times.
319936 bool operator==(const MeanCell&) const = default;
265 };
266 struct MeanCellHash {
267 std::size_t operator()(const MeanCell& cell) const;
268 };
269
270 BoulderLayerOptions options_;
271 std::shared_ptr<const CraterLayer> craters_;
272 double minCraterDiameter_ = 0.0;
273 double maxBackground_ = 1.0;
274 double clusterMean_ = 1.0;
275 double referenceCount_ = 0.0; ///< N(>=D0) of the sizes at a size slope map's D0, per m^2.
276 std::optional<BoulderMapAbundance> mapAbundance_;
277 double patchMean_ = 1.0; ///< Under a map, what the patches are divided by.
278 Vec2d meanOrigin_{}; ///< Corner of the bedrock mean lattice, in surface units.
279 Vec2d meanCell_{}; ///< Its cell sides, in surface units; signed as the map's.
280 std::shared_ptr<LruCache<MeanCell, double, MeanCellHash>> bedrockMeans_;
281 std::shared_ptr<const SizeFrequencyDistribution> candidates_;
282 std::shared_ptr<const SizeFrequencyDistribution> ejectaSizes_;
283 std::shared_ptr<CraterCellCache> cache_;
284 std::shared_ptr<LruCache<std::uint64_t, BoulderShape>> shapes_;
285 std::shared_ptr<LruCache<std::uint64_t, std::vector<BoulderFragment>>> fragments_;
286 std::optional<ExposureField> exposure_;
287 Surface surface_;
288 std::shared_ptr<const SurfaceCells> cells_;
289 };
290
291 /// The height (m) of a boulder's dome of its exposed height over its diameter `distance` metres
292 /// from its centre, averaged over a band `width` metres wide across that distance so its rim
293 /// slopes over a sample instead of standing as a wall; 0 `width` samples it at the point.
294 double DomeHeight(const Boulder& boulder, double distance, double width = 0.0);
295
296 /// The height at surface (x, y) after a boulder's exposed part: `height` plus its dome
297 /// (`DomeHeight`), as `BoulderRelief::Dome` draws it.
298 /// @param weight Weight of the dome, in [0, 1].
299 /// @param width Width (m) the dome is averaged over, such as the sample spacing; 0 for none.
300 float AddBoulder(float height, double x, double y, const Boulder& boulder, double weight,
301 const Surface& surface = {}, double width = 0.0);
302
303 } // namespace terraingen
304