albedo/rock_albedo.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/albedo/rock_albedo.h" | ||
| 2 | |||
| 3 | #include "terraingen/random/philox.h" | ||
| 4 | #include "terraingen/random/value_distribution.h" | ||
| 5 | #include "terraingen/util/parallel.h" | ||
| 6 | |||
| 7 | #include <algorithm> | ||
| 8 | #include <bit> | ||
| 9 | #include <cmath> | ||
| 10 | #include <stdexcept> | ||
| 11 | #include <utility> | ||
| 12 | |||
| 13 | namespace terraingen { | ||
| 14 | |||
| 15 | namespace { | ||
| 16 | |||
| 17 | constexpr std::uint32_t kRockStream = 0x524F434Bu; | ||
| 18 | |||
| 19 | /// Multiplies every band of `value` by 1 + `share` (`ratio` - 1); NaN stays NaN. | ||
| 20 | 169188 | void Brighten(AlbedoSample& value, std::size_t bands, double share, double ratio) { | |
| 21 | 169188 | if (share == 0.0) { | |
| 22 | 3906 | return; | |
| 23 | } | ||
| 24 | 165282 | const double factor = 1.0 + share * (ratio - 1.0); | |
| 25 | 330692 | for (std::size_t band = 0; band < bands; ++band) { | |
| 26 | 165410 | if (!std::isnan(value[band])) { | |
| 27 | 165410 | value[band] = static_cast<float>(factor * static_cast<double>(value[band])); | |
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | } // namespace | ||
| 33 | |||
| 34 | 15 | void ValidateRockAlbedo(const RockAlbedoOptions& options) { | |
| 35 | 15 | if (!(options.reflectanceRatio > 0.0 && std::isfinite(options.reflectanceRatio))) { | |
| 36 | 3 | throw std::invalid_argument("Rock reflectance ratio must be positive and finite"); | |
| 37 | } | ||
| 38 | 12 | } | |
| 39 | |||
| 40 | 369258 | double RockShare(double resolved, const UnresolvedCover& unresolved, double normal) { | |
| 41 | 369258 | double drawn = 0.0; | |
| 42 | 369258 | if (unresolved.mean > 0.0) { | |
| 43 | 365159 | const double s2 = std::log1p(unresolved.variance / (unresolved.mean * unresolved.mean)); | |
| 44 | drawn = | ||
| 45 | 365159 | std::fmin(1.0, std::exp(std::log(unresolved.mean) - 0.5 * s2 + std::sqrt(s2) * normal)); | |
| 46 | } | ||
| 47 | 369258 | const double kept = std::clamp(resolved, 0.0, 1.0); | |
| 48 | 369258 | return kept + (1.0 - kept) * drawn; | |
| 49 | } | ||
| 50 | |||
| 51 | 8 | RockAlbedo::RockAlbedo(RockAlbedoOptions options, std::vector<RockSource> sources) | |
| 52 | 8 | : options_(options), sources_(std::move(sources)) { | |
| 53 | 8 | ValidateRockAlbedo(options_); | |
| 54 | 14 | for (const RockSource& source : sources_) { | |
| 55 | 8 | if (!source.layer || !source.ground) { | |
| 56 | throw std::invalid_argument( | ||
| 57 | 2 | "A rock albedo source needs a boulder layer and its ground"); | |
| 58 | } | ||
| 59 | } | ||
| 60 | 10 | } | |
| 61 | |||
| 62 | 165156 | double RockAlbedo::Draw(double x, double y, std::size_t source) const { | |
| 63 | 165156 | const auto xs = std::bit_cast<std::uint64_t>(x); | |
| 64 | 165156 | const auto ys = std::bit_cast<std::uint64_t>(y); | |
| 65 | const U32x4 bits = | ||
| 66 | 165156 | Philox4x32({static_cast<std::uint32_t>(xs), static_cast<std::uint32_t>(xs >> 32), | |
| 67 | 165156 | static_cast<std::uint32_t>(ys), static_cast<std::uint32_t>(ys >> 32)}, | |
| 68 | 165156 | {options_.seed ^ kRockStream, static_cast<std::uint32_t>(source)}); | |
| 69 | 330312 | return StandardNormal(std::ldexp(static_cast<double>(bits.x), -32), | |
| 70 | 330312 | std::ldexp(static_cast<double>(bits.y), -32)); | |
| 71 | } | ||
| 72 | |||
| 73 | 132 | double RockAlbedo::ShareAt(double x, double y, const AlbedoContext& context) const { | |
| 74 | 132 | ValidateAlbedoContext(context); | |
| 75 | 132 | double bare = 1.0; | |
| 76 | 264 | for (std::size_t n = 0; n < sources_.size(); ++n) { | |
| 77 | 132 | const RockSource& source = sources_[n]; | |
| 78 | const double resolved = | ||
| 79 | 132 | source.layer->MaterialCoverAt(x, y, context.spacing, source.ground.get()); | |
| 80 | const UnresolvedCover unresolved = | ||
| 81 | 132 | options_.unresolved | |
| 82 | 132 | ? source.layer->UnresolvedCoverAt(x, y, context.spacing, source.ground.get()) | |
| 83 | 1 | : UnresolvedCover{}; | |
| 84 | 132 | bare *= 1.0 - RockShare(resolved, unresolved, options_.unresolved ? Draw(x, y, n) : 0.0); | |
| 85 | } | ||
| 86 | 132 | return 1.0 - bare; | |
| 87 | } | ||
| 88 | |||
| 89 | 131 | void RockAlbedo::ApplyAt(AlbedoSample& value, std::size_t bands, double x, double y, | |
| 90 | const AlbedoContext& context) const { | ||
| 91 | 131 | Brighten(value, bands, ShareAt(x, y, context), options_.reflectanceRatio); | |
| 92 | 131 | } | |
| 93 | |||
| 94 | 5 | void RockAlbedo::Apply(std::vector<Raster<float>>& bands, const AlbedoContext& context) const { | |
| 95 | 5 | ValidateAlbedoContext(context); | |
| 96 | 5 | if (bands.empty() || sources_.empty()) { | |
| 97 | 1 | return; | |
| 98 | } | ||
| 99 | 4 | const RasterGrid grid = RasterGrid::FromRaster(bands[0]); | |
| 100 | 4 | std::vector<double> bare(grid.width * grid.height, 1.0); | |
| 101 | 9 | for (std::size_t n = 0; n < sources_.size(); ++n) { | |
| 102 | 5 | const RockSource& source = sources_[n]; | |
| 103 | 5 | const Raster<double> resolved = source.layer->MaterialCoverOver( | |
| 104 | 5 | grid, context.spacing, source.ground.get(), context.threads); | |
| 105 | const std::vector<UnresolvedCover> unresolved = | ||
| 106 | 9 | options_.unresolved ? source.layer->UnresolvedCoverOver( | |
| 107 | 8 | grid, context.spacing, source.ground.get(), context.threads) | |
| 108 | 6 | : std::vector<UnresolvedCover>(bare.size()); | |
| 109 | 5 | ParallelFor(grid.height, context.threads, [&](std::size_t row) { | |
| 110 | 169666 | for (std::size_t col = 0; col < grid.width; ++col) { | |
| 111 | 169121 | const std::size_t cell = row * grid.width + col; | |
| 112 | 169121 | const Vec2d centre = bands[0].CellCenter(col, row); | |
| 113 | 169121 | bare[cell] *= | |
| 114 | 169121 | 1.0 - RockShare(resolved(col, row), unresolved[cell], | |
| 115 | 169121 | options_.unresolved ? Draw(centre.x, centre.y, n) : 0.0); | |
| 116 | } | ||
| 117 | 545 | }); | |
| 118 | 5 | } | |
| 119 | 4 | ParallelFor(grid.height, context.threads, [&](std::size_t row) { | |
| 120 | 169594 | for (std::size_t col = 0; col < grid.width; ++col) { | |
| 121 | 169057 | AlbedoSample value{}; | |
| 122 | 338242 | for (std::size_t band = 0; band < bands.size(); ++band) { | |
| 123 | 169185 | value[band] = bands[band](col, row); | |
| 124 | } | ||
| 125 | 169057 | Brighten(value, bands.size(), 1.0 - bare[row * grid.width + col], | |
| 126 | 169057 | options_.reflectanceRatio); | |
| 127 | 338242 | for (std::size_t band = 0; band < bands.size(); ++band) { | |
| 128 | 169185 | bands[band](col, row) = value[band]; | |
| 129 | } | ||
| 130 | } | ||
| 131 | 537 | }); | |
| 132 | 4 | } | |
| 133 | |||
| 134 | } // namespace terraingen | ||
| 135 |