tldraw
Version:
A tiny little drawing editor.
649 lines (648 loc) • 25.7 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var getElbowArrowInfo_exports = {};
__export(getElbowArrowInfo_exports, {
getEdgeFromNormalizedAnchor: () => getEdgeFromNormalizedAnchor,
getElbowArrowInfo: () => getElbowArrowInfo,
getRouteHandlePath: () => getRouteHandlePath,
getUsableEdge: () => getUsableEdge
});
module.exports = __toCommonJS(getElbowArrowInfo_exports);
var import_editor = require("@tldraw/editor");
var import_shared = require("../shared");
var import_definitions = require("./definitions");
var import_range = require("./range");
var import_ElbowArrowWorkingInfo = require("./routes/ElbowArrowWorkingInfo");
var import_routeArrowWithAutoEdgePicking = require("./routes/routeArrowWithAutoEdgePicking");
function getElbowArrowInfo(editor, arrow, bindings) {
const shapeOptions = editor.getShapeUtil(arrow.type).options;
const options = {
elbowMidpoint: arrow.props.elbowMidPoint,
expandElbowLegLength: shapeOptions.expandElbowLegLength[arrow.props.size] * arrow.props.scale,
minElbowLegLength: shapeOptions.minElbowLegLength[arrow.props.size] * arrow.props.scale
};
let startTerminal = getElbowArrowTerminalInfo(editor, arrow, bindings.start, arrow.props.start);
let endTerminal = getElbowArrowTerminalInfo(editor, arrow, bindings.end, arrow.props.end);
startTerminal = adjustTerminalForUnclosedPathIfNeeded(startTerminal, endTerminal, options);
endTerminal = adjustTerminalForUnclosedPathIfNeeded(endTerminal, startTerminal, options);
const swapOrder = !!(!startTerminal.side && endTerminal.side);
let { aTerminal, bTerminal } = swapOrder ? { aTerminal: endTerminal, bTerminal: startTerminal } : { aTerminal: startTerminal, bTerminal: endTerminal };
let edgesA = {
top: getUsableEdge(aTerminal, bTerminal, "top", options),
right: getUsableEdge(aTerminal, bTerminal, "right", options),
bottom: getUsableEdge(aTerminal, bTerminal, "bottom", options),
left: getUsableEdge(aTerminal, bTerminal, "left", options)
};
let edgesB = {
top: getUsableEdge(bTerminal, aTerminal, "top", options),
right: getUsableEdge(bTerminal, aTerminal, "right", options),
bottom: getUsableEdge(bTerminal, aTerminal, "bottom", options),
left: getUsableEdge(bTerminal, aTerminal, "left", options)
};
const aIsUsable = hasUsableEdge(edgesA, aTerminal.side);
const bIsUsable = hasUsableEdge(edgesB, bTerminal.side);
let needsNewEdges = false;
if (!aIsUsable || !bIsUsable) {
needsNewEdges = true;
if (!aIsUsable) {
bTerminal = convertTerminalToPoint(bTerminal);
}
if (!bIsUsable) {
aTerminal = convertTerminalToPoint(aTerminal);
}
if (bTerminal.bounds.containsPoint(aTerminal.target, options.expandElbowLegLength)) {
bTerminal = convertTerminalToPoint(bTerminal);
}
if (aTerminal.bounds.containsPoint(bTerminal.target, options.expandElbowLegLength)) {
aTerminal = convertTerminalToPoint(aTerminal);
}
}
if (needsNewEdges) {
edgesA = {
top: getUsableEdge(aTerminal, bTerminal, "top", options),
right: getUsableEdge(aTerminal, bTerminal, "right", options),
bottom: getUsableEdge(aTerminal, bTerminal, "bottom", options),
left: getUsableEdge(aTerminal, bTerminal, "left", options)
};
edgesB = {
top: getUsableEdge(bTerminal, aTerminal, "top", options),
right: getUsableEdge(bTerminal, aTerminal, "right", options),
bottom: getUsableEdge(bTerminal, aTerminal, "bottom", options),
left: getUsableEdge(bTerminal, aTerminal, "left", options)
};
}
const expandedA = aTerminal.isPoint ? aTerminal.bounds : aTerminal.bounds.clone().expandBy(options.expandElbowLegLength);
const expandedB = bTerminal.isPoint ? bTerminal.bounds : bTerminal.bounds.clone().expandBy(options.expandElbowLegLength);
const common = {
original: import_editor.Box.Common([aTerminal.bounds, bTerminal.bounds]),
expanded: import_editor.Box.Common([expandedA, expandedB])
};
let gapX = bTerminal.bounds.minX - aTerminal.bounds.maxX;
if (gapX < 0) {
gapX = aTerminal.bounds.minX - bTerminal.bounds.maxX;
if (gapX < 0) {
gapX = 0;
}
gapX = -gapX;
}
let gapY = bTerminal.bounds.minY - aTerminal.bounds.maxY;
if (gapY < 0) {
gapY = aTerminal.bounds.minY - bTerminal.bounds.maxY;
if (gapY < 0) {
gapY = 0;
}
gapY = -gapY;
}
const aMinLength = aTerminal.minEndSegmentLength * 3;
const bMinLength = bTerminal.minEndSegmentLength * 3;
const minLegDistanceNeeded = (aTerminal.isPoint ? aMinLength : options.minElbowLegLength) + (bTerminal.isPoint ? bMinLength : options.minElbowLegLength);
let mxRange = null;
if (gapX > minLegDistanceNeeded) {
mxRange = {
a: aTerminal.isPoint ? aTerminal.bounds.maxX + aMinLength : expandedA.maxX,
b: bTerminal.isPoint ? bTerminal.bounds.minX - bMinLength : expandedB.minX
};
} else if (gapX < -minLegDistanceNeeded) {
mxRange = {
a: aTerminal.isPoint ? aTerminal.bounds.minX - aMinLength : expandedA.minX,
b: bTerminal.isPoint ? bTerminal.bounds.maxX + bMinLength : expandedB.maxX
};
}
let myRange = null;
if (gapY > minLegDistanceNeeded) {
myRange = {
a: aTerminal.isPoint ? aTerminal.bounds.maxY + aMinLength : expandedA.maxY,
b: bTerminal.isPoint ? bTerminal.bounds.minY - bMinLength : expandedB.minY
};
} else if (gapY < -minLegDistanceNeeded) {
myRange = {
a: aTerminal.isPoint ? aTerminal.bounds.minY - aMinLength : expandedA.minY,
b: bTerminal.isPoint ? bTerminal.bounds.maxY + bMinLength : expandedB.maxY
};
}
const midpoint = swapOrder ? 1 - options.elbowMidpoint : options.elbowMidpoint;
const mx = mxRange ? (0, import_editor.lerp)(mxRange.a, mxRange.b, midpoint) : null;
const my = myRange ? (0, import_editor.lerp)(myRange.a, myRange.b, midpoint) : null;
const info = {
options,
swapOrder,
A: {
isPoint: aTerminal.isPoint,
target: aTerminal.target,
isExact: aTerminal.isExact,
arrowheadOffset: aTerminal.arrowheadOffset,
minEndSegmentLength: aTerminal.minEndSegmentLength,
original: aTerminal.bounds,
expanded: expandedA,
edges: edgesA,
geometry: aTerminal.geometry
},
B: {
isPoint: bTerminal.isPoint,
target: bTerminal.target,
isExact: bTerminal.isExact,
arrowheadOffset: bTerminal.arrowheadOffset,
minEndSegmentLength: bTerminal.minEndSegmentLength,
original: bTerminal.bounds,
expanded: expandedB,
edges: edgesB,
geometry: bTerminal.geometry
},
common,
gapX,
gapY,
midX: mx,
midY: my
};
const workingInfo = new import_ElbowArrowWorkingInfo.ElbowArrowWorkingInfo(info);
const aSide = getSideToUse(aTerminal, bTerminal, info.A.edges);
const bSide = getSideToUse(bTerminal, aTerminal, info.B.edges);
let route;
if (aSide && bSide) {
route = (0, import_routeArrowWithAutoEdgePicking.routeArrowWithManualEdgePicking)(workingInfo, aSide, bSide);
} else if (aSide && !bSide) {
route = (0, import_routeArrowWithAutoEdgePicking.routeArrowWithPartialEdgePicking)(workingInfo, aSide);
}
if (!route) {
route = (0, import_routeArrowWithAutoEdgePicking.routeArrowWithAutoEdgePicking)(workingInfo, aSide || bSide ? "fallback" : "auto");
}
if (route) {
castPathSegmentIntoGeometry("first", info.A, info.B, route);
castPathSegmentIntoGeometry("last", info.B, info.A, route);
fixTinyEndNubs(route, aTerminal, bTerminal);
if (swapOrder) route.points.reverse();
}
return {
...info,
route,
midXRange: mxRange ? swapOrder ? { lo: mxRange.b, hi: mxRange.a } : { lo: mxRange.a, hi: mxRange.b } : null,
midYRange: myRange ? swapOrder ? { lo: myRange.b, hi: myRange.a } : { lo: myRange.a, hi: myRange.b } : null
};
}
function getRouteHandlePath(info, route) {
const startTarget = info.swapOrder ? info.B.target : info.A.target;
const endTarget = info.swapOrder ? info.A.target : info.B.target;
const firstSegmentLength = import_editor.Vec.ManhattanDist(route.points[0], route.points[1]);
const lastSegmentLength = import_editor.Vec.ManhattanDist(
route.points[route.points.length - 2],
route.points[route.points.length - 1]
);
const newFirstSegmentLength = import_editor.Vec.ManhattanDist(startTarget, route.points[1]);
const newLastSegmentLength = import_editor.Vec.ManhattanDist(route.points[route.points.length - 2], endTarget);
const firstSegmentLengthChange = firstSegmentLength - newFirstSegmentLength;
const lastSegmentLengthChange = lastSegmentLength - newLastSegmentLength;
const newPoints = [startTarget, ...route.points, endTarget];
return {
name: route.name,
distance: route.distance + firstSegmentLengthChange + lastSegmentLengthChange,
points: newPoints.filter((p) => !route.skipPointsWhenDrawing.has(p)),
aEdgePicking: route.aEdgePicking,
bEdgePicking: route.bEdgePicking,
skipPointsWhenDrawing: route.skipPointsWhenDrawing,
midpointHandle: route.midpointHandle
};
}
function getEdgeFromNormalizedAnchor(normalizedAnchor) {
if ((0, import_editor.approximately)(normalizedAnchor.x, 0.5) && (0, import_editor.approximately)(normalizedAnchor.y, 0.5)) {
return null;
}
if (Math.abs(normalizedAnchor.x - 0.5) > // slightly bias towards x arrows to prevent flickering when the anchor is right on the line
// between the two directions
Math.abs(normalizedAnchor.y - 0.5) - 1e-4) {
return normalizedAnchor.x < 0.5 ? "left" : "right";
}
return normalizedAnchor.y < 0.5 ? "top" : "bottom";
}
function getElbowArrowTerminalInfo(editor, arrow, binding, point) {
const arrowStrokeSize = import_shared.STROKE_SIZES[arrow.props.size] * arrow.props.scale / 2;
const minEndSegmentLength = arrowStrokeSize * 3;
if (binding) {
const target = editor.getShape(binding.toId);
const geometry = getBindingGeometryInArrowSpace(editor, arrow, binding.toId, binding.props);
if (geometry && target) {
let arrowheadOffset = 0;
const arrowheadProp = binding.props.terminal === "start" ? "arrowheadStart" : "arrowheadEnd";
if (arrow.props[arrowheadProp] !== "none") {
const targetScale = "scale" in target.props ? target.props.scale : 1;
const targetStrokeSize = "size" in target.props ? (import_shared.STROKE_SIZES[target.props.size] ?? 0) * targetScale / 2 : 0;
arrowheadOffset = arrowStrokeSize + targetStrokeSize + import_shared.BOUND_ARROW_OFFSET * arrow.props.scale;
}
let side = null;
const targetPoint = geometry.target;
if (binding.props.isPrecise) {
side = getEdgeFromNormalizedAnchor(
import_editor.Vec.RotWith(
binding.props.normalizedAnchor,
{ x: 0.5, y: 0.5 },
geometry.shapeToArrowTransform.rotation()
)
);
}
return {
targetShapeId: binding.toId,
isPoint: false,
isExact: binding.props.isExact,
bounds: geometry.bounds,
geometry: geometry.geometry,
target: targetPoint,
arrowheadOffset,
minEndSegmentLength,
side,
snap: binding.props.snap
};
}
}
return {
targetShapeId: null,
bounds: import_editor.Box.FromCenter(point, { x: 0, y: 0 }),
geometry: null,
isExact: false,
isPoint: true,
target: import_editor.Vec.From(point),
arrowheadOffset: 0,
minEndSegmentLength,
side: null,
snap: "none"
};
}
function getBindingGeometryInArrowSpace(editor, arrow, targetId, bindingProps) {
const hasArrowhead = bindingProps.terminal === "start" ? arrow.props.arrowheadStart !== "none" : arrow.props.arrowheadEnd !== "none";
const targetGeometryInTargetSpace = editor.getShapeGeometry(
targetId,
hasArrowhead ? void 0 : { context: "@tldraw/arrow-without-arrowhead" }
);
if (!targetGeometryInTargetSpace) {
return null;
}
const arrowTransform = editor.getShapePageTransform(arrow.id);
const shapeTransform = editor.getShapePageTransform(targetId);
const shapeToArrowTransform = arrowTransform.clone().invert().multiply(shapeTransform);
const targetGeometryInArrowSpace = targetGeometryInTargetSpace.transform(shapeToArrowTransform);
const center = { x: 0.5, y: 0.5 };
const normalizedAnchor = bindingProps.isPrecise ? bindingProps.normalizedAnchor : center;
const targetInShapeSpace = {
x: (0, import_editor.lerp)(
targetGeometryInTargetSpace.bounds.minX,
targetGeometryInTargetSpace.bounds.maxX,
normalizedAnchor.x
),
y: (0, import_editor.lerp)(
targetGeometryInTargetSpace.bounds.minY,
targetGeometryInTargetSpace.bounds.maxY,
normalizedAnchor.y
)
};
const centerInShapeSpace = {
x: (0, import_editor.lerp)(
targetGeometryInTargetSpace.bounds.minX,
targetGeometryInTargetSpace.bounds.maxX,
center.x
),
y: (0, import_editor.lerp)(
targetGeometryInTargetSpace.bounds.minY,
targetGeometryInTargetSpace.bounds.maxY,
center.y
)
};
const targetInArrowSpace = import_editor.Mat.applyToPoint(shapeToArrowTransform, targetInShapeSpace);
const centerInArrowSpace = import_editor.Mat.applyToPoint(shapeToArrowTransform, centerInShapeSpace);
return {
bounds: targetGeometryInArrowSpace.bounds,
geometry: targetGeometryInArrowSpace,
target: targetInArrowSpace,
center: centerInArrowSpace,
shapeToArrowTransform
};
}
const sideProps = {
top: {
expand: -1,
main: "minY",
opposite: "maxY",
crossMid: "midX",
crossMin: "minX",
crossMax: "maxX",
bRangeExpand: "max",
crossAxis: "x"
},
bottom: {
expand: 1,
main: "maxY",
opposite: "minY",
crossMid: "midX",
crossMin: "minX",
crossMax: "maxX",
bRangeExpand: "min",
crossAxis: "x"
},
left: {
expand: -1,
main: "minX",
opposite: "maxX",
crossMid: "midY",
crossMin: "minY",
crossMax: "maxY",
bRangeExpand: "max",
crossAxis: "y"
},
right: {
expand: 1,
main: "maxX",
opposite: "minX",
crossMid: "midY",
crossMin: "minY",
crossMax: "maxY",
bRangeExpand: "min",
crossAxis: "y"
}
};
function getUsableEdge(a, b, side, options) {
const props = sideProps[side];
const isSelfBoundAndShouldRouteExternal = a.targetShapeId === b.targetShapeId && a.targetShapeId !== null && (a.snap === "edge" || a.snap === "edge-point") && (b.snap === "edge" || b.snap === "edge-point");
const aValue = a.bounds[props.main];
const aExpanded = a.isPoint ? null : aValue + props.expand * options.expandElbowLegLength;
const originalACrossRange = (0, import_range.createRange)(a.bounds[props.crossMin], a.bounds[props.crossMax]);
let aCrossRange = originalACrossRange;
if (!aCrossRange) {
return null;
}
(0, import_editor.assert)(originalACrossRange);
const bRange = (0, import_range.createRange)(b.bounds[props.main], b.bounds[props.opposite]);
if (!b.isPoint) {
bRange[props.bRangeExpand] -= options.minElbowLegLength * 2 * props.expand;
}
const bCrossRange = (0, import_range.expandRange)(
(0, import_range.createRange)(b.bounds[props.crossMin], b.bounds[props.crossMax]),
options.expandElbowLegLength
);
(0, import_editor.assert)(bRange && bCrossRange);
let isPartial = false;
if ((0, import_range.isWithinRange)(aValue, bRange) && !a.isPoint && !b.isPoint && !isSelfBoundAndShouldRouteExternal) {
const subtracted = (0, import_range.subtractRange)(aCrossRange, bCrossRange);
switch (subtracted.length) {
case 0:
return null;
case 1:
isPartial = subtracted[0] !== aCrossRange;
aCrossRange = subtracted[0];
break;
case 2:
isPartial = true;
aCrossRange = (0, import_range.rangeSize)(subtracted[0]) > (0, import_range.rangeSize)(subtracted[1]) ? subtracted[0] : subtracted[1];
break;
default:
(0, import_editor.exhaustiveSwitchError)(subtracted);
}
}
if (!(0, import_range.isWithinRange)(a.target[props.crossAxis], aCrossRange)) {
return null;
}
const crossTarget = a.target[props.crossAxis];
return {
value: aValue,
expanded: aExpanded,
cross: aCrossRange,
crossTarget,
isPartial
};
}
function hasUsableEdge(edges, side) {
if (side === null) {
return !!(edges.bottom || edges.left || edges.right || edges.top);
}
if (side === "x") {
return !!edges.left || !!edges.right;
}
if (side === "y") {
return !!edges.top || !!edges.bottom;
}
return !!edges[side];
}
function getSideToUse(binding, other, edges) {
switch (binding.side) {
case null:
return null;
case "x":
if (binding.bounds.center.x > other.bounds.center.x && edges?.left) {
return "left";
} else if (edges?.right) {
return "right";
}
return null;
case "y":
if (binding.bounds.center.y > other.bounds.center.y && edges?.top) {
return "top";
} else if (edges?.bottom) {
return "bottom";
}
return null;
default:
return binding.side;
}
}
function convertTerminalToPoint(terminal) {
if (terminal.isPoint) return terminal;
let side = null;
let arrowheadOffset = 0;
if (terminal.snap === "edge" || terminal.snap === "edge-point") {
arrowheadOffset = terminal.arrowheadOffset;
if (terminal.side === "x" || terminal.side === "left" || terminal.side === "right") {
side = "x";
}
if (terminal.side === "y" || terminal.side === "top" || terminal.side === "bottom") {
side = "y";
}
}
return {
targetShapeId: terminal.targetShapeId,
side,
bounds: new import_editor.Box(terminal.target.x, terminal.target.y, 0, 0),
geometry: terminal.geometry,
target: terminal.target,
arrowheadOffset,
minEndSegmentLength: terminal.minEndSegmentLength,
isExact: terminal.isExact,
isPoint: true,
snap: terminal.snap
};
}
function castPathSegmentIntoGeometry(segment, target, other, route) {
if (!target.geometry) return;
const point1 = segment === "first" ? route.points[0] : route.points[route.points.length - 1];
const point2 = segment === "first" ? route.points[1] : route.points[route.points.length - 2];
const pointToFindClosestIntersectionTo = target.geometry.isClosed ? point2 : target.target;
const initialDistance = import_editor.Vec.ManhattanDist(point1, pointToFindClosestIntersectionTo);
let nearestIntersectionToPoint2 = null;
let nearestDistanceToPoint2 = Infinity;
if (target.isExact) {
nearestIntersectionToPoint2 = target.target;
} else if (target.geometry) {
const intersections = target.geometry.intersectLineSegment(point2, target.target, {
includeLabels: false,
includeInternal: false
});
if (target.geometry.hitTestPoint(
target.target,
Math.max(1, target.arrowheadOffset),
true,
import_editor.Geometry2dFilters.EXCLUDE_NON_STANDARD
)) {
intersections.push(target.target);
}
for (const intersection of intersections) {
const point2Distance = import_editor.Vec.ManhattanDist(pointToFindClosestIntersectionTo, intersection);
if (point2Distance < nearestDistanceToPoint2) {
nearestDistanceToPoint2 = point2Distance;
nearestIntersectionToPoint2 = intersection;
}
}
}
if (nearestIntersectionToPoint2) {
let offset = target.arrowheadOffset;
const currentFinalSegmentLength = import_editor.Vec.ManhattanDist(point2, nearestIntersectionToPoint2);
const minLength = target.arrowheadOffset * 2;
if (currentFinalSegmentLength < minLength) {
const targetLength = minLength - target.arrowheadOffset;
offset = currentFinalSegmentLength - targetLength;
}
if (offset < target.minEndSegmentLength) {
if (target.geometry.bounds.containsPoint(other.target)) {
offset = Math.max(0, offset);
} else {
offset = -target.arrowheadOffset;
}
}
let nudgedPoint = nearestIntersectionToPoint2;
let shouldAddExtraPointForNudge = false;
if (!target.isExact && offset !== 0) {
const nudged = import_editor.Vec.Nudge(nearestIntersectionToPoint2, point2, offset);
nudgedPoint = nudged;
if (offset < 0 && !target.geometry.hitTestPoint(nudged, 0, true, import_editor.Geometry2dFilters.EXCLUDE_NON_STANDARD)) {
nudgedPoint = nearestIntersectionToPoint2;
} else {
if (offset < 0) {
shouldAddExtraPointForNudge = true;
}
nudgedPoint = nudged;
}
}
const newDistance = import_editor.Vec.ManhattanDist(point2, nudgedPoint);
route.distance += newDistance - initialDistance;
point1.x = nudgedPoint.x;
point1.y = nudgedPoint.y;
if (shouldAddExtraPointForNudge) {
const midPoint = import_editor.Vec.Lrp(point2, point1, 0.5);
route.skipPointsWhenDrawing.add(midPoint);
route.points.splice(segment === "first" ? 1 : route.points.length - 1, 0, midPoint);
}
}
}
function fixTinyEndNubs(route, aTerminal, bTerminal) {
if (!route) return;
if (route.points.length >= 3) {
const a = route.points[0];
const b = route.points[1];
const firstSegmentLength = import_editor.Vec.ManhattanDist(a, b);
if (firstSegmentLength < aTerminal.minEndSegmentLength) {
route.points.splice(1, 1);
if (route.points.length >= 3) {
const matchAxis = (0, import_editor.approximately)(a.x, b.x) ? "y" : "x";
route.points[1][matchAxis] = a[matchAxis];
}
}
}
if (route.points.length >= 3) {
const a = route.points[route.points.length - 1];
const b = route.points[route.points.length - 2];
const lastSegmentLength = import_editor.Vec.ManhattanDist(a, b);
if (lastSegmentLength < bTerminal.minEndSegmentLength) {
route.points.splice(route.points.length - 2, 1);
if (route.points.length >= 3) {
const matchAxis = (0, import_editor.approximately)(a.x, b.x) ? "y" : "x";
route.points[route.points.length - 2][matchAxis] = a[matchAxis];
}
}
}
}
function adjustTerminalForUnclosedPathIfNeeded(terminal, otherTerminal, options) {
if (!terminal.geometry || terminal.geometry.isClosed) return terminal;
const normalizedPointAlongPath = terminal.geometry.uninterpolateAlongEdge(
terminal.target,
import_editor.Geometry2dFilters.EXCLUDE_NON_STANDARD
);
const prev = terminal.geometry.interpolateAlongEdge(
normalizedPointAlongPath - 0.01 / terminal.geometry.length
);
const next = terminal.geometry.interpolateAlongEdge(
normalizedPointAlongPath + 0.01 / terminal.geometry.length
);
const normal = next.sub(prev).per().uni();
const axis = Math.abs(normal.x) > Math.abs(normal.y) ? import_definitions.ElbowArrowAxes.x : import_definitions.ElbowArrowAxes.y;
if (terminal.geometry.bounds.containsPoint(otherTerminal.target, options.expandElbowLegLength)) {
terminal.side = axis.self;
return convertTerminalToPoint(terminal);
}
const min = axis.v(
terminal.target[axis.self] - terminal.bounds[axis.size] * 2,
terminal.target[axis.cross]
);
const max = axis.v(
terminal.target[axis.self] + terminal.bounds[axis.size] * 2,
terminal.target[axis.cross]
);
let furthestIntersectionTowardsMin = null;
let furthestIntersectionTowardsMinDistance = 0;
let furthestIntersectionTowardsMax = null;
let furthestIntersectionTowardsMaxDistance = 0;
let side = axis.self;
for (const intersection of terminal.geometry.intersectLineSegment(
min,
max,
import_editor.Geometry2dFilters.EXCLUDE_NON_STANDARD
)) {
if (Math.abs(intersection[axis.self] - terminal.target[axis.self]) < 1) {
continue;
}
if (intersection[axis.self] < terminal.target[axis.self]) {
if (import_editor.Vec.ManhattanDist(intersection, terminal.target) > furthestIntersectionTowardsMinDistance) {
furthestIntersectionTowardsMinDistance = import_editor.Vec.ManhattanDist(intersection, terminal.target);
furthestIntersectionTowardsMin = intersection;
}
} else {
if (import_editor.Vec.ManhattanDist(intersection, terminal.target) > furthestIntersectionTowardsMaxDistance) {
furthestIntersectionTowardsMaxDistance = import_editor.Vec.ManhattanDist(intersection, terminal.target);
furthestIntersectionTowardsMax = intersection;
}
}
}
if (furthestIntersectionTowardsMin && furthestIntersectionTowardsMax) {
if (furthestIntersectionTowardsMinDistance > furthestIntersectionTowardsMaxDistance) {
side = axis.hiEdge;
} else {
side = axis.loEdge;
}
} else if (furthestIntersectionTowardsMin && !furthestIntersectionTowardsMax) {
side = axis.hiEdge;
} else if (!furthestIntersectionTowardsMin && furthestIntersectionTowardsMax) {
side = axis.loEdge;
}
terminal.side = side;
return terminal;
}
//# sourceMappingURL=getElbowArrowInfo.js.map