Interpolate decoded animation keys across compression boundaries

This commit is contained in:
chazmm committed 2026-09-07 01:46:02 +01:00
1 parent e4311d8ffe
commit cd48c0c809
2 files changed
+56 -3

No files matched your search

+6 -3
View File
@@ -4,7 +4,7 @@ use skate_core::animation::{
output::{self, NativeMatrix, Sqt},
playback_tree::PoseCommand,
pose_add, pose_blend, pose_mirror,
pose_sample::{sample_key_vbr, select_frames},
pose_sample::{sample_key, select_frames},
pose_trajectory::{self, LoopTransform},
};
use skate_data::{
@@ -163,7 +163,10 @@ fn sample_clip(clip: &ClipFrames, time: f32) -> Result<Vec<Sqt>, String> {
.zip(&clip.frames[selection.second])
.enumerate()
.map(|(bone, (&first, &second))| {
let mut sample = sample_key_vbr(sqt(first), sqt(second), selection);
// ClipFrames already contains independently decoded keys. The raw
// VBR block-boundary compatibility path substitutes the next key,
// which skips then holds a frame when applied to this flat cache.
let mut sample = sample_key(sqt(first), sqt(second), selection);
sample.translation[3] = f32::from_bits(clip.channel_weights[bone]);
sample
})
@@ -178,7 +181,7 @@ fn sample_bone(clip: &ClipFrames, time: f32, bone: usize) -> Result<Sqt, String>
true,
0.0,
)?;
let mut sample = sample_key_vbr(
let mut sample = sample_key(
sqt(clip.frames[selection.first][bone]),
sqt(clip.frames[selection.second][bone]),
selection,
@@ -1,5 +1,55 @@
use super::*;
#[test]
fn decoded_clip_interpolates_across_compression_boundaries() {
// Uniform motion makes a skipped key or a held pose unambiguous. Exercise
// both full-pose sampling and the trajectory's previous-frame sampler.
for fps in [30.0_f32, 60.0] {
let clip = ClipFrames {
name: "BOUNDARY_RAMP".into(),
source_offset: 0,
fps_bits: fps.to_bits(),
loop_translation_bits: [0; 3],
loop_rotation_bits: [0, 0, 0, 1.0_f32.to_bits()],
channel_animation: true,
channel_weights: vec![0.25_f32.to_bits()],
frames: (0..=17)
.map(|frame| {
let half_angle = frame as f32 * 0.02;
vec![
[
1.0,
1.0,
1.0,
0.0,
0.0,
half_angle.sin(),
half_angle.cos(),
frame as f32,
0.0,
0.0,
]
.map(f32::to_bits),
]
})
.collect(),
};
for quarter in 0..=68 {
let frame = quarter as f32 * 0.25;
let time = frame / fps;
let pose = sample_clip(&clip, time).unwrap()[0];
assert_eq!(pose, sample_bone(&clip, time, 0).unwrap());
assert!(
(pose.translation[0] - frame).abs() < 0.0001,
"{fps} fps, frame {frame}: skipped/held decoded key: {pose:?}"
);
let angle = 2.0 * pose.rotation[2].atan2(pose.rotation[3]);
assert!((angle - frame * 0.04).abs() < 0.0001);
assert_eq!(pose.translation[3], 0.25);
}
}
}
#[test]
#[ignore = "requires the user's decoded stock animation assets"]
fn stock_pose_commands_sample_complete_bones_and_preserve_tree_order() {