feat: complete map viewer scene and static CTL pose preview

Complete the interactive mission viewer with environment rendering, audio events, dynamic shadows, free-flight camera controls, and per-component CTL pose sampling.
This commit is contained in:
2026-10-11 13:12:50 +04:00
parent aa51f3574d
commit a8faba8aad
42 changed files with 24411 additions and 1234 deletions
+46 -4
View File
@@ -254,12 +254,16 @@ impl AnimKey24 {
];
let time = AnimationTime(read_f32(bytes, 12)?);
validate_time(time)?;
let raw_rotation = [
let disk_wxyz = [
f32::from(read_i16(bytes, 16)?) / 32767.0,
f32::from(read_i16(bytes, 18)?) / 32767.0,
f32::from(read_i16(bytes, 20)?) / 32767.0,
f32::from(read_i16(bytes, 22)?) / 32767.0,
];
// Native AniMesh stores the scalar first (WXYZ). The portable pose
// contract is XYZW, matching the rotate/multiply helpers below. Keep
// the decoded values unnormalized; sampling_pose owns normalization.
let raw_rotation = [disk_wxyz[1], disk_wxyz[2], disk_wxyz[3], disk_wxyz[0]];
Ok(Self {
time,
pose: Pose {
@@ -870,9 +874,47 @@ mod tests {
assert_eq!(key.time, AnimationTime(12.5));
assert_eq!(key.pose.translation, [-1.0, 2.0, 0.0]);
assert!(key.pose.rotation[1] < 0.0);
assert!((key.pose.rotation[1] + std::f32::consts::FRAC_1_SQRT_2).abs() < 0.000_05);
assert!((key.pose.rotation[3] - std::f32::consts::FRAC_1_SQRT_2).abs() < 0.000_05);
assert!(key.pose.rotation[0] < 0.0);
assert!((key.pose.rotation[0] + std::f32::consts::FRAC_1_SQRT_2).abs() < 0.000_05);
assert!((key.pose.rotation[2] - std::f32::consts::FRAC_1_SQRT_2).abs() < 0.000_05);
}
#[test]
fn anim_key24_preserves_disk_wxyz_as_internal_xyzw() {
let mut bytes = [0_u8; 24];
bytes[12..16].copy_from_slice(&0.0_f32.to_bits().to_le_bytes());
bytes[16..18].copy_from_slice(&32609_i16.to_le_bytes());
bytes[18..20].copy_from_slice(&0_i16.to_le_bytes());
bytes[20..22].copy_from_slice(&0_i16.to_le_bytes());
bytes[22..24].copy_from_slice(&3211_i16.to_le_bytes());
let key = AnimKey24::decode(&bytes).expect("bunker key");
let expected = [0.0, 0.0, 3211.0 / 32767.0, 32609.0 / 32767.0];
assert_eq!(key.pose.rotation, expected);
}
#[test]
fn bunker_fallback_pose_rotates_a_point_with_native_matrix_contract() {
let mut bytes = [0_u8; 24];
bytes[12..16].copy_from_slice(&0.0_f32.to_bits().to_le_bytes());
bytes[16..18].copy_from_slice(&32609_i16.to_le_bytes());
bytes[18..20].copy_from_slice(&0_i16.to_le_bytes());
bytes[20..22].copy_from_slice(&0_i16.to_le_bytes());
bytes[22..24].copy_from_slice(&3211_i16.to_le_bytes());
let pose = AnimKey24::decode(&bytes)
.expect("bunker key")
.sampling_pose();
let rotated = rotate_point(pose.rotation, [0.0, 1.0, 0.0]);
assert!((pose.rotation[0] - 0.0).abs() < 0.000_001);
assert!((pose.rotation[1] - 0.0).abs() < 0.000_001);
assert!((pose.rotation[2] - 0.097_995_8).abs() < 0.000_01);
assert!((pose.rotation[3] - 0.995_186_8).abs() < 0.000_01);
assert!((rotated[0] + 0.195_048_3).abs() < 0.000_01);
assert!((rotated[1] - 0.980_793_6).abs() < 0.000_01);
assert!(rotated[2].abs() < 0.000_01);
}
#[test]