nx_nxa_rig_motion_20260909.nx source
↩ module page · 67 lines · 3283 B
1// Caller-owned native rig composition extracted from nx_nxa_retarget, 2026-09-09.
2// Inputs are admitted parent-first identity-bind rigs in the SAME target raw basis.
3// No allocator, shared writable arena, entity count, or joint ceiling.
4// q/d source inputs are immutable; outputs W(nt*4), P(nt*3), tdt(nt*3) must be disjoint.
5// scratch requires NRM_SCR_W words. Validate owned spans at the external context boundary.
6import "nx_nxa_fk.nx"
7const NRM_Q12: i64 = 4096
8const NRM_SCR_W: i64 = 32
9func nrm_q(p: *i64, i: i64) -> *i64 { return ((p as i64) + i*32) as *i64 }
10func nrm_compose(ns: i64, nt: i64, sD: *i64, sdt: *i64, map: *i64, tpar: *i64, tbind: *i64, bdst: *i64, bound: i64, sse: i64, tse: i64, W: *i64, P: *i64, tdt: *i64, scratch: *i64) -> i64 {
11 if ns < 1 { return 0 - 1 }
12 if nt < 1 { return 0 - 1 }
13 if sse < 1 { return 0 - 1 }
14 if tse < 1 { return 0 - 1 }
15 var v: i64 = 0
16 while v < nt {
17 if map[v] < 0 { return 0 - 2 }
18 if map[v] >= ns { return 0 - 2 }
19 if tpar[v] >= v { return 0 - 3 }
20 if tpar[v] < (0 - 1) { return 0 - 3 }
21 v = v + 1
22 }
23 let cq: *i64 = scratch
24 let aq: *i64 = ((scratch as i64) + 32) as *i64
25 let o3: *i64 = ((scratch as i64) + 64) as *i64
26 let scr: *i64 = ((scratch as i64) + 128) as *i64
27 var j: i64 = 0
28 while j < nt {
29 let s: i64 = map[j]
30 let pj: i64 = tpar[j]
31 if pj < 0 {
32 // root: no parent to conjugate against, so A = D. Root translation is the ONE quantity
33 // that must cross skeletons by size, so it is scaled by the ratio of the two rigs' own
34 // measured stature extents -- derived from the assets, never a picked factor.
35 W[j*4] = sD[s*4]; W[j*4+1] = sD[s*4+1]; W[j*4+2] = sD[s*4+2]; W[j*4+3] = sD[s*4+3]
36 P[j*3] = tbind[j*3] + sdt[s*3] * tse / sse
37 P[j*3+1] = tbind[j*3+1] + sdt[s*3+1] * tse / sse
38 P[j*3+2] = tbind[j*3+2] + sdt[s*3+2] * tse / sse
39 }
40 if pj >= 0 {
41 let sp: i64 = map[pj]
42 // A joint the source cannot resolve within its OWN joint spacing gets IDENTITY local
43 // articulation, so it rides its parent rigidly instead of being handed a rotation
44 // borrowed from whatever joint happened to be nearest. Fabricated articulation on an
45 // unresolvable joint is exactly the awful-motion class: it looks like animation and is
46 // not derived from anything.
47 aq[0] = 0; aq[1] = 0; aq[2] = 0; aq[3] = NRM_Q12
48 if bdst[j] <= bound {
49 nf_qconj(nrm_q(sD, sp), cq)
50 nf_qmul(cq, nrm_q(sD, s), aq)
51 }
52 nf_qmul(nrm_q(W, pj), aq, nrm_q(W, j))
53 nf_qrotv(nrm_q(W, pj), tbind[j*3] - tbind[pj*3], tbind[j*3+1] - tbind[pj*3+1], tbind[j*3+2] - tbind[pj*3+2], o3, scr)
54 P[j*3] = P[pj*3] + o3[0]
55 P[j*3+1] = P[pj*3+1] + o3[1]
56 P[j*3+2] = P[pj*3+2] + o3[2]
57 }
58 // q12 multiplication truncates; normalize before this joint becomes a parent.
59 nf_qnorm(nrm_q(W, j))
60 tdt[j*3] = P[j*3] - tbind[j*3]
61 tdt[j*3+1] = P[j*3+1] - tbind[j*3+1]
62 tdt[j*3+2] = P[j*3+2] - tbind[j*3+2]
63 j = j + 1
64 }
65
66 return 0
67}