code wiki / (root) / nx_gltf_accessor_candidate_t181.nx

nx_gltf_accessor_candidate_t181.nx source

↩ module page · 1018 lines · 48569 B

1// nx_gltf_load.nx -- ★sovereign glTF (.glb) LOADER: parse a binary glTF back into nx_trimesh buffers, so the Nishi stack 2// (Nishi OS / Nishi browser) can NATIVELY VIEW a .glb -- and so we can render our own exports on our own renderer to prove 3// the round-trip (the honest-scope gap: "eyeball the model sovereignly"). Inverse of nx_gltf_mesh: includes an integer 4// IEEE-754 float32 DECODER. Loads POSITION + COLOR_0 + indices from our exporter's layout; recomputes normals. license_tier: ORIGINAL 5import "nx_syscalls.nx" 6import "nx_trimesh.nx" 7const K_MAGIC_8388607: i64 = 8388607 8const K_MAGIC_8388608: i64 = 8388608 9const K_MAGIC_65536: i64 = 65536 10const K_MAGIC_2147483647: i64 = 2147483647 11const K_MAGIC_2000000000: i64 = 2000000000 12const K_MAGIC_1024: i64 = 1024 13const K_MAGIC_5123: i64 = 5123 14const K_MAGIC_5125: i64 = 5125 15 16// IEEE-754 float32 bit pattern -> integer (inverse of gl_f32bits; exact for the integer coordinates we export) 17func gll_f32i(bits: i64) -> i64 { 18 if bits == 0 { return 0 } 19 let sign: i64 = (bits>>31)&1 20 let exp: i64 = (bits>>23)&255 21 let mant: i64 = bits & K_MAGIC_8388607 22 if exp == 0 { return 0 } 23 let e: i64 = exp - 127 24 if e < 0-30 { return 0 } // |v| < 2^-30 rounds to integer 0 (also guards shift-by->63 UB) 25 let m: i64 = K_MAGIC_8388608 + mant // 1.mant scaled by 2^23 26 var val: i64 = 0 27 if e >= 23 { val = m << (e-23) } else { val = m >> (23-e) } 28 if sign == 1 { val = 0-val } 29 return val 30} 31func gll_ru8(buf: *u8, at: i64) -> i64 { return buf[at] as i64 } 32func gll_ru32(buf: *u8, at: i64) -> i64 { return (buf[at] as i64) + ((buf[at+1] as i64)<<8) + ((buf[at+2] as i64)<<16) + ((buf[at+3] as i64)<<24) } 33func gll_align4(x: i64) -> i64 { return (x+3)/4*4 } 34func gll_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 35// ★D1 FAIL-CLOSED (debt 1785902685): a loader that cannot parse an input must REFUSE with a NAMED 36// reason and never read outside the mapped file. GLL_WHY carries the last refusal for callers/gates. 37static GLL_WHY: i64 38func gltf_load_why() -> *u8 { if GLL_WHY == 0 { return "ok" as *u8 } return GLL_WHY as *u8 } 39func gll_refuse(why: *u8) -> i64 { 40 GLL_WHY = why as i64 41 let pre: *u8 = "gltf-load REFUSE: " as *u8 42 sys_write(1, pre, gll_slen(pre)) 43 sys_write(1, why, gll_slen(why)) 44 let nl: *u8 = sys_mmap(1) as *u8 45 nl[0] = 10 as u8 46 sys_write(1, nl, 1) 47 return 0 48} 49// every byte of [off, off+need) must lie inside the mapped file 50func gll_inb(off: i64, need: i64, size: i64) -> i64 { 51 if off < 0 { return 0 } 52 if need < 0 { return 0 } 53 if off > size { return 0 } 54 if off + need > size { return 0 } 55 return 1 56} 57// all three vertex indices must name a loaded vertex 58func gll_idx3(a: i64, b: i64, c: i64, nv: i64) -> i64 { 59 if a < 0 { return 0 } 60 if a >= nv { return 0 } 61 if b < 0 { return 0 } 62 if b >= nv { return 0 } 63 if c < 0 { return 0 } 64 if c >= nv { return 0 } 65 return 1 66} 67// find the n-th integer that follows `needle` within buf[start,end) 68func gll_nth_num(buf: *u8, start: i64, end: i64, needle: *u8, n: i64) -> i64 { 69 let m: i64 = gll_slen(needle) 70 var found: i64 = 0 71 var i: i64 = start 72 while i+m <= end { 73 var k: i64 = 0 74 while k < m { if (buf[i+k] as i64) != (needle[k] as i64) { k = m+9 } else { k = k+1 } } 75 if k == m { 76 found = found + 1 77 if found == n { 78 var p: i64 = i+m 79 while (buf[p] as i64) == 32 { p = p+1 } 80 var neg: i64 = 0 81 if (buf[p] as i64) == 45 { neg = 1; p = p+1 } 82 var v: i64 = 0 83 while p < end { let c: i64 = buf[p] as i64; if c >= 48 { if c <= 57 { v = v*10 + (c-48); p = p+1 } else { p = end } } else { p = end } } 84 if neg == 1 { v = 0-v } 85 return v 86 } 87 i = i + m 88 } else { i = i + 1 } 89 } 90 return 0-1 91} 92 93// load a .glb file into the trimesh buffers. returns 1 on success, 0 on failure. base_col used where colours absent. 94func gltf_load(path: *u8) -> i64 { 95 let szp: *i64 = sys_mmap(16) as *i64 96 let buf: *u8 = sys_read_file(path, szp) 97 let size: i64 = szp[0] 98 GLL_WHY = 0 99 if (buf as i64) == 0 { return gll_refuse("file-unreadable" as *u8) } 100 if size < 20 { return gll_refuse("file-too-short" as *u8) } 101 if (buf[0] as i64) != 103 { return gll_refuse("not-glb-magic" as *u8) } // 'g' 102 if (buf[1] as i64) != 108 { return gll_refuse("not-glb-magic" as *u8) } // 'l' 103 if (buf[2] as i64) != 84 { return gll_refuse("not-glb-magic" as *u8) } // 'T' 104 if (buf[3] as i64) != 70 { return gll_refuse("not-glb-magic" as *u8) } // 'F' 105 let jsonLen: i64 = gll_ru32(buf, 12) 106 let jsonStart: i64 = 20 107 let binStart: i64 = 20 + jsonLen + 8 108 if gll_inb(jsonStart, jsonLen, size) == 0 { return gll_refuse("json-chunk-oob" as *u8) } 109 if binStart > size { return gll_refuse("bin-chunk-missing" as *u8) } 110 // NV = first "count" (POSITION accessor), NI = fourth "count" (indices accessor) 111 let NV: i64 = gll_nth_num(buf, jsonStart, jsonStart+jsonLen, "\"count\":" as *u8, 1) 112 let NI: i64 = gll_nth_num(buf, jsonStart, jsonStart+jsonLen, "\"count\":" as *u8, 4) 113 if NV <= 0 { return gll_refuse("position-count-absent" as *u8) } 114 if NI <= 0 { return gll_refuse("index-count-absent" as *u8) } 115 // derive our exporter's BIN layout 116 let posOff: i64 = binStart 117 let colOff: i64 = binStart + gll_align4(NV*12 + NV*6) 118 let idxOff: i64 = colOff + NV*4 119 if gll_inb(posOff, NV*12, size) == 0 { return gll_refuse("legacy-position-oob" as *u8) } 120 if gll_inb(colOff, NV*4, size) == 0 { return gll_refuse("legacy-colour-oob" as *u8) } 121 if gll_inb(idxOff, NI*4, size) == 0 { return gll_refuse("legacy-index-oob" as *u8) } 122 tm_reset() 123 var i: i64 = 0 124 while i < NV { 125 let x: i64 = gll_f32i(gll_ru32(buf, posOff+i*12)) 126 let y: i64 = gll_f32i(gll_ru32(buf, posOff+i*12+4)) 127 let z: i64 = gll_f32i(gll_ru32(buf, posOff+i*12+8)) 128 let idx: i64 = tm_vert(x, y, z) 129 let cr: i64 = gll_ru8(buf, colOff+i*4) 130 let cg: i64 = gll_ru8(buf, colOff+i*4+1) 131 let cb: i64 = gll_ru8(buf, colOff+i*4+2) 132 tm_vcol(idx, cr + cg*256 + cb*K_MAGIC_65536) 133 i = i + 1 134 } 135 let NTRI: i64 = NI/3 136 var t: i64 = 0 137 while t < NTRI { 138 let a: i64 = gll_ru32(buf, idxOff + (t*3)*4) 139 let b: i64 = gll_ru32(buf, idxOff + (t*3+1)*4) 140 let c: i64 = gll_ru32(buf, idxOff + (t*3+2)*4) 141 if gll_idx3(a, b, c, NV) == 0 { return gll_refuse("index-out-of-range" as *u8) } 142 tm_tri(a, b, c, 200+200*256+200*K_MAGIC_65536) 143 t = t + 1 144 } 145 if tm_ovf() != 0 { return gll_refuse("mesh-capacity-overflow" as *u8) } 146 tm_compute_normals() 147 return 1 148} 149 150// ================================================================================================================ 151// ★STANDARD-glTF loader (2026-07-11, the graphics-MSU rung): parse REAL third-party .glb (Khronos conformance 152// assets), not just our own exporter's layout -- proper JSON accessor/bufferView walking (byteOffset defaults, 153// byteStride, u16/u32 indices, float32 positions/normals, baseColorFactor). Needles searched WITHOUT quote chars 154// (avoids the "-in-literal hazard); slices keep keys unambiguous. license part of nx_gltf_load. ================== 155func gls_ru16(buf: *u8, at: i64) -> i64 { return (buf[at] as i64) + ((buf[at+1] as i64)<<8) } 156// float32 bits -> integer of (value * 2^k) by exponent shift; zero-guarded. 157func gls_f32s(bits: i64, k: i64) -> i64 { if (bits & K_MAGIC_2147483647) == 0 { return 0 } return gll_f32i(bits + (k<<23)) } 158func gls_find(buf: *u8, end: i64, from: i64, needle: *u8) -> i64 { 159 let m: i64 = gll_slen(needle) 160 var i: i64 = from 161 while i + m <= end { 162 var k: i64 = 0 163 while k < m { if (buf[i+k] as i64) != (needle[k] as i64) { k = m + 9 } else { k = k + 1 } } 164 if k == m { return i } 165 i = i + 1 166 } 167 return 0-1 168} 169// integer value right after `key` (skips to ':', then spaces). -1 if absent in [s,e). 170func gls_key_int(buf: *u8, s: i64, e: i64, key: *u8) -> i64 { 171 let p0: i64 = gls_find(buf, e, s, key) 172 if p0 < 0 { return 0-1 } 173 var p: i64 = p0 + gll_slen(key) 174 var col: i64 = 0-1 175 while p < e { if (buf[p] as i64) == 58 { col = p; p = e } else { p = p + 1 } } 176 if col < 0 { return 0-1 } 177 p = col + 1 178 var sk: i64 = 1 179 while sk == 1 { if p < e { if (buf[p] as i64) == 32 { p = p + 1 } else { sk = 0 } } else { sk = 0 } } 180 var v: i64 = 0 181 var any: i64 = 0 182 var go: i64 = 1 183 while go == 1 { 184 if p < e { 185 let c: i64 = buf[p] as i64 186 var isd: i64 = 0 187 if c >= 48 { if c <= 57 { isd = 1 } } 188 if isd == 1 { v = v*10 + (c-48); any = 1; p = p + 1 } else { go = 0 } 189 } else { go = 0 } 190 } 191 if any == 0 { return 0-1 } 192 return v 193} 194// bounds of the k-th object in the JSON array named `arrName`: out[0]=pos of '{', out[1]=pos after matching '}'. 195func gls_arr_obj(buf: *u8, end: i64, arrName: *u8, k: i64, out: *i64) -> i64 { 196 return gls_arr_obj_from(buf, end, 0, arrName, k, out) 197} 198// same, but the array NAME is searched from `from` -- for arrays nested inside one object's bounds 199// (each mesh carries its OWN "primitives" array; D2 multi-mesh concat needs the mesh-local one) 200func gls_arr_obj_from(buf: *u8, end: i64, from: i64, arrName: *u8, k: i64, out: *i64) -> i64 { 201 let an: i64 = gls_find(buf, end, from, arrName) 202 if an < 0 { return 0 } 203 var p: i64 = an 204 var br: i64 = 0-1 205 while p < end { if (buf[p] as i64) == 91 { br = p; p = end } else { p = p + 1 } } 206 if br < 0 { return 0 } 207 var depth: i64 = 0 208 var idx: i64 = 0-1 209 var objS: i64 = 0-1 210 p = br + 1 211 while p < end { 212 let c: i64 = buf[p] as i64 213 if c == 123 { 214 if depth == 0 { idx = idx + 1; if idx == k { objS = p } } 215 depth = depth + 1 216 } 217 if c == 125 { 218 depth = depth - 1 219 if depth == 0 { if idx == k { out[0] = objS; out[1] = p + 1; return 1 } } 220 } 221 if c == 93 { if depth == 0 { p = end } } 222 p = p + 1 223 } 224 return 0 225} 226// accessor A -> out: [bufferView, accessorByteOffset, componentType, count] 227func gls_accessor(buf: *u8, jE: i64, A: i64, out: *i64) -> i64 { 228 let ob: *i64 = sys_mmap(16) as *i64 229 if gls_arr_obj(buf, jE, "accessors" as *u8, A, ob) == 0 { return 0 } 230 var bv: i64 = gls_key_int(buf, ob[0], ob[1], "bufferView" as *u8) 231 if bv < 0 { bv = 0 } 232 var bo: i64 = gls_key_int(buf, ob[0], ob[1], "byteOffset" as *u8) 233 if bo < 0 { bo = 0 } 234 out[0] = bv 235 out[1] = bo 236 out[2] = gls_key_int(buf, ob[0], ob[1], "componentType" as *u8) 237 out[3] = gls_key_int(buf, ob[0], ob[1], "count" as *u8) 238 return 1 239} 240// bufferView B -> out: [byteOffset, byteStride] 241func gls_bufview(buf: *u8, jE: i64, B: i64, out: *i64) -> i64 { 242 let ob: *i64 = sys_mmap(16) as *i64 243 if gls_arr_obj(buf, jE, "bufferViews" as *u8, B, ob) == 0 { return 0 } 244 var bo: i64 = gls_key_int(buf, ob[0], ob[1], "byteOffset" as *u8) 245 if bo < 0 { bo = 0 } 246 var st: i64 = gls_key_int(buf, ob[0], ob[1], "byteStride" as *u8) 247 if st < 0 { st = 0 } 248 out[0] = bo 249 out[1] = st 250 return 1 251} 252// ★locate the FIRST embedded image's bytes in a .glb: out[0]=absolute offset, out[1]=length. 1 ok. 253func gls_image_slice(buf: *u8, size: i64, out: *i64) -> i64 { 254 let jsonLen: i64 = gll_ru32(buf, 12) 255 let jE: i64 = 20 + jsonLen 256 let binStart: i64 = jE + 8 257 let ob: *i64 = sys_mmap(16) as *i64 258 if gls_arr_obj(buf, jE, "images" as *u8, 0, ob) == 0 { return 0 } 259 let bvi: i64 = gls_key_int(buf, ob[0], ob[1], "bufferView" as *u8) 260 if bvi < 0 { return 0 } 261 if gls_arr_obj(buf, jE, "bufferViews" as *u8, bvi, ob) == 0 { return 0 } 262 var bo: i64 = gls_key_int(buf, ob[0], ob[1], "byteOffset" as *u8) 263 if bo < 0 { bo = 0 } 264 let bl: i64 = gls_key_int(buf, ob[0], ob[1], "byteLength" as *u8) 265 if bl <= 0 { return 0 } 266 out[0] = binStart + bo 267 out[1] = bl 268 return 1 269} 270// baseColorFactor -> out r,g,b (0..255). 1 if present (parses decimals like 0.800000011920929, keeps 3 frac digits). 271func gls_basecolor(buf: *u8, jE: i64, out: *i64) -> i64 { return gls_basecolor_in(buf, 0, jE, out) } 272// bounded variant: search only [s, jE) -- one MATERIAL object's bounds (D2 per-primitive colour) 273func gls_basecolor_in(buf: *u8, s: i64, jE: i64, out: *i64) -> i64 { 274 var p: i64 = gls_find(buf, jE, s, "baseColorFactor" as *u8) 275 if p < 0 { return 0 } 276 var ch: i64 = 0 277 while ch < 3 { 278 var go: i64 = 1 279 while go == 1 { 280 if p >= jE { go = 0 } else { 281 let c: i64 = buf[p] as i64 282 var isd: i64 = 0 283 if c >= 48 { if c <= 57 { isd = 1 } } 284 if isd == 1 { go = 0 } else { p = p + 1 } 285 } 286 } 287 var ip: i64 = 0 288 go = 1 289 while go == 1 { 290 if p >= jE { go = 0 } else { 291 let c: i64 = buf[p] as i64 292 var isd: i64 = 0 293 if c >= 48 { if c <= 57 { isd = 1 } } 294 if isd == 1 { ip = ip*10 + (c-48); p = p + 1 } else { go = 0 } 295 } 296 } 297 var milli: i64 = ip * 1000 298 var dotp: i64 = 0 299 if p < jE { if (buf[p] as i64) == 46 { dotp = 1 } } 300 if dotp == 1 { 301 p = p + 1 302 var fd: i64 = 0 303 var mul: i64 = 100 304 go = 1 305 while go == 1 { 306 if p >= jE { go = 0 } else { 307 let c: i64 = buf[p] as i64 308 var isd: i64 = 0 309 if c >= 48 { if c <= 57 { isd = 1 } } 310 if isd == 1 { 311 if fd < 3 { milli = milli + (c-48)*mul; mul = mul/10; fd = fd + 1 } 312 p = p + 1 313 } else { go = 0 } 314 } 315 } 316 } 317 if milli > 1000 { milli = 1000 } 318 out[ch] = milli * 255 / 1000 319 ch = ch + 1 320 } 321 return 1 322} 323// ★load a REAL .glb (Khronos-conformant): parse primitive/accessors/bufferViews properly, normalize the model to 324// `target` extent centered at origin (integer), per-vertex normals from the file (or recomputed), baseColorFactor. 325func gltf_load_std(path: *u8, target: i64) -> i64 { 326 let szp: *i64 = sys_mmap(16) as *i64 327 let buf: *u8 = sys_read_file(path, szp) 328 let size: i64 = szp[0] 329 GLL_WHY = 0 330 if (buf as i64) == 0 { return gll_refuse("file-unreadable" as *u8) } 331 if size < 20 { return gll_refuse("file-too-short" as *u8) } 332 if (buf[0] as i64) != 103 { return gll_refuse("not-glb-magic" as *u8) } 333 if (buf[1] as i64) != 108 { return gll_refuse("not-glb-magic" as *u8) } 334 if (buf[2] as i64) != 84 { return gll_refuse("not-glb-magic" as *u8) } 335 if (buf[3] as i64) != 70 { return gll_refuse("not-glb-magic" as *u8) } 336 let jsonLen: i64 = gll_ru32(buf, 12) 337 let jE: i64 = 20 + jsonLen 338 let binStart: i64 = jE + 8 339 if gll_inb(20, jsonLen, size) == 0 { return gll_refuse("json-chunk-oob" as *u8) } 340 if binStart > size { return gll_refuse("bin-chunk-missing" as *u8) } 341 let pr: *i64 = sys_mmap(16) as *i64 342 if gls_arr_obj(buf, jE, "primitives" as *u8, 0, pr) == 0 { return gll_refuse("no-primitives" as *u8) } 343 let posA: i64 = gls_key_int(buf, pr[0], pr[1], "POSITION" as *u8) 344 let nrmA: i64 = gls_key_int(buf, pr[0], pr[1], "NORMAL" as *u8) 345 let uvA: i64 = gls_key_int(buf, pr[0], pr[1], "TEXCOORD_0" as *u8) 346 let idxA: i64 = gls_key_int(buf, pr[0], pr[1], "indices" as *u8) 347 if posA < 0 { return gll_refuse("no-POSITION-attribute" as *u8) } 348 if idxA < 0 { return gll_refuse("no-indices" as *u8) } 349 let ac: *i64 = sys_mmap(32) as *i64 350 let bv: *i64 = sys_mmap(16) as *i64 351 if gls_accessor(buf, jE, posA, ac) == 0 { return gll_refuse("position-accessor-unparsable" as *u8) } 352 let NV: i64 = ac[3] 353 if NV <= 0 { return gll_refuse("position-count-absent" as *u8) } 354 if NV > tm_vcap() { return gll_refuse("mesh-capacity-overflow" as *u8) } 355 if gls_bufview(buf, jE, ac[0], bv) == 0 { return gll_refuse("position-bufferview-unparsable" as *u8) } 356 let posBase: i64 = binStart + bv[0] + ac[1] 357 var posStride: i64 = bv[1] 358 if posStride == 0 { posStride = 12 } 359 if posStride < 12 { return gll_refuse("position-stride-under-12" as *u8) } 360 if gll_inb(posBase, (NV-1)*posStride + 12, size) == 0 { return gll_refuse("position-accessor-oob" as *u8) } 361 // pass 1: bbox over raw ints (float x256) 362 var minx: i64 = K_MAGIC_2000000000; var miny: i64 = K_MAGIC_2000000000; var minz: i64 = K_MAGIC_2000000000 363 var maxx: i64 = 0-K_MAGIC_2000000000; var maxy: i64 = 0-K_MAGIC_2000000000; var maxz: i64 = 0-K_MAGIC_2000000000 364 var i: i64 = 0 365 while i < NV { 366 let x: i64 = gls_f32s(gll_ru32(buf, posBase+i*posStride), 8) 367 let y: i64 = gls_f32s(gll_ru32(buf, posBase+i*posStride+4), 8) 368 let z: i64 = gls_f32s(gll_ru32(buf, posBase+i*posStride+8), 8) 369 if x<minx {minx=x} if x>maxx {maxx=x} 370 if y<miny {miny=y} if y>maxy {maxy=y} 371 if z<minz {minz=z} if z>maxz {maxz=z} 372 i = i + 1 373 } 374 var ext: i64 = maxx-minx 375 if maxy-miny > ext { ext = maxy-miny } 376 if maxz-minz > ext { ext = maxz-minz } 377 if ext < 1 { ext = 1 } 378 let s1024: i64 = target*K_MAGIC_1024/ext 379 let cx: i64 = (minx+maxx)/2; let cy: i64 = (miny+maxy)/2; let cz: i64 = (minz+maxz)/2 380 // normals accessor (optional) 381 var nBase: i64 = 0 382 var nStride: i64 = 12 383 var hasN: i64 = 0 384 if nrmA >= 0 { 385 if gls_accessor(buf, jE, nrmA, ac) == 1 { 386 if gls_bufview(buf, jE, ac[0], bv) == 1 { 387 nBase = binStart + bv[0] + ac[1] 388 if bv[1] != 0 { nStride = bv[1] } 389 hasN = 1 390 } 391 } 392 } 393 if hasN == 1 { if nStride < 12 { return gll_refuse("normal-stride-under-12" as *u8) } } 394 if hasN == 1 { if gll_inb(nBase, (NV-1)*nStride + 12, size) == 0 { return gll_refuse("normal-accessor-oob" as *u8) } } 395 // UV accessor (optional; Q16) 396 var uBase: i64 = 0 397 var uStride: i64 = 8 398 var hasUV: i64 = 0 399 if uvA >= 0 { 400 if gls_accessor(buf, jE, uvA, ac) == 1 { 401 if gls_bufview(buf, jE, ac[0], bv) == 1 { 402 uBase = binStart + bv[0] + ac[1] 403 if bv[1] != 0 { uStride = bv[1] } 404 hasUV = 1 405 } 406 } 407 } 408 if hasUV == 1 { if uStride < 8 { return gll_refuse("uv-stride-under-8" as *u8) } } 409 if hasUV == 1 { if gll_inb(uBase, (NV-1)*uStride + 8, size) == 0 { return gll_refuse("uv-accessor-oob" as *u8) } } 410 // colour 411 let colv: *i64 = sys_mmap(24) as *i64 412 var col: i64 = 205 + 205*256 + 205*K_MAGIC_65536 413 if gls_basecolor(buf, jE, colv) == 1 { col = colv[0] + colv[1]*256 + colv[2]*K_MAGIC_65536 } 414 // pass 2: emit 415 tm_reset() 416 i = 0 417 while i < NV { 418 let x: i64 = (gls_f32s(gll_ru32(buf, posBase+i*posStride), 8) - cx)*s1024/K_MAGIC_1024 419 let y: i64 = (gls_f32s(gll_ru32(buf, posBase+i*posStride+4), 8) - cy)*s1024/K_MAGIC_1024 420 let z: i64 = (gls_f32s(gll_ru32(buf, posBase+i*posStride+8), 8) - cz)*s1024/K_MAGIC_1024 421 if hasN == 1 { 422 let nx: i64 = gls_f32s(gll_ru32(buf, nBase+i*nStride), 12) 423 let ny: i64 = gls_f32s(gll_ru32(buf, nBase+i*nStride+4), 12) 424 let nz: i64 = gls_f32s(gll_ru32(buf, nBase+i*nStride+8), 12) 425 tm_vert_n(x, y, z, nx, ny, nz) 426 } else { tm_vert(x, y, z) } 427 tm_vcol(i, col) 428 if hasUV == 1 { 429 let uu: i64 = gls_f32s(gll_ru32(buf, uBase+i*uStride), 16) 430 let vv: i64 = gls_f32s(gll_ru32(buf, uBase+i*uStride+4), 16) 431 tm_vuv(i, uu, vv) 432 } 433 i = i + 1 434 } 435 // indices 436 if gls_accessor(buf, jE, idxA, ac) == 0 { return gll_refuse("index-accessor-unparsable" as *u8) } 437 let NI: i64 = ac[3] 438 let ctype: i64 = ac[2] 439 if gls_bufview(buf, jE, ac[0], bv) == 0 { return gll_refuse("index-bufferview-unparsable" as *u8) } 440 let iBase: i64 = binStart + bv[0] + ac[1] 441 if NI <= 0 { return gll_refuse("index-count-absent" as *u8) } 442 if NI/3 > TM_TCAP { return gll_refuse("mesh-capacity-overflow" as *u8) } 443 var isz: i64 = 4 444 if ctype == K_MAGIC_5123 { isz = 2 } else { if ctype != K_MAGIC_5125 { return gll_refuse("index-componentType-unsupported" as *u8) } } 445 if gll_inb(iBase, NI*isz, size) == 0 { return gll_refuse("index-accessor-oob" as *u8) } 446 var t: i64 = 0 447 while t < NI/3 { 448 var a: i64 = 0 449 var b: i64 = 0 450 var c: i64 = 0 451 if ctype == K_MAGIC_5123 { a = gls_ru16(buf, iBase+(t*3)*2); b = gls_ru16(buf, iBase+(t*3+1)*2); c = gls_ru16(buf, iBase+(t*3+2)*2) } 452 else { a = gll_ru32(buf, iBase+(t*3)*4); b = gll_ru32(buf, iBase+(t*3+1)*4); c = gll_ru32(buf, iBase+(t*3+2)*4) } 453 if gll_idx3(a, b, c, NV) == 0 { return gll_refuse("index-out-of-range" as *u8) } 454 tm_tri(a, b, c, col) 455 t = t + 1 456 } 457 if tm_ovf() != 0 { return gll_refuse("mesh-capacity-overflow" as *u8) } 458 if hasN == 0 { tm_compute_normals() } 459 return 1 460} 461 462// ================================================================================================================ 463// ★D2 DONOR-SCOPE loader (debt 1786545740, 2026-08-12): load EVERY primitive of EVERY mesh, concatenated into 464// one trimesh, with per-primitive material baseColorFactor. Additive: gltf_load_std keeps its contract (first 465// primitive only) for existing callers; whole-file callers use THIS. Same D1 fail-closed law: every JSON-derived 466// offset is bounds-checked and refusals are NAMED (gll_refuse / gltf_load_why). NAMED residue: node world 467// transforms are NOT applied (skinned humanoid meshes share bind space so that donor class assembles correctly); 468// textures + skins are the next rungs. ⚠needle scan takes the FIRST "meshes" occurrence as THE meshes array -- 469// an extension key containing 'meshes' appearing earlier would misparse (unobserved in practice). 470const K_MAGIC_PRIMCAP: i64 = 512 471const K_MAGIC_PSTRIDE: i64 = 13 472 473func gltf_load_all(path: *u8, target: i64) -> i64 { 474 let szp: *i64 = sys_mmap(16) as *i64 475 let buf: *u8 = sys_read_file(path, szp) 476 let size: i64 = szp[0] 477 GLL_WHY = 0 478 if (buf as i64) == 0 { return gll_refuse("file-unreadable" as *u8) } 479 if size < 20 { return gll_refuse("file-too-short" as *u8) } 480 if (buf[0] as i64) != 103 { return gll_refuse("not-glb-magic" as *u8) } 481 if (buf[1] as i64) != 108 { return gll_refuse("not-glb-magic" as *u8) } 482 if (buf[2] as i64) != 84 { return gll_refuse("not-glb-magic" as *u8) } 483 if (buf[3] as i64) != 70 { return gll_refuse("not-glb-magic" as *u8) } 484 let jsonLen: i64 = gll_ru32(buf, 12) 485 let jE: i64 = 20 + jsonLen 486 let binStart: i64 = jE + 8 487 if gll_inb(20, jsonLen, size) == 0 { return gll_refuse("json-chunk-oob" as *u8) } 488 if binStart > size { return gll_refuse("bin-chunk-missing" as *u8) } 489 let mb: *i64 = sys_mmap(16) as *i64 490 let pr: *i64 = sys_mmap(16) as *i64 491 let mo: *i64 = sys_mmap(16) as *i64 492 let ac: *i64 = sys_mmap(32) as *i64 493 let bv: *i64 = sys_mmap(16) as *i64 494 let colv: *i64 = sys_mmap(24) as *i64 495 let pt: *i64 = sys_mmap(K_MAGIC_PRIMCAP*K_MAGIC_PSTRIDE*8 + 64) as *i64 496 var np: i64 = 0 497 var mi: i64 = 0 498 var moremesh: i64 = 1 499 while moremesh == 1 { 500 if gls_arr_obj(buf, jE, "meshes" as *u8, mi, mb) == 0 { moremesh = 0 } else { 501 var pi: i64 = 0 502 var moreprim: i64 = 1 503 while moreprim == 1 { 504 if gls_arr_obj_from(buf, mb[1], mb[0], "primitives" as *u8, pi, pr) == 0 { moreprim = 0 } else { 505 if np >= K_MAGIC_PRIMCAP { return gll_refuse("primitive-count-over-capacity" as *u8) } 506 let posA: i64 = gls_key_int(buf, pr[0], pr[1], "POSITION" as *u8) 507 let nrmA: i64 = gls_key_int(buf, pr[0], pr[1], "NORMAL" as *u8) 508 let uvA: i64 = gls_key_int(buf, pr[0], pr[1], "TEXCOORD_0" as *u8) 509 let idxA: i64 = gls_key_int(buf, pr[0], pr[1], "indices" as *u8) 510 let matI: i64 = gls_key_int(buf, pr[0], pr[1], "material" as *u8) 511 if posA < 0 { return gll_refuse("no-POSITION-attribute" as *u8) } 512 if idxA < 0 { return gll_refuse("no-indices" as *u8) } 513 if gls_accessor(buf, jE, posA, ac) == 0 { return gll_refuse("position-accessor-unparsable" as *u8) } 514 let NV: i64 = ac[3] 515 if NV <= 0 { return gll_refuse("position-count-absent" as *u8) } 516 if gls_bufview(buf, jE, ac[0], bv) == 0 { return gll_refuse("position-bufferview-unparsable" as *u8) } 517 let posBase: i64 = binStart + bv[0] + ac[1] 518 var posStride: i64 = bv[1] 519 if posStride == 0 { posStride = 12 } 520 if posStride < 12 { return gll_refuse("position-stride-under-12" as *u8) } 521 if gll_inb(posBase, (NV-1)*posStride + 12, size) == 0 { return gll_refuse("position-accessor-oob" as *u8) } 522 var nBase: i64 = 0 523 var nStride: i64 = 12 524 var hasN: i64 = 0 525 if nrmA >= 0 { 526 if gls_accessor(buf, jE, nrmA, ac) == 1 { 527 if gls_bufview(buf, jE, ac[0], bv) == 1 { 528 nBase = binStart + bv[0] + ac[1] 529 if bv[1] != 0 { nStride = bv[1] } 530 hasN = 1 531 } 532 } 533 } 534 if hasN == 1 { if nStride < 12 { return gll_refuse("normal-stride-under-12" as *u8) } } 535 if hasN == 1 { if gll_inb(nBase, (NV-1)*nStride + 12, size) == 0 { return gll_refuse("normal-accessor-oob" as *u8) } } 536 var uBase: i64 = 0 537 var uStride: i64 = 8 538 var hasUV: i64 = 0 539 if uvA >= 0 { 540 if gls_accessor(buf, jE, uvA, ac) == 1 { 541 if gls_bufview(buf, jE, ac[0], bv) == 1 { 542 uBase = binStart + bv[0] + ac[1] 543 if bv[1] != 0 { uStride = bv[1] } 544 hasUV = 1 545 } 546 } 547 } 548 if hasUV == 1 { if uStride < 8 { return gll_refuse("uv-stride-under-8" as *u8) } } 549 if hasUV == 1 { if gll_inb(uBase, (NV-1)*uStride + 8, size) == 0 { return gll_refuse("uv-accessor-oob" as *u8) } } 550 if gls_accessor(buf, jE, idxA, ac) == 0 { return gll_refuse("index-accessor-unparsable" as *u8) } 551 let NI: i64 = ac[3] 552 let ctype: i64 = ac[2] 553 if gls_bufview(buf, jE, ac[0], bv) == 0 { return gll_refuse("index-bufferview-unparsable" as *u8) } 554 let iBase: i64 = binStart + bv[0] + ac[1] 555 if NI <= 0 { return gll_refuse("index-count-absent" as *u8) } 556 var isz: i64 = 4 557 if ctype == K_MAGIC_5123 { isz = 2 } else { if ctype != K_MAGIC_5125 { return gll_refuse("index-componentType-unsupported" as *u8) } } 558 if gll_inb(iBase, NI*isz, size) == 0 { return gll_refuse("index-accessor-oob" as *u8) } 559 var col: i64 = 205 + 205*256 + 205*K_MAGIC_65536 560 if matI >= 0 { 561 if gls_arr_obj(buf, jE, "materials" as *u8, matI, mo) == 1 { 562 if gls_basecolor_in(buf, mo[0], mo[1], colv) == 1 { col = colv[0] + colv[1]*256 + colv[2]*K_MAGIC_65536 } 563 } 564 } 565 let pb: i64 = np*K_MAGIC_PSTRIDE 566 pt[pb] = posBase 567 pt[pb+1] = posStride 568 pt[pb+2] = NV 569 pt[pb+3] = hasN 570 pt[pb+4] = nBase 571 pt[pb+5] = nStride 572 pt[pb+6] = hasUV 573 pt[pb+7] = uBase 574 pt[pb+8] = uStride 575 pt[pb+9] = iBase 576 pt[pb+10] = isz 577 pt[pb+11] = NI 578 pt[pb+12] = col 579 np = np + 1 580 pi = pi + 1 581 } 582 } 583 mi = mi + 1 584 } 585 } 586 if np == 0 { return gll_refuse("no-primitives" as *u8) } 587 // capacity BEFORE emit: tm_vcol/tm_vuv take raw indices, so the cap must hold up front 588 var sumv: i64 = 0 589 var sumt: i64 = 0 590 var p1: i64 = 0 591 while p1 < np { sumv = sumv + pt[p1*K_MAGIC_PSTRIDE+2]; sumt = sumt + pt[p1*K_MAGIC_PSTRIDE+11]/3; p1 = p1 + 1 } 592 if sumv > tm_vcap() { return gll_refuse("mesh-capacity-overflow" as *u8) } 593 if sumt > TM_TCAP { return gll_refuse("mesh-capacity-overflow" as *u8) } 594 // global bbox across every primitive (float x256 ints) 595 var minx: i64 = K_MAGIC_2000000000 596 var miny: i64 = K_MAGIC_2000000000 597 var minz: i64 = K_MAGIC_2000000000 598 var maxx: i64 = 0-K_MAGIC_2000000000 599 var maxy: i64 = 0-K_MAGIC_2000000000 600 var maxz: i64 = 0-K_MAGIC_2000000000 601 var p2: i64 = 0 602 while p2 < np { 603 let pb2: i64 = p2*K_MAGIC_PSTRIDE 604 var i: i64 = 0 605 while i < pt[pb2+2] { 606 let x: i64 = gls_f32s(gll_ru32(buf, pt[pb2]+i*pt[pb2+1]), 8) 607 let y: i64 = gls_f32s(gll_ru32(buf, pt[pb2]+i*pt[pb2+1]+4), 8) 608 let z: i64 = gls_f32s(gll_ru32(buf, pt[pb2]+i*pt[pb2+1]+8), 8) 609 if x<minx {minx=x} if x>maxx {maxx=x} 610 if y<miny {miny=y} if y>maxy {maxy=y} 611 if z<minz {minz=z} if z>maxz {maxz=z} 612 i = i + 1 613 } 614 p2 = p2 + 1 615 } 616 var ext: i64 = maxx-minx 617 if maxy-miny > ext { ext = maxy-miny } 618 if maxz-minz > ext { ext = maxz-minz } 619 if ext < 1 { ext = 1 } 620 let s1024: i64 = target*K_MAGIC_1024/ext 621 let cx: i64 = (minx+maxx)/2 622 let cy: i64 = (miny+maxy)/2 623 let cz: i64 = (minz+maxz)/2 624 // emit: every primitive appended at a running base offset, per-primitive material colour 625 tm_reset() 626 var allN: i64 = 1 627 var base: i64 = 0 628 var p3: i64 = 0 629 while p3 < np { 630 let pb3: i64 = p3*K_MAGIC_PSTRIDE 631 let NV3: i64 = pt[pb3+2] 632 var i3: i64 = 0 633 while i3 < NV3 { 634 let x: i64 = (gls_f32s(gll_ru32(buf, pt[pb3]+i3*pt[pb3+1]), 8) - cx)*s1024/K_MAGIC_1024 635 let y: i64 = (gls_f32s(gll_ru32(buf, pt[pb3]+i3*pt[pb3+1]+4), 8) - cy)*s1024/K_MAGIC_1024 636 let z: i64 = (gls_f32s(gll_ru32(buf, pt[pb3]+i3*pt[pb3+1]+8), 8) - cz)*s1024/K_MAGIC_1024 637 if pt[pb3+3] == 1 { 638 let nx: i64 = gls_f32s(gll_ru32(buf, pt[pb3+4]+i3*pt[pb3+5]), 12) 639 let ny: i64 = gls_f32s(gll_ru32(buf, pt[pb3+4]+i3*pt[pb3+5]+4), 12) 640 let nz: i64 = gls_f32s(gll_ru32(buf, pt[pb3+4]+i3*pt[pb3+5]+8), 12) 641 tm_vert_n(x, y, z, nx, ny, nz) 642 } else { tm_vert(x, y, z) } 643 tm_vcol(base + i3, pt[pb3+12]) 644 if pt[pb3+6] == 1 { 645 let uu: i64 = gls_f32s(gll_ru32(buf, pt[pb3+7]+i3*pt[pb3+8]), 16) 646 let vv: i64 = gls_f32s(gll_ru32(buf, pt[pb3+7]+i3*pt[pb3+8]+4), 16) 647 tm_vuv(base + i3, uu, vv) 648 } 649 i3 = i3 + 1 650 } 651 if pt[pb3+3] == 0 { allN = 0 } 652 var t: i64 = 0 653 while t < pt[pb3+11]/3 { 654 var a: i64 = 0 655 var b2: i64 = 0 656 var c2: i64 = 0 657 if pt[pb3+10] == 2 { a = gls_ru16(buf, pt[pb3+9]+(t*3)*2); b2 = gls_ru16(buf, pt[pb3+9]+(t*3+1)*2); c2 = gls_ru16(buf, pt[pb3+9]+(t*3+2)*2) } 658 else { a = gll_ru32(buf, pt[pb3+9]+(t*3)*4); b2 = gll_ru32(buf, pt[pb3+9]+(t*3+1)*4); c2 = gll_ru32(buf, pt[pb3+9]+(t*3+2)*4) } 659 if gll_idx3(a, b2, c2, NV3) == 0 { return gll_refuse("index-out-of-range" as *u8) } 660 tm_tri(base + a, base + b2, base + c2, pt[pb3+12]) 661 t = t + 1 662 } 663 base = base + NV3 664 p3 = p3 + 1 665 } 666 if tm_ovf() != 0 { return gll_refuse("mesh-capacity-overflow" as *u8) } 667 if allN == 0 { tm_compute_normals() } 668 return 1 669} 670 671// Preservation-first interchange normalization. Existing mesh loaders remain unchanged. 672// This entry point does not convert authored rigs/materials into nx_trimesh or NXA. 673import "nx_compare_openapi.nx" 674import "nx_base64.nx" 675 676func glp_eq(t: *NxJsonTok, key: *u8) -> i64 { 677 if t.kind != NX_JSON_STRING { return 0 } 678 let n: i64 = gll_slen(key) 679 if t.tok_len != n+2 { return 0 } 680 var i:i64=0 681 while i<n { if t.src[t.tok_off+1+i]!=key[i] {return 0}; i=i+1 } 682 return 1 683} 684// out: value start/end, removable member start/end. Refuse escaped identity keys. 685func glp_member(t: *NxJsonTok, key: *u8, out: *i64, states: *u8, cap: i64) -> i64 { 686 if t.kind!=NX_JSON_LBRACE {return -1} 687 var previous:i64=-1; var found:i64=0 688 nx_json_next(t) 689 while t.kind!=NX_JSON_RBRACE { 690 if t.kind!=NX_JSON_STRING {return -1} 691 var k:i64=1 692 while k<t.tok_len-1 {if t.src[t.tok_off+k]==92 as u8{return -2}; k=k+1} 693 let start:i64=t.tok_off 694 let keymatch:i64=glp_eq(t,key) 695 if nx_json_next(t)!=NX_JSON_COLON{return -1} 696 nx_json_next(t) 697 let vs:i64=t.tok_off 698 let ve:i64=cgo_value_end(t,states,cap) 699 if ve<0{return -1} 700 if keymatch==1 { 701 if found!=0{return -3}; found=1; out[0]=vs; out[1]=ve 702 if previous>=0 {out[2]=previous;out[3]=ve} 703 else {out[2]=start;out[3]=ve;if t.kind==NX_JSON_COMMA{out[3]=t.pos}} 704 } 705 if t.kind==NX_JSON_RBRACE {break} 706 if t.kind!=NX_JSON_COMMA{return -1} 707 previous=t.tok_off;nx_json_next(t) 708 } 709 nx_json_next(t) 710 return found 711} 712func glp_reset(t: *NxJsonTok, src: *u8, n: i64, pos: i64) { 713 t.src=src;t.len=n;t.pos=pos;nx_json_next(t) 714} 715func glp_u32(out:*u8, at:i64, value:i64) { 716 var i:i64=0;while i<4 {out[at+i]=((value>>(i*8))&255) as u8;i=i+1} 717} 718// Return complete GLB length or a named negative contract code. Caller owns output. 719// scratch owns tokenizer + spans + grammar stack; wrapper checks allocations and frees them. 720func glp_build(src:*u8,n:i64,out:*u8,cap:i64,t:*NxJsonTok,states:*u8,sp:*i64)->i64 { 721 glp_reset(t,src,n,0) 722 if cgo_value_end(t,states,n)<0{return -10} 723 if t.kind!=NX_JSON_EOF{return -10} 724 glp_reset(t,src,n,0) 725 if glp_member(t,"asset" as *u8,sp,states,n)!=1{return -11} 726 glp_reset(t,src,n,sp[0]) 727 if glp_member(t,"version" as *u8,sp,states,n)!=1{return -11} 728 glp_reset(t,src,n,sp[0]) 729 if glp_eq(t,"2.0" as *u8)!=1{return -12} 730 glp_reset(t,src,n,0) 731 if glp_member(t,"buffers" as *u8,sp,states,n)!=1{return -13} 732 glp_reset(t,src,n,sp[0]) 733 if t.kind!=NX_JSON_LBRACKET{return -13} 734 nx_json_next(t) 735 let bo:i64=t.tok_off 736 if t.kind!=NX_JSON_LBRACE{return -13} 737 if cgo_value_end(t,states,n)<0{return -10} 738 if t.kind!=NX_JSON_RBRACKET{return -14} 739 glp_reset(t,src,n,bo) 740 if glp_member(t,"byteLength" as *u8,sp,states,n)!=1{return -15} 741 glp_reset(t,src,n,sp[0]) 742 if t.kind!=NX_JSON_NUMBER{return -15} 743 var bn:i64=0;var i:i64=0 744 while i<t.tok_len { 745 let c:i64=src[t.tok_off+i] as i64 746 if c<48{return -15};if c>57{return -15} 747 if bn>(cap-(c-48))/10{return -16} 748 bn=bn*10+c-48;i=i+1 749 } 750 if bn<=0{return -15} 751 glp_reset(t,src,n,bo) 752 if glp_member(t,"uri" as *u8,sp,states,n)!=1{return -17} 753 let cut0:i64=sp[2];let cut1:i64=sp[3] 754 glp_reset(t,src,n,sp[0]) 755 if t.kind!=NX_JSON_STRING{return -17} 756 let uri:*u8=(src as i64+t.tok_off+1) as *u8 757 let un:i64=t.tok_len-2 758 var prefix:*u8="data:application/octet-stream;base64," as *u8 759 var pn:i64=gll_slen(prefix);var same:i64=1;i=0 760 while i<pn {if i>=un{return -18};if uri[i]!=prefix[i]{same=0};i=i+1} 761 if same==0 { 762 prefix="data:application/gltf-buffer;base64," as *u8;pn=gll_slen(prefix);i=0 763 while i<pn {if i>=un{return -18};if uri[i]!=prefix[i]{return -18};i=i+1} 764 } 765 let encoded:i64=un-pn 766 if encoded!=((bn+2)/3)*4{return -19} 767 let jn:i64=n-(cut1-cut0) 768 let jp:i64=gll_align4(jn);let bp:i64=gll_align4(bn) 769 if jp>cap-28{return -16};if bp>cap-28-jp{return -16} 770 let total:i64=28+jp+bp 771 if total>4294967295{return -16} 772 // Canonical roundtrip checks every input byte, including padding and trailing garbage. 773 let bin:*u8=(out as i64+28+jp) as *u8 774 let decoded:*u8=sys_mmap(encoded) 775 if (decoded as i64)<=0{return -20} 776 if b64_decode((uri as i64+pn) as *u8,encoded,decoded)!=bn{sys_munmap(decoded,encoded);return -19} 777 let check:*u8=sys_mmap(encoded) 778 if (check as i64)<=0{sys_munmap(decoded,encoded);return -20} 779 let en:i64=b64_encode(decoded,bn,check);var valid:i64=1;i=0 780 while i<encoded {if check[i]!=uri[pn+i]{valid=0};i=i+1} 781 sys_munmap(check,encoded) 782 if valid==0{sys_munmap(decoded,encoded);return -19};if en!=encoded{sys_munmap(decoded,encoded);return -19} 783 i=0;while i<bn{bin[i]=decoded[i];i=i+1};sys_munmap(decoded,encoded) 784 glp_u32(out,0,1179937895);glp_u32(out,4,2);glp_u32(out,8,total) 785 glp_u32(out,12,jp);glp_u32(out,16,1313821514) 786 var o:i64=cgo_copy(out,cap,20,src,0,cut0) 787 o=cgo_copy(out,cap,o,src,cut1,n) 788 if o<0{return -16} 789 while o<20+jp {out[o]=32 as u8;o=o+1} 790 glp_u32(out,20+jp,bp);glp_u32(out,24+jp,5130562) 791 i=bn;while i<bp{bin[i]=0 as u8;i=i+1} 792 return total 793} 794func gltf_json_to_glb(src:*u8,n:i64,out:*u8,cap:i64)->i64 { 795 if (src as i64)<=0{return -1};if n<=0{return -1} 796 if (out as i64)<=0{return -1};if cap<28{return -1} 797 if n>cap{return -16} 798 if cap>4294967295{return -16} 799 if (out as i64)<(src as i64)+n {if (src as i64)<(out as i64)+cap{return -1}} 800 let states:*u8=sys_mmap(n) 801 if (states as i64)<=0{return -20} 802 let t:*NxJsonTok=nx_json_new(src,n) 803 if (t as i64)<=0{sys_munmap(states,n);return -20} 804 let sp:*i64=sys_mmap(32) as *i64 805 if (sp as i64)<=0{sys_munmap(states,n);sys_munmap(t as *u8,NX_JSONTOK_BYTES);return -20} 806 let result:i64=glp_build(src,n,out,cap,t,states,sp) 807 sys_munmap(sp as *u8,32);sys_munmap(t as *u8,NX_JSONTOK_BYTES);sys_munmap(states,n) 808 return result 809} 810 811// Read-only GLB structural inventory. All authored JSON survives in the report; 812// this is not accessor decoding, unit inference, or rendering qualification. 813const GLI_STATS_WORDS:i64=34 814const GLI_CONTEXT_WORDS:i64=6 815func gli_name(i:i64)->*u8 { 816 if i==4{return "scenes"};if i==5{return "nodes"};if i==6{return "meshes"} 817 if i==7{return "materials"};if i==8{return "skins"};if i==9{return "animations"} 818 if i==10{return "accessors"};if i==11{return "images"};if i==12{return "textures"} 819 if i==13{return "buffers"};if i==14{return "bufferViews"};if i==15{return "samplers"} 820 if i==16{return "cameras"};if i==17{return "primitives"};if i==18{return "morph_target_records"} 821 if i==19{return "meshes_with_morph_targets"};if i==20{return "skin_joint_references"} 822 if i==21{return "animation_channels"};if i==22{return "translation_channels"} 823 if i==23{return "rotation_channels"};if i==24{return "scale_channels"} 824 if i==25{return "weights_channels"};if i==26{return "other_target_paths"} 825 if i==27{return "nodes_with_matrix"};if i==28{return "nodes_with_translation"} 826 if i==29{return "nodes_with_rotation"};if i==30{return "nodes_with_scale"} 827 if i==31{return "meshes_with_weights"};if i==32{return "nodes_with_weights"} 828 return "unknown_glb_chunks" 829} 830func gli_member(cx:*i64,parent:i64,key:*u8)->i64 { 831 let t:*NxJsonTok=cx[2] as *NxJsonTok 832 glp_reset(t,cx[0] as *u8,cx[1],parent) 833 return glp_member(t,key,cx[4] as *i64,cx[3] as *u8,cx[1]) 834} 835func gli_present(cx:*i64,parent:i64,key:*u8,slot:i64)->i64 { 836 let have:i64=gli_member(cx,parent,key) 837 if have<0{return -1} 838 if have==1 {let counts:*i64=cx[5] as *i64;counts[slot]=counts[slot]+1} 839 return 0 840} 841// mode: 0 scalar/object count, 1 mesh, 2 primitive, 3 skin, 4 animation, 842// 5 animation channel, 6 node. No fixed item table or silent truncation. 843func gli_array(cx:*i64,parent:i64,key:*u8,mode:i64)->i64 { 844 let have:i64=gli_member(cx,parent,key) 845 if have<0{return -1};if have==0{return 0} 846 let t:*NxJsonTok=cx[2] as *NxJsonTok;let sp:*i64=cx[4] as *i64 847 let counts:*i64=cx[5] as *i64 848 glp_reset(t,cx[0] as *u8,cx[1],sp[0]) 849 if t.kind!=NX_JSON_LBRACKET{return -1} 850 nx_json_next(t);var count:i64=0 851 while t.kind!=NX_JSON_RBRACKET { 852 let start:i64=t.tok_off 853 if mode>0 {if t.kind!=NX_JSON_LBRACE{return -1}} 854 let end:i64=cgo_value_end(t,cx[3] as *u8,cx[1]) 855 if end<0{return -1} 856 if mode==1 { 857 let before:i64=counts[18] 858 let np:i64=gli_array(cx,start,"primitives",2) 859 if np<0{return -1};counts[17]=counts[17]+np 860 if counts[18]>before {counts[19]=counts[19]+1} 861 if gli_present(cx,start,"weights",31)<0{return -1} 862 } 863 if mode==2 {let nt:i64=gli_array(cx,start,"targets",0);if nt<0{return -1};counts[18]=counts[18]+nt} 864 if mode==3 {let nj:i64=gli_array(cx,start,"joints",0);if nj<0{return -1};counts[20]=counts[20]+nj} 865 if mode==4 {let nc:i64=gli_array(cx,start,"channels",5);if nc<0{return -1};counts[21]=counts[21]+nc} 866 if mode==5 { 867 let ht:i64=gli_member(cx,start,"target") 868 if ht!=1{return -1} 869 let target:i64=sp[0] 870 if gli_member(cx,target,"path")!=1{return -1} 871 glp_reset(t,cx[0] as *u8,cx[1],sp[0]) 872 if t.kind!=NX_JSON_STRING{return -1} 873 var slot:i64=26 874 if glp_eq(t,"translation")==1{slot=22} 875 if glp_eq(t,"rotation")==1{slot=23} 876 if glp_eq(t,"scale")==1{slot=24} 877 if glp_eq(t,"weights")==1{slot=25} 878 counts[slot]=counts[slot]+1 879 } 880 if mode==6 { 881 if gli_present(cx,start,"matrix",27)<0{return -1} 882 if gli_present(cx,start,"translation",28)<0{return -1} 883 if gli_present(cx,start,"rotation",29)<0{return -1} 884 if gli_present(cx,start,"scale",30)<0{return -1} 885 if gli_present(cx,start,"weights",32)<0{return -1} 886 } 887 count=count+1;glp_reset(t,cx[0] as *u8,cx[1],end) 888 if t.kind==NX_JSON_RBRACKET{break} 889 if t.kind!=NX_JSON_COMMA{return -1};nx_json_next(t) 890 } 891 return count 892} 893// stats0 JSON offset,1 JSON length,2 BIN bytes,3 chunks,4..33 counts. 894func gltf_inventory_glb(src:*u8,n:i64,stats:*i64)->i64 { 895 if (src as i64)<=0 || (stats as i64)<=0 || n<20{return -1} 896 var z:i64=0;while z<GLI_STATS_WORDS{stats[z]=0;z=z+1} 897 if gll_ru32(src,0)!=0x46546c67 || gll_ru32(src,4)!=2 || gll_ru32(src,8)!=n{return -2} 898 var off:i64=12;var bins:i64=0 899 while off<n { 900 if n-off<8{return -3} 901 let size:i64=gll_ru32(src,off);let kind:i64=gll_ru32(src,off+4) 902 if size%4!=0 || size>n-off-8{return -3} 903 if stats[3]==0 && kind!=0x4e4f534a{return -4} 904 if kind==0x4e4f534a { 905 if stats[0]!=0{return -4};stats[0]=off+8;stats[1]=size 906 } else {if kind==0x004e4942 {bins=bins+1;if bins>1 || stats[3]!=1{return -4};stats[2]=size} else {stats[33]=stats[33]+1}} 907 stats[3]=stats[3]+1;off=off+8+size 908 } 909 if stats[0]==0 || stats[1]<=0{return -4} 910 let json:*u8=(src as i64+stats[0]) as *u8;let jn:i64=stats[1] 911 let states:*u8=sys_mmap(jn);let t:*NxJsonTok=nx_json_new(json,jn) 912 let sp:*i64=sys_mmap(32) as *i64;let cx:*i64=sys_mmap(GLI_CONTEXT_WORDS*8) as *i64 913 var rc:i64=-7 914 if (states as i64)>0 && (t as i64)>0 && (sp as i64)>0 && (cx as i64)>0 { 915 rc=-5;glp_reset(t,json,jn,0) 916 if t.kind==NX_JSON_LBRACE { 917 let end:i64=cgo_value_end(t,states,jn) 918 if end>0 && t.kind==NX_JSON_EOF { 919 cx[0]=json as i64;cx[1]=jn;cx[2]=t as i64;cx[3]=states as i64;cx[4]=sp as i64;cx[5]=stats as i64 920 rc=0;var i:i64=4 921 while i<=16 { 922 var mode:i64=0 923 if i==5{mode=6};if i==6{mode=1};if i==8{mode=3};if i==9{mode=4} 924 let count:i64=gli_array(cx,0,gli_name(i),mode) 925 if count<0{rc=-6;break};stats[i]=count;i=i+1 926 } 927 } 928 } 929 } 930 if (cx as i64)>0{sys_munmap(cx as *u8,GLI_CONTEXT_WORDS*8)} 931 if (sp as i64)>0{sys_munmap(sp as *u8,32)} 932 if (t as i64)>0{sys_munmap(t as *u8,NX_JSONTOK_BYTES)} 933 if (states as i64)>0{sys_munmap(states,jn)} 934 return rc 935} 936 937// Structural accessor view over the original GLB. Caller retains source lifetime. 938const GLD_VIEW_WORDS:i64=8 939const GLD_I64_MAX:i64=9223372036854775807 940func gld_uint(t:*NxJsonTok)->i64 { 941 if t.kind!=NX_JSON_NUMBER||t.tok_len<=0{return -1} 942 var v:i64=0;var i:i64=0 943 while i<t.tok_len{let d:i64=(t.src[t.tok_off+i] as i64)-48;if d<0||d>9||v>(GLD_I64_MAX-d)/10{return -1};v=v*10+d;i=i+1};return v 944} 945func gld_int(cx:*i64,parent:i64,key:*u8,missing:i64)->i64 { 946 let have:i64=gli_member(cx,parent,key);if have<0{return -1};if have==0{return missing} 947 let sp:*i64=cx[4] as *i64;let t:*NxJsonTok=cx[2] as *NxJsonTok 948 glp_reset(t,cx[0] as *u8,cx[1],sp[0]);return gld_uint(t) 949} 950func gld_nth(cx:*i64,parent:i64,key:*u8,index:i64)->i64 { 951 if index<0||gli_member(cx,parent,key)!=1{return -1} 952 let sp:*i64=cx[4] as *i64;let t:*NxJsonTok=cx[2] as *NxJsonTok 953 glp_reset(t,cx[0] as *u8,cx[1],sp[0]);if t.kind!=NX_JSON_LBRACKET{return -1};nx_json_next(t) 954 var i:i64=0 955 while t.kind!=NX_JSON_RBRACKET{ 956 let at:i64=t.tok_off;if i==index{return at} 957 let end:i64=cgo_value_end(t,cx[3] as *u8,cx[1]);if end<0{return -1} 958 glp_reset(t,cx[0] as *u8,cx[1],end);if t.kind==NX_JSON_RBRACKET{return -1} 959 if t.kind!=NX_JSON_COMMA{return -1};nx_json_next(t);i=i+1 960 };return -1 961} 962// Output: BIN-relative byte offset,count,stride,component type,component bytes, 963// components,normalized flag,occupied element bytes. No source bytes rewritten. 964// Sparse/compressed or padded narrow matrix accessors explicitly refuse this rung. 965func gltf_accessor_view(cx:*i64,index:i64,bin_bytes:i64,out:*i64)->i64 { 966 if bin_bytes<0||(out as i64)<=0{return -1} 967 let buffer:i64=gld_nth(cx,0,"buffers",0);if buffer<0{return -1};let declared:i64=gld_int(cx,buffer,"byteLength",-1);if declared<0||declared>bin_bytes{return -3} 968 let a:i64=gld_nth(cx,0,"accessors",index);if a<0{return -1} 969 if gli_member(cx,a,"sparse")!=0||gli_member(cx,a,"extensions")!=0{return -2} 970 let vi:i64=gld_int(cx,a,"bufferView",-1);let off:i64=gld_int(cx,a,"byteOffset",0) 971 let count:i64=gld_int(cx,a,"count",-1);let ct:i64=gld_int(cx,a,"componentType",-1) 972 if vi<0||off<0||count<=0{return -1} 973 var es:i64=0;if ct==5120||ct==5121{es=1};if ct==5122||ct==5123{es=2};if ct==5125||ct==5126{es=4};if es==0{return -2} 974 if gli_member(cx,a,"type")!=1{return -1} 975 let sp:*i64=cx[4] as *i64;let t:*NxJsonTok=cx[2] as *NxJsonTok 976 glp_reset(t,cx[0] as *u8,cx[1],sp[0]);var nc:i64=0 977 if glp_eq(t,"SCALAR")==1{nc=1};if glp_eq(t,"VEC2")==1{nc=2};if glp_eq(t,"VEC3")==1{nc=3};if glp_eq(t,"VEC4")==1{nc=4} 978 if glp_eq(t,"MAT2")==1{if es!=4{return -2};nc=4};if glp_eq(t,"MAT3")==1{if es!=4{return -2};nc=9};if glp_eq(t,"MAT4")==1{nc=16} 979 if nc==0{return -2};let occupied:i64=nc*es 980 var norm:i64=0;let nh:i64=gli_member(cx,a,"normalized");if nh<0{return -1} 981 if nh==1{glp_reset(t,cx[0] as *u8,cx[1],sp[0]);if t.kind==NX_JSON_TRUE{norm=1}else{if t.kind!=NX_JSON_FALSE{return -1}}} 982 if norm==1&&(ct==5125||ct==5126){return -1} 983 let v:i64=gld_nth(cx,0,"bufferViews",vi);if v<0{return -1} 984 if gli_member(cx,v,"extensions")!=0{return -2} 985 if gld_int(cx,v,"buffer",-1)!=0{return -2} 986 let vo:i64=gld_int(cx,v,"byteOffset",0);let vl:i64=gld_int(cx,v,"byteLength",-1) 987 let stride:i64=gld_int(cx,v,"byteStride",occupied) 988 if vo<0||vl<0||vo>declared||vl>declared-vo{return -3} 989 let explicit_stride:i64=gli_member(cx,v,"byteStride");if explicit_stride<0{return -1};if explicit_stride==1{if stride<4||stride>252||stride%4!=0{return -3}} 990 if stride<occupied||stride%es!=0||off%es!=0||vo%es!=0{return -3} 991 if off>vl||occupied>vl-off{return -3} 992 if count-1>(vl-off-occupied)/stride{return -3} 993 out[0]=vo+off;out[1]=count;out[2]=stride;out[3]=ct;out[4]=es;out[5]=nc;out[6]=norm;out[7]=occupied 994 return 0 995} 996 997const GLD_CONTEXT_WORDS:i64=9 998func gltf_view_close(cx:*i64)->i64 { 999 if (cx as i64)<=0{return -1} 1000 if cx[2]>0{sys_munmap(cx[2] as *u8,NX_JSONTOK_BYTES)} 1001 if cx[3]>0{sys_munmap(cx[3] as *u8,cx[1])} 1002 if cx[4]>0{sys_munmap(cx[4] as *u8,32)} 1003 if cx[5]>0{sys_munmap(cx[5] as *u8,GLI_STATS_WORDS*8)} 1004 var i:i64=0;while i<GLD_CONTEXT_WORDS{cx[i]=0;i=i+1};return 0 1005} 1006func gltf_view_open(src:*u8,n:i64,cx:*i64)->i64 { 1007 if (cx as i64)<=0{return -1} 1008 var i:i64=0;while i<GLD_CONTEXT_WORDS{cx[i]=0;i=i+1} 1009 let stats:*i64=sys_mmap(GLI_STATS_WORDS*8) as *i64;if (stats as i64)<=0{return -7};cx[5]=stats as i64 1010 let rc:i64=gltf_inventory_glb(src,n,stats);if rc!=0{gltf_view_close(cx);return rc} 1011 cx[0]=src as i64+stats[0];cx[1]=stats[1];cx[2]=nx_json_new(cx[0] as *u8,cx[1]) as i64 1012 cx[3]=sys_mmap(cx[1]) as i64;cx[4]=sys_mmap(32) as i64;cx[6]=src as i64;cx[7]=n 1013 if cx[2]<=0||cx[3]<=0||cx[4]<=0{gltf_view_close(cx);return -7} 1014 if stats[13]!=1||stats[2]<=0{gltf_view_close(cx);return -2} 1015 let b:i64=gld_nth(cx,0,"buffers",0);if b<0{gltf_view_close(cx);return -2};let length:i64=gld_int(cx,b,"byteLength",-1) 1016 if b<0||length<=0||length>stats[2]||stats[2]-length>3||gli_member(cx,b,"uri")!=0{gltf_view_close(cx);return -2} 1017 cx[8]=src as i64+stats[0]+stats[1]+8;return 0 1018}