TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
25 / 25
100.0%
Branches:
20 / 40
50.0%
Functions:
4 / 4, 1 excl
100.0%

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
6/12
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 time.
✗ Branch 11 not taken.
✓ Branch 12 taken 1 time.
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
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
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
12/24
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 time.
✗ Branch 11 not taken.
✓ Branch 12 taken 1 time.
✗ Branch 14 not taken.
✓ Branch 15 taken 1 time.
✗ Branch 17 not taken.
✓ Branch 18 taken 1 time.
✗ Branch 19 not taken.
✓ Branch 20 taken 1 time.
✗ Branch 21 not taken.
✓ Branch 22 taken 1 time.
✗ Branch 24 not taken.
✓ Branch 25 taken 1 time.
✗ Branch 27 not taken.
✓ Branch 28 taken 1 time.
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