code wiki / (root) / nx_shellclose.nx

nx_shellclose.nx source

↩ module page · 178 lines · 6043 B

1// nx_shellclose.nx -- CLOSE a reconstructed open sheet into a WATERTIGHT SOLID by offsetting it along its 2// own normals and stitching the rim. 3// 4// WHY: a single-view reconstruction is an open sheet. nx_meshthick correctly returns -1 on it -- an open 5// surface has no opposing face, so wall thickness is UNDEFINED. Every downstream twin measurement that 6// matters for flip analysis (min wall, underbuilt flags) therefore cannot run on reconstructed geometry at 7// all. Offsetting the sheet gives a closed body those organs CAN measure. 8// 9// ⚠HONEST SCOPE, and it must travel with every number this produces: the back surface is SYNTHESISED, not 10// observed. This is a SHELL of assumed thickness, not a two-sided capture. It makes the reconstruction 11// measurable and watertight; it does NOT make the far side real. A true solid needs views from both sides. 12// license_tier: ORIGINAL 13import "nx_mesh3.nx" 14 15const SC_Q: i64 = 256 16 17// local integer sqrt (Newton). nx_render_core has rc_isqrt but this organ is mesh-only and should not drag 18// the whole rasteriser in just for one helper. 19func sc_isqrt(n: i64) -> i64 { 20 if n <= 0 { return 0 } 21 var x: i64 = n 22 if x > 65536 { x = 65536 } 23 var i: i64 = 0 24 while i < 24 { 25 let y: i64 = (x + n / x) / 2 26 if y == x { i = 24 } else { x = y } 27 i = i + 1 28 } 29 return x 30} 31// accumulate unit face normals per vertex, then normalise -> nrm[i*3..] 32func sc_vertex_normals(mesh: i64, nrm: *i64) -> i64 { 33 let hd: *i64 = m3_hdr(mesh) 34 var i: i64 = 0 35 while i < hd[0] { 36 nrm[i * 3 + 0] = 0 37 nrm[i * 3 + 1] = 0 38 nrm[i * 3 + 2] = 0 39 i = i + 1 40 } 41 var t: i64 = 0 42 while t < hd[1] { 43 let tr: *i64 = m3_tri(mesh, t) 44 let a: *i64 = m3_vert(mesh, tr[0]) 45 let b: *i64 = m3_vert(mesh, tr[1]) 46 let c: *i64 = m3_vert(mesh, tr[2]) 47 let ux: i64 = b[0] - a[0] 48 let uy: i64 = b[1] - a[1] 49 let uz: i64 = b[2] - a[2] 50 let vx: i64 = c[0] - a[0] 51 let vy: i64 = c[1] - a[1] 52 let vz: i64 = c[2] - a[2] 53 let nx: i64 = uy * vz - uz * vy 54 let ny: i64 = uz * vx - ux * vz 55 let nz: i64 = ux * vy - uy * vx 56 let n2: i64 = nx * nx + ny * ny + nz * nz 57 if n2 > 0 { 58 let nl: i64 = sc_isqrt(n2) 59 if nl > 0 { 60 var k: i64 = 0 61 while k < 3 { 62 let vi: i64 = tr[k] 63 nrm[vi * 3 + 0] = nrm[vi * 3 + 0] + (nx * SC_Q) / nl 64 nrm[vi * 3 + 1] = nrm[vi * 3 + 1] + (ny * SC_Q) / nl 65 nrm[vi * 3 + 2] = nrm[vi * 3 + 2] + (nz * SC_Q) / nl 66 k = k + 1 67 } 68 } 69 } 70 t = t + 1 71 } 72 i = 0 73 while i < hd[0] { 74 let sx: i64 = nrm[i * 3 + 0] 75 let sy: i64 = nrm[i * 3 + 1] 76 let sz: i64 = nrm[i * 3 + 2] 77 let s2: i64 = sx * sx + sy * sy + sz * sz 78 if s2 > 0 { 79 let sl: i64 = sc_isqrt(s2) 80 if sl > 0 { 81 nrm[i * 3 + 0] = (sx * SC_Q) / sl 82 nrm[i * 3 + 1] = (sy * SC_Q) / sl 83 nrm[i * 3 + 2] = (sz * SC_Q) / sl 84 } 85 } 86 i = i + 1 87 } 88 return 0 89} 90// how many triangles use the undirected edge (p,q) 91func sc_edge_uses(mesh: i64, p: i64, q: i64) -> i64 { 92 let hd: *i64 = m3_hdr(mesh) 93 var n: i64 = 0 94 var t: i64 = 0 95 while t < hd[1] { 96 let tr: *i64 = m3_tri(mesh, t) 97 var hits: i64 = 0 98 var k: i64 = 0 99 while k < 3 { 100 if tr[k] == p { hits = hits + 1 } 101 if tr[k] == q { hits = hits + 1 } 102 k = k + 1 103 } 104 if hits == 2 { n = n + 1 } 105 t = t + 1 106 } 107 return n 108} 109// count edges used by exactly ONE triangle -- 0 means the mesh is closed (watertight) 110func sc_boundary_edges(mesh: i64) -> i64 { 111 let hd: *i64 = m3_hdr(mesh) 112 var n: i64 = 0 113 var t: i64 = 0 114 while t < hd[1] { 115 let tr: *i64 = m3_tri(mesh, t) 116 var k: i64 = 0 117 while k < 3 { 118 var p: i64 = tr[k] 119 var q: i64 = tr[(k + 1) % 3] 120 if p < q { 121 if sc_edge_uses(mesh, p, q) == 1 { n = n + 1 } 122 } 123 k = k + 1 124 } 125 t = t + 1 126 } 127 return n 128} 129// Close `src` into `dst`: front sheet + back sheet offset by `thick` along -normal + a stitched rim. 130// Returns the triangle count, or -1 if it would exceed the mesh capacity (REFUSE, never truncate). 131func sc_close(src: i64, dst: i64, thick: i64, nrm: *i64) -> i64 { 132 let hs: *i64 = m3_hdr(src) 133 let nv: i64 = hs[0] 134 let nt: i64 = hs[1] 135 if nv * 2 > M3_MAXV { return 0 - 1 } 136 sc_vertex_normals(src, nrm) 137 // front verts, then back verts (same order, so back index = front index + nv) 138 var i: i64 = 0 139 while i < nv { 140 let v: *i64 = m3_vert(src, i) 141 m3_add_vert(dst, v[0], v[1], v[2]) 142 i = i + 1 143 } 144 i = 0 145 while i < nv { 146 let v: *i64 = m3_vert(src, i) 147 m3_add_vert(dst, v[0] - (nrm[i * 3 + 0] * thick) / SC_Q, v[1] - (nrm[i * 3 + 1] * thick) / SC_Q, v[2] - (nrm[i * 3 + 2] * thick) / SC_Q) 148 i = i + 1 149 } 150 var out: i64 = 0 151 var t: i64 = 0 152 while t < nt { 153 let tr: *i64 = m3_tri(src, t) 154 m3_add_tri(dst, tr[0], tr[1], tr[2]) 155 // back face with REVERSED winding so the shell is consistently oriented outward 156 m3_add_tri(dst, tr[2] + nv, tr[1] + nv, tr[0] + nv) 157 out = out + 2 158 t = t + 1 159 } 160 // rim: every boundary edge of the ORIGINAL sheet becomes a quad joining front to back 161 t = 0 162 while t < nt { 163 let tr: *i64 = m3_tri(src, t) 164 var k: i64 = 0 165 while k < 3 { 166 let p: i64 = tr[k] 167 let q: i64 = tr[(k + 1) % 3] 168 if sc_edge_uses(src, p, q) == 1 { 169 m3_add_tri(dst, p, q, q + nv) 170 m3_add_tri(dst, p, q + nv, p + nv) 171 out = out + 2 172 } 173 k = k + 1 174 } 175 t = t + 1 176 } 177 return out 178}