Skip to content

Commit e5a23a4

Browse files
committed
WIP Perlin noise support
1 parent 0f5fb7c commit e5a23a4

File tree

2 files changed

+253
-0
lines changed

2 files changed

+253
-0
lines changed

core/src/math.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub mod approx;
3030
pub mod color;
3131
pub mod float;
3232
pub mod mat;
33+
pub mod noise;
3334
pub mod rand;
3435
pub mod space;
3536
pub mod spline;

core/src/math/noise.rs

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
//! Procedural noise generation.
2+
3+
use super::{
4+
spline::smoothstep,
5+
vary::lerp,
6+
vec::{splat, vec2, vec3, Vec2, Vec3, Vector},
7+
};
8+
9+
/// Returns the Perlin noise value corresponding to the 2D point.
10+
pub fn perlin2(pt: Vec2) -> f32 {
11+
use super::float::f32;
12+
let f = pt - pt.map(f32::floor);
13+
let [fx, fy] = f.0;
14+
15+
let [g00, g01, g10, g11] = grads2(pt);
16+
17+
let d00 = g00.dot(&vec2(fx, fy));
18+
let d01 = g01.dot(&vec2(fx, fy - 1.0));
19+
let d10 = g10.dot(&vec2(fx - 1.0, fy));
20+
let d11 = g11.dot(&vec2(fx - 1.0, fy - 1.0));
21+
22+
let t = f.map(smoothstep);
23+
let (a, b) = lerp(t.x(), (d00, d01), (d10, d11));
24+
lerp(t.y(), a, b)
25+
}
26+
27+
/// Returns the Perlin gradient vector corresponding to the 2D point.
28+
///
29+
/// The vector is computed by taking the gradient vectors of all four
30+
/// surrounding grid points and smoothly interpolating between them.
31+
pub fn perlin2v(pt: Vec2) -> Vec2 {
32+
use super::float::f32;
33+
let fract = pt - pt.map(f32::floor);
34+
35+
let [g00, g01, g10, g11] = grads2(pt);
36+
37+
let t = fract.map(smoothstep);
38+
let (a, b) = lerp(t.x(), (g00, g01), (g10, g11));
39+
lerp(t.y(), a, b)
40+
}
41+
42+
/// Returns the Perlin noise value corresponding to the 3D point.
43+
pub fn perlin3(pt: Vec3) -> f32 {
44+
use super::float::f32;
45+
let pt0 = pt.map(f32::floor);
46+
let f = pt - pt0;
47+
let i_f: Vec3 = f - splat(1.0);
48+
49+
let [fx, fy, fz] = f.0;
50+
let [ifx, ify, ifz] = i_f.0;
51+
52+
let [g000, g001, g010, g011, g100, g101, g110, g111] = grads3(pt0);
53+
54+
let d000 = g000.dot(&vec3(fx, fy, fz));
55+
let d001 = g001.dot(&vec3(fx, fy, ifz));
56+
let d010 = g010.dot(&vec3(fx, ify, fz));
57+
let d011 = g011.dot(&vec3(fx, ify, ifz));
58+
let d100 = g100.dot(&vec3(ifx, fy, fz));
59+
let d101 = g101.dot(&vec3(ifx, fy, ifz));
60+
let d110 = g110.dot(&vec3(ifx, ify, fz));
61+
let d111 = g111.dot(&vec3(ifx, ify, ifz));
62+
63+
let t: Vec3 = f.map(smoothstep);
64+
65+
let x0 = Vector::<_>::new([d000, d010, d001, d011]);
66+
let x1 = Vector::<_>::new([d100, d110, d101, d111]);
67+
let [y0, y1, y2, y3] = lerp(t.x(), x0, x1).0;
68+
let (z0, z1) = lerp(t.y(), (y0, y2), (y1, y3));
69+
lerp(t.z(), z0, z1)
70+
}
71+
72+
/// Returns the Perlin gradient vector corresponding to the 3D point.
73+
///
74+
/// The vector is computed by taking the gradient vectors of all four
75+
/// surrounding grid points and smoothly interpolating between them.
76+
pub fn perlin3v(pt: Vec3) -> Vec3 {
77+
use super::float::f32;
78+
let fract = pt - pt.map(f32::floor);
79+
80+
let [g000, g001, g010, g011, g100, g101, g110, g111] = grads3(pt);
81+
82+
let t: Vec3 = fract.map(smoothstep);
83+
84+
let x0 = ((g000, g010), (g001, g011));
85+
let x1 = ((g100, g110), (g101, g111));
86+
let ((y0, y1), (y2, y3)) = lerp(t.x(), x0, x1);
87+
let (z0, z1) = lerp(t.y(), (y0, y2), (y1, y3));
88+
lerp(t.z(), z0, z1)
89+
}
90+
91+
fn perm(x: i32) -> i32 {
92+
PERM[(x & 0xFF) as usize] as i32
93+
}
94+
95+
fn grad2(x: f32, y: f32) -> Vec2 {
96+
let perm = perm(x as i32 + perm(y as i32));
97+
GRADS_2[perm as usize & 0x7]
98+
}
99+
100+
fn grads2(pt: Vec2) -> [Vec2; 4] {
101+
use super::float::f32;
102+
let [x0, y0] = pt.map(f32::floor).0;
103+
104+
let g00 = grad2(x0, y0);
105+
let g01 = grad2(x0, y0 + 1.0);
106+
let g10 = grad2(x0 + 1.0, y0);
107+
let g11 = grad2(x0 + 1.0, y0 + 1.0);
108+
109+
[g00, g01, g10, g11]
110+
}
111+
112+
fn grad3(x: f32, y: f32, z: f32) -> Vec3 {
113+
let perm = perm(x as i32 + perm(y as i32 + perm(z as i32)));
114+
GRADS_3[perm as usize & 0xF]
115+
}
116+
117+
fn grads3(pt0: Vec3) -> [Vec3; 8] {
118+
let pt1 = pt0 + splat(1.0);
119+
let [x0, y0, z0] = pt0.0;
120+
let [x1, y1, z1] = pt1.0;
121+
122+
//
123+
// 011 +--------------+ 111
124+
// / | / |
125+
// / | / |
126+
// 010 +--------------+ 110 |
127+
// | | | |
128+
// | +--------|-----+ 101
129+
// | / | /
130+
// | / | /
131+
// 000 +--------------+ 100
132+
//
133+
134+
let g000 = grad3(x0, y0, z0);
135+
let g001 = grad3(x0, y0, z1);
136+
let g010 = grad3(x0, y1, z0);
137+
let g011 = grad3(x0, y1, z1);
138+
let g100 = grad3(x1, y0, z0);
139+
let g101 = grad3(x1, y0, z1);
140+
let g110 = grad3(x1, y1, z0);
141+
let g111 = grad3(x1, y1, z1);
142+
143+
[g000, g001, g010, g011, g100, g101, g110, g111]
144+
}
145+
146+
const A: f32 = 1.237;
147+
const B: f32 = 0.513;
148+
/// Gradient vectors for 2D noise.
149+
static GRADS_2: [Vec2; 8] = [
150+
vec2(A, B),
151+
vec2(B, A),
152+
vec2(-B, A),
153+
vec2(-A, B),
154+
vec2(-A, -B),
155+
vec2(-B, -A),
156+
vec2(B, -A),
157+
vec2(A, -B),
158+
];
159+
160+
/// Gradient vectors for 3D noise.
161+
static GRADS_3: [Vec3; 16] = [
162+
//
163+
vec3(0.0, -1.0, -1.0),
164+
vec3(0.0, -1.0, 1.0),
165+
vec3(0.0, 1.0, -1.0),
166+
vec3(0.0, 1.0, 1.0),
167+
//
168+
vec3(-1.0, 0.0, -1.0),
169+
vec3(-1.0, 0.0, 1.0),
170+
vec3(1.0, 0.0, -1.0),
171+
vec3(1.0, 0.0, 1.0),
172+
//
173+
vec3(-1.0, -1.0, 0.0),
174+
vec3(-1.0, 1.0, 0.0),
175+
vec3(1.0, -1.0, 0.0),
176+
vec3(1.0, 1.0, 0.0),
177+
//
178+
vec3(1.0, 1.0, 0.0),
179+
vec3(-1.0, 1.0, 0.0),
180+
vec3(0.0, -1.0, 1.0),
181+
vec3(0.0, -1.0, -1.0),
182+
];
183+
184+
/// Permutation table for calculating a pseudo-random index for each grid point.
185+
#[rustfmt::skip]
186+
static PERM: [u8; 256] = [
187+
156, 2, 157, 90, 75, 199, 55, 167, 62, 92, 101, 253, 66, 134, 113, 83,
188+
1, 136, 78, 106, 254, 105, 248, 176, 234, 5, 195, 226, 49, 71, 87, 44,
189+
122, 94, 219, 140, 72, 159, 237, 212, 8, 162, 200, 124, 125, 69, 165, 74,
190+
245, 42, 89, 216, 158, 108, 238, 184, 217, 73, 126, 210, 14, 111, 19, 188,
191+
186, 45, 38, 223, 35, 112, 214, 26, 145, 95, 99, 193, 250, 189, 152, 182,
192+
166, 247, 148, 213, 168, 70, 96, 249, 127, 132, 4, 137, 41, 60, 102, 28,
193+
27, 240, 227, 155, 211, 230, 9, 80, 178, 3, 68, 153, 143, 84, 179, 181,
194+
12, 97, 103, 16, 225, 146, 63, 82, 203, 175, 163, 147, 11, 116, 185, 215,
195+
57, 120, 208, 129, 115, 198, 37, 201, 39, 98, 20, 183, 56, 118, 109, 142,
196+
138, 65, 117, 114, 160, 25, 43, 191, 204, 161, 22, 251, 139, 79, 131, 231,
197+
76, 0, 205, 206, 244, 51, 174, 13, 110, 85, 209, 77, 64, 53, 48, 221,
198+
133, 93, 224, 24, 33, 164, 23, 47, 171, 128, 243, 18, 52, 119, 149, 100,
199+
246, 233, 31, 192, 252, 190, 15, 172, 91, 229, 144, 54, 61, 58, 220, 36,
200+
222, 29, 50, 88, 121, 173, 232, 194, 239, 197, 32, 180, 107, 46, 7, 130,
201+
169, 81, 218, 67, 21, 170, 187, 59, 86, 235, 154, 123, 150, 177, 135, 228,
202+
104, 242, 6, 151, 255, 34, 30, 141, 202, 196, 236, 207, 241, 40, 17, 10
203+
];
204+
205+
#[cfg(test)]
206+
mod tests {
207+
use super::*;
208+
use std::eprintln;
209+
210+
#[test]
211+
fn perlin2_statistics() {
212+
let count = 1000u32;
213+
let div = 10.0;
214+
let [mut avg, mut min, mut max] = [0.0f32, 1.0, -1.0];
215+
for i in 0..count {
216+
for j in 0..count {
217+
let v = perlin2(vec2(i as f32, j as f32) / div);
218+
avg += v;
219+
min = min.min(v);
220+
max = max.max(v);
221+
}
222+
}
223+
avg /= count.pow(2) as f32;
224+
225+
assert_eq!(avg, -0.0005586566);
226+
assert_eq!(min, -0.8853002);
227+
assert_eq!(max, 0.6612066);
228+
}
229+
#[test]
230+
fn perlin3_statistics() {
231+
let count = 100u32;
232+
let div = 10.0;
233+
let [mut avg, mut min, mut max] = [0.0f32, 1.0, -1.0];
234+
for i in 0..count {
235+
for j in 0..count {
236+
for k in 0..count {
237+
let pt = vec3(i, j, k).map(|c| c as f32) / div;
238+
let v = perlin3(pt);
239+
240+
avg += v;
241+
min = min.min(v);
242+
max = max.max(v);
243+
}
244+
}
245+
}
246+
avg /= count.pow(3) as f32;
247+
248+
assert_eq!(avg, 0.00039595732);
249+
assert_eq!(min, -0.8853568);
250+
assert_eq!(max, 0.8853002);
251+
}
252+
}

0 commit comments

Comments
 (0)