use std::time::Duration;
use crate::{BlenderArmature, Bone, JointIndicesRef, SampleDesc};
pub use self::interpolated_bones::*;
use std::collections::BTreeMap;
mod interpolated_bones;
mod sample_action;
pub fn linear_200_milliseconds(elapsed: Duration) -> f32 {
(5.0 * elapsed.as_secs_f32()).min(1.0)
}
impl BlenderArmature {
pub fn interpolate_bones(
&self,
action_name: &str,
joint_indices: JointIndicesRef,
sample_desc: SampleDesc,
) -> BTreeMap<u8, Bone> {
self.sample_action(action_name, joint_indices, sample_desc)
}
}
#[cfg(test)]
pub(super) mod tests {
use crate::{Bone, BoneKeyframe, FrameOffset, JointIndicesRef, Keyframe, SampleDesc};
use super::*;
use crate::test_util::{action_name, action_with_keyframes, BONE_IDX};
use nalgebra::{DualQuaternion, Quaternion};
struct DualQuatTestCase {
keyframes: Vec<TestKeyframeDualQuat>,
expected_bone: [f32; 8],
sample_desc: SampleDesc,
}
struct TestKeyframeDualQuat {
frame: u16,
bone: [f32; 8],
}
const ONE_FPS: u8 = 1;
#[test]
fn less_than_total_duration() {
DualQuatTestCase {
keyframes: vec![
TestKeyframeDualQuat {
frame: 0,
bone: [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0],
},
TestKeyframeDualQuat {
frame: 2,
bone: [1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0],
},
],
expected_bone: [0.75, 0.75, 0.75, 0.75, 0.25, 0.25, 0.25, 0.25],
sample_desc: SampleDesc {
frame_offset: FrameOffset::new_with_elapsed_time_and_frames_per_second(
Duration::from_secs_f32(1.5),
ONE_FPS,
),
should_loop: true,
},
}
.test();
}
#[test]
fn looping_action() {
DualQuatTestCase {
keyframes: vec![
TestKeyframeDualQuat {
frame: 1,
bone: [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0],
},
TestKeyframeDualQuat {
frame: 3,
bone: [1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0],
},
],
expected_bone: [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0],
sample_desc: SampleDesc {
frame_offset: FrameOffset::new_with_elapsed_time_and_frames_per_second(
Duration::from_secs(4),
ONE_FPS,
),
should_loop: true,
},
}
.test();
}
#[test]
fn looping_order_bugfix() {
DualQuatTestCase {
keyframes: vec![
TestKeyframeDualQuat {
frame: 1,
bone: [8.0, 8.0, 8.0, 8.0, 0.0, 0.0, 0.0, 0.0],
},
TestKeyframeDualQuat {
frame: 2,
bone: [20.0, 20.0, 20.0, 20.0, 00.0, 00.0, 0.0, 0.0],
},
TestKeyframeDualQuat {
frame: 0,
bone: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
},
],
expected_bone: [4.0, 4.0, 4.0, 4.0, 0.0, 0.0, 0.0, 0.0],
sample_desc: SampleDesc {
frame_offset: FrameOffset::new_with_elapsed_time_and_frames_per_second(
Duration::from_secs_f32(2.5),
ONE_FPS,
),
should_loop: true,
},
}
.test();
}
#[test]
fn non_looping_animation() {
DualQuatTestCase {
keyframes: vec![
TestKeyframeDualQuat {
frame: 3,
bone: [1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0],
},
TestKeyframeDualQuat {
frame: 5,
bone: [3.0, 3.0, 3.0, 3.0, 1.0, 1.0, 1.0, 1.0],
},
],
expected_bone: [3.0, 3.0, 3.0, 3.0, 1.0, 1.0, 1.0, 1.0],
sample_desc: SampleDesc {
frame_offset: FrameOffset::new_with_elapsed_time_and_frames_per_second(
Duration::from_secs(7),
ONE_FPS,
),
should_loop: false,
},
}
.test();
}
#[test]
fn no_elapsed_time() {
DualQuatTestCase {
keyframes: vec![
TestKeyframeDualQuat {
frame: 0,
bone: [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0],
},
TestKeyframeDualQuat {
frame: 2,
bone: [1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0],
},
],
expected_bone: [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0],
sample_desc: SampleDesc {
frame_offset: FrameOffset::new_with_elapsed_time_and_frames_per_second(
Duration::from_secs(0),
ONE_FPS,
),
should_loop: true,
},
}
.test();
}
#[test]
fn uses_frames_per_second() {
DualQuatTestCase {
keyframes: vec![
TestKeyframeDualQuat {
frame: 0,
bone: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
},
TestKeyframeDualQuat {
frame: 10,
bone: [100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0],
},
],
expected_bone: [20., 20., 20., 20., 20., 20., 20., 20.],
sample_desc: SampleDesc {
frame_offset: FrameOffset::new_with_elapsed_time_and_frames_per_second(
Duration::from_secs_f32(0.2),
10,
),
should_loop: false,
},
}
.test();
}
impl DualQuatTestCase {
fn test(self) {
let mut keyframes = vec![];
for keyframe in self.keyframes.iter() {
keyframes.push(BoneKeyframe::new(keyframe.frame, dq_to_bone(keyframe.bone)));
}
let armature = BlenderArmature {
bone_space_actions: action_with_keyframes(keyframes),
..BlenderArmature::default()
};
let interpolated_bones = armature.interpolate_bones(
&action_name(),
JointIndicesRef::Some(&[BONE_IDX]),
self.sample_desc,
);
let interpolated_bone = interpolated_bones.get(&BONE_IDX).unwrap();
assert_eq!(interpolated_bone, &dq_to_bone(self.expected_bone));
}
}
pub(crate) fn dq_to_bone(dq: [f32; 8]) -> Bone {
Bone::DualQuat(DualQuaternion::from_real_and_dual(
Quaternion::new(dq[0], dq[1], dq[2], dq[3]),
Quaternion::new(dq[4], dq[5], dq[6], dq[7]),
))
}
}