code wiki / _hdl_build / nx_mesh2glb.nx

nx_mesh2glb.nx source

↩ module page · 338 lines · 15086 B

1// nx_mesh2glb.nx -- NXMSH2 -> INTERACTIVE .glb (operator 2026-08-05: "a picture vs the other files' 2// full interactivity panel -- that's dumb"). The measurement organs emit NXMSH2 (per-tri colour = the 3// deviation heatmap lives there); the showcase viewer consumes .glb. This organ closes that seam, so a 4// FITTED body and a PAINTED HEATMAP both become drag-to-rotate panels, not screenshots -- an interactive 5// deviation map is a capability the reference industrial tools ship as static report pages. 6// Positions PASS THROUGH as raw f32 bits (zero re-quantisation); per-tri colour is replicated to the 7// tri's 3 vertices as normalized u8 VEC4; per-vertex normals transcode to normalized i16 -- the exact 8// accessor types our proven nishi_walk.glb already uses, so the viewer needs nothing new. 9// nx_mesh2glb <in.nxmesh> <out.glb> | selftest 10// license_tier: ORIGINAL expect_exit: 0 11import "nx_syscalls.nx" 12const MG_MAGIC_65536: i64 = 65536 13const MG_MAGIC_32767: i64 = 32767 14const MG_MAGIC_65535: i64 = 65535 15const MG_MAGIC_100000: i64 = 100000 16const MG_MAGIC_8192: i64 = 8192 17 18const MG_CAP: i64 = 33554432 19const MG_OCAP: i64 = 25165824 20const MG_MAXTRI: i64 = 400000 21const MG_M8388607: i64 = 8388607 22const MG_M8388608: i64 = 8388608 23const MG_BIG: i64 = 4611686018427387903 24 25func hw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 26func pn(v: i64) -> i64 { let b: *u8=sys_mmap(32) as *u8; var x: i64=v; var ng: i64=0; if x<0{ng=1;x=0-x} var i: i64=31; if x==0{b[i]=48 as u8;i=i-1} while x>0{b[i]=(48+x%10) as u8;x=x/10;i=i-1} if ng==1{b[i]=45 as u8;i=i-1} sys_write(1,(b as i64+i+1) as *u8,31-i); return 0 } 27func mg_u32(b: *u8, o: i64) -> i64 { return (b[o] as i64) + ((b[o+1] as i64)<<8) + ((b[o+2] as i64)<<16) + ((b[o+3] as i64)<<24) } 28func mg_w8(b: *u8, o: i64, v: i64) -> i64 { b[o] = (v&255) as u8; return o+1 } 29func mg_w16(b: *u8, o: i64, v: i64) -> i64 { b[o]=(v&255) as u8; b[o+1]=((v>>8)&255) as u8; return o+2 } 30func mg_w32(b: *u8, o: i64, v: i64) -> i64 { b[o]=(v&255) as u8; b[o+1]=((v>>8)&255) as u8; b[o+2]=((v>>16)&255) as u8; b[o+3]=((v>>24)&255) as u8; return o+4 } 31func mg_align4(x: i64) -> i64 { return (x+3)/4*4 } 32func mg_cat(o: *u8, at: i64, s: *u8) -> i64 { var i: i64=0; var a: i64=at; while s[i]!=(0 as u8){o[a]=s[i]; a=a+1; i=i+1} return a } 33func mg_num(o: *u8, at: i64, v: i64) -> i64 { 34 let b: *u8 = sys_mmap(32) as *u8 35 var x: i64 = v 36 var ng: i64 = 0 37 if x < 0 { ng = 1; x = 0-x } 38 var i: i64 = 31 39 if x == 0 { b[i]=48 as u8; i=i-1 } 40 while x > 0 { b[i]=(48+x%10) as u8; x=x/10; i=i-1 } 41 if ng == 1 { b[i]=45 as u8; i=i-1 } 42 var a: i64 = at 43 var j: i64 = i+1 44 while j <= 31 { o[a] = b[j]; a=a+1; j=j+1 } 45 return a 46} 47// f32 bits -> scaled integer (value * scale), for min/max compare and permille decode 48func mg_f32i(w: i64, scale: i64) -> i64 { 49 let sign: i64 = (w >> 31) & 1 50 let expo: i64 = (w >> 23) & 255 51 if expo == 0 { return 0 } 52 var mant: i64 = (w & MG_M8388607) | MG_M8388608 53 let sh: i64 = expo - 127 54 var v: i64 = 0 55 if sh >= 23 { if sh - 23 > 30 { return 0 } } 56 if sh >= 23 { v = mant * scale * (1 << (sh - 23)) } 57 if sh < 23 { if 23 - sh > 62 { return 0 } } 58 if sh < 23 { v = (mant * scale) >> (23 - sh) } 59 if sign == 1 { return 0 - v } 60 return v 61} 62func mg_refuse(reason: *u8) -> i64 { hw("MESH2GLB REFUSED: " as *u8); hw(reason); hw("\n" as *u8); return 0 } 63 64func mg_convert(inp: *u8, outp: *u8) -> i64 { 65 let fd: i64 = sys_openat_rd(inp) 66 if fd < 0 { mg_refuse("input unreadable" as *u8); return 3 } 67 let b: *u8 = sys_mmap(MG_CAP + 64) 68 var n: i64 = 0 69 var go: i64 = 1 70 while go == 1 { 71 let r: i64 = sys_read(fd, ((b as i64) + n) as *u8, MG_CAP - n) 72 if r <= 0 { go = 0 } else { n = n + r } 73 if n >= MG_CAP { go = 0 } 74 } 75 sys_close(fd) 76 if n < 44 { mg_refuse("too small for NXMSH2" as *u8); return 3 } 77 if b[0] != (78 as u8) { mg_refuse("not NXMSH2" as *u8); return 3 } 78 if b[5] != (50 as u8) { mg_refuse("not NXMSH2 v2" as *u8); return 3 } 79 let nlay: i64 = mg_u32(b, 8) 80 let nt: i64 = mg_u32(b, 12) 81 if nt <= 0 { mg_refuse("no triangles" as *u8); return 3 } 82 if nt > MG_MAXTRI { mg_refuse("over tri cap" as *u8); return 3 } 83 let hdr: i64 = 16 + nlay*24 84 if hdr + nt*84 > n { mg_refuse("truncated triangle records" as *u8); return 3 } 85 let nv: i64 = nt*3 86 let ni: i64 = nt*3 87 // ---- BIN layout ---- 88 let posLen: i64 = nv*12 89 let nrmOff: i64 = posLen 90 let nrmLen: i64 = nv*6 91 let colOff: i64 = mg_align4(nrmOff + nrmLen) 92 let colLen: i64 = nv*4 93 let idxOff: i64 = colOff + colLen 94 let idxLen: i64 = ni*4 95 let binLen: i64 = mg_align4(idxOff + idxLen) 96 let need: i64 = binLen + MG_MAGIC_65536 97 if need > MG_OCAP { mg_refuse("mesh exceeds the output budget -- decimate first, never truncate silently" as *u8); return 4 } 98 let bin: *u8 = sys_mmap(need) 99 // track min/max position BITS per axis (compare in decoded um space) 100 let mnb: *i64 = sys_mmap(48) as *i64 101 let mxb: *i64 = sys_mmap(48) as *i64 102 let mnv: *i64 = sys_mmap(48) as *i64 103 let mxv: *i64 = sys_mmap(48) as *i64 104 var a2: i64 = 0 105 while a2 < 3 { mnv[a2] = MG_BIG; mxv[a2] = 0-MG_BIG; mnb[a2] = 0; mxb[a2] = 0; a2 = a2 + 1 } 106 var t: i64 = 0 107 var at: i64 = 0 108 while t < nt { 109 var c: i64 = 0 110 while c < 3 { 111 var ax: i64 = 0 112 while ax < 3 { 113 let w: i64 = mg_u32(b, hdr + t*84 + c*12 + ax*4) 114 at = mg_w32(bin, at, w) 115 let dv: i64 = mg_f32i(w, 1000) 116 if dv < mnv[ax] { mnv[ax] = dv; mnb[ax] = w } 117 if dv > mxv[ax] { mxv[ax] = dv; mxb[ax] = w } 118 ax = ax + 1 119 } 120 c = c + 1 121 } 122 t = t + 1 123 } 124 // normals: per-vertex f32 permille -> normalized i16 (v*32767/1000) 125 t = 0 126 at = nrmOff 127 while t < nt { 128 var c2: i64 = 0 129 while c2 < 3 { 130 var ax2: i64 = 0 131 while ax2 < 3 { 132 let w2: i64 = mg_u32(b, hdr + t*84 + 36 + c2*12 + ax2*4) 133 var s16: i64 = mg_f32i(w2, MG_MAGIC_32767) / 1000 134 if s16 > MG_MAGIC_32767 { s16 = MG_MAGIC_32767 } 135 if s16 < 0-MG_MAGIC_32767 { s16 = 0-MG_MAGIC_32767 } 136 at = mg_w16(bin, at, s16 & MG_MAGIC_65535) 137 ax2 = ax2 + 1 138 } 139 c2 = c2 + 1 140 } 141 t = t + 1 142 } 143 // colors: per-TRI f32 permille -> u8 x3 verts, alpha 255; zero colour falls back to bone so the 144 // panel is never invisible (the meshview lesson, inherited deliberately) 145 t = 0 146 at = colOff 147 while t < nt { 148 var cr: i64 = mg_f32i(mg_u32(b, hdr + t*84 + 72), 255) / 1000 149 var cg: i64 = mg_f32i(mg_u32(b, hdr + t*84 + 76), 255) / 1000 150 var cb: i64 = mg_f32i(mg_u32(b, hdr + t*84 + 80), 255) / 1000 151 if cr + cg + cb < 12 { cr = 216; cg = 210; cb = 198 } 152 if cr > 255 { cr = 255 } 153 if cg > 255 { cg = 255 } 154 if cb > 255 { cb = 255 } 155 var c3: i64 = 0 156 while c3 < 3 { 157 at = mg_w8(bin, at, cr) 158 at = mg_w8(bin, at, cg) 159 at = mg_w8(bin, at, cb) 160 at = mg_w8(bin, at, 255) 161 c3 = c3 + 1 162 } 163 t = t + 1 164 } 165 // indices 0..nv-1 166 t = 0 167 at = idxOff 168 while t < ni { at = mg_w32(bin, at, t); t = t + 1 } 169 // ---- JSON ---- 170 let json: *u8 = sys_mmap(MG_MAGIC_65536) 171 var jj: i64 = 0 172 jj = mg_cat(json, jj, "{\x22asset\x22:{\x22version\x22:\x222.0\x22,\x22generator\x22:\x22nishi nx_mesh2glb\x22},\x22scene\x22:0,\x22scenes\x22:[{\x22nodes\x22:[0]}],\x22nodes\x22:[{\x22mesh\x22:0}]," as *u8) 173 jj = mg_cat(json, jj, "\x22meshes\x22:[{\x22primitives\x22:[{\x22attributes\x22:{\x22POSITION\x22:0,\x22NORMAL\x22:1,\x22COLOR_0\x22:2},\x22indices\x22:3,\x22material\x22:0}]}]," as *u8) 174 jj = mg_cat(json, jj, "\x22materials\x22:[{\x22pbrMetallicRoughness\x22:{\x22metallicFactor\x22:0,\x22roughnessFactor\x22:1},\x22doubleSided\x22:true}]," as *u8) 175 jj = mg_cat(json, jj, "\x22accessors\x22:[{\x22bufferView\x22:0,\x22componentType\x22:5126,\x22count\x22:" as *u8) 176 jj = mg_num(json, jj, nv) 177 jj = mg_cat(json, jj, ",\x22type\x22:\x22VEC3\x22,\x22min\x22:[" as *u8) 178 // min/max as decoded integers (mm truncation of um) -- viewers use these only for framing 179 jj = mg_num(json, jj, mnv[0]/1000) 180 jj = mg_cat(json, jj, "," as *u8) 181 jj = mg_num(json, jj, mnv[1]/1000) 182 jj = mg_cat(json, jj, "," as *u8) 183 jj = mg_num(json, jj, mnv[2]/1000) 184 jj = mg_cat(json, jj, "],\x22max\x22:[" as *u8) 185 jj = mg_num(json, jj, mxv[0]/1000 + 1) 186 jj = mg_cat(json, jj, "," as *u8) 187 jj = mg_num(json, jj, mxv[1]/1000 + 1) 188 jj = mg_cat(json, jj, "," as *u8) 189 jj = mg_num(json, jj, mxv[2]/1000 + 1) 190 jj = mg_cat(json, jj, "]}," as *u8) 191 jj = mg_cat(json, jj, "{\x22bufferView\x22:1,\x22componentType\x22:5122,\x22normalized\x22:true,\x22count\x22:" as *u8) 192 jj = mg_num(json, jj, nv) 193 jj = mg_cat(json, jj, ",\x22type\x22:\x22VEC3\x22}," as *u8) 194 jj = mg_cat(json, jj, "{\x22bufferView\x22:2,\x22componentType\x22:5121,\x22normalized\x22:true,\x22count\x22:" as *u8) 195 jj = mg_num(json, jj, nv) 196 jj = mg_cat(json, jj, ",\x22type\x22:\x22VEC4\x22}," as *u8) 197 jj = mg_cat(json, jj, "{\x22bufferView\x22:3,\x22componentType\x22:5125,\x22count\x22:" as *u8) 198 jj = mg_num(json, jj, ni) 199 jj = mg_cat(json, jj, ",\x22type\x22:\x22SCALAR\x22}]," as *u8) 200 jj = mg_cat(json, jj, "\x22bufferViews\x22:[{\x22buffer\x22:0,\x22byteOffset\x22:0,\x22byteLength\x22:" as *u8) 201 jj = mg_num(json, jj, posLen) 202 jj = mg_cat(json, jj, "},{\x22buffer\x22:0,\x22byteOffset\x22:" as *u8) 203 jj = mg_num(json, jj, nrmOff) 204 jj = mg_cat(json, jj, ",\x22byteLength\x22:" as *u8) 205 jj = mg_num(json, jj, nrmLen) 206 jj = mg_cat(json, jj, "},{\x22buffer\x22:0,\x22byteOffset\x22:" as *u8) 207 jj = mg_num(json, jj, colOff) 208 jj = mg_cat(json, jj, ",\x22byteLength\x22:" as *u8) 209 jj = mg_num(json, jj, colLen) 210 jj = mg_cat(json, jj, "},{\x22buffer\x22:0,\x22byteOffset\x22:" as *u8) 211 jj = mg_num(json, jj, idxOff) 212 jj = mg_cat(json, jj, ",\x22byteLength\x22:" as *u8) 213 jj = mg_num(json, jj, idxLen) 214 jj = mg_cat(json, jj, "}]," as *u8) 215 jj = mg_cat(json, jj, "\x22buffers\x22:[{\x22byteLength\x22:" as *u8) 216 jj = mg_num(json, jj, binLen) 217 jj = mg_cat(json, jj, "}]}" as *u8) 218 let jsonPad: i64 = mg_align4(jj) 219 // ---- GLB assembly ---- 220 let total: i64 = 12 + 8 + jsonPad + 8 + binLen 221 let out: *u8 = sys_mmap(total + 64) 222 var o: i64 = 0 223 o = mg_cat(out, o, "glTF" as *u8) 224 o = mg_w32(out, o, 2) 225 o = mg_w32(out, o, total) 226 o = mg_w32(out, o, jsonPad) 227 o = mg_cat(out, o, "JSON" as *u8) 228 var q: i64 = 0 229 while q < jj { out[o] = json[q]; o = o + 1; q = q + 1 } 230 while q < jsonPad { out[o] = 32 as u8; o = o + 1; q = q + 1 } 231 o = mg_w32(out, o, binLen) 232 o = mg_w8(out, o, 66) 233 o = mg_w8(out, o, 73) 234 o = mg_w8(out, o, 78) 235 o = mg_w8(out, o, 0) 236 q = 0 237 while q < binLen { out[o] = bin[q]; o = o + 1; q = q + 1 } 238 let ofd: i64 = sys_openat_wr(outp, 420) 239 if ofd < 0 { mg_refuse("output unwritable" as *u8); return 6 } 240 sys_write(ofd, out, o) 241 sys_close(ofd) 242 hw("{\x22organ\x22:\x22nx_mesh2glb\x22,\x22tris\x22:" as *u8); pn(nt) 243 hw(",\x22verts\x22:" as *u8); pn(nv) 244 hw(",\x22glb_bytes\x22:" as *u8); pn(o) 245 hw(",\x22note\x22:\x22positions pass through as raw f32 bits (zero requantisation); per-tri colour carried -- a painted HEATMAP stays a heatmap in the interactive viewer\x22}\n" as *u8) 246 return 0 247} 248 249// ---- teeth (literal-only fixture, nx_cc 1785936860 discipline) ---- 250func st_enc1000(v: i64) -> i64 { 251 if v == 0 { return 0 } 252 var neg: i64 = 0 253 var m: i64 = v 254 if m < 0 { neg = 1; m = 0-m } 255 var e: i64 = 0 256 var num: i64 = m 257 var den: i64 = 1000 258 while num >= den*2 { den = den*2; e = e+1 } 259 while num < den { num = num*2; e = e-1 } 260 let frac: i64 = ((num - den)*MG_M8388608)/den 261 var bits: i64 = ((e+127) << 23) | (frac & MG_M8388607) 262 if neg == 1 { bits = bits | (1<<31) } 263 return bits 264} 265func st_fix(path: *u8) -> i64 { 266 let b: *u8 = sys_mmap(256) 267 b[0]=78 as u8; b[1]=88 as u8; b[2]=77 as u8; b[3]=83 as u8 268 b[4]=72 as u8; b[5]=50 as u8; b[6]=0 as u8; b[7]=0 as u8 269 mg_w32(b, 8, 1) 270 mg_w32(b, 12, 1) 271 var q: i64 = 0 272 while q < 16 { b[16+q] = 0 as u8; q = q + 1 } 273 b[16]=115 as u8 274 mg_w32(b, 32, 0) 275 mg_w32(b, 36, 1) 276 var k: i64 = 0 277 while k < 21 { mg_w32(b, 40 + k*4, 0); k = k + 1 } 278 mg_w32(b, 52, st_enc1000(MG_MAGIC_100000)) 279 mg_w32(b, 68, st_enc1000(MG_MAGIC_100000)) 280 mg_w32(b, 40+72, st_enc1000(900)) 281 mg_w32(b, 40+76, st_enc1000(120)) 282 mg_w32(b, 40+80, st_enc1000(80)) 283 mg_w32(b, 124, 0) 284 let fd: i64 = sys_openat_wr(path, 420) 285 if fd < 0 { return 0 - 1 } 286 sys_write(fd, b, 128) 287 sys_close(fd) 288 return 0 289} 290func mg_hasstr(b: *u8, n: i64, lit: *u8) -> i64 { 291 var m: i64 = 0 292 while lit[m] != (0 as u8) { m = m + 1 } 293 var i: i64 = 0 294 while i + m <= n { 295 var k: i64 = 0 296 var ok: i64 = 1 297 while k < m { if b[i+k] != lit[k] { ok = 0; k = m } else { k = k + 1 } } 298 if ok == 1 { return 1 } 299 i = i + 1 300 } 301 return 0 302} 303func mg_selftest() -> i64 { 304 var fails: i64 = 0 305 st_fix("/tmp/mg_t.nxmesh" as *u8) 306 hw("T0 convert a 1-tri red fixture -> valid glb with COLOR_0:\n" as *u8) 307 if mg_convert("/tmp/mg_t.nxmesh" as *u8, "/tmp/mg_t.glb" as *u8) != 0 { fails = fails + 1; hw("T0 FAIL convert refused\n" as *u8) } else { 308 let vb: *u8 = sys_mmap(MG_MAGIC_8192) 309 let vfd: i64 = sys_openat_rd("/tmp/mg_t.glb" as *u8) 310 var vn: i64 = 0 311 if vfd >= 0 { vn = sys_read(vfd, vb, MG_MAGIC_8192); sys_close(vfd) } 312 var ok: i64 = 1 313 if vn < 100 { ok = 0 } 314 if vb[0] != (103 as u8) { ok = 0 } 315 if mg_u32(vb, 8) != vn { ok = 0 } 316 if mg_hasstr(vb, vn, "COLOR_0" as *u8) == 0 { ok = 0 } 317 if ok == 1 { hw("T0 PASS header+length+COLOR_0\n" as *u8) } else { fails = fails + 1; hw("T0 FAIL glb invalid\n" as *u8) } 318 } 319 hw("T1 absent input must REFUSE:\n" as *u8) 320 if mg_convert("/tmp/mg_absent_zz.nxmesh" as *u8, "/tmp/mg_x.glb" as *u8) == 0 { fails = fails + 1; hw("T1 FAIL\n" as *u8) } else { hw("T1 PASS\n" as *u8) } 321 if fails == 0 { hw("MESH2GLB-SELFTEST GREEN 2/2\n" as *u8); return 0 } 322 hw("MESH2GLB-SELFTEST RED fails=" as *u8); pn(fails); hw("\n" as *u8) 323 return 1 324} 325 326func main(argc: i64, argv: *i64) -> i64 { 327 if argc < 2 { 328 hw("usage: nx_mesh2glb <in.nxmesh> <out.glb> | selftest\n" as *u8) 329 sys_exit(2) 330 return 2 331 } 332 let a1: *u8 = argv[1] as *u8 333 if a1[0] == (115 as u8) { let rc: i64 = mg_selftest(); sys_exit(rc); return rc } 334 if argc < 3 { hw("usage: nx_mesh2glb <in.nxmesh> <out.glb>\n" as *u8); sys_exit(2); return 2 } 335 let rc2: i64 = mg_convert(argv[1] as *u8, argv[2] as *u8) 336 sys_exit(rc2) 337 return rc2 338}