code wiki / _hdl_build / nx_meshmerge.nx
nx_meshmerge.nx
buildroot/runtime/_hdl_build/nx_meshmerge.nx
about
nx_meshmerge.nx -- SPLICE ONE NXMSH2 INTO A NAMED LAYER OF ANOTHER, WITH A PLACEMENT TRANSFORM.
★WHY THIS EXISTS, and it is the measured integration point rather than a guess. `nx_skullsdf` emits a
skull that scores headline 73 / detail_head 63 against the 171,248-triangle BodyParts3D oracle, while
the LIVE head path (`nx_skullgen`, seven lofted tubes) scores 46 / 14 -- +59% and 4.5x, DARK for weeks.
It is dark because nothing could put it INTO the body: `nx_body_gen` consumes a CANON (rules -> rings)
and emits three layers, and there was no way to hand it a finished MESH.
★THE AUDIT RAN FIRST (this lane's law) AND IT CHANGED THE DESIGN TWICE.
(1) NOT a conversion to the "shared" format: `nx_mesh3` calls itself the ecosystem interchange and is
statically capped at 4096 verts / 8192 tris. Our 2mm skull is 48,814 / 97,660 and the oracle is
171,248 tris -- it holds about 5% of ONE skull. The shared format cannot carry this work.
(2) NOT a widening of `nx_body_gen`, whose signature is already 24 arguments. Compose, never widen.
⇒ merge INSIDE NXMSH2, in its own organ.
★THE HARD PART IS THE FORMAT'S CONTRACT, NOT THE APPEND. NXMSH2 layers are CONTIGUOUS TRIANGLE RANGES
(start,count), so triangles cannot simply be appended -- adding to a middle layer would leave every
later layer's range wrong. This regroups ALL triangles by layer and rebuilds the table, which is the
same contract `nx_headcrop` already had to honour when it cropped a band.
nx_meshmerge <out> <base> <add> <layer> [scale_permil] [tx] [ty] [tz]
nx_meshmerge selftest
layer = index into the base's layer table (0-based). scale_permil defaults 1000 (identity).
license_tier: ORIGINAL expect_exit: 0 No hw writes (Rule 26).
dependencies 2 imports · 0 importers
imports: nx_syscalls.nxnx_itoa_lib.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 27 | const MM_MAGIC_7000: i64 = 7000 |
| 28 | const MM_MAGIC_7001: i64 = 7001 |
| 29 | const MM_MAGIC_3500: i64 = 3500 |
| 30 | const MM_MAGIC_7010: i64 = 7010 |
| 31 | const MM_MAGIC_8388607: i64 = 8388607 |
| 32 | const MM_MAGIC_8388608: i64 = 8388608 |
| 34 | const MM_HDRB: i64 = 16 // magic(8) + nlayer(4) + ntri(4) |
| 35 | const MM_LREC: i64 = 24 // per-layer record: name(16) + start(4) + count(4) |
| 36 | const MM_TRIB: i64 = 84 // per-triangle: 9 f32 pos + 9 f32 nrm + 3 f32 colour |
| 37 | const MM_IDXB: i64 = 4 // per-triangle layer id |
| 38 | const MM_LENSLOT: i64 = 16 |
functions
| 44 | func mm_puts(s: *u8) -> i64 { if MM_QUIET == 1 { return 0 } var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } |
| 45 | func mm_say(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } |
| 50 | func mm_sn(v: i64) -> i64 { nxi_out(v); return 0 } |
| 58 | func mm_pn(v: i64) -> i64 { nxi_out(v); return 0 } |
| 59 | func mm_atoi(s: *u8) -> i64 called by 1: main |
| 65 | func mm_streq(a: *u8, b: *u8) -> i64 called by 1: main |
| 70 | func mm_rd32(b: *u8, o: i64) -> i64 |
| 73 | func mm_wr32(b: *u8, o: i64, v: i64) -> i64 |
| 81 | func mm_r_f32(bits: i64, scale: i64) -> i64 |
| 95 | func mm_f32(v: i64, scale: i64) -> i64 |
| 113 | func mm_is_nxmsh2(b: *u8) -> i64 called by 1: mm_merge |
| 126 | func mm_merge(outp: *u8, basep: *u8, addp: *u8, tgt: i64, scl: i64, tx: i64, ty: i64, tz: i64) -> i64 |
| 230 | func mm_fixture(path: *u8, nlayer: i64, c0: i64, c1: i64, c2: i64, seed: i64) -> i64 |
| 268 | func mm_lcount(path: *u8, L: i64) -> i64 |
| 274 | func mm_ntri(path: *u8) -> i64 |
| 280 | func mm_pos0(path: *u8, t: i64) -> i64 |
| 288 | func mm_selftest() -> i64 |
| 387 | func main(argc: i64, argv: *i64) -> i64 |