TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
108 / 108
100.0%
Branches:
96 / 186
51.6%
Functions:
16 / 16, 1 excl
100.0%

layers/boulders/boulder_options.h
Line Branch Exec Source
1 /// @file
2 /// @brief How a `BoulderLayer` sizes, places, poses and draws its boulders.
3 #pragma once
4
5 #include "terraingen/distribution/size_frequency.h"
6 #include "terraingen/layers/boulders/boulder_models.h"
7 #include "terraingen/layers/boulders/boulder_shape.h"
8 #include "terraingen/layers/craters/crater.h"
9 #include "terraingen/layers/density_map.h"
10 #include "terraingen/layers/exposure.h"
11 #include "terraingen/layers/fade.h"
12
13 #include <cstddef>
14 #include <cstdint>
15 #include <memory>
16 #include <optional>
17 #include <vector>
18
19 namespace terraingen {
20
21 /// How the boulders drawn into the heights look there.
22 enum class BoulderRelief {
23 Dome, ///< Each boulder's exposed part as a dome: cheap, and enough for small boulders.
24 Shape, ///< The top surface of each boulder's 3D boulder shape (`GenerateBoulderShape`).
25 /// No rock, only fillets: for programs that place every boulder as a model of its own.
26 None,
27 };
28
29 /// Which boulders become 3D models of their own: those at or above either lower threshold and
30 /// below every upper one set; no lower threshold means no models.
31 struct BoulderModelThreshold {
32 12 std::optional<double> minDiameter{}; ///< Boulders at least this wide (m) are models.
33 12 std::optional<double> minSamples{}; ///< Boulders spanning at least this many samples are.
34 12 std::optional<double> maxDiameter{}; ///< Boulders this wide (m) or wider are not.
35 12 std::optional<double> maxSamples{}; ///< Boulders spanning this many samples or more are not.
36
37 /// Whether a boulder of `diameter` metres is a model: at or above a lower threshold and below
38 /// the upper ones.
39 /// @param spacing Sample spacing in metres; 0 for none, when only `minDiameter` counts.
40 bool IsModel(double diameter, double spacing) const;
41
42
4/8
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 12 times.
12 bool operator==(const BoulderModelThreshold&) const = default;
43 };
44
45 /// The sizes of the boulders between craters: Golombek and Rapp (1997) boulders of
46 /// `coverage`, or `distribution`, from `minDiameter` up to `maxDiameter`.
47 struct BoulderSizes {
48 12 double coverage = 0.02; ///< Golombek-Rapp k, the fractional area covered by boulders.
49 /// Golombek-Rapp q = `qConstant` + `qCoverageCoefficient` / k, per metre: their qa and qb.
50 12 double qConstant = 1.79;
51 12 double qCoverageCoefficient = 0.152; ///< Golombek-Rapp qb (see `qConstant`).
52 /// Sizes per m^2 in place of Golombek-Rapp boulders (e.g. `Sp8023IntercraterBlocks`); patches
53 /// and bedrock scale its density, or k without it.
54 12 std::shared_ptr<const SizeFrequencyDistribution> distribution{};
55 12 double minDiameter = 0.1; ///< Smallest boulder (m).
56 12 double maxDiameter = 4.0; ///< Largest boulder (m), exclusive.
57
58
6/12
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 12 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 12 times.
12 bool operator==(const BoulderSizes&) const = default;
59 };
60
61 /// Patches of richer and poorer boulder cover.
62 struct BoulderPatches {
63 /// Abundance varies from 1 / `spread` to `spread` times its mean; 1 for uniform.
64 12 double spread = 1.0;
65 12 double size = 200.0; ///< Typical width of a patch (m).
66
67
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 bool operator==(const BoulderPatches&) const = default;
68 };
69
70 /// How boulder cluster centres are laid out and how their clusters combine.
71 enum class BoulderClusterModel {
72 /// One centre per cell of a jittered lattice `spacing` apart; each point takes the Gaussian of
73 /// its nearest centre only.
74 Nearest,
75 /// A Thomas cluster process: centres scattered as a Poisson process, one per `spacing`^2 on
76 /// average, each spreading its boulders in a Gaussian of `radius`, overlapping clusters adding
77 /// (Thomas 1949, https://doi.org/10.1093/biomet/36.1-2.18).
78 Thomas,
79 };
80
81 /// Boulders between craters gathered in clusters: `share` of them crowd within about `radius` of
82 /// cluster centres about `spacing` apart and the rest stay scattered. The mean abundance is kept:
83 /// the mean count with a size distribution, the mean coverage with Golombek-Rapp boulders.
84 struct BoulderClusters {
85 12 double share = 0.0; ///< Share of the boulders in clusters, in [0, 1]; 0 for none.
86 12 double spacing = 50.0; ///< Typical distance between cluster centres (m).
87 /// Gaussian radius of a cluster (m): below `spacing` for `Nearest`, at most twice it for
88 /// `Thomas`.
89 12 double radius = 5.0;
90 12 BoulderClusterModel model = BoulderClusterModel::Nearest; ///< How the clusters are laid out.
91
92
4/8
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 12 times.
12 bool operator==(const BoulderClusters&) const = default;
93 };
94
95 /// Boulders broken by wear into pieces that replace them. From `splitWear` a boulder at least
96 /// `minDiameter` across is split by near-vertical cracks into pieces that still fit, their gaps
97 /// opening to `gapRatio` of its diameter; from `rubbleWear` the pieces scatter up to `spreadRatio`
98 /// diameters further, turn and sink. A split wear of 1 breaks nothing.
99 struct BoulderFragmentation {
100 12 double minDiameter = 1.0; ///< Smallest boulder that breaks (m).
101 12 double splitWear = 1.0; ///< Wear from which boulders are split, in [0, 1].
102 12 double rubbleWear = 1.0; ///< Wear from which the pieces scatter, at least `splitWear`.
103 12 int minPieces = 2; ///< Fewest pieces, at least 2.
104 12 int maxPieces = 5; ///< Most pieces, at most 12.
105 12 double gapRatio = 0.04; ///< Widest crack between split pieces, over the diameter.
106 12 double spreadRatio = 0.6; ///< How far rubble scatters beyond that, over the diameter.
107 /// Share of broken boulders split into slabs by roughly parallel cracks right through them; the
108 /// rest crack out from the middle like a pie.
109 12 double slabShare = 0.7;
110
111 /// Whether any boulder breaks.
112 291754 bool Enabled() const {
113 291754 return splitWear < 1.0;
114 }
115
116
8/16
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 12 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 12 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 12 times.
12 bool operator==(const BoulderFragmentation&) const = default;
117 };
118
119 /// How the share of a crater's blocks falls off with distance from its centre, in radii.
120 enum class EjectaProfile {
121 /// Every block inside the rim, falling along a smoothstep to none at `reach`.
122 Smoothstep,
123 /// exp(-(rho - 1) / `decayRadii`) outside the rim, `floorShare` of the rim's on the floor, and
124 /// none from `reach`: synthterrain's crater probability map (decay 0.7, floor 0.05, reach 2).
125 Exponential,
126 };
127
128 /// How a crater's blocks wear away with time.
129 enum class EjectaAging {
130 /// By the crater's freshness (`CraterState::Freshness`): all from 1, none at or below
131 /// `minFreshness`, along a smoothstep.
132 Freshness,
133 /// By the crater's age: (1 - age / `lifetimeGyr`)^`agingExponent`, synthterrain's form
134 /// (lifetime 4 Gyr, exponent 3).
135 Age,
136 };
137
138 /// Blocks thrown out by craters that dug through the regolith, added to the boulders
139 /// between craters: densest at the rim, thinning outward and with age; overlapping ejecta take the
140 /// largest share.
141 struct BoulderEjecta {
142 /// Craters at least this large throw blocks out (m); 0, with no `regolithThickness`, disables
143 /// crater blocks.
144 12 double minCraterDiameter = 0.0;
145 /// The regolith's thickness (m): craters smaller than `regolithThickness /
146 /// excavationDepthRatio` do not dig through it and throw no blocks.
147 12 std::optional<double> regolithThickness{};
148 12 double excavationDepthRatio = 0.1; ///< Depth of excavation over crater diameter.
149 /// The blocks' sizes at the rim of a fresh crater, per m^2; unset for Golombek-Rapp boulders of
150 /// `coverage`.
151 12 std::shared_ptr<const SizeFrequencyDistribution> distribution{};
152 12 double coverage = 0.1; ///< Golombek-Rapp k at the rim of a fresh crater.
153 12 EjectaProfile profile = EjectaProfile::Smoothstep; ///< Falloff with distance.
154 12 double reach = 3.0; ///< Radii from the centre beyond which a crater adds no blocks.
155 12 double decayRadii = 0.7; ///< e-folding distance of `Exponential`, in radii.
156 12 double floorShare = 0.05; ///< The floor's share of the rim's blocks with `Exponential`.
157 12 EjectaAging aging = EjectaAging::Freshness; ///< Wear with time.
158 12 double minFreshness = 0.7; ///< Freshness at or below which a crater keeps no blocks.
159 12 double lifetimeGyr = 4.0; ///< Age at which a crater keeps no blocks, for `Age`.
160 12 double agingExponent = 3.0; ///< Exponent of the `Age` wear.
161 /// Surface gravity (m/s^2): when set with `regolithThickness`, a crater's blocks reach no
162 /// further than `BoulderThrowDistance`.
163 12 std::optional<double> gravity{};
164
165 /// The diameter (m) of the smallest crater that throws blocks, from `minCraterDiameter` and the
166 /// regolith; 0 when crater blocks are disabled.
167 double MinCraterDiameter() const;
168
169
14/28
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 12 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 12 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 12 times.
✗ Branch 16 not taken.
✓ Branch 17 taken 12 times.
✗ Branch 18 not taken.
✓ Branch 19 taken 12 times.
✗ Branch 20 not taken.
✓ Branch 21 taken 12 times.
✗ Branch 22 not taken.
✓ Branch 23 taken 12 times.
✗ Branch 24 not taken.
✓ Branch 25 taken 12 times.
✗ Branch 26 not taken.
✓ Branch 27 taken 12 times.
✗ Branch 29 not taken.
✓ Branch 30 taken 12 times.
12 bool operator==(const BoulderEjecta&) const = default;
170 };
171
172 /// The greatest throw (m) of blocks from a crater's centre over a regolith blanket: Bart and
173 /// Melosh's (2010, eq. 9) (138.5 / g) (D / H)^0.7086.
174 /// @param craterDiameter D, in metres.
175 /// @param regolithThickness H, in metres.
176 /// @param gravity g, in m/s^2.
177 /// @throws std::invalid_argument unless every argument is positive and finite.
178 double BoulderThrowDistance(double craterDiameter, double regolithThickness, double gravity);
179
180 /// Boulders breaking out of bedrock: abundance scales from 1 on soil to `abundance` on
181 /// bedrock, in proportion to the exposure.
182 struct BoulderBedrock {
183 /// The exposure field; unset for no bedrock boulders.
184 12 std::optional<ExposureParams> exposure{};
185 12 double abundance = 3.0; ///< Abundance on bedrock over that on soil.
186 /// Spacing the ground is read at for the exposure field (m), fixed so that which boulders exist
187 /// does not depend on the level of detail.
188 12 double exposureSpacing = 1.0;
189 /// With a density map, the side (m) of the squares bedrock is held to the map's mean over, so
190 /// it gathers boulders without adding any; a raster map's own cells are used instead.
191 12 double meanSize = 1000.0;
192
193
4/8
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 12 times.
12 bool operator==(const BoulderBedrock&) const = default;
194 };
195
196 /// How each boulder stands, drawn per boulder uniformly between these.
197 struct BoulderPose {
198 12 double minHeightRatio = 0.4; ///< Least boulder height over diameter.
199 12 double maxHeightRatio = 0.6; ///< Greatest boulder height over diameter.
200 12 double minBurial = 0.1; ///< Least buried share of the height.
201 12 double maxBurial = 0.4; ///< Greatest buried share of the height.
202 12 double maxLean = 0.26; ///< Largest tilt from the vertical (radians, about 15 degrees).
203 /// How far wear sets burial, from 0 to 1: the buried share is drawn at (1 - b) u + b w of the
204 /// way from `minBurial` to `maxBurial`, with u uniform and w the boulder's wear. A crater's
205 /// blocks wear with it (0 fresh to 1 gone); boulders between craters span every stage, their
206 /// wear uniform. 0 draws burial uniformly.
207 12 double burialByWear = 0.0;
208 /// Share of boulders resting on a side, their intermediate axis up, rather than their face.
209 12 double sideShare = 0.0;
210 /// Share of boulders standing on an end, their long axis up; with `sideShare`, at most 1.
211 12 double endShare = 0.0;
212 /// Largest tilt of a boulder's up axis from the vertical in a random direction, after its lean
213 /// (radians); drawn uniformly in the cosine, so steep tilts are as likely as on a sphere cap.
214 12 double maxTilt = 0.0;
215 /// Settles each boulder on the slope of the ground beneath it, its up axis along the ground's
216 /// normal before the tilt; needs the ground.
217 12 bool followSlope = false;
218 /// Tips each boulder model over, from its drawn pose, until it rests stably on level ground
219 /// (`PlaceBoulderModel`), before the tilt and the slope; off leaves shapes as drawn, some
220 /// balanced on an edge or corner.
221 12 bool settle = true;
222 /// Steepest ground, measured across a boulder's diameter, that boulders stay on (radians);
223 /// boulders on steeper ground are left out, models and boulders drawn into the heights alike,
224 /// so which boulders exist does not depend on the spacing. Unset keeps boulders on any slope;
225 /// needs the ground.
226 12 std::optional<double> maxSlope{};
227 /// With `maxSlope`, boulders on steeper ground roll down it instead, in steps along the
228 /// steepest descent, and come to rest where it is no steeper than `maxSlope`, gathering at the
229 /// foot of slopes; those that would roll farther than this many metres are left out. Unset
230 /// leaves out every boulder on steeper ground.
231 12 std::optional<double> rollDistance{};
232
233
13/26
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 12 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 12 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 12 times.
✗ Branch 16 not taken.
✓ Branch 17 taken 12 times.
✗ Branch 18 not taken.
✓ Branch 19 taken 12 times.
✗ Branch 20 not taken.
✓ Branch 21 taken 12 times.
✗ Branch 23 not taken.
✓ Branch 24 taken 12 times.
✗ Branch 26 not taken.
✓ Branch 27 taken 12 times.
12 bool operator==(const BoulderPose&) const = default;
234 };
235
236 /// Regolith piled against each boulder's base, drawn into the heights around boulders drawn
237 /// there and around boulder models alike: `heightShare` of the exposed height out to the boulder's
238 /// radius, then falling as (1 - t)^2 to nothing `widthRatio` diameters further out. Where the
239 /// boulder stands above it, the boulder shows.
240 struct BoulderFillet {
241 12 double heightShare = 0.0; ///< Height over the boulder's exposed height, in [0, 1]; 0 for none.
242 12 double widthRatio = 0.3; ///< Reach beyond the boulder's radius, over its diameter.
243
244
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 bool operator==(const BoulderFillet&) const = default;
245 };
246
247 /// Ventifacts: windward facets planed on boulders by sand-laden wind, set once the boulder has
248 /// settled. A facet faces upwind at `minAngle` to `maxAngle` from horizontal and meets the ground
249 /// at the boulder's upwind edge when fresh, moving in to its centre as it wears; boulders with a
250 /// keel carry a second facet facing the second wind. Angles are in radians.
251 struct BoulderVentifacts {
252 12 double share = 0.0; ///< Share of boulders with a facet, in [0, 1]; 0 for none.
253 /// Where the wind blows toward, radians counter-clockwise from east.
254 12 double windAzimuth = 0.0;
255 12 double minAngle = 0.8726646259971648; ///< Gentlest facet slope; 50 deg.
256 12 double maxAngle = 1.0471975511965976; ///< Steepest facet slope; 60 deg.
257 /// A second formative wind, where it blows toward; unset gives no keels.
258 12 std::optional<double> secondWindAzimuth{};
259 /// Share of faceted boulders with a second facet and a keel between, in [0, 1].
260 12 double keelShare = 0.0;
261
262
6/12
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 12 times.
12 bool operator==(const BoulderVentifacts&) const = default;
263 };
264
265 /// @throws std::invalid_argument for a share or keel share outside [0, 1], facet angles outside
266 /// (0, 90) deg or out of order, or non-finite wind azimuths.
267 void ValidateBoulderVentifacts(const BoulderVentifacts& ventifacts);
268
269 /// The ventifacts of Gale crater's Bradbury Landing to Rocknest traverse: half the rocks are
270 /// ventifacts and 78% of those faceted, 39% faceted in all, 12 of the 39 faceted with keels
271 /// (Bridges et al. 2014), and facets of 50 to 60 deg as on basalt (Laity and Bridges 2009). The
272 /// winds are left to the site: Gale's came from the southwest and north-northeast.
273 BoulderVentifacts GaleVentifacts();
274
275 /// The height (m) of `boulder`'s fillet `distance` metres from its centre.
276 double FilletHeight(const Boulder& boulder, double distance, const BoulderFillet& fillet);
277
278 /// How far (m) from its centre `boulder`'s fillet reaches: its radius plus the fillet's width.
279 double FilletReach(const Boulder& boulder, const BoulderFillet& fillet);
280
281 /// Which boulders are drawn into the heights: those from `minDiameter` up to `maxDiameter` that are
282 /// not models, and models too with `models`. Boulders drawn neither way stay in the lists only,
283 /// such as those too small to matter at the grid's scale.
284 struct BoulderReliefRange {
285 12 double minDiameter = 0.0; ///< Smallest boulder drawn (m).
286 12 std::optional<double> maxDiameter{}; ///< Boulders this wide (m) or wider are not drawn.
287 12 bool models = false; ///< Draw models into the heights as well, as a stand-in for far views.
288
289 /// Whether a boulder of `diameter` metres, a model or not, is drawn into the heights.
290 29539 bool Holds(double diameter, bool model) const {
291
5/6
✓ Branch 0 taken 29520 times.
✓ Branch 1 taken 19 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 29498 times.
✓ Branch 6 taken 22 times.
✗ Branch 7 not taken.
59059 return diameter >= minDiameter && (!maxDiameter || diameter < *maxDiameter) &&
292
3/4
✓ Branch 0 taken 451 times.
✓ Branch 1 taken 29069 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 451 times.
59059 (!model || models);
293 }
294
295
3/6
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
12 bool operator==(const BoulderReliefRange&) const = default;
296 };
297
298 /// How boulders are drawn: as relief in the heights or as 3D models, and with which shapes.
299 struct BoulderRendering {
300 12 BoulderRelief relief = BoulderRelief::Dome; ///< Look of boulders drawn into the heights.
301 12 BoulderModelThreshold models{}; ///< Which boulders are 3D models.
302 12 BoulderReliefRange reliefRange{}; ///< Which boulders are drawn into the heights.
303 /// The 3D shapes of the boulders, for `BoulderRelief::Shape` and for models.
304 12 BoulderShapeOptions shapes{};
305 /// How many shapes the boulders share, for instancing; 0 gives every boulder its own
306 /// (`BoulderShapeSeed`).
307 12 std::uint32_t shapePrototypes = 64;
308
309
5/10
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 12 times.
12 bool operator==(const BoulderRendering&) const = default;
310 };
311
312 /// How a `BoulderLayer` sizes, places, poses and draws its boulders.
313 struct BoulderLayerOptions {
314 12 BoulderSizes sizes{}; ///< Sizes of the boulders between craters.
315 12 std::uint32_t seed = 1; ///< Varies the boulders.
316 12 BoulderPatches patches{}; ///< Richer and poorer patches.
317 12 BoulderClusters clusters{}; ///< Clusters between craters; none by default.
318 12 BoulderEjecta ejecta{}; ///< Blocks around fresh craters.
319 12 BoulderBedrock bedrock{}; ///< Extra boulders on bedrock.
320 12 BoulderPose pose{}; ///< Heights, burial, lean.
321 12 BoulderFillet fillet{}; ///< Regolith piled against each boulder; none by default.
322 12 BoulderVentifacts ventifacts{}; ///< Facets planed by the wind; none by default.
323 12 BoulderFragmentation fragmentation{}; ///< Boulders broken by wear; none by default.
324 12 BoulderRendering rendering{}; ///< Relief or models, and shapes.
325 12 ResolutionFade fade{}; ///< Fade of boulders too small for the spacing.
326 /// Most boulder candidates to keep generated cells of; 0 regenerates cells every time.
327 12 std::size_t cacheBoulders = 4000000;
328 /// A map that sets the abundance of boulders between craters point by point, measuring the
329 /// boulder sizes (`DensityMap`); with `total` the blocks around craters are taken out of what
330 /// it measures first. Patches, clusters and bedrock still spread boulders within it. Unset
331 /// keeps the abundance `sizes` sets everywhere.
332 12 std::optional<DensityMap> densityMap{};
333 /// A map of how steeply boulder counts fall with size (`SizeSlopeMap`), such as a mean
334 /// diameter map, bending the sizes between craters into a local power law through their count
335 /// at its reference diameter; needs a size distribution rather than Golombek-Rapp sizes. Unset
336 /// keeps the sizes' own shape everywhere.
337 12 std::optional<SizeSlopeMap> slopeMap{};
338 /// Boulders from a catalog, placed as given and never thinned or left out; `x`, `y` and
339 /// `diameter` are read, and IDs come from position and size (`AppendManualCraters`).
340 12 std::vector<Crater> manual{};
341 /// The diameter (m) from which `manual` is a complete catalog at each point: generated boulders
342 /// at least this large centred there are dropped, so the catalog stands in for them. Infinite
343 /// or NaN where there is no catalog; unset keeps every generated boulder.
344 12 std::shared_ptr<const SurfaceField> completeness{};
345
346
17/34
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 12 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 12 times.
✗ Branch 18 not taken.
✓ Branch 19 taken 12 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 12 times.
✗ Branch 24 not taken.
✓ Branch 25 taken 12 times.
✗ Branch 27 not taken.
✓ Branch 28 taken 12 times.
✗ Branch 30 not taken.
✓ Branch 31 taken 12 times.
✗ Branch 33 not taken.
✓ Branch 34 taken 12 times.
✗ Branch 35 not taken.
✓ Branch 36 taken 12 times.
✗ Branch 38 not taken.
✓ Branch 39 taken 12 times.
✗ Branch 41 not taken.
✓ Branch 42 taken 12 times.
✗ Branch 44 not taken.
✓ Branch 45 taken 12 times.
✗ Branch 47 not taken.
✓ Branch 48 taken 12 times.
12 bool operator==(const BoulderLayerOptions&) const = default;
347 };
348
349 /// How a density map sets a boulder layer's abundance between craters: the abundance at which the
350 /// boulder sizes measure what the map does, once the crater blocks there are taken out of it for a
351 /// `total` map. With Golombek-Rapp sizes it solves for the coverage, as rock abundance maps give.
352 class BoulderMapAbundance {
353 public:
354 /// @throws std::invalid_argument for no map, an invalid one, or one measuring none of the
355 /// sizes.
356 /// @throws std::domain_error as `MapMeasure`.
357 explicit BoulderMapAbundance(const BoulderLayerOptions& options);
358
359 /// The abundance where the map reads `value` (`MapValueAt`) and the crater blocks hold `ejecta`
360 /// of their full abundance: 1 where `value` is NaN, 0 where the blocks make up the whole map.
361 double At(double value, double ejecta) const;
362
363 /// The largest abundance the map gives anywhere, at least 1.
364 27 double Max() const {
365 27 return max_;
366 }
367
368 private:
369 /// The coverage at which Golombek-Rapp boulders measure `target`.
370 double Coverage(double target) const;
371
372 DensityMap map_;
373 double coverage_ = 0.0;
374 double measure_ = 0.0;
375 double ejectaMeasure_ = 0.0;
376 std::vector<double> coverages_;
377 std::vector<double> measures_;
378 double max_ = 1.0;
379 };
380
381 /// Checks boulder sizes.
382 /// @throws std::invalid_argument unless 0 < `minDiameter` < `maxDiameter` are finite, and either
383 /// the distribution is a density covering the diameters (or extrapolating) or 0 < `coverage` < 1.
384 void ValidateBoulderSizes(const BoulderSizes& sizes);
385
386 /// Checks boulder patches.
387 /// @throws std::invalid_argument unless `spread` >= 1 is finite and `size` positive.
388 void ValidateBoulderPatches(const BoulderPatches& patches);
389
390 /// Checks crater blocks.
391 /// @param sizes The boulder sizes the blocks' distribution must cover.
392 /// @throws std::invalid_argument for a negative `minCraterDiameter`, a non-positive regolith
393 /// thickness, an excavation depth ratio outside (0, 1], and when blocks are enabled, an invalid
394 /// distribution or coverage, a reach of 1 radius or less, a non-positive decay, a floor share
395 /// outside [0, 1], a freshness outside [0, 1), a non-positive lifetime, a negative aging exponent
396 /// or a non-positive gravity.
397 void ValidateBoulderEjecta(const BoulderEjecta& ejecta, const BoulderSizes& sizes);
398
399 /// Checks bedrock boulders.
400 /// @throws std::invalid_argument for a non-positive abundance or exposure spacing, or invalid
401 /// exposure parameters.
402 void ValidateBoulderBedrock(const BoulderBedrock& bedrock);
403
404 /// Checks boulder poses.
405 /// @throws std::invalid_argument unless 0 < min <= max height ratio, 0 <= min <= max burial < 1,
406 /// the lean is finite and not negative, the burial by wear is in [0, 1], the side and end shares
407 /// are not negative and sum to at most 1, the tilt is in [0, pi], and any max slope is in
408 /// (0, pi / 2).
409 void ValidateBoulderPose(const BoulderPose& pose);
410
411 /// Checks boulder clusters.
412 /// @throws std::invalid_argument unless the share is in [0, 1], the spacing is finite and the
413 /// radius positive, below the spacing for `Nearest` and at most twice it for `Thomas`.
414 void ValidateBoulderClusters(const BoulderClusters& clusters);
415
416 /// A cluster's weight `distance` metres from its centre: a Gaussian of the cluster's radius.
417 double ClusterBump(double distance, const BoulderClusters& clusters);
418
419 /// The distance in radii beyond which a `Thomas` cluster adds nothing.
420 inline constexpr double kThomasReachRadii = 4.0;
421
422 /// The mean of `ClusterBump` of the nearest cluster centre over the plane, which the cluster field
423 /// is divided by to keep the mean density.
424 double MeanClusterBump(const BoulderClusters& clusters);
425
426 /// The mean over the surface of the `Thomas` field, the sum of `ClusterBump` within
427 /// `kThomasReachRadii` of every centre, which it is divided by to keep the mean density.
428 /// @param body Whether centres are scattered through space around a body, `spacing`^2 per square
429 /// metre of a plane through them, rather than over a plane.
430 double MeanThomasField(const BoulderClusters& clusters, bool body = false);
431
432 /// The largest a cluster field multiplies the scattered boulder density by; 1 without clusters.
433 /// For `Thomas` it is where as many centres crowd within 3 radii as a one in a million chance
434 /// allows, and the rare points above it are capped to it.
435 double MaxClusterAbundance(const BoulderClusters& clusters);
436
437 /// The mean over the plane of the patches' abundance, `spread` to the power of `PatchNoise`,
438 /// which is above 1 for a spread above 1; under a density map the patches are divided by it so
439 /// they keep the map's mean. 1 without patches.
440 double MeanPatchAbundance(const BoulderPatches& patches);
441
442 /// Checks boulder fragmentation.
443 /// @throws std::invalid_argument unless the minimum diameter is positive, 0 <= `splitWear` <=
444 /// `rubbleWear` <= 1, 2 <= `minPieces` <= `maxPieces` <= 12, and the ratios are finite and not
445 /// negative.
446 void ValidateBoulderFragmentation(const BoulderFragmentation& fragmentation);
447
448 /// Checks boulder fillets.
449 /// @throws std::invalid_argument unless the height share is in [0, 1] and the width ratio is
450 /// positive and finite.
451 void ValidateBoulderFillet(const BoulderFillet& fillet);
452
453 /// Checks how boulders are drawn.
454 /// @throws std::invalid_argument for a non-positive model threshold or invalid shapes.
455 void ValidateBoulderRendering(const BoulderRendering& rendering);
456
457 /// Checks boulder layer options.
458 /// @throws std::invalid_argument for any invalid part, or Golombek-Rapp coverage times the patch
459 /// spread and bedrock abundance of 1 or more.
460 void ValidateBoulderLayerOptions(const BoulderLayerOptions& options);
461
462 /// The largest background abundance, on the richest patches on bedrock: the patch spread, times
463 /// the bedrock abundance (at least 1) when bedrock is enabled.
464 double MaxBackgroundAbundance(const BoulderLayerOptions& options);
465
466 /// The sizes of the crater blocks at a fresh crater's rim: the ejecta distribution, or
467 /// Golombek-Rapp blocks of the ejecta coverage.
468 std::shared_ptr<const SizeFrequencyDistribution> EjectaSizes(const BoulderLayerOptions& options);
469
470 /// The distribution per m^2 candidate boulders are drawn from before thinning: the boulders
471 /// between craters at their richest, with a size slope map the power laws at its bounding
472 /// exponents too, plus fresh crater blocks when enabled.
473 /// @throws std::invalid_argument as `ValidateBoulderLayerOptions`.
474 std::shared_ptr<const SizeFrequencyDistribution>
475 BoulderCandidates(const BoulderLayerOptions& options);
476
477 } // namespace terraingen
478