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