Skip to content

CDT robustness: convert panics to errors, rotation retry, edge dedup - #4

Draft
lucia3e8 wants to merge 1 commit into
fix/collinear-perturbationfrom
fix/conn-smd-robustness
Draft

lucia3e8 wants to merge 1 commit into
fix/collinear-perturbationfrom
fix/conn-smd-robustness

Conversation

@lucia3e8

@lucia3e8 lucia3e8 commented Mar 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Improve CDT robustness for complex multi-contour geometry, specifically targeting CONN-SMD_2309413-1 (the last failing component with 73 face bounds on a single face).

Changes

CDT panic → error conversion (cdt/src/triangulate.rs):

  • Convert 4 .expect() calls and 2 assert!() checks in walk_fill to return Err(HalfEdgeInvariant) instead of panicking. These panics occurred on complex multi-contour faces where contour triangulation failed to produce expected edges.

Rotation retry (triangulate/src/triangulate.rs):

  • When CDT fails with CrossingFixedEdge, WedgeEscape, or HalfEdgeInvariant, retry with up to 4 rotated 2D coordinate systems (0.123, 0.347, 0.789, 1.234 radians)
  • Each rotation includes full edge preprocessing: point dedup, crossing edge removal, T-intersection resolution, duplicate edge removal
  • Uses remove_crossing_edges() (drops shorter edge in crossing pairs) since we can't add new 3D vertices in the retry path
  • Stops on first successful CDT run

Edge preprocessing improvements:

  • Add duplicate edge removal (HashSet dedup by canonical endpoint pair) after T-intersection resolution
  • Expand Phase 2 T-intersection check to cover all points (including those added by Phase 1 crossing resolution)
  • Increase Phase 2 iteration limit from 200 to 500
  • Remove unused perturb_collinear_points() function

Results

CONN-SMD_2309413-1 face errors reduced 5 → 2:

  • ✅ Face 101924 (B-spline, 1080 pts): CrossingFixedEdge → fixed by rotation
  • ✅ Face 195309 (Plane, 77 pts): WedgeEscape → fixed by rotation
  • ✅ Face 153369 (Plane, 294 pts, 73 contours): HalfEdgeInvariant (was panic) → fixed by rotation
  • ❌ Face 106344 (Plane, 120 pts): HalfEdgeInvariant — all boundary points collinear at x=402
  • ❌ Face 337698 (Plane, 524 pts): WedgeEscape — CDT walk escapes hull

Batch test: 91/92 maintained (CONN-SMD still counted as failing due to 2 remaining face errors, but significantly improved)

Remaining work for CONN-SMD_2309413-1

The 2 remaining face failures are caused by degenerate plane projections where the plane's reference direction aligns with the face's thin dimension, collapsing all boundary points to a line in 2D. Possible future fixes:

  • PCA-based alternative lowering for degenerate plane faces
  • CDT-level robustness for collinear point sets

Test plan

  • cargo test passes
  • Batch test: 91/92 pass (no regressions)
  • CONN-SMD_2309413-1 has only 2 face errors (down from 5), no panics
  • Verify no new panics in cdt crate (all converted to errors)

🤖 Generated with Claude Code

Stack: 4/4 — prev: fix/collinear-perturbation


Note

Medium Risk
Moderate risk: changes error handling and adds a lossy retry path that can drop constrained edges to make triangulation succeed, which may alter mesh output for some degenerate/crossing inputs.

Overview
Improves triangulation robustness by replacing several assert!/.expect() panics in cdt::Triangulation::walk_fill with Error::HalfEdgeInvariant returns when half-edge/hull invariants don’t hold.

When CDT fails with CrossingFixedEdge, WedgeEscape, or HalfEdgeInvariant, the higher-level triangulator now retries by rotating the 2D projection through a few fixed angles and re-running edge pre-processing before attempting CDT again.

Edge pre-processing is tightened by (a) expanding T-intersection splitting to consider all points (including those introduced by crossing resolution), (b) increasing iteration limits, (c) removing a collinearity-perturbation approach in favor of remove_crossing_edges (drops the shorter edge on crossings in the rotation retry path), and (d) deduplicating duplicate edges after splitting.

Written by Cursor Bugbot for commit 8690327. This will update automatically on new commits. Configure here.

- Convert CDT expect()/assert() panics in walk_fill to return
  HalfEdgeInvariant errors instead, preventing process-level panics
  on degenerate multi-contour geometry
- Add rotation retry: when CDT fails with CrossingFixedEdge,
  WedgeEscape, or HalfEdgeInvariant, retry with multiple rotated
  2D coordinate systems (0.123, 0.347, 0.789, 1.234 radians)
- Add remove_crossing_edges() for rotation retry path where we can't
  add new 3D vertices — removes shorter edge in crossing pairs
- Add T-intersection resolution and duplicate edge removal in rotation
  retry path
- Add duplicate edge removal (HashSet dedup) in resolve_crossing_edges
- Expand Phase 2 T-intersection check to cover all points (including
  those added by Phase 1 crossing resolution), increase iteration limit
- Remove unused perturb_collinear_points() function

CONN-SMD_2309413-1: reduced from 5 face errors to 2 (3 faces now
succeed via rotation retry). Remaining: face 106344 (HalfEdgeInvariant,
collinear plane) and face 337698 (WedgeEscape).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

((xmax - xmin).powi(2) + (ymax - ymin).powi(2)).sqrt()
};
let t_eps = if diag > 1e-15 { diag * 1e-7 } else { 1e-12 };
for _ in 0..200 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retry path uses lower T-intersection iteration limit than main path

Medium Severity

The T-intersection resolution loop in the rotation retry path uses 0..200 iterations, while this same PR increased the equivalent loop in resolve_crossing_edges from 200 to 0..500. Since the retry path specifically targets complex geometries that already failed the main CDT, these cases are more likely to need the higher limit. An incomplete T-intersection resolution could leave near-collinear points unresolved, causing the retry CDT to fail unnecessarily.

Additional Locations (1)
Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant