1use core::simd::{Select, Simd, cmp::SimdPartialOrd};
2use std::{
3 ops,
4 simd::{SimdCast, SimdElement, num::SimdFloat},
5};
6
7#[inline]
14#[must_use]
15pub fn clamped_lerp<T>(min: T, max: T, factor: T) -> T
16where
17 T: ops::Add<Output = T>
18 + Copy
19 + From<f32>
20 + ops::Mul<Output = T>
21 + PartialOrd
22 + ops::Sub<Output = T>,
23{
24 if factor < T::from(0.0) {
25 min
26 } else if factor > T::from(1.0) {
27 max
28 } else {
29 lerp(factor, min, max)
30 }
31}
32
33#[inline]
35#[must_use]
36pub fn clamped_lerp_simd<const N: usize>(
37 min: Simd<f64, N>,
38 max: Simd<f64, N>,
39 factor: Simd<f64, N>,
40) -> Simd<f64, N> {
41 let zero = Simd::splat(0.0);
42 let one = Simd::splat(1.0);
43 let below = factor.simd_lt(zero);
44 let above = factor.simd_gt(one);
45
46 let lerped = min + factor * (max - min);
48
49 let result = below.select(min, lerped);
51 above.select(max, result)
52}
53
54#[inline]
58#[must_use]
59pub fn clamp(value: f64, min: f64, max: f64) -> f64 {
60 if value < min {
61 min
62 } else if value > max {
63 max
64 } else {
65 value
66 }
67}
68
69#[inline]
71#[must_use]
72pub const fn clamp_i32(value: i32, min: i32, max: i32) -> i32 {
73 if value < min {
74 min
75 } else if value > max {
76 max
77 } else {
78 value
79 }
80}
81#[inline]
85#[must_use]
86pub fn inverse_lerp(value: f64, a: f64, b: f64) -> f64 {
87 (value - a) / (b - a)
88}
89
90#[expect(clippy::inline_always, reason = "hot-path noise primitive")]
96#[inline(always)]
97#[must_use]
98pub fn lerp<F>(alpha: F, a: F, b: F) -> F
99where
100 F: Copy + ops::Mul<Output = F> + ops::Add<Output = F> + ops::Sub<Output = F>,
101{
102 a + alpha * (b - a)
103}
104
105#[expect(clippy::inline_always, reason = "hot-path noise primitive")]
107#[inline(always)]
108#[must_use]
109pub fn lerp_simd<F, const N: usize>(alpha: Simd<F, N>, a: Simd<F, N>, b: Simd<F, N>) -> Simd<F, N>
110where
111 F: SimdElement,
112 Simd<F, N>: ops::Mul<Output = Simd<F, N>>
113 + ops::Add<Output = Simd<F, N>>
114 + ops::Sub<Output = Simd<F, N>>,
115{
116 a + alpha * (b - a)
117}
118
119#[expect(clippy::inline_always, reason = "hot-path noise primitive")]
125#[inline(always)]
126#[must_use]
127pub fn lerp2<F>(a1: F, a2: F, x00: F, x10: F, x01: F, x11: F) -> F
128where
129 F: Copy + ops::Mul<Output = F> + ops::Add<Output = F> + ops::Sub<Output = F>,
130{
131 lerp(a2, lerp(a1, x00, x10), lerp(a1, x01, x11))
132}
133
134#[expect(clippy::inline_always, reason = "hot-path noise primitive")]
136#[inline(always)]
137#[must_use]
138pub fn lerp2_simd<F, const N: usize>(
139 a1: Simd<F, N>,
140 a2: Simd<F, N>,
141 x00: Simd<F, N>,
142 x10: Simd<F, N>,
143 x01: Simd<F, N>,
144 x11: Simd<F, N>,
145) -> Simd<F, N>
146where
147 F: SimdElement,
148 Simd<F, N>: ops::Mul<Output = Simd<F, N>>
149 + ops::Add<Output = Simd<F, N>>
150 + ops::Sub<Output = Simd<F, N>>,
151{
152 lerp_simd(a2, lerp_simd(a1, x00, x10), lerp_simd(a1, x01, x11))
153}
154
155#[expect(clippy::inline_always, reason = "hot-path noise primitive")]
161#[inline(always)]
162#[must_use]
163#[expect(
164 clippy::too_many_arguments,
165 reason = "matches vanilla's Mth.lerp3 signature with 8 grid corner values"
166)]
167pub fn lerp3<F>(
168 a1: F,
169 a2: F,
170 a3: F,
171 x000: F,
172 x100: F,
173 x010: F,
174 x110: F,
175 x001: F,
176 x101: F,
177 x011: F,
178 x111: F,
179) -> F
180where
181 F: Copy + ops::Mul<Output = F> + ops::Add<Output = F> + ops::Sub<Output = F>,
182{
183 lerp(
184 a3,
185 lerp2(a1, a2, x000, x100, x010, x110),
186 lerp2(a1, a2, x001, x101, x011, x111),
187 )
188}
189
190#[inline]
192#[expect(clippy::too_many_arguments, reason = "mirrors lerp3 with SIMD vectors")]
193#[must_use]
194pub fn lerp3_simd<F, const N: usize>(
195 a1: Simd<F, N>,
196 a2: Simd<F, N>,
197 a3: Simd<F, N>,
198 x000: Simd<F, N>,
199 x100: Simd<F, N>,
200 x010: Simd<F, N>,
201 x110: Simd<F, N>,
202 x001: Simd<F, N>,
203 x101: Simd<F, N>,
204 x011: Simd<F, N>,
205 x111: Simd<F, N>,
206) -> Simd<F, N>
207where
208 F: SimdElement,
209 Simd<F, N>: ops::Mul<Output = Simd<F, N>>
210 + ops::Add<Output = Simd<F, N>>
211 + ops::Sub<Output = Simd<F, N>>,
212{
213 lerp_simd(
214 a3,
215 lerp2_simd(a1, a2, x000, x100, x010, x110),
216 lerp2_simd(a1, a2, x001, x101, x011, x111),
217 )
218}
219
220#[cfg(test)]
221mod lerp_tests {
222 use super::*;
223
224 #[test]
225 fn test_lerp() {
226 assert!((lerp(0.0_f64, 10.0, 20.0) - 10.0).abs() < 1e-10);
227 assert!((lerp(1.0_f64, 10.0, 20.0) - 20.0).abs() < 1e-10);
228 assert!((lerp(0.5_f64, 10.0, 20.0) - 15.0).abs() < 1e-10);
229 }
230}
231#[inline]
237#[must_use]
238pub fn map(value: f64, from_min: f64, from_max: f64, to_min: f64, to_max: f64) -> f64 {
239 lerp(inverse_lerp(value, from_min, from_max), to_min, to_max)
240}
241
242#[inline]
246#[must_use]
247pub fn map_clamped<T>(value: T, from_min: T, from_max: T, to_min: T, to_max: T) -> T
248where
249 T: ops::Add<Output = T>
250 + Copy
251 + ops::Div<Output = T>
252 + From<f32>
253 + ops::Mul<Output = T>
254 + PartialOrd
255 + ops::Sub<Output = T>,
256{
257 let t = (value - from_min) / (from_max - from_min);
258 clamped_lerp(to_min, to_max, t)
259}
260#[expect(clippy::inline_always, reason = "hot-path noise primitive")]
267#[inline(always)]
268#[must_use]
269pub fn smoothstep<F>(x: F) -> F
270where
271 F: Copy + From<f32> + ops::Mul<Output = F> + ops::Add<Output = F> + ops::Sub<Output = F>,
272{
273 x * x * x * (x * (x * F::from(6.0) - F::from(15.0)) + F::from(10.0))
274}
275
276#[inline]
282#[must_use]
283pub fn smoothstep_derivative<F>(x: F) -> F
284where
285 F: Copy + From<f32> + ops::Mul<Output = F> + ops::Add<Output = F> + ops::Sub<Output = F>,
286{
287 F::from(30.0) * x * x * (x - F::from(1.0)) * (x - F::from(1.0))
288}
289
290#[inline]
292#[must_use]
293pub fn smoothstep_simd<F, const N: usize>(x: Simd<F, N>) -> Simd<F, N>
294where
295 F: SimdElement + SimdCast,
296 Simd<F, N>: ops::Mul<Output = Simd<F, N>>
297 + ops::Sub<Output = Simd<F, N>>
298 + ops::Add<Output = Simd<F, N>>,
299{
300 x * x
301 * x
302 * (x * (x * Simd::splat(6.0).cast() - Simd::splat(15.0).cast()) + Simd::splat(10.0).cast())
303}
304
305#[cfg(test)]
306mod smoothstep_tests {
307 use super::*;
308 #[test]
309 fn test_smoothstep() {
310 assert!((smoothstep(0.0_f64) - 0.0).abs() < 1e-10);
312 assert!((smoothstep(1.0_f64) - 1.0).abs() < 1e-10);
313 assert!((smoothstep(0.5_f64) - 0.5).abs() < 1e-10);
315 }
316}