code wiki / (root) / nx_step_tess2.nx

nx_step_tess2.nx source

↩ module page · 787 lines · 31789 B

1// nx_step_tess2.nx -- COMPLETE planar-face tessellation: B-SPLINE CURVE edges + HOLE BRIDGING (cadtwin P3a, 2// the 13 missing faces). as1's loop edges are not just LINEs: the nut outline is a DEGREE-5 non-uniform 3// B_SPLINE_CURVE_WITH_KNOTS, hole rims are 2-edge spline loops (chord-walk saw <3 verts -> the old 'degenerate' 4// skips), and hole faces are multi-bound. This adds: (1) sgb_* = fixed-point COX-DE BOOR evaluator (any degree 5// <=8, clamped non-uniform knots Q20, ctrl fx256; endpoint-clamped so eval(t0)==P0) -- curve edges are SAMPLED, 6// not chorded; every edge's first sample is VERIFIED against the edge's own start vertex (invariant; mismatch -> 7// counted + chord fallback, never silent); (2) sgt2_* = loop walk v2 (curve-sampled) + KEYHOLE BRIDGING (holes 8// spliced into the outer loop via closest-pair bridges; bridge edges traverse both ways -> shoelace still 9// equals outer minus holes EXACTLY, so the v1 ear==shoelace invariant keeps guarding). Additive: v1 paths and 10// gates untouched. license_tier: ORIGINAL 11import "nx_step_nurbs.nx" 12import "nx_recon3d.nx" 13 14const SGB_QK: i64 = 1048576 // knot/param scale (Q20) 15const SGB_QB: i64 = 16384 // basis scale (Q14) 16 17// ---- parse B_SPLINE_CURVE_WITH_KNOTS entity idx: bc[0]=degree bc[1]=nctrl bc[2]=nknots(expanded); 18// ctrl -> cpts (fx256 xyz stride3, cap 40), expanded knots -> kn (Q20 knots in [0..59]; bytes 512+ = internal 19// scratch: refs@512, ints@840 -- ALLOCATE kn AS 1024 BYTES). 1 ok. 20func sgb_parse(st: *i64, idx: i64, bc: *i64, cpts: *i64, kn: *i64) -> i64 { 21 let buf: *u8 = st[0] as *u8 22 let out2: *i64 = st[9] as *i64 23 // degree = arg1 24 if sp_arg_span(st, idx, 1, out2) == 0 { return 0 } 25 var v: i64 = out2[0] 26 let e1: i64 = out2[0] + out2[1] 27 v = sg_skip_to_num(buf, v, e1) 28 if v >= e1 { return 0 } 29 v = sgn_int(buf, v, e1, bc) // bc[0] = degree 30 if bc[0] < 1 { return 0 } 31 if bc[0] > 8 { return 0 } 32 // ctrl points = arg2 refs 33 if sp_arg_span(st, idx, 2, out2) == 0 { return 0 } 34 let refs: *i64 = ((kn as i64) + 512) as *i64 // kn-buffer scratch region (kn alloc = 1024B) 35 let nc: i64 = sgt_refs(buf, out2[0], out2[1], refs, 40) 36 if nc < 2 { return 0 } 37 bc[1] = nc 38 var i: i64 = 0 39 while i < nc { 40 if sgt_tuple_of(st, refs[i], ((cpts as i64) + i * 24) as *i64) == 0 { return 0 } 41 i = i + 1 42 } 43 // multiplicities = arg6 (ints), knots = arg7 (reals) 44 if sp_arg_span(st, idx, 6, out2) == 0 { return 0 } 45 let mo: i64 = out2[0] 46 let me: i64 = out2[0] + out2[1] 47 if sp_arg_span(st, idx, 7, out2) == 0 { return 0 } 48 var kv: i64 = out2[0] 49 let ke: i64 = out2[0] + out2[1] 50 var nk: i64 = 0 51 var mv: i64 = mo 52 var going: i64 = 1 53 while going == 1 { 54 mv = sg_skip_to_num(buf, mv, me) 55 if mv >= me { going = 0 } else { 56 let m2: *i64 = ((kn as i64) + 840) as *i64 // scratch past the refs region 57 mv = sgn_int(buf, mv, me, m2) 58 kv = sg_skip_to_num(buf, kv, ke) 59 if kv >= ke { return 0 } 60 let kvv: *i64 = ((kn as i64) + 848) as *i64 61 kv = sgn_realq(buf, kv, ke, SGB_QK, kvv) 62 var r: i64 = 0 63 while r < m2[0] { 64 if nk < 60 { kn[nk] = kvv[0]; nk = nk + 1 } 65 r = r + 1 66 } 67 } 68 } 69 bc[2] = nk 70 // sanity: clamped B-spline needs nk == nctrl + degree + 1 71 if nk != bc[1] + bc[0] + 1 { return 0 } 72 return 1 73} 74 75// ---- Cox-de Boor basis at t (Q20) for span i, degree p; N out (Q14, p+1 values); scr = 160B scratch 76// (NO mmap here -- called per-sample in hot loops). NURBS-book A2.2, integer. 77func sgb_basis(kn: *i64, i: i64, t: i64, p: i64, N: *i64, scr: *i64) -> i64 { 78 let left: *i64 = scr 79 let right: *i64 = ((scr as i64) + 80) as *i64 80 N[0] = SGB_QB 81 var j: i64 = 1 82 while j <= p { 83 left[j] = t - kn[i + 1 - j] 84 right[j] = kn[i + j] - t 85 var saved: i64 = 0 86 var r: i64 = 0 87 while r < j { 88 let den: i64 = right[r + 1] + left[j - r] 89 var temp: i64 = 0 90 if den != 0 { temp = (N[r] * SGB_QK) / den } // Q14*Q20/Q20 = Q14 per unit 91 N[r] = saved + (right[r + 1] * temp) / SGB_QK 92 saved = (left[j - r] * temp) / SGB_QK 93 r = r + 1 94 } 95 N[j] = saved 96 j = j + 1 97 } 98 return 0 99} 100// find knot span for t (Q20): i in [p, nk-p-2] with kn[i] <= t < kn[i+1] 101func sgb_span(kn: *i64, nk: i64, p: i64, t: i64) -> i64 { 102 let hi: i64 = nk - p - 2 103 if t >= kn[hi + 1] { return hi } 104 var i: i64 = p 105 while i < hi { 106 if t >= kn[i] { if t < kn[i + 1] { return i } } 107 i = i + 1 108 } 109 return hi 110} 111// evaluate curve at t (Q20) -> out3 fx256; scr = 256B scratch (N@0, basis-scratch@88; NO mmap in hot path) 112func sgb_eval2(bc: *i64, cpts: *i64, kn: *i64, t: i64, out3: *i64, scr: *i64) -> i64 { 113 let p: i64 = bc[0] 114 let N: *i64 = scr 115 let sp2: i64 = sgb_span(kn, bc[2], p, t) 116 sgb_basis(kn, sp2, t, p, N, ((scr as i64) + 88) as *i64) 117 var x: i64 = 0 118 var y: i64 = 0 119 var z: i64 = 0 120 var sn: i64 = 0 121 var j: i64 = 0 122 while j <= p { 123 let ci: i64 = sp2 - p + j 124 x = x + N[j] * cpts[ci * 3] 125 y = y + N[j] * cpts[ci * 3 + 1] 126 z = z + N[j] * cpts[ci * 3 + 2] 127 sn = sn + N[j] 128 j = j + 1 129 } 130 if sn <= 0 { out3[0] = 0; out3[1] = 0; out3[2] = 0; return 0 } 131 out3[0] = sgn_rdiv(x, sn) // normalize by actual basis sum (rounding-robust) 132 out3[1] = sgn_rdiv(y, sn) 133 out3[2] = sgn_rdiv(z, sn) 134 return 1 135} 136 137// ---- CIRCLE support: angle-free arc sampling. STEP circle P(t)=O+R(cos t*u + sin t*w), +t rotation about the 138// axis n == 90-deg steps are EXACT integer cross products (rel' = n x rel / 256); finer points by CHORD 139// BISECTION renormalized to radius R (r3_normalize + scale) -- every sample ON the circle within ~1 fx. No 140// atan2, no trig tables. 141// parse CIRCLE entity cidx -> cc[0..2]=O fx256, cc[3..5]=n unit fx256, cc[6]=R fx256. 1 ok. 142func sgc_parse(st: *i64, cidx: i64, cc: *i64) -> i64 { 143 let out2: *i64 = st[9] as *i64 144 let axid: i64 = sgt_arg_ref(st, cidx, 1, out2) 145 if axid < 0 { return 0 } 146 let axidx: i64 = sp_find(st, axid) 147 if axidx < 0 { return 0 } 148 let oid: i64 = sgt_arg_ref(st, axidx, 1, out2) 149 let nid: i64 = sgt_arg_ref(st, axidx, 2, out2) 150 if oid < 0 { return 0 } 151 if nid < 0 { return 0 } 152 if sgt_tuple_of(st, oid, cc) == 0 { return 0 } 153 if sgt_tuple_of(st, nid, ((cc as i64) + 24) as *i64) == 0 { return 0 } 154 // radius = arg2 real (fx256) 155 if sp_arg_span(st, cidx, 2, out2) == 0 { return 0 } 156 let buf: *u8 = st[0] as *u8 157 var v: i64 = sg_skip_to_num(buf, out2[0], out2[0] + out2[1]) 158 if v >= out2[0] + out2[1] { return 0 } 159 let rr: *i64 = ((cc as i64) + 56) as *i64 160 sg_parse_real(buf, v, out2[0] + out2[1], rr) 161 cc[6] = rr[0] 162 if cc[6] <= 0 { return 0 } 163 return 1 164} 165// rel' = (n x rel)/256 (exact +90deg in the circle plane) 166func sgc_rot90(cc: *i64, rel: *i64, out: *i64) -> i64 { 167 let nx2: i64 = cc[3] 168 let ny: i64 = cc[4] 169 let nz: i64 = cc[5] 170 out[0] = (ny * rel[2] - nz * rel[1]) / 256 171 out[1] = (nz * rel[0] - nx2 * rel[2]) / 256 172 out[2] = (nx2 * rel[1] - ny * rel[0]) / 256 173 return 0 174} 175// arc-chord midpoint renormalized to R: out = normalize(a+b)*R 176func sgc_bis(cc: *i64, a: *i64, b: *i64, out: *i64) -> i64 { 177 let un: *i64 = ((cc as i64) + 64) as *i64 // scratch inside cc (alloc cc >= 96B) 178 r3_normalize(a[0] + b[0], a[1] + b[1], a[2] + b[2], un) 179 out[0] = (un[0] * cc[6]) / 16384 180 out[1] = (un[1] * cc[6]) / 16384 181 out[2] = (un[2] * cc[6]) / 16384 182 return 0 183} 184// n . (a x b) sign helper (orientation of b vs a about the axis) 185func sgc_tri(cc: *i64, a: *i64, b: *i64) -> i64 { 186 let cx: i64 = (a[1] * b[2] - a[2] * b[1]) / 256 187 let cy: i64 = (a[2] * b[0] - a[0] * b[2]) / 256 188 let cz: i64 = (a[0] * b[1] - a[1] * b[0]) / 256 189 return (cc[3] * cx + cc[4] * cy + cc[5] * cz) / 256 190} 191// emit interior samples of the +dir arc from rel ra to rb (both RELATIVE to O; A itself already emitted by the 192// caller; B emitted by the next edge). st8 = 8x3 scratch (cur/nq/mid/q1/q3). Returns samples written. 193func sgc_arc(cc: *i64, ra: *i64, rb: *i64, vout: *i64, cap: i64, st8: *i64) -> i64 { 194 let cur: *i64 = st8 195 let nq: *i64 = ((st8 as i64) + 24) as *i64 196 let mid: *i64 = ((st8 as i64) + 48) as *i64 197 let q1: *i64 = ((st8 as i64) + 72) as *i64 198 let q3: *i64 = ((st8 as i64) + 96) as *i64 199 cur[0] = ra[0]; cur[1] = ra[1]; cur[2] = ra[2] 200 // full circle? (A==B within tol) 201 var full: i64 = 0 202 var dd: i64 = 0 203 var k: i64 = 0 204 while k < 3 { var d: i64 = ra[k] - rb[k]; if d < 0 { d = 0 - d } dd = dd + d; k = k + 1 } 205 if dd <= 4 { full = 1 } 206 var nv: i64 = 0 207 var q: i64 = 0 208 var going: i64 = 1 209 while going == 1 { 210 if q >= 4 { going = 0 } else { 211 sgc_rot90(cc, cur, nq) 212 // B inside (cur -> nq]? (skip when full circle: walk all 4 quarters) 213 var inhere: i64 = 0 214 if full == 0 { 215 if sgc_tri(cc, cur, rb) >= 0 { if sgc_tri(cc, rb, nq) >= 0 { inhere = 1 } } 216 if q == 0 { if dd <= 4 { inhere = 1 } } 217 } 218 if inhere == 1 { 219 // partial sector cur->rb: 3 bisection points 220 sgc_bis(cc, cur, rb, mid) 221 sgc_bis(cc, cur, mid, q1) 222 sgc_bis(cc, mid, rb, q3) 223 if nv + 3 <= cap { 224 var k2: i64 = 0 225 while k2 < 3 { vout[nv * 3 + k2] = cc[k2] + q1[k2]; k2 = k2 + 1 } 226 nv = nv + 1 227 k2 = 0 228 while k2 < 3 { vout[nv * 3 + k2] = cc[k2] + mid[k2]; k2 = k2 + 1 } 229 nv = nv + 1 230 k2 = 0 231 while k2 < 3 { vout[nv * 3 + k2] = cc[k2] + q3[k2]; k2 = k2 + 1 } 232 nv = nv + 1 233 } 234 going = 0 235 } else { 236 // full quarter cur->nq: 3 bisection points + the quarter point 237 sgc_bis(cc, cur, nq, mid) 238 sgc_bis(cc, cur, mid, q1) 239 sgc_bis(cc, mid, nq, q3) 240 if nv + 4 <= cap { 241 var k3: i64 = 0 242 while k3 < 3 { vout[nv * 3 + k3] = cc[k3] + q1[k3]; k3 = k3 + 1 } 243 nv = nv + 1 244 k3 = 0 245 while k3 < 3 { vout[nv * 3 + k3] = cc[k3] + mid[k3]; k3 = k3 + 1 } 246 nv = nv + 1 247 k3 = 0 248 while k3 < 3 { vout[nv * 3 + k3] = cc[k3] + q3[k3]; k3 = k3 + 1 } 249 nv = nv + 1 250 k3 = 0 251 while k3 < 3 { vout[nv * 3 + k3] = cc[k3] + nq[k3]; k3 = k3 + 1 } 252 nv = nv + 1 253 } 254 cur[0] = nq[0]; cur[1] = nq[1]; cur[2] = nq[2] 255 q = q + 1 256 } 257 } 258 } 259 return nv 260} 261 262// ---- COMPLEX-INSTANCE rational Bezier curve (OCC circles-as-curves): ( BOUNDED_CURVE() B_SPLINE_CURVE(3,(4 263// ctrl)) B_SPLINE_CURVE_WITH_KNOTS(single span) RATIONAL_B_SPLINE_CURVE((weights)) ... ). Same math as the 264// tube surfaces: Bernstein Q12 x weights Q20 -> exact arcs. Parse into rc: ctrl slots 0..11 (fx256), wts 265// 12..15 (Q20), rc[16]=degree. Requires degree 3 + 4 ctrl (single Bezier span). 1 ok. 266func sgr_parse(st: *i64, cidx: i64, rc: *i64) -> i64 { 267 let buf: *u8 = st[0] as *u8 268 let aao: *i64 = st[5] as *i64 269 let aal: *i64 = st[6] as *i64 270 let out2: *i64 = st[9] as *i64 271 let off: i64 = aao[cidx] 272 let len: i64 = aal[cidx] 273 var p: i64 = sgn_find(buf, off, len, "B_SPLINE_CURVE(" as *u8) 274 if p < 0 { return 0 } 275 p = p + 15 276 let e: i64 = off + len 277 p = sgn_int(buf, p, e, out2) 278 if out2[0] != 3 { return 0 } 279 rc[16] = out2[0] 280 // 4 ctrl refs before WITH_KNOTS 281 let wk: i64 = sgn_find(buf, p, e - p, "B_SPLINE_CURVE_WITH_KNOTS" as *u8) 282 var refend: i64 = e 283 if wk >= 0 { refend = wk } 284 let refs: *i64 = ((rc as i64) + 144) as *i64 // scratch tail (alloc rc >= 256B) 285 let nr: i64 = sgt_refs(buf, p, refend - p, refs, 5) 286 if nr != 4 { return 0 } 287 var i: i64 = 0 288 while i < 4 { 289 if sgt_tuple_of(st, refs[i], ((rc as i64) + i * 24) as *i64) == 0 { return 0 } 290 i = i + 1 291 } 292 // weights (absent -> non-rational -> all 1.0) 293 var q: i64 = sgn_find(buf, off, len, "RATIONAL_B_SPLINE_CURVE(" as *u8) 294 if q < 0 { 295 rc[12] = 1048576 296 rc[13] = 1048576 297 rc[14] = 1048576 298 rc[15] = 1048576 299 return 1 300 } 301 q = q + 24 302 i = 0 303 while i < 4 { 304 q = sg_skip_to_num(buf, q, e) 305 if q >= e { return 0 } 306 q = sgn_realq(buf, q, e, 1048576, out2) 307 if out2[0] <= 0 { return 0 } 308 rc[12 + i] = out2[0] 309 i = i + 1 310 } 311 return 1 312} 313// rational cubic Bezier eval at t (Q12) -> out3 fx256 (scr >= 40B for Bernstein) 314func sgr_eval(rc: *i64, t: i64, out3: *i64, scr: *i64) -> i64 { 315 sgn_bern(t, scr) 316 var den: i64 = 0 317 var nx3: i64 = 0 318 var ny3: i64 = 0 319 var nz3: i64 = 0 320 var j: i64 = 0 321 while j < 4 { 322 let bw: i64 = scr[j] * rc[12 + j] 323 den = den + bw 324 nx3 = nx3 + bw * rc[j * 3] 325 ny3 = ny3 + bw * rc[j * 3 + 1] 326 nz3 = nz3 + bw * rc[j * 3 + 2] 327 j = j + 1 328 } 329 if den <= 0 { out3[0] = 0; out3[1] = 0; out3[2] = 0; return 0 } 330 out3[0] = sgn_rdiv(nx3, den) 331 out3[1] = sgn_rdiv(ny3, den) 332 out3[2] = sgn_rdiv(nz3, den) 333 return 1 334} 335 336// ---- resolve an EDGE_CURVE's geometry: out[0]=kind (0 chord, 1 bspline-named, 2 circle, 3 complex rational 337// Bezier curve), out[1]=entity idx 338func sgt2_resolve_curve(st: *i64, ecidx: i64, out: *i64) -> i64 { 339 let out2: *i64 = st[9] as *i64 340 var cid: i64 = sgt_arg_ref(st, ecidx, 3, out2) 341 if cid < 0 { out[0] = 0; return 0 } 342 var cidx: i64 = sp_find(st, cid) 343 if cidx < 0 { out[0] = 0; return 0 } 344 if sp_name_prefix(st, cidx, "SURFACE_CURVE" as *u8) == 1 { 345 let inner: i64 = sgt_arg_ref(st, cidx, 1, out2) 346 if inner >= 0 { let ii: i64 = sp_find(st, inner); if ii >= 0 { cidx = ii } } 347 } 348 if sp_name_prefix(st, cidx, "SEAM_CURVE" as *u8) == 1 { 349 let inner2: i64 = sgt_arg_ref(st, cidx, 1, out2) 350 if inner2 >= 0 { let ii2: i64 = sp_find(st, inner2); if ii2 >= 0 { cidx = ii2 } } 351 } 352 if sp_name_is(st, cidx, "B_SPLINE_CURVE_WITH_KNOTS" as *u8) == 1 { 353 out[0] = 1 354 out[1] = cidx 355 return 1 356 } 357 if sp_name_is(st, cidx, "CIRCLE" as *u8) == 1 { 358 out[0] = 2 359 out[1] = cidx 360 return 1 361 } 362 let anl2: *i64 = st[4] as *i64 363 if anl2[cidx] == 0 { 364 // complex instance: rational Bezier curve? 365 let aao2: *i64 = st[5] as *i64 366 let aal2: *i64 = st[6] as *i64 367 let f: i64 = sgn_find(st[0] as *u8, aao2[cidx], aal2[cidx], "B_SPLINE_CURVE(" as *u8) 368 if f >= 0 { 369 out[0] = 3 370 out[1] = cidx 371 return 1 372 } 373 } 374 out[0] = 0 375 return 0 376} 377 378// ---- kind-3 edge: complex rational Bezier arc (circle-as-curve). FLAT dedicated function (the inline deep- 379// nested version tripped the nx_cc deep-nesting miscompile -- counter page corrupted; extraction = the fix). 380// Samples 11 interior points after verifying the curve endpoint matches the edge's traversal-start vertex. 381// Returns the updated vertex count. 382func sgt2_bez_edge(t2: *i64, ecidx: i64, fwd: i64, vout: *i64, nv0: i64, vcap: i64) -> i64 { 383 let st: *i64 = t2[0] as *i64 384 let c: *i64 = t2[1] as *i64 385 let rc: *i64 = t2[12] as *i64 386 var nv: i64 = nv0 387 let kindslot: *i64 = ((t2[5] as i64) + 32) as *i64 388 if c[14] == 1 { 389 sp_puts(" [bez] ec-idx=" as *u8) 390 sp_putn(ecidx) 391 sp_puts(" curve-idx=" as *u8) 392 sp_putn(kindslot[1]) 393 sp_puts(" nv0=" as *u8) 394 sp_putn(nv0) 395 sp_puts("\n" as *u8) 396 } 397 if sgr_parse(st, kindslot[1], rc) == 0 { return nv } 398 if c[14] == 1 { 399 sp_puts(" [bez] parsed deg=" as *u8) 400 sp_putn(rc[16]) 401 sp_puts(" w1=" as *u8) 402 sp_putn(rc[13]) 403 sp_puts("\n" as *u8) 404 } 405 c[9] = c[9] + 1 406 let out2: *i64 = st[9] as *i64 407 let ss3: i64 = sgt_flag_T(st, ecidx, 4, out2) 408 var fwd3: i64 = 0 409 if fwd == ss3 { fwd3 = 1 } 410 let ev3: *i64 = t2[5] as *i64 411 let scr3: *i64 = t2[7] as *i64 412 var ts3: i64 = 0 413 if fwd3 == 0 { ts3 = SN_Q12 } 414 sgr_eval(rc, ts3, ev3, scr3) 415 var dd: i64 = 0 416 var k: i64 = 0 417 while k < 3 { 418 var d: i64 = ev3[k] - vout[(nv - 1) * 3 + k] 419 if d < 0 { d = 0 - d } 420 dd = dd + d 421 k = k + 1 422 } 423 if dd > 3 { 424 c[10] = c[10] + 1 425 return nv 426 } 427 var s3: i64 = 1 428 while s3 < 12 { 429 var tf3: i64 = s3 430 if fwd3 == 0 { tf3 = 12 - s3 } 431 sgr_eval(rc, (tf3 * SN_Q12) / 12, ev3, scr3) 432 if nv < vcap { 433 vout[nv * 3] = ev3[0] 434 vout[nv * 3 + 1] = ev3[1] 435 vout[nv * 3 + 2] = ev3[2] 436 nv = nv + 1 437 } 438 s3 = s3 + 1 439 } 440 return nv 441} 442 443// ---- loop walk v2: curve edges SAMPLED. ctx t2[]: [0]=st [1]=cnt-table c[16] (shares tess counters: 444// c[9]=curve-edges-sampled c[10]=endpoint-verify-fails->chord-fallback) [2]=bc(*i64 32B) [3]=cpts(*i64 960B) 445// [4]=kn(*i64 1024B incl. parse scratch) [5]=ev(64B) [6]=edge-refs(512B) [7]=eval-scratch(256B). 446// verts -> vout (fx256 stride3, cap vcap). returns vertex count. NO mmap inside (hot path). 447func sgt2_loop_verts(t2: *i64, loopid: i64, vout: *i64, vcap: i64) -> i64 { 448 let st: *i64 = t2[0] as *i64 449 let c: *i64 = t2[1] as *i64 450 let out2: *i64 = st[9] as *i64 451 let lidx: i64 = sp_find(st, loopid) 452 if lidx < 0 { return 0 } 453 if sp_arg_span(st, lidx, 1, out2) == 0 { return 0 } 454 let refs: *i64 = t2[6] as *i64 455 let ne: i64 = sgt_refs(st[0] as *u8, out2[0], out2[1], refs, 64) 456 if ne < 1 { return 0 } 457 var nv: i64 = 0 458 var i: i64 = 0 459 while i < ne { 460 let oeidx: i64 = sp_find(st, refs[i]) 461 if oeidx < 0 { return 0 } 462 let ecid: i64 = sgt_arg_ref(st, oeidx, 3, out2) 463 if ecid < 0 { return 0 } 464 let fwd: i64 = sgt_flag_T(st, oeidx, 4, out2) 465 let ecidx: i64 = sp_find(st, ecid) 466 if ecidx < 0 { return 0 } 467 // edge start vertex (per orientation) 468 var vk: i64 = 2 469 if fwd == 1 { vk = 1 } 470 let vpid: i64 = sgt_arg_ref(st, ecidx, vk, out2) 471 if vpid < 0 { return 0 } 472 let vpidx: i64 = sp_find(st, vpid) 473 if vpidx < 0 { return 0 } 474 let cpid: i64 = sgt_arg_ref(st, vpidx, 1, out2) 475 if cpid < 0 { return 0 } 476 let sv: *i64 = t2[5] as *i64 477 if sgt_tuple_of(st, cpid, sv) == 0 { return 0 } 478 if nv >= vcap { return 0 } 479 vout[nv * 3] = sv[0] 480 vout[nv * 3 + 1] = sv[1] 481 vout[nv * 3 + 2] = sv[2] 482 nv = nv + 1 483 // curve interior samples (t2[5] layout: sv/ra bytes 0..23, kind 32..47, rb 56..79 -- alloc >= 128B) 484 let kind: *i64 = ((t2[5] as i64) + 32) as *i64 485 sgt2_resolve_curve(st, ecidx, kind) 486 if kind[0] == 2 { 487 // CIRCLE edge: angle-free arc sampling (90-deg exact rotations + chord bisection at radius R) 488 let cc: *i64 = t2[9] as *i64 489 if sgc_parse(st, kind[1], cc) == 1 { 490 c[9] = c[9] + 1 491 // end vertex (per traversal): the OTHER of arg1/arg2 492 var vk2: i64 = 1 493 if fwd == 1 { vk2 = 2 } 494 let vpid2: i64 = sgt_arg_ref(st, ecidx, vk2, out2) 495 var okc: i64 = 0 496 let rb: *i64 = ((t2[5] as i64) + 56) as *i64 497 if vpid2 >= 0 { 498 let vpidx2: i64 = sp_find(st, vpid2) 499 if vpidx2 >= 0 { 500 let cpid2: i64 = sgt_arg_ref(st, vpidx2, 1, out2) 501 if cpid2 >= 0 { if sgt_tuple_of(st, cpid2, rb) == 1 { okc = 1 } } 502 } 503 } 504 if okc == 1 { 505 let ra: *i64 = t2[5] as *i64 506 var k4: i64 = 0 507 while k4 < 3 { 508 ra[k4] = vout[(nv - 1) * 3 + k4] - cc[k4] 509 rb[k4] = rb[k4] - cc[k4] 510 k4 = k4 + 1 511 } 512 // on-circle sanity for the start point (|ra|^2 vs R^2) 513 var r2d: i64 = ra[0] * ra[0] + ra[1] * ra[1] + ra[2] * ra[2] - cc[6] * cc[6] 514 if r2d < 0 { r2d = 0 - r2d } 515 if r2d > cc[6] * 16 { 516 c[10] = c[10] + 1 // not on this circle?! chord fallback, counted 517 } else { 518 // direction along the curve: +t iff oriented-flag == edge same-sense flag 519 let ss: i64 = sgt_flag_T(st, ecidx, 4, out2) 520 let st8: *i64 = t2[11] as *i64 521 if fwd == ss { 522 let added: i64 = sgc_arc(cc, ra, rb, ((vout as i64) + nv * 24) as *i64, vcap - nv - 1, st8) 523 nv = nv + added 524 } else { 525 let tmp: *i64 = t2[10] as *i64 526 let added2: i64 = sgc_arc(cc, rb, ra, tmp, 20, st8) 527 var e2: i64 = added2 - 1 528 while e2 >= 0 { 529 if nv < vcap { 530 var k5: i64 = 0 531 while k5 < 3 { vout[nv * 3 + k5] = tmp[e2 * 3 + k5]; k5 = k5 + 1 } 532 nv = nv + 1 533 } 534 e2 = e2 - 1 535 } 536 } 537 } 538 } 539 } 540 } 541 if kind[0] == 3 { nv = sgt2_bez_edge(t2, ecidx, fwd, vout, nv, vcap) } 542 if kind[0] == 1 { 543 let bc: *i64 = t2[2] as *i64 544 let cpts: *i64 = t2[3] as *i64 545 let kn: *i64 = t2[4] as *i64 546 if sgb_parse(st, kind[1], bc, cpts, kn) == 1 { 547 if bc[0] >= 2 { // degree>=2: real curve -> sample 548 c[9] = c[9] + 1 549 let p: i64 = bc[0] 550 let t0: i64 = kn[p] 551 let t1: i64 = kn[bc[2] - p - 1] 552 // endpoint VERIFY: eval at the oriented start must match the edge start vertex (tol 3 fx) 553 let ev: *i64 = t2[5] as *i64 554 let scr: *i64 = t2[7] as *i64 555 var ts: i64 = t0 556 if fwd == 0 { ts = t1 } 557 sgb_eval2(bc, cpts, kn, ts, ev, scr) 558 var dx: i64 = ev[0] - vout[(nv - 1) * 3] 559 if dx < 0 { dx = 0 - dx } 560 var dy: i64 = ev[1] - vout[(nv - 1) * 3 + 1] 561 if dy < 0 { dy = 0 - dy } 562 var dz: i64 = ev[2] - vout[(nv - 1) * 3 + 2] 563 if dz < 0 { dz = 0 - dz } 564 if dx + dy + dz > 3 { 565 c[10] = c[10] + 1 // verify-fail -> chord fallback (counted) 566 } else { 567 var s: i64 = 1 568 let S: i64 = 12 569 while s < S { 570 var tf: i64 = s 571 if fwd == 0 { tf = S - s } 572 let tq: i64 = t0 + ((t1 - t0) / S) * tf 573 sgb_eval2(bc, cpts, kn, tq, ev, scr) 574 if nv < vcap { 575 vout[nv * 3] = ev[0] 576 vout[nv * 3 + 1] = ev[1] 577 vout[nv * 3 + 2] = ev[2] 578 nv = nv + 1 579 } 580 s = s + 1 581 } 582 } 583 } 584 } 585 } 586 i = i + 1 587 } 588 return nv 589} 590 591// ---- keyhole bridge: splice hole polygon (hv, nh verts) into main polygon (mv, nm) at the closest vertex 592// pair. Result back into mv; returns new count. Bridge = mv[i], hole cycle from j, back to hole[j], mv[i] again. 593// Bridge edges are traversed BOTH ways -> the merged shoelace == outer - holes EXACTLY (invariant preserved). 594func sgt2_bridge(mv: *i64, nm: i64, hv: *i64, nh: i64, cap: i64) -> i64 { 595 if nm + nh + 2 > cap { return nm } 596 var bi: i64 = 0 597 var bj: i64 = 0 598 var bd: i64 = 0 - 1 599 var i: i64 = 0 600 while i < nm { 601 var j: i64 = 0 602 while j < nh { 603 let dx: i64 = (mv[i * 3] - hv[j * 3]) / 4 604 let dy: i64 = (mv[i * 3 + 1] - hv[j * 3 + 1]) / 4 605 let dz: i64 = (mv[i * 3 + 2] - hv[j * 3 + 2]) / 4 606 let d: i64 = dx * dx + dy * dy + dz * dz 607 if bd < 0 { bd = d; bi = i; bj = j } else { if d < bd { bd = d; bi = i; bj = j } } 608 j = j + 1 609 } 610 i = i + 1 611 } 612 let ins: i64 = nh + 2 613 var w: i64 = nm - 1 614 while w > bi { 615 var k: i64 = 0 616 while k < 3 { mv[(w + ins) * 3 + k] = mv[w * 3 + k]; k = k + 1 } 617 w = w - 1 618 } 619 var s: i64 = 0 620 while s <= nh { 621 let hj: i64 = (bj + s) % nh 622 var k2: i64 = 0 623 while k2 < 3 { mv[(bi + 1 + s) * 3 + k2] = hv[hj * 3 + k2]; k2 = k2 + 1 } 624 s = s + 1 625 } 626 var k3: i64 = 0 627 while k3 < 3 { mv[(bi + 1 + nh + 1) * 3 + k3] = mv[bi * 3 + k3]; k3 = k3 + 1 } 628 return nm + ins 629} 630 631// ---- tessellate ANY planar face (1..k bounds): curve-sampled loops (sgt2_loop_verts) + keyhole hole bridging. 632// tt = v1 ctx REPOINTED to big buffers (tt[4]=p3 >=256 verts, tt[5]=p2, tt[6]=alive; tt[7] stays plane block); 633// t2 = curve ctx (t2[1] shares tt[2] counters); slab = per-bound vert storage (6 bounds x 96 verts x 3); 634// counters: c[11]=v2-tessellated c[12]=holes-bridged c[13]=v2-fails. Returns 1 on tessellated. 635func sgt2_face(tt: *i64, t2: *i64, idx: i64, slab: *i64) -> i64 { 636 let st: *i64 = tt[0] as *i64 637 let c: *i64 = tt[2] as *i64 638 let out2: *i64 = tt[8] as *i64 639 if sgt_face_plane(tt, idx) == 0 { return 0 } // not planar (spline surfaces go to sgn path) 640 // bounds (cap 8 -- plate top = outer + 6 hole rims = 7 bounds; slab must be >= 20480B) 641 let brefs: *i64 = ((slab as i64) + 18432) as *i64 642 if sp_arg_span(st, idx, 1, out2) == 0 { c[13] = c[13] + 1; return 0 } 643 let nb: i64 = sgt_refs(st[0] as *u8, out2[0], out2[1], brefs, 8) 644 if nb < 1 { c[13] = c[13] + 1; return 0 } 645 // walk every bound into the slab; project (repointing tt[4]) to get signed area per bound 646 let bn: *i64 = ((slab as i64) + 18560) as *i64 // per-bound vert counts (cap 8) 647 let ba: *i64 = ((slab as i64) + 18688) as *i64 // per-bound signed areas 648 let p2: *i64 = tt[5] as *i64 649 var b: i64 = 0 650 while b < nb { 651 let bidx: i64 = sp_find(st, brefs[b]) 652 if bidx < 0 { c[13] = c[13] + 1; return 0 } 653 let loopid: i64 = sgt_arg_ref(st, bidx, 1, out2) 654 if loopid < 0 { c[13] = c[13] + 1; return 0 } 655 let bverts: *i64 = ((slab as i64) + b * 2304) as *i64 656 let nv: i64 = sgt2_loop_verts(t2, loopid, bverts, 96) 657 if nv < 3 { c[13] = c[13] + 1; return 0 } 658 bn[b] = nv 659 // project this bound (repoint tt[4] to it) 660 tt[4] = bverts as i64 661 sgt_project(tt, nv) 662 ba[b] = sgt_shoelace(p2, nv) 663 if ba[b] == 0 { c[13] = c[13] + 1; return 0 } 664 b = b + 1 665 } 666 // outer = bound with max |area| 667 var oi: i64 = 0 668 var oa: i64 = ba[0] 669 if oa < 0 { oa = 0 - oa } 670 b = 1 671 while b < nb { 672 var ab: i64 = ba[b] 673 if ab < 0 { ab = 0 - ab } 674 if ab > oa { oa = ab; oi = b } 675 b = b + 1 676 } 677 // merged polygon = outer; holes spliced in with OPPOSITE winding (reverse a hole if same sign as outer). 678 // tt[4] was repointed at the slab per-bound above -- now point it at the BIG merge buffer (t2[8]). 679 let mbuf: *i64 = t2[8] as *i64 680 tt[4] = mbuf as i64 681 let ov: *i64 = ((slab as i64) + oi * 2304) as *i64 682 var nm: i64 = bn[oi] 683 var k: i64 = 0 684 while k < nm * 3 { mbuf[k] = ov[k]; k = k + 1 } 685 b = 0 686 while b < nb { 687 if b != oi { 688 let hv: *i64 = ((slab as i64) + b * 2304) as *i64 689 // same winding sign as outer? reverse the hole in place 690 var same: i64 = 0 691 if ba[b] > 0 { if ba[oi] > 0 { same = 1 } } 692 if ba[b] < 0 { if ba[oi] < 0 { same = 1 } } 693 if same == 1 { 694 var lo: i64 = 0 695 var hi: i64 = bn[b] - 1 696 while lo < hi { 697 var k2: i64 = 0 698 while k2 < 3 { 699 let tv: i64 = hv[lo * 3 + k2] 700 hv[lo * 3 + k2] = hv[hi * 3 + k2] 701 hv[hi * 3 + k2] = tv 702 k2 = k2 + 1 703 } 704 lo = lo + 1 705 hi = hi - 1 706 } 707 } 708 nm = sgt2_bridge(mbuf, nm, hv, bn[b], 250) 709 c[12] = c[12] + 1 710 } 711 b = b + 1 712 } 713 // project merged, orient CCW, emit verts, ear-clip with the EXACT invariant 714 sgt_project(tt, nm) 715 var area: i64 = sgt_shoelace(p2, nm) 716 if area == 0 { c[13] = c[13] + 1; return 0 } 717 if area < 0 { 718 var lo2: i64 = 0 719 var hi2: i64 = nm - 1 720 while lo2 < hi2 { 721 var k3: i64 = 0 722 while k3 < 3 { 723 let t3: i64 = mbuf[lo2 * 3 + k3] 724 mbuf[lo2 * 3 + k3] = mbuf[hi2 * 3 + k3] 725 mbuf[hi2 * 3 + k3] = t3 726 k3 = k3 + 1 727 } 728 let t4: i64 = p2[lo2 * 2] 729 p2[lo2 * 2] = p2[hi2 * 2] 730 p2[hi2 * 2] = t4 731 let t5: i64 = p2[lo2 * 2 + 1] 732 p2[lo2 * 2 + 1] = p2[hi2 * 2 + 1] 733 p2[hi2 * 2 + 1] = t5 734 lo2 = lo2 + 1 735 hi2 = hi2 - 1 736 } 737 area = 0 - area 738 } 739 let mesh: i64 = tt[1] 740 let h: *i64 = m3_hdr(mesh) 741 let vbase: i64 = h[0] 742 var i: i64 = 0 743 while i < nm { 744 m3_add_vert(mesh, mbuf[i * 3], mbuf[i * 3 + 1], mbuf[i * 3 + 2]) 745 i = i + 1 746 } 747 let asum: i64 = sgt_earclip(tt, nm, vbase) 748 if asum != area { c[6] = c[6] + 1; return 0 } 749 c[11] = c[11] + 1 750 return 1 751} 752 753// v2 whole-model planar sweep (spline SURFACES stay on the sgn path). Set c[14]=1 for per-face counter tracing. 754func sgt2_tessellate(tt: *i64, t2: *i64, slab: *i64) -> i64 { 755 let st: *i64 = tt[0] as *i64 756 let c: *i64 = tt[2] as *i64 757 let aidt: *i64 = st[2] as *i64 758 let cnt: i64 = st[7] 759 var idx: i64 = 0 760 while idx < cnt { 761 if sp_name_is(st, idx, "ADVANCED_FACE" as *u8) == 1 { 762 let before9: i64 = c[9] 763 sgt2_face(tt, t2, idx, slab) 764 if c[14] == 1 { 765 if c[9] != before9 { 766 sp_puts(" [trace] face #" as *u8) 767 sp_putn(aidt[idx]) 768 sp_puts(" c9 " as *u8) 769 sp_putn(before9) 770 sp_puts("->" as *u8) 771 sp_putn(c[9]) 772 sp_puts(" | all c:" as *u8) 773 var tz: i64 = 0 774 while tz < 16 { 775 sp_puts(" " as *u8) 776 sp_putn(c[tz]) 777 tz = tz + 1 778 } 779 sp_puts("\n" as *u8) 780 } 781 } 782 } 783 idx = idx + 1 784 } 785 return c[11] 786} 787