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

@ -2,6 +2,8 @@ import type { GlobalPoint, Radians } from "../../math";
import { import {
arc, arc,
arcDistanceFromPoint, arcDistanceFromPoint,
curve,
curvePointDistance,
distanceToLineSegment, distanceToLineSegment,
lineSegment, lineSegment,
pointFrom, pointFrom,
@ -10,7 +12,7 @@ import {
} from "../../math"; } from "../../math";
import { ellipse, ellipseDistanceFromPoint } from "../../math/ellipse"; import { ellipse, ellipseDistanceFromPoint } from "../../math/ellipse";
import { getCornerRadius } from "../shapes"; import { getCornerRadius } from "../shapes";
import { createDiamondArc, createDiamondSide } from "./bounds"; import { getDiamondPoints } from "./bounds";
import type { import type {
ExcalidrawBindableElement, ExcalidrawBindableElement,
ExcalidrawDiamondElement, ExcalidrawDiamondElement,
@ -147,53 +149,31 @@ export const distanceToDiamondElement = (
pointFrom(element.x + leftX, element.y + leftY), pointFrom(element.x + leftX, element.y + leftY),
]; ];
const topRight = createDiamondSide( // Create the line segment parts of the diamond
lineSegment(top, right), // NOTE: Horizontal and vertical seems to be flipped here
verticalRadius, const topRight = lineSegment<GlobalPoint>(
horizontalRadius, pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius),
pointFrom(right[0] + verticalRadius, right[1] + horizontalRadius),
); );
const bottomRight = createDiamondSide( const bottomRight = lineSegment<GlobalPoint>(
lineSegment(bottom, right), pointFrom(bottom[0] + verticalRadius, bottom[1] + horizontalRadius),
verticalRadius, pointFrom(right[0] + verticalRadius, right[1] + horizontalRadius),
horizontalRadius,
); );
const bottomLeft = createDiamondSide( const bottomLeft = lineSegment<GlobalPoint>(
lineSegment(bottom, left), pointFrom(bottom[0] + verticalRadius, bottom[1] + horizontalRadius),
verticalRadius, pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius),
horizontalRadius,
); );
const topLeft = createDiamondSide( const topLeft = lineSegment<GlobalPoint>(
lineSegment(top, left), pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius),
verticalRadius, pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius),
horizontalRadius,
); );
const arcs = element.roundness const curves = element.roundness
? [ ? [
createDiamondArc( curve(topRight[1], right, right, bottomRight[1]), // RIGHT
topLeft[0], curve(bottomRight[0], bottom, bottom, bottomLeft[0]), // BOTTOM
topRight[0], curve(bottomLeft[1], left, left, topLeft[1]), // LEFT
pointFrom(top[0], top[1] + verticalRadius), curve(topLeft[0], top, top, topRight[0]), // LEFT
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
] ]
: []; : [];
@ -202,7 +182,7 @@ export const distanceToDiamondElement = (
...[topRight, bottomRight, bottomLeft, topLeft].map((s) => ...[topRight, bottomRight, bottomLeft, topLeft].map((s) =>
distanceToLineSegment(rotatedPoint, 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 type { AppState } from "../types";
import { import {
bindPointToSnapToElementOutline, bindPointToSnapToElementOutline,
distanceToBindableElement,
avoidRectangularCorner, avoidRectangularCorner,
FIXED_BINDING_DISTANCE, FIXED_BINDING_DISTANCE,
getHeadingForElbowArrowSnap, getHeadingForElbowArrowSnap,
@ -55,6 +54,7 @@ import type {
FixedPointBinding, FixedPointBinding,
FixedSegment, FixedSegment,
} from "./types"; } from "./types";
import { distanceToBindableElement } from "./distance";
type GridAddress = [number, number] & { _brand: "gridaddress" }; 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 // NOTE: Resize scales the binding position point too, so we need to update it
return Math.abs( return Math.abs(
distanceToBindableElement(boundElement, fixedGlobalPoint, elementsMap) - distanceToBindableElement(boundElement, fixedGlobalPoint) -
FIXED_BINDING_DISTANCE, FIXED_BINDING_DISTANCE,
) > 0.01 ) > 0.01
? getSnapPoint(initialPoint, otherPoint, boundElement, elementsMap) ? getSnapPoint(initialPoint, otherPoint, boundElement, elementsMap)
@ -2201,9 +2201,12 @@ const getBindPointHeading = (
hoveredElement && hoveredElement &&
aabbForElement( aabbForElement(
hoveredElement, hoveredElement,
Array(4).fill( Array(4).fill(distanceToBindableElement(hoveredElement, p)) as [
distanceToBindableElement(hoveredElement, p, elementsMap), number,
) as [number, number, number, number], number,
number,
number,
],
), ),
elementsMap, elementsMap,
origPoint, origPoint,

@ -78,7 +78,7 @@ export function curveIntersectLine<Point extends GlobalPoint | LocalPoint>(
return x; 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> { ): v is Curve<P> {
return ( return (
Array.isArray(v) && Array.isArray(v) &&
v.length !== 4 && v.length === 4 &&
isPoint(v[0]) && isPoint(v[0]) &&
isPoint(v[1]) && isPoint(v[1]) &&
isPoint(v[2]) && 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. * 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); 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 * Project the first vector onto the second vector
*/ */

Loading…
Cancel
Save