smpl_core/common/
transform_sequence.rs

1use gloss_renderer::components::ModelMatrix;
2use nalgebra as na;
3use ndarray as nd;
4use ndarray::s;
5use ndarray_npy::NpzReader;
6use std::time::Duration;
7fn map(value: f32, from_min: f32, from_max: f32, to_min: f32, to_max: f32) -> f32 {
8    to_min + (value - from_min) * (to_max - to_min) / (from_max - from_min)
9}
10/// The ``TransformSequence`` contains the rigid pose sequence of a model over time.
11/// It can contain per-frame translations, rotations (in angle-axis format) and scales.
12#[derive(Debug, Clone)]
13pub struct TransformSequence {
14    pub translations: nd::Array2<f32>,
15    pub rotations: nd::Array2<f32>,
16    pub scales: nd::Array1<f32>,
17}
18impl TransformSequence {
19    /// Create a new `TransformSequence` from given npz file path.
20    /// # Panics
21    /// Will panic if the number of frames in translations and rotations do not match.
22    pub fn new_from_npz(path: &str) -> Self {
23        let mut npz = NpzReader::new(std::fs::File::open(path).unwrap()).unwrap();
24        let translations: nd::Array2<f32> = npz.by_name("translation").unwrap();
25        let rotations: nd::Array2<f32> = npz.by_name("rotation").unwrap();
26        let scales: nd::Array1<f32> = npz.by_name("objectSize").unwrap();
27        assert_eq!(
28            translations.shape()[0],
29            rotations.shape()[0],
30            "Number of frames in translations and rotations must match"
31        );
32        Self {
33            translations,
34            rotations,
35            scales,
36        }
37    }
38    /// Create an empty `TransformSequence` with given number of frames.
39    pub fn new_empty(num_frames: usize) -> Self {
40        let translations = nd::Array2::<f32>::zeros((num_frames, 3));
41        let rotations = nd::Array2::<f32>::zeros((num_frames, 3));
42        let scales = nd::Array1::<f32>::zeros(num_frames);
43        Self {
44            translations,
45            rotations,
46            scales,
47        }
48    }
49    /// Duration of the animation
50    #[allow(clippy::cast_precision_loss)]
51    pub fn duration(&self, fps: f32) -> Duration {
52        Duration::from_secs_f32(self.num_frames() as f32 / fps)
53    }
54    #[allow(clippy::cast_precision_loss)]
55    #[allow(clippy::cast_possible_truncation)]
56    #[allow(clippy::cast_sign_loss)]
57    pub fn get_smooth_time_indices(&self, time_sec: f32, fps: f32) -> (usize, usize, f32) {
58        let frame_time = map(time_sec, 0.0, self.duration(fps).as_secs_f32(), 0.0, (self.num_frames() - 1) as f32);
59        let frame_ceil = frame_time.ceil();
60        let frame_ceil = frame_ceil.clamp(0.0, (self.num_frames() - 1) as f32);
61        let frame_floor = frame_time.floor();
62        let frame_floor = frame_floor.clamp(0.0, (self.num_frames() - 1) as f32);
63        let w_ceil = frame_ceil - frame_time;
64        let w_ceil = 1.0 - w_ceil;
65        (frame_floor as usize, frame_ceil as usize, w_ceil)
66    }
67    pub fn get_transform_at_idx(&self, idx: u32) -> ModelMatrix {
68        let translation = self.translations.slice(s![idx as usize, ..]);
69        let rotation = self.rotations.slice(s![idx as usize, ..]);
70        let scale = self.scales[idx as usize];
71        let translation_vec = na::Vector3::from_row_slice(translation.as_slice().unwrap());
72        let rotation_vec = na::Vector3::from_row_slice(rotation.as_slice().unwrap());
73        let rotation_matrix = if rotation_vec.norm() > 0.0 {
74            na::Rotation3::from_axis_angle(&na::UnitVector3::new_normalize(rotation_vec), rotation_vec.norm())
75        } else {
76            na::Rotation3::identity()
77        };
78        let similarity = na::SimilarityMatrix3::from_parts(na::Translation3::from(translation_vec), rotation_matrix, scale);
79        ModelMatrix(similarity)
80    }
81    #[allow(clippy::cast_possible_truncation)]
82    pub fn get_transform_at_time(&self, time_sec: f32, fps: f32) -> ModelMatrix {
83        let (frame_floor, frame_ceil, w_ceil) = self.get_smooth_time_indices(time_sec, fps);
84        let mm_floor = self.get_transform_at_idx(frame_floor as u32);
85        let mm_ceil = self.get_transform_at_idx(frame_ceil as u32);
86        mm_floor.interpolate(&mm_ceil, w_ceil)
87    }
88    /// Get the number of frames in the `TransformSequence`.
89    pub fn num_frames(&self) -> usize {
90        self.translations.shape()[0]
91    }
92}