util/noise.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | /// @file noise.cpp | ||
| 3 | /// @brief Seeded Perlin gradient noise and its fBm, and tileable value, Worley and Perlin noise. | ||
| 4 | #include "ernie/util/noise.h" | ||
| 5 | |||
| 6 | #include "ernie/util/math.h" | ||
| 7 | |||
| 8 | #include <glm/gtc/noise.hpp> | ||
| 9 | |||
| 10 | #include <algorithm> | ||
| 11 | #include <cmath> | ||
| 12 | #include <numeric> | ||
| 13 | #include <utility> | ||
| 14 | |||
| 15 | namespace ernie { | ||
| 16 | |||
| 17 | namespace { | ||
| 18 | |||
| 19 | // Perlin's quintic fade, 6t^5 - 15t^4 + 10t^3. | ||
| 20 | 27039 | double Fade(double t) { | |
| 21 | 27039 | return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); | |
| 22 | } | ||
| 23 | |||
| 24 | // The dot of offset (x, y, z) with one of Perlin's 12 edge gradients | ||
| 25 | // (16 with 4 repeated) that `hash` picks. | ||
| 26 | 72104 | double Gradient(int hash, double x, double y, double z) { | |
| 27 | 72104 | const int h = hash & 15; | |
| 28 | 72104 | const double u = h < 8 ? x : y; | |
| 29 | 72104 | const double v = h < 4 ? y : (h == 12 || h == 14 ? x : z); | |
| 30 | 72104 | return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); | |
| 31 | } | ||
| 32 | |||
| 33 | // The classic shader sine hash, matching Hillaire's noise. | ||
| 34 | 82283488 | double SineHash(double n) { | |
| 35 | 82283488 | return Fract(std::sin(n + 1.951) * 43758.5453); | |
| 36 | } | ||
| 37 | |||
| 38 | } // namespace | ||
| 39 | |||
| 40 | 5 | GradientNoise::GradientNoise(std::uint32_t seed) { | |
| 41 | 5 | std::array<std::uint8_t, 256> order{}; | |
| 42 | 5 | std::iota(order.begin(), order.end(), std::uint8_t{0}); | |
| 43 | // A Fisher-Yates shuffle driven by a 32-bit LCG (Numerical | ||
| 44 | // Recipes' constants), the same on every platform. | ||
| 45 | 5 | std::uint32_t state = seed * 2654435761u + 1013904223u; | |
| 46 | 1280 | for (std::size_t i = 255; i > 0; --i) { | |
| 47 | 1275 | state = state * 1664525u + 1013904223u; | |
| 48 | 1275 | const std::size_t j = (state >> 8) % (i + 1); | |
| 49 | 1275 | std::swap(order[i], order[j]); | |
| 50 | } | ||
| 51 | 2565 | for (std::size_t i = 0; i < 512; ++i) { | |
| 52 | 2560 | permutation_[i] = order[i & 255]; | |
| 53 | } | ||
| 54 | 5 | } | |
| 55 | |||
| 56 | 9013 | double GradientNoise::operator()(const glm::dvec3& p) const { | |
| 57 | 9013 | const glm::dvec3 floor = glm::floor(p); | |
| 58 | 9013 | const auto x = static_cast<std::size_t>(static_cast<int>(floor.x) & 255); | |
| 59 | 9013 | const auto y = static_cast<std::size_t>(static_cast<int>(floor.y) & 255); | |
| 60 | 9013 | const auto z = static_cast<std::size_t>(static_cast<int>(floor.z) & 255); | |
| 61 | 9013 | const glm::dvec3 f = p - floor; | |
| 62 | 9013 | const auto& P = permutation_; | |
| 63 | 9013 | const std::size_t a = P[x] + y; | |
| 64 | 9013 | const std::size_t aa = P[a] + z; | |
| 65 | 9013 | const std::size_t ab = P[a + 1] + z; | |
| 66 | 9013 | const std::size_t b = P[x + 1] + y; | |
| 67 | 9013 | const std::size_t ba = P[b] + z; | |
| 68 | 9013 | const std::size_t bb = P[b + 1] + z; | |
| 69 | 9013 | const std::array<double, 8> corners{Gradient(P[aa], f.x, f.y, f.z), | |
| 70 | 9013 | Gradient(P[ba], f.x - 1, f.y, f.z), | |
| 71 | 9013 | Gradient(P[ab], f.x, f.y - 1, f.z), | |
| 72 | 9013 | Gradient(P[bb], f.x - 1, f.y - 1, f.z), | |
| 73 | 9013 | Gradient(P[aa + 1], f.x, f.y, f.z - 1), | |
| 74 | 9013 | Gradient(P[ba + 1], f.x - 1, f.y, f.z - 1), | |
| 75 | 9013 | Gradient(P[ab + 1], f.x, f.y - 1, f.z - 1), | |
| 76 | 9013 | Gradient(P[bb + 1], f.x - 1, f.y - 1, f.z - 1)}; | |
| 77 | 9013 | return Trilinear(corners, glm::dvec3(Fade(f.x), Fade(f.y), Fade(f.z))); | |
| 78 | } | ||
| 79 | |||
| 80 | 1002 | double GradientNoise::Fbm(const glm::dvec3& p, int octaves, double lacunarity, double gain) const { | |
| 81 | 1002 | double sum = 0.0; | |
| 82 | 1002 | double total = 0.0; | |
| 83 | 1002 | double amplitude = 1.0; | |
| 84 | 1002 | double frequency = 1.0; | |
| 85 | 6005 | for (int octave = 0; octave < octaves; ++octave) { | |
| 86 | 5003 | sum += amplitude * (*this)(p * frequency); | |
| 87 | 5003 | total += amplitude; | |
| 88 | 5003 | amplitude *= gain; | |
| 89 | 5003 | frequency *= lacunarity; | |
| 90 | } | ||
| 91 | 1002 | return total > 0.0 ? sum / total : 0.0; | |
| 92 | } | ||
| 93 | |||
| 94 | 10285436 | double ValueNoise(const glm::dvec3& p) { | |
| 95 | 10285436 | const glm::dvec3 cell = glm::floor(p); | |
| 96 | 10285436 | glm::dvec3 f = p - cell; | |
| 97 | 10285436 | f = f * f * (3.0 - 2.0 * f); | |
| 98 | // Lattice points hash as n = x + 57 y + 113 z. | ||
| 99 | 10285436 | const double n = cell.x + cell.y * 57.0 + 113.0 * cell.z; | |
| 100 | 10285436 | const std::array<double, 8> corners{ | |
| 101 | 10285436 | SineHash(n), SineHash(n + 1.0), SineHash(n + 57.0), SineHash(n + 58.0), | |
| 102 | 10285436 | SineHash(n + 113.0), SineHash(n + 114.0), SineHash(n + 170.0), SineHash(n + 171.0)}; | |
| 103 | 10285436 | return Trilinear(corners, f); | |
| 104 | } | ||
| 105 | |||
| 106 | 380868 | double TileableWorley(const glm::dvec3& p, double cells) { | |
| 107 | 380868 | const glm::dvec3 c = p * cells; | |
| 108 | 380868 | const glm::dvec3 base = glm::floor(c); | |
| 109 | 380868 | double nearest = 1e10; | |
| 110 | 1523472 | for (int i = -1; i <= 1; ++i) { | |
| 111 | 4570416 | for (int j = -1; j <= 1; ++j) { | |
| 112 | 13711248 | for (int k = -1; k <= 1; ++k) { | |
| 113 | 10283436 | const glm::dvec3 t = base + glm::dvec3(i, j, k); | |
| 114 | // The cell's point, offset alike in all axes (as Hillaire's), its lattice wrapped | ||
| 115 | // so the noise tiles. | ||
| 116 | 10283436 | const double offset = | |
| 117 | 10283436 | ValueNoise(glm::dvec3(Mod(t.x, cells), Mod(t.y, cells), Mod(t.z, cells))); | |
| 118 | 10283436 | const glm::dvec3 d = c - t - offset; | |
| 119 | 11864709 | nearest = std::min(nearest, glm::dot(d, d)); | |
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
| 123 | 380868 | return std::clamp(nearest, 0.0, 1.0); | |
| 124 | } | ||
| 125 | |||
| 126 | 37864 | double WorleyFbm(const glm::dvec3& p, double cells) { | |
| 127 | 37864 | const double a = 1.0 - TileableWorley(p, cells * 2.0); | |
| 128 | 37864 | const double b = 1.0 - TileableWorley(p, cells * 4.0); | |
| 129 | 37864 | const double c = 1.0 - TileableWorley(p, cells * 8.0); | |
| 130 | 37864 | const double d = 1.0 - TileableWorley(p, cells * 16.0); | |
| 131 | 37864 | const double f0 = 0.625 * a + 0.25 * b + 0.125 * c; | |
| 132 | 37864 | const double f1 = 0.625 * b + 0.25 * c + 0.125 * d; | |
| 133 | 37864 | const double f2 = 0.75 * c + 0.25 * d; | |
| 134 | 37864 | return 0.625 * f0 + 0.25 * f1 + 0.125 * f2; | |
| 135 | } | ||
| 136 | |||
| 137 | 67593 | double TileablePerlin(const glm::dvec3& p, const glm::dvec3& frequency, int octaves) { | |
| 138 | 67593 | double sum = 0.0; | |
| 139 | 67593 | double weightSum = 0.0; | |
| 140 | 67593 | double weight = 1.0; | |
| 141 | 67593 | glm::dvec3 f = frequency; | |
| 142 | 352301 | for (int i = 0; i < octaves; ++i) { | |
| 143 | 284708 | const glm::vec4 point(glm::vec3(p * f), 0.0f); | |
| 144 | 284708 | const glm::vec4 period(glm::vec3(f), 1.0f); | |
| 145 | 284708 | sum += double(glm::perlin(point, period)) * weight; | |
| 146 | 284708 | weightSum += weight; | |
| 147 | 284708 | weight *= 0.5; | |
| 148 | 284708 | f *= 2.0; | |
| 149 | } | ||
| 150 | 67593 | return sum / weightSum; | |
| 151 | } | ||
| 152 | |||
| 153 | 6144 | glm::dvec3 TileablePerlin3(const glm::dvec3& p) { | |
| 154 | 6144 | const glm::dvec3 frequency(12.0); | |
| 155 | 6144 | return {TileablePerlin(p, frequency, 3), | |
| 156 | 6144 | TileablePerlin(glm::dvec3(p.y - 19.1, p.z + 33.4, p.x + 47.2), frequency, 3), | |
| 157 | 6144 | TileablePerlin(glm::dvec3(p.z + 74.2, p.x - 124.5, p.y + 99.4), frequency, 3)}; | |
| 158 | } | ||
| 159 | |||
| 160 | } // namespace ernie | ||
| 161 |