Skip to main content

pfx_core/anim/
clip.rs

1use super::AnimError;
2use super::math::{lerp3, nlerp, quat_normalize, slerp};
3use super::pose::Pose;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
6pub enum Interpolation {
7    Step,
8    Linear,
9    CubicSpline,
10}
11
12impl Interpolation {
13    fn stride(self) -> usize {
14        match self {
15            Interpolation::CubicSpline => 3,
16            Interpolation::Step | Interpolation::Linear => 1,
17        }
18    }
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
22pub enum Property {
23    Translation,
24    Rotation,
25    Scale,
26}
27
28impl Property {
29    pub fn width(self) -> usize {
30        match self {
31            Property::Rotation => 4,
32            Property::Translation | Property::Scale => 3,
33        }
34    }
35}
36
37#[derive(Clone, Debug, PartialEq)]
38pub struct Channel {
39    pub joint: usize,
40    pub property: Property,
41    pub interpolation: Interpolation,
42    pub times: Vec<f32>,
43    pub values: Vec<f32>,
44}
45
46impl Channel {
47    pub fn check(&self) -> Result<(), String> {
48        if self.times.is_empty() {
49            return Err("a channel has no keys".into());
50        }
51        if self
52            .times
53            .iter()
54            .any(|time| !time.is_finite() || *time < 0.0)
55        {
56            return Err("a key time is negative or not finite".into());
57        }
58        if self.times.windows(2).any(|pair| pair[1] <= pair[0]) {
59            return Err("key times do not rise".into());
60        }
61        let expected = self.times.len() * self.interpolation.stride() * self.property.width();
62        if self.values.len() != expected {
63            return Err(format!(
64                "a channel holds {} values where its {} keys need {expected}",
65                self.values.len(),
66                self.times.len()
67            ));
68        }
69        if self.values.iter().any(|value| !value.is_finite()) {
70            return Err("a key value is not finite".into());
71        }
72        Ok(())
73    }
74
75    fn key(&self, index: usize, part: usize) -> [f32; 4] {
76        let width = self.property.width();
77        let start = (index * self.interpolation.stride() + part) * width;
78        let mut out = [0.0; 4];
79        out[..width].copy_from_slice(&self.values[start..start + width]);
80        out
81    }
82
83    fn value(&self, index: usize) -> [f32; 4] {
84        match self.interpolation {
85            Interpolation::CubicSpline => self.key(index, 1),
86            Interpolation::Step | Interpolation::Linear => self.key(index, 0),
87        }
88    }
89
90    pub fn sample(&self, time: f32) -> [f32; 4] {
91        let after = self.times.partition_point(|key| *key <= time);
92        let last = self.times.len() - 1;
93        let raw = if after == 0 {
94            self.value(0)
95        } else if after > last {
96            self.value(last)
97        } else {
98            let k0 = after - 1;
99            let k1 = after;
100            let t0 = self.times[k0];
101            let t1 = self.times[k1];
102            let span = t1 - t0;
103            let u = ((time - t0) / span).clamp(0.0, 1.0);
104            match self.interpolation {
105                Interpolation::Step => self.value(k0),
106                Interpolation::Linear => {
107                    let a = self.value(k0);
108                    let b = self.value(k1);
109                    if self.property == Property::Rotation {
110                        slerp(a, b, u)
111                    } else {
112                        std::array::from_fn(|k| a[k] + (b[k] - a[k]) * u)
113                    }
114                }
115                Interpolation::CubicSpline => {
116                    let v0 = self.key(k0, 1);
117                    let b0 = self.key(k0, 2);
118                    let a1 = self.key(k1, 0);
119                    let v1 = self.key(k1, 1);
120                    let u2 = u * u;
121                    let u3 = u2 * u;
122                    let h00 = 2.0 * u3 - 3.0 * u2 + 1.0;
123                    let h10 = u3 - 2.0 * u2 + u;
124                    let h01 = -2.0 * u3 + 3.0 * u2;
125                    let h11 = u3 - u2;
126                    std::array::from_fn(|k| {
127                        h00 * v0[k] + h10 * span * b0[k] + h01 * v1[k] + h11 * span * a1[k]
128                    })
129                }
130            }
131        };
132        if self.property == Property::Rotation {
133            quat_normalize(raw)
134        } else {
135            raw
136        }
137    }
138
139    pub fn end(&self) -> f32 {
140        self.times.last().copied().unwrap_or(0.0)
141    }
142}
143
144#[derive(Clone, Debug, PartialEq)]
145pub struct ClipEvent {
146    pub time: f32,
147    pub name: String,
148}
149
150impl ClipEvent {
151    pub fn new(time: f32, name: impl Into<String>) -> Self {
152        Self {
153            time,
154            name: name.into(),
155        }
156    }
157}
158
159#[derive(Clone, Debug, PartialEq)]
160pub struct Clip {
161    name: String,
162    channels: Vec<Channel>,
163    events: Vec<ClipEvent>,
164    duration: f32,
165}
166
167impl Clip {
168    pub fn new(name: impl Into<String>, channels: Vec<Channel>) -> Result<Self, AnimError> {
169        let name = name.into();
170        for channel in &channels {
171            channel.check().map_err(|reason| AnimError::Channel {
172                clip: name.clone(),
173                reason,
174            })?;
175        }
176        let duration = channels.iter().map(Channel::end).fold(0.0f32, f32::max);
177        Ok(Self {
178            name,
179            channels,
180            events: Vec::new(),
181            duration,
182        })
183    }
184
185    pub(crate) fn empty(name: &str) -> Self {
186        Self {
187            name: name.to_string(),
188            channels: Vec::new(),
189            events: Vec::new(),
190            duration: 0.0,
191        }
192    }
193
194    pub fn with_duration(mut self, duration: f32) -> Result<Self, AnimError> {
195        if !duration.is_finite() || duration < self.duration {
196            return Err(AnimError::Channel {
197                clip: self.name.clone(),
198                reason: format!(
199                    "a duration of {duration} is shorter than the clip's keys ({})",
200                    self.duration
201                ),
202            });
203        }
204        self.duration = duration;
205        Ok(self)
206    }
207
208    pub fn with_events(mut self, mut events: Vec<ClipEvent>) -> Result<Self, AnimError> {
209        for event in &events {
210            if !event.time.is_finite() || event.time < 0.0 || event.time > self.duration {
211                return Err(AnimError::Event {
212                    clip: self.name.clone(),
213                    event: event.name.clone(),
214                    reason: format!(
215                        "its time {} is outside the clip's 0 to {}",
216                        event.time, self.duration
217                    ),
218                });
219            }
220        }
221        events.sort_by(|a, b| a.time.total_cmp(&b.time).then_with(|| a.name.cmp(&b.name)));
222        self.events = events;
223        Ok(self)
224    }
225
226    pub fn name(&self) -> &str {
227        &self.name
228    }
229
230    pub fn channels(&self) -> &[Channel] {
231        &self.channels
232    }
233
234    pub fn events(&self) -> &[ClipEvent] {
235        &self.events
236    }
237
238    pub fn duration(&self) -> f32 {
239        self.duration
240    }
241
242    pub fn sample(&self, time: f32, pose: &mut Pose) {
243        self.apply(time, pose, 1.0);
244    }
245
246    pub fn apply(&self, time: f32, pose: &mut Pose, weight: f32) {
247        if weight <= 0.0 {
248            return;
249        }
250        let locals = pose.locals_mut();
251        for channel in &self.channels {
252            let Some(local) = locals.get_mut(channel.joint) else {
253                continue;
254            };
255            let value = channel.sample(time);
256            let three = [value[0], value[1], value[2]];
257            match channel.property {
258                Property::Translation if weight >= 1.0 => local.translation = three,
259                Property::Translation => {
260                    local.translation = lerp3(local.translation, three, weight)
261                }
262                Property::Scale if weight >= 1.0 => local.scale = three,
263                Property::Scale => local.scale = lerp3(local.scale, three, weight),
264                Property::Rotation if weight >= 1.0 => local.rotation = value,
265                Property::Rotation => local.rotation = nlerp(local.rotation, value, weight),
266            }
267        }
268    }
269}