Vector art points have four types and the pen draws whole curves (#195)
The pen only pulled out the outgoing handle of a new point, because the opposite handle was mirrored only if it already existed. The first segment of every dragged point was therefore half a curve. Beyond that bug, a point had no way to say how its handles relate. - A vector point has an optional mode: straight, corner, smooth or symmetric. Missing means corner. The mode is not part of the fingerprint, because it changes no pixel, only the next drag. - Dragging a handle follows the mode. Corner leaves the other handle alone, smooth turns it along and keeps its length, symmetric mirrors it exactly. - The pen pulls out both handles mirrored and marks the point symmetric. A plain click still sets a point without handles. - The tab header has a "Point: ..." menu for the last clicked or set point. That point is drawn filled. Changing its type is one undo step. Text instead of four icons, because the UI kit has no tooltips. - Switching a point without handles to a curved type first grows handles along its neighbours. Smooth and symmetric line the handles up and keep the rough direction of the curve. - A point inserted on a curve is smooth. Symmetric neighbours become smooth, because the split shortens one of their handles. - Alt-click still toggles between straight and symmetric. Found on the way: the old Alt rounding took the point at the other end of an open line as a neighbour, so its handles came out skewed. Fixed and held by a unit test. Existing points with handles now count as corner. Before, every drag mirrored. No pixel changes, only the next drag behaves differently. Tests: - ./check.sh: 129 files, 1917 tests, exit 0 (before 1903) - GM_STEPS=47, freshly seeded: ALL PASSED, 39 checks (before 32) - Check sum 1195 in README and wiki is computed (1188 + 7), CI measures it - Counter-checks: pen without 'symmetric' fails 3 checks, no smooth branch fails 1 unit test and 1 e2e check Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
90627ccf47
commit
3ff7e91dbf
11 files changed
+487
-65
No files matched your search
@@ -239,15 +239,22 @@ export interface TilemapAsset extends AssetBase {
|
||||
* A point of a ring. `in` and `out` are the Bezier handles, as an offset from the point itself, so
|
||||
* a point may be dragged without touching them; absent is a corner, and the edge to that side is
|
||||
* straight. They are kept apart rather than mirrored, because a point may bend differently on each
|
||||
* side – what "smooth" means is the editor's business, not the model's (#129).
|
||||
* side (#129).
|
||||
*
|
||||
* `mode` says how the two handles move together when one is dragged (#195): a corner leaves the
|
||||
* other alone, a smooth point keeps it pointing the opposite way at its own length, a symmetric one
|
||||
* mirrors it exactly. Absent is a corner. It changes no pixel – only what the next drag does.
|
||||
*/
|
||||
export interface VectorPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
in?: Vec2;
|
||||
out?: Vec2;
|
||||
mode?: VectorPointMode;
|
||||
}
|
||||
|
||||
export type VectorPointMode = 'corner' | 'smooth' | 'symmetric';
|
||||
|
||||
/**
|
||||
* One outline of a shape, in canvas pixels from the canvas's top-left corner. A shape holds several
|
||||
* of them so it can have holes: the rings are filled even-odd, so a ring inside another cuts it out
|
||||
|
||||
@@ -127,14 +127,16 @@ export function vectorArtPicture(asset: Asset): GeneratedPicture | null {
|
||||
|
||||
/** A deep copy of a ring: its points and the handles on them are all plain data. */
|
||||
export function cloneVectorRing(ring: VectorRing): VectorRing {
|
||||
return { closed: ring.closed, points: ring.points.map(cloneVectorPoint) };
|
||||
}
|
||||
|
||||
export function cloneVectorPoint(point: VectorPoint): VectorPoint {
|
||||
return {
|
||||
closed: ring.closed,
|
||||
points: ring.points.map((point) => ({
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
...(point.in ? { in: { ...point.in } } : {}),
|
||||
...(point.out ? { out: { ...point.out } } : {}),
|
||||
})),
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
...(point.in ? { in: { ...point.in } } : {}),
|
||||
...(point.out ? { out: { ...point.out } } : {}),
|
||||
...(point.mode && point.mode !== 'corner' ? { mode: point.mode } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user