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