1use std::ops::{Add, Mul, Neg, Sub};
2use std::simd::Simd;
3#[cfg(target_feature = "avx512f")]
4use std::simd::{
5 Select,
6 cmp::{SimdPartialEq, SimdPartialOrd},
7};
8
9pub const GRADIENT: [[f64; 3]; 16] = [
11 [1.0, 1.0, 0.0],
12 [-1.0, 1.0, 0.0],
13 [1.0, -1.0, 0.0],
14 [-1.0, -1.0, 0.0],
15 [1.0, 0.0, 1.0],
16 [-1.0, 0.0, 1.0],
17 [1.0, 0.0, -1.0],
18 [-1.0, 0.0, -1.0],
19 [0.0, 1.0, 1.0],
20 [0.0, -1.0, 1.0],
21 [0.0, 1.0, -1.0],
22 [0.0, -1.0, -1.0],
23 [1.0, 1.0, 0.0],
24 [0.0, -1.0, 1.0],
25 [-1.0, 1.0, 0.0],
26 [0.0, -1.0, -1.0],
27];
28
29pub const GRADIENT_F32: [[f32; 3]; 16] = gradient_f32();
31
32#[inline]
34#[must_use]
35pub fn dot(g: &[f64; 3], x: f64, y: f64, z: f64) -> f64 {
36 g[0] * x + g[1] * y + g[2] * z
37}
38#[inline]
44#[must_use]
45pub fn grad_dot_simd<const N: usize>(
46 hashes: [usize; N],
47 x: Simd<f32, N>,
48 y: Simd<f32, N>,
49 z: Simd<f32, N>,
50) -> Simd<f32, N>
51where
52 Simd<f32, N>: Mul<Output = Simd<f32, N>>
53 + Add<Output = Simd<f32, N>>
54 + Sub<Output = Simd<f32, N>>
55 + Neg<Output = Simd<f32, N>>,
56{
57 #[cfg(target_feature = "avx512f")]
58 {
59 let hash_lanes = Simd::<i64, N>::from_array(hashes.map(|value| (value & 15) as i64));
60 let u_component = hash_lanes.simd_lt(Simd::splat(8)).select(x, y);
61 let v_component = hash_lanes.simd_lt(Simd::splat(4)).select(
62 y,
63 (hash_lanes.simd_eq(Simd::splat(12)) | hash_lanes.simd_eq(Simd::splat(14)))
64 .select(x, z),
65 );
66 let signed_u = (hash_lanes & Simd::splat(1))
67 .simd_eq(Simd::splat(0))
68 .select(u_component, -u_component);
69 let signed_v = (hash_lanes & Simd::splat(2))
70 .simd_eq(Simd::splat(0))
71 .select(v_component, -v_component);
72 signed_u + signed_v
73 }
74
75 #[cfg(not(target_feature = "avx512f"))]
76 {
77 let gradients = hashes.map(|hash| GRADIENT_F32[hash & 15]);
78 let gx = Simd::from_array(gradients.map(|gradient| gradient[0]));
79 let gy = Simd::from_array(gradients.map(|gradient| gradient[1]));
80 let gz = Simd::from_array(gradients.map(|gradient| gradient[2]));
81 gx * x + gy * y + gz * z
82 }
83}
84
85#[expect(clippy::inline_always, reason = "hot-path noise primitive")]
87#[inline(always)]
88#[must_use]
89pub fn grad_dot(hash: usize, x: f32, y: f32, z: f32) -> f32 {
90 let g = &GRADIENT_F32[hash & 15];
91 g[0] * x + g[1] * y + g[2] * z
92}
93
94#[inline]
96#[must_use]
97pub fn corner_noise_3d(index: usize, x: f64, y: f64, z: f64, base: f64) -> f64 {
98 let t0 = base - x * x - y * y - z * z;
99 if t0 < 0.0 {
100 0.0
101 } else {
102 let t0 = t0 * t0;
103 t0 * t0 * dot(&GRADIENT[index], x, y, z)
104 }
105}
106
107const fn gradient_f32() -> [[f32; 3]; 16] {
108 let mut result = [[0.0; 3]; 16];
109 let mut i = 0;
110
111 while i < GRADIENT.len() {
112 result[i] = [
113 GRADIENT[i][0] as f32,
114 GRADIENT[i][1] as f32,
115 GRADIENT[i][2] as f32,
116 ];
117 i += 1;
118 }
119
120 result
121}