1#![allow(
25 clippy::cast_possible_truncation,
26 clippy::cast_possible_wrap,
27 clippy::cast_sign_loss,
28 clippy::cast_precision_loss,
29 clippy::similar_names,
30 clippy::too_many_arguments,
31 clippy::doc_markdown,
32 clippy::many_single_char_names,
33 clippy::missing_panics_doc,
34 clippy::float_cmp,
35 clippy::useless_vec
36)]
37
38use roxlap_formats::kfa::{Hinge, KfaSprite, Point3};
39use roxlap_formats::sprite::Sprite;
40use roxlap_formats::xform::BoneXform;
41
42#[allow(clippy::too_many_arguments)]
48fn mat2(
49 a_s: [f32; 3],
50 a_h: [f32; 3],
51 a_f: [f32; 3],
52 a_o: [f32; 3],
53 b_s: [f32; 3],
54 b_h: [f32; 3],
55 b_f: [f32; 3],
56 b_o: [f32; 3],
57) -> ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) {
58 let c_s = [
59 a_s[0] * b_s[0] + a_h[0] * b_s[1] + a_f[0] * b_s[2],
60 a_s[1] * b_s[0] + a_h[1] * b_s[1] + a_f[1] * b_s[2],
61 a_s[2] * b_s[0] + a_h[2] * b_s[1] + a_f[2] * b_s[2],
62 ];
63 let c_h = [
64 a_s[0] * b_h[0] + a_h[0] * b_h[1] + a_f[0] * b_h[2],
65 a_s[1] * b_h[0] + a_h[1] * b_h[1] + a_f[1] * b_h[2],
66 a_s[2] * b_h[0] + a_h[2] * b_h[1] + a_f[2] * b_h[2],
67 ];
68 let c_f = [
69 a_s[0] * b_f[0] + a_h[0] * b_f[1] + a_f[0] * b_f[2],
70 a_s[1] * b_f[0] + a_h[1] * b_f[1] + a_f[1] * b_f[2],
71 a_s[2] * b_f[0] + a_h[2] * b_f[1] + a_f[2] * b_f[2],
72 ];
73 let c_o = [
74 a_s[0] * b_o[0] + a_h[0] * b_o[1] + a_f[0] * b_o[2] + a_o[0],
75 a_s[1] * b_o[0] + a_h[1] * b_o[1] + a_f[1] * b_o[2] + a_o[1],
76 a_s[2] * b_o[0] + a_h[2] * b_o[1] + a_f[2] * b_o[2] + a_o[2],
77 ];
78 (c_s, c_h, c_f, c_o)
79}
80
81fn genperp(a: [f32; 3]) -> ([f32; 3], [f32; 3]) {
91 if a == [0.0, 0.0, 0.0] {
92 return ([0.0; 3], [0.0; 3]);
93 }
94 let ax = a[0].abs();
98 let ay = a[1].abs();
99 let az = a[2].abs();
100 let b = if ax < ay && ax < az {
101 let t = 1.0 / (a[1] * a[1] + a[2] * a[2]).sqrt();
102 [0.0, a[2] * t, -a[1] * t]
103 } else if ay < az {
104 let t = 1.0 / (a[0] * a[0] + a[2] * a[2]).sqrt();
105 [-a[2] * t, 0.0, a[0] * t]
106 } else {
107 let t = 1.0 / (a[0] * a[0] + a[1] * a[1]).sqrt();
108 [a[1] * t, -a[0] * t, 0.0]
109 };
110 let c = [
111 a[1] * b[2] - a[2] * b[1],
112 a[2] * b[0] - a[0] * b[2],
113 a[0] * b[1] - a[1] * b[0],
114 ];
115 (b, c)
116}
117
118fn mat0(
125 b_s: [f32; 3],
126 b_h: [f32; 3],
127 b_f: [f32; 3],
128 b_o: [f32; 3],
129 c_s: [f32; 3],
130 c_h: [f32; 3],
131 c_f: [f32; 3],
132 c_o: [f32; 3],
133) -> ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) {
134 let ts = [
137 b_s[0] * c_s[0] + b_h[0] * c_h[0] + b_f[0] * c_f[0],
138 b_s[0] * c_s[1] + b_h[0] * c_h[1] + b_f[0] * c_f[1],
139 b_s[0] * c_s[2] + b_h[0] * c_h[2] + b_f[0] * c_f[2],
140 ];
141 let th = [
142 b_s[1] * c_s[0] + b_h[1] * c_h[0] + b_f[1] * c_f[0],
143 b_s[1] * c_s[1] + b_h[1] * c_h[1] + b_f[1] * c_f[1],
144 b_s[1] * c_s[2] + b_h[1] * c_h[2] + b_f[1] * c_f[2],
145 ];
146 let tf = [
147 b_s[2] * c_s[0] + b_h[2] * c_h[0] + b_f[2] * c_f[0],
148 b_s[2] * c_s[1] + b_h[2] * c_h[1] + b_f[2] * c_f[1],
149 b_s[2] * c_s[2] + b_h[2] * c_h[2] + b_f[2] * c_f[2],
150 ];
151 let to = [
152 c_o[0] - b_o[0] * ts[0] - b_o[1] * th[0] - b_o[2] * tf[0],
153 c_o[1] - b_o[0] * ts[1] - b_o[1] * th[1] - b_o[2] * tf[1],
154 c_o[2] - b_o[0] * ts[2] - b_o[1] * th[2] - b_o[2] * tf[2],
155 ];
156 (ts, th, tf, to)
157}
158
159#[inline]
160fn pt(p: Point3) -> [f32; 3] {
161 [p.x, p.y, p.z]
162}
163
164fn setlimb(limbs: &mut [Sprite], hinges: &[Hinge], i: usize, parent: usize, xform: &BoneXform) {
168 let p = &limbs[parent];
169 let (cs, ch, cf, co) = limb_xform((p.s, p.h, p.f, p.p), &hinges[i], xform);
170 let child = &mut limbs[i];
171 child.s = cs;
172 child.h = ch;
173 child.f = cf;
174 child.p = co;
175}
176
177fn limb_xform(
182 parent: ([f32; 3], [f32; 3], [f32; 3], [f32; 3]),
183 hinge: &Hinge,
184 xform: &BoneXform,
185) -> ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) {
186 let (parent_s, parent_h, parent_f, parent_p) = parent;
187
188 let qp0 = pt(hinge.p[0]);
191 let qp = [
192 qp0[0] + xform.t[0],
193 qp0[1] + xform.t[1],
194 qp0[2] + xform.t[2],
195 ];
196 let qs0 = pt(hinge.v[0]);
197 let (qh0, qf0) = genperp(qs0);
198
199 let qs = xform.r.rotate(qs0);
205 let qh = xform.r.rotate(qh0);
206 let qf = xform.r.rotate(qf0);
207
208 let pp = pt(hinge.p[1]);
210 let ps = pt(hinge.v[1]);
211 let (ph, pf) = genperp(ps);
212
213 let (rs, rh, rf, ro) = mat0(ps, ph, pf, pp, qs, qh, qf, qp);
215
216 let (cs, ch, cf, co) = mat2(parent_s, parent_h, parent_f, parent_p, rs, rh, rf, ro);
218
219 (
222 [cs[0] * xform.s[0], cs[1] * xform.s[0], cs[2] * xform.s[0]],
223 [ch[0] * xform.s[1], ch[1] * xform.s[1], ch[2] * xform.s[1]],
224 [cf[0] * xform.s[2], cf[1] * xform.s[2], cf[2] * xform.s[2]],
225 co,
226 )
227}
228
229pub fn solve_kfa_limbs(kfa: &mut KfaSprite) {
242 let n = kfa.hinge_sort.len();
246 for k in (0..n).rev() {
247 let j = kfa.hinge_sort[k];
248 let parent = kfa.hinges[j].parent;
249 if parent >= 0 {
250 let htype = kfa.hinges[j].htype;
254 let xform = if htype == 0 {
255 kfa.kfaval[j]
256 } else {
257 BoneXform::IDENTITY
258 };
259 setlimb(&mut kfa.limbs, &kfa.hinges, j, parent as usize, &xform);
260 } else {
261 let s = kfa.s;
264 let h = kfa.h;
265 let f = kfa.f;
266 let p_world = kfa.p;
267 let tp = pt(kfa.hinges[j].p[0]);
268 let limb = &mut kfa.limbs[j];
269 limb.s = s;
270 limb.h = h;
271 limb.f = f;
272 limb.p = [
273 p_world[0] - tp[0] * s[0] - tp[1] * h[0] - tp[2] * f[0],
274 p_world[1] - tp[0] * s[1] - tp[1] * h[1] - tp[2] * f[1],
275 p_world[2] - tp[0] * s[2] - tp[1] * h[2] - tp[2] * f[2],
276 ];
277 }
278 }
279}
280
281#[must_use]
292pub fn compose_attachment(
293 bs: [f32; 3],
294 bh: [f32; 3],
295 bf: [f32; 3],
296 bp: [f32; 3],
297 off: &BoneXform,
298) -> ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) {
299 let mbone = |v: [f32; 3]| {
301 [
302 bs[0] * v[0] + bh[0] * v[1] + bf[0] * v[2],
303 bs[1] * v[0] + bh[1] * v[1] + bf[1] * v[2],
304 bs[2] * v[0] + bh[2] * v[1] + bf[2] * v[2],
305 ]
306 };
307 let lx = off.r.rotate([off.s[0], 0.0, 0.0]);
309 let ly = off.r.rotate([0.0, off.s[1], 0.0]);
310 let lz = off.r.rotate([0.0, 0.0, off.s[2]]);
311 let s = mbone(lx);
312 let h = mbone(ly);
313 let f = mbone(lz);
314 let tp = mbone(off.t);
315 let p = [bp[0] + tp[0], bp[1] + tp[1], bp[2] + tp[2]];
316 (s, h, f, p)
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use roxlap_formats::xform::Quat;
323
324 #[test]
325 fn compose_attachment_identity_and_offset() {
326 let (bs, bh, bf, bp) = (
327 [1.0, 0.0, 0.0],
328 [0.0, 1.0, 0.0],
329 [0.0, 0.0, 1.0],
330 [10.0, 20.0, 30.0],
331 );
332
333 let r = compose_attachment(bs, bh, bf, bp, &BoneXform::IDENTITY);
335 assert_eq!(r, (bs, bh, bf, bp));
336
337 let off = BoneXform {
339 t: [1.0, 2.0, 3.0],
340 r: Quat::IDENTITY,
341 s: [1.0, 1.0, 1.0],
342 };
343 let (_, _, _, p) = compose_attachment(bs, bh, bf, bp, &off);
344 assert_eq!(p, [11.0, 22.0, 33.0]);
345 }
346
347 #[test]
348 fn compose_attachment_offset_is_in_bone_space() {
349 let (bs, bh, bf, bp) = (
351 [0.0, 1.0, 0.0],
352 [-1.0, 0.0, 0.0],
353 [0.0, 0.0, 1.0],
354 [0.0, 0.0, 0.0],
355 );
356 let off = BoneXform {
357 t: [2.0, 0.0, 0.0], r: Quat::IDENTITY,
359 s: [1.0, 1.0, 1.0],
360 };
361 let (s, _, _, p) = compose_attachment(bs, bh, bf, bp, &off);
362 assert!((p[0]).abs() < 1e-6 && (p[1] - 2.0).abs() < 1e-6 && p[2].abs() < 1e-6);
364 assert!(
366 (s[1] - 1.0).abs() < 1e-6,
367 "local +x stays the bone's +x (world +y)"
368 );
369 }
370
371 #[test]
374 fn genperp_orthonormal() {
375 let a = [1.0_f32, 0.0, 0.0];
376 let (b, c) = genperp(a);
377 assert!((a[0] * b[0] + a[1] * b[1] + a[2] * b[2]).abs() < 1e-6);
379 assert!((a[0] * c[0] + a[1] * c[1] + a[2] * c[2]).abs() < 1e-6);
381 assert!((b[0] * c[0] + b[1] * c[1] + b[2] * c[2]).abs() < 1e-6);
383 let lb = b[0] * b[0] + b[1] * b[1] + b[2] * b[2];
385 assert!((lb - 1.0).abs() < 1e-5, "|b|² = {lb}");
386 let lc = c[0] * c[0] + c[1] * c[1] + c[2] * c[2];
388 assert!((lc - 1.0).abs() < 1e-5, "|c|² = {lc}");
389 }
390
391 #[test]
393 fn genperp_zero() {
394 let (b, c) = genperp([0.0, 0.0, 0.0]);
395 assert_eq!(b, [0.0, 0.0, 0.0]);
396 assert_eq!(c, [0.0, 0.0, 0.0]);
397 }
398
399 fn legacy_limb_xform(
403 parent: ([f32; 3], [f32; 3], [f32; 3], [f32; 3]),
404 hinge: &Hinge,
405 val: i16,
406 ) -> ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) {
407 let (ps0, ph0, pf0, pp0) = parent;
408 let qp = pt(hinge.p[0]);
409 let qs = pt(hinge.v[0]);
410 let (mut qh, mut qf) = genperp(qs);
411 let ang = (i32::from(val) as f32) * (std::f32::consts::PI * 2.0 / 65536.0);
412 let (c, s) = (ang.cos(), ang.sin());
413 let (ih, jf) = (qh, qf);
414 qh = [
415 ih[0] * c - jf[0] * s,
416 ih[1] * c - jf[1] * s,
417 ih[2] * c - jf[2] * s,
418 ];
419 qf = [
420 ih[0] * s + jf[0] * c,
421 ih[1] * s + jf[1] * c,
422 ih[2] * s + jf[2] * c,
423 ];
424 let pp = pt(hinge.p[1]);
425 let ps = pt(hinge.v[1]);
426 let (ph, pf) = genperp(ps);
427 let (rs, rh, rf, ro) = mat0(ps, ph, pf, pp, qs, qh, qf, qp);
428 mat2(ps0, ph0, pf0, pp0, rs, rh, rf, ro)
429 }
430
431 #[test]
435 fn trs_solver_matches_the_legacy_hinge_rotation() {
436 let axis = Point3 {
437 x: 0.0,
438 y: 0.0,
439 z: 1.0,
440 };
441 let hinge = Hinge {
442 parent: 0,
443 p: [
444 Point3 {
445 x: 0.0,
446 y: 0.0,
447 z: 0.0,
448 },
449 Point3 {
450 x: 6.0,
451 y: 0.0,
452 z: 0.0,
453 },
454 ],
455 v: [axis, axis],
456 vmin: i16::MIN,
457 vmax: i16::MAX,
458 htype: 0,
459 filler: [0; 7],
460 };
461 let parent = (
463 [1.0, 0.0, 0.0],
464 [0.0, 1.0, 0.0],
465 [0.0, 0.0, 1.0],
466 [0.0, 0.0, 0.0],
467 );
468 let close = |a: [f32; 3], b: [f32; 3]| (0..3).all(|i| (a[i] - b[i]).abs() < 1e-4);
469 for val in [0i16, 8000, 16384, -16384, 30000, i16::MIN] {
470 let want = legacy_limb_xform(parent, &hinge, val);
471 let got = limb_xform(parent, &hinge, &BoneXform::from_hinge_angle(pt(axis), val));
472 assert!(
473 close(got.0, want.0),
474 "s mismatch at {val}: {:?} vs {:?}",
475 got.0,
476 want.0
477 );
478 assert!(close(got.1, want.1), "h mismatch at {val}");
479 assert!(close(got.2, want.2), "f mismatch at {val}");
480 assert!(close(got.3, want.3), "p mismatch at {val}");
481 }
482 }
483}