nx_gltf_scene.nx source
↩ module page · 176 lines · 10338 B
1// nx_gltf_scene.nx -- ★MULTI-MESH glTF export: assemble MANY nx_trimesh meshes (one per anatomical system) into ONE .glb
2// scene -- each a named node/mesh/material -> a PEELABLE holistic being as a single standard 3D asset. Reuses the byte +
3// float encoders from nx_gltf_mesh. Flow: sc_reset() ; for each system { build mesh in trimesh ; sc_add_mesh(name) } ;
4// sc_finalize(out). Each system is tm_reset separately so its indices are already local/0-based per mesh. license_tier: ORIGINAL
5import "nx_syscalls.nx"
6import "nx_trimesh.nx"
7import "nx_gltf_mesh.nx"
8const SC_MAGIC_16777216: i64 = 16777216
9const SC_MAGIC_2000000000: i64 = 2000000000
10const SC_MAGIC_32767: i64 = 32767
11const SC_MAGIC_65535: i64 = 65535
12const SC_MAGIC_131072: i64 = 131072
13
14const SC_MAXM: i64 = 16
15static SC_BIN: i64
16static SC_BINLEN: i64
17static SC_M: i64
18static SC_POSOFF: i64
19static SC_NRMOFF: i64
20static SC_COLOFF: i64
21static SC_IDXOFF: i64
22static SC_VCNT: i64
23static SC_ICNT: i64
24static SC_NAME: i64
25static SC_MINX: i64
26static SC_MINY: i64
27static SC_MINZ: i64
28static SC_MAXX: i64
29static SC_MAXY: i64
30static SC_MAXZ: i64
31static SC_ROUGH: i64 // ★PBR per-material roughness (milli, 0..1000)
32static SC_METAL: i64 // ★PBR per-material metalness (milli, 0..1000)
33// write milli/1000 as a JSON decimal (e.g. 750 -> "0.750", 1000 -> "1.000")
34func sc_jfrac(o: *u8, at: i64, milli: i64) -> i64 {
35 var a: i64 = at
36 let whole: i64 = milli/1000
37 o[a] = (48+whole) as u8; a = a+1
38 o[a] = 46 as u8; a = a+1 // '.'
39 var f: i64 = milli%1000
40 o[a] = (48 + f/100) as u8; a = a+1
41 o[a] = (48 + (f/10)%10) as u8; a = a+1
42 o[a] = (48 + f%10) as u8; a = a+1
43 return a
44}
45
46func sc_reset() -> i64 {
47 if SC_BIN == 0 {
48 SC_BIN = sys_mmap(SC_MAGIC_16777216) // 16 MB bin
49 SC_POSOFF = sys_mmap(SC_MAXM*8) as i64; SC_NRMOFF = sys_mmap(SC_MAXM*8) as i64; SC_COLOFF = sys_mmap(SC_MAXM*8) as i64; SC_IDXOFF = sys_mmap(SC_MAXM*8) as i64
50 SC_VCNT = sys_mmap(SC_MAXM*8) as i64; SC_ICNT = sys_mmap(SC_MAXM*8) as i64; SC_NAME = sys_mmap(SC_MAXM*8) as i64
51 SC_MINX = sys_mmap(SC_MAXM*8) as i64; SC_MINY = sys_mmap(SC_MAXM*8) as i64; SC_MINZ = sys_mmap(SC_MAXM*8) as i64
52 SC_MAXX = sys_mmap(SC_MAXM*8) as i64; SC_MAXY = sys_mmap(SC_MAXM*8) as i64; SC_MAXZ = sys_mmap(SC_MAXM*8) as i64
53 SC_ROUGH = sys_mmap(SC_MAXM*8) as i64; SC_METAL = sys_mmap(SC_MAXM*8) as i64
54 }
55 SC_BINLEN = 0; SC_M = 0
56 return 0
57}
58// append the CURRENT trimesh as a named mesh in the scene
59func sc_add_mesh(name: *u8, rough: i64, metal: i64) -> i64 {
60 let NV: i64 = tm_nv(); let NT: i64 = tm_nt(); let NI: i64 = NT*3
61 if NV == 0 { return 0 }
62 if SC_M >= SC_MAXM { return 0 }
63 let bin: *u8 = SC_BIN as *u8
64 let vp: *i64 = sys_mmap(24) as *i64
65 let tt: *i64 = sys_mmap(24) as *i64
66 let posOff: i64 = SC_BINLEN
67 var minx: i64 = SC_MAGIC_2000000000; var miny: i64 = SC_MAGIC_2000000000; var minz: i64 = SC_MAGIC_2000000000
68 var maxx: i64 = 0-SC_MAGIC_2000000000; var maxy: i64 = 0-SC_MAGIC_2000000000; var maxz: i64 = 0-SC_MAGIC_2000000000
69 var a: i64 = 0; var at: i64 = posOff
70 while a < NV {
71 tm_vpos(a, vp)
72 at = gl_f32(bin, at, vp[0]); at = gl_f32(bin, at, vp[1]); at = gl_f32(bin, at, vp[2])
73 if vp[0]<minx {minx=vp[0]} if vp[0]>maxx {maxx=vp[0]}
74 if vp[1]<miny {miny=vp[1]} if vp[1]>maxy {maxy=vp[1]}
75 if vp[2]<minz {minz=vp[2]} if vp[2]>maxz {maxz=vp[2]}
76 a = a + 1
77 }
78 // SPEC FIX 2026-08-25: {VEC3, FLOAT} unit normals via the SHARED emitter. This was the FOURTH copy of
79 // the same normalized-int16 loop in this estate, and it was found only because removing the other three
80 // left gl_u16 apparently unwired -- and it was not. glTF 2.0 permits only {VEC3, FLOAT} for NORMAL, and
81 // a tightly packed VEC3 of SHORT is a 6-byte element the 4-byte alignment rule forbids.
82 let nrmOff: i64 = posOff + NV*GL_VEC3_F32_BYTES
83 at = gl_emit_normals(bin, nrmOff, NV)
84 let colOff: i64 = gl_align4(nrmOff + NV*GL_VEC3_F32_BYTES)
85 a = 0; at = colOff
86 while a < NV {
87 let col: i64 = tm_getvcol(a)
88 at = gl_u8(bin, at, col&255); at = gl_u8(bin, at, (col>>8)&255); at = gl_u8(bin, at, (col>>16)&255); at = gl_u8(bin, at, 255)
89 a = a + 1
90 }
91 let idxOff: i64 = colOff + NV*4
92 var tix: i64 = 0; at = idxOff
93 while tix < NT { tm_tget(tix, tt); at = gl_u32(bin, at, tt[0]); at = gl_u32(bin, at, tt[1]); at = gl_u32(bin, at, tt[2]); tix = tix+1 }
94 SC_BINLEN = gl_align4(idxOff + NI*4)
95 let m: i64 = SC_M
96 let PO: *i64=SC_POSOFF as *i64; let NO: *i64=SC_NRMOFF as *i64; let CO: *i64=SC_COLOFF as *i64; let IO: *i64=SC_IDXOFF as *i64
97 let VC: *i64=SC_VCNT as *i64; let IC: *i64=SC_ICNT as *i64; let NM: *i64=SC_NAME as *i64
98 let MNX: *i64=SC_MINX as *i64; let MNY: *i64=SC_MINY as *i64; let MNZ: *i64=SC_MINZ as *i64
99 let MXX: *i64=SC_MAXX as *i64; let MXY: *i64=SC_MAXY as *i64; let MXZ: *i64=SC_MAXZ as *i64
100 PO[m]=posOff; NO[m]=nrmOff; CO[m]=colOff; IO[m]=idxOff
101 VC[m]=NV; IC[m]=NI; NM[m]=name as i64
102 MNX[m]=minx; MNY[m]=miny; MNZ[m]=minz
103 MXX[m]=maxx; MXY[m]=maxy; MXZ[m]=maxz
104 let RG: *i64=SC_ROUGH as *i64; let MT: *i64=SC_METAL as *i64
105 RG[m]=rough; MT[m]=metal
106 SC_M = m + 1
107 return 0
108}
109func sc_mesh_count() -> i64 { return SC_M }
110
111func sc_finalize(out: *u8) -> i64 {
112 let PO: *i64=SC_POSOFF as *i64; let NO: *i64=SC_NRMOFF as *i64; let CO: *i64=SC_COLOFF as *i64; let IO: *i64=SC_IDXOFF as *i64
113 let VC: *i64=SC_VCNT as *i64; let IC: *i64=SC_ICNT as *i64; let NM: *i64=SC_NAME as *i64
114 let MNX: *i64=SC_MINX as *i64; let MNY: *i64=SC_MINY as *i64; let MNZ: *i64=SC_MINZ as *i64
115 let MXX: *i64=SC_MAXX as *i64; let MXY: *i64=SC_MAXY as *i64; let MXZ: *i64=SC_MAXZ as *i64
116 let json: *u8 = sys_mmap(SC_MAGIC_131072)
117 var j: i64 = 0
118 j = jcat(json, j, "{\"asset\":{\"version\":\"2.0\",\"generator\":\"nishi nx_gltf_scene\"},\"scene\":0,\"scenes\":[{\"nodes\":[" as *u8)
119 var m: i64 = 0
120 while m < SC_M { if m>0 { j=jcat(json,j,","as *u8) } j=jnum(json,j,m); m=m+1 }
121 j = jcat(json, j, "]}],\"nodes\":[" as *u8)
122 m = 0
123 while m < SC_M { if m>0 { j=jcat(json,j,","as *u8) } j=jcat(json,j,"{\"mesh\":"as *u8); j=jnum(json,j,m); j=jcat(json,j,",\"name\":\""as *u8); j=jcat(json,j,NM[m] as *u8); j=jcat(json,j,"\"}"as *u8); m=m+1 }
124 j = jcat(json, j, "],\"meshes\":[" as *u8)
125 m = 0
126 while m < SC_M {
127 if m>0 { j=jcat(json,j,","as *u8) }
128 j=jcat(json,j,"{\"primitives\":[{\"attributes\":{\"POSITION\":"as *u8); j=jnum(json,j,4*m); j=jcat(json,j,",\"NORMAL\":"as *u8); j=jnum(json,j,4*m+1); j=jcat(json,j,",\"COLOR_0\":"as *u8); j=jnum(json,j,4*m+2); j=jcat(json,j,"},\"indices\":"as *u8); j=jnum(json,j,4*m+3); j=jcat(json,j,",\"material\":"as *u8); j=jnum(json,j,m); j=jcat(json,j,"}]}"as *u8)
129 m=m+1
130 }
131 j = jcat(json, j, "],\"materials\":[" as *u8)
132 let RG2: *i64=SC_ROUGH as *i64; let MT2: *i64=SC_METAL as *i64
133 m = 0
134 while m < SC_M {
135 if m>0 { j=jcat(json,j,","as *u8) }
136 j=jcat(json,j,"{\"pbrMetallicRoughness\":{\"metallicFactor\":"as *u8); j=sc_jfrac(json,j,MT2[m]); j=jcat(json,j,",\"roughnessFactor\":"as *u8); j=sc_jfrac(json,j,RG2[m]); j=jcat(json,j,"},\"doubleSided\":true}"as *u8)
137 m=m+1
138 }
139 j = jcat(json, j, "],\"accessors\":[" as *u8)
140 m = 0
141 while m < SC_M {
142 if m>0 { j=jcat(json,j,","as *u8) }
143 j=jcat(json,j,"{\"bufferView\":"as *u8); j=jnum(json,j,4*m); j=jcat(json,j,",\"componentType\":5126,\"count\":"as *u8); j=jnum(json,j,VC[m]); j=jcat(json,j,",\"type\":\"VEC3\",\"min\":["as *u8); j=jnum(json,j,MNX[m]); j=jcat(json,j,","as *u8); j=jnum(json,j,MNY[m]); j=jcat(json,j,","as *u8); j=jnum(json,j,MNZ[m]); j=jcat(json,j,"],\"max\":["as *u8); j=jnum(json,j,MXX[m]); j=jcat(json,j,","as *u8); j=jnum(json,j,MXY[m]); j=jcat(json,j,","as *u8); j=jnum(json,j,MXZ[m]); j=jcat(json,j,"]},"as *u8)
144 j=jcat(json,j,"{\"bufferView\":"as *u8); j=jnum(json,j,4*m+1); j=jcat(json,j,",\"componentType\":5126,\"count\":"as *u8); j=jnum(json,j,VC[m]); j=jcat(json,j,",\"type\":\"VEC3\"},"as *u8)
145 j=jcat(json,j,"{\"bufferView\":"as *u8); j=jnum(json,j,4*m+2); j=jcat(json,j,",\"componentType\":5121,\"normalized\":true,\"count\":"as *u8); j=jnum(json,j,VC[m]); j=jcat(json,j,",\"type\":\"VEC4\"},"as *u8)
146 j=jcat(json,j,"{\"bufferView\":"as *u8); j=jnum(json,j,4*m+3); j=jcat(json,j,",\"componentType\":5125,\"count\":"as *u8); j=jnum(json,j,IC[m]); j=jcat(json,j,",\"type\":\"SCALAR\"}"as *u8)
147 m=m+1
148 }
149 j = jcat(json, j, "],\"bufferViews\":[" as *u8)
150 m = 0
151 while m < SC_M {
152 if m>0 { j=jcat(json,j,","as *u8) }
153 j=jcat(json,j,"{\"buffer\":0,\"byteOffset\":"as *u8); j=jnum(json,j,PO[m]); j=jcat(json,j,",\"byteLength\":"as *u8); j=jnum(json,j,VC[m]*12); j=jcat(json,j,",\"target\":34962},"as *u8)
154 j=jcat(json,j,"{\"buffer\":0,\"byteOffset\":"as *u8); j=jnum(json,j,NO[m]); j=jcat(json,j,",\"byteLength\":"as *u8); j=jnum(json,j,VC[m]*6); j=jcat(json,j,",\"target\":34962},"as *u8)
155 j=jcat(json,j,"{\"buffer\":0,\"byteOffset\":"as *u8); j=jnum(json,j,CO[m]); j=jcat(json,j,",\"byteLength\":"as *u8); j=jnum(json,j,VC[m]*4); j=jcat(json,j,",\"target\":34962},"as *u8)
156 j=jcat(json,j,"{\"buffer\":0,\"byteOffset\":"as *u8); j=jnum(json,j,IO[m]); j=jcat(json,j,",\"byteLength\":"as *u8); j=jnum(json,j,IC[m]*4); j=jcat(json,j,",\"target\":34963}"as *u8)
157 m=m+1
158 }
159 j = jcat(json, j, "],\"buffers\":[{\"byteLength\":" as *u8); j = jnum(json, j, SC_BINLEN); j = jcat(json, j, "}]}" as *u8)
160 let jsonLen: i64 = j
161 let jsonPad: i64 = gl_align4(jsonLen)
162 // GLB
163 let bin: *u8 = SC_BIN as *u8
164 var o: i64 = 0
165 o = gl_str(out, o, "glTF" as *u8); o = gl_u32(out, o, 2)
166 let total: i64 = 12 + 8 + jsonPad + 8 + SC_BINLEN
167 o = gl_u32(out, o, total)
168 o = gl_u32(out, o, jsonPad); o = gl_str(out, o, "JSON" as *u8)
169 var q: i64 = 0
170 while q < jsonLen { out[o]=json[q]; o=o+1; q=q+1 }
171 while q < jsonPad { out[o]=32 as u8; o=o+1; q=q+1 }
172 o = gl_u32(out, o, SC_BINLEN); o = gl_u8(out, o, 66); o = gl_u8(out, o, 73); o = gl_u8(out, o, 78); o = gl_u8(out, o, 0)
173 q = 0
174 while q < SC_BINLEN { out[o]=bin[q]; o=o+1; q=q+1 }
175 return o
176}