nx_step_tess.nx source
↩ module page · 349 lines · 14284 B
1// nx_step_tess.nx -- PLANAR-FACE TESSELLATION from STEP B-rep (cadtwin P3a geometry half): turn the file's
2// actual topology into real triangles. Walk ADVANCED_FACE -> bound -> EDGE_LOOP -> ORIENTED_EDGE (.T. start /
3// .F. end vertex) -> EDGE_CURVE -> VERTEX_POINT -> CARTESIAN_POINT; project the loop onto the PLANE's 2D basis
4// (origin/normal/refdir from AXIS2_PLACEMENT_3D); EAR-CLIP the polygon (integer 2D cross products -- correct for
5// CONCAVE faces like the L-bracket, where a naive fan is wrong); emit triangles into mesh3. SELF-CHECK invariant:
6// the signed ear areas must sum EXACTLY to the polygon shoelace area (same integer coords) -- any ear-clip bug
7// shows as a mismatch count, not silent bad geometry. HONEST scope v1: single-bound planar faces; multi-bound
8// (holes/rims), curved surfaces (cylinder/B-spline) counted+skipped; curved EDGES on planar faces become chords
9// (counted). Composes nx_step_geom (STEP reals) + nx_mesh3. license_tier: ORIGINAL
10import "nx_step_geom.nx"
11import "nx_mesh3.nx"
12
13// ---- ctx table tt[]: [0]=st [1]=mesh [2]=counters c[16] [3]=refs(*i64,96) [4]=loop3d(*i64,192) [5]=loop2d(*i64,128)
14// [6]=alive(*i64,64) [7]=plane o/n/u/v (*i64,12) [8]=out2 [9]=max plane residual [10]=tuple scratch
15// counters c[]: 0=faces 1=planar 2=tessellated 3=skip-multibound 4=skip-curved-surface 5=chord-edges
16// 6=AREA-MISMATCH (must stay 0) 7=tris-emitted 8=skip-degenerate
17
18// iterate '#'-refs inside buf[off..off+len) -> out (cap), returns count
19func sgt_refs(buf: *u8, off: i64, len: i64, out: *i64, cap: i64) -> i64 {
20 var v: i64 = off
21 let e: i64 = off + len
22 var n: i64 = 0
23 while v < e {
24 if buf[v] == (35 as u8) {
25 var id: i64 = 0
26 var got: i64 = 0
27 var w: i64 = v + 1
28 var going: i64 = 1
29 while going == 1 {
30 if w < e { let d: i64 = buf[w] as i64; if sg_isdigit(d) == 1 { id = id * 10 + (d - 48); got = 1; w = w + 1 } else { going = 0 } } else { going = 0 }
31 }
32 if got == 1 { if n < cap { out[n] = id; n = n + 1 } }
33 v = w
34 } else { v = v + 1 }
35 }
36 return n
37}
38// first '#'-ref in arg k of entity idx; -1 if none
39func sgt_arg_ref(st: *i64, idx: i64, k: i64, out2: *i64) -> i64 {
40 let ok: i64 = sp_arg_span(st, idx, k, out2)
41 if ok == 0 { return 0 - 1 }
42 let one: *i64 = st[9] as *i64 // st scratch (16B) as 1-ref buffer
43 let n: i64 = sgt_refs(st[0] as *u8, out2[0], out2[1], one, 1)
44 if n == 0 { return 0 - 1 }
45 return one[0]
46}
47// arg k of entity idx contains 'T' (STEP .T. flag)? 1/0
48func sgt_flag_T(st: *i64, idx: i64, k: i64, out2: *i64) -> i64 {
49 let ok: i64 = sp_arg_span(st, idx, k, out2)
50 if ok == 0 { return 0 }
51 let buf: *u8 = st[0] as *u8
52 var v: i64 = out2[0]
53 let e: i64 = out2[0] + out2[1]
54 while v < e {
55 if buf[v] == (84 as u8) { return 1 }
56 v = v + 1
57 }
58 return 0
59}
60// parse the 3-tuple of entity ID id (CARTESIAN_POINT or DIRECTION) -> out3 fx256; 1 ok
61func sgt_tuple_of(st: *i64, id: i64, out3: *i64) -> i64 {
62 let idx: i64 = sp_find(st, id)
63 if idx < 0 { return 0 }
64 return sg_point_tuple(st, idx, out3)
65}
66
67// if face (entity idx) sits on a PLANE: fill tt[7] = o[0..2] n[3..5] u[6..8] v[9..11] (fx256), return 1.
68func sgt_face_plane(tt: *i64, idx: i64) -> i64 {
69 let st: *i64 = tt[0] as *i64
70 let out2: *i64 = tt[8] as *i64
71 let sid: i64 = sgt_arg_ref(st, idx, 2, out2) // surface ref
72 if sid < 0 { return 0 }
73 let sidx: i64 = sp_find(st, sid)
74 if sidx < 0 { return 0 }
75 if sp_name_is(st, sidx, "PLANE" as *u8) == 0 { return 0 }
76 let axid: i64 = sgt_arg_ref(st, sidx, 1, out2) // AXIS2_PLACEMENT_3D
77 if axid < 0 { return 0 }
78 let axidx: i64 = sp_find(st, axid)
79 if axidx < 0 { return 0 }
80 let pl: *i64 = tt[7] as *i64
81 let oid: i64 = sgt_arg_ref(st, axidx, 1, out2)
82 let nid: i64 = sgt_arg_ref(st, axidx, 2, out2)
83 let rid: i64 = sgt_arg_ref(st, axidx, 3, out2)
84 if oid < 0 { return 0 }
85 if nid < 0 { return 0 }
86 if rid < 0 { return 0 }
87 if sgt_tuple_of(st, oid, pl) == 0 { return 0 } // o -> pl[0..2]
88 if sgt_tuple_of(st, nid, ((pl as i64) + 24) as *i64) == 0 { return 0 } // n -> pl[3..5]
89 if sgt_tuple_of(st, rid, ((pl as i64) + 48) as *i64) == 0 { return 0 } // u -> pl[6..8]
90 // v = (n x u) / 256 (fx256 unit-ish)
91 pl[9] = (pl[4] * pl[8] - pl[5] * pl[7]) / 256
92 pl[10] = (pl[5] * pl[6] - pl[3] * pl[8]) / 256
93 pl[11] = (pl[3] * pl[7] - pl[4] * pl[6]) / 256
94 return 1
95}
96
97// walk EDGE_LOOP (entity id loopid): per ORIENTED_EDGE take start (.T.) / end (.F.) vertex -> tt[4] 3D fx256.
98// counts chord edges (curve not LINE) into c[5]. returns vertex count (0 on any broken ref).
99func sgt_loop_verts(tt: *i64, loopid: i64) -> i64 {
100 let st: *i64 = tt[0] as *i64
101 let c: *i64 = tt[2] as *i64
102 let refs: *i64 = tt[3] as *i64
103 let p3: *i64 = tt[4] as *i64
104 let out2: *i64 = tt[8] as *i64
105 let lidx: i64 = sp_find(st, loopid)
106 if lidx < 0 { return 0 }
107 let ok: i64 = sp_arg_span(st, lidx, 1, out2)
108 if ok == 0 { return 0 }
109 let ne: i64 = sgt_refs(st[0] as *u8, out2[0], out2[1], refs, 64)
110 if ne < 3 { return 0 }
111 if ne > 64 { return 0 }
112 var i: i64 = 0
113 while i < ne {
114 let oeidx: i64 = sp_find(st, refs[i])
115 if oeidx < 0 { return 0 }
116 let ecid: i64 = sgt_arg_ref(st, oeidx, 3, out2) // EDGE_CURVE
117 if ecid < 0 { return 0 }
118 let fwd: i64 = sgt_flag_T(st, oeidx, 4, out2) // orientation
119 let ecidx: i64 = sp_find(st, ecid)
120 if ecidx < 0 { return 0 }
121 var vk: i64 = 2
122 if fwd == 1 { vk = 1 } // .T. -> start vertex (arg1), .F. -> end (arg2)
123 let vpid: i64 = sgt_arg_ref(st, ecidx, vk, out2)
124 if vpid < 0 { return 0 }
125 // chord honesty: curve geometry (arg3) not a LINE -> this edge is approximated by its chord
126 let cvid: i64 = sgt_arg_ref(st, ecidx, 3, out2)
127 if cvid >= 0 {
128 let cvidx: i64 = sp_find(st, cvid)
129 if cvidx >= 0 {
130 var isline: i64 = sp_name_is(st, cvidx, "LINE" as *u8)
131 if isline == 0 { if sp_name_prefix(st, cvidx, "SURFACE_CURVE" as *u8) == 1 {
132 // SURFACE_CURVE wraps the real curve in ITS arg1
133 let inner: i64 = sgt_arg_ref(st, cvidx, 1, out2)
134 if inner >= 0 { let inidx: i64 = sp_find(st, inner); if inidx >= 0 { isline = sp_name_is(st, inidx, "LINE" as *u8) } }
135 } }
136 if isline == 0 { c[5] = c[5] + 1 }
137 }
138 }
139 let vpidx: i64 = sp_find(st, vpid)
140 if vpidx < 0 { return 0 }
141 let cpid: i64 = sgt_arg_ref(st, vpidx, 1, out2) // CARTESIAN_POINT
142 if cpid < 0 { return 0 }
143 if sgt_tuple_of(st, cpid, ((p3 as i64) + i * 24) as *i64) == 0 { return 0 }
144 i = i + 1
145 }
146 return ne
147}
148
149// project tt[4] (n verts, 3D) onto the plane basis -> tt[5] 2D fx256; track max |n.(p-o)| residual into tt[9]
150func sgt_project(tt: *i64, n: i64) -> i64 {
151 let p3: *i64 = tt[4] as *i64
152 let p2: *i64 = tt[5] as *i64
153 let pl: *i64 = tt[7] as *i64
154 var i: i64 = 0
155 while i < n {
156 let dx: i64 = p3[i * 3] - pl[0]
157 let dy: i64 = p3[i * 3 + 1] - pl[1]
158 let dz: i64 = p3[i * 3 + 2] - pl[2]
159 p2[i * 2] = (dx * pl[6] + dy * pl[7] + dz * pl[8]) / 256
160 p2[i * 2 + 1] = (dx * pl[9] + dy * pl[10] + dz * pl[11]) / 256
161 var res: i64 = (dx * pl[3] + dy * pl[4] + dz * pl[5]) / 256
162 if res < 0 { res = 0 - res }
163 if res > tt[9] { tt[9] = res }
164 i = i + 1
165 }
166 return 0
167}
168// signed shoelace area*2 of tt[5][0..n)
169func sgt_shoelace(p2: *i64, n: i64) -> i64 {
170 var s: i64 = 0
171 var i: i64 = 0
172 while i < n {
173 var j: i64 = i + 1
174 if j == n { j = 0 }
175 s = s + (p2[i * 2] * p2[j * 2 + 1] - p2[j * 2] * p2[i * 2 + 1])
176 i = i + 1
177 }
178 return s
179}
180// cross of (b-a)x(c-b) in 2D over vertex indices a,b,c of p2
181func sgt_cross3(p2: *i64, a: i64, b: i64, c: i64) -> i64 {
182 let abx: i64 = p2[b * 2] - p2[a * 2]
183 let aby: i64 = p2[b * 2 + 1] - p2[a * 2 + 1]
184 let bcx: i64 = p2[c * 2] - p2[b * 2]
185 let bcy: i64 = p2[c * 2 + 1] - p2[b * 2 + 1]
186 return abx * bcy - aby * bcx
187}
188// is alive vertex p strictly inside CCW triangle (a,b,c)?
189func sgt_inside(p2: *i64, tri3: *i64, p: i64) -> i64 {
190 let a: i64 = tri3[0]
191 let b: i64 = tri3[1]
192 let c: i64 = tri3[2]
193 if sgt_cross3(p2, a, b, p) <= 0 { return 0 }
194 if sgt_cross3(p2, b, c, p) <= 0 { return 0 }
195 if sgt_cross3(p2, c, a, p) <= 0 { return 0 }
196 return 1
197}
198
199// EAR-CLIP the CCW polygon (alive indices over tt[5]); emit tris into mesh (3D verts pre-added at vbase).
200// Returns the SIGNED sum of ear areas*2 (must equal the polygon shoelace exactly).
201func sgt_earclip(tt: *i64, n: i64, vbase: i64) -> i64 {
202 let p2: *i64 = tt[5] as *i64
203 let alive: *i64 = tt[6] as *i64
204 let mesh: i64 = tt[1]
205 let c: *i64 = tt[2] as *i64
206 let tri3: *i64 = ((tt[7] as i64) + 96) as *i64 // 3 spare slots after the 12-slot plane block
207 var i: i64 = 0
208 while i < n { alive[i] = i; i = i + 1 }
209 var m: i64 = n
210 var areasum: i64 = 0
211 var guard: i64 = 0
212 let maxguard: i64 = n * n * 4 + 64
213 while m > 3 {
214 if guard >= maxguard { return areasum + 1 } // stuck: poison the invariant -> counted as mismatch
215 guard = guard + 1
216 // scan for an ear
217 var found: i64 = 0
218 var k: i64 = 0
219 while k < m {
220 if found == 0 {
221 var pk: i64 = k - 1
222 if k == 0 { pk = m - 1 }
223 var nk: i64 = k + 1
224 if nk == m { nk = 0 }
225 let a: i64 = alive[pk]
226 let b: i64 = alive[k]
227 let cc: i64 = alive[nk]
228 let cr: i64 = sgt_cross3(p2, a, b, cc)
229 if cr == 0 {
230 // collinear: drop b, no triangle
231 var w: i64 = k
232 while w < m - 1 { alive[w] = alive[w + 1]; w = w + 1 }
233 m = m - 1
234 found = 1
235 } else { if cr > 0 {
236 // convex: ear iff no other alive vertex strictly inside
237 tri3[0] = a; tri3[1] = b; tri3[2] = cc
238 var blocked: i64 = 0
239 var q: i64 = 0
240 while q < m {
241 if q != pk { if q != k { if q != nk {
242 if sgt_inside(p2, tri3, alive[q]) == 1 { blocked = 1 }
243 } } }
244 q = q + 1
245 }
246 if blocked == 0 {
247 m3_add_tri(mesh, vbase + a, vbase + b, vbase + cc)
248 c[7] = c[7] + 1
249 areasum = areasum + cr
250 var w2: i64 = k
251 while w2 < m - 1 { alive[w2] = alive[w2 + 1]; w2 = w2 + 1 }
252 m = m - 1
253 found = 1
254 }
255 } }
256 }
257 k = k + 1
258 }
259 if found == 0 { return areasum + 1 } // no ear on a full sweep: poison -> mismatch
260 }
261 // final triangle
262 let fa: i64 = alive[0]
263 let fb: i64 = alive[1]
264 let fc: i64 = alive[2]
265 let fcr: i64 = sgt_cross3(p2, fa, fb, fc)
266 if fcr != 0 {
267 m3_add_tri(mesh, vbase + fa, vbase + fb, vbase + fc)
268 c[7] = c[7] + 1
269 areasum = areasum + fcr
270 }
271 return areasum
272}
273
274// tessellate one ADVANCED_FACE (entity idx). Updates counters.
275func sgt_face(tt: *i64, idx: i64) -> i64 {
276 let st: *i64 = tt[0] as *i64
277 let c: *i64 = tt[2] as *i64
278 let out2: *i64 = tt[8] as *i64
279 c[0] = c[0] + 1
280 if sgt_face_plane(tt, idx) == 0 { c[4] = c[4] + 1; return 0 }
281 c[1] = c[1] + 1
282 // bounds list (arg 1): v1 handles exactly one bound (no holes)
283 let refs: *i64 = tt[3] as *i64
284 let ok: i64 = sp_arg_span(st, idx, 1, out2)
285 if ok == 0 { c[8] = c[8] + 1; return 0 }
286 let nb: i64 = sgt_refs(st[0] as *u8, out2[0], out2[1], refs, 8)
287 if nb != 1 { c[3] = c[3] + 1; return 0 }
288 let bidx: i64 = sp_find(st, refs[0])
289 if bidx < 0 { c[8] = c[8] + 1; return 0 }
290 let loopid: i64 = sgt_arg_ref(st, bidx, 1, out2)
291 if loopid < 0 { c[8] = c[8] + 1; return 0 }
292 let nv: i64 = sgt_loop_verts(tt, loopid)
293 if nv < 3 { c[8] = c[8] + 1; return 0 }
294 sgt_project(tt, nv)
295 let p2: *i64 = tt[5] as *i64
296 var area: i64 = sgt_shoelace(p2, nv)
297 if area == 0 { c[8] = c[8] + 1; return 0 }
298 if area < 0 {
299 // reverse both 2D and 3D so the polygon is CCW
300 let p3: *i64 = tt[4] as *i64
301 var lo: i64 = 0
302 var hi: i64 = nv - 1
303 while lo < hi {
304 var t0: i64 = p2[lo * 2]
305 p2[lo * 2] = p2[hi * 2]
306 p2[hi * 2] = t0
307 t0 = p2[lo * 2 + 1]
308 p2[lo * 2 + 1] = p2[hi * 2 + 1]
309 p2[hi * 2 + 1] = t0
310 var k3: i64 = 0
311 while k3 < 3 {
312 t0 = p3[lo * 3 + k3]
313 p3[lo * 3 + k3] = p3[hi * 3 + k3]
314 p3[hi * 3 + k3] = t0
315 k3 = k3 + 1
316 }
317 lo = lo + 1
318 hi = hi - 1
319 }
320 area = 0 - area
321 }
322 // add the loop's 3D verts to the mesh
323 let mesh: i64 = tt[1]
324 let h: *i64 = m3_hdr(mesh)
325 let vbase: i64 = h[0]
326 let p3b: *i64 = tt[4] as *i64
327 var i: i64 = 0
328 while i < nv {
329 m3_add_vert(mesh, p3b[i * 3], p3b[i * 3 + 1], p3b[i * 3 + 2])
330 i = i + 1
331 }
332 let asum: i64 = sgt_earclip(tt, nv, vbase)
333 if asum != area { c[6] = c[6] + 1; return 0 }
334 c[2] = c[2] + 1
335 return 1
336}
337
338// tessellate every ADVANCED_FACE in the scanned STEP (st) into mesh; c = counters c[16]; returns tessellated count
339func sgt_tessellate(tt: *i64) -> i64 {
340 let st: *i64 = tt[0] as *i64
341 let cnt: i64 = st[7]
342 var idx: i64 = 0
343 while idx < cnt {
344 if sp_name_is(st, idx, "ADVANCED_FACE" as *u8) == 1 { sgt_face(tt, idx) }
345 idx = idx + 1
346 }
347 let c: *i64 = tt[2] as *i64
348 return c[2]
349}