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}