TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
310 / 315
98.4%
Branches:
293 / 334
87.7%
Functions:
24 / 24
100.0%

albedo/splotches.cpp
Line Branch Exec Source
1 #include "terraingen/albedo/splotches.h"
2
3 #include "terraingen/distribution/power_law.h"
4 #include "terraingen/layers/craters/ages/age_model.h"
5 #include "terraingen/layers/craters/generation/population.h"
6 #include "terraingen/noise/fractal.h"
7 #include "terraingen/random/samplers.h"
8 #include "terraingen/random/stream.h"
9 #include "terraingen/raster/grid.h"
10 #include "terraingen/util/checks.h"
11
12 #include <algorithm>
13 #include <array>
14 #include <cmath>
15 #include <numbers>
16 #include <span>
17 #include <stdexcept>
18 #include <string>
19 #include <utility>
20
21 namespace terraingen {
22
23 namespace {
24
25 constexpr std::uint32_t kSplotchStream = 0x53504c54u;
26 constexpr std::uint32_t kSwarmStream = 0x5357524du;
27 constexpr std::uint32_t kSwarmSeed = 0x50415253u;
28 constexpr std::size_t kBlockRows = 32;
29 constexpr double kLobeRing = 180.0 / (std::numbers::pi * 45.0);
30 constexpr double kBinCells = 16.0;
31
32 /// Checks that a splotch change is above -1 and bounded.
33 161 void CheckChange(const ValueDistribution& change, const char* what) {
34 161 ValidateValueDistribution(change);
35
5/6
✓ Branch 1 taken 156 times.
✓ Branch 2 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 156 times.
✓ Branch 7 taken 3 times.
✓ Branch 8 taken 156 times.
159 if (!(change.Lowest() > -1.0 && std::isfinite(change.Highest()))) {
36
3/3
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 3 times.
✓ Branch 8 taken 3 times.
9 throw std::invalid_argument(std::string(what) + " must be above -1 and bounded");
37 }
38 156 }
39
40 /// How much of `splotch` covers the point `offset` metres from its centre, from 1 inside to 0
41 /// beyond its lobed, soft edge.
42 6279531 double Cover(const Splotch& splotch, const Vec2d& offset, const SplotchOptions& options) {
43 6279531 const double radius = 0.5 * splotch.mark.diameter;
44 6279531 double distance = std::hypot(offset.x, offset.y) / radius;
45 6279531 double angle = 0.0;
46
3/4
✓ Branch 0 taken 6250308 times.
✓ Branch 1 taken 29223 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6250308 times.
6279531 if (splotch.elongation != 1.0 || splotch.wedge) {
47 29223 const double stretch = std::sqrt(splotch.elongation);
48 29223 const double c = std::cos(splotch.heading);
49 29223 const double s = std::sin(splotch.heading);
50 29223 const double along = (offset.x * c + offset.y * s) / (radius * stretch);
51 29223 double across = (offset.y * c - offset.x * s) * stretch / radius;
52
1/2
✓ Branch 0 taken 29223 times.
✗ Branch 1 not taken.
29223 if (splotch.wedge) {
53 29223 across /= std::fmax(0.15, 0.5 * (1.0 - along));
54 }
55 29223 distance = std::hypot(along, across);
56 29223 angle = std::atan2(across, along);
57
1/2
✓ Branch 0 taken 6250308 times.
✗ Branch 1 not taken.
6279531 } else if (options.raggedness > 0.0) {
58 6250308 angle = std::atan2(offset.y, offset.x);
59 }
60
2/2
✓ Branch 0 taken 6171990 times.
✓ Branch 1 taken 107541 times.
6279531 if (distance >= 1.0) {
61 6171990 return 0.0;
62 }
63 107541 double lobe = 1.0;
64
1/2
✓ Branch 0 taken 107541 times.
✗ Branch 1 not taken.
107541 if (options.raggedness > 0.0) {
65 107541 FractalParams params;
66 107541 params.seed = splotch.key;
67 107541 params.wavelength = 1.0f;
68 107541 params.amplitude = 1.0f;
69 107541 params.octaves = 3;
70 const double n = static_cast<double>(
71
1/1
✓ Branch 1 taken 107541 times.
107541 Fractal3(kLobeRing * std::cos(angle), kLobeRing * std::sin(angle), 0.0, params));
72
1/1
✓ Branch 1 taken 107541 times.
107541 lobe = 1.0 - options.raggedness * std::clamp(0.5 - n, 0.0, 1.0);
73 }
74 107541 const double t = distance / lobe;
75 return 1.0 -
76
2/2
✓ Branch 1 taken 107541 times.
✓ Branch 4 taken 107541 times.
107541 Smoothstep(std::clamp((t - (1.0 - options.softness)) / options.softness, 0.0, 1.0));
77 }
78
79 /// The change of `splotch` at `offset` metres from its centre, crossing from one side to the
80 /// other of a mixed splotch.
81 92054 double SideChange(const Splotch& splotch, const Vec2d& offset) {
82
2/2
✓ Branch 0 taken 90336 times.
✓ Branch 1 taken 1718 times.
92054 if (splotch.otherChange == splotch.change) {
83 90336 return splotch.change;
84 }
85 1718 const double across =
86 1718 (offset.x * std::cos(splotch.split) + offset.y * std::sin(splotch.split)) /
87 1718 (0.5 * splotch.mark.diameter);
88
2/2
✓ Branch 1 taken 1718 times.
✓ Branch 4 taken 1718 times.
1718 const double w = Smoothstep(std::clamp(0.5 + across / 0.6, 0.0, 1.0));
89 1718 return splotch.change + w * (splotch.otherChange - splotch.change);
90 }
91
92 /// The four points half a splotch `diameter` metres across east, west, north and south of
93 /// (`x`, `y`).
94 9779 std::array<Vec2d, 4> SlopePoints(const Surface& surface, double x, double y, double diameter) {
95 9779 const double h = 0.5 * diameter;
96
2/2
✓ Branch 1 taken 9779 times.
✓ Branch 4 taken 9779 times.
9779 return {surface.Locate({x, y}, {h, 0.0}), surface.Locate({x, y}, {-h, 0.0}),
97
2/2
✓ Branch 1 taken 9779 times.
✓ Branch 4 taken 9779 times.
9779 surface.Locate({x, y}, {0.0, h}), surface.Locate({x, y}, {0.0, -h})};
98 }
99
100 /// The spacing slopes under splotches are read at: `spacing`, or half the smallest splotch when 0.
101 379 double SlopeSpacing(double spacing, const SplotchOptions& options) {
102
1/2
✓ Branch 0 taken 379 times.
✗ Branch 1 not taken.
379 return spacing > 0.0 ? spacing : 0.5 * options.minDiameter;
103 }
104
105 /// The slope in degrees from the heights at `SlopePoints`; 0 where any is NaN.
106 9779 double SlopeOf(std::span<const float> heights, double diameter) {
107 9779 const double dx = static_cast<double>(heights[0] - heights[1]) / diameter;
108 9779 const double dy = static_cast<double>(heights[2] - heights[3]) / diameter;
109 9779 const double slope = std::atan(std::hypot(dx, dy)) * 180.0 / std::numbers::pi;
110
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 9779 times.
9779 return std::isnan(slope) ? 0.0 : slope;
111 }
112
113 /// Changes every band of `value` by the relative reflectance `change`, never below 0.
114 1095964 void ChangeBands(AlbedoSample& value, std::size_t bands, double change) {
115
2/2
✓ Branch 0 taken 1018886 times.
✓ Branch 1 taken 77078 times.
1095964 if (change == 0.0) {
116 1018886 return;
117 }
118 77078 const double factor = std::fmax(0.0, 1.0 + change);
119
2/2
✓ Branch 0 taken 77078 times.
✓ Branch 1 taken 77078 times.
154156 for (std::size_t band = 0; band < bands; ++band) {
120
1/2
✓ Branch 2 taken 77078 times.
✗ Branch 3 not taken.
77078 if (!std::isnan(value[band])) {
121 77078 value[band] = static_cast<float>(factor * static_cast<double>(value[band]));
122 }
123 }
124 }
125
126 } // namespace
127
128 260 double SplotchSwarms::Coefficient(double lifetimeMyr) const {
129 260 return craterRate * std::pow(10.0, craterSlope) * lifetimeMyr;
130 }
131
132 13644 double SplotchSwarms::Count(double diameter) const {
133 13644 return referenceCount * std::pow(diameter / referenceDiameter, 3.0 + 1.5 * mu);
134 }
135
136 862 double SplotchOptions::Coefficient() const {
137 862 return rateCoefficient * std::pow(1000.0, rateExponent) * lifetimeMyr;
138 }
139
140 65 void ValidateSplotches(const SplotchOptions& options) {
141
6/6
✓ Branch 1 taken 63 times.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 61 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 59 times.
126 if (!(IsPositive(options.rateCoefficient) && IsPositive(options.rateExponent) &&
142
3/4
✓ Branch 1 taken 59 times.
✓ Branch 2 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 59 times.
61 IsPositive(options.lifetimeMyr) && IsPositive(options.Coefficient()))) {
143 throw std::invalid_argument(
144
1/1
✓ Branch 2 taken 6 times.
6 "Splotches need a positive, finite formation rate, slope and lifetime");
145 }
146
5/6
✓ Branch 1 taken 57 times.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 57 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 55 times.
116 if (!(IsPositive(options.minDiameter) && IsPositive(options.maxDiameter) &&
147
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 55 times.
57 options.minDiameter < options.maxDiameter)) {
148
1/1
✓ Branch 2 taken 4 times.
4 throw std::invalid_argument("Splotch diameters must be positive and increasing");
149 }
150
5/6
✓ Branch 1 taken 53 times.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 53 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 51 times.
108 if (!(IsInUnitInterval(options.darkShare) && IsInUnitInterval(options.brightShare) &&
151
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 51 times.
53 options.darkShare + options.brightShare <= 1.0)) {
152 throw std::invalid_argument(
153
1/1
✓ Branch 2 taken 4 times.
4 "Splotch dark and bright shares must be in [0, 1] and sum to at most 1");
154 }
155 51 CheckChange(options.darkChange, "A dark splotch's change");
156 49 CheckChange(options.brightChange, "A bright splotch's change");
157
4/6
✓ Branch 0 taken 43 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 43 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 43 times.
✗ Branch 5 not taken.
47 if (!(options.softness > 0.0 && options.softness <= 1.0 && options.raggedness >= 0.0 &&
158
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 41 times.
43 options.raggedness < 1.0)) {
159
1/1
✓ Branch 2 taken 6 times.
6 throw std::invalid_argument("Splotch softness must be in (0, 1] and raggedness in [0, 1)");
160 }
161
6/6
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 37 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 40 times.
41 if (options.steepBrightShare && !IsInUnitInterval(*options.steepBrightShare)) {
162
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("The steep bright splotch share must be in [0, 1]");
163 }
164
3/4
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 39 times.
40 if (!(options.steepSlopeDegrees > 0.0 && options.steepSlopeDegrees < 90.0)) {
165
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("The steep splotch slope must be in (0, 90) degrees");
166 }
167 39 ValidateResolutionFade(options.fade);
168 37 const SplotchSwarms& s = options.swarms;
169
5/6
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 36 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✓ Branch 7 taken 32 times.
73 if (!(IsPositive(s.craterRate) && IsPositive(s.craterSlope) &&
170
2/4
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 36 times.
✗ Branch 5 not taken.
36 IsPositive(s.referenceDiameter) && IsPositive(s.referenceCount) &&
171
3/4
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 35 times.
✓ Branch 4 taken 1 time.
36 IsPositive(s.minCraterDiameter) && s.maxCraterDiameter > s.minCraterDiameter &&
172
4/6
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 35 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 34 times.
✓ Branch 7 taken 1 time.
35 std::isfinite(s.maxCraterDiameter) && IsPositive(s.minRange) && s.maxRange > s.minRange &&
173
4/6
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 34 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 32 times.
34 std::isfinite(s.maxRange) && s.mu > 0.0 && s.mu < 1.0)) {
174 throw std::invalid_argument("Splotch swarms need positive rates, counts and increasing "
175
1/1
✓ Branch 2 taken 5 times.
5 "crater diameters and ranges, with mu in (0, 1)");
176 }
177 32 ValidateValueDistribution(s.splotchDiameter);
178
3/6
✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 31 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 31 times.
31 if (!(s.splotchDiameter.Lowest() > 0.0 && std::isfinite(s.splotchDiameter.Highest()))) {
179
0/1
✗ Branch 2 not taken.
✗ throw std::invalid_argument("Swarm splotch diameters must be positive and bounded");
180 }
181 31 CheckChange(s.darkChange, "A dark swarm splotch's change");
182 30 CheckChange(s.brightChange, "A bright swarm splotch's change");
183
2/2
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 30 times.
34 for (const Crater& crater : s.craters) {
184
4/8
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
8 if (!(std::isfinite(crater.x) && std::isfinite(crater.y) && IsPositive(crater.diameter) &&
185
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 IsNonNegative(crater.ageGyr))) {
186 throw std::invalid_argument(
187
0/1
✗ Branch 2 not taken.
✗ "A swarm crater needs a finite position, a positive diameter and an age");
188 }
189 }
190
4/6
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 27 times.
60 if (!(IsInUnitInterval(s.darkShare) && IsInUnitInterval(s.brightShare) &&
191
4/4
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 28 times.
✓ Branch 4 taken 1 time.
30 s.darkShare + s.brightShare <= 1.0 && IsInUnitInterval(s.wedgeShare) &&
192
3/4
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 27 times.
28 s.wedgeElongation >= 1.0 && std::isfinite(s.wedgeElongation))) {
193 throw std::invalid_argument("Swarm shares must be in [0, 1] and sum to at most 1, with a "
194
1/1
✓ Branch 2 taken 3 times.
3 "wedge elongation of at least 1");
195 }
196 27 }
197
198 34 SplotchAlbedo::SplotchAlbedo(SplotchOptions options, Surface surface)
199 34 : options_(std::move(options)), surface_(std::move(surface)) {
200
1/1
✓ Branch 1 taken 22 times.
34 ValidateSplotches(options_);
201
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 21 times.
22 if (surface_.IsBody()) {
202
1/1
✓ Branch 1 taken 1 time.
1 cells_ = std::make_shared<CubeSphereCells>(surface_);
203 } else {
204
1/1
✓ Branch 1 taken 21 times.
21 cells_ = std::make_shared<PlanarCells>();
205 }
206 70 }
207
208 1 double SplotchAlbedo::SlopeDegrees(const Ground& ground, double x, double y, double diameter,
209 double spacing) const {
210
1/1
✓ Branch 1 taken 1 time.
1 const std::array<Vec2d, 4> points = SlopePoints(surface_, x, y, diameter);
211 1 std::array<float, 4> heights{};
212
1/1
✓ Branch 4 taken 1 time.
1 ground.HeightsAt(points, heights, SlopeSpacing(spacing, options_));
213 1 return SlopeOf(heights, diameter);
214 }
215
216 800 std::vector<Splotch> SplotchAlbedo::Splotches(const Bounds2d& region, double spacing,
217 const Ground* ground) const {
218 800 const SplotchOptions& o = options_;
219
1/1
✓ Branch 1 taken 800 times.
800 CraterGenerationOptions generation;
220 800 generation.dMin = o.minDiameter;
221 800 generation.dMax = o.maxDiameter;
222 800 generation.marginRadii = 1.0;
223 800 generation.surface = surface_;
224
1/2
✓ Branch 0 taken 800 times.
✗ Branch 1 not taken.
800 if (spacing > 0.0) {
225 800 generation.minResolvedDiameter = o.fade.startSamples * spacing;
226 }
227
1/1
✓ Branch 3 taken 800 times.
800 const PowerLawSfd density(o.Coefficient(), o.rateExponent);
228
1/1
✓ Branch 1 taken 800 times.
800 const UniformAges ages(1e-3 * o.lifetimeMyr);
229
1/1
✓ Branch 2 taken 800 times.
800 const CraterList marks = GenerateCraters(*cells_, region, density, ages, o.seed, generation);
230 800 std::vector<Splotch> splotches;
231
1/1
✓ Branch 2 taken 800 times.
800 splotches.reserve(marks.Size());
232
2/2
✓ Branch 1 taken 63734 times.
✓ Branch 2 taken 800 times.
64534 for (std::size_t n = 0; n < marks.Size(); ++n) {
233
4/4
✓ Branch 1 taken 63734 times.
✓ Branch 4 taken 63734 times.
✓ Branch 6 taken 55930 times.
✓ Branch 7 taken 7804 times.
63734 if (ResolutionWeight(marks.At(n).diameter, spacing, o.fade) > 0.0) {
234
2/2
✓ Branch 1 taken 55930 times.
✓ Branch 4 taken 55930 times.
55930 splotches.push_back({marks.At(n)});
235 }
236 }
237
1/1
✓ Branch 2 taken 800 times.
800 std::vector<double> slopes(splotches.size(), 0.0);
238
6/6
✓ Branch 0 taken 380 times.
✓ Branch 1 taken 420 times.
✓ Branch 3 taken 378 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 378 times.
✓ Branch 6 taken 422 times.
800 if (ground != nullptr && o.steepBrightShare) {
239 378 std::vector<Vec2d> points;
240
1/1
✓ Branch 2 taken 378 times.
378 points.reserve(4 * splotches.size());
241
2/2
✓ Branch 4 taken 9778 times.
✓ Branch 5 taken 378 times.
10156 for (const Splotch& splotch : splotches) {
242 const auto four =
243
1/1
✓ Branch 1 taken 9778 times.
9778 SlopePoints(surface_, splotch.mark.x, splotch.mark.y, splotch.mark.diameter);
244
1/1
✓ Branch 5 taken 9778 times.
9778 points.insert(points.end(), four.begin(), four.end());
245 }
246
1/1
✓ Branch 2 taken 378 times.
378 std::vector<float> heights(points.size());
247
1/1
✓ Branch 4 taken 378 times.
378 ground->HeightsAt(points, heights, SlopeSpacing(spacing, o));
248
2/2
✓ Branch 1 taken 9778 times.
✓ Branch 2 taken 378 times.
10156 for (std::size_t n = 0; n < splotches.size(); ++n) {
249 9778 slopes[n] = SlopeOf(std::span(heights).subspan(4 * n, 4), splotches[n].mark.diameter);
250 }
251 378 }
252
2/2
✓ Branch 1 taken 55930 times.
✓ Branch 2 taken 800 times.
56730 for (std::size_t n = 0; n < splotches.size(); ++n) {
253 55930 Splotch& splotch = splotches[n];
254 55930 double darkShare = o.darkShare;
255 55930 double brightShare = o.brightShare;
256
6/6
✓ Branch 1 taken 14036 times.
✓ Branch 2 taken 41894 times.
✓ Branch 4 taken 5520 times.
✓ Branch 5 taken 8516 times.
✓ Branch 6 taken 5520 times.
✓ Branch 7 taken 50410 times.
55930 if (o.steepBrightShare && slopes[n] > o.steepSlopeDegrees) {
257 5520 const double mixed = 1.0 - darkShare - brightShare;
258 5520 brightShare = *o.steepBrightShare;
259 5520 darkShare = std::fmax(0.0, 1.0 - mixed - brightShare);
260 }
261 55930 const IdWords id = SplitId(splotch.mark.id);
262
1/1
✓ Branch 1 taken 55930 times.
55930 RandomStream random(o.seed, kSplotchStream, id.low, id.high);
263
1/1
✓ Branch 1 taken 55930 times.
55930 const double kind = random.NextDouble();
264
1/1
✓ Branch 1 taken 55930 times.
55930 const double d1 = random.NextDouble();
265
1/1
✓ Branch 1 taken 55930 times.
55930 const double d2 = random.NextDouble();
266
1/1
✓ Branch 1 taken 55930 times.
55930 const double b1 = random.NextDouble();
267
1/1
✓ Branch 1 taken 55930 times.
55930 const double b2 = random.NextDouble();
268
1/1
✓ Branch 1 taken 55930 times.
55930 splotch.split = 2.0 * std::numbers::pi * random.NextDouble();
269
1/1
✓ Branch 1 taken 55930 times.
55930 splotch.key = random.NextU32();
270
1/1
✓ Branch 1 taken 55930 times.
55930 const double dark = o.darkChange.Sample(d1, d2);
271
1/1
✓ Branch 1 taken 55930 times.
55930 const double bright = o.brightChange.Sample(b1, b2);
272
4/4
✓ Branch 0 taken 54960 times.
✓ Branch 1 taken 970 times.
✓ Branch 2 taken 11121 times.
✓ Branch 3 taken 43839 times.
55930 splotch.change = kind < darkShare + brightShare && kind >= darkShare ? bright : dark;
273
2/2
✓ Branch 0 taken 54960 times.
✓ Branch 1 taken 970 times.
55930 splotch.otherChange = kind < darkShare + brightShare ? splotch.change : bright;
274 }
275
2/2
✓ Branch 0 taken 259 times.
✓ Branch 1 taken 541 times.
800 if (o.swarms.enabled) {
276
1/1
✓ Branch 1 taken 259 times.
259 AddSwarms(region, spacing, splotches);
277 }
278
1/1
✓ Branch 3 taken 800 times.
800 std::sort(splotches.begin(), splotches.end(), [](const Splotch& a, const Splotch& b) {
279
2/2
✓ Branch 0 taken 834806 times.
✓ Branch 1 taken 500 times.
835306 return a.mark.ageGyr != b.mark.ageGyr ? a.mark.ageGyr < b.mark.ageGyr
280 835306 : a.mark.id < b.mark.id;
281 });
282 1600 return splotches;
283 800 }
284
285 259 void SplotchAlbedo::AddSwarms(const Bounds2d& region, double spacing,
286 std::vector<Splotch>& splotches) const {
287 259 const SplotchOptions& o = options_;
288 259 const SplotchSwarms& s = o.swarms;
289
1/1
✓ Branch 1 taken 259 times.
259 const double margin = 0.5 * s.splotchDiameter.Highest() * std::sqrt(s.wedgeElongation);
290
1/1
✓ Branch 1 taken 259 times.
259 CraterGenerationOptions generation;
291 259 generation.dMin = s.minCraterDiameter;
292 259 generation.dMax = s.maxCraterDiameter;
293 259 generation.surface = surface_;
294
1/1
✓ Branch 3 taken 259 times.
259 const PowerLawSfd density(s.Coefficient(o.lifetimeMyr), s.craterSlope);
295
1/1
✓ Branch 1 taken 259 times.
259 const UniformAges ages(1e-3 * o.lifetimeMyr);
296 const CraterList craters =
297 259 GenerateCraters(*cells_, surface_.Expand(region, s.maxRange + margin), density, ages,
298
2/2
✓ Branch 1 taken 259 times.
✓ Branch 4 taken 259 times.
518 o.seed ^ kSwarmSeed, generation);
299
1/1
✓ Branch 1 taken 259 times.
259 const Bounds2d wanted = surface_.Expand(region, margin);
300
1/1
✓ Branch 1 taken 259 times.
259 const Bounds2d reach = surface_.Expand(region, s.maxRange + margin);
301
2/2
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 259 times.
260 for (Crater crater : s.craters) {
302
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 if (crater.id == 0) {
303
1/1
✓ Branch 1 taken 1 time.
1 crater.id = ManualCraterId(crater.x, crater.y, crater.diameter);
304 }
305
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
1 if (!surface_.Contains(reach, {crater.x, crater.y})) {
306 ✗ continue;
307 }
308
3/3
✓ Branch 1 taken 1 time.
✓ Branch 8 taken 83 times.
✓ Branch 9 taken 1 time.
84 for (const Splotch& splotch : Swarm(crater, spacing)) {
309
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 83 times.
83 if (surface_.Contains(wanted, {splotch.mark.x, splotch.mark.y})) {
310
0/1
✗ Branch 1 not taken.
✗ splotches.push_back(splotch);
311 }
312 1 }
313 }
314
2/2
✓ Branch 1 taken 13619 times.
✓ Branch 2 taken 259 times.
13878 for (std::size_t n = 0; n < craters.Size(); ++n) {
315
4/4
✓ Branch 1 taken 13619 times.
✓ Branch 4 taken 13619 times.
✓ Branch 11 taken 179596 times.
✓ Branch 12 taken 13619 times.
193215 for (const Splotch& splotch : Swarm(craters.At(n), spacing)) {
316
2/2
✓ Branch 1 taken 458 times.
✓ Branch 2 taken 179138 times.
179596 if (surface_.Contains(wanted, {splotch.mark.x, splotch.mark.y})) {
317
1/1
✓ Branch 1 taken 458 times.
458 splotches.push_back(splotch);
318 }
319 13619 }
320 }
321 259 }
322
323 13641 std::vector<Splotch> SplotchAlbedo::Swarm(const Crater& crater, double spacing) const {
324 13641 const SplotchOptions& o = options_;
325 13641 const SplotchSwarms& s = o.swarms;
326 13641 const double a = 1.5 * s.mu;
327 13641 const double near = std::pow(s.minRange, -a);
328 13641 const double far = std::pow(s.maxRange, -a);
329 13641 std::vector<Splotch> splotches;
330 13641 const IdWords id = SplitId(crater.id);
331
1/1
✓ Branch 1 taken 13641 times.
13641 RandomStream random(o.seed, kSwarmStream, id.low, id.high);
332
1/1
✓ Branch 2 taken 13641 times.
13641 const std::uint64_t count = SamplePoisson(random, s.Count(crater.diameter));
333
2/2
✓ Branch 0 taken 567055 times.
✓ Branch 1 taken 13641 times.
580696 for (std::uint64_t i = 0; i < count; ++i) {
334
1/1
✓ Branch 1 taken 567055 times.
567055 const double range = std::pow(near - random.NextDouble() * (near - far), -1.0 / a);
335
1/1
✓ Branch 1 taken 567055 times.
567055 const double azimuth = 2.0 * std::numbers::pi * random.NextDouble();
336
1/1
✓ Branch 1 taken 567055 times.
567055 const double s1 = random.NextDouble();
337
1/1
✓ Branch 1 taken 567055 times.
567055 const double s2 = random.NextDouble();
338
1/1
✓ Branch 1 taken 567055 times.
567055 const double kind = random.NextDouble();
339
1/1
✓ Branch 1 taken 567055 times.
567055 const double d1 = random.NextDouble();
340
1/1
✓ Branch 1 taken 567055 times.
567055 const double d2 = random.NextDouble();
341
1/1
✓ Branch 1 taken 567055 times.
567055 const double b1 = random.NextDouble();
342
1/1
✓ Branch 1 taken 567055 times.
567055 const double b2 = random.NextDouble();
343
1/1
✓ Branch 1 taken 567055 times.
567055 const double split = 2.0 * std::numbers::pi * random.NextDouble();
344
1/1
✓ Branch 1 taken 567055 times.
567055 const std::uint32_t key = random.NextU32();
345
1/1
✓ Branch 1 taken 567055 times.
567055 const bool wedge = random.NextDouble() < s.wedgeShare;
346
1/1
✓ Branch 1 taken 567055 times.
567055 const double size = s.splotchDiameter.Sample(s1, s2);
347
3/3
✓ Branch 1 taken 567055 times.
✓ Branch 3 taken 382209 times.
✓ Branch 4 taken 184846 times.
567055 if (ResolutionWeight(size, spacing, o.fade) == 0.0) {
348 382209 continue;
349 }
350 ✗ const Vec2d at = surface_.Locate({crater.x, crater.y},
351
1/1
✓ Branch 1 taken 184846 times.
184846 {range * std::cos(azimuth), range * std::sin(azimuth)});
352 184846 Splotch splotch;
353 184846 splotch.mark = {at.x, at.y, size, crater.ageGyr,
354 184846 crater.id ^ ((i + 1) * 0x9E3779B97F4A7C15ull)};
355
1/1
✓ Branch 1 taken 184846 times.
184846 const double dark = s.darkChange.Sample(d1, d2);
356
1/1
✓ Branch 1 taken 184846 times.
184846 const double bright = s.brightChange.Sample(b1, b2);
357 184846 const bool mixed = kind >= s.darkShare + s.brightShare;
358
4/4
✓ Branch 0 taken 7593 times.
✓ Branch 1 taken 177253 times.
✓ Branch 2 taken 2247 times.
✓ Branch 3 taken 5346 times.
184846 splotch.change = kind >= s.darkShare && !mixed ? bright : dark;
359
2/2
✓ Branch 0 taken 5346 times.
✓ Branch 1 taken 179500 times.
184846 splotch.otherChange = mixed ? bright : splotch.change;
360 184846 splotch.split = split;
361 184846 splotch.key = key;
362
2/2
✓ Branch 0 taken 39240 times.
✓ Branch 1 taken 145606 times.
184846 if (wedge) {
363
1/1
✓ Branch 1 taken 39240 times.
39240 const Vec2d back = surface_.Offset(at, {crater.x, crater.y});
364 39240 splotch.wedge = true;
365 39240 splotch.elongation = s.wedgeElongation;
366 39240 splotch.heading = std::atan2(back.y, back.x);
367 }
368
1/1
✓ Branch 1 taken 184846 times.
184846 splotches.push_back(splotch);
369 }
370 27282 return splotches;
371 }
372
373 11 double SplotchAlbedo::Change(const Splotch& splotch, double x, double y, double spacing) const {
374
2/2
✓ Branch 1 taken 11 times.
✓ Branch 4 taken 11 times.
33 return ChangeAt({&splotch}, x, y, spacing);
375 }
376
377 1095975 double SplotchAlbedo::ChangeAt(const std::vector<const Splotch*>& splotches, double x, double y,
378 double spacing) const {
379 1095975 double change = 0.0;
380 1095975 double open = 1.0;
381
2/2
✓ Branch 5 taken 6279531 times.
✓ Branch 6 taken 1095975 times.
7375506 for (const Splotch* splotch : splotches) {
382
1/1
✓ Branch 1 taken 6279531 times.
6279531 const Vec2d offset = surface_.Offset({splotch->mark.x, splotch->mark.y}, {x, y});
383
1/1
✓ Branch 1 taken 6279531 times.
6279531 const double cover = Cover(*splotch, offset, options_);
384
2/2
✓ Branch 0 taken 6187477 times.
✓ Branch 1 taken 92054 times.
6279531 if (cover == 0.0) {
385 6187477 continue;
386 }
387 92054 const double left = 1.0 - 1e3 * splotch->mark.ageGyr / options_.lifetimeMyr;
388 92054 const double strength = std::fmax(0.0, left) * cover;
389 276162 change += open * strength *
390
1/1
✓ Branch 1 taken 92054 times.
92054 ResolutionWeight(splotch->mark.diameter, spacing, options_.fade) *
391
1/1
✓ Branch 1 taken 92054 times.
92054 SideChange(*splotch, offset);
392 92054 open *= 1.0 - strength;
393 }
394 1095975 return change;
395 }
396
397 779 void SplotchAlbedo::ApplyAt(AlbedoSample& value, std::size_t bands, double x, double y,
398 const AlbedoContext& context) const {
399
1/1
✓ Branch 1 taken 779 times.
779 ValidateAlbedoContext(context);
400 const std::vector<Splotch> splotches =
401
1/1
✓ Branch 2 taken 779 times.
779 Splotches(PointBounds(x, y), context.spacing, context.ground);
402 779 std::vector<const Splotch*> pointers;
403
1/1
✓ Branch 2 taken 779 times.
779 pointers.reserve(splotches.size());
404
2/2
✓ Branch 4 taken 784 times.
✓ Branch 5 taken 779 times.
1563 for (const Splotch& splotch : splotches) {
405
1/1
✓ Branch 1 taken 784 times.
784 pointers.push_back(&splotch);
406 }
407
1/1
✓ Branch 1 taken 779 times.
779 ChangeBands(value, bands, ChangeAt(pointers, x, y, context.spacing));
408 779 }
409
410 12 void SplotchAlbedo::Apply(std::vector<Raster<float>>& bands, const AlbedoContext& context) const {
411
1/1
✓ Branch 1 taken 12 times.
12 ValidateAlbedoContext(context);
412
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 11 times.
12 if (bands.empty()) {
413 1 return;
414 }
415
1/1
✓ Branch 2 taken 11 times.
11 const RasterGrid grid = RasterGrid::FromRaster(bands.front());
416
1/1
✓ Branch 1 taken 11 times.
11 const Bounds2d bounds = grid.Bounds();
417
1/1
✓ Branch 1 taken 11 times.
11 const std::vector<Splotch> splotches = Splotches(bounds, context.spacing, context.ground);
418 11 const double width = bounds.maxX - bounds.minX;
419 11 const double height = bounds.maxY - bounds.minY;
420 11 const double bin = kBinCells * std::fmax(width / static_cast<double>(grid.width),
421 11 height / static_cast<double>(grid.height));
422 11 const auto columns = static_cast<std::size_t>(std::ceil(width / bin));
423 11 const auto rows = static_cast<std::size_t>(std::ceil(height / bin));
424 2265166 const auto index = [&](double value, double origin, std::size_t count) {
425 2265166 const double i = std::floor((value - origin) / bin);
426
1/1
✓ Branch 1 taken 2265166 times.
2265166 return static_cast<std::size_t>(std::clamp(i, 0.0, static_cast<double>(count - 1)));
427 11 };
428
1/1
✓ Branch 1 taken 11 times.
11 std::vector<std::vector<const Splotch*>> bins(columns * rows);
429
2/2
✓ Branch 5 taken 18703 times.
✓ Branch 6 taken 11 times.
18714 for (const Splotch& splotch : splotches) {
430 const Bounds2d box =
431
1/1
✓ Branch 1 taken 18703 times.
18703 surface_.Expand(PointBounds(splotch.mark.x, splotch.mark.y),
432 18703 0.5 * splotch.mark.diameter * std::sqrt(splotch.elongation));
433
5/6
✓ Branch 0 taken 18702 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 18702 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18700 times.
✓ Branch 5 taken 2 times.
18703 if (box.maxX < bounds.minX || box.minX > bounds.maxX || box.maxY < bounds.minY ||
434
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 18699 times.
18700 box.minY > bounds.maxY) {
435 4 continue;
436 }
437
1/1
✓ Branch 1 taken 18699 times.
18699 const std::size_t c0 = index(box.minX, bounds.minX, columns);
438
1/1
✓ Branch 1 taken 18699 times.
18699 const std::size_t c1 = index(box.maxX, bounds.minX, columns);
439
1/1
✓ Branch 1 taken 18699 times.
18699 const std::size_t r0 = index(box.minY, bounds.minY, rows);
440
1/1
✓ Branch 1 taken 18699 times.
18699 const std::size_t r1 = index(box.maxY, bounds.minY, rows);
441
2/2
✓ Branch 0 taken 21502 times.
✓ Branch 1 taken 18699 times.
40201 for (std::size_t r = r0; r <= r1; ++r) {
442
2/2
✓ Branch 0 taken 24819 times.
✓ Branch 1 taken 21502 times.
46321 for (std::size_t c = c0; c <= c1; ++c) {
443
1/1
✓ Branch 2 taken 24819 times.
24819 bins[r * columns + c].push_back(&splotch);
444 }
445 }
446 }
447 11 const std::size_t count = bands.size();
448 11 const double spacing = context.spacing;
449
2/2
✓ Branch 1 taken 11 times.
✓ Branch 4 taken 11 times.
11 ForEachAlbedoBlock(bands, context.threads, kBlockRows, [&](std::size_t, std::size_t) {
450 54 return AlbedoCellVisitor([&](AlbedoSample& value, double x, double y) {
451 const auto& near =
452 1095185 bins[index(y, bounds.minY, rows) * columns + index(x, bounds.minX, columns)];
453 1095185 ChangeBands(value, count, ChangeAt(near, x, y, spacing));
454
1/1
✓ Branch 1 taken 54 times.
108 });
455 });
456 11 }
457
458 } // namespace terraingen
459