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}