* lasso without 'real' shape detection * select a single linear el * improve ux * feed segments to worker * simplify path threshold adaptive to zoom * add a tiny threshold for checks * refactor code * lasso tests * fix: ts * do not capture lasso tool * try worker-loader in next config * update config * refactor * lint * feat: show active tool when using "more tools" * keep lasso if selected from toolbar * fix incorrect checks for resetting to selection * shift for additive selection * bound text related fixes * lint * keep alt toggled lasso selection if shift pressed * fix regression * fix 'dead' lassos * lint * use workerpool and polyfill * fix worker bundled with window related code * refactor * add file extension for worker constructor error * another attempt at constructor error * attempt at build issue * attempt with dynamic import * test not importing from math * narrow down imports * Reusing existing workers infrastructure (fallback to the main thread, type-safety) * Points on curve inside the shared chunk * Give up on experimental code splitting * Remove potentially unnecessary optimisation * Removing workers as the complexit is much worse, while perf. does not seem to be much better * fix selecting text containers and containing frames together * render fill directly from animated trail * do not re-render static when setting selected element ids in lasso * remove unnecessary property * tweak trail animation * slice points to remove notch * always start alt-lasso from initial point * revert build & worker changes (unused) * remove `lasso` from `hasStrokeColor` * label change * remove unused props * remove unsafe optimization * snaps --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> Co-authored-by: Marcel Mraz <marcel@excalidraw.com>
112 lines
3.0 KiB
TypeScript
112 lines
3.0 KiB
TypeScript
import { simplify } from "points-on-curve";
|
|
|
|
import {
|
|
polygonFromPoints,
|
|
polygonIncludesPoint,
|
|
lineSegment,
|
|
lineSegmentIntersectionPoints,
|
|
} from "@excalidraw/math";
|
|
|
|
import type { GlobalPoint, LineSegment } from "@excalidraw/math/types";
|
|
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
|
|
|
export type ElementsSegmentsMap = Map<string, LineSegment<GlobalPoint>[]>;
|
|
|
|
export const getLassoSelectedElementIds = (input: {
|
|
lassoPath: GlobalPoint[];
|
|
elements: readonly ExcalidrawElement[];
|
|
elementsSegments: ElementsSegmentsMap;
|
|
intersectedElements: Set<ExcalidrawElement["id"]>;
|
|
enclosedElements: Set<ExcalidrawElement["id"]>;
|
|
simplifyDistance?: number;
|
|
}): {
|
|
selectedElementIds: string[];
|
|
} => {
|
|
const {
|
|
lassoPath,
|
|
elements,
|
|
elementsSegments,
|
|
intersectedElements,
|
|
enclosedElements,
|
|
simplifyDistance,
|
|
} = input;
|
|
// simplify the path to reduce the number of points
|
|
let path: GlobalPoint[] = lassoPath;
|
|
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) {
|
|
if (
|
|
!intersectedElements.has(element.id) &&
|
|
!enclosedElements.has(element.id)
|
|
) {
|
|
const enclosed = enclosureTest(closedPath, element, elementsSegments);
|
|
if (enclosed) {
|
|
enclosedElements.add(element.id);
|
|
} else {
|
|
const intersects = intersectionTest(
|
|
closedPath,
|
|
element,
|
|
elementsSegments,
|
|
);
|
|
if (intersects) {
|
|
intersectedElements.add(element.id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const results = [...intersectedElements, ...enclosedElements];
|
|
|
|
return {
|
|
selectedElementIds: results,
|
|
};
|
|
};
|
|
|
|
const enclosureTest = (
|
|
lassoPath: GlobalPoint[],
|
|
element: ExcalidrawElement,
|
|
elementsSegments: ElementsSegmentsMap,
|
|
): boolean => {
|
|
const lassoPolygon = polygonFromPoints(lassoPath);
|
|
const segments = elementsSegments.get(element.id);
|
|
if (!segments) {
|
|
return false;
|
|
}
|
|
|
|
return segments.some((segment) => {
|
|
return segment.some((point) => polygonIncludesPoint(point, lassoPolygon));
|
|
});
|
|
};
|
|
|
|
const intersectionTest = (
|
|
lassoPath: GlobalPoint[],
|
|
element: ExcalidrawElement,
|
|
elementsSegments: ElementsSegmentsMap,
|
|
): boolean => {
|
|
const elementSegments = elementsSegments.get(element.id);
|
|
if (!elementSegments) {
|
|
return false;
|
|
}
|
|
|
|
const lassoSegments = lassoPath.reduce((acc, point, index) => {
|
|
if (index === 0) {
|
|
return acc;
|
|
}
|
|
acc.push(lineSegment(lassoPath[index - 1], point));
|
|
return acc;
|
|
}, [] as LineSegment<GlobalPoint>[]);
|
|
|
|
return lassoSegments.some((lassoSegment) =>
|
|
elementSegments.some(
|
|
(elementSegment) =>
|
|
// introduce a bit of tolerance to account for roughness and simplification of paths
|
|
lineSegmentIntersectionPoints(lassoSegment, elementSegment, 1) !== null,
|
|
),
|
|
);
|
|
};
|