Until now every boolean operation on vector art threw the curves away.
The shapes were flattened into line segments, the operation ran on those
segments, and the result came back as a polygon. A circle that nothing
even touched came out of an Exclude with 40 corners instead of its four
points and handles. Now the result carries the original curves again,
and only the edges the operation really drew stay straight.
- While flattening, every sample point remembers which curve it came
from and where on that curve it sits. The operation runs on the same
segments as before, so nothing about the boolean result changes.
- When a run of result points walks along one of those curves, that
piece is cut out of the original curve again with de Casteljau. This
is an exact cut, not a fit through the points.
- An edge whose two ends sit on the same curve but whose middle does
not follow it stays straight. Without that rule a cut-off cap would
bulge back into the shape.
- A closed contour keeps at least three points, so a lens shape does
not collapse into two.
- The rule lives in its own module, src/core/model/vectorCurves.ts.
vectorBoolean.ts only hands it the table of sampled points.
Findings:
- The two intersection points themselves sit up to 0.09 canvas pixels
off the original curve. Clipper cuts the flattened segments, not the
curves, so this was the case before as well and stays inside the
0.2 px sampling tolerance.
- Refining those points with Newton steps on the curve was measured and
gave nothing back (0.02952 px instead of 0.02978 px), because the
error is in the intersection itself and not in the estimate. The code
was removed again.
- Theme.ts rowHeight 22 -> 24 is in this commit. The change is not mine,
it was already in the working tree; gate and module 47 are green with
it.
Tests:
- ./check.sh: 143 files, 2133 tests, exit 0 (was 2127)
- e2e module 47 on a fresh seed: all passed, 59 checks (was 57)
- 6 new unit tests: untouched circle, untouched side plus a straight cut
edge, a cut in the middle of a curve, a chord crossing one curve
twice, two overlapping circles, the three point minimum
- 2 new e2e checks in block 47p: point count and handles from the model,
plus a pixel pair on the round edge
- Four counter-proofs, all bite: no reconstruction -> 5 unit tests and
the e2e check fail with 35 points and nothing curved; no chord test ->
the cap bulges back to 28.28; no three point rule -> the lens test
fails; handles halved -> the pixel check reads background
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Building a shape out of simpler ones meant drawing the result point by
point. The Vector Art tab now selects several shapes at once and
combines them with a boolean operation into one ordinary shape.
- Ctrl/Cmd+click in the tab and Ctrl or Shift+click in the shape list
add a shape to the selection. Shift stays pan in the canvas.
- A COMBINE block in the inspector offers Union, Subtract, Intersect
and Exclude. Subtract removes every upper shape from the lowest one.
- The result replaces the lowest selected shape and keeps its name,
colours, stroke and place in the order. It is one undo step.
- Curves are flattened to 0.2 canvas pixels, a circle gets about 48
points. Open lines and single shapes cannot be combined, the buttons
are off and a hint gives the reason. An empty result is rejected.
- Delete now removes every selected shape, and the selection drops
shapes that no longer exist.
- The geometry comes from clipper2-ts 2.0.1, pinned. It has no
dependencies and speaks even-odd directly. Only the engine module
is imported, the bundle grows by 55 kB instead of 110 kB.
Findings:
- clipper2-js was tried and dropped. It pulls in Angular 15 with a
high npm audit finding and no fix.
- Pixi finds holes by containment, not by even-odd depth. An island
inside a hole would be drawn as a hole again. Result rings are
emitted outer ring before its islands, and a test mirrors Pixi's
rule.
- A depth sort turned out to be dead code in a counter-check and was
removed. A pixel check in 47h stayed green without the subtract
and now checks both colours.
Tests:
- ./check.sh: 134 files, 1986 tests, exit 0 (before 133 / 1968)
- npm run build: exit 0
- GM_STEPS=47, freshly seeded: ALL PASSED, 44 checks (before 39)
- 18 new unit tests, 5 new e2e checks in block 47h
- Five counter-checks bite, originals restored and verified with cmp
- Check sum 1222 in README and wiki, calculated (1217 + 5)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>