code wiki / (root) / nx_gltf_export.nx

nx_gltf_export.nx source

↩ module page · 226 lines · 13721 B

1// nx_gltf_export.nx -- export a bestiary mesh as binary glTF (.glb), roadmap R7 / the VRM foundation. glTF 2.0 2// is THE modern interchange format (web three.js/babylon, Unity/Godot/Blender, and the container VRM extends for 3// riggable avatars). Extends the STL/OBJ export (nx_meshgen verts+tris) to a real scene-graph format: a JSON 4// chunk (scene/node/mesh/accessors) + a BIN chunk (float32 positions + uint32 indices). Positions are the fx1024 5// integer verts encoded as float32, with a node scale of 1/1024 so the imported mesh is correctly sized. v0 = 6// static mesh; the skeleton + skin + VRM humanoid extension are the next layer. license_tier: ORIGINAL 7import "nx_syscalls.nx" 8const K_MAGIC_8192: i64 = 8192 9 10// signed integer -> IEEE-754 float32 bits (low 32 of the return). Truncating mantissa (fine for geometry). 11func gl_i2f32(v: i64) -> i64 { 12 if v == 0 { return 0 } 13 var sign: i64 = 0 14 var a: i64 = v 15 if a < 0 { sign = 1; a = 0 - a } 16 var e: i64 = 0 17 var t: i64 = a 18 while t > 1 { t = t >> 1; e = e + 1 } // e = floor(log2(a)) 19 var mant: i64 = 0 20 if e <= 23 { mant = (a << (23 - e)) & 0x7fffff } 21 else { mant = (a >> (e - 23)) & 0x7fffff } 22 let exp: i64 = 127 + e 23 return (sign << 31) | (exp << 23) | mant 24} 25func gl_wr32(buf: *u8, o: i64, v: i64) -> i64 { 26 buf[o] = (v & 0xff) as u8; buf[o+1] = ((v>>8)&0xff) as u8; buf[o+2] = ((v>>16)&0xff) as u8; buf[o+3] = ((v>>24)&0xff) as u8 27 return o + 4 28} 29func gl_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){d[o+i]=s[i]; i=i+1} return o+i } 30func gl_num(d: *u8, o: i64, v: i64) -> i64 { 31 var x: i64 = v; var oo: i64 = o 32 if x < 0 { d[oo]=45 as u8; oo=oo+1; x=0-x } 33 if x == 0 { d[oo]=48 as u8; return oo+1 } 34 let t: *u8 = sys_mmap(24); var k: i64 = 0 35 while x > 0 { t[k]=(48+x%10) as u8; x=x/10; k=k+1 } 36 var i: i64 = 0 37 while i < k { d[oo+i]=t[k-1-i]; i=i+1 } 38 return oo + k 39} 40func gl_isqrt(v: i64) -> i64 { if v <= 0 { return 0 } var x: i64 = v; var y: i64 = (x + 1) / 2; while y < x { x = y; y = (x + v / x) / 2 } return x } 41// smooth per-vertex normals into nrm[nv*3] (~1000-scale; renderers re-normalise after the node's inverse-transpose). 42func gl_vnormals(vbuf: *i64, fbuf: *i64, nv: i64, nf: i64, nrm: *i64) -> i64 { 43 var i: i64 = 0 44 while i < nv * 3 { nrm[i] = 0; i = i + 1 } 45 var t: i64 = 0 46 while t < nf { 47 let a: i64 = fbuf[t*3]; let b: i64 = fbuf[t*3+1]; let c: i64 = fbuf[t*3+2] 48 let e1x: i64 = vbuf[b*3]-vbuf[a*3]; let e1y: i64 = vbuf[b*3+1]-vbuf[a*3+1]; let e1z: i64 = vbuf[b*3+2]-vbuf[a*3+2] 49 let e2x: i64 = vbuf[c*3]-vbuf[a*3]; let e2y: i64 = vbuf[c*3+1]-vbuf[a*3+1]; let e2z: i64 = vbuf[c*3+2]-vbuf[a*3+2] 50 let fnx: i64 = e1y*e2z - e1z*e2y; let fny: i64 = e1z*e2x - e1x*e2z; let fnz: i64 = e1x*e2y - e1y*e2x 51 nrm[a*3]=nrm[a*3]+fnx; nrm[a*3+1]=nrm[a*3+1]+fny; nrm[a*3+2]=nrm[a*3+2]+fnz 52 nrm[b*3]=nrm[b*3]+fnx; nrm[b*3+1]=nrm[b*3+1]+fny; nrm[b*3+2]=nrm[b*3+2]+fnz 53 nrm[c*3]=nrm[c*3]+fnx; nrm[c*3+1]=nrm[c*3+1]+fny; nrm[c*3+2]=nrm[c*3+2]+fnz 54 t = t + 1 55 } 56 i = 0 57 while i < nv { 58 let l: i64 = gl_isqrt(nrm[i*3]*nrm[i*3] + nrm[i*3+1]*nrm[i*3+1] + nrm[i*3+2]*nrm[i*3+2]) 59 if l < 1 { nrm[i*3]=0; nrm[i*3+1]=1000; nrm[i*3+2]=0 } 60 else { nrm[i*3]=nrm[i*3]*1000/l; nrm[i*3+1]=nrm[i*3+1]*1000/l; nrm[i*3+2]=nrm[i*3+2]*1000/l } 61 i = i + 1 62 } 63 return 0 64} 65 66// write a .glb WITH per-vertex COLOR_0 (the texture-bake path: photo colors baked onto the mesh, VR-loadable 67// with no UV unwrap). cbuf = nv*3 i64 (RGB 0..255). COLOR_0 = VEC4 normalized UNSIGNED_BYTE (RGBA, A=255) -- 68// the standard cleanly-4-aligned vertex-color format every glTF/VRM viewer accepts. Returns byte length or -1. 69func write_glb_colored(vbuf: *i64, fbuf: *i64, cbuf: *i64, nv: i64, nf: i64, path: *u8) -> i64 { 70 let posbytes: i64 = nv * 12 71 let normbytes: i64 = nv * 12 72 let colbytes: i64 = nv * 4 73 let idxbytes: i64 = nf * 3 * 4 74 let binlen: i64 = posbytes + normbytes + colbytes + idxbytes 75 let bin: *u8 = sys_mmap(binlen + 64) 76 let nrm: *i64 = sys_mmap(nv * 3 * 8) as *i64 77 gl_vnormals(vbuf, fbuf, nv, nf, nrm) 78 var bo: i64 = 0 79 var i: i64 = 0 80 while i < nv { 81 bo = gl_wr32(bin, bo, gl_i2f32(vbuf[i*3])); bo = gl_wr32(bin, bo, gl_i2f32(vbuf[i*3+1])); bo = gl_wr32(bin, bo, gl_i2f32(vbuf[i*3+2])) 82 i = i + 1 83 } 84 let normoff: i64 = bo 85 i = 0 86 while i < nv { 87 bo = gl_wr32(bin, bo, gl_i2f32(nrm[i*3])); bo = gl_wr32(bin, bo, gl_i2f32(nrm[i*3+1])); bo = gl_wr32(bin, bo, gl_i2f32(nrm[i*3+2])) 88 i = i + 1 89 } 90 let coloff: i64 = bo 91 i = 0 92 while i < nv { 93 var r: i64 = cbuf[i*3]; var g: i64 = cbuf[i*3+1]; var b: i64 = cbuf[i*3+2] 94 if r < 0 { r = 0 } if r > 255 { r = 255 } 95 if g < 0 { g = 0 } if g > 255 { g = 255 } 96 if b < 0 { b = 0 } if b > 255 { b = 255 } 97 bin[bo] = r as u8; bin[bo+1] = g as u8; bin[bo+2] = b as u8; bin[bo+3] = 255 as u8; bo = bo + 4 98 i = i + 1 99 } 100 let idxoff: i64 = bo 101 var j: i64 = 0 102 while j < nf * 3 { bo = gl_wr32(bin, bo, fbuf[j]); j = j + 1 } 103 var minx: i64 = vbuf[0]; var miny: i64 = vbuf[1]; var minz: i64 = vbuf[2] 104 var maxx: i64 = vbuf[0]; var maxy: i64 = vbuf[1]; var maxz: i64 = vbuf[2] 105 i = 1 106 while i < nv { 107 if vbuf[i*3]<minx{minx=vbuf[i*3]} if vbuf[i*3]>maxx{maxx=vbuf[i*3]} 108 if vbuf[i*3+1]<miny{miny=vbuf[i*3+1]} if vbuf[i*3+1]>maxy{maxy=vbuf[i*3+1]} 109 if vbuf[i*3+2]<minz{minz=vbuf[i*3+2]} if vbuf[i*3+2]>maxz{maxz=vbuf[i*3+2]} 110 i = i + 1 111 } 112 let js: *u8 = sys_mmap(K_MAGIC_8192) 113 var o: i64 = gl_cat(js, 0, "{\"asset\":{\"version\":\"2.0\",\"generator\":\"nishi-nx_gltf\"},\"scene\":0,\"scenes\":[{\"nodes\":[0]}]," as *u8) 114 o = gl_cat(js, o, "\"nodes\":[{\"mesh\":0,\"scale\":[0.0009765625,0.0009765625,0.0009765625]}]," as *u8) 115 o = gl_cat(js, o, "\"meshes\":[{\"primitives\":[{\"attributes\":{\"POSITION\":0,\"NORMAL\":1,\"COLOR_0\":2},\"indices\":3}]}]," as *u8) 116 o = gl_cat(js, o, "\"buffers\":[{\"byteLength\":" as *u8); o = gl_num(js, o, binlen); o = gl_cat(js, o, "}]," as *u8) 117 o = gl_cat(js, o, "\"bufferViews\":[{\"buffer\":0,\"byteOffset\":0,\"byteLength\":" as *u8); o = gl_num(js, o, posbytes); o = gl_cat(js, o, ",\"target\":34962}," as *u8) 118 o = gl_cat(js, o, "{\"buffer\":0,\"byteOffset\":" as *u8); o = gl_num(js, o, normoff); o = gl_cat(js, o, ",\"byteLength\":" as *u8); o = gl_num(js, o, normbytes); o = gl_cat(js, o, ",\"target\":34962}," as *u8) 119 o = gl_cat(js, o, "{\"buffer\":0,\"byteOffset\":" as *u8); o = gl_num(js, o, coloff); o = gl_cat(js, o, ",\"byteLength\":" as *u8); o = gl_num(js, o, colbytes); o = gl_cat(js, o, ",\"target\":34962}," as *u8) 120 o = gl_cat(js, o, "{\"buffer\":0,\"byteOffset\":" as *u8); o = gl_num(js, o, idxoff); o = gl_cat(js, o, ",\"byteLength\":" as *u8); o = gl_num(js, o, idxbytes); o = gl_cat(js, o, ",\"target\":34963}]," as *u8) 121 o = gl_cat(js, o, "\"accessors\":[{\"bufferView\":0,\"componentType\":5126,\"count\":" as *u8); o = gl_num(js, o, nv) 122 o = gl_cat(js, o, ",\"type\":\"VEC3\",\"min\":[" as *u8); o = gl_num(js, o, minx); o = gl_cat(js, o, "," as *u8); o = gl_num(js, o, miny); o = gl_cat(js, o, "," as *u8); o = gl_num(js, o, minz) 123 o = gl_cat(js, o, "],\"max\":[" as *u8); o = gl_num(js, o, maxx); o = gl_cat(js, o, "," as *u8); o = gl_num(js, o, maxy); o = gl_cat(js, o, "," as *u8); o = gl_num(js, o, maxz) 124 o = gl_cat(js, o, "]},{\"bufferView\":1,\"componentType\":5126,\"count\":" as *u8); o = gl_num(js, o, nv); o = gl_cat(js, o, ",\"type\":\"VEC3\"}," as *u8) 125 o = gl_cat(js, o, "{\"bufferView\":2,\"componentType\":5121,\"normalized\":true,\"count\":" as *u8); o = gl_num(js, o, nv); o = gl_cat(js, o, ",\"type\":\"VEC4\"}," as *u8) 126 o = gl_cat(js, o, "{\"bufferView\":3,\"componentType\":5125,\"count\":" as *u8); o = gl_num(js, o, nf*3); o = gl_cat(js, o, ",\"type\":\"SCALAR\"}]}" as *u8) 127 let jsonlen: i64 = o 128 var jpad: i64 = (4 - (jsonlen & 3)) & 3 129 var bpad: i64 = (4 - (binlen & 3)) & 3 130 let total: i64 = 12 + 8 + jsonlen + jpad + 8 + binlen + bpad 131 let out: *u8 = sys_mmap(total + 64) 132 gl_wr32(out, 0, 0x46546C67); gl_wr32(out, 4, 2); gl_wr32(out, 8, total) 133 gl_wr32(out, 12, jsonlen + jpad); gl_wr32(out, 16, 0x4E4F534A) 134 var p: i64 = 20 135 var k: i64 = 0 136 while k < jsonlen { out[p] = js[k]; p = p + 1; k = k + 1 } 137 k = 0; while k < jpad { out[p] = 32 as u8; p = p + 1; k = k + 1 } 138 gl_wr32(out, p, binlen + bpad); p = p + 4 139 gl_wr32(out, p, 0x004E4942); p = p + 4 140 k = 0; while k < binlen { out[p] = bin[k]; p = p + 1; k = k + 1 } 141 k = 0; while k < bpad { out[p] = 0 as u8; p = p + 1; k = k + 1 } 142 let fd: i64 = sys_openat_wr(path, 0x1a4) 143 if fd < 0 { return 0 - 1 } 144 sys_write(fd, out, p) 145 sys_close(fd) 146 return p 147} 148 149// write a .glb. vbuf = nv*3 fx1024 ints; fbuf = nf*3 tri vertex indices. Returns byte length, or -1. 150func write_glb(vbuf: *i64, fbuf: *i64, nv: i64, nf: i64, path: *u8) -> i64 { 151 // ---- BIN: float32 positions then uint32 indices (both 4-aligned; 12 & 4 are multiples of 4) ---- 152 let posbytes: i64 = nv * 12 153 let normbytes: i64 = nv * 12 154 let idxbytes: i64 = nf * 3 * 4 155 let binlen: i64 = posbytes + normbytes + idxbytes 156 let bin: *u8 = sys_mmap(binlen + 64) 157 let nrm: *i64 = sys_mmap(nv * 3 * 8) as *i64 158 gl_vnormals(vbuf, fbuf, nv, nf, nrm) 159 var bo: i64 = 0 160 var i: i64 = 0 161 while i < nv { 162 bo = gl_wr32(bin, bo, gl_i2f32(vbuf[i*3])) 163 bo = gl_wr32(bin, bo, gl_i2f32(vbuf[i*3+1])) 164 bo = gl_wr32(bin, bo, gl_i2f32(vbuf[i*3+2])) 165 i = i + 1 166 } 167 let normoff: i64 = bo 168 i = 0 169 while i < nv { 170 bo = gl_wr32(bin, bo, gl_i2f32(nrm[i*3])) 171 bo = gl_wr32(bin, bo, gl_i2f32(nrm[i*3+1])) 172 bo = gl_wr32(bin, bo, gl_i2f32(nrm[i*3+2])) 173 i = i + 1 174 } 175 let idxoff: i64 = bo 176 var j: i64 = 0 177 while j < nf * 3 { bo = gl_wr32(bin, bo, fbuf[j]); j = j + 1 } 178 // bounds (raw ints -> JSON numbers, matching the float positions) 179 var minx: i64 = vbuf[0]; var miny: i64 = vbuf[1]; var minz: i64 = vbuf[2] 180 var maxx: i64 = vbuf[0]; var maxy: i64 = vbuf[1]; var maxz: i64 = vbuf[2] 181 i = 1 182 while i < nv { 183 if vbuf[i*3]<minx{minx=vbuf[i*3]} if vbuf[i*3]>maxx{maxx=vbuf[i*3]} 184 if vbuf[i*3+1]<miny{miny=vbuf[i*3+1]} if vbuf[i*3+1]>maxy{maxy=vbuf[i*3+1]} 185 if vbuf[i*3+2]<minz{minz=vbuf[i*3+2]} if vbuf[i*3+2]>maxz{maxz=vbuf[i*3+2]} 186 i = i + 1 187 } 188 // ---- JSON chunk ---- 189 let js: *u8 = sys_mmap(K_MAGIC_8192) 190 var o: i64 = gl_cat(js, 0, "{\"asset\":{\"version\":\"2.0\",\"generator\":\"nishi-nx_gltf\"},\"scene\":0,\"scenes\":[{\"nodes\":[0]}]," as *u8) 191 o = gl_cat(js, o, "\"nodes\":[{\"mesh\":0,\"scale\":[0.0009765625,0.0009765625,0.0009765625]}]," as *u8) 192 o = gl_cat(js, o, "\"meshes\":[{\"primitives\":[{\"attributes\":{\"POSITION\":0,\"NORMAL\":1},\"indices\":2}]}]," as *u8) 193 o = gl_cat(js, o, "\"buffers\":[{\"byteLength\":" as *u8); o = gl_num(js, o, binlen); o = gl_cat(js, o, "}]," as *u8) 194 o = gl_cat(js, o, "\"bufferViews\":[{\"buffer\":0,\"byteOffset\":0,\"byteLength\":" as *u8); o = gl_num(js, o, posbytes); o = gl_cat(js, o, ",\"target\":34962}," as *u8) 195 o = gl_cat(js, o, "{\"buffer\":0,\"byteOffset\":" as *u8); o = gl_num(js, o, normoff); o = gl_cat(js, o, ",\"byteLength\":" as *u8); o = gl_num(js, o, normbytes); o = gl_cat(js, o, ",\"target\":34962}," as *u8) 196 o = gl_cat(js, o, "{\"buffer\":0,\"byteOffset\":" as *u8); o = gl_num(js, o, idxoff); o = gl_cat(js, o, ",\"byteLength\":" as *u8); o = gl_num(js, o, idxbytes); o = gl_cat(js, o, ",\"target\":34963}]," as *u8) 197 o = gl_cat(js, o, "\"accessors\":[{\"bufferView\":0,\"componentType\":5126,\"count\":" as *u8); o = gl_num(js, o, nv) 198 o = gl_cat(js, o, ",\"type\":\"VEC3\",\"min\":[" as *u8); o = gl_num(js, o, minx); o = gl_cat(js, o, "," as *u8); o = gl_num(js, o, miny); o = gl_cat(js, o, "," as *u8); o = gl_num(js, o, minz) 199 o = gl_cat(js, o, "],\"max\":[" as *u8); o = gl_num(js, o, maxx); o = gl_cat(js, o, "," as *u8); o = gl_num(js, o, maxy); o = gl_cat(js, o, "," as *u8); o = gl_num(js, o, maxz) 200 o = gl_cat(js, o, "]},{\"bufferView\":1,\"componentType\":5126,\"count\":" as *u8); o = gl_num(js, o, nv); o = gl_cat(js, o, ",\"type\":\"VEC3\"}," as *u8) 201 o = gl_cat(js, o, "{\"bufferView\":2,\"componentType\":5125,\"count\":" as *u8); o = gl_num(js, o, nf*3); o = gl_cat(js, o, ",\"type\":\"SCALAR\"}]}" as *u8) 202 let jsonlen: i64 = o 203 var jpad: i64 = (4 - (jsonlen & 3)) & 3 204 var bpad: i64 = (4 - (binlen & 3)) & 3 205 // ---- GLB container ---- 206 let total: i64 = 12 + 8 + jsonlen + jpad + 8 + binlen + bpad 207 let out: *u8 = sys_mmap(total + 64) 208 gl_wr32(out, 0, 0x46546C67) // magic "glTF" 209 gl_wr32(out, 4, 2) // version 2 210 gl_wr32(out, 8, total) 211 gl_wr32(out, 12, jsonlen + jpad) // JSON chunk length 212 gl_wr32(out, 16, 0x4E4F534A) // "JSON" 213 var p: i64 = 20 214 var k: i64 = 0 215 while k < jsonlen { out[p] = js[k]; p = p + 1; k = k + 1 } 216 k = 0; while k < jpad { out[p] = 32 as u8; p = p + 1; k = k + 1 } // pad JSON with spaces 217 gl_wr32(out, p, binlen + bpad); p = p + 4 // BIN chunk length 218 gl_wr32(out, p, 0x004E4942); p = p + 4 // "BIN\0" 219 k = 0; while k < binlen { out[p] = bin[k]; p = p + 1; k = k + 1 } 220 k = 0; while k < bpad { out[p] = 0 as u8; p = p + 1; k = k + 1 } 221 let fd: i64 = sys_openat_wr(path, 0x1a4) 222 if fd < 0 { return 0 - 1 } 223 sys_write(fd, out, p) 224 sys_close(fd) 225 return p 226}