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}