code wiki / (root) / nx_nxa_rig_motion_stream_t140.nx

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}