use std::fmt;
use crate::{
AppendTransformInit, BoneIndex, BoneInit, IkAngleLimit, IkLinkInit, IkSolverInit, MorphIndex,
MorphInit, build_morph_init_from_offsets,
};
#[cfg(test)]
use crate::MorphOffsetSpan;
pub struct FlatBoneInput<'a> {
pub parent_indices: &'a [i32],
pub rest_positions_xyz: &'a [f32],
pub inverse_bind_matrices: &'a [f32],
pub transform_orders: &'a [i32],
}
#[derive(Debug, Clone, Copy)]
pub struct FlatIkLinkInput {
pub bone_index: u32,
pub has_angle_limit: bool,
pub angle_limit_min_xyz: [f32; 3],
pub angle_limit_max_xyz: [f32; 3],
}
#[derive(Debug, Clone, Copy)]
pub struct FlatIkSolverInput {
pub ik_bone_index: u32,
pub target_bone_index: u32,
pub link_offset: usize,
pub link_count: usize,
pub iteration_count: u32,
pub limit_angle: f32,
}
#[derive(Debug, Clone, Copy)]
pub struct FlatAppendTransformInput {
pub target_bone_index: u32,
pub source_bone_index: u32,
pub ratio: f32,
pub affect_rotation: bool,
pub affect_translation: bool,
pub local: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct FlatBoneMorphInput {
pub morph_index: u32,
pub target_bone_index: u32,
pub position_offset_xyz: [f32; 3],
pub rotation_offset_xyzw: [f32; 4],
}
#[derive(Debug, Clone, Copy)]
pub struct FlatGroupMorphInput {
pub morph_index: u32,
pub child_morph_index: u32,
pub ratio: f32,
}
pub struct FlatMorphInput<'a> {
pub morph_count: u32,
pub bone_morphs: &'a [FlatBoneMorphInput],
pub group_morphs: &'a [FlatGroupMorphInput],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlatModelInputError {
EmptyBoneSet,
RestPositionsLen,
InverseBindMatricesLen,
TransformOrdersLen,
InvalidParentIndex,
RangeOverflow,
RangeOutOfBounds,
MorphCountZeroWithData,
BoneMorphIndexOutOfRange,
GroupMorphIndexOutOfRange,
}
impl fmt::Display for FlatModelInputError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self {
Self::EmptyBoneSet => "model must contain at least one bone",
Self::RestPositionsLen => "rest_positions_xyz must contain bone_count * 3 values",
Self::InverseBindMatricesLen => {
"inverse_bind_matrices must contain bone_count * 16 values"
}
Self::TransformOrdersLen => "transform_orders must contain bone_count values",
Self::InvalidParentIndex => "parent index must be -1 or non-negative",
Self::RangeOverflow => "range overflow",
Self::RangeOutOfBounds => "track keyframe range is out of bounds",
Self::MorphCountZeroWithData => {
"morph_count must be non-zero when morph data is provided"
}
Self::BoneMorphIndexOutOfRange => "bone morph index is out of range",
Self::GroupMorphIndexOutOfRange => "group morph index is out of range",
};
f.write_str(message)
}
}
impl std::error::Error for FlatModelInputError {}
pub fn build_bones_from_flat(
input: FlatBoneInput<'_>,
) -> Result<Vec<BoneInit>, FlatModelInputError> {
if input.parent_indices.is_empty() {
return Err(FlatModelInputError::EmptyBoneSet);
}
if input.rest_positions_xyz.len() != input.parent_indices.len() * 3 {
return Err(FlatModelInputError::RestPositionsLen);
}
if !input.inverse_bind_matrices.is_empty()
&& input.inverse_bind_matrices.len() != input.parent_indices.len() * 16
{
return Err(FlatModelInputError::InverseBindMatricesLen);
}
if !input.transform_orders.is_empty()
&& input.transform_orders.len() != input.parent_indices.len()
{
return Err(FlatModelInputError::TransformOrdersLen);
}
let mut bones = Vec::with_capacity(input.parent_indices.len());
for (bone_index, parent_index) in input.parent_indices.iter().enumerate() {
let parent = match *parent_index {
-1 => None,
parent if parent >= 0 => Some(BoneIndex(parent as u32)),
_ => return Err(FlatModelInputError::InvalidParentIndex),
};
let position_offset = bone_index * 3;
let mut bone = BoneInit::new(
parent,
glam::Vec3A::new(
input.rest_positions_xyz[position_offset],
input.rest_positions_xyz[position_offset + 1],
input.rest_positions_xyz[position_offset + 2],
),
);
if !input.inverse_bind_matrices.is_empty() {
let inverse_bind_offset = bone_index * 16;
let inverse_bind_matrix = input.inverse_bind_matrices
[inverse_bind_offset..inverse_bind_offset + 16]
.try_into()
.expect("validated inverse bind matrix slice length");
bone.inverse_bind_matrix = glam::Mat4::from_cols_array(inverse_bind_matrix);
}
if !input.transform_orders.is_empty() {
bone.transform_order = input.transform_orders[bone_index];
}
bones.push(bone);
}
Ok(bones)
}
pub fn build_ik_solvers_from_flat(
solvers: &[FlatIkSolverInput],
links: &[FlatIkLinkInput],
) -> Result<Vec<IkSolverInit>, FlatModelInputError> {
build_ik_solvers_from_flat_iter(solvers.iter().copied(), links)
}
pub fn build_ik_solvers_from_flat_iter(
solvers: impl IntoIterator<Item = FlatIkSolverInput>,
links: &[FlatIkLinkInput],
) -> Result<Vec<IkSolverInit>, FlatModelInputError> {
solvers
.into_iter()
.map(|solver| {
let link_end = solver
.link_offset
.checked_add(solver.link_count)
.ok_or(FlatModelInputError::RangeOverflow)?;
let solver_links = links
.get(solver.link_offset..link_end)
.ok_or(FlatModelInputError::RangeOutOfBounds)?
.iter()
.map(|link| {
let mut init = IkLinkInit::new(BoneIndex(link.bone_index));
if link.has_angle_limit {
init = init.with_angle_limit(IkAngleLimit::new(
glam::Vec3A::new(
link.angle_limit_min_xyz[0],
link.angle_limit_min_xyz[1],
link.angle_limit_min_xyz[2],
),
glam::Vec3A::new(
link.angle_limit_max_xyz[0],
link.angle_limit_max_xyz[1],
link.angle_limit_max_xyz[2],
),
));
}
init
})
.collect();
Ok(IkSolverInit {
ik_bone: BoneIndex(solver.ik_bone_index),
target_bone: BoneIndex(solver.target_bone_index),
links: solver_links,
iteration_count: solver.iteration_count,
limit_angle: solver.limit_angle,
})
})
.collect()
}
pub fn build_morph_init_from_flat(
input: FlatMorphInput<'_>,
) -> Result<MorphInit, FlatModelInputError> {
build_morph_init_from_flat_iter(
input.morph_count,
input.bone_morphs.iter().copied(),
input.group_morphs.iter().copied(),
)
}
pub fn build_morph_init_from_flat_iter(
morph_count: u32,
bone_morphs: impl IntoIterator<Item = FlatBoneMorphInput>,
group_morphs: impl IntoIterator<Item = FlatGroupMorphInput>,
) -> Result<MorphInit, FlatModelInputError> {
let bone_morphs = bone_morphs.into_iter().collect::<Vec<_>>();
let group_morphs = group_morphs.into_iter().collect::<Vec<_>>();
if morph_count == 0 {
if bone_morphs.is_empty() && group_morphs.is_empty() {
return Ok(MorphInit::default());
}
return Err(FlatModelInputError::MorphCountZeroWithData);
}
let bone_offsets = bone_morphs
.iter()
.map(|entry| {
(
MorphIndex(entry.morph_index),
crate::BoneMorphOffset {
target_bone: BoneIndex(entry.target_bone_index),
position_offset: glam::Vec3A::from_array(entry.position_offset_xyz),
rotation_offset: glam::Quat::from_xyzw(
entry.rotation_offset_xyzw[0],
entry.rotation_offset_xyzw[1],
entry.rotation_offset_xyzw[2],
entry.rotation_offset_xyzw[3],
),
},
)
})
.collect();
let group_offsets = group_morphs
.iter()
.map(|entry| {
(
MorphIndex(entry.morph_index),
crate::GroupMorphOffset {
child_morph: MorphIndex(entry.child_morph_index),
ratio: entry.ratio,
},
)
})
.collect();
build_morph_init_from_offsets(morph_count, bone_offsets, group_offsets).map_err(|error| {
match error {
crate::ModelBuildError::MorphCountZeroWithData => {
FlatModelInputError::MorphCountZeroWithData
}
crate::ModelBuildError::InvalidBoneMorphMorph { .. } => {
FlatModelInputError::BoneMorphIndexOutOfRange
}
crate::ModelBuildError::InvalidGroupMorph { .. }
| crate::ModelBuildError::InvalidGroupMorphChild { .. }
| crate::ModelBuildError::GroupMorphCycle { .. }
| crate::ModelBuildError::GroupMorphCycleAt { .. } => {
FlatModelInputError::GroupMorphIndexOutOfRange
}
_ => FlatModelInputError::RangeOverflow,
}
})
}
pub fn build_append_transforms_from_flat(
append_transforms: &[FlatAppendTransformInput],
) -> Vec<AppendTransformInit> {
build_append_transforms_from_flat_iter(append_transforms.iter().copied())
}
pub fn build_append_transforms_from_flat_iter(
append_transforms: impl IntoIterator<Item = FlatAppendTransformInput>,
) -> Vec<AppendTransformInit> {
append_transforms
.into_iter()
.map(|append| {
let mut init = AppendTransformInit::new(
BoneIndex(append.target_bone_index),
BoneIndex(append.source_bone_index),
append.ratio,
);
if append.affect_rotation {
init = init.with_rotation();
}
if append.affect_translation {
init = init.with_translation();
}
if append.local {
init = init.with_local();
}
init
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builds_bones_from_flat_arrays() {
let bones = build_bones_from_flat(FlatBoneInput {
parent_indices: &[-1, 0],
rest_positions_xyz: &[0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
inverse_bind_matrices: &[],
transform_orders: &[2, 1],
})
.unwrap();
assert_eq!(bones.len(), 2);
assert_eq!(bones[0].parent, None);
assert_eq!(bones[1].parent, Some(BoneIndex(0)));
assert_eq!(bones[1].rest_position.to_array(), [3.0, 4.0, 5.0]);
assert_eq!(bones[0].transform_order, 2);
assert_eq!(bones[1].transform_order, 1);
}
#[test]
fn rejects_invalid_flat_bone_arrays() {
let error = build_bones_from_flat(FlatBoneInput {
parent_indices: &[0],
rest_positions_xyz: &[0.0, 1.0],
inverse_bind_matrices: &[],
transform_orders: &[],
})
.unwrap_err();
assert_eq!(error, FlatModelInputError::RestPositionsLen);
assert_eq!(
error.to_string(),
"rest_positions_xyz must contain bone_count * 3 values"
);
}
#[test]
fn builds_ik_solvers_from_flat_arrays() {
let solvers = build_ik_solvers_from_flat(
&[FlatIkSolverInput {
ik_bone_index: 3,
target_bone_index: 2,
link_offset: 0,
link_count: 1,
iteration_count: 10,
limit_angle: 0.5,
}],
&[FlatIkLinkInput {
bone_index: 1,
has_angle_limit: true,
angle_limit_min_xyz: [-1.0, -2.0, -3.0],
angle_limit_max_xyz: [1.0, 2.0, 3.0],
}],
)
.unwrap();
assert_eq!(solvers.len(), 1);
assert_eq!(solvers[0].ik_bone, BoneIndex(3));
assert_eq!(solvers[0].target_bone, BoneIndex(2));
assert_eq!(solvers[0].links.len(), 1);
assert!(solvers[0].links[0].angle_limit.is_some());
}
#[test]
fn builds_append_transforms_from_flat_arrays() {
let append_transforms = build_append_transforms_from_flat(&[FlatAppendTransformInput {
target_bone_index: 2,
source_bone_index: 1,
ratio: 0.25,
affect_rotation: true,
affect_translation: false,
local: true,
}]);
assert_eq!(append_transforms.len(), 1);
assert_eq!(append_transforms[0].target_bone, BoneIndex(2));
assert_eq!(append_transforms[0].source_bone, BoneIndex(1));
assert_eq!(append_transforms[0].ratio, 0.25);
assert!(append_transforms[0].affect_rotation);
assert!(!append_transforms[0].affect_translation);
assert!(append_transforms[0].local);
}
#[test]
fn builds_morph_init_from_flat_arrays() {
let morph = build_morph_init_from_flat(FlatMorphInput {
morph_count: 2,
bone_morphs: &[FlatBoneMorphInput {
morph_index: 1,
target_bone_index: 0,
position_offset_xyz: [1.0, 2.0, 3.0],
rotation_offset_xyzw: [0.0, 0.0, 0.0, 1.0],
}],
group_morphs: &[],
})
.unwrap();
assert_eq!(morph.morph_count, 2);
assert_eq!(morph.bone_offsets.len(), 1);
assert_eq!(morph.bone_spans.len(), 2);
assert_eq!(morph.bone_spans[0], MorphOffsetSpan::default());
assert_eq!(morph.bone_spans[1], MorphOffsetSpan { start: 0, count: 1 });
assert_eq!(morph.bone_offsets[0].target_bone, BoneIndex(0));
}
#[test]
fn rejects_out_of_range_bone_morph_index() {
let error = build_morph_init_from_flat(FlatMorphInput {
morph_count: 1,
bone_morphs: &[FlatBoneMorphInput {
morph_index: 1,
target_bone_index: 0,
position_offset_xyz: [0.0, 0.0, 0.0],
rotation_offset_xyzw: [0.0, 0.0, 0.0, 1.0],
}],
group_morphs: &[],
})
.unwrap_err();
assert_eq!(error, FlatModelInputError::BoneMorphIndexOutOfRange);
}
#[test]
fn rejects_zero_morph_count_with_data() {
let error = build_morph_init_from_flat(FlatMorphInput {
morph_count: 0,
bone_morphs: &[FlatBoneMorphInput {
morph_index: 0,
target_bone_index: 0,
position_offset_xyz: [0.0, 0.0, 0.0],
rotation_offset_xyzw: [0.0, 0.0, 0.0, 1.0],
}],
group_morphs: &[],
})
.unwrap_err();
assert_eq!(error, FlatModelInputError::MorphCountZeroWithData);
assert_eq!(
error.to_string(),
"morph_count must be non-zero when morph data is provided"
);
}
}