import { curve, lineSegment, pointFrom, pointFromVector, rectangle, vectorFromPoint, vectorNormalize, vectorScale, type GlobalPoint, } from "@excalidraw/math"; import { elementCenterPoint } from "@excalidraw/common"; import type { Curve, LineSegment } from "@excalidraw/math"; import { getCornerRadius } from "./shapes"; import { getDiamondPoints } from "./bounds"; 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( element: ExcalidrawRectanguloidElement, offset: number = 0, ): [LineSegment[], Curve[]] { const roundness = getCornerRadius( Math.min(element.width, element.height), element, ); if (roundness <= 0) { const r = rectangle( pointFrom(element.x - offset, element.y - offset), pointFrom( element.x + element.width + offset, element.y + element.height + offset, ), ); 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]; return [sides, []]; } const center = elementCenterPoint(element); const r = rectangle( pointFrom(element.x, element.y), pointFrom(element.x + element.width, element.y + element.height), ); 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 offsets = [ vectorScale( vectorNormalize( vectorFromPoint(pointFrom(r[0][0] - offset, r[0][1] - offset), center), ), offset, ), // TOP LEFT vectorScale( vectorNormalize( vectorFromPoint(pointFrom(r[1][0] + offset, r[0][1] - offset), center), ), offset, ), //TOP RIGHT vectorScale( vectorNormalize( vectorFromPoint(pointFrom(r[1][0] + offset, r[1][1] + offset), center), ), offset, ), // BOTTOM RIGHT vectorScale( vectorNormalize( vectorFromPoint(pointFrom(r[0][0] - offset, r[1][1] + offset), center), ), offset, ), // BOTTOM LEFT ]; const corners = [ curve( pointFromVector(offsets[0], left[1]), pointFromVector( offsets[0], pointFrom( left[1][0] + (2 / 3) * (r[0][0] - left[1][0]), left[1][1] + (2 / 3) * (r[0][1] - left[1][1]), ), ), pointFromVector( offsets[0], pointFrom( top[0][0] + (2 / 3) * (r[0][0] - top[0][0]), top[0][1] + (2 / 3) * (r[0][1] - top[0][1]), ), ), pointFromVector(offsets[0], top[0]), ), // TOP LEFT curve( pointFromVector(offsets[1], top[1]), pointFromVector( offsets[1], pointFrom( top[1][0] + (2 / 3) * (r[1][0] - top[1][0]), top[1][1] + (2 / 3) * (r[0][1] - top[1][1]), ), ), pointFromVector( offsets[1], pointFrom( right[0][0] + (2 / 3) * (r[1][0] - right[0][0]), right[0][1] + (2 / 3) * (r[0][1] - right[0][1]), ), ), pointFromVector(offsets[1], right[0]), ), // TOP RIGHT curve( pointFromVector(offsets[2], right[1]), pointFromVector( offsets[2], pointFrom( right[1][0] + (2 / 3) * (r[1][0] - right[1][0]), right[1][1] + (2 / 3) * (r[1][1] - right[1][1]), ), ), pointFromVector( offsets[2], pointFrom( bottom[1][0] + (2 / 3) * (r[1][0] - bottom[1][0]), bottom[1][1] + (2 / 3) * (r[1][1] - bottom[1][1]), ), ), pointFromVector(offsets[2], bottom[1]), ), // BOTTOM RIGHT curve( pointFromVector(offsets[3], bottom[0]), pointFromVector( offsets[3], pointFrom( bottom[0][0] + (2 / 3) * (r[0][0] - bottom[0][0]), bottom[0][1] + (2 / 3) * (r[1][1] - bottom[0][1]), ), ), pointFromVector( offsets[3], pointFrom( left[0][0] + (2 / 3) * (r[0][0] - left[0][0]), left[0][1] + (2 / 3) * (r[1][1] - left[0][1]), ), ), pointFromVector(offsets[3], left[0]), ), // BOTTOM LEFT ]; const sides = [ lineSegment(corners[0][3], corners[1][0]), lineSegment(corners[1][3], corners[2][0]), lineSegment(corners[2][3], corners[3][0]), lineSegment(corners[3][3], corners[0][0]), ]; 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); if (element.roundness?.type == null) { 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), ); return [[topRight, bottomRight, bottomLeft, topLeft], []]; } const center = elementCenterPoint(element); 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), ]; const offsets = [ vectorScale(vectorNormalize(vectorFromPoint(right, center)), offset), // RIGHT vectorScale(vectorNormalize(vectorFromPoint(bottom, center)), offset), // BOTTOM vectorScale(vectorNormalize(vectorFromPoint(left, center)), offset), // LEFT vectorScale(vectorNormalize(vectorFromPoint(top, center)), offset), // TOP ]; const corners = [ curve( pointFromVector( offsets[0], pointFrom( right[0] - verticalRadius, right[1] - horizontalRadius, ), ), pointFromVector(offsets[0], right), pointFromVector(offsets[0], right), pointFromVector( offsets[0], pointFrom( right[0] - verticalRadius, right[1] + horizontalRadius, ), ), ), // RIGHT curve( pointFromVector( offsets[1], pointFrom( bottom[0] + verticalRadius, bottom[1] - horizontalRadius, ), ), pointFromVector(offsets[1], bottom), pointFromVector(offsets[1], bottom), pointFromVector( offsets[1], pointFrom( bottom[0] - verticalRadius, bottom[1] - horizontalRadius, ), ), ), // BOTTOM curve( pointFromVector( offsets[2], pointFrom( left[0] + verticalRadius, left[1] + horizontalRadius, ), ), pointFromVector(offsets[2], left), pointFromVector(offsets[2], left), pointFromVector( offsets[2], pointFrom( left[0] + verticalRadius, left[1] - horizontalRadius, ), ), ), // LEFT curve( pointFromVector( offsets[3], pointFrom( top[0] - verticalRadius, top[1] + horizontalRadius, ), ), pointFromVector(offsets[3], top), pointFromVector(offsets[3], top), pointFromVector( offsets[3], pointFrom( top[0] + verticalRadius, top[1] + horizontalRadius, ), ), ), // TOP ]; const sides = [ lineSegment(corners[0][3], corners[1][0]), lineSegment(corners[1][3], corners[2][0]), lineSegment(corners[2][3], corners[3][0]), lineSegment(corners[3][3], corners[0][0]), ]; return [sides, corners]; }