/// Normalizes a gesture by removing translation and scale /// /// # Arguments /// * `points` - Slice of Point structs representing the gesture /// /// # Returns /// Vector of normalized points centered at origin with unit scale /// /// # Example /// ``` /// use redoal::point::Point; /// use redoal::normalize; /// /// let points = vec![ /// Point::new(1.0, 2.0), /// Point::new(3.0, 4.0), /// Point::new(5.0, 6.0), /// ]; /// let normalized = normalize(&points); /// ``` use crate::Point; pub fn normalize(points: &[Point]) -> Vec { let n = points.len() as f64; // Calculate centroid let cx = points.iter().map(|p| p.x).sum::() / n; let cy = points.iter().map(|p| p.y).sum::() / n; // Center points at origin let mut out: Vec = points.iter().map(|p| { Point { x: p.x - cx, y: p.y - cy, } }).collect(); // Find maximum radius let mut max_r = 0.0; for p in &out { let r = (p.x * p.x + p.y * p.y).sqrt(); if r > max_r { max_r = r; } } // Scale to unit size if max_r > 0.0 { for p in &mut out { p.x /= max_r; p.y /= max_r; } } out } #[cfg(test)] mod tests { use super::*; use crate::point::Point; #[test] fn test_normalize_centers_points() { let points = vec![ Point::new(1.0, 1.0), Point::new(2.0, 2.0), Point::new(3.0, 3.0), ]; let normalized = normalize(&points); let sum_x: f64 = normalized.iter().map(|p| p.x).sum(); let sum_y: f64 = normalized.iter().map(|p| p.y).sum(); // Centroid should be at origin assert!( (sum_x.abs() < 1e-10) ); assert!( (sum_y.abs() < 1e-10) ); } #[test] fn test_normalize_scales_points() { let points = vec![ Point::new(0.0, 0.0), Point::new(10.0, 0.0), Point::new(5.0, 5.0), ]; let normalized = normalize(&points); let max_r = normalized.iter().map(|p| (p.x * p.x + p.y * p.y).sqrt()).fold(0.0, |a: f64, b| a.max(b)); // All points should be within unit circle assert!( (max_r - 1.0).abs() < 1e-10 ); } #[test] fn test_normalize_empty() { let points: Vec = vec![]; let normalized = normalize(&points); assert_eq!(normalized.len(), 0); } }