Skip to main content

vecslide_core/
pointer.rs

1use crate::manifest::TrailPoint;
2
3/// Filters a raw trail by removing points too close to the previous kept point.
4/// A point is kept only if its Euclidean distance from the last kept point exceeds `threshold`.
5/// The first point is always kept.
6pub fn filter_trail(points: &[TrailPoint], threshold: f32) -> Vec<TrailPoint> {
7    let threshold_sq = threshold * threshold;
8    let mut kept: Vec<TrailPoint> = Vec::with_capacity(points.len());
9
10    for point in points {
11        let keep = match kept.last() {
12            None => true,
13            Some(last) => {
14                let dx = point.x - last.x;
15                let dy = point.y - last.y;
16                dx * dx + dy * dy > threshold_sq
17            }
18        };
19        if keep {
20            kept.push(point.clone());
21        }
22    }
23
24    kept
25}
26
27/// Returns the opacity [0.0, 1.0] for a trail point at a given playback position.
28/// The point is fully opaque at `point_time_ms` and fades to 0.0 after `fade_duration_ms`.
29/// Returns 0.0 if the point has not yet been reached or has fully faded.
30pub fn trail_opacity(point_time_ms: u64, current_time_ms: u64, fade_duration_ms: u64) -> f32 {
31    if current_time_ms < point_time_ms || fade_duration_ms == 0 {
32        return 0.0;
33    }
34    let elapsed = current_time_ms - point_time_ms;
35    if elapsed >= fade_duration_ms {
36        return 0.0;
37    }
38    1.0 - (elapsed as f32 / fade_duration_ms as f32)
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    fn pt(time_ms: u64, x: f32, y: f32) -> TrailPoint {
46        TrailPoint { time_ms, x, y }
47    }
48
49    #[test]
50    fn filter_removes_close_points() {
51        let points = vec![pt(0, 0.0, 0.0), pt(10, 1.0, 1.0), pt(20, 100.0, 100.0)];
52        // threshold=5: (0,0)->(1,1) dist=sqrt(2)≈1.4 < 5, filtered; (0,0)->(100,100) dist>>5, kept
53        let result = filter_trail(&points, 5.0);
54        assert_eq!(result.len(), 2);
55        assert_eq!(result[0].x, 0.0);
56        assert_eq!(result[1].x, 100.0);
57    }
58
59    #[test]
60    fn filter_keeps_all_if_threshold_zero() {
61        let points = vec![pt(0, 0.0, 0.0), pt(10, 0.1, 0.1)];
62        assert_eq!(filter_trail(&points, 0.0).len(), 2);
63    }
64
65    #[test]
66    fn opacity_full_at_trigger_time() {
67        assert!((trail_opacity(1000, 1000, 500) - 1.0).abs() < f32::EPSILON);
68    }
69
70    #[test]
71    fn opacity_zero_before_trigger() {
72        assert_eq!(trail_opacity(1000, 500, 500), 0.0);
73    }
74
75    #[test]
76    fn opacity_zero_after_fade() {
77        assert_eq!(trail_opacity(1000, 1600, 500), 0.0);
78    }
79
80    #[test]
81    fn opacity_half_at_midpoint() {
82        let op = trail_opacity(1000, 1250, 500);
83        assert!((op - 0.5).abs() < 1e-5);
84    }
85}