code wiki / (root) / nx_depthfuse.nx

nx_depthfuse.nx source

↩ module page · 138 lines · 6001 B

1// nx_depthfuse.nx -- FUSE a plane-sweep depth map into a real nx_mesh3 TRIANGLE SURFACE. 2// This is the step that turns "we recovered depth" into "we have a reconstructed part": once the depth map 3// is a mesh3, the ENTIRE existing twin analysis chain runs on it unchanged -- nx_meshthick wall thickness, 4// nx_meshseg3d segmentation, nx_partid shape class, STL export -- none of which can consume a depth map. 5// DISCONTINUITY CULLING is the load-bearing detail: naively triangulating a depth grid webs a skin across 6// every depth jump, so a reconstructed part would fuse to its background and every downstream measurement 7// (wall thickness above all) would be taken on geometry that does not exist. 8// license_tier: ORIGINAL 9import "nx_planesweep.nx" 10import "nx_mesh3.nx" 11 12// Fuse depth[] (ray distance per pixel, -1 = unresolved) into mesh. stride decimates the grid; maxjump is 13// the largest depth difference allowed ACROSS a quad before it is treated as a discontinuity and dropped. 14// Returns the triangle count, or -1 if the grid would exceed the mesh capacity (REFUSE, never truncate: 15// a silently clipped surface would be measured downstream as if it were the whole part). 16func df_fuse(mesh: i64, depth: *i64, cx: i64, cy: i64, cz: i64, basis: *i64, f: i64, w: i64, h: i64, stride: i64, maxjump: i64) -> i64 { 17 let gw: i64 = w / stride 18 let gh: i64 = h / stride 19 if gw < 2 { return 0 - 1 } 20 if gh < 2 { return 0 - 1 } 21 if gw * gh > M3_MAXV { return 0 - 1 } 22 if (gw - 1) * (gh - 1) * 2 > M3_MAXT { return 0 - 1 } 23 let idx: *i64 = sys_mmap(gw * gh * 8 + 64) as *i64 24 let sval: *i64 = sys_mmap(gw * gh * 8 + 64) as *i64 25 let d3: *i64 = sys_mmap(32) as *i64 26 var gy: i64 = 0 27 while gy < gh { 28 var gx: i64 = 0 29 while gx < gw { 30 let px: i64 = gx * stride 31 let py: i64 = gy * stride 32 let s: i64 = depth[py * w + px] 33 var vi: i64 = 0 - 1 34 if s >= 0 { 35 r3_ray(basis, f, px - w / 2, py - h / 2, d3) 36 let wx: i64 = cx + (d3[0] * s) / R3_Q 37 let wy: i64 = cy + (d3[1] * s) / R3_Q 38 let wz: i64 = cz + (d3[2] * s) / R3_Q 39 vi = m3_add_vert(mesh, wx, wy, wz) 40 } 41 idx[gy * gw + gx] = vi 42 sval[gy * gw + gx] = s 43 gx = gx + 1 44 } 45 gy = gy + 1 46 } 47 var ntri: i64 = 0 48 gy = 0 49 while gy < gh - 1 { 50 var gx2: i64 = 0 51 while gx2 < gw - 1 { 52 let a: i64 = idx[gy * gw + gx2] 53 let b: i64 = idx[gy * gw + gx2 + 1] 54 let c: i64 = idx[(gy + 1) * gw + gx2] 55 let d: i64 = idx[(gy + 1) * gw + gx2 + 1] 56 if a >= 0 { 57 if b >= 0 { 58 if c >= 0 { 59 if d >= 0 { 60 // depth spread across the quad -> a jump means these samples are NOT one surface 61 let sa: i64 = sval[gy * gw + gx2] 62 let sb: i64 = sval[gy * gw + gx2 + 1] 63 let sc: i64 = sval[(gy + 1) * gw + gx2] 64 let sd: i64 = sval[(gy + 1) * gw + gx2 + 1] 65 var lo: i64 = sa 66 var hi: i64 = sa 67 if sb < lo { lo = sb } 68 if sb > hi { hi = sb } 69 if sc < lo { lo = sc } 70 if sc > hi { hi = sc } 71 if sd < lo { lo = sd } 72 if sd > hi { hi = sd } 73 if hi - lo <= maxjump { 74 m3_add_tri(mesh, a, b, c) 75 m3_add_tri(mesh, b, d, c) 76 ntri = ntri + 2 77 } 78 } 79 } 80 } 81 } 82 gx2 = gx2 + 1 83 } 84 gy = gy + 1 85 } 86 return ntri 87} 88// MERGE depth maps from several stereo pairs that share the SAME reference view. 89// The occlusion finding from nx_scanvcad_gate says the fix for an unseen region is ANOTHER VIEW, not a 90// better matcher: a point hidden from camera B is usually visible from camera C. Both maps are already in 91// the reference camera's pixel grid, so merging is per-pixel. Where both resolve, agreement is required -- 92// two independent pairs landing on the same depth is corroboration; a disagreement means at least one is 93// wrong, so the pixel is DROPPED rather than averaged into a plausible-looking compromise. 94// Returns the number of pixels resolved in the merged map. 95func df_merge_depth(a: *i64, b: *i64, out: *i64, n: i64, tol: i64) -> i64 { 96 var got: i64 = 0 97 var i: i64 = 0 98 while i < n { 99 let va: i64 = a[i] 100 let vb: i64 = b[i] 101 var r: i64 = 0 - 1 102 if va > 0 { 103 if vb > 0 { 104 var d: i64 = va - vb 105 if d < 0 { d = 0 - d } 106 if d <= tol { r = (va + vb) / 2 } 107 } else { r = va } 108 } else { 109 if vb > 0 { r = vb } 110 } 111 out[i] = r 112 if r > 0 { got = got + 1 } 113 i = i + 1 114 } 115 return got 116} 117// largest |z| span of any single triangle -- a surface webbed across a depth jump shows up here as a huge 118// value, so this is the measurable signature of failed discontinuity culling 119func df_max_tri_zspan(mesh: i64) -> i64 { 120 let hd: *i64 = m3_hdr(mesh) 121 var worst: i64 = 0 122 var t: i64 = 0 123 while t < hd[1] { 124 let tr: *i64 = m3_tri(mesh, t) 125 let va: *i64 = m3_vert(mesh, tr[0]) 126 let vb: *i64 = m3_vert(mesh, tr[1]) 127 let vc: *i64 = m3_vert(mesh, tr[2]) 128 var lo: i64 = va[2] 129 var hi: i64 = va[2] 130 if vb[2] < lo { lo = vb[2] } 131 if vb[2] > hi { hi = vb[2] } 132 if vc[2] < lo { lo = vc[2] } 133 if vc[2] > hi { hi = vc[2] } 134 if hi - lo > worst { worst = hi - lo } 135 t = t + 1 136 } 137 return worst 138}