Skip to content

Add separating-axis early-out to ConvexConvexSutherlandHodgman for disjoint inputs#411

Draft
asinghvi17 wants to merge 3 commits into
mainfrom
sh-disjoint-early-out
Draft

Add separating-axis early-out to ConvexConvexSutherlandHodgman for disjoint inputs#411
asinghvi17 wants to merge 3 commits into
mainfrom
sh-disjoint-early-out

Conversation

@asinghvi17

@asinghvi17 asinghvi17 commented Jun 9, 2026

Copy link
Copy Markdown
Member

Fixes #408.

intersection(ConvexConvexSutherlandHodgman(...), a, b) previously ran the full Sutherland-Hodgman clip — allocating intermediate point buffers — even when the two convex polygons are disjoint, which is the dominant case in workloads like ConservativeRegridding.jl (~80% of spatial-tree candidate pairs).

Change

Adds a separating-axis early-out before any buffers are built, for both manifolds:

  • Spherical: for each edge of either polygon, the edge's great-circle normal (robust_cross_product, hoisted out of the vertex loop) defines a candidate separating plane. If every vertex of the other polygon lies strictly on the negative side (semantics exactly matching spherical_orient < 0, same tolerance), the polygons are disjoint and the empty degenerate polygon is returned immediately.
  • Planar: the classic SAT equivalent using Predicates.orient.

The check is only a sufficient condition: touching, shared-edge, and exactly-antipodal configurations (where vertices lie on the candidate great circles) fall through to the full clip, so results never change — only speed. Degenerate edges (e.g. the ring's closing point) are skipped, since robust_cross_product(a, a) returns an arbitrary orthogonal vector that must not be used as a candidate separating plane.

Type-level toggle

The early-out is selected at the type level via a new DisjointCheck enum carried as a second type parameter, defaulting to on:

ConvexConvexSutherlandHodgman()                                # ::ConvexConvexSutherlandHodgman{Planar, CheckDisjoint}
ConvexConvexSutherlandHodgman(Spherical(), SkipDisjointCheck)  # early-out disabled

Since the enum value is a type parameter, the branch is resolved at compile time. Existing dispatch on ConvexConvexSutherlandHodgman{Planar} etc. still works via partial instantiation, and the constructor validates the parameter is a DisjointCheck value.

Benchmarks (Chairmarks, min time, 1°×1° cells)

Case SkipDisjointCheck (= before) CheckDisjoint (default)
spherical disjoint 367 ns, 24 allocs 40 ns, 6 allocs (~10×)
planar disjoint 60 ns, 10 allocs 25 ns, 6 allocs (~2.4×)
spherical adjacent (shared edge) 383 ns 398 ns (falls through)
spherical overlapping 431 ns 495 ns (cost of the failed sweep)
planar overlapping 170 ns 166 ns (unchanged)

The remaining 6 allocations on the disjoint path are the returned degenerate polygon itself. For the ~80%-disjoint regridding mix described in the issue this is roughly a 3× overall speedup of the clipping step; workloads where pairs almost always overlap can opt out with SkipDisjointCheck.

Tests

  • Direct predicate tests for both helpers: disjoint far/near/diagonal-gap pairs trigger in both argument orders; shared-edge, corner-touching, overlapping, and identical inputs do not (proving the conservative fall-through).
  • End-to-end zero-area results for disjoint inputs in both argument orders, plus an exactly-antipodal spherical pair documenting that the early-out stays conservative while the full clip still returns empty.
  • The DisjointCheck toggle: default parameter value, invalid-parameter ArgumentError, and identical results with the check disabled on both manifolds.

🤖 Generated with Claude Code

asinghvi17 and others added 3 commits June 9, 2026 16:25
…sjoint inputs

For each edge of either convex polygon, the edge's line (planar) or great
circle (spherical) is a candidate separating axis; if all vertices of the
other polygon lie strictly outside it, the polygons are disjoint and the
empty result is returned before any intermediate point buffers are
allocated. Touching and near-touching pairs fall through to the full clip,
so results are unchanged. Disjoint spherical pairs go from ~413ns/24
allocations to ~43ns/6 allocations.

Fixes #408

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a `SeparatingAxisCheck` enum (`CheckSeparatingAxis`, the default, and
`SkipSeparatingAxisCheck`) carried as a second type parameter on
`ConvexConvexSutherlandHodgman`, so the disjoint early-out can be disabled
at the type level with no runtime branch:

    ConvexConvexSutherlandHodgman(Spherical(), SkipSeparatingAxisCheck)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Name the toggle by intent (checking for disjoint inputs) rather than by
mechanism (the separating-axis test), and drop the redundant "Check" from
the enum values: `CheckDisjoint` (default) and `SkipDisjointCheck`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@asinghvi17

Copy link
Copy Markdown
Member Author

This may not actually help in the use case I care about, so I'm going to leave it as a draft for now until I can establish whether it helps.

@asinghvi17 asinghvi17 marked this pull request as draft June 11, 2026 02:46
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.

ConvexConvexSutherlandHodgman: separating-great-circle early-out for disjoint inputs

1 participant