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}