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        // The attribute stores the component count in a `u8`, and the buffer it
106        // allocates is sized from that. A larger count truncated on the way in
107        // while the length check below still used the full value, so the write
108        // that follows ran past the buffer.
109        if num_components > u8::MAX as u32 {
110            return -1;
111        }
112        // `T` and `data_type` are independent parameters, and only `data_type`
113        // sizes the buffer. Handing `DataType::Int8` alongside `&[f64]` sized
114        // the buffer for one byte per value and then wrote eight.
115        if std::mem::size_of::<T>() != data_type.byte_length() {
116            return -1;
117        }
118        // Reserve attribute id 0 for timestamps if nothing has been added yet.
119        if self.point_cloud.num_attributes() == 0 {
120            let mut temp_att = PointAttribute::new();
121            temp_att.init(
122                GeometryAttributeType::Generic,
123                num_components as u8,
124                data_type,
125                false,
126                0,
127            );
128            self.point_cloud.add_attribute(temp_att);
129            self.set_num_frames(data.len() as i32 / num_components as i32);
130        }
131
132        if data.len() != num_components as usize * self.num_frames() as usize {
133            return -1;
134        }
135
136        let mut keyframe_att = PointAttribute::new();
137        keyframe_att.init(
138            GeometryAttributeType::Generic,
139            num_components as u8,
140            data_type,
141            false,
142            self.num_frames() as usize,
143        );
144        let bytes: &[u8] = bytemuck::cast_slice(data);
145        keyframe_att.buffer_mut().write(0, bytes);
146        self.point_cloud.add_attribute(keyframe_att)
147    }
148
149    /// Returns the timestamp track, if present.
150    pub fn timestamps(&self) -> Option<&PointAttribute> {
151        self.point_cloud.attribute_by_unique_id(TIMESTAMP_ID)
152    }
153
154    /// Returns the keyframe track identified by `animation_id`, if present.
155    pub fn keyframes(&self, animation_id: i32) -> Option<&PointAttribute> {
156        if animation_id < 0 {
157            return None;
158        }
159        self.point_cloud.attribute_by_unique_id(animation_id as u32)
160    }
161
162    /// Returns the underlying point cloud.
163    pub fn point_cloud(&self) -> &PointCloud {
164        &self.point_cloud
165    }
166
167    /// Consumes the animation, returning the underlying point cloud.
168    pub fn into_point_cloud(self) -> PointCloud {
169        self.point_cloud
170    }
171}