mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feed segments to worker
This commit is contained in:
parent
b0cdf1c296
commit
33d5886123
4 changed files with 280 additions and 297 deletions
|
@ -1,23 +1,43 @@
|
||||||
import { GlobalPoint, pointFrom } from "../../math";
|
import { pointsOnBezierCurves } from "points-on-curve";
|
||||||
|
import {
|
||||||
|
type Curve,
|
||||||
|
type GlobalPoint,
|
||||||
|
type LineSegment,
|
||||||
|
type Radians,
|
||||||
|
lineSegment,
|
||||||
|
pointFrom,
|
||||||
|
pointRotateRads,
|
||||||
|
} from "../../math";
|
||||||
import { AnimatedTrail } from "../animated-trail";
|
import { AnimatedTrail } from "../animated-trail";
|
||||||
import { AnimationFrameHandler } from "../animation-frame-handler";
|
import { type AnimationFrameHandler } from "../animation-frame-handler";
|
||||||
import App from "../components/App";
|
import type App from "../components/App";
|
||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
import { isFrameLikeElement, isLinearElement } from "../element/typeChecks";
|
import { isFrameLikeElement, isLinearElement } from "../element/typeChecks";
|
||||||
import {
|
import type {
|
||||||
|
ElementsMap,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
|
ExcalidrawEllipseElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
|
ExcalidrawRectanguloidElement,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import { getFrameChildren } from "../frame";
|
import { getFrameChildren } from "../frame";
|
||||||
import { selectGroupsForSelectedElements } from "../groups";
|
import { selectGroupsForSelectedElements } from "../groups";
|
||||||
import { easeOut } from "../utils";
|
import { getElementShape } from "../shapes";
|
||||||
import { LassoWorkerInput, LassoWorkerOutput } from "./worker";
|
import { arrayToMap, easeOut } from "../utils";
|
||||||
|
import type { LassoWorkerInput, LassoWorkerOutput } from "./worker";
|
||||||
|
import {
|
||||||
|
deconstructDiamondElement,
|
||||||
|
deconstructRectanguloidElement,
|
||||||
|
} from "../element/utils";
|
||||||
|
import { getElementAbsoluteCoords } from "../element";
|
||||||
|
|
||||||
export class LassoTrail extends AnimatedTrail {
|
export class LassoTrail extends AnimatedTrail {
|
||||||
private intersectedElements: Set<ExcalidrawElement["id"]> = new Set();
|
private intersectedElements: Set<ExcalidrawElement["id"]> = new Set();
|
||||||
private enclosedElements: Set<ExcalidrawElement["id"]> = new Set();
|
private enclosedElements: Set<ExcalidrawElement["id"]> = new Set();
|
||||||
private worker: Worker | null = null;
|
private worker: Worker | null = null;
|
||||||
|
private elementsSegments: Map<string, LineSegment<GlobalPoint>[]> | null =
|
||||||
|
null;
|
||||||
|
|
||||||
constructor(animationFrameHandler: AnimationFrameHandler, app: App) {
|
constructor(animationFrameHandler: AnimationFrameHandler, app: App) {
|
||||||
super(animationFrameHandler, app, {
|
super(animationFrameHandler, app, {
|
||||||
|
@ -132,10 +152,20 @@ export class LassoTrail extends AnimatedTrail {
|
||||||
.getCurrentTrail()
|
.getCurrentTrail()
|
||||||
?.originalPoints?.map((p) => pointFrom<GlobalPoint>(p[0], p[1]));
|
?.originalPoints?.map((p) => pointFrom<GlobalPoint>(p[0], p[1]));
|
||||||
|
|
||||||
|
if (!this.elementsSegments) {
|
||||||
|
this.elementsSegments = new Map();
|
||||||
|
const visibleElementsMap = arrayToMap(this.app.visibleElements);
|
||||||
|
for (const element of this.app.visibleElements) {
|
||||||
|
const segments = getElementLineSegments(element, visibleElementsMap);
|
||||||
|
this.elementsSegments.set(element.id, segments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (lassoPath) {
|
if (lassoPath) {
|
||||||
const message: LassoWorkerInput = {
|
const message: LassoWorkerInput = {
|
||||||
lassoPath,
|
lassoPath,
|
||||||
elements: this.app.visibleElements,
|
elements: this.app.visibleElements,
|
||||||
|
elementsSegments: this.elementsSegments,
|
||||||
intersectedElements: this.intersectedElements,
|
intersectedElements: this.intersectedElements,
|
||||||
enclosedElements: this.enclosedElements,
|
enclosedElements: this.enclosedElements,
|
||||||
};
|
};
|
||||||
|
@ -149,9 +179,185 @@ export class LassoTrail extends AnimatedTrail {
|
||||||
super.clearTrails();
|
super.clearTrails();
|
||||||
this.intersectedElements.clear();
|
this.intersectedElements.clear();
|
||||||
this.enclosedElements.clear();
|
this.enclosedElements.clear();
|
||||||
|
this.elementsSegments = null;
|
||||||
this.app.setState({
|
this.app.setState({
|
||||||
lassoSelection: null,
|
lassoSelection: null,
|
||||||
});
|
});
|
||||||
this.worker?.terminate();
|
this.worker?.terminate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an element, return the line segments that make up the element.
|
||||||
|
*
|
||||||
|
* Uses helpers from /math
|
||||||
|
*/
|
||||||
|
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") {
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
|
@ -1,18 +1,8 @@
|
||||||
import {
|
import type { GlobalPoint, LineSegment } from "../../math/types";
|
||||||
GlobalPoint,
|
import { polygonFromPoints, polygonIncludesPoint } from "../../math/polygon";
|
||||||
LineSegment,
|
import type { ExcalidrawElement } from "../element/types";
|
||||||
LocalPoint,
|
import { lineSegment, lineSegmentIntersectionPoints } from "../../math/segment";
|
||||||
Radians,
|
import { simplify } from "points-on-curve";
|
||||||
} from "../../math/types";
|
|
||||||
import { pointFrom, pointRotateRads } from "../../math/point";
|
|
||||||
import { polygonFromPoints } from "../../math/polygon";
|
|
||||||
import { ElementsMap, ExcalidrawElement } from "../element/types";
|
|
||||||
import { pointsOnBezierCurves, simplify } from "points-on-curve";
|
|
||||||
import { lineSegment } from "../../math/segment";
|
|
||||||
import throttle from "lodash.throttle";
|
|
||||||
import { RoughGenerator } from "roughjs/bin/generator";
|
|
||||||
import { Point } from "roughjs/bin/geometry";
|
|
||||||
import { Drawable, Op } from "roughjs/bin/core";
|
|
||||||
|
|
||||||
// variables to track processing state and latest input data
|
// variables to track processing state and latest input data
|
||||||
// for "backpressure" purposes
|
// for "backpressure" purposes
|
||||||
|
@ -38,7 +28,9 @@ self.onmessage = (event: MessageEvent<LassoWorkerInput>) => {
|
||||||
// function to process the latest data
|
// function to process the latest data
|
||||||
const processInputData = () => {
|
const processInputData = () => {
|
||||||
// If no data to process, return
|
// If no data to process, return
|
||||||
if (!latestInputData) return;
|
if (!latestInputData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// capture the current data to process and reset latestData
|
// capture the current data to process and reset latestData
|
||||||
const dataToProcess = latestInputData;
|
const dataToProcess = latestInputData;
|
||||||
|
@ -79,9 +71,12 @@ const processInputData = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ElementsSegments = Map<string, LineSegment<GlobalPoint>[]>;
|
||||||
|
|
||||||
export type LassoWorkerInput = {
|
export type LassoWorkerInput = {
|
||||||
lassoPath: GlobalPoint[];
|
lassoPath: GlobalPoint[];
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
|
elementsSegments: ElementsSegments;
|
||||||
intersectedElements: Set<ExcalidrawElement["id"]>;
|
intersectedElements: Set<ExcalidrawElement["id"]>;
|
||||||
enclosedElements: Set<ExcalidrawElement["id"]>;
|
enclosedElements: Set<ExcalidrawElement["id"]>;
|
||||||
};
|
};
|
||||||
|
@ -90,304 +85,86 @@ export type LassoWorkerOutput = {
|
||||||
selectedElementIds: string[];
|
selectedElementIds: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateSelection = throttle(
|
export const updateSelection = (input: LassoWorkerInput): LassoWorkerOutput => {
|
||||||
(input: LassoWorkerInput): LassoWorkerOutput => {
|
const {
|
||||||
const { lassoPath, elements, intersectedElements, enclosedElements } =
|
lassoPath,
|
||||||
input;
|
elements,
|
||||||
|
elementsSegments,
|
||||||
const elementsMap = arrayToMap(elements);
|
intersectedElements,
|
||||||
// simplify the path to reduce the number of points
|
enclosedElements,
|
||||||
const simplifiedPath = simplify(lassoPath, 0.75) as GlobalPoint[];
|
} = input;
|
||||||
// close the path to form a polygon for enclosure check
|
// simplify the path to reduce the number of points
|
||||||
const closedPath = polygonFromPoints(simplifiedPath);
|
const path = simplify(lassoPath, 2) as GlobalPoint[];
|
||||||
// as the path might not enclose a shape anymore, clear before checking
|
// close the path to form a polygon for enclosure check
|
||||||
enclosedElements.clear();
|
const closedPath = polygonFromPoints(path);
|
||||||
for (const [, element] of elementsMap) {
|
// as the path might not enclose a shape anymore, clear before checking
|
||||||
if (
|
enclosedElements.clear();
|
||||||
!intersectedElements.has(element.id) &&
|
for (const element of elements) {
|
||||||
!enclosedElements.has(element.id)
|
if (
|
||||||
) {
|
!intersectedElements.has(element.id) &&
|
||||||
const enclosed = enclosureTest(closedPath, element, elementsMap);
|
!enclosedElements.has(element.id)
|
||||||
if (enclosed) {
|
) {
|
||||||
enclosedElements.add(element.id);
|
const enclosed = enclosureTest(closedPath, element, elementsSegments);
|
||||||
} else {
|
if (enclosed) {
|
||||||
const intersects = intersectionTest(closedPath, element, elementsMap);
|
enclosedElements.add(element.id);
|
||||||
if (intersects) {
|
} else {
|
||||||
intersectedElements.add(element.id);
|
const intersects = intersectionTest(
|
||||||
}
|
closedPath,
|
||||||
|
element,
|
||||||
|
elementsSegments,
|
||||||
|
);
|
||||||
|
if (intersects) {
|
||||||
|
intersectedElements.add(element.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const results = [...intersectedElements, ...enclosedElements];
|
const results = [...intersectedElements, ...enclosedElements];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedElementIds: results,
|
selectedElementIds: results,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
100,
|
|
||||||
);
|
|
||||||
|
|
||||||
const enclosureTest = (
|
const enclosureTest = (
|
||||||
lassoPath: GlobalPoint[],
|
lassoPath: GlobalPoint[],
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
elementsSegments: ElementsSegments,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const lassoPolygon = polygonFromPoints(lassoPath);
|
const lassoPolygon = polygonFromPoints(lassoPath);
|
||||||
const segments = getElementLineSegments(element, elementsMap);
|
const segments = elementsSegments.get(element.id);
|
||||||
|
if (!segments) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return segments.some((segment) => {
|
return segments.some((segment) => {
|
||||||
return segment.some((point) => isPointInPolygon(point, lassoPolygon));
|
return segment.some((point) => polygonIncludesPoint(point, lassoPolygon));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// // Helper function to check if a point is inside a polygon
|
|
||||||
const isPointInPolygon = (
|
|
||||||
point: GlobalPoint,
|
|
||||||
polygon: GlobalPoint[],
|
|
||||||
): boolean => {
|
|
||||||
let isInside = false;
|
|
||||||
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
||||||
const xi = polygon[i][0],
|
|
||||||
yi = polygon[i][1];
|
|
||||||
const xj = polygon[j][0],
|
|
||||||
yj = polygon[j][1];
|
|
||||||
|
|
||||||
const intersect =
|
|
||||||
yi > point[1] !== yj > point[1] &&
|
|
||||||
point[0] < ((xj - xi) * (point[1] - yi)) / (yj - yi) + xi;
|
|
||||||
if (intersect) isInside = !isInside;
|
|
||||||
}
|
|
||||||
return isInside;
|
|
||||||
};
|
|
||||||
|
|
||||||
const intersectionTest = (
|
const intersectionTest = (
|
||||||
lassoPath: GlobalPoint[],
|
lassoPath: GlobalPoint[],
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
elementsSegments: ElementsSegments,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const elementSegments = getElementLineSegments(element, elementsMap);
|
const elementSegments = elementsSegments.get(element.id);
|
||||||
|
if (!elementSegments) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const lassoSegments = lassoPath.reduce((acc, point, index) => {
|
const lassoSegments = lassoPath.reduce((acc, point, index) => {
|
||||||
if (index === 0) return acc;
|
if (index === 0) {
|
||||||
acc.push([lassoPath[index - 1], point] as [GlobalPoint, GlobalPoint]);
|
return acc;
|
||||||
|
}
|
||||||
|
acc.push(lineSegment(lassoPath[index - 1], point));
|
||||||
return acc;
|
return acc;
|
||||||
}, [] as [GlobalPoint, GlobalPoint][]);
|
}, [] as LineSegment<GlobalPoint>[]);
|
||||||
|
|
||||||
return lassoSegments.some((lassoSegment) =>
|
return lassoSegments.some((lassoSegment) =>
|
||||||
elementSegments.some((elementSegment) =>
|
elementSegments.some(
|
||||||
doLineSegmentsIntersect(lassoSegment, elementSegment),
|
(elementSegment) =>
|
||||||
|
lineSegmentIntersectionPoints(lassoSegment, elementSegment) !== null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to check if two line segments intersect
|
|
||||||
const doLineSegmentsIntersect = (
|
|
||||||
[p1, p2]: [GlobalPoint, GlobalPoint],
|
|
||||||
[p3, p4]: [GlobalPoint, GlobalPoint],
|
|
||||||
): boolean => {
|
|
||||||
const denominator =
|
|
||||||
(p4[1] - p3[1]) * (p2[0] - p1[0]) - (p4[0] - p3[0]) * (p2[1] - p1[1]);
|
|
||||||
|
|
||||||
if (denominator === 0) return false;
|
|
||||||
|
|
||||||
const ua =
|
|
||||||
((p4[0] - p3[0]) * (p1[1] - p3[1]) - (p4[1] - p3[1]) * (p1[0] - p3[0])) /
|
|
||||||
denominator;
|
|
||||||
const ub =
|
|
||||||
((p2[0] - p1[0]) * (p1[1] - p3[1]) - (p2[1] - p1[1]) * (p1[0] - p3[0])) /
|
|
||||||
denominator;
|
|
||||||
|
|
||||||
return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCurvePathOps = (shape: Drawable): Op[] => {
|
|
||||||
for (const set of shape.sets) {
|
|
||||||
if (set.type === "path") {
|
|
||||||
return set.ops;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return shape.sets[0].ops;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getElementLineSegments = (
|
|
||||||
element: ExcalidrawElement,
|
|
||||||
elementsMap: ElementsMap,
|
|
||||||
): LineSegment<GlobalPoint>[] => {
|
|
||||||
const [x1, y1, x2, y2, cx, cy] = [
|
|
||||||
element.x,
|
|
||||||
element.y,
|
|
||||||
element.x + element.width,
|
|
||||||
element.y + element.height,
|
|
||||||
element.x + element.width / 2,
|
|
||||||
element.y + element.height / 2,
|
|
||||||
];
|
|
||||||
|
|
||||||
const center: GlobalPoint = pointFrom(cx, cy);
|
|
||||||
|
|
||||||
if (
|
|
||||||
element.type === "line" ||
|
|
||||||
element.type === "arrow" ||
|
|
||||||
element.type === "freedraw"
|
|
||||||
) {
|
|
||||||
const segments: LineSegment<GlobalPoint>[] = [];
|
|
||||||
|
|
||||||
const getPointsOnCurve = () => {
|
|
||||||
const generator = new RoughGenerator();
|
|
||||||
|
|
||||||
const drawable = generator.curve(element.points as unknown as Point[]);
|
|
||||||
|
|
||||||
const ops = getCurvePathOps(drawable);
|
|
||||||
|
|
||||||
const _points: LocalPoint[] = [];
|
|
||||||
// let odd = false;
|
|
||||||
// for (const operation of ops) {
|
|
||||||
// if (operation.op === "move") {
|
|
||||||
// odd = !odd;
|
|
||||||
// if (odd) {
|
|
||||||
// if (
|
|
||||||
// Array.isArray(operation.data) &&
|
|
||||||
// operation.data.length >= 2 &&
|
|
||||||
// operation.data.every(
|
|
||||||
// (d) => d !== undefined && typeof d === "number",
|
|
||||||
// )
|
|
||||||
// ) {
|
|
||||||
// _points.push(pointFrom(operation.data[0], operation.data[1]));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else if (operation.op === "bcurveTo") {
|
|
||||||
// if (odd) {
|
|
||||||
// if (
|
|
||||||
// Array.isArray(operation.data) &&
|
|
||||||
// operation.data.length === 6 &&
|
|
||||||
// operation.data.every(
|
|
||||||
// (d) => d !== undefined && typeof d === "number",
|
|
||||||
// )
|
|
||||||
// ) {
|
|
||||||
// _points.push(pointFrom(operation.data[0], operation.data[1]));
|
|
||||||
// _points.push(pointFrom(operation.data[2], operation.data[3]));
|
|
||||||
// _points.push(pointFrom(operation.data[4], operation.data[5]));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else if (operation.op === "lineTo") {
|
|
||||||
// if (
|
|
||||||
// Array.isArray(operation.data) &&
|
|
||||||
// operation.data.length >= 2 &&
|
|
||||||
// odd &&
|
|
||||||
// operation.data.every(
|
|
||||||
// (d) => d !== undefined && typeof d === "number",
|
|
||||||
// )
|
|
||||||
// ) {
|
|
||||||
// _points.push(pointFrom(operation.data[0], operation.data[1]));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return pointsOnBezierCurves(_points, 10, 5);
|
|
||||||
};
|
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
|
|
||||||
// const points =
|
|
||||||
// element.roughness !== 0 && element.type !== "freedraw"
|
|
||||||
// ? getPointsOnCurve()
|
|
||||||
// : element.points;
|
|
||||||
|
|
||||||
const points = element.points;
|
|
||||||
|
|
||||||
while (i < points.length - 1) {
|
|
||||||
segments.push(
|
|
||||||
lineSegment(
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom(
|
|
||||||
element.points[i][0] + element.x,
|
|
||||||
element.points[i][1] + element.y,
|
|
||||||
),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom(
|
|
||||||
element.points[i + 1][0] + element.x,
|
|
||||||
element.points[i + 1][1] + element.y,
|
|
||||||
),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return segments;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [nw, ne, sw, se, n, s, 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));
|
|
||||||
|
|
||||||
if (element.type === "diamond") {
|
|
||||||
return [
|
|
||||||
lineSegment(n, w),
|
|
||||||
lineSegment(n, e),
|
|
||||||
lineSegment(s, w),
|
|
||||||
lineSegment(s, e),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.type === "ellipse") {
|
|
||||||
return [
|
|
||||||
lineSegment(n, w),
|
|
||||||
lineSegment(n, e),
|
|
||||||
lineSegment(s, w),
|
|
||||||
lineSegment(s, e),
|
|
||||||
lineSegment(n, w),
|
|
||||||
lineSegment(n, e),
|
|
||||||
lineSegment(s, w),
|
|
||||||
lineSegment(s, e),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.type === "frame" || element.type === "magicframe") {
|
|
||||||
return [
|
|
||||||
lineSegment(nw, ne),
|
|
||||||
lineSegment(ne, se),
|
|
||||||
lineSegment(se, sw),
|
|
||||||
lineSegment(sw, nw),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is a copy of arrayToMap from utils.ts
|
|
||||||
// copy to avoid accessing DOM related things in worker
|
|
||||||
const arrayToMap = <T extends { id: string } | string>(
|
|
||||||
items: readonly T[] | Map<string, T>,
|
|
||||||
) => {
|
|
||||||
if (items instanceof Map) {
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
return items.reduce((acc: Map<string, T>, element) => {
|
|
||||||
acc.set(typeof element === "string" ? element : element.id, element);
|
|
||||||
return acc;
|
|
||||||
}, new Map());
|
|
||||||
};
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
import { getContainingFrame } from "../frame";
|
import { getContainingFrame } from "../frame";
|
||||||
import { ShapeCache } from "../scene/ShapeCache";
|
import { ShapeCache } from "../scene/ShapeCache";
|
||||||
import { getVerticalOffset } from "../fonts";
|
import { getVerticalOffset } from "../fonts";
|
||||||
import { GlobalPoint, isRightAngleRads } from "../../math";
|
import { isRightAngleRads } from "../../math";
|
||||||
import { getCornerRadius } from "../shapes";
|
import { getCornerRadius } from "../shapes";
|
||||||
import { getUncroppedImageElement } from "../element/cropElement";
|
import { getUncroppedImageElement } from "../element/cropElement";
|
||||||
import { getLineHeightInPx } from "../element/textMeasurements";
|
import { getLineHeightInPx } from "../element/textMeasurements";
|
||||||
|
|
|
@ -42,7 +42,7 @@ import type { ContextMenuItems } from "./components/ContextMenu";
|
||||||
import type { SnapLine } from "./snapping";
|
import type { SnapLine } from "./snapping";
|
||||||
import type { Merge, MaybePromise, ValueOf, MakeBrand } from "./utility-types";
|
import type { Merge, MaybePromise, ValueOf, MakeBrand } from "./utility-types";
|
||||||
import type { StoreActionType } from "./store";
|
import type { StoreActionType } from "./store";
|
||||||
import { GlobalPoint } from "../math";
|
import type { GlobalPoint } from "../math";
|
||||||
|
|
||||||
export type SocketId = string & { _brand: "SocketId" };
|
export type SocketId = string & { _brand: "SocketId" };
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue