Skip to main content

mmd_anim_runtime/
model.rs

1use glam::{Mat4, Quat, Vec3A};
2use thiserror::Error;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
5pub struct BoneIndex(pub u32);
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
8pub struct MorphIndex(pub u32);
9
10impl MorphIndex {
11    #[inline]
12    pub fn as_usize(self) -> usize {
13        self.0 as usize
14    }
15}
16
17#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct BoneMorphOffset {
19    pub target_bone: BoneIndex,
20    pub position_offset: Vec3A,
21    pub rotation_offset: Quat,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq)]
25pub struct VertexMorphOffset {
26    pub vertex_index: u32,
27    pub position_offset: Vec3A,
28}
29
30#[derive(Clone, Copy, Debug, PartialEq)]
31pub struct GroupMorphOffset {
32    pub child_morph: MorphIndex,
33    pub ratio: f32,
34}
35
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
37pub struct MorphOffsetSpan {
38    pub start: u32,
39    pub count: u32,
40}
41
42#[derive(Clone, Debug, Default)]
43pub struct MorphInit {
44    pub morph_count: u32,
45    pub vertex_offsets: Vec<VertexMorphOffset>,
46    pub vertex_spans: Vec<MorphOffsetSpan>,
47    pub bone_offsets: Vec<BoneMorphOffset>,
48    pub bone_spans: Vec<MorphOffsetSpan>,
49    pub group_offsets: Vec<GroupMorphOffset>,
50    pub group_spans: Vec<MorphOffsetSpan>,
51}
52
53impl BoneIndex {
54    #[inline]
55    pub fn as_usize(self) -> usize {
56        self.0 as usize
57    }
58}
59
60#[derive(Clone, Debug)]
61pub struct BoneInit {
62    pub parent: Option<BoneIndex>,
63    pub rest_position: Vec3A,
64    pub inverse_bind_matrix: Mat4,
65    pub transform_order: i32,
66    pub fixed_axis: Option<Vec3A>,
67}
68
69impl BoneInit {
70    pub fn new(parent: Option<BoneIndex>, rest_position: Vec3A) -> Self {
71        Self {
72            parent,
73            rest_position,
74            inverse_bind_matrix: Mat4::IDENTITY,
75            transform_order: 0,
76            fixed_axis: None,
77        }
78    }
79
80    pub fn with_fixed_axis(mut self, axis: Vec3A) -> Self {
81        self.fixed_axis = Some(axis);
82        self
83    }
84}
85
86#[derive(Clone, Copy, Debug, PartialEq)]
87pub struct IkAngleLimit {
88    pub min: Vec3A,
89    pub max: Vec3A,
90}
91
92impl IkAngleLimit {
93    pub fn new(min: Vec3A, max: Vec3A) -> Self {
94        Self { min, max }
95    }
96}
97
98#[derive(Clone, Debug, PartialEq)]
99pub struct IkLinkInit {
100    pub bone: BoneIndex,
101    pub angle_limit: Option<IkAngleLimit>,
102}
103
104impl IkLinkInit {
105    pub fn new(bone: BoneIndex) -> Self {
106        Self {
107            bone,
108            angle_limit: None,
109        }
110    }
111
112    pub fn with_angle_limit(mut self, angle_limit: IkAngleLimit) -> Self {
113        self.angle_limit = Some(angle_limit);
114        self
115    }
116}
117
118#[derive(Clone, Debug, PartialEq)]
119pub struct IkSolverInit {
120    pub ik_bone: BoneIndex,
121    pub target_bone: BoneIndex,
122    pub links: Vec<IkLinkInit>,
123    pub iteration_count: u32,
124    pub limit_angle: f32,
125}
126
127#[derive(Clone, Debug, PartialEq)]
128pub struct AppendTransformInit {
129    pub target_bone: BoneIndex,
130    pub source_bone: BoneIndex,
131    pub ratio: f32,
132    pub affect_rotation: bool,
133    pub affect_translation: bool,
134    pub local: bool,
135}
136
137impl AppendTransformInit {
138    pub fn new(target_bone: BoneIndex, source_bone: BoneIndex, ratio: f32) -> Self {
139        Self {
140            target_bone,
141            source_bone,
142            ratio,
143            affect_rotation: false,
144            affect_translation: false,
145            local: false,
146        }
147    }
148
149    pub fn with_rotation(mut self) -> Self {
150        self.affect_rotation = true;
151        self
152    }
153
154    pub fn with_translation(mut self) -> Self {
155        self.affect_translation = true;
156        self
157    }
158
159    pub fn with_local(mut self) -> Self {
160        self.local = true;
161        self
162    }
163}
164
165impl IkSolverInit {
166    pub fn new(ik_bone: BoneIndex, target_bone: BoneIndex, links: Vec<IkLinkInit>) -> Self {
167        Self {
168            ik_bone,
169            target_bone,
170            links,
171            iteration_count: 1,
172            limit_angle: 0.0,
173        }
174    }
175}
176
177#[derive(Debug, Error, PartialEq, Eq)]
178pub enum ModelBuildError {
179    #[error("model must contain at least one bone")]
180    EmptyModel,
181    #[error("bone {bone} references invalid parent {parent}")]
182    InvalidParent { bone: usize, parent: u32 },
183    #[error("bone hierarchy contains a cycle involving bone {bone}")]
184    ParentCycle { bone: usize },
185    #[error("ik solver {solver} references invalid {role} bone {bone}")]
186    InvalidIkBone {
187        solver: usize,
188        role: &'static str,
189        bone: u32,
190    },
191    #[error("append transform {append} references invalid {role} bone {bone}")]
192    InvalidAppendBone {
193        append: usize,
194        role: &'static str,
195        bone: u32,
196    },
197    #[error("bone {bone} has more than one append transform")]
198    DuplicateAppendTransform { bone: u32 },
199    #[error("bone morph offset {offset} references invalid target bone {bone}")]
200    InvalidBoneMorphBone { offset: usize, bone: u32 },
201    #[error("morph span list has length {actual}, expected {expected}")]
202    InvalidMorphSpanCount { actual: usize, expected: usize },
203    #[error("morph {morph} has invalid {kind} offset span")]
204    InvalidMorphSpan { morph: usize, kind: &'static str },
205    #[error("group morph {morph} references invalid child morph {child}")]
206    InvalidGroupMorphChild { morph: usize, child: u32 },
207    #[error("group morph cycle detected at morph {morph}")]
208    GroupMorphCycle { morph: usize },
209}
210
211#[derive(Debug)]
212pub struct ModelArena {
213    parent_indices: Box<[i32]>,
214    rest_positions: Box<[Vec3A]>,
215    inverse_bind_matrices: Box<[Mat4]>,
216    transform_orders: Box<[i32]>,
217    fixed_axis_flags: Box<[u8]>,
218    fixed_axes: Box<[Vec3A]>,
219    eval_order: Box<[BoneIndex]>,
220    ik_solvers: Box<[IkSolver]>,
221    append_transforms: Box<[AppendTransform]>,
222    append_transform_indices: Box<[i32]>,
223    morph_count: u32,
224    vertex_morph_offsets: Box<[VertexMorphOffset]>,
225    vertex_morph_spans: Box<[MorphOffsetSpan]>,
226    bone_morph_offsets: Box<[BoneMorphOffset]>,
227    bone_morph_spans: Box<[MorphOffsetSpan]>,
228    group_morph_offsets: Box<[GroupMorphOffset]>,
229    group_morph_spans: Box<[MorphOffsetSpan]>,
230}
231
232impl ModelArena {
233    pub fn new(bones: Vec<BoneInit>) -> Result<Self, ModelBuildError> {
234        Self::new_full(bones, Vec::new(), Vec::new())
235    }
236
237    pub fn new_with_ik(
238        bones: Vec<BoneInit>,
239        ik_solvers: Vec<IkSolverInit>,
240    ) -> Result<Self, ModelBuildError> {
241        Self::new_full(bones, ik_solvers, Vec::new())
242    }
243
244    pub fn new_full(
245        bones: Vec<BoneInit>,
246        ik_solvers: Vec<IkSolverInit>,
247        append_transforms: Vec<AppendTransformInit>,
248    ) -> Result<Self, ModelBuildError> {
249        Self::new_with_morphs(bones, ik_solvers, append_transforms, MorphInit::default())
250    }
251
252    pub fn new_with_morphs(
253        bones: Vec<BoneInit>,
254        ik_solvers: Vec<IkSolverInit>,
255        append_transforms: Vec<AppendTransformInit>,
256        morph: MorphInit,
257    ) -> Result<Self, ModelBuildError> {
258        if bones.is_empty() {
259            return Err(ModelBuildError::EmptyModel);
260        }
261
262        let bone_count = bones.len();
263        let mut parent_indices = Vec::with_capacity(bone_count);
264        let mut rest_positions = Vec::with_capacity(bone_count);
265        let mut inverse_bind_matrices = Vec::with_capacity(bone_count);
266        let mut transform_orders = Vec::with_capacity(bone_count);
267        let mut fixed_axis_flags = Vec::with_capacity(bone_count);
268        let mut fixed_axes = Vec::with_capacity(bone_count);
269
270        for (bone_index, bone) in bones.iter().enumerate() {
271            let parent = match bone.parent {
272                Some(parent) if parent.as_usize() < bone_count => parent.0 as i32,
273                Some(parent) => {
274                    return Err(ModelBuildError::InvalidParent {
275                        bone: bone_index,
276                        parent: parent.0,
277                    });
278                }
279                None => -1,
280            };
281
282            parent_indices.push(parent);
283            rest_positions.push(bone.rest_position);
284            inverse_bind_matrices.push(bone.inverse_bind_matrix);
285            transform_orders.push(bone.transform_order);
286            match bone.fixed_axis {
287                Some(axis) if axis.length_squared() > f32::EPSILON => {
288                    fixed_axis_flags.push(1);
289                    fixed_axes.push(axis.normalize());
290                }
291                _ => {
292                    fixed_axis_flags.push(0);
293                    fixed_axes.push(Vec3A::X);
294                }
295            }
296        }
297
298        let eval_order = build_eval_order(&parent_indices, &transform_orders)?;
299        let ik_solvers = build_ik_solvers(ik_solvers, bone_count)?;
300        let (append_transforms, append_transform_indices) =
301            build_append_transforms(append_transforms, bone_count)?;
302        validate_morph_init(&morph, bone_count)?;
303
304        Ok(Self {
305            parent_indices: parent_indices.into_boxed_slice(),
306            rest_positions: rest_positions.into_boxed_slice(),
307            inverse_bind_matrices: inverse_bind_matrices.into_boxed_slice(),
308            transform_orders: transform_orders.into_boxed_slice(),
309            fixed_axis_flags: fixed_axis_flags.into_boxed_slice(),
310            fixed_axes: fixed_axes.into_boxed_slice(),
311            eval_order,
312            ik_solvers,
313            append_transforms,
314            append_transform_indices,
315            morph_count: morph.morph_count,
316            vertex_morph_offsets: morph.vertex_offsets.into_boxed_slice(),
317            vertex_morph_spans: morph.vertex_spans.into_boxed_slice(),
318            bone_morph_offsets: morph.bone_offsets.into_boxed_slice(),
319            bone_morph_spans: morph.bone_spans.into_boxed_slice(),
320            group_morph_offsets: morph.group_offsets.into_boxed_slice(),
321            group_morph_spans: morph.group_spans.into_boxed_slice(),
322        })
323    }
324
325    #[inline]
326    pub fn bone_count(&self) -> usize {
327        self.parent_indices.len()
328    }
329
330    #[inline]
331    pub fn parent_index(&self, bone: BoneIndex) -> Option<BoneIndex> {
332        let parent = self.parent_indices[bone.as_usize()];
333        if parent < 0 {
334            None
335        } else {
336            Some(BoneIndex(parent as u32))
337        }
338    }
339
340    #[inline]
341    pub fn rest_position(&self, bone: BoneIndex) -> Vec3A {
342        self.rest_positions[bone.as_usize()]
343    }
344
345    #[inline]
346    pub fn inverse_bind_matrix(&self, bone: BoneIndex) -> Mat4 {
347        self.inverse_bind_matrices[bone.as_usize()]
348    }
349
350    #[inline]
351    pub fn transform_order(&self, bone: BoneIndex) -> i32 {
352        self.transform_orders[bone.as_usize()]
353    }
354
355    #[inline]
356    pub fn fixed_axis(&self, bone: BoneIndex) -> Option<Vec3A> {
357        if self.fixed_axis_flags[bone.as_usize()] != 0 {
358            Some(self.fixed_axes[bone.as_usize()])
359        } else {
360            None
361        }
362    }
363
364    #[inline]
365    pub fn fixed_axis_count(&self) -> usize {
366        self.fixed_axis_flags
367            .iter()
368            .filter(|&&flag| flag != 0)
369            .count()
370    }
371
372    #[inline]
373    pub fn eval_order(&self) -> &[BoneIndex] {
374        &self.eval_order
375    }
376
377    #[inline]
378    pub fn ik_count(&self) -> usize {
379        self.ik_solvers.len()
380    }
381
382    #[inline]
383    pub fn ik_solvers(&self) -> &[IkSolver] {
384        &self.ik_solvers
385    }
386
387    #[inline]
388    pub fn append_transform_index(&self, bone: BoneIndex) -> Option<usize> {
389        let index = self.append_transform_indices[bone.as_usize()];
390        if index < 0 {
391            None
392        } else {
393            Some(index as usize)
394        }
395    }
396
397    #[inline]
398    pub fn append_transform(&self, append_index: usize) -> &AppendTransform {
399        &self.append_transforms[append_index]
400    }
401
402    #[inline]
403    pub fn append_transforms(&self) -> &[AppendTransform] {
404        &self.append_transforms
405    }
406
407    #[inline]
408    pub fn morph_count(&self) -> u32 {
409        self.morph_count
410    }
411
412    #[inline]
413    pub fn vertex_morph_offsets(&self) -> &[VertexMorphOffset] {
414        &self.vertex_morph_offsets
415    }
416
417    #[inline]
418    pub fn vertex_morph_spans(&self) -> &[MorphOffsetSpan] {
419        &self.vertex_morph_spans
420    }
421
422    #[inline]
423    pub fn bone_morph_offsets(&self) -> &[BoneMorphOffset] {
424        &self.bone_morph_offsets
425    }
426
427    #[inline]
428    pub fn bone_morph_spans(&self) -> &[MorphOffsetSpan] {
429        &self.bone_morph_spans
430    }
431
432    #[inline]
433    pub fn group_morph_offsets(&self) -> &[GroupMorphOffset] {
434        &self.group_morph_offsets
435    }
436
437    #[inline]
438    pub fn group_morph_spans(&self) -> &[MorphOffsetSpan] {
439        &self.group_morph_spans
440    }
441}
442
443#[derive(Clone, Debug, PartialEq)]
444pub struct IkSolver {
445    pub ik_bone: BoneIndex,
446    pub target_bone: BoneIndex,
447    pub links: Box<[IkLink]>,
448    pub iteration_count: u32,
449    pub limit_angle: f32,
450}
451
452#[derive(Clone, Debug, PartialEq)]
453pub struct IkLink {
454    pub bone: BoneIndex,
455    pub angle_limit: Option<IkAngleLimit>,
456}
457
458#[derive(Clone, Copy, Debug, PartialEq)]
459pub struct AppendTransform {
460    pub target_bone: BoneIndex,
461    pub source_bone: BoneIndex,
462    pub ratio: f32,
463    pub affect_rotation: bool,
464    pub affect_translation: bool,
465    pub local: bool,
466}
467
468type AppendTransformBuildOutput = (Box<[AppendTransform]>, Box<[i32]>);
469
470fn build_ik_solvers(
471    ik_solvers: Vec<IkSolverInit>,
472    bone_count: usize,
473) -> Result<Box<[IkSolver]>, ModelBuildError> {
474    let mut solvers = Vec::with_capacity(ik_solvers.len());
475
476    for (solver_index, solver) in ik_solvers.into_iter().enumerate() {
477        validate_ik_bone(solver_index, "ik", solver.ik_bone, bone_count)?;
478        validate_ik_bone(solver_index, "target", solver.target_bone, bone_count)?;
479
480        let mut links = Vec::with_capacity(solver.links.len());
481        for link in solver.links {
482            validate_ik_bone(solver_index, "link", link.bone, bone_count)?;
483            links.push(IkLink {
484                bone: link.bone,
485                angle_limit: link.angle_limit,
486            });
487        }
488
489        solvers.push(IkSolver {
490            ik_bone: solver.ik_bone,
491            target_bone: solver.target_bone,
492            links: links.into_boxed_slice(),
493            iteration_count: solver.iteration_count,
494            limit_angle: solver.limit_angle,
495        });
496    }
497
498    Ok(solvers.into_boxed_slice())
499}
500
501fn validate_ik_bone(
502    solver: usize,
503    role: &'static str,
504    bone: BoneIndex,
505    bone_count: usize,
506) -> Result<(), ModelBuildError> {
507    if bone.as_usize() < bone_count {
508        Ok(())
509    } else {
510        Err(ModelBuildError::InvalidIkBone {
511            solver,
512            role,
513            bone: bone.0,
514        })
515    }
516}
517
518fn build_append_transforms(
519    append_transforms: Vec<AppendTransformInit>,
520    bone_count: usize,
521) -> Result<AppendTransformBuildOutput, ModelBuildError> {
522    let mut transforms = Vec::with_capacity(append_transforms.len());
523    let mut indices = vec![-1; bone_count];
524
525    for (append_index, append) in append_transforms.into_iter().enumerate() {
526        validate_append_bone(append_index, "target", append.target_bone, bone_count)?;
527        validate_append_bone(append_index, "source", append.source_bone, bone_count)?;
528
529        let target = append.target_bone.as_usize();
530        if indices[target] >= 0 {
531            return Err(ModelBuildError::DuplicateAppendTransform {
532                bone: append.target_bone.0,
533            });
534        }
535        indices[target] = append_index as i32;
536        transforms.push(AppendTransform {
537            target_bone: append.target_bone,
538            source_bone: append.source_bone,
539            ratio: append.ratio,
540            affect_rotation: append.affect_rotation,
541            affect_translation: append.affect_translation,
542            local: append.local,
543        });
544    }
545
546    Ok((transforms.into_boxed_slice(), indices.into_boxed_slice()))
547}
548
549fn validate_append_bone(
550    append: usize,
551    role: &'static str,
552    bone: BoneIndex,
553    bone_count: usize,
554) -> Result<(), ModelBuildError> {
555    if bone.as_usize() < bone_count {
556        Ok(())
557    } else {
558        Err(ModelBuildError::InvalidAppendBone {
559            append,
560            role,
561            bone: bone.0,
562        })
563    }
564}
565
566fn build_eval_order(
567    parent_indices: &[i32],
568    transform_orders: &[i32],
569) -> Result<Box<[BoneIndex]>, ModelBuildError> {
570    let mut state = vec![VisitState::Unvisited; parent_indices.len()];
571    let mut order = Vec::with_capacity(parent_indices.len());
572    let mut start_order = Vec::with_capacity(parent_indices.len());
573    for bone in 0..parent_indices.len() {
574        start_order.push(bone);
575    }
576    start_order.sort_by_key(|bone| (transform_orders[*bone], *bone));
577
578    for bone in start_order {
579        visit_bone(bone, parent_indices, &mut state, &mut order)?;
580    }
581
582    Ok(order.into_boxed_slice())
583}
584
585fn visit_bone(
586    bone: usize,
587    parent_indices: &[i32],
588    state: &mut [VisitState],
589    order: &mut Vec<BoneIndex>,
590) -> Result<(), ModelBuildError> {
591    match state[bone] {
592        VisitState::Visited => return Ok(()),
593        VisitState::Visiting => return Err(ModelBuildError::ParentCycle { bone }),
594        VisitState::Unvisited => {}
595    }
596
597    state[bone] = VisitState::Visiting;
598
599    let parent = parent_indices[bone];
600    if parent >= 0 {
601        visit_bone(parent as usize, parent_indices, state, order)?;
602    }
603
604    state[bone] = VisitState::Visited;
605    order.push(BoneIndex(bone as u32));
606    Ok(())
607}
608
609#[derive(Clone, Copy, Debug, PartialEq, Eq)]
610enum VisitState {
611    Unvisited,
612    Visiting,
613    Visited,
614}
615
616fn validate_morph_init(morph: &MorphInit, bone_count: usize) -> Result<(), ModelBuildError> {
617    let morph_count = morph.morph_count as usize;
618    if !morph.vertex_spans.is_empty() || !morph.vertex_offsets.is_empty() {
619        validate_morph_spans(
620            "vertex",
621            &morph.vertex_spans,
622            morph_count,
623            morph.vertex_offsets.len(),
624        )?;
625    }
626    validate_morph_spans(
627        "bone",
628        &morph.bone_spans,
629        morph_count,
630        morph.bone_offsets.len(),
631    )?;
632    validate_morph_spans(
633        "group",
634        &morph.group_spans,
635        morph_count,
636        morph.group_offsets.len(),
637    )?;
638
639    for (offset_index, offset) in morph.bone_offsets.iter().enumerate() {
640        if offset.target_bone.as_usize() >= bone_count {
641            return Err(ModelBuildError::InvalidBoneMorphBone {
642                offset: offset_index,
643                bone: offset.target_bone.0,
644            });
645        }
646    }
647
648    for (morph_index, span) in morph.group_spans.iter().enumerate() {
649        for offset_index in span.start..span.start + span.count {
650            let child = morph.group_offsets[offset_index as usize].child_morph;
651            if child.as_usize() >= morph_count {
652                return Err(ModelBuildError::InvalidGroupMorphChild {
653                    morph: morph_index,
654                    child: child.0,
655                });
656            }
657        }
658    }
659    validate_group_morph_cycles(morph)?;
660
661    Ok(())
662}
663
664fn validate_group_morph_cycles(morph: &MorphInit) -> Result<(), ModelBuildError> {
665    let mut state = vec![VisitState::Unvisited; morph.morph_count as usize];
666    for morph_index in 0..morph.morph_count as usize {
667        visit_group_morph(morph_index, morph, &mut state)?;
668    }
669    Ok(())
670}
671
672fn visit_group_morph(
673    morph_index: usize,
674    morph: &MorphInit,
675    state: &mut [VisitState],
676) -> Result<(), ModelBuildError> {
677    match state[morph_index] {
678        VisitState::Visited => return Ok(()),
679        VisitState::Visiting => {
680            return Err(ModelBuildError::GroupMorphCycle { morph: morph_index });
681        }
682        VisitState::Unvisited => {}
683    }
684
685    state[morph_index] = VisitState::Visiting;
686    let span = morph.group_spans[morph_index];
687    for offset_index in span.start..span.start + span.count {
688        let child = morph.group_offsets[offset_index as usize]
689            .child_morph
690            .as_usize();
691        if morph.group_spans[child].count > 0 {
692            visit_group_morph(child, morph, state)?;
693        }
694    }
695    state[morph_index] = VisitState::Visited;
696    Ok(())
697}
698
699fn validate_morph_spans(
700    kind: &'static str,
701    spans: &[MorphOffsetSpan],
702    morph_count: usize,
703    offset_count: usize,
704) -> Result<(), ModelBuildError> {
705    if spans.len() != morph_count {
706        return Err(ModelBuildError::InvalidMorphSpanCount {
707            actual: spans.len(),
708            expected: morph_count,
709        });
710    }
711
712    for (morph_index, span) in spans.iter().enumerate() {
713        let start = span.start as usize;
714        let count = span.count as usize;
715        if start
716            .checked_add(count)
717            .is_none_or(|end| end > offset_count)
718        {
719            return Err(ModelBuildError::InvalidMorphSpan {
720                morph: morph_index,
721                kind,
722            });
723        }
724    }
725
726    Ok(())
727}
728
729#[cfg(test)]
730mod tests {
731    use super::*;
732
733    #[test]
734    fn rejects_invalid_parent() {
735        let error =
736            ModelArena::new(vec![BoneInit::new(Some(BoneIndex(10)), Vec3A::ZERO)]).unwrap_err();
737
738        assert_eq!(
739            error,
740            ModelBuildError::InvalidParent {
741                bone: 0,
742                parent: 10
743            }
744        );
745    }
746
747    #[test]
748    fn parent_is_ordered_before_child_even_if_input_order_is_not_transform_order() {
749        let mut root = BoneInit::new(None, Vec3A::ZERO);
750        root.transform_order = 10;
751        let child = BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO);
752
753        let model = ModelArena::new(vec![root, child]).unwrap();
754
755        assert_eq!(model.eval_order(), &[BoneIndex(0), BoneIndex(1)]);
756    }
757
758    #[test]
759    fn stores_ik_solver_descriptors() {
760        let solver = IkSolverInit {
761            ik_bone: BoneIndex(2),
762            target_bone: BoneIndex(1),
763            links: vec![
764                IkLinkInit::new(BoneIndex(0))
765                    .with_angle_limit(IkAngleLimit::new(Vec3A::splat(-1.0), Vec3A::splat(1.0))),
766            ],
767            iteration_count: 4,
768            limit_angle: 0.5,
769        };
770
771        let model = ModelArena::new_with_ik(
772            vec![
773                BoneInit::new(None, Vec3A::ZERO),
774                BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO),
775                BoneInit::new(Some(BoneIndex(1)), Vec3A::ZERO),
776            ],
777            vec![solver],
778        )
779        .unwrap();
780
781        assert_eq!(model.ik_count(), 1);
782        assert_eq!(model.ik_solvers()[0].ik_bone, BoneIndex(2));
783        assert_eq!(model.ik_solvers()[0].target_bone, BoneIndex(1));
784        assert_eq!(model.ik_solvers()[0].links[0].bone, BoneIndex(0));
785        assert_eq!(model.ik_solvers()[0].iteration_count, 4);
786        assert_eq!(model.ik_solvers()[0].limit_angle, 0.5);
787    }
788
789    #[test]
790    fn stores_normalized_fixed_axis_descriptors() {
791        let model = ModelArena::new(vec![
792            BoneInit::new(None, Vec3A::ZERO).with_fixed_axis(Vec3A::new(0.0, 2.0, 0.0)),
793            BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO),
794        ])
795        .unwrap();
796
797        assert_eq!(model.fixed_axis(BoneIndex(0)), Some(Vec3A::Y));
798        assert_eq!(model.fixed_axis(BoneIndex(1)), None);
799    }
800
801    #[test]
802    fn rejects_invalid_ik_link_bone() {
803        let error = ModelArena::new_with_ik(
804            vec![BoneInit::new(None, Vec3A::ZERO)],
805            vec![IkSolverInit::new(
806                BoneIndex(0),
807                BoneIndex(0),
808                vec![IkLinkInit::new(BoneIndex(10))],
809            )],
810        )
811        .unwrap_err();
812
813        assert_eq!(
814            error,
815            ModelBuildError::InvalidIkBone {
816                solver: 0,
817                role: "link",
818                bone: 10,
819            }
820        );
821    }
822
823    #[test]
824    fn stores_append_transform_descriptors() {
825        let model = ModelArena::new_full(
826            vec![
827                BoneInit::new(None, Vec3A::ZERO),
828                BoneInit::new(None, Vec3A::ZERO),
829            ],
830            Vec::new(),
831            vec![
832                AppendTransformInit::new(BoneIndex(1), BoneIndex(0), 0.5)
833                    .with_rotation()
834                    .with_translation(),
835            ],
836        )
837        .unwrap();
838
839        let append_index = model.append_transform_index(BoneIndex(1)).unwrap();
840        let append = model.append_transform(append_index);
841        assert_eq!(append.source_bone, BoneIndex(0));
842        assert_eq!(append.ratio, 0.5);
843        assert!(append.affect_rotation);
844        assert!(append.affect_translation);
845    }
846
847    #[test]
848    fn rejects_duplicate_append_transform_target() {
849        let error = ModelArena::new_full(
850            vec![
851                BoneInit::new(None, Vec3A::ZERO),
852                BoneInit::new(None, Vec3A::ZERO),
853            ],
854            Vec::new(),
855            vec![
856                AppendTransformInit::new(BoneIndex(1), BoneIndex(0), 1.0),
857                AppendTransformInit::new(BoneIndex(1), BoneIndex(0), 1.0),
858            ],
859        )
860        .unwrap_err();
861
862        assert_eq!(error, ModelBuildError::DuplicateAppendTransform { bone: 1 });
863    }
864
865    #[test]
866    fn rejects_invalid_bone_morph_target_bone() {
867        let error = ModelArena::new_with_morphs(
868            vec![BoneInit::new(None, Vec3A::ZERO)],
869            Vec::new(),
870            Vec::new(),
871            MorphInit {
872                morph_count: 1,
873                bone_offsets: vec![BoneMorphOffset {
874                    target_bone: BoneIndex(10),
875                    position_offset: Vec3A::ZERO,
876                    rotation_offset: Quat::IDENTITY,
877                }],
878                bone_spans: vec![MorphOffsetSpan { start: 0, count: 1 }],
879                group_offsets: Vec::new(),
880                group_spans: vec![MorphOffsetSpan::default()],
881                ..MorphInit::default()
882            },
883        )
884        .unwrap_err();
885
886        assert_eq!(
887            error,
888            ModelBuildError::InvalidBoneMorphBone {
889                offset: 0,
890                bone: 10
891            }
892        );
893    }
894
895    #[test]
896    fn accepts_group_morph_child_that_is_later() {
897        let model = ModelArena::new_with_morphs(
898            vec![BoneInit::new(None, Vec3A::ZERO)],
899            Vec::new(),
900            Vec::new(),
901            MorphInit {
902                morph_count: 2,
903                bone_offsets: Vec::new(),
904                bone_spans: vec![MorphOffsetSpan::default(), MorphOffsetSpan::default()],
905                group_offsets: vec![GroupMorphOffset {
906                    child_morph: MorphIndex(1),
907                    ratio: 1.0,
908                }],
909                group_spans: vec![
910                    MorphOffsetSpan { start: 0, count: 1 },
911                    MorphOffsetSpan::default(),
912                ],
913                ..MorphInit::default()
914            },
915        )
916        .unwrap();
917
918        assert_eq!(model.morph_count(), 2);
919    }
920
921    #[test]
922    fn rejects_group_morph_child_out_of_range() {
923        let error = ModelArena::new_with_morphs(
924            vec![BoneInit::new(None, Vec3A::ZERO)],
925            Vec::new(),
926            Vec::new(),
927            MorphInit {
928                morph_count: 2,
929                bone_offsets: Vec::new(),
930                bone_spans: vec![MorphOffsetSpan::default(), MorphOffsetSpan::default()],
931                group_offsets: vec![GroupMorphOffset {
932                    child_morph: MorphIndex(2),
933                    ratio: 1.0,
934                }],
935                group_spans: vec![
936                    MorphOffsetSpan { start: 0, count: 1 },
937                    MorphOffsetSpan::default(),
938                ],
939                ..MorphInit::default()
940            },
941        )
942        .unwrap_err();
943
944        assert_eq!(
945            error,
946            ModelBuildError::InvalidGroupMorphChild { morph: 0, child: 2 }
947        );
948    }
949
950    #[test]
951    fn rejects_group_morph_cycle() {
952        let error = ModelArena::new_with_morphs(
953            vec![BoneInit::new(None, Vec3A::ZERO)],
954            Vec::new(),
955            Vec::new(),
956            MorphInit {
957                morph_count: 2,
958                bone_offsets: Vec::new(),
959                bone_spans: vec![MorphOffsetSpan::default(), MorphOffsetSpan::default()],
960                group_offsets: vec![
961                    GroupMorphOffset {
962                        child_morph: MorphIndex(1),
963                        ratio: 1.0,
964                    },
965                    GroupMorphOffset {
966                        child_morph: MorphIndex(0),
967                        ratio: 1.0,
968                    },
969                ],
970                group_spans: vec![
971                    MorphOffsetSpan { start: 0, count: 1 },
972                    MorphOffsetSpan { start: 1, count: 1 },
973                ],
974                ..MorphInit::default()
975            },
976        )
977        .unwrap_err();
978
979        assert_eq!(error, ModelBuildError::GroupMorphCycle { morph: 0 });
980    }
981
982    #[test]
983    fn stores_vertex_morph_offsets() {
984        let model = ModelArena::new_with_morphs(
985            vec![BoneInit::new(None, Vec3A::ZERO)],
986            Vec::new(),
987            Vec::new(),
988            MorphInit {
989                morph_count: 1,
990                vertex_offsets: vec![VertexMorphOffset {
991                    vertex_index: 7,
992                    position_offset: Vec3A::new(1.0, 2.0, 3.0),
993                }],
994                vertex_spans: vec![MorphOffsetSpan { start: 0, count: 1 }],
995                bone_spans: vec![MorphOffsetSpan::default()],
996                group_spans: vec![MorphOffsetSpan::default()],
997                ..MorphInit::default()
998            },
999        )
1000        .unwrap();
1001
1002        assert_eq!(
1003            model.vertex_morph_offsets(),
1004            &[VertexMorphOffset {
1005                vertex_index: 7,
1006                position_offset: Vec3A::new(1.0, 2.0, 3.0),
1007            }]
1008        );
1009        assert_eq!(
1010            model.vertex_morph_spans(),
1011            &[MorphOffsetSpan { start: 0, count: 1 }]
1012        );
1013    }
1014}