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}