From 6fc85022ae6ad14e74271e071afc51a60b7dc3c5 Mon Sep 17 00:00:00 2001 From: Ryan Di Date: Tue, 8 Apr 2025 00:50:52 +1000 Subject: [PATCH] fix: lasso selection issues (#9353) * revert stroke slicing hack for knot * fix incorrect closing of path * nonzero enclosure * lint --- packages/excalidraw/animated-trail.ts | 6 +----- packages/excalidraw/lasso/utils.ts | 16 ++++++--------- packages/math/src/polygon.ts | 28 +++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/packages/excalidraw/animated-trail.ts b/packages/excalidraw/animated-trail.ts index 0ffec7cf58..af6162e99b 100644 --- a/packages/excalidraw/animated-trail.ts +++ b/packages/excalidraw/animated-trail.ts @@ -191,11 +191,7 @@ export class AnimatedTrail implements Trail { }); const stroke = this.trailAnimation - ? _stroke.slice( - // slicing from 6th point to get rid of the initial notch type of thing - Math.min(_stroke.length, 6), - _stroke.length / 2, - ) + ? _stroke.slice(0, _stroke.length / 2) : _stroke; return getSvgPathFromStroke(stroke, true); diff --git a/packages/excalidraw/lasso/utils.ts b/packages/excalidraw/lasso/utils.ts index b04e66db90..f5a7eefdc9 100644 --- a/packages/excalidraw/lasso/utils.ts +++ b/packages/excalidraw/lasso/utils.ts @@ -2,9 +2,9 @@ import { simplify } from "points-on-curve"; import { polygonFromPoints, - polygonIncludesPoint, lineSegment, lineSegmentIntersectionPoints, + polygonIncludesPointNonZero, } from "@excalidraw/math"; import type { GlobalPoint, LineSegment } from "@excalidraw/math/types"; @@ -35,8 +35,6 @@ export const getLassoSelectedElementIds = (input: { if (simplifyDistance) { path = simplify(lassoPath, simplifyDistance) as GlobalPoint[]; } - // close the path to form a polygon for enclosure check - const closedPath = polygonFromPoints(path); // as the path might not enclose a shape anymore, clear before checking enclosedElements.clear(); for (const element of elements) { @@ -44,15 +42,11 @@ export const getLassoSelectedElementIds = (input: { !intersectedElements.has(element.id) && !enclosedElements.has(element.id) ) { - const enclosed = enclosureTest(closedPath, element, elementsSegments); + const enclosed = enclosureTest(path, element, elementsSegments); if (enclosed) { enclosedElements.add(element.id); } else { - const intersects = intersectionTest( - closedPath, - element, - elementsSegments, - ); + const intersects = intersectionTest(path, element, elementsSegments); if (intersects) { intersectedElements.add(element.id); } @@ -79,7 +73,9 @@ const enclosureTest = ( } return segments.some((segment) => { - return segment.some((point) => polygonIncludesPoint(point, lassoPolygon)); + return segment.some((point) => + polygonIncludesPointNonZero(point, lassoPolygon), + ); }); }; diff --git a/packages/math/src/polygon.ts b/packages/math/src/polygon.ts index 762c82dbfb..a50d4e8536 100644 --- a/packages/math/src/polygon.ts +++ b/packages/math/src/polygon.ts @@ -41,6 +41,34 @@ export const polygonIncludesPoint = ( return inside; }; +export const polygonIncludesPointNonZero = ( + point: Point, + polygon: Point[], +): boolean => { + const [x, y] = point; + let windingNumber = 0; + + for (let i = 0; i < polygon.length; i++) { + const j = (i + 1) % polygon.length; + const [xi, yi] = polygon[i]; + const [xj, yj] = polygon[j]; + + if (yi <= y) { + if (yj > y) { + if ((xj - xi) * (y - yi) - (x - xi) * (yj - yi) > 0) { + windingNumber++; + } + } + } else if (yj <= y) { + if ((xj - xi) * (y - yi) - (x - xi) * (yj - yi) < 0) { + windingNumber--; + } + } + } + + return windingNumber !== 0; +}; + export const pointOnPolygon = ( p: Point, poly: Polygon,