Compare commits
10 Commits
master
...
arrow-boun
Author | SHA1 | Date | |
---|---|---|---|
![]() |
46c6916e1d | ||
![]() |
bec662eaf6 | ||
![]() |
068edfc1cd | ||
![]() |
4672bb948c | ||
![]() |
49fbad32ac | ||
![]() |
24858c9ace | ||
![]() |
fdbf316cc1 | ||
![]() |
eec3f67d72 | ||
![]() |
0cbf244bd8 | ||
![]() |
795511ee6b |
@ -233,7 +233,7 @@ import {
|
||||
findShapeByKey,
|
||||
getBoundTextShape,
|
||||
getCornerRadius,
|
||||
getElementShape,
|
||||
getElementShapes,
|
||||
isPathALoop,
|
||||
} from "../shapes";
|
||||
import { getSelectionBoxShape } from "../../utils/geometry/shape";
|
||||
@ -5009,7 +5009,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
x,
|
||||
y,
|
||||
element: elementWithHighestZIndex,
|
||||
shape: getElementShape(
|
||||
shapes: getElementShapes(
|
||||
elementWithHighestZIndex,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
),
|
||||
@ -5121,7 +5121,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
x,
|
||||
y,
|
||||
element,
|
||||
shape: getElementShape(element, this.scene.getNonDeletedElementsMap()),
|
||||
shapes: getElementShapes(element, this.scene.getNonDeletedElementsMap()),
|
||||
threshold: this.getElementHitThreshold(),
|
||||
frameNameBound: isFrameLikeElement(element)
|
||||
? this.frameNameBoundsCache.get(element)
|
||||
@ -5153,7 +5153,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
x,
|
||||
y,
|
||||
element: elements[index],
|
||||
shape: getElementShape(
|
||||
shapes: getElementShapes(
|
||||
elements[index],
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
),
|
||||
@ -5437,7 +5437,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
x: sceneX,
|
||||
y: sceneY,
|
||||
element: container,
|
||||
shape: getElementShape(
|
||||
shapes: getElementShapes(
|
||||
container,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
),
|
||||
@ -6211,7 +6211,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
x: scenePointerX,
|
||||
y: scenePointerY,
|
||||
element,
|
||||
shape: getElementShape(
|
||||
shapes: getElementShapes(
|
||||
element,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
),
|
||||
@ -9344,7 +9344,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
x: pointerDownState.origin.x,
|
||||
y: pointerDownState.origin.y,
|
||||
element: hitElement,
|
||||
shape: getElementShape(
|
||||
shapes: getElementShapes(
|
||||
hitElement,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
),
|
||||
|
@ -52,7 +52,7 @@ import { LinearElementEditor } from "./linearElementEditor";
|
||||
import { arrayToMap, tupleToCoors } from "../utils";
|
||||
import { KEYS } from "../keys";
|
||||
import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
||||
import { aabbForElement, getElementShape, pointInsideBounds } from "../shapes";
|
||||
import { aabbForElement, getElementShapes, pointInsideBounds } from "../shapes";
|
||||
import {
|
||||
compareHeading,
|
||||
HEADING_DOWN,
|
||||
@ -1406,9 +1406,9 @@ export const bindingBorderTest = (
|
||||
): boolean => {
|
||||
const threshold = maxBindingGap(element, element.width, element.height, zoom);
|
||||
|
||||
const shape = getElementShape(element, elementsMap);
|
||||
const shapes = getElementShapes(element, elementsMap);
|
||||
return (
|
||||
isPointOnShape(pointFrom(x, y), shape, threshold) ||
|
||||
shapes.some((shape) => isPointOnShape(pointFrom(x, y), shape, threshold)) ||
|
||||
(fullShape === true &&
|
||||
pointInsideBounds(pointFrom(x, y), aabbForElement(element)))
|
||||
);
|
||||
|
@ -8,7 +8,6 @@ import type {
|
||||
ElementsMap,
|
||||
} from "./types";
|
||||
import rough from "roughjs/bin/rough";
|
||||
import type { Point as RoughPoint } from "roughjs/bin/geometry";
|
||||
import type { Drawable, Op } from "roughjs/bin/core";
|
||||
import type { AppState } from "../types";
|
||||
import { generateRoughOptions } from "../scene/Shape";
|
||||
@ -24,13 +23,7 @@ import { getBoundTextElement, getContainerElement } from "./textElement";
|
||||
import { LinearElementEditor } from "./linearElementEditor";
|
||||
import { ShapeCache } from "../scene/ShapeCache";
|
||||
import { arrayToMap, invariant } from "../utils";
|
||||
import type {
|
||||
Degrees,
|
||||
GlobalPoint,
|
||||
LineSegment,
|
||||
LocalPoint,
|
||||
Radians,
|
||||
} from "../../math";
|
||||
import type { Degrees, GlobalPoint, LineSegment, Radians } from "../../math";
|
||||
import {
|
||||
degreesToRadians,
|
||||
lineSegment,
|
||||
@ -39,7 +32,6 @@ import {
|
||||
pointFromArray,
|
||||
pointRotateRads,
|
||||
} from "../../math";
|
||||
import type { Mutable } from "../utility-types";
|
||||
|
||||
export type RectangleBox = {
|
||||
x: number;
|
||||
@ -732,36 +724,12 @@ export const getArrowheadPoints = (
|
||||
return [x2, y2, x3, y3, x4, y4];
|
||||
};
|
||||
|
||||
const generateLinearElementShape = (
|
||||
element: ExcalidrawLinearElement,
|
||||
): Drawable => {
|
||||
const generator = rough.generator();
|
||||
const options = generateRoughOptions(element);
|
||||
|
||||
const method = (() => {
|
||||
if (element.roundness) {
|
||||
return "curve";
|
||||
}
|
||||
if (options.fill) {
|
||||
return "polygon";
|
||||
}
|
||||
return "linearPath";
|
||||
})();
|
||||
|
||||
return generator[method](
|
||||
element.points as Mutable<LocalPoint>[] as RoughPoint[],
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
const getLinearElementRotatedBounds = (
|
||||
element: ExcalidrawLinearElement,
|
||||
cx: number,
|
||||
cy: number,
|
||||
elementsMap: ElementsMap,
|
||||
): Bounds => {
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
|
||||
if (element.points.length < 2) {
|
||||
const [pointX, pointY] = element.points[0];
|
||||
const [x, y] = pointRotateRads(
|
||||
@ -771,6 +739,7 @@ const getLinearElementRotatedBounds = (
|
||||
);
|
||||
|
||||
let coords: Bounds = [x, y, x, y];
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (boundTextElement) {
|
||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||
element,
|
||||
@ -788,18 +757,48 @@ const getLinearElementRotatedBounds = (
|
||||
return coords;
|
||||
}
|
||||
|
||||
// first element is always the curve
|
||||
const cachedShape = ShapeCache.get(element)?.[0];
|
||||
const shape = cachedShape ?? generateLinearElementShape(element);
|
||||
const ops = getCurvePathOps(shape);
|
||||
const cachedShape =
|
||||
ShapeCache.get(element) ?? ShapeCache.generateElementShape(element, null);
|
||||
|
||||
const [arrowCurve, ...arrowhead] = cachedShape;
|
||||
|
||||
const transformXY = ([x, y]: GlobalPoint) =>
|
||||
pointRotateRads<GlobalPoint>(
|
||||
pointFrom(element.x + x, element.y + y),
|
||||
pointFrom(cx, cy),
|
||||
element.angle,
|
||||
);
|
||||
const res = getMinMaxXYFromCurvePathOps(ops, transformXY);
|
||||
let coords: Bounds = [res[0], res[1], res[2], res[3]];
|
||||
|
||||
let coords = getMinMaxXYFromCurvePathOps(
|
||||
getCurvePathOps(arrowCurve),
|
||||
transformXY,
|
||||
);
|
||||
|
||||
for (const shape of arrowhead) {
|
||||
let [minX, minY, maxX, maxY] = getMinMaxXYFromCurvePathOps(
|
||||
getCurvePathOps(shape),
|
||||
);
|
||||
|
||||
[minX, minY] = pointRotateRads<GlobalPoint>(
|
||||
pointFrom(minX + element.x, minY + element.y),
|
||||
pointFrom(cx, cy),
|
||||
element.angle,
|
||||
);
|
||||
[maxX, maxY] = pointRotateRads<GlobalPoint>(
|
||||
pointFrom(maxX + element.x, maxY + element.y),
|
||||
pointFrom(cx, cy),
|
||||
element.angle,
|
||||
);
|
||||
|
||||
coords = [
|
||||
Math.min(minX, coords[0]),
|
||||
Math.min(minY, coords[1]),
|
||||
Math.max(maxX, coords[2]),
|
||||
Math.max(maxY, coords[3]),
|
||||
];
|
||||
}
|
||||
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (boundTextElement) {
|
||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||
element,
|
||||
@ -814,6 +813,7 @@ const getLinearElementRotatedBounds = (
|
||||
coordsWithBoundText[3],
|
||||
];
|
||||
}
|
||||
|
||||
return coords;
|
||||
};
|
||||
|
||||
|
@ -45,7 +45,7 @@ export type HitTestArgs<Point extends GlobalPoint | LocalPoint> = {
|
||||
x: number;
|
||||
y: number;
|
||||
element: ExcalidrawElement;
|
||||
shape: GeometricShape<Point>;
|
||||
shapes: GeometricShape<Point>[];
|
||||
threshold?: number;
|
||||
frameNameBound?: FrameNameBounds | null;
|
||||
};
|
||||
@ -54,16 +54,18 @@ export const hitElementItself = <Point extends GlobalPoint | LocalPoint>({
|
||||
x,
|
||||
y,
|
||||
element,
|
||||
shape,
|
||||
shapes,
|
||||
threshold = 10,
|
||||
frameNameBound = null,
|
||||
}: HitTestArgs<Point>) => {
|
||||
let hit = shouldTestInside(element)
|
||||
? // Since `inShape` tests STRICTLY againt the insides of a shape
|
||||
// we would need `onShape` as well to include the "borders"
|
||||
isPointInShape(pointFrom(x, y), shape) ||
|
||||
const testInside = shouldTestInside(element);
|
||||
|
||||
let hit = shapes.some((shape) =>
|
||||
testInside || shape.isClosed
|
||||
? isPointInShape(pointFrom(x, y), shape) ||
|
||||
isPointOnShape(pointFrom(x, y), shape, threshold)
|
||||
: isPointOnShape(pointFrom(x, y), shape, threshold);
|
||||
: isPointOnShape(pointFrom(x, y), shape, threshold),
|
||||
);
|
||||
|
||||
// hit test against a frame's name
|
||||
if (!hit && frameNameBound) {
|
||||
|
@ -1720,10 +1720,10 @@ export class LinearElementEditor {
|
||||
includeBoundText: boolean = false,
|
||||
): [number, number, number, number, number, number] => {
|
||||
let coords: [number, number, number, number, number, number];
|
||||
let x1;
|
||||
let y1;
|
||||
let x2;
|
||||
let y2;
|
||||
let x1 = Infinity;
|
||||
let y1 = Infinity;
|
||||
let x2 = -Infinity;
|
||||
let y2 = -Infinity;
|
||||
if (element.points.length < 2 || !ShapeCache.get(element)) {
|
||||
// XXX this is just a poor estimate and not very useful
|
||||
const { minX, minY, maxX, maxY } = element.points.reduce(
|
||||
@ -1745,14 +1745,15 @@ export class LinearElementEditor {
|
||||
} else {
|
||||
const shape = ShapeCache.generateElementShape(element, null);
|
||||
|
||||
// first element is always the curve
|
||||
const ops = getCurvePathOps(shape[0]);
|
||||
for (const s of shape) {
|
||||
const ops = getCurvePathOps(s);
|
||||
|
||||
const [minX, minY, maxX, maxY] = getMinMaxXYFromCurvePathOps(ops);
|
||||
x1 = minX + element.x;
|
||||
y1 = minY + element.y;
|
||||
x2 = maxX + element.x;
|
||||
y2 = maxY + element.y;
|
||||
x1 = Math.min(minX + element.x, x1);
|
||||
y1 = Math.min(minY + element.y, y1);
|
||||
x2 = Math.max(maxX + element.x, x2);
|
||||
y2 = Math.max(maxY + element.y, y2);
|
||||
}
|
||||
}
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { pointsOnBezierCurves } from "points-on-curve";
|
||||
import {
|
||||
isPoint,
|
||||
pointFrom,
|
||||
@ -7,6 +8,7 @@ import {
|
||||
pointsEqual,
|
||||
type GlobalPoint,
|
||||
type LocalPoint,
|
||||
polygonFromPoints,
|
||||
} from "../math";
|
||||
import {
|
||||
getClosedCurveShape,
|
||||
@ -14,6 +16,7 @@ import {
|
||||
getCurveShape,
|
||||
getEllipseShape,
|
||||
getFreedrawShape,
|
||||
getPointsOnRoughCurve,
|
||||
getPolygonShape,
|
||||
type GeometricShape,
|
||||
} from "../utils/geometry/shape";
|
||||
@ -141,10 +144,10 @@ export const findShapeByKey = (key: string) => {
|
||||
* get the pure geometric shape of an excalidraw element
|
||||
* which is then used for hit detection
|
||||
*/
|
||||
export const getElementShape = <Point extends GlobalPoint | LocalPoint>(
|
||||
export const getElementShapes = <Point extends GlobalPoint | LocalPoint>(
|
||||
element: ExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
): GeometricShape<Point> => {
|
||||
): GeometricShape<Point>[] => {
|
||||
switch (element.type) {
|
||||
case "rectangle":
|
||||
case "diamond":
|
||||
@ -155,40 +158,102 @@ export const getElementShape = <Point extends GlobalPoint | LocalPoint>(
|
||||
case "iframe":
|
||||
case "text":
|
||||
case "selection":
|
||||
return getPolygonShape(element);
|
||||
return [getPolygonShape(element)];
|
||||
case "arrow":
|
||||
case "line": {
|
||||
const roughShape =
|
||||
ShapeCache.get(element)?.[0] ??
|
||||
ShapeCache.generateElementShape(element, null)[0];
|
||||
const [curve, ...arrowheads] =
|
||||
ShapeCache.get(element) ??
|
||||
ShapeCache.generateElementShape(element, null);
|
||||
const [, , , , cx, cy] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const center = pointFrom<Point>(cx, cy);
|
||||
const startingPoint = pointFrom<Point>(element.x, element.y);
|
||||
|
||||
return shouldTestInside(element)
|
||||
? getClosedCurveShape<Point>(
|
||||
if (shouldTestInside(element)) {
|
||||
return [
|
||||
getClosedCurveShape<Point>(
|
||||
element,
|
||||
roughShape,
|
||||
pointFrom<Point>(element.x, element.y),
|
||||
curve,
|
||||
startingPoint,
|
||||
element.angle,
|
||||
pointFrom(cx, cy),
|
||||
)
|
||||
: getCurveShape<Point>(
|
||||
roughShape,
|
||||
pointFrom<Point>(element.x, element.y),
|
||||
center,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
// otherwise return the curve shape (and also the shape of its arrowheads)
|
||||
const arrowheadShapes: GeometricShape<Point>[] = [];
|
||||
const transform = (p: Point): Point =>
|
||||
pointRotateRads(
|
||||
pointFrom(p[0] + startingPoint[0], p[1] + startingPoint[1]),
|
||||
center,
|
||||
element.angle,
|
||||
pointFrom(cx, cy),
|
||||
);
|
||||
|
||||
for (const arrowhead of arrowheads) {
|
||||
if (arrowhead.shape === "polygon") {
|
||||
const ops = arrowhead.sets[0].ops;
|
||||
|
||||
const otherPoints = ops.slice(1);
|
||||
const arrowheadShape: GeometricShape<Point> = {
|
||||
type: "polygon",
|
||||
data: polygonFromPoints(
|
||||
otherPoints.map((otherPoint) =>
|
||||
transform(
|
||||
pointFrom<Point>(otherPoint.data[0], otherPoint.data[1]),
|
||||
),
|
||||
),
|
||||
),
|
||||
isClosed: true,
|
||||
};
|
||||
|
||||
arrowheadShapes.push(arrowheadShape);
|
||||
}
|
||||
|
||||
if (arrowhead.shape === "circle") {
|
||||
const polygonPoints = pointsOnBezierCurves(
|
||||
getPointsOnRoughCurve(arrowhead),
|
||||
15,
|
||||
2,
|
||||
).map((p) => transform(p as Point)) as Point[];
|
||||
|
||||
arrowheadShapes.push({
|
||||
type: "polygon",
|
||||
data: polygonFromPoints(polygonPoints),
|
||||
isClosed: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (arrowhead.shape === "line") {
|
||||
arrowheadShapes.push(
|
||||
getCurveShape<Point>(
|
||||
arrowhead,
|
||||
element.angle,
|
||||
center,
|
||||
startingPoint,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
getCurveShape<Point>(
|
||||
curve,
|
||||
element.angle,
|
||||
pointFrom(cx, cy),
|
||||
startingPoint,
|
||||
),
|
||||
...arrowheadShapes,
|
||||
];
|
||||
}
|
||||
|
||||
case "ellipse":
|
||||
return getEllipseShape(element);
|
||||
return [getEllipseShape(element)];
|
||||
|
||||
case "freedraw": {
|
||||
const [, , , , cx, cy] = getElementAbsoluteCoords(element, elementsMap);
|
||||
return getFreedrawShape(
|
||||
element,
|
||||
pointFrom(cx, cy),
|
||||
shouldTestInside(element),
|
||||
);
|
||||
return [
|
||||
getFreedrawShape(element, pointFrom(cx, cy), shouldTestInside(element)),
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -201,7 +266,8 @@ export const getBoundTextShape = <Point extends GlobalPoint | LocalPoint>(
|
||||
|
||||
if (boundTextElement) {
|
||||
if (element.type === "arrow") {
|
||||
return getElementShape(
|
||||
return (
|
||||
getElementShapes<Point>(
|
||||
{
|
||||
...boundTextElement,
|
||||
// arrow's bound text accurate position is not stored in the element's property
|
||||
@ -213,9 +279,10 @@ export const getBoundTextShape = <Point extends GlobalPoint | LocalPoint>(
|
||||
),
|
||||
},
|
||||
elementsMap,
|
||||
)[0] ?? null
|
||||
);
|
||||
}
|
||||
return getElementShape(boundTextElement, elementsMap);
|
||||
return getElementShapes<Point>(boundTextElement, elementsMap)[0] ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -418,19 +418,12 @@ describe("element binding", () => {
|
||||
expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
|
||||
expect(arrow.endBinding?.elementId).toBe(rectRight.id);
|
||||
|
||||
// Drag arrow off of bound rectangle range
|
||||
const handles = getTransformHandles(
|
||||
arrow,
|
||||
h.state.zoom,
|
||||
arrayToMap(h.elements),
|
||||
"mouse",
|
||||
).se!;
|
||||
|
||||
Keyboard.keyDown(KEYS.CTRL_OR_CMD);
|
||||
const elX = handles[0] + handles[2] / 2;
|
||||
const elY = handles[1] + handles[3] / 2;
|
||||
mouse.downAt(elX, elY);
|
||||
mouse.moveTo(300, 400);
|
||||
mouse.downAt(
|
||||
arrow.x + arrow.points[arrow.points.length - 1][0],
|
||||
arrow.y + arrow.points[arrow.points.length - 1][1],
|
||||
);
|
||||
mouse.moveTo(300, 300);
|
||||
mouse.up();
|
||||
|
||||
expect(arrow.startBinding).not.toBe(null);
|
||||
|
@ -198,8 +198,8 @@ const checkElementsBoundingBox = async (
|
||||
|
||||
await waitFor(() => {
|
||||
// Check if width and height did not change
|
||||
expect(x2 - x1).toBeCloseTo(x22 - x12, -1);
|
||||
expect(y2 - y1).toBeCloseTo(y22 - y12, -1);
|
||||
expect(Math.abs(x2 - x1 - (x22 - x12))).toBeLessThanOrEqual(toleranceInPx);
|
||||
expect(Math.abs(y2 - y1 - (y22 - y12))).toBeLessThanOrEqual(toleranceInPx);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -535,7 +535,7 @@ describe("arrow element", () => {
|
||||
|
||||
UI.resize([rectangle, arrow], "nw", [300, 350]);
|
||||
|
||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(-0.144, 2);
|
||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(-0.168, 2);
|
||||
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.25);
|
||||
});
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ export type Ellipse<Point extends GlobalPoint | LocalPoint> = {
|
||||
halfHeight: number;
|
||||
};
|
||||
|
||||
export type GeometricShape<Point extends GlobalPoint | LocalPoint> =
|
||||
export type GeometricShape<Point extends GlobalPoint | LocalPoint> = (
|
||||
| {
|
||||
type: "line";
|
||||
data: LineSegment<Point>;
|
||||
@ -97,7 +97,10 @@ export type GeometricShape<Point extends GlobalPoint | LocalPoint> =
|
||||
| {
|
||||
type: "polycurve";
|
||||
data: Polycurve<Point>;
|
||||
};
|
||||
}
|
||||
) & {
|
||||
isClosed?: boolean;
|
||||
};
|
||||
|
||||
type RectangularElement =
|
||||
| ExcalidrawRectangleElement
|
||||
@ -203,9 +206,9 @@ export const getCurvePathOps = (shape: Drawable): Op[] => {
|
||||
// linear
|
||||
export const getCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
||||
roughShape: Drawable,
|
||||
startingPoint: Point = pointFrom(0, 0),
|
||||
angleInRadian: Radians,
|
||||
center: Point,
|
||||
startingPoint: Point = pointFrom(0, 0),
|
||||
): GeometricShape<Point> => {
|
||||
const transform = (p: Point): Point =>
|
||||
pointRotateRads(
|
||||
@ -285,6 +288,35 @@ export const getFreedrawShape = <Point extends GlobalPoint | LocalPoint>(
|
||||
) as GeometricShape<Point>;
|
||||
};
|
||||
|
||||
export const getPointsOnRoughCurve = <Point extends GlobalPoint | LocalPoint>(
|
||||
roughCurve: Drawable,
|
||||
) => {
|
||||
const ops = getCurvePathOps(roughCurve);
|
||||
|
||||
const points: Point[] = [];
|
||||
let odd = false;
|
||||
for (const operation of ops) {
|
||||
if (operation.op === "move") {
|
||||
odd = !odd;
|
||||
if (odd) {
|
||||
points.push(pointFrom(operation.data[0], operation.data[1]));
|
||||
}
|
||||
} else if (operation.op === "bcurveTo") {
|
||||
if (odd) {
|
||||
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 (odd) {
|
||||
points.push(pointFrom(operation.data[0], operation.data[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return points;
|
||||
};
|
||||
|
||||
export const getClosedCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
||||
element: ExcalidrawLinearElement,
|
||||
roughShape: Drawable,
|
||||
@ -308,31 +340,10 @@ export const getClosedCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
||||
};
|
||||
}
|
||||
|
||||
const ops = getCurvePathOps(roughShape);
|
||||
|
||||
const points: Point[] = [];
|
||||
let odd = false;
|
||||
for (const operation of ops) {
|
||||
if (operation.op === "move") {
|
||||
odd = !odd;
|
||||
if (odd) {
|
||||
points.push(pointFrom(operation.data[0], operation.data[1]));
|
||||
}
|
||||
} else if (operation.op === "bcurveTo") {
|
||||
if (odd) {
|
||||
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 (odd) {
|
||||
points.push(pointFrom(operation.data[0], operation.data[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const polygonPoints = pointsOnBezierCurves(points, 10, 5).map((p) =>
|
||||
transform(p as Point),
|
||||
const polygonPoints = pointsOnBezierCurves(
|
||||
getPointsOnRoughCurve(roughShape),
|
||||
10,
|
||||
5,
|
||||
) as Point[];
|
||||
|
||||
return {
|
||||
|
Loading…
x
Reference in New Issue
Block a user