albedo/splotches.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /// @file | ||
| 2 | /// @brief Splotches: the dark and bright marks secondary impacts of loose regolith leave. | ||
| 3 | #pragma once | ||
| 4 | |||
| 5 | #include "terraingen/albedo/albedo.h" | ||
| 6 | #include "terraingen/layers/craters/crater.h" | ||
| 7 | #include "terraingen/layers/fade.h" | ||
| 8 | #include "terraingen/random/value_distribution.h" | ||
| 9 | #include "terraingen/util/cells.h" | ||
| 10 | #include "terraingen/util/surface.h" | ||
| 11 | |||
| 12 | #include <cstdint> | ||
| 13 | #include <memory> | ||
| 14 | #include <optional> | ||
| 15 | #include <vector> | ||
| 16 | |||
| 17 | namespace terraingen { | ||
| 18 | |||
| 19 | /// Swarms of splotches around new craters, the low-velocity secondaries of their ejecta (Robinson | ||
| 20 | /// et al. 2015); off by default. New craters from `minCraterDiameter` to `maxCraterDiameter` form | ||
| 21 | /// at `craterRate` (D / 10 m)^-`craterSlope` per km^2 per year (Speyerer et al. 2016: 16 a year of | ||
| 22 | /// at least 10 m over 3.305e6 km^2 yr, with the Neukum slope of 2.97 to 3.15), each throwing | ||
| 23 | /// `referenceCount` (D / `referenceDiameter`)^(3 + 1.5 `mu`) splotches, as the ejecta mass faster | ||
| 24 | /// than a speed grows (Housen and Holsapple 2011, `mu` 0.41 for sand), landing between `minRange` | ||
| 25 | /// and `maxRange` with N(> r) ~ r^(-1.5 `mu`) for launches at 45 degrees. Robinson et al. counted | ||
| 26 | /// 248 splotches 2 to 22 m across (median 6 m) within 30 km of an 18.8 m crater, at a median | ||
| 27 | /// distance of 8.7 km, which sets `minRange`; 239 were darker (median -4%), 3 brighter (+4% to | ||
| 28 | /// +12%), and some wedges pointing back to the crater. The spread of sizes, the share of wedges and | ||
| 29 | /// their elongation are illustrative. The swarms fade with the splotches' lifetime. | ||
| 30 | struct SplotchSwarms { | ||
| 31 | 1 | bool enabled = false; ///< Whether new craters throw swarms. | |
| 32 | 1 | double craterRate = 4.84e-6; ///< New craters of at least 10 m, per km^2 per year. | |
| 33 | 1 | double craterSlope = 3.06; ///< Cumulative slope of the new craters; positive. | |
| 34 | 1 | double minCraterDiameter = 10.0; ///< Smallest crater with a swarm (m); positive. | |
| 35 | 1 | double maxCraterDiameter = 50.0; ///< Largest (m); above the smallest. | |
| 36 | 1 | double referenceDiameter = 18.8; ///< Diameter of the reference crater (m). | |
| 37 | 1 | double referenceCount = 248.0; ///< Splotches of the reference crater; positive. | |
| 38 | 1 | double mu = 0.41; ///< Velocity exponent of the target, in (0, 1). | |
| 39 | 1 | double minRange = 4300.0; ///< Nearest landing distance (m); positive. | |
| 40 | 1 | double maxRange = 30000.0; ///< Farthest landing distance (m); above the nearest. | |
| 41 | /// Diameter of a swarm splotch (m); positive and bounded. | ||
| 42 | 1 | ValueDistribution splotchDiameter{ValueDistribution::Kind::LogNormal, 6.0, 0.6, 2.0, 22.0}; | |
| 43 | 1 | double darkShare = 239.0 / 248.0; ///< Share darker than the ground, in [0, 1]. | |
| 44 | 1 | double brightShare = 3.0 / 248.0; ///< Share brighter; the rest are mixed. | |
| 45 | /// Reflectance change of a dark swarm splotch; above -1 and bounded. | ||
| 46 | 1 | ValueDistribution darkChange{ValueDistribution::Kind::Constant, -0.04}; | |
| 47 | /// Reflectance change of a bright swarm splotch; above -1 and bounded. | ||
| 48 | 1 | ValueDistribution brightChange{ValueDistribution::Kind::Uniform, 0.04, 0.12}; | |
| 49 | 1 | double wedgeShare = 0.2; ///< Share of wedges pointing back to the crater, [0, 1]. | |
| 50 | 1 | double wedgeElongation = 2.0; ///< Long over short axis of a wedge; at least 1. | |
| 51 | /// New craters given by hand, such as an observed impact, each throwing a swarm like the | ||
| 52 | /// generated ones; IDs of 0 come from `ManualCraterId`. | ||
| 53 | 1 | std::vector<Crater> craters; | |
| 54 | |||
| 55 | /// New craters of at least 1 m per m^2 within `lifetimeMyr`. | ||
| 56 | double Coefficient(double lifetimeMyr) const; | ||
| 57 | |||
| 58 | /// The splotches a crater `diameter` metres across throws, on average. | ||
| 59 | double Count(double diameter) const; | ||
| 60 | |||
| 61 | 1 | bool operator==(const SplotchSwarms&) const = default; | |
| 62 | }; | ||
| 63 | |||
| 64 | /// Splotches after Speyerer et al. (2016): formed at a cumulative rate of `rateCoefficient` | ||
| 65 | /// d^-`rateExponent` per km^2 per year for diameter d in km, each lasting `lifetimeMyr`, the | ||
| 66 | /// time splotches take to churn the top centimetres. Most are dark, some bright and the rest mixed, | ||
| 67 | /// each nearly round; a younger splotch covers older ones as it fades. | ||
| 68 | struct SplotchOptions { | ||
| 69 | 1 | std::uint32_t seed = 0; ///< Seed of where splotches fall and what they are. | |
| 70 | 1 | double rateCoefficient = 1.483e-11; ///< Formation rate coefficient, per km^2 per year. | |
| 71 | 1 | double rateExponent = 4.144; ///< Cumulative slope of the formation rate; positive. | |
| 72 | 1 | double lifetimeMyr = 0.081; ///< How long a splotch lasts, fading linearly (Myr). | |
| 73 | 1 | double minDiameter = 1.0; ///< Smallest splotch (m); the fit is extrapolated below 10. | |
| 74 | 1 | double maxDiameter = 40.0; ///< Largest splotch (m), above `minDiameter`. | |
| 75 | 1 | double darkShare = 0.907; ///< Share of dark splotches, in [0, 1]. | |
| 76 | 1 | double brightShare = 0.074; ///< Share of bright ones; the rest are mixed. | |
| 77 | /// Reflectance change of a dark splotch, relative to the ground before it; above -1. | ||
| 78 | 1 | ValueDistribution darkChange{ValueDistribution::Kind::Constant, -0.04}; | |
| 79 | /// Reflectance change of a bright splotch; above -1. | ||
| 80 | 1 | ValueDistribution brightChange{ValueDistribution::Kind::Constant, 0.10}; | |
| 81 | /// Share of splotches on slopes steeper than `steepSlopeDegrees` that are bright, in [0, 1]; | ||
| 82 | /// unset keeps `brightShare`. Bright splotches are found mostly on such slopes, where mature | ||
| 83 | /// regolith is thin (Speyerer et al. 2016), but the sources give no share; needs the ground. | ||
| 84 | 1 | std::optional<double> steepBrightShare; | |
| 85 | 1 | double steepSlopeDegrees = 15.0; ///< Slope from which ground is steep, in (0, 90) degrees. | |
| 86 | 1 | double softness = 0.3; ///< Share of the radius over which the edge fades, in (0, 1]. | |
| 87 | 1 | double raggedness = 0.15; ///< Share of the radius the edge's lobes pull in, in [0, 1). | |
| 88 | 1 | ResolutionFade fade{}; ///< How splotches too small to resolve fade out. | |
| 89 | 1 | SplotchSwarms swarms{}; ///< Swarms around new craters; off by default. | |
| 90 | |||
| 91 | /// The cumulative number of splotches of diameter at least 1 m per m^2 at any time. | ||
| 92 | double Coefficient() const; | ||
| 93 | |||
| 94 | 1 | bool operator==(const SplotchOptions&) const = default; | |
| 95 | }; | ||
| 96 | |||
| 97 | /// Checks splotch options. | ||
| 98 | /// @throws std::invalid_argument for a non-positive rate, slope or lifetime, diameters that are | ||
| 99 | /// not positive and increasing, shares outside [0, 1] or summing above 1, changes that can reach | ||
| 100 | /// -1 or are unbounded, a softness outside (0, 1], a raggedness outside [0, 1), a steep bright | ||
| 101 | /// share outside [0, 1], a steep slope outside (0, 90) degrees, an invalid fade, or swarms with | ||
| 102 | /// a non-positive rate, slope, count or range, crater diameters or ranges not increasing, a mu | ||
| 103 | /// outside (0, 1), invalid or unbounded distributions, shares outside [0, 1] or summing above 1, | ||
| 104 | /// or a wedge elongation below 1. | ||
| 105 | void ValidateSplotches(const SplotchOptions& options); | ||
| 106 | |||
| 107 | /// One splotch. | ||
| 108 | struct Splotch { | ||
| 109 | Crater mark; ///< Centre, diameter, age and ID, as a crater's. | ||
| 110 | double change = 0.0; ///< Reflectance change of its dark or only side. | ||
| 111 | double otherChange = 0.0; ///< Reflectance change of its other side; `change` unless mixed. | ||
| 112 | double split = 0.0; ///< Direction across which a mixed splotch changes side, radians. | ||
| 113 | std::uint32_t key = 0; ///< Key of its edge's lobes. | ||
| 114 | double elongation = 1.0; ///< Long over short axis; 1 for round. | ||
| 115 | double heading = 0.0; ///< Toward the crater that threw it, radians from east. | ||
| 116 | bool wedge = false; ///< Narrowing to a point toward the crater. | ||
| 117 | }; | ||
| 118 | |||
| 119 | /// The splotches of `SplotchOptions` on `surface`, darkening and brightening the albedo; the | ||
| 120 | /// same in any grid, tile or point. | ||
| 121 | class SplotchAlbedo final : public AlbedoLayer { | ||
| 122 | public: | ||
| 123 | /// @throws std::invalid_argument for options invalid as `ValidateSplotches`. | ||
| 124 | explicit SplotchAlbedo(SplotchOptions options = {}, Surface surface = {}); | ||
| 125 | |||
| 126 | /// Changes the albedo at one point by the splotches over it; NaN stays NaN. | ||
| 127 | void ApplyAt(AlbedoSample& value, std::size_t bands, double x, double y, | ||
| 128 | const AlbedoContext& context) const override; | ||
| 129 | |||
| 130 | /// `ApplyAt` at every cell centre, generating the splotches once. | ||
| 131 | void Apply(std::vector<Raster<float>>& bands, const AlbedoContext& context) const override; | ||
| 132 | |||
| 133 | /// The splotches resolved at `spacing` metres reaching `region`, youngest first; with | ||
| 134 | /// `ground`, those on steep slopes follow `steepBrightShare`. | ||
| 135 | std::vector<Splotch> Splotches(const Bounds2d& region, double spacing, | ||
| 136 | const Ground* ground = nullptr) const; | ||
| 137 | |||
| 138 | /// The splotches of the swarm `crater` throws, resolved at `spacing` metres (0 for all). | ||
| 139 | std::vector<Splotch> Swarm(const Crater& crater, double spacing) const; | ||
| 140 | |||
| 141 | /// The relative reflectance change `splotch` alone gives (`x`, `y`) at `spacing` metres. | ||
| 142 | double Change(const Splotch& splotch, double x, double y, double spacing) const; | ||
| 143 | |||
| 144 | /// The slope in degrees of `ground` at the centre of a splotch `diameter` metres across at | ||
| 145 | /// (`x`, `y`), over its width, read at `spacing` metres (half the smallest splotch for 0). | ||
| 146 | double SlopeDegrees(const Ground& ground, double x, double y, double diameter, | ||
| 147 | double spacing) const; | ||
| 148 | |||
| 149 | /// What the splotches are. | ||
| 150 | 1 | const SplotchOptions& Options() const { | |
| 151 | 1 | return options_; | |
| 152 | } | ||
| 153 | |||
| 154 | private: | ||
| 155 | /// Adds the splotches of the swarms reaching `region` to `splotches`. | ||
| 156 | void AddSwarms(const Bounds2d& region, double spacing, std::vector<Splotch>& splotches) const; | ||
| 157 | /// The change of `splotches`, youngest first, at (`x`, `y`). | ||
| 158 | double ChangeAt(const std::vector<const Splotch*>& splotches, double x, double y, | ||
| 159 | double spacing) const; | ||
| 160 | |||
| 161 | SplotchOptions options_; | ||
| 162 | Surface surface_; | ||
| 163 | std::shared_ptr<const SurfaceCells> cells_; | ||
| 164 | }; | ||
| 165 | |||
| 166 | } // namespace terraingen | ||
| 167 |