* solidcore: a trace point at a pole is the pole, its singular parameter along the curve's tangent A ball cut into a cylinder with its poles 1e-6 off the cylinder's face was not a solid in any frame (#15994). The intersection passes each pole 1e-6 away, within the tolerance the trace refines with, so the trace takes the pole as a point of the curve and ends two curves there. StepBackFromSingularity took the pole point's singular parameter 1 % of the last span back from the pole, where the curve's direction seen from the pole is set by the pole's offset (an angle of about the offset over the distance), not by the curve. Every span into the pole failed the span test by about 100 times the offset, the steps shrank to the tolerance, and the pole points got singular parameters a quarter turn off and UV tangents of order 1e5; the body check refused the trims. - A point on the degenerate boundary itself takes the singular parameter whose tangent across the boundary points along the curve (FindDegenParamForDirection, a Newton solve, as the vertex-use code places edges at poles), the period's image on the curve's side on a closed surface (pole_singular_parameter, part 1). - ComputePointValues places such a point at the pole: the boundary's parameter across it, the pole as its point on that surface, the pole's foot on the other surface, so the curves ending at one pole end at one point and their trims end on the degenerate boundary, where the volume enclosure closes the loop along the pole (place_at_pole, part 2). Tests: the half roll less the ball with its poles 1e-6 inside and outside the face, in the four frames that failed (one valid solid each, volume against the quadrature within 1e-8), and the same at 1e-4 (unchanged). The poles 1e-5 off (the zone tolerance) still fail: the curve then runs within the zone tolerance of the ball's seam near the poles (near-miss seam). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU * solidcore: a pole point the trace cannot place exactly keeps the step-back's values; a tangent pole is a tangent point Review of the pole construction: - pole_singular_parameter clamps its answer to the intersector's UV domain every time (the direction solve runs over the surface's natural domain), not only on a closed surface. - A direction solve that fails or does not converge leaves the step-back's sampled singular parameter, and a pole whose foot on the other surface is not found or has no normal (or whose evaluations fail) is left unplaced, as before: no new way for an exact pole to fail. Only Unported and Cancelled propagate. - Surfaces tangent at the pole make the point a TangentPoint, as ComputePointValues does for any point whose tangent vanishes, instead of an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU * solidcore: pole_singular_parameter bounds its answer with max/min and one fallback arm Clippy's match_same_arms and the panic-site check (f64::clamp) on the previous commit: the two arms that keep the step-back's value are one arm, and the clamp to the intersector's domain is max then min, which cannot panic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
56 KiB
Vendored
Surface/surface intersection
This page describes how solidcore intersects two surfaces: the theory the code rests on, the
invariants a change must keep, where each step lives, what is measured and what is open. It is
written for a reader who knows surface intersection and is new to this code, and for anyone stuck on
an intersection bug. Numbers in brackets are references (end of page). Every statement about the code
names the file it was checked in; paths are relative to crates/.
There are two implementations today, and layers.md §2 says one must go:
- the port,
solidcore/src/intersectand the analytic special cases insolidcore/src/surface: SMLib's intersector carried over unit by unit, with the fork's fixes behindKernel::fixesflags. This is what the boolean, the blend and every other caller run today; ssi,ssi/with its bridgesolidcore-ssi/: a method of its own, written without reading SMLib, measured against the port on the SSI corpus. Nothing incrates/solidcorecalls it yet.
Which copy survives is decided from the SSI corpus and the rig, and recorded on the job's issue.
The theory starts from a measurement: #17 scored the upstream intersector on 119 pairs with exact answers and found that every miss sits where the two normals are parallel. §3 is that measurement; the rest of the page is how the code handles, or still fails, those places.
Contents:
- Purpose and the one rule
- Scope
- The corpus and the measured gaps
- The pipeline
- The exact classes
- Certification and tolerances
- Failure modes
- Invariants
- Status
- If you're stuck
- References
1. Purpose and the one rule
An intersection curve is exact where its class allows, and a certified exact B-spline otherwise. A plane and a quadric, and the quadric pairs with a closed form (§5), meet in a line, a circle or an ellipse, and that is what is stored. Every other curve is a B-spline whose distance from the true intersection is bounded by a proof, never by a sample. Nothing is a fit through sampled points that is then hoped to be close enough. The pcurves (the curve's UV image on each surface) obey the same rule: an exact rational curve in the surface's parameters where one exists, a certified B-spline otherwise, built together with the 3D curve rather than projected onto the surface afterwards.
The rule is the target. §6 and §9 say where the code meets it today and where it does not: the exact classes do; the general curves of both implementations are cubic Hermite interpolants through Newton points, checked at interior samples, not yet certified.
The other rules follow from that one:
- Points come from roots. Every point on a curve is a Newton root of the intersection system, or
a bracketed root of a field. No point is chosen by thresholding distances at sample points
(
ssi/README.md). - Regions are dropped only by proof. A piece pair leaves the search when boxes, slabs or a field's coefficients prove it apart, never because a sample missed.
- A contact that cannot be proved is refused by name, never approximated (
ssi::Error::Tangent, codeBOOLEAN_TANGENT). - The topology is part of the answer. A curve ends where two branches cross; a touch point is a point; a coincident region is its boundary. A complete point set with the wrong topology is a wrong answer (the corpus grades it so, §9).
2. Scope
In.
- Surface/surface intersection:
Surface::global_surface_intersectand its analytic special cases; the general intersector (intersect/surface_intersector,intersect/adv_surface_intersector); thessicrate andsolidcore-ssi. - Curve/surface intersection, as far as SSI uses it: the start points on the natural boundaries are
boundary curves intersected with the other surface (§4.2). The API entry is
intersect::api_intersectors::intersect_curve_surface, a global solve over the cache trees. - What the boolean takes from SSI:
topology::face::p3::face_intersect(classification of each curve against both faces) and the topology intersector (boolean/topology_intersector), which imprints the kept intervals as edges (§4.6).
Out, each with its own page:
- The blend's spine and rails, which are an offset/offset intersection traced with the blend's own events: generic-blend.md.
- What a stored definition is, how a net is certified against it, and the certificate machinery (interval enclosures, the Krawczyk test) that §6 points to: definitions-and-certificates.md.
- Sweeps stored as their definition: procedural-sweep.md.
- Ray/body picking (
intersect/ray_tracer), silhouettes (intersect/surface_silhouette) and point classification (intersect/point_class), which share the solver framework but not the curve problem.
3. The corpus and the measured gaps
This section is
tangency-labs/solidcore#17, condensed. Before
the intersect.* units were ported, the upstream C++ (SMLib at c9c5979) was measured on 119
surface pairs whose exact intersection is known by construction. The measurement is the ground the
rest of this page stands on: it shows which parts of the problem the marcher solves and which it does
not, and the corpus it produced is how every SSI change is graded since.
3.1 Setup
- Build.
source/SMLibandsource/SM_APIonly, at-O3with the upstream release defines, no TBB. - Entry points.
SmSurface::GlobalSurfaceIntersect(whatSmApiIntersectSurfacescalls) is the primary one. Also measured:SmAdvSurfaceIntersector,SmSurfaceIntersector,SmFace::FaceIntersect(20°) andSmApiIntersectSurfaces. - Tolerances. Approximation tolerances 1e-6 (primary), 1e-4 and the default; global tolerances at their defaults (zone 1e-5).
- Failure sweep. Every failure re-run in 26 configurations: 4 intersectors × 3 tolerances × both
surface orders, plus the API in both orders. A trace build (
SM_DEBUG_CODE=1) reported no invalid-input errors. - Ground truth. Closed form for 116 cases. Three groups are traced numerically: the torus pairs, the tube arms and the appendix patch pair. Every stored point lies on both exact surfaces to 1e-9 or better.
- Scoring. Coverage both ways (one-sided Hausdorff); exact distance to both surfaces; connected components and free ends; duplicated curves; whether every singular point is a curve end or an output point.
3.2 Results
81 of 119 pass in the primary run: 25 are incomplete, 11 have wrong topology, 2 return an error,
none crash. Every returned curve lies within 1.9e-6 of both exact surfaces, except two tangent curves
at 3.4e-5 (y23_t2_ex4, y23_t6_ex1).
| class | cases | pass |
|---|---|---|
| transversal (branches, loops, nested loops) | 14 | 14 |
| tangent along a curve | 10 | 7 |
| isolated tangent points | 7 | 3 |
| singular crossing points | 6 | 3 |
| patch boundaries, corners, shared borders | 8 | 8 |
| coincident regions | 7 | 7 |
| near-degenerate | 24 | 11 (9 failures are below the 1e-5 zone tolerance) |
| seams, poles, closed surfaces | 12 | 12 |
| high degree, many spans, rational | 6 | 6 |
| Yang, Jia and Yan [16], published data | 6 | 5 |
| Yang, Jia and Yan [16], reconstructions | 19 | 5 |
Below the 1e-5 zone tolerance, angles of 1e-6 to 1e-8, offsets of 1e-7 and loops of 2e-6 or less are reported as such and not counted as defects.
What the table says. Every class in which the surfaces cross transversally passes: branches, loops, boundaries, coincident regions, seams and poles, high degree. Boundary start points, one seed per intersecting leaf pair and a Newton-corrected march (§4) are complete and accurate wherever n1 × n2 is bounded away from zero. Every miss is at a contact where n1 × n2 = 0, or nearly: touch points, tangent curves, branch points, tiny loops between nearly tangent surfaces. That is the theory of this page in one line: the hard part of SSI is the zero set of n1 × n2, and each of #17's four gaps is one way the marcher meets it (§4.4).
3.3 The four gaps
None of the 26 configurations closes them (unless a count says otherwise). The renders are #17's,
from GlobalSurfaceIntersect at 1e-6. In each, the top row is three 3D views with the exact
intersection drawn under SMLib's output and the missed parts marked; the bottom row is each surface's
parameter space and a close-up of the defect.
Gap 1: isolated tangent points inside a patch are mostly missed. The general path returns touch
points reliably only on patch boundaries; the sphere/plane and sphere/sphere special cases return
interior contacts. Missed: c3n_parab_apex, c3n_parab_parab_apex, c3n_four_points,
c3_sphere_torus_outer, y23_fig1g (both tangent contacts; the shared cone point at the apex is
found), y23_t2_ex6, y23_t6_ex4. Found: y23_fig1b (a contact between two transversal branches).
Why, in the code: find_interior_curves drops a leaf pair whose normals are within 5° at the seed or
2° at the solution (§4.2), which is every seed near a touch point.
Look at the four interior contacts in the parameter-space panels: none is in the output. From #17.
Look at the close-up: the single tangential point has no output point. From #17.
Gap 2: tiny loops above the zone tolerance are missed. Loops of diameter 2e-4 and 2e-5 on a model
of size 2. On a NURBS paraboloid nothing is returned (2 of 26 configurations find the 2e-4 loop); on
the analytic sphere a single point is returned (adv/basic at 1e-6 find the 2e-4 loop, 4 of 26).
Why: every seed of a small loop between nearly tangent surfaces is below the same 5°/2° thresholds,
and the sphere's closed form took a small circle for a touch point.
Look at the close-up: the 2e-4 loop is there in the truth and absent from the output. From #17.
Gap 3: curves are not split at singular crossing points. The point set is complete, but curves run
through the crossing without ending there: c4_steinmetz (1 of 2 crossings), c4_viviani_figure8,
c4n_viviani_nurbs, y23_fig1a, y23_t6_ex3, and the published y23_t3_ex4 torus/ellipsoid pair.
Why: compute_singularity_point runs on start points, not on points the march passes, so the marcher
steps straight through a branch point (§4.4).
Look at the marked crossing: an output curve runs through it without ending. From #17.
Look at the two crossings of the loops: neither ends a curve. From #17.
Gap 4: tangent and high-order contact curves.
- Open tangent curves come back twice, once in each direction:
c2n_cylN_on_planeN,y23_t6_ex1(also 3.4e-5 off the surfaces),y23_t2_ex4(an inflected tangent curve, 58% overlap, 3.4e-5 off; 7 of 26 configurations pass). - Tangent curves collapse to their two end points:
y23_t2_ex1(straight second-order contact) andy23_fig1f(second-order contact where the sheets cross). - A closed tangent circle on a quartic is missed:
c2n_quartic_circle. - Partial tangent curves:
y23_t2_ex2covered at 23% (6 of 26 pass, all at 1e-4);y23_t2_ex3, a loop and two curves meeting at four tangent nodes, covered at 8%; the tangential middle edge ofy23_fig1dcovered at 1%; one boundary curve ofy23_t6_ex6covered at 88%, another returned seven times. SM_ERR_NULL_POINTERfromReverseCurveDirection(), called byDoIntersection:c2n_quartic_contact_parabola(fourth-order contact) andy23_t2_ex5(two loops, one tangential along its whole length) at 1e-6; at 1e-4 and the default tolerancey23_t2_ex5returns without error but incomplete.
Why: the predictor steps along n1 × n2, which is zero along a tangent curve, and the corrector breaks down there; a tangent point is accepted as a tangent curve only when the principal curvatures match (§4.4); each cluster of tangent boundary start points starts its own trace; and a trace that comes back empty is reversed.
Look at the output list: the one tangent line is returned once in each direction. From #17.
Look at the close-up: the duplicated tangent line sits off the exact curve. From #17.
Look at the output: two end points where a whole tangent line should be. From #17.
Look at the contact curve: only its two ends are returned. From #17.
Look at the coverage: the output follows under a quarter of the tangent curve. From #17.
Look at the status line: the intersection fails instead of returning the contact parabola. From #17.
Look at the tangential loop: nothing of it comes back at 1e-6. From #17.
3.4 Reconstructions against the paper
Yang, Jia and Yan [16] publish no geometry for their Fig. 1, Table 2 and Table 6 examples, so #17 rebuilt them: each was fitted to the paper's figure through a camera recovered from the figure itself, keeping the stated degrees, topology and contact type, with the exact intersection known in closed form. An independent audit against the paper found 15 of 19 faithful in shape and topology and 4 matching in topology with a documented shape difference. The published-data cases are the Table 3/4 torus/ellipsoid pairs and appendix Example 2. Each image has three columns: the paper's figure, the case rendered from the fitted camera, and the two overlaid; the top row is the paper's surfaces panel and the bottom row its result panel.
Look at the overlay column: the rebuilt surfaces and loops sit on the paper's figure. From #17.
Look at the result panel: the rebuilt contact curve matches the paper's. From #17.
Look at the overlay: both loops, the tangential one included, match the paper's. From #17.
Look at the four boundary curves in the result panel against the paper's. From #17.
3.5 The proposal, and what came of it
#17 proposed three steps:
- The corpus becomes fixtures for the
intersect.*units: per case, the two surfaces, the exact truth (implicit or parametric) and the expected branches, isolated points, singular points and coincident regions with a coverage tolerance. The unit runners use only the surfaces, and their goldens record the results. - A separate ground-truth acceptance test that scores the port's output against the exact truth per case, and does not gate a unit's green status (a green unit must reproduce the gaps).
- Fixes after parity: each gap its own change, gated by the acceptance test going from fail to pass with no regression elsewhere in the corpus.
What came of it, from #17's thread:
- The corpus is public at jyviko/ssi-corpus (MIT OR Apache-2.0): the 119 case files with exact truth, the generators, the scorer, and the SMLib runner as the runner contract's example.
goldens --ssi-corpusis step 2. It scored the C++ at 81/119, matching #17, and its scorer agrees with the corpus'sscore.pyon 119/119 verdicts (§9). The 38 C++ misses are logged as WR-74 to WR-80, the fixes of §4.4.- #17 also recommends the method of Yang, Jia and Yan [16] for completeness, since neither SMLib nor SolidCraft's earlier kernel completed the cases reported in the literature.
- Step 3's policy has since been replaced: fixes change the default result, with no compatibility
flag, and the existing
UpstreamFixesflags are to be retired (AGENTS.md §4b, 2026-11-04). The WR-80 fix is the default; the WR-74 to WR-79 fixes still sit behind flags (§4.4), which is open work.
4. The pipeline
The problem: given S1(u, v) and S2(s, t), find the set S1(u, v) = S2(s, t) inside both domains, as curves and isolated points, each curve with both pcurves. In four unknowns with three equations the generic solution is a curve; where the normals n1 and n2 are parallel, the system loses rank and the curve may stop, branch, or widen into a region [11].
Every method has the same three parts [11, 1]: find at least one point on every component (start points), follow each component (marching), and handle the points where following fails (tangency, branch points, coincidence). Subdivision alone is complete but slow and fragments curves; marching alone is fast and accurate but can miss closed loops [2]. Methods that find the topology first, from the critical points of the problem, are the other family [7, 5]. Both implementations here subdivide for completeness and march for accuracy.
Figure 1. Look at the start points: one on S1's boundary for C, one inside a leaf pair for the closed loop L; from each, the march predicts along n1 × n2 and corrects onto both surfaces.
4.1 Dispatch: special cases first
Surface::global_surface_intersect (solidcore/src/surface/surface/p2.rs) matches on the first
surface's kind. A plane, cone (cylinders are cones of zero semi-angle), sphere or torus tries its
closed form against the other surface's kind first (§5). Each special case returns its curves and a
flag saying whether the general intersector is still needed; when it is, do_intersection
(intersect/surface_intersector/p5.rs) runs on an AdvSurfaceIntersector and resets the output
first. Any other pair, and every B-spline, procedural blend, offset or curve-bounded surface, goes
straight to the general intersector.
ssi orders its own paths the same way (ssi/src/intersect.rs, intersect_bodies): closed forms for
a plane against a sphere or a degree-2 form (quadric), then the field path where one side has an
implicit equation (trace2d), then subdivision and marching, then local Taylor fields near
tangencies (taylor).
4.2 Start points
In the port (do_intersection_with_caches, intersect/surface_intersector/p5.rs):
- Boxes. If the root boxes of the two surface caches' Bézier decomposition trees are apart by
more than the tolerance, there is no intersection. Otherwise a global solve
(
SM_SO_INTERSECTION_TEST) collects the leaf pairs whose patches may meet. - A thin box. One leaf pair whose box is thinner than twice the tolerance in some dimension goes
to
grazing_box_process: a corner pair of the two domains closer than the tolerance, with tangents that do not point into each other, is a touching point and ends the search. - Boundary planes.
do_boundary_plane_test(p2.rs) compares the planar boundaries of the two control nets: every sample of each boundary beyond the other's plane proves the surfaces apart; a pair just touching is a graze, answered by intersecting the two boundary curves. - Boundary start points.
find_boundary_start_points(p3.rs) intersects each surface's natural boundary curves with the other surface. Each start point is traced (§4.3), backwards too when the curve is open and starts along a boundary. - Interior start points.
find_interior_curves(adv_surface_intersector.rs) takes one candidate per intersecting leaf pair from the global solve and traces it when the normals are at least 5 degrees apart at the guess and 2 degrees at the solution, and the point is not on a curve found already. This is the loop detector of the port: a closed loop is found when some leaf pair seeds it.
The 5 and 2 degree thresholds are where interior loops go missing: a small loop between nearly
tangent surfaces has every seed below them. ssi_low_angle_start_on_probation (WR-79) traces such
seeds on probation and keeps the trace only as a closed crossing loop larger than twice the tolerance.
In ssi (ssi/src/intersect.rs, piece.rs) completeness is proved instead of seeded:
- each B-spline is cut into rational Bézier pieces by knot insertion [12];
- piece pairs are halved until each is apart (boxes, or slabs across a normal cone) or loop-free: their normal cones hold no parallel pair, so no curve closes inside the pair and every curve has both ends on the pair's boundary (Sederberg and Meyers's loop test [13]);
- in a loop-free pair, the boundary curves of each piece are intersected with the other piece: those points are the seeds, and they reach every arc.
A pair still neither apart nor loop-free when both pieces are smaller than Options::resolution
(1e-5) is a tangency, a coincidence or a degenerate piece at the crossing, and is refused by name
unless the field or Taylor path resolves it (§4.4).
4.3 Marching
Port (intersect/surface_intersector/p3.rs, compute_next_point; p4.rs, trace_curve):
- Predictor. A step along the point's UV tangents, the pullbacks of n1 × n2, clipped to both
domains. The first step is
100 × approx_tol_3d(compute_step_size,p1.rs). - Corrector.
refine_point(p1.rs) solves back onto both surfaces, at the approximation tolerance for crossing points and ten times that at tangent and singular points (Surface::local_surface_intersect,surface/surface/p11.rs, with a plane through the guess). - Span test.
test_span_accuracy(p1.rs) accepts the step when the tangent turns by less than the angle tolerance and, at five interior parameters, the 3D Hermite span and both UV Hermite spans mapped onto their surfaces agree withinapprox_tol_3d. A rejected span halves the step; a span well inside both limits (an eighth of the tolerance, a quarter of the angle) grows it by 1.5. - End. The trace stops at a domain boundary, at a through, start or end point it snaps to
(
test_point), on reaching a curve already found, or after 5000 points (an error). - Output.
flush_curve(p2.rs) writes the points as a piecewise cubic C0 B-spline in 3D and in each UV domain, one knot vector for all three, with the curve type and the largest deviation seen.
ssi marches each arc of a loop-free pair from its seed to its exit with a predictor along
nA × nB and a Newton corrector held to a plane across the curve; Newton stops at a residual of
Options::tolerance (1e-11) scaled by the sine of the angle between the surfaces, with steps of at
most max_step (0.02) and turns of at most max_turn (0.06 rad). Arcs are oriented along nA × nB
and joined end to start when their ends are within link (1e-7). Orientation: with both bodies'
normals pointing out, the part of A inside B is to the left of the curve seen from outside A.
solidcore-ssi/src/curves.rs turns the points into one 3D and two UV cubic Hermite curves on one
knot vector, the 3D tangent along n1 × n2 and each UV tangent its pullback through that surface's
Jacobian, and adds points on every knot line crossed and in spans whose measured deviation is over
the tolerance (up to six rounds). Near a domain edge or a pole the UV tangents are limited so each
coordinate stays monotone where its points are (Fritsch and Carlson's condition [4]).
4.4 Turning points and singularities
Where n1 ∥ n2 the marching direction n1 × n2 vanishes. The local picture is the difference of the two surfaces' second fundamental forms in the common tangent plane, read through the two Dupin indicatrices [11]: if the difference is definite the contact is an isolated touch point; if it is indefinite, two branches cross at a branch point (both surfaces cross along two curves); if it is semidefinite, the surfaces may be tangent along a tangent curve; if it vanishes identically, the contact is of higher order or the surfaces coincide there.
Figure 2. Look at where the normals are parallel: along the whole tangent curve (left) there is no marching direction; at the branch point B (right) there are four, and both branches end there.
Port. AdvSurfaceIntersector::compute_singularity_point (adv_surface_intersector.rs) classifies
a point with parallel normals from the two Dupin indicatrices: no common direction is a tangent
point, two a tangent curve, more a singularity whose directions are the indicatrices' intersections
(preferring one into both domains at a start, else the one closest to the previous direction).
Directions count as one only when they are the same one, to about 0.08°: upstream's 25.8° window read
two close branches as a tangent curve (#2695).
Equal umbilical curvatures are a coincidence. It runs on start points and on points the corrector
lands on, not on every point the marcher passes, so the base marcher steps straight through a branch
point. The fork's fixes, all off by default:
| flag (record, issue) | what it adds | file |
|---|---|---|
ssi_split_curves_at_singular_points (WR-77, #393) |
samples |n1 × n2| along each crossing curve, refines each sharp minimum by golden section, and splits the curve where the sine is below 1e-6 and rises above 1e-3 on both sides, more than ten approximation tolerances from its ends | surface_intersector/singular_splits.rs |
ssi_tangent_curve_tracer (WR-75, #395) |
traces tangent curves the marcher cannot follow: Levenberg-Marquardt on S1 − S2 = 0 with n1 ∥ n2, directions probed on a small circle in the tangent plane; traces crossing branches through tangent contact nodes and reports the nodes | surface_intersector/tangent_curves.rs, tangent_tracer.rs |
ssi_tangent_contact_points (WR-74, #394) |
Newton on a 4 × 4 system (the gap along S2's tangents, S1's tangents against n2) from the leaf pairs the interior search skips, plus the poles; a root is a touch point when the gap is below 1e-7 of the tolerance and the normals agree to 1e-4 rad | adv_surface_intersector/tangent_contacts.rs |
ssi_trace_tangent_curves_once (WR-76, #392) |
one start point per cluster of tangent boundary start points, so an open tangent curve is traced once | surface_intersector/p5.rs |
ssi_singular_start_inside_direction (WR-512) |
a curve starting at a singularity leaves along a direction into both domains | adv_surface_intersector.rs |
ssi_pole_point_keeps_its_tangent (WR-550) |
a point at a pole keeps its exact limit tangent when stepping back | surface_intersector/p1.rs |
ssi, the field path (ssi/src/trace2d.rs, field.rs). Where one patch lies on a surface with
an implicit equation f (an analytic surface, or a B-spline patch whose exact implicit form
algebraic.rs finds, §6.1), the intersection is the zero set of g = f(P(s, t)) on the other patch's
own parameters. On a rational piece, g is a tensor Bernstein polynomial, W^k f(X / W), with the sign
of f, and its Bernstein coefficients bound it there [3, 14]. Each piece's square is cut into cells:
- g of one sign: no intersection there, proved by the coefficients;
- ∂g/∂t of one sign: the zero set is a graph t(s), each point a bracketed root, so a trace cannot jump branches (and alike with s and t swapped);
- neither: the cell may hold a critical point of g. When the Hessian's determinant has one sign over the cell and ∇g winds once round its boundary, the cell holds exactly one nondegenerate critical point: an extremum at zero is a touch point, a saddle at zero is a branch point;
- where g is convex along one parameter's lines, or its third or fourth derivative along them has one sign, the curve of its extrema is a tangent curve, two branches, or none, proved on its chords;
- otherwise the cell is halved, down to the resolution, where nothing proved is refused.
Where neither patch has an implicit form, taylor.rs builds a local field: the separation λ(s, t) of
A from B along a fixed direction, a Taylor polynomial with proved bounds on its value's, gradient's
and Hessian's errors, read by the same cell tests. The polynomial proves; every point is still solved
on the surfaces themselves.
ssi joins branches straight through their crossings and reports the crossings as vertices beside
the curves (Intersection::vertices in solidcore-ssi); a B-rep builder splits its edges there.
4.5 Closed loops and coincident regions
A closed loop has no boundary start point. The port finds it from a leaf pair's seed (§4.2, step 5)
and closes the trace when it returns to its own start. ssi cannot miss it: in a loop-free pair a
loop cannot close, so subdivision halves until every loop crosses some pair's boundary and is seeded
there.
Coincident patches share a region, and the region's boundary is the intersection
(TsectCurveType::RegionBoundary). The port answers parallel coincident planes, coaxial surfaces of
revolution and parallel cylinders in their closed forms (§5); a general coincident pair leaves the
marcher with parallel normals everywhere. ssi/src/coincide.rs reads coincidence off the composed
field (every coefficient zero), then builds the boundary from each patch's edges inside the other,
deciding inside by inverting each edge point onto the other patch and comparing parameters, never
distances. In the boolean, faces known coincident are handled before SSI: face_intersect runs the
coincidence check first and returns the faces' own edge curves trimmed to each other.
4.6 What the boolean takes from SSI
face_intersect (topology/face/p3.rs) calls global_surface_intersect_with_context over the two
faces' UV domains with the curve angle tolerance, then for each curve:
- a crossing curve that bounds a cap within the tolerance is a touch at the B-rep's tolerance: at each
of eight points the surfaces' crossing angle θ over twice the curve's curvature κ (the cap's height
to second order: r² / 2R for a sphere cut in a circle of radius r) is at most the tolerance, its
centre of curvature lies within the tolerance of both surfaces (which a tangent curve's does not),
and the curve becomes a touch point at that centre, once per cap (
cap_centre_within_tolerance, WR-79, #2412). An edge there would join faces within tolerance of each other across a band tol / θ wide, wider than the cap, which the merge pairs as coincident; - a degenerate curve (a touch point) is classified at its point on both faces and kept as a point when it is on or inside both;
- every other curve is classified on each face (
curve_on_classify_mut), with its UV curve when SSI gave one, and the intervals inside or on both faces are kept.
The topology intersector (boolean/topology_intersector/p1.rs, face_face_intersect) imprints each
kept interval as an edge in each face and glues the two. The boolean consumes the 3D curves, the UV
curves (which become the imprinted edges' trims, #489), the curve types and the points. It does not
read deviations. A curve from a closed form has no UV curves (surface/cone/p3.rs, add: None
on both sides, deviation 0); its trims are made later by dropping the 3D curve onto each surface
(Surface::drop_curve, intersect/surface_drop_lib), which traces and fits a cubic within the drop
tolerance. So an exact edge can carry fitted trims; §5 lists the exact pcurves that replace them.
5. The exact classes
A pair of natural quadrics (plane, sphere, circular cylinder and cone) meets in a curve of degree at most 4, and in conics and lines for the configurations that are classically degenerate: a plane with any quadric, coaxial quadrics, parallel cylinders, and equal cylinders with meeting axes [9, 8]. These are the pairs solved in closed form; every other pair is traced.
| pair | configuration | result | code |
|---|---|---|---|
| plane/plane | not parallel | a line, or its segment in both domains | surface/plane/p2.rs, Plane::intersect_with_plane |
| plane/plane | parallel apart; coincident | nothing; the general intersector (coincident) | same |
| plane/cylinder | parallel to the axis | two lines, one tangent line, or nothing, from |d| against R | surface/cone/p3.rs, Cone::intersect_with_plane |
| plane/cylinder or cone | square to the axis | a circle | same, Plane::make_new_plane_ellipse |
| plane/cylinder | oblique | an ellipse | same |
| plane/cone | oblique, steeper than the semi-angle | an ellipse | same |
| plane/cone | through the apex, along the axis | two lines | same |
| plane/cone | parabola or hyperbola section, or parallel off-axis | the general intersector | same |
| plane/sphere | |d| < R | a circle; a touch point at |d| = R | surface/sphere.rs, Sphere::intersect_with_plane |
| sphere/sphere | not concentric | a circle, or a touch point | Sphere::intersect_with_sphere |
| sphere/cone | centre on the cone's axis | circles of the cone | Sphere::intersect_with_cone |
| cylinder/cylinder | parallel axes | lines from the circle/circle section | Cone::intersect_with_cone |
| cylinder/cylinder | equal radii, axes meeting | two ellipses | same |
| plane/surface of revolution | plane through the axis; square to it | the generator rotated in; circles | surface/surf_of_revolution/p1.rs |
| revolution/revolution | same axis, planar generators | circles at the generators' intersections | surf_of_revolution/p3.rs |
| plane/extrusion, extrusion/extrusion or cylinder | extrusion parallel to the plane or to the other | lines along the extrusion | surface/surf_of_extrusion/p2.rs |
| plane/torus | square to the axis; through it | circles; meridian circles | surface/torus.rs (plane side: surface/plane/p2.rs, WR-721) |
How each is made exactly: the curve's defining data (a centre, axes and radii, or a line's points)
are computed from the two surfaces' frames, and the curve is stored as its rational form (an
Ellipse, a circle or a line), then trimmed to the domains. Nothing is sampled.
Figure 3. Look at the angle θ between the axis and the plane's normal: it alone decides whether the exact section is a circle, an ellipse with semi-axes R and R / cos θ, or lines along the axis.
Plane/cylinder in detail (Cone::intersect_with_plane). With a the axis, n the plane's normal and
θ their angle:
- θ within
100 · EFF_ZERO · 360of 90° (plane parallel to the axis): d is the distance from the axis to the plane. |d| > R + tol gives nothing; |d| ≥ R, or two lines closer than the tolerance, is oneTangentline; otherwise twoCrossinglines at ±√(R² − d²) across the axis, each kept if its angle lies in the cylinder's sweep. |d| > R − zone tolerance of either face is also tangent (WR-510), so a plane a hair off a wall does not cut it in two lines microns apart; when that tangent line is outside the sweep, the crossings are tested instead. - θ near 0 (plane square to the axis): the circle of radius R about the axis point on the plane.
- otherwise: the ellipse, centre where the axis meets the plane, built from the two generator lines in the plane spanned by a and n.
ssi's closed forms (ssi/src/quadric.rs). A plane and a sphere meet in the circle about the
foot of the centre of radius √((R − |d|)(R + |d|)), d a compensated dot product, so the difference
R − |d| is exact where they are close [15] and a loop of radius 1e-7 keeps its radius
(c7_tiny_loop_sphere_1e-7). A plane and a patch whose exact implicit form has degree 2 meet in the
conic of the form restricted to the plane; where that conic's quadratic part is definite it is an
ellipse, sized from one evaluation of the form at its centre, told from rounding by the form's own
bound (c7_tiny_loop_parab_1e-7). Other conics take the field path.
Exact pcurves. A plane section on a cylinder has an exact UV curve: on one rational quadratic
segment of the cylinder's circle, s / (1 − s) is a ratio of affine functions of the point, so a
rational curve of degree p on the cylinder has a rational parameter s(t) of degree p and an axial
height that is a polynomial over its weight. surface_drop_lib/cylinder_section.rs builds it in the
curve's own parameter with twice its degree (WR-680); a curve it does not apply to is traced. On a
plane every curve's UV image is the same curve in the plane's frame.
Other exact pcurves (sphere, cone, torus sections) are open work.
6. Certification and tolerances
6.1 What is certified today
| object | port | ssi |
|---|---|---|
| a closed-form curve (§5) | exact by construction, deviation 0 | exact by construction |
| a point on a traced curve | a Newton root within approx_tol_3d |
a Newton root within 1e-11 × sine of the angle, or a bracketed root of the field |
| "no intersection here" | box and boundary-plane tests | boxes, slabs, normal cones, field coefficients of one sign |
| "this cell has one touch or crossing" | not proved | Hessian sign plus gradient winding (degree theory) |
| a tangent curve | not proved | the field's valley proved on its chords |
| an implicit form for a B-spline | none | composed with every piece, every coefficient within 1e-11 of the terms' scale (algebraic::PROOF) |
| the curve between its points | five interior samples per span (test_span_accuracy) |
solidcore-ssi measures the deviation at its points and three interior points a span; "not yet a proved bound" (its README) |
So both implementations prove where the curve is (the points) and ssi proves the topology, but
neither proves the B-spline between its points. The certificate the one rule asks for is the one the
blend's net carries (generic-blend.md §2.4,
definitions-and-certificates.md): the curve's error against the
intersection, known at the span's grid points to machine precision and bounded between them by the
interpolation remainder with derivatives enclosed over the span box, with the root's existence and
uniqueness in the box proved by the Krawczyk test [10]. The intersection curve is a definition in
Hoffmann's sense [6]: the system S1(u, v) − S2(s, t) = 0 with a normalizing equation, evaluated on
demand. A sampled check is not a certificate; where interval bounds over-estimate, the remedy is a
tighter enclosure, never a sample.
6.2 Tolerances
| name | value | where | used for |
|---|---|---|---|
zone_tol_3d |
1e-5 | Kernel::tolerances (kernel.rs), geom/tol.rs |
points closer than this are one point; the B-rep's base tolerance |
xsect_tol_3d |
2e-5 | same | default gap and intersection tolerance |
approx_tol_3d |
5e-6 | same | default curve approximation tolerance; the corrector's and the span test's tolerance |
ang_tol_deg |
0.5° | same | the span test's turn limit by default |
EFF_ZERO, EFF_ZERO_SQRT |
1e-12, 1e-6 | math.rs |
zero tests; the closed forms' tolerance when none is given |
| a face's tolerance | per face | the B-rep | plane boxes in the closed forms are expanded by it (PlaneOwner) |
| interior seed angles | 5°, 2° | adv_surface_intersector.rs |
below them a leaf pair's seed is skipped |
| corpus approximation tolerance | 1e-6 | goldens --ssi-corpus |
the tolerance passed to each engine |
| corpus accuracy tolerance | 1e-5 (10 × tol) | goldens/src/ssi/score.rs |
a curve farther than this from an exact surface is INACCURATE |
ssi Newton |
1e-11 | ssi::Options::tolerance |
residual, scaled by the sine of the angle |
ssi link, resolution |
1e-7, 1e-5 | ssi::Options |
arc ends joined; pieces below this unresolved are refused |
ssi field band |
1e-14 of the field's scale | trace2d.rs, CLEAR |
a value within it is zero to rounding |
ssi implicit form |
1e-13, 1e-11 | algebraic.rs, EXACT, PROOF |
least singular value at rounding; proof by composition |
The zone tolerance is the B-rep's: two points, edges or faces within it are one. An intersection curve must lie within the tolerance of both faces it joins, and its edge's tolerance grows to its largest deviation; a curve whose own error is a large fraction of the zone tolerance leaves no margin for the edges and vertices built on it (the gaps of #2537 and #2538 are 7e-6 to 1e-5 against tolerances of 7.5e-6). That is why the general curve's bound should sit far below the zone tolerance, and why the approximation tolerance is half of it.
7. Failure modes
| mode | what goes wrong | port today | ssi |
open |
|---|---|---|---|---|
| tangent curve (#17 gap 4) | n1 × n2 = 0 along it; the marcher has no direction | collapses to its end points (#395); fixed with ssi_tangent_curve_tracer |
a proved valley of the field, or of a Taylor field | flag on by default |
| touch point inside both faces (#17 gap 1) | no boundary start, normals within 5° at every seed | missed (#394); found with ssi_tangent_contact_points |
a proved extremum of g at zero | flag on by default |
| branch point (#17 gap 3) | the marcher steps straight through; one curve where two end | wrong topology (#393); split with ssi_split_curves_at_singular_points |
a proved saddle; branches joined through it, vertex reported | #393 |
| small loop, nearly tangent (#17 gap 2) | every seed below the 5°/2° thresholds | missed (#396); found with ssi_low_angle_start_on_probation |
loop-free pairs cannot hide it; closed form for plane/sphere and degree-2 | flag on by default |
| small circle taken as a point | gap to tangency within tolerance though the circle is not | fixed (WR-79): the sphere's closed forms collapse a circle only when its radius is within the tolerance; FaceIntersect takes a circle whose cap lies within the tolerance as a touch (§4.6) |
the radius from √((R − |d|)(R + |d|)) | |
| coincident faces | normals parallel everywhere | closed forms for planes, coaxial revolutions, parallel cylinders; else the boolean's coincidence check before SSI | region boundary from the field's zero coefficients | |
| near-coincident strip | two planes within tolerance across a strip; the crossing is fixed only to tol / sin θ | the crossing is dropped (WR-614, by default) | patches 1e-8 apart, separating only at second order: refused (y23_fig1d_two_quads) |
|
| grazing | boxes just touch; a boundary meets the other surface tangentially | grazing_box_process, do_boundary_plane_test |
the pair is apart or refused | |
| plane a hair off a cylinder wall | two lines microns apart instead of one tangent line | fixed (WR-510): within either face's zone tolerance it is one tangent line when that line lies in the face's sweep, else the crossings are tested | ||
| near-miss seam | an intersection runs within tolerance of a closed surface's seam | the curve and its trims end near but not on the seam: SmFace::Test #36 "Face Near missing Seam", or make_faceuse 1001 |
curves split at the seam by solidcore-ssi |
#2538, #2537, cause not traced |
| curve across a pole | the UV coordinate along a collapsed edge names no point | ssi_pole_point_keeps_its_tangent; a trace point on the degenerate boundary is the pole, with the pole's foot on the other surface (place_at_pole), and its singular parameter is the limit along the curve's tangent (pole_singular_parameter), also where the pole lies only within the tolerance of the curve |
split at the pole; placed by its approach; the pole divided out of the field | |
| empty trace reversed (#17 gap 4) | SM_ERR_NULL_POINTER |
not traced back: a trace that produced no points is not reversed (WR-80, surface_intersector/p4.rs, p5.rs) |
adv_surface_intersector.rs's own trace-back is unchecked; no known case reaches it (WR-80 addendum 2026-11-05) |
|
| trace that never ends | 5000 points, or a creeping boundary trace | an error at 5000; ssi_start_loop_trace_budget bounds all start points |
Error::Unfinished at 100,000 steps; pair budget 2,000,000 |
Refusals. ssi refuses by name: BOOLEAN_TANGENT (a contact it cannot prove), BAD_PATCH,
MARCHING_STALLED, EXIT_DIVERGED, ARC_UNFINISHED (ssi/src/lib.rs). A refusal names where. The
port returns SolidError statuses; a refusal there is an error code, and an unported callee is
SolidError::Unported, which the corpus report scores as DEFERRED, never as a result.
Near-miss seams (#2538, #2537). A box cut by a cylinder tilted 4° whose axis lies in a box face, or
whose end cap is bisected by a face, fails the B-rep check with SmFace::Test #36 or a 1001 in
make_faceuse; turning the cylinder's seam away from the material, the mirror image, or a cylinder
1 µm longer builds. The intersection curves pass within the zone tolerance of the cylinder's seam
line and of the cap's edge. The cause has not been traced; the place to look is where the
intersection's curve meets the seam (§4.6: the curve's trims and the seam split) rather than the
check. Whatever the fix, an intersection curve crossing a closed surface's seam must be split on the
seam with both pieces' trims in the domain, which solidcore-ssi does and the port leaves to the
drop.
8. Invariants
A reviewer can check each of these on a change to SSI.
- A pair of §5 is answered in closed form: the 3D curve is a line, circle or ellipse stored as such, with deviation 0.
- No point of an output curve is chosen by thresholding distances at sample points; every point is a root of the intersection system or a bracketed root of a field.
- No region is dropped except by a proof: disjoint boxes or slabs, a field of one sign, a boundary plane test.
- Every output curve lies within its reported deviation of both surfaces, and its UV curves map onto its 3D curve within that deviation.
- The 3D curve and its UV curves share one parameter and one knot vector.
- Every UV curve stays inside its surface's domain; a curve crossing a closed surface's seam is split there.
- A curve ends at every branch point it reaches, and the branch point is reported as a point.
- A touch point is a degenerate curve of type
Touching(or an isolated point), never dropped and never a short curve. - Every closed component is found: a loop inside both domains has a seed, or its absence is proved.
- Coincident regions come back as their boundary, typed
RegionBoundaryorCoincident. - No curve is reported twice, and no degenerate curve lies on another curve.
- A contact that cannot be resolved is refused by name with where; nothing is approximated to get past it.
- The same input gives bit-identical output, independent of thread count.
- A change's tests check values a wrong result changes: corpus verdicts, distances to the exact curve, counts of curves and points, exact volumes in the boolean. Never timings.
- A change to ported code that is not a fix keeps the goldens of its
intersect.*units bit for bit; a fix changes the default result, blesses the goldens it moves and lists what changed (AGENTS.md §1, §4b).
9. Status
The SSI corpus. goldens --ssi-corpus scores the engines on jyviko's corpus
(ssi-corpus, commit b04c8a6): 119 cases (planes,
quadrics, tori, B-spline and NURBS pairs, 25 from Yang, Jia and Yan [16]), each with the dense exact
intersection and its topology, at approximation tolerance 1e-6 (crates/goldens/README.md).
| engine | pass (of 119) | not passing | source |
|---|---|---|---|
| C++ reference (the pinned SMLib) | 81 | 25 incomplete, 11 wrong topology, 2 error | reports/ssi-corpus.json (2026-10-22) |
| the port, default flags | 78 | 21 incomplete, 10 wrong topology, 1 error, 9 deferred | same; parity with the C++ 110 of 110 compared |
ssi, points as marched |
118 | 1 refused (y23_fig1d_two_quads) |
crates/ssi/README.md |
solidcore-ssi, B-spline curves |
118 | the same refusal | crates/solidcore-ssi/README.md |
The C++ row reproduces #17's primary run (§3.2). The port's column is the pinned behaviour:
its fixes (§4.4, §7) are off in the corpus run. The one
case no engine passes, y23_fig1d_two_quads, is two patches 1e-8 apart that separate only at second
order in places, below the 4.4e-13 their degree-9 form is exact to.
Port units. The goldens units are intersect.surface_intersector_p1 to _p5,
intersect.adv_surface_intersector, intersect.surface_tracer, intersect.surface_drop_lib_p1 to
_p3, intersect.surface_drop_curve, intersect.curve_class_p1 to _p9, intersect.point_class,
intersect.topology_solver_p1 and _p2, intersect.api_intersectors, intersect.ray_tracer,
intersect.surface_silhouette (plan/units/), and boolean.topology_intersector_p1 to _p3 for the
boolean's side.
Open issues.
| issue | topic |
|---|---|
| #17 | the corpus and the measured gaps of the upstream intersector |
| #391 | an empty trace reversed (SM_ERR_NULL_POINTER); fixed on the default path |
| #393 | curves not split at singular crossing points; fix behind a flag |
| #395 | tangent and high-order contact curves missed or collapsed; fix behind a flag |
| #1552 | how solidcore-ssi is wired into the kernel |
| #2479 | pcurves end one ulp past their face's domain |
| #2537 | a tilted cylinder whose cap is bisected by a box face is refused (#36) |
| #2538 | a notch cylinder along a slanted box edge fails (1001, #36) |
Work that has no issue yet, to be filed before it is claimed: a certified bound for general intersection curves (§6.1); exact pcurves for sphere, cone and torus sections; the merge of the two implementations (layers.md §2, step 4 of its order).
10. If you're stuck
- Reproduce at the surface level first. Pull the two faces' surfaces and domains out of the
failing operation and call
Surface::global_surface_intersecton them alone. A boolean failure whose intersection is right is a classification or imprint bug (§4.6), not an SSI bug. - Run the same pair through
ssi.solidcore_ssi::intersecttakes the same inputs (Engine::Ssi,Engine::Port). Ifssigets it and the port does not, compare curve counts, types and end points; if both disagree with your expectation, check the expectation. - Find the case's class in #17's table (§3.2) and its gap (§3.3). A transversal pair that fails is rare and worth a corpus case; a contact pair is one of the four gaps.
- Check the class. Is the pair in §5? Then a traced curve is the wrong path: find why dispatch missed the closed form (a NURB that lost its analytic identity, a cone that is really a cylinder, a frame off by rounding).
- Look at n1 × n2 along the curve. Near zero anywhere means §4.4: a tangency, a branch point, a coincident patch. Most wrong-topology results are here.
- Look at the seam and poles. A curve within the zone tolerance of a closed surface's seam, or through a pole, is §7's near-miss seam or pole row.
- Check the flags. The port's fixes are off by default; SolidCraft runs with most of them on. A
result that differs between the two is usually a flag (
UpstreamFixes::flagslists them). - Use the corpus.
goldens --ssi-corpus 'c3_*'runs a class; the per-case JSON inreports/has each verdict's distances and topology signature. Add a corpus-style case for your pair when none exists. - Read the literature before designing. The survey chapters of [11] and the papers below cover nearly every failure here.
- Fix the cause. No retry with a smaller step, no second method tried after the first fails, no loosened tolerance. A failure that several cases share has one cause.
11. References
Each entry was checked against its publisher's record; the DOI is given.
- R. E. Barnhill, G. Farin, M. Jordan, B. R. Piper. Surface/surface intersection. Computer Aided Geometric Design 4(1–2) (1987) 3–16. doi:10.1016/0167-8396(87)90020-3
- R. E. Barnhill, S. N. Kersey. A marching method for parametric surface/surface intersection. Computer Aided Geometric Design 7(1–4) (1990) 257–280. doi:10.1016/0167-8396(90)90035-P
- R. T. Farouki. The characterization of parametric surface sections. Computer Vision, Graphics, and Image Processing 33(2) (1986) 209–236. doi:10.1016/0734-189X(86)90115-5
- F. N. Fritsch, R. E. Carlson. Monotone piecewise cubic interpolation. SIAM Journal on Numerical Analysis 17(2) (1980) 238–246. doi:10.1137/0717021
- T. A. Grandine, F. W. Klein. A new approach to the surface intersection problem. Computer Aided Geometric Design 14(2) (1997) 111–134. doi:10.1016/S0167-8396(96)00024-6
- C. M. Hoffmann. A dimensionality paradigm for surface interrogations. Computer Aided Geometric Design 7(6) (1990) 517–532. doi:10.1016/0167-8396(90)90013-H
- G. A. Kriezis, N. M. Patrikalakis, F.-E. Wolter. Topological and differential-equation methods for surface intersections. Computer-Aided Design 24(1) (1992) 41–55. doi:10.1016/0010-4485(92)90090-W
- J. Levin. A parametric algorithm for drawing pictures of solid objects composed of quadric surfaces. Communications of the ACM 19(10) (1976) 555–563. doi:10.1145/360349.360355
- J. R. Miller, R. N. Goldman. Geometric algorithms for detecting and calculating all conic sections in the intersection of any two natural quadric surfaces. Graphical Models and Image Processing 57(1) (1995) 55–66. doi:10.1006/gmip.1995.1006
- R. E. Moore. A test for existence of solutions to nonlinear systems. SIAM Journal on Numerical Analysis 14(4) (1977) 611–615. doi:10.1137/0714040
- N. M. Patrikalakis, T. Maekawa. Shape Interrogation for Computer Aided Design and Manufacturing. Springer (2002). doi:10.1007/978-3-642-04074-0
- L. Piegl, W. Tiller. The NURBS Book, 2nd ed. Springer (1997). doi:10.1007/978-3-642-59223-2
- T. W. Sederberg, R. J. Meyers. Loop detection in surface patch intersections. Computer Aided Geometric Design 5(2) (1988) 161–171. doi:10.1016/0167-8396(88)90029-5
- T. W. Sederberg, T. Nishita. Curve intersection using Bézier clipping. Computer-Aided Design 22(9) (1990) 538–549. doi:10.1016/0010-4485(90)90039-F
- P. H. Sterbenz. Floating-Point Computation. Prentice-Hall (1974); reviewed in SIAM Review 18(1) (1976), doi:10.1137/1018026.
- J. Yang, X. Jia, D.-M. Yan. Topology guaranteed B-spline surface/surface intersection. ACM Transactions on Graphics 42(6), 211 (2023). doi:10.1145/3618349 (cited by #17)















