smpl_core/common/
transform_sequence.rs1use 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#[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 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 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 #[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 pub fn num_frames(&self) -> usize {
90 self.translations.shape()[0]
91 }
92}