Rectangle distance and tests

This commit is contained in:
Mark Tolmacs 2024-09-25 15:04:46 +02:00
parent 9a8aabbeca
commit 47cc842415
No known key found for this signature in database
5 changed files with 72 additions and 25 deletions

View file

@ -0,0 +1,39 @@
import { point } from "./point";
import { rectangle, rectangleDistanceFromPoint } from "./rectangle";
describe("rectangle distance", () => {
it("finds the shortest distance", () => {
expect(
rectangleDistanceFromPoint(
rectangle(point(-1, -1), point(1, 1)),
point(2, 0),
),
).toBe(1);
expect(
rectangleDistanceFromPoint(
rectangle(point(-1, -1), point(1, 1)),
point(0, 2),
),
).toBe(1);
expect(
rectangleDistanceFromPoint(
rectangle(point(-1, -1), point(1, 1)),
point(-2, 0),
),
).toBe(1);
expect(
rectangleDistanceFromPoint(
rectangle(point(-1, -1), point(1, 1)),
point(0, -2),
),
).toBe(1);
});
it("finds the corner as closest point", () => {
expect(
rectangleDistanceFromPoint(
rectangle(point(-1, -1), point(1, 1)),
point(2, 2),
),
).toBe(Math.sqrt(2));
});
});

View file

@ -1,19 +1,19 @@
import { invariant } from "../excalidraw/utils"; import { invariant } from "../excalidraw/utils";
import { point } from "./point";
import { segment, segmentDistanceToPoint } from "./segment";
import type { GenericPoint, Rectangle } from "./types"; import type { GenericPoint, Rectangle } from "./types";
export function rectangle<P extends GenericPoint>( export function rectangle<P extends GenericPoint>(
a: P, topLeft: P,
b: P, bottomRight: P,
c: P,
d: P,
): Rectangle<P> { ): Rectangle<P> {
return [a, b, c, d] as Rectangle<P>; return [topLeft, bottomRight] as Rectangle<P>;
} }
export function rectangleFromQuad<P extends GenericPoint>( export function rectangleFromPair<P extends GenericPoint>(
quad: [a: P, b: P, c: P, d: P], pair: [a: P, b: P],
): Rectangle<P> { ): Rectangle<P> {
return quad as Rectangle<P>; return pair as Rectangle<P>;
} }
export function rectangleFromArray<P extends GenericPoint>( export function rectangleFromArray<P extends GenericPoint>(
@ -26,3 +26,17 @@ export function rectangleFromArray<P extends GenericPoint>(
return pointArray as Rectangle<P>; return pointArray as Rectangle<P>;
} }
export function rectangleDistanceFromPoint<Point extends GenericPoint>(
r: Rectangle<Point>,
p: Point,
): number {
const sides = [
segment(point(r[0][0], r[0][1]), point(r[1][0], r[0][1])),
segment(point(r[1][0], r[0][1]), point(r[1][0], r[1][1])),
segment(point(r[1][0], r[1][1]), point(r[0][0], r[1][1])),
segment(point(r[0][0], r[1][1]), point(r[0][0], r[0][1])),
];
return Math.min(...sides.map((side) => segmentDistanceToPoint(p, side)));
}

View file

@ -1,4 +1,3 @@
import { invariant } from "../excalidraw/utils";
import { import {
isPoint, isPoint,
pointCenter, pointCenter,
@ -23,10 +22,9 @@ import {
* @returns The line segment delineated by the points * @returns The line segment delineated by the points
*/ */
export function segment<P extends GenericPoint>(a: P, b: P): Segment<P> { export function segment<P extends GenericPoint>(a: P, b: P): Segment<P> {
invariant( if (pointsEqual(a, b)) {
!pointsEqual(a, b), console.warn("The start and end points of the segment cannot match");
"The start and end points of the segment cannot match", }
);
return [a, b] as Segment<P>; return [a, b] as Segment<P>;
} }

View file

@ -88,7 +88,7 @@ export type Triangle<P extends GenericPoint> = [a: P, b: P, c: P] & {
/** /**
* A rectangular shape represented by 4 points at its corners * A rectangular shape represented by 4 points at its corners
*/ */
export type Rectangle<P extends GenericPoint> = [a: P, b: P, c: P, d: P] & { export type Rectangle<P extends GenericPoint> = [a: P, b: P] & {
_brand: "excalimath__rectangle"; _brand: "excalimath__rectangle";
}; };

View file

@ -253,15 +253,13 @@ const polylineFromPoints = <
return polyline; return polyline;
}; };
export const getFreedrawShape = < export const getFreedrawShape = (
Point extends GlobalPoint | LocalPoint | ViewportPoint,
>(
element: ExcalidrawFreeDrawElement, element: ExcalidrawFreeDrawElement,
center: Point, center: GlobalPoint,
isClosed: boolean = false, isClosed: boolean = false,
): GeometricShape<Point> => { ): GeometricShape<GlobalPoint> => {
const transform = (p: Point): Point => const transform = (p: Readonly<LocalPoint>): GlobalPoint =>
pointRotateRads<Point>( pointRotateRads(
pointFromVector( pointFromVector(
vectorAdd(vectorFromPoint(p), vector(element.x, element.y)), vectorAdd(vectorFromPoint(p), vector(element.x, element.y)),
), ),
@ -269,9 +267,7 @@ export const getFreedrawShape = <
element.angle, element.angle,
); );
const polyline = polylineFromPoints( const polyline = polylineFromPoints(element.points.map((p) => transform(p)));
element.points.map((p) => transform(p as Point)),
);
return ( return (
isClosed isClosed
@ -283,7 +279,7 @@ export const getFreedrawShape = <
type: "polyline", type: "polyline",
data: polyline, data: polyline,
} }
) as GeometricShape<Point>; ) as GeometricShape<GlobalPoint>;
}; };
export const getClosedCurveShape = <Point extends GlobalPoint | LocalPoint>( export const getClosedCurveShape = <Point extends GlobalPoint | LocalPoint>(