math2/
utils.rs

1/// Quantizes `value` to the nearest multiple of `step`.
2///
3/// Useful for rounding or grid alignment of continuous values.
4///
5/// # Panics
6/// Panics if `step` is not positive.
7///
8/// # Example
9/// ```rust
10/// use math2::quantize;
11/// assert_eq!(quantize(15.0, 10.0), 20.0);
12/// ```
13pub fn quantize(value: f32, step: f32) -> f32 {
14    assert!(step > 0.0, "step must be positive");
15    let factor = 1.0 / step;
16    (value * factor).round() / factor
17}
18
19/// Clamps `value` between `min` and `max`.
20pub fn clamp(value: f32, min: f32, max: f32) -> f32 {
21    value.max(min).min(max)
22}
23
24/// Finds the nearest value to `value` from `points`.
25/// Returns `f32::INFINITY` if `points` is empty.
26pub fn nearest(value: f32, points: &[f32]) -> f32 {
27    points
28        .iter()
29        .map(|&p| (p, (p - value).abs()))
30        .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
31        .map(|(p, _)| p)
32        .unwrap_or(f32::INFINITY)
33}
34
35/// Converts an angle to its principal representation within `[-180, 180)`.
36pub fn principal_angle(angle: f32) -> f32 {
37    ((angle + 180.0) % 360.0) - 180.0
38}
39
40/// Determines whether an angle is closer to the X or Y axis.
41pub fn angle_to_axis(angle: f32) -> super::vector2::Axis {
42    let a = ((angle % 360.0) + 360.0) % 360.0;
43    let dist_horizontal = (a - 0.0)
44        .abs()
45        .min((a - 180.0).abs())
46        .min((a - 360.0).abs());
47    let dist_vertical = (a - 90.0).abs().min((a - 270.0).abs());
48    if dist_horizontal <= dist_vertical {
49        super::vector2::Axis::X
50    } else {
51        super::vector2::Axis::Y
52    }
53}
54
55/// Checks if all numbers in `arr` are equal within `tolerance`.
56pub fn is_uniform(arr: &[f32], tolerance: f32) -> bool {
57    if arr.len() <= 1 {
58        return true;
59    }
60    let first = arr[0];
61    if tolerance == 0.0 {
62        arr.iter().all(|&v| v == first)
63    } else {
64        arr.iter().all(|&v| (v - first).abs() <= tolerance)
65    }
66}
67
68/// Computes the mean (average) of the provided values.
69pub fn mean(values: &[f32]) -> f32 {
70    assert!(!values.is_empty(), "cannot compute mean of empty slice");
71    let sum: f32 = values.iter().sum();
72    sum / values.len() as f32
73}
74
75/// Generates all combinations of size `k` from the slice.
76pub fn combinations<T: Clone>(arr: &[T], k: usize) -> Vec<Vec<T>> {
77    if k == 0 {
78        return vec![vec![]];
79    }
80    if arr.is_empty() {
81        return vec![];
82    }
83    let (first, rest) = arr.split_first().unwrap();
84    let mut with_first: Vec<Vec<T>> = combinations(rest, k - 1)
85        .into_iter()
86        .map(|mut combo| {
87            combo.insert(0, first.clone());
88            combo
89        })
90        .collect();
91    let mut without_first = combinations(rest, k);
92    with_first.append(&mut without_first);
93    with_first
94}
95
96/// Generates all permutations of size `k` from the slice.
97pub fn permutations<T: Clone>(arr: &[T], k: usize) -> Vec<Vec<T>> {
98    if k == 0 {
99        return vec![vec![]];
100    }
101    if arr.is_empty() {
102        return vec![];
103    }
104    let mut result = Vec::new();
105    for (idx, item) in arr.iter().enumerate() {
106        let mut rest = arr.to_vec();
107        rest.remove(idx);
108        for mut perm in permutations(&rest, k - 1) {
109            perm.insert(0, item.clone());
110            result.push(perm);
111        }
112    }
113    result
114}
115
116/// Generates the power set of `arr` or subsets of a given size.
117pub fn powerset<T: Clone>(arr: &[T], k: Option<usize>) -> Vec<Vec<T>> {
118    match k {
119        None => {
120            let mut result = vec![vec![]];
121            for size in 1..=arr.len() {
122                result.extend(combinations(arr, size));
123            }
124            result
125        }
126        Some(size) => {
127            if size > arr.len() {
128                vec![]
129            } else {
130                combinations(arr, size)
131            }
132        }
133    }
134}