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

View File

@ -12,6 +12,10 @@ import {
scaleVector, scaleVector,
subtractVectors, subtractVectors,
} from "../../excalidraw/math"; } from "../../excalidraw/math";
import {
debugDrawPoint,
debugDrawSegments,
} from "../../excalidraw/visualdebug";
import type { import type {
Point, Point,
Line, 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 * Calculate a maximum of two intercept points for a line going throug an
* ellipse. * 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, center,
rectangle.angle, rectangle.angle,
); );
const rotatedSegment = [
rotatePoint(segment[0], center, rectangle.angle), debugDrawSegments([segment]);
rotatePoint(segment[1], center, rectangle.angle),
] as LineSegment;
const candidates = interceptPointsOfSegmentAndPolygon( const candidates = interceptPointsOfSegmentAndPolygon(
[ [
@ -1180,7 +1184,7 @@ export const interceptPointsOfSegmentAndRoundedRectangle = (
nonRotatedBottomLeft, nonRotatedBottomLeft,
nonRotatedTopLeft, nonRotatedTopLeft,
], ],
rotatedSegment, segment,
); );
// Check if candidate points are in rounded corner territory // Check if candidate points are in rounded corner territory
@ -1238,20 +1242,32 @@ export const interceptPointsOfSegmentAndRoundedRectangle = (
...result, ...result,
...interceptOfSymmetricArcAndSegment( ...interceptOfSymmetricArcAndSegment(
{ {
center: [ center: rotatePoint(
nonRotatedTopLeft[0] + rectangle.roundness.topLeft, [
nonRotatedTopLeft[1] + rectangle.roundness.topLeft, rectangle.points.topLeft[0] + rectangle.roundness.topLeft,
], rectangle.points.topLeft[1] + rectangle.roundness.topLeft,
from: [ ],
nonRotatedTopLeft[0], center,
nonRotatedTopLeft[1] + rectangle.roundness.topLeft, rectangle.angle,
], ),
to: [ from: rotatePoint(
nonRotatedTopLeft[0] + rectangle.roundness.topLeft, [
nonRotatedTopLeft[1], 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, ...result,
...interceptOfSymmetricArcAndSegment( ...interceptOfSymmetricArcAndSegment(
{ {
center: [ center: rotatePoint(
nonRotatedTopRight[0] - rectangle.roundness.topRight, [
nonRotatedTopRight[1] + rectangle.roundness.topRight, rectangle.points.topRight[0] - rectangle.roundness.topRight,
], rectangle.points.topRight[1] + rectangle.roundness.topRight,
from: [ ],
nonRotatedTopRight[0] - rectangle.roundness.topRight, center,
nonRotatedTopRight[1], rectangle.angle,
], ),
to: [ from: rotatePoint(
nonRotatedTopRight[0], [
nonRotatedTopRight[1] + rectangle.roundness.topRight, 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, ...result,
...interceptOfSymmetricArcAndSegment( ...interceptOfSymmetricArcAndSegment(
{ {
center: [ center: rotatePoint(
nonRotatedBottomRight[0] - rectangle.roundness.bottomRight, [
nonRotatedBottomRight[1] - rectangle.roundness.bottomRight, rectangle.points.bottomRight[0] - rectangle.roundness.bottomRight,
], rectangle.points.bottomRight[1] - rectangle.roundness.bottomRight,
from: [ ],
nonRotatedBottomRight[0], center,
nonRotatedBottomRight[1] - rectangle.roundness.bottomRight, rectangle.angle,
], ),
to: [ from: rotatePoint(
nonRotatedBottomRight[0] - rectangle.roundness.bottomRight, [
nonRotatedBottomRight[1], 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, ...result,
...interceptOfSymmetricArcAndSegment( ...interceptOfSymmetricArcAndSegment(
{ {
center: [ center: rotatePoint(
nonRotatedBottomLeft[0] + rectangle.roundness.bottomLeft, [
nonRotatedBottomLeft[1] - rectangle.roundness.bottomLeft, rectangle.points.bottomLeft[0] + rectangle.roundness.bottomLeft,
], rectangle.points.bottomLeft[1] - rectangle.roundness.bottomLeft,
from: [ ],
nonRotatedBottomLeft[0] + rectangle.roundness.bottomLeft, center,
nonRotatedBottomLeft[1], rectangle.angle,
], ),
to: [ from: rotatePoint(
nonRotatedBottomLeft[0], [
nonRotatedBottomLeft[1] - rectangle.roundness.bottomLeft, 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,
), ),
]; ];
} }