mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-09-19 07:20:21 +02:00
chore: Refactor doBoundsIntersect (#9657)
This commit is contained in:
@@ -24,7 +24,6 @@ import {
|
||||
pointsEqual,
|
||||
lineSegmentIntersectionPoints,
|
||||
PRECISION,
|
||||
doBoundsIntersect,
|
||||
} from "@excalidraw/math";
|
||||
|
||||
import type { LocalPoint, Radians } from "@excalidraw/math";
|
||||
@@ -33,7 +32,11 @@ import type { AppState } from "@excalidraw/excalidraw/types";
|
||||
|
||||
import type { MapEntry, Mutable } from "@excalidraw/common/utility-types";
|
||||
|
||||
import { getCenterForBounds, getElementBounds } from "./bounds";
|
||||
import {
|
||||
doBoundsIntersect,
|
||||
getCenterForBounds,
|
||||
getElementBounds,
|
||||
} from "./bounds";
|
||||
import { intersectElementWithLineSegment } from "./collision";
|
||||
import { distanceToElement } from "./distance";
|
||||
import {
|
||||
|
@@ -584,7 +584,7 @@ const solveQuadratic = (
|
||||
return [s1, s2];
|
||||
};
|
||||
|
||||
const getCubicBezierCurveBound = (
|
||||
export const getCubicBezierCurveBound = (
|
||||
p0: GlobalPoint,
|
||||
p1: GlobalPoint,
|
||||
p2: GlobalPoint,
|
||||
@@ -1230,6 +1230,20 @@ export const pointInsideBounds = <P extends GlobalPoint | LocalPoint>(
|
||||
): boolean =>
|
||||
p[0] > bounds[0] && p[0] < bounds[2] && p[1] > bounds[1] && p[1] < bounds[3];
|
||||
|
||||
export const doBoundsIntersect = (
|
||||
bounds1: Bounds | null,
|
||||
bounds2: Bounds | null,
|
||||
): boolean => {
|
||||
if (bounds1 == null || bounds2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [minX1, minY1, maxX1, maxY1] = bounds1;
|
||||
const [minX2, minY2, maxX2, maxY2] = bounds2;
|
||||
|
||||
return minX1 < maxX2 && maxX1 > minX2 && minY1 < maxY2 && maxY1 > minY2;
|
||||
};
|
||||
|
||||
export const elementCenterPoint = (
|
||||
element: ExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
|
@@ -11,7 +11,6 @@ import {
|
||||
vectorFromPoint,
|
||||
vectorNormalize,
|
||||
vectorScale,
|
||||
doBoundsIntersect,
|
||||
} from "@excalidraw/math";
|
||||
|
||||
import {
|
||||
@@ -19,15 +18,22 @@ import {
|
||||
ellipseSegmentInterceptPoints,
|
||||
} from "@excalidraw/math/ellipse";
|
||||
|
||||
import type { GlobalPoint, LineSegment, Radians } from "@excalidraw/math";
|
||||
import type {
|
||||
Curve,
|
||||
GlobalPoint,
|
||||
LineSegment,
|
||||
Radians,
|
||||
} from "@excalidraw/math";
|
||||
|
||||
import type { FrameNameBounds } from "@excalidraw/excalidraw/types";
|
||||
|
||||
import { isPathALoop } from "./utils";
|
||||
import {
|
||||
type Bounds,
|
||||
doBoundsIntersect,
|
||||
elementCenterPoint,
|
||||
getCenterForBounds,
|
||||
getCubicBezierCurveBound,
|
||||
getElementBounds,
|
||||
} from "./bounds";
|
||||
import {
|
||||
@@ -255,13 +261,75 @@ export const intersectElementWithLineSegment = (
|
||||
}
|
||||
};
|
||||
|
||||
const curveIntersections = (
|
||||
curves: Curve<GlobalPoint>[],
|
||||
segment: LineSegment<GlobalPoint>,
|
||||
intersections: GlobalPoint[],
|
||||
center: GlobalPoint,
|
||||
angle: Radians,
|
||||
onlyFirst = false,
|
||||
) => {
|
||||
for (const c of curves) {
|
||||
// Optimize by doing a cheap bounding box check first
|
||||
const b1 = getCubicBezierCurveBound(c[0], c[1], c[2], c[3]);
|
||||
const b2 = [
|
||||
Math.min(segment[0][0], segment[1][0]),
|
||||
Math.min(segment[0][1], segment[1][1]),
|
||||
Math.max(segment[0][0], segment[1][0]),
|
||||
Math.max(segment[0][1], segment[1][1]),
|
||||
] as Bounds;
|
||||
|
||||
if (!doBoundsIntersect(b1, b2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const hits = curveIntersectLineSegment(c, segment);
|
||||
|
||||
if (hits.length > 0) {
|
||||
for (const j of hits) {
|
||||
intersections.push(pointRotateRads(j, center, angle));
|
||||
}
|
||||
|
||||
if (onlyFirst) {
|
||||
return intersections;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return intersections;
|
||||
};
|
||||
|
||||
const lineIntersections = (
|
||||
lines: LineSegment<GlobalPoint>[],
|
||||
segment: LineSegment<GlobalPoint>,
|
||||
intersections: GlobalPoint[],
|
||||
center: GlobalPoint,
|
||||
angle: Radians,
|
||||
onlyFirst = false,
|
||||
) => {
|
||||
for (const l of lines) {
|
||||
const intersection = lineSegmentIntersectionPoints(l, segment);
|
||||
if (intersection) {
|
||||
intersections.push(pointRotateRads(intersection, center, angle));
|
||||
|
||||
if (onlyFirst) {
|
||||
return intersections;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return intersections;
|
||||
};
|
||||
|
||||
const intersectLinearOrFreeDrawWithLineSegment = (
|
||||
element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement,
|
||||
segment: LineSegment<GlobalPoint>,
|
||||
onlyFirst = false,
|
||||
): GlobalPoint[] => {
|
||||
// NOTE: This is the only one which return the decomposed elements
|
||||
// rotated! This is due to taking advantage of roughjs definitions.
|
||||
const [lines, curves] = deconstructLinearOrFreeDrawElement(element);
|
||||
const intersections = [];
|
||||
const intersections: GlobalPoint[] = [];
|
||||
|
||||
for (const l of lines) {
|
||||
const intersection = lineSegmentIntersectionPoints(l, segment);
|
||||
@@ -275,6 +343,19 @@ const intersectLinearOrFreeDrawWithLineSegment = (
|
||||
}
|
||||
|
||||
for (const c of curves) {
|
||||
// Optimize by doing a cheap bounding box check first
|
||||
const b1 = getCubicBezierCurveBound(c[0], c[1], c[2], c[3]);
|
||||
const b2 = [
|
||||
Math.min(segment[0][0], segment[1][0]),
|
||||
Math.min(segment[0][1], segment[1][1]),
|
||||
Math.max(segment[0][0], segment[1][0]),
|
||||
Math.max(segment[0][1], segment[1][1]),
|
||||
] as Bounds;
|
||||
|
||||
if (!doBoundsIntersect(b1, b2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const hits = curveIntersectLineSegment(c, segment);
|
||||
|
||||
if (hits.length > 0) {
|
||||
@@ -292,7 +373,7 @@ const intersectLinearOrFreeDrawWithLineSegment = (
|
||||
const intersectRectanguloidWithLineSegment = (
|
||||
element: ExcalidrawRectanguloidElement,
|
||||
elementsMap: ElementsMap,
|
||||
l: LineSegment<GlobalPoint>,
|
||||
segment: LineSegment<GlobalPoint>,
|
||||
offset: number = 0,
|
||||
onlyFirst = false,
|
||||
): GlobalPoint[] => {
|
||||
@@ -300,48 +381,43 @@ const intersectRectanguloidWithLineSegment = (
|
||||
// To emulate a rotated rectangle we rotate the point in the inverse angle
|
||||
// instead. It's all the same distance-wise.
|
||||
const rotatedA = pointRotateRads<GlobalPoint>(
|
||||
l[0],
|
||||
segment[0],
|
||||
center,
|
||||
-element.angle as Radians,
|
||||
);
|
||||
const rotatedB = pointRotateRads<GlobalPoint>(
|
||||
l[1],
|
||||
segment[1],
|
||||
center,
|
||||
-element.angle as Radians,
|
||||
);
|
||||
const rotatedIntersector = lineSegment(rotatedA, rotatedB);
|
||||
|
||||
// Get the element's building components we can test against
|
||||
const [sides, corners] = deconstructRectanguloidElement(element, offset);
|
||||
|
||||
const intersections: GlobalPoint[] = [];
|
||||
|
||||
for (const s of sides) {
|
||||
const intersection = lineSegmentIntersectionPoints(
|
||||
lineSegment(rotatedA, rotatedB),
|
||||
s,
|
||||
);
|
||||
if (intersection) {
|
||||
intersections.push(pointRotateRads(intersection, center, element.angle));
|
||||
lineIntersections(
|
||||
sides,
|
||||
rotatedIntersector,
|
||||
intersections,
|
||||
center,
|
||||
element.angle,
|
||||
onlyFirst,
|
||||
);
|
||||
|
||||
if (onlyFirst) {
|
||||
return intersections;
|
||||
}
|
||||
}
|
||||
if (onlyFirst && intersections.length > 0) {
|
||||
return intersections;
|
||||
}
|
||||
|
||||
for (const t of corners) {
|
||||
const hits = curveIntersectLineSegment(t, lineSegment(rotatedA, rotatedB));
|
||||
|
||||
if (hits.length > 0) {
|
||||
for (const j of hits) {
|
||||
intersections.push(pointRotateRads(j, center, element.angle));
|
||||
}
|
||||
|
||||
if (onlyFirst) {
|
||||
return intersections;
|
||||
}
|
||||
}
|
||||
}
|
||||
curveIntersections(
|
||||
corners,
|
||||
rotatedIntersector,
|
||||
intersections,
|
||||
center,
|
||||
element.angle,
|
||||
onlyFirst,
|
||||
);
|
||||
|
||||
return intersections;
|
||||
};
|
||||
@@ -366,38 +442,32 @@ const intersectDiamondWithLineSegment = (
|
||||
// points. It's all the same distance-wise.
|
||||
const rotatedA = pointRotateRads(l[0], center, -element.angle as Radians);
|
||||
const rotatedB = pointRotateRads(l[1], center, -element.angle as Radians);
|
||||
const rotatedIntersector = lineSegment(rotatedA, rotatedB);
|
||||
|
||||
const [sides, corners] = deconstructDiamondElement(element, offset);
|
||||
|
||||
const intersections: GlobalPoint[] = [];
|
||||
|
||||
for (const s of sides) {
|
||||
const intersection = lineSegmentIntersectionPoints(
|
||||
lineSegment(rotatedA, rotatedB),
|
||||
s,
|
||||
);
|
||||
if (intersection) {
|
||||
intersections.push(pointRotateRads(intersection, center, element.angle));
|
||||
lineIntersections(
|
||||
sides,
|
||||
rotatedIntersector,
|
||||
intersections,
|
||||
center,
|
||||
element.angle,
|
||||
onlyFirst,
|
||||
);
|
||||
|
||||
if (onlyFirst) {
|
||||
return intersections;
|
||||
}
|
||||
}
|
||||
if (onlyFirst && intersections.length > 0) {
|
||||
return intersections;
|
||||
}
|
||||
|
||||
for (const t of corners) {
|
||||
const hits = curveIntersectLineSegment(t, lineSegment(rotatedA, rotatedB));
|
||||
|
||||
if (hits.length > 0) {
|
||||
for (const j of hits) {
|
||||
intersections.push(pointRotateRads(j, center, element.angle));
|
||||
}
|
||||
|
||||
if (onlyFirst) {
|
||||
return intersections;
|
||||
}
|
||||
}
|
||||
}
|
||||
curveIntersections(
|
||||
corners,
|
||||
rotatedIntersector,
|
||||
intersections,
|
||||
center,
|
||||
element.angle,
|
||||
onlyFirst,
|
||||
);
|
||||
|
||||
return intersections;
|
||||
};
|
||||
|
@@ -90,6 +90,12 @@ const setElementShapesCacheEntry = <T extends ExcalidrawElement>(
|
||||
shapes.set(offset, shape);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the **rotated** components of freedraw, line or arrow elements.
|
||||
*
|
||||
* @param element The linear element to deconstruct
|
||||
* @returns The rotated in components.
|
||||
*/
|
||||
export function deconstructLinearOrFreeDrawElement(
|
||||
element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement,
|
||||
): [LineSegment<GlobalPoint>[], Curve<GlobalPoint>[]] {
|
||||
@@ -171,11 +177,11 @@ export function deconstructLinearOrFreeDrawElement(
|
||||
|
||||
/**
|
||||
* Get the building components of a rectanguloid element in the form of
|
||||
* line segments and curves.
|
||||
* line segments and curves **unrotated**.
|
||||
*
|
||||
* @param element Target rectanguloid element
|
||||
* @param offset Optional offset to expand the rectanguloid shape
|
||||
* @returns Tuple of line segments (0) and curves (1)
|
||||
* @returns Tuple of **unrotated** line segments (0) and curves (1)
|
||||
*/
|
||||
export function deconstructRectanguloidElement(
|
||||
element: ExcalidrawRectanguloidElement,
|
||||
@@ -310,12 +316,12 @@ export function deconstructRectanguloidElement(
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the building components of a diamond element in the form of
|
||||
* line segments and curves as a tuple, in this order.
|
||||
* Get the **unrotated** building components of a diamond element
|
||||
* in the form of line segments and curves as a tuple, in this order.
|
||||
*
|
||||
* @param element The element to deconstruct
|
||||
* @param offset An optional offset
|
||||
* @returns Tuple of line segments (0) and curves (1)
|
||||
* @returns Tuple of line **unrotated** segments (0) and curves (1)
|
||||
*/
|
||||
export function deconstructDiamondElement(
|
||||
element: ExcalidrawDiamondElement,
|
||||
|
Reference in New Issue
Block a user