TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
86 / 86
100.0%
Branches:
44 / 44
100.0%
Functions:
12 / 12, 1 excl
100.0%

noise/noise_layers.h
Line Branch Exec Source
1 /// @file
2 /// @brief Stacks of fractal noise layers with domain warps and blend modes.
3 #pragma once
4
5 #include "terraingen/noise/fractal.h"
6
7 namespace terraingen {
8
9 /// How a noise layer combines with the height accumulated from the layers before it.
10 enum class LayerBlend : uint32_t {
11 Add = 0, ///< height + layer
12 Max = 1, ///< max(height, layer)
13 Min = 2, ///< min(height, layer)
14 Mask = 3, ///< height * (layer / amplitude + 1) / 2: the layer modulates roughness by region.
15 };
16
17 /// Number of `LayerBlend` values.
18 static const uint32_t kLayerBlendCount = 4;
19
20 /// Most layers a synthetic terrain may stack; a fixed bound keeps layers uploadable to GPUs.
21 static const uint32_t kMaxNoiseLayers = 8;
22
23 /// One layer of a synthetic terrain: a fractal, an optional domain warp and a blend mode.
24 struct NoiseLayer {
25 21 bool enabled = true; ///< Whether the layer contributes.
26 20 FractalParams fractal; ///< The layer's fractal; its amplitude scales the layer.
27 19 LayerBlend blend = LayerBlend::Add; ///< How it combines with the layers before it.
28 17 float warpAmplitude = 0.0f; ///< Domain warp displacement in metres; 0 disables warping.
29 16 float warpWavelength = 500.0f; ///< Wavelength of the warp field in metres.
30
31 #ifndef __SLANG__
32
10/10
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 20 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 19 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 17 times.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 16 times.
✓ Branch 9 taken 1 time.
✓ Branch 10 taken 15 times.
21 bool operator==(const NoiseLayer&) const = default;
33 #endif
34 };
35
36 /// Up to `kMaxNoiseLayers` layers in a fixed-size block, the form GPU kernels receive.
37 struct NoiseLayers {
38 NoiseLayer layers[kMaxNoiseLayers]; ///< The layers; only the first `count` are used.
39 uint32_t count = 0; ///< Number of layers used, at most `kMaxNoiseLayers`.
40 };
41
42 /// A 2D domain warp offset.
43 struct WarpOffset2 {
44 float dx = 0.0f; ///< Offset in x, in metres.
45 float dy = 0.0f; ///< Offset in y, in metres.
46 };
47
48 namespace detail {
49
50 /// Mixed into a layer's seed to seed its warp fields apart from its own noise. Frozen for
51 /// reproducibility.
52 static const uint32_t kWarpSeedSalt = 0x5bd1e995u;
53
54 } // namespace detail
55
56 /// The fractal of the first warp field of `layer`: 4-octave Perlin fBm of the layer's warp
57 /// wavelength and amplitude. Each further field is seeded with `PcgHash` of the one before.
58 838181 inline FractalParams WarpParams(NoiseLayer layer) {
59 838181 FractalParams warp = {};
60 838181 warp.wavelength = layer.warpWavelength;
61 838181 warp.amplitude = layer.warpAmplitude;
62 838181 warp.octaves = 4;
63 838181 warp.seed = PcgHash(layer.fractal.seed ^ detail::kWarpSeedSalt);
64 838181 return warp;
65 }
66
67 /// A layer's domain warp offset from two independent warp fields (`WarpParams`).
68 /// @param x, y World coordinates in metres.
69 /// @return The offset, each component bounded by `layer.warpAmplitude` metres.
70 837920 inline WarpOffset2 DomainWarp2(float x, float y, NoiseLayer layer) {
71 837920 FractalParams warp = WarpParams(layer);
72 837920 WarpOffset2 offset = {};
73
1/1
✓ Branch 1 taken 837920 times.
837920 offset.dx = Fractal2(x, y, warp);
74 837920 warp.seed = PcgHash(warp.seed);
75
1/1
✓ Branch 1 taken 837920 times.
837920 offset.dy = Fractal2(x, y, warp);
76 837920 return offset;
77 }
78
79 /// A layer's normalized value, warped when `warpAmplitude` > 0, before amplitude and
80 /// blending.
81 /// @param x, y World coordinates in metres.
82 /// @return The value in [-1, 1].
83 81934947 inline float EvaluateNoiseLayer(float x, float y, NoiseLayer layer) {
84 81934947 FractalParams unit = layer.fractal;
85 81934947 unit.amplitude = 1.0f;
86
2/2
✓ Branch 0 taken 837918 times.
✓ Branch 1 taken 81097029 times.
81934947 if (layer.warpAmplitude > 0.0f) {
87
1/1
✓ Branch 1 taken 837918 times.
837918 const WarpOffset2 offset = DomainWarp2(x, y, layer);
88
1/1
✓ Branch 1 taken 837918 times.
837918 return Fractal2(x + offset.dx, y + offset.dy, unit);
89 }
90
1/1
✓ Branch 1 taken 81097029 times.
81097029 return Fractal2(x, y, unit);
91 }
92
93 namespace detail {
94
95 /// The height of a stack of layers so far.
96 struct NoiseStack {
97 float height = 0.0f; ///< Height so far, in metres.
98 bool started = false; ///< Whether any layer has set the height yet.
99 };
100
101 /// Combines an enabled layer with a stack; the first layer sets the height.
102 /// @param value The layer's normalized value, in [-1, 1].
103 83542653 inline NoiseStack BlendNoiseLayer(NoiseStack stack, NoiseLayer layer, float value) {
104 83542653 const float scaled = layer.fractal.amplitude * value;
105
2/2
✓ Branch 0 taken 81085539 times.
✓ Branch 1 taken 2457114 times.
83542653 if (!stack.started) {
106 81085539 stack.height = scaled;
107 81085539 stack.started = true;
108 81085539 return stack;
109 }
110
4/4
✓ Branch 0 taken 4098 times.
✓ Branch 1 taken 8194 times.
✓ Branch 2 taken 829641 times.
✓ Branch 3 taken 1615181 times.
2457114 switch (layer.blend) {
111 4098 case LayerBlend::Max:
112 4098 stack.height = max(stack.height, scaled);
113 4098 break;
114 8194 case LayerBlend::Min:
115 8194 stack.height = min(stack.height, scaled);
116 8194 break;
117 829641 case LayerBlend::Mask:
118 829641 stack.height *= 0.5f * (value + 1.0f);
119 829641 break;
120 1615181 default:
121 1615181 stack.height += scaled;
122 1615181 break;
123 }
124 2457114 return stack;
125 }
126
127 /// Adds a layer at a 2D point to a stack, if the layer is enabled.
128 /// @param x, y World coordinates in metres.
129 81906372 inline NoiseStack AddNoiseLayer2(NoiseStack stack, float x, float y, NoiseLayer layer) {
130
2/2
✓ Branch 0 taken 4202 times.
✓ Branch 1 taken 81902170 times.
81906372 if (!layer.enabled) {
131 4202 return stack;
132 }
133 81902170 return BlendNoiseLayer(stack, layer, EvaluateNoiseLayer(x, y, layer));
134 }
135
136 } // namespace detail
137
138 /// Height of stacked layers: the first enabled layer sets it (`amplitude * value`), each
139 /// later enabled layer combines by its `blend` mode.
140 /// @param x, y World coordinates in metres.
141 /// @return Height in metres; 0 if no layer is enabled.
142 4117 inline float EvaluateNoiseLayers(float x, float y, NoiseLayers layers) {
143 4117 detail::NoiseStack stack = {};
144
2/2
✓ Branch 0 taken 32828 times.
✓ Branch 1 taken 4117 times.
36945 for (uint32_t i = 0; i < layers.count; ++i) {
145
1/1
✓ Branch 1 taken 32828 times.
32828 stack = detail::AddNoiseLayer2(stack, x, y, layers.layers[i]);
146 }
147 4117 return stack.height;
148 }
149
150 #ifndef __SLANG__
151 /// `EvaluateNoiseLayers` of any number of layers, including more than a GPU block holds.
152 /// @param x, y World coordinates in metres.
153 /// @param layers The first of `count` layers.
154 /// @return Height in metres; 0 if no layer is enabled.
155 79954502 inline float EvaluateNoiseLayers(float x, float y, const NoiseLayer* layers, std::size_t count) {
156 79954502 detail::NoiseStack stack;
157
2/2
✓ Branch 0 taken 81873544 times.
✓ Branch 1 taken 79954502 times.
161828046 for (std::size_t i = 0; i < count; ++i) {
158
1/1
✓ Branch 1 taken 81873544 times.
81873544 stack = detail::AddNoiseLayer2(stack, x, y, layers[i]);
159 }
160 79954502 return stack.height;
161 }
162
163 /// A 3D domain warp offset.
164 struct WarpOffset3 {
165 double dx = 0.0; ///< Offset in x, in metres.
166 double dy = 0.0; ///< Offset in y, in metres.
167 double dz = 0.0; ///< Offset in z, in metres.
168 };
169
170 /// `DomainWarp2` in 3D: three warp fields (`WarpParams`) evaluated with `Fractal3`.
171 /// @param x, y, z World coordinates in metres.
172 /// @return The offset, each component bounded by `layer.warpAmplitude` metres.
173 260 inline WarpOffset3 DomainWarp3(double x, double y, double z, NoiseLayer layer) {
174 260 FractalParams warp = WarpParams(layer);
175 260 WarpOffset3 offset;
176
1/1
✓ Branch 1 taken 260 times.
260 offset.dx = static_cast<double>(Fractal3(x, y, z, warp));
177 260 warp.seed = PcgHash(warp.seed);
178
1/1
✓ Branch 1 taken 260 times.
260 offset.dy = static_cast<double>(Fractal3(x, y, z, warp));
179 260 warp.seed = PcgHash(warp.seed);
180
1/1
✓ Branch 1 taken 260 times.
260 offset.dz = static_cast<double>(Fractal3(x, y, z, warp));
181 520 return offset;
182 }
183
184 /// `EvaluateNoiseLayer` in 3D, with `Fractal3` and `DomainWarp3`.
185 /// @param x, y, z World coordinates in metres.
186 /// @return The layer's normalized value in [-1, 1].
187 1640484 inline float EvaluateNoiseLayer3(double x, double y, double z, NoiseLayer layer) {
188 1640484 FractalParams unit = layer.fractal;
189 1640484 unit.amplitude = 1.0f;
190
2/2
✓ Branch 0 taken 259 times.
✓ Branch 1 taken 1640225 times.
1640484 if (layer.warpAmplitude > 0.0f) {
191
1/1
✓ Branch 1 taken 259 times.
259 const WarpOffset3 offset = DomainWarp3(x, y, z, layer);
192
1/1
✓ Branch 1 taken 259 times.
259 return Fractal3(x + offset.dx, y + offset.dy, z + offset.dz, unit);
193 }
194
1/1
✓ Branch 1 taken 1640225 times.
1640225 return Fractal3(x, y, z, unit);
195 }
196
197 namespace detail {
198
199 /// Adds a layer at a 3D point to a stack, if the layer is enabled.
200 /// @param x, y, z World coordinates in metres.
201 1640485 inline NoiseStack AddNoiseLayer3(NoiseStack stack, double x, double y, double z, NoiseLayer layer) {
202
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1640483 times.
1640485 if (!layer.enabled) {
203 2 return stack;
204 }
205 1640483 return BlendNoiseLayer(stack, layer, EvaluateNoiseLayer3(x, y, z, layer));
206 }
207
208 } // namespace detail
209
210 /// `EvaluateNoiseLayers` in 3D with `EvaluateNoiseLayer3`, for noise on a sphere.
211 /// @param x, y, z World coordinates in metres.
212 /// @param layers The first of `count` layers.
213 /// @return Height in metres; 0 if no layer is enabled.
214 1126923 inline float EvaluateNoiseLayers3(double x, double y, double z, const NoiseLayer* layers,
215 std::size_t count) {
216 1126923 detail::NoiseStack stack;
217
2/2
✓ Branch 0 taken 1640485 times.
✓ Branch 1 taken 1126923 times.
2767408 for (std::size_t i = 0; i < count; ++i) {
218
1/1
✓ Branch 1 taken 1640485 times.
1640485 stack = detail::AddNoiseLayer3(stack, x, y, z, layers[i]);
219 }
220 1126923 return stack.height;
221 }
222 #endif
223
224 } // namespace terraingen
225