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    eval_order_positions: Box<[usize]>,
221    ik_solvers: Box<[IkSolver]>,
222    append_transforms: Box<[AppendTransform]>,
223    append_transform_indices: Box<[i32]>,
224    morph_count: u32,
225    vertex_morph_offsets: Box<[VertexMorphOffset]>,
226    vertex_morph_spans: Box<[MorphOffsetSpan]>,
227    bone_morph_offsets: Box<[BoneMorphOffset]>,
228    bone_morph_spans: Box<[MorphOffsetSpan]>,
229    group_morph_offsets: Box<[GroupMorphOffset]>,
230    group_morph_spans: Box<[MorphOffsetSpan]>,
231}
232
233impl ModelArena {
234    pub fn new(bones: Vec<BoneInit>) -> Result<Self, ModelBuildError> {
235        Self::new_full(bones, Vec::new(), Vec::new())
236    }
237
238    pub fn new_with_ik(
239        bones: Vec<BoneInit>,
240        ik_solvers: Vec<IkSolverInit>,
241    ) -> Result<Self, ModelBuildError> {
242        Self::new_full(bones, ik_solvers, Vec::new())
243    }
244
245    pub fn new_full(
246        bones: Vec<BoneInit>,
247        ik_solvers: Vec<IkSolverInit>,
248        append_transforms: Vec<AppendTransformInit>,
249    ) -> Result<Self, ModelBuildError> {
250        Self::new_with_morphs(bones, ik_solvers, append_transforms, MorphInit::default())
251    }
252
253    pub fn new_with_morphs(
254        bones: Vec<BoneInit>,
255        ik_solvers: Vec<IkSolverInit>,
256        append_transforms: Vec<AppendTransformInit>,
257        morph: MorphInit,
258    ) -> Result<Self, ModelBuildError> {
259        if bones.is_empty() {
260            return Err(ModelBuildError::EmptyModel);
261        }
262
263        let bone_count = bones.len();
264        let mut parent_indices = Vec::with_capacity(bone_count);
265        let mut rest_positions = Vec::with_capacity(bone_count);
266        let mut inverse_bind_matrices = Vec::with_capacity(bone_count);
267        let mut transform_orders = Vec::with_capacity(bone_count);
268        let mut fixed_axis_flags = Vec::with_capacity(bone_count);
269        let mut fixed_axes = Vec::with_capacity(bone_count);
270
271        for (bone_index, bone) in bones.iter().enumerate() {
272            let parent = match bone.parent {
273                Some(parent) if parent.as_usize() < bone_count => parent.0 as i32,
274                Some(parent) => {
275                    return Err(ModelBuildError::InvalidParent {
276                        bone: bone_index,
277                        parent: parent.0,
278                    });
279                }
280                None => -1,
281            };
282
283            parent_indices.push(parent);
284            rest_positions.push(bone.rest_position);
285            inverse_bind_matrices.push(bone.inverse_bind_matrix);
286            transform_orders.push(bone.transform_order);
287            match bone.fixed_axis {
288                Some(axis) if axis.length_squared() > f32::EPSILON => {
289                    fixed_axis_flags.push(1);
290                    fixed_axes.push(axis.normalize());
291                }
292                _ => {
293                    fixed_axis_flags.push(0);
294                    fixed_axes.push(Vec3A::X);
295                }
296            }
297        }
298
299        let eval_order = build_eval_order(&parent_indices, &transform_orders)?;
300        let eval_order_positions = build_eval_order_positions(&eval_order, bone_count);
301        let ik_solvers = build_ik_solvers(ik_solvers, bone_count)?;
302        let (append_transforms, append_transform_indices) =
303            build_append_transforms(append_transforms, bone_count)?;
304        validate_morph_init(&morph, bone_count)?;
305
306        Ok(Self {
307            parent_indices: parent_indices.into_boxed_slice(),
308            rest_positions: rest_positions.into_boxed_slice(),
309            inverse_bind_matrices: inverse_bind_matrices.into_boxed_slice(),
310            transform_orders: transform_orders.into_boxed_slice(),
311            fixed_axis_flags: fixed_axis_flags.into_boxed_slice(),
312            fixed_axes: fixed_axes.into_boxed_slice(),
313            eval_order,
314            eval_order_positions,
315            ik_solvers,
316            append_transforms,
317            append_transform_indices,
318            morph_count: morph.morph_count,
319            vertex_morph_offsets: morph.vertex_offsets.into_boxed_slice(),
320            vertex_morph_spans: morph.vertex_spans.into_boxed_slice(),
321            bone_morph_offsets: morph.bone_offsets.into_boxed_slice(),
322            bone_morph_spans: morph.bone_spans.into_boxed_slice(),
323            group_morph_offsets: morph.group_offsets.into_boxed_slice(),
324            group_morph_spans: morph.group_spans.into_boxed_slice(),
325        })
326    }
327
328    #[inline]
329    pub fn bone_count(&self) -> usize {
330        self.parent_indices.len()
331    }
332
333    #[inline]
334    pub fn parent_index(&self, bone: BoneIndex) -> Option<BoneIndex> {
335        let parent = self.parent_indices[bone.as_usize()];
336        if parent < 0 {
337            None
338        } else {
339            Some(BoneIndex(parent as u32))
340        }
341    }
342
343    #[inline]
344    pub fn rest_position(&self, bone: BoneIndex) -> Vec3A {
345        self.rest_positions[bone.as_usize()]
346    }
347
348    #[inline]
349    pub fn inverse_bind_matrix(&self, bone: BoneIndex) -> Mat4 {
350        self.inverse_bind_matrices[bone.as_usize()]
351    }
352
353    #[inline]
354    pub fn transform_order(&self, bone: BoneIndex) -> i32 {
355        self.transform_orders[bone.as_usize()]
356    }
357
358    #[inline]
359    pub fn fixed_axis(&self, bone: BoneIndex) -> Option<Vec3A> {
360        if self.fixed_axis_flags[bone.as_usize()] != 0 {
361            Some(self.fixed_axes[bone.as_usize()])
362        } else {
363            None
364        }
365    }
366
367    #[inline]
368    pub fn fixed_axis_count(&self) -> usize {
369        self.fixed_axis_flags
370            .iter()
371            .filter(|&&flag| flag != 0)
372            .count()
373    }
374
375    #[inline]
376    pub fn eval_order(&self) -> &[BoneIndex] {
377        &self.eval_order
378    }
379
380    #[inline]
381    pub(crate) fn eval_order_position(&self, bone: BoneIndex) -> usize {
382        self.eval_order_positions[bone.as_usize()]
383    }
384
385    #[inline]
386    pub fn ik_count(&self) -> usize {
387        self.ik_solvers.len()
388    }
389
390    #[inline]
391    pub fn ik_solvers(&self) -> &[IkSolver] {
392        &self.ik_solvers
393    }
394
395    #[inline]
396    pub fn append_transform_index(&self, bone: BoneIndex) -> Option<usize> {
397        let index = self.append_transform_indices[bone.as_usize()];
398        if index < 0 {
399            None
400        } else {
401            Some(index as usize)
402        }
403    }
404
405    #[inline]
406    pub fn append_transform(&self, append_index: usize) -> &AppendTransform {
407        &self.append_transforms[append_index]
408    }
409
410    #[inline]
411    pub fn append_transforms(&self) -> &[AppendTransform] {
412        &self.append_transforms
413    }
414
415    #[inline]
416    pub fn morph_count(&self) -> u32 {
417        self.morph_count
418    }
419
420    #[inline]
421    pub fn vertex_morph_offsets(&self) -> &[VertexMorphOffset] {
422        &self.vertex_morph_offsets
423    }
424
425    #[inline]
426    pub fn vertex_morph_spans(&self) -> &[MorphOffsetSpan] {
427        &self.vertex_morph_spans
428    }
429
430    #[inline]
431    pub fn bone_morph_offsets(&self) -> &[BoneMorphOffset] {
432        &self.bone_morph_offsets
433    }
434
435    #[inline]
436    pub fn bone_morph_spans(&self) -> &[MorphOffsetSpan] {
437        &self.bone_morph_spans
438    }
439
440    #[inline]
441    pub fn group_morph_offsets(&self) -> &[GroupMorphOffset] {
442        &self.group_morph_offsets
443    }
444
445    #[inline]
446    pub fn group_morph_spans(&self) -> &[MorphOffsetSpan] {
447        &self.group_morph_spans
448    }
449}
450
451#[derive(Clone, Debug, PartialEq)]
452pub struct IkSolver {
453    pub ik_bone: BoneIndex,
454    pub target_bone: BoneIndex,
455    pub links: Box<[IkLink]>,
456    pub iteration_count: u32,
457    pub limit_angle: f32,
458}
459
460#[derive(Clone, Debug, PartialEq)]
461pub struct IkLink {
462    pub bone: BoneIndex,
463    pub angle_limit: Option<IkAngleLimit>,
464}
465
466#[derive(Clone, Copy, Debug, PartialEq)]
467pub struct AppendTransform {
468    pub target_bone: BoneIndex,
469    pub source_bone: BoneIndex,
470    pub ratio: f32,
471    pub affect_rotation: bool,
472    pub affect_translation: bool,
473    pub local: bool,
474}
475
476type AppendTransformBuildOutput = (Box<[AppendTransform]>, Box<[i32]>);
477
478fn build_ik_solvers(
479    ik_solvers: Vec<IkSolverInit>,
480    bone_count: usize,
481) -> Result<Box<[IkSolver]>, ModelBuildError> {
482    let mut solvers = Vec::with_capacity(ik_solvers.len());
483
484    for (solver_index, solver) in ik_solvers.into_iter().enumerate() {
485        validate_ik_bone(solver_index, "ik", solver.ik_bone, bone_count)?;
486        validate_ik_bone(solver_index, "target", solver.target_bone, bone_count)?;
487
488        let mut links = Vec::with_capacity(solver.links.len());
489        for link in solver.links {
490            validate_ik_bone(solver_index, "link", link.bone, bone_count)?;
491            links.push(IkLink {
492                bone: link.bone,
493                angle_limit: link.angle_limit,
494            });
495        }
496
497        solvers.push(IkSolver {
498            ik_bone: solver.ik_bone,
499            target_bone: solver.target_bone,
500            links: links.into_boxed_slice(),
501            iteration_count: solver.iteration_count,
502            limit_angle: solver.limit_angle,
503        });
504    }
505
506    Ok(solvers.into_boxed_slice())
507}
508
509fn validate_ik_bone(
510    solver: usize,
511    role: &'static str,
512    bone: BoneIndex,
513    bone_count: usize,
514) -> Result<(), ModelBuildError> {
515    if bone.as_usize() < bone_count {
516        Ok(())
517    } else {
518        Err(ModelBuildError::InvalidIkBone {
519            solver,
520            role,
521            bone: bone.0,
522        })
523    }
524}
525
526fn build_append_transforms(
527    append_transforms: Vec<AppendTransformInit>,
528    bone_count: usize,
529) -> Result<AppendTransformBuildOutput, ModelBuildError> {
530    let mut transforms = Vec::with_capacity(append_transforms.len());
531    let mut indices = vec![-1; bone_count];
532
533    for (append_index, append) in append_transforms.into_iter().enumerate() {
534        validate_append_bone(append_index, "target", append.target_bone, bone_count)?;
535        validate_append_bone(append_index, "source", append.source_bone, bone_count)?;
536
537        let target = append.target_bone.as_usize();
538        if indices[target] >= 0 {
539            return Err(ModelBuildError::DuplicateAppendTransform {
540                bone: append.target_bone.0,
541            });
542        }
543        indices[target] = append_index as i32;
544        transforms.push(AppendTransform {
545            target_bone: append.target_bone,
546            source_bone: append.source_bone,
547            ratio: append.ratio,
548            affect_rotation: append.affect_rotation,
549            affect_translation: append.affect_translation,
550            local: append.local,
551        });
552    }
553
554    Ok((transforms.into_boxed_slice(), indices.into_boxed_slice()))
555}
556
557fn validate_append_bone(
558    append: usize,
559    role: &'static str,
560    bone: BoneIndex,
561    bone_count: usize,
562) -> Result<(), ModelBuildError> {
563    if bone.as_usize() < bone_count {
564        Ok(())
565    } else {
566        Err(ModelBuildError::InvalidAppendBone {
567            append,
568            role,
569            bone: bone.0,
570        })
571    }
572}
573
574fn build_eval_order(
575    parent_indices: &[i32],
576    transform_orders: &[i32],
577) -> Result<Box<[BoneIndex]>, ModelBuildError> {
578    let mut state = vec![VisitState::Unvisited; parent_indices.len()];
579    let mut order = Vec::with_capacity(parent_indices.len());
580    let mut start_order = Vec::with_capacity(parent_indices.len());
581    for bone in 0..parent_indices.len() {
582        start_order.push(bone);
583    }
584    start_order.sort_by_key(|bone| (transform_orders[*bone], *bone));
585
586    for bone in start_order {
587        visit_bone(bone, parent_indices, &mut state, &mut order)?;
588    }
589
590    Ok(order.into_boxed_slice())
591}
592
593fn build_eval_order_positions(eval_order: &[BoneIndex], bone_count: usize) -> Box<[usize]> {
594    let mut positions = vec![0; bone_count];
595    for (position, bone) in eval_order.iter().enumerate() {
596        positions[bone.as_usize()] = position;
597    }
598    positions.into_boxed_slice()
599}
600
601fn visit_bone(
602    bone: usize,
603    parent_indices: &[i32],
604    state: &mut [VisitState],
605    order: &mut Vec<BoneIndex>,
606) -> Result<(), ModelBuildError> {
607    match state[bone] {
608        VisitState::Visited => return Ok(()),
609        VisitState::Visiting => return Err(ModelBuildError::ParentCycle { bone }),
610        VisitState::Unvisited => {}
611    }
612
613    state[bone] = VisitState::Visiting;
614
615    let parent = parent_indices[bone];
616    if parent >= 0 {
617        visit_bone(parent as usize, parent_indices, state, order)?;
618    }
619
620    state[bone] = VisitState::Visited;
621    order.push(BoneIndex(bone as u32));
622    Ok(())
623}
624
625#[derive(Clone, Copy, Debug, PartialEq, Eq)]
626enum VisitState {
627    Unvisited,
628    Visiting,
629    Visited,
630}
631
632fn validate_morph_init(morph: &MorphInit, bone_count: usize) -> Result<(), ModelBuildError> {
633    let morph_count = morph.morph_count as usize;
634    if !morph.vertex_spans.is_empty() || !morph.vertex_offsets.is_empty() {
635        validate_morph_spans(
636            "vertex",
637            &morph.vertex_spans,
638            morph_count,
639            morph.vertex_offsets.len(),
640        )?;
641    }
642    validate_morph_spans(
643        "bone",
644        &morph.bone_spans,
645        morph_count,
646        morph.bone_offsets.len(),
647    )?;
648    validate_morph_spans(
649        "group",
650        &morph.group_spans,
651        morph_count,
652        morph.group_offsets.len(),
653    )?;
654
655    for (offset_index, offset) in morph.bone_offsets.iter().enumerate() {
656        if offset.target_bone.as_usize() >= bone_count {
657            return Err(ModelBuildError::InvalidBoneMorphBone {
658                offset: offset_index,
659                bone: offset.target_bone.0,
660            });
661        }
662    }
663
664    for (morph_index, span) in morph.group_spans.iter().enumerate() {
665        for offset_index in span.start..span.start + span.count {
666            let child = morph.group_offsets[offset_index as usize].child_morph;
667            if child.as_usize() >= morph_count {
668                return Err(ModelBuildError::InvalidGroupMorphChild {
669                    morph: morph_index,
670                    child: child.0,
671                });
672            }
673        }
674    }
675    validate_group_morph_cycles(morph)?;
676
677    Ok(())
678}
679
680fn validate_group_morph_cycles(morph: &MorphInit) -> Result<(), ModelBuildError> {
681    let mut state = vec![VisitState::Unvisited; morph.morph_count as usize];
682    for morph_index in 0..morph.morph_count as usize {
683        visit_group_morph(morph_index, morph, &mut state)?;
684    }
685    Ok(())
686}
687
688fn visit_group_morph(
689    morph_index: usize,
690    morph: &MorphInit,
691    state: &mut [VisitState],
692) -> Result<(), ModelBuildError> {
693    match state[morph_index] {
694        VisitState::Visited => return Ok(()),
695        VisitState::Visiting => {
696            return Err(ModelBuildError::GroupMorphCycle { morph: morph_index });
697        }
698        VisitState::Unvisited => {}
699    }
700
701    state[morph_index] = VisitState::Visiting;
702    let span = morph.group_spans[morph_index];
703    for offset_index in span.start..span.start + span.count {
704        let child = morph.group_offsets[offset_index as usize]
705            .child_morph
706            .as_usize();
707        if morph.group_spans[child].count > 0 {
708            visit_group_morph(child, morph, state)?;
709        }
710    }
711    state[morph_index] = VisitState::Visited;
712    Ok(())
713}
714
715fn validate_morph_spans(
716    kind: &'static str,
717    spans: &[MorphOffsetSpan],
718    morph_count: usize,
719    offset_count: usize,
720) -> Result<(), ModelBuildError> {
721    if spans.len() != morph_count {
722        return Err(ModelBuildError::InvalidMorphSpanCount {
723            actual: spans.len(),
724            expected: morph_count,
725        });
726    }
727
728    for (morph_index, span) in spans.iter().enumerate() {
729        let start = span.start as usize;
730        let count = span.count as usize;
731        if start
732            .checked_add(count)
733            .is_none_or(|end| end > offset_count)
734        {
735            return Err(ModelBuildError::InvalidMorphSpan {
736                morph: morph_index,
737                kind,
738            });
739        }
740    }
741
742    Ok(())
743}
744
745#[cfg(test)]
746mod tests {
747    use super::*;
748
749    #[test]
750    fn rejects_invalid_parent() {
751        let error =
752            ModelArena::new(vec![BoneInit::new(Some(BoneIndex(10)), Vec3A::ZERO)]).unwrap_err();
753
754        assert_eq!(
755            error,
756            ModelBuildError::InvalidParent {
757                bone: 0,
758                parent: 10
759            }
760        );
761    }
762
763    #[test]
764    fn parent_is_ordered_before_child_even_if_input_order_is_not_transform_order() {
765        let mut root = BoneInit::new(None, Vec3A::ZERO);
766        root.transform_order = 10;
767        let child = BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO);
768
769        let model = ModelArena::new(vec![root, child]).unwrap();
770
771        assert_eq!(model.eval_order(), &[BoneIndex(0), BoneIndex(1)]);
772    }
773
774    #[test]
775    fn stores_ik_solver_descriptors() {
776        let solver = IkSolverInit {
777            ik_bone: BoneIndex(2),
778            target_bone: BoneIndex(1),
779            links: vec![
780                IkLinkInit::new(BoneIndex(0))
781                    .with_angle_limit(IkAngleLimit::new(Vec3A::splat(-1.0), Vec3A::splat(1.0))),
782            ],
783            iteration_count: 4,
784            limit_angle: 0.5,
785        };
786
787        let model = ModelArena::new_with_ik(
788            vec![
789                BoneInit::new(None, Vec3A::ZERO),
790                BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO),
791                BoneInit::new(Some(BoneIndex(1)), Vec3A::ZERO),
792            ],
793            vec![solver],
794        )
795        .unwrap();
796
797        assert_eq!(model.ik_count(), 1);
798        assert_eq!(model.ik_solvers()[0].ik_bone, BoneIndex(2));
799        assert_eq!(model.ik_solvers()[0].target_bone, BoneIndex(1));
800        assert_eq!(model.ik_solvers()[0].links[0].bone, BoneIndex(0));
801        assert_eq!(model.ik_solvers()[0].iteration_count, 4);
802        assert_eq!(model.ik_solvers()[0].limit_angle, 0.5);
803    }
804
805    #[test]
806    fn stores_normalized_fixed_axis_descriptors() {
807        let model = ModelArena::new(vec![
808            BoneInit::new(None, Vec3A::ZERO).with_fixed_axis(Vec3A::new(0.0, 2.0, 0.0)),
809            BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO),
810        ])
811        .unwrap();
812
813        assert_eq!(model.fixed_axis(BoneIndex(0)), Some(Vec3A::Y));
814        assert_eq!(model.fixed_axis(BoneIndex(1)), None);
815    }
816
817    #[test]
818    fn rejects_invalid_ik_link_bone() {
819        let error = ModelArena::new_with_ik(
820            vec![BoneInit::new(None, Vec3A::ZERO)],
821            vec![IkSolverInit::new(
822                BoneIndex(0),
823                BoneIndex(0),
824                vec![IkLinkInit::new(BoneIndex(10))],
825            )],
826        )
827        .unwrap_err();
828
829        assert_eq!(
830            error,
831            ModelBuildError::InvalidIkBone {
832                solver: 0,
833                role: "link",
834                bone: 10,
835            }
836        );
837    }
838
839    #[test]
840    fn stores_append_transform_descriptors() {
841        let model = ModelArena::new_full(
842            vec![
843                BoneInit::new(None, Vec3A::ZERO),
844                BoneInit::new(None, Vec3A::ZERO),
845            ],
846            Vec::new(),
847            vec![
848                AppendTransformInit::new(BoneIndex(1), BoneIndex(0), 0.5)
849                    .with_rotation()
850                    .with_translation(),
851            ],
852        )
853        .unwrap();
854
855        let append_index = model.append_transform_index(BoneIndex(1)).unwrap();
856        let append = model.append_transform(append_index);
857        assert_eq!(append.source_bone, BoneIndex(0));
858        assert_eq!(append.ratio, 0.5);
859        assert!(append.affect_rotation);
860        assert!(append.affect_translation);
861    }
862
863    #[test]
864    fn rejects_duplicate_append_transform_target() {
865        let error = ModelArena::new_full(
866            vec![
867                BoneInit::new(None, Vec3A::ZERO),
868                BoneInit::new(None, Vec3A::ZERO),
869            ],
870            Vec::new(),
871            vec![
872                AppendTransformInit::new(BoneIndex(1), BoneIndex(0), 1.0),
873                AppendTransformInit::new(BoneIndex(1), BoneIndex(0), 1.0),
874            ],
875        )
876        .unwrap_err();
877
878        assert_eq!(error, ModelBuildError::DuplicateAppendTransform { bone: 1 });
879    }
880
881    #[test]
882    fn rejects_invalid_bone_morph_target_bone() {
883        let error = ModelArena::new_with_morphs(
884            vec![BoneInit::new(None, Vec3A::ZERO)],
885            Vec::new(),
886            Vec::new(),
887            MorphInit {
888                morph_count: 1,
889                bone_offsets: vec![BoneMorphOffset {
890                    target_bone: BoneIndex(10),
891                    position_offset: Vec3A::ZERO,
892                    rotation_offset: Quat::IDENTITY,
893                }],
894                bone_spans: vec![MorphOffsetSpan { start: 0, count: 1 }],
895                group_offsets: Vec::new(),
896                group_spans: vec![MorphOffsetSpan::default()],
897                ..MorphInit::default()
898            },
899        )
900        .unwrap_err();
901
902        assert_eq!(
903            error,
904            ModelBuildError::InvalidBoneMorphBone {
905                offset: 0,
906                bone: 10
907            }
908        );
909    }
910
911    #[test]
912    fn accepts_group_morph_child_that_is_later() {
913        let model = ModelArena::new_with_morphs(
914            vec![BoneInit::new(None, Vec3A::ZERO)],
915            Vec::new(),
916            Vec::new(),
917            MorphInit {
918                morph_count: 2,
919                bone_offsets: Vec::new(),
920                bone_spans: vec![MorphOffsetSpan::default(), MorphOffsetSpan::default()],
921                group_offsets: vec![GroupMorphOffset {
922                    child_morph: MorphIndex(1),
923                    ratio: 1.0,
924                }],
925                group_spans: vec![
926                    MorphOffsetSpan { start: 0, count: 1 },
927                    MorphOffsetSpan::default(),
928                ],
929                ..MorphInit::default()
930            },
931        )
932        .unwrap();
933
934        assert_eq!(model.morph_count(), 2);
935    }
936
937    #[test]
938    fn rejects_group_morph_child_out_of_range() {
939        let error = ModelArena::new_with_morphs(
940            vec![BoneInit::new(None, Vec3A::ZERO)],
941            Vec::new(),
942            Vec::new(),
943            MorphInit {
944                morph_count: 2,
945                bone_offsets: Vec::new(),
946                bone_spans: vec![MorphOffsetSpan::default(), MorphOffsetSpan::default()],
947                group_offsets: vec![GroupMorphOffset {
948                    child_morph: MorphIndex(2),
949                    ratio: 1.0,
950                }],
951                group_spans: vec![
952                    MorphOffsetSpan { start: 0, count: 1 },
953                    MorphOffsetSpan::default(),
954                ],
955                ..MorphInit::default()
956            },
957        )
958        .unwrap_err();
959
960        assert_eq!(
961            error,
962            ModelBuildError::InvalidGroupMorphChild { morph: 0, child: 2 }
963        );
964    }
965
966    #[test]
967    fn rejects_group_morph_cycle() {
968        let error = ModelArena::new_with_morphs(
969            vec![BoneInit::new(None, Vec3A::ZERO)],
970            Vec::new(),
971            Vec::new(),
972            MorphInit {
973                morph_count: 2,
974                bone_offsets: Vec::new(),
975                bone_spans: vec![MorphOffsetSpan::default(), MorphOffsetSpan::default()],
976                group_offsets: vec![
977                    GroupMorphOffset {
978                        child_morph: MorphIndex(1),
979                        ratio: 1.0,
980                    },
981                    GroupMorphOffset {
982                        child_morph: MorphIndex(0),
983                        ratio: 1.0,
984                    },
985                ],
986                group_spans: vec![
987                    MorphOffsetSpan { start: 0, count: 1 },
988                    MorphOffsetSpan { start: 1, count: 1 },
989                ],
990                ..MorphInit::default()
991            },
992        )
993        .unwrap_err();
994
995        assert_eq!(error, ModelBuildError::GroupMorphCycle { morph: 0 });
996    }
997
998    #[test]
999    fn stores_vertex_morph_offsets() {
1000        let model = ModelArena::new_with_morphs(
1001            vec![BoneInit::new(None, Vec3A::ZERO)],
1002            Vec::new(),
1003            Vec::new(),
1004            MorphInit {
1005                morph_count: 1,
1006                vertex_offsets: vec![VertexMorphOffset {
1007                    vertex_index: 7,
1008                    position_offset: Vec3A::new(1.0, 2.0, 3.0),
1009                }],
1010                vertex_spans: vec![MorphOffsetSpan { start: 0, count: 1 }],
1011                bone_spans: vec![MorphOffsetSpan::default()],
1012                group_spans: vec![MorphOffsetSpan::default()],
1013                ..MorphInit::default()
1014            },
1015        )
1016        .unwrap();
1017
1018        assert_eq!(
1019            model.vertex_morph_offsets(),
1020            &[VertexMorphOffset {
1021                vertex_index: 7,
1022                position_offset: Vec3A::new(1.0, 2.0, 3.0),
1023            }]
1024        );
1025        assert_eq!(
1026            model.vertex_morph_spans(),
1027            &[MorphOffsetSpan { start: 0, count: 1 }]
1028        );
1029    }
1030}