use binrw::io::{Cursor, Seek, Write};
use binrw::{BinRead, BinReaderExt};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub use ssbh_lib::formats::anim::GroupType;
use ssbh_lib::formats::anim::TrackTypeV1;
use ssbh_lib::{
formats::anim::{
Anim, CompressionType, Group, Node, TrackFlags, TrackTypeV2, TrackV2,
TransformFlags as AnimTransformFlags, UnkData,
},
SsbhArray, Vector3, Vector4, Version,
};
use ssbh_write::SsbhWrite;
use std::collections::HashMap;
use std::{
convert::{TryFrom, TryInto},
error::Error,
};
mod buffers;
use buffers::*;
mod bitutils;
mod compression;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, PartialEq, Clone)]
pub struct AnimData {
pub major_version: u16,
pub minor_version: u16,
pub final_frame_index: f32,
pub groups: Vec<GroupData>,
}
impl TryFrom<Anim> for AnimData {
type Error = Box<dyn Error>;
fn try_from(anim: Anim) -> Result<Self, Self::Error> {
(&anim).try_into()
}
}
impl TryFrom<&Anim> for AnimData {
type Error = Box<dyn Error>;
fn try_from(anim: &Anim) -> Result<Self, Self::Error> {
let (major_version, minor_version) = anim.major_minor_version();
Ok(Self {
major_version,
minor_version,
final_frame_index: match &anim {
Anim::V12 {
final_frame_index, ..
} => *final_frame_index,
Anim::V20 {
final_frame_index, ..
} => *final_frame_index,
Anim::V21 {
final_frame_index, ..
} => *final_frame_index,
},
groups: read_anim_groups(anim)?,
})
}
}
impl TryFrom<AnimData> for Anim {
type Error = error::Error;
fn try_from(data: AnimData) -> Result<Self, Self::Error> {
create_anim(&data)
}
}
impl TryFrom<&AnimData> for Anim {
type Error = error::Error;
fn try_from(data: &AnimData) -> Result<Self, Self::Error> {
create_anim(data)
}
}
pub mod error {
use super::*;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error(
"creating a version {}.{} anim is not supported",
major_version,
minor_version
)]
UnsupportedVersion {
major_version: u16,
minor_version: u16,
},
#[error(
"final frame index {} must be non negative and at least as
large as the index of the final frame in the longest track",
final_frame_index
)]
InvalidFinalFrameIndex { final_frame_index: f32 },
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
BinRead(#[from] binrw::error::Error),
#[error(transparent)]
BitError(#[from] bitutils::BitReadError),
#[error(
"compressed header bits per entry of {} does not match expected value of {}",
actual,
expected
)]
UnexpectedBitCount { expected: usize, actual: usize },
#[error(
"track data range {0}..{0}+{1} is out of range for a buffer of size {2}",
start,
size,
buffer_size
)]
InvalidTrackDataRange {
start: usize,
size: usize,
buffer_size: usize,
},
#[error(
"buffer index {} is out of range for a buffer collection of size {}",
buffer_index,
buffer_count
)]
BufferIndexOutOfRange {
buffer_index: usize,
buffer_count: usize,
},
#[error("the track data compression header is malformed and cannot be read")]
MalformedCompressionHeader,
}
}
enum AnimVersion {
Version20,
Version21,
}
fn create_anim(data: &AnimData) -> Result<Anim, error::Error> {
let version = match (data.major_version, data.minor_version) {
(2, 0) => Ok(AnimVersion::Version20),
(2, 1) => Ok(AnimVersion::Version21),
_ => Err(error::Error::UnsupportedVersion {
major_version: data.major_version,
minor_version: data.minor_version,
}),
}?;
let mut buffer = Cursor::new(Vec::new());
let animations = data
.groups
.iter()
.map(|g| create_anim_group(g, &mut buffer))
.collect::<Result<Vec<_>, _>>()?;
let max_frame_count = animations
.iter()
.filter_map(|a| {
a.nodes
.elements
.iter()
.filter_map(|n| n.tracks.elements.iter().map(|t| t.frame_count).max())
.max()
})
.max()
.unwrap_or(0);
let final_frame_index = if data.final_frame_index >= 0.0
&& data.final_frame_index >= max_frame_count as f32 - 1.0
{
Ok(data.final_frame_index)
} else {
Err(error::Error::InvalidFinalFrameIndex {
final_frame_index: data.final_frame_index,
})
}?;
match version {
AnimVersion::Version20 => Ok(Anim::V20 {
final_frame_index,
unk1: 1,
unk2: 3,
name: "".into(), groups: animations.into(),
buffer: buffer.into_inner().into(),
}),
AnimVersion::Version21 => Ok(Anim::V21 {
final_frame_index,
unk1: 1,
unk2: 3,
name: "".into(), groups: animations.into(),
buffer: buffer.into_inner().into(),
unk_data: UnkData {
unk1: SsbhArray::new(),
unk2: SsbhArray::new(),
},
}),
}
}
fn create_anim_group(g: &GroupData, buffer: &mut Cursor<Vec<u8>>) -> Result<Group, error::Error> {
Ok(Group {
group_type: g.group_type,
nodes: g
.nodes
.iter()
.map(|n| create_anim_node(n, buffer))
.collect::<Result<Vec<_>, _>>()?
.into(),
})
}
fn create_anim_node(n: &NodeData, buffer: &mut Cursor<Vec<u8>>) -> Result<Node, error::Error> {
Ok(Node {
name: n.name.as_str().into(), tracks: n
.tracks
.iter()
.map(|t| create_anim_track_v2(buffer, t))
.collect::<Result<Vec<_>, _>>()?
.into(),
})
}
fn create_anim_track_v2(
buffer: &mut Cursor<Vec<u8>>,
t: &TrackData,
) -> Result<TrackV2, error::Error> {
let compression_type = infer_optimal_compression_type(&t.values);
let pos_before = buffer.stream_position()?;
let mut track_data = Cursor::new(Vec::new());
t.values
.write(&mut track_data, compression_type, t.compensate_scale)?;
buffer.write_all(&track_data.into_inner())?;
let pos_after = buffer.stream_position()?;
Ok(TrackV2 {
name: t.name.as_str().into(),
flags: TrackFlags {
track_type: t.values.track_type(),
compression_type,
},
frame_count: t.values.len() as u32,
transform_flags: t.transform_flags.into(),
data_offset: pos_before as u32,
data_size: pos_after - pos_before,
})
}
fn infer_optimal_compression_type(values: &TrackValues) -> CompressionType {
match (values, values.len()) {
(TrackValues::Transform(_), 0..=1) => CompressionType::ConstTransform,
(_, 0..=1) => CompressionType::Constant,
_ => {
let uncompressed_frames_per_header =
values.compressed_overhead_in_bytes() / values.data_size_in_bytes();
if values.len() > uncompressed_frames_per_header as usize + 1 {
CompressionType::Compressed
} else {
CompressionType::Direct
}
}
}
}
fn read_anim_groups(anim: &Anim) -> Result<Vec<GroupData>, error::Error> {
match anim {
ssbh_lib::prelude::Anim::V12 {
tracks, buffers, ..
} => {
read_groups_v12(&tracks.elements, &buffers.elements)
}
ssbh_lib::formats::anim::Anim::V20 { groups, buffer, .. } => {
read_groups_v20(&groups.elements, &buffer.elements)
}
ssbh_lib::formats::anim::Anim::V21 { groups, buffer, .. } => {
read_groups_v20(&groups.elements, &buffer.elements)
}
}
}
fn group_type_v12(track_type: TrackTypeV1) -> GroupType {
match track_type {
TrackTypeV1::Transform => GroupType::Transform,
TrackTypeV1::UvTransform => GroupType::Material,
TrackTypeV1::Visibility => GroupType::Visibility,
}
}
fn read_groups_v12(
tracks: &[ssbh_lib::formats::anim::TrackV1],
buffers: &[ssbh_lib::SsbhByteBuffer],
) -> Result<Vec<GroupData>, error::Error> {
let mut tracks_by_type = HashMap::new();
for track in tracks {
let group_type = group_type_v12(track.track_type);
let track_data = create_track_data_v12(track, buffers).unwrap();
tracks_by_type
.entry(group_type)
.or_insert(Vec::new())
.push((track.name.to_string_lossy(), track_data));
}
let groups = tracks_by_type
.into_iter()
.map(|(group_type, tracks)| GroupData {
group_type,
nodes: tracks
.into_iter()
.map(|(name, track)| NodeData {
name,
tracks: vec![track],
})
.collect(),
})
.collect();
Ok(groups)
}
fn create_track_data_v12(
track: &ssbh_lib::formats::anim::TrackV1,
buffers: &[ssbh_lib::SsbhByteBuffer],
) -> Result<TrackData, error::Error> {
println!("{:?}", track.name.to_string_lossy());
for property in &track.properties.elements {
let data = buffers.get(property.buffer_index as usize).ok_or(
error::Error::BufferIndexOutOfRange {
buffer_index: property.buffer_index as usize,
buffer_count: buffers.len(),
},
)?;
let mut reader = Cursor::new(&data.elements);
let header: u32 = reader.read_le()?;
println!("{:?},{:x?}", property.name.to_string_lossy(), header);
match header {
0x1003 => {
println!("{:x?}", reader.read_le::<f32>()?);
}
0x2003 => {
println!("{:?}", reader.read_le::<(f32, f32)>()?);
}
0x3003 => {
println!("{:?}", reader.read_le::<Vector3>()?);
}
0x4003 => {
println!("{:?}", reader.read_le::<Vector4>()?);
}
0x1013 => {
println!("{:x?}", reader.read_le::<u16>()?);
}
0x3409 => {
println!("{:?}", reader.read_le::<V12Test1>()?);
println!("Compressed: {:?} bytes", data.elements.len() - 52 - 4);
}
0x4308 => {
println!("{:?}", reader.read_le::<V12Test3>()?);
println!("Compressed: {:?} bytes", data.elements.len() - 72 - 4);
}
0x4409 => {
let test = reader.read_le::<V12Test2>()?;
println!("{test:?}");
println!("Compressed: {:?} bytes", data.elements.len() - 64 - 4);
}
x => println!("Unrecognized header: {x:?}"),
}
}
println!();
Ok(TrackData {
name: match track.track_type {
TrackTypeV1::Transform => "Transform".to_owned(),
TrackTypeV1::Visibility => "Visibility".to_owned(),
TrackTypeV1::UvTransform => "Material".to_owned(),
},
compensate_scale: false,
values: TrackValues::Float(Vec::new()),
transform_flags: TransformFlags::default(),
})
}
fn read_groups_v20(
anim_groups: &[ssbh_lib::formats::anim::Group],
anim_buffer: &[u8],
) -> Result<Vec<GroupData>, error::Error> {
let mut groups = Vec::new();
for anim_group in anim_groups {
let mut nodes = Vec::new();
for anim_node in &anim_group.nodes.elements {
let mut tracks = Vec::new();
for anim_track in &anim_node.tracks.elements {
let track = create_track_data_v20(anim_track, anim_buffer)?;
tracks.push(track);
}
let node = NodeData {
name: anim_node.name.to_string_lossy(),
tracks,
};
nodes.push(node);
}
let group = GroupData {
group_type: anim_group.group_type,
nodes,
};
groups.push(group);
}
Ok(groups)
}
fn create_track_data_v20(
track: &ssbh_lib::formats::anim::TrackV2,
buffer: &[u8],
) -> Result<TrackData, error::Error> {
let start = track.data_offset as usize;
let end =
start
.checked_add(track.data_size as usize)
.ok_or(error::Error::InvalidTrackDataRange {
start: track.data_offset as usize,
size: track.data_size as usize,
buffer_size: buffer.len(),
})?;
let buffer = buffer
.get(start..end)
.ok_or(error::Error::InvalidTrackDataRange {
start: track.data_offset as usize,
size: track.data_size as usize,
buffer_size: buffer.len(),
})?;
let (values, compensate_scale) =
read_track_values(buffer, track.flags, track.frame_count as usize)?;
Ok(TrackData {
name: track.name.to_string_lossy(),
values,
compensate_scale,
transform_flags: track.transform_flags.into(),
})
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, PartialEq, Clone)]
pub struct GroupData {
pub group_type: GroupType,
pub nodes: Vec<NodeData>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, PartialEq, Clone)]
pub struct NodeData {
pub name: String,
pub tracks: Vec<TrackData>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, PartialEq, Clone)]
pub struct TrackData {
pub name: String,
pub compensate_scale: bool,
pub transform_flags: TransformFlags,
pub values: TrackValues,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, PartialEq, Eq, Default, Clone, Copy)]
pub struct TransformFlags {
pub override_translation: bool,
pub override_rotation: bool,
pub override_scale: bool,
pub override_compensate_scale: bool,
}
impl From<TransformFlags> for AnimTransformFlags {
fn from(f: TransformFlags) -> Self {
Self::new()
.with_override_translation(f.override_translation)
.with_override_rotation(f.override_rotation)
.with_override_scale(f.override_scale)
.with_override_compensate_scale(f.override_compensate_scale)
}
}
impl From<AnimTransformFlags> for TransformFlags {
fn from(f: AnimTransformFlags) -> Self {
Self {
override_translation: f.override_translation(),
override_rotation: f.override_rotation(),
override_scale: f.override_scale(),
override_compensate_scale: f.override_compensate_scale(),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, BinRead, PartialEq, SsbhWrite, Default, Clone, Copy)]
pub struct UvTransform {
pub scale_u: f32,
pub scale_v: f32,
pub rotation: f32,
pub translate_u: f32,
pub translate_v: f32,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, PartialEq, Clone, Copy, Default)]
pub struct Transform {
pub scale: Vector3,
pub rotation: Vector4,
pub translation: Vector3,
}
impl Transform {
pub const IDENTITY: Transform = Transform {
scale: Vector3 {
x: 1.0,
y: 1.0,
z: 1.0,
},
rotation: Vector4 {
x: 0.0,
y: 0.0,
z: 0.0,
w: 1.0,
},
translation: Vector3 {
x: 0.0,
y: 0.0,
z: 0.0,
},
};
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, PartialEq, Clone)]
pub enum TrackValues {
Transform(Vec<Transform>),
UvTransform(Vec<UvTransform>),
Float(Vec<f32>),
PatternIndex(Vec<u32>),
Boolean(Vec<bool>),
Vector4(Vec<Vector4>),
}
impl TrackValues {
pub fn len(&self) -> usize {
match self {
TrackValues::Transform(v) => v.len(),
TrackValues::UvTransform(v) => v.len(),
TrackValues::Float(v) => v.len(),
TrackValues::PatternIndex(v) => v.len(),
TrackValues::Boolean(v) => v.len(),
TrackValues::Vector4(v) => v.len(),
}
}
pub fn is_empty(&self) -> bool {
match self {
TrackValues::Transform(v) => v.is_empty(),
TrackValues::UvTransform(v) => v.is_empty(),
TrackValues::Float(v) => v.is_empty(),
TrackValues::PatternIndex(v) => v.is_empty(),
TrackValues::Boolean(v) => v.is_empty(),
TrackValues::Vector4(v) => v.is_empty(),
}
}
fn track_type(&self) -> TrackTypeV2 {
match self {
TrackValues::Transform(_) => TrackTypeV2::Transform,
TrackValues::UvTransform(_) => TrackTypeV2::UvTransform,
TrackValues::Float(_) => TrackTypeV2::Float,
TrackValues::PatternIndex(_) => TrackTypeV2::PatternIndex,
TrackValues::Boolean(_) => TrackTypeV2::Boolean,
TrackValues::Vector4(_) => TrackTypeV2::Vector4,
}
}
}
#[allow(dead_code)]
#[derive(Debug, BinRead)]
struct V12Test1 {
unk0: u32, unk1: f32,
unk2: f32,
unk3: u16, unk4: u16,
unk5: [Vector3; 3],
}
#[allow(dead_code)]
#[derive(Debug, BinRead)]
struct V12Test2 {
unk0: u32, unk1: f32,
unk2: f32,
unk3: u16, unk4: u16,
unk5: [Vector4; 3],
}
#[allow(dead_code)]
#[derive(Debug, BinRead)]
struct V12Test3 {
frame_count: u32,
unk1: f32,
#[br(count = frame_count, align_after = 4)] unk2: Vec<u8>, unk3: [Vector3; 3],
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_empty_anim_v_2_0() {
let anim = create_anim(&AnimData {
major_version: 2,
minor_version: 0,
final_frame_index: 1.5,
groups: Vec::new(),
})
.unwrap();
assert!(matches!(
anim,
Anim::V20 {
final_frame_index,
..
} if final_frame_index == 1.5
));
}
#[test]
fn create_empty_anim_v_2_1() {
let anim = create_anim(&AnimData {
major_version: 2,
minor_version: 1,
final_frame_index: 2.5,
groups: Vec::new(),
})
.unwrap();
assert!(matches!(anim, Anim::V21 {
final_frame_index,
..
} if final_frame_index == 2.5));
}
#[test]
fn create_anim_negative_frame_index() {
let result = create_anim(&AnimData {
major_version: 2,
minor_version: 1,
final_frame_index: -1.0,
groups: Vec::new(),
});
assert!(matches!(
result,
Err(error::Error::InvalidFinalFrameIndex {
final_frame_index
}) if final_frame_index == -1.0
));
}
#[test]
fn create_anim_insufficient_frame_index() {
let result = create_anim(&AnimData {
major_version: 2,
minor_version: 1,
final_frame_index: 2.0,
groups: vec![GroupData {
group_type: GroupType::Visibility,
nodes: vec![NodeData {
name: String::new(),
tracks: vec![TrackData {
name: String::new(),
values: TrackValues::Boolean(vec![true; 4]),
compensate_scale: false,
transform_flags: TransformFlags::default(),
}],
}],
}],
});
assert!(matches!(
result,
Err(error::Error::InvalidFinalFrameIndex {
final_frame_index
}) if final_frame_index == 2.0
));
}
#[test]
fn create_anim_zero_frame_index() {
let anim = create_anim(&AnimData {
major_version: 2,
minor_version: 1,
final_frame_index: 0.0,
groups: Vec::new(),
})
.unwrap();
assert!(matches!(anim, Anim::V21 {
final_frame_index,
..
} if final_frame_index == 0.0));
}
#[test]
fn create_empty_anim_invalid_version() {
let result = create_anim(&AnimData {
major_version: 1,
minor_version: 2,
final_frame_index: 0.0,
groups: Vec::new(),
});
assert!(matches!(
result,
Err(error::Error::UnsupportedVersion {
major_version: 1,
minor_version: 2
})
));
}
#[test]
fn create_node_no_tracks() {
let node = NodeData {
name: "empty".to_string(),
tracks: Vec::new(),
};
let mut buffer = Cursor::new(Vec::new());
let anim_node = create_anim_node(&node, &mut buffer).unwrap();
assert_eq!("empty", anim_node.name.to_str().unwrap());
assert!(anim_node.tracks.elements.is_empty());
}
#[test]
fn create_node_multiple_tracks() {
let node = NodeData {
name: "empty".to_string(),
tracks: vec![
TrackData {
name: "t1".to_string(),
values: TrackValues::Float(vec![1.0, 2.0, 3.0]),
compensate_scale: false,
transform_flags: TransformFlags::default(),
},
TrackData {
name: "t2".to_string(),
values: TrackValues::PatternIndex(vec![4, 5]),
compensate_scale: false,
transform_flags: TransformFlags::default(),
},
],
};
let mut buffer = Cursor::new(Vec::new());
let anim_node = create_anim_node(&node, &mut buffer).unwrap();
assert_eq!("empty", anim_node.name.to_str().unwrap());
assert_eq!(2, anim_node.tracks.elements.len());
let t1 = &anim_node.tracks.elements[0];
assert_eq!("t1", t1.name.to_str().unwrap());
assert_eq!(
TrackFlags {
track_type: TrackTypeV2::Float,
compression_type: CompressionType::Direct
},
t1.flags
);
assert_eq!(3, t1.frame_count);
assert_eq!(0, t1.data_offset);
assert_eq!(12, t1.data_size);
let t2 = &anim_node.tracks.elements[1];
assert_eq!("t2", t2.name.to_str().unwrap());
assert_eq!(
TrackFlags {
track_type: TrackTypeV2::PatternIndex,
compression_type: CompressionType::Direct
},
t2.flags
);
assert_eq!(2, t2.frame_count);
assert_eq!(12, t2.data_offset);
assert_eq!(8, t2.data_size);
}
#[test]
fn compression_type_empty() {
assert_eq!(
CompressionType::ConstTransform,
infer_optimal_compression_type(&TrackValues::Transform(Vec::new()))
);
assert_eq!(
CompressionType::Constant,
infer_optimal_compression_type(&TrackValues::UvTransform(Vec::new()))
);
assert_eq!(
CompressionType::Constant,
infer_optimal_compression_type(&TrackValues::Float(Vec::new()))
);
assert_eq!(
CompressionType::Constant,
infer_optimal_compression_type(&TrackValues::PatternIndex(Vec::new()))
);
assert_eq!(
CompressionType::Constant,
infer_optimal_compression_type(&TrackValues::Boolean(Vec::new()))
);
assert_eq!(
CompressionType::Constant,
infer_optimal_compression_type(&TrackValues::Vector4(Vec::new()))
);
}
#[test]
fn compression_type_boolean_multiple_frames() {
assert_eq!(
CompressionType::Direct,
infer_optimal_compression_type(&TrackValues::Boolean(vec![true; 8]))
);
assert_eq!(
CompressionType::Direct,
infer_optimal_compression_type(&TrackValues::Boolean(vec![true; 34]))
);
assert_eq!(
CompressionType::Compressed,
infer_optimal_compression_type(&TrackValues::Boolean(vec![true; 35]))
);
assert_eq!(
CompressionType::Compressed,
infer_optimal_compression_type(&TrackValues::Boolean(vec![true; 100]))
);
}
#[test]
fn compression_type_float_multiple_frames() {
assert_eq!(
CompressionType::Direct,
infer_optimal_compression_type(&TrackValues::Float(vec![0.0; 8]))
);
assert_eq!(
CompressionType::Direct,
infer_optimal_compression_type(&TrackValues::Float(vec![0.0; 10]))
);
assert_eq!(
CompressionType::Compressed,
infer_optimal_compression_type(&TrackValues::Float(vec![0.0; 11]))
);
assert_eq!(
CompressionType::Compressed,
infer_optimal_compression_type(&TrackValues::Float(vec![0.0; 100]))
);
}
#[test]
fn compression_type_pattern_index_multiple_frames() {
assert_eq!(
CompressionType::Direct,
infer_optimal_compression_type(&TrackValues::PatternIndex(vec![0; 8]))
);
assert_eq!(
CompressionType::Direct,
infer_optimal_compression_type(&TrackValues::PatternIndex(vec![0; 10]))
);
assert_eq!(
CompressionType::Compressed,
infer_optimal_compression_type(&TrackValues::PatternIndex(vec![0; 11]))
);
assert_eq!(
CompressionType::Compressed,
infer_optimal_compression_type(&TrackValues::PatternIndex(vec![0; 100]))
);
}
#[test]
fn compression_type_uv_transform_multiple_frames() {
assert_eq!(
CompressionType::Direct,
infer_optimal_compression_type(&TrackValues::UvTransform(vec![
UvTransform::default();
3
]))
);
assert_eq!(
CompressionType::Direct,
infer_optimal_compression_type(&TrackValues::UvTransform(vec![
UvTransform::default();
6
]))
);
assert_eq!(
CompressionType::Compressed,
infer_optimal_compression_type(&TrackValues::UvTransform(vec![
UvTransform::default();
7
]))
);
assert_eq!(
CompressionType::Compressed,
infer_optimal_compression_type(&TrackValues::UvTransform(vec![
UvTransform::default();
100
]))
);
}
#[test]
fn compression_type_vector4_multiple_frames() {
assert_eq!(
CompressionType::Direct,
infer_optimal_compression_type(&TrackValues::Vector4(vec![Vector4::default(); 3]))
);
assert_eq!(
CompressionType::Direct,
infer_optimal_compression_type(&TrackValues::Vector4(vec![Vector4::default(); 7]))
);
assert_eq!(
CompressionType::Compressed,
infer_optimal_compression_type(&TrackValues::Vector4(vec![Vector4::default(); 8]))
);
assert_eq!(
CompressionType::Compressed,
infer_optimal_compression_type(&TrackValues::Vector4(vec![Vector4::default(); 100]))
);
}
#[test]
fn compression_type_transform_multiple_frames() {
assert_eq!(
CompressionType::Direct,
infer_optimal_compression_type(&TrackValues::Transform(vec![Transform::default(); 3]))
);
assert_eq!(
CompressionType::Direct,
infer_optimal_compression_type(&TrackValues::Transform(vec![Transform::default(); 5]))
);
assert_eq!(
CompressionType::Compressed,
infer_optimal_compression_type(&TrackValues::Transform(vec![Transform::default(); 6]))
);
assert_eq!(
CompressionType::Compressed,
infer_optimal_compression_type(&TrackValues::Transform(vec![
Transform::default();
100
]))
);
}
#[test]
fn read_v20_track_invalid_offset() {
let result = create_track_data_v20(
&TrackV2 {
name: "abc".into(),
flags: TrackFlags {
track_type: TrackTypeV2::Transform,
compression_type: CompressionType::Compressed,
},
frame_count: 2,
transform_flags: AnimTransformFlags::new(),
data_offset: 5,
data_size: 1,
},
&[0u8; 4],
);
assert!(matches!(
result,
Err(error::Error::InvalidTrackDataRange {
start: 5,
size: 1,
buffer_size: 4
})
));
}
#[test]
fn read_v20_track_offset_overflow() {
let result = create_track_data_v20(
&TrackV2 {
name: "abc".into(),
flags: TrackFlags {
track_type: TrackTypeV2::Transform,
compression_type: CompressionType::Compressed,
},
frame_count: 2,
transform_flags: AnimTransformFlags::new(),
data_offset: u32::MAX,
data_size: 1,
},
&[0u8; 4],
);
assert!(matches!(
result,
Err(error::Error::InvalidTrackDataRange {
start: 4294967295,
size: 1,
buffer_size: 4
})
));
}
#[test]
fn read_v20_track_invalid_size() {
let result = create_track_data_v20(
&TrackV2 {
name: "abc".into(),
flags: TrackFlags {
track_type: TrackTypeV2::Transform,
compression_type: CompressionType::Compressed,
},
frame_count: 2,
transform_flags: AnimTransformFlags::new(),
data_offset: 0,
data_size: 5,
},
&[0u8; 3],
);
assert!(matches!(
result,
Err(error::Error::InvalidTrackDataRange {
start: 0,
size: 5,
buffer_size: 3
})
));
}
}