Skip to main content

mmd_anim_runtime/
flat_model.rs

1use std::fmt;
2
3use crate::{
4    AppendTransformInit, BoneIndex, BoneInit, IkAngleLimit, IkLinkInit, IkSolverInit, MorphIndex,
5    MorphInit, build_morph_init_from_offsets,
6};
7
8#[cfg(test)]
9use crate::MorphOffsetSpan;
10
11pub struct FlatBoneInput<'a> {
12    pub parent_indices: &'a [i32],
13    pub rest_positions_xyz: &'a [f32],
14    pub inverse_bind_matrices: &'a [f32],
15    pub transform_orders: &'a [i32],
16}
17
18#[derive(Debug, Clone, Copy)]
19pub struct FlatIkLinkInput {
20    pub bone_index: u32,
21    pub has_angle_limit: bool,
22    pub angle_limit_min_xyz: [f32; 3],
23    pub angle_limit_max_xyz: [f32; 3],
24}
25
26#[derive(Debug, Clone, Copy)]
27pub struct FlatIkSolverInput {
28    pub ik_bone_index: u32,
29    pub target_bone_index: u32,
30    pub link_offset: usize,
31    pub link_count: usize,
32    pub iteration_count: u32,
33    pub limit_angle: f32,
34}
35
36#[derive(Debug, Clone, Copy)]
37pub struct FlatAppendTransformInput {
38    pub target_bone_index: u32,
39    pub source_bone_index: u32,
40    pub ratio: f32,
41    pub affect_rotation: bool,
42    pub affect_translation: bool,
43    pub local: bool,
44}
45
46#[derive(Debug, Clone, Copy)]
47pub struct FlatBoneMorphInput {
48    pub morph_index: u32,
49    pub target_bone_index: u32,
50    pub position_offset_xyz: [f32; 3],
51    pub rotation_offset_xyzw: [f32; 4],
52}
53
54#[derive(Debug, Clone, Copy)]
55pub struct FlatGroupMorphInput {
56    pub morph_index: u32,
57    pub child_morph_index: u32,
58    pub ratio: f32,
59}
60
61pub struct FlatMorphInput<'a> {
62    pub morph_count: u32,
63    pub bone_morphs: &'a [FlatBoneMorphInput],
64    pub group_morphs: &'a [FlatGroupMorphInput],
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum FlatModelInputError {
69    EmptyBoneSet,
70    RestPositionsLen,
71    InverseBindMatricesLen,
72    TransformOrdersLen,
73    InvalidParentIndex,
74    RangeOverflow,
75    RangeOutOfBounds,
76    MorphCountZeroWithData,
77    BoneMorphIndexOutOfRange,
78    GroupMorphIndexOutOfRange,
79}
80
81impl fmt::Display for FlatModelInputError {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        let message = match self {
84            Self::EmptyBoneSet => "model must contain at least one bone",
85            Self::RestPositionsLen => "rest_positions_xyz must contain bone_count * 3 values",
86            Self::InverseBindMatricesLen => {
87                "inverse_bind_matrices must contain bone_count * 16 values"
88            }
89            Self::TransformOrdersLen => "transform_orders must contain bone_count values",
90            Self::InvalidParentIndex => "parent index must be -1 or non-negative",
91            Self::RangeOverflow => "range overflow",
92            Self::RangeOutOfBounds => "track keyframe range is out of bounds",
93            Self::MorphCountZeroWithData => {
94                "morph_count must be non-zero when morph data is provided"
95            }
96            Self::BoneMorphIndexOutOfRange => "bone morph index is out of range",
97            Self::GroupMorphIndexOutOfRange => "group morph index is out of range",
98        };
99        f.write_str(message)
100    }
101}
102
103impl std::error::Error for FlatModelInputError {}
104
105pub fn build_bones_from_flat(
106    input: FlatBoneInput<'_>,
107) -> Result<Vec<BoneInit>, FlatModelInputError> {
108    if input.parent_indices.is_empty() {
109        return Err(FlatModelInputError::EmptyBoneSet);
110    }
111    if input.rest_positions_xyz.len() != input.parent_indices.len() * 3 {
112        return Err(FlatModelInputError::RestPositionsLen);
113    }
114    if !input.inverse_bind_matrices.is_empty()
115        && input.inverse_bind_matrices.len() != input.parent_indices.len() * 16
116    {
117        return Err(FlatModelInputError::InverseBindMatricesLen);
118    }
119    if !input.transform_orders.is_empty()
120        && input.transform_orders.len() != input.parent_indices.len()
121    {
122        return Err(FlatModelInputError::TransformOrdersLen);
123    }
124
125    let mut bones = Vec::with_capacity(input.parent_indices.len());
126    for (bone_index, parent_index) in input.parent_indices.iter().enumerate() {
127        let parent = match *parent_index {
128            -1 => None,
129            parent if parent >= 0 => Some(BoneIndex(parent as u32)),
130            _ => return Err(FlatModelInputError::InvalidParentIndex),
131        };
132        let position_offset = bone_index * 3;
133        let mut bone = BoneInit::new(
134            parent,
135            glam::Vec3A::new(
136                input.rest_positions_xyz[position_offset],
137                input.rest_positions_xyz[position_offset + 1],
138                input.rest_positions_xyz[position_offset + 2],
139            ),
140        );
141        if !input.inverse_bind_matrices.is_empty() {
142            let inverse_bind_offset = bone_index * 16;
143            let inverse_bind_matrix = input.inverse_bind_matrices
144                [inverse_bind_offset..inverse_bind_offset + 16]
145                .try_into()
146                .expect("validated inverse bind matrix slice length");
147            bone.inverse_bind_matrix = glam::Mat4::from_cols_array(inverse_bind_matrix);
148        }
149        if !input.transform_orders.is_empty() {
150            bone.transform_order = input.transform_orders[bone_index];
151        }
152        bones.push(bone);
153    }
154
155    Ok(bones)
156}
157
158pub fn build_ik_solvers_from_flat(
159    solvers: &[FlatIkSolverInput],
160    links: &[FlatIkLinkInput],
161) -> Result<Vec<IkSolverInit>, FlatModelInputError> {
162    build_ik_solvers_from_flat_iter(solvers.iter().copied(), links)
163}
164
165pub fn build_ik_solvers_from_flat_iter(
166    solvers: impl IntoIterator<Item = FlatIkSolverInput>,
167    links: &[FlatIkLinkInput],
168) -> Result<Vec<IkSolverInit>, FlatModelInputError> {
169    solvers
170        .into_iter()
171        .map(|solver| {
172            let link_end = solver
173                .link_offset
174                .checked_add(solver.link_count)
175                .ok_or(FlatModelInputError::RangeOverflow)?;
176            let solver_links = links
177                .get(solver.link_offset..link_end)
178                .ok_or(FlatModelInputError::RangeOutOfBounds)?
179                .iter()
180                .map(|link| {
181                    let mut init = IkLinkInit::new(BoneIndex(link.bone_index));
182                    if link.has_angle_limit {
183                        init = init.with_angle_limit(IkAngleLimit::new(
184                            glam::Vec3A::new(
185                                link.angle_limit_min_xyz[0],
186                                link.angle_limit_min_xyz[1],
187                                link.angle_limit_min_xyz[2],
188                            ),
189                            glam::Vec3A::new(
190                                link.angle_limit_max_xyz[0],
191                                link.angle_limit_max_xyz[1],
192                                link.angle_limit_max_xyz[2],
193                            ),
194                        ));
195                    }
196                    init
197                })
198                .collect();
199
200            Ok(IkSolverInit {
201                ik_bone: BoneIndex(solver.ik_bone_index),
202                target_bone: BoneIndex(solver.target_bone_index),
203                links: solver_links,
204                iteration_count: solver.iteration_count,
205                limit_angle: solver.limit_angle,
206            })
207        })
208        .collect()
209}
210
211pub fn build_morph_init_from_flat(
212    input: FlatMorphInput<'_>,
213) -> Result<MorphInit, FlatModelInputError> {
214    build_morph_init_from_flat_iter(
215        input.morph_count,
216        input.bone_morphs.iter().copied(),
217        input.group_morphs.iter().copied(),
218    )
219}
220
221pub fn build_morph_init_from_flat_iter(
222    morph_count: u32,
223    bone_morphs: impl IntoIterator<Item = FlatBoneMorphInput>,
224    group_morphs: impl IntoIterator<Item = FlatGroupMorphInput>,
225) -> Result<MorphInit, FlatModelInputError> {
226    let bone_morphs = bone_morphs.into_iter().collect::<Vec<_>>();
227    let group_morphs = group_morphs.into_iter().collect::<Vec<_>>();
228    if morph_count == 0 {
229        if bone_morphs.is_empty() && group_morphs.is_empty() {
230            return Ok(MorphInit::default());
231        }
232        return Err(FlatModelInputError::MorphCountZeroWithData);
233    }
234    let bone_offsets = bone_morphs
235        .iter()
236        .map(|entry| {
237            (
238                MorphIndex(entry.morph_index),
239                crate::BoneMorphOffset {
240                    target_bone: BoneIndex(entry.target_bone_index),
241                    position_offset: glam::Vec3A::from_array(entry.position_offset_xyz),
242                    rotation_offset: glam::Quat::from_xyzw(
243                        entry.rotation_offset_xyzw[0],
244                        entry.rotation_offset_xyzw[1],
245                        entry.rotation_offset_xyzw[2],
246                        entry.rotation_offset_xyzw[3],
247                    ),
248                },
249            )
250        })
251        .collect();
252    let group_offsets = group_morphs
253        .iter()
254        .map(|entry| {
255            (
256                MorphIndex(entry.morph_index),
257                crate::GroupMorphOffset {
258                    child_morph: MorphIndex(entry.child_morph_index),
259                    ratio: entry.ratio,
260                },
261            )
262        })
263        .collect();
264    build_morph_init_from_offsets(morph_count, bone_offsets, group_offsets).map_err(|error| {
265        match error {
266            crate::ModelBuildError::MorphCountZeroWithData => {
267                FlatModelInputError::MorphCountZeroWithData
268            }
269            crate::ModelBuildError::InvalidBoneMorphMorph { .. } => {
270                FlatModelInputError::BoneMorphIndexOutOfRange
271            }
272            crate::ModelBuildError::InvalidGroupMorph { .. }
273            | crate::ModelBuildError::InvalidGroupMorphChild { .. }
274            | crate::ModelBuildError::GroupMorphCycle { .. }
275            | crate::ModelBuildError::GroupMorphCycleAt { .. } => {
276                FlatModelInputError::GroupMorphIndexOutOfRange
277            }
278            _ => FlatModelInputError::RangeOverflow,
279        }
280    })
281}
282
283pub fn build_append_transforms_from_flat(
284    append_transforms: &[FlatAppendTransformInput],
285) -> Vec<AppendTransformInit> {
286    build_append_transforms_from_flat_iter(append_transforms.iter().copied())
287}
288
289pub fn build_append_transforms_from_flat_iter(
290    append_transforms: impl IntoIterator<Item = FlatAppendTransformInput>,
291) -> Vec<AppendTransformInit> {
292    append_transforms
293        .into_iter()
294        .map(|append| {
295            let mut init = AppendTransformInit::new(
296                BoneIndex(append.target_bone_index),
297                BoneIndex(append.source_bone_index),
298                append.ratio,
299            );
300            if append.affect_rotation {
301                init = init.with_rotation();
302            }
303            if append.affect_translation {
304                init = init.with_translation();
305            }
306            if append.local {
307                init = init.with_local();
308            }
309            init
310        })
311        .collect()
312}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317
318    #[test]
319    fn builds_bones_from_flat_arrays() {
320        let bones = build_bones_from_flat(FlatBoneInput {
321            parent_indices: &[-1, 0],
322            rest_positions_xyz: &[0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
323            inverse_bind_matrices: &[],
324            transform_orders: &[2, 1],
325        })
326        .unwrap();
327
328        assert_eq!(bones.len(), 2);
329        assert_eq!(bones[0].parent, None);
330        assert_eq!(bones[1].parent, Some(BoneIndex(0)));
331        assert_eq!(bones[1].rest_position.to_array(), [3.0, 4.0, 5.0]);
332        assert_eq!(bones[0].transform_order, 2);
333        assert_eq!(bones[1].transform_order, 1);
334    }
335
336    #[test]
337    fn rejects_invalid_flat_bone_arrays() {
338        let error = build_bones_from_flat(FlatBoneInput {
339            parent_indices: &[0],
340            rest_positions_xyz: &[0.0, 1.0],
341            inverse_bind_matrices: &[],
342            transform_orders: &[],
343        })
344        .unwrap_err();
345
346        assert_eq!(error, FlatModelInputError::RestPositionsLen);
347        assert_eq!(
348            error.to_string(),
349            "rest_positions_xyz must contain bone_count * 3 values"
350        );
351    }
352
353    #[test]
354    fn builds_ik_solvers_from_flat_arrays() {
355        let solvers = build_ik_solvers_from_flat(
356            &[FlatIkSolverInput {
357                ik_bone_index: 3,
358                target_bone_index: 2,
359                link_offset: 0,
360                link_count: 1,
361                iteration_count: 10,
362                limit_angle: 0.5,
363            }],
364            &[FlatIkLinkInput {
365                bone_index: 1,
366                has_angle_limit: true,
367                angle_limit_min_xyz: [-1.0, -2.0, -3.0],
368                angle_limit_max_xyz: [1.0, 2.0, 3.0],
369            }],
370        )
371        .unwrap();
372
373        assert_eq!(solvers.len(), 1);
374        assert_eq!(solvers[0].ik_bone, BoneIndex(3));
375        assert_eq!(solvers[0].target_bone, BoneIndex(2));
376        assert_eq!(solvers[0].links.len(), 1);
377        assert!(solvers[0].links[0].angle_limit.is_some());
378    }
379
380    #[test]
381    fn builds_append_transforms_from_flat_arrays() {
382        let append_transforms = build_append_transforms_from_flat(&[FlatAppendTransformInput {
383            target_bone_index: 2,
384            source_bone_index: 1,
385            ratio: 0.25,
386            affect_rotation: true,
387            affect_translation: false,
388            local: true,
389        }]);
390
391        assert_eq!(append_transforms.len(), 1);
392        assert_eq!(append_transforms[0].target_bone, BoneIndex(2));
393        assert_eq!(append_transforms[0].source_bone, BoneIndex(1));
394        assert_eq!(append_transforms[0].ratio, 0.25);
395        assert!(append_transforms[0].affect_rotation);
396        assert!(!append_transforms[0].affect_translation);
397        assert!(append_transforms[0].local);
398    }
399
400    #[test]
401    fn builds_morph_init_from_flat_arrays() {
402        let morph = build_morph_init_from_flat(FlatMorphInput {
403            morph_count: 2,
404            bone_morphs: &[FlatBoneMorphInput {
405                morph_index: 1,
406                target_bone_index: 0,
407                position_offset_xyz: [1.0, 2.0, 3.0],
408                rotation_offset_xyzw: [0.0, 0.0, 0.0, 1.0],
409            }],
410            group_morphs: &[],
411        })
412        .unwrap();
413
414        assert_eq!(morph.morph_count, 2);
415        assert_eq!(morph.bone_offsets.len(), 1);
416        assert_eq!(morph.bone_spans.len(), 2);
417        assert_eq!(morph.bone_spans[0], MorphOffsetSpan::default());
418        assert_eq!(morph.bone_spans[1], MorphOffsetSpan { start: 0, count: 1 });
419        assert_eq!(morph.bone_offsets[0].target_bone, BoneIndex(0));
420    }
421
422    #[test]
423    fn rejects_out_of_range_bone_morph_index() {
424        let error = build_morph_init_from_flat(FlatMorphInput {
425            morph_count: 1,
426            bone_morphs: &[FlatBoneMorphInput {
427                morph_index: 1,
428                target_bone_index: 0,
429                position_offset_xyz: [0.0, 0.0, 0.0],
430                rotation_offset_xyzw: [0.0, 0.0, 0.0, 1.0],
431            }],
432            group_morphs: &[],
433        })
434        .unwrap_err();
435
436        assert_eq!(error, FlatModelInputError::BoneMorphIndexOutOfRange);
437    }
438
439    #[test]
440    fn rejects_zero_morph_count_with_data() {
441        let error = build_morph_init_from_flat(FlatMorphInput {
442            morph_count: 0,
443            bone_morphs: &[FlatBoneMorphInput {
444                morph_index: 0,
445                target_bone_index: 0,
446                position_offset_xyz: [0.0, 0.0, 0.0],
447                rotation_offset_xyzw: [0.0, 0.0, 0.0, 1.0],
448            }],
449            group_morphs: &[],
450        })
451        .unwrap_err();
452
453        assert_eq!(error, FlatModelInputError::MorphCountZeroWithData);
454        assert_eq!(
455            error.to_string(),
456            "morph_count must be non-zero when morph data is provided"
457        );
458    }
459}