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