code wiki / (root) / nx_gltf_load.nx

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}