rg3d_core/
curve.rs

1use crate::{
2    math::{cubicf, lerpf},
3    visitor::prelude::*,
4};
5use std::cmp::Ordering;
6use uuid::Uuid;
7
8fn stepf(p0: f32, p1: f32, t: f32) -> f32 {
9    if t.eq(&1.0) {
10        p1
11    } else {
12        p0
13    }
14}
15
16#[derive(Visit, Clone, Debug, PartialEq)]
17pub enum CurveKeyKind {
18    Constant,
19    Linear,
20    Cubic {
21        /// A `tan(angle)` of left tangent.
22        left_tangent: f32,
23        /// A `tan(angle)` of right tangent.
24        right_tangent: f32,
25    },
26}
27
28impl CurveKeyKind {
29    #[inline]
30    pub fn new_cubic(left_angle_radians: f32, right_angle_radians: f32) -> Self {
31        Self::Cubic {
32            left_tangent: left_angle_radians.tan(),
33            right_tangent: right_angle_radians.tan(),
34        }
35    }
36}
37
38impl Default for CurveKeyKind {
39    #[inline]
40    fn default() -> Self {
41        Self::Constant
42    }
43}
44
45#[derive(Visit, Clone, Default, Debug, PartialEq)]
46pub struct CurveKey {
47    pub id: Uuid,
48    location: f32,
49    pub value: f32,
50    pub kind: CurveKeyKind,
51}
52
53impl CurveKey {
54    #[inline]
55    pub fn new(location: f32, value: f32, kind: CurveKeyKind) -> Self {
56        Self {
57            id: Uuid::new_v4(),
58            location,
59            value,
60            kind,
61        }
62    }
63}
64
65impl CurveKey {
66    #[inline]
67    pub fn location(&self) -> f32 {
68        self.location
69    }
70
71    #[inline]
72    pub fn interpolate(&self, other: &Self, t: f32) -> f32 {
73        match (&self.kind, &other.kind) {
74            // Constant-to-any
75            (CurveKeyKind::Constant, CurveKeyKind::Constant)
76            | (CurveKeyKind::Constant, CurveKeyKind::Linear)
77            | (CurveKeyKind::Constant, CurveKeyKind::Cubic { .. }) => {
78                stepf(self.value, other.value, t)
79            }
80
81            // Linear-to-any
82            (CurveKeyKind::Linear, CurveKeyKind::Constant)
83            | (CurveKeyKind::Linear, CurveKeyKind::Linear)
84            | (CurveKeyKind::Linear, CurveKeyKind::Cubic { .. }) => {
85                lerpf(self.value, other.value, t)
86            }
87
88            // Cubic-to-constant or cubic-to-linear
89            (
90                CurveKeyKind::Cubic {
91                    right_tangent: left_tangent,
92                    ..
93                },
94                CurveKeyKind::Constant,
95            )
96            | (
97                CurveKeyKind::Cubic {
98                    right_tangent: left_tangent,
99                    ..
100                },
101                CurveKeyKind::Linear,
102            ) => cubicf(self.value, other.value, t, *left_tangent, 0.0),
103
104            // Cubic-to-cubic
105            (
106                CurveKeyKind::Cubic {
107                    right_tangent: left_tangent,
108                    ..
109                },
110                CurveKeyKind::Cubic {
111                    left_tangent: right_tangent,
112                    ..
113                },
114            ) => cubicf(self.value, other.value, t, *left_tangent, *right_tangent),
115        }
116    }
117}
118
119#[derive(Visit, Default, Clone, Debug, PartialEq)]
120pub struct Curve {
121    keys: Vec<CurveKey>,
122}
123
124fn sort_keys(keys: &mut [CurveKey]) {
125    keys.sort_by(|a, b| {
126        if a.location > b.location {
127            Ordering::Greater
128        } else if a.location < b.location {
129            Ordering::Less
130        } else {
131            Ordering::Equal
132        }
133    });
134}
135
136impl From<Vec<CurveKey>> for Curve {
137    fn from(mut keys: Vec<CurveKey>) -> Self {
138        sort_keys(&mut keys);
139        Self { keys }
140    }
141}
142
143impl Curve {
144    #[inline]
145    pub fn clear(&mut self) {
146        self.keys.clear()
147    }
148
149    #[inline]
150    pub fn is_empty(&self) -> bool {
151        self.keys.is_empty()
152    }
153
154    #[inline]
155    pub fn keys(&self) -> &[CurveKey] {
156        &self.keys
157    }
158
159    #[inline]
160    pub fn add_key(&mut self, new_key: CurveKey) {
161        self.keys.push(new_key);
162        sort_keys(&mut self.keys);
163    }
164
165    #[inline]
166    pub fn move_key(&mut self, key_id: usize, location: f32) {
167        if let Some(key) = self.keys.get_mut(key_id) {
168            key.location = location;
169            sort_keys(&mut self.keys);
170        }
171    }
172
173    #[inline]
174    pub fn value_at(&self, location: f32) -> f32 {
175        if self.keys.is_empty() {
176            // Stub - zero
177            return Default::default();
178        } else if self.keys.len() == 1 {
179            // Single key - just return its value
180            return self.keys.first().unwrap().value;
181        } else if self.keys.len() == 2 {
182            // Special case for two keys (much faster than generic)
183            let pt_a = self.keys.get(0).unwrap();
184            let pt_b = self.keys.get(1).unwrap();
185            if location >= pt_a.location && location <= pt_b.location {
186                let span = pt_b.location - pt_a.location;
187                let t = (location - pt_a.location) / span;
188                return pt_a.interpolate(pt_b, t);
189            } else if location < pt_a.location {
190                return pt_a.value;
191            } else {
192                return pt_b.value;
193            }
194        }
195
196        // Generic case - check for out-of-bounds
197        let first = self.keys.first().unwrap();
198        let last = self.keys.last().unwrap();
199        if location <= first.location {
200            first.value
201        } else if location >= last.location {
202            last.value
203        } else {
204            // Find span first
205            let mut pt_a_index = 0;
206            for (i, pt) in self.keys.iter().enumerate() {
207                if location >= pt.location {
208                    pt_a_index = i;
209                }
210            }
211            let pt_b_index = pt_a_index + 1;
212
213            let pt_a = self.keys.get(pt_a_index).unwrap();
214            let pt_b = self.keys.get(pt_b_index).unwrap();
215
216            let span = pt_b.location - pt_a.location;
217            let t = (location - pt_a.location) / span;
218            pt_a.interpolate(pt_b, t)
219        }
220    }
221}