TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
351 / 365
96.2%
Branches:
190 / 194
97.9%
Functions:
33 / 34
97.1%

python/src/layers.cpp
Line Branch Exec Source
1 #include "bindings.h"
2 #include "common.h"
3 #include "objects.h"
4 #include "trampolines.h"
5
6 #include "terraingen/layers/bedforms.h"
7 #include "terraingen/layers/density_map.h"
8 #include "terraingen/layers/detail.h"
9 #include "terraingen/layers/dunes.h"
10 #include "terraingen/layers/exposure.h"
11 #include "terraingen/layers/fade.h"
12 #include "terraingen/layers/layer.h"
13 #include "terraingen/layers/material.h"
14 #include "terraingen/layers/noise_relief.h"
15 #include "terraingen/layers/pavement.h"
16 #include "terraingen/layers/region.h"
17 #include "terraingen/layers/roughness.h"
18 #include "terraingen/layers/sand_fill.h"
19 #include "terraingen/layers/strata.h"
20 #include "terraingen/layers/transforms.h"
21
22 #include <stdexcept>
23 #include <utility>
24 #include <vector>
25
26 namespace terraingen::python {
27
28 namespace {
29
30 /// @brief A layer written in Python: `apply_at(height, x, y, context)` changes one height,
31 /// `apply(heights, context)` a whole raster at once (faster with NumPy); either is enough.
32 class PyLayer : public Layer {
33 public:
34 NB_TRAMPOLINE(Layer);
35
36 protected:
37 11 void ApplySamples(SampleBlock& samples, const LayerContext& context) const override {
38 11 Raster<float>* grid = samples.Grid();
39
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 3 times.
19 if (grid != nullptr &&
40
5/5
✓ Branch 1 taken 8 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 7 times.
19 CallOverrideIgnoringResult(nb_trampoline, "apply", ByReference<Raster<float>>{*grid},
41 context)) {
42 4 return;
43 }
44 7 const bool pointwise = Overrides(nb_trampoline, "apply_at");
45
6/6
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 6 times.
7 if (!pointwise && !Overrides(nb_trampoline, "apply")) {
46
1/1
✓ Branch 2 taken 1 time.
1 throw std::logic_error("A Python Layer must define apply_at or apply");
47 }
48
2/2
✓ Branch 1 taken 136326 times.
✓ Branch 2 taken 6 times.
136332 for (std::size_t i = 0; i < samples.Size(); ++i) {
49
2/3
✓ Branch 1 taken 136326 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 136326 times.
136326 if (samples.IsNoData(i)) {
50 ✗ continue;
51 }
52
1/1
✓ Branch 1 taken 136326 times.
136326 const Vec2d p = samples.Position(i);
53 136326 float& height = samples.Height(i);
54
3/3
✓ Branch 0 taken 136325 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 136325 times.
272652 height = pointwise ? *CallOverride<float>(nb_trampoline, "apply_at", height, p.x, p.y,
55 136325 context)
56
1/1
✓ Branch 1 taken 1 time.
1 : ApplyToOneCell(height, p, context);
57 }
58 }
59
60 private:
61 /// `apply` on a raster of one cell of side `context.spacing` centred at `p`.
62 1 float ApplyToOneCell(float height, const Vec2d& p, const LayerContext& context) const {
63
1/1
✓ Branch 1 taken 1 time.
1 Raster<float> cell(1, 1, height);
64 1 cell.SetTransform(GeoTransform::NorthUp(p.x - 0.5 * context.spacing,
65 1 p.y + 0.5 * context.spacing, context.spacing));
66
1/1
✓ Branch 1 taken 1 time.
1 CallOverrideIgnoringResult(nb_trampoline, "apply", ByReference<Raster<float>>{cell},
67 context);
68 2 return cell(0, 0);
69 1 }
70 };
71
72 44 void BindLayerBase(nb::module_& m) {
73 44 nb::class_<LayerContext>(m, "LayerContext",
74 "How a layer is evaluated: the sample spacing (m) and threads.")
75 44 .def(
76 "__init__",
77 45 [](LayerContext* self, double spacing, unsigned threads) {
78 1 new (self) LayerContext{spacing, threads, nullptr};
79 1 },
80
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
132 "spacing"_a = 0.0, "threads"_a = 1)
81 44 .def_ro("spacing", &LayerContext::spacing)
82 .def_ro("threads", &LayerContext::threads);
83 44 nb::class_<Layer, PyLayer>(
84 m, "Layer",
85 "Relief added to a terrain. Subclass it and implement `apply_at(height, x, y, context)` "
86 "or `apply(heights, context)` (a Raster changed in place), for a layer of your own.")
87 44 .def(nb::init<>())
88 44 .def(
89 "apply",
90 ✗ [](const Layer& layer, Raster<float>& heights, const LayerContext& context) {
91 ✗ layer.Apply(heights, context);
92 ✗ },
93
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 "heights"_a, "context"_a)
94
4/4
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
88 .def("apply_at", &Layer::ApplyAt, "height"_a, "x"_a, "y"_a, "context"_a);
95 44 }
96
97 44 void BindFades(nb::module_& m) {
98 88 nb::enum_<BlendShape>(m, "BlendShape", "How a weight falls from 1 to 0.")
99 44 .value("LINEAR", BlendShape::Linear)
100 44 .value("SMOOTHSTEP", BlendShape::Smoothstep)
101 44 .value("SMOOTHERSTEP", BlendShape::Smootherstep)
102 .value("GAUSSIAN", BlendShape::Gaussian);
103 auto fade = nb::class_<ResolutionFade>(
104 m, "ResolutionFade",
105 "How features too small to resolve fade out, by how many samples span them: absent up "
106 44 "to `start_samples`, whole from `end_samples`.");
107
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<ResolutionFade>(fade);
108 44 fade.def_rw("start_samples", &ResolutionFade::startSamples)
109 44 .def_rw("end_samples", &ResolutionFade::endSamples)
110 .def_rw("shape", &ResolutionFade::shape);
111
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_resolution_fade", &ValidateResolutionFade, "fade"_a, kValidateDoc);
112
5/5
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
✓ Branch 13 taken 44 times.
44 m.def("falloff", &Falloff, "x"_a, "start"_a, "end"_a, "shape"_a,
113 "The weight at `x` of a falloff from 1 at `start` to 0 at `end` along `shape`.");
114
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("smoothstep", &Smoothstep, "t"_a, "The cubic smoothstep of `t` clamped to [0, 1].");
115
4/4
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
44 m.def("resolution_weight", &ResolutionWeight, "diameter"_a, "spacing"_a, "fade"_a,
116 "The weight of a feature `diameter` across sampled every `spacing`.");
117 44 }
118
119 44 void BindTransforms(nb::module_& m) {
120 44 nb::class_<NoiseRelief, Layer>(m, "NoiseRelief",
121 "A stack of 1 to 8 noise layers added to the terrain, measured "
122 "from `origin`.")
123 .def(
124 "__init__",
125 118 [](NoiseRelief* self, std::vector<NoiseLayer> layers, const Vec2d& origin,
126 const Surface& surface) {
127
2/3
✓ Branch 5 taken 73 times.
✓ Branch 11 taken 1 time.
✗ Branch 12 not taken.
76 new (self) NoiseRelief(SyntheticTerrainParams{std::move(layers)}, origin, surface);
128 73 },
129
3/3
✓ Branch 2 taken 44 times.
✓ Branch 5 taken 44 times.
✓ Branch 8 taken 44 times.
176 "layers"_a, "origin"_a = Vec2d{}, "surface"_a = Surface{},
130 "On a sphere the noise is 3D at each point's position, seamless everywhere, and "
131 "`origin` is unused.");
132 44 nb::class_<ScaleLayer, Layer>(m, "ScaleLayer", "height * scale + offset.")
133
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
132 .def(nb::init<double, double>(), "scale"_a, "offset"_a = 0.0);
134 44 nb::class_<DetailLayer, Layer>(
135 m, "DetailLayer",
136 "Adds `weight` times the detail of `full` above `smooth` at each sample, such as a DEM's "
137 "detail above its own low-pass.")
138 .def(
139 "__init__",
140 45 [](DetailLayer* self, std::shared_ptr<Source> full, std::shared_ptr<Source> smooth,
141 double weight, const Surface& surface) {
142
1/3
✓ Branch 7 taken 1 time.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
1 new (self) DetailLayer(std::move(full), std::move(smooth), weight, surface);
143 1 },
144
4/4
✓ Branch 2 taken 44 times.
✓ Branch 5 taken 44 times.
✓ Branch 8 taken 44 times.
✓ Branch 11 taken 44 times.
176 "full"_a, "smooth"_a, "weight"_a = 1.0, "surface"_a = Surface{});
145 88 nb::class_<ClampLayer, Layer>(m, "ClampLayer", "Heights held between `min` and `max`.")
146
1/1
✓ Branch 1 taken 44 times.
88 .def(nb::init<std::optional<double>, std::optional<double>>(), "min"_a = nb::none(),
147
1/1
✓ Branch 1 taken 44 times.
88 "max"_a = nb::none());
148 ✗ nb::class_<FunctionLayer, Layer>(
149 m, "FunctionLayer",
150 "A layer from a function `fn(x, y, spacing, height)` returning the new height; simpler "
151 "than subclassing Layer for point-wise changes. The function is called for every sample "
152 "from every evaluation thread, and Python calls take turns on the GIL, so evaluate "
153 "terrains with it on one thread (`threads=1`).")
154
2/2
✓ Branch 2 taken 44 times.
✓ Branch 5 taken 44 times.
132 .def(nb::init<HeightFunction, Surface>(), "function"_a, "surface"_a = Surface{});
155 44 }
156
157 44 void BindRegions(nb::module_& m) {
158 ✗ nb::class_<RegionShape>(m, "RegionShape", "A rectangle or circle in surface coordinates.")
159 44 .def_static(
160 "rectangle",
161 ✗ [](double minX, double minY, double maxX, double maxY) {
162 3 return RegionShape::Rectangle({minX, minY, maxX, maxY});
163 },
164
4/4
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
44 "min_x"_a, "min_y"_a, "max_x"_a, "max_y"_a)
165 44 .def_static(
166 "circle",
167 4 [](double x, double y, double radius) { return RegionShape::Circle({x, y}, radius); },
168
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
44 "x"_a, "y"_a, "radius"_a)
169
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("distance_outside", &RegionShape::DistanceOutside, "x"_a, "y"_a,
170
1/1
✓ Branch 2 taken 44 times.
88 "surface"_a = Surface{});
171
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_region_shape", &ValidateRegionShape, "shape"_a, kValidateDoc);
172 ✗ nb::class_<RegionLayer, Layer>(
173 m, "RegionLayer",
174 "`layer` applied only inside `shape`, faded out over `feather` metres outside it.")
175 ✗ .def(nb::init<std::shared_ptr<Layer>, RegionShape, double, BlendShape, Surface>(),
176
4/4
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
176 "layer"_a, "shape"_a, "feather"_a = 0.0, "blend"_a = BlendShape::Smoothstep,
177
1/1
✓ Branch 2 taken 44 times.
88 "surface"_a = Surface{})
178
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("weight", &RegionLayer::Weight, "x"_a, "y"_a)
179 88 .def_prop_ro("inner", &RegionLayer::Inner, nb::rv_policy::reference_internal,
180 "The layer applied inside the region.");
181 44 }
182
183 44 void BindRoughness(nb::module_& m) {
184 auto options = nb::class_<RoughnessOptions>(
185 m, "RoughnessOptions",
186 "Fine roughness of `rms_slope` (m/m) over `baseline` metres with Hurst exponent `hurst`, "
187 44 "between `min_wavelength` and `max_wavelength` metres.");
188
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<RoughnessOptions>(options);
189 44 options.def_rw("rms_slope", &RoughnessOptions::rmsSlope)
190 44 .def_rw("baseline", &RoughnessOptions::baseline)
191 44 .def_rw("hurst", &RoughnessOptions::hurst)
192 44 .def_rw("max_wavelength", &RoughnessOptions::maxWavelength)
193 44 .def_rw("min_wavelength", &RoughnessOptions::minWavelength)
194 44 .def_rw("seed", &RoughnessOptions::seed)
195 .def_rw("fade", &RoughnessOptions::fade);
196
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_roughness_options", &ValidateRoughnessOptions, "options"_a, kValidateDoc);
197 ✗ nb::class_<RoughnessLayer, Layer>(m, "RoughnessLayer", "Fine roughness of a given RMS slope.")
198
1/1
✓ Branch 1 taken 44 times.
88 .def(nb::init<RoughnessOptions, Surface>(), "options"_a = RoughnessOptions{},
199
1/1
✓ Branch 2 taken 44 times.
88 "surface"_a = Surface{})
200
1/1
✓ Branch 1 taken 44 times.
44 .def("rms_slope", &RoughnessLayer::RmsSlope, "baseline"_a)
201 88 .def("wavelengths", &RoughnessLayer::Wavelengths,
202 "The wavelengths (m) of the roughness octaves.");
203
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("perlin_structure_function", &PerlinStructureFunction, "r"_a,
204 "The structure function of 2D Perlin noise at lag `r` lattice units.");
205
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("perlin3_structure_function", &Perlin3StructureFunction, "r"_a,
206 "The structure function of 3D Perlin noise at lag `r` lattice units.");
207 44 }
208
209 44 void BindExposureParams(nb::module_& m) {
210 auto params = nb::class_<ExposureParams>(
211 m, "ExposureParams",
212 "How exposed the ground is (crests and steep slopes shed regolith): rises and slopes "
213 44 "for soil and boulder, read over `reach` metres.");
214
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<ExposureParams>(params);
215 44 params.def_rw("reach", &ExposureParams::reach)
216 44 .def_rw("rise_soil", &ExposureParams::riseSoil)
217 44 .def_rw("rise_boulder", &ExposureParams::riseBoulder)
218 44 .def_rw("slope_soil", &ExposureParams::slopeSoil)
219 44 .def_rw("slope_boulder", &ExposureParams::slopeBoulder)
220 44 .def_rw("floor_cover", &ExposureParams::floorCover)
221 44 .def_rw("floor_patch", &ExposureParams::floorPatch)
222 .def_rw("seed", &ExposureParams::seed);
223
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_exposure_params", &ValidateExposureParams, "params"_a, kValidateDoc);
224 44 }
225
226 44 void BindExposureField(nb::module_& m) {
227 ✗ nb::class_<ExposureField>(
228 m, "ExposureField",
229 "How exposed the ground is, from 0 (buried under regolith) to 1 (bare boulder): crests and "
230 "steep slopes shed regolith, read over `params.reach` metres.")
231
1/1
✓ Branch 1 taken 44 times.
88 .def(nb::init<ExposureParams, Surface>(), "params"_a = ExposureParams{},
232
1/1
✓ Branch 2 taken 44 times.
88 "surface"_a = Surface{})
233 44 .def(
234 "fill",
235 ✗ [](const ExposureField& field, const PyTerrain& terrain, std::size_t layers,
236 const RasterGrid& grid, double spacing, unsigned threads) {
237 5 Raster<float> exposure = grid.MakeRaster();
238
1/1
✓ Branch 1 taken 5 times.
5 WithGround(&terrain, layers, [&](const Ground* ground) {
239 5 field.Fill(exposure, *ground, spacing, threads);
240 5 });
241 5 return exposure;
242 ✗ },
243
5/5
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
✓ Branch 13 taken 44 times.
88 "terrain"_a, "layers"_a, "grid"_a, "spacing"_a, "threads"_a = 0,
244 44 nb::call_guard<nb::gil_scoped_release>(),
245 "The exposure over `grid` of the ground made by `terrain`'s source and its first "
246 "`layers` layers, sampled every `spacing` metres.")
247 .def(
248 "exposure",
249 45 [](const ExposureField& field, const PyTerrain& terrain, std::size_t layers, double x,
250 double y, double spacing) {
251 1 return WithGround(&terrain, layers, [&](const Ground* ground) {
252 1 return field.Exposure(*ground, x, y, spacing);
253 1 });
254 },
255
5/5
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
✓ Branch 13 taken 44 times.
88 "terrain"_a, "layers"_a, "x"_a, "y"_a, "spacing"_a);
256 44 }
257
258 44 void BindBedformOptions(nb::module_& m) {
259 auto options = nb::class_<BedformOptions>(
260 m, "BedformOptions",
261 "Ripples and dunes: `wavelength` and `height` (m), crests across `wind_azimuth` (degrees "
262 44 "from north), covering `cover` of the ground in fields `field_size` metres across.");
263
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<BedformOptions>(options);
264 44 options.def_rw("wavelength", &BedformOptions::wavelength)
265 44 .def_rw("height", &BedformOptions::height)
266 44 .def_rw("wind_azimuth", &BedformOptions::windAzimuth)
267 44 .def_rw("azimuth_spread", &BedformOptions::azimuthSpread)
268 44 .def_rw("lee_fraction", &BedformOptions::leeFraction)
269 44 .def_rw("sinuosity", &BedformOptions::sinuosity)
270 44 .def_rw("cover", &BedformOptions::cover)
271 44 .def_rw("field_size", &BedformOptions::fieldSize)
272 44 .def_rw("seed", &BedformOptions::seed)
273 44 .def_rw("exposure", &BedformOptions::exposure)
274 44 .def_rw("exposure_spacing", &BedformOptions::exposureSpacing)
275 .def_rw("fade", &BedformOptions::fade);
276
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_bedform_options", &ValidateBedformOptions, "options"_a, kValidateDoc);
277
1/1
✓ Branch 1 taken 44 times.
44 m.def("mars_large_ripples", &MarsLargeRipples, "Martian large ripples.");
278
1/1
✓ Branch 1 taken 44 times.
44 m.def("mars_transverse_aeolian_ridges", &MarsTransverseAeolianRidges,
279 "Martian transverse aeolian ridges.");
280 44 }
281
282 44 void BindBedforms(nb::module_& m) {
283
1/1
✓ Branch 1 taken 44 times.
44 BindBedformOptions(m);
284 44 auto bedforms = nb::class_<BedformLayer, Layer>(m, "BedformLayer", "Aeolian bedforms.");
285
2/2
✓ Branch 2 taken 44 times.
✓ Branch 5 taken 44 times.
132 bedforms.def(nb::init<BedformOptions, Surface>(), "options"_a, "surface"_a = Surface{})
286 .def(
287 "cover_at",
288 4302 [](const BedformLayer& layer, double x, double y, const PyTerrain* terrain,
289 std::size_t layers) {
290 4258 return WithGround(terrain, layers, [&](const Ground* ground) {
291 4258 return layer.CoverAt(x, y, ground);
292 4257 });
293 },
294
4/4
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
176 "x"_a, "y"_a, "terrain"_a = nb::none(), "layers"_a = 0,
295 "The share of (x, y) covered by bedforms; bedforms that read the exposure field need "
296 "the ground beneath them: `terrain`'s source and its first `layers` layers.");
297
1/1
✓ Branch 1 taken 44 times.
44 BindMaterialCover<BedformLayer>(bedforms);
298 44 }
299
300 44 void BindMaterialClasses(nb::module_& m) {
301 88 nb::enum_<MaterialClass>(m, "MaterialClass", "What the surface is made of at a point.")
302 44 .value("SOIL", MaterialClass::Soil)
303 44 .value("SAND", MaterialClass::Sand)
304 44 .value("DUST", MaterialClass::Dust)
305 44 .value("BEDROCK", MaterialClass::Bedrock)
306 .value("ROCK", MaterialClass::Rock);
307
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.attr("MATERIAL_CLASSES") = kMaterialClasses;
308 44 }
309
310 44 void BindSandFill(nb::module_& m) {
311 auto options = nb::class_<SandFillOptions>(
312 m, "SandFillOptions",
313 "Sand pooled in lows: `fill` of the depth below the surroundings `reach` metres around, up "
314 44 "to `max_thickness` metres.");
315
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<SandFillOptions>(options);
316 44 options.def_rw("reach", &SandFillOptions::reach)
317 44 .def_rw("fill", &SandFillOptions::fill)
318 44 .def_rw("max_thickness", &SandFillOptions::maxThickness)
319 .def_rw("cover_thickness", &SandFillOptions::coverThickness);
320
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_sand_fill_options", &ValidateSandFillOptions, "options"_a, kValidateDoc);
321 auto layer = nb::class_<SandFillLayer, Layer>(m, "SandFillLayer",
322 44 "Sand pooled in the lows of the ground beneath.");
323 layer
324
1/1
✓ Branch 1 taken 44 times.
88 .def(nb::init<SandFillOptions, Surface>(), "options"_a = SandFillOptions{},
325
1/1
✓ Branch 2 taken 44 times.
88 "surface"_a = Surface{})
326 44 .def(
327 "thickness_at",
328 45 [](const SandFillLayer& fill, double x, double y, double spacing,
329 const PyTerrain& terrain, std::size_t layers) {
330 1 return WithGround(&terrain, layers, [&](const Ground* ground) {
331 1 return fill.ThicknessAt(*ground, x, y, spacing);
332 1 });
333 },
334
5/5
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
✓ Branch 13 taken 44 times.
44 "x"_a, "y"_a, "spacing"_a, "terrain"_a, "layers"_a,
335 "The sand thickness at (x, y) in metres over `terrain`'s source and its first "
336 "`layers` layers.")
337 88 .def_prop_ro("options", &SandFillLayer::Options);
338
1/1
✓ Branch 1 taken 44 times.
44 BindMaterialCover<SandFillLayer>(layer);
339 44 }
340
341 44 void BindPavement(nb::module_& m) {
342 auto options = nb::class_<PavementOptions>(
343 m, "PavementOptions",
344 "Fractured bedrock: Voronoi plates `plate_size` metres across, each tilted and stepped, "
345 "parted by cracks (or raised ridges) `crack_width` metres wide and `crack_depth` deep; "
346 44 "angles in radians.");
347
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<PavementOptions>(options);
348 44 options.def_rw("plate_size", &PavementOptions::plateSize)
349 44 .def_rw("jitter", &PavementOptions::jitter)
350 44 .def_rw("warp", &PavementOptions::warp)
351 44 .def_rw("crack_width", &PavementOptions::crackWidth)
352 44 .def_rw("crack_depth", &PavementOptions::crackDepth)
353 44 .def_rw("raised", &PavementOptions::raised)
354 44 .def_rw("edge_rounding", &PavementOptions::edgeRounding)
355 44 .def_rw("max_tilt", &PavementOptions::maxTilt)
356 44 .def_rw("max_step", &PavementOptions::maxStep)
357 44 .def_rw("octaves", &PavementOptions::octaves)
358 44 .def_rw("lacunarity", &PavementOptions::lacunarity)
359 44 .def_rw("gain", &PavementOptions::gain)
360 44 .def_rw("seed", &PavementOptions::seed)
361 44 .def_rw("exposure", &PavementOptions::exposure)
362 44 .def_rw("exposure_spacing", &PavementOptions::exposureSpacing)
363 44 .def_rw("anchor", &PavementOptions::anchor)
364 .def_rw("fade", &PavementOptions::fade);
365
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_pavement_options", &ValidatePavementOptions, "options"_a, kValidateDoc);
366
1/1
✓ Branch 1 taken 44 times.
44 m.def("mars_desiccation_ridges", &MarsDesiccationRidges,
367 "The desiccation ridges of Gale crater's Old Soaker slab (Stein et al. 2018).");
368 44 auto layer = nb::class_<PavementLayer, Layer>(m, "PavementLayer", "Fractured bedrock plates.");
369
2/2
✓ Branch 2 taken 44 times.
✓ Branch 5 taken 44 times.
132 layer.def(nb::init<PavementOptions, Surface>(), "options"_a, "surface"_a = Surface{})
370
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("relief_at", &PavementLayer::ReliefAt, "x"_a, "y"_a,
371 "The pavement's relief at (x, y) in metres, before exposure and fading.")
372
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("edge_distance_at", &PavementLayer::EdgeDistanceAt, "x"_a, "y"_a,
373 "The distance from (x, y) to the nearest plate edge, in metres.")
374 88 .def_prop_ro("options", &PavementLayer::Options);
375
1/1
✓ Branch 1 taken 44 times.
44 BindMaterialCover<PavementLayer>(layer);
376 44 }
377
378 44 void BindStrata(nb::module_& m) {
379 auto options = nb::class_<StrataOptions>(
380 m, "StrataOptions",
381 "Layered rock: beds `bed_thickness` metres thick, dipping `dip` toward `dip_azimuth`, each "
382 44 "cut into a flat bench and a cliff by `sharpness`; angles in radians.");
383
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<StrataOptions>(options);
384 44 options.def_rw("bed_thickness", &StrataOptions::bedThickness)
385 44 .def_rw("thickness_variation", &StrataOptions::thicknessVariation)
386 44 .def_rw("dip", &StrataOptions::dip)
387 44 .def_rw("dip_azimuth", &StrataOptions::dipAzimuth)
388 44 .def_rw("sharpness", &StrataOptions::sharpness)
389 44 .def_rw("strength", &StrataOptions::strength)
390 44 .def_rw("cliff_share", &StrataOptions::cliffShare)
391 44 .def_rw("seed", &StrataOptions::seed)
392 44 .def_rw("anchor", &StrataOptions::anchor)
393 44 .def_rw("hard_share", &StrataOptions::hardShare)
394 .def_rw("soft_sharpness", &StrataOptions::softSharpness);
395
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_strata_options", &ValidateStrataOptions, "options"_a, kValidateDoc);
396 auto layer = nb::class_<StrataLayer, Layer>(m, "StrataLayer",
397 44 "Layered rock terraced into benches and cliffs.");
398
2/2
✓ Branch 2 taken 44 times.
✓ Branch 5 taken 44 times.
132 layer.def(nb::init<StrataOptions, Surface>(), "options"_a, "surface"_a = Surface{})
399
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
44 .def("terraced_at", &StrataLayer::TerracedAt, "height"_a, "x"_a, "y"_a,
400 "The terraced height of ground `height` metres high at (x, y).")
401
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
44 .def("bed_position_at", &StrataLayer::BedPositionAt, "height"_a, "x"_a, "y"_a,
402 "How far through its bed ground `height` metres high at (x, y) lies, in [0, 1].")
403 88 .def_prop_ro("options", &StrataLayer::Options);
404
1/1
✓ Branch 1 taken 44 times.
44 BindMaterialCover<StrataLayer>(layer);
405 44 }
406
407 44 void BindDuneOptions(nb::module_& m) {
408 88 nb::enum_<DuneShape>(m, "DuneShape", "The kind of dune a DuneLayer builds.")
409 44 .value("BARCHAN", DuneShape::Barchan)
410 44 .value("DOME", DuneShape::Dome)
411 44 .value("TRANSVERSE", DuneShape::Transverse)
412 44 .value("BARCHANOID", DuneShape::Barchanoid)
413 .value("LINEAR", DuneShape::Linear);
414 auto ripples = nb::class_<DuneRipples>(
415 m, "DuneRipples",
416 "Ripples on dunes: `wavelength` and `height` (m) on flat sand, turned and tightened by the "
417 44 "dune's slope by `slope_coupling`; a wavelength of 0 gives none.");
418
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<DuneRipples>(ripples);
419 44 ripples.def_rw("wavelength", &DuneRipples::wavelength)
420 44 .def_rw("height", &DuneRipples::height)
421 44 .def_rw("lee_fraction", &DuneRipples::leeFraction)
422 44 .def_rw("sinuosity", &DuneRipples::sinuosity)
423 44 .def_rw("slope_coupling", &DuneRipples::slopeCoupling)
424 44 .def_rw("interdune_cover", &DuneRipples::interduneCover)
425 44 .def_rw("defect_density", &DuneRipples::defectDensity)
426 44 .def_rw("flank_straightening", &DuneRipples::flankStraightening)
427 .def_rw("fade", &DuneRipples::fade);
428 auto flows = nb::class_<DuneGrainflows>(
429 m, "DuneGrainflows",
430 "Grainflows on slip faces: scarps and lobes with sizes (m) uniform between their bounds, "
431 44 "`thickness` metres deep and high, in `cover` of the lattice cells; 0 cover gives none.");
432
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<DuneGrainflows>(flows);
433 44 flows.def_rw("cover", &DuneGrainflows::cover)
434 44 .def_rw("min_scarp_width", &DuneGrainflows::minScarpWidth)
435 44 .def_rw("max_scarp_width", &DuneGrainflows::maxScarpWidth)
436 44 .def_rw("min_scarp_length", &DuneGrainflows::minScarpLength)
437 44 .def_rw("max_scarp_length", &DuneGrainflows::maxScarpLength)
438 44 .def_rw("min_lobe_width", &DuneGrainflows::minLobeWidth)
439 44 .def_rw("max_lobe_width", &DuneGrainflows::maxLobeWidth)
440 44 .def_rw("min_lobe_length", &DuneGrainflows::minLobeLength)
441 44 .def_rw("max_lobe_length", &DuneGrainflows::maxLobeLength)
442 44 .def_rw("thickness", &DuneGrainflows::thickness)
443 .def_rw("fade", &DuneGrainflows::fade);
444
1/1
✓ Branch 1 taken 44 times.
44 m.def("mars_dune_grainflows", &MarsDuneGrainflows,
445 "The grainflows of Namib Dune's lee slope (Ewing et al. 2017).");
446 auto options = nb::class_<DuneOptions>(
447 m, "DuneOptions",
448 "Dunes: barchans and domes of `width` (m) in cells `spacing` metres apart, or crest-line "
449 "dunes `wavelength` metres apart and `height` metres high; angles in radians, the wind "
450 44 "counter-clockwise from +x.");
451
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<DuneOptions>(options);
452 44 options.def_rw("shape", &DuneOptions::shape)
453 44 .def_rw("wind_azimuth", &DuneOptions::windAzimuth)
454 44 .def_rw("azimuth_spread", &DuneOptions::azimuthSpread)
455 44 .def_rw("slip_face_angle", &DuneOptions::slipFaceAngle)
456 44 .def_rw("stoss_slope", &DuneOptions::stossSlope)
457 44 .def_rw("stoss_exponent", &DuneOptions::stossExponent)
458 44 .def_rw("dome_exponent", &DuneOptions::domeExponent)
459 44 .def_rw("spacing", &DuneOptions::spacing)
460 44 .def_rw("density", &DuneOptions::density)
461 44 .def_rw("width", &DuneOptions::width)
462 44 .def_rw("width_spread", &DuneOptions::widthSpread)
463 44 .def_rw("min_width", &DuneOptions::minWidth)
464 44 .def_rw("height_ratio", &DuneOptions::heightRatio)
465 44 .def_rw("horn_length", &DuneOptions::hornLength)
466 44 .def_rw("dome_length", &DuneOptions::domeLength)
467 44 .def_rw("wavelength", &DuneOptions::wavelength)
468 44 .def_rw("height", &DuneOptions::height)
469 44 .def_rw("sinuosity", &DuneOptions::sinuosity)
470 44 .def_rw("defects", &DuneOptions::defects)
471 44 .def_rw("defect_density", &DuneOptions::defectDensity)
472 44 .def_rw("flank_slope", &DuneOptions::flankSlope)
473 44 .def_rw("seed", &DuneOptions::seed)
474 44 .def_rw("anchor", &DuneOptions::anchor)
475 44 .def_rw("fade", &DuneOptions::fade)
476 44 .def_rw("ripples", &DuneOptions::ripples)
477 .def_rw("grainflows", &DuneOptions::grainflows);
478
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_dune_options", &ValidateDuneOptions, "options"_a, kValidateDoc);
479
1/1
✓ Branch 1 taken 44 times.
44 m.def("mars_dune_ripples", &MarsDuneRipples, "Martian large wind ripples to lay on dunes.");
480
1/1
✓ Branch 1 taken 44 times.
44 m.def("mars_barchans", &MarsBarchans, "Martian barchans with large ripples.");
481
1/1
✓ Branch 1 taken 44 times.
44 m.def("mars_transverse_dunes", &MarsTransverseDunes,
482 "The transverse dunes of Mars's north polar erg, with large ripples.");
483 44 }
484
485 44 void BindDunes(nb::module_& m) {
486
1/1
✓ Branch 1 taken 44 times.
44 BindDuneOptions(m);
487 auto dunes =
488 44 nb::class_<DuneLayer, Layer>(m, "DuneLayer", "Wind-blown dunes, with ripples on them.");
489
2/2
✓ Branch 2 taken 44 times.
✓ Branch 5 taken 44 times.
132 dunes.def(nb::init<DuneOptions, Surface>(), "options"_a, "surface"_a = Surface{})
490
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("dune_height_at", &DuneLayer::DuneHeightAt, "x"_a, "y"_a,
491 "The dunes' height at (x, y) in metres, without ripples.")
492 88 .def_prop_ro("options", &DuneLayer::Options);
493
1/1
✓ Branch 1 taken 44 times.
44 BindMaterialCover<DuneLayer>(dunes);
494 44 }
495
496 } // namespace
497
498 44 void BindDensityMaps(nb::module_& m) {
499 88 nb::enum_<DensityMeasure>(m, "DensityMeasure", "What a density map's values measure.")
500 44 .value("SCALE", DensityMeasure::Scale)
501 44 .value("COUNT", DensityMeasure::Count)
502 .value("AREAL_FRACTION", DensityMeasure::ArealFraction);
503 auto map = nb::class_<DensityMap>(
504 m, "DensityMap",
505 "A map of a crater or boulder population that sets a layer's density point by point: "
506 "its `field` measures (`measure`) a scale, a count per `area_unit` square metres or an "
507 "areal fraction of features from `min_diameter` to `max_diameter` metres. With `total` it "
508 "counts every feature of its kind, and the secondaries thrown from a crater layer and the "
509 44 "boulders a layer throws out around craters are taken out of it first.");
510
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<DensityMap>(map);
511 44 map.def_rw("field", &DensityMap::field)
512 44 .def_rw("measure", &DensityMap::measure)
513 44 .def_rw("min_diameter", &DensityMap::minDiameter)
514 44 .def_rw("max_diameter", &DensityMap::maxDiameter)
515 44 .def_rw("area_unit", &DensityMap::areaUnit)
516 .def_rw("total", &DensityMap::total);
517
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_density_map", &ValidateDensityMap, "map"_a,
518 "Raise ValueError if the map is unusable.");
519
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
44 m.def("map_measure", &MapMeasure, "map"_a, "distribution"_a,
520 "What `map` measures of `distribution` as it is.");
521 ✗ nb::class_<DensityScale>(m, "DensityScale",
522 "The local scale a density map gives a size distribution.")
523
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
88 .def(nb::init<DensityMap, const SizeFrequencyDistribution&>(), "map"_a, "distribution"_a)
524
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("at", &DensityScale::At, "x"_a, "y"_a)
525
1/1
✓ Branch 1 taken 44 times.
44 .def("scale", &DensityScale::Scale, "value"_a)
526 44 .def_prop_ro("measure", &DensityScale::Measure)
527 88 .def_prop_ro("max", &DensityScale::Max);
528 44 }
529
530 44 void BindSizeSlopeMaps(nb::module_& m) {
531 88 nb::enum_<SizeSlopeMeasure>(m, "SizeSlopeMeasure", "What a size slope map's values measure.")
532 44 .value("EXPONENT", SizeSlopeMeasure::Exponent)
533 .value("MEAN_DIAMETER", SizeSlopeMeasure::MeanDiameter);
534 auto map = nb::class_<SizeSlopeMap>(
535 m, "SizeSlopeMap",
536 "A map of how steeply feature counts fall with size: its `field` gives the cumulative "
537 "exponent alpha, or the mean diameter from `reference_diameter` up, and the sizes become "
538 "a local power law through their count at `reference_diameter`, alpha clamped to "
539 44 "[`min_exponent`, `max_exponent`].");
540
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<SizeSlopeMap>(map);
541 44 map.def_rw("field", &SizeSlopeMap::field)
542 44 .def_rw("measure", &SizeSlopeMap::measure)
543 44 .def_rw("reference_diameter", &SizeSlopeMap::referenceDiameter)
544 44 .def_rw("min_exponent", &SizeSlopeMap::minExponent)
545 .def_rw("max_exponent", &SizeSlopeMap::maxExponent);
546
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_size_slope_map", &ValidateSizeSlopeMap, "map"_a,
547 "Raise ValueError if the map is unusable.");
548
4/4
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
44 m.def("size_slope_at", &SizeSlopeAt, "map"_a, "x"_a, "y"_a,
549 "The exponent the map gives at (x, y), clamped; None where it has no value.");
550 44 }
551
552 44 void BindLayers(nb::module_& m) {
553 44 BindDensityMaps(m);
554 44 BindSizeSlopeMaps(m);
555 44 BindLayerBase(m);
556 44 BindFades(m);
557 44 BindTransforms(m);
558 44 BindRegions(m);
559 44 BindRoughness(m);
560 44 BindExposureParams(m);
561 44 BindExposureField(m);
562 44 BindBedforms(m);
563 44 BindDunes(m);
564 44 BindMaterialClasses(m);
565 44 BindSandFill(m);
566 44 BindPavement(m);
567 44 BindStrata(m);
568 44 }
569
570 } // namespace terraingen::python
571