@@ -65,6 +65,18 @@ def _subdivide_sq_dist_net(F, axis, t=0.5):
6565 left_v , right_v = de_casteljau_split_nd (Fv , axis = axis , t = t )
6666 return left_v [..., 0 ], right_v [..., 0 ]
6767
68+ def _subdivide_sq_dist_net_2d (F , u = 0.5 ,v = 0.5 ):
69+ """Subdivide the scalar sq-dist Bernstein net along *axis*.
70+
71+ ``de_casteljau_split_nd`` requires a trailing value dimension, so we
72+ temporarily add one and squeeze it back off.
73+ """
74+ Fv = F [..., np .newaxis ]
75+
76+ left_v , right_v = de_casteljau_split_nd (Fv , axis = 1 , t = u )
77+ left_u_left_v ,right_u_left_v = de_casteljau_split_nd (left_v , axis = 0 , t = v )
78+ left_u_right_v ,right_u_right_v = de_casteljau_split_nd (right_v , axis = 0 , t = v )
79+ return left_u_left_v [..., 0 ], left_u_right_v [..., 0 ],right_u_right_v [..., 0 ],right_u_left_v [..., 0 ]
6880
6981def _boundary_zero_to_uv (bz : BoundaryZero , u0 : float , u1 : float , v0 : float , v1 : float ) -> tuple [float , float ]:
7082 """Convert a BoundaryZero from the classifier into global (u, v) parameters.
@@ -216,7 +228,7 @@ def _phase2_ccx(F, C1, C2, C1_orig, C2_orig,
216228 cells += 1
217229
218230 seg1 , seg2 , F_cell , pw , qw , u0 , u1 , v0 , v1 , depth = stack .pop ()
219-
231+ #print(f"CCX: {cells} cells: {( ( u0, u1), (v0, v1), depth)}")
220232 # AABB prune: cheapest possible check — control point bounding boxes
221233 from mmcore .numeric ._aabb import aabb , aabb_intersect
222234 if rational :
@@ -246,7 +258,7 @@ def _phase2_ccx(F, C1, C2, C1_orig, C2_orig,
246258 for ax in range (2 ):
247259 dF = bernstein_partial_derivative_coeffs (Fv , axis = ax )
248260 coeffs = dF [..., 0 ]
249- if np .min (coeffs ) >= 0 or np .max (coeffs ) <= 0 :
261+ if np .min (coeffs ) > 0 or np .max (coeffs ) <= 0 :
250262 can_have_stationary = False
251263 break
252264 if not can_have_stationary :
@@ -257,69 +269,87 @@ def _phase2_ccx(F, C1, C2, C1_orig, C2_orig,
257269 u_mid = 0.5 * (u0 + u1 )
258270 v_mid = 0.5 * (v0 + v1 )
259271 pt = eval_curve (C1_orig , u_mid , rational = rational )
272+
260273 if not _is_duplicate (isolated , pt , atol ):
261274 isolated .append ({"u" : float (u_mid ), "v" : float (v_mid ), "point" : pt , "_micro" : True })
262275 continue
263276
264277 # Newton from cell center
265278 u_mid = 0.5 * (u0 + u1 )
266279 v_mid = 0.5 * (v0 + v1 )
267- u_sol , v_sol , G , last_step = newton_ccx (
268- C1_orig , C2_orig , u_mid , v_mid , rational = rational ,
269- )
270- step_norm = abs (last_step [0 ]) + abs (last_step [1 ])
271- residual_ok = float (np .linalg .norm (G )) < atol
272- converged = ((step_norm > 0 or residual_ok )
273- and abs (last_step [0 ]) <= ptol_u
274- and abs (last_step [1 ]) <= ptol_v )
275-
276- if converged and u0 - ptol_u <= u_sol <= u1 + ptol_u and v0 - ptol_v <= v_sol <= v1 + ptol_v :
277- pt = eval_curve (C1_orig , u_sol , rational = rational )
278- is_new = not _is_duplicate (isolated , pt , atol )
279- if is_new :
280- isolated .append ({"u" : float (u_sol ), "v" : float (v_sol ), "point" : pt })
281- sub_cells = _cutout_2d (
282- F_cell , seg1 , seg2 , pw , qw , u0 , u1 , v0 , v1 , depth ,
283- float (u_sol ), float (v_sol ), ptol_u , ptol_v , rational ,
284- )
285- stack .extend (sub_cells )
286- continue
287-
288- if converged :
280+ uv_candidates = [(u0 , v0 ),(u0 ,v1 ),(u1 ,v1 ),(u1 ,v0 )]
281+ root_found = False
282+ is_converged = False
283+ for u_mid ,v_mid in uv_candidates :
284+ if root_found :
285+ break
286+ u_sol , v_sol , G , last_step = newton_ccx (
287+ C1_orig ,C2_orig , u_mid , v_mid , rational = rational ,
288+ )
289+ step_norm = abs (last_step [0 ]) + abs (last_step [1 ])
290+ residual_ok = float (np .linalg .norm (G )) < atol
291+ converged = ((step_norm > 0 or residual_ok )
292+ and abs (last_step [0 ]) < ptol_u
293+ and abs (last_step [1 ]) < ptol_v )
294+ #print(f"CCX: {cells} cells NEWTON: {( u_sol, v_sol, G, last_step, ( u0, u1), (v0, v1), depth)}: {converged}")
295+ if converged :
296+ is_converged = True
297+ if converged and u0 < u_sol < u1 and v0 < v_sol < v1 :
298+
299+ pt = eval_curve (C1_orig , u_sol , rational = rational )
300+ is_new = not _is_duplicate (isolated , pt , atol )
301+ #print(f"CCX: is_new: {is_new}")
302+ if is_new :
303+ isolated .append ({"u" : float (u_sol ), "v" : float (v_sol ), "point" : pt })
304+ sub_cells = _cutout_2d (
305+ F_cell , seg1 , seg2 , pw , qw , u0 , u1 , v0 , v1 , depth ,
306+ float (u_sol ), float (v_sol ), ptol_u , ptol_v , rational ,
307+ )
308+ stack .extend (sub_cells )
309+ root_found = True
310+ break
311+
312+ if root_found :continue
313+
314+
315+
316+ if is_converged :
289317 # Converged outside cell → prune
290318 continue
291319
320+
292321 if depth >= max_depth :
293322 continue
294323
295324 # Subdivide
296- u_span = u1 - u0
297- v_span = v1 - v0
298- axis = 0 if u_span >= v_span else 1
299-
300- if axis == 0 :
301- u_mid_split = 0.5 * (u0 + u1 )
302- seg1_L , seg1_R = _subdivide_curve (seg1 )
303- F_L , F_R = _subdivide_sq_dist_net (F_cell , axis = 0 )
304- pw_L = seg1_L [:, - 1 ].copy () if rational else np .ones (seg1_L .shape [0 ])
305- pw_R = seg1_R [:, - 1 ].copy () if rational else np .ones (seg1_R .shape [0 ])
306- stack .append ((seg1_L , seg2 .copy (), F_L , pw_L , qw .copy (), u0 , u_mid_split , v0 , v1 , depth + 1 ))
307- stack .append ((seg1_R , seg2 .copy (), F_R , pw_R , qw .copy (), u_mid_split , u1 , v0 , v1 , depth + 1 ))
308- else :
309- v_mid_split = 0.5 * (v0 + v1 )
310- seg2_L , seg2_R = _subdivide_curve (seg2 )
311- F_L , F_R = _subdivide_sq_dist_net (F_cell , axis = 1 )
312- qw_L = seg2_L [:, - 1 ].copy () if rational else np .ones (seg2_L .shape [0 ])
313- qw_R = seg2_R [:, - 1 ].copy () if rational else np .ones (seg2_R .shape [0 ])
314- stack .append ((seg1 .copy (), seg2_L , F_L , pw .copy (), qw_L , u0 , u1 , v0 , v_mid_split , depth + 1 ))
315- stack .append ((seg1 .copy (), seg2_R , F_R , pw .copy (), qw_R , u0 , u1 , v_mid_split , v1 , depth + 1 ))
325+
326+
327+
328+
329+ u_mid_split = 0.5 * (u0 + u1 )
330+ v_mid_split = 0.5 * (v0 + v1 )
331+ seg1_L , seg1_R = _subdivide_curve (seg1 )
332+ seg2_L , seg2_R = _subdivide_curve (seg2 )
333+ F_LL , F_LR , F_RR ,F_RL , = _subdivide_sq_dist_net_2d (F_cell ,0.5 ,0.5 )
334+
335+
336+ pw_L = seg1_L [:, - 1 ].copy () if rational else np .ones (seg1_L .shape [0 ])
337+ pw_R = seg1_R [:, - 1 ].copy () if rational else np .ones (seg1_R .shape [0 ])
338+ qw_L = seg2_L [:, - 1 ].copy () if rational else np .ones (seg2_L .shape [0 ])
339+ qw_R = seg2_R [:, - 1 ].copy () if rational else np .ones (seg2_R .shape [0 ])
340+ stack .append ((seg1_L , seg2_L , F_LL , pw_L , qw_L , u0 , u_mid_split , v0 , v_mid_split , depth + 1 ))
341+ stack .append ((seg1_L , seg2_R , F_LR , pw_L , qw_R ,u0 , u_mid_split , v_mid_split , v1 , depth + 1 ))
342+ stack .append ((seg1_R , seg2_R , F_RR , pw_R , qw_R ,u_mid_split , u1 , v_mid_split , v1 , depth + 1 ))
343+ stack .append ((seg1_R , seg2_L , F_RL , pw_R , qw_L ,u_mid_split , u1 , v0 , v_mid_split , depth + 1 ))
316344
317345 return isolated [n_known :]
318346
319347
320348# ---------------------------------------------------------------------------
321349# Main algorithm: two-phase architecture
322350# ---------------------------------------------------------------------------
351+ from .._bezier_common import _compute_remaining_intervals
352+
323353
324354def bez_ccx (
325355 C1 ,
@@ -338,14 +368,7 @@ def bez_ccx(
338368 """
339369 C1 = np .asarray (C1 , dtype = np .float64 )
340370 C2 = np .asarray (C2 , dtype = np .float64 )
341- if rational :
342- bb1 = aabb_offset (aabb (C1 [...,:- 1 ] / C1 [...,None ,- 1 ]),atol / 2 )
343- bb2 = aabb_offset (aabb (C2 [...,:- 1 ] / C2 [..., None , - 1 ]),atol / 2 )
344- else :
345- bb1 = aabb_offset (aabb (C1 ),atol / 2 )
346- bb2 = aabb_offset (aabb (C2 ),atol / 2 )
347- if not aabb_intersect (bb1 , bb2 ):
348- return {"isolated" : [], "overlaps" : []}
371+
349372 F = curve_curve_squared_net_homog (C1 , C2 , rational = rational )
350373
351374 _ , Pw = extract_weights (C1 , rational = rational )
@@ -363,7 +386,7 @@ def bez_ccx(
363386 # PHASE 1: Boundary analysis + overlap (initial patch only)
364387 # ===================================================================
365388 cls = classify_sq_dist_net (F , atol , Pw , Qw )
366-
389+ #print(f"CCX: {cls} (phase 1)")
367390 if cls .kind == NO_INTERSECTION :
368391 return {"isolated" : [], "overlaps" : []}
369392
@@ -417,7 +440,7 @@ def bez_ccx(
417440 u_lo_ovl = min (ovl ["u_range" ])
418441 u_hi_ovl = max (ovl ["u_range" ])
419442 for u_bz , v_bz , pt in boundary_hits :
420- if not (u_lo_ovl - atol <= u_bz <= u_hi_ovl + atol ):
443+ if not (u_lo_ovl - ptol_u <= u_bz <= u_hi_ovl + ptol_u ):
421444 if not _is_duplicate (isolated , pt , atol ):
422445 isolated .append ({"u" : u_bz , "v" : v_bz , "point" : pt })
423446 else :
@@ -442,7 +465,7 @@ def bez_ccx(
442465 for iso in isolated :
443466 u_exclude .append ((iso ["u" ] - ptol_u , iso ["u" ] + ptol_u ))
444467
445- from mmcore . numeric . intersection . csx . _bez_csx4 import _compute_remaining_intervals
468+
446469 u_intervals = _compute_remaining_intervals (u_exclude , 0.0 , 1.0 )
447470
448471 for u_lo , u_hi in u_intervals :
0 commit comments