nx_nxa_rig_motion_stream_t140.nx source
↩ module page · 192 lines · 7677 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}
68
69import "nx_nxa_anim_lib.nx"
70
71// Continuous frame evaluation uses the same admitted map and source ANIM as the native gate.
72// Workspace and rows belong to one caller; no global palette, entity count or joint ceiling.
73func nrm_frame_words(ns:i64,nt:i64)->i64 {
74 if ns<1||nt<1 {return 0}
75 let max:i64=9223372036854775807/8
76 if nt>(max-64)/10 {return 0}
77 let rest:i64=max-64-nt*10
78 if ns>rest/7 {return 0}
79 return ns*7+nt*10+64
80}
81func nrm_basis(v:*i64,out:*i64,sax:*i64,ssg:*i64,tax:*i64,tsg:*i64)->i64 {
82 var c:i64=0
83 while c<3 {out[tax[c]]=v[sax[c]]*ssg[c]*tsg[c];c=c+1}
84 return 0
85}
86func nrm_basis_det(sax:*i64,ssg:*i64,tax:*i64,tsg:*i64,m:*i64)->i64 {
87 var i:i64=0
88 while i<9 {m[i]=0;i=i+1}
89 i=0
90 while i<3 {m[tax[i]*3+sax[i]]=ssg[i]*tsg[i];i=i+1}
91 return m[0]*(m[4]*m[8]-m[5]*m[7])-m[1]*(m[3]*m[8]-m[5]*m[6])+m[2]*(m[3]*m[7]-m[4]*m[6])
92}
93// External initialization must admit the ANIM index, signed-permutation frame and disjoint spans.
94// Time is source clip milliseconds; caller retains phase and actor/root policy.
95struct NrmFrameInput {
96 anim: *i64
97 tidx: *i64
98 order: *i64
99 ns: i64
100 nt: i64
101 sax: *i64
102 ssg: *i64
103 tax: *i64
104 tsg: *i64
105 map: *i64
106 tpar: *i64
107 tbind: *i64
108 bdst: *i64
109 bound: i64
110 sse: i64
111 tse: i64
112}
113func nrm_frame(frame:*NrmFrameInput,t:i64,rows:*i64,row_words:i64,work:*i64,work_words:i64)->i64 {
114 let anim:*i64=frame.anim
115 let tidx:*i64=frame.tidx
116 let order:*i64=frame.order
117 let ns:i64=frame.ns
118 let nt:i64=frame.nt
119 let sax:*i64=frame.sax
120 let ssg:*i64=frame.ssg
121 let tax:*i64=frame.tax
122 let tsg:*i64=frame.tsg
123 let map:*i64=frame.map
124 let tpar:*i64=frame.tpar
125 let tbind:*i64=frame.tbind
126 let bdst:*i64=frame.bdst
127 let bound:i64=frame.bound
128 let sse:i64=frame.sse
129 let tse:i64=frame.tse
130 let need:i64=nrm_frame_words(ns,nt)
131 if need==0||work_words<need {return 0-10}
132 if row_words/8<nt {return 0-11}
133 let q:*i64=work
134 let d:*i64=((q as i64)+ns*32) as *i64
135 let W:*i64=((d as i64)+ns*24) as *i64
136 let P:*i64=((W as i64)+nt*32) as *i64
137 let dt:*i64=((P as i64)+nt*24) as *i64
138 let scratch:*i64=((dt as i64)+nt*24) as *i64
139 let tmp:*i64=((scratch as i64)+64) as *i64
140 let matrix:*i64=((scratch as i64)+128) as *i64
141 let det:i64=nrm_basis_det(sax,ssg,tax,tsg,matrix)
142 var j:i64=0
143 while j<ns {
144 let qp:*i64=((q as i64)+j*32) as *i64
145 let dp:*i64=((d as i64)+j*24) as *i64
146 qp[0]=0;qp[1]=0;qp[2]=0;qp[3]=NRM_Q12
147 dp[0]=0;dp[1]=0;dp[2]=0
148 if tidx[j*2]>=0 {
149 if order[j]==1 {na_eval_track_ordered(anim,tidx[j*2],tidx[j*2+1],t,qp,dp,scratch)}
150 else {na_eval_track(anim,tidx[j*2],tidx[j*2+1],t,qp,dp,scratch)}
151 }
152 nrm_basis(qp,tmp,sax,ssg,tax,tsg)
153 qp[0]=tmp[0]*det;qp[1]=tmp[1]*det;qp[2]=tmp[2]*det
154 nrm_basis(dp,tmp,sax,ssg,tax,tsg)
155 dp[0]=tmp[0];dp[1]=tmp[1];dp[2]=tmp[2]
156 j=j+1
157 }
158 let rc:i64=nrm_compose(ns,nt,q,d,map,tpar,tbind,bdst,bound,sse,tse,W,P,dt,scratch)
159 if rc!=0 {return rc}
160 return nrm_affine_rows(nt,tbind,W,dt,rows,scratch)
161}
162
163func nrm_affine_rows(nj:i64,bind:*i64,q:*i64,dt:*i64,out:*i64,scr:*i64)->i64 {
164 // Bound both quaternion products and the three-term affine dual sum in i64.
165 let coordinate_limit:i64=9223372036854775807/(64*NRM_Q12)
166 var lane:i64=0
167 while lane<nj*4 {if q[lane]<0-NRM_Q12||q[lane]>NRM_Q12 {return 0-1}lane=lane+1}
168 lane=0
169 while lane<nj*3 {
170 if bind[lane]<0-coordinate_limit||bind[lane]>coordinate_limit {return 0-1}
171 if dt[lane]<0-coordinate_limit||dt[lane]>coordinate_limit {return 0-1}
172 lane=lane+1
173 }
174 let rot:*i64=scr
175 let work:*i64=((scr as i64)+32) as *i64
176 var j:i64=0
177 while j<nj {
178 let r:i64=j*4;let d:i64=nj*4+r
179 let qp:*i64=((q as i64)+r*8) as *i64
180 nf_qrotv(qp,bind[j*3],bind[j*3+1],bind[j*3+2],rot,work)
181 let tx:i64=bind[j*3]+dt[j*3]-rot[0]
182 let ty:i64=bind[j*3+1]+dt[j*3+1]-rot[1]
183 let tz:i64=bind[j*3+2]+dt[j*3+2]-rot[2]
184 out[r]=q[r];out[r+1]=q[r+1];out[r+2]=q[r+2];out[r+3]=q[r+3]
185 out[d]=(tx*q[r+3]+ty*q[r+2]-tz*q[r+1])/2
186 out[d+1]=(0-tx*q[r+2]+ty*q[r+3]+tz*q[r])/2
187 out[d+2]=(tx*q[r+1]-ty*q[r]+tz*q[r+3])/2
188 out[d+3]=(0-tx*q[r]-ty*q[r+1]-tz*q[r+2])/2
189 j=j+1
190 }
191 return 0
192}