code wiki / (root) / nx_wasm_explodelab.nx

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 }