Browse Source

fix: lasso selection issues (#9353)

* revert stroke slicing hack for knot

* fix incorrect closing of path

* nonzero enclosure

* lint
Ryan Di 4 months ago
parent
commit
6fc85022ae

+ 1 - 5
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);

+ 6 - 10
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),
+    );
   });
 };
 

+ 28 - 0
packages/math/src/polygon.ts

@@ -41,6 +41,34 @@ export const polygonIncludesPoint = <Point extends LocalPoint | GlobalPoint>(
   return inside;
 };
 
+export const polygonIncludesPointNonZero = <Point extends [number, number]>(
+  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 = <Point extends LocalPoint | GlobalPoint>(
   p: Point,
   poly: Polygon<Point>,