code wiki / (root) / nx_nxa_affine_asset_t35.nx

nx_nxa_affine_asset_t35.nx source

↩ module page · 54 lines · 3522 B

1// Private AFM1 additive animation section; existing NXA container/skin ownership. 2import "nx_nxa_affine_palette_t34.nx" 3const AFM_VERSION:i64=1 4const AFM_HEADER:i64=8 5const AFM_COEFF:i64=12 6const AFM_TICKS_SECOND:i64=46186158000 7const AFM_BAD:i64=0-70 8const AFM_FUTURE:i64=0-71 9// Payload: version, stack ID, joints, frames, ticks/sec, length numerator, denominator, interpolation=1. 10// Joint source IDs in SKIN encounter order. Each frame: source ticks then column-major 3x4 Q20 deltas. 11// Translation coefficients already use VERT model units. Linear interpolation is explicit, never DQ projection. 12func afm_validate(p:*i64,words:i64,nj:i64)->i64{ 13 if words<AFM_HEADER{return AFM_BAD} 14 if p[0]>AFM_VERSION{return AFM_FUTURE} 15 if p[0]!=AFM_VERSION||p[2]!=nj||nj<=0||p[3]<2||p[4]!=AFM_TICKS_SECOND||p[5]<=0||p[6]<=0||p[7]!=1{return AFM_BAD} 16 if nj>(words-AFM_HEADER)/AFM_COEFF{return AFM_BAD} 17 let stride:i64=1+nj*AFM_COEFF;let base:i64=AFM_HEADER+nj 18 if base>words||(words-base)%stride!=0||(words-base)/stride!=p[3]{return AFM_BAD} 19 var j:i64=0;while j<nj{var k:i64=0;while k<j{if p[AFM_HEADER+j]==p[AFM_HEADER+k]{return AFM_BAD};k=k+1};j=j+1} 20 var frame:i64=0;var prev:i64=0-1 21 while frame<p[3]{let off:i64=base+frame*stride;if p[off]<=prev||p[off]>FBB_I64_MAX/FBB_Q{return AFM_BAD};prev=p[off] 22 j=0;while j<nj*AFM_COEFF{if p[off+1+j]<=0-AF_COMPONENT_LIMIT||p[off+1+j]>=AF_COMPONENT_LIMIT{return AFM_BAD};j=j+1};frame=frame+1} 23 return 0 24} 25func afm_sample(p:*i64,words:i64,nj:i64,ticks:i64,out:*i64,capacity:i64)->i64{ 26 let checked:i64=afm_validate(p,words,nj);if checked!=0{return checked} 27 if capacity<nj*16{return AF_CAPACITY} 28 let stride:i64=1+nj*AFM_COEFF;let base:i64=AFM_HEADER+nj 29 if ticks<p[base]||ticks>p[base+(p[3]-1)*stride]{return AF_TIMING} 30 var f:i64=0;while f+1<p[3]&&p[base+(f+1)*stride]<ticks{f=f+1} 31 let a:i64=base+f*stride;var b:i64=a;if f+1<p[3]{b=a+stride} 32 var blend:i64=0;if b!=a{blend=(ticks-p[a])*FBB_Q/(p[b]-p[a])} 33 var j:i64=0;while j<nj{var c:i64=0;while c<4{var r:i64=0;while r<3{ 34 let index:i64=1+j*AFM_COEFF+c*3+r 35 out[j*16+c*4+r]=p[a+index]+(p[b+index]-p[a+index])*blend/FBB_Q;r=r+1} 36 out[j*16+c*4+3]=0;c=c+1};out[j*16+15]=FBB_Q;j=j+1} 37 return 0 38} 39// Uses existing validated TOC/checksum owner and preserves all original payload/padding bytes. 40func afm_append(b:*u8,n:i64,p:*i64,words:i64,out:*u8,capacity:i64)->i64{ 41 if n<32||n%8!=0||words<0||capacity<n+32||words>(capacity-n-32)/8{return AF_CAPACITY} 42 if nxw_ranges_overlap(b as i64,n,out as i64,capacity)==1||nxw_ranges_overlap(p as i64,words*8,out as i64,capacity)==1{return NXW_ERR_OVERLAP} 43 let sk:i64=nxa_counted_section(b,n,nxa_tag4("SKEL" as *u8),8);if sk<0{return AFM_BAD} 44 let h:*i64=b as *i64;let validated:i64=afm_validate(p,words,h[sk]);if validated!=0{return validated} 45 let ns:i64=h[2];var i:i64=0 46 while i<ns{let e:i64=4+i*4;if nxa_section_entry(b,n,h[e])!=e||h[e]==nxa_tag4("AFM1" as *u8){return AFM_BAD};var j:i64=0;while j<i{let q:i64=4+j*4;if nxw_ranges_overlap(h[e+1],h[e+2]*8,h[q+1],h[q+2]*8)==1{return AFM_BAD};j=j+1};i=i+1} 47 let begin:i64=32+ns*32;i=begin;while i<n{out[i+32]=b[i];i=i+1} 48 let dst:*i64=out as *i64;dst[0]=nxa_magic();dst[1]=NXA_VER;dst[2]=ns+1 49 i=0;while i<ns{let e:i64=4+i*4;dst[e]=h[e];dst[e+1]=h[e+1]+32;dst[e+2]=h[e+2];dst[e+3]=h[e+3];i=i+1} 50 let at:i64=(n+32)/8;i=0;while i<words{dst[at+i]=p[i];i=i+1} 51 let e:i64=4+ns*4;dst[e]=nxa_tag4("AFM1" as *u8);dst[e+1]=n+32;dst[e+2]=words;dst[e+3]=nxa_check2(1,p,words) 52 dst[3]=nxa_check2(1,((out as i64)+32) as *i64,(ns+1)*4) 53 return n+32+words*8 54}