mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-04-14 16:40:58 -04:00
fix: Rounded diamond edge elbow arrow U route (#9349)
Some checks failed
Tests / test (push) Successful in 4m54s
Auto release excalidraw next / Auto-release-excalidraw-next (push) Successful in 2m15s
Build Docker image / build-docker (push) Failing after 8s
Cancel previous runs / cancel (push) Failing after 2s
Publish Docker / publish-docker (push) Failing after 27s
New Sentry production release / sentry (push) Failing after 2m58s
Some checks failed
Tests / test (push) Successful in 4m54s
Auto release excalidraw next / Auto-release-excalidraw-next (push) Successful in 2m15s
Build Docker image / build-docker (push) Failing after 8s
Cancel previous runs / cancel (push) Failing after 2s
Publish Docker / publish-docker (push) Failing after 27s
New Sentry production release / sentry (push) Failing after 2m58s
This commit is contained in:
parent
c2caf78e95
commit
e48b63a0ae
1 changed files with 166 additions and 89 deletions
|
@ -1,11 +1,15 @@
|
|||
import { invariant, isDevEnv, isTestEnv } from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
normalizeRadians,
|
||||
pointFrom,
|
||||
pointFromVector,
|
||||
pointRotateRads,
|
||||
pointScaleFromOrigin,
|
||||
radiansToDegrees,
|
||||
pointsEqual,
|
||||
triangleIncludesPoint,
|
||||
vectorCross,
|
||||
vectorFromPoint,
|
||||
vectorScale,
|
||||
} from "@excalidraw/math";
|
||||
|
||||
import type {
|
||||
|
@ -13,7 +17,6 @@ import type {
|
|||
GlobalPoint,
|
||||
Triangle,
|
||||
Vector,
|
||||
Radians,
|
||||
} from "@excalidraw/math";
|
||||
|
||||
import { getCenterForBounds, type Bounds } from "./bounds";
|
||||
|
@ -26,24 +29,6 @@ export const HEADING_LEFT = [-1, 0] as Heading;
|
|||
export const HEADING_UP = [0, -1] as Heading;
|
||||
export type Heading = [1, 0] | [0, 1] | [-1, 0] | [0, -1];
|
||||
|
||||
export const headingForDiamond = <Point extends GlobalPoint | LocalPoint>(
|
||||
a: Point,
|
||||
b: Point,
|
||||
) => {
|
||||
const angle = radiansToDegrees(
|
||||
normalizeRadians(Math.atan2(b[1] - a[1], b[0] - a[0]) as Radians),
|
||||
);
|
||||
|
||||
if (angle >= 315 || angle < 45) {
|
||||
return HEADING_UP;
|
||||
} else if (angle >= 45 && angle < 135) {
|
||||
return HEADING_RIGHT;
|
||||
} else if (angle >= 135 && angle < 225) {
|
||||
return HEADING_DOWN;
|
||||
}
|
||||
return HEADING_LEFT;
|
||||
};
|
||||
|
||||
export const vectorToHeading = (vec: Vector): Heading => {
|
||||
const [x, y] = vec;
|
||||
const absX = Math.abs(x);
|
||||
|
@ -76,6 +61,165 @@ export const headingIsHorizontal = (a: Heading) =>
|
|||
|
||||
export const headingIsVertical = (a: Heading) => !headingIsHorizontal(a);
|
||||
|
||||
const headingForPointFromDiamondElement = (
|
||||
element: Readonly<ExcalidrawBindableElement>,
|
||||
aabb: Readonly<Bounds>,
|
||||
point: Readonly<GlobalPoint>,
|
||||
): Heading => {
|
||||
const midPoint = getCenterForBounds(aabb);
|
||||
|
||||
if (isDevEnv() || isTestEnv()) {
|
||||
invariant(
|
||||
element.width > 0 && element.height > 0,
|
||||
"Diamond element has no width or height",
|
||||
);
|
||||
invariant(
|
||||
!pointsEqual(midPoint, point),
|
||||
"The point is too close to the element mid point to determine heading",
|
||||
);
|
||||
}
|
||||
|
||||
const SHRINK = 0.95; // Rounded elements tolerance
|
||||
const top = pointFromVector(
|
||||
vectorScale(
|
||||
vectorFromPoint(
|
||||
pointRotateRads(
|
||||
pointFrom<GlobalPoint>(element.x + element.width / 2, element.y),
|
||||
midPoint,
|
||||
element.angle,
|
||||
),
|
||||
midPoint,
|
||||
),
|
||||
SHRINK,
|
||||
),
|
||||
midPoint,
|
||||
);
|
||||
const right = pointFromVector(
|
||||
vectorScale(
|
||||
vectorFromPoint(
|
||||
pointRotateRads(
|
||||
pointFrom<GlobalPoint>(
|
||||
element.x + element.width,
|
||||
element.y + element.height / 2,
|
||||
),
|
||||
midPoint,
|
||||
element.angle,
|
||||
),
|
||||
midPoint,
|
||||
),
|
||||
SHRINK,
|
||||
),
|
||||
midPoint,
|
||||
);
|
||||
const bottom = pointFromVector(
|
||||
vectorScale(
|
||||
vectorFromPoint(
|
||||
pointRotateRads(
|
||||
pointFrom<GlobalPoint>(
|
||||
element.x + element.width / 2,
|
||||
element.y + element.height,
|
||||
),
|
||||
midPoint,
|
||||
element.angle,
|
||||
),
|
||||
midPoint,
|
||||
),
|
||||
SHRINK,
|
||||
),
|
||||
midPoint,
|
||||
);
|
||||
const left = pointFromVector(
|
||||
vectorScale(
|
||||
vectorFromPoint(
|
||||
pointRotateRads(
|
||||
pointFrom<GlobalPoint>(element.x, element.y + element.height / 2),
|
||||
midPoint,
|
||||
element.angle,
|
||||
),
|
||||
midPoint,
|
||||
),
|
||||
SHRINK,
|
||||
),
|
||||
midPoint,
|
||||
);
|
||||
|
||||
// Corners
|
||||
if (
|
||||
vectorCross(vectorFromPoint(point, top), vectorFromPoint(top, right)) <=
|
||||
0 &&
|
||||
vectorCross(vectorFromPoint(point, top), vectorFromPoint(top, left)) > 0
|
||||
) {
|
||||
return headingForPoint(top, midPoint);
|
||||
} else if (
|
||||
vectorCross(
|
||||
vectorFromPoint(point, right),
|
||||
vectorFromPoint(right, bottom),
|
||||
) <= 0 &&
|
||||
vectorCross(vectorFromPoint(point, right), vectorFromPoint(right, top)) > 0
|
||||
) {
|
||||
return headingForPoint(right, midPoint);
|
||||
} else if (
|
||||
vectorCross(
|
||||
vectorFromPoint(point, bottom),
|
||||
vectorFromPoint(bottom, left),
|
||||
) <= 0 &&
|
||||
vectorCross(
|
||||
vectorFromPoint(point, bottom),
|
||||
vectorFromPoint(bottom, right),
|
||||
) > 0
|
||||
) {
|
||||
return headingForPoint(bottom, midPoint);
|
||||
} else if (
|
||||
vectorCross(vectorFromPoint(point, left), vectorFromPoint(left, top)) <=
|
||||
0 &&
|
||||
vectorCross(vectorFromPoint(point, left), vectorFromPoint(left, bottom)) > 0
|
||||
) {
|
||||
return headingForPoint(left, midPoint);
|
||||
}
|
||||
|
||||
// Sides
|
||||
if (
|
||||
vectorCross(
|
||||
vectorFromPoint(point, midPoint),
|
||||
vectorFromPoint(top, midPoint),
|
||||
) <= 0 &&
|
||||
vectorCross(
|
||||
vectorFromPoint(point, midPoint),
|
||||
vectorFromPoint(right, midPoint),
|
||||
) > 0
|
||||
) {
|
||||
const p = element.width > element.height ? top : right;
|
||||
return headingForPoint(p, midPoint);
|
||||
} else if (
|
||||
vectorCross(
|
||||
vectorFromPoint(point, midPoint),
|
||||
vectorFromPoint(right, midPoint),
|
||||
) <= 0 &&
|
||||
vectorCross(
|
||||
vectorFromPoint(point, midPoint),
|
||||
vectorFromPoint(bottom, midPoint),
|
||||
) > 0
|
||||
) {
|
||||
const p = element.width > element.height ? bottom : right;
|
||||
return headingForPoint(p, midPoint);
|
||||
} else if (
|
||||
vectorCross(
|
||||
vectorFromPoint(point, midPoint),
|
||||
vectorFromPoint(bottom, midPoint),
|
||||
) <= 0 &&
|
||||
vectorCross(
|
||||
vectorFromPoint(point, midPoint),
|
||||
vectorFromPoint(left, midPoint),
|
||||
) > 0
|
||||
) {
|
||||
const p = element.width > element.height ? bottom : left;
|
||||
return headingForPoint(p, midPoint);
|
||||
}
|
||||
|
||||
const p = element.width > element.height ? top : left;
|
||||
return headingForPoint(p, midPoint);
|
||||
};
|
||||
|
||||
// Gets the heading for the point by creating a bounding box around the rotated
|
||||
// close fitting bounding box, then creating 4 search cones around the center of
|
||||
// the external bbox.
|
||||
|
@ -89,74 +233,7 @@ export const headingForPointFromElement = <Point extends GlobalPoint>(
|
|||
const midPoint = getCenterForBounds(aabb);
|
||||
|
||||
if (element.type === "diamond") {
|
||||
if (p[0] < element.x) {
|
||||
return HEADING_LEFT;
|
||||
} else if (p[1] < element.y) {
|
||||
return HEADING_UP;
|
||||
} else if (p[0] > element.x + element.width) {
|
||||
return HEADING_RIGHT;
|
||||
} else if (p[1] > element.y + element.height) {
|
||||
return HEADING_DOWN;
|
||||
}
|
||||
|
||||
const top = pointRotateRads(
|
||||
pointScaleFromOrigin(
|
||||
pointFrom(element.x + element.width / 2, element.y),
|
||||
midPoint,
|
||||
SEARCH_CONE_MULTIPLIER,
|
||||
),
|
||||
midPoint,
|
||||
element.angle,
|
||||
);
|
||||
const right = pointRotateRads(
|
||||
pointScaleFromOrigin(
|
||||
pointFrom(element.x + element.width, element.y + element.height / 2),
|
||||
midPoint,
|
||||
SEARCH_CONE_MULTIPLIER,
|
||||
),
|
||||
midPoint,
|
||||
element.angle,
|
||||
);
|
||||
const bottom = pointRotateRads(
|
||||
pointScaleFromOrigin(
|
||||
pointFrom(element.x + element.width / 2, element.y + element.height),
|
||||
midPoint,
|
||||
SEARCH_CONE_MULTIPLIER,
|
||||
),
|
||||
midPoint,
|
||||
element.angle,
|
||||
);
|
||||
const left = pointRotateRads(
|
||||
pointScaleFromOrigin(
|
||||
pointFrom(element.x, element.y + element.height / 2),
|
||||
midPoint,
|
||||
SEARCH_CONE_MULTIPLIER,
|
||||
),
|
||||
midPoint,
|
||||
element.angle,
|
||||
);
|
||||
|
||||
if (
|
||||
triangleIncludesPoint<Point>([top, right, midPoint] as Triangle<Point>, p)
|
||||
) {
|
||||
return headingForDiamond(top, right);
|
||||
} else if (
|
||||
triangleIncludesPoint<Point>(
|
||||
[right, bottom, midPoint] as Triangle<Point>,
|
||||
p,
|
||||
)
|
||||
) {
|
||||
return headingForDiamond(right, bottom);
|
||||
} else if (
|
||||
triangleIncludesPoint<Point>(
|
||||
[bottom, left, midPoint] as Triangle<Point>,
|
||||
p,
|
||||
)
|
||||
) {
|
||||
return headingForDiamond(bottom, left);
|
||||
}
|
||||
|
||||
return headingForDiamond(left, top);
|
||||
return headingForPointFromDiamondElement(element, aabb, p);
|
||||
}
|
||||
|
||||
const topLeft = pointScaleFromOrigin(
|
||||
|
|
Loading…
Add table
Reference in a new issue