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