Unify common code in distance and intersection into a helper file

feat/remove-ga
Mark Tolmacs 2 weeks ago
parent f5caf0b4b9
commit ab6d3a454c
No known key found for this signature in database

@ -6,7 +6,7 @@ import type {
ExcalidrawRectangleElement, ExcalidrawRectangleElement,
ExcalidrawRectanguloidElement, ExcalidrawRectanguloidElement,
} from "./types"; } from "./types";
import { getDiamondPoints, getElementBounds } from "./bounds"; import { getElementBounds } from "./bounds";
import type { FrameNameBounds } from "../types"; import type { FrameNameBounds } from "../types";
import type { GeometricShape } from "../../utils/geometry/shape"; import type { GeometricShape } from "../../utils/geometry/shape";
import { getPolygonShape } from "../../utils/geometry/shape"; import { getPolygonShape } from "../../utils/geometry/shape";
@ -18,7 +18,7 @@ import {
isImageElement, isImageElement,
isTextElement, isTextElement,
} from "./typeChecks"; } from "./typeChecks";
import { getBoundTextShape, getCornerRadius, isPathALoop } from "../shapes"; import { getBoundTextShape, isPathALoop } from "../shapes";
import type { import type {
GlobalPoint, GlobalPoint,
LineSegment, LineSegment,
@ -27,7 +27,6 @@ import type {
Radians, Radians,
} from "../../math"; } from "../../math";
import { import {
curve,
curveIntersectLineSegment, curveIntersectLineSegment,
isPointWithinBounds, isPointWithinBounds,
line, line,
@ -36,9 +35,12 @@ import {
pointFrom, pointFrom,
pointRotateRads, pointRotateRads,
pointsEqual, pointsEqual,
rectangle,
} from "../../math"; } from "../../math";
import { ellipse, ellipseLineIntersectionPoints } from "../../math/ellipse"; import { ellipse, ellipseLineIntersectionPoints } from "../../math/ellipse";
import {
deconstructDiamondElement,
deconstructRectanguloidElement,
} from "./utils";
export const shouldTestInside = (element: ExcalidrawElement) => { export const shouldTestInside = (element: ExcalidrawElement) => {
if (element.type === "arrow") { if (element.type === "arrow") {
@ -179,13 +181,6 @@ const intersectRectanguloidWithLineSegment = (
l: LineSegment<GlobalPoint>, l: LineSegment<GlobalPoint>,
offset: number, offset: number,
): GlobalPoint[] => { ): GlobalPoint[] => {
const r = rectangle(
pointFrom(element.x - offset, element.y - offset),
pointFrom(
element.x + element.width + offset,
element.y + element.height + offset,
),
);
const center = pointFrom<GlobalPoint>( const center = pointFrom<GlobalPoint>(
element.x + element.width / 2, element.x + element.width / 2,
element.y + element.height / 2, element.y + element.height / 2,
@ -202,83 +197,18 @@ const intersectRectanguloidWithLineSegment = (
center, center,
-element.angle as Radians, -element.angle as Radians,
); );
const roundness = getCornerRadius(
Math.min(element.width, element.height),
element,
);
const top = lineSegment<GlobalPoint>( // Get the element's building components we can test against
pointFrom<GlobalPoint>(r[0][0] + roundness, r[0][1]), const [sides, corners] = deconstructRectanguloidElement<GlobalPoint>(
pointFrom<GlobalPoint>(r[1][0] - roundness, r[0][1]), element,
); offset,
const right = lineSegment<GlobalPoint>(
pointFrom<GlobalPoint>(r[1][0], r[0][1] + roundness),
pointFrom<GlobalPoint>(r[1][0], r[1][1] - roundness),
);
const bottom = lineSegment<GlobalPoint>(
pointFrom<GlobalPoint>(r[0][0] + roundness, r[1][1]),
pointFrom<GlobalPoint>(r[1][0] - roundness, r[1][1]),
);
const left = lineSegment<GlobalPoint>(
pointFrom<GlobalPoint>(r[0][0], r[1][1] - roundness),
pointFrom<GlobalPoint>(r[0][0], r[0][1] + roundness),
); );
const sides = [top, right, bottom, left];
const corners =
roundness > 0
? [
curve(
left[1],
pointFrom(
left[1][0] + (2 / 3) * (r[0][0] - left[1][0]),
left[1][1] + (2 / 3) * (r[0][1] - left[1][1]),
),
pointFrom(
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(
top[1][0] + (2 / 3) * (r[1][0] - top[1][0]),
top[1][1] + (2 / 3) * (r[0][1] - top[1][1]),
),
pointFrom(
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(
right[1][0] + (2 / 3) * (r[1][0] - right[1][0]),
right[1][1] + (2 / 3) * (r[1][1] - right[1][1]),
),
pointFrom(
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(
bottom[0][0] + (2 / 3) * (r[0][0] - bottom[0][0]),
bottom[0][1] + (2 / 3) * (r[1][1] - bottom[0][1]),
),
pointFrom(
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 sideIntersections: GlobalPoint[] = sides return (
[
// Test intersection against the sides, keep only the valid
// intersection points and rotate them back to scene space
...sides
.map((s) => .map((s) =>
lineSegmentIntersectionPoints( lineSegmentIntersectionPoints(
lineSegment<GlobalPoint>(rotatedA, rotatedB), lineSegment<GlobalPoint>(rotatedA, rotatedB),
@ -286,23 +216,17 @@ const intersectRectanguloidWithLineSegment = (
), ),
) )
.filter((x) => x != null) .filter((x) => x != null)
.map((j) => pointRotateRads<GlobalPoint>(j!, center, element.angle)); .map((j) => pointRotateRads<GlobalPoint>(j!, center, element.angle)),
// Test intersection against the corners which are cubic bezier curves,
const cornerIntersections: GlobalPoint[] = corners // keep only the valid intersection points and rotate them back to scene
// space
...corners
.flatMap((t) => .flatMap((t) =>
curveIntersectLineSegment(t, lineSegment(rotatedA, rotatedB)), curveIntersectLineSegment(t, lineSegment(rotatedA, rotatedB)),
) )
.filter((i) => i != null) .filter((i) => i != null)
.map((j) => pointRotateRads(j, center, element.angle)); .map((j) => pointRotateRads(j, center, element.angle)),
// const cornerIntersections2: GlobalPoint[] = corners ]
// .flatMap((t) =>
// curveIntersectLineSegment2(t, lineSegment(rotatedA, rotatedB)),
// )
// .filter((i) => i != null)
// .map((j) => pointRotateRads(j, center, element.angle));
return (
[...sideIntersections, ...cornerIntersections]
// Remove duplicates // Remove duplicates
.filter( .filter(
(p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx, (p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx,
@ -322,100 +246,21 @@ const intersectDiamondWithLineSegment = (
l: LineSegment<GlobalPoint>, l: LineSegment<GlobalPoint>,
offset: number = 0, offset: number = 0,
): GlobalPoint[] => { ): GlobalPoint[] => {
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
getDiamondPoints(element);
const center = pointFrom<GlobalPoint>( const center = pointFrom<GlobalPoint>(
(topX + bottomX) / 2, element.x + element.width / 2,
(topY + bottomY) / 2, element.y + element.height / 2,
); );
const verticalRadius = getCornerRadius(Math.abs(topX - leftX), element);
const horizontalRadius = getCornerRadius(Math.abs(rightY - topY), element);
// Rotate the point to the inverse direction to simulate the rotated diamond // Rotate the point to the inverse direction to simulate the rotated diamond
// points. It's all the same distance-wise. // points. It's all the same distance-wise.
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 [top, right, bottom, left]: GlobalPoint[] = [ const [sides, curves] = deconstructDiamondElement(element, offset);
pointFrom(element.x + topX, element.y + topY - offset),
pointFrom(element.x + rightX + offset, element.y + rightY),
pointFrom(element.x + bottomX, element.y + bottomY + offset),
pointFrom(element.x + leftX - offset, element.y + leftY),
];
// Create the line segment parts of the diamond
// NOTE: Horizontal and vertical seems to be flipped here
const topRight = lineSegment<GlobalPoint>(
pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius),
pointFrom(right[0] - verticalRadius, right[1] - horizontalRadius),
);
const bottomRight = lineSegment<GlobalPoint>(
pointFrom(right[0] - verticalRadius, right[1] + horizontalRadius),
pointFrom(bottom[0] + verticalRadius, bottom[1] - horizontalRadius),
);
const bottomLeft = lineSegment<GlobalPoint>(
pointFrom(bottom[0] - verticalRadius, bottom[1] - horizontalRadius),
pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius),
);
const topLeft = lineSegment<GlobalPoint>(
pointFrom(left[0] + verticalRadius, left[1] - horizontalRadius),
pointFrom(top[0] - verticalRadius, top[1] + horizontalRadius),
);
const curves = element.roundness
? [
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 sides: GlobalPoint[] = [topRight, bottomRight, bottomLeft, topLeft] return (
[
...sides
.map((s) => .map((s) =>
lineSegmentIntersectionPoints( lineSegmentIntersectionPoints(
lineSegment<GlobalPoint>(rotatedA, rotatedB), lineSegment<GlobalPoint>(rotatedA, rotatedB),
@ -424,17 +269,15 @@ const intersectDiamondWithLineSegment = (
) )
.filter((p): p is GlobalPoint => p != null) .filter((p): p is GlobalPoint => p != null)
// Rotate back intersection points // Rotate back intersection points
.map((p) => pointRotateRads<GlobalPoint>(p!, center, element.angle)); .map((p) => pointRotateRads<GlobalPoint>(p!, center, element.angle)),
const corners = curves ...curves
.flatMap((p) => .flatMap((p) =>
curveIntersectLineSegment(p, lineSegment(rotatedA, rotatedB)), curveIntersectLineSegment(p, lineSegment(rotatedA, rotatedB)),
) )
.filter((p) => p != null) .filter((p) => p != null)
// Rotate back intersection points // Rotate back intersection points
.map((p) => pointRotateRads(p, center, element.angle)); .map((p) => pointRotateRads(p, center, element.angle)),
]
return (
[...sides, ...corners]
// Remove duplicates // Remove duplicates
.filter( .filter(
(p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx, (p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx,

@ -1,22 +1,22 @@
import type { GlobalPoint, Radians } from "../../math"; import type { GlobalPoint, Radians } from "../../math";
import { import {
curve,
curvePointDistance, curvePointDistance,
distanceToLineSegment, distanceToLineSegment,
lineSegment,
pointFrom, pointFrom,
pointRotateRads, pointRotateRads,
rectangle,
} from "../../math"; } from "../../math";
import { ellipse, ellipseDistanceFromPoint } from "../../math/ellipse"; import { ellipse, ellipseDistanceFromPoint } from "../../math/ellipse";
import { getCornerRadius } from "../shapes"; import { debugClear } from "../visualdebug";
import { getDiamondPoints } from "./bounds";
import type { import type {
ExcalidrawBindableElement, ExcalidrawBindableElement,
ExcalidrawDiamondElement, ExcalidrawDiamondElement,
ExcalidrawEllipseElement, ExcalidrawEllipseElement,
ExcalidrawRectanguloidElement, ExcalidrawRectanguloidElement,
} from "./types"; } from "./types";
import {
deconstructDiamondElement,
deconstructRectanguloidElement,
} from "./utils";
export const distanceToBindableElement = ( export const distanceToBindableElement = (
element: ExcalidrawBindableElement, element: ExcalidrawBindableElement,
@ -50,95 +50,23 @@ export const distanceToRectanguloidElement = (
element: ExcalidrawRectanguloidElement, element: ExcalidrawRectanguloidElement,
p: GlobalPoint, p: GlobalPoint,
) => { ) => {
const r = rectangle( const center = pointFrom<GlobalPoint>(
pointFrom(element.x, element.y), element.x + element.width / 2,
pointFrom(element.x + element.width, element.y + element.height), element.y + element.height / 2,
); );
// To emulate a rotated rectangle we rotate the point in the inverse angle // To emulate a rotated rectangle we rotate the point in the inverse angle
// instead. It's all the same distance-wise. // instead. It's all the same distance-wise.
const rotatedPoint = pointRotateRads( const rotatedPoint = pointRotateRads(p, center, -element.angle as Radians);
p,
pointFrom(element.x + element.width / 2, element.y + element.height / 2),
-element.angle as Radians,
);
const roundness = getCornerRadius(
Math.min(element.width, element.height),
element,
);
const top = lineSegment<GlobalPoint>(
pointFrom<GlobalPoint>(r[0][0] + roundness, r[0][1]),
pointFrom<GlobalPoint>(r[1][0] - roundness, r[0][1]),
);
const right = lineSegment<GlobalPoint>(
pointFrom<GlobalPoint>(r[1][0], r[0][1] + roundness),
pointFrom<GlobalPoint>(r[1][0], r[1][1] - roundness),
);
const bottom = lineSegment<GlobalPoint>(
pointFrom<GlobalPoint>(r[0][0] + roundness, r[1][1]),
pointFrom<GlobalPoint>(r[1][0] - roundness, r[1][1]),
);
const left = lineSegment<GlobalPoint>(
pointFrom<GlobalPoint>(r[0][0], r[1][1] - roundness),
pointFrom<GlobalPoint>(r[0][0], r[0][1] + roundness),
);
const sideDistances = [top, right, bottom, left].map((s) =>
distanceToLineSegment(rotatedPoint, s),
);
const cornerDistances =
roundness > 0
? [
curve(
left[1],
pointFrom(
left[1][0] + (2 / 3) * (r[0][0] - left[1][0]),
left[1][1] + (2 / 3) * (r[0][1] - left[1][1]),
),
pointFrom(
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(
top[1][0] + (2 / 3) * (r[1][0] - top[1][0]),
top[1][1] + (2 / 3) * (r[0][1] - top[1][1]),
),
pointFrom(
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(
right[1][0] + (2 / 3) * (r[1][0] - right[1][0]),
right[1][1] + (2 / 3) * (r[1][1] - right[1][1]),
),
pointFrom(
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(
bottom[0][0] + (2 / 3) * (r[0][0] - bottom[0][0]),
bottom[0][1] + (2 / 3) * (r[1][1] - bottom[0][1]),
),
pointFrom(
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
].map((a) => curvePointDistance(a, rotatedPoint))
: [];
return Math.min(...sideDistances, ...cornerDistances); // Get the element's building components we can test against
const [sides, corners] = deconstructRectanguloidElement<GlobalPoint>(element);
debugClear();
return Math.min(
...sides.map((s) => distanceToLineSegment(rotatedPoint, s)),
...corners
.map((a) => curvePointDistance(a, rotatedPoint))
.filter((d): d is number => d !== null),
);
}; };
/** /**
@ -153,60 +81,22 @@ export const distanceToDiamondElement = (
element: ExcalidrawDiamondElement, element: ExcalidrawDiamondElement,
p: GlobalPoint, p: GlobalPoint,
): number => { ): number => {
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
getDiamondPoints(element);
const center = pointFrom<GlobalPoint>( const center = pointFrom<GlobalPoint>(
(topX + bottomX) / 2, element.x + element.width / 2,
(topY + bottomY) / 2, element.y + element.height / 2,
); );
const verticalRadius = getCornerRadius(Math.abs(topX - leftX), element);
const horizontalRadius = getCornerRadius(Math.abs(rightY - topY), element);
// Rotate the point to the inverse direction to simulate the rotated diamond // Rotate the point to the inverse direction to simulate the rotated diamond
// 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 [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),
];
// Create the line segment parts of the diamond
// NOTE: Horizontal and vertical seems to be flipped here
const topRight = lineSegment<GlobalPoint>(
pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius),
pointFrom(right[0] + verticalRadius, right[1] + horizontalRadius),
);
const bottomRight = lineSegment<GlobalPoint>(
pointFrom(bottom[0] + verticalRadius, bottom[1] + horizontalRadius),
pointFrom(right[0] + verticalRadius, right[1] + horizontalRadius),
);
const bottomLeft = lineSegment<GlobalPoint>(
pointFrom(bottom[0] + verticalRadius, bottom[1] + horizontalRadius),
pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius),
);
const topLeft = lineSegment<GlobalPoint>(
pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius),
pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius),
);
const curves = element.roundness const [sides, curves] = deconstructDiamondElement(element);
? [
curve(topRight[1], right, right, bottomRight[1]), // RIGHT
curve(bottomRight[0], bottom, bottom, bottomLeft[0]), // BOTTOM
curve(bottomLeft[1], left, left, topLeft[1]), // LEFT
curve(topLeft[0], top, top, topRight[0]), // LEFT
]
: [];
return Math.min( return Math.min(
...[ ...sides.map((s) => distanceToLineSegment(rotatedPoint, s)),
...[topRight, bottomRight, bottomLeft, topLeft].map((s) => ...curves
distanceToLineSegment(rotatedPoint, s), .map((a) => curvePointDistance(a, rotatedPoint))
), .filter((d): d is number => d !== null),
...curves.map((a) => curvePointDistance(a, rotatedPoint)),
],
); );
}; };

@ -0,0 +1,215 @@
import { getDiamondPoints } from ".";
import type { Curve, LineSegment } from "../../math";
import {
curve,
lineSegment,
pointFrom,
rectangle,
type GlobalPoint,
type LocalPoint,
} from "../../math";
import { getCornerRadius } from "../shapes";
import type {
ExcalidrawDiamondElement,
ExcalidrawRectanguloidElement,
} from "./types";
/**
* 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 deconstructRectanguloidElement<
Point extends GlobalPoint | LocalPoint,
>(
element: ExcalidrawRectanguloidElement,
offset: number = 0,
): [LineSegment<Point>[], Curve<Point>[]] {
const r = rectangle(
pointFrom(element.x - offset, element.y - offset),
pointFrom(
element.x + element.width + offset,
element.y + element.height + offset,
),
);
const roundness = getCornerRadius(
Math.min(element.width, element.height),
element,
);
const top = lineSegment<Point>(
pointFrom<Point>(r[0][0] + roundness, r[0][1]),
pointFrom<Point>(r[1][0] - roundness, r[0][1]),
);
const right = lineSegment<Point>(
pointFrom<Point>(r[1][0], r[0][1] + roundness),
pointFrom<Point>(r[1][0], r[1][1] - roundness),
);
const bottom = lineSegment<Point>(
pointFrom<Point>(r[0][0] + roundness, r[1][1]),
pointFrom<Point>(r[1][0] - roundness, r[1][1]),
);
const left = lineSegment<Point>(
pointFrom<Point>(r[0][0], r[1][1] - roundness),
pointFrom<Point>(r[0][0], r[0][1] + roundness),
);
const sides = [top, right, bottom, left];
const corners =
roundness > 0
? [
curve(
left[1],
pointFrom(
left[1][0] + (2 / 3) * (r[0][0] - left[1][0]),
left[1][1] + (2 / 3) * (r[0][1] - left[1][1]),
),
pointFrom(
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(
top[1][0] + (2 / 3) * (r[1][0] - top[1][0]),
top[1][1] + (2 / 3) * (r[0][1] - top[1][1]),
),
pointFrom(
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(
right[1][0] + (2 / 3) * (r[1][0] - right[1][0]),
right[1][1] + (2 / 3) * (r[1][1] - right[1][1]),
),
pointFrom(
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(
bottom[0][0] + (2 / 3) * (r[0][0] - bottom[0][0]),
bottom[0][1] + (2 / 3) * (r[1][1] - bottom[0][1]),
),
pointFrom(
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
]
: [];
return [sides, corners];
}
/**
* 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 = getCornerRadius(Math.abs(topX - leftX), element);
const horizontalRadius = getCornerRadius(Math.abs(rightY - topY), element);
const [top, right, bottom, left]: GlobalPoint[] = [
pointFrom(element.x + topX, element.y + topY - offset),
pointFrom(element.x + rightX + offset, element.y + rightY),
pointFrom(element.x + bottomX, element.y + bottomY + offset),
pointFrom(element.x + leftX - offset, element.y + leftY),
];
// Create the line segment parts of the diamond
// NOTE: Horizontal and vertical seems to be flipped here
const topRight = lineSegment<GlobalPoint>(
pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius),
pointFrom(right[0] - verticalRadius, right[1] - horizontalRadius),
);
const bottomRight = lineSegment<GlobalPoint>(
pointFrom(right[0] - verticalRadius, right[1] + horizontalRadius),
pointFrom(bottom[0] + verticalRadius, bottom[1] - horizontalRadius),
);
const bottomLeft = lineSegment<GlobalPoint>(
pointFrom(bottom[0] - verticalRadius, bottom[1] - horizontalRadius),
pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius),
);
const topLeft = lineSegment<GlobalPoint>(
pointFrom(left[0] + verticalRadius, left[1] - horizontalRadius),
pointFrom(top[0] - verticalRadius, top[1] + horizontalRadius),
);
const curves = element.roundness
? [
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
]
: [];
return [[topRight, bottomRight, bottomLeft, topLeft], curves];
}

@ -80,6 +80,21 @@ function solve(
return [t0, s0]; return [t0, s0];
} }
const bezierEquation = <Point extends GlobalPoint | LocalPoint>(
c: Curve<Point>,
t: number,
) =>
pointFrom<Point>(
(1 - t) ** 3 * c[0][0] +
3 * (1 - t) ** 2 * t * c[1][0] +
3 * (1 - t) * t ** 2 * c[2][0] +
t ** 3 * c[3][0],
(1 - t) ** 3 * c[0][1] +
3 * (1 - t) ** 2 * t * c[1][1] +
3 * (1 - t) * t ** 2 * c[2][1] +
t ** 3 * c[3][1],
);
/** /**
* Computes the intersection between a cubic spline and a line segment. * Computes the intersection between a cubic spline and a line segment.
*/ */
@ -100,17 +115,6 @@ export function curveIntersectLineSegment<
return []; return [];
} }
const bezier = (t: number) =>
pointFrom<Point>(
(1 - t) ** 3 * c[0][0] +
3 * (1 - t) ** 2 * t * c[1][0] +
3 * (1 - t) * t ** 2 * c[2][0] +
t ** 3 * c[3][0],
(1 - t) ** 3 * c[0][1] +
3 * (1 - t) ** 2 * t * c[1][1] +
3 * (1 - t) * t ** 2 * c[2][1] +
t ** 3 * c[3][1],
);
const line = (s: number) => const line = (s: number) =>
pointFrom<Point>( pointFrom<Point>(
l[0][0] + s * (l[1][0] - l[0][0]), l[0][0] + s * (l[1][0] - l[0][0]),
@ -126,7 +130,7 @@ export function curveIntersectLineSegment<
const calculate = ([t0, s0]: [number, number]) => { const calculate = ([t0, s0]: [number, number]) => {
const solution = solve( const solution = solve(
(t: number, s: number) => { (t: number, s: number) => {
const bezier_point = bezier(t); const bezier_point = bezierEquation(c, t);
const line_point = line(s); const line_point = line(s);
return [ return [
@ -148,7 +152,7 @@ export function curveIntersectLineSegment<
return null; return null;
} }
return bezier(t); return bezierEquation(c, t);
}; };
let solution = calculate(initial_guesses[0]); let solution = calculate(initial_guesses[0]);

Loading…
Cancel
Save