TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
314 / 325
96.6%
Branches:
153 / 178
86.0%
Functions:
35 / 35
100.0%

python/src/boulders.cpp
Line Branch Exec Source
1 #include "bindings.h"
2 #include "common.h"
3 #include "objects.h"
4 #include "tables.h"
5
6 #include "terraingen/layers/boulders/boulder.h"
7 #include "terraingen/layers/boulders/boulder_layer.h"
8 #include "terraingen/layers/boulders/boulder_models.h"
9 #include "terraingen/layers/boulders/boulder_options.h"
10 #include "terraingen/layers/boulders/boulder_shape.h"
11 #include "terraingen/layers/sand_shadow.h"
12
13 #include <algorithm>
14 #include <memory>
15 #include <utility>
16 #include <vector>
17
18 namespace terraingen::python {
19
20 namespace {
21
22 44 void BindBoulderStyle(nb::module_& m) {
23 44 nb::class_<ValueRange>(m, "ValueRange", "An interval values are drawn uniformly from.")
24 44 .def(
25 "__init__",
26 7 [](ValueRange* self, double min, double max) { new (self) ValueRange{min, max}; },
27
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
132 "min"_a = 0.0, "max"_a = 0.0)
28 44 .def_rw("min", &ValueRange::min)
29 .def_rw("max", &ValueRange::max);
30 auto style = nb::class_<BoulderStyle>(
31 m, "BoulderStyle",
32 "One kind of boulder: its `solid`, stretched to axes 1 : `intermediate` : `short_axis`, "
33 "rounded toward an ellipsoid by `rounding`, roughened by noise and cut by fracture planes; "
34 44 "`weight` is its share of boulders.");
35
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<BoulderStyle>(style);
36 44 style.def_rw("name", &BoulderStyle::name)
37 44 .def_rw("weight", &BoulderStyle::weight)
38 44 .def_rw("solid", &BoulderStyle::solid)
39 44 .def_rw("intermediate", &BoulderStyle::intermediate)
40 44 .def_rw("short_axis", &BoulderStyle::shortAxis)
41 44 .def_rw("rounding", &BoulderStyle::rounding)
42 44 .def_rw("min_fractures", &BoulderStyle::minFractures)
43 44 .def_rw("max_fractures", &BoulderStyle::maxFractures)
44 44 .def_rw("fracture_depth", &BoulderStyle::fractureDepth)
45 44 .def_rw("max_fracture_tilt", &BoulderStyle::maxFractureTilt)
46 44 .def_rw("noise_amplitude", &BoulderStyle::noiseAmplitude)
47 44 .def_rw("noise_wavelength", &BoulderStyle::noiseWavelength)
48 44 .def_rw("noise_octaves", &BoulderStyle::noiseOctaves)
49 44 .def_rw("edge_softness", &BoulderStyle::edgeSoftness)
50 .def_rw("face_roughness", &BoulderStyle::faceRoughness);
51
1/1
✓ Branch 1 taken 44 times.
44 m.def("default_boulder_styles", &DefaultBoulderStyles,
52 "The default mix: blocks and slabs, rounded boulders, wedges, pyramids and shards.");
53 44 }
54
55 44 void BindBoulderShapes(nb::module_& m) {
56
1/1
✓ Branch 1 taken 44 times.
44 BindBoulderStyle(m);
57 auto options = nb::class_<BoulderShapeOptions>(
58 m, "BoulderShapeOptions",
59 44 "Boulder shapes: the `styles` to draw from, `subdivisions` of each solid and a `seed`.");
60
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<BoulderShapeOptions>(options);
61 44 options.def_rw("styles", &BoulderShapeOptions::styles)
62 44 .def_rw("subdivisions", &BoulderShapeOptions::subdivisions)
63 44 .def_rw("weathering", &BoulderShapeOptions::weathering)
64 44 .def_rw("seed", &BoulderShapeOptions::seed)
65 .def_prop_rw(
66 "library",
67 45 [](const BoulderShapeOptions& o) {
68 1 std::vector<SceneMesh> rocks;
69
2/2
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1 time.
2 for (const auto& rock : o.library) {
70
1/1
✓ Branch 2 taken 1 time.
1 rocks.push_back(*rock);
71 }
72 1 return rocks;
73 ✗ },
74 44 [](BoulderShapeOptions& o, const std::vector<SceneMesh>& rocks) {
75 1 o.library.clear();
76
2/2
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
2 for (const SceneMesh& rock : rocks) {
77
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 o.library.push_back(std::make_shared<const SceneMesh>(rock));
78 }
79 1 },
80 "Rock meshes (SceneMesh, such as `read_mesh` gives) that replace the styles when not "
81 "empty, each shape seed picking one.");
82 88 nb::class_<BoulderShape>(m, "BoulderShape",
83 "A boulder shape with a long axis of 1 along x, `intermediate` along "
84 "y and `short_axis` along z.")
85 44 .def_ro("mesh", &BoulderShape::mesh)
86 44 .def_ro("style", &BoulderShape::style)
87 44 .def_ro("material", &BoulderShape::material)
88 44 .def_ro("intermediate", &BoulderShape::intermediate)
89 .def_ro("short_axis", &BoulderShape::shortAxis);
90
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("normalized_boulder_shape", &NormalizedBoulderShape, "mesh"_a,
91 "A rock mesh in the unit form of BoulderShape: centred, its longer horizontal side along "
92 "x and 1 long.");
93
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("generate_boulder_shape", &GenerateBoulderShape, "shape_seed"_a,
94
2/4
✓ Branch 1 taken 44 times.
✓ Branch 5 taken 44 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
88 "options"_a = BoulderShapeOptions{},
95 "The shape `shape_seed` gives: the same seed always gives the same shape.");
96
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
88 m.def("boulder_shape_seed", &BoulderShapeSeed, "id"_a, "prototypes"_a = 0,
97 "The shape seed of boulder `id`, or of one of `prototypes` shared shapes.");
98
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_boulder_shape_options", &ValidateBoulderShapeOptions, "options"_a,
99 kValidateDoc);
100 44 }
101
102 44 void BindBoulder(nb::module_& m) {
103 88 nb::enum_<BoulderRest>(m, "BoulderRest", "Which of its axes a boulder stands along.")
104 44 .value("FACE", BoulderRest::Face)
105 44 .value("SIDE", BoulderRest::Side)
106 .value("END", BoulderRest::End);
107 44 nb::class_<BoulderFacet>(m, "BoulderFacet",
108 "A ventifact facet: facing `azimuth` (radians from east), sloping "
109 "`angle` radians, meeting the ground `reach` metres from the centre.")
110 44 .def(
111 "__init__",
112 ✗ [](BoulderFacet* self, double azimuth, double angle, double reach) {
113 1 new (self) BoulderFacet{azimuth, angle, reach};
114 1 },
115
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
44 "azimuth"_a, "angle"_a, "reach"_a)
116 44 .def_rw("azimuth", &BoulderFacet::azimuth)
117 44 .def_rw("angle", &BoulderFacet::angle)
118 44 .def_rw("reach", &BoulderFacet::reach)
119 .def(
120 "height_at",
121 45 [](const BoulderFacet& f, double east, double north) {
122 1 return f.HeightAt({east, north});
123 },
124
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
88 "east"_a, "north"_a,
125 "The facet's height (m) above the ground east and north metres from the centre.");
126 ✗ nb::class_<Boulder>(m, "Boulder", "One boulder: centre, size, burial, orientation and ID.")
127 44 .def(
128 "__init__",
129 49 [](Boulder* self, double x, double y, double diameter, double height, double burial,
130 double yaw, double lean, std::uint64_t id) {
131 5 new (self) Boulder{x, y, diameter, height, burial, yaw, lean, id};
132 5 },
133
7/7
✓ 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.
✓ Branch 16 taken 44 times.
✓ Branch 19 taken 44 times.
220 "x"_a, "y"_a, "diameter"_a, "height"_a, "burial"_a = 0.0, "yaw"_a = 0.0, "lean"_a = 0.0,
134
1/1
✓ Branch 1 taken 44 times.
88 "id"_a = 0)
135 44 .def_rw("x", &Boulder::x)
136 44 .def_rw("y", &Boulder::y)
137 44 .def_rw("diameter", &Boulder::diameter)
138 44 .def_rw("height", &Boulder::height)
139 44 .def_rw("burial", &Boulder::burial)
140 44 .def_rw("yaw", &Boulder::yaw)
141 44 .def_rw("lean", &Boulder::lean)
142 44 .def_rw("id", &Boulder::id)
143 44 .def_rw("ground", &Boulder::ground)
144 44 .def_rw("model", &Boulder::model)
145 44 .def_rw("wear", &Boulder::wear)
146 44 .def_rw("rest", &Boulder::rest)
147 44 .def_rw("tilt", &Boulder::tilt)
148 44 .def_rw("tilt_azimuth", &Boulder::tiltAzimuth)
149 44 .def_rw("parent", &Boulder::parent)
150 44 .def_rw("piece", &Boulder::piece)
151 44 .def_prop_rw(
152 "facets",
153 2 [](const Boulder& b) {
154
1/1
✓ Branch 3 taken 2 times.
4 return std::vector<BoulderFacet>(b.facets.begin(), b.facets.begin() + b.facetCount);
155 },
156 45 [](Boulder& b, const std::vector<BoulderFacet>& facets) {
157
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
2 if (facets.size() > b.facets.size()) {
158
0/1
✗ Branch 2 not taken.
✗ throw nb::value_error("A boulder has at most 2 facets");
159 }
160 1 std::copy(facets.begin(), facets.end(), b.facets.begin());
161 1 b.facetCount = static_cast<std::uint8_t>(facets.size());
162 1 },
163 "Its ventifact facets, up to 2.")
164 88 .def_prop_ro("exposed_height", &Boulder::ExposedHeight);
165 44 }
166
167 44 void BindPatchesAndEjecta(nb::module_& m) {
168 auto patches = nb::class_<BoulderPatches>(
169 m, "BoulderPatches",
170 "Patches of richer and poorer boulder cover, about `size` metres across: the abundance "
171 44 "varies from 1 / `spread` to `spread` times its mean (1 for the same everywhere).");
172
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<BoulderPatches>(patches);
173 44 patches.def_rw("spread", &BoulderPatches::spread).def_rw("size", &BoulderPatches::size);
174 88 nb::enum_<BoulderClusterModel>(m, "BoulderClusterModel",
175 "How boulder cluster centres are laid out and combine.")
176 44 .value("NEAREST", BoulderClusterModel::Nearest)
177 .value("THOMAS", BoulderClusterModel::Thomas);
178 88 nb::enum_<EjectaProfile>(m, "EjectaProfile",
179 "How ejecta boulders fall off from a crater's rim.")
180 44 .value("SMOOTHSTEP", EjectaProfile::Smoothstep)
181 .value("EXPONENTIAL", EjectaProfile::Exponential);
182 88 nb::enum_<EjectaAging>(m, "EjectaAging", "How ejecta boulders fade as their crater ages.")
183 44 .value("FRESHNESS", EjectaAging::Freshness)
184 .value("AGE", EjectaAging::Age);
185 auto ejecta = nb::class_<BoulderEjecta>(
186 m, "BoulderEjecta",
187 "Boulders thrown out by craters at least `min_crater_diameter` across (or deep enough to "
188 "reach bedrock under `regolith_thickness`): extra `coverage` near the rim, falling off "
189 44 "to `reach` radii, fading as the crater ages.");
190
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<BoulderEjecta>(ejecta);
191 44 ejecta.def_rw("min_crater_diameter", &BoulderEjecta::minCraterDiameter)
192 44 .def_rw("regolith_thickness", &BoulderEjecta::regolithThickness)
193 44 .def_rw("excavation_depth_ratio", &BoulderEjecta::excavationDepthRatio)
194 44 .def_rw("distribution", &BoulderEjecta::distribution)
195 44 .def_rw("coverage", &BoulderEjecta::coverage)
196 44 .def_rw("profile", &BoulderEjecta::profile)
197 44 .def_rw("reach", &BoulderEjecta::reach)
198 44 .def_rw("decay_radii", &BoulderEjecta::decayRadii)
199 44 .def_rw("floor_share", &BoulderEjecta::floorShare)
200 44 .def_rw("aging", &BoulderEjecta::aging)
201 44 .def_rw("min_freshness", &BoulderEjecta::minFreshness)
202 44 .def_rw("lifetime_gyr", &BoulderEjecta::lifetimeGyr)
203 .def_rw("aging_exponent", &BoulderEjecta::agingExponent);
204 44 }
205
206 44 void BindClustersAndFragments(nb::module_& m) {
207 auto clusters = nb::class_<BoulderClusters>(
208 m, "BoulderClusters",
209 "Boulders between craters gathered in clusters: `share` of them crowd within about "
210 "`radius` metres of cluster centres about `spacing` metres apart; the mean density is "
211 44 "kept.");
212
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<BoulderClusters>(clusters);
213 44 clusters.def_rw("share", &BoulderClusters::share)
214 44 .def_rw("spacing", &BoulderClusters::spacing)
215 44 .def_rw("radius", &BoulderClusters::radius)
216 .def_rw("model", &BoulderClusters::model);
217
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_boulder_clusters", &ValidateBoulderClusters, "clusters"_a, kValidateDoc);
218
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("mean_patch_abundance", &MeanPatchAbundance, "patches"_a,
219 "The mean of the patches' abundance, which a density map divides them by.");
220
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("max_cluster_abundance", &MaxClusterAbundance, "clusters"_a,
221 "The most a cluster field multiplies the scattered boulder density by.");
222 auto fragmentation = nb::class_<BoulderFragmentation>(
223 m, "BoulderFragmentation",
224 "Boulders broken by wear into pieces that replace them: from `split_wear` boulders at "
225 "least `min_diameter` across split into `min_pieces` to `max_pieces` fitting pieces, "
226 "their cracks opening to `gap_ratio` diameters; from `rubble_wear` the pieces scatter up "
227 44 "to `spread_ratio` diameters further, turn and sink. A `split_wear` of 1 breaks nothing.");
228
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<BoulderFragmentation>(fragmentation);
229 44 fragmentation.def_rw("min_diameter", &BoulderFragmentation::minDiameter)
230 44 .def_rw("split_wear", &BoulderFragmentation::splitWear)
231 44 .def_rw("rubble_wear", &BoulderFragmentation::rubbleWear)
232 44 .def_rw("min_pieces", &BoulderFragmentation::minPieces)
233 44 .def_rw("max_pieces", &BoulderFragmentation::maxPieces)
234 44 .def_rw("gap_ratio", &BoulderFragmentation::gapRatio)
235 44 .def_rw("spread_ratio", &BoulderFragmentation::spreadRatio)
236 44 .def_rw("slab_share", &BoulderFragmentation::slabShare)
237 44 .def_prop_ro("enabled", &BoulderFragmentation::Enabled);
238
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_boulder_fragmentation", &ValidateBoulderFragmentation, "fragmentation"_a,
239 kValidateDoc);
240 44 }
241
242 44 void BindFillet(nb::module_& m) {
243 auto fillet = nb::class_<BoulderFillet>(
244 m, "BoulderFillet",
245 "Regolith piled against each boulder's base: `height_share` of its exposed height out to "
246 "its radius, falling to nothing `width_ratio` diameters further out; 0 `height_share` "
247 44 "draws none.");
248
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<BoulderFillet>(fillet);
249 44 fillet.def_rw("height_share", &BoulderFillet::heightShare)
250 .def_rw("width_ratio", &BoulderFillet::widthRatio);
251
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("boulder_fillet_height", &FilletHeight, "boulder"_a, "distance"_a, "fillet"_a,
252 "The height (m) of `boulder`'s fillet `distance` metres from its centre.");
253
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_boulder_fillet", &ValidateBoulderFillet, "fillet"_a, kValidateDoc);
254 auto ventifacts = nb::class_<BoulderVentifacts>(
255 m, "BoulderVentifacts",
256 "Windward facets planed on `share` of the boulders by wind blowing toward "
257 "`wind_azimuth`, sloping `min_angle` to `max_angle`, with keels on `keel_share` of them "
258 44 "toward `second_wind_azimuth`; angles in radians.");
259
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<BoulderVentifacts>(ventifacts);
260 44 ventifacts.def_rw("share", &BoulderVentifacts::share)
261 44 .def_rw("wind_azimuth", &BoulderVentifacts::windAzimuth)
262 44 .def_rw("min_angle", &BoulderVentifacts::minAngle)
263 44 .def_rw("max_angle", &BoulderVentifacts::maxAngle)
264 44 .def_rw("second_wind_azimuth", &BoulderVentifacts::secondWindAzimuth)
265 .def_rw("keel_share", &BoulderVentifacts::keelShare);
266
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_boulder_ventifacts", &ValidateBoulderVentifacts, "ventifacts"_a, kValidateDoc);
267
1/1
✓ Branch 1 taken 44 times.
44 m.def("gale_ventifacts", &GaleVentifacts,
268 "The ventifacts of Gale crater's first 100 sols (Bridges et al. 2014; facet slopes "
269 "from Laity and Bridges 2009); the winds are left to the site.");
270 44 }
271
272 44 void BindRendering(nb::module_& m) {
273 88 nb::enum_<BoulderRelief>(m, "BoulderRelief", "How boulders are drawn into the heights.")
274 44 .value("DOME", BoulderRelief::Dome)
275 44 .value("SHAPE", BoulderRelief::Shape)
276 .value("NONE", BoulderRelief::None);
277 auto threshold = nb::class_<BoulderModelThreshold>(
278 m, "BoulderModelThreshold",
279 "Which boulders are placed as 3D boulder models: at least `min_diameter` metres across, or "
280 44 "`min_samples` samples across, and below `max_diameter` and `max_samples` when set.");
281
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<BoulderModelThreshold>(threshold);
282 44 threshold.def_rw("min_diameter", &BoulderModelThreshold::minDiameter)
283 44 .def_rw("min_samples", &BoulderModelThreshold::minSamples)
284 44 .def_rw("max_diameter", &BoulderModelThreshold::maxDiameter)
285 44 .def_rw("max_samples", &BoulderModelThreshold::maxSamples)
286
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
132 .def("is_model", &BoulderModelThreshold::IsModel, "diameter"_a, "spacing"_a = 0.0,
287 "Whether a boulder of `diameter` metres is a model at `spacing`.");
288 auto range = nb::class_<BoulderReliefRange>(
289 m, "BoulderReliefRange",
290 "Which boulders are drawn into the heights: from `min_diameter` up to `max_diameter` "
291 44 "metres, models only with `models`.");
292
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<BoulderReliefRange>(range);
293 44 range.def_rw("min_diameter", &BoulderReliefRange::minDiameter)
294 44 .def_rw("max_diameter", &BoulderReliefRange::maxDiameter)
295 44 .def_rw("models", &BoulderReliefRange::models)
296
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("holds", &BoulderReliefRange::Holds, "diameter"_a, "model"_a,
297 "Whether a boulder of `diameter` metres is drawn into the heights.");
298 44 }
299
300 /// @brief The grouped options under their flat config names.
301 44 void BindGroupedOptions(nb::class_<BoulderLayerOptions>& options) {
302 using O = BoulderLayerOptions;
303 44 BindGrouped(options, "coverage", &O::sizes, &BoulderSizes::coverage);
304 44 BindGrouped(options, "qa", &O::sizes, &BoulderSizes::qConstant);
305 44 BindGrouped(options, "qb", &O::sizes, &BoulderSizes::qCoverageCoefficient);
306 44 BindGrouped(options, "distribution", &O::sizes, &BoulderSizes::distribution);
307 44 BindGrouped(options, "min_diameter", &O::sizes, &BoulderSizes::minDiameter);
308 44 BindGrouped(options, "max_diameter", &O::sizes, &BoulderSizes::maxDiameter);
309 88 options.def_rw("seed", &O::seed).def_rw("patches", &O::patches).def_rw("ejecta", &O::ejecta);
310 44 BindGrouped(options, "exposure", &O::bedrock, &BoulderBedrock::exposure);
311 44 BindGrouped(options, "bedrock_abundance", &O::bedrock, &BoulderBedrock::abundance);
312 44 BindGrouped(options, "exposure_spacing", &O::bedrock, &BoulderBedrock::exposureSpacing);
313 44 BindGrouped(options, "bedrock_mean_size", &O::bedrock, &BoulderBedrock::meanSize);
314 44 BindGrouped(options, "min_height_ratio", &O::pose, &BoulderPose::minHeightRatio);
315 44 BindGrouped(options, "max_height_ratio", &O::pose, &BoulderPose::maxHeightRatio);
316 44 BindGrouped(options, "min_burial", &O::pose, &BoulderPose::minBurial);
317 44 BindGrouped(options, "max_burial", &O::pose, &BoulderPose::maxBurial);
318 44 BindGrouped(options, "max_lean", &O::pose, &BoulderPose::maxLean);
319 44 BindGrouped(options, "burial_by_wear", &O::pose, &BoulderPose::burialByWear);
320 44 BindGrouped(options, "side_share", &O::pose, &BoulderPose::sideShare);
321 44 BindGrouped(options, "end_share", &O::pose, &BoulderPose::endShare);
322 44 BindGrouped(options, "max_tilt", &O::pose, &BoulderPose::maxTilt);
323 44 BindGrouped(options, "follow_slope", &O::pose, &BoulderPose::followSlope);
324 44 BindGrouped(options, "settle", &O::pose, &BoulderPose::settle);
325 44 BindGrouped(options, "max_slope", &O::pose, &BoulderPose::maxSlope);
326 44 BindGrouped(options, "roll_distance", &O::pose, &BoulderPose::rollDistance);
327 options.def_rw("fillet", &O::fillet);
328 44 options.def_rw("density_map", &O::densityMap)
329 44 .def_rw("slope_map", &O::slopeMap)
330 44 .def_rw("manual", &O::manual, "Catalog boulders placed as given, as craters.")
331 .def_rw("completeness", &O::completeness,
332 "Diameter (m) from which `manual` is a complete catalog at each point.");
333 options.def_rw("ventifacts", &O::ventifacts);
334 44 options.def_rw("clusters", &O::clusters).def_rw("fragmentation", &O::fragmentation);
335 44 BindGrouped(options, "relief", &O::rendering, &BoulderRendering::relief);
336 44 BindGrouped(options, "models", &O::rendering, &BoulderRendering::models);
337 44 BindGrouped(options, "relief_range", &O::rendering, &BoulderRendering::reliefRange);
338 44 BindGrouped(options, "shapes", &O::rendering, &BoulderRendering::shapes);
339 44 BindGrouped(options, "shape_prototypes", &O::rendering, &BoulderRendering::shapePrototypes);
340 options.def_rw("fade", &O::fade);
341 44 }
342
343 44 void BindLayerOptions(nb::module_& m) {
344 nb::class_<BoulderLayerOptions> options(
345 m, "BoulderLayerOptions",
346 "A boulder layer's settings: sizes from Golombek and Rapp's model for `coverage` (or "
347 "`distribution`), between `min_diameter` and `max_diameter`; `patches`, `ejecta` and "
348 "`exposure` shape where they lie; heights, burial and lean shape each boulder; `relief` "
349 44 "and `models` how they are drawn.");
350
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<BoulderLayerOptions>(options);
351
1/1
✓ Branch 1 taken 44 times.
44 BindGroupedOptions(options);
352
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_boulder_layer_options", &ValidateBoulderLayerOptions, "options"_a,
353 kValidateDoc);
354
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("boulder_throw_distance", &BoulderThrowDistance, "crater_diameter"_a,
355
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 "regolith_thickness"_a, "gravity"_a,
356 "Bart and Melosh's (2010) farthest block throw (m) over a regolith blanket.");
357
1/1
✓ Branch 1 taken 44 times.
44 m.def(
358 "boulder_candidates",
359
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 1 time.
45 [](const BoulderLayerOptions& o) { return Mutable(BoulderCandidates(o)); }, "options"_a,
360 "The distribution candidate boulders are drawn from before thinning.");
361 44 }
362
363 44 void BindLayer(nb::module_& m) {
364 auto boulderLayer =
365 ✗ nb::class_<BoulderLayer, Layer>(
366 m, "BoulderLayer",
367 "Boulders drawn into the heights, the largest optionally "
368 "placed as 3D boulder models; `craters` supplies the craters "
369 "that throw ejecta boulders.")
370 ✗ .def(nb::init<BoulderLayerOptions, std::shared_ptr<CraterLayer>, Surface>(),
371
3/23
✓ Branch 1 taken 44 times.
✓ Branch 17 taken 44 times.
✓ Branch 25 taken 44 times.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 45 not taken.
✗ Branch 46 not taken.
✗ Branch 48 not taken.
✗ Branch 49 not taken.
✗ Branch 51 not taken.
✗ Branch 52 not taken.
✗ Branch 54 not taken.
✗ Branch 55 not taken.
✗ Branch 57 not taken.
✗ Branch 58 not taken.
✗ Branch 60 not taken.
✗ Branch 61 not taken.
132 "options"_a = BoulderLayerOptions{}, "craters"_a = nb::none(),
372
1/1
✓ Branch 2 taken 44 times.
88 "surface"_a = Surface{})
373 44 .def(
374 "boulders",
375 ✗ [](const BoulderLayer& layer, const Bounds2d& region, double spacing) {
376
2/2
✓ Branch 1 taken 6 times.
✓ Branch 4 taken 6 times.
6 return BoulderTable(layer.Boulders(region, spacing));
377 },
378
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
132 "region"_a, "spacing"_a = 0.0,
379 "The boulders over `region` (min_x, min_y, max_x, max_y) as NumPy columns, without "
380 "evaluating any heights or ground.")
381 44 .def(
382 "abundance_at",
383 ✗ [](const BoulderLayer& layer, double x, double y, const PyTerrain* terrain,
384 std::size_t layers) {
385 const BoulderAbundance abundance =
386
1/1
✓ Branch 1 taken 30437 times.
30437 WithGround(terrain, layers,
387 30437 [&](const Ground* g) { return layer.AbundanceAt(x, y, g); });
388 30437 return std::pair{abundance.background, abundance.ejecta};
389 },
390
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,
391 "The boulder abundance at (x, y): (background, ejecta). With the exposure field, "
392 "give "
393 "the ground beneath the boulders: `terrain`'s source and its first `layers` "
394 "layers.")
395 44 .def(
396 "density_at",
397 ✗ [](const BoulderLayer& layer, double diameter, double x, double y,
398 const PyTerrain* terrain, std::size_t layers) {
399 2 return WithGround(terrain, layers, [&](const Ground* g) {
400 2 return layer.DensityAt(diameter, x, y, g);
401 2 });
402 },
403
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.
176 "diameter"_a, "x"_a, "y"_a, "terrain"_a = nb::none(), "layers"_a = 0,
404 "Boulders at least `diameter` across per square metre at (x, y).")
405 44 .def(
406 "unresolved_cover_at",
407 45 [](const BoulderLayer& layer, double x, double y, double spacing,
408 const PyTerrain* terrain, std::size_t layers) {
409
1/1
✓ Branch 1 taken 1 time.
1 const UnresolvedCover cover = WithGround(terrain, layers, [&](const Ground* g) {
410 1 return layer.UnresolvedCoverAt(x, y, spacing, g);
411 });
412 1 return std::pair{cover.mean, cover.variance};
413 },
414
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.
176 "x"_a, "y"_a, "spacing"_a, "terrain"_a = nb::none(), "layers"_a = 0,
415 "The share of a sample `spacing` metres across at (x, y) that boulders too small "
416 "to resolve cover: (mean, variance).")
417 88 .def_prop_ro("options", &BoulderLayer::Options);
418
1/1
✓ Branch 1 taken 44 times.
44 BindMaterialCover<BoulderLayer>(boulderLayer);
419
6/6
✓ 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.
✓ Branch 16 taken 44 times.
88 m.def("add_boulder", &AddBoulder, "height"_a, "x"_a, "y"_a, "boulder"_a, "weight"_a = 1.0,
420
2/2
✓ Branch 1 taken 44 times.
✓ Branch 5 taken 44 times.
132 "surface"_a = Surface{}, "width"_a = 0.0,
421 "`height` at (x, y) with `boulder` drawn into it at `weight`, its dome averaged over "
422 "`width` metres.");
423
4/4
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
88 m.def("dome_height", &DomeHeight, "boulder"_a, "distance"_a, "width"_a = 0.0,
424 "A boulder's dome `distance` metres from its centre, averaged over `width` metres.");
425 44 }
426
427 44 void BindPlacement(nb::module_& m) {
428 44 nb::class_<BoulderPlacement>(m, "BoulderPlacement",
429 "Where a boulder shape goes: scaled per axis, then posed.")
430 44 .def_ro("scale", &BoulderPlacement::scale)
431 44 .def_ro("pose", &BoulderPlacement::pose)
432
1/1
✓ Branch 1 taken 44 times.
88 .def("apply", &BoulderPlacement::Apply, "point"_a);
433
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("place_boulder_model", &PlaceBoulderModel, "boulder"_a, "shape"_a, "ground_height"_a,
434
1/1
✓ Branch 1 taken 44 times.
88 "settle"_a = false,
435 "The placement of `shape` as `boulder` on ground at `ground_height`; with `settle`, "
436 "tipped over until it rests stably.");
437
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("placement_at_site", &PlacementAtSite, "placement"_a, "boulder"_a, "site"_a, "surface"_a,
438 "A boulder's placement moved into the local east-north-up frame of `site`.");
439 44 }
440
441 44 void BindModels(nb::module_& m) {
442 44 nb::class_<BoulderModel>(
443 m, "BoulderModel", "A boulder placed as a 3D model: the boulder, its shape and placement.")
444 44 .def_ro("boulder", &BoulderModel::boulder)
445 44 .def_ro("shape_seed", &BoulderModel::shapeSeed)
446 46 .def_prop_ro("shape", [](const BoulderModel& b) { return *b.shape; })
447 44 .def_ro("placement", &BoulderModel::placement)
448 .def_prop_ro(
449 "mesh",
450 44 [](const BoulderModel& b) {
451 2 TriangleMesh placed = b.shape->mesh;
452
2/2
✓ Branch 5 taken 516 times.
✓ Branch 6 taken 2 times.
518 for (Vec3d& p : placed.positions) {
453
1/1
✓ Branch 1 taken 516 times.
516 p = b.placement.Apply(p);
454 }
455 2 return placed;
456 ✗ },
457 "The boulder's mesh placed in the world.");
458
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("boulder_mesh", &BoulderMesh, "boulder"_a, "options"_a, "ground_height"_a,
459
1/1
✓ Branch 1 taken 44 times.
88 "prototypes"_a = 0, "`boulder` as a mesh in the world, standing on the ground.");
460
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("top_surface", &TopSurface, "placed"_a, "x"_a, "y"_a,
461 "The height of a placed mesh's top above (x, y), or None outside it.");
462 44 }
463
464 44 void BindSandShadows(nb::module_& m) {
465 auto options = nb::class_<SandShadowOptions>(
466 m, "SandShadowOptions",
467 "Sand tails behind boulders: blown toward `wind_azimuth`, `length_ratio` boulder widths "
468 44 "long, flanks at `flank_angle`; angles in radians.");
469
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<SandShadowOptions>(options);
470 44 options.def_rw("wind_azimuth", &SandShadowOptions::windAzimuth)
471 44 .def_rw("length_ratio", &SandShadowOptions::lengthRatio)
472 44 .def_rw("flank_angle", &SandShadowOptions::flankAngle)
473 44 .def_rw("min_diameter", &SandShadowOptions::minDiameter)
474 44 .def_rw("cover_thickness", &SandShadowOptions::coverThickness)
475 .def_rw("fade", &SandShadowOptions::fade);
476
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_sand_shadow_options", &ValidateSandShadowOptions, "options"_a,
477 "Raises ValueError for invalid options.");
478 auto layer = nb::class_<SandShadowLayer, Layer>(m, "SandShadowLayer",
479 44 "Sand tails in the lee of boulders.");
480 layer
481 44 .def(
482 "__init__",
483 ✗ [](SandShadowLayer* self, std::shared_ptr<BoulderLayer> boulders,
484 const SandShadowOptions& shadowOptions, const Surface& surface) {
485
2/3
✓ Branch 5 taken 1 time.
✓ Branch 11 taken 1 time.
✗ Branch 12 not taken.
4 new (self) SandShadowLayer(std::move(boulders), shadowOptions, surface);
486 1 },
487
3/3
✓ Branch 2 taken 44 times.
✓ Branch 5 taken 44 times.
✓ Branch 8 taken 44 times.
176 "boulders"_a, "options"_a = SandShadowOptions{}, "surface"_a = Surface{})
488 44 .def(
489 "thickness_at",
490 45 [](const SandShadowLayer& shadows, double x, double y, double spacing,
491 const PyTerrain* terrain, std::size_t layers) {
492 1 return WithGround(terrain, layers, [&](const Ground* ground) {
493 1 return shadows.ThicknessAt(x, y, spacing, ground);
494 1 });
495 },
496
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.
220 "x"_a, "y"_a, "spacing"_a = 0.0, "terrain"_a = nb::none(), "layers"_a = 0,
497 "The sand depth at (x, y) in metres; with the boulders' exposure field, give the "
498 "ground: `terrain`'s source and its first `layers` layers.")
499 88 .def_prop_ro("options", &SandShadowLayer::Options);
500
1/1
✓ Branch 1 taken 44 times.
44 BindMaterialCover<SandShadowLayer>(layer);
501 44 }
502
503 } // namespace
504
505 44 void BindBoulders(nb::module_& m) {
506 44 BindBoulderShapes(m);
507 44 BindBoulder(m);
508 44 BindPatchesAndEjecta(m);
509 44 BindClustersAndFragments(m);
510 44 BindFillet(m);
511 44 BindRendering(m);
512 44 BindLayerOptions(m);
513 44 BindLayer(m);
514 44 BindSandShadows(m);
515 44 BindPlacement(m);
516 44 BindModels(m);
517 44 }
518
519 } // namespace terraingen::python
520