1use super::types::{Quat, Vec3};
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct Mat4 {
12 pub data: [f32; 16],
14}
15
16impl Mat4 {
17 pub const IDENTITY: Self = Self {
19 data: [
20 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, ],
25 };
26
27 pub fn identity() -> Self {
29 Self::IDENTITY
30 }
31
32 pub fn from_translation(v: Vec3) -> Self {
34 Self {
35 data: [
36 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, v.x, v.y, v.z, 1.0, ],
41 }
42 }
43
44 pub fn from_scale(v: Vec3) -> Self {
46 Self {
47 data: [
48 v.x, 0.0, 0.0, 0.0, 0.0, v.y, 0.0, 0.0, 0.0, 0.0, v.z, 0.0, 0.0, 0.0, 0.0, 1.0, ],
53 }
54 }
55
56 pub fn from_rotation(q: Quat) -> Self {
58 let x = q.x;
59 let y = q.y;
60 let z = q.z;
61 let w = q.w;
62
63 let x2 = x + x;
64 let y2 = y + y;
65 let z2 = z + z;
66
67 let xx = x * x2;
68 let xy = x * y2;
69 let xz = x * z2;
70 let yy = y * y2;
71 let yz = y * z2;
72 let zz = z * z2;
73 let wx = w * x2;
74 let wy = w * y2;
75 let wz = w * z2;
76
77 Self {
78 data: [
79 1.0 - (yy + zz),
80 xy + wz,
81 xz - wy,
82 0.0,
83 xy - wz,
84 1.0 - (xx + zz),
85 yz + wx,
86 0.0,
87 xz + wy,
88 yz - wx,
89 1.0 - (xx + yy),
90 0.0,
91 0.0,
92 0.0,
93 0.0,
94 1.0,
95 ],
96 }
97 }
98
99 pub fn from_rotation_translation_scale(rotation: Quat, translation: Vec3, scale: Vec3) -> Self {
101 let x = rotation.x;
102 let y = rotation.y;
103 let z = rotation.z;
104 let w = rotation.w;
105
106 let x2 = x + x;
107 let y2 = y + y;
108 let z2 = z + z;
109
110 let xx = x * x2;
111 let xy = x * y2;
112 let xz = x * z2;
113 let yy = y * y2;
114 let yz = y * z2;
115 let zz = z * z2;
116 let wx = w * x2;
117 let wy = w * y2;
118 let wz = w * z2;
119
120 let sx = scale.x;
121 let sy = scale.y;
122 let sz = scale.z;
123
124 Self {
125 data: [
126 (1.0 - (yy + zz)) * sx,
127 (xy + wz) * sx,
128 (xz - wy) * sx,
129 0.0,
130 (xy - wz) * sy,
131 (1.0 - (xx + zz)) * sy,
132 (yz + wx) * sy,
133 0.0,
134 (xz + wy) * sz,
135 (yz - wx) * sz,
136 (1.0 - (xx + yy)) * sz,
137 0.0,
138 translation.x,
139 translation.y,
140 translation.z,
141 1.0,
142 ],
143 }
144 }
145
146 pub fn mul(&self, other: &Self) -> Self {
148 let a = &self.data;
149 let b = &other.data;
150
151 let a00 = a[0];
152 let a01 = a[1];
153 let a02 = a[2];
154 let a03 = a[3];
155 let a10 = a[4];
156 let a11 = a[5];
157 let a12 = a[6];
158 let a13 = a[7];
159 let a20 = a[8];
160 let a21 = a[9];
161 let a22 = a[10];
162 let a23 = a[11];
163 let a30 = a[12];
164 let a31 = a[13];
165 let a32 = a[14];
166 let a33 = a[15];
167
168 let b00 = b[0];
169 let b01 = b[1];
170 let b02 = b[2];
171 let b03 = b[3];
172 let b10 = b[4];
173 let b11 = b[5];
174 let b12 = b[6];
175 let b13 = b[7];
176 let b20 = b[8];
177 let b21 = b[9];
178 let b22 = b[10];
179 let b23 = b[11];
180 let b30 = b[12];
181 let b31 = b[13];
182 let b32 = b[14];
183 let b33 = b[15];
184
185 Self {
186 data: [
187 b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30,
188 b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31,
189 b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32,
190 b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33,
191 b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30,
192 b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31,
193 b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32,
194 b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33,
195 b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30,
196 b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31,
197 b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32,
198 b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33,
199 b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30,
200 b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31,
201 b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32,
202 b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33,
203 ],
204 }
205 }
206
207 pub fn transform_point(&self, p: Vec3) -> Vec3 {
209 let m = &self.data;
210 Vec3 {
211 x: m[0] * p.x + m[4] * p.y + m[8] * p.z + m[12],
212 y: m[1] * p.x + m[5] * p.y + m[9] * p.z + m[13],
213 z: m[2] * p.x + m[6] * p.y + m[10] * p.z + m[14],
214 }
215 }
216
217 pub fn transform_normal(&self, n: Vec3) -> Vec3 {
219 let m = &self.data;
220 Vec3 {
221 x: m[0] * n.x + m[4] * n.y + m[8] * n.z,
222 y: m[1] * n.x + m[5] * n.y + m[9] * n.z,
223 z: m[2] * n.x + m[6] * n.y + m[10] * n.z,
224 }
225 }
226
227 pub fn as_array(&self) -> &[f32; 16] {
229 &self.data
230 }
231
232 pub fn as_4x3(&self) -> [f32; 12] {
234 [
235 self.data[0],
236 self.data[1],
237 self.data[2],
238 self.data[4],
239 self.data[5],
240 self.data[6],
241 self.data[8],
242 self.data[9],
243 self.data[10],
244 self.data[12],
245 self.data[13],
246 self.data[14],
247 ]
248 }
249}
250
251impl Default for Mat4 {
252 fn default() -> Self {
253 Self::IDENTITY
254 }
255}
256
257impl std::ops::Mul for Mat4 {
258 type Output = Mat4;
259
260 fn mul(self, rhs: Self) -> Self::Output {
261 Mat4::mul(&self, &rhs)
262 }
263}
264
265#[derive(Debug, Clone, Copy, Default)]
267pub struct BoneFlags {
268 pub ignore_parent_translate: bool,
270 pub ignore_parent_scale: bool,
272 pub ignore_parent_rotation: bool,
274 pub spherical_billboard: bool,
276 pub cylindrical_billboard_lock_x: bool,
278 pub cylindrical_billboard_lock_y: bool,
280 pub cylindrical_billboard_lock_z: bool,
282}
283
284impl BoneFlags {
285 pub fn from_raw(flags: u32) -> Self {
287 Self {
288 ignore_parent_translate: (flags & 0x01) != 0,
289 ignore_parent_scale: (flags & 0x02) != 0,
290 ignore_parent_rotation: (flags & 0x04) != 0,
291 spherical_billboard: (flags & 0x08) != 0,
292 cylindrical_billboard_lock_x: (flags & 0x10) != 0,
293 cylindrical_billboard_lock_y: (flags & 0x20) != 0,
294 cylindrical_billboard_lock_z: (flags & 0x40) != 0,
295 }
296 }
297
298 pub fn is_billboard(&self) -> bool {
300 self.spherical_billboard
301 || self.cylindrical_billboard_lock_x
302 || self.cylindrical_billboard_lock_y
303 || self.cylindrical_billboard_lock_z
304 }
305}
306
307#[derive(Debug, Clone)]
309pub struct ComputedBone {
310 pub transform: Mat4,
312 pub post_billboard_transform: Mat4,
314 pub is_spherical_billboard: bool,
316}
317
318impl Default for ComputedBone {
319 fn default() -> Self {
320 Self {
321 transform: Mat4::IDENTITY,
322 post_billboard_transform: Mat4::IDENTITY,
323 is_spherical_billboard: false,
324 }
325 }
326}
327
328#[allow(dead_code)]
333pub struct BoneTransformComputer {
334 bones: Vec<ComputedBone>,
336 pivots: Vec<Mat4>,
338 anti_pivots: Vec<Mat4>,
340 parents: Vec<i16>,
342 flags: Vec<BoneFlags>,
344 scratch: Mat4,
346}
347
348impl BoneTransformComputer {
349 pub fn new(pivot_points: &[Vec3], parents: &[i16], raw_flags: &[u32]) -> Self {
356 let count = pivot_points.len();
357
358 let mut pivots = Vec::with_capacity(count);
359 let mut anti_pivots = Vec::with_capacity(count);
360 let mut flags = Vec::with_capacity(count);
361 let mut bones = Vec::with_capacity(count);
362
363 let mut is_billboard = vec![false; count];
365
366 for i in 0..count {
367 let pivot = pivot_points[i];
368 pivots.push(Mat4::from_translation(pivot));
369 anti_pivots.push(Mat4::from_translation(Vec3::new(
370 -pivot.x, -pivot.y, -pivot.z,
371 )));
372
373 let bone_flags = BoneFlags::from_raw(raw_flags[i]);
374 is_billboard[i] = bone_flags.spherical_billboard;
375 flags.push(bone_flags);
376 bones.push(ComputedBone::default());
377 }
378
379 for i in 0..count {
381 let parent_idx = parents[i];
382 if parent_idx >= 0 && (parent_idx as usize) < count && is_billboard[parent_idx as usize]
383 {
384 is_billboard[i] = true;
385 }
386 }
387
388 for i in 0..count {
390 bones[i].is_spherical_billboard = is_billboard[i];
391 }
392
393 Self {
394 bones,
395 pivots,
396 anti_pivots,
397 parents: parents.to_vec(),
398 flags,
399 scratch: Mat4::IDENTITY,
400 }
401 }
402
403 pub fn empty() -> Self {
405 Self {
406 bones: Vec::new(),
407 pivots: Vec::new(),
408 anti_pivots: Vec::new(),
409 parents: Vec::new(),
410 flags: Vec::new(),
411 scratch: Mat4::IDENTITY,
412 }
413 }
414
415 pub fn bone_count(&self) -> usize {
417 self.bones.len()
418 }
419
420 pub fn update(&mut self, translations: &[Vec3], rotations: &[Quat], scales: &[Vec3]) {
427 let count = self.bones.len();
428 if count == 0 {
429 return;
430 }
431
432 for i in 0..count {
433 let translation = translations.get(i).copied().unwrap_or(Vec3::ZERO);
434 let rotation = rotations.get(i).copied().unwrap_or(Quat::IDENTITY);
435 let scale = scales.get(i).copied().unwrap_or(Vec3::ONE);
436
437 self.scratch = Mat4::from_rotation_translation_scale(rotation, translation, scale);
439
440 let local_bone_transform = self.pivots[i].mul(&self.scratch);
442
443 let parent_idx = self.parents[i];
444 let is_billboard = self.bones[i].is_spherical_billboard;
445
446 if parent_idx >= 0 && (parent_idx as usize) < count {
447 let parent_idx = parent_idx as usize;
448 let parent_transform = self.bones[parent_idx].transform;
450 let parent_post_billboard = self.bones[parent_idx].post_billboard_transform;
451
452 if is_billboard {
453 self.bones[i].transform = parent_transform.mul(&self.anti_pivots[i]);
456 self.bones[i].post_billboard_transform =
457 parent_post_billboard.mul(&local_bone_transform);
458 } else {
459 let final_local = local_bone_transform.mul(&self.anti_pivots[i]);
461 self.bones[i].post_billboard_transform =
462 parent_post_billboard.mul(&final_local);
463 self.bones[i].transform = self.bones[i].post_billboard_transform;
465 }
466 } else {
467 if is_billboard {
469 self.bones[i].transform = self.anti_pivots[i];
470 self.bones[i].post_billboard_transform = local_bone_transform;
471 } else {
472 let final_local = local_bone_transform.mul(&self.anti_pivots[i]);
473 self.bones[i].post_billboard_transform = final_local;
474 self.bones[i].transform = final_local;
475 }
476 }
477 }
478 }
479
480 pub fn bones(&self) -> &[ComputedBone] {
482 &self.bones
483 }
484
485 pub fn get_transform(&self, bone_index: usize) -> Mat4 {
487 self.bones
488 .get(bone_index)
489 .map(|b| b.transform)
490 .unwrap_or(Mat4::IDENTITY)
491 }
492
493 pub fn get_post_billboard_transform(&self, bone_index: usize) -> Mat4 {
495 self.bones
496 .get(bone_index)
497 .map(|b| b.post_billboard_transform)
498 .unwrap_or(Mat4::IDENTITY)
499 }
500
501 pub fn get_skinning_transform(&self, bone_index: usize) -> Mat4 {
507 self.bones
508 .get(bone_index)
509 .map(|b| {
510 if b.is_spherical_billboard {
511 b.post_billboard_transform
514 } else {
515 b.post_billboard_transform
516 }
517 })
518 .unwrap_or(Mat4::IDENTITY)
519 }
520
521 pub fn is_spherical_billboard(&self, bone_index: usize) -> bool {
523 self.bones
524 .get(bone_index)
525 .is_some_and(|b| b.is_spherical_billboard)
526 }
527
528 pub fn get_gpu_data(&self) -> Vec<f32> {
534 let mut data = Vec::with_capacity(self.bones.len() * 28); for bone in &self.bones {
537 data.push(if bone.is_spherical_billboard {
539 1.0
540 } else {
541 0.0
542 });
543 data.push(0.0);
545 data.push(0.0);
546 data.push(0.0);
547
548 data.extend_from_slice(&bone.transform.as_4x3());
550
551 data.extend_from_slice(&bone.post_billboard_transform.as_4x3());
553 }
554
555 data
556 }
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562
563 #[test]
564 fn test_mat4_identity() {
565 let m = Mat4::identity();
566 assert_eq!(m.data[0], 1.0);
567 assert_eq!(m.data[5], 1.0);
568 assert_eq!(m.data[10], 1.0);
569 assert_eq!(m.data[15], 1.0);
570 }
571
572 #[test]
573 fn test_mat4_translation() {
574 let m = Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0));
575 let p = m.transform_point(Vec3::ZERO);
576 assert!((p.x - 1.0).abs() < 0.001);
577 assert!((p.y - 2.0).abs() < 0.001);
578 assert!((p.z - 3.0).abs() < 0.001);
579 }
580
581 #[test]
582 fn test_mat4_scale() {
583 let m = Mat4::from_scale(Vec3::new(2.0, 3.0, 4.0));
584 let p = m.transform_point(Vec3::new(1.0, 1.0, 1.0));
585 assert!((p.x - 2.0).abs() < 0.001);
586 assert!((p.y - 3.0).abs() < 0.001);
587 assert!((p.z - 4.0).abs() < 0.001);
588 }
589
590 #[test]
591 fn test_mat4_multiply_identity() {
592 let a = Mat4::identity();
593 let b = Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0));
594 let c = a.mul(&b);
595 assert_eq!(c.data, b.data);
596 }
597
598 #[test]
599 fn test_bone_flags() {
600 let flags = BoneFlags::from_raw(0x08); assert!(flags.spherical_billboard);
602 assert!(!flags.cylindrical_billboard_lock_x);
603
604 let flags = BoneFlags::from_raw(0x10); assert!(!flags.spherical_billboard);
606 assert!(flags.cylindrical_billboard_lock_x);
607 }
608
609 #[test]
610 fn test_bone_transform_computer_empty() {
611 let computer = BoneTransformComputer::empty();
612 assert_eq!(computer.bone_count(), 0);
613 }
614
615 #[test]
616 fn test_bone_transform_single_bone() {
617 let pivots = vec![Vec3::ZERO];
618 let parents = vec![-1i16];
619 let flags = vec![0u32];
620
621 let mut computer = BoneTransformComputer::new(&pivots, &parents, &flags);
622
623 let translations = vec![Vec3::ZERO];
625 let rotations = vec![Quat::IDENTITY];
626 let scales = vec![Vec3::ONE];
627
628 computer.update(&translations, &rotations, &scales);
629
630 let transform = computer.get_transform(0);
631 assert!((transform.data[0] - 1.0).abs() < 0.001);
633 assert!((transform.data[5] - 1.0).abs() < 0.001);
634 assert!((transform.data[10] - 1.0).abs() < 0.001);
635 }
636
637 #[test]
638 fn test_bone_transform_with_pivot() {
639 let pivots = vec![Vec3::new(0.0, 0.0, 10.0)];
640 let parents = vec![-1i16];
641 let flags = vec![0u32];
642
643 let mut computer = BoneTransformComputer::new(&pivots, &parents, &flags);
644
645 let translations = vec![Vec3::ZERO];
646 let rotations = vec![Quat::IDENTITY];
647 let scales = vec![Vec3::ONE];
648
649 computer.update(&translations, &rotations, &scales);
650
651 let transform = computer.get_transform(0);
652 let p = transform.transform_point(Vec3::ZERO);
654 assert!(p.x.abs() < 0.001);
655 assert!(p.y.abs() < 0.001);
656 assert!(p.z.abs() < 0.001);
657 }
658
659 #[test]
660 fn test_bone_transform_parent_chain() {
661 let pivots = vec![Vec3::ZERO, Vec3::ZERO];
663 let parents = vec![-1i16, 0]; let flags = vec![0u32, 0u32];
665
666 let mut computer = BoneTransformComputer::new(&pivots, &parents, &flags);
667
668 let translations = vec![Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, 1.0, 0.0)];
669 let rotations = vec![Quat::IDENTITY, Quat::IDENTITY];
670 let scales = vec![Vec3::ONE, Vec3::ONE];
671
672 computer.update(&translations, &rotations, &scales);
673
674 let root_transform = computer.get_transform(0);
676 let p = root_transform.transform_point(Vec3::ZERO);
677 assert!((p.x - 1.0).abs() < 0.001);
678
679 let child_transform = computer.get_transform(1);
681 let p = child_transform.transform_point(Vec3::ZERO);
682 assert!((p.x - 1.0).abs() < 0.001);
683 assert!((p.y - 1.0).abs() < 0.001);
684 }
685
686 #[test]
687 fn test_billboard_inheritance() {
688 let pivots = vec![Vec3::ZERO, Vec3::ZERO];
690 let parents = vec![-1i16, 0];
691 let flags = vec![0x08, 0]; let computer = BoneTransformComputer::new(&pivots, &parents, &flags);
694
695 assert!(computer.is_spherical_billboard(0));
696 assert!(computer.is_spherical_billboard(1)); }
698}