nx_gltf_load_inventory_t180.nx source
↩ module page · 935 lines · 43619 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}