From b0954d25fba9effed4351b8f96fb96e18e33f942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Rom=C3=A1n=20N=C3=BA=C3=B1ez?= Date: Mon, 21 Sep 2026 22:23:18 +0200 Subject: [PATCH] Fix animation baking unit tests --- src/fighter/baked_animation/mod.rs | 105 +++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 5 deletions(-) diff --git a/src/fighter/baked_animation/mod.rs b/src/fighter/baked_animation/mod.rs index a0b4663..d5b6213 100644 --- a/src/fighter/baked_animation/mod.rs +++ b/src/fighter/baked_animation/mod.rs @@ -21,6 +21,7 @@ mod tests { use asset_baking::{ Channel, ChannelValues, bake_gltf_document, baked_frame_count, sample_vec3, }; + use serde_json::json; use std::collections::HashMap; #[test] @@ -29,11 +30,7 @@ mod tests { "../../../assets/fighters/test/test.fighter.ron" )) .unwrap(); - let (document, buffers, _) = gltf::import(concat!( - env!("CARGO_MANIFEST_DIR"), - "/assets/fighters/test/test_fighter.gltf" - )) - .unwrap(); + let (document, buffers) = test_gltf(&manifest.animations); let baked = bake_gltf_document(document, buffers, &manifest.animations).unwrap(); assert_eq!(baked.frame_rate, BAKED_ANIMATION_FPS); @@ -51,6 +48,104 @@ mod tests { assert_eq!(baked.parents.len(), baked.bone_names.len()); } + /// Builds the smallest glTF document that still exercises the complete + /// animation-baking path. Keeping this fixture in memory avoids making + /// unit tests depend on the production Blender asset or Blender itself. + fn test_gltf( + animations: &HashMap, + ) -> (gltf::Document, Vec) { + let mut names: Vec<_> = animations.values().cloned().collect(); + names.sort(); + names.dedup(); + + let mut data = Vec::new(); + let mut buffer_views = Vec::new(); + let mut accessors = Vec::new(); + let mut gltf_animations = Vec::new(); + + for name in names { + let duration: f32 = match name.as_str() { + "Idle_Loop_Game" => 2.0, + "Walk_Loop_IK_Game" | "Run_Game" => 0.533333361, + "Dash_Game" => 0.333333343, + "Jump_Land_Game" => 0.133333340, + "JumpB_Game" => 56.0 / 60.0, + _ => 0.0, + }; + let input_offset = data.len(); + data.extend_from_slice(&0.0_f32.to_le_bytes()); + data.extend_from_slice(&duration.to_le_bytes()); + let output_offset = data.len(); + data.extend_from_slice( + &[0.0_f32, 0.0, 0.0] + .into_iter() + .flat_map(f32::to_le_bytes) + .collect::>(), + ); + data.extend_from_slice( + &[1.0_f32, 0.0, 0.0] + .into_iter() + .flat_map(f32::to_le_bytes) + .collect::>(), + ); + + let input_view = buffer_views.len(); + buffer_views.push(json!({ + "buffer": 0, + "byteOffset": input_offset, + "byteLength": 8 + })); + let output_view = buffer_views.len(); + buffer_views.push(json!({ + "buffer": 0, + "byteOffset": output_offset, + "byteLength": 24 + })); + let input_accessor = accessors.len(); + accessors.push(json!({ + "bufferView": input_view, + "componentType": 5126, + "count": 2, + "type": "SCALAR", + "min": [0.0], + "max": [duration] + })); + let output_accessor = accessors.len(); + accessors.push(json!({ + "bufferView": output_view, + "componentType": 5126, + "count": 2, + "type": "VEC3" + })); + gltf_animations.push(json!({ + "name": name, + "samplers": [{ + "input": input_accessor, + "output": output_accessor, + "interpolation": "LINEAR" + }], + "channels": [{ + "sampler": 0, + "target": { "node": 0, "path": "translation" } + }] + })); + } + + let document = json!({ + "asset": { "version": "2.0" }, + "scene": 0, + "scenes": [{ "nodes": [0] }], + "nodes": [{ "name": "root", "children": [1] }, { "name": "hand" }], + "buffers": [{ "byteLength": data.len() }], + "bufferViews": buffer_views, + "accessors": accessors, + "animations": gltf_animations + }); + let bytes = serde_json::to_vec(&document).unwrap(); + let gltf = gltf::Gltf::from_slice(&bytes).unwrap(); + (gltf.document, vec![gltf::buffer::Data(data)]) + } + #[test] fn pose_sampling_wraps_repeating_frames_and_clamps_finished_frames() { let first = FixedMat4::IDENTITY;