code wiki / (root) / nx_boneheat_lib.nx

nx_boneheat_lib.nx source

↩ module page · 824 lines · 38086 B

1// nx_boneheat_lib.nx -- SOVEREIGN AUTO-WEIGHTS: geodesic bone-heat skin weights over the VOXELISED VOLUME of a mesh. 2// 3// WHY (2026-09-05). modding MD4 and dcc DC16 both price auto-rigging, and MD4's pre-declared accept rule says the 4// learned rig must beat a nearest-joint heat-weight CONTROL -- an organ the estate did not have (nx_catalog: 5// nx_boneheat ABSENT; nx_capsearch: no voxel flood-fill and no geodesic-weight ruler anywhere in 7,414 sources). 6// nx_nxa_skin's autoskin is nearest-segment inverse-distance, which bleeds across air: a hand resting on a hip 7// takes hip weight. That is the failure Blender's Automatic Weights (bone heat, Baran and Popovic 2007) avoids and 8// the one Maya's geodesic voxel bind (Dionne and de Lasa 2013) avoids more robustly, because it never needs a 9// manifold mesh -- it needs a volume. This lib is that class, integer and deterministic, first byte up: 10// 1. VOXELISE: rasterise every triangle into a grid sized from knowledge/boneheat.conf, flood the EXTERIOR from 11// the padded border through non-surface cells, and call the rest INTERIOR. The partition is printed and SUMS. 12// A mesh that is not sealed simply has interior=0 and the solve runs over its shell: no refusal, announced. 13// 2. SEED: each joint owns the segment from itself to the mean of its children (a leaf owns a point); the 14// segment's cells are distance 0. A joint whose segment lies outside the volume is PROJECTED to the nearest 15// domain cell and COUNTED, never silently dropped. 16// 3. SOLVE: per joint, Dijkstra over the 26-connected domain with chamfer weights 10/14/17 (Dial buckets, an 17// exact push bound of 26 per settled cell). Air is not domain, so distance cannot cross it -- that is the 18// whole difference from nearest-joint, and the gate proves it on two shells. 19// 4. WEIGH: per vertex keep the four nearest joints at 2^40/(d+half)^2, normalise to the NXA sum of 4096 with the 20// residual folded into the largest influence (the estate's own zero-volume-loss rule). Vertices no joint can 21// reach fall back to the nearest segment and are COUNTED. 22// The NEAREST mode is the control MD4 names: one joint, weight 4096, the nearest segment by Euclidean distance. 23// It shares every reader and the writer, so the two modes cannot disagree about the asset. 24// UNITS: VERT and SKEL are 0.01 mm model units (NXA v1); the grid cell is derived from the bounding box. 25// license_tier: ORIGINAL 26import "nx_syscalls.nx" 27import "nx_nxa.nx" 28import "nx_vecmath.nx" 29 30const BH_Q12: i64 = 4096 // NXA v1: q12 weights summing 4096 (format-fixed) 31const BH_INF_SLOTS: i64 = 4 // NXA v1: 4 influences per vertex (format-fixed) 32const BH_JOINT_WORDS: i64 = 8 // SKEL row: parent tx ty tz qx qy qz qw 33const BH_SKIN_WORDS: i64 = 8 // SKIN row: 4 joint ids then 4 q12 weights 34const BH_VERT_WORDS: i64 = 3 35const BH_TRI_WORDS: i64 = 3 36const BH_HDR_BYTES: i64 = 32 // magic ver ns toc-check 37const BH_TOC_WORDS: i64 = 4 // tag byte-offset word-len payload-check 38const BH_TOC_BYTES: i64 = 32 39const BH_MAX_SECTIONS: i64 = 64 // nxa_find's own bound 40const BH_KIND_EXTERIOR: i64 = 0 41const BH_KIND_SURFACE: i64 = 1 42const BH_KIND_INTERIOR: i64 = 2 43const BH_KIND_UNSEEN: i64 = 3 44const BH_DILATE_STEPS: i64 = 1 // bh_dilate runs ONE 6-dilation; the shell depth below is derived from it 45const BH_WALL_DEPTH: i64 = 3 // 1 (the raster's own cell) + 2 x BH_DILATE_STEPS: the deepest six-step a REAL shell cell can sit from the exterior 46const BG_WALLS: i64 = 21 // internal-wall cells stripped to INTERIOR by bh_flood (grid descriptor slot) 47const BR_WALLS: i64 = 20 // the same count in the report, so the caller can print walls= 48const BH_WALL_HIST: i64 = 10 // shell-depth census buckets d1..d9 (d9 = nine or deeper): the measurement the strip depth is DERIVED from 49const BH_STEP: i64 = 10 // one face step = 10 so the chamfer weights stay integer 50const BH_W_FACE: i64 = 10 // sqrt(1) x 10 51const BH_W_EDGE: i64 = 14 // sqrt(2) x 10, rounded 52const BH_W_CORNER: i64 = 17 // sqrt(3) x 10, rounded 53const BH_NBUCKET: i64 = 18 // Dial buckets = largest edge weight + 1 54const BH_PUSH_PER_CELL: i64 = 26 // a settled cell relaxes at most its 26 neighbours: the pool bound is EXACT 55const BH_DIST_INF: i64 = 1152921504606846976 56const BH_HALF_STEP: i64 = 5 // half a face step: the smallest distance the grid resolves 57const BH_WSCALE_SHIFT: i64 = 40 // raw weight = 2^40 / (d + half)^2 58const BH_PAD_CELLS: i64 = 2 // DERIVED = 1 + BH_DILATE_STEPS: bh_dilate grows the shell OUTWARD by one cell, so a one-cell pad is FILLED wherever a face touches the bounding box and the exterior flood is starved there. MEASURED 2026-09-06 on a lone 60 mm prism (no internal geometry): exterior 472 of 14,112 cells, shell-depth census RISING with depth (d1=1352 d3=2424 d5=2136) and 4,416 false walls; the sheet neg-control stopped being refused. One spare layer beyond the dilation keeps a flood seed on every side. 59const BH_SAMPLE_DIV: i64 = 2 // triangles and segments are sampled at half a cell: no cell is skipped 60const BH_GRID_DEFAULT: i64 = 96 // used ONLY when the conf is absent; provenance is printed 61const BH_MODE_GEODESIC: i64 = 1 62const BH_MODE_NEAREST: i64 = 2 63const BH_Q10: i64 = 1024 // segment parameter fixed point for the nearest control 64const BH_MODE_0644: i64 = 420 65const BH_NOJOINT: i64 = 0 - 1 66const BH_CONF_KEY: *u8 = "grid_cells_longest=" 67const BH_CONF_PATH: *u8 = "knowledge/boneheat.conf" 68// grid descriptor word offsets 69const BG_NX: i64 = 0 70const BG_NY: i64 = 1 71const BG_NZ: i64 = 2 72const BG_CELL: i64 = 3 73const BG_OX: i64 = 4 74const BG_OY: i64 = 5 75const BG_OZ: i64 = 6 76const BG_N: i64 = 7 77const BG_KIND: i64 = 8 78const BG_DIST: i64 = 9 79const BG_SET: i64 = 10 80const BG_POOLC: i64 = 11 81const BG_POOLN: i64 = 12 82const BG_POOLCAP: i64 = 13 83const BG_HEAD: i64 = 14 84const BG_EXT: i64 = 15 85const BG_SURF: i64 = 16 86const BG_INT: i64 = 17 87const BG_DOMAIN: i64 = 18 88const BG_POOLUSED: i64 = 19 89const BG_QUEUE: i64 = 20 90const BG_WORDS: i64 = 24 91// report word offsets (shared with the program and the gate, so there is one vocabulary) 92const BR_VERTS: i64 = 0 93const BR_TRIS: i64 = 1 94const BR_JOINTS: i64 = 2 95const BR_CELLS: i64 = 3 96const BR_EXT: i64 = 4 97const BR_SURF: i64 = 5 98const BR_INT: i64 = 6 99const BR_SEALED: i64 = 7 100const BR_SEEDS: i64 = 8 101const BR_PROJECTED: i64 = 9 102const BR_UNREACH_J: i64 = 10 103const BR_UNREACH_V: i64 = 11 104const BR_MIXED: i64 = 12 105const BR_BYTES: i64 = 13 106const BR_NX: i64 = 14 107const BR_NY: i64 = 15 108const BR_NZ: i64 = 16 109const BR_CELL: i64 = 17 110const BR_CONF: i64 = 18 111const BR_GRIDN: i64 = 19 112const BR_WORDS: i64 = 24 113// exit codes 114const BH_EXIT_OK: i64 = 0 115const BH_EXIT_USAGE: i64 = 2 116const BH_EXIT_REFUSE: i64 = 3 117 118func bh_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 119func bh_num(v: i64) -> i64 { 120 let t: *u8 = sys_mmap(32) 121 var m: i64 = v 122 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 123 var k: i64 = 0 124 if m == 0 { t[0] = 48 as u8; k = 1 } 125 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 126 let b: *u8 = sys_mmap(32) 127 var j: i64 = 0 128 while j < k { b[j] = t[k - 1 - j]; j = j + 1 } 129 sys_write(1, b, k) 130 return 0 131} 132func bh_kv(k: *u8, v: i64) -> i64 { bh_puts(k); bh_num(v); return 0 } 133func bh_max(a: i64, b: i64) -> i64 { if a > b { return a } return b } 134func bh_min(a: i64, b: i64) -> i64 { if a < b { return a } return b } 135func bh_abs(a: i64) -> i64 { if a < 0 { return 0 - a } return a } 136func bh_streq(a: *u8, b: *u8) -> i64 { 137 var i: i64 = 0 138 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 139 if b[i] != (0 as u8) { return 0 } 140 return 1 141} 142 143// ---- conf: the ONE tunable, with provenance ---- 144func bh_conf_cells(path: *u8) -> i64 { 145 let lp: *i64 = sys_mmap(16) as *i64 146 let b: *u8 = sys_read_file(path, lp) 147 if (b as i64) == 0 { return 0 } 148 let n: i64 = lp[0] 149 var kl: i64 = 0 150 while BH_CONF_KEY[kl] != (0 as u8) { kl = kl + 1 } 151 var i: i64 = 0 152 while i + kl <= n { 153 var k: i64 = 0 154 var ok: i64 = 1 155 while k < kl { if b[i + k] != BH_CONF_KEY[k] { ok = 0; k = kl } k = k + 1 } 156 if ok == 1 { 157 var j: i64 = i + kl 158 var v: i64 = 0 159 var any: i64 = 0 160 while j < n { 161 let c: i64 = b[j] as i64 162 if c < 48 { break } 163 if c > 57 { break } 164 v = v * 10 + (c - 48) 165 any = 1 166 j = j + 1 167 } 168 if any == 1 { return v } 169 return 0 170 } 171 i = i + 1 172 } 173 return 0 174} 175 176// ---- grid ---- 177func bh_idx(g: *i64, ix: i64, iy: i64, iz: i64) -> i64 { 178 if ix < 0 { return 0 - 1 } 179 if iy < 0 { return 0 - 1 } 180 if iz < 0 { return 0 - 1 } 181 if ix >= g[BG_NX] { return 0 - 1 } 182 if iy >= g[BG_NY] { return 0 - 1 } 183 if iz >= g[BG_NZ] { return 0 - 1 } 184 return ix + g[BG_NX] * (iy + g[BG_NY] * iz) 185} 186func bh_cell_of(g: *i64, x: i64, y: i64, z: i64) -> i64 { 187 let c: i64 = g[BG_CELL] 188 return bh_idx(g, (x - g[BG_OX]) / c, (y - g[BG_OY]) / c, (z - g[BG_OZ]) / c) 189} 190func bh_kind(g: *i64, c: i64) -> i64 { let k: *u8 = g[BG_KIND] as *u8; return k[c] as i64 } 191func bh_is_domain(g: *i64, c: i64) -> i64 { 192 if c < 0 { return 0 } 193 let k: i64 = bh_kind(g, c) 194 if k == BH_KIND_SURFACE { return 1 } 195 if k == BH_KIND_INTERIOR { return 1 } 196 return 0 197} 198// size the grid from the vertex bounding box: `cells` cells along the longest axis, one pad cell around 199func bh_grid_build(g: *i64, w: *i64, vwo: i64, nv: i64, cells: i64) -> i64 { 200 var mnx: i64 = w[vwo + 1]; var mxx: i64 = mnx 201 var mny: i64 = w[vwo + 2]; var mxy: i64 = mny 202 var mnz: i64 = w[vwo + 3]; var mxz: i64 = mnz 203 var i: i64 = 1 204 while i < nv { 205 let o: i64 = vwo + 1 + i * BH_VERT_WORDS 206 mnx = bh_min(mnx, w[o]); mxx = bh_max(mxx, w[o]) 207 mny = bh_min(mny, w[o + 1]); mxy = bh_max(mxy, w[o + 1]) 208 mnz = bh_min(mnz, w[o + 2]); mxz = bh_max(mxz, w[o + 2]) 209 i = i + 1 210 } 211 let ext: i64 = bh_max(bh_max(mxx - mnx, mxy - mny), mxz - mnz) 212 var cell: i64 = (ext + cells - 1) / cells 213 if cell < 1 { cell = 1 } 214 g[BG_CELL] = cell 215 g[BG_OX] = mnx - BH_PAD_CELLS * cell 216 g[BG_OY] = mny - BH_PAD_CELLS * cell 217 g[BG_OZ] = mnz - BH_PAD_CELLS * cell 218 g[BG_NX] = (mxx - mnx) / cell + 1 + 2 * BH_PAD_CELLS 219 g[BG_NY] = (mxy - mny) / cell + 1 + 2 * BH_PAD_CELLS 220 g[BG_NZ] = (mxz - mnz) / cell + 1 + 2 * BH_PAD_CELLS 221 let n: i64 = g[BG_NX] * g[BG_NY] * g[BG_NZ] 222 g[BG_N] = n 223 let kind: *u8 = sys_mmap(n + 8) 224 var c: i64 = 0 225 while c < n { kind[c] = BH_KIND_UNSEEN as u8; c = c + 1 } 226 g[BG_KIND] = kind as i64 227 g[BG_DIST] = sys_mmap(n * 8 + 64) as i64 228 g[BG_SET] = sys_mmap(n + 8) as i64 229 g[BG_QUEUE] = sys_mmap(n * 8 + 64) as i64 230 g[BG_HEAD] = sys_mmap(BH_NBUCKET * 8) as i64 231 return n 232} 233func bh_mark_surface(g: *i64, x: i64, y: i64, z: i64) -> i64 { 234 let c: i64 = bh_cell_of(g, x, y, z) 235 if c < 0 { return 0 } 236 let kind: *u8 = g[BG_KIND] as *u8 237 kind[c] = BH_KIND_SURFACE as u8 238 return 1 239} 240func bh_seglen(ax: i64, ay: i64, az: i64, bx: i64, by: i64, bz: i64) -> i64 { 241 let dx: i64 = bx - ax; let dy: i64 = by - ay; let dz: i64 = bz - az 242 return vm_isqrt(dx * dx + dy * dy + dz * dz) 243} 244// rasterise every triangle at half-cell spacing so the surface is a closed voxel shell 245func bh_raster(g: *i64, w: *i64, vwo: i64, two: i64, nt: i64) -> i64 { 246 let half: i64 = bh_max(g[BG_CELL] / BH_SAMPLE_DIV, 1) 247 var t: i64 = 0 248 while t < nt { 249 let o: i64 = two + 1 + t * BH_TRI_WORDS 250 let a: i64 = vwo + 1 + w[o] * BH_VERT_WORDS 251 let b: i64 = vwo + 1 + w[o + 1] * BH_VERT_WORDS 252 let cc: i64 = vwo + 1 + w[o + 2] * BH_VERT_WORDS 253 let l1: i64 = bh_seglen(w[a], w[a+1], w[a+2], w[b], w[b+1], w[b+2]) 254 let l2: i64 = bh_seglen(w[a], w[a+1], w[a+2], w[cc], w[cc+1], w[cc+2]) 255 let l3: i64 = bh_seglen(w[b], w[b+1], w[b+2], w[cc], w[cc+1], w[cc+2]) 256 let n: i64 = bh_max(bh_max(l1, l2), l3) / half + 1 257 var i: i64 = 0 258 while i <= n { 259 var j: i64 = 0 260 while j <= n - i { 261 let px: i64 = w[a] + (w[b] - w[a]) * i / n + (w[cc] - w[a]) * j / n 262 let py: i64 = w[a+1] + (w[b+1] - w[a+1]) * i / n + (w[cc+1] - w[a+1]) * j / n 263 let pz: i64 = w[a+2] + (w[b+2] - w[a+2]) * i / n + (w[cc+2] - w[a+2]) * j / n 264 bh_mark_surface(g, px, py, pz) 265 j = j + 1 266 } 267 i = i + 1 268 } 269 t = t + 1 270 } 271 return nt 272} 273// close diagonal seams: a tilted flat face point-samples into a 26-connected shell that a 6-connected flood 274// slips through at the vertical edges between faces. ONE 6-dilation of the surface makes it 6-separating -- it 275// closes seams up to two cells wide and CANNOT close a real opening (an 8 mm mouth is ~12 cells), so an open 276// mesh stays open (the gate proves both directions). Deterministic, O(cells). 277func bh_dilate(g: *i64) -> i64 { 278 let nx: i64 = g[BG_NX]; let ny: i64 = g[BG_NY] 279 let kind: *u8 = g[BG_KIND] as *u8 280 let add: *i64 = sys_mmap(g[BG_N] * 8 + 64) as *i64 281 var na: i64 = 0 282 var c: i64 = 0 283 while c < g[BG_N] { 284 if (kind[c] as i64) == BH_KIND_UNSEEN { 285 let cx: i64 = c % nx 286 let cy: i64 = (c / nx) % ny 287 let cz: i64 = c / (nx * ny) 288 var hit: i64 = 0 289 var d: i64 = 0 290 while d < 6 { 291 var ddx: i64 = 0; var ddy: i64 = 0; var ddz: i64 = 0 292 if d == 0 { ddx = 1 } if d == 1 { ddx = 0 - 1 } if d == 2 { ddy = 1 } 293 if d == 3 { ddy = 0 - 1 } if d == 4 { ddz = 1 } if d == 5 { ddz = 0 - 1 } 294 let m: i64 = bh_idx(g, cx + ddx, cy + ddy, cz + ddz) 295 if m >= 0 { if (kind[m] as i64) == BH_KIND_SURFACE { hit = 1 } } 296 d = d + 1 297 } 298 if hit == 1 { add[na] = c; na = na + 1 } 299 } 300 c = c + 1 301 } 302 var i: i64 = 0 303 while i < na { kind[add[i]] = BH_KIND_SURFACE as u8; i = i + 1 } 304 return na 305} 306// flood the EXTERIOR from the padded border (6-connected, never through a surface cell); the rest is INTERIOR 307func bh_flood(g: *i64) -> i64 { 308 let nx: i64 = g[BG_NX]; let ny: i64 = g[BG_NY]; let nz: i64 = g[BG_NZ] 309 let kind: *u8 = g[BG_KIND] as *u8 310 bh_dilate(g) 311 let q: *i64 = g[BG_QUEUE] as *i64 312 var qh: i64 = 0 313 var qt: i64 = 0 314 var iz: i64 = 0 315 while iz < nz { 316 var iy: i64 = 0 317 while iy < ny { 318 var ix: i64 = 0 319 while ix < nx { 320 var border: i64 = 0 321 if ix == 0 { border = 1 } if iy == 0 { border = 1 } if iz == 0 { border = 1 } 322 if ix == nx - 1 { border = 1 } if iy == ny - 1 { border = 1 } if iz == nz - 1 { border = 1 } 323 if border == 1 { 324 let c: i64 = bh_idx(g, ix, iy, iz) 325 if (kind[c] as i64) == BH_KIND_UNSEEN { kind[c] = BH_KIND_EXTERIOR as u8; q[qt] = c; qt = qt + 1 } 326 } 327 ix = ix + 1 328 } 329 iy = iy + 1 330 } 331 iz = iz + 1 332 } 333 while qh < qt { 334 let c: i64 = q[qh] 335 qh = qh + 1 336 let cx: i64 = c % nx 337 let cy: i64 = (c / nx) % ny 338 let cz: i64 = c / (nx * ny) 339 var d: i64 = 0 340 while d < 6 { 341 var ddx: i64 = 0; var ddy: i64 = 0; var ddz: i64 = 0 342 if d == 0 { ddx = 1 } if d == 1 { ddx = 0 - 1 } if d == 2 { ddy = 1 } 343 if d == 3 { ddy = 0 - 1 } if d == 4 { ddz = 1 } if d == 5 { ddz = 0 - 1 } 344 let m: i64 = bh_idx(g, cx + ddx, cy + ddy, cz + ddz) 345 if m >= 0 { if (kind[m] as i64) == BH_KIND_UNSEEN { kind[m] = BH_KIND_EXTERIOR as u8; q[qt] = m; qt = qt + 1 } } 346 d = d + 1 347 } 348 } 349 // ---- STRIP INTERNAL WALLS (2026-09-06). A surface cell that no exterior cell can reach within the shell's own 350 // depth is not a surface: it is a zero-thickness double wall INSIDE the volume -- two abutting parts whose SDFs 351 // touch at 0 (a leg box sharing its face with its neighbour, a garment shell inside a body, a game rip's overlapping 352 // shells). The marcher emits faces there, the raster marks them, and the flood then sees a wall the model does not 353 // have. MEASURED on a biped whose two legs share the plane x=0: the touching mesh carried 9,456 more vertices than 354 // the separated one, and the leg beside the wall peeled THREE times (tips at -51/-35/-25 mm) because the wall halved 355 // its medial radius. The shell depth is DERIVED, never picked: the raster marks one cell and bh_dilate adds one on 356 // each side, so a real shell cell is at most BH_WALL_DEPTH six-steps from the exterior; a surface cell deeper than 357 // that is a wall and becomes INTERIOR, counted in BG_WALLS. Known floor, announced: a wall inside a part thinner 358 // than twice that depth is left standing, because there its cells are within reach of the exterior anyway. 359 let wq: *i64 = g[BG_QUEUE] as *i64 360 let wd: *i64 = g[BG_DIST] as *i64 361 var c3: i64 = 0 362 while c3 < g[BG_N] { wd[c3] = 0 - 1; c3 = c3 + 1 } 363 var wh: i64 = 0 364 var wt: i64 = 0 365 c3 = 0 366 while c3 < g[BG_N] { 367 if (kind[c3] as i64) == BH_KIND_EXTERIOR { wd[c3] = 0; wq[wt] = c3; wt = wt + 1 } 368 c3 = c3 + 1 369 } 370 while wh < wt { 371 let wc: i64 = wq[wh] 372 wh = wh + 1 373 if wd[wc] >= 0 { 374 let wx: i64 = wc % nx 375 let wy: i64 = (wc / nx) % ny 376 let wz: i64 = wc / (nx * ny) 377 // 26-CONNECTED, deliberately: the rastered shell is a 26-connected staircase on every tilted face, and a 378 // six-step walk through it takes long detours, so a real shell cell read as nine deep (MEASURED on the dragon, 379 // no abutting boxes: d4..d9+ held 9,889 cells under a six-step census). Thickness is a 26-step quantity. 380 var ez: i64 = 0 - 1 381 while ez <= 1 { 382 var ey: i64 = 0 - 1 383 while ey <= 1 { 384 var ex: i64 = 0 - 1 385 while ex <= 1 { 386 let wm: i64 = bh_idx(g, wx + ex, wy + ey, wz + ez) 387 if wm >= 0 { if (kind[wm] as i64) == BH_KIND_SURFACE { if wd[wm] < 0 { wd[wm] = wd[wc] + 1; wq[wt] = wm; wt = wt + 1 } } } 388 ex = ex + 1 389 } 390 ey = ey + 1 391 } 392 ez = ez + 1 393 } 394 } 395 } 396 // the census FIRST: how deep does the real shell go, six-steps from the exterior? Printed on every flood so the 397 // strip depth is a measurement any reader can check against this line, never a constant nobody re-derives. 398 var walls: i64 = 0 399 let hist: *i64 = sys_mmap(BH_WALL_HIST * 8 + 64) as *i64 400 var hb: i64 = 0 401 while hb < BH_WALL_HIST { hist[hb] = 0; hb = hb + 1 } 402 var unreached: i64 = 0 403 c3 = 0 404 while c3 < g[BG_N] { 405 if (kind[c3] as i64) == BH_KIND_SURFACE { 406 var dd: i64 = wd[c3] 407 if dd < 0 { unreached = unreached + 1 } else { if dd >= BH_WALL_HIST { dd = BH_WALL_HIST - 1 } hist[dd] = hist[dd] + 1 } 408 var strip: i64 = 0 409 if wd[c3] < 0 { strip = 1 } 410 if wd[c3] > BH_WALL_DEPTH { strip = 1 } 411 if strip == 1 { kind[c3] = BH_KIND_UNSEEN as u8; walls = walls + 1 } 412 } 413 c3 = c3 + 1 414 } 415 g[BG_WALLS] = walls 416 bh_puts("BH-FLOOD shell-depth census (surface cells by 26-connected steps from the exterior; the last bucket is that depth or deeper):" as *u8) 417 hb = 1 418 while hb < BH_WALL_HIST { bh_kv(" d" as *u8, hb); bh_kv("=" as *u8, hist[hb]); hb = hb + 1 } 419 bh_kv(" unreached=" as *u8, unreached); bh_kv(" strip_deeper_than=" as *u8, BH_WALL_DEPTH); bh_kv(" walls_stripped=" as *u8, walls) 420 bh_puts("\n" as *u8) 421 var ext: i64 = 0; var surf: i64 = 0; var intr: i64 = 0 422 var c2: i64 = 0 423 while c2 < g[BG_N] { 424 let k: i64 = kind[c2] as i64 425 if k == BH_KIND_UNSEEN { kind[c2] = BH_KIND_INTERIOR as u8; intr = intr + 1 } 426 else { if k == BH_KIND_SURFACE { surf = surf + 1 } else { ext = ext + 1 } } 427 c2 = c2 + 1 428 } 429 g[BG_EXT] = ext; g[BG_SURF] = surf; g[BG_INT] = intr; g[BG_DOMAIN] = surf + intr 430 // the Dial pool: 26 pushes per settled cell is an exact bound, so no refusal path is needed 431 g[BG_POOLCAP] = g[BG_DOMAIN] * BH_PUSH_PER_CELL + BH_NBUCKET 432 g[BG_POOLC] = sys_mmap(g[BG_POOLCAP] * 8 + 64) as i64 433 g[BG_POOLN] = sys_mmap(g[BG_POOLCAP] * 8 + 64) as i64 434 return g[BG_DOMAIN] 435} 436 437// ---- Dial-bucket Dijkstra over the domain (chamfer 10/14/17) ---- 438func bh_reset(g: *i64) -> i64 { 439 let dist: *i64 = g[BG_DIST] as *i64 440 let st: *u8 = g[BG_SET] as *u8 441 var c: i64 = 0 442 while c < g[BG_N] { dist[c] = BH_DIST_INF; st[c] = 0 as u8; c = c + 1 } 443 let head: *i64 = g[BG_HEAD] as *i64 444 var b: i64 = 0 445 while b < BH_NBUCKET { head[b] = 0 - 1; b = b + 1 } 446 g[BG_POOLUSED] = 0 447 return 0 448} 449func bh_push(g: *i64, c: i64, d: i64) -> i64 { 450 let pc: *i64 = g[BG_POOLC] as *i64 451 let pn: *i64 = g[BG_POOLN] as *i64 452 let head: *i64 = g[BG_HEAD] as *i64 453 let e: i64 = g[BG_POOLUSED] 454 if e >= g[BG_POOLCAP] { return 0 - 1 } 455 pc[e] = c 456 pn[e] = head[d % BH_NBUCKET] 457 head[d % BH_NBUCKET] = e 458 g[BG_POOLUSED] = e + 1 459 return 1 460} 461// seed one cell at distance 0 (returns 1 if it is a domain cell) 462func bh_seed(g: *i64, c: i64) -> i64 { 463 if bh_is_domain(g, c) == 0 { return 0 } 464 let dist: *i64 = g[BG_DIST] as *i64 465 if dist[c] == 0 { return 1 } 466 dist[c] = 0 467 bh_push(g, c, 0) 468 return 1 469} 470// run from the seeds already pushed; returns the number of settled cells; pending counted by pushes minus pops 471func bh_dijkstra(g: *i64) -> i64 { 472 let nx: i64 = g[BG_NX]; let ny: i64 = g[BG_NY] 473 let dist: *i64 = g[BG_DIST] as *i64 474 let st: *u8 = g[BG_SET] as *u8 475 let pc: *i64 = g[BG_POOLC] as *i64 476 let pn: *i64 = g[BG_POOLN] as *i64 477 let head: *i64 = g[BG_HEAD] as *i64 478 var pending: i64 = g[BG_POOLUSED] 479 var dcur: i64 = 0 480 var settled: i64 = 0 481 while pending > 0 { 482 let b: i64 = dcur % BH_NBUCKET 483 while head[b] >= 0 { 484 let e: i64 = head[b] 485 head[b] = pn[e] 486 pending = pending - 1 487 let c: i64 = pc[e] 488 if (st[c] as i64) == 0 { 489 if dist[c] == dcur { 490 st[c] = 1 as u8 491 settled = settled + 1 492 let cx: i64 = c % nx 493 let cy: i64 = (c / nx) % ny 494 let cz: i64 = c / (nx * ny) 495 var dz: i64 = 0 - 1 496 while dz <= 1 { 497 var dy: i64 = 0 - 1 498 while dy <= 1 { 499 var dx: i64 = 0 - 1 500 while dx <= 1 { 501 let nz0: i64 = bh_abs(dx) + bh_abs(dy) + bh_abs(dz) 502 if nz0 > 0 { 503 let m: i64 = bh_idx(g, cx + dx, cy + dy, cz + dz) 504 if m >= 0 { if (st[m] as i64) == 0 { if bh_is_domain(g, m) == 1 { 505 var wgt: i64 = BH_W_FACE 506 if nz0 == 2 { wgt = BH_W_EDGE } 507 if nz0 == 3 { wgt = BH_W_CORNER } 508 let nd: i64 = dcur + wgt 509 if nd < dist[m] { dist[m] = nd; if bh_push(g, m, nd) == 1 { pending = pending + 1 } } 510 } } } 511 } 512 dx = dx + 1 513 } 514 dy = dy + 1 515 } 516 dz = dz + 1 517 } 518 } 519 } 520 } 521 dcur = dcur + 1 522 } 523 return settled 524} 525// nearest domain cell to a point by expanding Chebyshev shells (deterministic scan order); -1 when the grid has none 526func bh_project(g: *i64, x: i64, y: i64, z: i64) -> i64 { 527 let cell: i64 = g[BG_CELL] 528 let ix: i64 = (x - g[BG_OX]) / cell 529 let iy: i64 = (y - g[BG_OY]) / cell 530 let iz: i64 = (z - g[BG_OZ]) / cell 531 let rmax: i64 = bh_max(bh_max(g[BG_NX], g[BG_NY]), g[BG_NZ]) 532 var r: i64 = 0 533 while r <= rmax { 534 var dz: i64 = 0 - r 535 while dz <= r { 536 var dy: i64 = 0 - r 537 while dy <= r { 538 var dx: i64 = 0 - r 539 while dx <= r { 540 if bh_max(bh_max(bh_abs(dx), bh_abs(dy)), bh_abs(dz)) == r { 541 let m: i64 = bh_idx(g, ix + dx, iy + dy, iz + dz) 542 if bh_is_domain(g, m) == 1 { return m } 543 } 544 dx = dx + 1 545 } 546 dy = dy + 1 547 } 548 dz = dz + 1 549 } 550 r = r + 1 551 } 552 return 0 - 1 553} 554// seed joint j's segment a->b at half-cell spacing; returns seeded cells, projecting when none lies in the volume 555func bh_seed_segment(g: *i64, ax: i64, ay: i64, az: i64, bx: i64, by: i64, bz: i64, projected: *i64) -> i64 { 556 let half: i64 = bh_max(g[BG_CELL] / BH_SAMPLE_DIV, 1) 557 let n: i64 = bh_seglen(ax, ay, az, bx, by, bz) / half + 1 558 var seeds: i64 = 0 559 var s: i64 = 0 560 while s <= n { 561 let px: i64 = ax + (bx - ax) * s / n 562 let py: i64 = ay + (by - ay) * s / n 563 let pz: i64 = az + (bz - az) * s / n 564 seeds = seeds + bh_seed(g, bh_cell_of(g, px, py, pz)) 565 s = s + 1 566 } 567 if seeds == 0 { 568 let m: i64 = bh_project(g, (ax + bx) / 2, (ay + by) / 2, (az + bz) / 2) 569 if m >= 0 { seeds = bh_seed(g, m); projected[0] = projected[0] + 1 } 570 } 571 return seeds 572} 573 574// ---- joints: segment = joint -> mean of its children (a leaf is a point) ---- 575func bh_joint_segments(w: *i64, swo: i64, nj: i64, seg: *i64) -> i64 { 576 let cs: *i64 = sys_mmap(nj * 4 * 8 + 64) as *i64 577 var j: i64 = 0 578 while j < nj { 579 let p: i64 = w[swo + 1 + j * BH_JOINT_WORDS] 580 if p >= 0 { if p < nj { 581 let o: i64 = swo + 1 + j * BH_JOINT_WORDS 582 cs[p * 4] = cs[p * 4] + w[o + 1] 583 cs[p * 4 + 1] = cs[p * 4 + 1] + w[o + 2] 584 cs[p * 4 + 2] = cs[p * 4 + 2] + w[o + 3] 585 cs[p * 4 + 3] = cs[p * 4 + 3] + 1 586 } } 587 j = j + 1 588 } 589 j = 0 590 while j < nj { 591 let o: i64 = swo + 1 + j * BH_JOINT_WORDS 592 seg[j * 6] = w[o + 1]; seg[j * 6 + 1] = w[o + 2]; seg[j * 6 + 2] = w[o + 3] 593 if cs[j * 4 + 3] > 0 { 594 seg[j * 6 + 3] = cs[j * 4] / cs[j * 4 + 3] 595 seg[j * 6 + 4] = cs[j * 4 + 1] / cs[j * 4 + 3] 596 seg[j * 6 + 5] = cs[j * 4 + 2] / cs[j * 4 + 3] 597 } else { 598 seg[j * 6 + 3] = seg[j * 6]; seg[j * 6 + 4] = seg[j * 6 + 1]; seg[j * 6 + 5] = seg[j * 6 + 2] 599 } 600 j = j + 1 601 } 602 return nj 603} 604// squared Euclidean distance from a point to joint j's segment (q10 parameter, clamped) 605func bh_seg_dist2(seg: *i64, j: i64, x: i64, y: i64, z: i64) -> i64 { 606 let ax: i64 = seg[j * 6]; let ay: i64 = seg[j * 6 + 1]; let az: i64 = seg[j * 6 + 2] 607 let ux: i64 = seg[j * 6 + 3] - ax; let uy: i64 = seg[j * 6 + 4] - ay; let uz: i64 = seg[j * 6 + 5] - az 608 let uu: i64 = ux * ux + uy * uy + uz * uz 609 var t: i64 = 0 610 if uu > 0 { 611 t = ((x - ax) * ux + (y - ay) * uy + (z - az) * uz) * BH_Q10 / uu 612 if t < 0 { t = 0 } 613 if t > BH_Q10 { t = BH_Q10 } 614 } 615 let cx: i64 = ax + ux * t / BH_Q10 616 let cy: i64 = ay + uy * t / BH_Q10 617 let cz: i64 = az + uz * t / BH_Q10 618 return (x - cx) * (x - cx) + (y - cy) * (y - cy) + (z - cz) * (z - cz) 619} 620func bh_nearest_joint(seg: *i64, nj: i64, x: i64, y: i64, z: i64) -> i64 { 621 var best: i64 = 0 622 var bd: i64 = bh_seg_dist2(seg, 0, x, y, z) 623 var j: i64 = 1 624 while j < nj { 625 let d: i64 = bh_seg_dist2(seg, j, x, y, z) 626 if d < bd { bd = d; best = j } 627 j = j + 1 628 } 629 return best 630} 631// keep the four largest raw weights per vertex (unsorted slots; the weakest slot is replaced) 632func bh_top4_insert(tj: *i64, tw: *i64, v: i64, j: i64, wgt: i64) -> i64 { 633 var slot: i64 = 0 634 var k: i64 = 1 635 while k < BH_INF_SLOTS { if tw[v * BH_INF_SLOTS + k] < tw[v * BH_INF_SLOTS + slot] { slot = k } k = k + 1 } 636 if wgt > tw[v * BH_INF_SLOTS + slot] { tj[v * BH_INF_SLOTS + slot] = j; tw[v * BH_INF_SLOTS + slot] = wgt; return 1 } 637 return 0 638} 639 640// ---- NXA writer: ns sections (tags, payload pointers, word lengths) -> one file; returns bytes written ---- 641func bh_nxa_write(path: *u8, ns: i64, tags: *i64, ptrs: *i64, wls: *i64) -> i64 { 642 if ns < 1 { return 0 - 1 } 643 if ns > BH_MAX_SECTIONS { return 0 - 1 } 644 let hdr: *i64 = sys_mmap(BH_HDR_BYTES + 64) as *i64 645 let toc: *i64 = sys_mmap(ns * BH_TOC_BYTES + 64) as *i64 646 var off: i64 = BH_HDR_BYTES + ns * BH_TOC_BYTES 647 var s: i64 = 0 648 while s < ns { 649 toc[s * BH_TOC_WORDS] = tags[s] 650 toc[s * BH_TOC_WORDS + 1] = off 651 toc[s * BH_TOC_WORDS + 2] = wls[s] 652 toc[s * BH_TOC_WORDS + 3] = nxa_check2(1, ptrs[s] as *i64, wls[s]) 653 off = off + wls[s] * 8 654 s = s + 1 655 } 656 hdr[0] = nxa_magic() 657 hdr[1] = NXA_VER 658 hdr[2] = ns 659 hdr[3] = nxa_check2(1, toc, ns * BH_TOC_WORDS) 660 let fd: i64 = sys_openat_wr(path, BH_MODE_0644) 661 if fd < 0 { return 0 - 2 } 662 sys_write(fd, hdr as *u8, BH_HDR_BYTES) 663 sys_write(fd, toc as *u8, ns * BH_TOC_BYTES) 664 s = 0 665 while s < ns { sys_write(fd, ptrs[s] as *u8, wls[s] * 8); s = s + 1 } 666 sys_close(fd) 667 return off 668} 669 670// ---- the run: read, voxelise, solve, weigh, write. rep[] carries every count the report prints ---- 671func bh_run(inpath: *u8, outpath: *u8, mode: i64, cells_conf: i64, rep: *i64) -> i64 { 672 var cells: i64 = cells_conf 673 rep[BR_CONF] = 1 674 if cells < 1 { cells = BH_GRID_DEFAULT; rep[BR_CONF] = 0 } 675 rep[BR_GRIDN] = cells 676 let lp: *i64 = sys_mmap(16) as *i64 677 let b: *u8 = sys_read_file(inpath, lp) 678 if (b as i64) == 0 { bh_puts("BONEHEAT-REFUSE cannot read input\n" as *u8); return BH_EXIT_REFUSE } 679 let flen: i64 = lp[0] 680 let w: *i64 = b as *i64 681 let vwo: i64 = nxa_find(b, flen, nxa_tag4("VERT" as *u8)) 682 let two: i64 = nxa_find(b, flen, nxa_tag4("TRIS" as *u8)) 683 let swo: i64 = nxa_find(b, flen, nxa_tag4("SKEL" as *u8)) 684 if vwo < 0 { bh_puts("BONEHEAT-REFUSE no valid VERT section (not an NXA or corrupt)\n" as *u8); return BH_EXIT_REFUSE } 685 if two < 0 { bh_puts("BONEHEAT-REFUSE no valid TRIS section (a point cloud has no volume to solve over)\n" as *u8); return BH_EXIT_REFUSE } 686 if swo < 0 { bh_puts("BONEHEAT-REFUSE no valid SKEL section (nothing to weight against -- rig it first)\n" as *u8); return BH_EXIT_REFUSE } 687 let nv: i64 = w[vwo] 688 let nt: i64 = w[two] 689 let nj: i64 = w[swo] 690 if nv < 1 { bh_puts("BONEHEAT-REFUSE empty VERT\n" as *u8); return BH_EXIT_REFUSE } 691 if nt < 1 { bh_puts("BONEHEAT-REFUSE empty TRIS\n" as *u8); return BH_EXIT_REFUSE } 692 if nj < 1 { bh_puts("BONEHEAT-REFUSE empty SKEL\n" as *u8); return BH_EXIT_REFUSE } 693 rep[BR_VERTS] = nv; rep[BR_TRIS] = nt; rep[BR_JOINTS] = nj 694 let seg: *i64 = sys_mmap(nj * 6 * 8 + 64) as *i64 695 bh_joint_segments(w, swo, nj, seg) 696 let tj: *i64 = sys_mmap(nv * BH_INF_SLOTS * 8 + 64) as *i64 697 let tw: *i64 = sys_mmap(nv * BH_INF_SLOTS * 8 + 64) as *i64 698 var unreached_v: i64 = 0 699 if mode == BH_MODE_GEODESIC { 700 let g: *i64 = sys_mmap(BG_WORDS * 8) as *i64 701 bh_grid_build(g, w, vwo, nv, cells) 702 bh_raster(g, w, vwo, two, nt) 703 bh_flood(g) 704 rep[BR_CELLS] = g[BG_N]; rep[BR_EXT] = g[BG_EXT]; rep[BR_SURF] = g[BG_SURF]; rep[BR_INT] = g[BG_INT] 705 rep[BR_WALLS] = g[BG_WALLS] 706 rep[BR_NX] = g[BG_NX]; rep[BR_NY] = g[BG_NY]; rep[BR_NZ] = g[BG_NZ]; rep[BR_CELL] = g[BG_CELL] 707 if g[BG_INT] > 0 { rep[BR_SEALED] = 1 } else { rep[BR_SEALED] = 0 } 708 // vertex -> cell (every vertex of a triangle sits in a surface cell by construction) 709 let vcell: *i64 = sys_mmap(nv * 8 + 64) as *i64 710 var v: i64 = 0 711 while v < nv { 712 let o: i64 = vwo + 1 + v * BH_VERT_WORDS 713 var c: i64 = bh_cell_of(g, w[o], w[o + 1], w[o + 2]) 714 if bh_is_domain(g, c) == 0 { c = 0 - 1 } 715 vcell[v] = c 716 v = v + 1 717 } 718 let projected: *i64 = sys_mmap(16) as *i64 719 var seeds_total: i64 = 0 720 var unreach_j: i64 = 0 721 let dist: *i64 = g[BG_DIST] as *i64 722 var j: i64 = 0 723 while j < nj { 724 bh_reset(g) 725 let sd: i64 = bh_seed_segment(g, seg[j*6], seg[j*6+1], seg[j*6+2], seg[j*6+3], seg[j*6+4], seg[j*6+5], projected) 726 if sd == 0 { unreach_j = unreach_j + 1 } else { 727 seeds_total = seeds_total + sd 728 bh_dijkstra(g) 729 v = 0 730 while v < nv { 731 let c: i64 = vcell[v] 732 if c >= 0 { let d: i64 = dist[c]; if d < BH_DIST_INF { 733 let dd: i64 = d + BH_HALF_STEP 734 bh_top4_insert(tj, tw, v, j, (1 << BH_WSCALE_SHIFT) / (dd * dd)) 735 } } 736 v = v + 1 737 } 738 } 739 j = j + 1 740 } 741 rep[BR_SEEDS] = seeds_total; rep[BR_PROJECTED] = projected[0]; rep[BR_UNREACH_J] = unreach_j 742 } else { 743 var v2: i64 = 0 744 while v2 < nv { 745 let o: i64 = vwo + 1 + v2 * BH_VERT_WORDS 746 tj[v2 * BH_INF_SLOTS] = bh_nearest_joint(seg, nj, w[o], w[o + 1], w[o + 2]) 747 tw[v2 * BH_INF_SLOTS] = 1 748 v2 = v2 + 1 749 } 750 } 751 // normalise to exactly 4096 per vertex; residual folded into the largest influence; unreached -> nearest segment 752 let skin: *i64 = sys_mmap((1 + nv * BH_SKIN_WORDS) * 8 + 64) as *i64 753 skin[0] = nv 754 var mixed: i64 = 0 755 var v3: i64 = 0 756 while v3 < nv { 757 var s: i64 = 0 758 var k: i64 = 0 759 while k < BH_INF_SLOTS { s = s + tw[v3 * BH_INF_SLOTS + k]; k = k + 1 } 760 let row: i64 = 1 + v3 * BH_SKIN_WORDS 761 if s == 0 { 762 unreached_v = unreached_v + 1 763 let o: i64 = vwo + 1 + v3 * BH_VERT_WORDS 764 skin[row] = bh_nearest_joint(seg, nj, w[o], w[o + 1], w[o + 2]); skin[row + 4] = BH_Q12 765 skin[row + 1] = 0; skin[row + 2] = 0; skin[row + 3] = 0; skin[row + 5] = 0; skin[row + 6] = 0; skin[row + 7] = 0 766 } else { 767 var acc: i64 = 0 768 var big: i64 = 0 769 var nz: i64 = 0 770 k = 0 771 while k < BH_INF_SLOTS { 772 let wq: i64 = tw[v3 * BH_INF_SLOTS + k] * BH_Q12 / s 773 skin[row + k] = tj[v3 * BH_INF_SLOTS + k] 774 skin[row + 4 + k] = wq 775 if tw[v3 * BH_INF_SLOTS + k] == 0 { skin[row + k] = 0 } 776 acc = acc + wq 777 if wq > skin[row + 4 + big] { big = k } 778 if wq > 0 { nz = nz + 1 } 779 k = k + 1 780 } 781 skin[row + 4 + big] = skin[row + 4 + big] + (BH_Q12 - acc) 782 if nz >= 2 { mixed = mixed + 1 } 783 } 784 v3 = v3 + 1 785 } 786 rep[BR_UNREACH_V] = unreached_v; rep[BR_MIXED] = mixed 787 // rebuild the file: every original section in order, SKIN replaced (or appended) 788 let h: *i64 = b as *i64 789 let ns0: i64 = h[2] 790 let tb: *i64 = ((b as i64) + BH_HDR_BYTES) as *i64 791 let tags: *i64 = sys_mmap((ns0 + 1) * 8 + 64) as *i64 792 let ptrs: *i64 = sys_mmap((ns0 + 1) * 8 + 64) as *i64 793 let wls: *i64 = sys_mmap((ns0 + 1) * 8 + 64) as *i64 794 let skintag: i64 = nxa_tag4("SKIN" as *u8) 795 var ns: i64 = 0 796 var replaced: i64 = 0 797 var s0: i64 = 0 798 while s0 < ns0 { 799 tags[ns] = tb[s0 * BH_TOC_WORDS] 800 if tags[ns] == skintag { ptrs[ns] = skin as i64; wls[ns] = 1 + nv * BH_SKIN_WORDS; replaced = 1 } 801 else { ptrs[ns] = (b as i64) + tb[s0 * BH_TOC_WORDS + 1]; wls[ns] = tb[s0 * BH_TOC_WORDS + 2] } 802 ns = ns + 1 803 s0 = s0 + 1 804 } 805 if replaced == 0 { tags[ns] = skintag; ptrs[ns] = skin as i64; wls[ns] = 1 + nv * BH_SKIN_WORDS; ns = ns + 1 } 806 let wrote: i64 = bh_nxa_write(outpath, ns, tags, ptrs, wls) 807 if wrote < 0 { bh_puts("BONEHEAT-REFUSE cannot write output\n" as *u8); return BH_EXIT_REFUSE } 808 rep[BR_BYTES] = wrote 809 return BH_EXIT_OK 810} 811func bh_report(mode: i64, rep: *i64, inpath: *u8, outpath: *u8) -> i64 { 812 bh_puts("BONEHEAT mode=" as *u8) 813 if mode == BH_MODE_GEODESIC { bh_puts("geodesic" as *u8) } else { bh_puts("nearest" as *u8) } 814 bh_kv(" verts=" as *u8, rep[BR_VERTS]); bh_kv(" tris=" as *u8, rep[BR_TRIS]); bh_kv(" joints=" as *u8, rep[BR_JOINTS]) 815 bh_kv(" grid=" as *u8, rep[BR_NX]); bh_kv("x" as *u8, rep[BR_NY]); bh_kv("x" as *u8, rep[BR_NZ]); bh_kv(" cell=" as *u8, rep[BR_CELL]) 816 bh_kv(" cells=" as *u8, rep[BR_CELLS]); bh_kv(" exterior=" as *u8, rep[BR_EXT]); bh_kv(" surface=" as *u8, rep[BR_SURF]); bh_kv(" interior=" as *u8, rep[BR_INT]) 817 bh_kv(" sum=" as *u8, rep[BR_EXT] + rep[BR_SURF] + rep[BR_INT]); bh_kv(" sealed=" as *u8, rep[BR_SEALED]) 818 bh_kv(" seeds=" as *u8, rep[BR_SEEDS]); bh_kv(" joints_projected=" as *u8, rep[BR_PROJECTED]); bh_kv(" joints_unreachable=" as *u8, rep[BR_UNREACH_J]) 819 bh_kv(" verts_unreached=" as *u8, rep[BR_UNREACH_V]); bh_kv(" mixed_verts=" as *u8, rep[BR_MIXED]) 820 bh_puts(" conf=" as *u8); if rep[BR_CONF] == 1 { bh_puts("CONF" as *u8) } else { bh_puts("DEFAULT" as *u8) } 821 bh_kv(" cells_longest=" as *u8, rep[BR_GRIDN]); bh_kv(" wrote=" as *u8, rep[BR_BYTES]) 822 bh_puts(" in=" as *u8); bh_puts(inpath); bh_puts(" out=" as *u8); bh_puts(outpath); bh_puts("\n" as *u8) 823 return 0 824}