Skip to main content

anvilkit_core/math/
interpolation.rs

1//! # 插值和动画支持
2//! 
3//! 提供各种插值算法,用于动画、过渡效果和数值平滑。
4//! 
5//! ## 核心 Trait
6//! 
7//! - [`Lerp`]: 线性插值
8//! - [`Slerp`]: 球面线性插值(用于旋转)
9//! - [`Interpolate`]: 通用插值接口
10//! 
11//! ## 缓动函数
12//! 
13//! 提供常用的缓动函数,用于创建自然的动画效果。
14//! 
15//! ## 使用示例
16//! 
17//! ```rust
18//! use anvilkit_core::math::interpolation::{Lerp, Slerp, ease_in_out_cubic};
19//! use glam::{Vec3, Quat};
20//! 
21//! // 线性插值
22//! let start = Vec3::ZERO;
23//! let end = Vec3::new(10.0, 20.0, 30.0);
24//! let mid = start.lerp(end, 0.5);
25//! 
26//! // 旋转插值
27//! let rot1 = Quat::IDENTITY;
28//! let rot2 = Quat::from_rotation_y(std::f32::consts::PI);
29//! let mid_rot = rot1.slerp(rot2, 0.5);
30//! 
31//! // 使用缓动函数
32//! let t = ease_in_out_cubic(0.3);
33//! let smooth_pos = start.lerp(end, t);
34//! ```
35
36use glam::{Vec2, Vec3, Vec4, Quat};
37
38/// 线性插值 trait
39/// 
40/// 为支持线性插值的类型提供统一接口。
41pub trait Lerp<T = Self> {
42    /// 在两个值之间进行线性插值
43    /// 
44    /// # 参数
45    /// 
46    /// - `other`: 目标值
47    /// - `t`: 插值参数,通常在 [0, 1] 范围内
48    ///   - `t = 0.0` 返回 `self`
49    ///   - `t = 1.0` 返回 `other`
50    ///   - `t = 0.5` 返回中点
51    /// 
52    /// # 注意
53    /// 
54    /// `t` 可以超出 [0, 1] 范围进行外推。
55    fn lerp(&self, other: T, t: f32) -> Self;
56}
57
58/// 球面线性插值 trait
59/// 
60/// 主要用于旋转插值,提供更自然的旋转过渡。
61pub trait Slerp<T = Self> {
62    /// 在两个值之间进行球面线性插值
63    /// 
64    /// # 参数
65    /// 
66    /// - `other`: 目标值
67    /// - `t`: 插值参数,通常在 [0, 1] 范围内
68    fn slerp(&self, other: T, t: f32) -> Self;
69}
70
71/// 通用插值接口
72/// 
73/// 提供多种插值方法的统一接口。
74pub trait Interpolate<T = Self> {
75    /// 线性插值
76    fn interpolate_linear(&self, other: T, t: f32) -> Self;
77    
78    /// 平滑插值(使用 smoothstep 函数)
79    fn interpolate_smooth(&self, other: T, t: f32) -> Self;
80    
81    /// 使用自定义缓动函数插值
82    fn interpolate_eased(&self, other: T, t: f32, ease_fn: fn(f32) -> f32) -> Self;
83}
84
85// 为基础数值类型实现 Lerp
86impl 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
98// 为 glam 向量类型实现 Lerp
99impl 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
117// 为四元数实现 Slerp
118impl Slerp for Quat {
119    fn slerp(&self, other: Quat, t: f32) -> Quat {
120        Quat::slerp(*self, other, t)
121    }
122}
123
124// 为四元数实现 Lerp(使用 nlerp)
125impl Lerp for Quat {
126    fn lerp(&self, other: Quat, t: f32) -> Quat {
127        Quat::lerp(*self, other, t).normalize()
128    }
129}
130
131// 为支持 Lerp 的类型实现 Interpolate
132impl<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
148/// Smoothstep 函数,提供平滑的 S 形曲线插值
149/// 
150/// 在 t ∈ [0, 1] 范围内,提供比线性插值更自然的过渡。
151/// 
152/// # 公式
153/// 
154/// `smoothstep(t) = 3t² - 2t³`
155/// 
156/// # 示例
157/// 
158/// ```rust
159/// use anvilkit_core::math::interpolation::smoothstep;
160/// 
161/// assert_eq!(smoothstep(0.0), 0.0);
162/// assert_eq!(smoothstep(1.0), 1.0);
163/// assert_eq!(smoothstep(0.5), 0.5); // S 形曲线在中点与线性插值相同
164/// assert!(smoothstep(0.25) < 0.25); // 前半段比线性插值慢
165/// assert!(smoothstep(0.75) > 0.75); // 后半段比线性插值快
166/// ```
167pub fn smoothstep(t: f32) -> f32 {
168    let t = t.clamp(0.0, 1.0);
169    t * t * (3.0 - 2.0 * t)
170}
171
172/// Smootherstep 函数,提供更平滑的过渡
173/// 
174/// 比 smoothstep 提供更平滑的一阶和二阶导数。
175/// 
176/// # 公式
177/// 
178/// `smootherstep(t) = 6t⁵ - 15t⁴ + 10t³`
179pub 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
184/// 将值从一个范围重映射到另一个范围
185/// 
186/// # 参数
187/// 
188/// - `value`: 输入值
189/// - `from_min`, `from_max`: 输入范围
190/// - `to_min`, `to_max`: 输出范围
191/// 
192/// # 示例
193/// 
194/// ```rust
195/// use anvilkit_core::math::interpolation::remap;
196/// 
197/// // 将 [0, 100] 范围的值映射到 [0, 1] 范围
198/// let normalized = remap(50.0, 0.0, 100.0, 0.0, 1.0);
199/// assert_eq!(normalized, 0.5);
200/// ```
201pub 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        // Degenerate range: return midpoint of target range to avoid division by zero
205        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
211// 缓动函数
212// 这些函数提供各种动画缓动效果
213
214/// 二次缓入函数
215/// 
216/// 动画开始时较慢,然后加速。
217pub fn ease_in_quad(t: f32) -> f32 {
218    t * t
219}
220
221/// 二次缓出函数
222/// 
223/// 动画开始时较快,然后减速。
224pub fn ease_out_quad(t: f32) -> f32 {
225    1.0 - (1.0 - t) * (1.0 - t)
226}
227
228/// 二次缓入缓出函数
229/// 
230/// 动画开始和结束时较慢,中间较快。
231pub 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
239/// 三次缓入函数
240pub fn ease_in_cubic(t: f32) -> f32 {
241    t * t * t
242}
243
244/// 三次缓出函数
245pub fn ease_out_cubic(t: f32) -> f32 {
246    let t = 1.0 - t;
247    1.0 - t * t * t
248}
249
250/// 三次缓入缓出函数
251pub 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
260/// 四次缓入函数
261pub fn ease_in_quart(t: f32) -> f32 {
262    t * t * t * t
263}
264
265/// 四次缓出函数
266pub fn ease_out_quart(t: f32) -> f32 {
267    let t = 1.0 - t;
268    1.0 - t * t * t * t
269}
270
271/// 四次缓入缓出函数
272pub 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
281/// 弹性缓出函数
282/// 
283/// 创建弹性效果,超出目标值然后回弹。
284pub 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
296/// 回弹缓出函数
297/// 
298/// 创建回弹效果,模拟球落地的弹跳。
299pub 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    // 自定义的近似相等比较函数
319    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        // 四元数 q 和 -q 表示相同的旋转,所以我们需要检查两种情况
326        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        // 验证插值结果是有效的四元数
353        assert!(mid.is_finite());
354        assert!((mid.length() - 1.0).abs() < 1e-6, "Quaternion should be normalized");
355
356        // 验证旋转角度是否正确(应该是90度)
357        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        // 验证 S 形曲线特性
370        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        // 测试缓动函数的边界值
384        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        // 测试缓入缓出的对称性
390        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        // 在 t=0.25 时测试差异,因为在 t=0.5 时平滑插值可能与线性插值相同
408        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)); // 平滑插值应该不同于线性插值
413        assert!(!vec3_approx_eq(eased_quarter, linear_quarter, 1e-6)); // 缓动插值应该不同于线性插值
414    }
415
416    #[test]
417    fn test_elastic_and_bounce() {
418        // 测试弹性和回弹函数的边界值
419        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        // 弹性函数应该在某些点超出 [0, 1] 范围
425        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        // 测试超出 [0, 1] 范围的插值(外推)
432        let start = 0.0;
433        let end = 10.0;
434
435        assert_eq!(start.lerp(end, -0.5), -5.0); // 向后外推
436        assert_eq!(start.lerp(end, 1.5), 15.0);  // 向前外推
437    }
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        // 比 smoothstep 在边界处更平滑
487        assert!(smootherstep(0.1) < smoothstep(0.1));
488    }
489
490    #[test]
491    fn test_smoothstep_clamping() {
492        // 超出范围应被钳制
493        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        // 反向映射
500        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        // 缓动函数在 [0,1] 范围内应单调递增(对标准缓动而言)
517        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}