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}