chore: Introducing RotatedBounds
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import type { Radians } from "@excalidraw/math";
|
||||
|
||||
/**
|
||||
* x and y position of top left corner, x and y position of bottom right corner
|
||||
*/
|
||||
@@ -6,7 +8,31 @@ export type Bounds = readonly [
|
||||
minY: number,
|
||||
maxX: number,
|
||||
maxY: number,
|
||||
];
|
||||
] & { _brand: "excalidraw__bounds" };
|
||||
|
||||
export type RotatedBounds = readonly [
|
||||
minX: number,
|
||||
minY: number,
|
||||
maxX: number,
|
||||
maxY: number,
|
||||
angle: Radians,
|
||||
] & {
|
||||
_brand_rotated: "excalidraw__rotated_bounds";
|
||||
};
|
||||
|
||||
export const bounds = <T extends Radians | undefined = undefined>(
|
||||
minX: number,
|
||||
minY: number,
|
||||
maxX: number,
|
||||
maxY: number,
|
||||
angle: T = undefined as T,
|
||||
) => {
|
||||
return (
|
||||
angle
|
||||
? ([minX, minY, maxX, maxY, angle] as unknown)
|
||||
: ([minX, minY, maxX, maxY] as unknown)
|
||||
) as T extends Radians ? RotatedBounds : Bounds;
|
||||
};
|
||||
|
||||
export const isBounds = (box: unknown): box is Bounds =>
|
||||
Array.isArray(box) &&
|
||||
@@ -15,3 +41,12 @@ export const isBounds = (box: unknown): box is Bounds =>
|
||||
typeof box[1] === "number" &&
|
||||
typeof box[2] === "number" &&
|
||||
typeof box[3] === "number";
|
||||
|
||||
export const isRotatedBounds = (box: unknown): box is RotatedBounds =>
|
||||
Array.isArray(box) &&
|
||||
box.length === 5 &&
|
||||
typeof box[0] === "number" &&
|
||||
typeof box[1] === "number" &&
|
||||
typeof box[2] === "number" &&
|
||||
typeof box[3] === "number" &&
|
||||
typeof box[4] === "number";
|
||||
|
||||
@@ -2,9 +2,11 @@ import rough from "roughjs/bin/rough";
|
||||
|
||||
import {
|
||||
arrayToMap,
|
||||
bounds,
|
||||
type Bounds,
|
||||
invariant,
|
||||
rescalePoints,
|
||||
type RotatedBounds,
|
||||
sizeOf,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
@@ -27,7 +29,6 @@ import type {
|
||||
GlobalPoint,
|
||||
LineSegment,
|
||||
LocalPoint,
|
||||
NonRotated,
|
||||
Radians,
|
||||
} from "@excalidraw/math";
|
||||
|
||||
@@ -91,7 +92,7 @@ export class ElementBounds {
|
||||
private static boundsCache = new WeakMap<
|
||||
ExcalidrawElement,
|
||||
{
|
||||
bounds: Bounds;
|
||||
bounds: RotatedBounds;
|
||||
version: ExcalidrawElement["version"];
|
||||
}
|
||||
>();
|
||||
@@ -103,11 +104,11 @@ export class ElementBounds {
|
||||
}
|
||||
>();
|
||||
|
||||
static getBounds(
|
||||
static getBounds<T extends boolean = false>(
|
||||
element: ExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
nonRotated: boolean = false,
|
||||
) {
|
||||
nonRotated: T = false as T,
|
||||
): T extends true ? Bounds : RotatedBounds {
|
||||
const cachedBounds =
|
||||
nonRotated && element.angle !== 0
|
||||
? ElementBounds.nonRotatedBoundsCache.get(element)
|
||||
@@ -120,40 +121,41 @@ export class ElementBounds {
|
||||
// which is causing problems down the line. Fix TBA.
|
||||
!isBoundToContainer(element)
|
||||
) {
|
||||
return cachedBounds.bounds;
|
||||
return cachedBounds.bounds as T extends true ? Bounds : RotatedBounds;
|
||||
}
|
||||
|
||||
if (nonRotated && element.angle !== 0) {
|
||||
const nonRotatedBounds = ElementBounds.calculateBounds(
|
||||
const [minX, minY, maxX, maxY] = ElementBounds.calculateBounds(
|
||||
{
|
||||
...element,
|
||||
angle: 0 as Radians,
|
||||
},
|
||||
elementsMap,
|
||||
);
|
||||
const nonRotatedBounds = bounds(minX, minY, maxX, maxY);
|
||||
ElementBounds.nonRotatedBoundsCache.set(element, {
|
||||
version: element.version,
|
||||
bounds: nonRotatedBounds,
|
||||
});
|
||||
|
||||
return nonRotatedBounds;
|
||||
return nonRotatedBounds as T extends true ? Bounds : RotatedBounds;
|
||||
}
|
||||
|
||||
const bounds = ElementBounds.calculateBounds(element, elementsMap);
|
||||
const _bounds = ElementBounds.calculateBounds(element, elementsMap);
|
||||
|
||||
ElementBounds.boundsCache.set(element, {
|
||||
version: element.version,
|
||||
bounds,
|
||||
bounds: _bounds,
|
||||
});
|
||||
|
||||
return bounds;
|
||||
return _bounds as T extends true ? Bounds : RotatedBounds;
|
||||
}
|
||||
|
||||
private static calculateBounds(
|
||||
element: ExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
): Bounds {
|
||||
let bounds: Bounds;
|
||||
): RotatedBounds {
|
||||
let _bounds: RotatedBounds;
|
||||
|
||||
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||
element,
|
||||
@@ -170,14 +172,15 @@ export class ElementBounds {
|
||||
),
|
||||
);
|
||||
|
||||
return [
|
||||
return bounds(
|
||||
minX + element.x,
|
||||
minY + element.y,
|
||||
maxX + element.x,
|
||||
maxY + element.y,
|
||||
];
|
||||
element.angle,
|
||||
);
|
||||
} else if (isLinearElement(element)) {
|
||||
bounds = getLinearElementRotatedBounds(element, cx, cy, elementsMap);
|
||||
_bounds = getLinearElementRotatedBounds(element, cx, cy, elementsMap);
|
||||
} else if (element.type === "diamond") {
|
||||
const [x11, y11] = pointRotateRads(
|
||||
pointFrom(cx, y1),
|
||||
@@ -203,7 +206,7 @@ export class ElementBounds {
|
||||
const minY = Math.min(y11, y12, y22, y21);
|
||||
const maxX = Math.max(x11, x12, x22, x21);
|
||||
const maxY = Math.max(y11, y12, y22, y21);
|
||||
bounds = [minX, minY, maxX, maxY];
|
||||
_bounds = bounds(minX, minY, maxX, maxY, element.angle);
|
||||
} else if (element.type === "ellipse") {
|
||||
const w = (x2 - x1) / 2;
|
||||
const h = (y2 - y1) / 2;
|
||||
@@ -211,7 +214,7 @@ export class ElementBounds {
|
||||
const sin = Math.sin(element.angle);
|
||||
const ww = Math.hypot(w * cos, h * sin);
|
||||
const hh = Math.hypot(h * cos, w * sin);
|
||||
bounds = [cx - ww, cy - hh, cx + ww, cy + hh];
|
||||
_bounds = bounds(cx - ww, cy - hh, cx + ww, cy + hh, element.angle);
|
||||
} else {
|
||||
const [x11, y11] = pointRotateRads(
|
||||
pointFrom(x1, y1),
|
||||
@@ -237,10 +240,10 @@ export class ElementBounds {
|
||||
const minY = Math.min(y11, y12, y22, y21);
|
||||
const maxX = Math.max(x11, x12, x22, x21);
|
||||
const maxY = Math.max(y11, y12, y22, y21);
|
||||
bounds = [minX, minY, maxX, maxY];
|
||||
_bounds = bounds(minX, minY, maxX, maxY, element.angle);
|
||||
}
|
||||
|
||||
return bounds;
|
||||
return _bounds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,7 +628,7 @@ export const getCubicBezierCurveBound = (
|
||||
minY = Math.min(minY, ...ys);
|
||||
maxY = Math.max(maxY, ...ys);
|
||||
}
|
||||
return [minX, minY, maxX, maxY];
|
||||
return bounds(minX, minY, maxX, maxY);
|
||||
};
|
||||
|
||||
export const getMinMaxXYFromCurvePathOps = (
|
||||
@@ -678,7 +681,7 @@ export const getMinMaxXYFromCurvePathOps = (
|
||||
},
|
||||
{ minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity },
|
||||
);
|
||||
return [minX, minY, maxX, maxY];
|
||||
return bounds(minX, minY, maxX, maxY);
|
||||
};
|
||||
|
||||
export const getBoundsFromPoints = (
|
||||
@@ -696,7 +699,7 @@ export const getBoundsFromPoints = (
|
||||
maxY = Math.max(maxY, y);
|
||||
}
|
||||
|
||||
return [minX, minY, maxX, maxY];
|
||||
return bounds(minX, minY, maxX, maxY);
|
||||
};
|
||||
|
||||
const getFreeDrawElementAbsoluteCoords = (
|
||||
@@ -939,7 +942,7 @@ const getLinearElementRotatedBounds = (
|
||||
cx: number,
|
||||
cy: number,
|
||||
elementsMap: ElementsMap,
|
||||
): Bounds => {
|
||||
): RotatedBounds => {
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
|
||||
if (element.points.length < 2) {
|
||||
@@ -950,20 +953,21 @@ const getLinearElementRotatedBounds = (
|
||||
element.angle,
|
||||
);
|
||||
|
||||
let coords: Bounds = [x, y, x, y];
|
||||
let coords: RotatedBounds = bounds(x, y, x, y, element.angle);
|
||||
if (boundTextElement) {
|
||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||
element,
|
||||
elementsMap,
|
||||
[x, y, x, y],
|
||||
bounds(x, y, x, y, element.angle),
|
||||
boundTextElement,
|
||||
);
|
||||
coords = [
|
||||
coords = bounds(
|
||||
coordsWithBoundText[0],
|
||||
coordsWithBoundText[1],
|
||||
coordsWithBoundText[2],
|
||||
coordsWithBoundText[3],
|
||||
];
|
||||
element.angle,
|
||||
);
|
||||
}
|
||||
return coords;
|
||||
}
|
||||
@@ -979,7 +983,13 @@ const getLinearElementRotatedBounds = (
|
||||
element.angle,
|
||||
);
|
||||
const res = getMinMaxXYFromCurvePathOps(ops, transformXY);
|
||||
let coords: Bounds = [res[0], res[1], res[2], res[3]];
|
||||
let coords: RotatedBounds = bounds(
|
||||
res[0],
|
||||
res[1],
|
||||
res[2],
|
||||
res[3],
|
||||
element.angle,
|
||||
);
|
||||
if (boundTextElement) {
|
||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||
element,
|
||||
@@ -987,12 +997,13 @@ const getLinearElementRotatedBounds = (
|
||||
coords,
|
||||
boundTextElement,
|
||||
);
|
||||
coords = [
|
||||
coords = bounds(
|
||||
coordsWithBoundText[0],
|
||||
coordsWithBoundText[1],
|
||||
coordsWithBoundText[2],
|
||||
coordsWithBoundText[3],
|
||||
];
|
||||
element.angle,
|
||||
);
|
||||
}
|
||||
return coords;
|
||||
};
|
||||
@@ -1001,12 +1012,8 @@ export const getElementBounds = <T extends boolean = false>(
|
||||
element: ExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
nonRotated: T = false as T,
|
||||
): T extends true ? NonRotated<Bounds> : Bounds => {
|
||||
return ElementBounds.getBounds(
|
||||
element,
|
||||
elementsMap,
|
||||
nonRotated,
|
||||
) as T extends true ? NonRotated<Bounds> : Bounds;
|
||||
) => {
|
||||
return ElementBounds.getBounds<T>(element, elementsMap, nonRotated);
|
||||
};
|
||||
|
||||
export const getCommonBounds = (
|
||||
@@ -1014,7 +1021,7 @@ export const getCommonBounds = (
|
||||
elementsMap?: ElementsMap,
|
||||
): Bounds => {
|
||||
if (!sizeOf(elements)) {
|
||||
return [0, 0, 0, 0];
|
||||
return bounds(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
let minX = Infinity;
|
||||
@@ -1032,7 +1039,7 @@ export const getCommonBounds = (
|
||||
maxY = Math.max(maxY, y2);
|
||||
});
|
||||
|
||||
return [minX, minY, maxX, maxY];
|
||||
return bounds(minX, minY, maxX, maxY);
|
||||
};
|
||||
|
||||
export const getDraggedElementsBounds = (
|
||||
@@ -1055,12 +1062,12 @@ export const getResizedElementAbsoluteCoords = (
|
||||
normalizePoints: boolean,
|
||||
): Bounds => {
|
||||
if (!(isLinearElement(element) || isFreeDrawElement(element))) {
|
||||
return [
|
||||
return bounds(
|
||||
element.x,
|
||||
element.y,
|
||||
element.x + nextWidth,
|
||||
element.y + nextHeight,
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
const points = rescalePoints(
|
||||
@@ -1070,11 +1077,11 @@ export const getResizedElementAbsoluteCoords = (
|
||||
normalizePoints,
|
||||
);
|
||||
|
||||
let bounds: Bounds;
|
||||
let _bounds: Bounds;
|
||||
|
||||
if (isFreeDrawElement(element)) {
|
||||
// Free Draw
|
||||
bounds = getBoundsFromPoints(points);
|
||||
_bounds = getBoundsFromPoints(points);
|
||||
} else {
|
||||
// Line
|
||||
const gen = rough.generator();
|
||||
@@ -1086,16 +1093,16 @@ export const getResizedElementAbsoluteCoords = (
|
||||
: gen.curve(points as [number, number][], generateRoughOptions(element));
|
||||
|
||||
const ops = getCurvePathOps(curve);
|
||||
bounds = getMinMaxXYFromCurvePathOps(ops);
|
||||
_bounds = getMinMaxXYFromCurvePathOps(ops);
|
||||
}
|
||||
|
||||
const [minX, minY, maxX, maxY] = bounds;
|
||||
return [
|
||||
const [minX, minY, maxX, maxY] = _bounds;
|
||||
return bounds(
|
||||
minX + element.x,
|
||||
minY + element.y,
|
||||
maxX + element.x,
|
||||
maxY + element.y,
|
||||
];
|
||||
);
|
||||
};
|
||||
|
||||
export const getElementPointsCoords = (
|
||||
@@ -1113,20 +1120,20 @@ export const getElementPointsCoords = (
|
||||
: gen.curve(points as [number, number][], generateRoughOptions(element));
|
||||
const ops = getCurvePathOps(curve);
|
||||
const [minX, minY, maxX, maxY] = getMinMaxXYFromCurvePathOps(ops);
|
||||
return [
|
||||
return bounds(
|
||||
minX + element.x,
|
||||
minY + element.y,
|
||||
maxX + element.x,
|
||||
maxY + element.y,
|
||||
];
|
||||
);
|
||||
};
|
||||
|
||||
export const getClosestElementBounds = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
from: { x: number; y: number },
|
||||
): Bounds => {
|
||||
): RotatedBounds => {
|
||||
if (!elements.length) {
|
||||
return [0, 0, 0, 0];
|
||||
return bounds(0, 0, 0, 0, 0 as Radians);
|
||||
}
|
||||
|
||||
let minDistance = Infinity;
|
||||
@@ -1195,7 +1202,9 @@ export const getVisibleSceneBounds = ({
|
||||
];
|
||||
};
|
||||
|
||||
export const getCenterForBounds = (bounds: Bounds): GlobalPoint =>
|
||||
export const getCenterForBounds = (
|
||||
bounds: Bounds | RotatedBounds,
|
||||
): GlobalPoint =>
|
||||
pointFrom(
|
||||
bounds[0] + (bounds[2] - bounds[0]) / 2,
|
||||
bounds[1] + (bounds[3] - bounds[1]) / 2,
|
||||
@@ -1240,24 +1249,24 @@ export const aabbForElement = (
|
||||
element.angle,
|
||||
);
|
||||
|
||||
const bounds = [
|
||||
const _bounds = bounds(
|
||||
Math.min(topLeftX, topRightX, bottomRightX, bottomLeftX),
|
||||
Math.min(topLeftY, topRightY, bottomRightY, bottomLeftY),
|
||||
Math.max(topLeftX, topRightX, bottomRightX, bottomLeftX),
|
||||
Math.max(topLeftY, topRightY, bottomRightY, bottomLeftY),
|
||||
] as Bounds;
|
||||
);
|
||||
|
||||
if (offset) {
|
||||
const [topOffset, rightOffset, downOffset, leftOffset] = offset;
|
||||
return [
|
||||
bounds[0] - leftOffset,
|
||||
bounds[1] - topOffset,
|
||||
bounds[2] + rightOffset,
|
||||
bounds[3] + downOffset,
|
||||
] as Bounds;
|
||||
return bounds(
|
||||
_bounds[0] - leftOffset,
|
||||
_bounds[1] - topOffset,
|
||||
_bounds[2] + rightOffset,
|
||||
_bounds[3] + downOffset,
|
||||
);
|
||||
}
|
||||
|
||||
return bounds;
|
||||
return _bounds;
|
||||
};
|
||||
|
||||
export const pointInsideBounds = <P extends GlobalPoint | LocalPoint>(
|
||||
@@ -1266,7 +1275,7 @@ 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 = (
|
||||
export const doNonRotatedBoundsIntersect = (
|
||||
bounds1: Bounds | null,
|
||||
bounds2: Bounds | null,
|
||||
): boolean => {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { invariant, isTransparent, type Bounds } from "@excalidraw/common";
|
||||
import {
|
||||
bounds,
|
||||
invariant,
|
||||
isTransparent,
|
||||
type Bounds,
|
||||
} from "@excalidraw/common";
|
||||
import {
|
||||
curveIntersectLineSegment,
|
||||
isPointWithinBounds,
|
||||
@@ -22,7 +27,6 @@ import type {
|
||||
Curve,
|
||||
GlobalPoint,
|
||||
LineSegment,
|
||||
NonRotated,
|
||||
Radians,
|
||||
} from "@excalidraw/math";
|
||||
|
||||
@@ -30,7 +34,7 @@ import type { FrameNameBounds } from "@excalidraw/excalidraw/types";
|
||||
|
||||
import { isPathALoop } from "./utils";
|
||||
import {
|
||||
doBoundsIntersect,
|
||||
doNonRotatedBoundsIntersect,
|
||||
elementCenterPoint,
|
||||
getCenterForBounds,
|
||||
getCubicBezierCurveBound,
|
||||
@@ -194,7 +198,7 @@ export function getBoundsCorners(
|
||||
bounds: Bounds,
|
||||
): readonly [GlobalPoint, GlobalPoint, GlobalPoint, GlobalPoint];
|
||||
export function getBoundsCorners(
|
||||
bounds: NonRotated<Bounds>,
|
||||
bounds: Bounds,
|
||||
angle: Radians,
|
||||
): readonly [GlobalPoint, GlobalPoint, GlobalPoint, GlobalPoint];
|
||||
export function getBoundsCorners(
|
||||
@@ -234,7 +238,7 @@ export const getBoundsEdges = (
|
||||
|
||||
const isPointInRotatedBounds = (
|
||||
point: GlobalPoint,
|
||||
bounds: NonRotated<Bounds>,
|
||||
bounds: Bounds,
|
||||
angle: Radians,
|
||||
tolerance = 0,
|
||||
) => {
|
||||
@@ -344,9 +348,13 @@ const bindingBorderTest = (
|
||||
// PERF: Run a cheap test to see if the binding element
|
||||
// is even close to the element
|
||||
const t = Math.max(1, tolerance);
|
||||
const bounds = [x - t, y - t, x + t, y + t] as Bounds;
|
||||
const elementBounds = getElementBounds(element, elementsMap);
|
||||
if (!doBoundsIntersect(bounds, elementBounds)) {
|
||||
const elementBounds = getElementBounds(element, elementsMap, true);
|
||||
if (
|
||||
!doNonRotatedBoundsIntersect(
|
||||
bounds(x - t, y - t, x + t, y + t),
|
||||
elementBounds,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -357,6 +365,7 @@ const bindingBorderTest = (
|
||||
const enclosingFrameBounds = getElementBounds(
|
||||
enclosingFrame,
|
||||
elementsMap,
|
||||
true,
|
||||
);
|
||||
if (!pointInsideBounds(p, enclosingFrameBounds)) {
|
||||
return false;
|
||||
@@ -506,15 +515,15 @@ export const intersectElementWithLineSegment = (
|
||||
): GlobalPoint[] => {
|
||||
// First check if the line intersects the element's axis-aligned bounding box
|
||||
// as it is much faster than checking intersection against the element's shape
|
||||
const intersectorBounds = [
|
||||
const intersectorBounds = bounds(
|
||||
Math.min(line[0][0] - offset, line[1][0] - offset),
|
||||
Math.min(line[0][1] - offset, line[1][1] - offset),
|
||||
Math.max(line[0][0] + offset, line[1][0] + offset),
|
||||
Math.max(line[0][1] + offset, line[1][1] + offset),
|
||||
] as Bounds;
|
||||
const elementBounds = getElementBounds(element, elementsMap);
|
||||
);
|
||||
const elementBounds = getElementBounds(element, elementsMap, true);
|
||||
|
||||
if (!doBoundsIntersect(intersectorBounds, elementBounds)) {
|
||||
if (!doNonRotatedBoundsIntersect(intersectorBounds, elementBounds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -573,14 +582,14 @@ const curveIntersections = (
|
||||
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 = [
|
||||
const b2 = bounds(
|
||||
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)) {
|
||||
if (!doNonRotatedBoundsIntersect(b1, b2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -650,14 +659,14 @@ 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 = [
|
||||
const b2 = bounds(
|
||||
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)) {
|
||||
if (!doNonRotatedBoundsIntersect(b1, b2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
invariant,
|
||||
isShallowEqual,
|
||||
getFeatureFlag,
|
||||
bounds,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
@@ -42,7 +43,7 @@ import type {
|
||||
NullableGridSize,
|
||||
Zoom,
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
import type { Bounds } from "@excalidraw/common";
|
||||
import type { RotatedBounds } from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
calculateFixedPointForNonElbowArrowBinding,
|
||||
@@ -1879,7 +1880,7 @@ export class LinearElementEditor {
|
||||
static getMinMaxXYWithBoundText = (
|
||||
element: ExcalidrawLinearElement,
|
||||
elementsMap: ElementsMap,
|
||||
elementBounds: Bounds,
|
||||
elementBounds: RotatedBounds,
|
||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||
): [number, number, number, number, number, number] => {
|
||||
let [x1, y1, x2, y2] = elementBounds;
|
||||
@@ -2004,7 +2005,7 @@ export class LinearElementEditor {
|
||||
return LinearElementEditor.getMinMaxXYWithBoundText(
|
||||
element,
|
||||
elementsMap,
|
||||
[x1, y1, x2, y2],
|
||||
bounds(x1, y1, x2, y2, element.angle),
|
||||
boundTextElement,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ import {
|
||||
bindOrUnbindBindingElement,
|
||||
mutateElement,
|
||||
getElementBounds,
|
||||
doBoundsIntersect,
|
||||
doNonRotatedBoundsIntersect,
|
||||
isPointInElement,
|
||||
maxBindingDistance_simple,
|
||||
convertToExcalidrawElements,
|
||||
@@ -1154,7 +1154,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
startBounds &&
|
||||
endBounds &&
|
||||
startElement.id !== endElement.id &&
|
||||
doBoundsIntersect(startBounds, endBounds)
|
||||
doNonRotatedBoundsIntersect(startBounds, endBounds)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { arrayToMap, easeOut, THEME } from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
computeBoundTextPosition,
|
||||
doBoundsIntersect,
|
||||
doNonRotatedBoundsIntersect,
|
||||
getBoundTextElement,
|
||||
getElementBounds,
|
||||
getElementLineSegments,
|
||||
@@ -219,7 +219,7 @@ const eraserTest = (
|
||||
origElementBounds[3] + threshold,
|
||||
];
|
||||
|
||||
if (!doBoundsIntersect(segmentBounds, elementBounds)) {
|
||||
if (!doNonRotatedBoundsIntersect(segmentBounds, elementBounds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { type Bounds } from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
computeBoundTextPosition,
|
||||
doBoundsIntersect,
|
||||
doNonRotatedBoundsIntersect,
|
||||
getBoundTextElement,
|
||||
getElementBounds,
|
||||
intersectElementWithLineSegment,
|
||||
@@ -66,7 +66,7 @@ export const getLassoSelectedElementIds = (input: {
|
||||
const elementBounds = getElementBounds(element, elementsMap);
|
||||
|
||||
if (
|
||||
doBoundsIntersect(lassoBounds, elementBounds) &&
|
||||
doNonRotatedBoundsIntersect(lassoBounds, elementBounds) &&
|
||||
!intersectedElements.has(element.id) &&
|
||||
!enclosedElements.has(element.id)
|
||||
) {
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
//
|
||||
// Generic markers
|
||||
//
|
||||
|
||||
/**
|
||||
* Can be used for any type of point-likes to mark them as rotated to enlist
|
||||
* the type checker to weed out subtle bugs due to rotated and non-rotated
|
||||
* versions of the same data point.
|
||||
*/
|
||||
export type Rotated<T> = T & { _brand_rotated: "excalimath_rotated" };
|
||||
|
||||
//
|
||||
// Measurements
|
||||
//
|
||||
@@ -23,12 +34,6 @@ export type Degrees = number & { _brand: "excalimath_degree" };
|
||||
*/
|
||||
export type InclusiveRange = [number, number] & { _brand: "excalimath_degree" };
|
||||
|
||||
/**
|
||||
* Can be used for any type of point-likes that are non-rotated, such as
|
||||
* non-AABB Bounds, or non-rotated Point.
|
||||
*/
|
||||
export type NonRotated<T> = T & { _brand_nonrotated: "excalimath_nonrotated" };
|
||||
|
||||
//
|
||||
// Point
|
||||
//
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { arrayToMap, type Bounds } from "@excalidraw/common";
|
||||
import {
|
||||
arrayToMap,
|
||||
bounds,
|
||||
type RotatedBounds,
|
||||
type Bounds,
|
||||
} from "@excalidraw/common";
|
||||
import { getElementBounds } from "@excalidraw/element";
|
||||
import {
|
||||
isArrowElement,
|
||||
@@ -90,7 +95,7 @@ const getMinMaxPoints = (points: Points) => {
|
||||
return ret;
|
||||
};
|
||||
|
||||
const getRotatedBBox = (element: Element): Bounds => {
|
||||
const getRotatedBBox = (element: Element): RotatedBounds => {
|
||||
const points = getElementRelativePoints(element);
|
||||
|
||||
const { cx, cy } = getMinMaxPoints(points);
|
||||
@@ -101,12 +106,13 @@ const getRotatedBBox = (element: Element): Bounds => {
|
||||
);
|
||||
const { minX, minY, maxX, maxY } = getMinMaxPoints(rotatedPoints);
|
||||
|
||||
return [
|
||||
return bounds(
|
||||
minX + element.x,
|
||||
minY + element.y,
|
||||
maxX + element.x,
|
||||
maxY + element.y,
|
||||
];
|
||||
element.angle,
|
||||
);
|
||||
};
|
||||
|
||||
export const isElementInsideBBox = (
|
||||
@@ -160,12 +166,12 @@ export const elementPartiallyOverlapsWithOrContainsBBox = (
|
||||
|
||||
export const elementsOverlappingBBox = ({
|
||||
elements,
|
||||
bounds,
|
||||
bounds: _bounds,
|
||||
type,
|
||||
errorMargin = 0,
|
||||
}: {
|
||||
elements: Elements;
|
||||
bounds: Bounds | ExcalidrawElement;
|
||||
bounds: RotatedBounds | ExcalidrawElement;
|
||||
/** safety offset. Defaults to 0. */
|
||||
errorMargin?: number;
|
||||
/**
|
||||
@@ -175,15 +181,16 @@ export const elementsOverlappingBBox = ({
|
||||
**/
|
||||
type: "overlap" | "contain" | "inside";
|
||||
}) => {
|
||||
if (isExcalidrawElement(bounds)) {
|
||||
bounds = getElementBounds(bounds, arrayToMap(elements));
|
||||
if (isExcalidrawElement(_bounds)) {
|
||||
_bounds = getElementBounds(_bounds, arrayToMap(elements));
|
||||
}
|
||||
const adjustedBBox: Bounds = [
|
||||
bounds[0] - errorMargin,
|
||||
bounds[1] - errorMargin,
|
||||
bounds[2] + errorMargin,
|
||||
bounds[3] + errorMargin,
|
||||
];
|
||||
const adjustedBBox = bounds(
|
||||
_bounds[0] - errorMargin,
|
||||
_bounds[1] - errorMargin,
|
||||
_bounds[2] + errorMargin,
|
||||
_bounds[3] + errorMargin,
|
||||
_bounds[4],
|
||||
);
|
||||
|
||||
const includedElementSet = new Set<string>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user