mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-24 10:56:51 -06:00
* Evaluate spline basis functions on the stack `basis()` allocated three Vecs for every evaluated point, and a large drawing evaluates millions of points while computing its extents on open: `basis` was 10% and `calloc` 6% of `cadcraft-cli info` on the 1.1 M-entity drawing of #1076. `eval` now works in fixed arrays up to degree 10 (the cap of the DXF reader and the SPLINE command) and falls back to the heap above it. Results are bit-identical to the old code, which the test keeps as its reference. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Validate a spline once per tessellation `tessellate` called `eval` for every sample, and `eval` re-ran `is_valid` (a walk over the whole knot vector) and `domain()` each time, so a spline with many control points cost O(knots) per point. Validate and read the domain once, then evaluate the samples with the checked variant. Part of the open time of large drawings (#1076). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Size spline bounds sampling relative to the spline `Spline::bounds` tessellated at an absolute tolerance of 1e-3, so every spline of a drawing in millimetres hit the 4000-point cap: the extents computed on open evaluated 12 M points for the 3072 five-point cubics of the 1.1 M-entity drawing of #1076, a third of its open time in the CLI and the GUI alike. Sample at 1e-3 of the spline's own control-box diagonal instead, with at least four samples per knot span, straight into the box without an intermediate Vec. The box of a typical spline now takes a few hundred evaluations and stays within 1e-3 of the spline's size of the finely sampled one (3.4e-4 at worst over the test set); a degree-1 spline gets its exact control box. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Give a spline with a non-finite control point its control box `Bounds2::from_points` skips a non-finite point, so the control box and its diagonal were finite while a NaN or infinite control point poisoned every sample (`c * 0.0` is still NaN): `bounds` returned the empty box instead of the box of the finite control points that the other degenerate cases get. Check the control points before sampling. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Size spline bounds sampling by a dimensionless ratio `bounds` passed `diag * 1e-3` to `sample_count`, whose absolute floor of 1e-9 on the tolerance made the count absolute again for a spline with a diagonal under 1e-6: at a scale of 1e-12 every spline got the minimum eight segments per degree whatever its shape, and an extreme between two samples exceeded the 1e-3 margin the box promises. Derive the count from the ratio of control-polygon length to tolerance in a helper that `tessellate` (with its floor, unchanged) and `bounds` (with the ratio to the diagonal) share, so a scaled copy of a spline samples exactly like the original. The scale test now also runs at 1e-9 and 1e-12. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Sample spline bounds over every knot span `bounds` sampled one uniform grid over the parameter domain; the four-per-span floor only raised the count. A narrow non-empty knot span (a degree-2 peak supported on 0.5003..0.5004, say) fell between grid points, at any count up to the cap, so the box missed part of the curve and zoom extents clipped it. Spread the samples over the non-empty knot spans in proportion to their length with a floor of four per span, ends included, still folded straight into the box. The cap holds over all spans: the floor drops to two or three when four would exceed it, and a spline with more spans than two each allow falls back to the grid. A typical spline evaluates as many points as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Hold a narrow knot span to eight samples, and say what that buys The doc said the box falls short of an extreme by far under 1% of the spline's size, which holds where the budget sets the count but not in a span held to the floor: four segments across a degree-2 piece can miss an off-grid peak by about 3% of the control box. Eight segments bring that under 1%, and the comment now says both figures. A test puts the peak between the grid's samples and checks the margin; four segments would miss it by more. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Give a spline with more spans than the cap samples its control box Past two thousand knot spans the grid of the cap's four thousand samples can step over a narrow span and leave its extreme out of the box, which selection uses as a pre-filter. The control box contains the curve whenever no weight is negative (the convex hull property), so such a spline gets that box, conservative rather than sampled; only a negative weight, which voids the property, keeps the grid. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Keep the degree-1 shortcut for positive weights A degree-1 spline is its control polygon only under the convex hull property, which a negative weight voids: with control (1, 0), (2, 0) and weights 1, -0.5 the curve passes (4, 0), outside the control box that bounds() returned for every degree-1 spline. The shortcut, like the many-spans one, now needs every weight positive; otherwise the spline is sampled like any other. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Sample a degenerate spline whose weights are not positive Coincident control points with zero weights evaluate to the origin, not to the point they sit on: eval() leaves the unnormalised sum alone when the weights sum to nothing. The zero-diagonal shortcut returned that point's box all the same. Like the degree-1 and many-spans shortcuts, it now needs positive weights; otherwise the curve is sampled, and the box holds what eval() gives. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>