1use std::fmt;
2
3use crate::{
4 AppendTransformInit, BoneIndex, BoneInit, BoneMorphOffset, GroupMorphOffset, IkAngleLimit,
5 IkLinkInit, IkSolverInit, MorphIndex, MorphInit, MorphOffsetSpan,
6};
7
8pub struct FlatBoneInput<'a> {
9 pub parent_indices: &'a [i32],
10 pub rest_positions_xyz: &'a [f32],
11 pub inverse_bind_matrices: &'a [f32],
12 pub transform_orders: &'a [i32],
13}
14
15#[derive(Debug, Clone, Copy)]
16pub struct FlatIkLinkInput {
17 pub bone_index: u32,
18 pub has_angle_limit: bool,
19 pub angle_limit_min_xyz: [f32; 3],
20 pub angle_limit_max_xyz: [f32; 3],
21}
22
23#[derive(Debug, Clone, Copy)]
24pub struct FlatIkSolverInput {
25 pub ik_bone_index: u32,
26 pub target_bone_index: u32,
27 pub link_offset: usize,
28 pub link_count: usize,
29 pub iteration_count: u32,
30 pub limit_angle: f32,
31}
32
33#[derive(Debug, Clone, Copy)]
34pub struct FlatAppendTransformInput {
35 pub target_bone_index: u32,
36 pub source_bone_index: u32,
37 pub ratio: f32,
38 pub affect_rotation: bool,
39 pub affect_translation: bool,
40 pub local: bool,
41}
42
43#[derive(Debug, Clone, Copy)]
44pub struct FlatBoneMorphInput {
45 pub morph_index: u32,
46 pub target_bone_index: u32,
47 pub position_offset_xyz: [f32; 3],
48 pub rotation_offset_xyzw: [f32; 4],
49}
50
51#[derive(Debug, Clone, Copy)]
52pub struct FlatGroupMorphInput {
53 pub morph_index: u32,
54 pub child_morph_index: u32,
55 pub ratio: f32,
56}
57
58pub struct FlatMorphInput<'a> {
59 pub morph_count: u32,
60 pub bone_morphs: &'a [FlatBoneMorphInput],
61 pub group_morphs: &'a [FlatGroupMorphInput],
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum FlatModelInputError {
66 EmptyBoneSet,
67 RestPositionsLen,
68 InverseBindMatricesLen,
69 TransformOrdersLen,
70 InvalidParentIndex,
71 RangeOverflow,
72 RangeOutOfBounds,
73 MorphCountZeroWithData,
74 BoneMorphIndexOutOfRange,
75 GroupMorphIndexOutOfRange,
76}
77
78impl fmt::Display for FlatModelInputError {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 let message = match self {
81 Self::EmptyBoneSet => "model must contain at least one bone",
82 Self::RestPositionsLen => "rest_positions_xyz must contain bone_count * 3 values",
83 Self::InverseBindMatricesLen => {
84 "inverse_bind_matrices must contain bone_count * 16 values"
85 }
86 Self::TransformOrdersLen => "transform_orders must contain bone_count values",
87 Self::InvalidParentIndex => "parent index must be -1 or non-negative",
88 Self::RangeOverflow => "range overflow",
89 Self::RangeOutOfBounds => "track keyframe range is out of bounds",
90 Self::MorphCountZeroWithData => {
91 "morph_count must be non-zero when morph data is provided"
92 }
93 Self::BoneMorphIndexOutOfRange => "bone morph index is out of range",
94 Self::GroupMorphIndexOutOfRange => "group morph index is out of range",
95 };
96 f.write_str(message)
97 }
98}
99
100impl std::error::Error for FlatModelInputError {}
101
102pub fn build_bones_from_flat(
103 input: FlatBoneInput<'_>,
104) -> Result<Vec<BoneInit>, FlatModelInputError> {
105 if input.parent_indices.is_empty() {
106 return Err(FlatModelInputError::EmptyBoneSet);
107 }
108 if input.rest_positions_xyz.len() != input.parent_indices.len() * 3 {
109 return Err(FlatModelInputError::RestPositionsLen);
110 }
111 if !input.inverse_bind_matrices.is_empty()
112 && input.inverse_bind_matrices.len() != input.parent_indices.len() * 16
113 {
114 return Err(FlatModelInputError::InverseBindMatricesLen);
115 }
116 if !input.transform_orders.is_empty()
117 && input.transform_orders.len() != input.parent_indices.len()
118 {
119 return Err(FlatModelInputError::TransformOrdersLen);
120 }
121
122 let mut bones = Vec::with_capacity(input.parent_indices.len());
123 for (bone_index, parent_index) in input.parent_indices.iter().enumerate() {
124 let parent = match *parent_index {
125 -1 => None,
126 parent if parent >= 0 => Some(BoneIndex(parent as u32)),
127 _ => return Err(FlatModelInputError::InvalidParentIndex),
128 };
129 let position_offset = bone_index * 3;
130 let mut bone = BoneInit::new(
131 parent,
132 glam::Vec3A::new(
133 input.rest_positions_xyz[position_offset],
134 input.rest_positions_xyz[position_offset + 1],
135 input.rest_positions_xyz[position_offset + 2],
136 ),
137 );
138 if !input.inverse_bind_matrices.is_empty() {
139 let inverse_bind_offset = bone_index * 16;
140 let inverse_bind_matrix = input.inverse_bind_matrices
141 [inverse_bind_offset..inverse_bind_offset + 16]
142 .try_into()
143 .expect("validated inverse bind matrix slice length");
144 bone.inverse_bind_matrix = glam::Mat4::from_cols_array(inverse_bind_matrix);
145 }
146 if !input.transform_orders.is_empty() {
147 bone.transform_order = input.transform_orders[bone_index];
148 }
149 bones.push(bone);
150 }
151
152 Ok(bones)
153}
154
155pub fn build_ik_solvers_from_flat(
156 solvers: &[FlatIkSolverInput],
157 links: &[FlatIkLinkInput],
158) -> Result<Vec<IkSolverInit>, FlatModelInputError> {
159 build_ik_solvers_from_flat_iter(solvers.iter().copied(), links)
160}
161
162pub fn build_ik_solvers_from_flat_iter(
163 solvers: impl IntoIterator<Item = FlatIkSolverInput>,
164 links: &[FlatIkLinkInput],
165) -> Result<Vec<IkSolverInit>, FlatModelInputError> {
166 solvers
167 .into_iter()
168 .map(|solver| {
169 let link_end = solver
170 .link_offset
171 .checked_add(solver.link_count)
172 .ok_or(FlatModelInputError::RangeOverflow)?;
173 let solver_links = links
174 .get(solver.link_offset..link_end)
175 .ok_or(FlatModelInputError::RangeOutOfBounds)?
176 .iter()
177 .map(|link| {
178 let mut init = IkLinkInit::new(BoneIndex(link.bone_index));
179 if link.has_angle_limit {
180 init = init.with_angle_limit(IkAngleLimit::new(
181 glam::Vec3A::new(
182 link.angle_limit_min_xyz[0],
183 link.angle_limit_min_xyz[1],
184 link.angle_limit_min_xyz[2],
185 ),
186 glam::Vec3A::new(
187 link.angle_limit_max_xyz[0],
188 link.angle_limit_max_xyz[1],
189 link.angle_limit_max_xyz[2],
190 ),
191 ));
192 }
193 init
194 })
195 .collect();
196
197 Ok(IkSolverInit {
198 ik_bone: BoneIndex(solver.ik_bone_index),
199 target_bone: BoneIndex(solver.target_bone_index),
200 links: solver_links,
201 iteration_count: solver.iteration_count,
202 limit_angle: solver.limit_angle,
203 })
204 })
205 .collect()
206}
207
208pub fn build_morph_init_from_flat(
209 input: FlatMorphInput<'_>,
210) -> Result<MorphInit, FlatModelInputError> {
211 build_morph_init_from_flat_iter(
212 input.morph_count,
213 input.bone_morphs.iter().copied(),
214 input.group_morphs.iter().copied(),
215 )
216}
217
218pub fn build_morph_init_from_flat_iter(
219 morph_count: u32,
220 bone_morphs: impl IntoIterator<Item = FlatBoneMorphInput>,
221 group_morphs: impl IntoIterator<Item = FlatGroupMorphInput>,
222) -> Result<MorphInit, FlatModelInputError> {
223 let bone_morphs = bone_morphs.into_iter().collect::<Vec<_>>();
224 let group_morphs = group_morphs.into_iter().collect::<Vec<_>>();
225 if morph_count == 0 {
226 if bone_morphs.is_empty() && group_morphs.is_empty() {
227 return Ok(MorphInit::default());
228 }
229 return Err(FlatModelInputError::MorphCountZeroWithData);
230 }
231 let morph_count_usize = morph_count as usize;
232 let (bone_offsets, bone_spans) =
233 build_bone_morph_offset_tables(morph_count_usize, &bone_morphs)?;
234 let (group_offsets, group_spans) =
235 build_group_morph_offset_tables(morph_count_usize, &group_morphs)?;
236 Ok(MorphInit {
237 morph_count,
238 bone_offsets,
239 bone_spans,
240 group_offsets,
241 group_spans,
242 ..MorphInit::default()
243 })
244}
245
246fn build_bone_morph_offset_tables(
247 morph_count: usize,
248 bone_morphs: &[FlatBoneMorphInput],
249) -> Result<(Vec<BoneMorphOffset>, Vec<MorphOffsetSpan>), FlatModelInputError> {
250 if bone_morphs.is_empty() {
251 return Ok((Vec::new(), vec![MorphOffsetSpan::default(); morph_count]));
252 }
253
254 let mut sorted: Vec<&FlatBoneMorphInput> = bone_morphs.iter().collect();
255 sorted.sort_by_key(|entry| entry.morph_index);
256 if sorted.last().unwrap().morph_index as usize >= morph_count {
257 return Err(FlatModelInputError::BoneMorphIndexOutOfRange);
258 }
259
260 let mut offsets = Vec::with_capacity(bone_morphs.len());
261 let mut spans = vec![MorphOffsetSpan::default(); morph_count];
262 let mut index = 0;
263 while index < sorted.len() {
264 let morph = sorted[index].morph_index as usize;
265 let start = offsets.len() as u32;
266 let mut count = 0u32;
267 while index < sorted.len() && sorted[index].morph_index as usize == morph {
268 let entry = sorted[index];
269 offsets.push(BoneMorphOffset {
270 target_bone: BoneIndex(entry.target_bone_index),
271 position_offset: glam::Vec3A::new(
272 entry.position_offset_xyz[0],
273 entry.position_offset_xyz[1],
274 entry.position_offset_xyz[2],
275 ),
276 rotation_offset: glam::Quat::from_xyzw(
277 entry.rotation_offset_xyzw[0],
278 entry.rotation_offset_xyzw[1],
279 entry.rotation_offset_xyzw[2],
280 entry.rotation_offset_xyzw[3],
281 ),
282 });
283 count += 1;
284 index += 1;
285 }
286 spans[morph] = MorphOffsetSpan { start, count };
287 }
288
289 Ok((offsets, spans))
290}
291
292fn build_group_morph_offset_tables(
293 morph_count: usize,
294 group_morphs: &[FlatGroupMorphInput],
295) -> Result<(Vec<GroupMorphOffset>, Vec<MorphOffsetSpan>), FlatModelInputError> {
296 if group_morphs.is_empty() {
297 return Ok((Vec::new(), vec![MorphOffsetSpan::default(); morph_count]));
298 }
299
300 let mut sorted: Vec<&FlatGroupMorphInput> = group_morphs.iter().collect();
301 sorted.sort_by_key(|entry| entry.morph_index);
302 if sorted.last().unwrap().morph_index as usize >= morph_count {
303 return Err(FlatModelInputError::GroupMorphIndexOutOfRange);
304 }
305
306 let mut offsets = Vec::with_capacity(group_morphs.len());
307 let mut spans = vec![MorphOffsetSpan::default(); morph_count];
308 let mut index = 0;
309 while index < sorted.len() {
310 let morph = sorted[index].morph_index as usize;
311 let start = offsets.len() as u32;
312 let mut count = 0u32;
313 while index < sorted.len() && sorted[index].morph_index as usize == morph {
314 let entry = sorted[index];
315 offsets.push(GroupMorphOffset {
316 child_morph: MorphIndex(entry.child_morph_index),
317 ratio: entry.ratio,
318 });
319 count += 1;
320 index += 1;
321 }
322 spans[morph] = MorphOffsetSpan { start, count };
323 }
324
325 Ok((offsets, spans))
326}
327
328pub fn build_append_transforms_from_flat(
329 append_transforms: &[FlatAppendTransformInput],
330) -> Vec<AppendTransformInit> {
331 build_append_transforms_from_flat_iter(append_transforms.iter().copied())
332}
333
334pub fn build_append_transforms_from_flat_iter(
335 append_transforms: impl IntoIterator<Item = FlatAppendTransformInput>,
336) -> Vec<AppendTransformInit> {
337 append_transforms
338 .into_iter()
339 .map(|append| {
340 let mut init = AppendTransformInit::new(
341 BoneIndex(append.target_bone_index),
342 BoneIndex(append.source_bone_index),
343 append.ratio,
344 );
345 if append.affect_rotation {
346 init = init.with_rotation();
347 }
348 if append.affect_translation {
349 init = init.with_translation();
350 }
351 if append.local {
352 init = init.with_local();
353 }
354 init
355 })
356 .collect()
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn builds_bones_from_flat_arrays() {
365 let bones = build_bones_from_flat(FlatBoneInput {
366 parent_indices: &[-1, 0],
367 rest_positions_xyz: &[0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
368 inverse_bind_matrices: &[],
369 transform_orders: &[2, 1],
370 })
371 .unwrap();
372
373 assert_eq!(bones.len(), 2);
374 assert_eq!(bones[0].parent, None);
375 assert_eq!(bones[1].parent, Some(BoneIndex(0)));
376 assert_eq!(bones[1].rest_position.to_array(), [3.0, 4.0, 5.0]);
377 assert_eq!(bones[0].transform_order, 2);
378 assert_eq!(bones[1].transform_order, 1);
379 }
380
381 #[test]
382 fn rejects_invalid_flat_bone_arrays() {
383 let error = build_bones_from_flat(FlatBoneInput {
384 parent_indices: &[0],
385 rest_positions_xyz: &[0.0, 1.0],
386 inverse_bind_matrices: &[],
387 transform_orders: &[],
388 })
389 .unwrap_err();
390
391 assert_eq!(error, FlatModelInputError::RestPositionsLen);
392 assert_eq!(
393 error.to_string(),
394 "rest_positions_xyz must contain bone_count * 3 values"
395 );
396 }
397
398 #[test]
399 fn builds_ik_solvers_from_flat_arrays() {
400 let solvers = build_ik_solvers_from_flat(
401 &[FlatIkSolverInput {
402 ik_bone_index: 3,
403 target_bone_index: 2,
404 link_offset: 0,
405 link_count: 1,
406 iteration_count: 10,
407 limit_angle: 0.5,
408 }],
409 &[FlatIkLinkInput {
410 bone_index: 1,
411 has_angle_limit: true,
412 angle_limit_min_xyz: [-1.0, -2.0, -3.0],
413 angle_limit_max_xyz: [1.0, 2.0, 3.0],
414 }],
415 )
416 .unwrap();
417
418 assert_eq!(solvers.len(), 1);
419 assert_eq!(solvers[0].ik_bone, BoneIndex(3));
420 assert_eq!(solvers[0].target_bone, BoneIndex(2));
421 assert_eq!(solvers[0].links.len(), 1);
422 assert!(solvers[0].links[0].angle_limit.is_some());
423 }
424
425 #[test]
426 fn builds_append_transforms_from_flat_arrays() {
427 let append_transforms = build_append_transforms_from_flat(&[FlatAppendTransformInput {
428 target_bone_index: 2,
429 source_bone_index: 1,
430 ratio: 0.25,
431 affect_rotation: true,
432 affect_translation: false,
433 local: true,
434 }]);
435
436 assert_eq!(append_transforms.len(), 1);
437 assert_eq!(append_transforms[0].target_bone, BoneIndex(2));
438 assert_eq!(append_transforms[0].source_bone, BoneIndex(1));
439 assert_eq!(append_transforms[0].ratio, 0.25);
440 assert!(append_transforms[0].affect_rotation);
441 assert!(!append_transforms[0].affect_translation);
442 assert!(append_transforms[0].local);
443 }
444
445 #[test]
446 fn builds_morph_init_from_flat_arrays() {
447 let morph = build_morph_init_from_flat(FlatMorphInput {
448 morph_count: 2,
449 bone_morphs: &[FlatBoneMorphInput {
450 morph_index: 1,
451 target_bone_index: 0,
452 position_offset_xyz: [1.0, 2.0, 3.0],
453 rotation_offset_xyzw: [0.0, 0.0, 0.0, 1.0],
454 }],
455 group_morphs: &[],
456 })
457 .unwrap();
458
459 assert_eq!(morph.morph_count, 2);
460 assert_eq!(morph.bone_offsets.len(), 1);
461 assert_eq!(morph.bone_spans.len(), 2);
462 assert_eq!(morph.bone_spans[0], MorphOffsetSpan::default());
463 assert_eq!(morph.bone_spans[1], MorphOffsetSpan { start: 0, count: 1 });
464 assert_eq!(morph.bone_offsets[0].target_bone, BoneIndex(0));
465 }
466
467 #[test]
468 fn rejects_out_of_range_bone_morph_index() {
469 let error = build_morph_init_from_flat(FlatMorphInput {
470 morph_count: 1,
471 bone_morphs: &[FlatBoneMorphInput {
472 morph_index: 1,
473 target_bone_index: 0,
474 position_offset_xyz: [0.0, 0.0, 0.0],
475 rotation_offset_xyzw: [0.0, 0.0, 0.0, 1.0],
476 }],
477 group_morphs: &[],
478 })
479 .unwrap_err();
480
481 assert_eq!(error, FlatModelInputError::BoneMorphIndexOutOfRange);
482 }
483
484 #[test]
485 fn rejects_zero_morph_count_with_data() {
486 let error = build_morph_init_from_flat(FlatMorphInput {
487 morph_count: 0,
488 bone_morphs: &[FlatBoneMorphInput {
489 morph_index: 0,
490 target_bone_index: 0,
491 position_offset_xyz: [0.0, 0.0, 0.0],
492 rotation_offset_xyzw: [0.0, 0.0, 0.0, 1.0],
493 }],
494 group_morphs: &[],
495 })
496 .unwrap_err();
497
498 assert_eq!(error, FlatModelInputError::MorphCountZeroWithData);
499 assert_eq!(
500 error.to_string(),
501 "morph_count must be non-zero when morph data is provided"
502 );
503 }
504}