minetest_gltf/model/animation.rs
1// Based on https://whoisryosuke.com/blog/2022/importing-gltf-with-wgpu-and-rust
2// You can thank ryosuke for this information.
3
4use std::error::Error;
5
6use ahash::AHashMap;
7use glam::{Quat, Vec3};
8use gltf::{animation::util, buffer::Data, Gltf};
9use log::error;
10
11use crate::minetest_gltf::MinetestGLTF;
12
13/// Raw animation data. Unionized.
14pub enum Keyframes {
15 /// Translation raw data.
16 Translation(Vec<Vec3>),
17 /// Rotation raw data.
18 Rotation(Vec<Quat>),
19 /// Scale raw data.
20 Scale(Vec<Vec3>),
21 /// Morph Target Weights raw data.
22 Weights(Vec<f32>),
23 /// An absolute failure that shows something blew up.
24 Explosion,
25}
26
27/// Container containing raw TRS animation data for a node (bone).
28#[derive(Default)]
29pub struct BoneAnimationChannel {
30 /// Translation data.
31 pub translations: Vec<Vec3>,
32 /// Translation timestamp data.
33 pub translation_timestamps: Vec<f32>,
34
35 /// Rotation data.
36 pub rotations: Vec<Quat>,
37 /// Rotation timestamp data.
38 pub rotation_timestamps: Vec<f32>,
39
40 /// Scale data.
41 pub scales: Vec<Vec3>,
42 /// Scale timestamp data.
43 pub scale_timestamps: Vec<f32>,
44
45 /// Weight data.
46 pub weights: Vec<f32>,
47 /// Not sure why you'll need this but it's here.
48 ///
49 /// Weight timestamp data.
50 pub weight_timestamps: Vec<f32>,
51}
52
53impl BoneAnimationChannel {
54 ///
55 /// Create new bone animation.
56 ///
57 pub(crate) fn new() -> Self {
58 BoneAnimationChannel {
59 translations: vec![],
60 translation_timestamps: vec![],
61 rotations: vec![],
62 rotation_timestamps: vec![],
63 scales: vec![],
64 scale_timestamps: vec![],
65 weights: vec![],
66 weight_timestamps: vec![],
67 }
68 }
69}
70
71///
72/// We need a comparable data set. Cast this this thing 0.00001 f32 5 precision points into 1 i32
73///
74fn into_precision(x: f32) -> i32 {
75 (x * 100_000.0) as i32
76}
77
78pub(crate) fn grab_animations(
79 gltf_data: Gltf,
80 buffers: Vec<Data>,
81 file_name: &str,
82) -> AHashMap<i32, BoneAnimationChannel> {
83 // We always want the animation data as well.
84 // You can thank: https://whoisryosuke.com/blog/2022/importing-gltf-with-wgpu-and-rust
85 let mut bone_animation_channels: AHashMap<i32, BoneAnimationChannel> = AHashMap::new();
86
87 // ? We are mimicking minetest C++ and only getting the first animation.
88 if let Some(first_animation) = gltf_data.animations().next() {
89 // ? Now we want to get all channels which contains node (bone) TRS data in random order.
90 for (channel_index, channel) in first_animation.channels().enumerate() {
91 let reader = channel.reader(|buffer| Some(&buffers[buffer.index()]));
92
93 // * If the timestamp accessor is sparse, or something has gone horribly wrong, it's a static model.
94 let result_timestamps = if let Some(inputs) = reader.read_inputs() {
95 match inputs {
96 gltf::accessor::Iter::Standard(times) => {
97 let times: Vec<f32> = times.collect();
98 // println!("Time: {}", times.len());
99 // dbg!(times);
100 Ok(times)
101 }
102 gltf::accessor::Iter::Sparse(_) => Err(format!(
103 "minetest-gltf: Sparse keyframes not supported. Model: [{}]. Model will not be animated.",
104 file_name
105 )),
106 }
107 } else {
108 Err(format!("minetest-gltf: No animation data detected in animation channel [{}]. [{}] is probably a broken model. Model will not be animated.", channel_index, file_name))
109 };
110
111 // * If something blows up when parsing the model animations, it's now a static model.
112 match result_timestamps {
113 Ok(timestamps) => {
114 let keyframes = if let Some(outputs) = reader.read_outputs() {
115 // More advanced control flow and boilerplate reduction for when something
116 // that's not implemented blows up.
117 let mut blew_up = false;
118 let mut generic_failure = |data_type: &str, implementation_type: &str| {
119 error!(
120 "Minetest_gltf: {} is not implemented for animation {}.",
121 data_type, implementation_type
122 );
123 bone_animation_channels.clear();
124 blew_up = true;
125 Keyframes::Explosion
126 };
127
128 let keyframe_result = match outputs {
129 util::ReadOutputs::Translations(translation) => {
130 Keyframes::Translation(translation.map(Vec3::from_array).collect())
131 }
132
133 util::ReadOutputs::Rotations(rotation) => match rotation {
134 util::Rotations::I8(_rotation) => generic_failure("i8", "rotation"),
135 util::Rotations::U8(_rotation) => generic_failure("u8", "rotation"),
136 util::Rotations::I16(_rotation) => generic_failure("i16", "rotation"),
137 util::Rotations::U16(_rotation) => generic_failure("u16", "rotation"),
138 util::Rotations::F32(rotation) => Keyframes::Rotation(
139 rotation
140 .map(|rot| {
141 Quat::from_array({
142 let mut returning_array: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
143 for (i, v) in rot.iter().enumerate() {
144 returning_array[i] = *v;
145 }
146 returning_array
147 })
148 })
149 .collect(),
150 ),
151 },
152 util::ReadOutputs::Scales(scale) => {
153 Keyframes::Scale(scale.map(Vec3::from_array).collect())
154 }
155 util::ReadOutputs::MorphTargetWeights(target_weight) => match target_weight {
156 util::MorphTargetWeights::I8(_weights) => {
157 generic_failure("i8", "morph weight targets")
158 }
159 util::MorphTargetWeights::U8(_weights) => {
160 generic_failure("u8", "morph weight targets")
161 }
162 util::MorphTargetWeights::I16(_weights) => {
163 generic_failure("i16", "morph weight targets")
164 }
165 util::MorphTargetWeights::U16(_weights) => {
166 generic_failure("u16", "morph weight targets")
167 }
168 util::MorphTargetWeights::F32(weights) => {
169 let mut container: Vec<f32> = vec![];
170
171 // There can be a bug in the iterator given due to how rust GLTF works, we want to drop out when the end is hit.
172 // This prevents an infinite loop.
173 let limit = weights.len();
174 for (index, value) in weights.enumerate() {
175 container.push(value);
176 // Bail out.
177 if index >= limit {
178 break;
179 }
180 }
181 Keyframes::Weights(container)
182 }
183 },
184 };
185
186 // And now we capture if this thing failed and stop it if it did.
187 if blew_up {
188 break;
189 }
190
191 keyframe_result
192 } else {
193 // * Something blew up, it's now a static model.
194 error!(
195 "minetest-gltf: Unknown keyframe in model [{}]. This model is probably corrupted. Model will not be animated.",
196 file_name
197 );
198 bone_animation_channels.clear();
199 break;
200 };
201
202 let bone_id = channel.target().node().index() as i32;
203
204 let enable_debug_spam = false;
205
206 if enable_debug_spam {
207 println!("found target bone: {}", bone_id);
208 }
209
210 match keyframes {
211 Keyframes::Translation(translations) => {
212 let animation_channel = bone_animation_channels.entry(bone_id).or_default();
213
214 // * If the animation already has translation for this node (bone), that means that something has gone horribly wrong.
215 if !animation_channel.translations.is_empty() {
216 error!("minetest-gltf: Attempted to overwrite node (bone) channel [{}]'s translation animation data! Model [{}] is broken! This is now a static model.", bone_id, file_name);
217 bone_animation_channels.clear();
218 break;
219 }
220
221 // * If the translation animation channel data does not match the length of timestamp data, it blew up.
222 if translations.len() != timestamps.len() {
223 error!(
224 "minetest-gltf: Mismatched node (bone) translations length in channel [{}] of model [{}]. [{}] translation compared to [{}] timestamps. This is now a static model.",
225 bone_id,
226 file_name,
227 translations.len(),
228 timestamps.len());
229
230 bone_animation_channels.clear();
231 break;
232 }
233
234 animation_channel.translations = translations;
235 animation_channel.translation_timestamps = timestamps;
236 }
237
238 Keyframes::Rotation(rotations) => {
239 let animation_channel = bone_animation_channels.entry(bone_id).or_default();
240
241 // * If the animation already has rotation for this node (bone), that means that something has gone horribly wrong.
242 if !animation_channel.rotations.is_empty() {
243 error!("minetest-gltf: Attempted to overwrite node (bone) channel [{}]'s rotation animation data! Model [{}] is broken! This is now a static model.", bone_id, file_name);
244 bone_animation_channels.clear();
245 break;
246 }
247
248 // * If the rotations animation channel data does not match the length of timestamp data, it blew up.
249 if rotations.len() != timestamps.len() {
250 error!(
251 "minetest-gltf: Mismatched node (bone) rotations length in channel [{}] of model [{}]. [{}] rotation compared to [{}] timestamps. This is now a static model.",
252 bone_id,
253 file_name,
254 rotations.len(),
255 timestamps.len());
256
257 bone_animation_channels.clear();
258 break;
259 }
260
261 animation_channel.rotations = rotations;
262 animation_channel.rotation_timestamps = timestamps;
263 }
264 Keyframes::Scale(scales) => {
265 let gotten_animation_channel = bone_animation_channels.entry(bone_id).or_default();
266
267 // * If the animation already has scale for this node (bone), that means that something has gone horribly wrong.
268 if !gotten_animation_channel.scales.is_empty() {
269 error!("minetest-gltf: Attempted to overwrite node (bone) channel [{}]'s scale animation data! Model [{}] is broken! This is now a static model", bone_id, file_name);
270 bone_animation_channels.clear();
271 break;
272 }
273
274 // * If the scales animation channel data does not match the length of timestamp data, it blew up.
275 if scales.len() != timestamps.len() {
276 error!(
277 "minetest-gltf: Mismatched node (bone) scales length in channel [{}] of model [{}]. [{}] scale compared to [{}] timestamps. This is now a static model.",
278 bone_id,
279 file_name,
280 scales.len(),
281 timestamps.len());
282
283 bone_animation_channels.clear();
284 break;
285 }
286
287 gotten_animation_channel.scales = scales;
288 gotten_animation_channel.scale_timestamps = timestamps;
289 }
290 Keyframes::Weights(weights) => {
291 let gotten_animation_channel = bone_animation_channels.entry(bone_id).or_default();
292
293 // * If the animation already has weight for this node (bone), that means that something has gone horribly wrong.
294 if !gotten_animation_channel.weights.is_empty() {
295 error!("minetest-gltf: Attempted to overwrite node (bone) channel [{}]'s weight animation data! Model [{}] is broken! This is now a static model", bone_id, file_name);
296 bone_animation_channels.clear();
297 break;
298 }
299
300 // ? We don't do a timestamp comparison here because weights probably shouldn't have timestamp data anyways??
301
302 gotten_animation_channel.weights = weights;
303 gotten_animation_channel.weight_timestamps = timestamps;
304 }
305
306 Keyframes::Explosion => {
307 panic!("minetest-gltf: Explosion was somehow reached in animation!");
308 }
309 }
310 }
311
312 // * Something blew up, it's now a static model.
313 Err(e) => {
314 error!("{}", e);
315 bone_animation_channels.clear();
316 break;
317 }
318 }
319 }
320 }
321
322 bone_animation_channels
323}
324
325pub(crate) fn finalize_animations(
326 minetest_gltf: &mut MinetestGLTF,
327 gltf_data: Gltf,
328 buffers: Vec<Data>,
329 file_name: &str,
330) -> Result<(), Box<dyn Error + Send + Sync>> {
331 // We're going to take the raw data.
332 let bone_animations = grab_animations(gltf_data, buffers, file_name);
333
334 // Then finalize it.
335 // (finalization is interpolating the frames so they're all equal distance from eachother in the scale of time.)
336
337 // Chuck this into a scope so we can have immutable values.
338 let (_min_time, max_time, min_distance) = {
339 let mut min_time_worker = 0.0;
340 let mut max_time_worker = 0.0;
341 let mut min_distance_worker = f32::MAX;
342
343 for (_id, animation) in &bone_animations {
344 // A closure so I don't have to type this out 4 times.
345 let mut devolve_timestamp_data = |raw_timestamps: &Vec<f32>| {
346 let mut old_timestamp = f32::MIN;
347 for timestamp in raw_timestamps {
348 // Time distance data.
349 if *timestamp - old_timestamp < min_distance_worker {
350 min_distance_worker = *timestamp - old_timestamp;
351 }
352
353 // Min time data.
354 if timestamp < &min_time_worker {
355 min_time_worker = *timestamp;
356 }
357 // Max time data.
358 if timestamp > &max_time_worker {
359 max_time_worker = *timestamp;
360 }
361
362 old_timestamp = *timestamp;
363 }
364 };
365
366 // Translation timestamps.
367 devolve_timestamp_data(&animation.translation_timestamps);
368
369 // Rotation timestamps.
370 devolve_timestamp_data(&animation.rotation_timestamps);
371
372 // Scale timestamps.
373 devolve_timestamp_data(&animation.rotation_timestamps);
374
375 // Weight timestamps.
376 devolve_timestamp_data(&animation.weight_timestamps);
377 }
378
379 (min_time_worker, max_time_worker, min_distance_worker)
380 };
381
382 // Now we need a triple checker variable.
383 // We need to make sure that all the channels have this many frames.
384 // This will also work as an iterator.
385 // Timestamps start at 0.0. That's why it's + 1. It's a zero counted container.
386 let required_frames = (max_time / min_distance).round() as usize + 1;
387
388 // println!(
389 // "min_time: {}\nmax_time: {}\nmin_distance: {}\nrequired_frames: {}",
390 // min_time, max_time, min_distance, required_frames
391 // );
392
393 let enable_timestamp_spam = false;
394
395 if enable_timestamp_spam {
396 for i in 0..required_frames {
397 println!("test: {}", i as f32 * min_distance);
398 }
399 }
400
401 // Now we finalize all animation channels.
402 let mut finalized_bone_animations: AHashMap<i32, BoneAnimationChannel> = AHashMap::new();
403
404 for (id, animation) in &bone_animations {
405 // ! This is going to get a bit complicated.
406 // ! Like, extremely complicated.
407
408 // Add a channel to the current id in the finalized animations container.
409 let mut new_finalized_channel = BoneAnimationChannel::new();
410
411 // ? ////////////////////////////////////////////////////////////
412 // ? TRANSLATIONS
413 // ? ////////////////////////////////////////////////////////////
414
415 // Final check for translation equality.
416 if animation.translation_timestamps.len() != animation.translations.len() {
417 return Err(format!("Unequal animation translation lengths in channel {}.", id).into());
418 }
419
420 if animation.translation_timestamps.is_empty() {
421 // error!("hit none");
422 // If it's blank, we want to polyfill in default data.
423 for i in 0..required_frames {
424 new_finalized_channel
425 .translation_timestamps
426 .push(i as f32 * min_distance);
427 new_finalized_channel
428 .translations
429 .push(Vec3::new(0.0, 0.0, 0.0));
430 }
431 } else if animation.translation_timestamps.len() == 1 {
432 // If there's only one, we can simply use the one translation point as the entire translation animation.
433 // error!("hit one");
434 let polyfill = match animation.translations.first() {
435 Some(translation) => translation,
436 None => panic!("translation was already checked, why did this panic!? 1"),
437 };
438
439 for i in 0..required_frames {
440 new_finalized_channel
441 .translation_timestamps
442 .push(i as f32 * min_distance);
443 new_finalized_channel.translations.push(*polyfill);
444 }
445 } else {
446 // Now if we can't polyfill with the easiest data set,
447 // we're going to have to get creative.
448
449 // error!("Hit another?");
450 // println!("got: {}", animation.translation_timestamps.len());
451 // println!("got: {}", animation.translations.len());
452
453 let mut raw_add = false;
454
455 // Let's see if we can take the easist route with start to finish polyfill.
456 match animation.translation_timestamps.first() {
457 Some(first_timestamp) => {
458 if into_precision(*first_timestamp) == 0 {
459 match animation.translation_timestamps.last() {
460 Some(last_timestamp) => {
461 if into_precision(*last_timestamp) == into_precision(max_time) {
462 raw_add = true;
463 }
464 }
465 None => panic!("translation was already checked, why did this panic!? 2"),
466 }
467 }
468 }
469 None => panic!("translation was already checked, why did this panic!? 3"),
470 }
471
472 // Now if we can raw add let's see if we can just dump the raw frames in because they're finalized.
473 if raw_add && animation.translation_timestamps.len() == required_frames {
474 // We can!
475 // error!("OKAY TO RAW ADD!");
476 new_finalized_channel.translation_timestamps = animation.translation_timestamps.clone();
477 new_finalized_channel.translations = animation.translations.clone();
478 } else if raw_add && animation.translation_timestamps.len() == 2 {
479 // But if we only have the start and finish, we now have to polyfill between beginning and end.
480 // error!("POLYFILLING FROM START TO FINISH!");
481 let start = match animation.translations.first() {
482 Some(start) => start,
483 None => panic!("translation was already checked, why did this panic!? 4"),
484 };
485 let finish = match animation.translations.last() {
486 Some(finish) => finish,
487 None => panic!("translation was already checked, why did this panic!? 5"),
488 };
489
490 for i in 0..required_frames {
491 // 0.0 to 1.0.
492 let current_percentile = i as f32 / (required_frames - 1) as f32;
493 // 0.0 to X max time.
494 let current_stamp = current_percentile * max_time;
495
496 // println!("current: {}", current_stamp);
497
498 let result = start.lerp(*finish, current_percentile);
499
500 // println!("result: {:?}", result);
501
502 new_finalized_channel
503 .translation_timestamps
504 .push(current_stamp);
505 new_finalized_channel.translations.push(result);
506 }
507 } else {
508 // And if we can't do either of those, now we have to brute force our way through the polyfill calculations. :(
509
510 // To begin this atrocity let's start by grabbing the current size of the animation container.
511 let old_frame_size = animation.translation_timestamps.len();
512
513 // This gives me great pain.
514 for i in 0..required_frames {
515 // 0.0 to 1.0.
516 let current_percentile = i as f32 / (required_frames - 1) as f32;
517 // 0.0 to X max time.
518 let current_stamp = current_percentile * max_time;
519 // 5 points of precision integral positioning.
520 let precise_stamp = into_precision(current_stamp);
521
522 // Okay now that we got our data, let's see if this model has it.
523 // We need index ONLY cause we have to walk back and forth.
524 // There might be a logic thing missing in here. If you find it. Halp.
525 // ? Fun begins here.
526 let mut found_frame_key = None;
527
528 // Let's find if we have a frame that already exists in the animation.
529 for i in 0..old_frame_size {
530 let gotten = animation.translation_timestamps[i];
531
532 let gotten_precise = into_precision(gotten);
533
534 // We got lucky and found an existing frame! :D
535 if gotten_precise == precise_stamp {
536 found_frame_key = Some(i);
537 break;
538 }
539
540 // And if this loop completes and we didn't find anything. We gotta get creative.
541 }
542
543 // If it's none we now have to either interpolate this thing or we have to insert it.
544 if found_frame_key.is_none() {
545 // If there's no starting keyframe.
546 // First of all, why is this allowed?
547 // Second of all, polyfill from the next available frame.
548 // We know this thing has more than 2 available frames at this point.
549 if precise_stamp == 0 {
550 new_finalized_channel
551 .translation_timestamps
552 .push(current_stamp);
553 // If this crashes, there's something truly horrible that has happened.
554 new_finalized_channel
555 .translations
556 .push(animation.translations[1]);
557 } else {
558 // Else we're going to have to figure this mess out.
559 // ! Here is where the program performance just tanks.
560
561 // ? So we have no direct frame, we have to find out 2 things:
562 // ? 1.) The leading frame.
563 // ? 2.) The following frame.
564 // ? Then we have to interpolate them together.
565
566 // This is an option because if it's none, we have to brute force with animation frame 0.
567 let mut leading_frame = None;
568
569 for i in 0..old_frame_size {
570 let gotten = animation.translation_timestamps[i];
571
572 let gotten_precise = into_precision(gotten);
573
574 // Here we check for a frame that is less than goal.
575 // aka, the leading frame.
576 // We already checked if it's got an equal to frame, there's only unequal to frames now.
577 // We need to let this keep going until it overshoots or else it won't be accurate.
578 if gotten_precise < precise_stamp {
579 leading_frame = Some(i);
580 } else {
581 // We overshot, now time to abort.
582 break;
583 }
584 }
585
586 // ! If we have no leading leading frame is now whatever is first.
587 if leading_frame.is_none() {
588 leading_frame = Some(0);
589 }
590
591 // This is an option because if it's none, we have to brute force with animation frame 0.
592 let mut following_frame = None;
593
594 for i in 0..old_frame_size {
595 let gotten = animation.translation_timestamps[i];
596
597 let gotten_precise = into_precision(gotten);
598
599 // Here we check for a frame that is less than goal.
600 // aka, the leading frame.
601 // We already checked if it's got an equal to frame, there's only unequal to frames now.
602 // We need to let this keep going until it overshoots or else it won't be accurate.
603 if gotten_precise > precise_stamp {
604 following_frame = Some(i);
605 }
606
607 // Can't do a logic gate in the previous statement. If it's found then break.
608 if following_frame.is_some() {
609 break;
610 }
611 }
612
613 // ? If it's none, the safe fallback is to just equalize the start and finish, which is extremely wrong.
614 if following_frame.is_none() {
615 following_frame = leading_frame;
616 }
617
618 // Now we do the interpolation.
619 // This isn't perfect, but it's something.
620 match leading_frame {
621 Some(leader) => match following_frame {
622 Some(follower) => {
623 let lead_timestamp = animation.translation_timestamps[leader];
624 let lead_translation = animation.translations[leader];
625
626 let follow_timestamp = animation.translation_timestamps[follower];
627 let follow_translation = animation.translations[follower];
628
629 // This is a simple zeroing out of the scales.
630 let scale = follow_timestamp - lead_timestamp;
631
632 // Shift the current timestamp into the range of our work.
633 let shifted_stamp = current_stamp - lead_timestamp;
634
635 // Get it into 0.0 - 1.0.
636 let finalized_percentile = shifted_stamp / scale;
637
638 // println!("finalized: {}", finalized_percentile);
639
640 let finalized_translation_interpolation =
641 lead_translation.lerp(follow_translation, finalized_percentile);
642
643 // Now we finally push the interpolated translation into the finalized animation channel.
644 new_finalized_channel
645 .translations
646 .push(finalized_translation_interpolation);
647 new_finalized_channel
648 .translation_timestamps
649 .push(current_stamp);
650 }
651 None => panic!("how?!"),
652 },
653 None => panic!("how?!"),
654 }
655 }
656 } else {
657 // ! We found a keyframe! :D
658 // If it's some we have an existing good frame, work with it.
659 let key = match found_frame_key {
660 Some(key) => key,
661 None => panic!("how is that even possible?!"),
662 };
663
664 // This should never blow up. That's immutable data it's working with, within range!
665 new_finalized_channel
666 .translation_timestamps
667 .push(animation.translation_timestamps[key]);
668
669 new_finalized_channel
670 .translations
671 .push(animation.translations[key]);
672 }
673
674 // println!("test: {:?}", found_frame_key);
675
676 // println!("{} {}", current_stamp, precise_stamp);
677 }
678
679 // panic!("minetest-gltf: This translation logic branch is disabled because I have no model that has this available yet. If this is hit. Give me your model.")
680 }
681 }
682
683 if new_finalized_channel.translation_timestamps.len()
684 != new_finalized_channel.translations.len()
685 {
686 panic!("BLEW UP! Mismatched translation lengths.");
687 }
688 if new_finalized_channel.translation_timestamps.len() != required_frames {
689 panic!(
690 "BLEW UP! translation frames Expected: {} got: {}",
691 required_frames,
692 new_finalized_channel.translation_timestamps.len()
693 );
694 }
695
696 // println!("t: {:?}", new_finalized_channel.translations);
697 // println!("t: {:?}", new_finalized_channel.translation_timestamps);
698
699 // println!("-=-=-=-=-");
700
701 // ? ////////////////////////////////////////////////////////////
702 // ? ROTATIONS
703 // ? ////////////////////////////////////////////////////////////
704
705 // Final check for rotation equality.
706 if animation.rotation_timestamps.len() != animation.rotations.len() {
707 return Err(format!("Unequal animation rotation lengths in channel {}.", id).into());
708 }
709
710 if animation.rotation_timestamps.is_empty() {
711 // error!("hit none");
712 // If it's blank, we want to polyfill in default data.
713 for i in 0..required_frames {
714 new_finalized_channel
715 .rotation_timestamps
716 .push(i as f32 * min_distance);
717 new_finalized_channel.rotations.push(Quat::IDENTITY);
718 }
719 } else if animation.rotation_timestamps.len() == 1 {
720 // If there's only one, we can simply use the one rotation point as the entire rotation animation.
721 // error!("hit one");
722 let polyfill = match animation.rotations.first() {
723 Some(rotation) => rotation,
724 None => panic!("rotation was already checked, why did this panic!? 1"),
725 };
726
727 for i in 0..required_frames {
728 new_finalized_channel
729 .rotation_timestamps
730 .push(i as f32 * min_distance);
731 new_finalized_channel.rotations.push(*polyfill);
732 }
733 } else {
734 // Now if we can't polyfill with the easiest data set,
735 // we're going to have to get creative.
736
737 // error!("Hit another?");
738 // println!("got: {}", animation.rotation_timestamps.len());
739 // println!("got: {}", animation.rotations.len());
740
741 let mut raw_add = false;
742
743 // Let's see if we can take the easist route with start to finish polyfill.
744 match animation.rotation_timestamps.first() {
745 Some(first_timestamp) => {
746 if into_precision(*first_timestamp) == 0 {
747 match animation.rotation_timestamps.last() {
748 Some(last_timestamp) => {
749 if into_precision(*last_timestamp) == into_precision(max_time) {
750 raw_add = true;
751 }
752 }
753 None => panic!("rotation was already checked, why did this panic!? 2"),
754 }
755 }
756 }
757 None => panic!("rotation was already checked, why did this panic!? 3"),
758 }
759
760 // Now if we can raw add let's see if we can just dump the raw frames in because they're finalized.
761 if raw_add && animation.rotation_timestamps.len() == required_frames {
762 // We can!
763 // error!("OKAY TO RAW ADD!");
764 new_finalized_channel.rotation_timestamps = animation.rotation_timestamps.clone();
765 new_finalized_channel.rotations = animation.rotations.clone();
766 } else if raw_add && animation.rotation_timestamps.len() == 2 {
767 // But if we only have the start and finish, we now have to polyfill between beginning and end.
768 // error!("POLYFILLING FROM START TO FINISH!");
769 let start = match animation.rotations.first() {
770 Some(start) => start,
771 None => panic!("rotation was already checked, why did this panic!? 4"),
772 };
773 let finish = match animation.rotations.last() {
774 Some(finish) => finish,
775 None => panic!("rotation was already checked, why did this panic!? 5"),
776 };
777
778 for i in 0..required_frames {
779 // 0.0 to 1.0.
780 let current_percentile = i as f32 / (required_frames - 1) as f32;
781 // 0.0 to X max time.
782 let current_stamp = current_percentile * max_time;
783
784 // println!("current: {}", current_stamp);
785
786 let result = start.lerp(*finish, current_percentile);
787
788 // println!("result: {:?}", result);
789
790 new_finalized_channel
791 .rotation_timestamps
792 .push(current_stamp);
793 new_finalized_channel.rotations.push(result);
794 }
795 } else {
796 // And if we can't do either of those, now we have to brute force our way through the polyfill calculations. :(
797
798 // To begin this atrocity let's start by grabbing the current size of the animation container.
799 let old_frame_size = animation.rotation_timestamps.len();
800
801 // This gives me great pain.
802 for i in 0..required_frames {
803 // 0.0 to 1.0.
804 let current_percentile = i as f32 / (required_frames - 1) as f32;
805 // 0.0 to X max time.
806 let current_stamp = current_percentile * max_time;
807 // 5 points of precision integral positioning.
808 let precise_stamp = into_precision(current_stamp);
809
810 // Okay now that we got our data, let's see if this model has it.
811 // We need index ONLY cause we have to walk back and forth.
812 // There might be a logic thing missing in here. If you find it. Halp.
813 // ? Fun begins here.
814 let mut found_frame_key = None;
815
816 // Let's find if we have a frame that already exists in the animation.
817 for i in 0..old_frame_size {
818 let gotten = animation.rotation_timestamps[i];
819
820 let gotten_precise = into_precision(gotten);
821
822 // We got lucky and found an existing frame! :D
823 if gotten_precise == precise_stamp {
824 found_frame_key = Some(i);
825 break;
826 }
827
828 // And if this loop completes and we didn't find anything. We gotta get creative.
829 }
830
831 // If it's none we now have to either interpolate this thing or we have to insert it.
832 if found_frame_key.is_none() {
833 // If there's no starting keyframe.
834 // First of all, why is this allowed?
835 // Second of all, polyfill from the next available frame.
836 // We know this thing has more than 2 available frames at this point.
837 if precise_stamp == 0 {
838 new_finalized_channel
839 .rotation_timestamps
840 .push(current_stamp);
841 // If this crashes, there's something truly horrible that has happened.
842 new_finalized_channel.rotations.push(animation.rotations[1]);
843 } else {
844 // Else we're going to have to figure this mess out.
845 // ! Here is where the program performance just tanks.
846
847 // ? So we have no direct frame, we have to find out 2 things:
848 // ? 1.) The leading frame.
849 // ? 2.) The following frame.
850 // ? Then we have to interpolate them together.
851
852 // This is an option because if it's none, we have to brute force with animation frame 0.
853 let mut leading_frame = None;
854
855 for i in 0..old_frame_size {
856 let gotten = animation.rotation_timestamps[i];
857
858 let gotten_precise = into_precision(gotten);
859
860 // Here we check for a frame that is less than goal.
861 // aka, the leading frame.
862 // We already checked if it's got an equal to frame, there's only unequal to frames now.
863 // We need to let this keep going until it overshoots or else it won't be accurate.
864 if gotten_precise < precise_stamp {
865 leading_frame = Some(i);
866 } else {
867 // We overshot, now time to abort.
868 break;
869 }
870 }
871
872 // ! If we have no leading leading frame is now whatever is first.
873 if leading_frame.is_none() {
874 leading_frame = Some(0);
875 }
876
877 // This is an option because if it's none, we have to brute force with animation frame 0.
878 let mut following_frame = None;
879
880 for i in 0..old_frame_size {
881 let gotten = animation.rotation_timestamps[i];
882
883 let gotten_precise = into_precision(gotten);
884
885 // Here we check for a frame that is less than goal.
886 // aka, the leading frame.
887 // We already checked if it's got an equal to frame, there's only unequal to frames now.
888 // We need to let this keep going until it overshoots or else it won't be accurate.
889 if gotten_precise > precise_stamp {
890 following_frame = Some(i);
891 }
892
893 // Can't do a logic gate in the previous statement. If it's found then break.
894 if following_frame.is_some() {
895 break;
896 }
897 }
898
899 // ? If it's none, the safe fallback is to just equalize the start and finish, which is extremely wrong.
900 if following_frame.is_none() {
901 following_frame = leading_frame;
902 }
903
904 // Now we do the interpolation.
905 // This isn't perfect, but it's something.
906 match leading_frame {
907 Some(leader) => match following_frame {
908 Some(follower) => {
909 let lead_timestamp = animation.rotation_timestamps[leader];
910 let lead_rotation = animation.rotations[leader];
911
912 let follow_timestamp = animation.rotation_timestamps[follower];
913 let follow_rotation = animation.rotations[follower];
914
915 // This is a simple zeroing out of the scales.
916 let scale = follow_timestamp - lead_timestamp;
917
918 // Shift the current timestamp into the range of our work.
919 let shifted_stamp = current_stamp - lead_timestamp;
920
921 // Get it into 0.0 - 1.0.
922 let finalized_percentile = shifted_stamp / scale;
923
924 // println!("finalized: {}", finalized_percentile);
925
926 let finalized_rotation_interpolation =
927 lead_rotation.lerp(follow_rotation, finalized_percentile);
928
929 // Now we finally push the interpolated rotation into the finalized animation channel.
930 new_finalized_channel
931 .rotations
932 .push(finalized_rotation_interpolation);
933 new_finalized_channel
934 .rotation_timestamps
935 .push(current_stamp);
936 }
937 None => panic!("how?!"),
938 },
939 None => panic!("how?!"),
940 }
941 }
942 } else {
943 // ! We found a keyframe! :D
944 // If it's some we have an existing good frame, work with it.
945 let key = match found_frame_key {
946 Some(key) => key,
947 None => panic!("how is that even possible?!"),
948 };
949
950 // This should never blow up. That's immutable data it's working with, within range!
951 new_finalized_channel
952 .rotation_timestamps
953 .push(animation.rotation_timestamps[key]);
954
955 new_finalized_channel
956 .rotations
957 .push(animation.rotations[key]);
958 }
959
960 // println!("test: {:?}", found_frame_key);
961
962 // println!("{} {}", current_stamp, precise_stamp);
963 }
964
965 // panic!("minetest-gltf: This rotation logic branch is disabled because I have no model that has this available yet. If this is hit. Give me your model.")
966 }
967 }
968
969 if new_finalized_channel.rotation_timestamps.len() != new_finalized_channel.rotations.len() {
970 panic!("BLEW UP! Mismatched rotation lengths.");
971 }
972 if new_finalized_channel.rotation_timestamps.len() != required_frames {
973 panic!(
974 "BLEW UP! rotation frames Expected: {} got: {}",
975 required_frames,
976 new_finalized_channel.rotation_timestamps.len()
977 );
978 }
979
980 // println!("t: {:?}", new_finalized_channel.rotations);
981 // println!("t: {:?}", new_finalized_channel.rotation_timestamps);
982
983 // ? ////////////////////////////////////////////////////////////
984 // ? SCALES
985 // ? ////////////////////////////////////////////////////////////
986
987 // Final check for scale equality.
988 if animation.scale_timestamps.len() != animation.scales.len() {
989 return Err(format!("Unequal animation scale lengths in channel {}.", id).into());
990 }
991
992 if animation.scale_timestamps.is_empty() {
993 // error!("hit none");
994 // If it's blank, we want to polyfill in default data.
995 for i in 0..required_frames {
996 new_finalized_channel
997 .scale_timestamps
998 .push(i as f32 * min_distance);
999 new_finalized_channel.scales.push(Vec3::new(1.0, 1.0, 1.0));
1000 }
1001 } else if animation.scale_timestamps.len() == 1 {
1002 // If there's only one, we can simply use the one scale point as the entire scale animation.
1003 // error!("hit one");
1004 let polyfill = match animation.scales.first() {
1005 Some(scale) => scale,
1006 None => panic!("scale was already checked, why did this panic!? 1"),
1007 };
1008
1009 for i in 0..required_frames {
1010 new_finalized_channel
1011 .scale_timestamps
1012 .push(i as f32 * min_distance);
1013 new_finalized_channel.scales.push(*polyfill);
1014 }
1015 } else {
1016 // Now if we can't polyfill with the easiest data set,
1017 // we're going to have to get creative.
1018
1019 // error!("Hit another?");
1020 // println!("got: {}", animation.scale_timestamps.len());
1021 // println!("got: {}", animation.scales.len());
1022
1023 let mut raw_add = false;
1024
1025 // Let's see if we can take the easist route with start to finish polyfill.
1026 match animation.scale_timestamps.first() {
1027 Some(first_timestamp) => {
1028 if into_precision(*first_timestamp) == 0 {
1029 match animation.scale_timestamps.last() {
1030 Some(last_timestamp) => {
1031 if into_precision(*last_timestamp) == into_precision(max_time) {
1032 raw_add = true;
1033 }
1034 }
1035 None => panic!("scale was already checked, why did this panic!? 2"),
1036 }
1037 }
1038 }
1039 None => panic!("scale was already checked, why did this panic!? 3"),
1040 }
1041
1042 // Now if we can raw add let's see if we can just dump the raw frames in because they're finalized.
1043 if raw_add && animation.scale_timestamps.len() == required_frames {
1044 // We can!
1045 // error!("OKAY TO RAW ADD!");
1046 new_finalized_channel.scale_timestamps = animation.scale_timestamps.clone();
1047 new_finalized_channel.scales = animation.scales.clone();
1048 } else if raw_add && animation.scale_timestamps.len() == 2 {
1049 // But if we only have the start and finish, we now have to polyfill between beginning and end.
1050 // error!("POLYFILLING FROM START TO FINISH!");
1051 let start = match animation.scales.first() {
1052 Some(start) => start,
1053 None => panic!("scale was already checked, why did this panic!? 4"),
1054 };
1055 let finish = match animation.scales.last() {
1056 Some(finish) => finish,
1057 None => panic!("scale was already checked, why did this panic!? 5"),
1058 };
1059
1060 for i in 0..required_frames {
1061 // 0.0 to 1.0.
1062 let current_percentile = i as f32 / (required_frames - 1) as f32;
1063 // 0.0 to X max time.
1064 let current_stamp = current_percentile * max_time;
1065
1066 // println!("current: {}", current_stamp);
1067
1068 let result = start.lerp(*finish, current_percentile);
1069
1070 // println!("result: {:?}", result);
1071
1072 new_finalized_channel.scale_timestamps.push(current_stamp);
1073 new_finalized_channel.scales.push(result);
1074 }
1075 } else {
1076 // And if we can't do either of those, now we have to brute force our way through the polyfill calculations. :(
1077
1078 // To begin this atrocity let's start by grabbing the current size of the animation container.
1079 let old_frame_size = animation.scale_timestamps.len();
1080
1081 // This gives me great pain.
1082 for i in 0..required_frames {
1083 // 0.0 to 1.0.
1084 let current_percentile = i as f32 / (required_frames - 1) as f32;
1085 // 0.0 to X max time.
1086 let current_stamp = current_percentile * max_time;
1087 // 5 points of precision integral positioning.
1088 let precise_stamp = into_precision(current_stamp);
1089
1090 // Okay now that we got our data, let's see if this model has it.
1091 // We need index ONLY cause we have to walk back and forth.
1092 // There might be a logic thing missing in here. If you find it. Halp.
1093 // ? Fun begins here.
1094 let mut found_frame_key = None;
1095
1096 // Let's find if we have a frame that already exists in the animation.
1097 for i in 0..old_frame_size {
1098 let gotten = animation.scale_timestamps[i];
1099
1100 let gotten_precise = into_precision(gotten);
1101
1102 // We got lucky and found an existing frame! :D
1103 if gotten_precise == precise_stamp {
1104 found_frame_key = Some(i);
1105 break;
1106 }
1107
1108 // And if this loop completes and we didn't find anything. We gotta get creative.
1109 }
1110
1111 // If it's none we now have to either interpolate this thing or we have to insert it.
1112 if found_frame_key.is_none() {
1113 // If there's no starting keyframe.
1114 // First of all, why is this allowed?
1115 // Second of all, polyfill from the next available frame.
1116 // We know this thing has more than 2 available frames at this point.
1117 if precise_stamp == 0 {
1118 new_finalized_channel.scale_timestamps.push(current_stamp);
1119 // If this crashes, there's something truly horrible that has happened.
1120 new_finalized_channel.scales.push(animation.scales[1]);
1121 } else {
1122 // Else we're going to have to figure this mess out.
1123 // ! Here is where the program performance just tanks.
1124
1125 // ? So we have no direct frame, we have to find out 2 things:
1126 // ? 1.) The leading frame.
1127 // ? 2.) The following frame.
1128 // ? Then we have to interpolate them together.
1129
1130 // This is an option because if it's none, we have to brute force with animation frame 0.
1131 let mut leading_frame = None;
1132
1133 for i in 0..old_frame_size {
1134 let gotten = animation.scale_timestamps[i];
1135
1136 let gotten_precise = into_precision(gotten);
1137
1138 // Here we check for a frame that is less than goal.
1139 // aka, the leading frame.
1140 // We already checked if it's got an equal to frame, there's only unequal to frames now.
1141 // We need to let this keep going until it overshoots or else it won't be accurate.
1142 if gotten_precise < precise_stamp {
1143 leading_frame = Some(i);
1144 } else {
1145 // We overshot, now time to abort.
1146 break;
1147 }
1148 }
1149
1150 // ! If we have no leading leading frame is now whatever is first.
1151 if leading_frame.is_none() {
1152 leading_frame = Some(0);
1153 }
1154
1155 // This is an option because if it's none, we have to brute force with animation frame 0.
1156 let mut following_frame = None;
1157
1158 for i in 0..old_frame_size {
1159 let gotten = animation.scale_timestamps[i];
1160
1161 let gotten_precise = into_precision(gotten);
1162
1163 // Here we check for a frame that is less than goal.
1164 // aka, the leading frame.
1165 // We already checked if it's got an equal to frame, there's only unequal to frames now.
1166 // We need to let this keep going until it overshoots or else it won't be accurate.
1167 if gotten_precise > precise_stamp {
1168 following_frame = Some(i);
1169 }
1170
1171 // Can't do a logic gate in the previous statement. If it's found then break.
1172 if following_frame.is_some() {
1173 break;
1174 }
1175 }
1176
1177 // ? If it's none, the safe fallback is to just equalize the start and finish, which is extremely wrong.
1178 if following_frame.is_none() {
1179 following_frame = leading_frame;
1180 }
1181
1182 // Now we do the interpolation.
1183 // This isn't perfect, but it's something.
1184 match leading_frame {
1185 Some(leader) => match following_frame {
1186 Some(follower) => {
1187 let lead_timestamp = animation.scale_timestamps[leader];
1188 let lead_scale = animation.scales[leader];
1189
1190 let follow_timestamp = animation.scale_timestamps[follower];
1191 let follow_scale = animation.scales[follower];
1192
1193 // This is a simple zeroing out of the scales.
1194 let scale = follow_timestamp - lead_timestamp;
1195
1196 // Shift the current timestamp into the range of our work.
1197 let shifted_stamp = current_stamp - lead_timestamp;
1198
1199 // Get it into 0.0 - 1.0.
1200 let finalized_percentile = shifted_stamp / scale;
1201
1202 // println!("finalized: {}", finalized_percentile);
1203
1204 let finalized_scale_interpolation =
1205 lead_scale.lerp(follow_scale, finalized_percentile);
1206
1207 // Now we finally push the interpolated scale into the finalized animation channel.
1208 new_finalized_channel
1209 .scales
1210 .push(finalized_scale_interpolation);
1211 new_finalized_channel.scale_timestamps.push(current_stamp);
1212 }
1213 None => panic!("how?!"),
1214 },
1215 None => panic!("how?!"),
1216 }
1217 }
1218 } else {
1219 // ! We found a keyframe! :D
1220 // If it's some we have an existing good frame, work with it.
1221 let key = match found_frame_key {
1222 Some(key) => key,
1223 None => panic!("how is that even possible?!"),
1224 };
1225
1226 // This should never blow up. That's immutable data it's working with, within range!
1227 new_finalized_channel
1228 .scale_timestamps
1229 .push(animation.scale_timestamps[key]);
1230
1231 new_finalized_channel.scales.push(animation.scales[key]);
1232 }
1233
1234 // println!("test: {:?}", found_frame_key);
1235
1236 // println!("{} {}", current_stamp, precise_stamp);
1237 }
1238
1239 // panic!("minetest-gltf: This scale logic branch is disabled because I have no model that has this available yet. If this is hit. Give me your model.")
1240 }
1241 }
1242
1243 if new_finalized_channel.scale_timestamps.len() != new_finalized_channel.scales.len() {
1244 panic!("BLEW UP! Mismatched scale lengths.");
1245 }
1246 if new_finalized_channel.scale_timestamps.len() != required_frames {
1247 panic!(
1248 "BLEW UP! scale frames Expected: {} got: {}",
1249 required_frames,
1250 new_finalized_channel.scale_timestamps.len()
1251 );
1252 }
1253
1254 // println!("t: {:?}", new_finalized_channel.scales);
1255 // println!("t: {:?}", new_finalized_channel.scale_timestamps);
1256
1257 // println!("-=-=-=-=-");
1258
1259 // Finally add it in.
1260 // println!("Adding in channel: {}", id);
1261 finalized_bone_animations.insert(*id, new_finalized_channel);
1262 }
1263
1264 // Then insert the finalized data here.
1265 minetest_gltf.bone_animations = Some(finalized_bone_animations);
1266 minetest_gltf.is_animated = true;
1267
1268 Ok(())
1269}