All notable changes to the cavalier_contours crate will be documented in this file.
Focus of this release is on robustness and performance of polyline offset generation.
The main changes are:
- Invalid slices are detected and tracked in raw offset creation to filter out invalid slices.
- Slices are stitched together using intersect topology rather than global geometric queries.
Both changes improve robustness for heavily overlapping or degenerate inputs. They also remove the
need for slice_join_eps and enable options for handling coincident segments (keep or discard) and
"touching loops" (longest continuation or split) in offset results.
The intersect topology approach may also improve robustness in boolean operations in a future
refactor (not yet updated). The Shape offset algorithm has not yet been updated either.
Other changes avoid or defer square roots and trigonometric work in low-level segment and intersection functions. This significantly improves performance for arc-heavy inputs and raw offsets with many self-intersection points.
The following benchmarks compare release 0.8.0 with this release:
Negative differences mean this release is faster.
| Case | Before (f5fe179) |
After (5d0749c) |
Difference |
|---|---|---|---|
profile1 |
319.45 µs | 202.42 µs | -36.63% |
profile2 |
673.86 µs | 428.44 µs | -36.42% |
profile1_no_arcs |
4.387 ms | 4.025 ms | -8.25% |
profile2_no_arcs |
9.270 ms | 8.562 ms | -7.64% |
floor_plan |
157.83 µs | 158.15 µs | +0.20% |
mechanical_bracket |
105.23 µs | 91.04 µs | -13.48% |
road_centerline |
823.36 µs | 812.69 µs | -1.30% |
bezier_enclosure |
3.481 ms | 3.279 ms | -5.81% |
involute_gear |
4.395 ms | 4.145 ms | -5.69% |
involute_gear_with_arcs |
3.453 ms | 3.300 ms | -4.43% |
pathological1 |
62.825 ms | 18.498 ms | -70.56% |
pathological1_no_arcs |
245.604 ms | 96.917 ms | -60.54% |
invalid_line_zigzag |
9.851 ms | 4.102 ms | -58.36% |
invalid_line_arc_zigzag |
19.296 ms | 5.638 ms | -70.78% |
closed_invalid_runs |
12.569 ms | 6.822 ms | -45.72% |
tapered_link_strip/128 |
890.35 µs | 685.12 µs | -23.05% |
tapered_link_strip/512 |
4.710 ms | 3.444 ms | -26.87% |
tapered_link_strip/2048 |
20.045 ms | 15.642 ms | -21.96% |
tapered_link_strip/4096 |
42.864 ms | 33.826 ms | -21.08% |
- Added
TouchingLoopBehaviorandCoincidentSegmentBehaviorto polyline offset options. The defaults preserve tangent-touching loops and coincident spans. Callers can instead separate tangent-touching loops or discard every coincident raw span. The C FFI exposes matching constants and option fields. - Two new dependencies:
ahashandsmallvec. These provide meaningful speedups and are both small and popular crates.
⚠️ BREAKING: RemovedPlineOffsetOptions::slice_join_epsand the matchingcavc_pline_parallel_offset_ofield. Polyline offset slices now connect through explicit intersection topology and usepos_equal_epsonly to clean repeated positions. The separateShapeOffsetOptions::slice_join_epsfield remains unchanged.⚠️ BREAKING: ReplacedPlineIntersectVisitor::visit_basic_intrandvisit_overlapping_intrwith a singlevisit(PlineIntersect)method. Custom visitors must now match theBasicandOverlappingvariants. The trait also has an optionalfilter_maphook.⚠️ BREAKING: Removed the unused publicPlineVertexVisitorandPlineSegVisitortraits.⚠️ BREAKING: Changedcavc_pline_parallel_offset_o::to_internalto returnOptionwhen behavior values are invalid. The Ccavc_pline_parallel_offsetfunction can now return error code2for an unrecognized behavior value.
- Fixed repeated polyline offsets dropping a valid small-loop span when two loops touch at a tangent point. The default result keeps the full self-touching path, while separate-touch mode returns the two closed loops.
- Fixed parallel offsets containing locally inverted source spans, which could pass the global distance checks to produce invalid outputs (this is also a significant optimization). This fixes the "wifi leaking" pattern with repeated offsets reported (#79).
- Fixed visible arc distortion in the UI at high zoom by using a more accurate shared arc approximation for polyline and raw offset rendering.
- Improved polyline offset stitching by following recorded intersections directly instead of searching for nearby endpoints or sorting temporary candidate lists.
- Reduced time and memory use for offsets with many intersections or coincident segments.
- Sped up common polyline arc operations, including bounds, closest points, splits, lengths, and distance checks.
- Sped up line-line, line-circle, circle-circle, and polyline segment intersections by deferring geometric setup, reusing division results, and simplifying tangent and same-circle arc cases.
- Sped up arc sweep checks by comparing squared boundary distances.
- Sped up round joins in raw offsets.
- Reused temporary storage when finding open-offset end-circle intersections.
- Sped up arc winding checks by avoiding unneeded center and radius calculations.
- Reduced allocations when finding all self-intersections in a polyline.
- Sped up two-polyline intersection duplicate cleanup by sorting candidate segment indexes instead of storing them in hash sets.
- Added Criterion benchmarks for polyline area, polyline segment geometry, raw round joins, raw and final parallel offset creation, offset topology scaling, intersection duplicate cleanup, core intersections, and arc-sweep paths; split the suite into source-aligned modules.
- Added initial AGENTS.md.
- Refactored raw offset slice validation to share common logic between single and dual raw offsets.
- Marked workspace-only algorithm APIs as hidden from generated documentation.
⚠️ BREAKING: Normalized C FFI container counts, lengths, capacities, and indexes tosize_tfromuint32_toruintptr_t, and updated the generated header accordingly.⚠️ BREAKING: ChangedShape::parallel_offsetto borrowShapeOffsetOptionsinstead of taking it by value.⚠️ BREAKING: ChangedShape::stitch_slices_togetherto borrow a slice ofDissectedSlicevalues instead of taking aVecby value.- Changed the public boolean slice pruning helpers under
polyline::internalto use explicit pruning modes, grouped slice boundaries, and a separate intersection lookup helper. - Added
#[must_use]to public functions and types, which may affect users of-D warnings. - Updated
static_aabb2d_indexfrom 2.0 to 2.1.
- Fixed how position epsilon is applied to line-circle intersections so distinct near-tangent intersections are not merged.
- Fixed how position epsilon is applied to arc sweep checks so they behave consistently at different scales and small negative offsets are no longer dropped (#82).
- Improved
parallel_offsetrobustness for repeat-position input by sanitizing repeat vertices before offsetting. - Fixed collapsed near-vertex offset slices that could cause a debug panic (#83).
- Fixed C FFI shape API documentation to reference
shapeparameters andcavc_shape_createinstead of their polyline counterparts. - Fixed the UI polyline editor failing to detect pending changes when polylines were added or removed.
- Updated the UI crate to Rust 1.95.0.
- Updated the UI crate to
egui0.36.1 and matching support crates. - Enabled workspace-wide Clippy pedantic lints and fixed the reported warnings.
- Added a checked-in
cbindgenconfiguration for C header generation. - Updated the GitHub Actions checkout and Rust toolchain actions.
⚠️ BREAKING: Added collapsed area parameter to pline boolean options to allow for pruning collapsed polylines from results. This is only breaking due to struct initialization, if you use default initialization this defaults to no change in behavior (#71).- Added
examplescrate to workspace to demonstrate cavalier_contours functionality (#74).
- Fixed bug in pline segment intersection when two arcs only touch at endpoints at one point, have the same arc radius and center, and are in opposite directions. This also fixes some cases for algorithms that depend on finding interescts (boolean, offset, etc.) (#71).
- Fixed offset slice stitching to use consistent epsilon (
join_eps) when removing repeat vertices, preventing tiny segments at slice boundaries when offsetting polylines with close vertices (#77).
- Added
eguiinteractive demo UI crate and auto deployment to GitHub pages for the demo page is here. - Added
visit_intersects,contains, andscan_for_self_intersectto pline traits (#68). - Added
user_datato traits for tracking data through operations (#63). - Added multi polyline offset algorithm to c ffi (#63).
- Added more doc comments/tests for
PlineSourceandPlineSourceMut. - Added
README.mdfile tocavalier_contours_fficrate.
⚠️ BREAKING: Updated MSRV to 1.88 and Rust edition 2024. Only breaking if unable to compile with Rust 1.88 or later.- Refactored multipolyline offset algorithm to be step-by-step.
- Simplified
prune_slicesfunction in pline_boolean. - Refactored two-polyline intersection visitation to use visitor pattern and eliminated allocation inside loop (#68).
⚠️ BREAKING: Fix memory leak in cavc_plinelist by implementing Drop trait (#64). This is not likely breaking for most users but if you are callingcavc_pline_fon each pline in acavc_plinelistwithout removing them from the list then you will get a double free on the plines not removed when thecavc_plinelistis freed/dropped.- Improved offset slice validation by checking multiple segment midpoints (#69). Fixes bug reported in issue #66.
cargo releasegot so excited it jumped a version! Nothing to see here...
- Added
Shapetype and parallel offset method on shape type to perform simultaneous multi/island polyline parallel offsetting (#7).
⚠️ BREAKING: Removedslice_join_epsfromPlineBooleanOptions. This is breaking for any code that interacts with this option struct directly (does not use defaults).slice_join_epswas also removed from the equivalentcavc_pline_boolean_ostruct in the C FFI. (#31)⚠️ BREAKING:PlineSource::create_aabb_indexandPlineSource::create_approx_aabb_indexnow just return aStaticAABB2DIndexrather than anOption<StaticAABB2DIndex>. The equivalent C api callscavc_pline_create_aabbindexandcavc_pline_create_approx_aabbindexalso no longer return an error code of 2 when polyline has less than 2 vertexes (empty aabb index is returned). (#29)- Updated
StaticAABB2DIndexdependency to version 2.0. - Bumped rust edition to 2021.
- Use
with_capacityinstead ofreservein some places to avoid over allocation behavior of Vec (minor performance improvement).
- Fixed
PlineView::from_slice_pointsto handle wrapping on same segment (#28). - Fixed
line_circle_intraccuracy in cases with nearly vertical line (#30).
- Added
BooleanResultInfoenum used for newresult_infofield onBooleanResult. The enum is used to return information about what happened during the boolean operation. - Added doc tests and improved documentation to intersect functions
circle_circle_intr,line_circle_intr, andline_line_intr(#18). - Added
#![forbid(unsafe_code)]to maincavalier_contourscrate to ensure no use of unsafe (obviously does not apply to thecavalier_contours_fficrate).
- Added epsilon parameter to
PlineSource::closest_pointmethod to allow for consistency with other calculations using epsilon values for fuzzy comparing. line_line_intrfunction now scales parametric t values by segment lengths before fuzzy comparing with epsilon value to avoid introduced error when line segments are very long or very short.pline_seg_intrfunction now scales parametric t values by segment lengths and angle values by arc radii before fuzzy comparing with epsilon value to avoid introduced error when line segments are very long or very short, or arc radius is very small or very large.- Reimplemented
line_circle_intrfunction for improved numerical stability. - Added epsilon parameter to
parametric_from_pointfunction to allow consistency with other calculations using epsilon values for fuzzy comparing. - Reduce error in calculation done in
parametric_from_pointby using larger component difference rather than explicitly checking for vertical line case. - Added epsilon parameter to
point_within_arc_sweepfunction to allow for consistency with other calculations using epsilon values for fuzzy comparing. - Added
#[inline]attribute to all of the small Vector2 and base math functions. - Updated
static_aabb2d_indexcrate to 0.7.0 (for use ofIndexableNum::total_cmp). - Replaced all uses for
PartialOrd::partial_cmpwithIndexableNum::total_cmpto avoid panics whenpartial_cmpreturnsNone(e.g., if float is a NaN) and to eliminate branch in compare.
- Fixed
PlineSource::remove_redundantto use epsilon values in all fuzzy compares. Previously used default epsilon value from Num trait in some comparisons. If epsilon value passed in is less than default Num trait value (1e-8forf32andf64values) then it is more aggressive in removing vertexes. - Fixed pline
find_intersectsfunction to use epsilon parameter in all cases. - Fixed boolean operation bugs caused by
line_circle_intrnumerical stability problem, inconsistencies between epsilon values used across functions, and lack of scaling parametric t values and angles for fuzzy comparing with epsilon values (#23). - Fixed some of the doc comments around pline boolean operation types.
- Fixed polyline find_intersects to use pos_equal_eps passed in options for querying bounding boxes (#22).
- Fixed
PlineViewData::from_new_startto not discard bulge value if start point lies on top of the first vertex of a closed polyline, and added tests to cover case. - Fixed pline
remove_redundantpanic when there was more than 2 equal points at start (#26).
- Added CHANGELOG.md file for tracking changes and releases.
- New traits
PlineSource,PlineSourceMut, andPlineCreationfor sharing methods across different polyline data views (for example sub views/selections over polylines or direction inversion). PlineViewDataandPlineViewtypes.PlineViewimplementsPlineSourcetrait allowing for flexible views over polyline data to be processed with all the shared behavior/methods.PlineViewDataholds the data used to index into an underlying source polyline to form aPlineView.
- All Polyline methods have moved to the appropriate trait (
PlineSource,PlineSourceMut, orPlineCreation). - Slices used in offset and boolean operations now all use new
PlineViewDataandPlineViewtypes. - Changed polyline methods that returned
Cowtypes to returnOptiontypes to indicate if changes were done on the input polyline or not. - Changed intersect functions (
circle_circle_intr,line_circle_intr, andline_line_intr) to accept epsilon value parameter for fuzzy float comparisons. - Changed polyline offset and boolean operations to use
pos_equal_epsepsilon value in intersect functions. - Changed
pline_seg_intrfunction to avoid inconsistencies between intersect functions in some cases involving line-arc intersects due to fuzzy comparing - this fixes problems that can arise in operations that depend on finding intersects. - Added
include_overlappingparameter toall_self_intersects_as_basicfunction.
- Fixed arc overlap intersects not being found in some cases and segment intersects disagreeing due to fuzzy comparing causing incorrect output for polyline boolean operations and added test cases that found the issue.
- Fixed bug in parallel offset caused when raw offset polyline produced an overlapping self intersect. Parallel offset algorithm no longer includes overlapping intersects when finding self intersects of raw offset polyline for creating slices.
Polyline::visit_segments(usePlineSource::iter_segmentsinstead).Polyline::lenmethod removed (usePlineSource::vertex_countinstead).PolylineSlicetrait andOpenPlineSlicetype (covered by new view types and polyline traits).PolylineSlice::stitch_ontoandPolylineSlice::to_polylineremoved, to get same functionality construct the view withPlineData::viewand pass it toPlineSourceMut::extend_remove_repeatorPlineCreation::create_from_remove_repeat.