materials/materials.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/materials/materials.h" | ||
| 2 | |||
| 3 | #include "terraingen/layers/fade.h" | ||
| 4 | #include "terraingen/util/checks.h" | ||
| 5 | #include "terraingen/util/parallel.h" | ||
| 6 | |||
| 7 | #include <algorithm> | ||
| 8 | #include <cmath> | ||
| 9 | #include <cstddef> | ||
| 10 | #include <optional> | ||
| 11 | #include <stdexcept> | ||
| 12 | #include <string> | ||
| 13 | #include <utility> | ||
| 14 | |||
| 15 | namespace terraingen { | ||
| 16 | |||
| 17 | namespace { | ||
| 18 | |||
| 19 | /// Width of the patch noise band over which dust comes in. | ||
| 20 | constexpr double kDustEdge = 0.2; | ||
| 21 | |||
| 22 | 90 | std::size_t Index(MaterialClass material) { | |
| 23 | 90 | return static_cast<std::size_t>(material); | |
| 24 | } | ||
| 25 | |||
| 26 | const char* const kClassNames[kMaterialClasses] = {"soil", "sand", "dust", "bedrock", "rock"}; | ||
| 27 | |||
| 28 | /// The layers before the first that lays a material: the bare ground's. | ||
| 29 | 13520 | std::size_t BareLayers(const Terrain& terrain) { | |
| 30 | 13520 | std::size_t n = 0; | |
| 31 | 44076 | while (n < terrain.layers.size() && | |
| 32 | 17036 | dynamic_cast<const MaterialSource*>(terrain.layers[n].get()) == nullptr) { | |
| 33 | 13520 | ++n; | |
| 34 | } | ||
| 35 | 13520 | return n; | |
| 36 | } | ||
| 37 | |||
| 38 | /// The shares before any material layer: soil, bedrock where `bare` is exposed and dust patches. | ||
| 39 | 356908 | MaterialShares GroundShares(const Terrain& terrain, const MaterialOptions& options, | |
| 40 | const ExposureField* field, const Ground& bare, double x, double y, | ||
| 41 | double spacing) { | ||
| 42 | 356908 | MaterialShares shares = SoilShares(); | |
| 43 | 356908 | if (field != nullptr) { | |
| 44 | 290858 | CoverWith(shares, MaterialClass::Bedrock, field->Exposure(bare, x, y, spacing)); | |
| 45 | } | ||
| 46 | 356908 | if (options.dustCover > 0.0) { | |
| 47 | 83313 | const double noise = PatchNoise(terrain.surface, x, y, options.dustPatch, options.seed); | |
| 48 | 83313 | const double threshold = 1.0 - 2.0 * options.dustCover; | |
| 49 | 83313 | CoverWith(shares, MaterialClass::Dust, Smoothstep((noise - threshold) / kDustEdge)); | |
| 50 | } | ||
| 51 | 356908 | return shares; | |
| 52 | } | ||
| 53 | |||
| 54 | } // namespace | ||
| 55 | |||
| 56 | 13540 | void ValidateMaterialOptions(const MaterialOptions& options) { | |
| 57 | 13540 | if (!(options.dustCover >= 0.0 && options.dustCover < 1.0)) { | |
| 58 | 2 | throw std::invalid_argument("Material dustCover must be in [0, 1)"); | |
| 59 | } | ||
| 60 | 13538 | RequirePositive(options.dustPatch, "Material dustPatch"); | |
| 61 | 13537 | if (options.exposure) { | |
| 62 | 13529 | ValidateExposureParams(*options.exposure); | |
| 63 | } | ||
| 64 | 13536 | } | |
| 65 | |||
| 66 | 13511 | MaterialShares MaterialsAt(const Terrain& terrain, const MaterialOptions& options, double x, | |
| 67 | double y, double spacing) { | ||
| 68 | 13511 | ValidateMaterialOptions(options); | |
| 69 | 13511 | ValidateTerrain(terrain); | |
| 70 | 13511 | RequirePositive(spacing, "Material spacing"); | |
| 71 | 13510 | std::optional<ExposureField> field; | |
| 72 | 13510 | if (options.exposure) { | |
| 73 | 13509 | field.emplace(*options.exposure, terrain.surface); | |
| 74 | } | ||
| 75 | 13510 | const TerrainGround bare(terrain, BareLayers(terrain)); | |
| 76 | MaterialShares shares = | ||
| 77 | 13510 | GroundShares(terrain, options, field ? &*field : nullptr, bare, x, y, spacing); | |
| 78 | 33599 | for (std::size_t n = 0; n < terrain.layers.size(); ++n) { | |
| 79 | 20089 | const auto* source = dynamic_cast<const MaterialSource*>(terrain.layers[n].get()); | |
| 80 | 20089 | if (source == nullptr) { | |
| 81 | 13510 | continue; | |
| 82 | } | ||
| 83 | 6579 | const TerrainGround beneath(terrain, n); | |
| 84 | 6579 | CoverWith(shares, source->Material(), source->MaterialCoverAt(x, y, spacing, &beneath)); | |
| 85 | 6579 | } | |
| 86 | 27020 | return shares; | |
| 87 | 13510 | } | |
| 88 | |||
| 89 | 10 | std::vector<Raster<float>> EvaluateMaterials(const Terrain& terrain, const MaterialOptions& options, | |
| 90 | const RasterGrid& grid, | ||
| 91 | const EvaluationOptions& evaluation) { | ||
| 92 | 10 | ValidateMaterialOptions(options); | |
| 93 | 10 | ValidateTerrain(terrain); | |
| 94 | 10 | RequireNonNegative(evaluation.spacing, "Material spacing"); | |
| 95 | 10 | const double spacing = std::max(evaluation.spacing, grid.Spacing(terrain.surface)); | |
| 96 | 10 | std::vector<std::pair<MaterialClass, Raster<double>>> covers; | |
| 97 | 34 | for (std::size_t n = 0; n < terrain.layers.size(); ++n) { | |
| 98 | 24 | const auto* source = dynamic_cast<const MaterialSource*>(terrain.layers[n].get()); | |
| 99 | 24 | if (source != nullptr) { | |
| 100 | 14 | const TerrainGround beneath(terrain, n); | |
| 101 | 14 | covers.emplace_back( | |
| 102 | 14 | source->Material(), | |
| 103 | 28 | source->MaterialCoverOver(grid, spacing, &beneath, evaluation.threads)); | |
| 104 | 14 | } | |
| 105 | } | ||
| 106 | 10 | std::optional<ExposureField> field; | |
| 107 | 10 | if (options.exposure) { | |
| 108 | 9 | field.emplace(*options.exposure, terrain.surface); | |
| 109 | } | ||
| 110 | 10 | const TerrainGround bare(terrain, BareLayers(terrain)); | |
| 111 | 10 | std::vector<Raster<float>> bands(kMaterialClasses, grid.MakeRaster()); | |
| 112 | 10 | ParallelFor(grid.height, evaluation.threads, [&](std::size_t row) { | |
| 113 | 344886 | for (std::size_t col = 0; col < grid.width; ++col) { | |
| 114 | 343398 | const Vec2d centre = bands[0].CellCenter(col, row); | |
| 115 | 343398 | MaterialShares shares = GroundShares(terrain, options, field ? &*field : nullptr, bare, | |
| 116 | 343398 | centre.x, centre.y, spacing); | |
| 117 | 954000 | for (const auto& [material, cover] : covers) { | |
| 118 | 610602 | CoverWith(shares, material, cover(col, row)); | |
| 119 | } | ||
| 120 | 2060388 | for (std::size_t k = 0; k < kMaterialClasses; ++k) { | |
| 121 | 1716990 | bands[k](col, row) = static_cast<float>(shares[k]); | |
| 122 | } | ||
| 123 | } | ||
| 124 | 1488 | }); | |
| 125 | 20 | return bands; | |
| 126 | 10 | } | |
| 127 | |||
| 128 | 18 | MaterialPhotometry MarsPancamPhotometry() { | |
| 129 | 18 | MaterialPhotometry photometry{}; | |
| 130 | 18 | photometry[Index(MaterialClass::Soil)] = HapkeParameters{0.76, 0.262, 0.715, 15.0}; | |
| 131 | 18 | photometry[Index(MaterialClass::Sand)] = HapkeParameters{0.52, 0.255, 0.434, 9.0}; | |
| 132 | 18 | photometry[Index(MaterialClass::Dust)] = HapkeParameters{0.66, 0.449, 0.364, 16.0}; | |
| 133 | 18 | photometry[Index(MaterialClass::Rock)] = HapkeParameters{0.83, 0.931, 0.065, 7.0}; | |
| 134 | 18 | photometry[Index(MaterialClass::Bedrock)] = HapkeParameters{0.90, 0.372, 0.202, 17.0}; | |
| 135 | 18 | return photometry; | |
| 136 | } | ||
| 137 | |||
| 138 | 26 | void ValidateMaterialPhotometry(const MaterialPhotometry& photometry) { | |
| 139 | 147 | for (std::size_t k = 0; k < kMaterialClasses; ++k) { | |
| 140 | 124 | if (!photometry[k]) { | |
| 141 | 27 | continue; | |
| 142 | } | ||
| 143 | 97 | const HapkeParameters& p = *photometry[k]; | |
| 144 | 287 | const auto unit = [](double v) { return v >= 0.0 && v <= 1.0; }; | |
| 145 | 192 | if (!(unit(p.albedo) && unit(p.b) && unit(p.c) && p.roughness >= 0.0 && | |
| 146 | 95 | p.roughness < 90.0)) { | |
| 147 | 6 | throw std::invalid_argument(std::string("Hapke parameters of ") + kClassNames[k] + | |
| 148 | " must have w, b and c in [0, 1] and a roughness in " | ||
| 149 | 9 | "[0, 90) degrees"); | |
| 150 | } | ||
| 151 | } | ||
| 152 | 23 | } | |
| 153 | |||
| 154 | 267461 | HapkeParameters MixPhotometry(const MaterialShares& shares, const MaterialPhotometry& photometry) { | |
| 155 | 267461 | HapkeParameters mixed; | |
| 156 | 1604762 | for (std::size_t k = 0; k < kMaterialClasses; ++k) { | |
| 157 | 1337303 | if (shares[k] <= 0.0) { | |
| 158 | 967503 | continue; | |
| 159 | } | ||
| 160 | 369800 | if (!photometry[k]) { | |
| 161 | 4 | throw std::invalid_argument(std::string("No Hapke parameters for ") + kClassNames[k] + | |
| 162 | 6 | ", which covers part of the surface"); | |
| 163 | } | ||
| 164 | 369798 | const HapkeParameters& p = *photometry[k]; | |
| 165 | 369798 | mixed.albedo += shares[k] * p.albedo; | |
| 166 | 369798 | mixed.b += shares[k] * p.b; | |
| 167 | 369798 | mixed.c += shares[k] * p.c; | |
| 168 | 369798 | mixed.roughness += shares[k] * p.roughness; | |
| 169 | } | ||
| 170 | 267459 | return mixed; | |
| 171 | } | ||
| 172 | |||
| 173 | 9 | std::vector<Raster<float>> EvaluatePhotometry(const std::vector<Raster<float>>& materials, | |
| 174 | const MaterialPhotometry& photometry) { | ||
| 175 | 9 | if (materials.size() != kMaterialClasses) { | |
| 176 | 1 | throw std::invalid_argument("Photometry needs one material raster per class"); | |
| 177 | } | ||
| 178 | 8 | ValidateMaterialPhotometry(photometry); | |
| 179 | 8 | const RasterGrid grid = RasterGrid::FromRaster(materials[0]); | |
| 180 | 45 | for (const Raster<float>& band : materials) { | |
| 181 | 38 | if (!(RasterGrid::FromRaster(band) == grid)) { | |
| 182 | 1 | throw std::invalid_argument("Material rasters must share one grid"); | |
| 183 | } | ||
| 184 | } | ||
| 185 | 7 | std::vector<Raster<float>> bands(4, grid.MakeRaster()); | |
| 186 | 267464 | for (std::size_t i = 0; i < materials[0].Size(); ++i) { | |
| 187 | 267457 | MaterialShares shares{}; | |
| 188 | 1604742 | for (std::size_t k = 0; k < kMaterialClasses; ++k) { | |
| 189 | 1337285 | shares[k] = static_cast<double>(materials[k].Values()[i]); | |
| 190 | } | ||
| 191 | 267457 | const HapkeParameters mixed = MixPhotometry(shares, photometry); | |
| 192 | 267457 | bands[0].Values()[i] = static_cast<float>(mixed.albedo); | |
| 193 | 267457 | bands[1].Values()[i] = static_cast<float>(mixed.b); | |
| 194 | 267457 | bands[2].Values()[i] = static_cast<float>(mixed.c); | |
| 195 | 267457 | bands[3].Values()[i] = static_cast<float>(mixed.roughness); | |
| 196 | } | ||
| 197 | 14 | return bands; | |
| 198 | } | ||
| 199 | |||
| 200 | } // namespace terraingen | ||
| 201 |