From 47cc842415a3f9f778b729a8c5963f150c4b2022 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Wed, 25 Sep 2024 15:04:46 +0200 Subject: [PATCH] Rectangle distance and tests --- packages/math/rectangle.test.ts | 39 ++++++++++++++++++++++++++++++++ packages/math/rectangle.ts | 30 +++++++++++++++++------- packages/math/segment.ts | 8 +++---- packages/math/types.ts | 2 +- packages/utils/geometry/shape.ts | 18 ++++++--------- 5 files changed, 72 insertions(+), 25 deletions(-) create mode 100644 packages/math/rectangle.test.ts diff --git a/packages/math/rectangle.test.ts b/packages/math/rectangle.test.ts new file mode 100644 index 0000000000..632ae4562d --- /dev/null +++ b/packages/math/rectangle.test.ts @@ -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)); + }); +}); diff --git a/packages/math/rectangle.ts b/packages/math/rectangle.ts index 96c00fbedc..f0c76a1c21 100644 --- a/packages/math/rectangle.ts +++ b/packages/math/rectangle.ts @@ -1,19 +1,19 @@ import { invariant } from "../excalidraw/utils"; +import { point } from "./point"; +import { segment, segmentDistanceToPoint } from "./segment"; import type { GenericPoint, Rectangle } from "./types"; export function rectangle

( - a: P, - b: P, - c: P, - d: P, + topLeft: P, + bottomRight: P, ): Rectangle

{ - return [a, b, c, d] as Rectangle

; + return [topLeft, bottomRight] as Rectangle

; } -export function rectangleFromQuad

( - quad: [a: P, b: P, c: P, d: P], +export function rectangleFromPair

( + pair: [a: P, b: P], ): Rectangle

{ - return quad as Rectangle

; + return pair as Rectangle

; } export function rectangleFromArray

( @@ -26,3 +26,17 @@ export function rectangleFromArray

( return pointArray as Rectangle

; } + +export function rectangleDistanceFromPoint( + r: Rectangle, + 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))); +} diff --git a/packages/math/segment.ts b/packages/math/segment.ts index d29e9c25fd..204769b83c 100644 --- a/packages/math/segment.ts +++ b/packages/math/segment.ts @@ -1,4 +1,3 @@ -import { invariant } from "../excalidraw/utils"; import { isPoint, pointCenter, @@ -23,10 +22,9 @@ import { * @returns The line segment delineated by the points */ export function segment

(a: P, b: P): Segment

{ - invariant( - !pointsEqual(a, b), - "The start and end points of the segment cannot match", - ); + if (pointsEqual(a, b)) { + console.warn("The start and end points of the segment cannot match"); + } return [a, b] as Segment

; } diff --git a/packages/math/types.ts b/packages/math/types.ts index 742d6724b7..4a7c73fa02 100644 --- a/packages/math/types.ts +++ b/packages/math/types.ts @@ -88,7 +88,7 @@ export type Triangle

= [a: P, b: P, c: P] & { /** * A rectangular shape represented by 4 points at its corners */ -export type Rectangle

= [a: P, b: P, c: P, d: P] & { +export type Rectangle

= [a: P, b: P] & { _brand: "excalimath__rectangle"; }; diff --git a/packages/utils/geometry/shape.ts b/packages/utils/geometry/shape.ts index ef005a3dc3..2d7c2bf9e1 100644 --- a/packages/utils/geometry/shape.ts +++ b/packages/utils/geometry/shape.ts @@ -253,15 +253,13 @@ const polylineFromPoints = < return polyline; }; -export const getFreedrawShape = < - Point extends GlobalPoint | LocalPoint | ViewportPoint, ->( +export const getFreedrawShape = ( element: ExcalidrawFreeDrawElement, - center: Point, + center: GlobalPoint, isClosed: boolean = false, -): GeometricShape => { - const transform = (p: Point): Point => - pointRotateRads( +): GeometricShape => { + const transform = (p: Readonly): GlobalPoint => + pointRotateRads( pointFromVector( vectorAdd(vectorFromPoint(p), vector(element.x, element.y)), ), @@ -269,9 +267,7 @@ export const getFreedrawShape = < element.angle, ); - const polyline = polylineFromPoints( - element.points.map((p) => transform(p as Point)), - ); + const polyline = polylineFromPoints(element.points.map((p) => transform(p))); return ( isClosed @@ -283,7 +279,7 @@ export const getFreedrawShape = < type: "polyline", data: polyline, } - ) as GeometricShape; + ) as GeometricShape; }; export const getClosedCurveShape = (