1use crate::model::{Clip, Interpolation, Skeleton, Track, TrackValues, Transform};
15use glam::{Mat4, Quat, Vec3};
16
17#[derive(Debug)]
19pub struct PoseGrid {
20 pub times: Vec<f32>,
22 bone_count: usize,
23 local: Vec<Transform>,
25 model: Vec<Mat4>,
26}
27
28impl PoseGrid {
29 fn index(&self, frame: usize, bone: usize) -> usize {
30 assert!(
31 frame < self.frame_count(),
32 "frame index {frame} outside PoseGrid frame count {}",
33 self.frame_count()
34 );
35 assert!(
36 bone < self.bone_count,
37 "bone index {bone} outside PoseGrid bone count {}",
38 self.bone_count
39 );
40 frame * self.bone_count + bone
41 }
42
43 pub fn frame_count(&self) -> usize {
45 self.times.len()
46 }
47
48 pub fn bone_count(&self) -> usize {
50 self.bone_count
51 }
52
53 pub fn local(&self, frame: usize, bone: usize) -> Transform {
59 self.local[self.index(frame, bone)]
60 }
61
62 pub fn model(&self, frame: usize, bone: usize) -> Mat4 {
68 self.model[self.index(frame, bone)]
69 }
70
71 pub fn model_position(&self, frame: usize, bone: usize) -> Vec3 {
77 self.model(frame, bone).w_axis.truncate()
78 }
79}
80
81pub fn default_frame_count(clip: &Clip) -> usize {
85 clip.tracks
86 .iter()
87 .map(Track::key_count)
88 .max()
89 .unwrap_or(2)
90 .max(2)
91}
92
93pub fn sample_clip(skeleton: &Skeleton, clip: &Clip, frames: usize) -> PoseGrid {
101 let frames = frames.max(2);
102 let nb = skeleton.bones.len();
103 let duration = clip.duration_s as f32;
104 let times: Vec<f32> = (0..frames)
105 .map(|i| duration * i as f32 / (frames - 1) as f32)
106 .collect();
107
108 let mut local = vec![Transform::IDENTITY; frames * nb];
109 for f in 0..frames {
110 for (b, bone) in skeleton.bones.iter().enumerate() {
111 local[f * nb + b] = bone.rest;
112 }
113 }
114
115 for track in &clip.tracks {
116 if track.times.is_empty() || track.bone >= nb {
117 continue;
118 }
119 for (f, &t) in times.iter().enumerate() {
120 let slot = &mut local[f * nb + track.bone];
121 match &track.values {
122 TrackValues::Vec3s(_) => {
123 let v = sample_vec3(track, t);
124 match track.property {
125 crate::model::Property::Translation => slot.translation = v,
126 crate::model::Property::Scale => slot.scale = v,
127 crate::model::Property::Rotation => {}
128 }
129 }
130 TrackValues::Quats(_) => slot.rotation = sample_quat(track, t),
131 }
132 }
133 }
134
135 let mut model = vec![Mat4::IDENTITY; frames * nb];
136 for f in 0..frames {
137 for (b, bone) in skeleton.bones.iter().enumerate() {
138 let m = local[f * nb + b].to_mat4();
139 model[f * nb + b] = match bone.parent {
140 Some(p) => model[f * nb + p] * m,
141 None => m,
142 };
143 }
144 }
145
146 PoseGrid {
147 times,
148 bone_count: nb,
149 local,
150 model,
151 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq)]
156pub enum TrackSample {
157 Vec3(Vec3),
159 Quat(Quat),
161}
162
163pub fn sample_track(track: &Track, t: f32) -> TrackSample {
166 match &track.values {
167 TrackValues::Vec3s(_) => TrackSample::Vec3(sample_vec3(track, t)),
168 TrackValues::Quats(_) => TrackSample::Quat(sample_quat(track, t)),
169 }
170}
171
172fn segment(times: &[f32], t: f32) -> (usize, usize, f32) {
180 let n = times.len();
181 if n <= 1 || t <= times[0] {
182 return (0, 0, 0.0);
183 }
184 if t >= times[n - 1] {
185 return (n - 1, n - 1, 0.0);
186 }
187 let k1 = times.partition_point(|&k| k <= t).min(n - 1);
188 if k1 == 0 {
189 return (0, 0, 0.0);
190 }
191 let k0 = k1 - 1;
192 let dt = times[k1] - times[k0];
193 let u = if dt > 0.0 { (t - times[k0]) / dt } else { 0.0 };
194 (k0, k1, u)
195}
196
197fn value_at<T: Copy + Default>(vals: &[T], index: usize) -> T {
201 vals.get(index).copied().unwrap_or_default()
202}
203
204fn hermite(u: f32) -> (f32, f32, f32, f32) {
206 let u2 = u * u;
207 let u3 = u2 * u;
208 (
209 2.0 * u3 - 3.0 * u2 + 1.0, u3 - 2.0 * u2 + u, -2.0 * u3 + 3.0 * u2, u3 - u2, )
214}
215
216fn sample_vec3(track: &Track, t: f32) -> Vec3 {
217 let TrackValues::Vec3s(vals) = &track.values else {
218 return Vec3::ZERO;
219 };
220 let (k0, k1, u) = segment(&track.times, t);
221 let v0 = value_at(vals, track.value_index(k0));
222 if k0 == k1 {
223 return v0;
224 }
225 let v1 = value_at(vals, track.value_index(k1));
226 match track.interpolation {
227 Interpolation::Step => v0,
228 Interpolation::Linear => v0.lerp(v1, u),
229 Interpolation::CubicSpline => {
230 let dt = track.times[k1] - track.times[k0];
231 let m0 = value_at(vals, 3 * k0 + 2) * dt;
233 let m1 = value_at(vals, 3 * k1) * dt;
234 let (h00, h10, h01, h11) = hermite(u);
235 v0 * h00 + m0 * h10 + v1 * h01 + m1 * h11
236 }
237 }
238}
239
240fn sample_quat(track: &Track, t: f32) -> Quat {
241 let TrackValues::Quats(vals) = &track.values else {
242 return Quat::IDENTITY;
243 };
244 let (k0, k1, u) = segment(&track.times, t);
245 let q0: Quat = value_at(vals, track.value_index(k0));
246 if k0 == k1 {
247 return q0.normalize();
248 }
249 let q1 = value_at(vals, track.value_index(k1));
250 match track.interpolation {
251 Interpolation::Step => q0.normalize(),
252 Interpolation::Linear => {
253 let q1 = if q0.dot(q1) < 0.0 { -q1 } else { q1 };
256 q0.normalize().slerp(q1.normalize(), u)
257 }
258 Interpolation::CubicSpline => {
259 let dt = track.times[k1] - track.times[k0];
262 let m0 = value_at(vals, 3 * k0 + 2).to_array();
263 let m1 = value_at(vals, 3 * k1).to_array();
264 let a0 = q0.to_array();
265 let a1 = q1.to_array();
266 let (h00, h10, h01, h11) = hermite(u);
267 let mut out = [0.0f32; 4];
268 for i in 0..4 {
269 out[i] = a0[i] * h00 + m0[i] * dt * h10 + a1[i] * h01 + m1[i] * dt * h11;
270 }
271 Quat::from_array(out).normalize()
272 }
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use crate::model::{Bone, Property};
280
281 fn track(times: Vec<f32>, quats: Vec<Quat>) -> Track {
282 Track {
283 bone: 0,
284 property: Property::Rotation,
285 interpolation: Interpolation::Linear,
286 times,
287 values: TrackValues::Quats(quats),
288 }
289 }
290
291 fn vec3_track(interpolation: Interpolation, times: Vec<f32>, vals: Vec<Vec3>) -> Track {
292 Track {
293 bone: 0,
294 property: Property::Translation,
295 interpolation,
296 times,
297 values: TrackValues::Vec3s(vals),
298 }
299 }
300
301 #[test]
302 #[should_panic(expected = "outside PoseGrid bone count")]
303 fn pose_grid_rejects_bone_index_at_frame_boundary() {
304 let skeleton = Skeleton {
305 bones: vec![Bone {
306 name: "root".into(),
307 parent: None,
308 rest: Transform::IDENTITY,
309 inverse_bind: None,
310 }],
311 };
312 let clip = Clip {
313 name: "idle".into(),
314 duration_s: 1.0,
315 tracks: Vec::new(),
316 };
317 let grid = sample_clip(&skeleton, &clip, 2);
318
319 let _ = grid.local(0, grid.bone_count());
320 }
321
322 #[test]
325 fn nan_first_key_time_samples_without_panicking() {
326 let t = track(
327 vec![f32::NAN, 0.5, 1.0],
328 vec![Quat::IDENTITY, Quat::IDENTITY, Quat::IDENTITY],
329 );
330 for time in [-1.0, 0.0, 0.25, 0.75, 2.0] {
331 let TrackSample::Quat(q) = sample_track(&t, time) else {
332 panic!("rotation track samples a quat");
333 };
334 assert!(q.is_finite() || q.is_nan()); }
336 }
337
338 #[test]
339 fn all_nan_times_sample_without_panicking() {
340 let t = track(
341 vec![f32::NAN, f32::NAN],
342 vec![Quat::IDENTITY, Quat::IDENTITY],
343 );
344 sample_track(&t, 0.5);
345 }
346
347 #[test]
348 fn empty_track_samples_default_without_panicking() {
349 let t = track(vec![], vec![]);
350 let TrackSample::Quat(q) = sample_track(&t, 0.5) else {
351 panic!("rotation track samples a quat");
352 };
353 assert_eq!(q, Quat::IDENTITY.normalize());
354 }
355
356 #[test]
360 fn short_values_sample_default_without_panicking() {
361 let t = track(vec![0.0, 0.5, 1.0], vec![Quat::IDENTITY, Quat::IDENTITY]);
362 sample_track(&t, 0.75); }
364
365 #[test]
366 fn short_vec3_values_sample_default_without_panicking() {
367 let t = vec3_track(Interpolation::Linear, vec![0.0, 0.5, 1.0], vec![Vec3::ONE]);
368 let TrackSample::Vec3(v) = sample_track(&t, 0.75) else {
369 panic!("translation track samples a vec3");
370 };
371 assert!(v.is_finite());
372 }
373
374 #[test]
377 fn short_cubic_values_sample_default_without_panicking() {
378 let t = vec3_track(
379 Interpolation::CubicSpline,
380 vec![0.0, 1.0],
381 vec![Vec3::ZERO, Vec3::ONE, Vec3::ZERO], );
383 sample_track(&t, 0.5);
384 }
385}