Initial work on ledge & improved baking of root motion. (#90)
Reviewed-on: #90 Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
This commit was merged in pull request #90.
This commit is contained in:
@@ -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,
|
||||
})
|
||||
|
||||
@@ -36,7 +36,13 @@ mod tests {
|
||||
))
|
||||
.unwrap();
|
||||
let (document, buffers) = test_gltf(&manifest.animations);
|
||||
let baked = bake_gltf_document(document, buffers, &manifest.animations).unwrap();
|
||||
let baked = bake_gltf_document(
|
||||
document,
|
||||
buffers,
|
||||
&manifest.animations,
|
||||
&manifest.root_motion_bone,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(baked.frame_rate, BAKED_ANIMATION_FPS);
|
||||
assert_eq!(baked.frame_count(AnimKind::Wait), Some(121));
|
||||
@@ -51,6 +57,15 @@ mod tests {
|
||||
baked.bone_names.len()
|
||||
);
|
||||
assert_eq!(baked.parents.len(), baked.bone_names.len());
|
||||
assert_eq!(baked.root_bone_index, 0);
|
||||
assert_eq!(
|
||||
baked.initial_root_position(AnimKind::Wait, FGi32::lit("2")),
|
||||
Some(FGVec3::ZERO)
|
||||
);
|
||||
assert_eq!(
|
||||
baked.root_motion_cycle_delta(AnimKind::Wait, FGi32::lit("2")),
|
||||
Some(FGVec3::lit("2", "0", "0"))
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds the smallest glTF document that still exercises the complete
|
||||
@@ -162,8 +177,14 @@ mod tests {
|
||||
frame_rate: BAKED_ANIMATION_FPS,
|
||||
bone_names: vec!["root".into()],
|
||||
parents: vec![None],
|
||||
root_bone_index: 0,
|
||||
clips: vec![BakedAnimationClip {
|
||||
frames: vec![vec![first], vec![second], vec![terminal]],
|
||||
root_positions: vec![
|
||||
FGVec3::ZERO,
|
||||
FGVec3::lit("2", "0", "0"),
|
||||
FGVec3::lit("4", "0", "0"),
|
||||
],
|
||||
}],
|
||||
clip_indices: HashMap::from([(AnimKind::Wait, 0)]),
|
||||
};
|
||||
@@ -339,7 +360,8 @@ mod tests {
|
||||
|
||||
matrices.set_pose_with_frame(
|
||||
frame,
|
||||
"root",
|
||||
Some(0),
|
||||
Some(FGVec3::lit("2", "0", "0")),
|
||||
FGVec3::ZERO,
|
||||
None,
|
||||
AnimKind::Wait,
|
||||
@@ -349,10 +371,15 @@ mod tests {
|
||||
FGi32::lit("2"),
|
||||
);
|
||||
assert_eq!(matrices.root_motion_delta(), FGVec3::ZERO);
|
||||
assert_eq!(
|
||||
matrices.root_motion_position(),
|
||||
Some(FGVec3::lit("2", "0", "0"))
|
||||
);
|
||||
|
||||
matrices.set_pose_with_frame(
|
||||
crate::fighter::animation::FighterAnimationFrame { frame: 1, ..frame },
|
||||
"root",
|
||||
Some(0),
|
||||
Some(FGVec3::lit("6", "0", "0")),
|
||||
FGVec3::ZERO,
|
||||
None,
|
||||
AnimKind::Wait,
|
||||
@@ -377,7 +404,8 @@ mod tests {
|
||||
|
||||
matrices.set_pose_with_frame(
|
||||
frame,
|
||||
"root",
|
||||
Some(0),
|
||||
Some(FGVec3::lit("3", "4", "5")),
|
||||
FGVec3::ZERO,
|
||||
None,
|
||||
AnimKind::Wait,
|
||||
@@ -396,6 +424,68 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_animation_extracts_root_translation_and_retains_its_position() {
|
||||
let names = ["root".into(), "hand".into()];
|
||||
let parents = [None, Some(0)];
|
||||
let mut root = FixedMat4::IDENTITY;
|
||||
root.translate(FGVec3::lit("3", "4", "5"));
|
||||
let mut hand = FixedMat4::IDENTITY;
|
||||
hand.translate(FGVec3::lit("1", "2", "0"));
|
||||
let mut matrices = FighterBoneMatrices::default();
|
||||
let frame = crate::fighter::animation::FighterAnimationFrame::new(
|
||||
AnimKind::LedgeWait,
|
||||
true,
|
||||
);
|
||||
|
||||
matrices.set_pose_with_frame(
|
||||
frame,
|
||||
Some(0),
|
||||
Some(FGVec3::lit("3", "4", "5")),
|
||||
FGVec3::ZERO,
|
||||
None,
|
||||
AnimKind::LedgeWait,
|
||||
&names,
|
||||
&parents,
|
||||
&[root, hand],
|
||||
FGi32::ONE,
|
||||
);
|
||||
|
||||
assert_eq!(matrices.root_motion_delta(), FGVec3::ZERO);
|
||||
assert_eq!(
|
||||
matrices.root_motion_position(),
|
||||
Some(FGVec3::lit("3", "4", "5"))
|
||||
);
|
||||
assert_eq!(matrices.local(0).unwrap().get_translation(), FGVec3::ZERO);
|
||||
assert_eq!(
|
||||
matrices.get_current("root").unwrap().get_translation(),
|
||||
FGVec3::ZERO
|
||||
);
|
||||
assert_eq!(
|
||||
matrices.get_current("hand").unwrap().get_translation(),
|
||||
FGVec3::lit("1", "2", "0")
|
||||
);
|
||||
|
||||
root.translate(FGVec3::lit("6", "4", "5"));
|
||||
matrices.set_pose_with_frame(
|
||||
crate::fighter::animation::FighterAnimationFrame { frame: 1, ..frame },
|
||||
Some(0),
|
||||
Some(FGVec3::lit("6", "4", "5")),
|
||||
FGVec3::ZERO,
|
||||
None,
|
||||
AnimKind::LedgeWait,
|
||||
&names,
|
||||
&parents,
|
||||
&[root, hand],
|
||||
FGi32::ONE,
|
||||
);
|
||||
assert_eq!(matrices.root_motion_delta(), FGVec3::lit("3", "0", "0"));
|
||||
assert_eq!(
|
||||
matrices.get_current("root").unwrap().get_translation(),
|
||||
FGVec3::ZERO
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeating_root_motion_preserves_cycle_displacement_at_wrap() {
|
||||
let names = ["root".into()];
|
||||
@@ -411,7 +501,8 @@ mod tests {
|
||||
for (frame_number, x) in [(0, "0"), (1, "1")] {
|
||||
matrices.set_pose_with_frame(
|
||||
frame(frame_number),
|
||||
"root",
|
||||
Some(0),
|
||||
Some(FGVec3::lit(x, "0", "0")),
|
||||
FGVec3::lit("2", "0", "0"),
|
||||
Some(2),
|
||||
AnimKind::Wait,
|
||||
@@ -423,7 +514,8 @@ mod tests {
|
||||
}
|
||||
matrices.set_pose_with_frame(
|
||||
frame(2),
|
||||
"root",
|
||||
Some(0),
|
||||
Some(FGVec3::ZERO),
|
||||
FGVec3::lit("2", "0", "0"),
|
||||
Some(2),
|
||||
AnimKind::Wait,
|
||||
|
||||
@@ -21,6 +21,9 @@ use crate::{
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct BakedAnimationClip {
|
||||
pub frames: Vec<Vec<FixedMat4>>,
|
||||
/// Unscaled model-space position of the configured root-motion bone at
|
||||
/// each baked frame.
|
||||
pub root_positions: Vec<FGVec3>,
|
||||
}
|
||||
|
||||
#[derive(Asset, bevy::reflect::TypePath, Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -29,11 +32,22 @@ pub struct BakedFighterAnimations {
|
||||
pub frame_rate: u32,
|
||||
pub bone_names: Vec<String>,
|
||||
pub parents: Vec<Option<usize>>,
|
||||
pub(super) root_bone_index: usize,
|
||||
pub(super) clips: Vec<BakedAnimationClip>,
|
||||
pub(super) clip_indices: HashMap<AnimKind, usize>,
|
||||
}
|
||||
|
||||
impl BakedFighterAnimations {
|
||||
fn sample_index(&self, frame: &FighterAnimationFrame) -> Option<usize> {
|
||||
let frames = &self.clip(frame.kind)?.frames;
|
||||
let frame_index = if frame.repeat {
|
||||
let loop_frame_count = frames.len().saturating_sub(1).max(1) as u32;
|
||||
frame.frame % loop_frame_count
|
||||
} else {
|
||||
frame.frame
|
||||
};
|
||||
Some((frame_index as usize).min(frames.len().saturating_sub(1)))
|
||||
}
|
||||
pub fn contains_bone(&self, bone: &str) -> bool {
|
||||
self.bone_names.iter().any(|name| name == bone)
|
||||
}
|
||||
@@ -42,17 +56,20 @@ impl BakedFighterAnimations {
|
||||
self.bone_names.iter().all(|name| names.insert(name))
|
||||
}
|
||||
pub fn sample_pose(&self, frame: &FighterAnimationFrame) -> Option<&[FixedMat4]> {
|
||||
let frames = &self.clip(frame.kind)?.frames;
|
||||
let frame_index = if frame.repeat {
|
||||
let loop_frame_count = frames.len().saturating_sub(1).max(1) as u32;
|
||||
frame.frame % loop_frame_count
|
||||
} else {
|
||||
frame.frame
|
||||
};
|
||||
frames
|
||||
.get(frame_index as usize)
|
||||
.or_else(|| frames.last())
|
||||
.map(Vec::as_slice)
|
||||
let clip = self.clip(frame.kind)?;
|
||||
clip.frames.get(self.sample_index(frame)?).map(Vec::as_slice)
|
||||
}
|
||||
pub fn sample_root_position(
|
||||
&self,
|
||||
frame: &FighterAnimationFrame,
|
||||
model_scale: FGi32,
|
||||
) -> Option<FGVec3> {
|
||||
let clip = self.clip(frame.kind)?;
|
||||
let position = *clip.root_positions.get(self.sample_index(frame)?)?;
|
||||
Some(position * model_scale)
|
||||
}
|
||||
pub fn initial_root_position(&self, kind: AnimKind, model_scale: FGi32) -> Option<FGVec3> {
|
||||
self.sample_root_position(&FighterAnimationFrame::new(kind, false), model_scale)
|
||||
}
|
||||
pub fn frame_count(&self, kind: AnimKind) -> Option<u32> {
|
||||
self.clip(kind).map(|clip| clip.frames.len() as u32)
|
||||
@@ -60,27 +77,10 @@ impl BakedFighterAnimations {
|
||||
pub fn root_motion_cycle_delta(
|
||||
&self,
|
||||
kind: AnimKind,
|
||||
bone: &str,
|
||||
model_scale: FGi32,
|
||||
) -> Option<FGVec3> {
|
||||
let last = self.frame_count(kind)?.saturating_sub(1);
|
||||
let first = FighterAnimationFrame {
|
||||
kind,
|
||||
frame: 0,
|
||||
repeat: false,
|
||||
};
|
||||
let last = FighterAnimationFrame {
|
||||
kind,
|
||||
frame: last,
|
||||
repeat: false,
|
||||
};
|
||||
Some(
|
||||
self.sample_bone_model_transform(&last, bone, model_scale)?
|
||||
.get_translation()
|
||||
- self
|
||||
.sample_bone_model_transform(&first, bone, model_scale)?
|
||||
.get_translation(),
|
||||
)
|
||||
let clip = self.clip(kind)?;
|
||||
Some((*clip.root_positions.last()? - *clip.root_positions.first()?) * model_scale)
|
||||
}
|
||||
/// Samples one bone in model space without depending on the runtime's
|
||||
/// previous-frame buffers. Tooling uses this when the user scrubs between
|
||||
@@ -124,13 +124,12 @@ pub struct FighterBoneMatrices {
|
||||
model_matrices: Vec<Option<FixedMat4>>,
|
||||
presentation_kind: Option<AnimKind>,
|
||||
previous_animation_frame: Option<FighterAnimationFrame>,
|
||||
root_motion_position: Option<FGVec3>,
|
||||
/// The raw, scaled model-space root translation used as the baseline for
|
||||
/// root-motion extraction. This is kept separately because the published
|
||||
/// pose has the root translation removed.
|
||||
previous_root_motion_translation: Option<FGVec3>,
|
||||
root_motion_delta: crate::math::vec3::FGVec3,
|
||||
cached_root_motion_kind: Option<AnimKind>,
|
||||
cached_root_motion_cycle_delta: FGVec3,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Debug, Default)]
|
||||
@@ -149,7 +148,8 @@ impl FighterBoneMatrices {
|
||||
};
|
||||
self.set_pose_with_frame(
|
||||
*frame,
|
||||
"root",
|
||||
Some(baked.root_bone_index),
|
||||
baked.sample_root_position(frame, model_scale),
|
||||
FGVec3::ZERO,
|
||||
None,
|
||||
frame.kind,
|
||||
@@ -203,6 +203,11 @@ impl FighterBoneMatrices {
|
||||
pub fn root_motion_delta(&self) -> FGVec3 {
|
||||
self.root_motion_delta
|
||||
}
|
||||
/// Current raw, scaled model-space position of the configured root-motion
|
||||
/// bone. The published skeleton has this translation removed.
|
||||
pub fn root_motion_position(&self) -> Option<FGVec3> {
|
||||
self.root_motion_position
|
||||
}
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, FixedMat4)> {
|
||||
self.bone_names
|
||||
.iter()
|
||||
@@ -236,7 +241,8 @@ impl FighterBoneMatrices {
|
||||
) {
|
||||
self.set_pose_with_frame(
|
||||
FighterAnimationFrame::new(kind, false),
|
||||
"",
|
||||
None,
|
||||
None,
|
||||
FGVec3::ZERO,
|
||||
None,
|
||||
kind,
|
||||
@@ -249,7 +255,8 @@ impl FighterBoneMatrices {
|
||||
pub(super) fn set_pose_with_frame(
|
||||
&mut self,
|
||||
animation_frame: FighterAnimationFrame,
|
||||
root_motion_bone: &str,
|
||||
root_bone_index: Option<usize>,
|
||||
root_motion_position: Option<FGVec3>,
|
||||
root_motion_cycle_delta: FGVec3,
|
||||
root_motion_loop_frame_count: Option<u32>,
|
||||
kind: AnimKind,
|
||||
@@ -260,7 +267,8 @@ impl FighterBoneMatrices {
|
||||
) {
|
||||
self.sample_pose(
|
||||
animation_frame,
|
||||
root_motion_bone,
|
||||
root_bone_index,
|
||||
root_motion_position,
|
||||
root_motion_cycle_delta,
|
||||
root_motion_loop_frame_count,
|
||||
kind,
|
||||
@@ -282,7 +290,8 @@ impl FighterBoneMatrices {
|
||||
) {
|
||||
self.resample_pose_with_frame(
|
||||
FighterAnimationFrame::new(kind, false),
|
||||
"",
|
||||
None,
|
||||
None,
|
||||
FGVec3::ZERO,
|
||||
None,
|
||||
kind,
|
||||
@@ -295,7 +304,8 @@ impl FighterBoneMatrices {
|
||||
pub(super) fn resample_pose_with_frame(
|
||||
&mut self,
|
||||
animation_frame: FighterAnimationFrame,
|
||||
root_motion_bone: &str,
|
||||
root_bone_index: Option<usize>,
|
||||
root_motion_position: Option<FGVec3>,
|
||||
_root_motion_cycle_delta: FGVec3,
|
||||
_root_motion_loop_frame_count: Option<u32>,
|
||||
kind: AnimKind,
|
||||
@@ -306,7 +316,8 @@ impl FighterBoneMatrices {
|
||||
) {
|
||||
self.sample_pose(
|
||||
animation_frame,
|
||||
root_motion_bone,
|
||||
root_bone_index,
|
||||
root_motion_position,
|
||||
FGVec3::ZERO,
|
||||
None,
|
||||
kind,
|
||||
@@ -320,7 +331,8 @@ impl FighterBoneMatrices {
|
||||
fn sample_pose(
|
||||
&mut self,
|
||||
animation_frame: FighterAnimationFrame,
|
||||
root_motion_bone: &str,
|
||||
root_index: Option<usize>,
|
||||
raw_root_motion_translation: Option<FGVec3>,
|
||||
root_motion_cycle_delta: FGVec3,
|
||||
root_motion_loop_frame_count: Option<u32>,
|
||||
kind: AnimKind,
|
||||
@@ -360,16 +372,6 @@ impl FighterBoneMatrices {
|
||||
self.local_matrices.clear();
|
||||
self.local_matrices.extend_from_slice(local);
|
||||
|
||||
let root_index = if root_motion_bone.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
self.bone_names
|
||||
.iter()
|
||||
.position(|name| name == root_motion_bone)
|
||||
.expect("configured root motion bone should exist"),
|
||||
)
|
||||
};
|
||||
let model_scale = FixedMat4 {
|
||||
cols: [
|
||||
[model_scale, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO],
|
||||
@@ -378,21 +380,8 @@ impl FighterBoneMatrices {
|
||||
[FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE],
|
||||
],
|
||||
};
|
||||
// Root motion only needs the configured bone's model transform. Resolve
|
||||
// that ancestry first, then do the normal full-pose pass once below.
|
||||
// Previously the complete hierarchy was resolved once before removing
|
||||
// root translation and once afterwards.
|
||||
let raw_root_motion_translation = root_index.map(|root_index| {
|
||||
self.model_matrices.resize(local.len(), None);
|
||||
self.model_matrices.fill(None);
|
||||
let root = resolve_model_transform(root_index, parents, local, &mut self.model_matrices)
|
||||
.expect("baked bone hierarchy should be valid");
|
||||
model_scale.mul(root).get_translation()
|
||||
});
|
||||
|
||||
// Gameplay owns the root's displacement. Keep rotation and scale in
|
||||
// the animation, but remove its translation before publishing the pose
|
||||
// used by visuals and gameplay volumes.
|
||||
// Gameplay always owns root displacement. Keep rotation and scale in
|
||||
// the pose while removing the configured root bone's translation.
|
||||
if let Some(root_index) = root_index {
|
||||
self.local_matrices[root_index].translate(FGVec3::ZERO);
|
||||
}
|
||||
@@ -419,7 +408,8 @@ impl FighterBoneMatrices {
|
||||
let mut delta = current - previous;
|
||||
if animation_frame.repeat
|
||||
&& self.previous_animation_frame.is_some_and(|previous| {
|
||||
previous.frame.saturating_add(1) == animation_frame.frame
|
||||
previous.kind == animation_frame.kind
|
||||
&& previous.frame.saturating_add(1) == animation_frame.frame
|
||||
})
|
||||
&& root_motion_loop_frame_count == Some(animation_frame.frame)
|
||||
{
|
||||
@@ -429,6 +419,7 @@ impl FighterBoneMatrices {
|
||||
} else {
|
||||
FGVec3::ZERO
|
||||
};
|
||||
self.root_motion_position = raw_root_motion_translation;
|
||||
self.previous_root_motion_translation = raw_root_motion_translation;
|
||||
if advance_history {
|
||||
self.previous_animation_frame = Some(animation_frame);
|
||||
@@ -519,23 +510,18 @@ fn sample_fighter_bone_matrices(
|
||||
let manifest = manifests
|
||||
.get(&fighter.manifest)
|
||||
.expect("fighter manifest should be loaded before the match starts");
|
||||
let root_motion_cycle_delta = if matrices.cached_root_motion_kind == Some(frame.kind) {
|
||||
matrices.cached_root_motion_cycle_delta
|
||||
} else {
|
||||
let delta = baked
|
||||
.root_motion_cycle_delta(frame.kind, &manifest.root_motion_bone, manifest.model_scale)
|
||||
.unwrap_or(FGVec3::ZERO);
|
||||
matrices.cached_root_motion_kind = Some(frame.kind);
|
||||
matrices.cached_root_motion_cycle_delta = delta;
|
||||
delta
|
||||
};
|
||||
let root_motion_position = baked.sample_root_position(frame, manifest.model_scale);
|
||||
let root_motion_cycle_delta = baked
|
||||
.root_motion_cycle_delta(frame.kind, manifest.model_scale)
|
||||
.unwrap_or(FGVec3::ZERO);
|
||||
let root_motion_loop_frame_count = baked
|
||||
.frame_count(frame.kind)
|
||||
.map(|count| count.saturating_sub(1).max(1));
|
||||
if advance_history {
|
||||
matrices.set_pose_with_frame(
|
||||
*frame,
|
||||
&manifest.root_motion_bone,
|
||||
Some(baked.root_bone_index),
|
||||
root_motion_position,
|
||||
root_motion_cycle_delta,
|
||||
root_motion_loop_frame_count,
|
||||
frame.kind,
|
||||
@@ -547,7 +533,8 @@ fn sample_fighter_bone_matrices(
|
||||
} else {
|
||||
matrices.resample_pose_with_frame(
|
||||
*frame,
|
||||
&manifest.root_motion_bone,
|
||||
Some(baked.root_bone_index),
|
||||
root_motion_position,
|
||||
FGVec3::ZERO,
|
||||
None,
|
||||
frame.kind,
|
||||
@@ -568,7 +555,7 @@ fn sample_fighter_bone_matrices(
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_model_transform(
|
||||
pub(super) fn resolve_model_transform(
|
||||
index: usize,
|
||||
parents: &[Option<usize>],
|
||||
local: &[FixedMat4],
|
||||
|
||||
Reference in New Issue
Block a user