1use glam::{Vec2, Vec3, Vec4, Quat};
37
38pub trait Lerp<T = Self> {
42 fn lerp(&self, other: T, t: f32) -> Self;
56}
57
58pub trait Slerp<T = Self> {
62 fn slerp(&self, other: T, t: f32) -> Self;
69}
70
71pub trait Interpolate<T = Self> {
75 fn interpolate_linear(&self, other: T, t: f32) -> Self;
77
78 fn interpolate_smooth(&self, other: T, t: f32) -> Self;
80
81 fn interpolate_eased(&self, other: T, t: f32, ease_fn: fn(f32) -> f32) -> Self;
83}
84
85impl Lerp for f32 {
87 fn lerp(&self, other: f32, t: f32) -> f32 {
88 self + (other - self) * t
89 }
90}
91
92impl Lerp for f64 {
93 fn lerp(&self, other: f64, t: f32) -> f64 {
94 self + (other - self) * t as f64
95 }
96}
97
98impl Lerp for Vec2 {
100 fn lerp(&self, other: Vec2, t: f32) -> Vec2 {
101 *self + (other - *self) * t
102 }
103}
104
105impl Lerp for Vec3 {
106 fn lerp(&self, other: Vec3, t: f32) -> Vec3 {
107 *self + (other - *self) * t
108 }
109}
110
111impl Lerp for Vec4 {
112 fn lerp(&self, other: Vec4, t: f32) -> Vec4 {
113 *self + (other - *self) * t
114 }
115}
116
117impl Slerp for Quat {
119 fn slerp(&self, other: Quat, t: f32) -> Quat {
120 Quat::slerp(*self, other, t)
121 }
122}
123
124impl Lerp for Quat {
126 fn lerp(&self, other: Quat, t: f32) -> Quat {
127 Quat::lerp(*self, other, t).normalize()
128 }
129}
130
131impl<T: Lerp + Copy> Interpolate for T {
133 fn interpolate_linear(&self, other: T, t: f32) -> T {
134 self.lerp(other, t)
135 }
136
137 fn interpolate_smooth(&self, other: T, t: f32) -> T {
138 let smooth_t = smoothstep(t);
139 self.lerp(other, smooth_t)
140 }
141
142 fn interpolate_eased(&self, other: T, t: f32, ease_fn: fn(f32) -> f32) -> T {
143 let eased_t = ease_fn(t);
144 self.lerp(other, eased_t)
145 }
146}
147
148pub fn smoothstep(t: f32) -> f32 {
168 let t = t.clamp(0.0, 1.0);
169 t * t * (3.0 - 2.0 * t)
170}
171
172pub fn smootherstep(t: f32) -> f32 {
180 let t = t.clamp(0.0, 1.0);
181 t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
182}
183
184pub fn remap(value: f32, from_min: f32, from_max: f32, to_min: f32, to_max: f32) -> f32 {
202 let range = from_max - from_min;
203 if range.abs() < f32::EPSILON {
204 return (to_min + to_max) * 0.5;
206 }
207 let t = (value - from_min) / range;
208 to_min + t * (to_max - to_min)
209}
210
211pub fn ease_in_quad(t: f32) -> f32 {
218 t * t
219}
220
221pub fn ease_out_quad(t: f32) -> f32 {
225 1.0 - (1.0 - t) * (1.0 - t)
226}
227
228pub fn ease_in_out_quad(t: f32) -> f32 {
232 if t < 0.5 {
233 2.0 * t * t
234 } else {
235 1.0 - 2.0 * (1.0 - t) * (1.0 - t)
236 }
237}
238
239pub fn ease_in_cubic(t: f32) -> f32 {
241 t * t * t
242}
243
244pub fn ease_out_cubic(t: f32) -> f32 {
246 let t = 1.0 - t;
247 1.0 - t * t * t
248}
249
250pub fn ease_in_out_cubic(t: f32) -> f32 {
252 if t < 0.5 {
253 4.0 * t * t * t
254 } else {
255 let t = 1.0 - t;
256 1.0 - 4.0 * t * t * t
257 }
258}
259
260pub fn ease_in_quart(t: f32) -> f32 {
262 t * t * t * t
263}
264
265pub fn ease_out_quart(t: f32) -> f32 {
267 let t = 1.0 - t;
268 1.0 - t * t * t * t
269}
270
271pub fn ease_in_out_quart(t: f32) -> f32 {
273 if t < 0.5 {
274 8.0 * t * t * t * t
275 } else {
276 let t = 1.0 - t;
277 1.0 - 8.0 * t * t * t * t
278 }
279}
280
281pub fn ease_out_elastic(t: f32) -> f32 {
285 if t == 0.0 {
286 0.0
287 } else if t == 1.0 {
288 1.0
289 } else {
290 let p = 0.3;
291 let s = p / 4.0;
292 2.0_f32.powf(-10.0 * t) * ((t - s) * (2.0 * std::f32::consts::PI) / p).sin() + 1.0
293 }
294}
295
296pub fn ease_out_bounce(t: f32) -> f32 {
300 if t < 1.0 / 2.75 {
301 7.5625 * t * t
302 } else if t < 2.0 / 2.75 {
303 let t = t - 1.5 / 2.75;
304 7.5625 * t * t + 0.75
305 } else if t < 2.5 / 2.75 {
306 let t = t - 2.25 / 2.75;
307 7.5625 * t * t + 0.9375
308 } else {
309 let t = t - 2.625 / 2.75;
310 7.5625 * t * t + 0.984375
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317
318 fn vec3_approx_eq(a: Vec3, b: Vec3, epsilon: f32) -> bool {
320 (a - b).length() < epsilon
321 }
322
323 #[allow(dead_code)]
324 fn quat_approx_eq(a: glam::Quat, b: glam::Quat, epsilon: f32) -> bool {
325 let dot = a.dot(b).abs();
327 (dot - 1.0).abs() < epsilon
328 }
329
330 #[test]
331 fn test_f32_lerp() {
332 assert_eq!(0.0.lerp(10.0, 0.0), 0.0);
333 assert_eq!(0.0.lerp(10.0, 1.0), 10.0);
334 assert_eq!(0.0.lerp(10.0, 0.5), 5.0);
335 }
336
337 #[test]
338 fn test_vec3_lerp() {
339 let start = Vec3::ZERO;
340 let end = Vec3::new(10.0, 20.0, 30.0);
341 let mid = start.lerp(end, 0.5);
342
343 assert!(vec3_approx_eq(mid, Vec3::new(5.0, 10.0, 15.0), 1e-6));
344 }
345
346 #[test]
347 fn test_quat_slerp() {
348 let start = Quat::IDENTITY;
349 let end = Quat::from_rotation_y(std::f32::consts::PI);
350 let mid = start.slerp(end, 0.5);
351
352 assert!(mid.is_finite());
354 assert!((mid.length() - 1.0).abs() < 1e-6, "Quaternion should be normalized");
355
356 let angle = 2.0 * mid.w.abs().acos();
358 let expected_angle = std::f32::consts::PI * 0.5;
359 assert!((angle - expected_angle).abs() < 1e-3,
360 "Expected angle {}, got {}", expected_angle, angle);
361 }
362
363 #[test]
364 fn test_smoothstep() {
365 assert_eq!(smoothstep(0.0), 0.0);
366 assert_eq!(smoothstep(1.0), 1.0);
367 assert!((smoothstep(0.5) - 0.5).abs() < 1e-6);
368
369 assert!(smoothstep(0.25) < 0.25);
371 assert!(smoothstep(0.75) > 0.75);
372 }
373
374 #[test]
375 fn test_remap() {
376 assert_eq!(remap(50.0, 0.0, 100.0, 0.0, 1.0), 0.5);
377 assert_eq!(remap(0.0, 0.0, 100.0, -1.0, 1.0), -1.0);
378 assert_eq!(remap(100.0, 0.0, 100.0, -1.0, 1.0), 1.0);
379 }
380
381 #[test]
382 fn test_ease_functions() {
383 assert_eq!(ease_in_quad(0.0), 0.0);
385 assert_eq!(ease_in_quad(1.0), 1.0);
386 assert_eq!(ease_out_quad(0.0), 0.0);
387 assert_eq!(ease_out_quad(1.0), 1.0);
388
389 let t = 0.3;
391 let ease_in = ease_in_out_cubic(t);
392 let ease_out = ease_in_out_cubic(1.0 - t);
393 assert!((ease_in - (1.0 - ease_out)).abs() < 1e-6);
394 }
395
396 #[test]
397 fn test_interpolate_trait() {
398 let start = Vec3::ZERO;
399 let end = Vec3::new(10.0, 20.0, 30.0);
400
401 let linear = start.interpolate_linear(end, 0.5);
402 let _smooth = start.interpolate_smooth(end, 0.5);
403 let _eased = start.interpolate_eased(end, 0.5, ease_in_out_cubic);
404
405 assert!(vec3_approx_eq(linear, Vec3::new(5.0, 10.0, 15.0), 1e-6));
406
407 let linear_quarter = start.interpolate_linear(end, 0.25);
409 let smooth_quarter = start.interpolate_smooth(end, 0.25);
410 let eased_quarter = start.interpolate_eased(end, 0.25, ease_in_out_cubic);
411
412 assert!(!vec3_approx_eq(smooth_quarter, linear_quarter, 1e-6)); assert!(!vec3_approx_eq(eased_quarter, linear_quarter, 1e-6)); }
415
416 #[test]
417 fn test_elastic_and_bounce() {
418 assert_eq!(ease_out_elastic(0.0), 0.0);
420 assert!((ease_out_elastic(1.0) - 1.0).abs() < 1e-6);
421 assert_eq!(ease_out_bounce(0.0), 0.0);
422 assert!((ease_out_bounce(1.0) - 1.0).abs() < 1e-6);
423
424 let elastic_mid = ease_out_elastic(0.5);
426 assert!(elastic_mid > 1.0 || elastic_mid < 0.0);
427 }
428
429 #[test]
430 fn test_extrapolation() {
431 let start = 0.0;
433 let end = 10.0;
434
435 assert_eq!(start.lerp(end, -0.5), -5.0); assert_eq!(start.lerp(end, 1.5), 15.0); }
438
439 #[test]
440 fn test_lerp_same_values() {
441 assert_eq!(5.0_f32.lerp(5.0, 0.5), 5.0);
442 let v = Vec3::ONE;
443 assert!(vec3_approx_eq(v.lerp(v, 0.7), Vec3::ONE, 1e-6));
444 }
445
446 #[test]
447 fn test_f64_lerp() {
448 assert_eq!(0.0_f64.lerp(100.0, 0.25), 25.0);
449 assert_eq!(0.0_f64.lerp(100.0, 0.0), 0.0);
450 assert_eq!(0.0_f64.lerp(100.0, 1.0), 100.0);
451 }
452
453 #[test]
454 fn test_vec2_lerp() {
455 let start = Vec2::ZERO;
456 let end = Vec2::new(10.0, 20.0);
457 let mid = start.lerp(end, 0.5);
458 assert!((mid.x - 5.0).abs() < 1e-6);
459 assert!((mid.y - 10.0).abs() < 1e-6);
460 }
461
462 #[test]
463 fn test_vec4_lerp() {
464 let start = Vec4::ZERO;
465 let end = Vec4::new(4.0, 8.0, 12.0, 16.0);
466 let mid = start.lerp(end, 0.25);
467 assert!((mid.x - 1.0).abs() < 1e-6);
468 assert!((mid.y - 2.0).abs() < 1e-6);
469 assert!((mid.z - 3.0).abs() < 1e-6);
470 assert!((mid.w - 4.0).abs() < 1e-6);
471 }
472
473 #[test]
474 fn test_quat_lerp() {
475 let start = Quat::IDENTITY;
476 let end = Quat::from_rotation_y(std::f32::consts::PI);
477 let mid = Lerp::lerp(&start, end, 0.0);
478 assert!(quat_approx_eq(mid, start, 1e-5));
479 }
480
481 #[test]
482 fn test_smootherstep() {
483 assert_eq!(smootherstep(0.0), 0.0);
484 assert_eq!(smootherstep(1.0), 1.0);
485 assert!((smootherstep(0.5) - 0.5).abs() < 1e-6);
486 assert!(smootherstep(0.1) < smoothstep(0.1));
488 }
489
490 #[test]
491 fn test_smoothstep_clamping() {
492 assert_eq!(smoothstep(-0.5), 0.0);
494 assert_eq!(smoothstep(1.5), 1.0);
495 }
496
497 #[test]
498 fn test_remap_inverse_range() {
499 let result = remap(0.5, 0.0, 1.0, 10.0, 0.0);
501 assert!((result - 5.0).abs() < 1e-6);
502 }
503
504 #[test]
505 fn test_ease_quart_boundary() {
506 assert_eq!(ease_in_quart(0.0), 0.0);
507 assert_eq!(ease_in_quart(1.0), 1.0);
508 assert_eq!(ease_out_quart(0.0), 0.0);
509 assert!((ease_out_quart(1.0) - 1.0).abs() < 1e-6);
510 assert_eq!(ease_in_out_quart(0.0), 0.0);
511 assert!((ease_in_out_quart(1.0) - 1.0).abs() < 1e-6);
512 }
513
514 #[test]
515 fn test_ease_monotonicity() {
516 let points = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];
518 for window in points.windows(2) {
519 assert!(ease_in_quad(window[1]) >= ease_in_quad(window[0]));
520 assert!(ease_out_quad(window[1]) >= ease_out_quad(window[0]));
521 assert!(ease_in_out_quad(window[1]) >= ease_in_out_quad(window[0]));
522 assert!(ease_in_cubic(window[1]) >= ease_in_cubic(window[0]));
523 assert!(ease_out_cubic(window[1]) >= ease_out_cubic(window[0]));
524 }
525 }
526
527 #[test]
528 fn test_interpolate_smooth_at_boundaries() {
529 let start = 0.0_f32;
530 let end = 10.0_f32;
531 assert!((start.interpolate_smooth(end, 0.0) - 0.0).abs() < 1e-6);
532 assert!((start.interpolate_smooth(end, 1.0) - 10.0).abs() < 1e-6);
533 }
534}