diff --git a/packages/excalidraw/element/collision.ts b/packages/excalidraw/element/collision.ts index 178b84f003..04ad0f6106 100644 --- a/packages/excalidraw/element/collision.ts +++ b/packages/excalidraw/element/collision.ts @@ -6,7 +6,7 @@ import type { ExcalidrawRectangleElement, ExcalidrawRectanguloidElement, } from "./types"; -import { getDiamondPoints, getElementBounds } from "./bounds"; +import { getElementBounds } from "./bounds"; import type { FrameNameBounds } from "../types"; import type { GeometricShape } from "../../utils/geometry/shape"; import { getPolygonShape } from "../../utils/geometry/shape"; @@ -18,7 +18,7 @@ import { isImageElement, isTextElement, } from "./typeChecks"; -import { getBoundTextShape, getCornerRadius, isPathALoop } from "../shapes"; +import { getBoundTextShape, isPathALoop } from "../shapes"; import type { GlobalPoint, LineSegment, @@ -27,7 +27,6 @@ import type { Radians, } from "../../math"; import { - curve, curveIntersectLineSegment, isPointWithinBounds, line, @@ -36,9 +35,12 @@ import { pointFrom, pointRotateRads, pointsEqual, - rectangle, } from "../../math"; import { ellipse, ellipseLineIntersectionPoints } from "../../math/ellipse"; +import { + deconstructDiamondElement, + deconstructRectanguloidElement, +} from "./utils"; export const shouldTestInside = (element: ExcalidrawElement) => { if (element.type === "arrow") { @@ -179,13 +181,6 @@ const intersectRectanguloidWithLineSegment = ( l: LineSegment, offset: number, ): 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( element.x + element.width / 2, element.y + element.height / 2, @@ -202,107 +197,36 @@ const intersectRectanguloidWithLineSegment = ( center, -element.angle as Radians, ); - const roundness = getCornerRadius( - Math.min(element.width, element.height), - element, - ); - const top = lineSegment( - pointFrom(r[0][0] + roundness, r[0][1]), - pointFrom(r[1][0] - roundness, r[0][1]), - ); - const right = lineSegment( - pointFrom(r[1][0], r[0][1] + roundness), - pointFrom(r[1][0], r[1][1] - roundness), - ); - const bottom = lineSegment( - pointFrom(r[0][0] + roundness, r[1][1]), - pointFrom(r[1][0] - roundness, r[1][1]), - ); - const left = lineSegment( - pointFrom(r[0][0], r[1][1] - roundness), - pointFrom(r[0][0], r[0][1] + roundness), + // Get the element's building components we can test against + const [sides, corners] = deconstructRectanguloidElement( + element, + offset, ); - 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 - .map((s) => - lineSegmentIntersectionPoints( - lineSegment(rotatedA, rotatedB), - s, - ), - ) - .filter((x) => x != null) - .map((j) => pointRotateRads(j!, center, element.angle)); - - const cornerIntersections: GlobalPoint[] = corners - .flatMap((t) => - curveIntersectLineSegment(t, lineSegment(rotatedA, rotatedB)), - ) - .filter((i) => i != null) - .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] + [ + // Test intersection against the sides, keep only the valid + // intersection points and rotate them back to scene space + ...sides + .map((s) => + lineSegmentIntersectionPoints( + lineSegment(rotatedA, rotatedB), + s, + ), + ) + .filter((x) => x != null) + .map((j) => pointRotateRads(j!, center, element.angle)), + // Test intersection against the corners which are cubic bezier curves, + // keep only the valid intersection points and rotate them back to scene + // space + ...corners + .flatMap((t) => + curveIntersectLineSegment(t, lineSegment(rotatedA, rotatedB)), + ) + .filter((i) => i != null) + .map((j) => pointRotateRads(j, center, element.angle)), + ] // Remove duplicates .filter( (p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx, @@ -322,119 +246,38 @@ const intersectDiamondWithLineSegment = ( l: LineSegment, offset: number = 0, ): GlobalPoint[] => { - const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] = - getDiamondPoints(element); const center = pointFrom( - (topX + bottomX) / 2, - (topY + bottomY) / 2, + element.x + element.width / 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 // points. It's all the same distance-wise. const rotatedA = pointRotateRads(l[0], center, -element.angle as Radians); const rotatedB = pointRotateRads(l[1], center, -element.angle as Radians); - 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( - pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius), - pointFrom(right[0] - verticalRadius, right[1] - horizontalRadius), - ); - const bottomRight = lineSegment( - pointFrom(right[0] - verticalRadius, right[1] + horizontalRadius), - pointFrom(bottom[0] + verticalRadius, bottom[1] - horizontalRadius), - ); - const bottomLeft = lineSegment( - pointFrom(bottom[0] - verticalRadius, bottom[1] - horizontalRadius), - pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius), - ); - const topLeft = lineSegment( - pointFrom(left[0] + verticalRadius, left[1] - horizontalRadius), - pointFrom(top[0] - verticalRadius, top[1] + horizontalRadius), - ); - - const curves = element.roundness - ? [ - curve( - pointFrom( - right[0] - verticalRadius, - right[1] - horizontalRadius, - ), - right, - right, - pointFrom( - right[0] - verticalRadius, - right[1] + horizontalRadius, - ), - ), // RIGHT - curve( - pointFrom( - bottom[0] + verticalRadius, - bottom[1] - horizontalRadius, - ), - bottom, - bottom, - pointFrom( - bottom[0] - verticalRadius, - bottom[1] - horizontalRadius, - ), - ), // BOTTOM - curve( - pointFrom( - left[0] + verticalRadius, - left[1] + horizontalRadius, - ), - left, - left, - pointFrom( - left[0] + verticalRadius, - left[1] - horizontalRadius, - ), - ), // LEFT - curve( - pointFrom( - top[0] - verticalRadius, - top[1] + horizontalRadius, - ), - top, - top, - pointFrom( - top[0] + verticalRadius, - top[1] + horizontalRadius, - ), - ), // TOP - ] - : []; - - const sides: GlobalPoint[] = [topRight, bottomRight, bottomLeft, topLeft] - .map((s) => - lineSegmentIntersectionPoints( - lineSegment(rotatedA, rotatedB), - s, - ), - ) - .filter((p): p is GlobalPoint => p != null) - // Rotate back intersection points - .map((p) => pointRotateRads(p!, center, element.angle)); - const corners = curves - .flatMap((p) => - curveIntersectLineSegment(p, lineSegment(rotatedA, rotatedB)), - ) - .filter((p) => p != null) - // Rotate back intersection points - .map((p) => pointRotateRads(p, center, element.angle)); + const [sides, curves] = deconstructDiamondElement(element, offset); return ( - [...sides, ...corners] + [ + ...sides + .map((s) => + lineSegmentIntersectionPoints( + lineSegment(rotatedA, rotatedB), + s, + ), + ) + .filter((p): p is GlobalPoint => p != null) + // Rotate back intersection points + .map((p) => pointRotateRads(p!, center, element.angle)), + ...curves + .flatMap((p) => + curveIntersectLineSegment(p, lineSegment(rotatedA, rotatedB)), + ) + .filter((p) => p != null) + // Rotate back intersection points + .map((p) => pointRotateRads(p, center, element.angle)), + ] // Remove duplicates .filter( (p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx, diff --git a/packages/excalidraw/element/distance.ts b/packages/excalidraw/element/distance.ts index 39bb5af8bd..22d7efb89a 100644 --- a/packages/excalidraw/element/distance.ts +++ b/packages/excalidraw/element/distance.ts @@ -1,22 +1,22 @@ import type { GlobalPoint, Radians } from "../../math"; import { - curve, curvePointDistance, distanceToLineSegment, - lineSegment, pointFrom, pointRotateRads, - rectangle, } from "../../math"; import { ellipse, ellipseDistanceFromPoint } from "../../math/ellipse"; -import { getCornerRadius } from "../shapes"; -import { getDiamondPoints } from "./bounds"; +import { debugClear } from "../visualdebug"; import type { ExcalidrawBindableElement, ExcalidrawDiamondElement, ExcalidrawEllipseElement, ExcalidrawRectanguloidElement, } from "./types"; +import { + deconstructDiamondElement, + deconstructRectanguloidElement, +} from "./utils"; export const distanceToBindableElement = ( element: ExcalidrawBindableElement, @@ -50,95 +50,23 @@ export const distanceToRectanguloidElement = ( element: ExcalidrawRectanguloidElement, p: GlobalPoint, ) => { - const r = rectangle( - pointFrom(element.x, element.y), - pointFrom(element.x + element.width, element.y + element.height), + const center = pointFrom( + element.x + element.width / 2, + element.y + element.height / 2, ); // To emulate a rotated rectangle we rotate the point in the inverse angle // instead. It's all the same distance-wise. - const rotatedPoint = pointRotateRads( - 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( - pointFrom(r[0][0] + roundness, r[0][1]), - pointFrom(r[1][0] - roundness, r[0][1]), - ); - const right = lineSegment( - pointFrom(r[1][0], r[0][1] + roundness), - pointFrom(r[1][0], r[1][1] - roundness), - ); - const bottom = lineSegment( - pointFrom(r[0][0] + roundness, r[1][1]), - pointFrom(r[1][0] - roundness, r[1][1]), - ); - const left = lineSegment( - pointFrom(r[0][0], r[1][1] - roundness), - pointFrom(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)) - : []; + const rotatedPoint = pointRotateRads(p, center, -element.angle as Radians); - return Math.min(...sideDistances, ...cornerDistances); + // Get the element's building components we can test against + const [sides, corners] = deconstructRectanguloidElement(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, p: GlobalPoint, ): number => { - const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] = - getDiamondPoints(element); const center = pointFrom( - (topX + bottomX) / 2, - (topY + bottomY) / 2, + element.x + element.width / 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 // points. It's all the same distance-wise. 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( - pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius), - pointFrom(right[0] + verticalRadius, right[1] + horizontalRadius), - ); - const bottomRight = lineSegment( - pointFrom(bottom[0] + verticalRadius, bottom[1] + horizontalRadius), - pointFrom(right[0] + verticalRadius, right[1] + horizontalRadius), - ); - const bottomLeft = lineSegment( - pointFrom(bottom[0] + verticalRadius, bottom[1] + horizontalRadius), - pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius), - ); - const topLeft = lineSegment( - pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius), - pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius), - ); - const curves = element.roundness - ? [ - 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 - ] - : []; + const [sides, curves] = deconstructDiamondElement(element); return Math.min( - ...[ - ...[topRight, bottomRight, bottomLeft, topLeft].map((s) => - distanceToLineSegment(rotatedPoint, s), - ), - ...curves.map((a) => curvePointDistance(a, rotatedPoint)), - ], + ...sides.map((s) => distanceToLineSegment(rotatedPoint, s)), + ...curves + .map((a) => curvePointDistance(a, rotatedPoint)) + .filter((d): d is number => d !== null), ); }; diff --git a/packages/excalidraw/element/utils.ts b/packages/excalidraw/element/utils.ts new file mode 100644 index 0000000000..1ab6b881eb --- /dev/null +++ b/packages/excalidraw/element/utils.ts @@ -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[], Curve[]] { + 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( + pointFrom(r[0][0] + roundness, r[0][1]), + pointFrom(r[1][0] - roundness, r[0][1]), + ); + const right = lineSegment( + pointFrom(r[1][0], r[0][1] + roundness), + pointFrom(r[1][0], r[1][1] - roundness), + ); + const bottom = lineSegment( + pointFrom(r[0][0] + roundness, r[1][1]), + pointFrom(r[1][0] - roundness, r[1][1]), + ); + const left = lineSegment( + pointFrom(r[0][0], r[1][1] - roundness), + pointFrom(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[], Curve[]] { + 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( + pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius), + pointFrom(right[0] - verticalRadius, right[1] - horizontalRadius), + ); + const bottomRight = lineSegment( + pointFrom(right[0] - verticalRadius, right[1] + horizontalRadius), + pointFrom(bottom[0] + verticalRadius, bottom[1] - horizontalRadius), + ); + const bottomLeft = lineSegment( + pointFrom(bottom[0] - verticalRadius, bottom[1] - horizontalRadius), + pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius), + ); + const topLeft = lineSegment( + pointFrom(left[0] + verticalRadius, left[1] - horizontalRadius), + pointFrom(top[0] - verticalRadius, top[1] + horizontalRadius), + ); + + const curves = element.roundness + ? [ + curve( + pointFrom( + right[0] - verticalRadius, + right[1] - horizontalRadius, + ), + right, + right, + pointFrom( + right[0] - verticalRadius, + right[1] + horizontalRadius, + ), + ), // RIGHT + curve( + pointFrom( + bottom[0] + verticalRadius, + bottom[1] - horizontalRadius, + ), + bottom, + bottom, + pointFrom( + bottom[0] - verticalRadius, + bottom[1] - horizontalRadius, + ), + ), // BOTTOM + curve( + pointFrom( + left[0] + verticalRadius, + left[1] + horizontalRadius, + ), + left, + left, + pointFrom( + left[0] + verticalRadius, + left[1] - horizontalRadius, + ), + ), // LEFT + curve( + pointFrom( + top[0] - verticalRadius, + top[1] + horizontalRadius, + ), + top, + top, + pointFrom( + top[0] + verticalRadius, + top[1] + horizontalRadius, + ), + ), // TOP + ] + : []; + + return [[topRight, bottomRight, bottomLeft, topLeft], curves]; +} diff --git a/packages/math/curve.ts b/packages/math/curve.ts index f33ecc722b..f6f919d1f8 100644 --- a/packages/math/curve.ts +++ b/packages/math/curve.ts @@ -80,6 +80,21 @@ function solve( return [t0, s0]; } +const bezierEquation = ( + c: Curve, + t: number, +) => + pointFrom( + (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. */ @@ -100,17 +115,6 @@ export function curveIntersectLineSegment< return []; } - const bezier = (t: number) => - pointFrom( - (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) => pointFrom( l[0][0] + s * (l[1][0] - l[0][0]), @@ -126,7 +130,7 @@ export function curveIntersectLineSegment< const calculate = ([t0, s0]: [number, number]) => { const solution = solve( (t: number, s: number) => { - const bezier_point = bezier(t); + const bezier_point = bezierEquation(c, t); const line_point = line(s); return [ @@ -148,7 +152,7 @@ export function curveIntersectLineSegment< return null; } - return bezier(t); + return bezierEquation(c, t); }; let solution = calculate(initial_guesses[0]);