Eliipse and rounded rectangle with one edge case

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
Mark Tolmacs 2024-07-28 22:35:42 +02:00
parent 946d88be36
commit b97aee6092
No known key found for this signature in database
2 changed files with 172 additions and 114 deletions

View File

@ -63,7 +63,11 @@ import {
rotatePoint,
vectorToHeading,
} from "../math";
import { debugDrawPoint } from "../visualdebug";
import {
debugDrawBounds,
debugDrawPoint,
debugDrawSegments,
} from "../visualdebug";
import {
interceptPointsOfLineAndEllipse,
interceptPointsOfSegmentAndRoundedRectangle,
@ -743,36 +747,35 @@ export const bindPointToSnapToElementOutline = (
bindableElement,
elementsMap,
);
debugDrawPoint(point, "green");
//debugDrawBounds(aabb);
//debugDrawPoint(point, "green");
if (heading) {
const headingIsVertical =
compareHeading(heading, HEADING_UP) ||
compareHeading(heading, HEADING_DOWN);
const intersections = _intersectElementWithLine(
bindableElement,
rotatePoint(
headingIsVertical
? [point[0], aabb[1] - bindableElement.height]
: [aabb[0] - bindableElement.width, point[1]],
center,
-bindableElement.angle,
),
rotatePoint(
headingIsVertical
? [point[0], aabb[3] + bindableElement.height]
: [aabb[2] + bindableElement.width, point[1]],
center,
-bindableElement.angle,
),
headingIsVertical
? [point[0], point[1] - 2 * bindableElement.height]
: [point[0] - 2 * bindableElement.width, point[1]],
headingIsVertical
? [point[0], point[1] + 2 * bindableElement.height]
: [point[0] + 2 * bindableElement.width, point[1]],
FIXED_BINDING_DISTANCE,
);
intersections.sort(
(a, b) => distanceSq2d(a, point) - distanceSq2d(b, point),
);
debugDrawSegments([
headingIsVertical
? [point[0], point[1] - 2 * bindableElement.height]
: [point[0] - 2 * bindableElement.width, point[1]],
headingIsVertical
? [point[0], point[1] + 2 * bindableElement.height]
: [point[0] + 2 * bindableElement.width, point[1]],
]);
if (intersections.length > 0) {
debugDrawPoint(intersections[0], "red");
intersections.forEach((point) => debugDrawPoint(point, "red"));
return intersections[0];
}
}
@ -825,10 +828,13 @@ export const _intersectElementWithLine = (
const halfHeight = element.height / 2;
return interceptPointsOfLineAndEllipse(
{
center: [element.x + element.width, element.y + element.height],
center: [
element.x + element.width / 2,
element.y + element.height / 2,
],
angle: element.angle,
halfWidth,
halfHeight,
halfWidth: halfWidth + gap,
halfHeight: halfHeight + gap,
},
[a, b] as LineSegment,
);

View File

@ -12,6 +12,10 @@ import {
scaleVector,
subtractVectors,
} from "../../excalidraw/math";
import {
debugDrawPoint,
debugDrawSegments,
} from "../../excalidraw/visualdebug";
import type {
Point,
Line,
@ -984,40 +988,6 @@ export const pointInEllipse = (point: Point, ellipse: Ellipse) => {
);
};
/**
* Returns the intersection point(s) of a line segment represented by a start
* point and end point and a symmetric arc.
*/
export const interceptOfSymmetricArcAndSegment = (
arc: CircularArc,
line: Readonly<LineSegment>,
): Point[] => {
const radius = distancePoints(arc.from, arc.center);
const [, fromAngle] = carthesian2Polar(arc.from, arc.center);
const [, toAngle] = carthesian2Polar(arc.to, arc.center);
return interceptPointsOfLineAndEllipse(
{
center: arc.center,
angle: 0,
halfHeight: radius,
halfWidth: radius,
},
line,
).filter((candidate) => {
const [candidateRadius, candidateAngle] = carthesian2Polar(
candidate,
arc.center,
);
return fromAngle < toAngle
? Math.abs(radius - candidateRadius) < 0.0000001 &&
fromAngle <= candidateAngle &&
toAngle >= candidateAngle
: fromAngle <= candidateAngle || toAngle >= candidateAngle;
});
};
/**
* Calculate a maximum of two intercept points for a line going throug an
* ellipse.
@ -1080,7 +1050,43 @@ export const interceptPointsOfLineAndEllipse = (
}
}
return intersections;
return intersections.map((point) =>
rotatePoint(point, ellipse.center, ellipse.angle),
);
};
/**
* Returns the intersection point(s) of a line segment represented by a start
* point and end point and a symmetric arc.
*/
export const interceptOfSymmetricArcAndSegment = (
arc: CircularArc,
line: Readonly<LineSegment>,
): Point[] => {
const radius = distancePoints(arc.from, arc.center);
const [, fromAngle] = carthesian2Polar(arc.from, arc.center);
const [, toAngle] = carthesian2Polar(arc.to, arc.center);
return interceptPointsOfLineAndEllipse(
{
center: arc.center,
angle: 0,
halfHeight: radius,
halfWidth: radius,
},
line,
).filter((candidate) => {
const [candidateRadius, candidateAngle] = carthesian2Polar(
candidate,
arc.center,
);
return fromAngle < toAngle
? Math.abs(radius - candidateRadius) < 0.0000001 &&
fromAngle <= candidateAngle &&
toAngle >= candidateAngle
: fromAngle <= candidateAngle || toAngle >= candidateAngle;
});
};
/**
@ -1167,10 +1173,8 @@ export const interceptPointsOfSegmentAndRoundedRectangle = (
center,
rectangle.angle,
);
const rotatedSegment = [
rotatePoint(segment[0], center, rectangle.angle),
rotatePoint(segment[1], center, rectangle.angle),
] as LineSegment;
debugDrawSegments([segment]);
const candidates = interceptPointsOfSegmentAndPolygon(
[
@ -1180,7 +1184,7 @@ export const interceptPointsOfSegmentAndRoundedRectangle = (
nonRotatedBottomLeft,
nonRotatedTopLeft,
],
rotatedSegment,
segment,
);
// Check if candidate points are in rounded corner territory
@ -1238,20 +1242,32 @@ export const interceptPointsOfSegmentAndRoundedRectangle = (
...result,
...interceptOfSymmetricArcAndSegment(
{
center: [
nonRotatedTopLeft[0] + rectangle.roundness.topLeft,
nonRotatedTopLeft[1] + rectangle.roundness.topLeft,
],
from: [
nonRotatedTopLeft[0],
nonRotatedTopLeft[1] + rectangle.roundness.topLeft,
],
to: [
nonRotatedTopLeft[0] + rectangle.roundness.topLeft,
nonRotatedTopLeft[1],
],
center: rotatePoint(
[
rectangle.points.topLeft[0] + rectangle.roundness.topLeft,
rectangle.points.topLeft[1] + rectangle.roundness.topLeft,
],
center,
rectangle.angle,
),
from: rotatePoint(
[
rectangle.points.topLeft[0],
rectangle.points.topLeft[1] + rectangle.roundness.topLeft,
],
center,
rectangle.angle,
),
to: rotatePoint(
[
rectangle.points.topLeft[0] + rectangle.roundness.topLeft,
rectangle.points.topLeft[1],
],
center,
rectangle.angle,
),
},
rotatedSegment,
segment,
),
];
}
@ -1261,20 +1277,32 @@ export const interceptPointsOfSegmentAndRoundedRectangle = (
...result,
...interceptOfSymmetricArcAndSegment(
{
center: [
nonRotatedTopRight[0] - rectangle.roundness.topRight,
nonRotatedTopRight[1] + rectangle.roundness.topRight,
],
from: [
nonRotatedTopRight[0] - rectangle.roundness.topRight,
nonRotatedTopRight[1],
],
to: [
nonRotatedTopRight[0],
nonRotatedTopRight[1] + rectangle.roundness.topRight,
],
center: rotatePoint(
[
rectangle.points.topRight[0] - rectangle.roundness.topRight,
rectangle.points.topRight[1] + rectangle.roundness.topRight,
],
center,
rectangle.angle,
),
from: rotatePoint(
[
rectangle.points.topRight[0] - rectangle.roundness.topRight,
rectangle.points.topRight[1],
],
center,
rectangle.angle,
),
to: rotatePoint(
[
rectangle.points.topRight[0],
rectangle.points.topRight[1] + rectangle.roundness.topRight,
],
center,
rectangle.angle,
),
},
rotatedSegment,
segment,
),
];
}
@ -1284,20 +1312,32 @@ export const interceptPointsOfSegmentAndRoundedRectangle = (
...result,
...interceptOfSymmetricArcAndSegment(
{
center: [
nonRotatedBottomRight[0] - rectangle.roundness.bottomRight,
nonRotatedBottomRight[1] - rectangle.roundness.bottomRight,
],
from: [
nonRotatedBottomRight[0],
nonRotatedBottomRight[1] - rectangle.roundness.bottomRight,
],
to: [
nonRotatedBottomRight[0] - rectangle.roundness.bottomRight,
nonRotatedBottomRight[1],
],
center: rotatePoint(
[
rectangle.points.bottomRight[0] - rectangle.roundness.bottomRight,
rectangle.points.bottomRight[1] - rectangle.roundness.bottomRight,
],
center,
rectangle.angle,
),
from: rotatePoint(
[
rectangle.points.bottomRight[0],
rectangle.points.bottomRight[1] - rectangle.roundness.bottomRight,
],
center,
rectangle.angle,
),
to: rotatePoint(
[
rectangle.points.bottomRight[0] - rectangle.roundness.bottomRight,
rectangle.points.bottomRight[1],
],
center,
rectangle.angle,
),
},
rotatedSegment,
segment,
),
];
}
@ -1307,20 +1347,32 @@ export const interceptPointsOfSegmentAndRoundedRectangle = (
...result,
...interceptOfSymmetricArcAndSegment(
{
center: [
nonRotatedBottomLeft[0] + rectangle.roundness.bottomLeft,
nonRotatedBottomLeft[1] - rectangle.roundness.bottomLeft,
],
from: [
nonRotatedBottomLeft[0] + rectangle.roundness.bottomLeft,
nonRotatedBottomLeft[1],
],
to: [
nonRotatedBottomLeft[0],
nonRotatedBottomLeft[1] - rectangle.roundness.bottomLeft,
],
center: rotatePoint(
[
rectangle.points.bottomLeft[0] + rectangle.roundness.bottomLeft,
rectangle.points.bottomLeft[1] - rectangle.roundness.bottomLeft,
],
center,
rectangle.angle,
),
from: rotatePoint(
[
rectangle.points.bottomLeft[0] + rectangle.roundness.bottomLeft,
rectangle.points.bottomLeft[1],
],
center,
rectangle.angle,
),
to: rotatePoint(
[
rectangle.points.bottomLeft[0],
rectangle.points.bottomLeft[1] - rectangle.roundness.bottomLeft,
],
center,
rectangle.angle,
),
},
rotatedSegment,
segment,
),
];
}