nx_wasm_explodelab.nx source
↩ module page · 400 lines · 16882 B
1// nx_wasm_explodelab.nx -- EXPLODELAB: the interactive exploded-view surface (cadtwin R2b = the explodeview
2// benchmark's viewer half). The REAL as1-oc-214 STEP assembly (baked by nx_explodelab_treegen from the R1a
3// parse) explodes/assembles in staged 3D (nx_explode3d) through the sovereign mesh renderer (nx_meshrender
4// z-buffer raster) -- ALL INTEGER, so the whole surface vets byte-for-byte VM vs native. ALL logic lives
5// here (the no-JS-logic law): JS passes raw mouse+key bits and blits the framebuffer.
6// CONTROLS (bits): drag = orbit; short click = cycle part highlight; bit1(R) = reset; bit2(E) = explode;
7// bit3(D) = assemble. Until touched, the explosion auto-oscillates and the view slowly orbits (demo mode).
8// license_tier: ORIGINAL
9import "nx_syscalls.nx"
10import "nx_explode3d.nx"
11import "nx_mechparts.nx"
12import "nx_render_core.nx"
13import "nx_explodelab_tree.nx"
14import "nx_explodelab_meshes.nx"
15const O_MAGIC_4096: i64 = 4096
16const O_MAGIC_8192: i64 = 8192
17const O_MAGIC_4280860390: i64 = 4280860390
18const O_MAGIC_4280824550: i64 = 4280824550
19const O_MAGIC_4284139580: i64 = 4284139580
20const O_MAGIC_4288714390: i64 = 4288714390
21const O_MAGIC_4293294150: i64 = 4293294150
22const O_MAGIC_4292006610: i64 = 4292006610
23const O_MAGIC_1024: i64 = 1024
24const O_MAGIC_65536: i64 = 65536
25const O_MAGIC_16777216: i64 = 16777216
26const O_MAGIC_4278190080: i64 = 4278190080
27// ^ REAL 53/53 file-true as1 geometry (generated by nx_explodelab_meshgen from the STEP read pipeline):
28// exm_load(e,kind,cx,cy,cz,col) replaces the mp_* stand-ins -- true proportions at 32 world units/mm.
29// All exm_* functions are <=6 args (wat-lane safe). mechparts stays imported as the unknown-kind fallback era.
30// NOTE: nx_meshrender is DELIBERATELY NOT imported -- it defines mr_render (21 params); a >16-param function
31// (and any >16-arg call) is mis-emitted by the wat lane -> the BROWSER validator rejects the whole module
32// (our VM tolerates it, so the vet was blind). We inline the only two helpers we need (exl_roty/exl_project,
33// both <=10 args) so the shipped module has ZERO >16-param functions. See the compiler-arc TODO.
34
35// R-V2 RESOLUTION: 320x240 -> 960x720 (9x pixels; fb/zb are 8 B/px -> 5529600 B each). Offsets re-laid with
36// the ORIGINAL spacings for every non-framebuffer region (verified against the 320x240 table).
37const W: i64 = 960
38const H: i64 = 720
39const O_FB: i64 = 0
40const O_ZB: i64 = 5529600
41const O_LV: i64 = 11059200
42const O_WV: i64 = 11190272
43const O_IDX: i64 = 11321344
44const O_HOME: i64 = 11517952
45const O_DIR: i64 = 11519488
46const O_DEP: i64 = 11521024
47const O_VST: i64 = 11521536
48const O_VCT: i64 = 11522048
49const O_E: i64 = 11522560
50const O_SC: i64 = 11522816
51const O_PROJ: i64 = 11522832
52const O_ROTY: i64 = 11522960
53const O_MV: i64 = 11523088
54const O_TRANS: i64 = 11523216
55const O_MVP: i64 = 11523344
56const O_VBUF: i64 = 11523472
57const O_CLIP: i64 = 11523536
58const O_SCR: i64 = 11523600 // PER-VERTEX scratch: 4096 verts * 4 i64 = 131072 B (mr_render writes scr[vi*4])
59const O_TRI: i64 = 11654672
60const O_ST: i64 = 11654800
61// st: [0]=tick [1]=prevbtn [2]=t 0..1024 [3]=angle deg [4]=selected(-1 none) [5]=lastmx [6]=explode-touched
62// [7]=autodir [8]=downmx [9]=dragged [10]=orbit-touched
63const DIST: i64 = 40000
64const FOVH: i64 = 30
65const BG: i64 = 4280293912
66const EXL_NEAR: i64 = 16384 // = nx_meshrender MR_NEAR (1.0 Q14 near plane)
67const EXL_FAR: i64 = 16384000 // = nx_meshrender MR_FAR (1000.0 Q14 far plane)
68const O_ROTX: i64 = 11654928 // pitch matrix scratch (past O_ST+128)
69const O_YP: i64 = 11655056 // yaw*pitch product scratch
70const PITCH: i64 = 26 // fixed camera elevation (deg) -> the 3/4 CAD view that reveals 3D form
71
72func st_ptr(base: i64) -> *i64 { return (base + O_ST) as *i64 }
73func mem_bytes() -> i64 { return O_YP + 128 }
74func e_ptr(base: i64) -> *i64 { return (base + O_E) as *i64 }
75
76func world_init(base: i64) -> i64 {
77 let e: *i64 = e_ptr(base)
78 let ig: i64 = ex3_init(e, (base + O_LV) as *i64, (base + O_IDX) as *i64, (base + O_HOME) as *i64, (base + O_DIR) as *i64, (base + O_DEP) as *i64, (base + O_VST) as *i64, (base + O_VCT) as *i64, O_MAGIC_4096, O_MAGIC_8192, 64, (base + O_SC) as *i64)
79 // colors packed (a<<24 | b<<16 | g<<8 | r) -- same palette as the native gate
80 var i: i64 = 0
81 let n: i64 = exl_n()
82 while i < n {
83 let kind: i64 = exl_kind(i)
84 let hx: i64 = exl_hx(i)
85 let hy: i64 = exl_hy(i)
86 let hz: i64 = exl_hz(i)
87 let p: i64 = ex3_part_begin(e, hx, hy, hz, exl_dx(i), exl_dy(i), exl_dz(i), exl_depth(i))
88 var done: i64 = 0
89 // REAL file-true geometry (exm_load: baked from the as1 STEP via the full read pipeline)
90 if kind == 1 { let g1: i64 = exm_load(e, 1, hx, hy, hz, O_MAGIC_4280860390); done = 1 } // nut 20x15x3mm
91 if kind == 2 { let g2: i64 = exm_load(e, 2, hx, hy, hz, O_MAGIC_4280824550); done = 1 } // bolt 15x15x37mm
92 if kind == 3 { let g3: i64 = exm_load(e, 3, hx, hy, hz, O_MAGIC_4284139580); done = 1 } // rod 200mm
93 if kind == 4 { let g4: i64 = exm_load(e, 4, hx, hy, hz, O_MAGIC_4288714390); done = 1 } // plate 180x150x20mm
94 if kind == 5 { let g5: i64 = exm_load(e, 5, hx, hy, hz, O_MAGIC_4293294150); done = 1 } // l-bracket 50x60x100mm
95 if done == 0 { let g6: i64 = ex3_add_box(e, hx, hy, hz, 900, 900, 900, O_MAGIC_4292006610) }
96 i = i + 1
97 }
98 let st: *i64 = st_ptr(base)
99 st[0] = 0
100 st[1] = 0
101 st[2] = 0
102 st[3] = 28
103 st[4] = 0 - 1
104 st[5] = 0
105 st[6] = 0
106 st[7] = 8
107 st[8] = 0
108 st[9] = 0
109 st[10] = 0
110 return 0
111}
112
113func start_at(base: i64) -> i64 { return world_init(base) }
114
115func tick_at(base: i64, mx: i64, my: i64, buttons: i64) -> i64 {
116 let st: *i64 = st_ptr(base)
117 if (buttons & 2) != 0 { world_init(base); return 0 }
118 let prevb: i64 = st[1]
119 // left button edge-down: arm click/drag tracking
120 if (buttons & 1) != 0 {
121 if (prevb & 1) == 0 {
122 st[8] = mx
123 st[9] = 0
124 } else {
125 // held: orbit by horizontal drag
126 let dxm: i64 = mx - st[5]
127 if dxm != 0 {
128 st[3] = st[3] + dxm
129 st[10] = 1
130 }
131 var moved: i64 = mx - st[8]
132 if moved < 0 { moved = 0 - moved }
133 if moved > 3 { st[9] = 1 }
134 }
135 } else {
136 // edge-up with no drag = click -> cycle highlighted part
137 if (prevb & 1) != 0 {
138 if st[9] == 0 {
139 st[4] = st[4] + 1
140 if st[4] >= exl_n() { st[4] = 0 - 1 }
141 }
142 }
143 }
144 // explode controls
145 if (buttons & 4) != 0 { st[2] = st[2] + 16; st[6] = 1 }
146 if (buttons & 8) != 0 { st[2] = st[2] - 16; st[6] = 1 }
147 if st[2] > O_MAGIC_1024 { st[2] = O_MAGIC_1024 }
148 if st[2] < 0 { st[2] = 0 }
149 // demo mode until touched: auto explode-oscillate + slow orbit
150 if st[6] == 0 {
151 st[2] = st[2] + st[7]
152 if st[2] >= O_MAGIC_1024 { st[2] = O_MAGIC_1024; st[7] = 0 - 8 }
153 if st[2] <= 0 { st[2] = 0; st[7] = 8 }
154 }
155 if st[10] == 0 {
156 if (st[0] % 3) == 0 { st[3] = st[3] + 1 }
157 }
158 // wrap angle into 0..359
159 var a: i64 = st[3] % 360
160 if a < 0 { a = a + 360 }
161 st[3] = a
162 st[5] = mx
163 st[1] = buttons
164 st[0] = st[0] + 1
165 return 0
166}
167
168// inlined from nx_meshrender (so we don't import mr_render's 21-param definition). Y-rotation Q14 matrix.
169func exl_roty(deg: i64, out: *i64) -> i64 {
170 let c: i64 = rc_cos_q14(deg)
171 let s: i64 = rc_sin_q14(deg)
172 var i: i64 = 0
173 while i < 16 { out[i] = 0; i = i + 1 }
174 out[0] = c
175 out[2] = s
176 out[5] = RC_Q
177 out[8] = 0 - s
178 out[10] = c
179 out[15] = RC_Q
180 return 0
181}
182// X-rotation (pitch) Q14 matrix into out[16] -- tilts the model so the camera looks DOWN at it (3/4 view).
183func exl_rotx(deg: i64, out: *i64) -> i64 {
184 let c: i64 = rc_cos_q14(deg)
185 let s: i64 = rc_sin_q14(deg)
186 var i: i64 = 0
187 while i < 16 { out[i] = 0; i = i + 1 }
188 out[0] = RC_Q
189 out[5] = c
190 out[6] = 0 - s
191 out[9] = s
192 out[10] = c
193 out[15] = RC_Q
194 return 0
195}
196// inlined from nx_meshrender: project one model vertex through mvp -> scr[base+0..3] (screen x,y,depth,visible)
197func exl_project(mvp: *i64, vx: i64, vy: i64, vz: i64, w: i64, h: i64, vbuf: *i64, clipbuf: *i64, scr: *i64, base: i64) -> i64 {
198 vbuf[0] = vx
199 vbuf[1] = vy
200 vbuf[2] = vz
201 vbuf[3] = RC_Q
202 rc_mat4_vec4(mvp, vbuf, clipbuf)
203 let cw: i64 = clipbuf[3]
204 if cw <= 0 { scr[base + 3] = 0; return 0 }
205 let ndcx: i64 = clipbuf[0] * RC_Q / cw
206 let ndcy: i64 = clipbuf[1] * RC_Q / cw
207 let ndcz: i64 = clipbuf[2] * RC_Q / cw
208 scr[base + 0] = (ndcx + RC_Q) * w / (2 * RC_Q)
209 scr[base + 1] = (RC_Q - ndcy) * h / (2 * RC_Q)
210 scr[base + 2] = ndcz + RC_Q
211 scr[base + 3] = 1
212 return 0
213}
214
215// mr_render's body ported base-relative with ZERO >16-arg calls (mr_render takes 21 args; the wat lane
216// mis-emits >16-param funcs/calls -> the BROWSER rejects the module). Same semantics:
217// clear -> MVP -> project all verts -> z-buffer rasterize visible tris. All callees <= 10 args.
218// R-V1/R-V3 SHADING: scale channels by diffuse intensity li (Q8) + ADD white specular sp (metal look)
219func exl_shade(col: i64, li: i64, sp: i64) -> i64 {
220 var r: i64 = (col % 256) * li / 256 + sp
221 var g: i64 = ((col / 256) % 256) * li / 256 + sp
222 var b: i64 = ((col / O_MAGIC_65536) % 256) * li / 256 + sp
223 if r > 255 { r = 255 }
224 if g > 255 { g = 255 }
225 if b > 255 { b = 255 }
226 let a: i64 = (col / O_MAGIC_16777216) % 256
227 return r + g * 256 + b * O_MAGIC_65536 + a * O_MAGIC_16777216
228}
229// specular Q8 from the diffuse cos (Q8): metallic pow-8 falloff, 200-strength
230func exl_spec(cosq: i64) -> i64 {
231 let c2: i64 = (cosq * cosq) / 256
232 let c4: i64 = (c2 * c2) / 256
233 let c8: i64 = (c4 * c4) / 256
234 return (200 * c8) / 256
235}
236// double-sided Lambert cos (Q8, 0..256) from the WORLD face normal of tri (a,b,c).
237// Light dir fixed (0.29,0.65,0.65) Q10. Meshes are translation-posed only -> world normals always valid;
238// winding is unoriented post-earclip -> ABS(dot) = double-sided. Caller: diff = 96 + 160*cos/256, spec = pow8.
239func exl_lambert(verts: *i64, a: i64, b: i64, c: i64) -> i64 {
240 let ux: i64 = verts[b * 4] - verts[a * 4]
241 let uy: i64 = verts[b * 4 + 1] - verts[a * 4 + 1]
242 let uz: i64 = verts[b * 4 + 2] - verts[a * 4 + 2]
243 let vx: i64 = verts[c * 4] - verts[a * 4]
244 let vy: i64 = verts[c * 4 + 1] - verts[a * 4 + 1]
245 let vz: i64 = verts[c * 4 + 2] - verts[a * 4 + 2]
246 let nx: i64 = uy * vz - uz * vy
247 let ny: i64 = uz * vx - ux * vz
248 let nz: i64 = ux * vy - uy * vx
249 let n2: i64 = nx * nx + ny * ny + nz * nz
250 if n2 <= 0 { return 128 }
251 let nl: i64 = rc_isqrt(n2)
252 if nl == 0 { return 128 }
253 var d: i64 = (nx * 297 + ny * 666 + nz * 666) / O_MAGIC_1024
254 if d < 0 { d = 0 - d }
255 var cosq: i64 = (d * 256) / nl
256 if cosq > 256 { cosq = 256 }
257 return cosq
258}
259
260// R-V3 EDGE AA: in-place luma-edge blend over the framebuffer (px = (2*px + right + down)/4 on edges).
261// Integer, no extra buffers, deterministic -> the VM/native byte-identity property is preserved.
262func exl_aa(fb: *i64, w: i64, h: i64) -> i64 {
263 var y: i64 = 0
264 while y < h - 1 {
265 var x: i64 = 0
266 while x < w - 1 {
267 let i: i64 = y * w + x
268 let p: i64 = fb[i]
269 let r: i64 = fb[i + 1]
270 let d2: i64 = fb[i + w]
271 let lp: i64 = (p % 256) + 2 * ((p / 256) % 256) + ((p / O_MAGIC_65536) % 256)
272 let lr: i64 = (r % 256) + 2 * ((r / 256) % 256) + ((r / O_MAGIC_65536) % 256)
273 let ld: i64 = (d2 % 256) + 2 * ((d2 / 256) % 256) + ((d2 / O_MAGIC_65536) % 256)
274 var e1: i64 = lp - lr
275 if e1 < 0 { e1 = 0 - e1 }
276 var e2: i64 = lp - ld
277 if e2 < 0 { e2 = 0 - e2 }
278 if e1 + e2 > 96 {
279 let nr: i64 = (2 * (p % 256) + (r % 256) + (d2 % 256)) / 4
280 let ng: i64 = (2 * ((p / 256) % 256) + ((r / 256) % 256) + ((d2 / 256) % 256)) / 4
281 let nb: i64 = (2 * ((p / O_MAGIC_65536) % 256) + ((r / O_MAGIC_65536) % 256) + ((d2 / O_MAGIC_65536) % 256)) / 4
282 let na: i64 = (p / O_MAGIC_16777216) % 256
283 fb[i] = nr + ng * 256 + nb * O_MAGIC_65536 + na * O_MAGIC_16777216
284 }
285 x = x + 1
286 }
287 y = y + 1
288 }
289 return 0
290}
291
292func exr_render(base: i64) -> i64 {
293 let e: *i64 = e_ptr(base)
294 let st: *i64 = st_ptr(base)
295 let verts: *i64 = (base + O_WV) as *i64
296 let idx: *i64 = (base + O_IDX) as *i64
297 let nverts: i64 = e[1]
298 let ntris: i64 = e[3]
299 let proj: *i64 = (base + O_PROJ) as *i64
300 let roty: *i64 = (base + O_ROTY) as *i64
301 let mv: *i64 = (base + O_MV) as *i64
302 let trans: *i64 = (base + O_TRANS) as *i64
303 let mvp: *i64 = (base + O_MVP) as *i64
304 let vbuf: *i64 = (base + O_VBUF) as *i64
305 let clipbuf: *i64 = (base + O_CLIP) as *i64
306 let scr: *i64 = (base + O_SCR) as *i64
307 let tribuf: *i64 = (base + O_TRI) as *i64
308 let fb: *i64 = (base + O_FB) as *i64
309 let zb: *i64 = (base + O_ZB) as *i64
310 rc_clear(fb, W, H, BG)
311 rc_zclear(zb, W, H)
312 let cosh: i64 = rc_cos_q14(FOVH)
313 let sinh: i64 = rc_sin_q14(FOVH)
314 let aspect: i64 = W * RC_Q / H
315 rc_perspective_cs(cosh, sinh, aspect, EXL_NEAR, EXL_FAR, proj)
316 exl_roty(st[3], roty)
317 exl_rotx(PITCH, (base + O_ROTX) as *i64)
318 rc_mat4_mul((base + O_ROTX) as *i64, roty, (base + O_YP) as *i64) // yaw first, then pitch = 3/4 view
319 rc_translation_4x4(0, 0, 0 - DIST, trans)
320 rc_mat4_mul(trans, (base + O_YP) as *i64, mv)
321 rc_mat4_mul(proj, mv, mvp)
322 var vi: i64 = 0
323 while vi < nverts {
324 exl_project(mvp, verts[vi * 4 + 0], verts[vi * 4 + 1], verts[vi * 4 + 2], W, H, vbuf, clipbuf, scr, vi * 4)
325 vi = vi + 1
326 }
327 var ti: i64 = 0
328 var drawn: i64 = 0
329 while ti < ntris {
330 let a: i64 = idx[ti * 3 + 0]
331 let b: i64 = idx[ti * 3 + 1]
332 let c: i64 = idx[ti * 3 + 2]
333 if scr[a * 4 + 3] == 1 { if scr[b * 4 + 3] == 1 { if scr[c * 4 + 3] == 1 {
334 let cosq: i64 = exl_lambert(verts, a, b, c)
335 let li: i64 = 96 + (160 * cosq) / 256
336 let sp: i64 = exl_spec(cosq)
337 let ca: i64 = exl_shade(verts[a * 4 + 3], li, sp)
338 let cb: i64 = exl_shade(verts[b * 4 + 3], li, sp)
339 let cc2: i64 = exl_shade(verts[c * 4 + 3], li, sp)
340 tribuf[0] = scr[a * 4 + 0]; tribuf[1] = scr[a * 4 + 1]; tribuf[2] = scr[a * 4 + 2]; tribuf[3] = ca
341 tribuf[4] = scr[b * 4 + 0]; tribuf[5] = scr[b * 4 + 1]; tribuf[6] = scr[b * 4 + 2]; tribuf[7] = cb
342 tribuf[8] = scr[c * 4 + 0]; tribuf[9] = scr[c * 4 + 1]; tribuf[10] = scr[c * 4 + 2]; tribuf[11] = cc2
343 rc_triangle(fb, zb, W, H, tribuf)
344 drawn = drawn + 1
345 } } }
346 ti = ti + 1
347 }
348 exl_aa(fb, W, H)
349 return drawn
350}
351
352func render_at(base: i64) -> i64 {
353 let e: *i64 = e_ptr(base)
354 let st: *i64 = st_ptr(base)
355 let wv: *i64 = (base + O_WV) as *i64
356 let ig: i64 = ex3_pose(e, st[2], wv)
357 // highlight the selected part: brighten its vertex colors toward white
358 let sel: i64 = st[4]
359 if sel >= 0 {
360 let vst: *i64 = e[8] as *i64
361 let vct: *i64 = e[9] as *i64
362 var i: i64 = vst[sel]
363 let iend: i64 = vst[sel] + vct[sel]
364 while i < iend {
365 let c: i64 = wv[i * 4 + 3]
366 var r: i64 = c % 256
367 var g: i64 = (c / 256) % 256
368 var b: i64 = (c / O_MAGIC_65536) % 256
369 r = r + 90
370 g = g + 90
371 b = b + 90
372 if r > 255 { r = 255 }
373 if g > 255 { g = 255 }
374 if b > 255 { b = 255 }
375 wv[i * 4 + 3] = O_MAGIC_4278190080 + b * O_MAGIC_65536 + g * 256 + r
376 i = i + 1
377 }
378 }
379 let drawn: i64 = exr_render(base)
380 return drawn
381}
382
383// HUD counters (JS displays; logic stays here)
384func part_count_at(base: i64) -> i64 { return exl_n() }
385func selected_at(base: i64) -> i64 { let st: *i64 = st_ptr(base); return st[4] }
386func explode_t_at(base: i64) -> i64 { let st: *i64 = st_ptr(base); return st[2] }
387func angle_at(base: i64) -> i64 { let st: *i64 = st_ptr(base); return st[3] }
388
389// ---- base-0 wasm exports ----
390func start() -> i64 { return start_at(0) }
391func tick(mx: i64, my: i64, buttons: i64) -> i64 { return tick_at(0, mx, my, buttons) }
392func render() -> i64 { return render_at(0) }
393func part_count() -> i64 { return part_count_at(0) }
394func selected() -> i64 { return selected_at(0) }
395func explode_t() -> i64 { return explode_t_at(0) }
396func angle() -> i64 { return angle_at(0) }
397func fb_off() -> i64 { return O_FB }
398func ww() -> i64 { return W }
399func hh() -> i64 { return H }
400func main() -> i64 { start(); return 0 }