Skip to main content

geng_utils/
bounded.rs

1use geng::prelude::*;
2
3/// A value bounded by a closed interval.
4#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
5pub struct Bounded<T> {
6    value: T,
7    min: T,
8    max: T,
9}
10
11impl<T: PartialOrd + Copy> Bounded<T> {
12    /// Construct a new value bounded by the given `range`.
13    pub fn new(value: T, range: std::ops::RangeInclusive<T>) -> Self {
14        Self {
15            value,
16            min: *range.start(),
17            max: *range.end(),
18        }
19        .normalized()
20    }
21
22    pub fn value(&self) -> T {
23        self.value
24    }
25
26    pub fn min(&self) -> T {
27        self.min
28    }
29
30    pub fn max(&self) -> T {
31        self.max
32    }
33
34    pub fn map<U>(&mut self, f: impl Fn(T) -> U) -> Bounded<U> {
35        Bounded {
36            value: f(self.value),
37            min: f(self.min),
38            max: f(self.max),
39        }
40    }
41
42    fn normalized(self) -> Self {
43        let value = self.value.clamp_range(self.min..=self.max);
44        Self { value, ..self }
45    }
46
47    /// Whether the value is at a maximum.
48    pub fn is_max(&self) -> bool {
49        self.value >= self.max
50    }
51
52    /// Whether the value is at a minimum.
53    pub fn is_min(&self) -> bool {
54        self.value <= self.min
55    }
56
57    /// Whether the value is above minimum.
58    pub fn is_above_min(&self) -> bool {
59        self.value > self.min
60    }
61}
62
63impl<T: UNum> Bounded<T> {
64    /// Construct a new non-negative value with max set to be the initial value.
65    pub fn new_max(value: T) -> Self {
66        Self::new(value, T::ZERO..=value)
67    }
68
69    /// Construct a new non-negative value with a given max and initial value set to zero.
70    pub fn new_zero(max: T) -> Self {
71        Self::new(T::ZERO, T::ZERO..=max)
72    }
73
74    /// Changes the value by a `delta`, but keeps it in the interval.
75    pub fn change(&mut self, delta: T) {
76        self.value += delta;
77        *self = self.normalized();
78    }
79
80    /// Sets the value after clamping it by the bounds.
81    pub fn set(&mut self, value: T) {
82        self.value = value;
83        *self = self.normalized();
84    }
85}
86
87impl<T: Float> Bounded<T> {
88    /// Returns a number in range 0..=1 representing the value in the interval
89    /// where `0 = min`, and `1 = max`.
90    pub fn get_ratio(&self) -> T {
91        let len = self.max - self.min;
92        if len.approx_eq(&T::ZERO) {
93            T::ZERO
94        } else {
95            (self.value - self.min) / len
96        }
97    }
98
99    pub fn set_ratio(&mut self, ratio: T) {
100        self.value = ratio.clamp_range(T::ZERO..=T::ONE) * (self.max - self.min) + self.min;
101    }
102}
103
104#[test]
105fn test_bounded() {
106    let mut bounded = Bounded::new(0.0, 0.0..=10.0);
107    assert_eq!(bounded.value(), 0.0);
108
109    bounded.change(5.0);
110    assert_eq!(bounded.value(), 5.0);
111
112    bounded.change(6.0);
113    assert_eq!(bounded.value(), 10.0);
114
115    bounded.change(-11.0);
116    assert_eq!(bounded.value(), 0.0);
117}