mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
More refactor
This commit is contained in:
parent
392dd5b0b8
commit
b697f63cad
18 changed files with 206 additions and 199 deletions
|
@ -13,15 +13,15 @@ import {
|
||||||
} from "../../packages/excalidraw/components/icons";
|
} from "../../packages/excalidraw/components/icons";
|
||||||
import { STORAGE_KEYS } from "../app_constants";
|
import { STORAGE_KEYS } from "../app_constants";
|
||||||
import {
|
import {
|
||||||
isLineSegment,
|
isSegment,
|
||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
type LineSegment,
|
type Segment,
|
||||||
} from "../../packages/math";
|
} from "../../packages/math";
|
||||||
|
|
||||||
const renderLine = (
|
const renderLine = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
zoom: number,
|
zoom: number,
|
||||||
segment: LineSegment<GlobalPoint>,
|
segment: Segment<GlobalPoint>,
|
||||||
color: string,
|
color: string,
|
||||||
) => {
|
) => {
|
||||||
context.save();
|
context.save();
|
||||||
|
@ -52,11 +52,11 @@ const render = (
|
||||||
) => {
|
) => {
|
||||||
frame.forEach((el: DebugElement) => {
|
frame.forEach((el: DebugElement) => {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case isLineSegment(el.data):
|
case isSegment(el.data):
|
||||||
renderLine(
|
renderLine(
|
||||||
context,
|
context,
|
||||||
appState.zoom.value,
|
appState.zoom.value,
|
||||||
el.data as LineSegment<GlobalPoint>,
|
el.data as Segment<GlobalPoint>,
|
||||||
el.color,
|
el.color,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -65,7 +65,7 @@ import {
|
||||||
} from "./heading";
|
} from "./heading";
|
||||||
import type { LocalPoint, Radians } from "../../math";
|
import type { LocalPoint, Radians } from "../../math";
|
||||||
import {
|
import {
|
||||||
lineSegment,
|
segment,
|
||||||
point,
|
point,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
|
@ -1615,7 +1615,7 @@ const intersectElementWithLine = (
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
): GlobalPoint[] | undefined => {
|
): GlobalPoint[] | undefined => {
|
||||||
if (isRectangularElement(element)) {
|
if (isRectangularElement(element)) {
|
||||||
return segmentIntersectRectangleElement(element, lineSegment(a, b), gap);
|
return segmentIntersectRectangleElement(element, segment(a, b), gap);
|
||||||
}
|
}
|
||||||
|
|
||||||
const relateToCenter = relativizationToElementCenter(element, elementsMap);
|
const relateToCenter = relativizationToElementCenter(element, elementsMap);
|
||||||
|
|
|
@ -21,10 +21,10 @@ import type { Bounds } from "./bounds";
|
||||||
import { getElementAbsoluteCoords } from "./bounds";
|
import { getElementAbsoluteCoords } from "./bounds";
|
||||||
import { SIDE_RESIZING_THRESHOLD } from "../constants";
|
import { SIDE_RESIZING_THRESHOLD } from "../constants";
|
||||||
import { isLinearElement } from "./typeChecks";
|
import { isLinearElement } from "./typeChecks";
|
||||||
import type { GlobalPoint, LineSegment, LocalPoint } from "../../math";
|
import type { GlobalPoint, Segment, LocalPoint } from "../../math";
|
||||||
import {
|
import {
|
||||||
point,
|
point,
|
||||||
pointOnLineSegment,
|
segmentIncludesPoint,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
type Radians,
|
type Radians,
|
||||||
} from "../../math";
|
} from "../../math";
|
||||||
|
@ -101,9 +101,9 @@ export const resizeTest = <Point extends GlobalPoint | LocalPoint>(
|
||||||
for (const [dir, side] of Object.entries(sides)) {
|
for (const [dir, side] of Object.entries(sides)) {
|
||||||
// test to see if x, y are on the line segment
|
// test to see if x, y are on the line segment
|
||||||
if (
|
if (
|
||||||
pointOnLineSegment(
|
segmentIncludesPoint(
|
||||||
point<Point>(x, y),
|
point<Point>(x, y),
|
||||||
side as LineSegment<Point>,
|
side as Segment<Point>,
|
||||||
SPACING,
|
SPACING,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
@ -187,9 +187,9 @@ export const getTransformHandleTypeFromCoords = (
|
||||||
for (const [dir, side] of Object.entries(sides)) {
|
for (const [dir, side] of Object.entries(sides)) {
|
||||||
// test to see if x, y are on the line segment
|
// test to see if x, y are on the line segment
|
||||||
if (
|
if (
|
||||||
pointOnLineSegment(
|
segmentIncludesPoint(
|
||||||
scenePointer,
|
scenePointer,
|
||||||
side as LineSegment<GlobalPoint>,
|
side as Segment<GlobalPoint>,
|
||||||
SPACING,
|
SPACING,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -33,10 +33,10 @@ import {
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
} from "./element/typeChecks";
|
} from "./element/typeChecks";
|
||||||
import type { ReadonlySetLike } from "./utility-types";
|
import type { ReadonlySetLike } from "./utility-types";
|
||||||
import type { GlobalPoint, LineSegment } from "../math";
|
import type { GlobalPoint, Segment } from "../math";
|
||||||
import {
|
import {
|
||||||
isPointWithinBounds,
|
isPointWithinBounds,
|
||||||
lineSegment,
|
segment,
|
||||||
point,
|
point,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
segmentsIntersectAt,
|
segmentsIntersectAt,
|
||||||
|
@ -82,7 +82,7 @@ export const bindElementsToFramesAfterDuplication = (
|
||||||
const getElementLineSegments = (
|
const getElementLineSegments = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
): LineSegment<GlobalPoint>[] => {
|
): Segment<GlobalPoint>[] => {
|
||||||
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
|
@ -91,13 +91,13 @@ const getElementLineSegments = (
|
||||||
const center: GlobalPoint = point(cx, cy);
|
const center: GlobalPoint = point(cx, cy);
|
||||||
|
|
||||||
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
||||||
const segments: LineSegment<GlobalPoint>[] = [];
|
const segments: Segment<GlobalPoint>[] = [];
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
while (i < element.points.length - 1) {
|
while (i < element.points.length - 1) {
|
||||||
segments.push(
|
segments.push(
|
||||||
lineSegment(
|
segment(
|
||||||
pointRotateRads(
|
pointRotateRads(
|
||||||
point(
|
point(
|
||||||
element.points[i][0] + element.x,
|
element.points[i][0] + element.x,
|
||||||
|
@ -137,35 +137,35 @@ const getElementLineSegments = (
|
||||||
|
|
||||||
if (element.type === "diamond") {
|
if (element.type === "diamond") {
|
||||||
return [
|
return [
|
||||||
lineSegment(n, w),
|
segment(n, w),
|
||||||
lineSegment(n, e),
|
segment(n, e),
|
||||||
lineSegment(s, w),
|
segment(s, w),
|
||||||
lineSegment(s, e),
|
segment(s, e),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.type === "ellipse") {
|
if (element.type === "ellipse") {
|
||||||
return [
|
return [
|
||||||
lineSegment(n, w),
|
segment(n, w),
|
||||||
lineSegment(n, e),
|
segment(n, e),
|
||||||
lineSegment(s, w),
|
segment(s, w),
|
||||||
lineSegment(s, e),
|
segment(s, e),
|
||||||
lineSegment(n, w),
|
segment(n, w),
|
||||||
lineSegment(n, e),
|
segment(n, e),
|
||||||
lineSegment(s, w),
|
segment(s, w),
|
||||||
lineSegment(s, e),
|
segment(s, e),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
lineSegment(nw, ne),
|
segment(nw, ne),
|
||||||
lineSegment(sw, se),
|
segment(sw, se),
|
||||||
lineSegment(nw, sw),
|
segment(nw, sw),
|
||||||
lineSegment(ne, se),
|
segment(ne, se),
|
||||||
lineSegment(nw, e),
|
segment(nw, e),
|
||||||
lineSegment(sw, e),
|
segment(sw, e),
|
||||||
lineSegment(ne, w),
|
segment(ne, w),
|
||||||
lineSegment(se, w),
|
segment(se, w),
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import {
|
import {
|
||||||
fireEvent,
|
fireEvent,
|
||||||
|
@ -27,8 +26,8 @@ import { KEYS } from "../keys";
|
||||||
import { getBoundTextElementPosition } from "../element/textElement";
|
import { getBoundTextElementPosition } from "../element/textElement";
|
||||||
import { createPasteEvent } from "../clipboard";
|
import { createPasteEvent } from "../clipboard";
|
||||||
import { arrayToMap, cloneJSON } from "../utils";
|
import { arrayToMap, cloneJSON } from "../utils";
|
||||||
import type { LocalPoint } from "../../math";
|
import type { LocalPoint, Radians } from "../../math";
|
||||||
import { point, type Radians } from "../../math";
|
import { point, radians } from "../../math";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
const mouse = new Pointer("mouse");
|
const mouse = new Pointer("mouse");
|
||||||
|
@ -132,7 +131,7 @@ const createLinearElementWithCurveInsideMinMaxPoints = (
|
||||||
y: -2412.5069664197654,
|
y: -2412.5069664197654,
|
||||||
width: 1750.4888916015625,
|
width: 1750.4888916015625,
|
||||||
height: 410.51605224609375,
|
height: 410.51605224609375,
|
||||||
angle: 0 as Radians,
|
angle: radians(0),
|
||||||
strokeColor: "#000000",
|
strokeColor: "#000000",
|
||||||
backgroundColor: "#fa5252",
|
backgroundColor: "#fa5252",
|
||||||
fillStyle: "hachure",
|
fillStyle: "hachure",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { LineSegment } from "../math";
|
import type { Segment } from "../math";
|
||||||
import { isLineSegment, lineSegment, point, type GlobalPoint } from "../math";
|
import { isSegment, segment, point, type GlobalPoint } from "../math";
|
||||||
import type { Bounds } from "./element/bounds";
|
import type { Bounds } from "./element/bounds";
|
||||||
import { isBounds } from "./element/typeChecks";
|
import { isBounds } from "./element/typeChecks";
|
||||||
|
|
||||||
|
@ -15,20 +15,20 @@ declare global {
|
||||||
|
|
||||||
export type DebugElement = {
|
export type DebugElement = {
|
||||||
color: string;
|
color: string;
|
||||||
data: LineSegment<GlobalPoint>;
|
data: Segment<GlobalPoint>;
|
||||||
permanent: boolean;
|
permanent: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const debugDrawLine = (
|
export const debugDrawLine = (
|
||||||
segment: LineSegment<GlobalPoint> | LineSegment<GlobalPoint>[],
|
segment: Segment<GlobalPoint> | Segment<GlobalPoint>[],
|
||||||
opts?: {
|
opts?: {
|
||||||
color?: string;
|
color?: string;
|
||||||
permanent?: boolean;
|
permanent?: boolean;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const segments = (
|
const segments = (
|
||||||
isLineSegment(segment) ? [segment] : segment
|
isSegment(segment) ? [segment] : segment
|
||||||
) as LineSegment<GlobalPoint>[];
|
) as Segment<GlobalPoint>[];
|
||||||
|
|
||||||
segments.forEach((data) =>
|
segments.forEach((data) =>
|
||||||
addToCurrentFrame({
|
addToCurrentFrame({
|
||||||
|
@ -51,7 +51,7 @@ export const debugDrawPoint = (
|
||||||
const yOffset = opts?.fuzzy ? Math.random() * 3 : 0;
|
const yOffset = opts?.fuzzy ? Math.random() * 3 : 0;
|
||||||
|
|
||||||
debugDrawLine(
|
debugDrawLine(
|
||||||
lineSegment(
|
segment(
|
||||||
point<GlobalPoint>(p[0] + xOffset - 10, p[1] + yOffset - 10),
|
point<GlobalPoint>(p[0] + xOffset - 10, p[1] + yOffset - 10),
|
||||||
point<GlobalPoint>(p[0] + xOffset + 10, p[1] + yOffset + 10),
|
point<GlobalPoint>(p[0] + xOffset + 10, p[1] + yOffset + 10),
|
||||||
),
|
),
|
||||||
|
@ -61,7 +61,7 @@ export const debugDrawPoint = (
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
debugDrawLine(
|
debugDrawLine(
|
||||||
lineSegment(
|
segment(
|
||||||
point<GlobalPoint>(p[0] + xOffset - 10, p[1] + yOffset + 10),
|
point<GlobalPoint>(p[0] + xOffset - 10, p[1] + yOffset + 10),
|
||||||
point<GlobalPoint>(p[0] + xOffset + 10, p[1] + yOffset - 10),
|
point<GlobalPoint>(p[0] + xOffset + 10, p[1] + yOffset - 10),
|
||||||
),
|
),
|
||||||
|
@ -82,19 +82,19 @@ export const debugDrawBounds = (
|
||||||
(isBounds(box) ? [box] : box).forEach((bbox) =>
|
(isBounds(box) ? [box] : box).forEach((bbox) =>
|
||||||
debugDrawLine(
|
debugDrawLine(
|
||||||
[
|
[
|
||||||
lineSegment(
|
segment(
|
||||||
point<GlobalPoint>(bbox[0], bbox[1]),
|
point<GlobalPoint>(bbox[0], bbox[1]),
|
||||||
point<GlobalPoint>(bbox[2], bbox[1]),
|
point<GlobalPoint>(bbox[2], bbox[1]),
|
||||||
),
|
),
|
||||||
lineSegment(
|
segment(
|
||||||
point<GlobalPoint>(bbox[2], bbox[1]),
|
point<GlobalPoint>(bbox[2], bbox[1]),
|
||||||
point<GlobalPoint>(bbox[2], bbox[3]),
|
point<GlobalPoint>(bbox[2], bbox[3]),
|
||||||
),
|
),
|
||||||
lineSegment(
|
segment(
|
||||||
point<GlobalPoint>(bbox[2], bbox[3]),
|
point<GlobalPoint>(bbox[2], bbox[3]),
|
||||||
point<GlobalPoint>(bbox[0], bbox[3]),
|
point<GlobalPoint>(bbox[0], bbox[3]),
|
||||||
),
|
),
|
||||||
lineSegment(
|
segment(
|
||||||
point<GlobalPoint>(bbox[0], bbox[3]),
|
point<GlobalPoint>(bbox[0], bbox[3]),
|
||||||
point<GlobalPoint>(bbox[0], bbox[1]),
|
point<GlobalPoint>(bbox[0], bbox[1]),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { radians } from "./angle";
|
import { radians } from "./angle";
|
||||||
import { arc, isPointOnSymmetricArc } from "./arc";
|
import { arc, arcIncludesPoint } from "./arc";
|
||||||
import { point } from "./point";
|
import { point } from "./point";
|
||||||
|
|
||||||
describe("point on arc", () => {
|
describe("point on arc", () => {
|
||||||
it("should detect point on simple arc", () => {
|
it("should detect point on simple arc", () => {
|
||||||
expect(
|
expect(
|
||||||
isPointOnSymmetricArc(
|
arcIncludesPoint(
|
||||||
arc(point(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
arc(point(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
||||||
point(0.92291667, 0.385),
|
point(0.92291667, 0.385),
|
||||||
),
|
),
|
||||||
|
@ -13,7 +13,7 @@ describe("point on arc", () => {
|
||||||
});
|
});
|
||||||
it("should not detect point outside of a simple arc", () => {
|
it("should not detect point outside of a simple arc", () => {
|
||||||
expect(
|
expect(
|
||||||
isPointOnSymmetricArc(
|
arcIncludesPoint(
|
||||||
arc(point(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
arc(point(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
||||||
point(-0.92291667, 0.385),
|
point(-0.92291667, 0.385),
|
||||||
),
|
),
|
||||||
|
@ -21,7 +21,7 @@ describe("point on arc", () => {
|
||||||
});
|
});
|
||||||
it("should not detect point with good angle but incorrect radius", () => {
|
it("should not detect point with good angle but incorrect radius", () => {
|
||||||
expect(
|
expect(
|
||||||
isPointOnSymmetricArc(
|
arcIncludesPoint(
|
||||||
arc(point(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
arc(point(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
||||||
point(-0.5, 0.5),
|
point(-0.5, 0.5),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { cartesian2Polar, radians } from "./angle";
|
import { cartesian2Polar, radians } from "./angle";
|
||||||
import { ellipse, interceptPointsOfLineAndEllipse } from "./ellipse";
|
import { ellipse, ellipseSegmentInterceptPoints } from "./ellipse";
|
||||||
import { point } from "./point";
|
import { point } from "./point";
|
||||||
import type { GenericPoint, LineSegment, Radians, SymmetricArc } from "./types";
|
import type { GenericPoint, Segment, Radians, Arc } from "./types";
|
||||||
import { PRECISION } from "./utils";
|
import { PRECISION } from "./utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,15 +20,15 @@ export function arc<Point extends GenericPoint>(
|
||||||
startAngle: Radians,
|
startAngle: Radians,
|
||||||
endAngle: Radians,
|
endAngle: Radians,
|
||||||
) {
|
) {
|
||||||
return { center, radius, startAngle, endAngle } as SymmetricArc<Point>;
|
return { center, radius, startAngle, endAngle } as Arc<Point>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if a cartesian point lies on a symmetric arc, i.e. an arc which
|
* 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.
|
* is part of a circle contour centered on 0, 0.
|
||||||
*/
|
*/
|
||||||
export function isPointOnSymmetricArc<P extends GenericPoint>(
|
export function arcIncludesPoint<P extends GenericPoint>(
|
||||||
{ center, radius: arcRadius, startAngle, endAngle }: SymmetricArc<P>,
|
{ center, radius: arcRadius, startAngle, endAngle }: Arc<P>,
|
||||||
p: P,
|
p: P,
|
||||||
): boolean {
|
): boolean {
|
||||||
const [radius, angle] = cartesian2Polar(
|
const [radius, angle] = cartesian2Polar(
|
||||||
|
@ -47,10 +47,10 @@ export function isPointOnSymmetricArc<P extends GenericPoint>(
|
||||||
* point and end point and a symmetric arc.
|
* point and end point and a symmetric arc.
|
||||||
*/
|
*/
|
||||||
export function interceptOfSymmetricArcAndSegment<Point extends GenericPoint>(
|
export function interceptOfSymmetricArcAndSegment<Point extends GenericPoint>(
|
||||||
a: Readonly<SymmetricArc<Point>>,
|
a: Readonly<Arc<Point>>,
|
||||||
l: Readonly<LineSegment<Point>>,
|
l: Readonly<Segment<Point>>,
|
||||||
): Point[] {
|
): Point[] {
|
||||||
return interceptPointsOfLineAndEllipse(
|
return ellipseSegmentInterceptPoints(
|
||||||
ellipse(a.center, radians(0), a.radius, a.radius),
|
ellipse(a.center, radians(0), a.radius, a.radius),
|
||||||
l,
|
l,
|
||||||
).filter((candidate) => {
|
).filter((candidate) => {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { radians } from "./angle";
|
import { radians } from "./angle";
|
||||||
import {
|
import {
|
||||||
ellipse,
|
ellipse,
|
||||||
interceptPointsOfLineAndEllipse,
|
ellipseSegmentInterceptPoints,
|
||||||
pointInEllipse,
|
ellipseIncludesPoint,
|
||||||
pointOnEllipse,
|
ellipseTouchesPoint,
|
||||||
} from "./ellipse";
|
} from "./ellipse";
|
||||||
import { point } from "./point";
|
import { point } from "./point";
|
||||||
import { lineSegment } from "./segment";
|
import { segment } from "./segment";
|
||||||
import type { Ellipse, GlobalPoint } from "./types";
|
import type { Ellipse, GlobalPoint } from "./types";
|
||||||
|
|
||||||
describe("point and ellipse", () => {
|
describe("point and ellipse", () => {
|
||||||
|
@ -14,41 +14,44 @@ describe("point and ellipse", () => {
|
||||||
|
|
||||||
it("point on ellipse", () => {
|
it("point on ellipse", () => {
|
||||||
[point(0, 1), point(0, -1), point(2, 0), point(-2, 0)].forEach((p) => {
|
[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(ellipseTouchesPoint(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.71), target, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnEllipse(point(1.4, 0.7), target, 0.1)).toBe(true);
|
expect(ellipseTouchesPoint(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.71), target, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnEllipse(point(1, -0.86), target, 0.1)).toBe(true);
|
expect(ellipseTouchesPoint(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.01)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnEllipse(point(-1, -0.86), target, 0.1)).toBe(true);
|
expect(ellipseTouchesPoint(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.01)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnEllipse(point(-1, 0.8), target)).toBe(false);
|
expect(ellipseTouchesPoint(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);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("point in ellipse", () => {
|
it("point in ellipse", () => {
|
||||||
[point(0, 1), point(0, -1), point(2, 0), point(-2, 0)].forEach((p) => {
|
[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(ellipseIncludesPoint(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(pointInEllipse(point(-1, 1), target)).toBe(false);
|
// Point on outline
|
||||||
expect(pointInEllipse(point(-1.4, 0.8), target)).toBe(false);
|
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", () => {
|
describe("line and ellipse", () => {
|
||||||
it("detects outside segment", () => {
|
it("detects outside segment", () => {
|
||||||
const l = lineSegment<GlobalPoint>(point(-100, 0), point(-10, 0));
|
const l = segment<GlobalPoint>(point(-100, 0), point(-10, 0));
|
||||||
const e = ellipse(point(0, 0), radians(0), 2, 2);
|
const e = ellipse(point(0, 0), radians(0), 2, 2);
|
||||||
expect(interceptPointsOfLineAndEllipse(e, l).length).toBe(0);
|
expect(ellipseSegmentInterceptPoints(e, l).length).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
pointFromVector,
|
pointFromVector,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
} from "./point";
|
} from "./point";
|
||||||
import type { Ellipse, GenericPoint, LineSegment, Radians } from "./types";
|
import type { Ellipse, GenericPoint, Segment, Radians } from "./types";
|
||||||
import { PRECISION } from "./utils";
|
import { PRECISION } from "./utils";
|
||||||
import {
|
import {
|
||||||
vector,
|
vector,
|
||||||
|
@ -16,6 +16,15 @@ import {
|
||||||
vectorScale,
|
vectorScale,
|
||||||
} from "./vector";
|
} 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<Point extends GenericPoint>(
|
export function ellipse<Point extends GenericPoint>(
|
||||||
center: Point,
|
center: Point,
|
||||||
angle: Radians,
|
angle: Radians,
|
||||||
|
@ -30,7 +39,14 @@ export function ellipse<Point extends GenericPoint>(
|
||||||
} as Ellipse<Point>;
|
} as Ellipse<Point>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const pointInEllipse = <Point extends GenericPoint>(
|
/**
|
||||||
|
* 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 = <Point extends GenericPoint>(
|
||||||
p: Point,
|
p: Point,
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
) => {
|
) => {
|
||||||
|
@ -52,7 +68,16 @@ export const pointInEllipse = <Point extends GenericPoint>(
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pointOnEllipse = <Point extends GenericPoint>(
|
/**
|
||||||
|
* 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 extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
threshold = PRECISION,
|
threshold = PRECISION,
|
||||||
|
@ -60,7 +85,7 @@ export const pointOnEllipse = <Point extends GenericPoint>(
|
||||||
return distanceToEllipse(point, ellipse) <= threshold;
|
return distanceToEllipse(point, ellipse) <= threshold;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ellipseAxes = <Point extends GenericPoint>(
|
export const ellipseFocusToCenter = <Point extends GenericPoint>(
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
) => {
|
) => {
|
||||||
const widthGreaterThanHeight = ellipse.halfWidth > ellipse.halfHeight;
|
const widthGreaterThanHeight = ellipse.halfWidth > ellipse.halfHeight;
|
||||||
|
@ -72,17 +97,6 @@ export const ellipseAxes = <Point extends GenericPoint>(
|
||||||
? ellipse.halfHeight * 2
|
? ellipse.halfHeight * 2
|
||||||
: ellipse.halfWidth * 2;
|
: ellipse.halfWidth * 2;
|
||||||
|
|
||||||
return {
|
|
||||||
majorAxis,
|
|
||||||
minorAxis,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ellipseFocusToCenter = <Point extends GenericPoint>(
|
|
||||||
ellipse: Ellipse<Point>,
|
|
||||||
) => {
|
|
||||||
const { majorAxis, minorAxis } = ellipseAxes(ellipse);
|
|
||||||
|
|
||||||
return Math.sqrt(majorAxis ** 2 - minorAxis ** 2);
|
return Math.sqrt(majorAxis ** 2 - minorAxis ** 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -90,7 +104,14 @@ export const ellipseExtremes = <Point extends GenericPoint>(
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
) => {
|
) => {
|
||||||
const { center, angle } = 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 cos = Math.cos(angle);
|
||||||
const sin = Math.sin(angle);
|
const sin = Math.sin(angle);
|
||||||
|
@ -175,9 +196,9 @@ const distanceToEllipse = <Point extends GenericPoint>(
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
export function interceptPointsOfLineAndEllipse<Point extends GenericPoint>(
|
export function ellipseSegmentInterceptPoints<Point extends GenericPoint>(
|
||||||
e: Readonly<Ellipse<Point>>,
|
e: Readonly<Ellipse<Point>>,
|
||||||
l: Readonly<LineSegment<Point>>,
|
l: Readonly<Segment<Point>>,
|
||||||
): Point[] {
|
): Point[] {
|
||||||
const rx = e.halfWidth;
|
const rx = e.halfWidth;
|
||||||
const ry = e.halfHeight;
|
const ry = e.halfHeight;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { pointsEqual } from "./point";
|
import { pointsEqual } from "./point";
|
||||||
import { lineSegment, pointOnLineSegment } from "./segment";
|
import { segment, segmentIncludesPoint } from "./segment";
|
||||||
import type { GenericPoint, Polygon } from "./types";
|
import type { GenericPoint, Polygon } from "./types";
|
||||||
import { PRECISION } from "./utils";
|
import { PRECISION } from "./utils";
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ export const pointOnPolygon = <Point extends GenericPoint>(
|
||||||
let on = false;
|
let on = false;
|
||||||
|
|
||||||
for (let i = 0, l = poly.length - 1; i < l; i++) {
|
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;
|
on = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { point } from "./point";
|
import { point } from "./point";
|
||||||
import { lineSegment, segmentsIntersectAt } from "./segment";
|
import { segment, segmentsIntersectAt } from "./segment";
|
||||||
import type { GlobalPoint, LineSegment } from "./types";
|
import type { GlobalPoint, Segment } from "./types";
|
||||||
|
|
||||||
describe("segment intersects segment", () => {
|
describe("segment intersects segment", () => {
|
||||||
const lineA: LineSegment<GlobalPoint> = lineSegment(point(1, 4), point(3, 4));
|
const lineA: Segment<GlobalPoint> = segment(point(1, 4), point(3, 4));
|
||||||
const lineB: LineSegment<GlobalPoint> = lineSegment(point(2, 1), point(2, 7));
|
const lineB: Segment<GlobalPoint> = segment(point(2, 1), point(2, 7));
|
||||||
const lineC: LineSegment<GlobalPoint> = lineSegment(point(1, 8), point(3, 8));
|
const lineC: Segment<GlobalPoint> = segment(point(1, 8), point(3, 8));
|
||||||
const lineD: LineSegment<GlobalPoint> = lineSegment(point(1, 8), point(3, 8));
|
const lineD: Segment<GlobalPoint> = segment(point(1, 8), point(3, 8));
|
||||||
const lineE: LineSegment<GlobalPoint> = lineSegment(point(1, 9), point(3, 9));
|
const lineE: Segment<GlobalPoint> = segment(point(1, 9), point(3, 9));
|
||||||
const lineF: LineSegment<GlobalPoint> = lineSegment(point(1, 2), point(3, 4));
|
const lineF: Segment<GlobalPoint> = segment(point(1, 2), point(3, 4));
|
||||||
const lineG: LineSegment<GlobalPoint> = lineSegment(point(0, 1), point(2, 3));
|
const lineG: Segment<GlobalPoint> = segment(point(0, 1), point(2, 3));
|
||||||
|
|
||||||
it("intersection", () => {
|
it("intersection", () => {
|
||||||
expect(segmentsIntersectAt(lineA, lineB)).toEqual([2, 4]);
|
expect(segmentsIntersectAt(lineA, lineB)).toEqual([2, 4]);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
pointFromVector,
|
pointFromVector,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
} from "./point";
|
} from "./point";
|
||||||
import type { GenericPoint, LineSegment, Radians } from "./types";
|
import type { GenericPoint, Segment, Radians } from "./types";
|
||||||
import { PRECISION } from "./utils";
|
import { PRECISION } from "./utils";
|
||||||
import {
|
import {
|
||||||
vectorAdd,
|
vectorAdd,
|
||||||
|
@ -20,18 +20,15 @@ import {
|
||||||
* @param points The two points delimiting the line segment on each end
|
* @param points The two points delimiting the line segment on each end
|
||||||
* @returns The line segment delineated by the points
|
* @returns The line segment delineated by the points
|
||||||
*/
|
*/
|
||||||
export function lineSegment<P extends GenericPoint>(
|
export function segment<P extends GenericPoint>(a: P, b: P): Segment<P> {
|
||||||
a: P,
|
return [a, b] as Segment<P>;
|
||||||
b: P,
|
|
||||||
): LineSegment<P> {
|
|
||||||
return [a, b] as LineSegment<P>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function lineSegmentFromPointArray<P extends GenericPoint>(
|
export function segmentFromPointArray<P extends GenericPoint>(
|
||||||
pointArray: P[],
|
pointArray: P[],
|
||||||
): LineSegment<P> | undefined {
|
): Segment<P> | undefined {
|
||||||
return pointArray.length === 2
|
return pointArray.length === 2
|
||||||
? lineSegment<P>(pointArray[0], pointArray[1])
|
? segment<P>(pointArray[0], pointArray[1])
|
||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,9 +37,9 @@ export function lineSegmentFromPointArray<P extends GenericPoint>(
|
||||||
* @param segment
|
* @param segment
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const isLineSegment = <Point extends GenericPoint>(
|
export const isSegment = <Point extends GenericPoint>(
|
||||||
segment: unknown,
|
segment: unknown,
|
||||||
): segment is LineSegment<Point> =>
|
): segment is Segment<Point> =>
|
||||||
Array.isArray(segment) &&
|
Array.isArray(segment) &&
|
||||||
segment.length === 2 &&
|
segment.length === 2 &&
|
||||||
isPoint(segment[0]) &&
|
isPoint(segment[0]) &&
|
||||||
|
@ -57,12 +54,12 @@ export const isLineSegment = <Point extends GenericPoint>(
|
||||||
* @param origin
|
* @param origin
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const lineSegmentRotate = <Point extends GenericPoint>(
|
export const segmentRotate = <Point extends GenericPoint>(
|
||||||
l: LineSegment<Point>,
|
l: Segment<Point>,
|
||||||
angle: Radians,
|
angle: Radians,
|
||||||
origin?: Point,
|
origin?: Point,
|
||||||
): LineSegment<Point> => {
|
): Segment<Point> => {
|
||||||
return lineSegment(
|
return segment(
|
||||||
pointRotateRads(l[0], origin || pointCenter(l[0], l[1]), angle),
|
pointRotateRads(l[0], origin || pointCenter(l[0], l[1]), angle),
|
||||||
pointRotateRads(l[1], origin || pointCenter(l[0], l[1]), angle),
|
pointRotateRads(l[1], origin || pointCenter(l[0], l[1]), angle),
|
||||||
);
|
);
|
||||||
|
@ -73,8 +70,8 @@ export const lineSegmentRotate = <Point extends GenericPoint>(
|
||||||
* intersect at.
|
* intersect at.
|
||||||
*/
|
*/
|
||||||
export const segmentsIntersectAt = <Point extends GenericPoint>(
|
export const segmentsIntersectAt = <Point extends GenericPoint>(
|
||||||
a: Readonly<LineSegment<Point>>,
|
a: Readonly<Segment<Point>>,
|
||||||
b: Readonly<LineSegment<Point>>,
|
b: Readonly<Segment<Point>>,
|
||||||
): Point | null => {
|
): Point | null => {
|
||||||
const a0 = vectorFromPoint(a[0]);
|
const a0 = vectorFromPoint(a[0]);
|
||||||
const a1 = vectorFromPoint(a[1]);
|
const a1 = vectorFromPoint(a[1]);
|
||||||
|
@ -105,12 +102,12 @@ export const segmentsIntersectAt = <Point extends GenericPoint>(
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pointOnLineSegment = <Point extends GenericPoint>(
|
export const segmentIncludesPoint = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
line: LineSegment<Point>,
|
line: Segment<Point>,
|
||||||
threshold = PRECISION,
|
threshold = PRECISION,
|
||||||
) => {
|
) => {
|
||||||
const distance = distanceToLineSegment(point, line);
|
const distance = segmentDistanceToPoint(point, line);
|
||||||
|
|
||||||
if (distance === 0) {
|
if (distance === 0) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -119,9 +116,9 @@ export const pointOnLineSegment = <Point extends GenericPoint>(
|
||||||
return distance < threshold;
|
return distance < threshold;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const distanceToLineSegment = <Point extends GenericPoint>(
|
export const segmentDistanceToPoint = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
line: LineSegment<Point>,
|
line: Segment<Point>,
|
||||||
) => {
|
) => {
|
||||||
const [x, y] = point;
|
const [x, y] = point;
|
||||||
const [[x1, y1], [x2, y2]] = line;
|
const [[x1, y1], [x2, y2]] = line;
|
||||||
|
|
|
@ -67,8 +67,8 @@ export type Line<P extends GenericPoint> = [p: P, q: P] & {
|
||||||
* line that is bounded by two distinct end points, and
|
* line that is bounded by two distinct end points, and
|
||||||
* contains every point on the line that is between its endpoints.
|
* contains every point on the line that is between its endpoints.
|
||||||
*/
|
*/
|
||||||
export type LineSegment<P extends GenericPoint> = [a: P, b: P] & {
|
export type Segment<P extends GenericPoint> = [a: P, b: P] & {
|
||||||
_brand: "excalimath_linesegment";
|
_brand: "excalimath_segment";
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,23 +81,14 @@ export type Vector = [u: number, v: number] & {
|
||||||
/**
|
/**
|
||||||
* A triangle represented by 3 points
|
* A triangle represented by 3 points
|
||||||
*/
|
*/
|
||||||
export type Triangle<P extends GlobalPoint | LocalPoint | ViewportPoint> = [
|
export type Triangle<P extends GenericPoint> = [a: P, b: P, c: P] & {
|
||||||
a: P,
|
|
||||||
b: P,
|
|
||||||
c: P,
|
|
||||||
] & {
|
|
||||||
_brand: "excalimath__triangle";
|
_brand: "excalimath__triangle";
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 GlobalPoint | LocalPoint | ViewportPoint> = [
|
export type Rectangle<P extends GenericPoint> = [a: P, b: P, c: P, d: P] & {
|
||||||
a: P,
|
|
||||||
b: P,
|
|
||||||
c: P,
|
|
||||||
d: P,
|
|
||||||
] & {
|
|
||||||
_brand: "excalimath__rectangle";
|
_brand: "excalimath__rectangle";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -105,28 +96,24 @@ export type Rectangle<P extends GlobalPoint | LocalPoint | ViewportPoint> = [
|
||||||
* A polygon is a closed shape by connecting the given points
|
* A polygon is a closed shape by connecting the given points
|
||||||
* rectangles and diamonds are modelled by polygons
|
* rectangles and diamonds are modelled by polygons
|
||||||
*/
|
*/
|
||||||
export type Polygon<Point extends GlobalPoint | LocalPoint | ViewportPoint> =
|
export type Polygon<Point extends GenericPoint> = Point[] & {
|
||||||
Point[] & {
|
|
||||||
_brand: "excalimath_polygon";
|
_brand: "excalimath_polygon";
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cubic bezier curve with four control points
|
* Cubic bezier curve with four control points
|
||||||
*/
|
*/
|
||||||
export type Curve<Point extends GlobalPoint | LocalPoint | ViewportPoint> = [
|
export type Curve<Point extends GenericPoint> = [Point, Point, Point, Point] & {
|
||||||
Point,
|
|
||||||
Point,
|
|
||||||
Point,
|
|
||||||
Point,
|
|
||||||
] & {
|
|
||||||
_brand: "excalimath_curve";
|
_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
|
* 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"
|
* corresponds to (1, 0) cartesian coordinates (point), i.e. to the "right"
|
||||||
*/
|
*/
|
||||||
export type SymmetricArc<Point extends GenericPoint> = {
|
export type Arc<Point extends GenericPoint> = {
|
||||||
center: Point;
|
center: Point;
|
||||||
radius: number;
|
radius: number;
|
||||||
startAngle: Radians;
|
startAngle: Radians;
|
||||||
|
|
|
@ -2,8 +2,8 @@ import type { Curve, Degrees, GlobalPoint } from "../math";
|
||||||
import {
|
import {
|
||||||
curve,
|
curve,
|
||||||
degreesToRadians,
|
degreesToRadians,
|
||||||
lineSegment,
|
segment,
|
||||||
lineSegmentRotate,
|
segmentRotate,
|
||||||
point,
|
point,
|
||||||
pointRotateDegs,
|
pointRotateDegs,
|
||||||
} from "../math";
|
} from "../math";
|
||||||
|
@ -34,10 +34,10 @@ describe("point and curve", () => {
|
||||||
|
|
||||||
describe("point and polylines", () => {
|
describe("point and polylines", () => {
|
||||||
const polyline: Polyline<GlobalPoint> = [
|
const polyline: Polyline<GlobalPoint> = [
|
||||||
lineSegment(point(1, 0), point(1, 2)),
|
segment(point(1, 0), point(1, 2)),
|
||||||
lineSegment(point(1, 2), point(2, 2)),
|
segment(point(1, 2), point(2, 2)),
|
||||||
lineSegment(point(2, 2), point(2, 1)),
|
segment(point(2, 2), point(2, 1)),
|
||||||
lineSegment(point(2, 1), point(3, 1)),
|
segment(point(2, 1), point(3, 1)),
|
||||||
];
|
];
|
||||||
|
|
||||||
it("point on the line", () => {
|
it("point on the line", () => {
|
||||||
|
@ -68,7 +68,7 @@ describe("point and polylines", () => {
|
||||||
const rotation = (Math.random() * 360) as Degrees;
|
const rotation = (Math.random() * 360) as Degrees;
|
||||||
const rotatedPoint = pointRotateDegs(p, point(0, 0), rotation);
|
const rotatedPoint = pointRotateDegs(p, point(0, 0), rotation);
|
||||||
const rotatedPolyline = polyline.map((line) =>
|
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);
|
expect(pointOnPolyline(rotatedPoint, rotatedPolyline)).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -79,7 +79,7 @@ describe("point and polylines", () => {
|
||||||
const rotation = (Math.random() * 360) as Degrees;
|
const rotation = (Math.random() * 360) as Degrees;
|
||||||
const rotatedPoint = pointRotateDegs(p, point(0, 0), rotation);
|
const rotatedPoint = pointRotateDegs(p, point(0, 0), rotation);
|
||||||
const rotatedPolyline = polyline.map((line) =>
|
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);
|
expect(pointOnPolyline(rotatedPoint, rotatedPolyline)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,14 +2,14 @@ import type { Polycurve, Polyline } from "./geometry/shape";
|
||||||
import { type GeometricShape } from "./geometry/shape";
|
import { type GeometricShape } from "./geometry/shape";
|
||||||
import type { Curve, GenericPoint } from "../math";
|
import type { Curve, GenericPoint } from "../math";
|
||||||
import {
|
import {
|
||||||
lineSegment,
|
segment,
|
||||||
point,
|
point,
|
||||||
polygonIncludesPoint,
|
polygonIncludesPoint,
|
||||||
pointOnLineSegment,
|
segmentIncludesPoint,
|
||||||
pointOnPolygon,
|
pointOnPolygon,
|
||||||
polygonFromPoints,
|
polygonFromPoints,
|
||||||
pointOnEllipse,
|
ellipseTouchesPoint,
|
||||||
pointInEllipse,
|
ellipseIncludesPoint,
|
||||||
} from "../math";
|
} from "../math";
|
||||||
|
|
||||||
// check if the given point is considered on the given shape's border
|
// check if the given point is considered on the given shape's border
|
||||||
|
@ -22,9 +22,9 @@ export const isPointOnShape = <Point extends GenericPoint>(
|
||||||
case "polygon":
|
case "polygon":
|
||||||
return pointOnPolygon(point, shape.data, tolerance);
|
return pointOnPolygon(point, shape.data, tolerance);
|
||||||
case "ellipse":
|
case "ellipse":
|
||||||
return pointOnEllipse(point, shape.data, tolerance);
|
return ellipseTouchesPoint(point, shape.data, tolerance);
|
||||||
case "line":
|
case "line":
|
||||||
return pointOnLineSegment(point, shape.data, tolerance);
|
return segmentIncludesPoint(point, shape.data, tolerance);
|
||||||
case "polyline":
|
case "polyline":
|
||||||
return pointOnPolyline(point, shape.data, tolerance);
|
return pointOnPolyline(point, shape.data, tolerance);
|
||||||
case "curve":
|
case "curve":
|
||||||
|
@ -49,7 +49,7 @@ export const isPointInShape = <Point extends GenericPoint>(
|
||||||
case "curve":
|
case "curve":
|
||||||
return false;
|
return false;
|
||||||
case "ellipse":
|
case "ellipse":
|
||||||
return pointInEllipse(p, shape.data);
|
return ellipseIncludesPoint(p, shape.data);
|
||||||
case "polyline": {
|
case "polyline": {
|
||||||
const polygon = polygonFromPoints(shape.data.flat());
|
const polygon = polygonFromPoints(shape.data.flat());
|
||||||
return polygonIncludesPoint(p, polygon);
|
return polygonIncludesPoint(p, polygon);
|
||||||
|
@ -96,7 +96,7 @@ const polyLineFromCurve = <Point extends GenericPoint>(
|
||||||
t += increment;
|
t += increment;
|
||||||
if (t <= 1) {
|
if (t <= 1) {
|
||||||
const nextPoint: Point = point(equation(t, 0), equation(t, 1));
|
const nextPoint: Point = point(equation(t, 0), equation(t, 1));
|
||||||
lineSegments.push(lineSegment(startingPoint, nextPoint));
|
lineSegments.push(segment(startingPoint, nextPoint));
|
||||||
startingPoint = nextPoint;
|
startingPoint = nextPoint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,5 +117,5 @@ export const pointOnPolyline = <Point extends GenericPoint>(
|
||||||
polyline: Polyline<Point>,
|
polyline: Polyline<Point>,
|
||||||
threshold = 10e-5,
|
threshold = 10e-5,
|
||||||
) => {
|
) => {
|
||||||
return polyline.some((line) => pointOnLineSegment(point, line, threshold));
|
return polyline.some((line) => segmentIncludesPoint(point, line, threshold));
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import type { GlobalPoint, LineSegment, Polygon } from "../../math";
|
import type { GlobalPoint, Segment, Polygon } from "../../math";
|
||||||
import {
|
import {
|
||||||
point,
|
point,
|
||||||
lineSegment,
|
segment,
|
||||||
polygon,
|
polygon,
|
||||||
pointOnLineSegment,
|
segmentIncludesPoint,
|
||||||
pointOnPolygon,
|
pointOnPolygon,
|
||||||
polygonIncludesPoint,
|
polygonIncludesPoint,
|
||||||
} from "../../math";
|
} from "../../math";
|
||||||
|
|
||||||
describe("point and line", () => {
|
describe("point and line", () => {
|
||||||
const s: LineSegment<GlobalPoint> = lineSegment(point(1, 0), point(1, 2));
|
const s: Segment<GlobalPoint> = segment(point(1, 0), point(1, 2));
|
||||||
|
|
||||||
it("point on the line", () => {
|
it("point on the line", () => {
|
||||||
expect(pointOnLineSegment(point(0, 1), s)).toBe(false);
|
expect(segmentIncludesPoint(point(0, 1), s)).toBe(false);
|
||||||
expect(pointOnLineSegment(point(1, 1), s, 0)).toBe(true);
|
expect(segmentIncludesPoint(point(1, 1), s, 0)).toBe(true);
|
||||||
expect(pointOnLineSegment(point(2, 1), s)).toBe(false);
|
expect(segmentIncludesPoint(point(2, 1), s)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import type {
|
||||||
Curve,
|
Curve,
|
||||||
Ellipse,
|
Ellipse,
|
||||||
GenericPoint,
|
GenericPoint,
|
||||||
LineSegment,
|
Segment,
|
||||||
Polygon,
|
Polygon,
|
||||||
Radians,
|
Radians,
|
||||||
ViewportPoint,
|
ViewportPoint,
|
||||||
|
@ -24,7 +24,7 @@ import type {
|
||||||
import {
|
import {
|
||||||
curve,
|
curve,
|
||||||
ellipse,
|
ellipse,
|
||||||
lineSegment,
|
segment,
|
||||||
point,
|
point,
|
||||||
pointFromArray,
|
pointFromArray,
|
||||||
pointFromVector,
|
pointFromVector,
|
||||||
|
@ -63,7 +63,7 @@ import { invariant } from "../../excalidraw/utils";
|
||||||
// this corresponds to a straight line element in the editor but it could also
|
// this corresponds to a straight line element in the editor but it could also
|
||||||
// be used to model other elements
|
// be used to model other elements
|
||||||
export type Polyline<Point extends GlobalPoint | LocalPoint | ViewportPoint> =
|
export type Polyline<Point extends GlobalPoint | LocalPoint | ViewportPoint> =
|
||||||
LineSegment<Point>[];
|
Segment<Point>[];
|
||||||
|
|
||||||
// a polycurve is a curve consisting of ther curves, this corresponds to a complex
|
// a polycurve is a curve consisting of ther curves, this corresponds to a complex
|
||||||
// curve on the canvas
|
// curve on the canvas
|
||||||
|
@ -73,7 +73,7 @@ export type Polycurve<Point extends GlobalPoint | LocalPoint | ViewportPoint> =
|
||||||
export type GeometricShape<Point extends GenericPoint> =
|
export type GeometricShape<Point extends GenericPoint> =
|
||||||
| {
|
| {
|
||||||
type: "line";
|
type: "line";
|
||||||
data: LineSegment<Point>;
|
data: Segment<Point>;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "polygon";
|
type: "polygon";
|
||||||
|
@ -242,11 +242,11 @@ const polylineFromPoints = <
|
||||||
points: Point[],
|
points: Point[],
|
||||||
): Polyline<Point> => {
|
): Polyline<Point> => {
|
||||||
let previousPoint: Point = points[0];
|
let previousPoint: Point = points[0];
|
||||||
const polyline: LineSegment<Point>[] = [];
|
const polyline: Segment<Point>[] = [];
|
||||||
|
|
||||||
for (let i = 1; i < points.length; i++) {
|
for (let i = 1; i < points.length; i++) {
|
||||||
const nextPoint = points[i];
|
const nextPoint = points[i];
|
||||||
polyline.push(lineSegment<Point>(previousPoint, nextPoint));
|
polyline.push(segment<Point>(previousPoint, nextPoint));
|
||||||
previousPoint = nextPoint;
|
previousPoint = nextPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +356,7 @@ export const segmentIntersectRectangleElement = <
|
||||||
Point extends LocalPoint | GlobalPoint,
|
Point extends LocalPoint | GlobalPoint,
|
||||||
>(
|
>(
|
||||||
element: ExcalidrawBindableElement,
|
element: ExcalidrawBindableElement,
|
||||||
segment: LineSegment<Point>,
|
s: Segment<Point>,
|
||||||
gap: number = 0,
|
gap: number = 0,
|
||||||
): Point[] => {
|
): Point[] => {
|
||||||
const bounds = [
|
const bounds = [
|
||||||
|
@ -371,23 +371,23 @@ export const segmentIntersectRectangleElement = <
|
||||||
);
|
);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
lineSegment(
|
segment(
|
||||||
pointRotateRads(point(bounds[0], bounds[1]), center, element.angle),
|
pointRotateRads(point(bounds[0], bounds[1]), center, element.angle),
|
||||||
pointRotateRads(point(bounds[2], 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[1]), center, element.angle),
|
||||||
pointRotateRads(point(bounds[2], bounds[3]), 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[2], bounds[3]), center, element.angle),
|
||||||
pointRotateRads(point(bounds[0], 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[3]), center, element.angle),
|
||||||
pointRotateRads(point(bounds[0], bounds[1]), 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);
|
.filter((i): i is Point => !!i);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue