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