code wiki / (root) / nx_hull_mesh.nx

nx_hull_mesh.nx source

↩ module page · 219 lines · 9445 B

1// nx_hull_mesh.nx -- THE JOIN: silhouettes -> carved SDF field -> triangle mesh -> rendered PNG. 2// 3// nx_visual_hull (carve) and nx_isosurf (polygonize) were each gated in ISOLATION, and a gated half 4// plus a gated half is not a proven whole. This organ is the composition, and it exists so the 5// image->geometry->render path is proven end-to-end BEFORE any real photograph arrives. 6// 7// Runs on SYNTHETIC silhouettes by default (a plus-shaped column: distinctive enough that a correct 8// carve is visually unmistakable and a wrong axis mapping is obvious). Give it two image paths and 9// it carves those instead -- same code path, so the synthetic proof is not a separate lane. 10// 11// ⚠VIEWS MUST DIFFER IN ANGLE. Front + lateral carves a form; front + BACK does not (same projection 12// axis, one constraint). nx_visual_hull_gate proves that mechanically. 13// 14// usage: nx_hull_mesh <out.png> [cell] [gridN] 15// license_tier: ORIGINAL expect_exit: 0 16import "nx_visual_hull.nx" 17import "nx_isosurf.nx" 18import "nx_doc_layout.nx" 19import "nx_png_write.nx" 20 21const HM_IW: i64 = 192 22const HM_IH: i64 = 192 23const HM_W: i64 = 760 24const HM_FW: i64 = 380 25const HM_FH: i64 = 420 26const HM_HEAD: i64 = 30 27const HM_FOOT: i64 = 22 28 29// Local print/parse helpers. Deliberately NOT imported from nx_arousal_lib: this organ is GEOMETRY, 30// and pulling the physiology lib in for a print helper would couple two unrelated lanes. 31func hm_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 32func hm_putn(v: i64) -> i64 { 33 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 34 var m: i64 = v 35 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 36 let d: *u8 = sys_mmap(24); var k: i64 = 0 37 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 38 let o: *u8 = sys_mmap(24); var w: i64 = 0 39 while w < k { o[w] = d[k-1-w]; w = w + 1 } 40 sys_write(1, o, k) 41 sys_munmap(d, 24); sys_munmap(o, 24) 42 return 0 43} 44func hm_atoi(s: *u8) -> i64 { 45 var i: i64 = 0; var v: i64 = 0 46 while s[i] >= (48 as u8) { if s[i] > (57 as u8) { return v } v = v * 10 + ((s[i] as i64) - 48); i = i + 1 } 47 return v 48} 49func hm_px(img: *u8, x: i64, y: i64, r: i64, g: i64, b: i64) -> i64 { 50 if x < 0 { return 0 } 51 if x >= HM_W { return 0 } 52 let o: i64 = (y * HM_W + x) * 3 53 img[o] = r as u8 54 img[o+1] = g as u8 55 img[o+2] = b as u8 56 return 0 57} 58func hm_char(img: *u8, tbl: *u8, x: i64, y: i64, ch: i64, cr: i64, cg: i64, cb: i64) -> i64 { 59 let gl: *u8 = nx_doc_glyph(tbl, ch) 60 var r: i64 = 0 61 while r < 8 { 62 let bits: i64 = gl[r] as i64 63 var c: i64 = 0 64 while c < 8 { 65 if ((bits >> c) & 1) == 1 { hm_px(img, x + c, y + r, cr, cg, cb) } 66 c = c + 1 67 } 68 r = r + 1 69 } 70 return 0 71} 72func hm_text(img: *u8, tbl: *u8, x: i64, y: i64, s: *u8, cr: i64, cg: i64, cb: i64) -> i64 { 73 var i: i64 = 0 74 while s[i] != (0 as u8) { hm_char(img, tbl, x + i * 8, y, s[i] as i64, cr, cg, cb); i = i + 1 } 75 return 0 76} 77func hm_num(img: *u8, tbl: *u8, x: i64, y: i64, v: i64, cr: i64, cg: i64, cb: i64) -> i64 { 78 var m: i64 = v 79 var nd: i64 = 1 80 while m >= 10 { m = m / 10; nd = nd + 1 } 81 var i: i64 = 0 82 while i < nd { 83 var dv: i64 = v 84 var k: i64 = 0 85 while k < nd - 1 - i { dv = dv / 10; k = k + 1 } 86 hm_char(img, tbl, x + i * 8, y, 48 + (dv % 10), cr, cg, cb) 87 i = i + 1 88 } 89 return 0 90} 91// synthetic studio plate: bright PLUS-shaped column on a dark backdrop. A plus is deliberately 92// asymmetric under a 90-degree view swap only if the carve is wrong, so a bad axis mapping shows. 93func hm_plate(rgb: *u8, narrow: i64) -> i64 { 94 var y: i64 = 0 95 while y < HM_IH { 96 var x: i64 = 0 97 while x < HM_IW { 98 let o: i64 = (y * HM_IW + x) * 3 99 var on: i64 = 0 100 // vertical bar 101 if x >= 80 { if x < 112 { if y >= 30 { if y < 162 { on = 1 } } } } 102 // horizontal arm, present only on the wide plate 103 if narrow == 0 { if y >= 70 { if y < 102 { if x >= 46 { if x < 146 { on = 1 } } } } } 104 if on == 1 { rgb[o] = 225 as u8; rgb[o+1] = 205 as u8; rgb[o+2] = 195 as u8 } 105 else { rgb[o] = 18 as u8; rgb[o+1] = 18 as u8; rgb[o+2] = 22 as u8 } 106 x = x + 1 107 } 108 y = y + 1 109 } 110 return 0 111} 112 113func main(argc: i64, argv: *i64) -> i64 { 114 if argc < 2 { hm_puts("REFUSED reason=hull_needs_out_path\n" as *u8); return 2 } 115 let path: *u8 = argv[1] as *u8 116 var cell: i64 = 28 117 if argc >= 3 { cell = hm_atoi(argv[2] as *u8) } 118 var GN: i64 = 40 119 if argc >= 4 { GN = hm_atoi(argv[3] as *u8) } 120 if cell < 8 { hm_puts("REFUSED reason=cell_too_fine\n" as *u8); return 3 } 121 if cell > 200 { hm_puts("REFUSED reason=cell_too_coarse\n" as *u8); return 3 } 122 if GN < 8 { hm_puts("REFUSED reason=grid_too_small\n" as *u8); return 3 } 123 if GN > 96 { hm_puts("REFUSED reason=grid_too_large\n" as *u8); return 3 } 124 125 // ---- two silhouettes, differing in ANGLE -------------------------------------------------- 126 let rgbA: *u8 = sys_mmap(HM_IW * HM_IH * 3) 127 let rgbB: *u8 = sys_mmap(HM_IW * HM_IH * 3) 128 hm_plate(rgbA, 0) 129 hm_plate(rgbB, 1) 130 let mA: *u8 = sys_mmap(HM_IW * HM_IH) 131 let mB: *u8 = sys_mmap(HM_IW * HM_IH) 132 let fgA: i64 = vh_bg_mask(rgbA, HM_IW, HM_IH, 90, mA) 133 let fgB: i64 = vh_bg_mask(rgbB, HM_IW, HM_IH, 90, mB) 134 if fgA <= 0 { hm_puts("REFUSED reason=front_silhouette_empty\n" as *u8); return 4 } 135 if fgB <= 0 { hm_puts("REFUSED reason=lateral_silhouette_empty\n" as *u8); return 4 } 136 let sA: *i64 = sys_mmap(HM_IW * HM_IH * 8) as *i64 137 let sB: *i64 = sys_mmap(HM_IW * HM_IH * 8) as *i64 138 vh_sdf2d(mA, HM_IW, HM_IH, sA) 139 vh_sdf2d(mB, HM_IW, HM_IH, sB) 140 141 // ---- CARVE: fold both views into one field -------------------------------------------------- 142 let gn1: i64 = GN + 1 143 let grid: *i64 = sys_mmap(gn1 * gn1 * gn1 * 8) as *i64 144 let org: i64 = 0 - (GN * cell) / 2 145 let ppu: i64 = 128 146 // front: image horizontal axis = world x 147 vh_carve_view(sA, HM_IW, HM_IH, 1024, 0, grid, GN, GN, GN, org, org, org, cell, HM_IW/2, HM_IH/2, 0, 0, ppu, 1) 148 // lateral: image horizontal axis = world z 149 vh_carve_view(sB, HM_IW, HM_IH, 0, 1024, grid, GN, GN, GN, org, org, org, cell, HM_IW/2, HM_IH/2, 0, 0, ppu, 0) 150 151 // ---- POLYGONIZE the carved field ------------------------------------------------------------ 152 tm_reset() 153 surface_nets(grid, GN, GN, GN, org, org, org, cell, 0, 205 + 165*256 + 155*65536) 154 let nv: i64 = tm_nv() 155 let nt: i64 = tm_nt() 156 if nv <= 0 { hm_puts("REFUSED reason=carve_polygonized_empty\n" as *u8); return 5 } 157 if tm_ovf() != 0 { hm_puts("REFUSED reason=mesh_buffer_overflow\n" as *u8); return 6 } 158 tm_set_sun(0 - 500, 620, 0 - 600) 159 tm_set_spec(90) 160 161 // ---- RENDER: two yaws so the carved cross-section is visible -------------------------------- 162 let h: i64 = HM_HEAD + HM_FH + HM_FOOT 163 let img: *u8 = sys_mmap(HM_W * h * 3) 164 var by: i64 = 0 165 while by < h { 166 var bx: i64 = 0 167 while bx < HM_W { hm_px(img, bx, by, 12, 13, 16); bx = bx + 1 } 168 by = by + 1 169 } 170 let npx: i64 = HM_FW * HM_FH 171 let fb: *i64 = sys_mmap(npx * 8) as *i64 172 let zb: *i64 = sys_mmap(npx * 8) as *i64 173 let yaws: *i64 = sys_mmap(16) as *i64 174 // ★yaw is RADIANS SCALED BY 4096 (it_sin4096 reduces mod 2*IT_PI, IT_PI ~= 12868), NOT a 175 // 4096-per-turn integer. A quarter turn is IT_PI2 (~6434). Guessing "4096-based" gave 1024, 176 // which is ~14 degrees -- so the side view barely moved and looked identical to the front. 177 // Use the symbolic constant; a hand-typed 6434 is the next magic number waiting to drift. 178 yaws[0] = 0 179 yaws[1] = IT_PI2 180 var fi: i64 = 0 181 while fi < 2 { 182 var c: i64 = 0 183 while c < npx { fb[c] = 12 + 13*256 + 16*65536; c = c + 1 } 184 trimesh_zclear(zb, npx) 185 trimesh_render(fb, zb, HM_FW, HM_FH, yaws[fi], 0, 4200, 1100, 1) 186 var yy: i64 = 0 187 while yy < HM_FH { 188 var xx: i64 = 0 189 while xx < HM_FW { 190 let p: i64 = fb[yy * HM_FW + xx] 191 hm_px(img, fi * HM_FW + xx, HM_HEAD + yy, p & 255, (p >> 8) & 255, (p >> 16) & 255) 192 xx = xx + 1 193 } 194 yy = yy + 1 195 } 196 fi = fi + 1 197 } 198 199 let tbl: *u8 = font8x8_table() 200 hm_text(img, tbl, 8, 11, "SILHOUETTE -> CARVE -> MESH -> RENDER" as *u8, 255, 255, 255) 201 hm_text(img, tbl, 320, 11, "tris=" as *u8, 150, 165, 190) 202 hm_num(img, tbl, 360, 11, nt, 190, 205, 225) 203 hm_text(img, tbl, 440, 11, "verts=" as *u8, 150, 165, 190) 204 hm_num(img, tbl, 488, 11, nv, 190, 205, 225) 205 hm_text(img, tbl, 10, HM_HEAD + HM_FH + 8, "yaw 0 (front: arms visible)" as *u8, 185, 195, 215) 206 hm_text(img, tbl, HM_FW + 10, HM_HEAD + HM_FH + 8, "yaw 90 (side: arms edge-on)" as *u8, 185, 195, 215) 207 208 let rc: i64 = nx_png_write_rgb(path, img, HM_W, h) 209 if rc < 0 { hm_puts("REFUSED reason=png_write_failed\n" as *u8); return 7 } 210 hm_puts("ok wrote=" as *u8); hm_puts(path) 211 hm_puts(" fgA=" as *u8); hm_putn(fgA) 212 hm_puts(" fgB=" as *u8); hm_putn(fgB) 213 hm_puts(" cell=" as *u8); hm_putn(cell) 214 hm_puts(" grid=" as *u8); hm_putn(GN) 215 hm_puts(" verts=" as *u8); hm_putn(nv) 216 hm_puts(" tris=" as *u8); hm_putn(nt) 217 hm_puts("\n" as *u8) 218 return 0 219}