From b697f63cad7f1f152e917696f91d77b1f563ea8a Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Wed, 25 Sep 2024 11:59:27 +0200 Subject: [PATCH] More refactor --- excalidraw-app/components/DebugCanvas.tsx | 10 ++-- packages/excalidraw/element/binding.ts | 4 +- packages/excalidraw/element/resizeTest.ts | 12 ++--- packages/excalidraw/frame.ts | 50 ++++++++++---------- packages/excalidraw/tests/flip.test.tsx | 7 ++- packages/excalidraw/visualdebug.ts | 24 +++++----- packages/math/arc.test.ts | 8 ++-- packages/math/arc.ts | 16 +++---- packages/math/ellipse.test.ts | 47 ++++++++++--------- packages/math/ellipse.ts | 57 ++++++++++++++++------- packages/math/polygon.ts | 4 +- packages/math/segment.test.ts | 18 +++---- packages/math/segment.ts | 41 ++++++++-------- packages/math/types.ts | 35 +++++--------- packages/utils/collision.test.ts | 16 +++---- packages/utils/collision.ts | 18 +++---- packages/utils/geometry/geometry.test.ts | 14 +++--- packages/utils/geometry/shape.ts | 24 +++++----- 18 files changed, 206 insertions(+), 199 deletions(-) diff --git a/excalidraw-app/components/DebugCanvas.tsx b/excalidraw-app/components/DebugCanvas.tsx index 471167989c..15707c0e5a 100644 --- a/excalidraw-app/components/DebugCanvas.tsx +++ b/excalidraw-app/components/DebugCanvas.tsx @@ -13,15 +13,15 @@ import { } from "../../packages/excalidraw/components/icons"; import { STORAGE_KEYS } from "../app_constants"; import { - isLineSegment, + isSegment, type GlobalPoint, - type LineSegment, + type Segment, } from "../../packages/math"; const renderLine = ( context: CanvasRenderingContext2D, zoom: number, - segment: LineSegment, + segment: Segment, color: string, ) => { context.save(); @@ -52,11 +52,11 @@ const render = ( ) => { frame.forEach((el: DebugElement) => { switch (true) { - case isLineSegment(el.data): + case isSegment(el.data): renderLine( context, appState.zoom.value, - el.data as LineSegment, + el.data as Segment, el.color, ); break; diff --git a/packages/excalidraw/element/binding.ts b/packages/excalidraw/element/binding.ts index 2df0fa083a..07783523af 100644 --- a/packages/excalidraw/element/binding.ts +++ b/packages/excalidraw/element/binding.ts @@ -65,7 +65,7 @@ import { } from "./heading"; import type { LocalPoint, Radians } from "../../math"; import { - lineSegment, + segment, point, pointRotateRads, type GlobalPoint, @@ -1615,7 +1615,7 @@ const intersectElementWithLine = ( elementsMap: ElementsMap, ): GlobalPoint[] | undefined => { if (isRectangularElement(element)) { - return segmentIntersectRectangleElement(element, lineSegment(a, b), gap); + return segmentIntersectRectangleElement(element, segment(a, b), gap); } const relateToCenter = relativizationToElementCenter(element, elementsMap); diff --git a/packages/excalidraw/element/resizeTest.ts b/packages/excalidraw/element/resizeTest.ts index 55e5cca87e..c23cef1917 100644 --- a/packages/excalidraw/element/resizeTest.ts +++ b/packages/excalidraw/element/resizeTest.ts @@ -21,10 +21,10 @@ import type { Bounds } from "./bounds"; import { getElementAbsoluteCoords } from "./bounds"; import { SIDE_RESIZING_THRESHOLD } from "../constants"; import { isLinearElement } from "./typeChecks"; -import type { GlobalPoint, LineSegment, LocalPoint } from "../../math"; +import type { GlobalPoint, Segment, LocalPoint } from "../../math"; import { point, - pointOnLineSegment, + segmentIncludesPoint, pointRotateRads, type Radians, } from "../../math"; @@ -101,9 +101,9 @@ export const resizeTest = ( for (const [dir, side] of Object.entries(sides)) { // test to see if x, y are on the line segment if ( - pointOnLineSegment( + segmentIncludesPoint( point(x, y), - side as LineSegment, + side as Segment, SPACING, ) ) { @@ -187,9 +187,9 @@ export const getTransformHandleTypeFromCoords = ( for (const [dir, side] of Object.entries(sides)) { // test to see if x, y are on the line segment if ( - pointOnLineSegment( + segmentIncludesPoint( scenePointer, - side as LineSegment, + side as Segment, SPACING, ) ) { diff --git a/packages/excalidraw/frame.ts b/packages/excalidraw/frame.ts index d91e78ae3c..898d196a74 100644 --- a/packages/excalidraw/frame.ts +++ b/packages/excalidraw/frame.ts @@ -33,10 +33,10 @@ import { isLinearElement, } from "./element/typeChecks"; import type { ReadonlySetLike } from "./utility-types"; -import type { GlobalPoint, LineSegment } from "../math"; +import type { GlobalPoint, Segment } from "../math"; import { isPointWithinBounds, - lineSegment, + segment, point, pointRotateRads, segmentsIntersectAt, @@ -82,7 +82,7 @@ export const bindElementsToFramesAfterDuplication = ( const getElementLineSegments = ( element: ExcalidrawElement, elementsMap: ElementsMap, -): LineSegment[] => { +): Segment[] => { const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords( element, elementsMap, @@ -91,13 +91,13 @@ const getElementLineSegments = ( const center: GlobalPoint = point(cx, cy); if (isLinearElement(element) || isFreeDrawElement(element)) { - const segments: LineSegment[] = []; + const segments: Segment[] = []; let i = 0; while (i < element.points.length - 1) { segments.push( - lineSegment( + segment( pointRotateRads( point( element.points[i][0] + element.x, @@ -137,35 +137,35 @@ const getElementLineSegments = ( if (element.type === "diamond") { return [ - lineSegment(n, w), - lineSegment(n, e), - lineSegment(s, w), - lineSegment(s, e), + segment(n, w), + segment(n, e), + segment(s, w), + segment(s, e), ]; } if (element.type === "ellipse") { return [ - lineSegment(n, w), - lineSegment(n, e), - lineSegment(s, w), - lineSegment(s, e), - lineSegment(n, w), - lineSegment(n, e), - lineSegment(s, w), - lineSegment(s, e), + segment(n, w), + segment(n, e), + segment(s, w), + segment(s, e), + segment(n, w), + segment(n, e), + segment(s, w), + segment(s, e), ]; } return [ - lineSegment(nw, ne), - lineSegment(sw, se), - lineSegment(nw, sw), - lineSegment(ne, se), - lineSegment(nw, e), - lineSegment(sw, e), - lineSegment(ne, w), - lineSegment(se, w), + segment(nw, ne), + segment(sw, se), + segment(nw, sw), + segment(ne, se), + segment(nw, e), + segment(sw, e), + segment(ne, w), + segment(se, w), ]; }; diff --git a/packages/excalidraw/tests/flip.test.tsx b/packages/excalidraw/tests/flip.test.tsx index 53cbc53c8b..778f148b5c 100644 --- a/packages/excalidraw/tests/flip.test.tsx +++ b/packages/excalidraw/tests/flip.test.tsx @@ -1,4 +1,3 @@ -import React from "react"; import ReactDOM from "react-dom"; import { fireEvent, @@ -27,8 +26,8 @@ import { KEYS } from "../keys"; import { getBoundTextElementPosition } from "../element/textElement"; import { createPasteEvent } from "../clipboard"; import { arrayToMap, cloneJSON } from "../utils"; -import type { LocalPoint } from "../../math"; -import { point, type Radians } from "../../math"; +import type { LocalPoint, Radians } from "../../math"; +import { point, radians } from "../../math"; const { h } = window; const mouse = new Pointer("mouse"); @@ -132,7 +131,7 @@ const createLinearElementWithCurveInsideMinMaxPoints = ( y: -2412.5069664197654, width: 1750.4888916015625, height: 410.51605224609375, - angle: 0 as Radians, + angle: radians(0), strokeColor: "#000000", backgroundColor: "#fa5252", fillStyle: "hachure", diff --git a/packages/excalidraw/visualdebug.ts b/packages/excalidraw/visualdebug.ts index f6cb1d3c87..5bf9340442 100644 --- a/packages/excalidraw/visualdebug.ts +++ b/packages/excalidraw/visualdebug.ts @@ -1,5 +1,5 @@ -import type { LineSegment } from "../math"; -import { isLineSegment, lineSegment, point, type GlobalPoint } from "../math"; +import type { Segment } from "../math"; +import { isSegment, segment, point, type GlobalPoint } from "../math"; import type { Bounds } from "./element/bounds"; import { isBounds } from "./element/typeChecks"; @@ -15,20 +15,20 @@ declare global { export type DebugElement = { color: string; - data: LineSegment; + data: Segment; permanent: boolean; }; export const debugDrawLine = ( - segment: LineSegment | LineSegment[], + segment: Segment | Segment[], opts?: { color?: string; permanent?: boolean; }, ) => { const segments = ( - isLineSegment(segment) ? [segment] : segment - ) as LineSegment[]; + isSegment(segment) ? [segment] : segment + ) as Segment[]; segments.forEach((data) => addToCurrentFrame({ @@ -51,7 +51,7 @@ export const debugDrawPoint = ( const yOffset = opts?.fuzzy ? Math.random() * 3 : 0; debugDrawLine( - lineSegment( + segment( point(p[0] + xOffset - 10, p[1] + yOffset - 10), point(p[0] + xOffset + 10, p[1] + yOffset + 10), ), @@ -61,7 +61,7 @@ export const debugDrawPoint = ( }, ); debugDrawLine( - lineSegment( + segment( point(p[0] + xOffset - 10, p[1] + yOffset + 10), point(p[0] + xOffset + 10, p[1] + yOffset - 10), ), @@ -82,19 +82,19 @@ export const debugDrawBounds = ( (isBounds(box) ? [box] : box).forEach((bbox) => debugDrawLine( [ - lineSegment( + segment( point(bbox[0], bbox[1]), point(bbox[2], bbox[1]), ), - lineSegment( + segment( point(bbox[2], bbox[1]), point(bbox[2], bbox[3]), ), - lineSegment( + segment( point(bbox[2], bbox[3]), point(bbox[0], bbox[3]), ), - lineSegment( + segment( point(bbox[0], bbox[3]), point(bbox[0], bbox[1]), ), diff --git a/packages/math/arc.test.ts b/packages/math/arc.test.ts index 456d1a1add..c3d13dc59f 100644 --- a/packages/math/arc.test.ts +++ b/packages/math/arc.test.ts @@ -1,11 +1,11 @@ import { radians } from "./angle"; -import { arc, isPointOnSymmetricArc } from "./arc"; +import { arc, arcIncludesPoint } from "./arc"; import { point } from "./point"; describe("point on arc", () => { it("should detect point on simple arc", () => { expect( - isPointOnSymmetricArc( + arcIncludesPoint( arc(point(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)), point(0.92291667, 0.385), ), @@ -13,7 +13,7 @@ describe("point on arc", () => { }); it("should not detect point outside of a simple arc", () => { expect( - isPointOnSymmetricArc( + arcIncludesPoint( arc(point(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)), point(-0.92291667, 0.385), ), @@ -21,7 +21,7 @@ describe("point on arc", () => { }); it("should not detect point with good angle but incorrect radius", () => { expect( - isPointOnSymmetricArc( + arcIncludesPoint( arc(point(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)), point(-0.5, 0.5), ), diff --git a/packages/math/arc.ts b/packages/math/arc.ts index 716db8b26f..699dae9c86 100644 --- a/packages/math/arc.ts +++ b/packages/math/arc.ts @@ -1,7 +1,7 @@ import { cartesian2Polar, radians } from "./angle"; -import { ellipse, interceptPointsOfLineAndEllipse } from "./ellipse"; +import { ellipse, ellipseSegmentInterceptPoints } from "./ellipse"; import { point } from "./point"; -import type { GenericPoint, LineSegment, Radians, SymmetricArc } from "./types"; +import type { GenericPoint, Segment, Radians, Arc } from "./types"; import { PRECISION } from "./utils"; /** @@ -20,15 +20,15 @@ export function arc( startAngle: Radians, endAngle: Radians, ) { - return { center, radius, startAngle, endAngle } as SymmetricArc; + return { center, radius, startAngle, endAngle } as Arc; } /** * Determines if a cartesian point lies on a symmetric arc, i.e. an arc which * is part of a circle contour centered on 0, 0. */ -export function isPointOnSymmetricArc

( - { center, radius: arcRadius, startAngle, endAngle }: SymmetricArc

, +export function arcIncludesPoint

( + { center, radius: arcRadius, startAngle, endAngle }: Arc

, p: P, ): boolean { const [radius, angle] = cartesian2Polar( @@ -47,10 +47,10 @@ export function isPointOnSymmetricArc

( * point and end point and a symmetric arc. */ export function interceptOfSymmetricArcAndSegment( - a: Readonly>, - l: Readonly>, + a: Readonly>, + l: Readonly>, ): Point[] { - return interceptPointsOfLineAndEllipse( + return ellipseSegmentInterceptPoints( ellipse(a.center, radians(0), a.radius, a.radius), l, ).filter((candidate) => { diff --git a/packages/math/ellipse.test.ts b/packages/math/ellipse.test.ts index d7336ecf8a..ea039c9ce0 100644 --- a/packages/math/ellipse.test.ts +++ b/packages/math/ellipse.test.ts @@ -1,12 +1,12 @@ import { radians } from "./angle"; import { ellipse, - interceptPointsOfLineAndEllipse, - pointInEllipse, - pointOnEllipse, + ellipseSegmentInterceptPoints, + ellipseIncludesPoint, + ellipseTouchesPoint, } from "./ellipse"; import { point } from "./point"; -import { lineSegment } from "./segment"; +import { segment } from "./segment"; import type { Ellipse, GlobalPoint } from "./types"; describe("point and ellipse", () => { @@ -14,41 +14,44 @@ describe("point and ellipse", () => { it("point on ellipse", () => { [point(0, 1), point(0, -1), point(2, 0), point(-2, 0)].forEach((p) => { - expect(pointOnEllipse(p, target)).toBe(true); + expect(ellipseTouchesPoint(p, target)).toBe(true); }); - expect(pointOnEllipse(point(-1.4, 0.7), target, 0.1)).toBe(true); - expect(pointOnEllipse(point(-1.4, 0.71), target, 0.01)).toBe(true); + expect(ellipseTouchesPoint(point(-1.4, 0.7), target, 0.1)).toBe(true); + expect(ellipseTouchesPoint(point(-1.4, 0.71), target, 0.01)).toBe(true); - expect(pointOnEllipse(point(1.4, 0.7), target, 0.1)).toBe(true); - expect(pointOnEllipse(point(1.4, 0.71), target, 0.01)).toBe(true); + expect(ellipseTouchesPoint(point(1.4, 0.7), target, 0.1)).toBe(true); + expect(ellipseTouchesPoint(point(1.4, 0.71), target, 0.01)).toBe(true); - expect(pointOnEllipse(point(1, -0.86), target, 0.1)).toBe(true); - expect(pointOnEllipse(point(1, -0.86), target, 0.01)).toBe(true); + expect(ellipseTouchesPoint(point(1, -0.86), target, 0.1)).toBe(true); + expect(ellipseTouchesPoint(point(1, -0.86), target, 0.01)).toBe(true); - expect(pointOnEllipse(point(-1, -0.86), target, 0.1)).toBe(true); - expect(pointOnEllipse(point(-1, -0.86), target, 0.01)).toBe(true); + expect(ellipseTouchesPoint(point(-1, -0.86), target, 0.1)).toBe(true); + expect(ellipseTouchesPoint(point(-1, -0.86), target, 0.01)).toBe(true); - expect(pointOnEllipse(point(-1, 0.8), target)).toBe(false); - expect(pointOnEllipse(point(1, -0.8), target)).toBe(false); + expect(ellipseTouchesPoint(point(-1, 0.8), target)).toBe(false); + expect(ellipseTouchesPoint(point(1, -0.8), target)).toBe(false); }); it("point in ellipse", () => { [point(0, 1), point(0, -1), point(2, 0), point(-2, 0)].forEach((p) => { - expect(pointInEllipse(p, target)).toBe(true); + expect(ellipseIncludesPoint(p, target)).toBe(true); }); - expect(pointInEllipse(point(-1, 0.8), target)).toBe(true); - expect(pointInEllipse(point(1, -0.8), target)).toBe(true); + expect(ellipseIncludesPoint(point(-1, 0.8), target)).toBe(true); + expect(ellipseIncludesPoint(point(1, -0.8), target)).toBe(true); - expect(pointInEllipse(point(-1, 1), target)).toBe(false); - expect(pointInEllipse(point(-1.4, 0.8), target)).toBe(false); + // Point on outline + expect(ellipseIncludesPoint(point(2, 0), target)).toBe(true); + + expect(ellipseIncludesPoint(point(-1, 1), target)).toBe(false); + expect(ellipseIncludesPoint(point(-1.4, 0.8), target)).toBe(false); }); }); describe("line and ellipse", () => { it("detects outside segment", () => { - const l = lineSegment(point(-100, 0), point(-10, 0)); + const l = segment(point(-100, 0), point(-10, 0)); const e = ellipse(point(0, 0), radians(0), 2, 2); - expect(interceptPointsOfLineAndEllipse(e, l).length).toBe(0); + expect(ellipseSegmentInterceptPoints(e, l).length).toBe(0); }); }); diff --git a/packages/math/ellipse.ts b/packages/math/ellipse.ts index 9bcadbc09a..4b9775b180 100644 --- a/packages/math/ellipse.ts +++ b/packages/math/ellipse.ts @@ -6,7 +6,7 @@ import { pointFromVector, pointRotateRads, } from "./point"; -import type { Ellipse, GenericPoint, LineSegment, Radians } from "./types"; +import type { Ellipse, GenericPoint, Segment, Radians } from "./types"; import { PRECISION } from "./utils"; import { vector, @@ -16,6 +16,15 @@ import { vectorScale, } from "./vector"; +/** + * Construct an Ellipse object from the parameters + * + * @param center The center of the ellipse + * @param angle The slanting of the ellipse in radians + * @param halfWidth Half of the width of a non-slanted version of the ellipse + * @param halfHeight Half of the height of a non-slanted version of the ellipse + * @returns The constructed Ellipse object + */ export function ellipse( center: Point, angle: Radians, @@ -30,7 +39,14 @@ export function ellipse( } as Ellipse; } -export const pointInEllipse = ( +/** + * Determines if a point is inside or on the ellipse outline + * + * @param p The point to test + * @param ellipse The ellipse to compare against + * @returns TRUE if the point is inside or on the outline of the ellipse + */ +export const ellipseIncludesPoint = ( p: Point, ellipse: Ellipse, ) => { @@ -52,7 +68,16 @@ export const pointInEllipse = ( ); }; -export const pointOnEllipse = ( +/** + * Tests whether a point lies on the outline of the ellipse within a given + * tolerance + * + * @param point The point to test + * @param ellipse The ellipse to compare against + * @param threshold The distance to consider a point close enough to be "on" the outline + * @returns TRUE if the point is on the ellise outline + */ +export const ellipseTouchesPoint = ( point: Point, ellipse: Ellipse, threshold = PRECISION, @@ -60,7 +85,7 @@ export const pointOnEllipse = ( return distanceToEllipse(point, ellipse) <= threshold; }; -export const ellipseAxes = ( +export const ellipseFocusToCenter = ( ellipse: Ellipse, ) => { const widthGreaterThanHeight = ellipse.halfWidth > ellipse.halfHeight; @@ -72,17 +97,6 @@ export const ellipseAxes = ( ? ellipse.halfHeight * 2 : ellipse.halfWidth * 2; - return { - majorAxis, - minorAxis, - }; -}; - -export const ellipseFocusToCenter = ( - ellipse: Ellipse, -) => { - const { majorAxis, minorAxis } = ellipseAxes(ellipse); - return Math.sqrt(majorAxis ** 2 - minorAxis ** 2); }; @@ -90,7 +104,14 @@ export const ellipseExtremes = ( ellipse: Ellipse, ) => { const { center, angle } = ellipse; - const { majorAxis, minorAxis } = ellipseAxes(ellipse); + const widthGreaterThanHeight = ellipse.halfWidth > ellipse.halfHeight; + + const majorAxis = widthGreaterThanHeight + ? ellipse.halfWidth * 2 + : ellipse.halfHeight * 2; + const minorAxis = widthGreaterThanHeight + ? ellipse.halfHeight * 2 + : ellipse.halfWidth * 2; const cos = Math.cos(angle); const sin = Math.sin(angle); @@ -175,9 +196,9 @@ const distanceToEllipse = ( * Calculate a maximum of two intercept points for a line going throug an * ellipse. */ -export function interceptPointsOfLineAndEllipse( +export function ellipseSegmentInterceptPoints( e: Readonly>, - l: Readonly>, + l: Readonly>, ): Point[] { const rx = e.halfWidth; const ry = e.halfHeight; diff --git a/packages/math/polygon.ts b/packages/math/polygon.ts index 557dd78692..fdf26027c3 100644 --- a/packages/math/polygon.ts +++ b/packages/math/polygon.ts @@ -1,5 +1,5 @@ import { pointsEqual } from "./point"; -import { lineSegment, pointOnLineSegment } from "./segment"; +import { segment, segmentIncludesPoint } from "./segment"; import type { GenericPoint, Polygon } from "./types"; import { PRECISION } from "./utils"; @@ -44,7 +44,7 @@ export const pointOnPolygon = ( let on = false; for (let i = 0, l = poly.length - 1; i < l; i++) { - if (pointOnLineSegment(p, lineSegment(poly[i], poly[i + 1]), threshold)) { + if (segmentIncludesPoint(p, segment(poly[i], poly[i + 1]), threshold)) { on = true; break; } diff --git a/packages/math/segment.test.ts b/packages/math/segment.test.ts index 74d3496f59..c956923e40 100644 --- a/packages/math/segment.test.ts +++ b/packages/math/segment.test.ts @@ -1,15 +1,15 @@ import { point } from "./point"; -import { lineSegment, segmentsIntersectAt } from "./segment"; -import type { GlobalPoint, LineSegment } from "./types"; +import { segment, segmentsIntersectAt } from "./segment"; +import type { GlobalPoint, Segment } from "./types"; describe("segment intersects segment", () => { - const lineA: LineSegment = lineSegment(point(1, 4), point(3, 4)); - const lineB: LineSegment = lineSegment(point(2, 1), point(2, 7)); - const lineC: LineSegment = lineSegment(point(1, 8), point(3, 8)); - const lineD: LineSegment = lineSegment(point(1, 8), point(3, 8)); - const lineE: LineSegment = lineSegment(point(1, 9), point(3, 9)); - const lineF: LineSegment = lineSegment(point(1, 2), point(3, 4)); - const lineG: LineSegment = lineSegment(point(0, 1), point(2, 3)); + const lineA: Segment = segment(point(1, 4), point(3, 4)); + const lineB: Segment = segment(point(2, 1), point(2, 7)); + const lineC: Segment = segment(point(1, 8), point(3, 8)); + const lineD: Segment = segment(point(1, 8), point(3, 8)); + const lineE: Segment = segment(point(1, 9), point(3, 9)); + const lineF: Segment = segment(point(1, 2), point(3, 4)); + const lineG: Segment = segment(point(0, 1), point(2, 3)); it("intersection", () => { expect(segmentsIntersectAt(lineA, lineB)).toEqual([2, 4]); diff --git a/packages/math/segment.ts b/packages/math/segment.ts index a3fa629b91..15cd181cde 100644 --- a/packages/math/segment.ts +++ b/packages/math/segment.ts @@ -4,7 +4,7 @@ import { pointFromVector, pointRotateRads, } from "./point"; -import type { GenericPoint, LineSegment, Radians } from "./types"; +import type { GenericPoint, Segment, Radians } from "./types"; import { PRECISION } from "./utils"; import { vectorAdd, @@ -20,18 +20,15 @@ import { * @param points The two points delimiting the line segment on each end * @returns The line segment delineated by the points */ -export function lineSegment

( - a: P, - b: P, -): LineSegment

{ - return [a, b] as LineSegment

; +export function segment

(a: P, b: P): Segment

{ + return [a, b] as Segment

; } -export function lineSegmentFromPointArray

( +export function segmentFromPointArray

( pointArray: P[], -): LineSegment

| undefined { +): Segment

| undefined { return pointArray.length === 2 - ? lineSegment

(pointArray[0], pointArray[1]) + ? segment

(pointArray[0], pointArray[1]) : undefined; } @@ -40,9 +37,9 @@ export function lineSegmentFromPointArray

( * @param segment * @returns */ -export const isLineSegment = ( +export const isSegment = ( segment: unknown, -): segment is LineSegment => +): segment is Segment => Array.isArray(segment) && segment.length === 2 && isPoint(segment[0]) && @@ -57,12 +54,12 @@ export const isLineSegment = ( * @param origin * @returns */ -export const lineSegmentRotate = ( - l: LineSegment, +export const segmentRotate = ( + l: Segment, angle: Radians, origin?: Point, -): LineSegment => { - return lineSegment( +): Segment => { + return segment( pointRotateRads(l[0], origin || pointCenter(l[0], l[1]), angle), pointRotateRads(l[1], origin || pointCenter(l[0], l[1]), angle), ); @@ -73,8 +70,8 @@ export const lineSegmentRotate = ( * intersect at. */ export const segmentsIntersectAt = ( - a: Readonly>, - b: Readonly>, + a: Readonly>, + b: Readonly>, ): Point | null => { const a0 = vectorFromPoint(a[0]); const a1 = vectorFromPoint(a[1]); @@ -105,12 +102,12 @@ export const segmentsIntersectAt = ( return null; }; -export const pointOnLineSegment = ( +export const segmentIncludesPoint = ( point: Point, - line: LineSegment, + line: Segment, threshold = PRECISION, ) => { - const distance = distanceToLineSegment(point, line); + const distance = segmentDistanceToPoint(point, line); if (distance === 0) { return true; @@ -119,9 +116,9 @@ export const pointOnLineSegment = ( return distance < threshold; }; -export const distanceToLineSegment = ( +export const segmentDistanceToPoint = ( point: Point, - line: LineSegment, + line: Segment, ) => { const [x, y] = point; const [[x1, y1], [x2, y2]] = line; diff --git a/packages/math/types.ts b/packages/math/types.ts index 10c0932417..742d6724b7 100644 --- a/packages/math/types.ts +++ b/packages/math/types.ts @@ -67,8 +67,8 @@ export type Line

= [p: P, q: P] & { * line that is bounded by two distinct end points, and * contains every point on the line that is between its endpoints. */ -export type LineSegment

= [a: P, b: P] & { - _brand: "excalimath_linesegment"; +export type Segment

= [a: P, b: P] & { + _brand: "excalimath_segment"; }; /** @@ -81,23 +81,14 @@ export type Vector = [u: number, v: number] & { /** * A triangle represented by 3 points */ -export type Triangle

= [ - a: P, - b: P, - c: P, -] & { +export type Triangle

= [a: P, b: P, c: P] & { _brand: "excalimath__triangle"; }; /** * 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, c: P, d: P] & { _brand: "excalimath__rectangle"; }; @@ -105,28 +96,24 @@ export type Rectangle

= [ * A polygon is a closed shape by connecting the given points * rectangles and diamonds are modelled by polygons */ -export type Polygon = - Point[] & { - _brand: "excalimath_polygon"; - }; +export type Polygon = Point[] & { + _brand: "excalimath_polygon"; +}; /** * Cubic bezier curve with four control points */ -export type Curve = [ - Point, - Point, - Point, - Point, -] & { +export type Curve = [Point, Point, Point, Point] & { _brand: "excalimath_curve"; }; /** + * Represents a symmetric arc, a segment of a circular path + * * Angles are in radians and centered on 0, 0. Zero radians on a 1 radius circle * corresponds to (1, 0) cartesian coordinates (point), i.e. to the "right" */ -export type SymmetricArc = { +export type Arc = { center: Point; radius: number; startAngle: Radians; diff --git a/packages/utils/collision.test.ts b/packages/utils/collision.test.ts index 398c3cb680..9f3ad61343 100644 --- a/packages/utils/collision.test.ts +++ b/packages/utils/collision.test.ts @@ -2,8 +2,8 @@ import type { Curve, Degrees, GlobalPoint } from "../math"; import { curve, degreesToRadians, - lineSegment, - lineSegmentRotate, + segment, + segmentRotate, point, pointRotateDegs, } from "../math"; @@ -34,10 +34,10 @@ describe("point and curve", () => { describe("point and polylines", () => { const polyline: Polyline = [ - lineSegment(point(1, 0), point(1, 2)), - lineSegment(point(1, 2), point(2, 2)), - lineSegment(point(2, 2), point(2, 1)), - lineSegment(point(2, 1), point(3, 1)), + segment(point(1, 0), point(1, 2)), + segment(point(1, 2), point(2, 2)), + segment(point(2, 2), point(2, 1)), + segment(point(2, 1), point(3, 1)), ]; it("point on the line", () => { @@ -68,7 +68,7 @@ describe("point and polylines", () => { const rotation = (Math.random() * 360) as Degrees; const rotatedPoint = pointRotateDegs(p, point(0, 0), rotation); const rotatedPolyline = polyline.map((line) => - lineSegmentRotate(line, degreesToRadians(rotation), point(0, 0)), + segmentRotate(line, degreesToRadians(rotation), point(0, 0)), ); expect(pointOnPolyline(rotatedPoint, rotatedPolyline)).toBe(true); }); @@ -79,7 +79,7 @@ describe("point and polylines", () => { const rotation = (Math.random() * 360) as Degrees; const rotatedPoint = pointRotateDegs(p, point(0, 0), rotation); const rotatedPolyline = polyline.map((line) => - lineSegmentRotate(line, degreesToRadians(rotation), point(0, 0)), + segmentRotate(line, degreesToRadians(rotation), point(0, 0)), ); expect(pointOnPolyline(rotatedPoint, rotatedPolyline)).toBe(false); }); diff --git a/packages/utils/collision.ts b/packages/utils/collision.ts index 4c67bc85e6..84d617454b 100644 --- a/packages/utils/collision.ts +++ b/packages/utils/collision.ts @@ -2,14 +2,14 @@ import type { Polycurve, Polyline } from "./geometry/shape"; import { type GeometricShape } from "./geometry/shape"; import type { Curve, GenericPoint } from "../math"; import { - lineSegment, + segment, point, polygonIncludesPoint, - pointOnLineSegment, + segmentIncludesPoint, pointOnPolygon, polygonFromPoints, - pointOnEllipse, - pointInEllipse, + ellipseTouchesPoint, + ellipseIncludesPoint, } from "../math"; // check if the given point is considered on the given shape's border @@ -22,9 +22,9 @@ export const isPointOnShape = ( case "polygon": return pointOnPolygon(point, shape.data, tolerance); case "ellipse": - return pointOnEllipse(point, shape.data, tolerance); + return ellipseTouchesPoint(point, shape.data, tolerance); case "line": - return pointOnLineSegment(point, shape.data, tolerance); + return segmentIncludesPoint(point, shape.data, tolerance); case "polyline": return pointOnPolyline(point, shape.data, tolerance); case "curve": @@ -49,7 +49,7 @@ export const isPointInShape = ( case "curve": return false; case "ellipse": - return pointInEllipse(p, shape.data); + return ellipseIncludesPoint(p, shape.data); case "polyline": { const polygon = polygonFromPoints(shape.data.flat()); return polygonIncludesPoint(p, polygon); @@ -96,7 +96,7 @@ const polyLineFromCurve = ( t += increment; if (t <= 1) { const nextPoint: Point = point(equation(t, 0), equation(t, 1)); - lineSegments.push(lineSegment(startingPoint, nextPoint)); + lineSegments.push(segment(startingPoint, nextPoint)); startingPoint = nextPoint; } } @@ -117,5 +117,5 @@ export const pointOnPolyline = ( polyline: Polyline, threshold = 10e-5, ) => { - return polyline.some((line) => pointOnLineSegment(point, line, threshold)); + return polyline.some((line) => segmentIncludesPoint(point, line, threshold)); }; diff --git a/packages/utils/geometry/geometry.test.ts b/packages/utils/geometry/geometry.test.ts index 1225ee8139..f770d4d45a 100644 --- a/packages/utils/geometry/geometry.test.ts +++ b/packages/utils/geometry/geometry.test.ts @@ -1,20 +1,20 @@ -import type { GlobalPoint, LineSegment, Polygon } from "../../math"; +import type { GlobalPoint, Segment, Polygon } from "../../math"; import { point, - lineSegment, + segment, polygon, - pointOnLineSegment, + segmentIncludesPoint, pointOnPolygon, polygonIncludesPoint, } from "../../math"; describe("point and line", () => { - const s: LineSegment = lineSegment(point(1, 0), point(1, 2)); + const s: Segment = segment(point(1, 0), point(1, 2)); it("point on the line", () => { - expect(pointOnLineSegment(point(0, 1), s)).toBe(false); - expect(pointOnLineSegment(point(1, 1), s, 0)).toBe(true); - expect(pointOnLineSegment(point(2, 1), s)).toBe(false); + expect(segmentIncludesPoint(point(0, 1), s)).toBe(false); + expect(segmentIncludesPoint(point(1, 1), s, 0)).toBe(true); + expect(segmentIncludesPoint(point(2, 1), s)).toBe(false); }); }); diff --git a/packages/utils/geometry/shape.ts b/packages/utils/geometry/shape.ts index 1dc3d4e07e..ef005a3dc3 100644 --- a/packages/utils/geometry/shape.ts +++ b/packages/utils/geometry/shape.ts @@ -16,7 +16,7 @@ import type { Curve, Ellipse, GenericPoint, - LineSegment, + Segment, Polygon, Radians, ViewportPoint, @@ -24,7 +24,7 @@ import type { import { curve, ellipse, - lineSegment, + segment, point, pointFromArray, pointFromVector, @@ -63,7 +63,7 @@ import { invariant } from "../../excalidraw/utils"; // this corresponds to a straight line element in the editor but it could also // be used to model other elements export type Polyline = - LineSegment[]; + Segment[]; // a polycurve is a curve consisting of ther curves, this corresponds to a complex // curve on the canvas @@ -73,7 +73,7 @@ export type Polycurve = export type GeometricShape = | { type: "line"; - data: LineSegment; + data: Segment; } | { type: "polygon"; @@ -242,11 +242,11 @@ const polylineFromPoints = < points: Point[], ): Polyline => { let previousPoint: Point = points[0]; - const polyline: LineSegment[] = []; + const polyline: Segment[] = []; for (let i = 1; i < points.length; i++) { const nextPoint = points[i]; - polyline.push(lineSegment(previousPoint, nextPoint)); + polyline.push(segment(previousPoint, nextPoint)); previousPoint = nextPoint; } @@ -356,7 +356,7 @@ export const segmentIntersectRectangleElement = < Point extends LocalPoint | GlobalPoint, >( element: ExcalidrawBindableElement, - segment: LineSegment, + s: Segment, gap: number = 0, ): Point[] => { const bounds = [ @@ -371,23 +371,23 @@ export const segmentIntersectRectangleElement = < ); return [ - lineSegment( + segment( pointRotateRads(point(bounds[0], bounds[1]), center, element.angle), pointRotateRads(point(bounds[2], bounds[1]), center, element.angle), ), - lineSegment( + segment( pointRotateRads(point(bounds[2], bounds[1]), center, element.angle), pointRotateRads(point(bounds[2], bounds[3]), center, element.angle), ), - lineSegment( + segment( pointRotateRads(point(bounds[2], bounds[3]), center, element.angle), pointRotateRads(point(bounds[0], bounds[3]), center, element.angle), ), - lineSegment( + segment( pointRotateRads(point(bounds[0], bounds[3]), center, element.angle), pointRotateRads(point(bounds[0], bounds[1]), center, element.angle), ), ] - .map((s) => segmentsIntersectAt(segment, s)) + .map((l) => segmentsIntersectAt(s, l)) .filter((i): i is Point => !!i); };