nx_meshthick.nx source
↩ module page · 155 lines · 6718 B
1// nx_meshthick.nx -- R5 of the CAD car-twin ladder (cadtwin P6a): MESH WALL-THICKNESS by ray-casting (the
2// GOM/ZEISS-INSPECT "ray method"), the GEOMETRIC half of "identify underbuilt parts when flipping cars"
3// (R6 nx_underbuilt_board is the DATA half). For each triangle face: from its centroid, cast a ray along the
4// INWARD normal and find the nearest OTHER face -> that hit distance is the local wall thickness. The MIN over
5// all faces = the thinnest wall = the underbuilt candidate. A data-driven THRESHOLD (passed in, never
6// hardcoded) classifies a part OK vs UNDERBUILT. All fixed-point fx256 (256 = 1.0), deterministic. Composes
7// nx_mesh3 (real tri-mesh + STL interchange). Ray-triangle = Moller-Trumbore, KAT-gated. license_tier: ORIGINAL
8import "nx_mesh3.nx"
9import "nx_vecmath.nx"
10
11const MTK_ONE: i64 = 256 // 1.0 in fx256
12const MTK_EPS: i64 = 64 // 0.25 real: skip self/coplanar hits (t must exceed this)
13const MTK_BIG: i64 = 4611686018427387904 // "no hit" sentinel thickness
14
15func mtk_fxmul(a: i64, b: i64) -> i64 { return (a * b) / MTK_ONE }
16func mtk_fxdiv(a: i64, b: i64) -> i64 { if b == 0 { return 0 } return (a * MTK_ONE) / b }
17func mtk_abs(a: i64) -> i64 { if a < 0 { return 0 - a } return a }
18
19// integer sqrt (Newton) on a plain (non-fx) magnitude
20func mtk_isqrt(n: i64) -> i64 { return vm_isqrt(n) }
21
22// Moller-Trumbore ray/triangle. orig/dir + tri verts all fx256; dir is a UNIT fx256 vector (|dir|=256).
23// Returns hit distance t in fx256 (>0), or -1 on miss/parallel/behind. u,v barycentric checked in [0,256].
24func mtk_ray_tri(ox: i64, oy: i64, oz: i64, dx: i64, dy: i64, dz: i64,
25 ax: i64, ay: i64, az: i64, bx: i64, by: i64, bz: i64, cx: i64, cy: i64, cz: i64) -> i64 {
26 let e1x: i64 = bx - ax
27 let e1y: i64 = by - ay
28 let e1z: i64 = bz - az
29 let e2x: i64 = cx - ax
30 let e2y: i64 = cy - ay
31 let e2z: i64 = cz - az
32 // h = dir x e2
33 let hx: i64 = mtk_fxmul(dy, e2z) - mtk_fxmul(dz, e2y)
34 let hy: i64 = mtk_fxmul(dz, e2x) - mtk_fxmul(dx, e2z)
35 let hz: i64 = mtk_fxmul(dx, e2y) - mtk_fxmul(dy, e2x)
36 // a = e1 . h (determinant)
37 let det: i64 = mtk_fxmul(e1x, hx) + mtk_fxmul(e1y, hy) + mtk_fxmul(e1z, hz)
38 if mtk_abs(det) < 1 { return 0 - 1 } // ray parallel to triangle
39 // s = orig - a
40 let sx: i64 = ox - ax
41 let sy: i64 = oy - ay
42 let sz: i64 = oz - az
43 // u = (s . h) / det -> barycentric in fx256 [0,256]
44 let su: i64 = mtk_fxmul(sx, hx) + mtk_fxmul(sy, hy) + mtk_fxmul(sz, hz)
45 let u: i64 = mtk_fxdiv(su, det)
46 if u < 0 { return 0 - 1 }
47 if u > MTK_ONE { return 0 - 1 }
48 // q = s x e1
49 let qx: i64 = mtk_fxmul(sy, e1z) - mtk_fxmul(sz, e1y)
50 let qy: i64 = mtk_fxmul(sz, e1x) - mtk_fxmul(sx, e1z)
51 let qz: i64 = mtk_fxmul(sx, e1y) - mtk_fxmul(sy, e1x)
52 // v = (dir . q) / det
53 let sv: i64 = mtk_fxmul(dx, qx) + mtk_fxmul(dy, qy) + mtk_fxmul(dz, qz)
54 let v: i64 = mtk_fxdiv(sv, det)
55 if v < 0 { return 0 - 1 }
56 if u + v > MTK_ONE { return 0 - 1 }
57 // t = (e2 . q) / det
58 let st: i64 = mtk_fxmul(e2x, qx) + mtk_fxmul(e2y, qy) + mtk_fxmul(e2z, qz)
59 let t: i64 = mtk_fxdiv(st, det)
60 if t < MTK_EPS { return 0 - 1 }
61 return t
62}
63
64// nearest hit of ray (orig,dir) against every mesh triangle EXCEPT skip_tri; -1 if none. dir is UNIT fx256.
65func mtk_cast(base: i64, ox: i64, oy: i64, oz: i64, dx: i64, dy: i64, dz: i64, skip_tri: i64) -> i64 {
66 let h: *i64 = m3_hdr(base)
67 var best: i64 = 0 - 1
68 var ti: i64 = 0
69 while ti < h[1] {
70 if ti != skip_tri {
71 let t: *i64 = m3_tri(base, ti)
72 let va: *i64 = m3_vert(base, t[0])
73 let vb: *i64 = m3_vert(base, t[1])
74 let vc: *i64 = m3_vert(base, t[2])
75 let hit: i64 = mtk_ray_tri(ox, oy, oz, dx, dy, dz,
76 va[0], va[1], va[2], vb[0], vb[1], vb[2], vc[0], vc[1], vc[2])
77 if hit > 0 {
78 if best < 0 { best = hit } else { if hit < best { best = hit } }
79 }
80 }
81 ti = ti + 1
82 }
83 return best
84}
85
86// unit fx256 outward normal of triangle ti into out3 (from winding v0->v1->v2, right-handed)
87func mtk_face_normal(base: i64, ti: i64, out3: *i64) -> i64 {
88 let t: *i64 = m3_tri(base, ti)
89 let va: *i64 = m3_vert(base, t[0])
90 let vb: *i64 = m3_vert(base, t[1])
91 let vc: *i64 = m3_vert(base, t[2])
92 let e1x: i64 = vb[0] - va[0]
93 let e1y: i64 = vb[1] - va[1]
94 let e1z: i64 = vb[2] - va[2]
95 let e2x: i64 = vc[0] - va[0]
96 let e2y: i64 = vc[1] - va[1]
97 let e2z: i64 = vc[2] - va[2]
98 let nx: i64 = e1y * e2z - e1z * e2y
99 let ny: i64 = e1z * e2x - e1x * e2z
100 let nz: i64 = e1x * e2y - e1y * e2x
101 let mag: i64 = mtk_isqrt(nx * nx + ny * ny + nz * nz)
102 if mag == 0 { out3[0] = 0; out3[1] = 0; out3[2] = 0; return 0 - 1 }
103 out3[0] = (nx * MTK_ONE) / mag
104 out3[1] = (ny * MTK_ONE) / mag
105 out3[2] = (nz * MTK_ONE) / mag
106 return 0
107}
108
109// centroid (fx256) of triangle ti into out3
110func mtk_centroid(base: i64, ti: i64, out3: *i64) -> i64 {
111 let t: *i64 = m3_tri(base, ti)
112 let va: *i64 = m3_vert(base, t[0])
113 let vb: *i64 = m3_vert(base, t[1])
114 let vc: *i64 = m3_vert(base, t[2])
115 out3[0] = (va[0] + vb[0] + vc[0]) / 3
116 out3[1] = (va[1] + vb[1] + vc[1]) / 3
117 out3[2] = (va[2] + vb[2] + vc[2]) / 3
118 return 0
119}
120
121// local wall thickness at face ti: cast INWARD (-normal); if no hit, try +normal (winding-robust). fx256, -1 none.
122func mtk_face_thickness(base: i64, ti: i64) -> i64 {
123 let n3: *i64 = sys_mmap(32) as *i64
124 let c3: *i64 = sys_mmap(32) as *i64
125 if mtk_face_normal(base, ti, n3) < 0 { return 0 - 1 }
126 mtk_centroid(base, ti, c3)
127 // inward = -normal
128 let t1: i64 = mtk_cast(base, c3[0], c3[1], c3[2], 0 - n3[0], 0 - n3[1], 0 - n3[2], ti)
129 if t1 > 0 { return t1 }
130 let t2: i64 = mtk_cast(base, c3[0], c3[1], c3[2], n3[0], n3[1], n3[2], ti)
131 return t2
132}
133
134// MIN wall thickness over all faces (fx256); argmin face index into out_face[0]; -1 if unmeasurable
135func mtk_min_thickness(base: i64, out_face: *i64) -> i64 {
136 let h: *i64 = m3_hdr(base)
137 var best: i64 = MTK_BIG
138 var bestf: i64 = 0 - 1
139 var ti: i64 = 0
140 while ti < h[1] {
141 let th: i64 = mtk_face_thickness(base, ti)
142 if th > 0 { if th < best { best = th; bestf = ti } }
143 ti = ti + 1
144 }
145 out_face[0] = bestf
146 if bestf < 0 { return 0 - 1 }
147 return best
148}
149
150// data-driven underbuilt classification: 1 = UNDERBUILT (thinner than threshold), 0 = OK. threshold fx256.
151func mtk_is_underbuilt(min_thick: i64, threshold_fx: i64) -> i64 {
152 if min_thick < 0 { return 0 - 1 } // unmeasurable
153 if min_thick < threshold_fx { return 1 }
154 return 0
155}