code wiki / (root) / nx_step_tess.nx

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}