1use crate::mesh::K3dMesh;
10use crate::tween::lerp;
11use nalgebra::{Point3, UnitQuaternion};
12
13#[derive(Debug, Clone, Copy)]
15pub struct TransformKeyframe {
16 pub time: f32,
17 pub position: [f32; 3],
18 pub roll: f32,
20 pub pitch: f32,
21 pub yaw: f32,
22 pub scale: f32,
23}
24
25impl TransformKeyframe {
26 pub const fn new(
27 time: f32,
28 position: [f32; 3],
29 roll: f32,
30 pitch: f32,
31 yaw: f32,
32 scale: f32,
33 ) -> Self {
34 Self {
35 time,
36 position,
37 roll,
38 pitch,
39 yaw,
40 scale,
41 }
42 }
43
44 #[inline]
45 pub fn quat(&self) -> UnitQuaternion<f32> {
46 UnitQuaternion::from_euler_angles(self.roll, self.pitch, self.yaw)
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq)]
52pub struct SampledTransform {
53 pub position: [f32; 3],
54 pub roll: f32,
55 pub pitch: f32,
56 pub yaw: f32,
57 pub scale: f32,
58}
59
60impl SampledTransform {
61 pub fn apply_to(&self, mesh: &mut K3dMesh<'_>) {
63 mesh.set_position(self.position[0], self.position[1], self.position[2]);
64 mesh.set_rotation(UnitQuaternion::from_euler_angles(
65 self.roll, self.pitch, self.yaw,
66 ));
67 mesh.set_scale(self.scale);
68 }
69
70 pub fn apply_position_to_camera(&self, camera: &mut crate::camera::Camera) {
72 camera.set_position(Point3::new(
73 self.position[0],
74 self.position[1],
75 self.position[2],
76 ));
77 }
78}
79
80#[derive(Debug)]
82pub struct TransformTrack<'a> {
83 keyframes: &'a [TransformKeyframe],
84 looping: bool,
85}
86
87impl<'a> TransformTrack<'a> {
88 pub fn new(keyframes: &'a [TransformKeyframe], looping: bool) -> Self {
89 assert!(
90 !keyframes.is_empty(),
91 "TransformTrack requires at least one keyframe"
92 );
93 Self { keyframes, looping }
94 }
95
96 pub fn duration(&self) -> f32 {
97 self.keyframes.last().map(|k| k.time).unwrap_or(0.0)
98 }
99
100 pub fn keyframe_count(&self) -> usize {
101 self.keyframes.len()
102 }
103
104 pub fn is_looping(&self) -> bool {
105 self.looping
106 }
107
108 pub fn sample(&self, time: f32) -> SampledTransform {
110 if self.keyframes.len() == 1 {
111 return keyframe_to_sampled(self.keyframes[0]);
112 }
113
114 let duration = self.duration();
115 let t = if self.looping {
116 if duration > 0.0 { time % duration } else { 0.0 }
117 } else {
118 time.clamp(0.0, duration)
119 };
120
121 let mut kf1_idx = self
122 .keyframes
123 .partition_point(|kf| kf.time <= t)
124 .saturating_sub(1);
125 if self.keyframes[kf1_idx].time > t {
126 kf1_idx = 0;
127 }
128
129 if kf1_idx == self.keyframes.len() - 1 {
130 return keyframe_to_sampled(self.keyframes[kf1_idx]);
131 }
132 let kf2_idx = kf1_idx + 1;
133
134 let kf1 = self.keyframes[kf1_idx];
135 let kf2 = self.keyframes[kf2_idx];
136
137 let alpha = if kf2.time > kf1.time {
138 (t - kf1.time) / (kf2.time - kf1.time)
139 } else {
140 0.0
141 };
142
143 #[cfg(feature = "anim-blend")]
145 let (roll, pitch, yaw) = {
146 let q = kf1.quat().slerp(&kf2.quat(), alpha);
147 q.euler_angles()
148 };
149 #[cfg(not(feature = "anim-blend"))]
150 let (roll, pitch, yaw) = (
151 lerp(kf1.roll, kf2.roll, alpha),
152 lerp(kf1.pitch, kf2.pitch, alpha),
153 lerp(kf1.yaw, kf2.yaw, alpha),
154 );
155
156 SampledTransform {
157 position: [
158 lerp(kf1.position[0], kf2.position[0], alpha),
159 lerp(kf1.position[1], kf2.position[1], alpha),
160 lerp(kf1.position[2], kf2.position[2], alpha),
161 ],
162 roll,
163 pitch,
164 yaw,
165 scale: lerp(kf1.scale, kf2.scale, alpha),
166 }
167 }
168}
169
170fn keyframe_to_sampled(kf: TransformKeyframe) -> SampledTransform {
171 SampledTransform {
172 position: kf.position,
173 roll: kf.roll,
174 pitch: kf.pitch,
175 yaw: kf.yaw,
176 scale: kf.scale,
177 }
178}
179
180#[derive(Debug)]
182pub struct AnimationPlayer<'a> {
183 track: TransformTrack<'a>,
184 time: f32,
185 playing: bool,
186 speed: f32,
187}
188
189impl<'a> AnimationPlayer<'a> {
190 pub fn new(track: TransformTrack<'a>) -> Self {
191 Self {
192 track,
193 time: 0.0,
194 playing: true,
195 speed: 1.0,
196 }
197 }
198
199 pub fn with_speed(mut self, speed: f32) -> Self {
200 self.speed = speed;
201 self
202 }
203
204 pub fn set_playing(&mut self, playing: bool) {
205 self.playing = playing;
206 }
207
208 pub fn is_playing(&self) -> bool {
209 self.playing
210 }
211
212 pub fn reset(&mut self) {
213 self.time = 0.0;
214 }
215
216 pub fn set_time(&mut self, time: f32) {
217 self.time = time;
218 }
219
220 pub fn time(&self) -> f32 {
221 self.time
222 }
223
224 pub fn track(&self) -> &TransformTrack<'a> {
225 &self.track
226 }
227
228 pub fn advance(&mut self, dt: f32) {
229 if self.playing && dt > 0.0 {
230 self.time += dt * self.speed;
231 }
232 }
233
234 pub fn sample(&self) -> SampledTransform {
235 self.track.sample(self.time)
236 }
237
238 pub fn apply_to(&self, mesh: &mut K3dMesh<'_>) {
239 self.sample().apply_to(mesh);
240 }
241
242 pub fn is_done(&self) -> bool {
244 !self.track.is_looping() && self.time >= self.track.duration()
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 extern crate std;
251
252 use super::*;
253
254 const TRACK: &[TransformKeyframe] = &[
255 TransformKeyframe::new(0.0, [0.0, -2.0, 0.0], 0.0, 0.0, 0.0, 1.0),
256 TransformKeyframe::new(1.0, [0.0, 0.0, 0.0], 0.0, 0.0, 3.14, 1.0),
257 ];
258
259 #[test]
260 fn test_track_interpolation() {
261 let track = TransformTrack::new(TRACK, false);
262 let mid = track.sample(0.5);
263 assert!((mid.position[1] - (-1.0)).abs() < 1e-5);
264 assert!((mid.yaw - 1.57).abs() < 0.15);
265 }
266
267 #[test]
268 fn test_player_done() {
269 let track = TransformTrack::new(TRACK, false);
270 let mut player = AnimationPlayer::new(track);
271 player.advance(1.5);
272 assert!(player.is_done());
273 }
274
275 #[test]
276 fn test_looping_track() {
277 const LOOP_TRACK: &[TransformKeyframe] = &[
278 TransformKeyframe::new(0.0, [0.0, 0.0, 0.0], 0.0, 0.0, 0.0, 1.0),
279 TransformKeyframe::new(1.0, [1.0, 0.0, 0.0], 0.0, 0.0, 0.0, 1.0),
280 ];
281 let track = TransformTrack::new(LOOP_TRACK, true);
282 let s = track.sample(1.5);
283 assert!((s.position[0] - 0.5).abs() < 1e-5);
284 }
285}