Revert "State"
This reverts commit 4ba1bdaead0d74afea3c151efedeb59dfddf24de.
This commit is contained in:
parent
6ab3b6c029
commit
6e06fe9fda
@ -1,8 +1,17 @@
|
|||||||
import { simplify } from "points-on-curve";
|
import { simplify } from "points-on-curve";
|
||||||
|
|
||||||
import { pointFrom, type LocalPoint } from "@excalidraw/math";
|
import {
|
||||||
|
pointFrom,
|
||||||
|
pointDistance,
|
||||||
|
type LocalPoint,
|
||||||
|
pointRotateRads,
|
||||||
|
} from "@excalidraw/math";
|
||||||
import { ROUGHNESS, isTransparent, assertNever } from "@excalidraw/common";
|
import { ROUGHNESS, isTransparent, assertNever } from "@excalidraw/common";
|
||||||
|
|
||||||
|
import { RoughGenerator } from "roughjs/bin/generator";
|
||||||
|
|
||||||
|
import type { GlobalPoint, Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { Mutable } from "@excalidraw/common/utility-types";
|
import type { Mutable } from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
import type { EmbedsValidationStatus } from "@excalidraw/excalidraw/types";
|
import type { EmbedsValidationStatus } from "@excalidraw/excalidraw/types";
|
||||||
@ -16,11 +25,15 @@ import {
|
|||||||
isLinearElement,
|
isLinearElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
import { getCornerRadius, isPathALoop } from "./shapes";
|
import { getCornerRadius, isPathALoop } from "./shapes";
|
||||||
import { generateElbowArrowRougJshPathCommands } from "./utils";
|
import { headingForPointIsHorizontal } from "./heading";
|
||||||
|
|
||||||
import { canChangeRoundness } from "./comparisons";
|
import { canChangeRoundness } from "./comparisons";
|
||||||
import { generateFreeDrawShape } from "./renderElement";
|
import { generateFreeDrawShape } from "./renderElement";
|
||||||
import { getArrowheadPoints, getDiamondPoints } from "./bounds";
|
import {
|
||||||
|
getArrowheadPoints,
|
||||||
|
getDiamondPoints,
|
||||||
|
getElementBounds,
|
||||||
|
} from "./bounds";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
@ -28,11 +41,11 @@ import type {
|
|||||||
ExcalidrawSelectionElement,
|
ExcalidrawSelectionElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
Arrowhead,
|
Arrowhead,
|
||||||
|
ExcalidrawFreeDrawElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
import type { Drawable, Options } from "roughjs/bin/core";
|
import type { Drawable, Options } from "roughjs/bin/core";
|
||||||
import type { Point as RoughPoint } from "roughjs/bin/geometry";
|
import type { Point as RoughPoint } from "roughjs/bin/geometry";
|
||||||
import type { RoughGenerator } from "roughjs/bin/generator";
|
|
||||||
|
|
||||||
const getDashArrayDashed = (strokeWidth: number) => [8, 8 + strokeWidth];
|
const getDashArrayDashed = (strokeWidth: number) => [8, 8 + strokeWidth];
|
||||||
|
|
||||||
@ -303,6 +316,125 @@ const getArrowheadShapes = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateLinearCollisionShape = (
|
||||||
|
element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement,
|
||||||
|
) => {
|
||||||
|
const generator = new RoughGenerator();
|
||||||
|
const options: Options = {
|
||||||
|
seed: element.seed,
|
||||||
|
disableMultiStroke: true,
|
||||||
|
disableMultiStrokeFill: true,
|
||||||
|
roughness: 0,
|
||||||
|
preserveVertices: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (element.type) {
|
||||||
|
case "line":
|
||||||
|
case "arrow": {
|
||||||
|
// points array can be empty in the beginning, so it is important to add
|
||||||
|
// initial position to it
|
||||||
|
const points = element.points.length
|
||||||
|
? element.points
|
||||||
|
: [pointFrom<LocalPoint>(0, 0)];
|
||||||
|
const [x1, y1, x2, y2] = getElementBounds(
|
||||||
|
{
|
||||||
|
...element,
|
||||||
|
angle: 0 as Radians,
|
||||||
|
},
|
||||||
|
new Map(),
|
||||||
|
);
|
||||||
|
const center = pointFrom<GlobalPoint>((x1 + x2) / 2, (y1 + y2) / 2);
|
||||||
|
|
||||||
|
if (isElbowArrow(element)) {
|
||||||
|
return generator.path(generateElbowArrowShape(points, 16), options)
|
||||||
|
.sets[0].ops;
|
||||||
|
} else if (!element.roundness) {
|
||||||
|
return points.map((point, idx) => {
|
||||||
|
const p = pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(element.x + point[0], element.y + point[1]),
|
||||||
|
center,
|
||||||
|
element.angle,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
op: idx === 0 ? "move" : "lineTo",
|
||||||
|
data: pointFrom<LocalPoint>(p[0] - element.x, p[1] - element.y),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return generator
|
||||||
|
.curve(points as unknown as RoughPoint[], options)
|
||||||
|
.sets[0].ops.slice(0, element.points.length)
|
||||||
|
.map((op, i, arr) => {
|
||||||
|
if (i === 0) {
|
||||||
|
const p = pointRotateRads<GlobalPoint>(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + op.data[0],
|
||||||
|
element.y + op.data[1],
|
||||||
|
),
|
||||||
|
center,
|
||||||
|
element.angle,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
op: "move",
|
||||||
|
data: pointFrom<LocalPoint>(p[0] - element.x, p[1] - element.y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
op: "bcurveTo",
|
||||||
|
data: [
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + op.data[0],
|
||||||
|
element.y + op.data[1],
|
||||||
|
),
|
||||||
|
center,
|
||||||
|
element.angle,
|
||||||
|
),
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + op.data[2],
|
||||||
|
element.y + op.data[3],
|
||||||
|
),
|
||||||
|
center,
|
||||||
|
element.angle,
|
||||||
|
),
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + op.data[4],
|
||||||
|
element.y + op.data[5],
|
||||||
|
),
|
||||||
|
center,
|
||||||
|
element.angle,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.map((p) =>
|
||||||
|
pointFrom<LocalPoint>(p[0] - element.x, p[1] - element.y),
|
||||||
|
)
|
||||||
|
.flat(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case "freedraw": {
|
||||||
|
if (element.points.length < 2) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const simplifiedPoints = simplify(
|
||||||
|
element.points as Mutable<LocalPoint[]>,
|
||||||
|
0.75,
|
||||||
|
);
|
||||||
|
|
||||||
|
return generator
|
||||||
|
.curve(simplifiedPoints as [number, number][], options)
|
||||||
|
.sets[0].ops.slice(0, element.points.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the roughjs shape for given element.
|
* Generates the roughjs shape for given element.
|
||||||
*
|
*
|
||||||
@ -452,7 +584,7 @@ export const _generateElementShape = (
|
|||||||
} else {
|
} else {
|
||||||
shape = [
|
shape = [
|
||||||
generator.path(
|
generator.path(
|
||||||
generateElbowArrowRougJshPathCommands(points, 16),
|
generateElbowArrowShape(points, 16),
|
||||||
generateRoughOptions(element, true),
|
generateRoughOptions(element, true),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@ -546,3 +678,68 @@ export const _generateElementShape = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generateElbowArrowShape = (
|
||||||
|
points: readonly LocalPoint[],
|
||||||
|
radius: number,
|
||||||
|
) => {
|
||||||
|
const subpoints = [] as [number, number][];
|
||||||
|
for (let i = 1; i < points.length - 1; i += 1) {
|
||||||
|
const prev = points[i - 1];
|
||||||
|
const next = points[i + 1];
|
||||||
|
const point = points[i];
|
||||||
|
const prevIsHorizontal = headingForPointIsHorizontal(point, prev);
|
||||||
|
const nextIsHorizontal = headingForPointIsHorizontal(next, point);
|
||||||
|
const corner = Math.min(
|
||||||
|
radius,
|
||||||
|
pointDistance(points[i], next) / 2,
|
||||||
|
pointDistance(points[i], prev) / 2,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (prevIsHorizontal) {
|
||||||
|
if (prev[0] < point[0]) {
|
||||||
|
// LEFT
|
||||||
|
subpoints.push([points[i][0] - corner, points[i][1]]);
|
||||||
|
} else {
|
||||||
|
// RIGHT
|
||||||
|
subpoints.push([points[i][0] + corner, points[i][1]]);
|
||||||
|
}
|
||||||
|
} else if (prev[1] < point[1]) {
|
||||||
|
// UP
|
||||||
|
subpoints.push([points[i][0], points[i][1] - corner]);
|
||||||
|
} else {
|
||||||
|
subpoints.push([points[i][0], points[i][1] + corner]);
|
||||||
|
}
|
||||||
|
|
||||||
|
subpoints.push(points[i] as [number, number]);
|
||||||
|
|
||||||
|
if (nextIsHorizontal) {
|
||||||
|
if (next[0] < point[0]) {
|
||||||
|
// LEFT
|
||||||
|
subpoints.push([points[i][0] - corner, points[i][1]]);
|
||||||
|
} else {
|
||||||
|
// RIGHT
|
||||||
|
subpoints.push([points[i][0] + corner, points[i][1]]);
|
||||||
|
}
|
||||||
|
} else if (next[1] < point[1]) {
|
||||||
|
// UP
|
||||||
|
subpoints.push([points[i][0], points[i][1] - corner]);
|
||||||
|
} else {
|
||||||
|
// DOWN
|
||||||
|
subpoints.push([points[i][0], points[i][1] + corner]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = [`M ${points[0][0]} ${points[0][1]}`];
|
||||||
|
for (let i = 0; i < subpoints.length; i += 3) {
|
||||||
|
d.push(`L ${subpoints[i][0]} ${subpoints[i][1]}`);
|
||||||
|
d.push(
|
||||||
|
`Q ${subpoints[i + 1][0]} ${subpoints[i + 1][1]}, ${
|
||||||
|
subpoints[i + 2][0]
|
||||||
|
} ${subpoints[i + 2][1]}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
d.push(`L ${points[points.length - 1][0]} ${points[points.length - 1][1]}`);
|
||||||
|
|
||||||
|
return d.join(" ");
|
||||||
|
};
|
||||||
|
@ -226,7 +226,10 @@ const getOriginalBindingIfStillCloseOfLinearElementEdge = (
|
|||||||
: linearElement.endBinding?.elementId;
|
: linearElement.endBinding?.elementId;
|
||||||
if (elementId) {
|
if (elementId) {
|
||||||
const element = elementsMap.get(elementId);
|
const element = elementsMap.get(elementId);
|
||||||
if (isBindableElement(element) && bindingBorderTest(element, coors, zoom)) {
|
if (
|
||||||
|
isBindableElement(element) &&
|
||||||
|
bindingBorderTest(element, coors, elementsMap, zoom)
|
||||||
|
) {
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -556,6 +559,7 @@ export const getHoveredElementForBinding = (
|
|||||||
bindingBorderTest(
|
bindingBorderTest(
|
||||||
element,
|
element,
|
||||||
pointerCoords,
|
pointerCoords,
|
||||||
|
elementsMap,
|
||||||
zoom,
|
zoom,
|
||||||
(fullShape ||
|
(fullShape ||
|
||||||
!isBindingFallthroughEnabled(
|
!isBindingFallthroughEnabled(
|
||||||
@ -588,7 +592,7 @@ export const getHoveredElementForBinding = (
|
|||||||
|
|
||||||
// Prefer the shape with the border being tested (if any)
|
// Prefer the shape with the border being tested (if any)
|
||||||
const borderTestElements = candidateElements.filter((element) =>
|
const borderTestElements = candidateElements.filter((element) =>
|
||||||
bindingBorderTest(element, pointerCoords, zoom, false),
|
bindingBorderTest(element, pointerCoords, elementsMap, zoom, false),
|
||||||
);
|
);
|
||||||
if (borderTestElements.length === 1) {
|
if (borderTestElements.length === 1) {
|
||||||
return borderTestElements[0];
|
return borderTestElements[0];
|
||||||
@ -609,6 +613,7 @@ export const getHoveredElementForBinding = (
|
|||||||
bindingBorderTest(
|
bindingBorderTest(
|
||||||
element,
|
element,
|
||||||
pointerCoords,
|
pointerCoords,
|
||||||
|
elementsMap,
|
||||||
zoom,
|
zoom,
|
||||||
// disable fullshape snapping for frame elements so we
|
// disable fullshape snapping for frame elements so we
|
||||||
// can bind to frame children
|
// can bind to frame children
|
||||||
@ -1540,6 +1545,7 @@ const newBoundElements = (
|
|||||||
export const bindingBorderTest = (
|
export const bindingBorderTest = (
|
||||||
element: NonDeleted<ExcalidrawBindableElement>,
|
element: NonDeleted<ExcalidrawBindableElement>,
|
||||||
{ x, y }: { x: number; y: number },
|
{ x, y }: { x: number; y: number },
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
zoom?: AppState["zoom"],
|
zoom?: AppState["zoom"],
|
||||||
fullShape?: boolean,
|
fullShape?: boolean,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
|
@ -9,22 +9,29 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
degreesToRadians,
|
degreesToRadians,
|
||||||
|
lineSegment,
|
||||||
pointDistance,
|
pointDistance,
|
||||||
pointFrom,
|
pointFrom,
|
||||||
pointFromArray,
|
pointFromArray,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
|
import { getCurvePathOps } from "@excalidraw/utils/shape";
|
||||||
|
|
||||||
|
import { pointsOnBezierCurves } from "points-on-curve";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
Curve,
|
||||||
Degrees,
|
Degrees,
|
||||||
GlobalPoint,
|
GlobalPoint,
|
||||||
|
LineSegment,
|
||||||
LocalPoint,
|
LocalPoint,
|
||||||
Radians,
|
Radians,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type { AppState } from "@excalidraw/excalidraw/types";
|
import type { AppState } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import { getCurvePathOps } from "./utils";
|
import type { Mutable } from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
import { generateRoughOptions } from "./Shape";
|
import { generateRoughOptions } from "./Shape";
|
||||||
import { ShapeCache } from "./ShapeCache";
|
import { ShapeCache } from "./ShapeCache";
|
||||||
@ -38,6 +45,13 @@ import {
|
|||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
|
|
||||||
|
import { getElementShape } from "./shapes";
|
||||||
|
|
||||||
|
import {
|
||||||
|
deconstructDiamondElement,
|
||||||
|
deconstructRectanguloidElement,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
import type { Drawable, Op } from "roughjs/bin/core";
|
import type { Drawable, Op } from "roughjs/bin/core";
|
||||||
import type { Point as RoughPoint } from "roughjs/bin/geometry";
|
import type { Point as RoughPoint } from "roughjs/bin/geometry";
|
||||||
import type {
|
import type {
|
||||||
@ -45,8 +59,10 @@ import type {
|
|||||||
ElementsMap,
|
ElementsMap,
|
||||||
ElementsMapOrArray,
|
ElementsMapOrArray,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
|
ExcalidrawEllipseElement,
|
||||||
ExcalidrawFreeDrawElement,
|
ExcalidrawFreeDrawElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
|
ExcalidrawRectanguloidElement,
|
||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
@ -251,6 +267,199 @@ export const getElementAbsoluteCoords = (
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* for a given element, `getElementLineSegments` returns line segments
|
||||||
|
* that can be used for visual collision detection (useful for frames)
|
||||||
|
* as opposed to bounding box collision detection
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Given an element, return the line segments that make up the element.
|
||||||
|
*
|
||||||
|
* Uses helpers from /math
|
||||||
|
*/
|
||||||
|
export const getElementLineSegments = (
|
||||||
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
): LineSegment<GlobalPoint>[] => {
|
||||||
|
const shape = getElementShape(element, elementsMap);
|
||||||
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
const center = pointFrom<GlobalPoint>(cx, cy);
|
||||||
|
|
||||||
|
if (shape.type === "polycurve") {
|
||||||
|
const curves = shape.data;
|
||||||
|
const points = curves
|
||||||
|
.map((curve) => pointsOnBezierCurves(curve, 10))
|
||||||
|
.flat();
|
||||||
|
let i = 0;
|
||||||
|
const segments: LineSegment<GlobalPoint>[] = [];
|
||||||
|
while (i < points.length - 1) {
|
||||||
|
segments.push(
|
||||||
|
lineSegment(
|
||||||
|
pointFrom(points[i][0], points[i][1]),
|
||||||
|
pointFrom(points[i + 1][0], points[i + 1][1]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments;
|
||||||
|
} else if (shape.type === "polyline") {
|
||||||
|
return shape.data as LineSegment<GlobalPoint>[];
|
||||||
|
} else if (_isRectanguloidElement(element)) {
|
||||||
|
const [sides, corners] = deconstructRectanguloidElement(element);
|
||||||
|
const cornerSegments: LineSegment<GlobalPoint>[] = corners
|
||||||
|
.map((corner) => getSegmentsOnCurve(corner, center, element.angle))
|
||||||
|
.flat();
|
||||||
|
const rotatedSides = getRotatedSides(sides, center, element.angle);
|
||||||
|
return [...rotatedSides, ...cornerSegments];
|
||||||
|
} else if (element.type === "diamond") {
|
||||||
|
const [sides, corners] = deconstructDiamondElement(element);
|
||||||
|
const cornerSegments = corners
|
||||||
|
.map((corner) => getSegmentsOnCurve(corner, center, element.angle))
|
||||||
|
.flat();
|
||||||
|
const rotatedSides = getRotatedSides(sides, center, element.angle);
|
||||||
|
|
||||||
|
return [...rotatedSides, ...cornerSegments];
|
||||||
|
} else if (shape.type === "polygon") {
|
||||||
|
if (isTextElement(element)) {
|
||||||
|
const container = getContainerElement(element, elementsMap);
|
||||||
|
if (container && isLinearElement(container)) {
|
||||||
|
const segments: LineSegment<GlobalPoint>[] = [
|
||||||
|
lineSegment(pointFrom(x1, y1), pointFrom(x2, y1)),
|
||||||
|
lineSegment(pointFrom(x2, y1), pointFrom(x2, y2)),
|
||||||
|
lineSegment(pointFrom(x2, y2), pointFrom(x1, y2)),
|
||||||
|
lineSegment(pointFrom(x1, y2), pointFrom(x1, y1)),
|
||||||
|
];
|
||||||
|
return segments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const points = shape.data as GlobalPoint[];
|
||||||
|
const segments: LineSegment<GlobalPoint>[] = [];
|
||||||
|
for (let i = 0; i < points.length - 1; i++) {
|
||||||
|
segments.push(lineSegment(points[i], points[i + 1]));
|
||||||
|
}
|
||||||
|
return segments;
|
||||||
|
} else if (shape.type === "ellipse") {
|
||||||
|
return getSegmentsOnEllipse(element as ExcalidrawEllipseElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [nw, ne, sw, se, , , w, e] = (
|
||||||
|
[
|
||||||
|
[x1, y1],
|
||||||
|
[x2, y1],
|
||||||
|
[x1, y2],
|
||||||
|
[x2, y2],
|
||||||
|
[cx, y1],
|
||||||
|
[cx, y2],
|
||||||
|
[x1, cy],
|
||||||
|
[x2, cy],
|
||||||
|
] as GlobalPoint[]
|
||||||
|
).map((point) => pointRotateRads(point, center, element.angle));
|
||||||
|
|
||||||
|
return [
|
||||||
|
lineSegment(nw, ne),
|
||||||
|
lineSegment(sw, se),
|
||||||
|
lineSegment(nw, sw),
|
||||||
|
lineSegment(ne, se),
|
||||||
|
lineSegment(nw, e),
|
||||||
|
lineSegment(sw, e),
|
||||||
|
lineSegment(ne, w),
|
||||||
|
lineSegment(se, w),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const _isRectanguloidElement = (
|
||||||
|
element: ExcalidrawElement,
|
||||||
|
): element is ExcalidrawRectanguloidElement => {
|
||||||
|
return (
|
||||||
|
element != null &&
|
||||||
|
(element.type === "rectangle" ||
|
||||||
|
element.type === "image" ||
|
||||||
|
element.type === "iframe" ||
|
||||||
|
element.type === "embeddable" ||
|
||||||
|
element.type === "frame" ||
|
||||||
|
element.type === "magicframe" ||
|
||||||
|
(element.type === "text" && !element.containerId))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRotatedSides = (
|
||||||
|
sides: LineSegment<GlobalPoint>[],
|
||||||
|
center: GlobalPoint,
|
||||||
|
angle: Radians,
|
||||||
|
) => {
|
||||||
|
return sides.map((side) => {
|
||||||
|
return lineSegment(
|
||||||
|
pointRotateRads<GlobalPoint>(side[0], center, angle),
|
||||||
|
pointRotateRads<GlobalPoint>(side[1], center, angle),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSegmentsOnCurve = (
|
||||||
|
curve: Curve<GlobalPoint>,
|
||||||
|
center: GlobalPoint,
|
||||||
|
angle: Radians,
|
||||||
|
): LineSegment<GlobalPoint>[] => {
|
||||||
|
const points = pointsOnBezierCurves(curve, 10);
|
||||||
|
let i = 0;
|
||||||
|
const segments: LineSegment<GlobalPoint>[] = [];
|
||||||
|
while (i < points.length - 1) {
|
||||||
|
segments.push(
|
||||||
|
lineSegment(
|
||||||
|
pointRotateRads<GlobalPoint>(
|
||||||
|
pointFrom(points[i][0], points[i][1]),
|
||||||
|
center,
|
||||||
|
angle,
|
||||||
|
),
|
||||||
|
pointRotateRads<GlobalPoint>(
|
||||||
|
pointFrom(points[i + 1][0], points[i + 1][1]),
|
||||||
|
center,
|
||||||
|
angle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSegmentsOnEllipse = (
|
||||||
|
ellipse: ExcalidrawEllipseElement,
|
||||||
|
): LineSegment<GlobalPoint>[] => {
|
||||||
|
const center = pointFrom<GlobalPoint>(
|
||||||
|
ellipse.x + ellipse.width / 2,
|
||||||
|
ellipse.y + ellipse.height / 2,
|
||||||
|
);
|
||||||
|
|
||||||
|
const a = ellipse.width / 2;
|
||||||
|
const b = ellipse.height / 2;
|
||||||
|
|
||||||
|
const segments: LineSegment<GlobalPoint>[] = [];
|
||||||
|
const points: GlobalPoint[] = [];
|
||||||
|
const n = 90;
|
||||||
|
const deltaT = (Math.PI * 2) / n;
|
||||||
|
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const t = i * deltaT;
|
||||||
|
const x = center[0] + a * Math.cos(t);
|
||||||
|
const y = center[1] + b * Math.sin(t);
|
||||||
|
points.push(pointRotateRads(pointFrom(x, y), center, ellipse.angle));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < points.length - 1; i++) {
|
||||||
|
segments.push(lineSegment(points[i], points[i + 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
segments.push(lineSegment(points[points.length - 1], points[0]));
|
||||||
|
return segments;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scene -> Scene coords, but in x1,x2,y1,y2 format.
|
* Scene -> Scene coords, but in x1,x2,y1,y2 format.
|
||||||
*
|
*
|
||||||
@ -659,7 +868,7 @@ const generateLinearElementShape = (
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
return generator[method](
|
return generator[method](
|
||||||
element.points as LocalPoint[] as RoughPoint[],
|
element.points as Mutable<LocalPoint>[] as RoughPoint[],
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
import { simplify } from "points-on-curve";
|
|
||||||
|
|
||||||
import { RoughGenerator } from "roughjs/bin/generator";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isTransparent,
|
isTransparent,
|
||||||
elementCenterPoint,
|
elementCenterPoint,
|
||||||
@ -21,43 +17,43 @@ import {
|
|||||||
vectorFromPoint,
|
vectorFromPoint,
|
||||||
vectorNormalize,
|
vectorNormalize,
|
||||||
vectorScale,
|
vectorScale,
|
||||||
curve,
|
} from "@excalidraw/math";
|
||||||
curveCatmullRomCubicApproxPoints,
|
|
||||||
curveOffsetPoints,
|
import {
|
||||||
pointFromArray,
|
|
||||||
rectangle,
|
|
||||||
ellipse,
|
ellipse,
|
||||||
ellipseSegmentInterceptPoints,
|
ellipseSegmentInterceptPoints,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math/ellipse";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Curve,
|
Curve,
|
||||||
GlobalPoint,
|
GlobalPoint,
|
||||||
LineSegment,
|
LineSegment,
|
||||||
LocalPoint,
|
|
||||||
Radians,
|
Radians,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type { FrameNameBounds } from "@excalidraw/excalidraw/types";
|
import type { FrameNameBounds } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import { getCornerRadius, isPathALoop } from "./shapes";
|
import { isPathALoop } from "./shapes";
|
||||||
import { getDiamondPoints, getElementBounds } from "./bounds";
|
import { getElementBounds } from "./bounds";
|
||||||
import { getBoundTextElement } from "./textElement";
|
|
||||||
import { LinearElementEditor } from "./linearElementEditor";
|
|
||||||
import { distanceToElement } from "./distance";
|
|
||||||
import { generateElbowArrowRougJshPathCommands } from "./utils";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
hasBoundTextElement,
|
hasBoundTextElement,
|
||||||
isElbowArrow,
|
|
||||||
isFreeDrawElement,
|
isFreeDrawElement,
|
||||||
isIframeLikeElement,
|
isIframeLikeElement,
|
||||||
isImageElement,
|
isImageElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
|
import {
|
||||||
|
deconstructDiamondElement,
|
||||||
|
deconstructLinearOrFreeDrawElement,
|
||||||
|
deconstructRectanguloidElement,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
import type { Options } from "roughjs/bin/core";
|
import { getBoundTextElement } from "./textElement";
|
||||||
import type { Point as RoughPoint } from "roughjs/bin/geometry";
|
|
||||||
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
|
|
||||||
|
import { distanceToElement } from "./distance";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
@ -115,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"
|
||||||
isPointInElement(point, element) ||
|
isPointInShape(point, element) ||
|
||||||
isPointOnElementOutline(point, element, threshold)
|
isPointOnShape(point, element, threshold)
|
||||||
: isPointOnElementOutline(point, element, threshold)
|
: isPointOnShape(point, element, threshold)
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
// hit test against a frame's name
|
// hit test against a frame's name
|
||||||
@ -181,7 +177,7 @@ export const hitElementBoundText = (
|
|||||||
}
|
}
|
||||||
: boundTextElementCandidate;
|
: boundTextElementCandidate;
|
||||||
|
|
||||||
return isPointInElement(point, boundTextElement);
|
return isPointInShape(point, boundTextElement);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -222,17 +218,19 @@ const intersectLinearOrFreeDrawWithLineSegment = (
|
|||||||
element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement,
|
element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement,
|
||||||
segment: LineSegment<GlobalPoint>,
|
segment: LineSegment<GlobalPoint>,
|
||||||
): GlobalPoint[] => {
|
): GlobalPoint[] => {
|
||||||
const shapes = deconstructLinearOrFreeDrawElementForCollision(element);
|
const shapes = deconstructLinearOrFreeDrawElement(element);
|
||||||
const intersections: GlobalPoint[] = [];
|
const intersections: GlobalPoint[] = [];
|
||||||
|
|
||||||
for (const shape of shapes) {
|
for (const shape of shapes) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case isCurve(shape):
|
case isCurve(shape):
|
||||||
|
//debugDrawCubicBezier(shape);
|
||||||
intersections.push(
|
intersections.push(
|
||||||
...curveIntersectLineSegment(shape as Curve<GlobalPoint>, segment),
|
...curveIntersectLineSegment(shape as Curve<GlobalPoint>, segment),
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
case isLineSegment(shape):
|
case isLineSegment(shape):
|
||||||
|
//debugDrawLine(shape);
|
||||||
const point = lineSegmentIntersectionPoints(
|
const point = lineSegmentIntersectionPoints(
|
||||||
segment,
|
segment,
|
||||||
shape as LineSegment<GlobalPoint>,
|
shape as LineSegment<GlobalPoint>,
|
||||||
@ -269,10 +267,7 @@ const intersectRectanguloidWithLineSegment = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Get the element's building components we can test against
|
// Get the element's building components we can test against
|
||||||
const [sides, corners] = deconstructRectanguloidElementForCollision(
|
const [sides, corners] = deconstructRectanguloidElement(element, offset);
|
||||||
element,
|
|
||||||
offset,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// Test intersection against the sides, keep only the valid
|
// Test intersection against the sides, keep only the valid
|
||||||
@ -323,10 +318,7 @@ const intersectDiamondWithLineSegment = (
|
|||||||
const rotatedA = pointRotateRads(l[0], center, -element.angle as Radians);
|
const rotatedA = pointRotateRads(l[0], center, -element.angle as Radians);
|
||||||
const rotatedB = pointRotateRads(l[1], center, -element.angle as Radians);
|
const rotatedB = pointRotateRads(l[1], center, -element.angle as Radians);
|
||||||
|
|
||||||
const [sides, curves] = deconstructDiamondElementForCollision(
|
const [sides, curves] = deconstructDiamondElement(element, offset);
|
||||||
element,
|
|
||||||
offset,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
sides
|
sides
|
||||||
@ -379,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 isPointOnElementOutline = (
|
const isPointOnShape = (
|
||||||
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 isPointInElement = (
|
export const isPointInShape = (
|
||||||
point: GlobalPoint,
|
point: GlobalPoint,
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
) => {
|
) => {
|
||||||
@ -415,437 +407,3 @@ export const isPointInElement = (
|
|||||||
|
|
||||||
return intersections.length % 2 === 1;
|
return intersections.length % 2 === 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function deconstructLinearOrFreeDrawElementForCollision(
|
|
||||||
element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement,
|
|
||||||
): (Curve<GlobalPoint> | LineSegment<GlobalPoint>)[] {
|
|
||||||
const ops = generateLinearShapesForCollision(element) as {
|
|
||||||
op: string;
|
|
||||||
data: number[];
|
|
||||||
}[];
|
|
||||||
const components = [];
|
|
||||||
|
|
||||||
for (let idx = 0; idx < ops.length; idx += 1) {
|
|
||||||
const op = ops[idx];
|
|
||||||
const prevPoint =
|
|
||||||
ops[idx - 1] && pointFromArray<LocalPoint>(ops[idx - 1].data.slice(-2));
|
|
||||||
switch (op.op) {
|
|
||||||
case "move":
|
|
||||||
continue;
|
|
||||||
case "lineTo":
|
|
||||||
if (!prevPoint) {
|
|
||||||
throw new Error("prevPoint is undefined");
|
|
||||||
}
|
|
||||||
|
|
||||||
components.push(
|
|
||||||
lineSegment<GlobalPoint>(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + prevPoint[0],
|
|
||||||
element.y + prevPoint[1],
|
|
||||||
),
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + op.data[0],
|
|
||||||
element.y + op.data[1],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
case "bcurveTo":
|
|
||||||
if (!prevPoint) {
|
|
||||||
throw new Error("prevPoint is undefined");
|
|
||||||
}
|
|
||||||
|
|
||||||
components.push(
|
|
||||||
curve<GlobalPoint>(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + prevPoint[0],
|
|
||||||
element.y + prevPoint[1],
|
|
||||||
),
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + op.data[0],
|
|
||||||
element.y + op.data[1],
|
|
||||||
),
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + op.data[2],
|
|
||||||
element.y + op.data[3],
|
|
||||||
),
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + op.data[4],
|
|
||||||
element.y + op.data[5],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
default: {
|
|
||||||
console.error("Unknown op type", op.op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return components;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the building components of a rectanguloid element in the form of
|
|
||||||
* line segments and curves.
|
|
||||||
*
|
|
||||||
* @param element Target rectanguloid element
|
|
||||||
* @param offset Optional offset to expand the rectanguloid shape
|
|
||||||
* @returns Tuple of line segments (0) and curves (1)
|
|
||||||
*/
|
|
||||||
export function deconstructRectanguloidElementForCollision(
|
|
||||||
element: ExcalidrawRectanguloidElement,
|
|
||||||
offset: number = 0,
|
|
||||||
): [LineSegment<GlobalPoint>[], Curve<GlobalPoint>[]] {
|
|
||||||
let radius = getCornerRadius(
|
|
||||||
Math.min(element.width, element.height),
|
|
||||||
element,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (radius === 0) {
|
|
||||||
radius = 0.01;
|
|
||||||
}
|
|
||||||
|
|
||||||
const r = rectangle(
|
|
||||||
pointFrom(element.x, element.y),
|
|
||||||
pointFrom(element.x + element.width, element.y + element.height),
|
|
||||||
);
|
|
||||||
|
|
||||||
const top = lineSegment<GlobalPoint>(
|
|
||||||
pointFrom<GlobalPoint>(r[0][0] + radius, r[0][1]),
|
|
||||||
pointFrom<GlobalPoint>(r[1][0] - radius, r[0][1]),
|
|
||||||
);
|
|
||||||
const right = lineSegment<GlobalPoint>(
|
|
||||||
pointFrom<GlobalPoint>(r[1][0], r[0][1] + radius),
|
|
||||||
pointFrom<GlobalPoint>(r[1][0], r[1][1] - radius),
|
|
||||||
);
|
|
||||||
const bottom = lineSegment<GlobalPoint>(
|
|
||||||
pointFrom<GlobalPoint>(r[0][0] + radius, r[1][1]),
|
|
||||||
pointFrom<GlobalPoint>(r[1][0] - radius, r[1][1]),
|
|
||||||
);
|
|
||||||
const left = lineSegment<GlobalPoint>(
|
|
||||||
pointFrom<GlobalPoint>(r[0][0], r[1][1] - radius),
|
|
||||||
pointFrom<GlobalPoint>(r[0][0], r[0][1] + radius),
|
|
||||||
);
|
|
||||||
|
|
||||||
const baseCorners = [
|
|
||||||
curve(
|
|
||||||
left[1],
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
left[1][0] + (2 / 3) * (r[0][0] - left[1][0]),
|
|
||||||
left[1][1] + (2 / 3) * (r[0][1] - left[1][1]),
|
|
||||||
),
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
top[0][0] + (2 / 3) * (r[0][0] - top[0][0]),
|
|
||||||
top[0][1] + (2 / 3) * (r[0][1] - top[0][1]),
|
|
||||||
),
|
|
||||||
top[0],
|
|
||||||
), // TOP LEFT
|
|
||||||
curve(
|
|
||||||
top[1],
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
top[1][0] + (2 / 3) * (r[1][0] - top[1][0]),
|
|
||||||
top[1][1] + (2 / 3) * (r[0][1] - top[1][1]),
|
|
||||||
),
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
right[0][0] + (2 / 3) * (r[1][0] - right[0][0]),
|
|
||||||
right[0][1] + (2 / 3) * (r[0][1] - right[0][1]),
|
|
||||||
),
|
|
||||||
right[0],
|
|
||||||
), // TOP RIGHT
|
|
||||||
curve(
|
|
||||||
right[1],
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
right[1][0] + (2 / 3) * (r[1][0] - right[1][0]),
|
|
||||||
right[1][1] + (2 / 3) * (r[1][1] - right[1][1]),
|
|
||||||
),
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
bottom[1][0] + (2 / 3) * (r[1][0] - bottom[1][0]),
|
|
||||||
bottom[1][1] + (2 / 3) * (r[1][1] - bottom[1][1]),
|
|
||||||
),
|
|
||||||
bottom[1],
|
|
||||||
), // BOTTOM RIGHT
|
|
||||||
curve(
|
|
||||||
bottom[0],
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
bottom[0][0] + (2 / 3) * (r[0][0] - bottom[0][0]),
|
|
||||||
bottom[0][1] + (2 / 3) * (r[1][1] - bottom[0][1]),
|
|
||||||
),
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
left[0][0] + (2 / 3) * (r[0][0] - left[0][0]),
|
|
||||||
left[0][1] + (2 / 3) * (r[1][1] - left[0][1]),
|
|
||||||
),
|
|
||||||
left[0],
|
|
||||||
), // BOTTOM LEFT
|
|
||||||
];
|
|
||||||
|
|
||||||
const corners =
|
|
||||||
offset > 0
|
|
||||||
? baseCorners.map(
|
|
||||||
(corner) =>
|
|
||||||
curveCatmullRomCubicApproxPoints(
|
|
||||||
curveOffsetPoints(corner, offset),
|
|
||||||
)!,
|
|
||||||
)
|
|
||||||
: [
|
|
||||||
[baseCorners[0]],
|
|
||||||
[baseCorners[1]],
|
|
||||||
[baseCorners[2]],
|
|
||||||
[baseCorners[3]],
|
|
||||||
];
|
|
||||||
|
|
||||||
const sides = [
|
|
||||||
lineSegment<GlobalPoint>(
|
|
||||||
corners[0][corners[0].length - 1][3],
|
|
||||||
corners[1][0][0],
|
|
||||||
),
|
|
||||||
lineSegment<GlobalPoint>(
|
|
||||||
corners[1][corners[1].length - 1][3],
|
|
||||||
corners[2][0][0],
|
|
||||||
),
|
|
||||||
lineSegment<GlobalPoint>(
|
|
||||||
corners[2][corners[2].length - 1][3],
|
|
||||||
corners[3][0][0],
|
|
||||||
),
|
|
||||||
lineSegment<GlobalPoint>(
|
|
||||||
corners[3][corners[3].length - 1][3],
|
|
||||||
corners[0][0][0],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
return [sides, corners.flat()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the 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)
|
|
||||||
*/
|
|
||||||
export function deconstructDiamondElementForCollision(
|
|
||||||
element: ExcalidrawDiamondElement,
|
|
||||||
offset: number = 0,
|
|
||||||
): [LineSegment<GlobalPoint>[], Curve<GlobalPoint>[]] {
|
|
||||||
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
|
|
||||||
getDiamondPoints(element);
|
|
||||||
const verticalRadius = element.roundness
|
|
||||||
? getCornerRadius(Math.abs(topX - leftX), element)
|
|
||||||
: (topX - leftX) * 0.01;
|
|
||||||
const horizontalRadius = element.roundness
|
|
||||||
? getCornerRadius(Math.abs(rightY - topY), element)
|
|
||||||
: (rightY - topY) * 0.01;
|
|
||||||
|
|
||||||
const [top, right, bottom, left]: GlobalPoint[] = [
|
|
||||||
pointFrom(element.x + topX, element.y + topY),
|
|
||||||
pointFrom(element.x + rightX, element.y + rightY),
|
|
||||||
pointFrom(element.x + bottomX, element.y + bottomY),
|
|
||||||
pointFrom(element.x + leftX, element.y + leftY),
|
|
||||||
];
|
|
||||||
|
|
||||||
const baseCorners = [
|
|
||||||
curve(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
right[0] - verticalRadius,
|
|
||||||
right[1] - horizontalRadius,
|
|
||||||
),
|
|
||||||
right,
|
|
||||||
right,
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
right[0] - verticalRadius,
|
|
||||||
right[1] + horizontalRadius,
|
|
||||||
),
|
|
||||||
), // RIGHT
|
|
||||||
curve(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
bottom[0] + verticalRadius,
|
|
||||||
bottom[1] - horizontalRadius,
|
|
||||||
),
|
|
||||||
bottom,
|
|
||||||
bottom,
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
bottom[0] - verticalRadius,
|
|
||||||
bottom[1] - horizontalRadius,
|
|
||||||
),
|
|
||||||
), // BOTTOM
|
|
||||||
curve(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
left[0] + verticalRadius,
|
|
||||||
left[1] + horizontalRadius,
|
|
||||||
),
|
|
||||||
left,
|
|
||||||
left,
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
left[0] + verticalRadius,
|
|
||||||
left[1] - horizontalRadius,
|
|
||||||
),
|
|
||||||
), // LEFT
|
|
||||||
curve(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
top[0] - verticalRadius,
|
|
||||||
top[1] + horizontalRadius,
|
|
||||||
),
|
|
||||||
top,
|
|
||||||
top,
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
top[0] + verticalRadius,
|
|
||||||
top[1] + horizontalRadius,
|
|
||||||
),
|
|
||||||
), // TOP
|
|
||||||
];
|
|
||||||
|
|
||||||
const corners =
|
|
||||||
offset > 0
|
|
||||||
? baseCorners.map(
|
|
||||||
(corner) =>
|
|
||||||
curveCatmullRomCubicApproxPoints(
|
|
||||||
curveOffsetPoints(corner, offset),
|
|
||||||
)!,
|
|
||||||
)
|
|
||||||
: [
|
|
||||||
[baseCorners[0]],
|
|
||||||
[baseCorners[1]],
|
|
||||||
[baseCorners[2]],
|
|
||||||
[baseCorners[3]],
|
|
||||||
];
|
|
||||||
|
|
||||||
const sides = [
|
|
||||||
lineSegment<GlobalPoint>(
|
|
||||||
corners[0][corners[0].length - 1][3],
|
|
||||||
corners[1][0][0],
|
|
||||||
),
|
|
||||||
lineSegment<GlobalPoint>(
|
|
||||||
corners[1][corners[1].length - 1][3],
|
|
||||||
corners[2][0][0],
|
|
||||||
),
|
|
||||||
lineSegment<GlobalPoint>(
|
|
||||||
corners[2][corners[2].length - 1][3],
|
|
||||||
corners[3][0][0],
|
|
||||||
),
|
|
||||||
lineSegment<GlobalPoint>(
|
|
||||||
corners[3][corners[3].length - 1][3],
|
|
||||||
corners[0][0][0],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
return [sides, corners.flat()];
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateLinearShapesForCollision = (
|
|
||||||
element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement,
|
|
||||||
) => {
|
|
||||||
const generator = new RoughGenerator();
|
|
||||||
const options: Options = {
|
|
||||||
seed: element.seed,
|
|
||||||
disableMultiStroke: true,
|
|
||||||
disableMultiStrokeFill: true,
|
|
||||||
roughness: 0,
|
|
||||||
preserveVertices: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (element.type) {
|
|
||||||
case "line":
|
|
||||||
case "arrow": {
|
|
||||||
// points array can be empty in the beginning, so it is important to add
|
|
||||||
// initial position to it
|
|
||||||
const points = element.points.length
|
|
||||||
? element.points
|
|
||||||
: [pointFrom<LocalPoint>(0, 0)];
|
|
||||||
const [x1, y1, x2, y2] = getElementBounds(
|
|
||||||
{
|
|
||||||
...element,
|
|
||||||
angle: 0 as Radians,
|
|
||||||
},
|
|
||||||
new Map(),
|
|
||||||
);
|
|
||||||
const center = pointFrom<GlobalPoint>((x1 + x2) / 2, (y1 + y2) / 2);
|
|
||||||
|
|
||||||
if (isElbowArrow(element)) {
|
|
||||||
return generator.path(
|
|
||||||
generateElbowArrowRougJshPathCommands(points, 16),
|
|
||||||
options,
|
|
||||||
).sets[0].ops;
|
|
||||||
} else if (!element.roundness) {
|
|
||||||
return points.map((point, idx) => {
|
|
||||||
const p = pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(element.x + point[0], element.y + point[1]),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
op: idx === 0 ? "move" : "lineTo",
|
|
||||||
data: pointFrom<LocalPoint>(p[0] - element.x, p[1] - element.y),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return generator
|
|
||||||
.curve(points as unknown as RoughPoint[], options)
|
|
||||||
.sets[0].ops.slice(0, element.points.length)
|
|
||||||
.map((op, i, arr) => {
|
|
||||||
if (i === 0) {
|
|
||||||
const p = pointRotateRads<GlobalPoint>(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + op.data[0],
|
|
||||||
element.y + op.data[1],
|
|
||||||
),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
op: "move",
|
|
||||||
data: pointFrom<LocalPoint>(p[0] - element.x, p[1] - element.y),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
op: "bcurveTo",
|
|
||||||
data: [
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + op.data[0],
|
|
||||||
element.y + op.data[1],
|
|
||||||
),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + op.data[2],
|
|
||||||
element.y + op.data[3],
|
|
||||||
),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + op.data[4],
|
|
||||||
element.y + op.data[5],
|
|
||||||
),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
.map((p) =>
|
|
||||||
pointFrom<LocalPoint>(p[0] - element.x, p[1] - element.y),
|
|
||||||
)
|
|
||||||
.flat(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case "freedraw": {
|
|
||||||
if (element.points.length < 2) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const simplifiedPoints = simplify(element.points as LocalPoint[], 0.75);
|
|
||||||
|
|
||||||
return generator
|
|
||||||
.curve(simplifiedPoints as [number, number][], options)
|
|
||||||
.sets[0].ops.slice(0, element.points.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -18,10 +18,10 @@ import type {
|
|||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deconstructDiamondElementForCollision,
|
deconstructDiamondElement,
|
||||||
deconstructLinearOrFreeDrawElementForCollision,
|
deconstructLinearOrFreeDrawElement,
|
||||||
deconstructRectanguloidElementForCollision,
|
deconstructRectanguloidElement,
|
||||||
} from "./collision";
|
} from "./utils";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawDiamondElement,
|
ExcalidrawDiamondElement,
|
||||||
@ -75,7 +75,7 @@ const distanceToRectanguloidElement = (
|
|||||||
const rotatedPoint = pointRotateRads(p, center, -element.angle as Radians);
|
const rotatedPoint = pointRotateRads(p, center, -element.angle as Radians);
|
||||||
|
|
||||||
// Get the element's building components we can test against
|
// Get the element's building components we can test against
|
||||||
const [sides, corners] = deconstructRectanguloidElementForCollision(element);
|
const [sides, corners] = deconstructRectanguloidElement(element);
|
||||||
|
|
||||||
return Math.min(
|
return Math.min(
|
||||||
...sides.map((s) => distanceToLineSegment(rotatedPoint, s)),
|
...sides.map((s) => distanceToLineSegment(rotatedPoint, s)),
|
||||||
@ -103,7 +103,7 @@ const distanceToDiamondElement = (
|
|||||||
// points. It's all the same distance-wise.
|
// points. It's all the same distance-wise.
|
||||||
const rotatedPoint = pointRotateRads(p, center, -element.angle as Radians);
|
const rotatedPoint = pointRotateRads(p, center, -element.angle as Radians);
|
||||||
|
|
||||||
const [sides, curves] = deconstructDiamondElementForCollision(element);
|
const [sides, curves] = deconstructDiamondElement(element);
|
||||||
|
|
||||||
return Math.min(
|
return Math.min(
|
||||||
...sides.map((s) => distanceToLineSegment(rotatedPoint, s)),
|
...sides.map((s) => distanceToLineSegment(rotatedPoint, s)),
|
||||||
@ -137,7 +137,7 @@ const distanceToLinearOrFreeDraElement = (
|
|||||||
element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement,
|
element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement,
|
||||||
p: GlobalPoint,
|
p: GlobalPoint,
|
||||||
) => {
|
) => {
|
||||||
const shapes = deconstructLinearOrFreeDrawElementForCollision(element);
|
const shapes = deconstructLinearOrFreeDrawElement(element);
|
||||||
let distance = Infinity;
|
let distance = Infinity;
|
||||||
|
|
||||||
for (const shape of shapes) {
|
for (const shape of shapes) {
|
||||||
|
@ -15,7 +15,7 @@ import { getElementsWithinSelection, getSelectedElements } from "./selection";
|
|||||||
import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
|
import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
approximateElementWithLineSegments,
|
getElementLineSegments,
|
||||||
getCommonBounds,
|
getCommonBounds,
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
@ -69,15 +69,9 @@ export function isElementIntersectingFrame(
|
|||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
) {
|
) {
|
||||||
const frameLineSegments = approximateElementWithLineSegments(
|
const frameLineSegments = getElementLineSegments(frame, elementsMap);
|
||||||
frame,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
const elementLineSegments = approximateElementWithLineSegments(
|
const elementLineSegments = getElementLineSegments(element, elementsMap);
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
const intersecting = frameLineSegments.some((frameLineSegment) =>
|
const intersecting = frameLineSegments.some((frameLineSegment) =>
|
||||||
elementLineSegments.some((elementLineSegment) =>
|
elementLineSegments.some((elementLineSegment) =>
|
||||||
|
@ -9,6 +9,8 @@ import {
|
|||||||
vectorFromPoint,
|
vectorFromPoint,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
|
import { getCurvePathOps } from "@excalidraw/utils/shape";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DRAGGING_THRESHOLD,
|
DRAGGING_THRESHOLD,
|
||||||
KEYS,
|
KEYS,
|
||||||
@ -18,7 +20,7 @@ import {
|
|||||||
tupleToCoors,
|
tupleToCoors,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import { getCurvePathOps, type Store } from "@excalidraw/element";
|
import type { Store } from "@excalidraw/element";
|
||||||
|
|
||||||
import type { Radians } from "@excalidraw/math";
|
import type { Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
|
@ -16,21 +16,116 @@ import {
|
|||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
type LocalPoint,
|
type LocalPoint,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
import {
|
||||||
|
getClosedCurveShape,
|
||||||
|
getCurvePathOps,
|
||||||
|
getCurveShape,
|
||||||
|
getEllipseShape,
|
||||||
|
getFreedrawShape,
|
||||||
|
getPolygonShape,
|
||||||
|
type GeometricShape,
|
||||||
|
} from "@excalidraw/utils/shape";
|
||||||
|
|
||||||
import type { NormalizedZoomValue, Zoom } from "@excalidraw/excalidraw/types";
|
import type { NormalizedZoomValue, Zoom } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
|
import { shouldTestInside } from "./collision";
|
||||||
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
|
import { getBoundTextElement } from "./textElement";
|
||||||
import { ShapeCache } from "./ShapeCache";
|
import { ShapeCache } from "./ShapeCache";
|
||||||
|
|
||||||
import { getCurvePathOps } from "./utils";
|
import { getElementAbsoluteCoords, type Bounds } from "./bounds";
|
||||||
|
|
||||||
import type { Bounds } from "./bounds";
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
ElementsMap,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the pure geometric shape of an excalidraw elementw
|
||||||
|
* which is then used for hit detection
|
||||||
|
*/
|
||||||
|
export const getElementShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
): GeometricShape<Point> => {
|
||||||
|
switch (element.type) {
|
||||||
|
case "rectangle":
|
||||||
|
case "diamond":
|
||||||
|
case "frame":
|
||||||
|
case "magicframe":
|
||||||
|
case "embeddable":
|
||||||
|
case "image":
|
||||||
|
case "iframe":
|
||||||
|
case "text":
|
||||||
|
case "selection":
|
||||||
|
return getPolygonShape(element);
|
||||||
|
case "arrow":
|
||||||
|
case "line": {
|
||||||
|
const roughShape =
|
||||||
|
ShapeCache.get(element)?.[0] ??
|
||||||
|
ShapeCache.generateElementShape(element, null)[0];
|
||||||
|
const [, , , , cx, cy] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
|
|
||||||
|
return shouldTestInside(element)
|
||||||
|
? getClosedCurveShape<Point>(
|
||||||
|
element,
|
||||||
|
roughShape,
|
||||||
|
pointFrom<Point>(element.x, element.y),
|
||||||
|
element.angle,
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
)
|
||||||
|
: getCurveShape<Point>(
|
||||||
|
roughShape,
|
||||||
|
pointFrom<Point>(element.x, element.y),
|
||||||
|
element.angle,
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "ellipse":
|
||||||
|
return getEllipseShape(element);
|
||||||
|
|
||||||
|
case "freedraw": {
|
||||||
|
const [, , , , cx, cy] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
|
return getFreedrawShape(
|
||||||
|
element,
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
shouldTestInside(element),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBoundTextShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
): GeometricShape<Point> | null => {
|
||||||
|
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||||
|
|
||||||
|
if (boundTextElement) {
|
||||||
|
if (element.type === "arrow") {
|
||||||
|
return getElementShape(
|
||||||
|
{
|
||||||
|
...boundTextElement,
|
||||||
|
// arrow's bound text accurate position is not stored in the element's property
|
||||||
|
// but rather calculated and returned from the following static method
|
||||||
|
...LinearElementEditor.getBoundTextElementPosition(
|
||||||
|
element,
|
||||||
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return getElementShape(boundTextElement, elementsMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export const getControlPointsForBezierCurve = <
|
export const getControlPointsForBezierCurve = <
|
||||||
P extends GlobalPoint | LocalPoint,
|
P extends GlobalPoint | LocalPoint,
|
||||||
>(
|
>(
|
||||||
|
@ -1,81 +1,341 @@
|
|||||||
import { pointDistance } from "@excalidraw/math";
|
import {
|
||||||
|
curve,
|
||||||
|
curveCatmullRomCubicApproxPoints,
|
||||||
|
curveOffsetPoints,
|
||||||
|
lineSegment,
|
||||||
|
pointFrom,
|
||||||
|
pointFromArray,
|
||||||
|
rectangle,
|
||||||
|
type GlobalPoint,
|
||||||
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type { LocalPoint } from "@excalidraw/math";
|
import type { Curve, LineSegment, LocalPoint } from "@excalidraw/math";
|
||||||
|
|
||||||
import { headingForPointIsHorizontal } from "./heading";
|
import { getCornerRadius } from "./shapes";
|
||||||
|
|
||||||
import type { Drawable, Op } from "roughjs/bin/core";
|
import { getDiamondPoints } from "./bounds";
|
||||||
|
|
||||||
export const getCurvePathOps = (shape: Drawable): Op[] => {
|
import { generateLinearCollisionShape } from "./Shape";
|
||||||
for (const set of shape.sets) {
|
|
||||||
if (set.type === "path") {
|
import type {
|
||||||
return set.ops;
|
ExcalidrawDiamondElement,
|
||||||
|
ExcalidrawFreeDrawElement,
|
||||||
|
ExcalidrawLinearElement,
|
||||||
|
ExcalidrawRectanguloidElement,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
export function deconstructLinearOrFreeDrawElement(
|
||||||
|
element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement,
|
||||||
|
): (Curve<GlobalPoint> | LineSegment<GlobalPoint>)[] {
|
||||||
|
const ops = generateLinearCollisionShape(element) as {
|
||||||
|
op: string;
|
||||||
|
data: number[];
|
||||||
|
}[];
|
||||||
|
const components = [];
|
||||||
|
|
||||||
|
for (let idx = 0; idx < ops.length; idx += 1) {
|
||||||
|
const op = ops[idx];
|
||||||
|
const prevPoint =
|
||||||
|
ops[idx - 1] && pointFromArray<LocalPoint>(ops[idx - 1].data.slice(-2));
|
||||||
|
switch (op.op) {
|
||||||
|
case "move":
|
||||||
|
continue;
|
||||||
|
case "lineTo":
|
||||||
|
if (!prevPoint) {
|
||||||
|
throw new Error("prevPoint is undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
components.push(
|
||||||
|
lineSegment<GlobalPoint>(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + prevPoint[0],
|
||||||
|
element.y + prevPoint[1],
|
||||||
|
),
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + op.data[0],
|
||||||
|
element.y + op.data[1],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
case "bcurveTo":
|
||||||
|
if (!prevPoint) {
|
||||||
|
throw new Error("prevPoint is undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
components.push(
|
||||||
|
curve<GlobalPoint>(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + prevPoint[0],
|
||||||
|
element.y + prevPoint[1],
|
||||||
|
),
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + op.data[0],
|
||||||
|
element.y + op.data[1],
|
||||||
|
),
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + op.data[2],
|
||||||
|
element.y + op.data[3],
|
||||||
|
),
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + op.data[4],
|
||||||
|
element.y + op.data[5],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
default: {
|
||||||
|
console.error("Unknown op type", op.op);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return shape.sets[0].ops;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const generateElbowArrowRougJshPathCommands = (
|
return components;
|
||||||
points: readonly LocalPoint[],
|
}
|
||||||
radius: number,
|
|
||||||
) => {
|
/**
|
||||||
const subpoints = [] as [number, number][];
|
* Get the building components of a rectanguloid element in the form of
|
||||||
for (let i = 1; i < points.length - 1; i += 1) {
|
* line segments and curves.
|
||||||
const prev = points[i - 1];
|
*
|
||||||
const next = points[i + 1];
|
* @param element Target rectanguloid element
|
||||||
const point = points[i];
|
* @param offset Optional offset to expand the rectanguloid shape
|
||||||
const prevIsHorizontal = headingForPointIsHorizontal(point, prev);
|
* @returns Tuple of line segments (0) and curves (1)
|
||||||
const nextIsHorizontal = headingForPointIsHorizontal(next, point);
|
*/
|
||||||
const corner = Math.min(
|
export function deconstructRectanguloidElement(
|
||||||
radius,
|
element: ExcalidrawRectanguloidElement,
|
||||||
pointDistance(points[i], next) / 2,
|
offset: number = 0,
|
||||||
pointDistance(points[i], prev) / 2,
|
): [LineSegment<GlobalPoint>[], Curve<GlobalPoint>[]] {
|
||||||
|
let radius = getCornerRadius(
|
||||||
|
Math.min(element.width, element.height),
|
||||||
|
element,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (prevIsHorizontal) {
|
if (radius === 0) {
|
||||||
if (prev[0] < point[0]) {
|
radius = 0.01;
|
||||||
// LEFT
|
|
||||||
subpoints.push([points[i][0] - corner, points[i][1]]);
|
|
||||||
} else {
|
|
||||||
// RIGHT
|
|
||||||
subpoints.push([points[i][0] + corner, points[i][1]]);
|
|
||||||
}
|
|
||||||
} else if (prev[1] < point[1]) {
|
|
||||||
// UP
|
|
||||||
subpoints.push([points[i][0], points[i][1] - corner]);
|
|
||||||
} else {
|
|
||||||
subpoints.push([points[i][0], points[i][1] + corner]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subpoints.push(points[i] as [number, number]);
|
const r = rectangle(
|
||||||
|
pointFrom(element.x, element.y),
|
||||||
if (nextIsHorizontal) {
|
pointFrom(element.x + element.width, element.y + element.height),
|
||||||
if (next[0] < point[0]) {
|
|
||||||
// LEFT
|
|
||||||
subpoints.push([points[i][0] - corner, points[i][1]]);
|
|
||||||
} else {
|
|
||||||
// RIGHT
|
|
||||||
subpoints.push([points[i][0] + corner, points[i][1]]);
|
|
||||||
}
|
|
||||||
} else if (next[1] < point[1]) {
|
|
||||||
// UP
|
|
||||||
subpoints.push([points[i][0], points[i][1] - corner]);
|
|
||||||
} else {
|
|
||||||
// DOWN
|
|
||||||
subpoints.push([points[i][0], points[i][1] + corner]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const d = [`M ${points[0][0]} ${points[0][1]}`];
|
|
||||||
for (let i = 0; i < subpoints.length; i += 3) {
|
|
||||||
d.push(`L ${subpoints[i][0]} ${subpoints[i][1]}`);
|
|
||||||
d.push(
|
|
||||||
`Q ${subpoints[i + 1][0]} ${subpoints[i + 1][1]}, ${
|
|
||||||
subpoints[i + 2][0]
|
|
||||||
} ${subpoints[i + 2][1]}`,
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
d.push(`L ${points[points.length - 1][0]} ${points[points.length - 1][1]}`);
|
|
||||||
|
|
||||||
return d.join(" ");
|
const top = lineSegment<GlobalPoint>(
|
||||||
};
|
pointFrom<GlobalPoint>(r[0][0] + radius, r[0][1]),
|
||||||
|
pointFrom<GlobalPoint>(r[1][0] - radius, r[0][1]),
|
||||||
|
);
|
||||||
|
const right = lineSegment<GlobalPoint>(
|
||||||
|
pointFrom<GlobalPoint>(r[1][0], r[0][1] + radius),
|
||||||
|
pointFrom<GlobalPoint>(r[1][0], r[1][1] - radius),
|
||||||
|
);
|
||||||
|
const bottom = lineSegment<GlobalPoint>(
|
||||||
|
pointFrom<GlobalPoint>(r[0][0] + radius, r[1][1]),
|
||||||
|
pointFrom<GlobalPoint>(r[1][0] - radius, r[1][1]),
|
||||||
|
);
|
||||||
|
const left = lineSegment<GlobalPoint>(
|
||||||
|
pointFrom<GlobalPoint>(r[0][0], r[1][1] - radius),
|
||||||
|
pointFrom<GlobalPoint>(r[0][0], r[0][1] + radius),
|
||||||
|
);
|
||||||
|
|
||||||
|
const baseCorners = [
|
||||||
|
curve(
|
||||||
|
left[1],
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
left[1][0] + (2 / 3) * (r[0][0] - left[1][0]),
|
||||||
|
left[1][1] + (2 / 3) * (r[0][1] - left[1][1]),
|
||||||
|
),
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
top[0][0] + (2 / 3) * (r[0][0] - top[0][0]),
|
||||||
|
top[0][1] + (2 / 3) * (r[0][1] - top[0][1]),
|
||||||
|
),
|
||||||
|
top[0],
|
||||||
|
), // TOP LEFT
|
||||||
|
curve(
|
||||||
|
top[1],
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
top[1][0] + (2 / 3) * (r[1][0] - top[1][0]),
|
||||||
|
top[1][1] + (2 / 3) * (r[0][1] - top[1][1]),
|
||||||
|
),
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
right[0][0] + (2 / 3) * (r[1][0] - right[0][0]),
|
||||||
|
right[0][1] + (2 / 3) * (r[0][1] - right[0][1]),
|
||||||
|
),
|
||||||
|
right[0],
|
||||||
|
), // TOP RIGHT
|
||||||
|
curve(
|
||||||
|
right[1],
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
right[1][0] + (2 / 3) * (r[1][0] - right[1][0]),
|
||||||
|
right[1][1] + (2 / 3) * (r[1][1] - right[1][1]),
|
||||||
|
),
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
bottom[1][0] + (2 / 3) * (r[1][0] - bottom[1][0]),
|
||||||
|
bottom[1][1] + (2 / 3) * (r[1][1] - bottom[1][1]),
|
||||||
|
),
|
||||||
|
bottom[1],
|
||||||
|
), // BOTTOM RIGHT
|
||||||
|
curve(
|
||||||
|
bottom[0],
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
bottom[0][0] + (2 / 3) * (r[0][0] - bottom[0][0]),
|
||||||
|
bottom[0][1] + (2 / 3) * (r[1][1] - bottom[0][1]),
|
||||||
|
),
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
left[0][0] + (2 / 3) * (r[0][0] - left[0][0]),
|
||||||
|
left[0][1] + (2 / 3) * (r[1][1] - left[0][1]),
|
||||||
|
),
|
||||||
|
left[0],
|
||||||
|
), // BOTTOM LEFT
|
||||||
|
];
|
||||||
|
|
||||||
|
const corners =
|
||||||
|
offset > 0
|
||||||
|
? baseCorners.map(
|
||||||
|
(corner) =>
|
||||||
|
curveCatmullRomCubicApproxPoints(
|
||||||
|
curveOffsetPoints(corner, offset),
|
||||||
|
)!,
|
||||||
|
)
|
||||||
|
: [
|
||||||
|
[baseCorners[0]],
|
||||||
|
[baseCorners[1]],
|
||||||
|
[baseCorners[2]],
|
||||||
|
[baseCorners[3]],
|
||||||
|
];
|
||||||
|
|
||||||
|
const sides = [
|
||||||
|
lineSegment<GlobalPoint>(
|
||||||
|
corners[0][corners[0].length - 1][3],
|
||||||
|
corners[1][0][0],
|
||||||
|
),
|
||||||
|
lineSegment<GlobalPoint>(
|
||||||
|
corners[1][corners[1].length - 1][3],
|
||||||
|
corners[2][0][0],
|
||||||
|
),
|
||||||
|
lineSegment<GlobalPoint>(
|
||||||
|
corners[2][corners[2].length - 1][3],
|
||||||
|
corners[3][0][0],
|
||||||
|
),
|
||||||
|
lineSegment<GlobalPoint>(
|
||||||
|
corners[3][corners[3].length - 1][3],
|
||||||
|
corners[0][0][0],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
return [sides, corners.flat()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the 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)
|
||||||
|
*/
|
||||||
|
export function deconstructDiamondElement(
|
||||||
|
element: ExcalidrawDiamondElement,
|
||||||
|
offset: number = 0,
|
||||||
|
): [LineSegment<GlobalPoint>[], Curve<GlobalPoint>[]] {
|
||||||
|
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
|
||||||
|
getDiamondPoints(element);
|
||||||
|
const verticalRadius = element.roundness
|
||||||
|
? getCornerRadius(Math.abs(topX - leftX), element)
|
||||||
|
: (topX - leftX) * 0.01;
|
||||||
|
const horizontalRadius = element.roundness
|
||||||
|
? getCornerRadius(Math.abs(rightY - topY), element)
|
||||||
|
: (rightY - topY) * 0.01;
|
||||||
|
|
||||||
|
const [top, right, bottom, left]: GlobalPoint[] = [
|
||||||
|
pointFrom(element.x + topX, element.y + topY),
|
||||||
|
pointFrom(element.x + rightX, element.y + rightY),
|
||||||
|
pointFrom(element.x + bottomX, element.y + bottomY),
|
||||||
|
pointFrom(element.x + leftX, element.y + leftY),
|
||||||
|
];
|
||||||
|
|
||||||
|
const baseCorners = [
|
||||||
|
curve(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
right[0] - verticalRadius,
|
||||||
|
right[1] - horizontalRadius,
|
||||||
|
),
|
||||||
|
right,
|
||||||
|
right,
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
right[0] - verticalRadius,
|
||||||
|
right[1] + horizontalRadius,
|
||||||
|
),
|
||||||
|
), // RIGHT
|
||||||
|
curve(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
bottom[0] + verticalRadius,
|
||||||
|
bottom[1] - horizontalRadius,
|
||||||
|
),
|
||||||
|
bottom,
|
||||||
|
bottom,
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
bottom[0] - verticalRadius,
|
||||||
|
bottom[1] - horizontalRadius,
|
||||||
|
),
|
||||||
|
), // BOTTOM
|
||||||
|
curve(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
left[0] + verticalRadius,
|
||||||
|
left[1] + horizontalRadius,
|
||||||
|
),
|
||||||
|
left,
|
||||||
|
left,
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
left[0] + verticalRadius,
|
||||||
|
left[1] - horizontalRadius,
|
||||||
|
),
|
||||||
|
), // LEFT
|
||||||
|
curve(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
top[0] - verticalRadius,
|
||||||
|
top[1] + horizontalRadius,
|
||||||
|
),
|
||||||
|
top,
|
||||||
|
top,
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
top[0] + verticalRadius,
|
||||||
|
top[1] + horizontalRadius,
|
||||||
|
),
|
||||||
|
), // TOP
|
||||||
|
];
|
||||||
|
|
||||||
|
const corners =
|
||||||
|
offset > 0
|
||||||
|
? baseCorners.map(
|
||||||
|
(corner) =>
|
||||||
|
curveCatmullRomCubicApproxPoints(
|
||||||
|
curveOffsetPoints(corner, offset),
|
||||||
|
)!,
|
||||||
|
)
|
||||||
|
: [
|
||||||
|
[baseCorners[0]],
|
||||||
|
[baseCorners[1]],
|
||||||
|
[baseCorners[2]],
|
||||||
|
[baseCorners[3]],
|
||||||
|
];
|
||||||
|
|
||||||
|
const sides = [
|
||||||
|
lineSegment<GlobalPoint>(
|
||||||
|
corners[0][corners[0].length - 1][3],
|
||||||
|
corners[1][0][0],
|
||||||
|
),
|
||||||
|
lineSegment<GlobalPoint>(
|
||||||
|
corners[1][corners[1].length - 1][3],
|
||||||
|
corners[2][0][0],
|
||||||
|
),
|
||||||
|
lineSegment<GlobalPoint>(
|
||||||
|
corners[2][corners[2].length - 1][3],
|
||||||
|
corners[3][0][0],
|
||||||
|
),
|
||||||
|
lineSegment<GlobalPoint>(
|
||||||
|
corners[3][corners[3].length - 1][3],
|
||||||
|
corners[0][0][0],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
return [sides, corners.flat()];
|
||||||
|
}
|
||||||
|
@ -130,7 +130,7 @@ import {
|
|||||||
refreshTextDimensions,
|
refreshTextDimensions,
|
||||||
deepCopyElement,
|
deepCopyElement,
|
||||||
duplicateElements,
|
duplicateElements,
|
||||||
isPointInElement,
|
isPointInShape,
|
||||||
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 (isPointInElement(pointFrom(x, y), element)) {
|
if (isPointInShape(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 {
|
||||||
getBoundTextElement,
|
lineSegment,
|
||||||
intersectElementWithLineSegment,
|
lineSegmentIntersectionPoints,
|
||||||
isPointInElement,
|
pointFrom,
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/math";
|
||||||
import { lineSegment, pointFrom } from "@excalidraw/math";
|
|
||||||
|
|
||||||
import { getElementsInGroup } from "@excalidraw/element";
|
import { getElementsInGroup } from "@excalidraw/element";
|
||||||
|
|
||||||
@ -12,7 +12,12 @@ 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 { GlobalPoint, LineSegment } from "@excalidraw/math/types";
|
import type { GeometricShape } from "@excalidraw/utils/shape";
|
||||||
|
import type {
|
||||||
|
ElementsSegmentsMap,
|
||||||
|
GlobalPoint,
|
||||||
|
LineSegment,
|
||||||
|
} from "@excalidraw/math/types";
|
||||||
import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types";
|
import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types";
|
||||||
|
|
||||||
import { AnimatedTrail } from "../animated-trail";
|
import { AnimatedTrail } from "../animated-trail";
|
||||||
@ -28,6 +33,8 @@ 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, {
|
||||||
@ -103,6 +110,8 @@ export class EraserTrail extends AnimatedTrail {
|
|||||||
const intersects = eraserTest(
|
const intersects = eraserTest(
|
||||||
pathSegments,
|
pathSegments,
|
||||||
element,
|
element,
|
||||||
|
this.segmentsCache,
|
||||||
|
this.geometricShapesCache,
|
||||||
candidateElementsMap,
|
candidateElementsMap,
|
||||||
this.app,
|
this.app,
|
||||||
);
|
);
|
||||||
@ -139,6 +148,8 @@ export class EraserTrail extends AnimatedTrail {
|
|||||||
const intersects = eraserTest(
|
const intersects = eraserTest(
|
||||||
pathSegments,
|
pathSegments,
|
||||||
element,
|
element,
|
||||||
|
this.segmentsCache,
|
||||||
|
this.geometricShapesCache,
|
||||||
candidateElementsMap,
|
candidateElementsMap,
|
||||||
this.app,
|
this.app,
|
||||||
);
|
);
|
||||||
@ -190,23 +201,31 @@ export class EraserTrail extends AnimatedTrail {
|
|||||||
const eraserTest = (
|
const eraserTest = (
|
||||||
pathSegments: LineSegment<GlobalPoint>[],
|
pathSegments: LineSegment<GlobalPoint>[],
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
|
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) && isPointInElement(lastPoint, element)) {
|
if (shouldTestInside(element) && isPointInShape(lastPoint, element)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const offset = app.getElementHitThreshold();
|
let elementSegments = elementsSegments.get(element.id);
|
||||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
|
||||||
|
|
||||||
return pathSegments.some(
|
if (!elementSegments) {
|
||||||
(pathSegment) =>
|
elementSegments = getElementLineSegments(element, elementsMap);
|
||||||
intersectElementWithLineSegment(element, pathSegment, offset).length >
|
elementsSegments.set(element.id, elementSegments);
|
||||||
0 ||
|
}
|
||||||
(boundTextElement &&
|
|
||||||
intersectElementWithLineSegment(boundTextElement, pathSegment, offset)
|
return pathSegments.some((pathSegment) =>
|
||||||
.length > 0),
|
elementSegments?.some(
|
||||||
|
(elementSegment) =>
|
||||||
|
lineSegmentIntersectionPoints(
|
||||||
|
pathSegment,
|
||||||
|
elementSegment,
|
||||||
|
app.getElementHitThreshold(),
|
||||||
|
) !== null,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
pointFrom,
|
pointFrom,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import { approximateElementWithLineSegments } from "@excalidraw/element";
|
import { getElementLineSegments } from "@excalidraw/element";
|
||||||
import { LinearElementEditor } from "@excalidraw/element";
|
import { LinearElementEditor } from "@excalidraw/element";
|
||||||
import {
|
import {
|
||||||
isFrameLikeElement,
|
isFrameLikeElement,
|
||||||
@ -190,10 +190,7 @@ export class LassoTrail extends AnimatedTrail {
|
|||||||
this.elementsSegments = new Map();
|
this.elementsSegments = new Map();
|
||||||
const visibleElementsMap = arrayToMap(this.app.visibleElements);
|
const visibleElementsMap = arrayToMap(this.app.visibleElements);
|
||||||
for (const element of this.app.visibleElements) {
|
for (const element of this.app.visibleElements) {
|
||||||
const segments = approximateElementWithLineSegments(
|
const segments = getElementLineSegments(element, visibleElementsMap);
|
||||||
element,
|
|
||||||
visibleElementsMap,
|
|
||||||
);
|
|
||||||
this.elementsSegments.set(element.id, segments);
|
this.elementsSegments.set(element.id, segments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,11 @@ import type {
|
|||||||
LineSegment,
|
LineSegment,
|
||||||
} from "@excalidraw/math/types";
|
} from "@excalidraw/math/types";
|
||||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||||
import { intersectElementWithLineSegment } from "@excalidraw/element";
|
|
||||||
import App from "../components/App";
|
|
||||||
|
|
||||||
export const getLassoSelectedElementIds = (input: {
|
export const getLassoSelectedElementIds = (input: {
|
||||||
lassoPath: GlobalPoint[];
|
lassoPath: GlobalPoint[];
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
|
elementsSegments: ElementsSegmentsMap;
|
||||||
intersectedElements: Set<ExcalidrawElement["id"]>;
|
intersectedElements: Set<ExcalidrawElement["id"]>;
|
||||||
enclosedElements: Set<ExcalidrawElement["id"]>;
|
enclosedElements: Set<ExcalidrawElement["id"]>;
|
||||||
simplifyDistance?: number;
|
simplifyDistance?: number;
|
||||||
@ -28,6 +27,7 @@ export const getLassoSelectedElementIds = (input: {
|
|||||||
const {
|
const {
|
||||||
lassoPath,
|
lassoPath,
|
||||||
elements,
|
elements,
|
||||||
|
elementsSegments,
|
||||||
intersectedElements,
|
intersectedElements,
|
||||||
enclosedElements,
|
enclosedElements,
|
||||||
simplifyDistance,
|
simplifyDistance,
|
||||||
@ -48,7 +48,7 @@ export const getLassoSelectedElementIds = (input: {
|
|||||||
if (enclosed) {
|
if (enclosed) {
|
||||||
enclosedElements.add(element.id);
|
enclosedElements.add(element.id);
|
||||||
} else {
|
} else {
|
||||||
const intersects = intersectionTest(path, element, app);
|
const intersects = intersectionTest(path, element, elementsSegments);
|
||||||
if (intersects) {
|
if (intersects) {
|
||||||
intersectedElements.add(element.id);
|
intersectedElements.add(element.id);
|
||||||
}
|
}
|
||||||
@ -66,13 +66,13 @@ export const getLassoSelectedElementIds = (input: {
|
|||||||
const enclosureTest = (
|
const enclosureTest = (
|
||||||
lassoPath: GlobalPoint[],
|
lassoPath: GlobalPoint[],
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
app: App,
|
elementsSegments: ElementsSegmentsMap,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const lassoSegments = lassoPath
|
const lassoPolygon = polygonFromPoints(lassoPath);
|
||||||
.slice(1)
|
const segments = elementsSegments.get(element.id);
|
||||||
.map((point, index) => lineSegment(lassoPath[index], point))
|
if (!segments) {
|
||||||
.concat(lineSegment(lassoPath[lassoPath.length - 1], lassoPath[0]));
|
return false;
|
||||||
const offset = app.getElementHitThreshold();
|
}
|
||||||
|
|
||||||
return segments.some((segment) => {
|
return segments.some((segment) => {
|
||||||
return segment.some((point) =>
|
return segment.some((point) =>
|
||||||
@ -84,15 +84,26 @@ const enclosureTest = (
|
|||||||
const intersectionTest = (
|
const intersectionTest = (
|
||||||
lassoPath: GlobalPoint[],
|
lassoPath: GlobalPoint[],
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
app: App,
|
elementsSegments: ElementsSegmentsMap,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const lassoSegments = lassoPath
|
const elementSegments = elementsSegments.get(element.id);
|
||||||
.slice(1)
|
if (!elementSegments) {
|
||||||
.map((point, index) => lineSegment(lassoPath[index], point))
|
return false;
|
||||||
.concat(lineSegment(lassoPath[lassoPath.length - 1], lassoPath[0]));
|
}
|
||||||
const offset = app.getElementHitThreshold();
|
|
||||||
|
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) =>
|
return lassoSegments.some((lassoSegment) =>
|
||||||
intersectElementWithLineSegment(element, lassoSegment, offset),
|
elementSegments.some(
|
||||||
|
(elementSegment) =>
|
||||||
|
// introduce a bit of tolerance to account for roughness and simplification of paths
|
||||||
|
lineSegmentIntersectionPoints(lassoSegment, elementSegment, 1) !== null,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
type ElementsSegmentsMap,
|
type ElementsSegmentsMap,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import { approximateElementWithLineSegments } from "@excalidraw/element";
|
import { getElementLineSegments } from "@excalidraw/element";
|
||||||
|
|
||||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ const updatePath = (startPoint: GlobalPoint, points: LocalPoint[]) => {
|
|||||||
|
|
||||||
const elementsSegments: ElementsSegmentsMap = new Map();
|
const elementsSegments: ElementsSegmentsMap = new Map();
|
||||||
for (const element of h.elements) {
|
for (const element of h.elements) {
|
||||||
const segments = approximateElementWithLineSegments(
|
const segments = getElementLineSegments(
|
||||||
element,
|
element,
|
||||||
h.app.scene.getElementsMapIncludingDeleted(),
|
h.app.scene.getElementsMapIncludingDeleted(),
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
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";
|
||||||
|
@ -13,516 +13,532 @@
|
|||||||
*/
|
*/
|
||||||
import { pointsOnBezierCurves } from "points-on-curve";
|
import { pointsOnBezierCurves } from "points-on-curve";
|
||||||
|
|
||||||
// import { invariant } from "@excalidraw/common";
|
import { invariant } from "@excalidraw/common";
|
||||||
// import {
|
import {
|
||||||
// curve,
|
curve,
|
||||||
// lineSegment,
|
lineSegment,
|
||||||
// pointFrom,
|
pointFrom,
|
||||||
// pointDistance,
|
pointDistance,
|
||||||
// pointFromArray,
|
pointFromArray,
|
||||||
// pointFromVector,
|
pointFromVector,
|
||||||
// pointRotateRads,
|
pointRotateRads,
|
||||||
// polygon,
|
polygon,
|
||||||
// polygonFromPoints,
|
polygonFromPoints,
|
||||||
// PRECISION,
|
PRECISION,
|
||||||
// segmentsIntersectAt,
|
segmentsIntersectAt,
|
||||||
// vector,
|
vector,
|
||||||
// vectorAdd,
|
vectorAdd,
|
||||||
// vectorFromPoint,
|
vectorFromPoint,
|
||||||
// vectorScale,
|
vectorScale,
|
||||||
// type GlobalPoint,
|
type GlobalPoint,
|
||||||
// type LocalPoint,
|
type LocalPoint,
|
||||||
// } from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
// import { getElementAbsoluteCoords } from "@excalidraw/element";
|
import { getElementAbsoluteCoords } from "@excalidraw/element";
|
||||||
|
|
||||||
// import type {
|
import type {
|
||||||
// ElementsMap,
|
ElementsMap,
|
||||||
// ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
// ExcalidrawDiamondElement,
|
ExcalidrawDiamondElement,
|
||||||
// ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
// ExcalidrawEllipseElement,
|
ExcalidrawEllipseElement,
|
||||||
// ExcalidrawEmbeddableElement,
|
ExcalidrawEmbeddableElement,
|
||||||
// ExcalidrawFrameLikeElement,
|
ExcalidrawFrameLikeElement,
|
||||||
// ExcalidrawFreeDrawElement,
|
ExcalidrawFreeDrawElement,
|
||||||
// ExcalidrawIframeElement,
|
ExcalidrawIframeElement,
|
||||||
// ExcalidrawImageElement,
|
ExcalidrawImageElement,
|
||||||
// ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
// ExcalidrawRectangleElement,
|
ExcalidrawRectangleElement,
|
||||||
// ExcalidrawSelectionElement,
|
ExcalidrawSelectionElement,
|
||||||
// ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
// } from "@excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
// import type { Curve, LineSegment, Polygon, Radians } from "@excalidraw/math";
|
import type { Curve, LineSegment, Polygon, Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
// // a polyline (made up term here) is a line consisting of other line segments
|
import type { Drawable, Op } from "roughjs/bin/core";
|
||||||
// // this corresponds to a straight line element in the editor but it could also
|
|
||||||
// // be used to model other elements
|
|
||||||
// export type Polyline<Point extends GlobalPoint | LocalPoint> =
|
|
||||||
// LineSegment<Point>[];
|
|
||||||
|
|
||||||
// // a polycurve is a curve consisting of ther curves, this corresponds to a complex
|
// a polyline (made up term here) is a line consisting of other line segments
|
||||||
// // curve on the canvas
|
// this corresponds to a straight line element in the editor but it could also
|
||||||
// export type Polycurve<Point extends GlobalPoint | LocalPoint> = Curve<Point>[];
|
// be used to model other elements
|
||||||
|
export type Polyline<Point extends GlobalPoint | LocalPoint> =
|
||||||
|
LineSegment<Point>[];
|
||||||
|
|
||||||
// // an ellipse is specified by its center, angle, and its major and minor axes
|
// a polycurve is a curve consisting of ther curves, this corresponds to a complex
|
||||||
// // but for the sake of simplicity, we've used halfWidth and halfHeight instead
|
// curve on the canvas
|
||||||
// // in replace of semi major and semi minor axes
|
export type Polycurve<Point extends GlobalPoint | LocalPoint> = Curve<Point>[];
|
||||||
// export type Ellipse<Point extends GlobalPoint | LocalPoint> = {
|
|
||||||
// center: Point;
|
|
||||||
// angle: Radians;
|
|
||||||
// halfWidth: number;
|
|
||||||
// halfHeight: number;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export type GeometricShape<Point extends GlobalPoint | LocalPoint> =
|
// an ellipse is specified by its center, angle, and its major and minor axes
|
||||||
// | {
|
// but for the sake of simplicity, we've used halfWidth and halfHeight instead
|
||||||
// type: "line";
|
// in replace of semi major and semi minor axes
|
||||||
// data: LineSegment<Point>;
|
export type Ellipse<Point extends GlobalPoint | LocalPoint> = {
|
||||||
// }
|
center: Point;
|
||||||
// | {
|
angle: Radians;
|
||||||
// type: "polygon";
|
halfWidth: number;
|
||||||
// data: Polygon<Point>;
|
halfHeight: number;
|
||||||
// }
|
};
|
||||||
// | {
|
|
||||||
// type: "curve";
|
|
||||||
// data: Curve<Point>;
|
|
||||||
// }
|
|
||||||
// | {
|
|
||||||
// type: "ellipse";
|
|
||||||
// data: Ellipse<Point>;
|
|
||||||
// }
|
|
||||||
// | {
|
|
||||||
// type: "polyline";
|
|
||||||
// data: Polyline<Point>;
|
|
||||||
// }
|
|
||||||
// | {
|
|
||||||
// type: "polycurve";
|
|
||||||
// data: Polycurve<Point>;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// type RectangularElement =
|
export type GeometricShape<Point extends GlobalPoint | LocalPoint> =
|
||||||
// | ExcalidrawRectangleElement
|
| {
|
||||||
// | ExcalidrawDiamondElement
|
type: "line";
|
||||||
// | ExcalidrawFrameLikeElement
|
data: LineSegment<Point>;
|
||||||
// | ExcalidrawEmbeddableElement
|
}
|
||||||
// | ExcalidrawImageElement
|
| {
|
||||||
// | ExcalidrawIframeElement
|
type: "polygon";
|
||||||
// | ExcalidrawTextElement
|
data: Polygon<Point>;
|
||||||
// | ExcalidrawSelectionElement;
|
}
|
||||||
|
| {
|
||||||
|
type: "curve";
|
||||||
|
data: Curve<Point>;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "ellipse";
|
||||||
|
data: Ellipse<Point>;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "polyline";
|
||||||
|
data: Polyline<Point>;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "polycurve";
|
||||||
|
data: Polycurve<Point>;
|
||||||
|
};
|
||||||
|
|
||||||
// // polygon
|
type RectangularElement =
|
||||||
// export const getPolygonShape = <Point extends GlobalPoint | LocalPoint>(
|
| ExcalidrawRectangleElement
|
||||||
// element: RectangularElement,
|
| ExcalidrawDiamondElement
|
||||||
// ): GeometricShape<Point> => {
|
| ExcalidrawFrameLikeElement
|
||||||
// const { angle, width, height, x, y } = element;
|
| ExcalidrawEmbeddableElement
|
||||||
|
| ExcalidrawImageElement
|
||||||
|
| ExcalidrawIframeElement
|
||||||
|
| ExcalidrawTextElement
|
||||||
|
| ExcalidrawSelectionElement;
|
||||||
|
|
||||||
// const cx = x + width / 2;
|
// polygon
|
||||||
// const cy = y + height / 2;
|
export const getPolygonShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
|
element: RectangularElement,
|
||||||
|
): GeometricShape<Point> => {
|
||||||
|
const { angle, width, height, x, y } = element;
|
||||||
|
|
||||||
// const center: Point = pointFrom(cx, cy);
|
const cx = x + width / 2;
|
||||||
|
const cy = y + height / 2;
|
||||||
|
|
||||||
// let data: Polygon<Point>;
|
const center: Point = pointFrom(cx, cy);
|
||||||
|
|
||||||
// if (element.type === "diamond") {
|
let data: Polygon<Point>;
|
||||||
// data = polygon(
|
|
||||||
// pointRotateRads(pointFrom(cx, y), center, angle),
|
|
||||||
// pointRotateRads(pointFrom(x + width, cy), center, angle),
|
|
||||||
// pointRotateRads(pointFrom(cx, y + height), center, angle),
|
|
||||||
// pointRotateRads(pointFrom(x, cy), center, angle),
|
|
||||||
// );
|
|
||||||
// } else {
|
|
||||||
// data = polygon(
|
|
||||||
// pointRotateRads(pointFrom(x, y), center, angle),
|
|
||||||
// pointRotateRads(pointFrom(x + width, y), center, angle),
|
|
||||||
// pointRotateRads(pointFrom(x + width, y + height), center, angle),
|
|
||||||
// pointRotateRads(pointFrom(x, y + height), center, angle),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return {
|
if (element.type === "diamond") {
|
||||||
// type: "polygon",
|
data = polygon(
|
||||||
// data,
|
pointRotateRads(pointFrom(cx, y), center, angle),
|
||||||
// };
|
pointRotateRads(pointFrom(x + width, cy), center, angle),
|
||||||
// };
|
pointRotateRads(pointFrom(cx, y + height), center, angle),
|
||||||
|
pointRotateRads(pointFrom(x, cy), center, angle),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
data = polygon(
|
||||||
|
pointRotateRads(pointFrom(x, y), center, angle),
|
||||||
|
pointRotateRads(pointFrom(x + width, y), center, angle),
|
||||||
|
pointRotateRads(pointFrom(x + width, y + height), center, angle),
|
||||||
|
pointRotateRads(pointFrom(x, y + height), center, angle),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// // return the selection box for an element, possibly rotated as well
|
return {
|
||||||
// export const getSelectionBoxShape = <Point extends GlobalPoint | LocalPoint>(
|
type: "polygon",
|
||||||
// element: ExcalidrawElement,
|
data,
|
||||||
// elementsMap: ElementsMap,
|
};
|
||||||
// padding = 10,
|
};
|
||||||
// ) => {
|
|
||||||
// let [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
|
||||||
// element,
|
|
||||||
// elementsMap,
|
|
||||||
// true,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// x1 -= padding;
|
// return the selection box for an element, possibly rotated as well
|
||||||
// x2 += padding;
|
export const getSelectionBoxShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
// y1 -= padding;
|
element: ExcalidrawElement,
|
||||||
// y2 += padding;
|
elementsMap: ElementsMap,
|
||||||
|
padding = 10,
|
||||||
|
) => {
|
||||||
|
let [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
// //const angleInDegrees = angleToDegrees(element.angle);
|
x1 -= padding;
|
||||||
// const center = pointFrom(cx, cy);
|
x2 += padding;
|
||||||
// const topLeft = pointRotateRads(pointFrom(x1, y1), center, element.angle);
|
y1 -= padding;
|
||||||
// const topRight = pointRotateRads(pointFrom(x2, y1), center, element.angle);
|
y2 += padding;
|
||||||
// const bottomLeft = pointRotateRads(pointFrom(x1, y2), center, element.angle);
|
|
||||||
// const bottomRight = pointRotateRads(pointFrom(x2, y2), center, element.angle);
|
|
||||||
|
|
||||||
// return {
|
//const angleInDegrees = angleToDegrees(element.angle);
|
||||||
// type: "polygon",
|
const center = pointFrom(cx, cy);
|
||||||
// data: [topLeft, topRight, bottomRight, bottomLeft],
|
const topLeft = pointRotateRads(pointFrom(x1, y1), center, element.angle);
|
||||||
// } as GeometricShape<Point>;
|
const topRight = pointRotateRads(pointFrom(x2, y1), center, element.angle);
|
||||||
// };
|
const bottomLeft = pointRotateRads(pointFrom(x1, y2), center, element.angle);
|
||||||
|
const bottomRight = pointRotateRads(pointFrom(x2, y2), center, element.angle);
|
||||||
|
|
||||||
// // ellipse
|
return {
|
||||||
// export const getEllipseShape = <Point extends GlobalPoint | LocalPoint>(
|
type: "polygon",
|
||||||
// element: ExcalidrawEllipseElement,
|
data: [topLeft, topRight, bottomRight, bottomLeft],
|
||||||
// ): GeometricShape<Point> => {
|
} as GeometricShape<Point>;
|
||||||
// const { width, height, angle, x, y } = element;
|
};
|
||||||
|
|
||||||
// return {
|
// ellipse
|
||||||
// type: "ellipse",
|
export const getEllipseShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
// data: {
|
element: ExcalidrawEllipseElement,
|
||||||
// center: pointFrom(x + width / 2, y + height / 2),
|
): GeometricShape<Point> => {
|
||||||
// angle,
|
const { width, height, angle, x, y } = element;
|
||||||
// halfWidth: width / 2,
|
|
||||||
// halfHeight: height / 2,
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // linear
|
return {
|
||||||
// export const getCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
type: "ellipse",
|
||||||
// roughShape: Drawable,
|
data: {
|
||||||
// startingPoint: Point = pointFrom(0, 0),
|
center: pointFrom(x + width / 2, y + height / 2),
|
||||||
// angleInRadian: Radians,
|
angle,
|
||||||
// center: Point,
|
halfWidth: width / 2,
|
||||||
// ): GeometricShape<Point> => {
|
halfHeight: height / 2,
|
||||||
// const transform = (p: Point): Point =>
|
},
|
||||||
// pointRotateRads(
|
};
|
||||||
// pointFrom(p[0] + startingPoint[0], p[1] + startingPoint[1]),
|
};
|
||||||
// center,
|
|
||||||
// angleInRadian,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const ops = getCurvePathOps(roughShape);
|
export const getCurvePathOps = (shape: Drawable): Op[] => {
|
||||||
// const polycurve: Polycurve<Point> = [];
|
// NOTE (mtolmacs): Temporary fix for extremely large elements
|
||||||
// let p0 = pointFrom<Point>(0, 0);
|
if (!shape) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
// for (const op of ops) {
|
for (const set of shape.sets) {
|
||||||
// if (op.op === "move") {
|
if (set.type === "path") {
|
||||||
// const p = pointFromArray<Point>(op.data);
|
return set.ops;
|
||||||
// invariant(p != null, "Ops data is not a point");
|
}
|
||||||
// p0 = transform(p);
|
}
|
||||||
// }
|
return shape.sets[0].ops;
|
||||||
// if (op.op === "bcurveTo") {
|
};
|
||||||
// const p1 = transform(pointFrom<Point>(op.data[0], op.data[1]));
|
|
||||||
// const p2 = transform(pointFrom<Point>(op.data[2], op.data[3]));
|
|
||||||
// const p3 = transform(pointFrom<Point>(op.data[4], op.data[5]));
|
|
||||||
// polycurve.push(curve<Point>(p0, p1, p2, p3));
|
|
||||||
// p0 = p3;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return {
|
// linear
|
||||||
// type: "polycurve",
|
export const getCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
// data: polycurve,
|
roughShape: Drawable,
|
||||||
// };
|
startingPoint: Point = pointFrom(0, 0),
|
||||||
// };
|
angleInRadian: Radians,
|
||||||
|
center: Point,
|
||||||
|
): GeometricShape<Point> => {
|
||||||
|
const transform = (p: Point): Point =>
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom(p[0] + startingPoint[0], p[1] + startingPoint[1]),
|
||||||
|
center,
|
||||||
|
angleInRadian,
|
||||||
|
);
|
||||||
|
|
||||||
// const polylineFromPoints = <Point extends GlobalPoint | LocalPoint>(
|
const ops = getCurvePathOps(roughShape);
|
||||||
// points: Point[],
|
const polycurve: Polycurve<Point> = [];
|
||||||
// ): Polyline<Point> => {
|
let p0 = pointFrom<Point>(0, 0);
|
||||||
// let previousPoint: Point = points[0];
|
|
||||||
// const polyline: LineSegment<Point>[] = [];
|
|
||||||
|
|
||||||
// for (let i = 1; i < points.length; i++) {
|
for (const op of ops) {
|
||||||
// const nextPoint = points[i];
|
if (op.op === "move") {
|
||||||
// polyline.push(lineSegment<Point>(previousPoint, nextPoint));
|
const p = pointFromArray<Point>(op.data);
|
||||||
// previousPoint = nextPoint;
|
invariant(p != null, "Ops data is not a point");
|
||||||
// }
|
p0 = transform(p);
|
||||||
|
}
|
||||||
|
if (op.op === "bcurveTo") {
|
||||||
|
const p1 = transform(pointFrom<Point>(op.data[0], op.data[1]));
|
||||||
|
const p2 = transform(pointFrom<Point>(op.data[2], op.data[3]));
|
||||||
|
const p3 = transform(pointFrom<Point>(op.data[4], op.data[5]));
|
||||||
|
polycurve.push(curve<Point>(p0, p1, p2, p3));
|
||||||
|
p0 = p3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// return polyline;
|
return {
|
||||||
// };
|
type: "polycurve",
|
||||||
|
data: polycurve,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// export const getFreedrawShape = <Point extends GlobalPoint | LocalPoint>(
|
const polylineFromPoints = <Point extends GlobalPoint | LocalPoint>(
|
||||||
// element: ExcalidrawFreeDrawElement,
|
points: Point[],
|
||||||
// center: Point,
|
): Polyline<Point> => {
|
||||||
// isClosed: boolean = false,
|
let previousPoint: Point = points[0];
|
||||||
// ): GeometricShape<Point> => {
|
const polyline: LineSegment<Point>[] = [];
|
||||||
// const transform = (p: Point) =>
|
|
||||||
// pointRotateRads(
|
|
||||||
// pointFromVector(
|
|
||||||
// vectorAdd(vectorFromPoint(p), vector(element.x, element.y)),
|
|
||||||
// ),
|
|
||||||
// center,
|
|
||||||
// element.angle,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const polyline = polylineFromPoints(
|
for (let i = 1; i < points.length; i++) {
|
||||||
// element.points.map((p) => transform(p as Point)),
|
const nextPoint = points[i];
|
||||||
// );
|
polyline.push(lineSegment<Point>(previousPoint, nextPoint));
|
||||||
|
previousPoint = nextPoint;
|
||||||
|
}
|
||||||
|
|
||||||
// return (
|
return polyline;
|
||||||
// isClosed
|
};
|
||||||
// ? {
|
|
||||||
// type: "polygon",
|
|
||||||
// data: polygonFromPoints(polyline.flat()),
|
|
||||||
// }
|
|
||||||
// : {
|
|
||||||
// type: "polyline",
|
|
||||||
// data: polyline,
|
|
||||||
// }
|
|
||||||
// ) as GeometricShape<Point>;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export const getClosedCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
export const getFreedrawShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
// element: ExcalidrawLinearElement,
|
element: ExcalidrawFreeDrawElement,
|
||||||
// roughShape: Drawable,
|
center: Point,
|
||||||
// startingPoint: Point = pointFrom<Point>(0, 0),
|
isClosed: boolean = false,
|
||||||
// angleInRadian: Radians,
|
): GeometricShape<Point> => {
|
||||||
// center: Point,
|
const transform = (p: Point) =>
|
||||||
// ): GeometricShape<Point> => {
|
pointRotateRads(
|
||||||
// const transform = (p: Point) =>
|
pointFromVector(
|
||||||
// pointRotateRads(
|
vectorAdd(vectorFromPoint(p), vector(element.x, element.y)),
|
||||||
// pointFrom(p[0] + startingPoint[0], p[1] + startingPoint[1]),
|
),
|
||||||
// center,
|
center,
|
||||||
// angleInRadian,
|
element.angle,
|
||||||
// );
|
);
|
||||||
|
|
||||||
// if (element.roundness === null) {
|
const polyline = polylineFromPoints(
|
||||||
// return {
|
element.points.map((p) => transform(p as Point)),
|
||||||
// type: "polygon",
|
);
|
||||||
// data: polygonFromPoints(
|
|
||||||
// element.points.map((p) => transform(p as Point)) as Point[],
|
|
||||||
// ),
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const ops = getCurvePathOps(roughShape);
|
return (
|
||||||
|
isClosed
|
||||||
|
? {
|
||||||
|
type: "polygon",
|
||||||
|
data: polygonFromPoints(polyline.flat()),
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: "polyline",
|
||||||
|
data: polyline,
|
||||||
|
}
|
||||||
|
) as GeometricShape<Point>;
|
||||||
|
};
|
||||||
|
|
||||||
// const points: Point[] = [];
|
export const getClosedCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
// let odd = false;
|
element: ExcalidrawLinearElement,
|
||||||
// for (const operation of ops) {
|
roughShape: Drawable,
|
||||||
// if (operation.op === "move") {
|
startingPoint: Point = pointFrom<Point>(0, 0),
|
||||||
// odd = !odd;
|
angleInRadian: Radians,
|
||||||
// if (odd) {
|
center: Point,
|
||||||
// points.push(pointFrom(operation.data[0], operation.data[1]));
|
): GeometricShape<Point> => {
|
||||||
// }
|
const transform = (p: Point) =>
|
||||||
// } else if (operation.op === "bcurveTo") {
|
pointRotateRads(
|
||||||
// if (odd) {
|
pointFrom(p[0] + startingPoint[0], p[1] + startingPoint[1]),
|
||||||
// points.push(pointFrom(operation.data[0], operation.data[1]));
|
center,
|
||||||
// points.push(pointFrom(operation.data[2], operation.data[3]));
|
angleInRadian,
|
||||||
// points.push(pointFrom(operation.data[4], operation.data[5]));
|
);
|
||||||
// }
|
|
||||||
// } else if (operation.op === "lineTo") {
|
|
||||||
// if (odd) {
|
|
||||||
// points.push(pointFrom(operation.data[0], operation.data[1]));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const polygonPoints = pointsOnBezierCurves(points, 10, 5).map((p) =>
|
if (element.roundness === null) {
|
||||||
// transform(p as Point),
|
return {
|
||||||
// ) as Point[];
|
type: "polygon",
|
||||||
|
data: polygonFromPoints(
|
||||||
|
element.points.map((p) => transform(p as Point)) as Point[],
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// return {
|
const ops = getCurvePathOps(roughShape);
|
||||||
// type: "polygon",
|
|
||||||
// data: polygonFromPoints<Point>(polygonPoints),
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
|
|
||||||
// /**
|
const points: Point[] = [];
|
||||||
// * Determine intersection of a rectangular shaped element and a
|
let odd = false;
|
||||||
// * line segment.
|
for (const operation of ops) {
|
||||||
// *
|
if (operation.op === "move") {
|
||||||
// * @param element The rectangular element to test against
|
odd = !odd;
|
||||||
// * @param segment The segment intersecting the element
|
if (odd) {
|
||||||
// * @param gap Optional value to inflate the shape before testing
|
points.push(pointFrom(operation.data[0], operation.data[1]));
|
||||||
// * @returns An array of intersections
|
}
|
||||||
// */
|
} else if (operation.op === "bcurveTo") {
|
||||||
// // TODO: Replace with final rounded rectangle code
|
if (odd) {
|
||||||
// export const segmentIntersectRectangleElement = <
|
points.push(pointFrom(operation.data[0], operation.data[1]));
|
||||||
// Point extends LocalPoint | GlobalPoint,
|
points.push(pointFrom(operation.data[2], operation.data[3]));
|
||||||
// >(
|
points.push(pointFrom(operation.data[4], operation.data[5]));
|
||||||
// element: ExcalidrawBindableElement,
|
}
|
||||||
// segment: LineSegment<Point>,
|
} else if (operation.op === "lineTo") {
|
||||||
// gap: number = 0,
|
if (odd) {
|
||||||
// ): Point[] => {
|
points.push(pointFrom(operation.data[0], operation.data[1]));
|
||||||
// const bounds = [
|
}
|
||||||
// element.x - gap,
|
}
|
||||||
// element.y - gap,
|
}
|
||||||
// element.x + element.width + gap,
|
|
||||||
// element.y + element.height + gap,
|
|
||||||
// ];
|
|
||||||
// const center = pointFrom(
|
|
||||||
// (bounds[0] + bounds[2]) / 2,
|
|
||||||
// (bounds[1] + bounds[3]) / 2,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// return [
|
const polygonPoints = pointsOnBezierCurves(points, 10, 5).map((p) =>
|
||||||
// lineSegment(
|
transform(p as Point),
|
||||||
// pointRotateRads(pointFrom(bounds[0], bounds[1]), center, element.angle),
|
) as Point[];
|
||||||
// pointRotateRads(pointFrom(bounds[2], bounds[1]), center, element.angle),
|
|
||||||
// ),
|
|
||||||
// lineSegment(
|
|
||||||
// pointRotateRads(pointFrom(bounds[2], bounds[1]), center, element.angle),
|
|
||||||
// pointRotateRads(pointFrom(bounds[2], bounds[3]), center, element.angle),
|
|
||||||
// ),
|
|
||||||
// lineSegment(
|
|
||||||
// pointRotateRads(pointFrom(bounds[2], bounds[3]), center, element.angle),
|
|
||||||
// pointRotateRads(pointFrom(bounds[0], bounds[3]), center, element.angle),
|
|
||||||
// ),
|
|
||||||
// lineSegment(
|
|
||||||
// pointRotateRads(pointFrom(bounds[0], bounds[3]), center, element.angle),
|
|
||||||
// pointRotateRads(pointFrom(bounds[0], bounds[1]), center, element.angle),
|
|
||||||
// ),
|
|
||||||
// ]
|
|
||||||
// .map((s) => segmentsIntersectAt(segment, s))
|
|
||||||
// .filter((i): i is Point => !!i);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const distanceToEllipse = <Point extends LocalPoint | GlobalPoint>(
|
return {
|
||||||
// p: Point,
|
type: "polygon",
|
||||||
// ellipse: Ellipse<Point>,
|
data: polygonFromPoints<Point>(polygonPoints),
|
||||||
// ) => {
|
};
|
||||||
// const { angle, halfWidth, halfHeight, center } = ellipse;
|
};
|
||||||
// const a = halfWidth;
|
|
||||||
// const b = halfHeight;
|
|
||||||
// const translatedPoint = vectorAdd(
|
|
||||||
// vectorFromPoint(p),
|
|
||||||
// vectorScale(vectorFromPoint(center), -1),
|
|
||||||
// );
|
|
||||||
// const [rotatedPointX, rotatedPointY] = pointRotateRads(
|
|
||||||
// pointFromVector(translatedPoint),
|
|
||||||
// pointFrom(0, 0),
|
|
||||||
// -angle as Radians,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const px = Math.abs(rotatedPointX);
|
/**
|
||||||
// const py = Math.abs(rotatedPointY);
|
* Determine intersection of a rectangular shaped element and a
|
||||||
|
* line segment.
|
||||||
|
*
|
||||||
|
* @param element The rectangular element to test against
|
||||||
|
* @param segment The segment intersecting the element
|
||||||
|
* @param gap Optional value to inflate the shape before testing
|
||||||
|
* @returns An array of intersections
|
||||||
|
*/
|
||||||
|
// TODO: Replace with final rounded rectangle code
|
||||||
|
export const segmentIntersectRectangleElement = <
|
||||||
|
Point extends LocalPoint | GlobalPoint,
|
||||||
|
>(
|
||||||
|
element: ExcalidrawBindableElement,
|
||||||
|
segment: LineSegment<Point>,
|
||||||
|
gap: number = 0,
|
||||||
|
): Point[] => {
|
||||||
|
const bounds = [
|
||||||
|
element.x - gap,
|
||||||
|
element.y - gap,
|
||||||
|
element.x + element.width + gap,
|
||||||
|
element.y + element.height + gap,
|
||||||
|
];
|
||||||
|
const center = pointFrom(
|
||||||
|
(bounds[0] + bounds[2]) / 2,
|
||||||
|
(bounds[1] + bounds[3]) / 2,
|
||||||
|
);
|
||||||
|
|
||||||
// let tx = 0.707;
|
return [
|
||||||
// let ty = 0.707;
|
lineSegment(
|
||||||
|
pointRotateRads(pointFrom(bounds[0], bounds[1]), center, element.angle),
|
||||||
|
pointRotateRads(pointFrom(bounds[2], bounds[1]), center, element.angle),
|
||||||
|
),
|
||||||
|
lineSegment(
|
||||||
|
pointRotateRads(pointFrom(bounds[2], bounds[1]), center, element.angle),
|
||||||
|
pointRotateRads(pointFrom(bounds[2], bounds[3]), center, element.angle),
|
||||||
|
),
|
||||||
|
lineSegment(
|
||||||
|
pointRotateRads(pointFrom(bounds[2], bounds[3]), center, element.angle),
|
||||||
|
pointRotateRads(pointFrom(bounds[0], bounds[3]), center, element.angle),
|
||||||
|
),
|
||||||
|
lineSegment(
|
||||||
|
pointRotateRads(pointFrom(bounds[0], bounds[3]), center, element.angle),
|
||||||
|
pointRotateRads(pointFrom(bounds[0], bounds[1]), center, element.angle),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.map((s) => segmentsIntersectAt(segment, s))
|
||||||
|
.filter((i): i is Point => !!i);
|
||||||
|
};
|
||||||
|
|
||||||
// for (let i = 0; i < 3; i++) {
|
const distanceToEllipse = <Point extends LocalPoint | GlobalPoint>(
|
||||||
// const x = a * tx;
|
p: Point,
|
||||||
// const y = b * ty;
|
ellipse: Ellipse<Point>,
|
||||||
|
) => {
|
||||||
|
const { angle, halfWidth, halfHeight, center } = ellipse;
|
||||||
|
const a = halfWidth;
|
||||||
|
const b = halfHeight;
|
||||||
|
const translatedPoint = vectorAdd(
|
||||||
|
vectorFromPoint(p),
|
||||||
|
vectorScale(vectorFromPoint(center), -1),
|
||||||
|
);
|
||||||
|
const [rotatedPointX, rotatedPointY] = pointRotateRads(
|
||||||
|
pointFromVector(translatedPoint),
|
||||||
|
pointFrom(0, 0),
|
||||||
|
-angle as Radians,
|
||||||
|
);
|
||||||
|
|
||||||
// const ex = ((a * a - b * b) * tx ** 3) / a;
|
const px = Math.abs(rotatedPointX);
|
||||||
// const ey = ((b * b - a * a) * ty ** 3) / b;
|
const py = Math.abs(rotatedPointY);
|
||||||
|
|
||||||
// const rx = x - ex;
|
let tx = 0.707;
|
||||||
// const ry = y - ey;
|
let ty = 0.707;
|
||||||
|
|
||||||
// const qx = px - ex;
|
for (let i = 0; i < 3; i++) {
|
||||||
// const qy = py - ey;
|
const x = a * tx;
|
||||||
|
const y = b * ty;
|
||||||
|
|
||||||
// const r = Math.hypot(ry, rx);
|
const ex = ((a * a - b * b) * tx ** 3) / a;
|
||||||
// const q = Math.hypot(qy, qx);
|
const ey = ((b * b - a * a) * ty ** 3) / b;
|
||||||
|
|
||||||
// tx = Math.min(1, Math.max(0, ((qx * r) / q + ex) / a));
|
const rx = x - ex;
|
||||||
// ty = Math.min(1, Math.max(0, ((qy * r) / q + ey) / b));
|
const ry = y - ey;
|
||||||
// const t = Math.hypot(ty, tx);
|
|
||||||
// tx /= t;
|
|
||||||
// ty /= t;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const [minX, minY] = [
|
const qx = px - ex;
|
||||||
// a * tx * Math.sign(rotatedPointX),
|
const qy = py - ey;
|
||||||
// b * ty * Math.sign(rotatedPointY),
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// return pointDistance(
|
const r = Math.hypot(ry, rx);
|
||||||
// pointFrom(rotatedPointX, rotatedPointY),
|
const q = Math.hypot(qy, qx);
|
||||||
// pointFrom(minX, minY),
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export const pointOnEllipse = <Point extends LocalPoint | GlobalPoint>(
|
tx = Math.min(1, Math.max(0, ((qx * r) / q + ex) / a));
|
||||||
// point: Point,
|
ty = Math.min(1, Math.max(0, ((qy * r) / q + ey) / b));
|
||||||
// ellipse: Ellipse<Point>,
|
const t = Math.hypot(ty, tx);
|
||||||
// threshold = PRECISION,
|
tx /= t;
|
||||||
// ) => {
|
ty /= t;
|
||||||
// return distanceToEllipse(point, ellipse) <= threshold;
|
}
|
||||||
// };
|
|
||||||
|
|
||||||
// export const pointInEllipse = <Point extends LocalPoint | GlobalPoint>(
|
const [minX, minY] = [
|
||||||
// p: Point,
|
a * tx * Math.sign(rotatedPointX),
|
||||||
// ellipse: Ellipse<Point>,
|
b * ty * Math.sign(rotatedPointY),
|
||||||
// ) => {
|
];
|
||||||
// const { center, angle, halfWidth, halfHeight } = ellipse;
|
|
||||||
// const translatedPoint = vectorAdd(
|
|
||||||
// vectorFromPoint(p),
|
|
||||||
// vectorScale(vectorFromPoint(center), -1),
|
|
||||||
// );
|
|
||||||
// const [rotatedPointX, rotatedPointY] = pointRotateRads(
|
|
||||||
// pointFromVector(translatedPoint),
|
|
||||||
// pointFrom(0, 0),
|
|
||||||
// -angle as Radians,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// return (
|
return pointDistance(
|
||||||
// (rotatedPointX / halfWidth) * (rotatedPointX / halfWidth) +
|
pointFrom(rotatedPointX, rotatedPointY),
|
||||||
// (rotatedPointY / halfHeight) * (rotatedPointY / halfHeight) <=
|
pointFrom(minX, minY),
|
||||||
// 1
|
);
|
||||||
// );
|
};
|
||||||
// };
|
|
||||||
|
|
||||||
// export const ellipseAxes = <Point extends LocalPoint | GlobalPoint>(
|
export const pointOnEllipse = <Point extends LocalPoint | GlobalPoint>(
|
||||||
// ellipse: Ellipse<Point>,
|
point: Point,
|
||||||
// ) => {
|
ellipse: Ellipse<Point>,
|
||||||
// const widthGreaterThanHeight = ellipse.halfWidth > ellipse.halfHeight;
|
threshold = PRECISION,
|
||||||
|
) => {
|
||||||
|
return distanceToEllipse(point, ellipse) <= threshold;
|
||||||
|
};
|
||||||
|
|
||||||
// const majorAxis = widthGreaterThanHeight
|
export const pointInEllipse = <Point extends LocalPoint | GlobalPoint>(
|
||||||
// ? ellipse.halfWidth * 2
|
p: Point,
|
||||||
// : ellipse.halfHeight * 2;
|
ellipse: Ellipse<Point>,
|
||||||
// const minorAxis = widthGreaterThanHeight
|
) => {
|
||||||
// ? ellipse.halfHeight * 2
|
const { center, angle, halfWidth, halfHeight } = ellipse;
|
||||||
// : ellipse.halfWidth * 2;
|
const translatedPoint = vectorAdd(
|
||||||
|
vectorFromPoint(p),
|
||||||
|
vectorScale(vectorFromPoint(center), -1),
|
||||||
|
);
|
||||||
|
const [rotatedPointX, rotatedPointY] = pointRotateRads(
|
||||||
|
pointFromVector(translatedPoint),
|
||||||
|
pointFrom(0, 0),
|
||||||
|
-angle as Radians,
|
||||||
|
);
|
||||||
|
|
||||||
// return {
|
return (
|
||||||
// majorAxis,
|
(rotatedPointX / halfWidth) * (rotatedPointX / halfWidth) +
|
||||||
// minorAxis,
|
(rotatedPointY / halfHeight) * (rotatedPointY / halfHeight) <=
|
||||||
// };
|
1
|
||||||
// };
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// export const ellipseFocusToCenter = <Point extends LocalPoint | GlobalPoint>(
|
export const ellipseAxes = <Point extends LocalPoint | GlobalPoint>(
|
||||||
// ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
// ) => {
|
) => {
|
||||||
// const { majorAxis, minorAxis } = ellipseAxes(ellipse);
|
const widthGreaterThanHeight = ellipse.halfWidth > ellipse.halfHeight;
|
||||||
|
|
||||||
// return Math.sqrt(majorAxis ** 2 - minorAxis ** 2);
|
const majorAxis = widthGreaterThanHeight
|
||||||
// };
|
? ellipse.halfWidth * 2
|
||||||
|
: ellipse.halfHeight * 2;
|
||||||
|
const minorAxis = widthGreaterThanHeight
|
||||||
|
? ellipse.halfHeight * 2
|
||||||
|
: ellipse.halfWidth * 2;
|
||||||
|
|
||||||
// export const ellipseExtremes = <Point extends LocalPoint | GlobalPoint>(
|
return {
|
||||||
// ellipse: Ellipse<Point>,
|
majorAxis,
|
||||||
// ) => {
|
minorAxis,
|
||||||
// const { center, angle } = ellipse;
|
};
|
||||||
// const { majorAxis, minorAxis } = ellipseAxes(ellipse);
|
};
|
||||||
|
|
||||||
// const cos = Math.cos(angle);
|
export const ellipseFocusToCenter = <Point extends LocalPoint | GlobalPoint>(
|
||||||
// const sin = Math.sin(angle);
|
ellipse: Ellipse<Point>,
|
||||||
|
) => {
|
||||||
|
const { majorAxis, minorAxis } = ellipseAxes(ellipse);
|
||||||
|
|
||||||
// const sqSum = majorAxis ** 2 + minorAxis ** 2;
|
return Math.sqrt(majorAxis ** 2 - minorAxis ** 2);
|
||||||
// const sqDiff = (majorAxis ** 2 - minorAxis ** 2) * Math.cos(2 * angle);
|
};
|
||||||
|
|
||||||
// const yMax = Math.sqrt((sqSum - sqDiff) / 2);
|
export const ellipseExtremes = <Point extends LocalPoint | GlobalPoint>(
|
||||||
// const xAtYMax =
|
ellipse: Ellipse<Point>,
|
||||||
// (yMax * sqSum * sin * cos) /
|
) => {
|
||||||
// (majorAxis ** 2 * sin ** 2 + minorAxis ** 2 * cos ** 2);
|
const { center, angle } = ellipse;
|
||||||
|
const { majorAxis, minorAxis } = ellipseAxes(ellipse);
|
||||||
|
|
||||||
// const xMax = Math.sqrt((sqSum + sqDiff) / 2);
|
const cos = Math.cos(angle);
|
||||||
// const yAtXMax =
|
const sin = Math.sin(angle);
|
||||||
// (xMax * sqSum * sin * cos) /
|
|
||||||
// (majorAxis ** 2 * cos ** 2 + minorAxis ** 2 * sin ** 2);
|
|
||||||
// const centerVector = vectorFromPoint(center);
|
|
||||||
|
|
||||||
// return [
|
const sqSum = majorAxis ** 2 + minorAxis ** 2;
|
||||||
// vectorAdd(vector(xAtYMax, yMax), centerVector),
|
const sqDiff = (majorAxis ** 2 - minorAxis ** 2) * Math.cos(2 * angle);
|
||||||
// vectorAdd(vectorScale(vector(xAtYMax, yMax), -1), centerVector),
|
|
||||||
// vectorAdd(vector(xMax, yAtXMax), centerVector),
|
const yMax = Math.sqrt((sqSum - sqDiff) / 2);
|
||||||
// vectorAdd(vector(xMax, yAtXMax), centerVector),
|
const xAtYMax =
|
||||||
// ];
|
(yMax * sqSum * sin * cos) /
|
||||||
// };
|
(majorAxis ** 2 * sin ** 2 + minorAxis ** 2 * cos ** 2);
|
||||||
|
|
||||||
|
const xMax = Math.sqrt((sqSum + sqDiff) / 2);
|
||||||
|
const yAtXMax =
|
||||||
|
(xMax * sqSum * sin * cos) /
|
||||||
|
(majorAxis ** 2 * cos ** 2 + minorAxis ** 2 * sin ** 2);
|
||||||
|
const centerVector = vectorFromPoint(center);
|
||||||
|
|
||||||
|
return [
|
||||||
|
vectorAdd(vector(xAtYMax, yMax), centerVector),
|
||||||
|
vectorAdd(vectorScale(vector(xAtYMax, yMax), -1), centerVector),
|
||||||
|
vectorAdd(vector(xMax, yAtXMax), centerVector),
|
||||||
|
vectorAdd(vector(xMax, yAtXMax), centerVector),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
@ -8,7 +8,14 @@ import {
|
|||||||
segmentsIntersectAt,
|
segmentsIntersectAt,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type { GlobalPoint, LineSegment, Polygon } from "@excalidraw/math";
|
import type {
|
||||||
|
GlobalPoint,
|
||||||
|
LineSegment,
|
||||||
|
Polygon,
|
||||||
|
Radians,
|
||||||
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
|
import { pointInEllipse, pointOnEllipse, type Ellipse } from "../src/shape";
|
||||||
|
|
||||||
describe("point and line", () => {
|
describe("point and line", () => {
|
||||||
// const l: Line<GlobalPoint> = line(point(1, 0), point(1, 2));
|
// const l: Line<GlobalPoint> = line(point(1, 0), point(1, 2));
|
||||||
@ -64,56 +71,56 @@ describe("point and polygon", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// describe("point and ellipse", () => {
|
describe("point and ellipse", () => {
|
||||||
// const ellipse: Ellipse<GlobalPoint> = {
|
const ellipse: Ellipse<GlobalPoint> = {
|
||||||
// center: pointFrom(0, 0),
|
center: pointFrom(0, 0),
|
||||||
// angle: 0 as Radians,
|
angle: 0 as Radians,
|
||||||
// halfWidth: 2,
|
halfWidth: 2,
|
||||||
// halfHeight: 1,
|
halfHeight: 1,
|
||||||
// };
|
};
|
||||||
|
|
||||||
// it("point on ellipse", () => {
|
it("point on ellipse", () => {
|
||||||
// [
|
[
|
||||||
// pointFrom(0, 1),
|
pointFrom(0, 1),
|
||||||
// pointFrom(0, -1),
|
pointFrom(0, -1),
|
||||||
// pointFrom(2, 0),
|
pointFrom(2, 0),
|
||||||
// pointFrom(-2, 0),
|
pointFrom(-2, 0),
|
||||||
// ].forEach((p) => {
|
].forEach((p) => {
|
||||||
// expect(pointOnEllipse(p, ellipse)).toBe(true);
|
expect(pointOnEllipse(p, ellipse)).toBe(true);
|
||||||
// });
|
});
|
||||||
// expect(pointOnEllipse(pointFrom(-1.4, 0.7), ellipse, 0.1)).toBe(true);
|
expect(pointOnEllipse(pointFrom(-1.4, 0.7), ellipse, 0.1)).toBe(true);
|
||||||
// expect(pointOnEllipse(pointFrom(-1.4, 0.71), ellipse, 0.01)).toBe(true);
|
expect(pointOnEllipse(pointFrom(-1.4, 0.71), ellipse, 0.01)).toBe(true);
|
||||||
|
|
||||||
// expect(pointOnEllipse(pointFrom(1.4, 0.7), ellipse, 0.1)).toBe(true);
|
expect(pointOnEllipse(pointFrom(1.4, 0.7), ellipse, 0.1)).toBe(true);
|
||||||
// expect(pointOnEllipse(pointFrom(1.4, 0.71), ellipse, 0.01)).toBe(true);
|
expect(pointOnEllipse(pointFrom(1.4, 0.71), ellipse, 0.01)).toBe(true);
|
||||||
|
|
||||||
// expect(pointOnEllipse(pointFrom(1, -0.86), ellipse, 0.1)).toBe(true);
|
expect(pointOnEllipse(pointFrom(1, -0.86), ellipse, 0.1)).toBe(true);
|
||||||
// expect(pointOnEllipse(pointFrom(1, -0.86), ellipse, 0.01)).toBe(true);
|
expect(pointOnEllipse(pointFrom(1, -0.86), ellipse, 0.01)).toBe(true);
|
||||||
|
|
||||||
// expect(pointOnEllipse(pointFrom(-1, -0.86), ellipse, 0.1)).toBe(true);
|
expect(pointOnEllipse(pointFrom(-1, -0.86), ellipse, 0.1)).toBe(true);
|
||||||
// expect(pointOnEllipse(pointFrom(-1, -0.86), ellipse, 0.01)).toBe(true);
|
expect(pointOnEllipse(pointFrom(-1, -0.86), ellipse, 0.01)).toBe(true);
|
||||||
|
|
||||||
// expect(pointOnEllipse(pointFrom(-1, 0.8), ellipse)).toBe(false);
|
expect(pointOnEllipse(pointFrom(-1, 0.8), ellipse)).toBe(false);
|
||||||
// expect(pointOnEllipse(pointFrom(1, -0.8), ellipse)).toBe(false);
|
expect(pointOnEllipse(pointFrom(1, -0.8), ellipse)).toBe(false);
|
||||||
// });
|
});
|
||||||
|
|
||||||
// it("point in ellipse", () => {
|
it("point in ellipse", () => {
|
||||||
// [
|
[
|
||||||
// pointFrom(0, 1),
|
pointFrom(0, 1),
|
||||||
// pointFrom(0, -1),
|
pointFrom(0, -1),
|
||||||
// pointFrom(2, 0),
|
pointFrom(2, 0),
|
||||||
// pointFrom(-2, 0),
|
pointFrom(-2, 0),
|
||||||
// ].forEach((p) => {
|
].forEach((p) => {
|
||||||
// expect(pointInEllipse(p, ellipse)).toBe(true);
|
expect(pointInEllipse(p, ellipse)).toBe(true);
|
||||||
// });
|
});
|
||||||
|
|
||||||
// expect(pointInEllipse(pointFrom(-1, 0.8), ellipse)).toBe(true);
|
expect(pointInEllipse(pointFrom(-1, 0.8), ellipse)).toBe(true);
|
||||||
// expect(pointInEllipse(pointFrom(1, -0.8), ellipse)).toBe(true);
|
expect(pointInEllipse(pointFrom(1, -0.8), ellipse)).toBe(true);
|
||||||
|
|
||||||
// expect(pointInEllipse(pointFrom(-1, 1), ellipse)).toBe(false);
|
expect(pointInEllipse(pointFrom(-1, 1), ellipse)).toBe(false);
|
||||||
// expect(pointInEllipse(pointFrom(-1.4, 0.8), ellipse)).toBe(false);
|
expect(pointInEllipse(pointFrom(-1.4, 0.8), ellipse)).toBe(false);
|
||||||
// });
|
});
|
||||||
// });
|
});
|
||||||
|
|
||||||
describe("line and line", () => {
|
describe("line and line", () => {
|
||||||
const lineA: LineSegment<GlobalPoint> = lineSegment(
|
const lineA: LineSegment<GlobalPoint> = lineSegment(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user