nx_gltf_export_material_candidate_t340.nx source
↩ module page · 282 lines · 17320 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"
8import "nx_atomic_rewrite.nx"
9import "nx_vecmath.nx"
10import "nx_vnormals_lib.nx"
11import "nx_glbnorm_lib.nx"
12const K_MAGIC_8192: i64 = 8192
13
14// signed integer -> IEEE-754 float32 bits (low 32 of the return). Truncating mantissa (fine for geometry).
15func gl_i2f32(v: i64) -> i64 {
16 if v == 0 { return 0 }
17 var sign: i64 = 0
18 var a: i64 = v
19 if a < 0 { sign = 1; a = 0 - a }
20 var e: i64 = 0
21 var t: i64 = a
22 while t > 1 { t = t >> 1; e = e + 1 } // e = floor(log2(a))
23 var mant: i64 = 0
24 if e <= 23 { mant = (a << (23 - e)) & 0x7fffff }
25 else { mant = (a >> (e - 23)) & 0x7fffff }
26 let exp: i64 = 127 + e
27 return (sign << 31) | (exp << 23) | mant
28}
29func gl_wr32(buf: *u8, o: i64, v: i64) -> i64 {
30 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
31 return o + 4
32}
33func 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 }
34func gl_num(d: *u8, o: i64, v: i64) -> i64 {
35 var x: i64 = v; var oo: i64 = o
36 if x < 0 { d[oo]=45 as u8; oo=oo+1; x=0-x }
37 if x == 0 { d[oo]=48 as u8; return oo+1 }
38 let t: *u8 = sys_mmap(24); var k: i64 = 0
39 while x > 0 { t[k]=(48+x%10) as u8; x=x/10; k=k+1 }
40 var i: i64 = 0
41 while i < k { d[oo+i]=t[k-1-i]; i=i+1 }
42 return oo + k
43}
44// (gl_isqrt removed 2026-08-25: a one-line wrapper over vm_isqrt whose ONLY caller was gl_vnormals's
45// former body, now retired onto nx_vnormals_lib. Proven callerless first -- nx_absent over the whole of
46// buildroot/runtime, corpus_complete=1: the only other hit is a SEPARATE gl_isqrt that
47// nx_procgen_gtlabels_gate defines for itself.)
48// smooth per-vertex normals into nrm[nv*3], at vn_smooth_area's VN_SCALE of ~1000.
49// CORRECTED 2026-08-25: this line used to end "renderers re-normalise after the node's inverse-transpose",
50// which was a COMMENT, NOT A MEASUREMENT, and it was false. glTF 2.0 requires the NORMAL accessor itself to
51// carry unit vectors, and the Khronos validator ranks a non-unit one an Error -- 33,656 of them across the
52// 12 published .glb files this function wrote. The ~1000 scale is still correct HERE, because nx_obj_export
53// consumes the same buffer and OBJ importers do rescale; the unit conversion now happens at the glTF write
54// site only, in nx_glbnorm_lib.gn_unit3.
55// RETIRED ONTO nx_vnormals_lib.vn_smooth_area, 2026-08-25. This function's body WAS the area-weighted
56// accumulate-and-normalise, character for character the same computation as the block that was inline inside
57// nx_obj_export.write_obj. One owner now; nx_vnormals_gate carries a verbatim transcription of this former
58// body as a reference oracle and asserts the owner reproduces it EXACTLY on every fixture, so the retirement
59// is proven rather than assumed.
60// The one difference, named: the owner REFUSES nv <= 0 (VN_E_ARGS) where this returned 0. Both call sites
61// below discard the return value and neither passes nv <= 0, so no caller observes it; for nv > 0 the return
62// is 0 exactly as before.
63func gl_vnormals(vbuf: *i64, fbuf: *i64, nv: i64, nf: i64, nrm: *i64) -> i64 {
64 return vn_smooth_area(vbuf, fbuf, nv, nf, nrm)
65}
66
67// write a .glb WITH per-vertex COLOR_0 (the texture-bake path: photo colors baked onto the mesh, VR-loadable
68// with no UV unwrap). cbuf = nv*3 i64 (RGB 0..255). COLOR_0 = VEC4 normalized UNSIGNED_BYTE (RGBA, A=255) --
69// the standard cleanly-4-aligned vertex-color format every glTF/VRM viewer accepts. Returns byte length or -1.
70func write_glb_colored(vbuf: *i64, fbuf: *i64, cbuf: *i64, nv: i64, nf: i64, path: *u8) -> i64 {
71 let posbytes: i64 = nv * 12
72 let normbytes: i64 = nv * 12
73 let colbytes: i64 = nv * 4
74 let idxbytes: i64 = nf * 3 * 4
75 let binlen: i64 = posbytes + normbytes + colbytes + idxbytes
76 let bin: *u8 = sys_mmap(binlen + 64)
77 let nrm: *i64 = sys_mmap(nv * 3 * 8) as *i64
78 gl_vnormals(vbuf, fbuf, nv, nf, nrm)
79 var bo: i64 = 0
80 var i: i64 = 0
81 while i < nv {
82 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]))
83 i = i + 1
84 }
85 let normoff: i64 = bo
86 // SPEC FIX 2026-08-25 (glTF 2.0 requires unit NORMALs; the validator ranks a non-unit one an Error).
87 // Allocated ONCE, outside the loop -- never allocate in a hot loop.
88 let un: *i64 = sys_mmap(GN_V3 * 8) as *i64
89 i = 0
90 while i < nv {
91 gn_unit3(nrm[i*3], nrm[i*3+1], nrm[i*3+2], un)
92 bo = gl_wr32(bin, bo, gn_f32(un[0])); bo = gl_wr32(bin, bo, gn_f32(un[1])); bo = gl_wr32(bin, bo, gn_f32(un[2]))
93 i = i + 1
94 }
95 let coloff: i64 = bo
96 i = 0
97 while i < nv {
98 var r: i64 = cbuf[i*3]; var g: i64 = cbuf[i*3+1]; var b: i64 = cbuf[i*3+2]
99 if r < 0 { r = 0 } if r > 255 { r = 255 }
100 if g < 0 { g = 0 } if g > 255 { g = 255 }
101 if b < 0 { b = 0 } if b > 255 { b = 255 }
102 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
103 i = i + 1
104 }
105 let idxoff: i64 = bo
106 var j: i64 = 0
107 while j < nf * 3 { bo = gl_wr32(bin, bo, fbuf[j]); j = j + 1 }
108 var minx: i64 = vbuf[0]; var miny: i64 = vbuf[1]; var minz: i64 = vbuf[2]
109 var maxx: i64 = vbuf[0]; var maxy: i64 = vbuf[1]; var maxz: i64 = vbuf[2]
110 i = 1
111 while i < nv {
112 if vbuf[i*3]<minx{minx=vbuf[i*3]} if vbuf[i*3]>maxx{maxx=vbuf[i*3]}
113 if vbuf[i*3+1]<miny{miny=vbuf[i*3+1]} if vbuf[i*3+1]>maxy{maxy=vbuf[i*3+1]}
114 if vbuf[i*3+2]<minz{minz=vbuf[i*3+2]} if vbuf[i*3+2]>maxz{maxz=vbuf[i*3+2]}
115 i = i + 1
116 }
117 let js: *u8 = sys_mmap(K_MAGIC_8192)
118 var o: i64 = gl_cat(js, 0, "{\"asset\":{\"version\":\"2.0\",\"generator\":\"nishi-nx_gltf\"},\"scene\":0,\"scenes\":[{\"nodes\":[0]}]," as *u8)
119 o = gl_cat(js, o, "\"nodes\":[{\"mesh\":0,\"scale\":[0.0009765625,0.0009765625,0.0009765625]}]," as *u8)
120 o = gl_cat(js, o, "\"meshes\":[{\"primitives\":[{\"attributes\":{\"POSITION\":0,\"NORMAL\":1,\"COLOR_0\":2},\"indices\":3}]}]," as *u8)
121 o = gl_cat(js, o, "\"buffers\":[{\"byteLength\":" as *u8); o = gl_num(js, o, binlen); o = gl_cat(js, o, "}]," as *u8)
122 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)
123 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)
124 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)
125 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)
126 o = gl_cat(js, o, "\"accessors\":[{\"bufferView\":0,\"componentType\":5126,\"count\":" as *u8); o = gl_num(js, o, nv)
127 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)
128 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)
129 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)
130 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)
131 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)
132 let jsonlen: i64 = o
133 var jpad: i64 = (4 - (jsonlen & 3)) & 3
134 var bpad: i64 = (4 - (binlen & 3)) & 3
135 let total: i64 = 12 + 8 + jsonlen + jpad + 8 + binlen + bpad
136 let out: *u8 = sys_mmap(total + 64)
137 gl_wr32(out, 0, 0x46546C67); gl_wr32(out, 4, 2); gl_wr32(out, 8, total)
138 gl_wr32(out, 12, jsonlen + jpad); gl_wr32(out, 16, 0x4E4F534A)
139 var p: i64 = 20
140 var k: i64 = 0
141 while k < jsonlen { out[p] = js[k]; p = p + 1; k = k + 1 }
142 k = 0; while k < jpad { out[p] = 32 as u8; p = p + 1; k = k + 1 }
143 gl_wr32(out, p, binlen + bpad); p = p + 4
144 gl_wr32(out, p, 0x004E4942); p = p + 4
145 k = 0; while k < binlen { out[p] = bin[k]; p = p + 1; k = k + 1 }
146 k = 0; while k < bpad { out[p] = 0 as u8; p = p + 1; k = k + 1 }
147 let fd: i64 = sys_openat_wr(path, 0x1a4)
148 if fd < 0 { return 0 - 1 }
149 sys_write(fd, out, p)
150 sys_close(fd)
151 return p
152}
153
154// write a .glb. vbuf = nv*3 fx1024 ints; fbuf = nf*3 tri vertex indices. Returns byte length, or -1.
155
156// Scalar glTF material ABI. RGB is linear, never encoded sRGB. Ratios use explicit fixed-point scales.
157const GL_MAT_R:i64=0
158const GL_MAT_G:i64=1
159const GL_MAT_B:i64=2
160const GL_MAT_METALLIC:i64=3
161const GL_MAT_ROUGHNESS:i64=4
162const GL_MAT_WORDS:i64=5
163const GL_MAT_IO_RC_BASE:i64=-100
164const GL_MAT_LINEAR_Q:i64=100000
165const GL_MAT_RATIO_Q:i64=1000
166func gl_material_ok(m:*i64,words:i64)->i64{
167 if (m as i64)<=0||words!=GL_MAT_WORDS{return 0}
168 var i:i64=GL_MAT_R;while i<=GL_MAT_B{if m[i]<0||m[i]>GL_MAT_LINEAR_Q{return 0};i=i+1}
169 if m[GL_MAT_METALLIC]<0||m[GL_MAT_METALLIC]>GL_MAT_RATIO_Q||m[GL_MAT_ROUGHNESS]<0||m[GL_MAT_ROUGHNESS]>GL_MAT_RATIO_Q{return 0}
170 return 1
171}
172func gl_material_decimal(out:*u8,pos:i64,value:i64,q:i64)->i64{
173 var p:i64=gl_num(out,pos,value/q);p=gl_cat(out,p,".");var d:i64=q/10
174 while d>0{out[p]=(48+(value/d)%10) as u8;p=p+1;d=d/10};return p
175}
176func gl_material_json(out:*u8,pos:i64,m:*i64)->i64{
177 var p:i64=gl_cat(out,pos,"\"materials\":[{\"pbrMetallicRoughness\":{\"baseColorFactor\":[")
178 var i:i64=GL_MAT_R;while i<=GL_MAT_B{if i>GL_MAT_R{p=gl_cat(out,p,",")};p=gl_material_decimal(out,p,m[i],GL_MAT_LINEAR_Q);i=i+1}
179 p=gl_cat(out,p,",1],\"metallicFactor\":");p=gl_material_decimal(out,p,m[GL_MAT_METALLIC],GL_MAT_RATIO_Q)
180 p=gl_cat(out,p,",\"roughnessFactor\":");p=gl_material_decimal(out,p,m[GL_MAT_ROUGHNESS],GL_MAT_RATIO_Q)
181 return gl_cat(out,p,"}}],")
182}
183
184func gl_write_glb_scalar(vbuf: *i64, fbuf: *i64, nv: i64, nf: i64, path: *u8, material:*i64) -> i64 {
185 // ---- BIN: float32 positions then uint32 indices (both 4-aligned; 12 & 4 are multiples of 4) ----
186 let posbytes: i64 = nv * 12
187 let normbytes: i64 = nv * 12
188 let idxbytes: i64 = nf * 3 * 4
189 let binlen: i64 = posbytes + normbytes + idxbytes
190 let bin: *u8 = sys_mmap(binlen + 64)
191 let nrm: *i64 = sys_mmap(nv * 3 * 8) as *i64
192 gl_vnormals(vbuf, fbuf, nv, nf, nrm)
193 var bo: i64 = 0
194 var i: i64 = 0
195 while i < nv {
196 bo = gl_wr32(bin, bo, gl_i2f32(vbuf[i*3]))
197 bo = gl_wr32(bin, bo, gl_i2f32(vbuf[i*3+1]))
198 bo = gl_wr32(bin, bo, gl_i2f32(vbuf[i*3+2]))
199 i = i + 1
200 }
201 let normoff: i64 = bo
202 // SPEC FIX 2026-08-25: the NORMAL accessor must carry UNIT vectors (glTF 2.0). This was the whole of
203 // ACCESSOR_VECTOR3_NON_UNIT x 33,656 across 12 published files. Positions and indices are untouched.
204 // Allocated ONCE, outside the loop -- never allocate in a hot loop.
205 let un: *i64 = sys_mmap(GN_V3 * 8) as *i64
206 i = 0
207 while i < nv {
208 gn_unit3(nrm[i*3], nrm[i*3+1], nrm[i*3+2], un)
209 bo = gl_wr32(bin, bo, gn_f32(un[0]))
210 bo = gl_wr32(bin, bo, gn_f32(un[1]))
211 bo = gl_wr32(bin, bo, gn_f32(un[2]))
212 i = i + 1
213 }
214 let idxoff: i64 = bo
215 var j: i64 = 0
216 while j < nf * 3 { bo = gl_wr32(bin, bo, fbuf[j]); j = j + 1 }
217 // bounds (raw ints -> JSON numbers, matching the float positions)
218 var minx: i64 = vbuf[0]; var miny: i64 = vbuf[1]; var minz: i64 = vbuf[2]
219 var maxx: i64 = vbuf[0]; var maxy: i64 = vbuf[1]; var maxz: i64 = vbuf[2]
220 i = 1
221 while i < nv {
222 if vbuf[i*3]<minx{minx=vbuf[i*3]} if vbuf[i*3]>maxx{maxx=vbuf[i*3]}
223 if vbuf[i*3+1]<miny{miny=vbuf[i*3+1]} if vbuf[i*3+1]>maxy{maxy=vbuf[i*3+1]}
224 if vbuf[i*3+2]<minz{minz=vbuf[i*3+2]} if vbuf[i*3+2]>maxz{maxz=vbuf[i*3+2]}
225 i = i + 1
226 }
227 // ---- JSON chunk ----
228 let js: *u8 = sys_mmap(K_MAGIC_8192)
229 var o: i64 = gl_cat(js, 0, "{\"asset\":{\"version\":\"2.0\",\"generator\":\"nishi-nx_gltf\"},\"scene\":0,\"scenes\":[{\"nodes\":[0]}]," as *u8)
230 o = gl_cat(js, o, "\"nodes\":[{\"mesh\":0,\"scale\":[0.0009765625,0.0009765625,0.0009765625]}]," as *u8)
231 if (material as i64)>0{o=gl_material_json(js,o,material)}
232 o = gl_cat(js, o, "\"meshes\":[{\"primitives\":[{\"attributes\":{\"POSITION\":0,\"NORMAL\":1},\"indices\":2" as *u8)
233 if (material as i64)>0{o=gl_cat(js,o,",\"material\":0")}
234 o=gl_cat(js,o,"}]}],")
235 o = gl_cat(js, o, "\"buffers\":[{\"byteLength\":" as *u8); o = gl_num(js, o, binlen); o = gl_cat(js, o, "}]," as *u8)
236 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)
237 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)
238 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)
239 o = gl_cat(js, o, "\"accessors\":[{\"bufferView\":0,\"componentType\":5126,\"count\":" as *u8); o = gl_num(js, o, nv)
240 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)
241 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)
242 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)
243 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)
244 let jsonlen: i64 = o
245 var jpad: i64 = (4 - (jsonlen & 3)) & 3
246 var bpad: i64 = (4 - (binlen & 3)) & 3
247 // ---- GLB container ----
248 let total: i64 = 12 + 8 + jsonlen + jpad + 8 + binlen + bpad
249 let out: *u8 = sys_mmap(total + 64)
250 gl_wr32(out, 0, 0x46546C67) // magic "glTF"
251 gl_wr32(out, 4, 2) // version 2
252 gl_wr32(out, 8, total)
253 gl_wr32(out, 12, jsonlen + jpad) // JSON chunk length
254 gl_wr32(out, 16, 0x4E4F534A) // "JSON"
255 var p: i64 = 20
256 var k: i64 = 0
257 while k < jsonlen { out[p] = js[k]; p = p + 1; k = k + 1 }
258 k = 0; while k < jpad { out[p] = 32 as u8; p = p + 1; k = k + 1 } // pad JSON with spaces
259 gl_wr32(out, p, binlen + bpad); p = p + 4 // BIN chunk length
260 gl_wr32(out, p, 0x004E4942); p = p + 4 // "BIN\0"
261 k = 0; while k < binlen { out[p] = bin[k]; p = p + 1; k = k + 1 }
262 k = 0; while k < bpad { out[p] = 0 as u8; p = p + 1; k = k + 1 }
263 if (material as i64)>0{
264 // Existing checked writer owns commit semantics. -107 means rename may be visible: do not retry blindly.
265 let rc:i64=atomic_rewrite_checked(path,out,p)
266 sys_munmap(bin,binlen+64);sys_munmap(nrm as *u8,nv*3*8);sys_munmap(un as *u8,GN_V3*8);sys_munmap(js,K_MAGIC_8192);sys_munmap(out,total+64)
267 if rc!=0{return GL_MAT_IO_RC_BASE+rc};return p
268 }
269 let fd: i64 = sys_openat_wr(path, 0x1a4)
270 if fd < 0 { return 0 - 1 }
271 sys_write(fd, out, p)
272 sys_close(fd)
273 return p
274}
275
276// Legacy output remains material-free and byte-identical. Geometry buffers retain the existing trusted internal contract.
277func write_glb(vbuf:*i64,fbuf:*i64,nv:i64,nf:i64,path:*u8)->i64{return gl_write_glb_scalar(vbuf,fbuf,nv,nf,path,0 as *i64)}
278// Validate the complete material boundary before any allocation or file operation.
279func write_glb_material(vbuf:*i64,fbuf:*i64,nv:i64,nf:i64,material:*i64,words:i64,path:*u8)->i64{
280 if gl_material_ok(material,words)!=1{return -2}
281 return gl_write_glb_scalar(vbuf,fbuf,nv,nf,path,material)
282}