Refactor
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
parent
cd3ca3b4ca
commit
3b1c6444e2
@ -1,12 +1,6 @@
|
|||||||
import { simplify } from "points-on-curve";
|
import { simplify } from "points-on-curve";
|
||||||
|
|
||||||
import {
|
import { pointFrom, pointDistance, type LocalPoint } from "@excalidraw/math";
|
||||||
pointFrom,
|
|
||||||
pointDistance,
|
|
||||||
type LocalPoint,
|
|
||||||
curve,
|
|
||||||
pointFromArray,
|
|
||||||
} from "@excalidraw/math";
|
|
||||||
import { ROUGHNESS, isTransparent, assertNever } from "@excalidraw/common";
|
import { ROUGHNESS, isTransparent, assertNever } from "@excalidraw/common";
|
||||||
|
|
||||||
import { RoughGenerator } from "roughjs/bin/generator";
|
import { RoughGenerator } from "roughjs/bin/generator";
|
||||||
@ -36,6 +30,7 @@ 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";
|
||||||
@ -68,37 +63,6 @@ function adjustRoughness(element: ExcalidrawElement): number {
|
|||||||
return Math.min(roughness / (maxSize < 10 ? 3 : 2), 2.5);
|
return Math.min(roughness / (maxSize < 10 ? 3 : 2), 2.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generateRoughOptionsForCollision = (
|
|
||||||
element: ExcalidrawElement,
|
|
||||||
): Options => {
|
|
||||||
const options: Options = {
|
|
||||||
seed: element.seed,
|
|
||||||
disableMultiStroke: true,
|
|
||||||
roughness: 0,
|
|
||||||
preserveVertices: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (element.type) {
|
|
||||||
case "rectangle":
|
|
||||||
case "iframe":
|
|
||||||
case "embeddable":
|
|
||||||
case "diamond":
|
|
||||||
case "ellipse": {
|
|
||||||
if (element.type === "ellipse") {
|
|
||||||
options.curveFitting = 1;
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
case "line":
|
|
||||||
case "freedraw":
|
|
||||||
case "arrow":
|
|
||||||
return options;
|
|
||||||
default: {
|
|
||||||
throw new Error(`Unimplemented type ${element.type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const generateRoughOptions = (
|
export const generateRoughOptions = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
continuousPath = false,
|
continuousPath = false,
|
||||||
@ -341,50 +305,22 @@ const getArrowheadShapes = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateComponentsForCollision = (element: ExcalidrawElement) => {
|
export const generateLinearCollisionShape = (
|
||||||
const ops = generateRoughOpsForCollision(element) as {
|
element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement,
|
||||||
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 "bcurveTo":
|
|
||||||
if (!prevPoint) {
|
|
||||||
throw new Error("prevPoint is undefined");
|
|
||||||
}
|
|
||||||
|
|
||||||
components.push(
|
|
||||||
curve(
|
|
||||||
prevPoint,
|
|
||||||
pointFrom<LocalPoint>(op.data[0], op.data[1]),
|
|
||||||
pointFrom<LocalPoint>(op.data[2], op.data[3]),
|
|
||||||
pointFrom<LocalPoint>(op.data[4], op.data[5]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
default: {
|
|
||||||
console.error("Unknown op type", op.op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return components;
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateRoughOpsForCollision = (element: ExcalidrawElement) => {
|
|
||||||
const generator = new RoughGenerator();
|
const generator = new RoughGenerator();
|
||||||
|
const options: Options = {
|
||||||
|
seed: element.seed,
|
||||||
|
disableMultiStroke: true,
|
||||||
|
disableMultiStrokeFill: true,
|
||||||
|
roughness: 0,
|
||||||
|
preserveVertices: true,
|
||||||
|
};
|
||||||
|
|
||||||
switch (element.type) {
|
switch (element.type) {
|
||||||
case "line":
|
case "line":
|
||||||
case "arrow": {
|
case "arrow": {
|
||||||
let shape: any;
|
let shape: any;
|
||||||
const options = generateRoughOptions(element);
|
|
||||||
|
|
||||||
// points array can be empty in the beginning, so it is important to add
|
// points array can be empty in the beginning, so it is important to add
|
||||||
// initial position to it
|
// initial position to it
|
||||||
@ -393,42 +329,24 @@ const generateRoughOpsForCollision = (element: ExcalidrawElement) => {
|
|||||||
: [pointFrom<LocalPoint>(0, 0)];
|
: [pointFrom<LocalPoint>(0, 0)];
|
||||||
|
|
||||||
if (isElbowArrow(element)) {
|
if (isElbowArrow(element)) {
|
||||||
// NOTE (mtolmacs): Temporary fix for extremely big arrow shapes
|
shape = generator.path(generateElbowArrowShape(points, 16), options)
|
||||||
if (
|
.sets[0].ops;
|
||||||
!points.every(
|
|
||||||
(point) => Math.abs(point[0]) <= 1e6 && Math.abs(point[1]) <= 1e6,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
console.error(
|
|
||||||
`Elbow arrow with extreme point positions detected. Arrow not rendered.`,
|
|
||||||
element.id,
|
|
||||||
JSON.stringify(points),
|
|
||||||
);
|
|
||||||
shape = [];
|
|
||||||
} else {
|
|
||||||
shape = generator.path(
|
|
||||||
generateElbowArrowShape(points, 16),
|
|
||||||
generateRoughOptionsForCollision(element),
|
|
||||||
).sets[0].ops;
|
|
||||||
}
|
|
||||||
} else if (!element.roundness) {
|
} else if (!element.roundness) {
|
||||||
// curve is always the first element
|
shape = points.map((point, idx) => {
|
||||||
// this simplifies finding the curve for an element
|
return idx === 0
|
||||||
if (options.fill) {
|
? { op: "move", data: point }
|
||||||
shape = generator.polygon(points as unknown as RoughPoint[], options)
|
: {
|
||||||
.sets[0].ops;
|
op: "lineTo",
|
||||||
|
data: [point[0], point[1]],
|
||||||
|
};
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
shape = generator.linearPath(
|
shape = generator
|
||||||
points as unknown as RoughPoint[],
|
.curve(points as unknown as RoughPoint[], options)
|
||||||
options,
|
.sets[0].ops.slice(0, element.points.length);
|
||||||
).sets[0].ops;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
shape = generator.curve(points as unknown as RoughPoint[], options)
|
|
||||||
.sets[0].ops;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return shape.slice(0, element.points.length);
|
return shape;
|
||||||
}
|
}
|
||||||
case "freedraw": {
|
case "freedraw": {
|
||||||
const simplifiedPoints = simplify(
|
const simplifiedPoints = simplify(
|
||||||
@ -437,10 +355,7 @@ const generateRoughOpsForCollision = (element: ExcalidrawElement) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return generator
|
return generator
|
||||||
.curve(
|
.curve(simplifiedPoints as [number, number][], options)
|
||||||
simplifiedPoints as [number, number][],
|
|
||||||
generateRoughOptionsForCollision(element),
|
|
||||||
)
|
|
||||||
.sets[0].ops.slice(0, element.points.length);
|
.sets[0].ops.slice(0, element.points.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@ import {
|
|||||||
arrayToMap,
|
arrayToMap,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
import {
|
import {
|
||||||
curve,
|
|
||||||
curveIntersectLineSegment,
|
curveIntersectLineSegment,
|
||||||
isCurve,
|
isCurve,
|
||||||
|
isLineSegment,
|
||||||
isPointWithinBounds,
|
isPointWithinBounds,
|
||||||
lineSegment,
|
lineSegment,
|
||||||
lineSegmentIntersectionPoints,
|
lineSegmentIntersectionPoints,
|
||||||
@ -22,7 +22,12 @@ import {
|
|||||||
|
|
||||||
import { isPointInShape, isPointOnShape } from "@excalidraw/utils/collision";
|
import { isPointInShape, isPointOnShape } from "@excalidraw/utils/collision";
|
||||||
|
|
||||||
import type { GlobalPoint, LineSegment, Radians } from "@excalidraw/math";
|
import type {
|
||||||
|
Curve,
|
||||||
|
GlobalPoint,
|
||||||
|
LineSegment,
|
||||||
|
Radians,
|
||||||
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type { FrameNameBounds } from "@excalidraw/excalidraw/types";
|
import type { FrameNameBounds } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
@ -37,6 +42,7 @@ import {
|
|||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
import {
|
import {
|
||||||
deconstructDiamondElement,
|
deconstructDiamondElement,
|
||||||
|
deconstructLinearOrFreeDrawElement,
|
||||||
deconstructRectanguloidElement,
|
deconstructRectanguloidElement,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
|
||||||
@ -44,18 +50,16 @@ import { getBoundTextElement } from "./textElement";
|
|||||||
|
|
||||||
import { LinearElementEditor } from "./linearElementEditor";
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
|
|
||||||
import { generateComponentsForCollision } from "./Shape";
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
ExcalidrawDiamondElement,
|
ExcalidrawDiamondElement,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawEllipseElement,
|
ExcalidrawEllipseElement,
|
||||||
|
ExcalidrawFreeDrawElement,
|
||||||
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawRectanguloidElement,
|
ExcalidrawRectanguloidElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
import { debugDrawCubicBezier } from "@excalidraw/excalidraw/visualdebug";
|
|
||||||
|
|
||||||
export const shouldTestInside = (element: ExcalidrawElement) => {
|
export const shouldTestInside = (element: ExcalidrawElement) => {
|
||||||
if (element.type === "arrow") {
|
if (element.type === "arrow") {
|
||||||
return false;
|
return false;
|
||||||
@ -107,21 +111,6 @@ export const hitElementItself = ({
|
|||||||
: isPointOnShape(point, element, threshold)
|
: isPointOnShape(point, element, threshold)
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
element.type === "freedraw" &&
|
|
||||||
generateComponentsForCollision(element).forEach((c) => {
|
|
||||||
if (isCurve(c)) {
|
|
||||||
debugDrawCubicBezier(
|
|
||||||
curve(
|
|
||||||
pointFrom<GlobalPoint>(element.x + c[0][0], element.y + c[0][1]),
|
|
||||||
pointFrom<GlobalPoint>(element.x + c[1][0], element.y + c[1][1]),
|
|
||||||
pointFrom<GlobalPoint>(element.x + c[2][0], element.y + c[2][1]),
|
|
||||||
pointFrom<GlobalPoint>(element.x + c[3][0], element.y + c[3][1]),
|
|
||||||
),
|
|
||||||
{ color: "red" },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// hit test against a frame's name
|
// hit test against a frame's name
|
||||||
if (!hit && frameNameBound) {
|
if (!hit && frameNameBound) {
|
||||||
const x1 = frameNameBound.x - threshold;
|
const x1 = frameNameBound.x - threshold;
|
||||||
@ -216,11 +205,41 @@ export const intersectElementWithLineSegment = (
|
|||||||
case "line":
|
case "line":
|
||||||
case "freedraw":
|
case "freedraw":
|
||||||
case "arrow":
|
case "arrow":
|
||||||
return [];
|
return intersectLinearOrFreeDrawWithLineSegment(element, line);
|
||||||
//throw new Error(`Unimplemented element type '${element.type}'`);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const intersectLinearOrFreeDrawWithLineSegment = (
|
||||||
|
element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement,
|
||||||
|
segment: LineSegment<GlobalPoint>,
|
||||||
|
): GlobalPoint[] => {
|
||||||
|
const shapes = deconstructLinearOrFreeDrawElement(element);
|
||||||
|
const intersections: GlobalPoint[] = [];
|
||||||
|
|
||||||
|
for (const shape of shapes) {
|
||||||
|
switch (true) {
|
||||||
|
case isCurve(shape):
|
||||||
|
intersections.push(
|
||||||
|
...curveIntersectLineSegment(shape as Curve<GlobalPoint>, segment),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
case isLineSegment(shape):
|
||||||
|
const point = lineSegmentIntersectionPoints(
|
||||||
|
segment,
|
||||||
|
shape as LineSegment<GlobalPoint>,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (point) {
|
||||||
|
intersections.push(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intersections;
|
||||||
|
};
|
||||||
|
|
||||||
const intersectRectanguloidWithLineSegment = (
|
const intersectRectanguloidWithLineSegment = (
|
||||||
element: ExcalidrawRectanguloidElement,
|
element: ExcalidrawRectanguloidElement,
|
||||||
l: LineSegment<GlobalPoint>,
|
l: LineSegment<GlobalPoint>,
|
||||||
|
@ -2,6 +2,7 @@ import {
|
|||||||
curve,
|
curve,
|
||||||
lineSegment,
|
lineSegment,
|
||||||
pointFrom,
|
pointFrom,
|
||||||
|
pointFromArray,
|
||||||
pointFromVector,
|
pointFromVector,
|
||||||
rectangle,
|
rectangle,
|
||||||
vectorFromPoint,
|
vectorFromPoint,
|
||||||
@ -12,17 +13,90 @@ import {
|
|||||||
|
|
||||||
import { elementCenterPoint } from "@excalidraw/common";
|
import { elementCenterPoint } from "@excalidraw/common";
|
||||||
|
|
||||||
import type { Curve, LineSegment } from "@excalidraw/math";
|
import type { Curve, LineSegment, LocalPoint } from "@excalidraw/math";
|
||||||
|
|
||||||
import { getCornerRadius } from "./shapes";
|
import { getCornerRadius } from "./shapes";
|
||||||
|
|
||||||
import { getDiamondPoints } from "./bounds";
|
import { getDiamondPoints } from "./bounds";
|
||||||
|
|
||||||
|
import { generateLinearCollisionShape } from "./Shape";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawDiamondElement,
|
ExcalidrawDiamondElement,
|
||||||
|
ExcalidrawFreeDrawElement,
|
||||||
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawRectanguloidElement,
|
ExcalidrawRectanguloidElement,
|
||||||
} from "./types";
|
} 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 components;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the building components of a rectanguloid element in the form of
|
* Get the building components of a rectanguloid element in the form of
|
||||||
* line segments and curves.
|
* line segments and curves.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user