layers/craters/secondary_model.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /// @file | ||
| 2 | /// @brief The laws secondary craters follow, their options, and their density around a primary. | ||
| 3 | #pragma once | ||
| 4 | |||
| 5 | #include "terraingen/layers/craters/crater.h" | ||
| 6 | #include "terraingen/layers/craters/rays.h" | ||
| 7 | #include "terraingen/random/value_distribution.h" | ||
| 8 | #include "terraingen/util/geometry.h" | ||
| 9 | |||
| 10 | #include <cstdint> | ||
| 11 | #include <limits> | ||
| 12 | #include <memory> | ||
| 13 | #include <optional> | ||
| 14 | #include <vector> | ||
| 15 | |||
| 16 | namespace terraingen { | ||
| 17 | |||
| 18 | /// The largest secondary crater's diameter at `range` metres from a primary of `diameter` metres, | ||
| 19 | /// in metres, after the generalized fit of Singer et al. (2020): d = a R^-b with a = 0.022 D^1.96 | ||
| 20 | /// and b = 0.14 ln D, D and R in km. | ||
| 21 | double SingerMaxSecondaryDiameter(double diameter, double range); | ||
| 22 | |||
| 23 | /// The range in metres beyond which `SingerMaxSecondaryDiameter` falls below `size` metres: its | ||
| 24 | /// inverse in range. Infinite for primaries of 1 km or less, whose largest secondary does not | ||
| 25 | /// shrink with range. | ||
| 26 | double SingerRangeForSize(double diameter, double size); | ||
| 27 | |||
| 28 | /// How far from its centre a primary of `diameter` metres throws its first secondaries (Singer et | ||
| 29 | /// al. 2020): the radius plus about 1.8 diameters up to 3 km and 1.3 from 31 km, in metres. | ||
| 30 | double SecondaryOnsetRange(double diameter); | ||
| 31 | |||
| 32 | /// The median ray length in metres of Elliott et al. (2018, eq. 2): `coefficient` R^`exponent` km | ||
| 33 | /// for a crater of radius R km. | ||
| 34 | /// @param diameter In metres. | ||
| 35 | /// @param coefficient In km. | ||
| 36 | double ElliottMedianRayLength(double diameter, double coefficient = 6.59, double exponent = 1.27); | ||
| 37 | |||
| 38 | /// Secondaries gathered into their primary's rays and clumped along them, as they commonly lie | ||
| 39 | /// in radial chains and clusters pointing back to the primary (Singer et al. 2020); off by | ||
| 40 | /// default. `share` of the secondaries follow the rays of `layout`, each ray's across-profile | ||
| 41 | /// Gaussian at the ray's width 2 radii from the centre but never narrower than | ||
| 42 | /// `minHalfAngleDegrees`, and clumped along the ray by noise in log range with a wavelength of | ||
| 43 | /// `clusterSpacing`. The count is kept: secondaries move into the rays, none are added. The | ||
| 44 | /// share, floor angle, clustering and spacing are illustrative, as the sources do not measure | ||
| 45 | /// them. Give a crater albedo layer the same layout for bright rays over the secondaries. | ||
| 46 | struct SecondaryRays { | ||
| 47 | 1 | bool enabled = false; ///< Whether secondaries follow rays. | |
| 48 | 1 | RayLayout layout{}; ///< The rays. | |
| 49 | 1 | double share = 0.7; ///< Share of secondaries in rays, in [0, 1]. | |
| 50 | 1 | double minHalfAngleDegrees = 2.0; ///< Narrowest half width of a ray, in (0, 90) degrees. | |
| 51 | 1 | double clustering = 0.8; ///< How strongly they clump along a ray, in [0, 1]. | |
| 52 | 1 | double clusterSpacing = 0.15; ///< Wavelength of the clumps in ln range; positive. | |
| 53 | |||
| 54 | 1 | bool operator==(const SecondaryRays&) const = default; | |
| 55 | }; | ||
| 56 | |||
| 57 | /// Secondaries stretched toward their primary, elliptical along the line back to it and rounder | ||
| 58 | /// with range, as distant secondaries are (Singer et al. 2020); round by default. The ellipticity | ||
| 59 | /// falls linearly to 1 at the outer range, which is illustrative. | ||
| 60 | struct SecondaryShapes { | ||
| 61 | 1 | double elongatedShare = 0.0; ///< Share of secondaries that are elliptical, in [0, 1]. | |
| 62 | /// Long over short axis near the primary; at least 1 and bounded. | ||
| 63 | 1 | ValueDistribution ellipticity{ValueDistribution::Kind::Uniform, 1.2, 1.8}; | |
| 64 | |||
| 65 | 1 | bool operator==(const SecondaryShapes&) const = default; | |
| 66 | }; | ||
| 67 | |||
| 68 | /// Which primaries throw secondaries, how many, and over what sizes. | ||
| 69 | /// | ||
| 70 | /// At range R from a primary of diameter D, secondaries at least d across number | ||
| 71 | /// n(R) (d / (`countFraction` D))^-`sizeSlope` per m^2, below `SingerMaxSecondaryDiameter` and | ||
| 72 | /// between `SecondaryOnsetRange` and the outer range. | ||
| 73 | struct SecondaryCraterOptions { | ||
| 74 | 1 | std::uint32_t seed = 0; ///< Varies the secondaries. | |
| 75 | /// Secondaries at least `countFraction` D across in an area the size of the primary at the | ||
| 76 | /// onset; required, as the sources fix sizes but not numbers. | ||
| 77 | 1 | std::optional<double> onsetCount; | |
| 78 | 1 | double countFraction = 0.01; ///< Reference size of `onsetCount`, over D. | |
| 79 | 1 | double sizeSlope = 4.0; ///< Cumulative size slope, positive. | |
| 80 | 1 | double minDiameter = 1.0; ///< Smallest secondary generated (m). | |
| 81 | 1 | double maxDiameter = std::numeric_limits<double>::infinity(); ///< Largest (m), exclusive. | |
| 82 | /// Outer range in primary diameters from the centre; unset uses 1.6 times Elliott et al.'s | ||
| 83 | /// median ray length (`ElliottMedianRayLength`). | ||
| 84 | 1 | std::optional<double> outerRange; | |
| 85 | /// The primaries that throw secondaries. | ||
| 86 | 1 | CraterSelection selection{}; | |
| 87 | /// Polar cell side as a multiple of each size band's largest diameter. | ||
| 88 | 1 | double cellSizeFactor = 16.0; | |
| 89 | /// Most secondaries expected over one region, to catch requests that would exhaust memory. | ||
| 90 | 1 | double maxExpected = 5e7; | |
| 91 | 1 | SecondaryRays rays{}; ///< Rays and clusters; off by default. | |
| 92 | 1 | SecondaryShapes shapes{}; ///< Elongation toward the primary; round by default. | |
| 93 | |||
| 94 | 1 | bool operator==(const SecondaryCraterOptions&) const = default; | |
| 95 | }; | ||
| 96 | |||
| 97 | /// Checks secondary crater options. | ||
| 98 | /// @throws std::invalid_argument without a positive `onsetCount`, for a non-positive count | ||
| 99 | /// fraction or size slope, an empty or negative diameter range, a non-positive outer range, an | ||
| 100 | /// invalid selection, a non-positive cell size factor or limit, rays with an invalid layout, a | ||
| 101 | /// share or clustering outside [0, 1], a floor angle outside (0, 90) or a non-positive spacing, or | ||
| 102 | /// shapes with a share outside [0, 1] or an ellipticity that can be below 1 or unbounded. | ||
| 103 | void ValidateSecondaryCraterOptions(const SecondaryCraterOptions& options); | ||
| 104 | |||
| 105 | /// The outer range from its centre, in metres, of the secondaries of a primary of `diameter` | ||
| 106 | /// metres. | ||
| 107 | double SecondaryOuterRange(double diameter, const SecondaryCraterOptions& options); | ||
| 108 | |||
| 109 | /// The largest secondary a primary of `diameter` metres throws under `options`, in metres: the | ||
| 110 | /// larger of `SingerMaxSecondaryDiameter` at the onset and the outer range, at most `maxDiameter`; | ||
| 111 | /// 0 when its outer range does not pass its onset. | ||
| 112 | double LargestSecondary(double diameter, const SecondaryCraterOptions& options); | ||
| 113 | |||
| 114 | /// Where a primary's secondaries gather: its rays and the clumps along them, as a density over | ||
| 115 | /// the even spread's. | ||
| 116 | struct SecondaryRayField { | ||
| 117 | std::vector<RayDirection> rays; | ||
| 118 | double diameter = 0.0; ///< The primary's, in metres. | ||
| 119 | double onset = 0.0; ///< `SecondaryOnsetRange`, in metres. | ||
| 120 | double share = 0.0; ///< `SecondaryRays::share`. | ||
| 121 | double sinFloor = 0.0; ///< Sine of `SecondaryRays::minHalfAngleDegrees`. | ||
| 122 | double clustering = 0.0; ///< `SecondaryRays::clustering`. | ||
| 123 | double spacing = 1.0; ///< `SecondaryRays::clusterSpacing`. | ||
| 124 | double meanClump = 1.0; ///< The mean clump bump, which the clumping is divided by. | ||
| 125 | |||
| 126 | /// The rays of `primary`'s secondaries under `rays`; none when they are off or the primary | ||
| 127 | /// has no rays. | ||
| 128 | static std::optional<SecondaryRayField> Make(const Crater& primary, const SecondaryRays& rays); | ||
| 129 | |||
| 130 | /// The across-ray half width of `ray` at `range` metres. | ||
| 131 | double Width(const RayDirection& ray, double range) const; | ||
| 132 | |||
| 133 | /// The across-ray profile of `ray` at `range` metres and `azimuth`, 1 on its centre line. | ||
| 134 | double Profile(const RayDirection& ray, double range, double azimuth) const; | ||
| 135 | |||
| 136 | /// The clumping along `ray` at `range` metres, 1 on average. | ||
| 137 | double Clump(const RayDirection& ray, double range) const; | ||
| 138 | |||
| 139 | /// The mean over azimuth of the summed profiles at `range` metres. | ||
| 140 | double MeanProfile(double range) const; | ||
| 141 | |||
| 142 | /// The density of secondaries at `range` metres and `azimuth` over the even spread's. | ||
| 143 | double Density(double range, double azimuth) const; | ||
| 144 | |||
| 145 | /// An upper bound of `Density` between `r0` and `r1` metres, with a margin. | ||
| 146 | double Peak(double r0, double r1) const; | ||
| 147 | }; | ||
| 148 | |||
| 149 | /// The density of the secondaries one primary throws, on average, as `SecondaryCraterLayer` draws | ||
| 150 | /// them: rings of n(R) (d / (`countFraction` D))^-`sizeSlope` below the largest secondary at each | ||
| 151 | /// range, gathered into rays when they are on. | ||
| 152 | class SecondaryDensity { | ||
| 153 | public: | ||
| 154 | /// The secondaries `primary` throws under `options`; none when its selection leaves it out. | ||
| 155 | SecondaryDensity(const Crater& primary, const SecondaryCraterOptions& options); | ||
| 156 | |||
| 157 | /// Secondaries per m^2 with diameters in [`dMin`, `dMax`) metres at `offset` metres east and | ||
| 158 | /// north of the primary's centre, in its local frame. | ||
| 159 | double At(const Vec2d& offset, double dMin, double dMax) const; | ||
| 160 | |||
| 161 | /// The share of ground covered by those secondaries, their areas pi d^2 / 4 summed per m^2. | ||
| 162 | double CoverAt(const Vec2d& offset, double dMin, double dMax) const; | ||
| 163 | |||
| 164 | /// The largest secondary it throws, in metres; 0 when it throws none. | ||
| 165 | double Largest() const; | ||
| 166 | |||
| 167 | /// The farthest a secondary lands from the primary's centre, in metres; 0 when it throws none. | ||
| 168 | 509554 | double Reach() const { | |
| 169 | 509554 | return outer_; | |
| 170 | } | ||
| 171 | |||
| 172 | private: | ||
| 173 | /// The count, or with `cover` the covered share, per m^2 in [`dMin`, `dMax`) at `offset`. | ||
| 174 | double Measure(const Vec2d& offset, double dMin, double dMax, bool cover) const; | ||
| 175 | |||
| 176 | double sizeSlope_ = 4.0; | ||
| 177 | double minSize_ = 0.0; | ||
| 178 | double maxSize_ = 0.0; | ||
| 179 | double diameter_ = 0.0; | ||
| 180 | double onset_ = 0.0; | ||
| 181 | double outer_ = 0.0; | ||
| 182 | double density0_ = 0.0; | ||
| 183 | double reference_ = 0.0; | ||
| 184 | std::optional<SecondaryRayField> rays_; | ||
| 185 | }; | ||
| 186 | |||
| 187 | } // namespace terraingen | ||
| 188 |