Initial work on ledge & improved baking of root motion.
CI / build-and-test (pull_request) Successful in 2m29s
CI / production-build (pull_request) Successful in 11m34s

This commit is contained in:
2026-10-12 22:24:10 +02:00
parent 0642a9234b
commit 6b7f1db9fe
22 changed files with 1094 additions and 141 deletions
+39 -9
View File
@@ -27,6 +27,7 @@ struct FighterBakeSource {
model_bytes: Vec<u8>,
buffers: Vec<Vec<u8>>,
animations: HashMap<AnimKind, String>,
root_motion_bone: String,
}
#[derive(Default, TypePath)]
struct FighterBakeSourceLoader;
@@ -82,6 +83,7 @@ impl AssetLoader for FighterBakeSourceLoader {
model_bytes,
buffers,
animations: manifest.animations,
root_motion_bone: manifest.root_motion_bone,
})
}
fn extensions(&self) -> &[&str] {
@@ -123,7 +125,12 @@ impl AssetTransformer for FighterBakeTransformer {
source: TransformedAsset<Self::AssetInput>,
_: &'a Self::Settings,
) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
let baked = bake_gltf(&source.model_bytes, &source.buffers, &source.animations)?;
let baked = bake_gltf(
&source.model_bytes,
&source.buffers,
&source.animations,
&source.root_motion_bone,
)?;
Ok(source.replace_asset(baked))
}
}
@@ -201,6 +208,7 @@ fn bake_gltf(
bytes: &[u8],
buffer_bytes: &[Vec<u8>],
animations: &HashMap<AnimKind, String>,
root_motion_bone: &str,
) -> io::Result<BakedFighterAnimations> {
let gltf = gltf::Gltf::from_slice(bytes)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
@@ -228,13 +236,14 @@ fn bake_gltf(
"glTF buffer data is incomplete",
));
}
bake_gltf_document(gltf.document, buffers, animations)
bake_gltf_document(gltf.document, buffers, animations, root_motion_bone)
}
pub(super) fn bake_gltf_document(
document: gltf::Document,
buffers: Vec<gltf::buffer::Data>,
animations: &HashMap<AnimKind, String>,
root_motion_bone: &str,
) -> io::Result<BakedFighterAnimations> {
let nodes: Vec<_> = document.nodes().collect();
if nodes.is_empty() {
@@ -260,6 +269,15 @@ pub(super) fn bake_gltf_document(
})
})
.collect::<io::Result<Vec<_>>>()?;
let root_bone_index = names
.iter()
.position(|name| name == root_motion_bone)
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("fighter model has no root-motion bone named {root_motion_bone}"),
)
})?;
let mut pending = HashMap::<&str, Vec<AnimKind>>::new();
for (kind, name) in animations {
pending.entry(name).or_default().push(*kind);
@@ -286,17 +304,28 @@ pub(super) fn bake_gltf_document(
.fold(0.0_f32, f32::max);
let frame_count = baked_frame_count(duration, BAKED_ANIMATION_FPS);
let mut frames = Vec::with_capacity(frame_count);
let mut root_positions = Vec::with_capacity(frame_count);
for frame in 0..frame_count {
let time = (frame as f32 / BAKED_ANIMATION_FPS as f32).min(duration);
frames.push(
nodes
.iter()
.map(|node| local_transform(node.clone(), &channels, time))
.collect::<io::Result<Vec<_>>>()?,
);
let local = nodes
.iter()
.map(|node| local_transform(node.clone(), &channels, time))
.collect::<io::Result<Vec<_>>>()?;
let mut model = vec![None; local.len()];
let root = super::runtime::resolve_model_transform(
root_bone_index,
&parents,
&local,
&mut model,
)?;
root_positions.push(root.get_translation());
frames.push(local);
}
let index = clips.len();
clips.push(BakedAnimationClip { frames });
clips.push(BakedAnimationClip {
frames,
root_positions,
});
for kind in kinds {
clip_indices.insert(kind, index);
}
@@ -322,6 +351,7 @@ pub(super) fn bake_gltf_document(
frame_rate: BAKED_ANIMATION_FPS,
bone_names: names,
parents,
root_bone_index,
clips,
clip_indices,
})