nx_gltf_load.nx source
↩ module page · 390 lines · 16421 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
14
15// IEEE-754 float32 bit pattern -> integer (inverse of gl_f32bits; exact for the integer coordinates we export)
16func gll_f32i(bits: i64) -> i64 {
17 if bits == 0 { return 0 }
18 let sign: i64 = (bits>>31)&1
19 let exp: i64 = (bits>>23)&255
20 let mant: i64 = bits & K_MAGIC_8388607
21 if exp == 0 { return 0 }
22 let e: i64 = exp - 127
23 if e < 0-30 { return 0 } // |v| < 2^-30 rounds to integer 0 (also guards shift-by->63 UB)
24 let m: i64 = K_MAGIC_8388608 + mant // 1.mant scaled by 2^23
25 var val: i64 = 0
26 if e >= 23 { val = m << (e-23) } else { val = m >> (23-e) }
27 if sign == 1 { val = 0-val }
28 return val
29}
30func gll_ru8(buf: *u8, at: i64) -> i64 { return buf[at] as i64 }
31func 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) }
32func gll_align4(x: i64) -> i64 { return (x+3)/4*4 }
33func gll_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
34// find the n-th integer that follows `needle` within buf[start,end)
35func gll_nth_num(buf: *u8, start: i64, end: i64, needle: *u8, n: i64) -> i64 {
36 let m: i64 = gll_slen(needle)
37 var found: i64 = 0
38 var i: i64 = start
39 while i+m <= end {
40 var k: i64 = 0
41 while k < m { if (buf[i+k] as i64) != (needle[k] as i64) { k = m+9 } else { k = k+1 } }
42 if k == m {
43 found = found + 1
44 if found == n {
45 var p: i64 = i+m
46 while (buf[p] as i64) == 32 { p = p+1 }
47 var neg: i64 = 0
48 if (buf[p] as i64) == 45 { neg = 1; p = p+1 }
49 var v: i64 = 0
50 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 } }
51 if neg == 1 { v = 0-v }
52 return v
53 }
54 i = i + m
55 } else { i = i + 1 }
56 }
57 return 0-1
58}
59
60// load a .glb file into the trimesh buffers. returns 1 on success, 0 on failure. base_col used where colours absent.
61func gltf_load(path: *u8) -> i64 {
62 let szp: *i64 = sys_mmap(16) as *i64
63 let buf: *u8 = sys_read_file(path, szp)
64 let size: i64 = szp[0]
65 if (buf as i64) == 0 { return 0 }
66 if size < 20 { return 0 }
67 if (buf[0] as i64) != 103 { return 0 } // 'g'
68 if (buf[1] as i64) != 108 { return 0 } // 'l'
69 if (buf[2] as i64) != 84 { return 0 } // 'T'
70 if (buf[3] as i64) != 70 { return 0 } // 'F'
71 let jsonLen: i64 = gll_ru32(buf, 12)
72 let jsonStart: i64 = 20
73 let binStart: i64 = 20 + jsonLen + 8
74 // NV = first "count" (POSITION accessor), NI = fourth "count" (indices accessor)
75 let NV: i64 = gll_nth_num(buf, jsonStart, jsonStart+jsonLen, "\"count\":" as *u8, 1)
76 let NI: i64 = gll_nth_num(buf, jsonStart, jsonStart+jsonLen, "\"count\":" as *u8, 4)
77 if NV <= 0 { return 0 }
78 if NI <= 0 { return 0 }
79 // derive our exporter's BIN layout
80 let posOff: i64 = binStart
81 let colOff: i64 = binStart + gll_align4(NV*12 + NV*6)
82 let idxOff: i64 = colOff + NV*4
83 tm_reset()
84 var i: i64 = 0
85 while i < NV {
86 let x: i64 = gll_f32i(gll_ru32(buf, posOff+i*12))
87 let y: i64 = gll_f32i(gll_ru32(buf, posOff+i*12+4))
88 let z: i64 = gll_f32i(gll_ru32(buf, posOff+i*12+8))
89 let idx: i64 = tm_vert(x, y, z)
90 let cr: i64 = gll_ru8(buf, colOff+i*4)
91 let cg: i64 = gll_ru8(buf, colOff+i*4+1)
92 let cb: i64 = gll_ru8(buf, colOff+i*4+2)
93 tm_vcol(idx, cr + cg*256 + cb*K_MAGIC_65536)
94 i = i + 1
95 }
96 let NTRI: i64 = NI/3
97 var t: i64 = 0
98 while t < NTRI {
99 let a: i64 = gll_ru32(buf, idxOff + (t*3)*4)
100 let b: i64 = gll_ru32(buf, idxOff + (t*3+1)*4)
101 let c: i64 = gll_ru32(buf, idxOff + (t*3+2)*4)
102 tm_tri(a, b, c, 200+200*256+200*K_MAGIC_65536)
103 t = t + 1
104 }
105 tm_compute_normals()
106 return 1
107}
108
109// ================================================================================================================
110// ★STANDARD-glTF loader (2026-07-11, the graphics-MSU rung): parse REAL third-party .glb (Khronos conformance
111// assets), not just our own exporter's layout -- proper JSON accessor/bufferView walking (byteOffset defaults,
112// byteStride, u16/u32 indices, float32 positions/normals, baseColorFactor). Needles searched WITHOUT quote chars
113// (avoids the "-in-literal hazard); slices keep keys unambiguous. license part of nx_gltf_load. ==================
114func gls_ru16(buf: *u8, at: i64) -> i64 { return (buf[at] as i64) + ((buf[at+1] as i64)<<8) }
115// float32 bits -> integer of (value * 2^k) by exponent shift; zero-guarded.
116func gls_f32s(bits: i64, k: i64) -> i64 { if (bits & K_MAGIC_2147483647) == 0 { return 0 } return gll_f32i(bits + (k<<23)) }
117func gls_find(buf: *u8, end: i64, from: i64, needle: *u8) -> i64 {
118 let m: i64 = gll_slen(needle)
119 var i: i64 = from
120 while i + m <= end {
121 var k: i64 = 0
122 while k < m { if (buf[i+k] as i64) != (needle[k] as i64) { k = m + 9 } else { k = k + 1 } }
123 if k == m { return i }
124 i = i + 1
125 }
126 return 0-1
127}
128// integer value right after `key` (skips to ':', then spaces). -1 if absent in [s,e).
129func gls_key_int(buf: *u8, s: i64, e: i64, key: *u8) -> i64 {
130 let p0: i64 = gls_find(buf, e, s, key)
131 if p0 < 0 { return 0-1 }
132 var p: i64 = p0 + gll_slen(key)
133 var col: i64 = 0-1
134 while p < e { if (buf[p] as i64) == 58 { col = p; p = e } else { p = p + 1 } }
135 if col < 0 { return 0-1 }
136 p = col + 1
137 var sk: i64 = 1
138 while sk == 1 { if p < e { if (buf[p] as i64) == 32 { p = p + 1 } else { sk = 0 } } else { sk = 0 } }
139 var v: i64 = 0
140 var any: i64 = 0
141 var go: i64 = 1
142 while go == 1 {
143 if p < e {
144 let c: i64 = buf[p] as i64
145 var isd: i64 = 0
146 if c >= 48 { if c <= 57 { isd = 1 } }
147 if isd == 1 { v = v*10 + (c-48); any = 1; p = p + 1 } else { go = 0 }
148 } else { go = 0 }
149 }
150 if any == 0 { return 0-1 }
151 return v
152}
153// bounds of the k-th object in the JSON array named `arrName`: out[0]=pos of '{', out[1]=pos after matching '}'.
154func gls_arr_obj(buf: *u8, end: i64, arrName: *u8, k: i64, out: *i64) -> i64 {
155 let an: i64 = gls_find(buf, end, 0, arrName)
156 if an < 0 { return 0 }
157 var p: i64 = an
158 var br: i64 = 0-1
159 while p < end { if (buf[p] as i64) == 91 { br = p; p = end } else { p = p + 1 } }
160 if br < 0 { return 0 }
161 var depth: i64 = 0
162 var idx: i64 = 0-1
163 var objS: i64 = 0-1
164 p = br + 1
165 while p < end {
166 let c: i64 = buf[p] as i64
167 if c == 123 {
168 if depth == 0 { idx = idx + 1; if idx == k { objS = p } }
169 depth = depth + 1
170 }
171 if c == 125 {
172 depth = depth - 1
173 if depth == 0 { if idx == k { out[0] = objS; out[1] = p + 1; return 1 } }
174 }
175 if c == 93 { if depth == 0 { p = end } }
176 p = p + 1
177 }
178 return 0
179}
180// accessor A -> out: [bufferView, accessorByteOffset, componentType, count]
181func gls_accessor(buf: *u8, jE: i64, A: i64, out: *i64) -> i64 {
182 let ob: *i64 = sys_mmap(16) as *i64
183 if gls_arr_obj(buf, jE, "accessors" as *u8, A, ob) == 0 { return 0 }
184 var bv: i64 = gls_key_int(buf, ob[0], ob[1], "bufferView" as *u8)
185 if bv < 0 { bv = 0 }
186 var bo: i64 = gls_key_int(buf, ob[0], ob[1], "byteOffset" as *u8)
187 if bo < 0 { bo = 0 }
188 out[0] = bv
189 out[1] = bo
190 out[2] = gls_key_int(buf, ob[0], ob[1], "componentType" as *u8)
191 out[3] = gls_key_int(buf, ob[0], ob[1], "count" as *u8)
192 return 1
193}
194// bufferView B -> out: [byteOffset, byteStride]
195func gls_bufview(buf: *u8, jE: i64, B: i64, out: *i64) -> i64 {
196 let ob: *i64 = sys_mmap(16) as *i64
197 if gls_arr_obj(buf, jE, "bufferViews" as *u8, B, ob) == 0 { return 0 }
198 var bo: i64 = gls_key_int(buf, ob[0], ob[1], "byteOffset" as *u8)
199 if bo < 0 { bo = 0 }
200 var st: i64 = gls_key_int(buf, ob[0], ob[1], "byteStride" as *u8)
201 if st < 0 { st = 0 }
202 out[0] = bo
203 out[1] = st
204 return 1
205}
206// ★locate the FIRST embedded image's bytes in a .glb: out[0]=absolute offset, out[1]=length. 1 ok.
207func gls_image_slice(buf: *u8, size: i64, out: *i64) -> i64 {
208 let jsonLen: i64 = gll_ru32(buf, 12)
209 let jE: i64 = 20 + jsonLen
210 let binStart: i64 = jE + 8
211 let ob: *i64 = sys_mmap(16) as *i64
212 if gls_arr_obj(buf, jE, "images" as *u8, 0, ob) == 0 { return 0 }
213 let bvi: i64 = gls_key_int(buf, ob[0], ob[1], "bufferView" as *u8)
214 if bvi < 0 { return 0 }
215 if gls_arr_obj(buf, jE, "bufferViews" as *u8, bvi, ob) == 0 { return 0 }
216 var bo: i64 = gls_key_int(buf, ob[0], ob[1], "byteOffset" as *u8)
217 if bo < 0 { bo = 0 }
218 let bl: i64 = gls_key_int(buf, ob[0], ob[1], "byteLength" as *u8)
219 if bl <= 0 { return 0 }
220 out[0] = binStart + bo
221 out[1] = bl
222 return 1
223}
224// baseColorFactor -> out r,g,b (0..255). 1 if present (parses decimals like 0.800000011920929, keeps 3 frac digits).
225func gls_basecolor(buf: *u8, jE: i64, out: *i64) -> i64 {
226 var p: i64 = gls_find(buf, jE, 0, "baseColorFactor" as *u8)
227 if p < 0 { return 0 }
228 var ch: i64 = 0
229 while ch < 3 {
230 var go: i64 = 1
231 while go == 1 {
232 if p >= jE { go = 0 } else {
233 let c: i64 = buf[p] as i64
234 var isd: i64 = 0
235 if c >= 48 { if c <= 57 { isd = 1 } }
236 if isd == 1 { go = 0 } else { p = p + 1 }
237 }
238 }
239 var ip: i64 = 0
240 go = 1
241 while go == 1 {
242 if p >= jE { go = 0 } else {
243 let c: i64 = buf[p] as i64
244 var isd: i64 = 0
245 if c >= 48 { if c <= 57 { isd = 1 } }
246 if isd == 1 { ip = ip*10 + (c-48); p = p + 1 } else { go = 0 }
247 }
248 }
249 var milli: i64 = ip * 1000
250 if (buf[p] as i64) == 46 {
251 p = p + 1
252 var fd: i64 = 0
253 var mul: i64 = 100
254 go = 1
255 while go == 1 {
256 if p >= jE { go = 0 } else {
257 let c: i64 = buf[p] as i64
258 var isd: i64 = 0
259 if c >= 48 { if c <= 57 { isd = 1 } }
260 if isd == 1 {
261 if fd < 3 { milli = milli + (c-48)*mul; mul = mul/10; fd = fd + 1 }
262 p = p + 1
263 } else { go = 0 }
264 }
265 }
266 }
267 if milli > 1000 { milli = 1000 }
268 out[ch] = milli * 255 / 1000
269 ch = ch + 1
270 }
271 return 1
272}
273// ★load a REAL .glb (Khronos-conformant): parse primitive/accessors/bufferViews properly, normalize the model to
274// `target` extent centered at origin (integer), per-vertex normals from the file (or recomputed), baseColorFactor.
275func gltf_load_std(path: *u8, target: i64) -> i64 {
276 let szp: *i64 = sys_mmap(16) as *i64
277 let buf: *u8 = sys_read_file(path, szp)
278 let size: i64 = szp[0]
279 if (buf as i64) == 0 { return 0 }
280 if size < 20 { return 0 }
281 if (buf[0] as i64) != 103 { return 0 }
282 let jsonLen: i64 = gll_ru32(buf, 12)
283 let jE: i64 = 20 + jsonLen
284 let binStart: i64 = jE + 8
285 let pr: *i64 = sys_mmap(16) as *i64
286 if gls_arr_obj(buf, jE, "primitives" as *u8, 0, pr) == 0 { return 0 }
287 let posA: i64 = gls_key_int(buf, pr[0], pr[1], "POSITION" as *u8)
288 let nrmA: i64 = gls_key_int(buf, pr[0], pr[1], "NORMAL" as *u8)
289 let uvA: i64 = gls_key_int(buf, pr[0], pr[1], "TEXCOORD_0" as *u8)
290 let idxA: i64 = gls_key_int(buf, pr[0], pr[1], "indices" as *u8)
291 if posA < 0 { return 0 }
292 if idxA < 0 { return 0 }
293 let ac: *i64 = sys_mmap(32) as *i64
294 let bv: *i64 = sys_mmap(16) as *i64
295 if gls_accessor(buf, jE, posA, ac) == 0 { return 0 }
296 let NV: i64 = ac[3]
297 if NV <= 0 { return 0 }
298 if gls_bufview(buf, jE, ac[0], bv) == 0 { return 0 }
299 let posBase: i64 = binStart + bv[0] + ac[1]
300 var posStride: i64 = bv[1]
301 if posStride == 0 { posStride = 12 }
302 // pass 1: bbox over raw ints (float x256)
303 var minx: i64 = K_MAGIC_2000000000; var miny: i64 = K_MAGIC_2000000000; var minz: i64 = K_MAGIC_2000000000
304 var maxx: i64 = 0-K_MAGIC_2000000000; var maxy: i64 = 0-K_MAGIC_2000000000; var maxz: i64 = 0-K_MAGIC_2000000000
305 var i: i64 = 0
306 while i < NV {
307 let x: i64 = gls_f32s(gll_ru32(buf, posBase+i*posStride), 8)
308 let y: i64 = gls_f32s(gll_ru32(buf, posBase+i*posStride+4), 8)
309 let z: i64 = gls_f32s(gll_ru32(buf, posBase+i*posStride+8), 8)
310 if x<minx {minx=x} if x>maxx {maxx=x}
311 if y<miny {miny=y} if y>maxy {maxy=y}
312 if z<minz {minz=z} if z>maxz {maxz=z}
313 i = i + 1
314 }
315 var ext: i64 = maxx-minx
316 if maxy-miny > ext { ext = maxy-miny }
317 if maxz-minz > ext { ext = maxz-minz }
318 if ext < 1 { ext = 1 }
319 let s1024: i64 = target*K_MAGIC_1024/ext
320 let cx: i64 = (minx+maxx)/2; let cy: i64 = (miny+maxy)/2; let cz: i64 = (minz+maxz)/2
321 // normals accessor (optional)
322 var nBase: i64 = 0
323 var nStride: i64 = 12
324 var hasN: i64 = 0
325 if nrmA >= 0 {
326 if gls_accessor(buf, jE, nrmA, ac) == 1 {
327 if gls_bufview(buf, jE, ac[0], bv) == 1 {
328 nBase = binStart + bv[0] + ac[1]
329 if bv[1] != 0 { nStride = bv[1] }
330 hasN = 1
331 }
332 }
333 }
334 // UV accessor (optional; Q16)
335 var uBase: i64 = 0
336 var uStride: i64 = 8
337 var hasUV: i64 = 0
338 if uvA >= 0 {
339 if gls_accessor(buf, jE, uvA, ac) == 1 {
340 if gls_bufview(buf, jE, ac[0], bv) == 1 {
341 uBase = binStart + bv[0] + ac[1]
342 if bv[1] != 0 { uStride = bv[1] }
343 hasUV = 1
344 }
345 }
346 }
347 // colour
348 let colv: *i64 = sys_mmap(24) as *i64
349 var col: i64 = 205 + 205*256 + 205*K_MAGIC_65536
350 if gls_basecolor(buf, jE, colv) == 1 { col = colv[0] + colv[1]*256 + colv[2]*K_MAGIC_65536 }
351 // pass 2: emit
352 tm_reset()
353 i = 0
354 while i < NV {
355 let x: i64 = (gls_f32s(gll_ru32(buf, posBase+i*posStride), 8) - cx)*s1024/K_MAGIC_1024
356 let y: i64 = (gls_f32s(gll_ru32(buf, posBase+i*posStride+4), 8) - cy)*s1024/K_MAGIC_1024
357 let z: i64 = (gls_f32s(gll_ru32(buf, posBase+i*posStride+8), 8) - cz)*s1024/K_MAGIC_1024
358 if hasN == 1 {
359 let nx: i64 = gls_f32s(gll_ru32(buf, nBase+i*nStride), 12)
360 let ny: i64 = gls_f32s(gll_ru32(buf, nBase+i*nStride+4), 12)
361 let nz: i64 = gls_f32s(gll_ru32(buf, nBase+i*nStride+8), 12)
362 tm_vert_n(x, y, z, nx, ny, nz)
363 } else { tm_vert(x, y, z) }
364 tm_vcol(i, col)
365 if hasUV == 1 {
366 let uu: i64 = gls_f32s(gll_ru32(buf, uBase+i*uStride), 16)
367 let vv: i64 = gls_f32s(gll_ru32(buf, uBase+i*uStride+4), 16)
368 tm_vuv(i, uu, vv)
369 }
370 i = i + 1
371 }
372 // indices
373 if gls_accessor(buf, jE, idxA, ac) == 0 { return 0 }
374 let NI: i64 = ac[3]
375 let ctype: i64 = ac[2]
376 if gls_bufview(buf, jE, ac[0], bv) == 0 { return 0 }
377 let iBase: i64 = binStart + bv[0] + ac[1]
378 var t: i64 = 0
379 while t < NI/3 {
380 var a: i64 = 0
381 var b: i64 = 0
382 var c: i64 = 0
383 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) }
384 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) }
385 tm_tri(a, b, c, col)
386 t = t + 1
387 }
388 if hasN == 0 { tm_compute_normals() }
389 return 1
390}