GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
102 / 102
100.0%
Branches:
28 / 30
93.3%
Functions:
11 / 11
100.0%

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
2/2
✓ Branch 0 taken 36110 times.
✓ Branch 1 taken 35994 times.
72104 const double u = h < 8 ? x : y;
29
4/4
✓ Branch 0 taken 54510 times.
✓ Branch 1 taken 17594 times.
✓ Branch 2 taken 45423 times.
✓ Branch 3 taken 9087 times.
72104 const double v = h < 4 ? y : (h == 12 || h == 14 ? x : z);
30
4/4
✓ Branch 0 taken 36260 times.
✓ Branch 1 taken 35844 times.
✓ Branch 2 taken 35625 times.
✓ Branch 3 taken 36479 times.
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
2/2
✓ Branch 0 taken 1275 times.
✓ Branch 1 taken 5 times.
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
2/2
✓ Branch 0 taken 2560 times.
✓ Branch 1 taken 5 times.
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
2/2
✓ Branch 0 taken 5003 times.
✓ Branch 1 taken 1002 times.
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
1/2
✓ Branch 0 taken 1002 times.
✗ Branch 1 not taken.
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
2/2
✓ Branch 0 taken 1142604 times.
✓ Branch 1 taken 380868 times.
1523472 for (int i = -1; i <= 1; ++i) {
111
2/2
✓ Branch 0 taken 3427812 times.
✓ Branch 1 taken 1142604 times.
4570416 for (int j = -1; j <= 1; ++j) {
112
2/2
✓ Branch 0 taken 10283436 times.
✓ Branch 1 taken 3427812 times.
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
2/2
✓ Branch 1 taken 1581273 times.
✓ Branch 2 taken 8702163 times.
11864709 nearest = std::min(nearest, glm::dot(d, d));
120 }
121 }
122 }
123
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 380868 times.
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
2/2
✓ Branch 0 taken 284708 times.
✓ Branch 1 taken 67593 times.
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