TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
128 / 128
100.0%
Branches:
88 / 93
94.6%
Functions:
9 / 9
100.0%

layers/bedforms.cpp
Line Branch Exec Source
1 #include "terraingen/layers/bedforms.h"
2
3 #include "terraingen/noise/perlin.h"
4 #include "terraingen/random/stream.h"
5 #include "terraingen/raster/cell_loops.h"
6 #include "terraingen/raster/grid.h"
7 #include "terraingen/util/checks.h"
8
9 #include <algorithm>
10 #include <cmath>
11 #include <numbers>
12 #include <stdexcept>
13
14 namespace terraingen {
15
16 namespace {
17
18 constexpr std::uint32_t kBedformStream = 0x42454446u;
19 constexpr std::uint32_t kBedformSphereStream = 0x42454453u;
20 constexpr double kRadiusMin = 0.40;
21 constexpr double kRadiusMax = 0.70;
22 constexpr double kRagged = 0.30;
23 constexpr double kEdgeShare = 0.35;
24 constexpr double kWavelengthMin = 0.8;
25 constexpr double kWavelengthSpan = 0.4;
26 constexpr double kWanderWavelengths = 5.0;
27 constexpr double kRagRadii = 0.5;
28
29 constexpr const char* kExposureReader = "Bedforms that read the exposure field";
30
31 } // namespace
32
33 41 void ValidateBedformOptions(const BedformOptions& options) {
34
5/6
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 40 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 40 times.
41 if (!(IsPositive(options.wavelength) && IsPositive(options.fieldSize))) {
35
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("Bedform wavelength and field size must be positive");
36 }
37
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 39 times.
40 if (!IsNonNegative(options.height)) {
38
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("Bedform height must not be negative");
39 }
40
3/4
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 37 times.
39 if (!(options.leeFraction > 0.0 && options.leeFraction < 1.0)) {
41
1/1
✓ Branch 2 taken 2 times.
2 throw std::invalid_argument("Bedform leeFraction must be in (0, 1)");
42 }
43
3/4
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 36 times.
37 if (!(options.cover >= 0.0 && options.cover <= 0.9)) {
44
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("Bedform cover must be in [0, 0.9]");
45 }
46
5/6
✓ Branch 1 taken 35 times.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 35 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 35 times.
71 if (!(IsNonNegative(options.sinuosity) && IsNonNegative(options.azimuthSpread) &&
47
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 35 times.
35 std::isfinite(options.windAzimuth))) {
48 throw std::invalid_argument(
49
1/1
✓ Branch 2 taken 1 time.
1 "Bedform sinuosity and azimuth spread must be finite and not negative");
50 }
51
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 34 times.
35 if (!IsPositive(options.exposureSpacing)) {
52
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("Bedform exposureSpacing must be positive");
53 }
54
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 27 times.
34 if (options.exposure) {
55 7 ValidateExposureParams(*options.exposure);
56 }
57 33 ValidateResolutionFade(options.fade);
58 32 }
59
60 8 BedformOptions MarsLargeRipples() {
61 8 BedformOptions ripples;
62 8 ripples.wavelength = 2.0;
63 8 ripples.height = 0.2;
64 8 const double leeSlope = std::tan(30.0 * std::numbers::pi / 180.0);
65 8 ripples.leeFraction = std::numbers::pi * ripples.height / (2.0 * ripples.wavelength * leeSlope);
66 8 ripples.sinuosity = 0.6;
67 8 ripples.cover = 0.15;
68 8 ripples.fieldSize = 80.0;
69 8 return ripples;
70 }
71
72 4 BedformOptions MarsTransverseAeolianRidges() {
73 4 BedformOptions ridges;
74 4 ridges.wavelength = 25.8;
75 4 ridges.height = 1.3;
76 4 ridges.leeFraction = 0.5;
77 4 ridges.sinuosity = 0.2;
78 4 ridges.cover = 0.07;
79 4 ridges.fieldSize = 250.0;
80 4 return ridges;
81 }
82
83 33 BedformLayer::BedformLayer(BedformOptions options, Surface surface)
84 33 : FormulaLayer(surface), options_(options) {
85
1/1
✓ Branch 1 taken 25 times.
33 ValidateBedformOptions(options_);
86 25 cell_ = options_.fieldSize / (kRadiusMin + kRadiusMax);
87 25 const double meanArea = std::numbers::pi *
88 (std::pow(kRadiusMax, 3.0) - std::pow(kRadiusMin, 3.0)) /
89 (3.0 * (kRadiusMax - kRadiusMin));
90 25 density_ = std::min(1.0, -std::log1p(-options_.cover) / meanArea);
91
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 20 times.
25 if (options_.exposure) {
92
1/1
✓ Branch 2 taken 5 times.
5 exposure_.emplace(*options_.exposure, surface_);
93 }
94
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 23 times.
25 if (surface_.IsBody()) {
95
1/1
✓ Branch 1 taken 2 times.
2 cells_.emplace(surface_);
96 }
97 57 }
98
99 8410993 void BedformLayer::AddField(Point& point, RandomStream& random, const Vec2d& centre,
100 const Vec2d& offset, const Ground* ground) const {
101 8410993 const double barren = random.NextDouble();
102 8410993 const auto ox = static_cast<float>(97.0 * random.NextDouble());
103 8410993 const auto oy = static_cast<float>(97.0 * random.NextDouble());
104 8410993 const double radius = (kRadiusMin + (kRadiusMax - kRadiusMin) * random.NextDouble()) * cell_;
105 8410993 const double scale = kWavelengthMin + kWavelengthSpan * random.NextDouble();
106 const double azimuth =
107 8410993 options_.windAzimuth + options_.azimuthSpread * (2.0 * random.NextDouble() - 1.0);
108 8410993 const double phase = random.NextDouble();
109 8410993 const double dx = offset.x;
110 8410993 const double dy = offset.y;
111 const double rag = static_cast<double>(
112 8410993 PerlinNoise2(static_cast<float>(dx / (kRagRadii * radius)) + ox,
113 8410993 static_cast<float>(dy / (kRagRadii * radius)) + oy, options_.seed));
114 8410993 const double inside = 1.0 - std::hypot(dx, dy) / (radius * (1.0 + kRagged * rag));
115
2/2
✓ Branch 0 taken 7497172 times.
✓ Branch 1 taken 913821 times.
8410993 if (inside <= 0.0) {
116 7497172 return;
117 }
118
4/4
✓ Branch 0 taken 569696 times.
✓ Branch 1 taken 344125 times.
✓ Branch 2 taken 96867 times.
✓ Branch 3 taken 816954 times.
1483517 if (exposure_ &&
119
2/2
✓ Branch 3 taken 96867 times.
✓ Branch 4 taken 472829 times.
1483517 barren < exposure_->Exposure(*ground, centre.x, centre.y, options_.exposureSpacing)) {
120 96867 return;
121 }
122 816954 const double cover = Smoothstep(inside / kEdgeShare);
123 816954 const double wavelength = options_.wavelength * scale;
124 816954 const double wander = static_cast<double>(PerlinNoise2(
125 816954 static_cast<float>(dx / (kWanderWavelengths * wavelength)) + oy,
126 816954 static_cast<float>(dy / (kWanderWavelengths * wavelength)) + ox, options_.seed + 1u));
127 816954 const double along = (dx * std::cos(azimuth) + dy * std::sin(azimuth)) / wavelength +
128 816954 options_.sinuosity * wander + phase;
129 816954 const double t = along - std::floor(along);
130 816954 const double upwind = 1.0 - options_.leeFraction;
131 816954 const double profile =
132
2/2
✓ Branch 0 taken 433774 times.
✓ Branch 1 taken 383180 times.
816954 t < upwind ? 0.5 * (1.0 - std::cos(std::numbers::pi * t / upwind))
133 383180 : 0.5 * (1.0 + std::cos(std::numbers::pi * (t - upwind) / options_.leeFraction));
134 816954 point.cover += cover;
135 816954 point.relief += cover * options_.height * scale * profile;
136 }
137
138 1134671 BedformLayer::Point BedformLayer::Evaluate(double x, double y, const Ground* ground) const {
139 1134671 Point point;
140
2/2
✓ Branch 0 taken 40001 times.
✓ Branch 1 taken 1094670 times.
1134671 if (density_ <= 0.0) {
141 40001 return point;
142 }
143
2/2
✓ Branch 1 taken 552362 times.
✓ Branch 2 taken 542308 times.
1094670 if (exposure_) {
144
1/1
✓ Branch 2 taken 552360 times.
552362 RequireGround(ground, kExposureReader);
145 }
146
2/2
✓ Branch 1 taken 10612 times.
✓ Branch 2 taken 1084056 times.
1094668 if (cells_) {
147
1/1
✓ Branch 2 taken 10612 times.
10612 const CellIndex home = cells_->CellOf(x, y, cell_);
148
3/3
✓ Branch 2 taken 10612 times.
✓ Branch 9 taken 265300 times.
✓ Branch 10 taken 10612 times.
275912 for (const CellIndex& cell : cells_->Neighbours(home, cell_, 2)) {
149 265300 RandomStream random(options_.seed, kBedformSphereStream,
150 265300 static_cast<std::uint32_t>(cell.i),
151
1/1
✓ Branch 1 taken 265300 times.
265300 static_cast<std::uint32_t>(cell.j));
152
1/1
✓ Branch 2 taken 265300 times.
265300 const double chance = density_ * cells_->CellArea(cell, cell_) / (cell_ * cell_);
153
3/3
✓ Branch 1 taken 265300 times.
✓ Branch 3 taken 165111 times.
✓ Branch 4 taken 100189 times.
265300 if (random.NextDouble() >= chance) {
154 165111 continue;
155 }
156
1/1
✓ Branch 1 taken 100189 times.
100189 const double u = random.NextDouble();
157
1/1
✓ Branch 1 taken 100189 times.
100189 const double v = random.NextDouble();
158
1/1
✓ Branch 2 taken 100189 times.
100189 const Vec2d centre = cells_->SamplePoint(u, v, cell, cell_);
159
2/2
✓ Branch 1 taken 100189 times.
✓ Branch 4 taken 100189 times.
100189 AddField(point, random, centre, surface_.Offset(centre, {x, y}), ground);
160 10612 }
161 10612 return point;
162 }
163 1084056 const auto ix = static_cast<std::int64_t>(std::floor(x / cell_));
164 1084056 const auto iy = static_cast<std::int64_t>(std::floor(y / cell_));
165
2/2
✓ Branch 0 taken 3252168 times.
✓ Branch 1 taken 1084056 times.
4336224 for (std::int64_t gy = iy - 1; gy <= iy + 1; ++gy) {
166
2/2
✓ Branch 0 taken 9756504 times.
✓ Branch 1 taken 3252168 times.
13008672 for (std::int64_t gx = ix - 1; gx <= ix + 1; ++gx) {
167 9756504 RandomStream random(options_.seed, kBedformStream, static_cast<std::uint32_t>(gx),
168 static_cast<std::uint32_t>(gy),
169
1/1
✓ Branch 1 taken 9756504 times.
9756504 static_cast<std::uint32_t>((gx >> 32) ^ ((gy >> 32) << 16)));
170
3/3
✓ Branch 1 taken 9756504 times.
✓ Branch 3 taken 1445700 times.
✓ Branch 4 taken 8310804 times.
9756504 if (random.NextDouble() >= density_) {
171 1445700 continue;
172 }
173
1/1
✓ Branch 1 taken 8310804 times.
8310804 const double cx = (static_cast<double>(gx) + random.NextDouble()) * cell_;
174
1/1
✓ Branch 1 taken 8310804 times.
8310804 const double cy = (static_cast<double>(gy) + random.NextDouble()) * cell_;
175
1/1
✓ Branch 1 taken 8310804 times.
8310804 AddField(point, random, {cx, cy}, {x - cx, y - cy}, ground);
176 }
177 }
178 1084056 return point;
179 }
180
181 203166 double BedformLayer::CoverAt(double x, double y, const Ground* ground) const {
182
1/1
✓ Branch 1 taken 203164 times.
203166 return std::min(1.0, Evaluate(x, y, ground).cover);
183 }
184
185 1194724 float BedformLayer::HeightAt(float height, double x, double y, double spacing,
186 const Ground* ground) const {
187
1/1
✓ Branch 1 taken 1194724 times.
1194724 const double weight = ResolutionWeight(options_.wavelength, spacing, options_.fade);
188
2/2
✓ Branch 0 taken 263219 times.
✓ Branch 1 taken 931505 times.
1194724 if (weight <= 0.0) {
189 263219 return height;
190 }
191
1/1
✓ Branch 1 taken 931505 times.
931505 const Point point = Evaluate(x, y, ground);
192
2/2
✓ Branch 0 taken 345556 times.
✓ Branch 1 taken 585949 times.
931505 if (point.cover <= 0.0) {
193 345556 return height;
194 }
195 1171898 return static_cast<float>(static_cast<double>(height) +
196 585949 weight * point.relief / std::max(1.0, point.cover));
197 }
198
199 40466 void BedformLayer::CheckContext(const LayerContext& context) const {
200
2/2
✓ Branch 1 taken 20401 times.
✓ Branch 2 taken 20065 times.
40466 if (exposure_) {
201
1/1
✓ Branch 2 taken 20400 times.
20401 RequireGround(context.ground, kExposureReader);
202 }
203 40465 }
204
205 } // namespace terraingen
206