Refactor eraser and lasso hit tests
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
parent
49613ad0c3
commit
8b248eda94
@ -111,9 +111,9 @@ export const hitElementItself = ({
|
|||||||
? shouldTestInside(element)
|
? shouldTestInside(element)
|
||||||
? // Since `inShape` tests STRICTLY againt the insides of a shape
|
? // Since `inShape` tests STRICTLY againt the insides of a shape
|
||||||
// we would need `onShape` as well to include the "borders"
|
// we would need `onShape` as well to include the "borders"
|
||||||
isPointInShape(point, element) ||
|
isPointInElement(point, element) ||
|
||||||
isPointOnShape(point, element, threshold)
|
isPointOnElementOutline(point, element, threshold)
|
||||||
: isPointOnShape(point, element, threshold)
|
: isPointOnElementOutline(point, element, threshold)
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
// hit test against a frame's name
|
// hit test against a frame's name
|
||||||
@ -177,7 +177,7 @@ export const hitElementBoundText = (
|
|||||||
}
|
}
|
||||||
: boundTextElementCandidate;
|
: boundTextElementCandidate;
|
||||||
|
|
||||||
return isPointInShape(point, boundTextElement);
|
return isPointInElement(point, boundTextElement);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -371,14 +371,14 @@ const intersectEllipseWithLineSegment = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
// check if the given point is considered on the given shape's border
|
// check if the given point is considered on the given shape's border
|
||||||
const isPointOnShape = (
|
const isPointOnElementOutline = (
|
||||||
point: GlobalPoint,
|
point: GlobalPoint,
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
tolerance = 1,
|
tolerance = 1,
|
||||||
) => distanceToElement(element, point) <= tolerance;
|
) => distanceToElement(element, point) <= tolerance;
|
||||||
|
|
||||||
// check if the given point is considered inside the element's border
|
// check if the given point is considered inside the element's border
|
||||||
export const isPointInShape = (
|
export const isPointInElement = (
|
||||||
point: GlobalPoint,
|
point: GlobalPoint,
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
) => {
|
) => {
|
||||||
|
@ -130,7 +130,7 @@ import {
|
|||||||
refreshTextDimensions,
|
refreshTextDimensions,
|
||||||
deepCopyElement,
|
deepCopyElement,
|
||||||
duplicateElements,
|
duplicateElements,
|
||||||
isPointInShape,
|
isPointInElement,
|
||||||
hasBoundTextElement,
|
hasBoundTextElement,
|
||||||
isArrowElement,
|
isArrowElement,
|
||||||
isBindingElement,
|
isBindingElement,
|
||||||
@ -5164,7 +5164,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
) {
|
) {
|
||||||
// if hitting the bounding box, return early
|
// if hitting the bounding box, return early
|
||||||
// but if not, we should check for other cases as well (e.g. frame name)
|
// but if not, we should check for other cases as well (e.g. frame name)
|
||||||
if (isPointInShape(pointFrom(x, y), element)) {
|
if (isPointInElement(pointFrom(x, y), element)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { arrayToMap, easeOut, THEME } from "@excalidraw/common";
|
import { arrayToMap, easeOut, THEME } from "@excalidraw/common";
|
||||||
import { getElementLineSegments, isPointInShape } from "@excalidraw/element";
|
|
||||||
import {
|
import {
|
||||||
lineSegment,
|
getBoundTextElement,
|
||||||
lineSegmentIntersectionPoints,
|
intersectElementWithLineSegment,
|
||||||
pointFrom,
|
isPointInElement,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/element";
|
||||||
|
import { lineSegment, pointFrom } from "@excalidraw/math";
|
||||||
|
|
||||||
import { getElementsInGroup } from "@excalidraw/element";
|
import { getElementsInGroup } from "@excalidraw/element";
|
||||||
|
|
||||||
@ -12,7 +12,6 @@ import { shouldTestInside } from "@excalidraw/element";
|
|||||||
import { hasBoundTextElement, isBoundToContainer } from "@excalidraw/element";
|
import { hasBoundTextElement, isBoundToContainer } from "@excalidraw/element";
|
||||||
import { getBoundTextElementId } from "@excalidraw/element";
|
import { getBoundTextElementId } from "@excalidraw/element";
|
||||||
|
|
||||||
import type { GeometricShape } from "@excalidraw/utils/shape";
|
|
||||||
import type {
|
import type {
|
||||||
ElementsSegmentsMap,
|
ElementsSegmentsMap,
|
||||||
GlobalPoint,
|
GlobalPoint,
|
||||||
@ -33,8 +32,6 @@ export class EraserTrail extends AnimatedTrail {
|
|||||||
private elementsToErase: Set<ExcalidrawElement["id"]> = new Set();
|
private elementsToErase: Set<ExcalidrawElement["id"]> = new Set();
|
||||||
private groupsToErase: Set<ExcalidrawElement["id"]> = new Set();
|
private groupsToErase: Set<ExcalidrawElement["id"]> = new Set();
|
||||||
private segmentsCache: Map<string, LineSegment<GlobalPoint>[]> = new Map();
|
private segmentsCache: Map<string, LineSegment<GlobalPoint>[]> = new Map();
|
||||||
private geometricShapesCache: Map<string, GeometricShape<GlobalPoint>> =
|
|
||||||
new Map();
|
|
||||||
|
|
||||||
constructor(animationFrameHandler: AnimationFrameHandler, app: App) {
|
constructor(animationFrameHandler: AnimationFrameHandler, app: App) {
|
||||||
super(animationFrameHandler, app, {
|
super(animationFrameHandler, app, {
|
||||||
@ -111,7 +108,6 @@ export class EraserTrail extends AnimatedTrail {
|
|||||||
pathSegments,
|
pathSegments,
|
||||||
element,
|
element,
|
||||||
this.segmentsCache,
|
this.segmentsCache,
|
||||||
this.geometricShapesCache,
|
|
||||||
candidateElementsMap,
|
candidateElementsMap,
|
||||||
this.app,
|
this.app,
|
||||||
);
|
);
|
||||||
@ -149,7 +145,6 @@ export class EraserTrail extends AnimatedTrail {
|
|||||||
pathSegments,
|
pathSegments,
|
||||||
element,
|
element,
|
||||||
this.segmentsCache,
|
this.segmentsCache,
|
||||||
this.geometricShapesCache,
|
|
||||||
candidateElementsMap,
|
candidateElementsMap,
|
||||||
this.app,
|
this.app,
|
||||||
);
|
);
|
||||||
@ -202,30 +197,23 @@ const eraserTest = (
|
|||||||
pathSegments: LineSegment<GlobalPoint>[],
|
pathSegments: LineSegment<GlobalPoint>[],
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
elementsSegments: ElementsSegmentsMap,
|
elementsSegments: ElementsSegmentsMap,
|
||||||
shapesCache: Map<string, GeometricShape<GlobalPoint>>,
|
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
app: App,
|
app: App,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const lastPoint = pathSegments[pathSegments.length - 1][1];
|
const lastPoint = pathSegments[pathSegments.length - 1][1];
|
||||||
if (shouldTestInside(element) && isPointInShape(lastPoint, element)) {
|
if (shouldTestInside(element) && isPointInElement(lastPoint, element)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let elementSegments = elementsSegments.get(element.id);
|
const offset = app.getElementHitThreshold();
|
||||||
|
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||||
|
|
||||||
if (!elementSegments) {
|
return pathSegments.some(
|
||||||
elementSegments = getElementLineSegments(element, elementsMap);
|
(pathSegment) =>
|
||||||
elementsSegments.set(element.id, elementSegments);
|
intersectElementWithLineSegment(element, pathSegment, offset).length >
|
||||||
}
|
0 ||
|
||||||
|
(boundTextElement &&
|
||||||
return pathSegments.some((pathSegment) =>
|
intersectElementWithLineSegment(boundTextElement, pathSegment, offset)
|
||||||
elementSegments?.some(
|
.length > 0),
|
||||||
(elementSegment) =>
|
|
||||||
lineSegmentIntersectionPoints(
|
|
||||||
pathSegment,
|
|
||||||
elementSegment,
|
|
||||||
app.getElementHitThreshold(),
|
|
||||||
) !== null,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -203,6 +203,7 @@ export class LassoTrail extends AnimatedTrail {
|
|||||||
intersectedElements: this.intersectedElements,
|
intersectedElements: this.intersectedElements,
|
||||||
enclosedElements: this.enclosedElements,
|
enclosedElements: this.enclosedElements,
|
||||||
simplifyDistance: 5 / this.app.state.zoom.value,
|
simplifyDistance: 5 / this.app.state.zoom.value,
|
||||||
|
hitThreshold: this.app.getElementHitThreshold(),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.selectElementsFromIds(selectedElementIds);
|
this.selectElementsFromIds(selectedElementIds);
|
||||||
|
@ -3,15 +3,12 @@ import { simplify } from "points-on-curve";
|
|||||||
import {
|
import {
|
||||||
polygonFromPoints,
|
polygonFromPoints,
|
||||||
lineSegment,
|
lineSegment,
|
||||||
lineSegmentIntersectionPoints,
|
|
||||||
polygonIncludesPointNonZero,
|
polygonIncludesPointNonZero,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type {
|
import { intersectElementWithLineSegment } from "@excalidraw/element";
|
||||||
ElementsSegmentsMap,
|
|
||||||
GlobalPoint,
|
import type { ElementsSegmentsMap, GlobalPoint } from "@excalidraw/math/types";
|
||||||
LineSegment,
|
|
||||||
} from "@excalidraw/math/types";
|
|
||||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||||
|
|
||||||
export const getLassoSelectedElementIds = (input: {
|
export const getLassoSelectedElementIds = (input: {
|
||||||
@ -21,6 +18,7 @@ export const getLassoSelectedElementIds = (input: {
|
|||||||
intersectedElements: Set<ExcalidrawElement["id"]>;
|
intersectedElements: Set<ExcalidrawElement["id"]>;
|
||||||
enclosedElements: Set<ExcalidrawElement["id"]>;
|
enclosedElements: Set<ExcalidrawElement["id"]>;
|
||||||
simplifyDistance?: number;
|
simplifyDistance?: number;
|
||||||
|
hitThreshold: number;
|
||||||
}): {
|
}): {
|
||||||
selectedElementIds: string[];
|
selectedElementIds: string[];
|
||||||
} => {
|
} => {
|
||||||
@ -31,6 +29,7 @@ export const getLassoSelectedElementIds = (input: {
|
|||||||
intersectedElements,
|
intersectedElements,
|
||||||
enclosedElements,
|
enclosedElements,
|
||||||
simplifyDistance,
|
simplifyDistance,
|
||||||
|
hitThreshold,
|
||||||
} = input;
|
} = input;
|
||||||
// simplify the path to reduce the number of points
|
// simplify the path to reduce the number of points
|
||||||
let path: GlobalPoint[] = lassoPath;
|
let path: GlobalPoint[] = lassoPath;
|
||||||
@ -48,7 +47,7 @@ export const getLassoSelectedElementIds = (input: {
|
|||||||
if (enclosed) {
|
if (enclosed) {
|
||||||
enclosedElements.add(element.id);
|
enclosedElements.add(element.id);
|
||||||
} else {
|
} else {
|
||||||
const intersects = intersectionTest(path, element, elementsSegments);
|
const intersects = intersectionTest(path, element, hitThreshold);
|
||||||
if (intersects) {
|
if (intersects) {
|
||||||
intersectedElements.add(element.id);
|
intersectedElements.add(element.id);
|
||||||
}
|
}
|
||||||
@ -84,26 +83,16 @@ const enclosureTest = (
|
|||||||
const intersectionTest = (
|
const intersectionTest = (
|
||||||
lassoPath: GlobalPoint[],
|
lassoPath: GlobalPoint[],
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
elementsSegments: ElementsSegmentsMap,
|
hitThreshold: number,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const elementSegments = elementsSegments.get(element.id);
|
const lassoSegments = lassoPath
|
||||||
if (!elementSegments) {
|
.slice(1)
|
||||||
return false;
|
.map((point: GlobalPoint, index) => lineSegment(lassoPath[index], point))
|
||||||
}
|
.concat([lineSegment(lassoPath[lassoPath.length - 1], lassoPath[0])]);
|
||||||
|
|
||||||
const lassoSegments = lassoPath.reduce((acc, point, index) => {
|
return lassoSegments.some(
|
||||||
if (index === 0) {
|
(lassoSegment) =>
|
||||||
return acc;
|
intersectElementWithLineSegment(element, lassoSegment, hitThreshold)
|
||||||
}
|
.length > 0,
|
||||||
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,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export * from "./angle";
|
export * from "./angle";
|
||||||
export * from "./curve";
|
export * from "./curve";
|
||||||
|
export * from "./ellipse";
|
||||||
export * from "./line";
|
export * from "./line";
|
||||||
export * from "./point";
|
export * from "./point";
|
||||||
export * from "./polygon";
|
export * from "./polygon";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user