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 | 19 | if (grid != nullptr && | |
| 40 | 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 | 7 | if (!pointwise && !Overrides(nb_trampoline, "apply")) { | |
| 46 | 1 | throw std::logic_error("A Python Layer must define apply_at or apply"); | |
| 47 | } | ||
| 48 | 136332 | for (std::size_t i = 0; i < samples.Size(); ++i) { | |
| 49 | 136326 | if (samples.IsNoData(i)) { | |
| 50 | ✗ | continue; | |
| 51 | } | ||
| 52 | 136326 | const Vec2d p = samples.Position(i); | |
| 53 | 136326 | float& height = samples.Height(i); | |
| 54 | 272652 | height = pointwise ? *CallOverride<float>(nb_trampoline, "apply_at", height, p.x, p.y, | |
| 55 | 136325 | context) | |
| 56 | 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 | 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 | 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 | 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 | 44 | "heights"_a, "context"_a) | |
| 94 | 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 | 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 | 44 | m.def("validate_resolution_fade", &ValidateResolutionFade, "fade"_a, kValidateDoc); | |
| 112 | 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 | 44 | m.def("smoothstep", &Smoothstep, "t"_a, "The cubic smoothstep of `t` clamped to [0, 1]."); | |
| 115 | 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 | 76 | new (self) NoiseRelief(SyntheticTerrainParams{std::move(layers)}, origin, surface); | |
| 128 | 73 | }, | |
| 129 | 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 | 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 | new (self) DetailLayer(std::move(full), std::move(smooth), weight, surface); | |
| 143 | 1 | }, | |
| 144 | 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 | 88 | .def(nb::init<std::optional<double>, std::optional<double>>(), "min"_a = nb::none(), | |
| 147 | 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 | 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 | 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 | 44 | "x"_a, "y"_a, "radius"_a) | |
| 169 | 44 | .def("distance_outside", &RegionShape::DistanceOutside, "x"_a, "y"_a, | |
| 170 | 88 | "surface"_a = Surface{}); | |
| 171 | 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 | 176 | "layer"_a, "shape"_a, "feather"_a = 0.0, "blend"_a = BlendShape::Smoothstep, | |
| 177 | 88 | "surface"_a = Surface{}) | |
| 178 | 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 | 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 | 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 | 88 | .def(nb::init<RoughnessOptions, Surface>(), "options"_a = RoughnessOptions{}, | |
| 199 | 88 | "surface"_a = Surface{}) | |
| 200 | 44 | .def("rms_slope", &RoughnessLayer::RmsSlope, "baseline"_a) | |
| 201 | 88 | .def("wavelengths", &RoughnessLayer::Wavelengths, | |
| 202 | "The wavelengths (m) of the roughness octaves."); | ||
| 203 | 44 | m.def("perlin_structure_function", &PerlinStructureFunction, "r"_a, | |
| 204 | "The structure function of 2D Perlin noise at lag `r` lattice units."); | ||
| 205 | 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 | 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 | 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 | 88 | .def(nb::init<ExposureParams, Surface>(), "params"_a = ExposureParams{}, | |
| 232 | 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 | 5 | WithGround(&terrain, layers, [&](const Ground* ground) { | |
| 239 | 5 | field.Fill(exposure, *ground, spacing, threads); | |
| 240 | 5 | }); | |
| 241 | 5 | return exposure; | |
| 242 | ✗ | }, | |
| 243 | 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 | 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 | 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 | 44 | m.def("validate_bedform_options", &ValidateBedformOptions, "options"_a, kValidateDoc); | |
| 277 | 44 | m.def("mars_large_ripples", &MarsLargeRipples, "Martian large ripples."); | |
| 278 | 44 | m.def("mars_transverse_aeolian_ridges", &MarsTransverseAeolianRidges, | |
| 279 | "Martian transverse aeolian ridges."); | ||
| 280 | 44 | } | |
| 281 | |||
| 282 | 44 | void BindBedforms(nb::module_& m) { | |
| 283 | 44 | BindBedformOptions(m); | |
| 284 | 44 | auto bedforms = nb::class_<BedformLayer, Layer>(m, "BedformLayer", "Aeolian bedforms."); | |
| 285 | 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 | 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 | 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 | 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 | 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 | 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 | 88 | .def(nb::init<SandFillOptions, Surface>(), "options"_a = SandFillOptions{}, | |
| 325 | 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 | 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 | 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 | 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 | 44 | m.def("validate_pavement_options", &ValidatePavementOptions, "options"_a, kValidateDoc); | |
| 366 | 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 | 132 | layer.def(nb::init<PavementOptions, Surface>(), "options"_a, "surface"_a = Surface{}) | |
| 370 | 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 | 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 | 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 | 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 | 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 | 132 | layer.def(nb::init<StrataOptions, Surface>(), "options"_a, "surface"_a = Surface{}) | |
| 399 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 44 | m.def("validate_dune_options", &ValidateDuneOptions, "options"_a, kValidateDoc); | |
| 479 | 44 | m.def("mars_dune_ripples", &MarsDuneRipples, "Martian large wind ripples to lay on dunes."); | |
| 480 | 44 | m.def("mars_barchans", &MarsBarchans, "Martian barchans with large ripples."); | |
| 481 | 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 | 44 | BindDuneOptions(m); | |
| 487 | auto dunes = | ||
| 488 | 44 | nb::class_<DuneLayer, Layer>(m, "DuneLayer", "Wind-blown dunes, with ripples on them."); | |
| 489 | 132 | dunes.def(nb::init<DuneOptions, Surface>(), "options"_a, "surface"_a = Surface{}) | |
| 490 | 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 | 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 | 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 | 44 | m.def("validate_density_map", &ValidateDensityMap, "map"_a, | |
| 518 | "Raise ValueError if the map is unusable."); | ||
| 519 | 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 | 88 | .def(nb::init<DensityMap, const SizeFrequencyDistribution&>(), "map"_a, "distribution"_a) | |
| 524 | 44 | .def("at", &DensityScale::At, "x"_a, "y"_a) | |
| 525 | 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 | 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 | 44 | m.def("validate_size_slope_map", &ValidateSizeSlopeMap, "map"_a, | |
| 547 | "Raise ValueError if the map is unusable."); | ||
| 548 | 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 |