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 {
|
||||
pointFrom,
|
||||
pointDistance,
|
||||
type LocalPoint,
|
||||
curve,
|
||||
pointFromArray,
|
||||
} from "@excalidraw/math";
|
||||
import { pointFrom, pointDistance, type LocalPoint } from "@excalidraw/math";
|
||||
import { ROUGHNESS, isTransparent, assertNever } from "@excalidraw/common";
|
||||
|
||||
import { RoughGenerator } from "roughjs/bin/generator";
|
||||
@ -36,6 +30,7 @@ import type {
|
||||
ExcalidrawSelectionElement,
|
||||
ExcalidrawLinearElement,
|
||||
Arrowhead,
|
||||
ExcalidrawFreeDrawElement,
|
||||
} from "./types";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 = (
|
||||
element: ExcalidrawElement,
|
||||
continuousPath = false,
|
||||
@ -341,50 +305,22 @@ const getArrowheadShapes = (
|
||||
}
|
||||
};
|
||||
|
||||
export const generateComponentsForCollision = (element: ExcalidrawElement) => {
|
||||
const ops = generateRoughOpsForCollision(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 "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) => {
|
||||
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": {
|
||||
let shape: any;
|
||||
const options = generateRoughOptions(element);
|
||||
|
||||
// points array can be empty in the beginning, so it is important to add
|
||||
// initial position to it
|
||||
@ -393,42 +329,24 @@ const generateRoughOpsForCollision = (element: ExcalidrawElement) => {
|
||||
: [pointFrom<LocalPoint>(0, 0)];
|
||||
|
||||
if (isElbowArrow(element)) {
|
||||
// NOTE (mtolmacs): Temporary fix for extremely big arrow shapes
|
||||
if (
|
||||
!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) {
|
||||
// curve is always the first element
|
||||
// this simplifies finding the curve for an element
|
||||
if (options.fill) {
|
||||
shape = generator.polygon(points as unknown as RoughPoint[], options)
|
||||
.sets[0].ops;
|
||||
} else {
|
||||
shape = generator.linearPath(
|
||||
points as unknown as RoughPoint[],
|
||||
options,
|
||||
).sets[0].ops;
|
||||
}
|
||||
} else {
|
||||
shape = generator.curve(points as unknown as RoughPoint[], options)
|
||||
shape = generator.path(generateElbowArrowShape(points, 16), options)
|
||||
.sets[0].ops;
|
||||
} else if (!element.roundness) {
|
||||
shape = points.map((point, idx) => {
|
||||
return idx === 0
|
||||
? { op: "move", data: point }
|
||||
: {
|
||||
op: "lineTo",
|
||||
data: [point[0], point[1]],
|
||||
};
|
||||
});
|
||||
} else {
|
||||
shape = generator
|
||||
.curve(points as unknown as RoughPoint[], options)
|
||||
.sets[0].ops.slice(0, element.points.length);
|
||||
}
|
||||
|
||||
return shape.slice(0, element.points.length);
|
||||
return shape;
|
||||
}
|
||||
case "freedraw": {
|
||||
const simplifiedPoints = simplify(
|
||||
@ -437,10 +355,7 @@ const generateRoughOpsForCollision = (element: ExcalidrawElement) => {
|
||||
);
|
||||
|
||||
return generator
|
||||
.curve(
|
||||
simplifiedPoints as [number, number][],
|
||||
generateRoughOptionsForCollision(element),
|
||||
)
|
||||
.curve(simplifiedPoints as [number, number][], options)
|
||||
.sets[0].ops.slice(0, element.points.length);
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import {
|
||||
arrayToMap,
|
||||
} from "@excalidraw/common";
|
||||
import {
|
||||
curve,
|
||||
curveIntersectLineSegment,
|
||||
isCurve,
|
||||
isLineSegment,
|
||||
isPointWithinBounds,
|
||||
lineSegment,
|
||||
lineSegmentIntersectionPoints,
|
||||
@ -22,7 +22,12 @@ import {
|
||||
|
||||
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";
|
||||
|
||||
@ -37,6 +42,7 @@ import {
|
||||
} from "./typeChecks";
|
||||
import {
|
||||
deconstructDiamondElement,
|
||||
deconstructLinearOrFreeDrawElement,
|
||||
deconstructRectanguloidElement,
|
||||
} from "./utils";
|
||||
|
||||
@ -44,18 +50,16 @@ import { getBoundTextElement } from "./textElement";
|
||||
|
||||
import { LinearElementEditor } from "./linearElementEditor";
|
||||
|
||||
import { generateComponentsForCollision } from "./Shape";
|
||||
|
||||
import type {
|
||||
ElementsMap,
|
||||
ExcalidrawDiamondElement,
|
||||
ExcalidrawElement,
|
||||
ExcalidrawEllipseElement,
|
||||
ExcalidrawFreeDrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawRectanguloidElement,
|
||||
} from "./types";
|
||||
|
||||
import { debugDrawCubicBezier } from "@excalidraw/excalidraw/visualdebug";
|
||||
|
||||
export const shouldTestInside = (element: ExcalidrawElement) => {
|
||||
if (element.type === "arrow") {
|
||||
return false;
|
||||
@ -107,21 +111,6 @@ export const hitElementItself = ({
|
||||
: isPointOnShape(point, element, threshold)
|
||||
: 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
|
||||
if (!hit && frameNameBound) {
|
||||
const x1 = frameNameBound.x - threshold;
|
||||
@ -216,11 +205,41 @@ export const intersectElementWithLineSegment = (
|
||||
case "line":
|
||||
case "freedraw":
|
||||
case "arrow":
|
||||
return [];
|
||||
//throw new Error(`Unimplemented element type '${element.type}'`);
|
||||
return intersectLinearOrFreeDrawWithLineSegment(element, line);
|
||||
}
|
||||
};
|
||||
|
||||
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 = (
|
||||
element: ExcalidrawRectanguloidElement,
|
||||
l: LineSegment<GlobalPoint>,
|
||||
|
@ -2,6 +2,7 @@ import {
|
||||
curve,
|
||||
lineSegment,
|
||||
pointFrom,
|
||||
pointFromArray,
|
||||
pointFromVector,
|
||||
rectangle,
|
||||
vectorFromPoint,
|
||||
@ -12,17 +13,90 @@ import {
|
||||
|
||||
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 { getDiamondPoints } from "./bounds";
|
||||
|
||||
import { generateLinearCollisionShape } from "./Shape";
|
||||
|
||||
import type {
|
||||
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 components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the building components of a rectanguloid element in the form of
|
||||
* line segments and curves.
|
||||
|
Loading…
x
Reference in New Issue
Block a user