code wiki / (root) / nx_gltf_anim.nx

nx_gltf_anim.nx source

↩ module page · 234 lines · 18843 B

1// nx_gltf_anim.nx -- ★SKINNED + ANIMATED glTF export: the being as a rigged, WALKING .glb (plays in any glTF viewer). 2// Reuses our FK rig: each mesh vertex is rigidly bound to its nearest part's deepest bone (joints supplied by the driver). 3// 9 joints (root + L/R shoulder,elbow,hip,knee) in a 2-level hierarchy; inverse-bind = translate(-pivot); a walk-cycle 4// animation = per-bone quaternion rotation keyframes about X (sagittal). Includes an integer float32 + fixed-point encoder. 5// GENERALIZED 2026-08-10 (ws=dance-motion): the writer is now gltf_anim_export_qtrack (caller-supplied full-quaternion 6// track, any key count, any animation name) and gltf_anim_export is the walk-table wrapper over it -- ONE writer, 7// two callers, so real mocap (nx_dance_emit) and the synthetic walk share every byte of container logic. 8// license_tier: ORIGINAL 9import "nx_syscalls.nx" 10import "nx_trimesh.nx" 11import "nx_itrig.nx" 12import "nx_gltf_mesh.nx" 13import "nx_glbnorm_lib.nx" 14const GA_MAGIC_3217: i64 = 3217 15const GA_MAGIC_25736: i64 = 25736 16const GA_MAGIC_4096: i64 = 4096 17const GA_MAGIC_1400: i64 = 1400 18const GA_MAGIC_1700: i64 = 1700 19const GA_MAGIC_2400: i64 = 2400 20const GA_MAGIC_8192: i64 = 8192 21const GA_MAGIC_2000000000: i64 = 2000000000 22const GA_MAGIC_32767: i64 = 32767 23const GA_MAGIC_65535: i64 = 65535 24const GA_MAGIC_65536: i64 = 65536 25 26// encode a fixed-point value n/2^fb as float32 bits (n integer). Divides by 2^fb = subtract fb from the exponent field. 27func ga_f32fx(n: i64, fb: i64) -> i64 { if n == 0 { return 0 } return gl_f32bits(n) - (fb<<23) } 28 29const GA_K: i64 = 9 // walk keyframes (dt = 1/8 s -> exact float times, 1.0 s cycle) 30const GA_NJ: i64 = 11 // joints: 0 root, 1 Lsh,2 Lel,3 Rsh,4 Rel,5 Lhip,6 Lkn,7 Rhip,8 Rkn, 9 Lank,10 Rank (ankle = the calf landmark, unanimated; debt 1785900993) 31 32// absolute bind pivots per joint (joint 0 = root at origin). 33// ★ROOT FIX 2026-08-04 (caught by the R3 closing measurement, debt 1785900993): this table was a BAKED 34// COPY of the atlas's base pivots, so a parametrically-reshaped body exported with the ORIGINAL skeleton -- 35// mesh moved, rig did not, and the retarget convergence test correctly refused to converge. The atlas is 36// the SSOT of its own rig: read ba_piv(). The literals below remain ONLY as the fallback for a caller that 37// exports before any atlas_build (pivots unallocated), and they are the atlas's own defaults. 38func ga_piv(j: i64, axis: i64) -> i64 { 39 if j > 0 { if ba_piv_ready() == 1 { return ba_piv(j, axis) } } 40 let px: *i64 = sys_mmap(GA_NJ*8) as *i64; let py: *i64 = sys_mmap(GA_NJ*8) as *i64; let pz: *i64 = sys_mmap(GA_NJ*8) as *i64 41 px[0]=0; py[0]=0; pz[0]=0 42 px[1]=0-185; py[1]=520; pz[1]=15 43 px[2]=0-205; py[2]=300; pz[2]=20 44 px[3]=185; py[3]=520; pz[3]=15 45 px[4]=205; py[4]=300; pz[4]=20 46 px[5]=0-88; py[5]=0-40; pz[5]=10 47 px[6]=0-84; py[6]=0-430; pz[6]=20 48 px[7]=88; py[7]=0-40; pz[7]=10 49 px[8]=84; py[8]=0-430; pz[8]=20 50 px[9]=0-84; py[9]=0-830; pz[9]=20 51 px[10]=84; py[10]=0-830; pz[10]=20 52 if axis == 0 { return px[j] } 53 if axis == 1 { return py[j] } 54 return pz[j] 55} 56// walk-cycle angle (it4096) for bone j (1..8) at keyframe k (0..K-1) 57func ga_angle(j: i64, k: i64) -> i64 { 58 let phase: i64 = k * GA_MAGIC_3217 // k * 2PI4096/(K-1) = k*GA_MAGIC_25736/8 59 let s: i64 = it_sin4096(phase) // [-GA_MAGIC_4096,GA_MAGIC_4096] 60 let c: i64 = it_cos4096(phase) 61 if j == 1 { return GA_MAGIC_1400*s/GA_MAGIC_4096 } // L shoulder (+arm swing) 62 if j == 3 { return 0-GA_MAGIC_1400*s/GA_MAGIC_4096 } // R shoulder (opposite) 63 if j == 2 { return 700 } // L elbow (slight constant bend) 64 if j == 4 { return 700 } // R elbow 65 if j == 5 { return 0-GA_MAGIC_1700*s/GA_MAGIC_4096 } // L hip (opposite the L arm) 66 if j == 7 { return GA_MAGIC_1700*s/GA_MAGIC_4096 } // R hip 67 if j == 6 { return GA_MAGIC_2400*(GA_MAGIC_4096-c)/GA_MAGIC_8192 } // L knee (bends 0..GA_MAGIC_2400 over the cycle) 68 if j == 8 { return GA_MAGIC_2400*(GA_MAGIC_4096+c)/GA_MAGIC_8192 } // R knee (opposite phase) 69 return 0 70} 71 72// THE writer: export the current trimesh as a skinned+animated .glb with a CALLER-SUPPLIED rotation track. 73// jarr[i] = joint index (0..8) vertex i is bound to. qtrk[((bn-1)*nk + k)*4 + c] = quaternion component c 74// (x,y,z,w) fx12 (x4096) for animated bone bn (1..8) at key k. nk = key count (times = k/8 s, exact f32). 75// aname = animation name bytes. -> total .glb length. 76func gltf_anim_export_qtrack(out: *u8, jarr: *i64, qtrk: *i64, nk: i64, aname: *u8) -> i64 { 77 let NV: i64 = tm_nv(); let NT: i64 = tm_nt(); let NI: i64 = NT*3 78 // ---- BIN layout ---- 79 let posOff: i64 = 0; let posLen: i64 = NV*12 80 let nrmOff: i64 = posLen; let nrmLen: i64 = NV*GL_VEC3_F32_BYTES 81 let colOff: i64 = gl_align4(nrmOff+nrmLen);let colLen: i64 = NV*4 82 let joOff: i64 = colOff+colLen; let joLen: i64 = NV*4 83 let weOff: i64 = joOff+joLen; let weLen: i64 = NV*4 84 let idxOff: i64 = weOff+weLen; let idxLen: i64 = NI*4 85 let ibmOff: i64 = idxOff+idxLen; let ibmLen: i64 = GA_NJ*64 86 let inOff: i64 = ibmOff+ibmLen; let inLen: i64 = nk*4 87 let outOff: i64 = inOff+inLen; let outLen1: i64 = nk*16 // per animated bone (8 bones) 88 let binLen: i64 = gl_align4(outOff + outLen1*8) 89 let bin: *u8 = sys_mmap(binLen + 64) 90 let vp: *i64 = sys_mmap(24) as *i64 91 let tt: *i64 = sys_mmap(24) as *i64 92 var minx: i64=GA_MAGIC_2000000000; var miny: i64=GA_MAGIC_2000000000; var minz: i64=GA_MAGIC_2000000000 93 var maxx: i64=0-GA_MAGIC_2000000000; var maxy: i64=0-GA_MAGIC_2000000000; var maxz: i64=0-GA_MAGIC_2000000000 94 var a: i64 = 0; var at: i64 = posOff 95 while a < NV { tm_vpos(a,vp); at=gl_f32(bin,at,vp[0]); at=gl_f32(bin,at,vp[1]); at=gl_f32(bin,at,vp[2]); if vp[0]<minx{minx=vp[0]} if vp[0]>maxx{maxx=vp[0]} if vp[1]<miny{miny=vp[1]} if vp[1]>maxy{maxy=vp[1]} if vp[2]<minz{minz=vp[2]} if vp[2]>maxz{maxz=vp[2]} a=a+1 } 96 // normals: {VEC3, FLOAT}, unit -- THE SHARED EMITTER, not a copy. This line used to be a 97 // character-for-character duplicate of nx_gltf_mesh's normalized-int16 loop, which is why the same two 98 // validator errors appeared in both exporters' output and had to be fixed twice. One owner now. 99 at = gl_emit_normals(bin, nrmOff, NV) 100 a=0; at=colOff 101 while a < NV { let col: i64=tm_getvcol(a); at=gl_u8(bin,at,col&255); at=gl_u8(bin,at,(col>>8)&255); at=gl_u8(bin,at,(col>>16)&255); at=gl_u8(bin,at,255); a=a+1 } 102 a=0; at=joOff 103 while a < NV { at=gl_u8(bin,at,jarr[a]); at=gl_u8(bin,at,0); at=gl_u8(bin,at,0); at=gl_u8(bin,at,0); a=a+1 } 104 a=0; at=weOff 105 while a < NV { at=gl_u8(bin,at,255); at=gl_u8(bin,at,0); at=gl_u8(bin,at,0); at=gl_u8(bin,at,0); a=a+1 } // normalized ubyte weight [1,0,0,0] 106 var tix: i64=0; at=idxOff 107 while tix < NT { tm_tget(tix,tt); at=gl_u32(bin,at,tt[0]); at=gl_u32(bin,at,tt[1]); at=gl_u32(bin,at,tt[2]); tix=tix+1 } 108 // inverse-bind matrices (column-major translate(-pivot)) 109 at=ibmOff; var j: i64=0 110 while j < GA_NJ { 111 at=gl_f32(bin,at,1); at=gl_f32(bin,at,0); at=gl_f32(bin,at,0); at=gl_f32(bin,at,0) 112 at=gl_f32(bin,at,0); at=gl_f32(bin,at,1); at=gl_f32(bin,at,0); at=gl_f32(bin,at,0) 113 at=gl_f32(bin,at,0); at=gl_f32(bin,at,0); at=gl_f32(bin,at,1); at=gl_f32(bin,at,0) 114 at=gl_f32(bin,at,0-ga_piv(j,0)); at=gl_f32(bin,at,0-ga_piv(j,1)); at=gl_f32(bin,at,0-ga_piv(j,2)); at=gl_f32(bin,at,1) 115 j=j+1 116 } 117 // animation input (times: k/8 seconds, exact) 118 at=inOff; var k: i64=0 119 while k < nk { at=gl_u32(bin,at,ga_f32fx(k,3)); k=k+1 } 120 // animation outputs: 8 bones (joints 1..8), nk quaternions each (full xyzw, fx12) 121 var bn: i64=1 122 while bn <= 8 { 123 k=0 124 while k < nk { 125 let qb: i64 = ((bn-1)*nk + k)*4 126 at=gl_u32(bin,at,ga_f32fx(qtrk[qb],12)) 127 at=gl_u32(bin,at,ga_f32fx(qtrk[qb+1],12)) 128 at=gl_u32(bin,at,ga_f32fx(qtrk[qb+2],12)) 129 at=gl_u32(bin,at,ga_f32fx(qtrk[qb+3],12)) 130 k=k+1 131 } 132 bn=bn+1 133 } 134 // ---- JSON ---- 135 let json: *u8 = sys_mmap(GA_MAGIC_65536) 136 var jj: i64 = 0 137 jj=jcat(json,jj,"{\"asset\":{\"version\":\"2.0\",\"generator\":\"nishi nx_gltf_anim\"},\"scene\":0,\"scenes\":[{\"nodes\":[0,1]}]," as *u8) 138 // nodes: 0 mesh(skin), 1 root, 2 Lsh,3 Lel,4 Rsh,5 Rel,6 Lhip,7 Lkn,8 Rhip,9 Rkn,10 Lank,11 Rank 139 jj=jcat(json,jj,"\"nodes\":[{\"mesh\":0,\"skin\":0},{\"name\":\"root\",\"children\":[2,4,6,8]}," as *u8) 140 // helper to write a joint node with translation (local = pivot - parentPivot) + optional child 141 // Lsh (parent root): T=pivot1 ; child Lel(3) 142 jj=jcat(json,jj,"{\"name\":\"Lsh\",\"children\":[3],\"translation\":["as *u8); jj=jnum(json,jj,ga_piv(1,0)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(1,1)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(1,2)); jj=jcat(json,jj,"]},"as *u8) 143 jj=jcat(json,jj,"{\"name\":\"Lel\",\"translation\":["as *u8); jj=jnum(json,jj,ga_piv(2,0)-ga_piv(1,0)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(2,1)-ga_piv(1,1)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(2,2)-ga_piv(1,2)); jj=jcat(json,jj,"]},"as *u8) 144 jj=jcat(json,jj,"{\"name\":\"Rsh\",\"children\":[5],\"translation\":["as *u8); jj=jnum(json,jj,ga_piv(3,0)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(3,1)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(3,2)); jj=jcat(json,jj,"]},"as *u8) 145 jj=jcat(json,jj,"{\"name\":\"Rel\",\"translation\":["as *u8); jj=jnum(json,jj,ga_piv(4,0)-ga_piv(3,0)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(4,1)-ga_piv(3,1)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(4,2)-ga_piv(3,2)); jj=jcat(json,jj,"]},"as *u8) 146 jj=jcat(json,jj,"{\"name\":\"Lhip\",\"children\":[7],\"translation\":["as *u8); jj=jnum(json,jj,ga_piv(5,0)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(5,1)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(5,2)); jj=jcat(json,jj,"]},"as *u8) 147 jj=jcat(json,jj,"{\"name\":\"Lkn\",\"children\":[10],\"translation\":["as *u8); jj=jnum(json,jj,ga_piv(6,0)-ga_piv(5,0)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(6,1)-ga_piv(5,1)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(6,2)-ga_piv(5,2)); jj=jcat(json,jj,"]},"as *u8) 148 jj=jcat(json,jj,"{\"name\":\"Rhip\",\"children\":[9],\"translation\":["as *u8); jj=jnum(json,jj,ga_piv(7,0)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(7,1)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(7,2)); jj=jcat(json,jj,"]},"as *u8) 149 jj=jcat(json,jj,"{\"name\":\"Rkn\",\"children\":[11],\"translation\":["as *u8); jj=jnum(json,jj,ga_piv(8,0)-ga_piv(7,0)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(8,1)-ga_piv(7,1)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(8,2)-ga_piv(7,2)); jj=jcat(json,jj,"]},"as *u8) 150 jj=jcat(json,jj,"{\"name\":\"Lank\",\"translation\":["as *u8); jj=jnum(json,jj,ga_piv(9,0)-ga_piv(6,0)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(9,1)-ga_piv(6,1)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(9,2)-ga_piv(6,2)); jj=jcat(json,jj,"]},"as *u8) 151 jj=jcat(json,jj,"{\"name\":\"Rank\",\"translation\":["as *u8); jj=jnum(json,jj,ga_piv(10,0)-ga_piv(8,0)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(10,1)-ga_piv(8,1)); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,ga_piv(10,2)-ga_piv(8,2)); jj=jcat(json,jj,"]}],"as *u8) 152 jj=jcat(json,jj,"\"skins\":[{\"inverseBindMatrices\":6,\"joints\":[1,2,3,4,5,6,7,8,9,10,11]}]," as *u8) 153 jj=jcat(json,jj,"\"meshes\":[{\"primitives\":[{\"attributes\":{\"POSITION\":0,\"NORMAL\":1,\"COLOR_0\":2,\"JOINTS_0\":3,\"WEIGHTS_0\":4},\"indices\":5,\"material\":0}]}]," as *u8) 154 jj=jcat(json,jj,"\"materials\":[{\"pbrMetallicRoughness\":{\"metallicFactor\":0,\"roughnessFactor\":1},\"doubleSided\":true}]," as *u8) 155 // accessors 0..15 156 jj=jcat(json,jj,"\"accessors\":[{\"bufferView\":0,\"componentType\":5126,\"count\":"as *u8); jj=jnum(json,jj,NV); jj=jcat(json,jj,",\"type\":\"VEC3\",\"min\":["as *u8); jj=jnum(json,jj,minx); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,miny); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,minz); jj=jcat(json,jj,"],\"max\":["as *u8); jj=jnum(json,jj,maxx); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,maxy); jj=jcat(json,jj,","as *u8); jj=jnum(json,jj,maxz); jj=jcat(json,jj,"]},"as *u8) 157 jj=jcat(json,jj,"{\"bufferView\":1,\"componentType\":5126,\"count\":"as *u8); jj=jnum(json,jj,NV); jj=jcat(json,jj,",\"type\":\"VEC3\"},"as *u8) 158 jj=jcat(json,jj,"{\"bufferView\":2,\"componentType\":5121,\"normalized\":true,\"count\":"as *u8); jj=jnum(json,jj,NV); jj=jcat(json,jj,",\"type\":\"VEC4\"},"as *u8) 159 jj=jcat(json,jj,"{\"bufferView\":3,\"componentType\":5121,\"count\":"as *u8); jj=jnum(json,jj,NV); jj=jcat(json,jj,",\"type\":\"VEC4\"},"as *u8) 160 jj=jcat(json,jj,"{\"bufferView\":4,\"componentType\":5121,\"normalized\":true,\"count\":"as *u8); jj=jnum(json,jj,NV); jj=jcat(json,jj,",\"type\":\"VEC4\"},"as *u8) 161 jj=jcat(json,jj,"{\"bufferView\":5,\"componentType\":5125,\"count\":"as *u8); jj=jnum(json,jj,NI); jj=jcat(json,jj,",\"type\":\"SCALAR\"},"as *u8) 162 jj=jcat(json,jj,"{\"bufferView\":6,\"componentType\":5126,\"count\":"as *u8); jj=jnum(json,jj,GA_NJ); jj=jcat(json,jj,",\"type\":\"MAT4\"},"as *u8) 163 // SPEC FIX 2026-08-25: the animation input accessor declared "max":[nk-1] -- the KEY INDEX -- while the 164 // values written are ga_f32fx(k,3), i.e. k/8 SECONDS. Every animated file therefore carried 165 // ACCESSOR_MAX_MISMATCH: the walk declared 8 against an actual 1, the dance declared 127 against 15.875. 166 // The bound is now rendered FROM THE VERY BITS THAT WERE WRITTEN, by the same expression that writes 167 // them, so it cannot drift again -- a declared bound recomputed in a different domain is exactly how 168 // this defect happened. 169 jj=jcat(json,jj,"{\"bufferView\":7,\"componentType\":5126,\"count\":"as *u8); jj=jnum(json,jj,nk); jj=jcat(json,jj,",\"type\":\"SCALAR\",\"min\":["as *u8); jj=gn_dec_f32(ga_f32fx(0,3), json, jj); jj=jcat(json,jj,"],\"max\":["as *u8); jj=gn_dec_f32(ga_f32fx(nk-1,3), json, jj); jj=jcat(json,jj,"]},"as *u8) 170 var acn: i64 = 8 171 while acn < 16 { if acn>8 { } jj=jcat(json,jj,"{\"bufferView\":"as *u8); jj=jnum(json,jj,acn); jj=jcat(json,jj,",\"componentType\":5126,\"count\":"as *u8); jj=jnum(json,jj,nk); jj=jcat(json,jj,",\"type\":\"VEC4\"}"as *u8); if acn<15 { jj=jcat(json,jj,","as *u8) } acn=acn+1 } 172 jj=jcat(json,jj,"]," as *u8) 173 // bufferViews 0..15 174 jj=jcat(json,jj,"\"bufferViews\":[" as *u8) 175 jj=jcat(json,jj,"{\"buffer\":0,\"byteOffset\":0,\"byteLength\":"as *u8); jj=jnum(json,jj,posLen); jj=jcat(json,jj,"},"as *u8) 176 jj=jcat(json,jj,"{\"buffer\":0,\"byteOffset\":"as *u8); jj=jnum(json,jj,nrmOff); jj=jcat(json,jj,",\"byteLength\":"as *u8); jj=jnum(json,jj,nrmLen); jj=jcat(json,jj,"},"as *u8) 177 jj=jcat(json,jj,"{\"buffer\":0,\"byteOffset\":"as *u8); jj=jnum(json,jj,colOff); jj=jcat(json,jj,",\"byteLength\":"as *u8); jj=jnum(json,jj,colLen); jj=jcat(json,jj,"},"as *u8) 178 jj=jcat(json,jj,"{\"buffer\":0,\"byteOffset\":"as *u8); jj=jnum(json,jj,joOff); jj=jcat(json,jj,",\"byteLength\":"as *u8); jj=jnum(json,jj,joLen); jj=jcat(json,jj,"},"as *u8) 179 jj=jcat(json,jj,"{\"buffer\":0,\"byteOffset\":"as *u8); jj=jnum(json,jj,weOff); jj=jcat(json,jj,",\"byteLength\":"as *u8); jj=jnum(json,jj,weLen); jj=jcat(json,jj,"},"as *u8) 180 jj=jcat(json,jj,"{\"buffer\":0,\"byteOffset\":"as *u8); jj=jnum(json,jj,idxOff); jj=jcat(json,jj,",\"byteLength\":"as *u8); jj=jnum(json,jj,idxLen); jj=jcat(json,jj,"},"as *u8) 181 jj=jcat(json,jj,"{\"buffer\":0,\"byteOffset\":"as *u8); jj=jnum(json,jj,ibmOff); jj=jcat(json,jj,",\"byteLength\":"as *u8); jj=jnum(json,jj,ibmLen); jj=jcat(json,jj,"},"as *u8) 182 jj=jcat(json,jj,"{\"buffer\":0,\"byteOffset\":"as *u8); jj=jnum(json,jj,inOff); jj=jcat(json,jj,",\"byteLength\":"as *u8); jj=jnum(json,jj,inLen); jj=jcat(json,jj,"}"as *u8) 183 var bvn: i64 = 0 184 while bvn < 8 { jj=jcat(json,jj,",{\"buffer\":0,\"byteOffset\":"as *u8); jj=jnum(json,jj,outOff+bvn*outLen1); jj=jcat(json,jj,",\"byteLength\":"as *u8); jj=jnum(json,jj,outLen1); jj=jcat(json,jj,"}"as *u8); bvn=bvn+1 } 185 jj=jcat(json,jj,"]," as *u8) 186 // animation: 8 samplers + 8 channels (bone j -> node j+1, path rotation) 187 jj=jcat(json,jj,"\"animations\":[{\"name\":\"" as *u8) 188 jj=jcat(json,jj,aname) 189 jj=jcat(json,jj,"\",\"samplers\":[" as *u8) 190 var sn: i64 = 0 191 while sn < 8 { if sn>0 { jj=jcat(json,jj,","as *u8) } jj=jcat(json,jj,"{\"input\":7,\"interpolation\":\"LINEAR\",\"output\":"as *u8); jj=jnum(json,jj,8+sn); jj=jcat(json,jj,"}"as *u8); sn=sn+1 } 192 jj=jcat(json,jj,"],\"channels\":[" as *u8) 193 sn = 0 194 while sn < 8 { if sn>0 { jj=jcat(json,jj,","as *u8) } jj=jcat(json,jj,"{\"sampler\":"as *u8); jj=jnum(json,jj,sn); jj=jcat(json,jj,",\"target\":{\"node\":"as *u8); jj=jnum(json,jj,sn+2); jj=jcat(json,jj,",\"path\":\"rotation\"}}"as *u8); sn=sn+1 } 195 jj=jcat(json,jj,"]}]," as *u8) 196 jj=jcat(json,jj,"\"buffers\":[{\"byteLength\":" as *u8); jj=jnum(json,jj,binLen); jj=jcat(json,jj,"}]}" as *u8) 197 let jsonLen: i64 = jj 198 let jsonPad: i64 = gl_align4(jsonLen) 199 // ---- GLB ---- 200 var o: i64 = 0 201 o=gl_str(out,o,"glTF"as *u8); o=gl_u32(out,o,2) 202 let total: i64 = 12+8+jsonPad+8+binLen 203 o=gl_u32(out,o,total); o=gl_u32(out,o,jsonPad); o=gl_str(out,o,"JSON"as *u8) 204 var q: i64=0 205 while q<jsonLen { out[o]=json[q]; o=o+1; q=q+1 } 206 while q<jsonPad { out[o]=32 as u8; o=o+1; q=q+1 } 207 o=gl_u32(out,o,binLen); o=gl_u8(out,o,66); o=gl_u8(out,o,73); o=gl_u8(out,o,78); o=gl_u8(out,o,0) 208 q=0 209 while q<binLen { out[o]=bin[q]; o=o+1; q=q+1 } 210 return o 211} 212 213// the walk export, unchanged contract: builds the walk-cycle quaternion table from ga_angle and hands it 214// to the shared writer. Values are the exact ones the pre-refactor writer computed inline (sin/cos of the 215// same half-angles), so the emitted .glb is byte-identical -- proven by the gate artifact hash. 216func gltf_anim_export(out: *u8, jarr: *i64) -> i64 { 217 let qtrk: *i64 = sys_mmap(8*GA_K*4*8) as *i64 218 var bn: i64 = 1 219 while bn <= 8 { 220 var k: i64 = 0 221 while k < GA_K { 222 let ang: i64 = ga_angle(bn,k) 223 let half: i64 = ang/2 224 let qb: i64 = ((bn-1)*GA_K + k)*4 225 qtrk[qb] = it_sin4096(half) 226 qtrk[qb+1] = 0 227 qtrk[qb+2] = 0 228 qtrk[qb+3] = it_cos4096(half) 229 k=k+1 230 } 231 bn=bn+1 232 } 233 return gltf_anim_export_qtrack(out, jarr, qtrk, GA_K, "walk" as *u8) 234}