Fix intersection shape tracking for rectangular + diamond

feat/remove-ga
Mark Tolmacs 1 month ago
parent 9b6e76aeb5
commit f425101604
No known key found for this signature in database

@ -68,6 +68,7 @@ import {
pointFromVector,
vectorScale,
vectorNormalize,
vectorRotate,
} from "../../math";
import { distanceToBindableElement } from "./distance";
import { intersectElementWithLine } from "./collision";
@ -1953,6 +1954,3 @@ export const normalizeFixedPoint = <T extends FixedPoint | null>(
}
return fixedPoint as any as T extends null ? null : FixedPoint;
};
function vectorRotate(arg0: any, arg1: any): import("../../math").Vector {
throw new Error("Function not implemented.");
}

@ -6,7 +6,7 @@ import type {
ExcalidrawRectangleElement,
ExcalidrawRectanguloidElement,
} from "./types";
import { getElementBounds } from "./bounds";
import { getDiamondPoints, getElementBounds } from "./bounds";
import type { FrameNameBounds } from "../types";
import type { GeometricShape } from "../../utils/geometry/shape";
import { getPolygonShape } from "../../utils/geometry/shape";
@ -20,7 +20,6 @@ import {
} from "./typeChecks";
import { getBoundTextShape, getCornerRadius, isPathALoop } from "../shapes";
import type {
Arc,
GlobalPoint,
Line,
LocalPoint,
@ -30,6 +29,8 @@ import type {
import {
arc,
arcLineInterceptPoints,
curve,
curveIntersectLine,
isPointWithinBounds,
line,
lineSegment,
@ -41,6 +42,12 @@ import {
rectangle,
} from "../../math";
import { ellipse, ellipseLineIntersectionPoints } from "../../math/ellipse";
import {
debugClear,
debugDrawArc,
debugDrawCubicBezier,
debugDrawLine,
} from "../visualdebug";
export const shouldTestInside = (element: ExcalidrawElement) => {
if (element.type === "arrow") {
@ -209,7 +216,7 @@ const intersectRectanguloidWithLine = (
element,
);
const sideIntersections: GlobalPoint[] = [
const sides = [
lineSegment<GlobalPoint>(
pointFrom<GlobalPoint>(r[0][0] + roundness, r[0][1]),
pointFrom<GlobalPoint>(r[1][0] - roundness, r[0][1]),
@ -226,25 +233,20 @@ const intersectRectanguloidWithLine = (
pointFrom<GlobalPoint>(r[0][0], r[1][1] - roundness),
pointFrom<GlobalPoint>(r[0][0], r[0][1] + roundness),
),
]
.map((s) =>
lineSegmentIntersectionPoints(line<GlobalPoint>(rotatedA, rotatedB), s),
)
.filter((x) => x != null)
.map((j) => pointRotateRads<GlobalPoint>(j!, center, element.angle));
const cornerIntersections: GlobalPoint[] =
];
const corners =
roundness > 0
? [
arc<GlobalPoint>(
pointFrom(r[0][0] + roundness, r[0][1] + roundness),
roundness,
((3 / 4) * Math.PI) as Radians,
0 as Radians,
Math.PI as Radians,
((3 / 2) * Math.PI) as Radians,
),
arc<GlobalPoint>(
pointFrom(r[1][0] - roundness, r[0][1] + roundness),
roundness,
((3 / 4) * Math.PI) as Radians,
((3 / 2) * Math.PI) as Radians,
0 as Radians,
),
arc<GlobalPoint>(
@ -260,11 +262,24 @@ const intersectRectanguloidWithLine = (
Math.PI as Radians,
),
]
.flatMap((t) => arcLineInterceptPoints(t, line(rotatedA, rotatedB)))
.filter((i) => i != null)
.map((j) => pointRotateRads(j, center, element.angle))
: [];
debugClear();
sides.forEach((s) => debugDrawLine(s, { color: "red", permanent: true }));
corners.forEach((s) => debugDrawArc(s, { color: "green", permanent: true }));
const sideIntersections: GlobalPoint[] = sides
.map((s) =>
lineSegmentIntersectionPoints(line<GlobalPoint>(rotatedA, rotatedB), s),
)
.filter((x) => x != null)
.map((j) => pointRotateRads<GlobalPoint>(j!, center, element.angle));
const cornerIntersections: GlobalPoint[] = corners
.flatMap((t) => arcLineInterceptPoints(t, line(rotatedA, rotatedB)))
.filter((i) => i != null)
.map((j) => pointRotateRads(j, center, element.angle));
return (
[...sideIntersections, ...cornerIntersections]
// Remove duplicates
@ -287,105 +302,73 @@ const intersectDiamondWithLine = (
l: Line<GlobalPoint>,
offset: number = 0,
): GlobalPoint[] => {
const top = pointFrom<GlobalPoint>(
element.x + element.width / 2,
element.y - offset,
);
const right = pointFrom<GlobalPoint>(
element.x + element.width + offset,
element.y + element.height / 2,
);
const bottom = pointFrom<GlobalPoint>(
element.x + element.width / 2,
element.y + element.height + offset,
);
const left = pointFrom<GlobalPoint>(
element.x - offset,
element.y + element.height / 2,
);
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
getDiamondPoints(element);
const center = pointFrom<GlobalPoint>(
element.x + element.width / 2,
element.y + element.height / 2,
);
const verticalRadius = getCornerRadius(Math.abs(top[0] - left[0]), element);
const horizontalRadius = getCornerRadius(
Math.abs(right[1] - top[1]),
element,
(topX + bottomX) / 2,
(topY + bottomY) / 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),
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<GlobalPoint>(
pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius),
pointFrom(right[0] - verticalRadius, right[1] - horizontalRadius),
pointFrom(top[0] + horizontalRadius, top[1] + verticalRadius),
pointFrom(right[0] - horizontalRadius, right[1] - verticalRadius),
);
const bottomRight = lineSegment<GlobalPoint>(
pointFrom(bottom[0] + verticalRadius, bottom[1] - horizontalRadius),
pointFrom(right[0] - verticalRadius, right[1] + horizontalRadius),
pointFrom(bottom[0] + horizontalRadius, bottom[1] - verticalRadius),
pointFrom(right[0] - horizontalRadius, right[1] + verticalRadius),
);
const bottomLeft = lineSegment<GlobalPoint>(
pointFrom(bottom[0] - verticalRadius, bottom[1] - horizontalRadius),
pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius),
pointFrom(bottom[0] - horizontalRadius, bottom[1] - verticalRadius),
pointFrom(left[0] + horizontalRadius, left[1] + verticalRadius),
);
const topLeft = lineSegment<GlobalPoint>(
pointFrom(top[0] - verticalRadius, top[1] + horizontalRadius),
pointFrom(left[0] + verticalRadius, left[1] - horizontalRadius),
pointFrom(top[0] - horizontalRadius, top[1] + verticalRadius),
pointFrom(left[0] + horizontalRadius, left[1] - verticalRadius),
);
const arcs: Arc<GlobalPoint>[] = element.roundness
const curves = element.roundness
? [
createDiamondArc(
topLeft[0],
topRight[0],
pointFrom(
top[0],
top[1] + Math.sqrt(2 * Math.pow(verticalRadius, 2)) - offset,
),
verticalRadius,
), // TOP
createDiamondArc(
topRight[1],
bottomRight[1],
pointFrom(
right[0] - Math.sqrt(2 * Math.pow(horizontalRadius, 2)) + offset,
right[1],
),
horizontalRadius,
), // RIGHT
createDiamondArc(
bottomRight[0],
bottomLeft[0],
pointFrom(
bottom[0],
bottom[1] - Math.sqrt(2 * Math.pow(verticalRadius, 2)) + offset,
),
verticalRadius,
), // BOTTOM
createDiamondArc(
bottomLeft[1],
topLeft[1],
pointFrom(
left[0] + Math.sqrt(2 * Math.pow(horizontalRadius, 2)) - offset,
left[1],
),
horizontalRadius,
), // LEFT
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
]
: [];
debugClear();
[topRight, bottomRight, bottomLeft, topLeft].forEach((s) => {
debugDrawLine(s, { color: "red", permanent: true });
});
curves.forEach((s) => {
debugDrawCubicBezier(s, { color: "green", permanent: true });
});
const sides: GlobalPoint[] = [topRight, bottomRight, bottomLeft, topLeft]
.map((s) =>
lineSegmentIntersectionPoints(line<GlobalPoint>(rotatedA, rotatedB), s),
)
.filter((x) => x != null)
.filter((p): p is GlobalPoint => p != null)
// Rotate back intersection points
.map((p) => pointRotateRads<GlobalPoint>(p!, center, element.angle));
const corners = arcs
.flatMap((x) => arcLineInterceptPoints(x, line(rotatedA, rotatedB)))
.filter((x) => x != null)
const corners = curves
.flatMap((p) => curveIntersectLine(p, line(rotatedA, rotatedB)))
.filter((p) => p != null)
// Rotate back intersection points
.map((p) => pointRotateRads(p, center, element.angle));

@ -2,6 +2,8 @@ import type { GlobalPoint, Radians } from "../../math";
import {
arc,
arcDistanceFromPoint,
curve,
curvePointDistance,
distanceToLineSegment,
lineSegment,
pointFrom,
@ -10,7 +12,7 @@ import {
} from "../../math";
import { ellipse, ellipseDistanceFromPoint } from "../../math/ellipse";
import { getCornerRadius } from "../shapes";
import { createDiamondArc, createDiamondSide } from "./bounds";
import { getDiamondPoints } from "./bounds";
import type {
ExcalidrawBindableElement,
ExcalidrawDiamondElement,
@ -147,53 +149,31 @@ export const distanceToDiamondElement = (
pointFrom(element.x + leftX, element.y + leftY),
];
const topRight = createDiamondSide(
lineSegment(top, right),
verticalRadius,
horizontalRadius,
// Create the line segment parts of the diamond
// NOTE: Horizontal and vertical seems to be flipped here
const topRight = lineSegment<GlobalPoint>(
pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius),
pointFrom(right[0] + verticalRadius, right[1] + horizontalRadius),
);
const bottomRight = createDiamondSide(
lineSegment(bottom, right),
verticalRadius,
horizontalRadius,
const bottomRight = lineSegment<GlobalPoint>(
pointFrom(bottom[0] + verticalRadius, bottom[1] + horizontalRadius),
pointFrom(right[0] + verticalRadius, right[1] + horizontalRadius),
);
const bottomLeft = createDiamondSide(
lineSegment(bottom, left),
verticalRadius,
horizontalRadius,
const bottomLeft = lineSegment<GlobalPoint>(
pointFrom(bottom[0] + verticalRadius, bottom[1] + horizontalRadius),
pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius),
);
const topLeft = createDiamondSide(
lineSegment(top, left),
verticalRadius,
horizontalRadius,
const topLeft = lineSegment<GlobalPoint>(
pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius),
pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius),
);
const arcs = element.roundness
const curves = element.roundness
? [
createDiamondArc(
topLeft[0],
topRight[0],
pointFrom(top[0], top[1] + verticalRadius),
verticalRadius,
), // TOP
createDiamondArc(
topRight[1],
bottomRight[1],
pointFrom(right[0] - horizontalRadius, right[1]),
horizontalRadius,
), // RIGHT
createDiamondArc(
bottomRight[0],
bottomLeft[0],
pointFrom(bottom[0], bottom[1] - verticalRadius),
verticalRadius,
), // BOTTOM
createDiamondArc(
bottomLeft[1],
topLeft[1],
pointFrom(right[0] + horizontalRadius, right[1]),
horizontalRadius,
), // LEFT
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
]
: [];
@ -202,7 +182,7 @@ export const distanceToDiamondElement = (
...[topRight, bottomRight, bottomLeft, topLeft].map((s) =>
distanceToLineSegment(rotatedPoint, s),
),
...arcs.map((a) => arcDistanceFromPoint(a, rotatedPoint)),
...curves.map((a) => curvePointDistance(a, rotatedPoint)),
],
);
};

@ -19,7 +19,6 @@ import { invariant, isAnyTrue, toBrandedType, tupleToCoors } from "../utils";
import type { AppState } from "../types";
import {
bindPointToSnapToElementOutline,
distanceToBindableElement,
avoidRectangularCorner,
FIXED_BINDING_DISTANCE,
getHeadingForElbowArrowSnap,
@ -55,6 +54,7 @@ import type {
FixedPointBinding,
FixedSegment,
} from "./types";
import { distanceToBindableElement } from "./distance";
type GridAddress = [number, number] & { _brand: "gridaddress" };
@ -2164,7 +2164,7 @@ const getGlobalPoint = (
// NOTE: Resize scales the binding position point too, so we need to update it
return Math.abs(
distanceToBindableElement(boundElement, fixedGlobalPoint, elementsMap) -
distanceToBindableElement(boundElement, fixedGlobalPoint) -
FIXED_BINDING_DISTANCE,
) > 0.01
? getSnapPoint(initialPoint, otherPoint, boundElement, elementsMap)
@ -2201,9 +2201,12 @@ const getBindPointHeading = (
hoveredElement &&
aabbForElement(
hoveredElement,
Array(4).fill(
distanceToBindableElement(hoveredElement, p, elementsMap),
) as [number, number, number, number],
Array(4).fill(distanceToBindableElement(hoveredElement, p)) as [
number,
number,
number,
number,
],
),
elementsMap,
origPoint,

@ -78,7 +78,7 @@ export function curveIntersectLine<Point extends GlobalPoint | LocalPoint>(
return x;
})
.filter((x) => x !== null);
.filter((x): x is Point => x !== null);
}
/*
@ -244,7 +244,7 @@ export default function isCurve<P extends GlobalPoint | LocalPoint>(
): v is Curve<P> {
return (
Array.isArray(v) &&
v.length !== 4 &&
v.length === 4 &&
isPoint(v[0]) &&
isPoint(v[1]) &&
isPoint(v[2]) &&

@ -1,4 +1,4 @@
import type { GlobalPoint, LocalPoint, Vector } from "./types";
import type { GlobalPoint, LocalPoint, Radians, Vector } from "./types";
/**
* Create a vector from the x and y coordiante elements.
@ -140,6 +140,19 @@ export const vectorNormalize = (v: Vector): Vector => {
return vector(v[0] / m, v[1] / m);
};
/**
* Rotate a vector by the given radians
* @param v Target vector
* @param a Angle to rotate in radians
* @returns The rotated vector
*/
export const vectorRotate = (v: Vector, a: Radians): Vector => {
const cos = Math.cos(a);
const sin = Math.sin(a);
return vector(v[0] * cos - v[1] * sin, v[0] * sin + v[1] * cos);
};
/**
* Project the first vector onto the second vector
*/

Loading…
Cancel
Save