1use glam::{Mat3, Mat4, Quat, Vec3, 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
53pub fn build_morph_init_from_offsets(
57 morph_count: u32,
58 bone_offsets: Vec<(MorphIndex, BoneMorphOffset)>,
59 group_offsets: Vec<(MorphIndex, GroupMorphOffset)>,
60) -> Result<MorphInit, ModelBuildError> {
61 let bone_offsets = index_morph_offsets(bone_offsets)?;
62 let group_offsets = index_morph_offsets(group_offsets)?;
63 build_morph_init_from_indexed_offsets(morph_count, bone_offsets, group_offsets).map_err(
64 |error| match error {
65 ModelBuildError::GroupMorphCycleAt { morph, .. } => {
66 ModelBuildError::GroupMorphCycle { morph }
67 }
68 other => other,
69 },
70 )
71}
72
73pub(crate) fn build_morph_init_from_indexed_offsets(
74 morph_count: u32,
75 bone_offsets: Vec<(usize, MorphIndex, BoneMorphOffset)>,
76 group_offsets: Vec<(usize, MorphIndex, GroupMorphOffset)>,
77) -> Result<MorphInit, ModelBuildError> {
78 let count = morph_count as usize;
79 if count == 0 && (!bone_offsets.is_empty() || !group_offsets.is_empty()) {
80 return Err(ModelBuildError::MorphCountZeroWithData);
81 }
82
83 let mut bone_counts = try_zeroed_vec(count)?;
84 for (_, index, _) in &bone_offsets {
85 let count = bone_counts
86 .get_mut(index.as_usize())
87 .ok_or(ModelBuildError::InvalidBoneMorphMorph { offset: index.0 })?;
88 *count = count
89 .checked_add(1)
90 .ok_or(ModelBuildError::MorphStorageAllocation)?;
91 }
92 let mut bone_buckets = try_empty_buckets(count)?;
93 reserve_bucket_storage(&mut bone_buckets, &bone_counts)?;
94 for (_, index, offset) in bone_offsets {
95 let bucket = bone_buckets
96 .get_mut(index.as_usize())
97 .ok_or(ModelBuildError::InvalidBoneMorphMorph { offset: index.0 })?;
98 bucket.push(offset);
99 }
100 let mut group_counts = try_zeroed_vec(count)?;
101 for (_, index, offset) in &group_offsets {
102 if offset.child_morph.as_usize() >= count {
103 return Err(ModelBuildError::InvalidGroupMorphChild {
104 morph: index.as_usize(),
105 child: offset.child_morph.0,
106 });
107 }
108 let count = group_counts
109 .get_mut(index.as_usize())
110 .ok_or(ModelBuildError::InvalidGroupMorph { morph: index.0 })?;
111 *count = count
112 .checked_add(1)
113 .ok_or(ModelBuildError::MorphStorageAllocation)?;
114 }
115 let mut group_buckets = try_empty_buckets(count)?;
116 let mut group_sources = try_empty_buckets(count)?;
117 reserve_bucket_storage(&mut group_buckets, &group_counts)?;
118 reserve_bucket_storage(&mut group_sources, &group_counts)?;
119 for (source, index, offset) in group_offsets {
120 let bucket = group_buckets
121 .get_mut(index.as_usize())
122 .ok_or(ModelBuildError::InvalidGroupMorph { morph: index.0 })?;
123 bucket.push(offset);
124 group_sources[index.as_usize()].push(source);
125 }
126
127 let mut bone_flat = try_vec_with_capacity(bone_counts.iter().sum())?;
128 let mut bone_spans = try_vec_with_capacity(count)?;
129 for bucket in bone_buckets {
130 let start =
131 u32::try_from(bone_flat.len()).map_err(|_| ModelBuildError::MorphStorageOverflow)?;
132 bone_flat.extend(bucket);
133 let count = u32::try_from(bone_flat.len() - start as usize)
134 .map_err(|_| ModelBuildError::MorphStorageOverflow)?;
135 bone_spans.push(MorphOffsetSpan { start, count });
136 }
137
138 let mut group_flat = try_vec_with_capacity(group_counts.iter().sum())?;
139 let mut group_source_flat = try_vec_with_capacity(group_counts.iter().sum())?;
140 let mut group_spans = try_vec_with_capacity(count)?;
141 for (bucket, sources) in group_buckets.into_iter().zip(group_sources) {
142 let start =
143 u32::try_from(group_flat.len()).map_err(|_| ModelBuildError::MorphStorageOverflow)?;
144 group_flat.extend(bucket);
145 group_source_flat.extend(sources);
146 let count = u32::try_from(group_flat.len() - start as usize)
147 .map_err(|_| ModelBuildError::MorphStorageOverflow)?;
148 group_spans.push(MorphOffsetSpan { start, count });
149 }
150 let morph = MorphInit {
151 morph_count,
152 bone_offsets: bone_flat,
153 bone_spans,
154 group_offsets: group_flat,
155 group_spans,
156 ..MorphInit::default()
157 };
158 validate_group_morph_cycles_with_sources(&morph, &group_source_flat)?;
159 Ok(morph)
160}
161
162fn index_morph_offsets<T>(
163 offsets: Vec<(MorphIndex, T)>,
164) -> Result<Vec<(usize, MorphIndex, T)>, ModelBuildError> {
165 let mut indexed = try_vec_with_capacity(offsets.len())?;
166 indexed.extend(
167 offsets
168 .into_iter()
169 .enumerate()
170 .map(|(source, (morph, offset))| (source, morph, offset)),
171 );
172 Ok(indexed)
173}
174
175fn try_vec_with_capacity<T>(capacity: usize) -> Result<Vec<T>, ModelBuildError> {
176 let mut values = Vec::new();
177 values
178 .try_reserve_exact(capacity)
179 .map_err(|_| ModelBuildError::MorphStorageAllocation)?;
180 Ok(values)
181}
182
183fn try_zeroed_vec(length: usize) -> Result<Vec<usize>, ModelBuildError> {
184 let mut values = try_vec_with_capacity(length)?;
185 values.resize(length, 0);
186 Ok(values)
187}
188
189fn try_empty_buckets<T>(length: usize) -> Result<Vec<Vec<T>>, ModelBuildError> {
190 let mut buckets = try_vec_with_capacity(length)?;
191 buckets.resize_with(length, Vec::new);
192 Ok(buckets)
193}
194
195fn reserve_bucket_storage<T>(
196 buckets: &mut [Vec<T>],
197 counts: &[usize],
198) -> Result<(), ModelBuildError> {
199 for (bucket, &count) in buckets.iter_mut().zip(counts) {
200 bucket
201 .try_reserve_exact(count)
202 .map_err(|_| ModelBuildError::MorphStorageAllocation)?;
203 }
204 Ok(())
205}
206
207impl BoneIndex {
208 #[inline]
209 pub fn as_usize(self) -> usize {
210 self.0 as usize
211 }
212}
213
214#[derive(Clone, Copy, Debug, PartialEq)]
220pub struct LocalAxis {
221 pub x: Vec3A,
222 pub z: Vec3A,
223}
224
225impl LocalAxis {
226 pub fn new(x: Vec3A, z: Vec3A) -> Self {
227 Self { x, z }
228 }
229
230 pub fn basis_quat(self) -> Option<Quat> {
236 build_local_axis_basis_quat(self.x, self.z)
237 }
238}
239
240#[derive(Clone, Debug)]
241pub struct BoneInit {
242 pub parent: Option<BoneIndex>,
243 pub rest_position: Vec3A,
244 pub inverse_bind_matrix: Mat4,
245 pub transform_order: i32,
246 pub transform_after_physics: bool,
247 pub fixed_axis: Option<Vec3A>,
248 pub enforce_fixed_axis: bool,
253}
254
255impl BoneInit {
256 pub fn new(parent: Option<BoneIndex>, rest_position: Vec3A) -> Self {
257 Self {
258 parent,
259 rest_position,
260 inverse_bind_matrix: Mat4::IDENTITY,
261 transform_order: 0,
262 transform_after_physics: false,
263 fixed_axis: None,
264 enforce_fixed_axis: false,
265 }
266 }
267
268 pub fn with_fixed_axis(mut self, axis: Vec3A) -> Self {
269 self.fixed_axis = Some(axis);
270 self.enforce_fixed_axis = true;
271 self
272 }
273}
274
275fn build_local_axis_basis_quat(x: Vec3A, z: Vec3A) -> Option<Quat> {
280 if !x.is_finite() || !z.is_finite() {
281 return None;
282 }
283 let x_len_sq = x.length_squared();
284 if x_len_sq <= f32::EPSILON {
285 return None;
286 }
287 let x_n = x * x_len_sq.sqrt().recip();
288 let y = z.cross(x_n);
289 let y_len_sq = y.length_squared();
290 if y_len_sq <= f32::EPSILON {
291 return None;
292 }
293 let y_n = y * y_len_sq.sqrt().recip();
294 let z_n = x_n.cross(y_n);
295 let z_len_sq = z_n.length_squared();
296 if z_len_sq <= f32::EPSILON {
297 return None;
298 }
299 let z_n = z_n * z_len_sq.sqrt().recip();
300 if !x_n.is_finite() || !y_n.is_finite() || !z_n.is_finite() {
301 return None;
302 }
303 let mat = Mat3::from_cols(Vec3::from(x_n), Vec3::from(y_n), Vec3::from(z_n));
304 let quat = Quat::from_mat3(&mat);
305 if !quat.is_finite() || quat.length_squared() <= f32::EPSILON {
306 return None;
307 }
308 Some(quat.normalize())
309}
310
311#[derive(Clone, Copy, Debug, PartialEq)]
312pub struct IkAngleLimit {
313 pub min: Vec3A,
314 pub max: Vec3A,
315}
316
317impl IkAngleLimit {
318 pub fn new(min: Vec3A, max: Vec3A) -> Self {
319 Self { min, max }
320 }
321}
322
323#[derive(Clone, Debug, PartialEq)]
324pub struct IkLinkInit {
325 pub bone: BoneIndex,
326 pub angle_limit: Option<IkAngleLimit>,
327}
328
329impl IkLinkInit {
330 pub fn new(bone: BoneIndex) -> Self {
331 Self {
332 bone,
333 angle_limit: None,
334 }
335 }
336
337 pub fn with_angle_limit(mut self, angle_limit: IkAngleLimit) -> Self {
338 self.angle_limit = Some(angle_limit);
339 self
340 }
341}
342
343#[derive(Clone, Debug, PartialEq)]
344pub struct IkSolverInit {
345 pub ik_bone: BoneIndex,
346 pub target_bone: BoneIndex,
347 pub links: Vec<IkLinkInit>,
348 pub iteration_count: u32,
349 pub limit_angle: f32,
350}
351
352#[derive(Clone, Debug, PartialEq)]
353pub struct AppendTransformInit {
354 pub target_bone: BoneIndex,
355 pub source_bone: BoneIndex,
356 pub ratio: f32,
357 pub affect_rotation: bool,
358 pub affect_translation: bool,
359 pub local: bool,
360}
361
362impl AppendTransformInit {
363 pub fn new(target_bone: BoneIndex, source_bone: BoneIndex, ratio: f32) -> Self {
364 Self {
365 target_bone,
366 source_bone,
367 ratio,
368 affect_rotation: false,
369 affect_translation: false,
370 local: false,
371 }
372 }
373
374 pub fn with_rotation(mut self) -> Self {
375 self.affect_rotation = true;
376 self
377 }
378
379 pub fn with_translation(mut self) -> Self {
380 self.affect_translation = true;
381 self
382 }
383
384 pub fn with_local(mut self) -> Self {
385 self.local = true;
386 self
387 }
388}
389
390impl IkSolverInit {
391 pub fn new(ik_bone: BoneIndex, target_bone: BoneIndex, links: Vec<IkLinkInit>) -> Self {
392 Self {
393 ik_bone,
394 target_bone,
395 links,
396 iteration_count: 1,
397 limit_angle: 0.0,
398 }
399 }
400}
401
402#[derive(Debug, Error, PartialEq, Eq)]
403pub enum ModelBuildError {
404 #[error("model must contain at least one bone")]
405 EmptyModel,
406 #[error("runtime descriptor validation failed at {path}: {reason}")]
407 InvalidRuntimeDescriptor { path: String, reason: String },
408 #[error("bone {bone} references invalid parent {parent}")]
409 InvalidParent { bone: usize, parent: u32 },
410 #[error("bone hierarchy contains a cycle involving bone {bone}")]
411 ParentCycle { bone: usize },
412 #[error("ik solver {solver} references invalid {role} bone {bone}")]
413 InvalidIkBone {
414 solver: usize,
415 role: &'static str,
416 bone: u32,
417 },
418 #[error("append transform {append} references invalid {role} bone {bone}")]
419 InvalidAppendBone {
420 append: usize,
421 role: &'static str,
422 bone: u32,
423 },
424 #[error("bone {bone} has more than one append transform")]
425 DuplicateAppendTransform { bone: u32 },
426 #[error("bone morph offset {offset} references invalid target bone {bone}")]
427 InvalidBoneMorphBone { offset: usize, bone: u32 },
428 #[error("morph span list has length {actual}, expected {expected}")]
429 InvalidMorphSpanCount { actual: usize, expected: usize },
430 #[error("morph {morph} has invalid {kind} offset span")]
431 InvalidMorphSpan { morph: usize, kind: &'static str },
432 #[error("group morph {morph} references invalid child morph {child}")]
433 InvalidGroupMorphChild { morph: usize, child: u32 },
434 #[error("group morph cycle detected at morph {morph}")]
435 GroupMorphCycle { morph: usize },
436 #[error("group morph cycle detected at morph {morph} (offset {offset})")]
437 GroupMorphCycleAt { morph: usize, offset: usize },
438 #[error("morph count is zero but morph offsets are present")]
439 MorphCountZeroWithData,
440 #[error("bone morph references invalid morph {offset}")]
441 InvalidBoneMorphMorph { offset: u32 },
442 #[error("group morph references invalid morph {morph}")]
443 InvalidGroupMorph { morph: u32 },
444 #[error("morph storage exceeds u32 span capacity")]
445 MorphStorageOverflow,
446 #[error("morph storage allocation failed")]
447 MorphStorageAllocation,
448}
449
450#[derive(Debug)]
451pub struct ModelArena {
452 parent_indices: Box<[i32]>,
453 rest_positions: Box<[Vec3A]>,
454 inverse_bind_matrices: Box<[Mat4]>,
455 transform_orders: Box<[i32]>,
456 fixed_axis_flags: Box<[u8]>,
457 fixed_axis_constraint_flags: Box<[u8]>,
458 fixed_axes: Box<[Vec3A]>,
459 local_axis_flags: Box<[u8]>,
460 local_axis_x: Box<[Vec3A]>,
461 local_axis_z: Box<[Vec3A]>,
462 local_axis_basis: Box<[Quat]>,
463 transform_after_physics_flags: Box<[u8]>,
464 ik_link_flags: Box<[u8]>,
465 eval_order: Box<[BoneIndex]>,
466 eval_order_before_physics: Box<[BoneIndex]>,
467 eval_order_after_physics: Box<[BoneIndex]>,
468 eval_order_positions: Box<[usize]>,
469 ik_bone_solver_spans: Box<[MorphOffsetSpan]>,
470 ik_bone_solver_indices: Box<[u32]>,
471 ik_solvers: Box<[IkSolver]>,
472 append_transforms: Box<[AppendTransform]>,
473 append_transform_indices: Box<[i32]>,
474 morph_count: u32,
475 vertex_morph_offsets: Box<[VertexMorphOffset]>,
476 vertex_morph_spans: Box<[MorphOffsetSpan]>,
477 bone_morph_offsets: Box<[BoneMorphOffset]>,
478 bone_morph_spans: Box<[MorphOffsetSpan]>,
479 group_morph_offsets: Box<[GroupMorphOffset]>,
480 group_morph_spans: Box<[MorphOffsetSpan]>,
481}
482
483impl ModelArena {
484 pub fn new(bones: Vec<BoneInit>) -> Result<Self, ModelBuildError> {
485 Self::new_full(bones, Vec::new(), Vec::new())
486 }
487
488 pub fn new_with_ik(
489 bones: Vec<BoneInit>,
490 ik_solvers: Vec<IkSolverInit>,
491 ) -> Result<Self, ModelBuildError> {
492 Self::new_full(bones, ik_solvers, Vec::new())
493 }
494
495 pub fn new_full(
496 bones: Vec<BoneInit>,
497 ik_solvers: Vec<IkSolverInit>,
498 append_transforms: Vec<AppendTransformInit>,
499 ) -> Result<Self, ModelBuildError> {
500 Self::new_with_morphs(bones, ik_solvers, append_transforms, MorphInit::default())
501 }
502
503 pub fn new_with_morphs(
504 bones: Vec<BoneInit>,
505 ik_solvers: Vec<IkSolverInit>,
506 append_transforms: Vec<AppendTransformInit>,
507 morph: MorphInit,
508 ) -> Result<Self, ModelBuildError> {
509 if bones.is_empty() {
510 return Err(ModelBuildError::EmptyModel);
511 }
512
513 let bone_count = bones.len();
514 let mut parent_indices = Vec::with_capacity(bone_count);
515 let mut rest_positions = Vec::with_capacity(bone_count);
516 let mut inverse_bind_matrices = Vec::with_capacity(bone_count);
517 let mut transform_orders = Vec::with_capacity(bone_count);
518 let mut transform_after_physics_flags = Vec::with_capacity(bone_count);
519 let mut fixed_axis_flags = Vec::with_capacity(bone_count);
520 let mut fixed_axis_constraint_flags = Vec::with_capacity(bone_count);
521 let mut fixed_axes = Vec::with_capacity(bone_count);
522 let mut local_axis_flags = Vec::with_capacity(bone_count);
523 let mut local_axis_x = Vec::with_capacity(bone_count);
524 let mut local_axis_z = Vec::with_capacity(bone_count);
525 let mut local_axis_basis = Vec::with_capacity(bone_count);
526
527 for (bone_index, bone) in bones.iter().enumerate() {
528 let parent = match bone.parent {
529 Some(parent) if parent.as_usize() < bone_count => parent.0 as i32,
530 Some(parent) => {
531 return Err(ModelBuildError::InvalidParent {
532 bone: bone_index,
533 parent: parent.0,
534 });
535 }
536 None => -1,
537 };
538
539 parent_indices.push(parent);
540 rest_positions.push(bone.rest_position);
541 inverse_bind_matrices.push(bone.inverse_bind_matrix);
542 transform_orders.push(bone.transform_order);
543 transform_after_physics_flags.push(u8::from(bone.transform_after_physics));
544 match bone.fixed_axis {
545 Some(axis) if axis.length_squared() > f32::EPSILON && axis.is_finite() => {
546 fixed_axis_flags.push(1);
547 fixed_axis_constraint_flags.push(u8::from(bone.enforce_fixed_axis));
548 fixed_axes.push(axis.normalize());
549 }
550 _ => {
551 fixed_axis_flags.push(0);
552 fixed_axis_constraint_flags.push(0);
553 fixed_axes.push(Vec3A::X);
554 }
555 }
556 local_axis_flags.push(0);
559 local_axis_x.push(Vec3A::X);
560 local_axis_z.push(Vec3A::Z);
561 local_axis_basis.push(Quat::IDENTITY);
562 }
563
564 let eval_order = build_eval_order(&parent_indices, &transform_orders)?;
565 let eval_order_before_physics =
566 build_eval_order_for_phase(&eval_order, &transform_after_physics_flags, false);
567 let eval_order_after_physics =
568 build_eval_order_for_phase(&eval_order, &transform_after_physics_flags, true);
569 let eval_order_positions = build_eval_order_positions(&eval_order, bone_count);
570 let (ik_solvers, ik_link_flags) = build_ik_solvers(ik_solvers, bone_count)?;
571 let (ik_bone_solver_spans, ik_bone_solver_indices) =
572 build_ik_bone_solver_lookup(&ik_solvers, bone_count);
573 let (append_transforms, append_transform_indices) =
574 build_append_transforms(append_transforms, bone_count)?;
575 validate_morph_init(&morph, bone_count)?;
576
577 Ok(Self {
578 parent_indices: parent_indices.into_boxed_slice(),
579 rest_positions: rest_positions.into_boxed_slice(),
580 inverse_bind_matrices: inverse_bind_matrices.into_boxed_slice(),
581 transform_orders: transform_orders.into_boxed_slice(),
582 fixed_axis_flags: fixed_axis_flags.into_boxed_slice(),
583 fixed_axis_constraint_flags: fixed_axis_constraint_flags.into_boxed_slice(),
584 fixed_axes: fixed_axes.into_boxed_slice(),
585 local_axis_flags: local_axis_flags.into_boxed_slice(),
586 local_axis_x: local_axis_x.into_boxed_slice(),
587 local_axis_z: local_axis_z.into_boxed_slice(),
588 local_axis_basis: local_axis_basis.into_boxed_slice(),
589 transform_after_physics_flags: transform_after_physics_flags.into_boxed_slice(),
590 ik_link_flags,
591 eval_order,
592 eval_order_before_physics,
593 eval_order_after_physics,
594 eval_order_positions,
595 ik_bone_solver_spans,
596 ik_bone_solver_indices,
597 ik_solvers,
598 append_transforms,
599 append_transform_indices,
600 morph_count: morph.morph_count,
601 vertex_morph_offsets: morph.vertex_offsets.into_boxed_slice(),
602 vertex_morph_spans: morph.vertex_spans.into_boxed_slice(),
603 bone_morph_offsets: morph.bone_offsets.into_boxed_slice(),
604 bone_morph_spans: morph.bone_spans.into_boxed_slice(),
605 group_morph_offsets: morph.group_offsets.into_boxed_slice(),
606 group_morph_spans: morph.group_spans.into_boxed_slice(),
607 })
608 }
609
610 #[inline]
611 pub fn bone_count(&self) -> usize {
612 self.parent_indices.len()
613 }
614
615 #[inline]
616 pub fn parent_index(&self, bone: BoneIndex) -> Option<BoneIndex> {
617 let parent = self.parent_indices[bone.as_usize()];
618 if parent < 0 {
619 None
620 } else {
621 Some(BoneIndex(parent as u32))
622 }
623 }
624
625 #[inline]
626 pub fn rest_position(&self, bone: BoneIndex) -> Vec3A {
627 self.rest_positions[bone.as_usize()]
628 }
629
630 #[inline]
631 pub fn inverse_bind_matrix(&self, bone: BoneIndex) -> Mat4 {
632 self.inverse_bind_matrices[bone.as_usize()]
633 }
634
635 #[inline]
636 pub fn transform_order(&self, bone: BoneIndex) -> i32 {
637 self.transform_orders[bone.as_usize()]
638 }
639
640 #[inline]
641 pub fn fixed_axis(&self, bone: BoneIndex) -> Option<Vec3A> {
642 if self.fixed_axis_flags[bone.as_usize()] != 0 {
643 Some(self.fixed_axes[bone.as_usize()])
644 } else {
645 None
646 }
647 }
648
649 #[inline]
650 pub(crate) fn fixed_axis_constraint(&self, bone: BoneIndex) -> Option<Vec3A> {
651 if self.fixed_axis_constraint_flags[bone.as_usize()] != 0 {
652 Some(self.fixed_axes[bone.as_usize()])
653 } else {
654 None
655 }
656 }
657
658 #[inline]
659 pub fn fixed_axis_count(&self) -> usize {
660 self.fixed_axis_flags
661 .iter()
662 .filter(|&&flag| flag != 0)
663 .count()
664 }
665
666 pub fn with_local_axes(
670 mut self,
671 local_axes: impl IntoIterator<Item = Option<LocalAxis>>,
672 ) -> Self {
673 for (bone_index, axis) in local_axes.into_iter().enumerate() {
674 if bone_index >= self.bone_count() {
675 break;
676 }
677 match axis.and_then(|axis| {
678 let basis = axis.basis_quat()?;
679 Some((axis.x, axis.z, basis))
680 }) {
681 Some((x, z, basis)) => {
682 self.local_axis_flags[bone_index] = 1;
683 self.local_axis_x[bone_index] = x;
684 self.local_axis_z[bone_index] = z;
685 self.local_axis_basis[bone_index] = basis;
686 }
687 None => {
688 self.local_axis_flags[bone_index] = 0;
689 self.local_axis_x[bone_index] = Vec3A::X;
690 self.local_axis_z[bone_index] = Vec3A::Z;
691 self.local_axis_basis[bone_index] = Quat::IDENTITY;
692 }
693 }
694 }
695 self
696 }
697
698 #[inline]
699 pub fn local_axis(&self, bone: BoneIndex) -> Option<LocalAxis> {
700 if self.local_axis_flags[bone.as_usize()] != 0 {
701 Some(LocalAxis {
702 x: self.local_axis_x[bone.as_usize()],
703 z: self.local_axis_z[bone.as_usize()],
704 })
705 } else {
706 None
707 }
708 }
709
710 #[inline]
713 pub fn local_axis_basis(&self, bone: BoneIndex) -> Option<Quat> {
714 if self.local_axis_flags[bone.as_usize()] != 0 {
715 Some(self.local_axis_basis[bone.as_usize()])
716 } else {
717 None
718 }
719 }
720
721 #[inline]
722 pub fn local_axis_count(&self) -> usize {
723 self.local_axis_flags
724 .iter()
725 .filter(|&&flag| flag != 0)
726 .count()
727 }
728
729 #[inline]
730 pub fn transform_after_physics(&self, bone: BoneIndex) -> bool {
731 self.transform_after_physics_flags[bone.as_usize()] != 0
732 }
733
734 #[inline]
735 pub(crate) fn is_ik_link_bone(&self, bone: BoneIndex) -> bool {
736 self.ik_link_flags[bone.as_usize()] != 0
737 }
738
739 #[inline]
740 pub fn eval_order(&self) -> &[BoneIndex] {
741 &self.eval_order
742 }
743
744 #[inline]
745 pub(crate) fn eval_order_for_phase(&self, after_physics: bool) -> &[BoneIndex] {
746 if after_physics {
747 &self.eval_order_after_physics
748 } else {
749 &self.eval_order_before_physics
750 }
751 }
752
753 #[inline]
754 pub(crate) fn eval_order_position(&self, bone: BoneIndex) -> usize {
755 self.eval_order_positions[bone.as_usize()]
756 }
757
758 #[inline]
759 pub fn ik_count(&self) -> usize {
760 self.ik_solvers.len()
761 }
762
763 #[inline]
764 pub fn ik_solvers(&self) -> &[IkSolver] {
765 &self.ik_solvers
766 }
767
768 #[inline]
769 pub(crate) fn ik_solver_count_for_bone(&self, bone: BoneIndex) -> usize {
770 self.ik_bone_solver_spans[bone.as_usize()].count as usize
771 }
772
773 #[inline]
774 pub(crate) fn ik_solver_index_for_bone(&self, bone: BoneIndex, local_index: usize) -> usize {
775 let span = &self.ik_bone_solver_spans[bone.as_usize()];
776 self.ik_bone_solver_indices[span.start as usize + local_index] as usize
777 }
778
779 #[inline]
780 pub fn append_transform_index(&self, bone: BoneIndex) -> Option<usize> {
781 let index = self.append_transform_indices[bone.as_usize()];
782 if index < 0 {
783 None
784 } else {
785 Some(index as usize)
786 }
787 }
788
789 #[inline]
790 pub fn append_transform(&self, append_index: usize) -> &AppendTransform {
791 &self.append_transforms[append_index]
792 }
793
794 #[inline]
795 pub fn append_transforms(&self) -> &[AppendTransform] {
796 &self.append_transforms
797 }
798
799 #[inline]
800 pub fn morph_count(&self) -> u32 {
801 self.morph_count
802 }
803
804 #[inline]
805 pub fn vertex_morph_offsets(&self) -> &[VertexMorphOffset] {
806 &self.vertex_morph_offsets
807 }
808
809 #[inline]
810 pub fn vertex_morph_spans(&self) -> &[MorphOffsetSpan] {
811 &self.vertex_morph_spans
812 }
813
814 #[inline]
815 pub fn bone_morph_offsets(&self) -> &[BoneMorphOffset] {
816 &self.bone_morph_offsets
817 }
818
819 #[inline]
820 pub fn bone_morph_spans(&self) -> &[MorphOffsetSpan] {
821 &self.bone_morph_spans
822 }
823
824 #[inline]
825 pub fn group_morph_offsets(&self) -> &[GroupMorphOffset] {
826 &self.group_morph_offsets
827 }
828
829 #[inline]
830 pub fn group_morph_spans(&self) -> &[MorphOffsetSpan] {
831 &self.group_morph_spans
832 }
833}
834
835#[derive(Clone, Debug, PartialEq)]
836pub struct IkSolver {
837 pub ik_bone: BoneIndex,
838 pub target_bone: BoneIndex,
839 pub links: Box<[IkLink]>,
840 pub iteration_count: u32,
841 pub limit_angle: f32,
842}
843
844#[derive(Clone, Debug, PartialEq)]
845pub struct IkLink {
846 pub bone: BoneIndex,
847 pub angle_limit: Option<IkAngleLimit>,
848}
849
850#[derive(Clone, Copy, Debug, PartialEq)]
851pub struct AppendTransform {
852 pub target_bone: BoneIndex,
853 pub source_bone: BoneIndex,
854 pub ratio: f32,
855 pub affect_rotation: bool,
856 pub affect_translation: bool,
857 pub local: bool,
858}
859
860type AppendTransformBuildOutput = (Box<[AppendTransform]>, Box<[i32]>);
861type IkSolverBuildOutput = (Box<[IkSolver]>, Box<[u8]>);
862
863fn build_ik_solvers(
864 ik_solvers: Vec<IkSolverInit>,
865 bone_count: usize,
866) -> Result<IkSolverBuildOutput, ModelBuildError> {
867 let mut solvers = Vec::with_capacity(ik_solvers.len());
868 let mut ik_link_flags = vec![0; bone_count];
869
870 for (solver_index, solver) in ik_solvers.into_iter().enumerate() {
871 validate_ik_bone(solver_index, "ik", solver.ik_bone, bone_count)?;
872 validate_ik_bone(solver_index, "target", solver.target_bone, bone_count)?;
873
874 let mut links = Vec::with_capacity(solver.links.len());
875 for link in solver.links {
876 validate_ik_bone(solver_index, "link", link.bone, bone_count)?;
877 ik_link_flags[link.bone.as_usize()] = 1;
878 links.push(IkLink {
879 bone: link.bone,
880 angle_limit: link.angle_limit,
881 });
882 }
883
884 solvers.push(IkSolver {
885 ik_bone: solver.ik_bone,
886 target_bone: solver.target_bone,
887 links: links.into_boxed_slice(),
888 iteration_count: solver.iteration_count,
889 limit_angle: solver.limit_angle,
890 });
891 }
892
893 Ok((solvers.into_boxed_slice(), ik_link_flags.into_boxed_slice()))
894}
895
896type IkBoneSolverLookup = (Box<[MorphOffsetSpan]>, Box<[u32]>);
897
898fn build_ik_bone_solver_lookup(ik_solvers: &[IkSolver], bone_count: usize) -> IkBoneSolverLookup {
899 let mut counts = vec![0u32; bone_count];
900 for solver in ik_solvers {
901 counts[solver.ik_bone.as_usize()] += 1;
902 }
903
904 let mut spans = Vec::with_capacity(bone_count);
905 let mut next_offset = 0u32;
906 for count in counts {
907 spans.push(MorphOffsetSpan {
908 start: next_offset,
909 count,
910 });
911 next_offset += count;
912 }
913
914 let mut indices = vec![0u32; ik_solvers.len()];
915 let mut write_positions: Vec<u32> = spans.iter().map(|span| span.start).collect();
916 for (solver_index, solver) in ik_solvers.iter().enumerate() {
917 let bone = solver.ik_bone.as_usize();
918 let write_index = write_positions[bone] as usize;
919 indices[write_index] = solver_index as u32;
920 write_positions[bone] += 1;
921 }
922
923 (spans.into_boxed_slice(), indices.into_boxed_slice())
924}
925
926fn validate_ik_bone(
927 solver: usize,
928 role: &'static str,
929 bone: BoneIndex,
930 bone_count: usize,
931) -> Result<(), ModelBuildError> {
932 if bone.as_usize() < bone_count {
933 Ok(())
934 } else {
935 Err(ModelBuildError::InvalidIkBone {
936 solver,
937 role,
938 bone: bone.0,
939 })
940 }
941}
942
943fn build_append_transforms(
944 append_transforms: Vec<AppendTransformInit>,
945 bone_count: usize,
946) -> Result<AppendTransformBuildOutput, ModelBuildError> {
947 let mut transforms = Vec::with_capacity(append_transforms.len());
948 let mut indices = vec![-1; bone_count];
949
950 for (append_index, append) in append_transforms.into_iter().enumerate() {
951 validate_append_bone(append_index, "target", append.target_bone, bone_count)?;
952 validate_append_bone(append_index, "source", append.source_bone, bone_count)?;
953
954 let target = append.target_bone.as_usize();
955 if indices[target] >= 0 {
956 return Err(ModelBuildError::DuplicateAppendTransform {
957 bone: append.target_bone.0,
958 });
959 }
960 indices[target] = append_index as i32;
961 transforms.push(AppendTransform {
962 target_bone: append.target_bone,
963 source_bone: append.source_bone,
964 ratio: append.ratio,
965 affect_rotation: append.affect_rotation,
966 affect_translation: append.affect_translation,
967 local: append.local,
968 });
969 }
970
971 Ok((transforms.into_boxed_slice(), indices.into_boxed_slice()))
972}
973
974fn validate_append_bone(
975 append: usize,
976 role: &'static str,
977 bone: BoneIndex,
978 bone_count: usize,
979) -> Result<(), ModelBuildError> {
980 if bone.as_usize() < bone_count {
981 Ok(())
982 } else {
983 Err(ModelBuildError::InvalidAppendBone {
984 append,
985 role,
986 bone: bone.0,
987 })
988 }
989}
990
991fn build_eval_order(
992 parent_indices: &[i32],
993 transform_orders: &[i32],
994) -> Result<Box<[BoneIndex]>, ModelBuildError> {
995 let mut state = vec![VisitState::Unvisited; parent_indices.len()];
996 let mut order = Vec::with_capacity(parent_indices.len());
997 let mut start_order = Vec::with_capacity(parent_indices.len());
998 for bone in 0..parent_indices.len() {
999 start_order.push(bone);
1000 }
1001 start_order.sort_by_key(|bone| (transform_orders[*bone], *bone));
1002
1003 for bone in start_order {
1004 visit_bone(bone, parent_indices, &mut state, &mut order)?;
1005 }
1006
1007 Ok(order.into_boxed_slice())
1008}
1009
1010fn build_eval_order_for_phase(
1011 eval_order: &[BoneIndex],
1012 transform_after_physics_flags: &[u8],
1013 after_physics: bool,
1014) -> Box<[BoneIndex]> {
1015 eval_order
1016 .iter()
1017 .copied()
1018 .filter(|bone| {
1019 let bone_after_physics = transform_after_physics_flags[bone.as_usize()] != 0;
1020 bone_after_physics == after_physics
1021 })
1022 .collect()
1023}
1024
1025fn build_eval_order_positions(eval_order: &[BoneIndex], bone_count: usize) -> Box<[usize]> {
1026 let mut positions = vec![0; bone_count];
1027 for (position, bone) in eval_order.iter().enumerate() {
1028 positions[bone.as_usize()] = position;
1029 }
1030 positions.into_boxed_slice()
1031}
1032
1033fn visit_bone(
1034 bone: usize,
1035 parent_indices: &[i32],
1036 state: &mut [VisitState],
1037 order: &mut Vec<BoneIndex>,
1038) -> Result<(), ModelBuildError> {
1039 if state[bone] == VisitState::Visited {
1040 return Ok(());
1041 }
1042
1043 let mut stack = vec![(bone, false)];
1047 while let Some((current, expanded)) = stack.pop() {
1048 if expanded {
1049 state[current] = VisitState::Visited;
1050 order.push(BoneIndex(current as u32));
1051 continue;
1052 }
1053
1054 match state[current] {
1055 VisitState::Visited => continue,
1056 VisitState::Visiting => {
1057 return Err(ModelBuildError::ParentCycle { bone: current });
1058 }
1059 VisitState::Unvisited => {}
1060 }
1061
1062 state[current] = VisitState::Visiting;
1063 stack.push((current, true));
1064 let parent = parent_indices[current];
1065 if parent >= 0 {
1066 stack.push((parent as usize, false));
1067 }
1068 }
1069
1070 Ok(())
1071}
1072
1073#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1074enum VisitState {
1075 Unvisited,
1076 Visiting,
1077 Visited,
1078}
1079
1080fn validate_morph_init(morph: &MorphInit, bone_count: usize) -> Result<(), ModelBuildError> {
1081 let morph_count = morph.morph_count as usize;
1082 if !morph.vertex_spans.is_empty() || !morph.vertex_offsets.is_empty() {
1083 validate_morph_spans(
1084 "vertex",
1085 &morph.vertex_spans,
1086 morph_count,
1087 morph.vertex_offsets.len(),
1088 )?;
1089 }
1090 validate_morph_spans(
1091 "bone",
1092 &morph.bone_spans,
1093 morph_count,
1094 morph.bone_offsets.len(),
1095 )?;
1096 validate_morph_spans(
1097 "group",
1098 &morph.group_spans,
1099 morph_count,
1100 morph.group_offsets.len(),
1101 )?;
1102
1103 for (offset_index, offset) in morph.bone_offsets.iter().enumerate() {
1104 if offset.target_bone.as_usize() >= bone_count {
1105 return Err(ModelBuildError::InvalidBoneMorphBone {
1106 offset: offset_index,
1107 bone: offset.target_bone.0,
1108 });
1109 }
1110 }
1111
1112 for (morph_index, span) in morph.group_spans.iter().enumerate() {
1113 for offset_index in span.start..span.start + span.count {
1114 let child = morph.group_offsets[offset_index as usize].child_morph;
1115 if child.as_usize() >= morph_count {
1116 return Err(ModelBuildError::InvalidGroupMorphChild {
1117 morph: morph_index,
1118 child: child.0,
1119 });
1120 }
1121 }
1122 }
1123 validate_group_morph_cycles(morph)?;
1124
1125 Ok(())
1126}
1127
1128fn validate_group_morph_cycles(morph: &MorphInit) -> Result<(), ModelBuildError> {
1129 let mut sources = try_vec_with_capacity(morph.group_offsets.len())?;
1130 sources.extend(0..morph.group_offsets.len());
1131 validate_group_morph_cycles_with_sources(morph, &sources).map_err(|error| match error {
1132 ModelBuildError::GroupMorphCycleAt { morph, .. } => {
1133 ModelBuildError::GroupMorphCycle { morph }
1134 }
1135 other => other,
1136 })
1137}
1138
1139fn validate_group_morph_cycles_with_sources(
1140 morph: &MorphInit,
1141 sources: &[usize],
1142) -> Result<(), ModelBuildError> {
1143 let mut state = try_vec_with_capacity(morph.morph_count as usize)?;
1144 state.resize(morph.morph_count as usize, VisitState::Unvisited);
1145 let mut stack = try_vec_with_capacity(morph.morph_count as usize)?;
1146 for morph_index in 0..morph.morph_count as usize {
1147 visit_group_morph(morph_index, morph, &mut state, sources, &mut stack)?;
1148 }
1149 Ok(())
1150}
1151
1152fn visit_group_morph(
1153 morph_index: usize,
1154 morph: &MorphInit,
1155 state: &mut [VisitState],
1156 sources: &[usize],
1157 stack: &mut Vec<(usize, usize)>,
1158) -> Result<(), ModelBuildError> {
1159 if state[morph_index] == VisitState::Visited {
1160 return Ok(());
1161 }
1162
1163 stack.clear();
1166 stack.push((morph_index, 0usize));
1167 state[morph_index] = VisitState::Visiting;
1168 while let Some((current, next_offset)) = stack.last_mut() {
1169 let span = morph.group_spans[*current];
1170 if *next_offset >= span.count as usize {
1171 state[*current] = VisitState::Visited;
1172 stack.pop();
1173 continue;
1174 }
1175
1176 let offset_index = span.start as usize + *next_offset;
1177 *next_offset += 1;
1178 let child = morph.group_offsets[offset_index].child_morph.as_usize();
1179 if morph.group_spans[child].count == 0 {
1180 continue;
1181 }
1182 match state[child] {
1183 VisitState::Visited => {}
1184 VisitState::Visiting => {
1185 return Err(ModelBuildError::GroupMorphCycleAt {
1186 morph: child,
1187 offset: sources[offset_index],
1188 });
1189 }
1190 VisitState::Unvisited => {
1191 state[child] = VisitState::Visiting;
1192 stack.push((child, 0));
1193 }
1194 }
1195 }
1196
1197 Ok(())
1198}
1199
1200fn validate_morph_spans(
1201 kind: &'static str,
1202 spans: &[MorphOffsetSpan],
1203 morph_count: usize,
1204 offset_count: usize,
1205) -> Result<(), ModelBuildError> {
1206 if spans.len() != morph_count {
1207 return Err(ModelBuildError::InvalidMorphSpanCount {
1208 actual: spans.len(),
1209 expected: morph_count,
1210 });
1211 }
1212
1213 for (morph_index, span) in spans.iter().enumerate() {
1214 let start = span.start as usize;
1215 let count = span.count as usize;
1216 if start
1217 .checked_add(count)
1218 .is_none_or(|end| end > offset_count)
1219 {
1220 return Err(ModelBuildError::InvalidMorphSpan {
1221 morph: morph_index,
1222 kind,
1223 });
1224 }
1225 }
1226
1227 Ok(())
1228}
1229
1230#[cfg(test)]
1231mod tests {
1232 use super::*;
1233
1234 #[test]
1235 fn reports_capacity_overflow_as_morph_storage_allocation_error() {
1236 assert_eq!(
1237 try_vec_with_capacity::<u8>(usize::MAX).unwrap_err(),
1238 ModelBuildError::MorphStorageAllocation
1239 );
1240 }
1241
1242 #[test]
1243 fn rejects_invalid_parent() {
1244 let error =
1245 ModelArena::new(vec![BoneInit::new(Some(BoneIndex(10)), Vec3A::ZERO)]).unwrap_err();
1246
1247 assert_eq!(
1248 error,
1249 ModelBuildError::InvalidParent {
1250 bone: 0,
1251 parent: 10
1252 }
1253 );
1254 }
1255
1256 #[test]
1257 fn eval_order_for_phase_preserves_relative_order_within_each_phase() {
1258 let mut root = BoneInit::new(None, Vec3A::ZERO);
1259 root.transform_order = 0;
1260 let mut pre_child = BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO);
1261 pre_child.transform_order = 2;
1262 let mut after_parent = BoneInit::new(None, Vec3A::ZERO);
1263 after_parent.transform_order = 1;
1264 after_parent.transform_after_physics = true;
1265 let mut after_child = BoneInit::new(Some(BoneIndex(2)), Vec3A::ZERO);
1266 after_child.transform_order = 3;
1267 after_child.transform_after_physics = true;
1268 let mut pre_sibling = BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO);
1269 pre_sibling.transform_order = 4;
1270
1271 let model = ModelArena::new(vec![
1272 root,
1273 pre_child,
1274 after_parent,
1275 after_child,
1276 pre_sibling,
1277 ])
1278 .unwrap();
1279
1280 let expected_before_physics: Vec<_> = model
1281 .eval_order()
1282 .iter()
1283 .copied()
1284 .filter(|bone| !model.transform_after_physics(*bone))
1285 .collect();
1286 let expected_after_physics: Vec<_> = model
1287 .eval_order()
1288 .iter()
1289 .copied()
1290 .filter(|bone| model.transform_after_physics(*bone))
1291 .collect();
1292
1293 assert_eq!(
1294 model.eval_order_for_phase(false),
1295 expected_before_physics.as_slice()
1296 );
1297 assert_eq!(
1298 model.eval_order_for_phase(true),
1299 expected_after_physics.as_slice()
1300 );
1301 assert_eq!(
1302 model.eval_order_for_phase(false),
1303 &[BoneIndex(0), BoneIndex(1), BoneIndex(4)]
1304 );
1305 assert_eq!(
1306 model.eval_order_for_phase(true),
1307 &[BoneIndex(2), BoneIndex(3)]
1308 );
1309 }
1310
1311 #[test]
1312 fn parent_is_ordered_before_child_even_if_input_order_is_not_transform_order() {
1313 let mut root = BoneInit::new(None, Vec3A::ZERO);
1314 root.transform_order = 10;
1315 let child = BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO);
1316
1317 let model = ModelArena::new(vec![root, child]).unwrap();
1318
1319 assert_eq!(model.eval_order(), &[BoneIndex(0), BoneIndex(1)]);
1320 }
1321
1322 #[test]
1323 fn stores_ik_solver_descriptors() {
1324 let solver = IkSolverInit {
1325 ik_bone: BoneIndex(2),
1326 target_bone: BoneIndex(1),
1327 links: vec![
1328 IkLinkInit::new(BoneIndex(0))
1329 .with_angle_limit(IkAngleLimit::new(Vec3A::splat(-1.0), Vec3A::splat(1.0))),
1330 ],
1331 iteration_count: 4,
1332 limit_angle: 0.5,
1333 };
1334
1335 let model = ModelArena::new_with_ik(
1336 vec![
1337 BoneInit::new(None, Vec3A::ZERO),
1338 BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO),
1339 BoneInit::new(Some(BoneIndex(1)), Vec3A::ZERO),
1340 ],
1341 vec![solver],
1342 )
1343 .unwrap();
1344
1345 assert_eq!(model.ik_count(), 1);
1346 assert_eq!(model.ik_solvers()[0].ik_bone, BoneIndex(2));
1347 assert_eq!(model.ik_solvers()[0].target_bone, BoneIndex(1));
1348 assert_eq!(model.ik_solvers()[0].links[0].bone, BoneIndex(0));
1349 assert_eq!(model.ik_solvers()[0].iteration_count, 4);
1350 assert_eq!(model.ik_solvers()[0].limit_angle, 0.5);
1351 }
1352
1353 #[test]
1354 fn ik_bone_solver_lookup_maps_multiple_bones_and_preserves_registration_order() {
1355 let model = ModelArena::new_with_ik(
1356 vec![
1357 BoneInit::new(None, Vec3A::ZERO),
1358 BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO),
1359 BoneInit::new(Some(BoneIndex(1)), Vec3A::ZERO),
1360 BoneInit::new(None, Vec3A::ZERO),
1361 BoneInit::new(None, Vec3A::ZERO),
1362 ],
1363 vec![
1364 IkSolverInit::new(
1365 BoneIndex(2),
1366 BoneIndex(1),
1367 vec![IkLinkInit::new(BoneIndex(0))],
1368 ),
1369 IkSolverInit::new(
1370 BoneIndex(4),
1371 BoneIndex(3),
1372 vec![IkLinkInit::new(BoneIndex(0))],
1373 ),
1374 IkSolverInit::new(
1375 BoneIndex(2),
1376 BoneIndex(1),
1377 vec![IkLinkInit::new(BoneIndex(0))],
1378 ),
1379 ],
1380 )
1381 .unwrap();
1382
1383 assert_eq!(model.ik_solver_count_for_bone(BoneIndex(0)), 0);
1384 assert_eq!(model.ik_solver_count_for_bone(BoneIndex(1)), 0);
1385 assert_eq!(model.ik_solver_count_for_bone(BoneIndex(2)), 2);
1386 assert_eq!(model.ik_solver_count_for_bone(BoneIndex(3)), 0);
1387 assert_eq!(model.ik_solver_count_for_bone(BoneIndex(4)), 1);
1388 assert_eq!(model.ik_solver_index_for_bone(BoneIndex(2), 0), 0);
1389 assert_eq!(model.ik_solver_index_for_bone(BoneIndex(2), 1), 2);
1390 assert_eq!(model.ik_solver_index_for_bone(BoneIndex(4), 0), 1);
1391 }
1392
1393 #[test]
1394 fn stores_normalized_fixed_axis_descriptors() {
1395 let model = ModelArena::new(vec![
1396 BoneInit::new(None, Vec3A::ZERO).with_fixed_axis(Vec3A::new(0.0, 2.0, 0.0)),
1397 BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO),
1398 ])
1399 .unwrap();
1400
1401 assert_eq!(model.fixed_axis(BoneIndex(0)), Some(Vec3A::Y));
1402 assert_eq!(model.fixed_axis(BoneIndex(1)), None);
1403 }
1404
1405 #[test]
1406 fn stores_local_axis_descriptors_and_basis() {
1407 let model = ModelArena::new(vec![
1408 BoneInit::new(None, Vec3A::ZERO),
1409 BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO),
1410 BoneInit::new(Some(BoneIndex(0)), Vec3A::ZERO),
1411 ])
1412 .unwrap()
1413 .with_local_axes([
1414 Some(LocalAxis::new(
1415 Vec3A::new(0.0, 1.0, 0.0),
1416 Vec3A::new(0.0, 0.0, 1.0),
1417 )),
1418 Some(LocalAxis::new(Vec3A::ZERO, Vec3A::Z)),
1419 None,
1420 ]);
1421
1422 let axis = model.local_axis(BoneIndex(0)).expect("local axis retained");
1423 assert_eq!(axis.x, Vec3A::new(0.0, 1.0, 0.0));
1424 assert_eq!(axis.z, Vec3A::new(0.0, 0.0, 1.0));
1425 let basis = model
1426 .local_axis_basis(BoneIndex(0))
1427 .expect("local axis basis");
1428 let x_dir = basis * Vec3A::X;
1430 let y_dir = basis * Vec3A::Y;
1431 let z_dir = basis * Vec3A::Z;
1432 assert!((x_dir - Vec3A::Y).length() < 1.0e-5);
1433 assert!((y_dir - (-Vec3A::X)).length() < 1.0e-5);
1434 assert!((z_dir - Vec3A::Z).length() < 1.0e-5);
1435 assert!(model.local_axis(BoneIndex(1)).is_none());
1436 assert!(model.local_axis_basis(BoneIndex(1)).is_none());
1437 assert!(model.local_axis(BoneIndex(2)).is_none());
1438 assert_eq!(model.local_axis_count(), 1);
1439 }
1440
1441 #[test]
1442 fn existing_bone_init_constructors_have_no_local_axes() {
1443 let via_new = ModelArena::new(vec![BoneInit::new(None, Vec3A::ZERO)]).unwrap();
1446 assert_eq!(via_new.local_axis_count(), 0);
1447 assert!(via_new.local_axis(BoneIndex(0)).is_none());
1448
1449 let via_literal = ModelArena::new(vec![BoneInit {
1450 parent: None,
1451 rest_position: Vec3A::ZERO,
1452 inverse_bind_matrix: Mat4::IDENTITY,
1453 transform_order: 0,
1454 transform_after_physics: false,
1455 fixed_axis: None,
1456 enforce_fixed_axis: false,
1457 }])
1458 .unwrap();
1459 assert_eq!(via_literal.local_axis_count(), 0);
1460 }
1461
1462 #[test]
1463 fn rejects_invalid_ik_link_bone() {
1464 let error = ModelArena::new_with_ik(
1465 vec![BoneInit::new(None, Vec3A::ZERO)],
1466 vec![IkSolverInit::new(
1467 BoneIndex(0),
1468 BoneIndex(0),
1469 vec![IkLinkInit::new(BoneIndex(10))],
1470 )],
1471 )
1472 .unwrap_err();
1473
1474 assert_eq!(
1475 error,
1476 ModelBuildError::InvalidIkBone {
1477 solver: 0,
1478 role: "link",
1479 bone: 10,
1480 }
1481 );
1482 }
1483
1484 #[test]
1485 fn stores_append_transform_descriptors() {
1486 let model = ModelArena::new_full(
1487 vec![
1488 BoneInit::new(None, Vec3A::ZERO),
1489 BoneInit::new(None, Vec3A::ZERO),
1490 ],
1491 Vec::new(),
1492 vec![
1493 AppendTransformInit::new(BoneIndex(1), BoneIndex(0), 0.5)
1494 .with_rotation()
1495 .with_translation(),
1496 ],
1497 )
1498 .unwrap();
1499
1500 let append_index = model.append_transform_index(BoneIndex(1)).unwrap();
1501 let append = model.append_transform(append_index);
1502 assert_eq!(append.source_bone, BoneIndex(0));
1503 assert_eq!(append.ratio, 0.5);
1504 assert!(append.affect_rotation);
1505 assert!(append.affect_translation);
1506 }
1507
1508 #[test]
1509 fn rejects_duplicate_append_transform_target() {
1510 let error = ModelArena::new_full(
1511 vec![
1512 BoneInit::new(None, Vec3A::ZERO),
1513 BoneInit::new(None, Vec3A::ZERO),
1514 ],
1515 Vec::new(),
1516 vec![
1517 AppendTransformInit::new(BoneIndex(1), BoneIndex(0), 1.0),
1518 AppendTransformInit::new(BoneIndex(1), BoneIndex(0), 1.0),
1519 ],
1520 )
1521 .unwrap_err();
1522
1523 assert_eq!(error, ModelBuildError::DuplicateAppendTransform { bone: 1 });
1524 }
1525
1526 #[test]
1527 fn rejects_invalid_bone_morph_target_bone() {
1528 let error = ModelArena::new_with_morphs(
1529 vec![BoneInit::new(None, Vec3A::ZERO)],
1530 Vec::new(),
1531 Vec::new(),
1532 MorphInit {
1533 morph_count: 1,
1534 bone_offsets: vec![BoneMorphOffset {
1535 target_bone: BoneIndex(10),
1536 position_offset: Vec3A::ZERO,
1537 rotation_offset: Quat::IDENTITY,
1538 }],
1539 bone_spans: vec![MorphOffsetSpan { start: 0, count: 1 }],
1540 group_offsets: Vec::new(),
1541 group_spans: vec![MorphOffsetSpan::default()],
1542 ..MorphInit::default()
1543 },
1544 )
1545 .unwrap_err();
1546
1547 assert_eq!(
1548 error,
1549 ModelBuildError::InvalidBoneMorphBone {
1550 offset: 0,
1551 bone: 10
1552 }
1553 );
1554 }
1555
1556 #[test]
1557 fn accepts_group_morph_child_that_is_later() {
1558 let model = ModelArena::new_with_morphs(
1559 vec![BoneInit::new(None, Vec3A::ZERO)],
1560 Vec::new(),
1561 Vec::new(),
1562 MorphInit {
1563 morph_count: 2,
1564 bone_offsets: Vec::new(),
1565 bone_spans: vec![MorphOffsetSpan::default(), MorphOffsetSpan::default()],
1566 group_offsets: vec![GroupMorphOffset {
1567 child_morph: MorphIndex(1),
1568 ratio: 1.0,
1569 }],
1570 group_spans: vec![
1571 MorphOffsetSpan { start: 0, count: 1 },
1572 MorphOffsetSpan::default(),
1573 ],
1574 ..MorphInit::default()
1575 },
1576 )
1577 .unwrap();
1578
1579 assert_eq!(model.morph_count(), 2);
1580 }
1581
1582 #[test]
1583 fn rejects_group_morph_child_out_of_range() {
1584 let error = ModelArena::new_with_morphs(
1585 vec![BoneInit::new(None, Vec3A::ZERO)],
1586 Vec::new(),
1587 Vec::new(),
1588 MorphInit {
1589 morph_count: 2,
1590 bone_offsets: Vec::new(),
1591 bone_spans: vec![MorphOffsetSpan::default(), MorphOffsetSpan::default()],
1592 group_offsets: vec![GroupMorphOffset {
1593 child_morph: MorphIndex(2),
1594 ratio: 1.0,
1595 }],
1596 group_spans: vec![
1597 MorphOffsetSpan { start: 0, count: 1 },
1598 MorphOffsetSpan::default(),
1599 ],
1600 ..MorphInit::default()
1601 },
1602 )
1603 .unwrap_err();
1604
1605 assert_eq!(
1606 error,
1607 ModelBuildError::InvalidGroupMorphChild { morph: 0, child: 2 }
1608 );
1609 }
1610
1611 #[test]
1612 fn rejects_group_morph_cycle() {
1613 let error = ModelArena::new_with_morphs(
1614 vec![BoneInit::new(None, Vec3A::ZERO)],
1615 Vec::new(),
1616 Vec::new(),
1617 MorphInit {
1618 morph_count: 2,
1619 bone_offsets: Vec::new(),
1620 bone_spans: vec![MorphOffsetSpan::default(), MorphOffsetSpan::default()],
1621 group_offsets: vec![
1622 GroupMorphOffset {
1623 child_morph: MorphIndex(1),
1624 ratio: 1.0,
1625 },
1626 GroupMorphOffset {
1627 child_morph: MorphIndex(0),
1628 ratio: 1.0,
1629 },
1630 ],
1631 group_spans: vec![
1632 MorphOffsetSpan { start: 0, count: 1 },
1633 MorphOffsetSpan { start: 1, count: 1 },
1634 ],
1635 ..MorphInit::default()
1636 },
1637 )
1638 .unwrap_err();
1639
1640 assert_eq!(error, ModelBuildError::GroupMorphCycle { morph: 0 });
1641 }
1642
1643 #[test]
1644 fn stores_vertex_morph_offsets() {
1645 let model = ModelArena::new_with_morphs(
1646 vec![BoneInit::new(None, Vec3A::ZERO)],
1647 Vec::new(),
1648 Vec::new(),
1649 MorphInit {
1650 morph_count: 1,
1651 vertex_offsets: vec![VertexMorphOffset {
1652 vertex_index: 7,
1653 position_offset: Vec3A::new(1.0, 2.0, 3.0),
1654 }],
1655 vertex_spans: vec![MorphOffsetSpan { start: 0, count: 1 }],
1656 bone_spans: vec![MorphOffsetSpan::default()],
1657 group_spans: vec![MorphOffsetSpan::default()],
1658 ..MorphInit::default()
1659 },
1660 )
1661 .unwrap();
1662
1663 assert_eq!(
1664 model.vertex_morph_offsets(),
1665 &[VertexMorphOffset {
1666 vertex_index: 7,
1667 position_offset: Vec3A::new(1.0, 2.0, 3.0),
1668 }]
1669 );
1670 assert_eq!(
1671 model.vertex_morph_spans(),
1672 &[MorphOffsetSpan { start: 0, count: 1 }]
1673 );
1674 }
1675}