Skip to main content

draco_core/
keyframe_animation.rs

1//! Keyframe animation container built on top of [`PointCloud`].
2//!
3//! Mirrors C++ Draco's `draco/animation/keyframe_animation.h`. A
4//! [`KeyframeAnimation`] is a point cloud whose first attribute (unique id `0`)
5//! always stores per-frame `f32` timestamps and whose remaining attributes each
6//! store one keyframe track. Because Draco implements keyframe animation as a
7//! point-cloud-like sequential stream, encode/decode reuse the existing
8//! sequential point-cloud path (see [`crate::keyframe_animation_encoder`] and
9//! [`crate::keyframe_animation_decoder`]).
10
11use crate::draco_types::DataType;
12use crate::geometry_attribute::{GeometryAttributeType, PointAttribute};
13use crate::point_cloud::PointCloud;
14
15/// Attribute unique id reserved for the timestamp track.
16const TIMESTAMP_ID: u32 = 0;
17
18/// Holds keyframe animation data as a [`PointCloud`].
19///
20/// The first attribute is always the timestamp track. Each additional attribute
21/// is one keyframe track with the same number of frames.
22#[derive(Debug, Default, Clone)]
23pub struct KeyframeAnimation {
24    point_cloud: PointCloud,
25}
26
27impl KeyframeAnimation {
28    /// Creates an empty keyframe animation.
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    /// Wraps an existing point cloud (e.g. a freshly decoded one).
34    pub fn from_point_cloud(point_cloud: PointCloud) -> Self {
35        Self { point_cloud }
36    }
37
38    /// Returns the number of animation frames (equal to the point count).
39    pub fn num_frames(&self) -> i32 {
40        self.point_cloud.num_points() as i32
41    }
42
43    /// Sets the number of animation frames (equal to the point count).
44    pub fn set_num_frames(&mut self, num_frames: i32) {
45        self.point_cloud.set_num_points(num_frames.max(0) as usize);
46    }
47
48    /// Returns the number of keyframe tracks (attributes minus the timestamp).
49    pub fn num_animations(&self) -> i32 {
50        self.point_cloud.num_attributes() - 1
51    }
52
53    /// Sets the per-frame timestamps. Must be called only once, before any
54    /// keyframe data is added unless a track was added first.
55    ///
56    /// Returns `false` if a timestamp track already holds data or if the frame
57    /// count is inconsistent with previously added keyframes. Mirrors C++
58    /// `KeyframeAnimation::SetTimestamps`.
59    pub fn set_timestamps(&mut self, timestamps: &[f32]) -> bool {
60        let num_frames = timestamps.len() as i32;
61        if self.point_cloud.num_attributes() > 0 {
62            // Timestamp attribute may be set only once.
63            match self.timestamps() {
64                Some(ts) if ts.size() > 0 => return false,
65                _ => {}
66            }
67            // Frame count must match keyframes added earlier.
68            if num_frames != self.num_frames() {
69                return false;
70            }
71        } else {
72            // This is the first attribute.
73            self.set_num_frames(num_frames);
74        }
75
76        let mut timestamp_att = PointAttribute::new();
77        timestamp_att.init(
78            GeometryAttributeType::Generic,
79            1,
80            DataType::Float32,
81            false,
82            num_frames as usize,
83        );
84        let bytes: &[u8] = bytemuck::cast_slice(timestamps);
85        timestamp_att.buffer_mut().write(0, bytes);
86        self.point_cloud
87            .set_attribute(TIMESTAMP_ID as i32, timestamp_att);
88        true
89    }
90
91    /// Adds one keyframe track and returns its animation id, or `-1` on error.
92    ///
93    /// `num_components` is the number of scalar components per frame, and `data`
94    /// holds `num_components * num_frames` values laid out frame-major. Mirrors
95    /// C++ `KeyframeAnimation::AddKeyframes`.
96    pub fn add_keyframes<T: bytemuck::NoUninit>(
97        &mut self,
98        data_type: DataType,
99        num_components: u32,
100        data: &[T],
101    ) -> i32 {
102        if num_components == 0 {
103            return -1;
104        }
105        // Reserve attribute id 0 for timestamps if nothing has been added yet.
106        if self.point_cloud.num_attributes() == 0 {
107            let mut temp_att = PointAttribute::new();
108            temp_att.init(
109                GeometryAttributeType::Generic,
110                num_components as u8,
111                data_type,
112                false,
113                0,
114            );
115            self.point_cloud.add_attribute(temp_att);
116            self.set_num_frames(data.len() as i32 / num_components as i32);
117        }
118
119        if data.len() != num_components as usize * self.num_frames() as usize {
120            return -1;
121        }
122
123        let mut keyframe_att = PointAttribute::new();
124        keyframe_att.init(
125            GeometryAttributeType::Generic,
126            num_components as u8,
127            data_type,
128            false,
129            self.num_frames() as usize,
130        );
131        let bytes: &[u8] = bytemuck::cast_slice(data);
132        keyframe_att.buffer_mut().write(0, bytes);
133        self.point_cloud.add_attribute(keyframe_att)
134    }
135
136    /// Returns the timestamp track, if present.
137    pub fn timestamps(&self) -> Option<&PointAttribute> {
138        self.point_cloud.attribute_by_unique_id(TIMESTAMP_ID)
139    }
140
141    /// Returns the keyframe track identified by `animation_id`, if present.
142    pub fn keyframes(&self, animation_id: i32) -> Option<&PointAttribute> {
143        if animation_id < 0 {
144            return None;
145        }
146        self.point_cloud.attribute_by_unique_id(animation_id as u32)
147    }
148
149    /// Returns the underlying point cloud.
150    pub fn point_cloud(&self) -> &PointCloud {
151        &self.point_cloud
152    }
153
154    /// Consumes the animation, returning the underlying point cloud.
155    pub fn into_point_cloud(self) -> PointCloud {
156        self.point_cloud
157    }
158}