code wiki / _hdl_build / nx_meshmerge.nx

nx_meshmerge.nx source

↩ module page · 403 lines · 19361 B

1// nx_meshmerge.nx -- SPLICE ONE NXMSH2 INTO A NAMED LAYER OF ANOTHER, WITH A PLACEMENT TRANSFORM. 2// 3// ★WHY THIS EXISTS, and it is the measured integration point rather than a guess. `nx_skullsdf` emits a 4// skull that scores headline 73 / detail_head 63 against the 171,248-triangle BodyParts3D oracle, while 5// the LIVE head path (`nx_skullgen`, seven lofted tubes) scores 46 / 14 -- +59% and 4.5x, DARK for weeks. 6// It is dark because nothing could put it INTO the body: `nx_body_gen` consumes a CANON (rules -> rings) 7// and emits three layers, and there was no way to hand it a finished MESH. 8// 9// ★THE AUDIT RAN FIRST (this lane's law) AND IT CHANGED THE DESIGN TWICE. 10// (1) NOT a conversion to the "shared" format: `nx_mesh3` calls itself the ecosystem interchange and is 11// statically capped at 4096 verts / 8192 tris. Our 2mm skull is 48,814 / 97,660 and the oracle is 12// 171,248 tris -- it holds about 5% of ONE skull. The shared format cannot carry this work. 13// (2) NOT a widening of `nx_body_gen`, whose signature is already 24 arguments. Compose, never widen. 14// ⇒ merge INSIDE NXMSH2, in its own organ. 15// 16// ★THE HARD PART IS THE FORMAT'S CONTRACT, NOT THE APPEND. NXMSH2 layers are CONTIGUOUS TRIANGLE RANGES 17// (start,count), so triangles cannot simply be appended -- adding to a middle layer would leave every 18// later layer's range wrong. This regroups ALL triangles by layer and rebuilds the table, which is the 19// same contract `nx_headcrop` already had to honour when it cropped a band. 20// 21// nx_meshmerge <out> <base> <add> <layer> [scale_permil] [tx] [ty] [tz] 22// nx_meshmerge selftest 23// layer = index into the base's layer table (0-based). scale_permil defaults 1000 (identity). 24// license_tier: ORIGINAL expect_exit: 0 No hw writes (Rule 26). 25import "nx_syscalls.nx" 26import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 27const MM_MAGIC_7000: i64 = 7000 28const MM_MAGIC_7001: i64 = 7001 29const MM_MAGIC_3500: i64 = 3500 30const MM_MAGIC_7010: i64 = 7010 31const MM_MAGIC_8388607: i64 = 8388607 32const MM_MAGIC_8388608: i64 = 8388608 33 34const MM_HDRB: i64 = 16 // magic(8) + nlayer(4) + ntri(4) 35const MM_LREC: i64 = 24 // per-layer record: name(16) + start(4) + count(4) 36const MM_TRIB: i64 = 84 // per-triangle: 9 f32 pos + 9 f32 nrm + 3 f32 colour 37const MM_IDXB: i64 = 4 // per-triangle layer id 38const MM_LENSLOT: i64 = 16 39 40// QUIET FLAG so the teeth can call the real merge without its JSON receipt interleaving into the gate 41// output. A static scalar, not a widened signature -- the merge has ONE definition and the teeth exercise 42// THAT one, because a test that calls a copy of the code proves nothing about the shipped path. 43static MM_QUIET: i64 44func 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 } 45func 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 } 46// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 47// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 48// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 49// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 50func mm_sn(v: i64) -> i64 { nxi_out(v); return 0 } 51// MM_QUIET must gate the NUMBER printer too. Gating only mm_puts silenced the receipt's TEXT while its 52// integers still streamed out, so the gate emitted a garbled run of digits between teeth -- a half-muted 53// instrument is its own small lie about what ran. 54// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 55// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 56// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 57// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 58func mm_pn(v: i64) -> i64 { nxi_out(v); return 0 } 59func mm_atoi(s: *u8) -> i64 { 60 var i: i64=0; var n: i64=0; var sg: i64=1 61 if s[0]==(45 as u8) { sg=0-1; i=1 } 62 while s[i]!=(0 as u8) { let c: i64=s[i] as i64; if c>=48 { if c<=57 { n=n*10+(c-48) } } i=i+1 } 63 return n*sg 64} 65func mm_streq(a: *u8, b: *u8) -> i64 { 66 var i: i64=0; var go: i64=1; var eq: i64=1 67 while go==1 { if a[i]!=b[i] { eq=0; go=0 } else { if a[i]==(0 as u8) { go=0 } else { i=i+1 } } } 68 return eq 69} 70func mm_rd32(b: *u8, o: i64) -> i64 { 71 return (b[o] as i64) | ((b[o+1] as i64)<<8) | ((b[o+2] as i64)<<16) | ((b[o+3] as i64)<<24) 72} 73func mm_wr32(b: *u8, o: i64, v: i64) -> i64 { 74 b[o] = (v & 255) as u8 75 b[o+1] = ((v >> 8) & 255) as u8 76 b[o+2] = ((v >> 16) & 255) as u8 77 b[o+3] = ((v >> 24) & 255) as u8 78 return 0 79} 80// IEEE-754 single <-> integer, value = v/scale. Byte-exact for the magnitudes this format carries. 81func mm_r_f32(bits: i64, scale: i64) -> i64 { 82 let s: i64 = (bits >> 31) & 1 83 let e: i64 = (bits >> 23) & 255 84 let m: i64 = bits & MM_MAGIC_8388607 85 if e == 0 { return 0 } 86 if e == 255 { return 0 } 87 let mant: i64 = MM_MAGIC_8388608 | m 88 let sh: i64 = e - 127 - 23 89 var v: i64 = 0 90 if sh >= 0 { if sh > 30 { return 0 } v = (mant*scale) << sh } 91 else { let rs: i64 = 0 - sh; if rs > 62 { return 0 } v = (mant*scale) >> rs } 92 if s == 1 { return 0 - v } 93 return v 94} 95func mm_f32(v: i64, scale: i64) -> i64 { 96 if v == 0 { return 0 } 97 var neg: i64 = 0 98 var m: i64 = v 99 if m < 0 { neg = 1; m = 0 - m } 100 var e: i64 = 127 101 var num: i64 = m 102 var den: i64 = scale 103 while num >= den*2 { den = den*2; e = e + 1 } 104 while num < den { num = num*2; e = e - 1 } 105 let frac: i64 = ((num - den) * MM_MAGIC_8388608) / den 106 var bits: i64 = (e << 23) | (frac & MM_MAGIC_8388607) 107 if neg == 1 { bits = bits | (1 << 31) } 108 return bits 109} 110// ★FAIL-CLOSED HEADER CHECK. A merge that runs on a non-NXMSH2 buffer would read triangle counts out of 111// arbitrary bytes and emit a plausible-looking mesh from garbage, which is the worst possible outcome 112// for a tool whose whole job is to be trusted with someone else's geometry. 113func mm_is_nxmsh2(b: *u8) -> i64 { 114 if b[0]!=(78 as u8) { return 0 } 115 if b[1]!=(88 as u8) { return 0 } 116 if b[2]!=(77 as u8) { return 0 } 117 if b[3]!=(83 as u8) { return 0 } 118 if b[4]!=(72 as u8) { return 0 } 119 if b[5]!=(50 as u8) { return 0 } 120 return 1 121} 122 123// ---- THE MERGE. 124// Regroups every triangle by layer so each layer stays ONE contiguous range, inserting `add`'s triangles 125// at the end of layer `tgt`. Returns the new triangle count, or a negative code. 126func mm_merge(outp: *u8, basep: *u8, addp: *u8, tgt: i64, scl: i64, tx: i64, ty: i64, tz: i64) -> i64 { 127 let bl: *i64 = sys_mmap(MM_LENSLOT) as *i64 128 let B: *u8 = sys_read_file(basep, bl) 129 if (B as i64) == 0 { mm_puts("{\x22error\x22:\x22cannot read base\x22}\n" as *u8); return 0-3 } 130 let al: *i64 = sys_mmap(MM_LENSLOT) as *i64 131 let A: *u8 = sys_read_file(addp, al) 132 if (A as i64) == 0 { mm_puts("{\x22error\x22:\x22cannot read add\x22}\n" as *u8); return 0-4 } 133 if mm_is_nxmsh2(B) == 0 { mm_puts("{\x22error\x22:\x22base is not NXMSH2\x22}\n" as *u8); return 0-5 } 134 if mm_is_nxmsh2(A) == 0 { mm_puts("{\x22error\x22:\x22add is not NXMSH2\x22}\n" as *u8); return 0-6 } 135 let bn: i64 = mm_rd32(B, 8) 136 let bt: i64 = mm_rd32(B, 12) 137 let an: i64 = mm_rd32(A, 8) 138 let at: i64 = mm_rd32(A, 12) 139 if tgt < 0 { mm_puts("{\x22error\x22:\x22layer index negative\x22}\n" as *u8); return 0-7 } 140 if tgt >= bn { mm_puts("{\x22error\x22:\x22layer index beyond the base layer table\x22}\n" as *u8); return 0-7 } 141 let bhdr: i64 = MM_HDRB + bn*MM_LREC 142 let ahdr: i64 = MM_HDRB + an*MM_LREC 143 let nt: i64 = bt + at 144 let ohdr: i64 = MM_HDRB + bn*MM_LREC 145 let obytes: i64 = ohdr + nt*MM_TRIB + nt*MM_IDXB 146 let O: *u8 = sys_mmap(obytes + 64) 147 var q: i64 = 0 148 while q < 8 { O[q] = B[q]; q = q + 1 } 149 mm_wr32(O, 8, bn); mm_wr32(O, 12, nt) 150 // copy the layer NAMES verbatim; starts/counts are recomputed below from what we actually emit 151 var L: i64 = 0 152 while L < bn { 153 var k: i64 = 0 154 while k < 16 { O[MM_HDRB + L*MM_LREC + k] = B[MM_HDRB + L*MM_LREC + k]; k = k + 1 } 155 L = L + 1 156 } 157 // ★ONE PASS PER LAYER, IN LAYER ORDER -- this is what keeps every range contiguous. Triangles are 158 // emitted grouped by layer id, and `add`'s triangles are appended while emitting layer `tgt`. 159 var w: i64 = 0 160 L = 0 161 while L < bn { 162 let lstart: i64 = w 163 var t: i64 = 0 164 while t < bt { 165 if mm_rd32(B, bhdr + bt*MM_TRIB + t*MM_IDXB) == L { 166 var k: i64 = 0 167 while k < MM_TRIB { O[ohdr + w*MM_TRIB + k] = B[bhdr + t*MM_TRIB + k]; k = k + 1 } 168 mm_wr32(O, ohdr + nt*MM_TRIB + w*MM_IDXB, L) 169 w = w + 1 170 } 171 t = t + 1 172 } 173 if L == tgt { 174 var s: i64 = 0 175 while s < at { 176 var k: i64 = 0 177 while k < MM_TRIB { O[ohdr + w*MM_TRIB + k] = A[ahdr + s*MM_TRIB + k]; k = k + 1 } 178 // ★TRANSFORM ONLY THE NINE POSITION FLOATS. Normals are direction vectors: a uniform 179 // scale and a translation both leave them unchanged, and rescaling them would shorten 180 // every normal and collapse the lambert term -- the exact defect banked when surface_nets 181 // normals were read at the wrong fixed-point scale and the whole surface rendered dark. 182 var c: i64 = 0 183 while c < 9 { 184 let off: i64 = ohdr + w*MM_TRIB + c*4 185 var v: i64 = mm_r_f32(mm_rd32(O, off), 1) 186 v = v * scl / 1000 187 if c % 3 == 0 { v = v + tx } 188 if c % 3 == 1 { v = v + ty } 189 if c % 3 == 2 { v = v + tz } 190 mm_wr32(O, off, mm_f32(v, 1)) 191 c = c + 1 192 } 193 mm_wr32(O, ohdr + nt*MM_TRIB + w*MM_IDXB, L) 194 w = w + 1 195 s = s + 1 196 } 197 } 198 mm_wr32(O, MM_HDRB + L*MM_LREC + 16, lstart) 199 mm_wr32(O, MM_HDRB + L*MM_LREC + 20, w - lstart) 200 L = L + 1 201 } 202 // ★DECLARE THE SHORTFALL RATHER THAN HIDE IT. If the base carried triangles whose layer id is not in 203 // the table, they are silently dropped by the regroup above; w < nt is the detector. A mesh that 204 // quietly loses geometry is the failure this lane keeps convicting. 205 if w != nt { 206 mm_puts("{\x22error\x22:\x22regroup lost triangles\x22,\x22expected\x22:" as *u8); mm_pn(nt) 207 mm_puts(",\x22emitted\x22:" as *u8); mm_pn(w) 208 mm_puts(",\x22why\x22:\x22base carries triangles whose layer id is outside its own layer table\x22}\n" as *u8) 209 return 0-8 210 } 211 let fd: i64 = sys_openat_wr(outp, 420) 212 sys_write(fd, O, obytes) 213 sys_close(fd) 214 mm_puts("{\x22organ\x22:\x22nx_meshmerge\x22,\x22layers\x22:" as *u8); mm_pn(bn) 215 mm_puts(",\x22base_tris\x22:" as *u8); mm_pn(bt) 216 mm_puts(",\x22add_tris\x22:" as *u8); mm_pn(at) 217 mm_puts(",\x22out_tris\x22:" as *u8); mm_pn(nt) 218 mm_puts(",\x22target_layer\x22:" as *u8); mm_pn(tgt) 219 mm_puts(",\x22target_count\x22:" as *u8); mm_pn(mm_rd32(O, MM_HDRB + tgt*MM_LREC + 20)) 220 mm_puts(",\x22scale_permil\x22:" as *u8); mm_pn(scl) 221 mm_puts(",\x22bytes\x22:" as *u8); mm_pn(obytes) 222 mm_puts("}\n" as *u8) 223 return nt 224} 225 226// ---- TEETH. Fixtures are WRITTEN, not asserted about: each tooth builds real NXMSH2 files on disk and 227// runs the SHIPPED mm_merge over them, so a tooth cannot pass against a reimplementation of the logic. 228// Triangle t in a fixture carries all nine position floats = seed + t, which makes both the identity 229// round-trip and the scaled transform checkable to the exact float. 230func mm_fixture(path: *u8, nlayer: i64, c0: i64, c1: i64, c2: i64, seed: i64) -> i64 { 231 let nt: i64 = c0 + c1 + c2 232 let hdr: i64 = MM_HDRB + nlayer*MM_LREC 233 let bytes: i64 = hdr + nt*MM_TRIB + nt*MM_IDXB 234 let B: *u8 = sys_mmap(bytes + 64) 235 B[0]=78 as u8; B[1]=88 as u8; B[2]=77 as u8; B[3]=83 as u8 236 B[4]=72 as u8; B[5]=50 as u8; B[6]=0 as u8; B[7]=0 as u8 237 mm_wr32(B, 8, nlayer); mm_wr32(B, 12, nt) 238 var t: i64 = 0 239 while t < nt { 240 var lid: i64 = 2 241 if t < c0 { lid = 0 } else { if t < c0+c1 { lid = 1 } } 242 var c: i64 = 0 243 while c < 21 { mm_wr32(B, hdr + t*MM_TRIB + c*4, 0); c = c + 1 } 244 c = 0 245 while c < 9 { mm_wr32(B, hdr + t*MM_TRIB + c*4, mm_f32(seed + t, 1)); c = c + 1 } 246 mm_wr32(B, hdr + nt*MM_TRIB + t*MM_IDXB, lid) 247 t = t + 1 248 } 249 var L: i64 = 0 250 var st: i64 = 0 251 while L < nlayer { 252 var cn: i64 = 0 253 if L == 0 { cn = c0 } 254 if L == 1 { cn = c1 } 255 if L == 2 { cn = c2 } 256 var k: i64 = 0 257 while k < 16 { B[MM_HDRB + L*MM_LREC + k] = 0 as u8; k = k + 1 } 258 mm_wr32(B, MM_HDRB + L*MM_LREC + 16, st) 259 mm_wr32(B, MM_HDRB + L*MM_LREC + 20, cn) 260 st = st + cn 261 L = L + 1 262 } 263 let fd: i64 = sys_openat_wr(path, 420) 264 sys_write(fd, B, bytes) 265 sys_close(fd) 266 return nt 267} 268func mm_lcount(path: *u8, L: i64) -> i64 { 269 let ln: *i64 = sys_mmap(MM_LENSLOT) as *i64 270 let B: *u8 = sys_read_file(path, ln) 271 if (B as i64) == 0 { return 0-1 } 272 return mm_rd32(B, MM_HDRB + L*MM_LREC + 20) 273} 274func mm_ntri(path: *u8) -> i64 { 275 let ln: *i64 = sys_mmap(MM_LENSLOT) as *i64 276 let B: *u8 = sys_read_file(path, ln) 277 if (B as i64) == 0 { return 0-1 } 278 return mm_rd32(B, 12) 279} 280func mm_pos0(path: *u8, t: i64) -> i64 { 281 let ln: *i64 = sys_mmap(MM_LENSLOT) as *i64 282 let B: *u8 = sys_read_file(path, ln) 283 if (B as i64) == 0 { return 0-1 } 284 let n: i64 = mm_rd32(B, 8) 285 let hdr: i64 = MM_HDRB + n*MM_LREC 286 return mm_r_f32(mm_rd32(B, hdr + t*MM_TRIB), 1) 287} 288func mm_selftest() -> i64 { 289 var pass: i64 = 0 290 var total: i64 = 0 291 let base: *u8 = "_offc/mm_t_base.nxmesh" as *u8 292 let add: *u8 = "_offc/mm_t_add.nxmesh" as *u8 293 let out: *u8 = "_offc/mm_t_out.nxmesh" as *u8 294 let bad: *u8 = "_offc/mm_t_bad.bin" as *u8 295 mm_fixture(base, 3, 4, 3, 5, 1000) 296 mm_fixture(add, 1, 2, 0, 0, MM_MAGIC_7000) 297 MM_QUIET = 1 298 let r: i64 = mm_merge(out, base, add, 1, 1000, 0, 0, 0) 299 MM_QUIET = 0 300 total = total + 1 301 var t1: i64 = 0 302 if r == 14 { t1 = 1 } 303 if mm_ntri(out) != 14 { t1 = 0 } 304 if t1 == 1 { pass = pass + 1 } 305 mm_say("T1 total_preserved_12plus2=" as *u8); mm_sn(t1); mm_say("\n" as *u8) 306 total = total + 1 307 var t2: i64 = 0 308 if mm_lcount(out, 1) == 5 { t2 = 1 } 309 if t2 == 1 { pass = pass + 1 } 310 mm_say("T2 target_grew_by_exactly_add=" as *u8); mm_sn(t2); mm_say("\n" as *u8) 311 // T3 -- EVERY NON-TARGET LAYER UNCHANGED. This is the tooth that catches a broken regroup, which 312 // would smear triangles between layers while leaving the TOTAL correct. 313 total = total + 1 314 var t3: i64 = 0 315 if mm_lcount(out, 0) == 4 { if mm_lcount(out, 2) == 5 { t3 = 1 } } 316 if t3 == 1 { pass = pass + 1 } 317 mm_say("T3 non_target_layers_unchanged=" as *u8); mm_sn(t3); mm_say("\n" as *u8) 318 // T4 -- INDEX DERIVED, NOT GUESSED: the regroup emits layers in order, so out is 319 // [L0's 4 | L1's 3 | THE 2 ADDED | L2's 5] and the added pair sits at 7..8. I first wrote 4..5 and 320 // the layout refutes it -- a tooth asserting the wrong index would have failed a CORRECT merge. 321 total = total + 1 322 var t4: i64 = 0 323 if mm_pos0(out, 7) == MM_MAGIC_7000 { if mm_pos0(out, 8) == MM_MAGIC_7001 { t4 = 1 } } 324 if t4 == 1 { pass = pass + 1 } 325 mm_say("T4 identity_transform_exact=" as *u8); mm_sn(t4); mm_say("\n" as *u8) 326 // T5 -- ANTI-INERT: a scale that should HALVE the added coordinates must actually move them. 327 // Without this a transform that silently does nothing passes every tooth above -- the exact 328 // knob-applied-but-by-an-amount-nothing-can-see class this lane has banked twice. 329 total = total + 1 330 MM_QUIET = 1 331 mm_merge(out, base, add, 1, 500, 0, 0, 0) 332 MM_QUIET = 0 333 var t5: i64 = 0 334 if mm_pos0(out, 7) == MM_MAGIC_3500 { if mm_pos0(out, 8) == MM_MAGIC_3500 { t5 = 1 } } 335 if t5 == 1 { pass = pass + 1 } 336 mm_say("T5 scale_actually_applied=" as *u8); mm_sn(t5); mm_say("\n" as *u8) 337 total = total + 1 338 MM_QUIET = 1 339 mm_merge(out, base, add, 1, 1000, 10, 0, 0) 340 MM_QUIET = 0 341 var t6: i64 = 0 342 if mm_pos0(out, 7) == MM_MAGIC_7010 { t6 = 1 } 343 if t6 == 1 { pass = pass + 1 } 344 mm_say("T6 translate_applied=" as *u8); mm_sn(t6); mm_say("\n" as *u8) 345 // T7 -- a NON-NXMSH2 input REFUSES; otherwise a mesh gets built out of arbitrary bytes. 346 total = total + 1 347 let junk: *u8 = sys_mmap(256) 348 var j: i64 = 0 349 while j < 256 { junk[j] = 65 as u8; j = j + 1 } 350 let jfd: i64 = sys_openat_wr(bad, 420) 351 sys_write(jfd, junk, 256) 352 sys_close(jfd) 353 MM_QUIET = 1 354 let rbad: i64 = mm_merge(out, bad, add, 0, 1000, 0, 0, 0) 355 MM_QUIET = 0 356 var t7: i64 = 0 357 if rbad < 0 { t7 = 1 } 358 if t7 == 1 { pass = pass + 1 } 359 mm_say("T7 non_nxmsh2_refuses=" as *u8); mm_sn(t7); mm_say("\n" as *u8) 360 total = total + 1 361 MM_QUIET = 1 362 let rbig: i64 = mm_merge(out, base, add, 9, 1000, 0, 0, 0) 363 MM_QUIET = 0 364 var t8: i64 = 0 365 if rbig < 0 { t8 = 1 } 366 if t8 == 1 { pass = pass + 1 } 367 mm_say("T8 layer_out_of_range_refuses=" as *u8); mm_sn(t8); mm_say("\n" as *u8) 368 // T9 -- NON-VACUITY OF THE REFUSAL TEETH: a VALID call must still succeed, or refuse-everything 369 // would pass T7 and T8. Pairing is the point (the nx_skelgen T4/T5 precedent). 370 total = total + 1 371 MM_QUIET = 1 372 let rok: i64 = mm_merge(out, base, add, 0, 1000, 0, 0, 0) 373 MM_QUIET = 0 374 var t9: i64 = 0 375 if rok == 14 { t9 = 1 } 376 if t9 == 1 { pass = pass + 1 } 377 mm_say("T9 valid_call_still_succeeds=" as *u8); mm_sn(t9); mm_say("\n" as *u8) 378 mm_say("{\x22organ\x22:\x22nx_meshmerge\x22,\x22verb\x22:\x22selftest\x22,\x22pass\x22:" as *u8); mm_sn(pass) 379 mm_say(",\x22total\x22:" as *u8); mm_sn(total) 380 mm_say(",\x22verdict\x22:\x22" as *u8) 381 if pass == total { mm_say("GREEN" as *u8) } else { mm_say("RED" as *u8) } 382 mm_say("\x22}\n" as *u8) 383 if pass == total { return 0 } 384 return 1 385} 386 387func main(argc: i64, argv: *i64) -> i64 { 388 if argc < 2 { mm_puts("usage: nx_meshmerge <out> <base> <add> <layer> [scale_permil] [tx] [ty] [tz] | selftest\n" as *u8); return 2 } 389 if mm_streq(argv[1] as *u8, "selftest" as *u8) == 1 { return mm_selftest() } 390 if argc < 5 { mm_puts("usage: nx_meshmerge <out> <base> <add> <layer> [scale_permil] [tx] [ty] [tz]\n" as *u8); return 2 } 391 var scl: i64 = 1000 392 if argc > 5 { scl = mm_atoi(argv[5] as *u8) } 393 if scl <= 0 { mm_puts("{\x22error\x22:\x22scale_permil must be positive\x22}\n" as *u8); return 2 } 394 var tx: i64 = 0 395 var ty: i64 = 0 396 var tz: i64 = 0 397 if argc > 6 { tx = mm_atoi(argv[6] as *u8) } 398 if argc > 7 { ty = mm_atoi(argv[7] as *u8) } 399 if argc > 8 { tz = mm_atoi(argv[8] as *u8) } 400 let r: i64 = mm_merge(argv[1] as *u8, argv[2] as *u8, argv[3] as *u8, mm_atoi(argv[4] as *u8), scl, tx, ty, tz) 401 if r < 0 { return 0 - r } 402 return 0 403}