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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 59059 | return diameter >= minDiameter && (!maxDiameter || diameter < *maxDiameter) && | |
| 292 | 59059 | (!model || models); | |
| 293 | } | ||
| 294 | |||
| 295 | 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 | 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 | 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 |