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}