Skip to main content

mmd_anim_runtime/
flat_model.rs

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