feat(editor): visualize binding midpoints + support for simple arrows (#10611)
* feat: Force exact center focus point When the projected point is close to center snap it to the exact center. Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Tests Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Snap to center around side mid point. Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * Trigger CI * fix: Midpoint outline focus point Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Tests Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Dragging existing arrow reset focus point on outline Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Tests Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * feat: Midpoint indicator Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Rotated mid points Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: No hole Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * feat: Cache hits and scene lookups Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * chore: Remove debug Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Consider hit threshold and inside override too Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Increase outline midpoint sticky distance Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Don't show midpoint indicator when no snapping is possible Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * feat: Indicate lock-in Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * chore: Remove Map caching Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: incorrect threshold Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: threshold setting Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Hit caching Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Simple arrow mid point selection inconsistency Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: cache override Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Precise know dragging with midpoint refactor Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fear: Frame support Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Crossing arrow won't trigger mid point Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Arrow creation point highlight Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Restore types & tests Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * chore: Restore restore.ts Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: restore.ts Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Elbow arrows reliably highlight center point Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Highlight point ordering Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * feat: Bind with focus point across shape Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Lint * fix: Midpoint and binding alignment Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * chore: Indicator color Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * chore: More knob tuning Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Radius Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Tests Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * simplify point indicators --------- Signed-off-by: Mark Tolmacs <mark@lazycat.hu> Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
@@ -800,6 +800,7 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
|
||||
hit,
|
||||
startDragged ? "start" : "end",
|
||||
elementsMap,
|
||||
appState.zoom,
|
||||
) || globalPoint,
|
||||
}
|
||||
: { mode: null };
|
||||
@@ -833,6 +834,7 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
|
||||
otherBindableElement,
|
||||
startDragged ? "end" : "start",
|
||||
elementsMap,
|
||||
appState.zoom,
|
||||
) || otherEndpoint,
|
||||
}
|
||||
: { mode: undefined }
|
||||
@@ -1389,14 +1391,16 @@ export const bindPointToSnapToElementOutline = (
|
||||
headingForPointFromElement(bindableElement, aabb, point),
|
||||
);
|
||||
const snapPoint = snapToMid(
|
||||
arrowElement,
|
||||
bindableElement,
|
||||
elementsMap,
|
||||
edgePoint,
|
||||
0.05,
|
||||
arrowElement,
|
||||
);
|
||||
const resolved = snapPoint || point;
|
||||
const otherPoint = pointFrom<GlobalPoint>(
|
||||
isHorizontal ? bindableCenter[0] : snapPoint[0],
|
||||
!isHorizontal ? bindableCenter[1] : snapPoint[1],
|
||||
isHorizontal ? bindableCenter[0] : resolved[0],
|
||||
!isHorizontal ? bindableCenter[1] : resolved[1],
|
||||
);
|
||||
const intersector =
|
||||
customIntersector ??
|
||||
@@ -1404,7 +1408,7 @@ export const bindPointToSnapToElementOutline = (
|
||||
otherPoint,
|
||||
pointFromVector(
|
||||
vectorScale(
|
||||
vectorNormalize(vectorFromPoint(snapPoint, otherPoint)),
|
||||
vectorNormalize(vectorFromPoint(resolved, otherPoint)),
|
||||
Math.max(bindableElement.width, bindableElement.height) * 2,
|
||||
),
|
||||
otherPoint,
|
||||
@@ -1419,14 +1423,14 @@ export const bindPointToSnapToElementOutline = (
|
||||
|
||||
if (!intersection) {
|
||||
const anotherPoint = pointFrom<GlobalPoint>(
|
||||
!isHorizontal ? bindableCenter[0] : snapPoint[0],
|
||||
isHorizontal ? bindableCenter[1] : snapPoint[1],
|
||||
!isHorizontal ? bindableCenter[0] : resolved[0],
|
||||
isHorizontal ? bindableCenter[1] : resolved[1],
|
||||
);
|
||||
const anotherIntersector = lineSegment(
|
||||
anotherPoint,
|
||||
pointFromVector(
|
||||
vectorScale(
|
||||
vectorNormalize(vectorFromPoint(snapPoint, anotherPoint)),
|
||||
vectorNormalize(vectorFromPoint(resolved, anotherPoint)),
|
||||
Math.max(bindableElement.width, bindableElement.height) * 2,
|
||||
),
|
||||
anotherPoint,
|
||||
@@ -1573,18 +1577,18 @@ export const avoidRectangularCorner = (
|
||||
return p;
|
||||
};
|
||||
|
||||
const snapToMid = (
|
||||
arrowElement: ExcalidrawArrowElement,
|
||||
export const snapToMid = (
|
||||
bindTarget: ExcalidrawBindableElement,
|
||||
elementsMap: ElementsMap,
|
||||
p: GlobalPoint,
|
||||
tolerance: number = 0.05,
|
||||
): GlobalPoint => {
|
||||
arrowElement?: ExcalidrawArrowElement,
|
||||
): GlobalPoint | undefined => {
|
||||
const { x, y, width, height, angle } = bindTarget;
|
||||
const center = elementCenterPoint(bindTarget, elementsMap, -0.1, -0.1);
|
||||
const nonRotated = pointRotateRads(p, center, -angle as Radians);
|
||||
|
||||
const bindingGap = getBindingGap(bindTarget, arrowElement);
|
||||
const bindingGap = arrowElement ? getBindingGap(bindTarget, arrowElement) : 0;
|
||||
|
||||
// snap-to-center point is adaptive to element size, but we don't want to go
|
||||
// above and below certain px distance
|
||||
@@ -1593,7 +1597,7 @@ const snapToMid = (
|
||||
|
||||
// Too close to the center makes it hard to resolve direction precisely
|
||||
if (pointDistance(center, nonRotated) < bindingGap) {
|
||||
return p;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -1602,8 +1606,8 @@ const snapToMid = (
|
||||
nonRotated[1] < center[1] + verticalThreshold
|
||||
) {
|
||||
// LEFT
|
||||
return pointRotateRads<GlobalPoint>(
|
||||
pointFrom(x - bindingGap, center[1]),
|
||||
return pointRotateRads(
|
||||
pointFrom<GlobalPoint>(x - bindingGap, center[1]),
|
||||
center,
|
||||
angle,
|
||||
);
|
||||
@@ -1613,7 +1617,11 @@ const snapToMid = (
|
||||
nonRotated[0] < center[0] + horizontalThreshold
|
||||
) {
|
||||
// TOP
|
||||
return pointRotateRads(pointFrom(center[0], y - bindingGap), center, angle);
|
||||
return pointRotateRads(
|
||||
pointFrom<GlobalPoint>(center[0], y - bindingGap),
|
||||
center,
|
||||
angle,
|
||||
);
|
||||
} else if (
|
||||
nonRotated[0] >= x + width / 2 &&
|
||||
nonRotated[1] > center[1] - verticalThreshold &&
|
||||
@@ -1621,7 +1629,7 @@ const snapToMid = (
|
||||
) {
|
||||
// RIGHT
|
||||
return pointRotateRads(
|
||||
pointFrom(x + width + bindingGap, center[1]),
|
||||
pointFrom<GlobalPoint>(x + width + bindingGap, center[1]),
|
||||
center,
|
||||
angle,
|
||||
);
|
||||
@@ -1632,7 +1640,7 @@ const snapToMid = (
|
||||
) {
|
||||
// DOWN
|
||||
return pointRotateRads(
|
||||
pointFrom(center[0], y + height + bindingGap),
|
||||
pointFrom<GlobalPoint>(center[0], y + height + bindingGap),
|
||||
center,
|
||||
angle,
|
||||
);
|
||||
@@ -1681,7 +1689,7 @@ const snapToMid = (
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const compareElementArea = (
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
|
||||
import {
|
||||
deconstructLinearOrFreeDrawElement,
|
||||
getSnapOutlineMidPoint,
|
||||
isPathALoop,
|
||||
moveArrowAboveBindable,
|
||||
projectFixedPointOntoDiagonal,
|
||||
@@ -48,6 +49,7 @@ import {
|
||||
calculateFixedPointForNonElbowArrowBinding,
|
||||
getBindingStrategyForDraggingBindingElementEndpoints,
|
||||
isBindingEnabled,
|
||||
snapToMid,
|
||||
updateBoundPoint,
|
||||
} from "./binding";
|
||||
import {
|
||||
@@ -355,6 +357,7 @@ export class LinearElementEditor {
|
||||
app,
|
||||
shouldRotateWithDiscreteAngle(event),
|
||||
event.altKey,
|
||||
linearElementEditor,
|
||||
);
|
||||
|
||||
LinearElementEditor.movePoints(element, app.scene, positions, {
|
||||
@@ -408,13 +411,14 @@ export class LinearElementEditor {
|
||||
altFocusPoint:
|
||||
!linearElementEditor.initialState.altFocusPoint &&
|
||||
startBindingElement &&
|
||||
updates?.suggestedBinding?.id !== startBindingElement.id
|
||||
updates?.suggestedBinding?.element.id !== startBindingElement.id
|
||||
? projectFixedPointOntoDiagonal(
|
||||
element,
|
||||
pointFrom<GlobalPoint>(element.x, element.y),
|
||||
startBindingElement,
|
||||
"start",
|
||||
elementsMap,
|
||||
app.state.zoom,
|
||||
)
|
||||
: linearElementEditor.initialState.altFocusPoint,
|
||||
},
|
||||
@@ -532,6 +536,7 @@ export class LinearElementEditor {
|
||||
app,
|
||||
shouldRotateWithDiscreteAngle(event) && singlePointDragged,
|
||||
event.altKey,
|
||||
linearElementEditor,
|
||||
);
|
||||
|
||||
LinearElementEditor.movePoints(element, app.scene, positions, {
|
||||
@@ -607,11 +612,11 @@ export class LinearElementEditor {
|
||||
const altFocusPointBindableElement =
|
||||
endIsSelected && // The "other" end (i.e. "end") is dragged
|
||||
startBindingElement &&
|
||||
updates?.suggestedBinding?.id !== startBindingElement.id // The end point is not hovering the start bindable + it's binding gap
|
||||
updates?.suggestedBinding?.element.id !== startBindingElement.id // The end point is not hovering the start bindable + it's binding gap
|
||||
? startBindingElement
|
||||
: startIsSelected && // The "other" end (i.e. "start") is dragged
|
||||
endBindingElement &&
|
||||
updates?.suggestedBinding?.id !== endBindingElement.id // The start point is not hovering the end bindable + it's binding gap
|
||||
updates?.suggestedBinding?.element.id !== endBindingElement.id // The start point is not hovering the end bindable + it's binding gap
|
||||
? endBindingElement
|
||||
: null;
|
||||
|
||||
@@ -631,6 +636,7 @@ export class LinearElementEditor {
|
||||
altFocusPointBindableElement,
|
||||
"start",
|
||||
elementsMap,
|
||||
app.state.zoom,
|
||||
)
|
||||
: linearElementEditor.initialState.altFocusPoint,
|
||||
},
|
||||
@@ -2080,6 +2086,7 @@ const pointDraggingUpdates = (
|
||||
app: AppClassProperties,
|
||||
angleLocked: boolean,
|
||||
altKey: boolean,
|
||||
linearElementEditor: LinearElementEditor,
|
||||
): {
|
||||
positions: PointsPositionUpdates;
|
||||
updates?: PointMoveOtherUpdates;
|
||||
@@ -2127,13 +2134,27 @@ const pointDraggingUpdates = (
|
||||
);
|
||||
|
||||
if (isElbowArrow(element)) {
|
||||
const suggestedBindingElement = startIsDragged
|
||||
? start.element
|
||||
: endIsDragged
|
||||
? end.element
|
||||
: null;
|
||||
|
||||
return {
|
||||
positions: naiveDraggingPoints,
|
||||
updates: {
|
||||
suggestedBinding: startIsDragged
|
||||
? start.element
|
||||
: endIsDragged
|
||||
? end.element
|
||||
suggestedBinding: suggestedBindingElement
|
||||
? {
|
||||
element: suggestedBindingElement,
|
||||
midPoint: snapToMid(
|
||||
suggestedBindingElement,
|
||||
elementsMap,
|
||||
pointFrom<GlobalPoint>(
|
||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||
),
|
||||
),
|
||||
}
|
||||
: null,
|
||||
},
|
||||
};
|
||||
@@ -2226,7 +2247,20 @@ const pointDraggingUpdates = (
|
||||
(updates.startBinding.mode === "orbit" ||
|
||||
!getFeatureFlag("COMPLEX_BINDINGS"))
|
||||
) {
|
||||
updates.suggestedBinding = start.element;
|
||||
updates.suggestedBinding = start.element
|
||||
? {
|
||||
element: start.element,
|
||||
midPoint: getSnapOutlineMidPoint(
|
||||
pointFrom<GlobalPoint>(
|
||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||
),
|
||||
start.element,
|
||||
elementsMap,
|
||||
app.state.zoom,
|
||||
),
|
||||
}
|
||||
: null;
|
||||
}
|
||||
} else if (startIsDragged) {
|
||||
updates.suggestedBinding = app.state.suggestedBinding;
|
||||
@@ -2252,7 +2286,20 @@ const pointDraggingUpdates = (
|
||||
(updates.endBinding.mode === "orbit" ||
|
||||
!getFeatureFlag("COMPLEX_BINDINGS"))
|
||||
) {
|
||||
updates.suggestedBinding = end.element;
|
||||
updates.suggestedBinding = end.element
|
||||
? {
|
||||
element: end.element,
|
||||
midPoint: getSnapOutlineMidPoint(
|
||||
pointFrom<GlobalPoint>(
|
||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||
),
|
||||
end.element,
|
||||
elementsMap,
|
||||
app.state.zoom,
|
||||
),
|
||||
}
|
||||
: null;
|
||||
}
|
||||
} else if (endIsDragged) {
|
||||
updates.suggestedBinding = app.state.suggestedBinding;
|
||||
|
||||
+119
-25
@@ -7,6 +7,7 @@ import {
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
bezierEquation,
|
||||
curve,
|
||||
curveCatmullRomCubicApproxPoints,
|
||||
curveOffsetPoints,
|
||||
@@ -27,19 +28,25 @@ import {
|
||||
|
||||
import type { Curve, LineSegment, LocalPoint } from "@excalidraw/math";
|
||||
|
||||
import type { NormalizedZoomValue, Zoom } from "@excalidraw/excalidraw/types";
|
||||
import type {
|
||||
AppState,
|
||||
NormalizedZoomValue,
|
||||
Zoom,
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
|
||||
import { elementCenterPoint, getDiamondPoints } from "./bounds";
|
||||
|
||||
import { generateLinearCollisionShape } from "./shape";
|
||||
|
||||
import { isPointInElement } from "./collision";
|
||||
import { hitElementItself, isPointInElement } from "./collision";
|
||||
import { LinearElementEditor } from "./linearElementEditor";
|
||||
import { isRectangularElement } from "./typeChecks";
|
||||
import { maxBindingDistance_simple } from "./binding";
|
||||
|
||||
import type {
|
||||
ElementsMap,
|
||||
ExcalidrawArrowElement,
|
||||
ExcalidrawBindableElement,
|
||||
ExcalidrawDiamondElement,
|
||||
ExcalidrawElement,
|
||||
ExcalidrawFreeDrawElement,
|
||||
@@ -329,24 +336,10 @@ export function deconstructRectanguloidElement(
|
||||
return shape;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the **unrotated** 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 **unrotated** segments (0) and curves (1)
|
||||
*/
|
||||
export function deconstructDiamondElement(
|
||||
export function getDiamondBaseCorners(
|
||||
element: ExcalidrawDiamondElement,
|
||||
offset: number = 0,
|
||||
): [LineSegment<GlobalPoint>[], Curve<GlobalPoint>[]] {
|
||||
const cachedShape = getElementShapesCacheEntry(element, offset);
|
||||
|
||||
if (cachedShape) {
|
||||
return cachedShape;
|
||||
}
|
||||
|
||||
): Curve<GlobalPoint>[] {
|
||||
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
|
||||
getDiamondPoints(element);
|
||||
const verticalRadius = element.roundness
|
||||
@@ -363,7 +356,7 @@ export function deconstructDiamondElement(
|
||||
pointFrom(element.x + leftX, element.y + leftY),
|
||||
];
|
||||
|
||||
const baseCorners = [
|
||||
return [
|
||||
curve(
|
||||
pointFrom<GlobalPoint>(
|
||||
right[0] - verticalRadius,
|
||||
@@ -413,6 +406,27 @@ export function deconstructDiamondElement(
|
||||
),
|
||||
), // TOP
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the **unrotated** 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 **unrotated** segments (0) and curves (1)
|
||||
*/
|
||||
export function deconstructDiamondElement(
|
||||
element: ExcalidrawDiamondElement,
|
||||
offset: number = 0,
|
||||
): [LineSegment<GlobalPoint>[], Curve<GlobalPoint>[]] {
|
||||
const cachedShape = getElementShapesCacheEntry(element, offset);
|
||||
|
||||
if (cachedShape) {
|
||||
return cachedShape;
|
||||
}
|
||||
|
||||
const baseCorners = getDiamondBaseCorners(element, offset);
|
||||
|
||||
const corners = baseCorners.map(
|
||||
(corner) =>
|
||||
@@ -570,18 +584,94 @@ const getDiagonalsForBindableElement = (
|
||||
return [diagonalOne, diagonalTwo];
|
||||
};
|
||||
|
||||
export const getSnapOutlineMidPoint = (
|
||||
point: GlobalPoint,
|
||||
element: ExcalidrawBindableElement,
|
||||
elementsMap: ElementsMap,
|
||||
zoom: AppState["zoom"],
|
||||
) => {
|
||||
const center = elementCenterPoint(element, elementsMap);
|
||||
const sideMidpoints =
|
||||
element.type === "diamond"
|
||||
? getDiamondBaseCorners(element).map((curve) => {
|
||||
const point = bezierEquation(curve, 0.5);
|
||||
const rotatedPoint = pointRotateRads(point, center, element.angle);
|
||||
|
||||
return pointFrom<GlobalPoint>(rotatedPoint[0], rotatedPoint[1]);
|
||||
})
|
||||
: [
|
||||
// RIGHT midpoint
|
||||
pointRotateRads(
|
||||
pointFrom<GlobalPoint>(
|
||||
element.x + element.width,
|
||||
element.y + element.height / 2,
|
||||
),
|
||||
center,
|
||||
element.angle,
|
||||
),
|
||||
// BOTTOM midpoint
|
||||
pointRotateRads(
|
||||
pointFrom<GlobalPoint>(
|
||||
element.x + element.width / 2,
|
||||
element.y + element.height,
|
||||
),
|
||||
center,
|
||||
element.angle,
|
||||
),
|
||||
// LEFT midpoint
|
||||
pointRotateRads(
|
||||
pointFrom<GlobalPoint>(element.x, element.y + element.height / 2),
|
||||
center,
|
||||
element.angle,
|
||||
),
|
||||
// TOP midpoint
|
||||
pointRotateRads(
|
||||
pointFrom<GlobalPoint>(element.x + element.width / 2, element.y),
|
||||
center,
|
||||
element.angle,
|
||||
),
|
||||
];
|
||||
const candidate = sideMidpoints.find(
|
||||
(midpoint) =>
|
||||
pointDistance(point, midpoint) <=
|
||||
maxBindingDistance_simple(zoom) + element.strokeWidth / 2 &&
|
||||
!hitElementItself({
|
||||
point,
|
||||
element,
|
||||
threshold: 0,
|
||||
elementsMap,
|
||||
overrideShouldTestInside: true,
|
||||
}),
|
||||
);
|
||||
|
||||
return candidate;
|
||||
};
|
||||
|
||||
export const projectFixedPointOntoDiagonal = (
|
||||
arrow: ExcalidrawArrowElement,
|
||||
point: GlobalPoint,
|
||||
element: ExcalidrawElement,
|
||||
element: ExcalidrawBindableElement,
|
||||
startOrEnd: "start" | "end",
|
||||
elementsMap: ElementsMap,
|
||||
zoom: AppState["zoom"],
|
||||
): GlobalPoint | null => {
|
||||
invariant(arrow.points.length >= 2, "Arrow must have at least two points");
|
||||
if (arrow.width < 3 && arrow.height < 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sideMidPoint = getSnapOutlineMidPoint(
|
||||
point,
|
||||
element,
|
||||
elementsMap,
|
||||
zoom,
|
||||
);
|
||||
if (sideMidPoint) {
|
||||
return sideMidPoint;
|
||||
}
|
||||
|
||||
// Do the projection onto the diagonals (or center lines
|
||||
// for non-rectangular shapes)
|
||||
const [diagonalOne, diagonalTwo] = getDiagonalsForBindableElement(
|
||||
element,
|
||||
elementsMap,
|
||||
@@ -603,18 +693,22 @@ export const projectFixedPointOntoDiagonal = (
|
||||
),
|
||||
a,
|
||||
);
|
||||
const intersector = lineSegment<GlobalPoint>(point, b);
|
||||
const intersector = lineSegment<GlobalPoint>(b, a);
|
||||
const p1 = lineSegmentIntersectionPoints(diagonalOne, intersector);
|
||||
const p2 = lineSegmentIntersectionPoints(diagonalTwo, intersector);
|
||||
const d1 = p1 && pointDistance(a, p1);
|
||||
const d2 = p2 && pointDistance(a, p2);
|
||||
|
||||
let p = null;
|
||||
let projection = null;
|
||||
if (d1 != null && d2 != null) {
|
||||
p = d1 < d2 ? p1 : p2;
|
||||
projection = d1 < d2 ? p1 : p2;
|
||||
} else {
|
||||
p = p1 || p2 || null;
|
||||
projection = p1 || p2 || null;
|
||||
}
|
||||
|
||||
return p && isPointInElement(p, element, elementsMap) ? p : null;
|
||||
if (projection && isPointInElement(projection, element, elementsMap)) {
|
||||
return projection;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user