mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
refactor: separate elements logic into a standalone package (#9285)
Some checks failed
Auto release excalidraw next / Auto-release-excalidraw-next (push) Failing after 2m36s
Build Docker image / build-docker (push) Failing after 6s
Cancel previous runs / cancel (push) Failing after 1s
Publish Docker / publish-docker (push) Failing after 31s
New Sentry production release / sentry (push) Failing after 2m3s
Some checks failed
Auto release excalidraw next / Auto-release-excalidraw-next (push) Failing after 2m36s
Build Docker image / build-docker (push) Failing after 6s
Cancel previous runs / cancel (push) Failing after 1s
Publish Docker / publish-docker (push) Failing after 31s
New Sentry production release / sentry (push) Failing after 2m3s
This commit is contained in:
parent
a18f059188
commit
432a46ef9e
372 changed files with 3466 additions and 2466 deletions
102
packages/math/tests/curve.test.ts
Normal file
102
packages/math/tests/curve.test.ts
Normal file
|
@ -0,0 +1,102 @@
|
|||
import "@excalidraw/utils/test-utils";
|
||||
|
||||
import {
|
||||
curve,
|
||||
curveClosestPoint,
|
||||
curveIntersectLineSegment,
|
||||
curvePointDistance,
|
||||
} from "../src/curve";
|
||||
import { pointFrom } from "../src/point";
|
||||
import { lineSegment } from "../src/segment";
|
||||
|
||||
describe("Math curve", () => {
|
||||
describe("line segment intersection", () => {
|
||||
it("point is found when control points are the same", () => {
|
||||
const c = curve(
|
||||
pointFrom(100, 0),
|
||||
pointFrom(100, 100),
|
||||
pointFrom(100, 100),
|
||||
pointFrom(0, 100),
|
||||
);
|
||||
const l = lineSegment(pointFrom(0, 0), pointFrom(200, 200));
|
||||
|
||||
expect(curveIntersectLineSegment(c, l)).toCloselyEqualPoints([
|
||||
[87.5, 87.5],
|
||||
]);
|
||||
});
|
||||
|
||||
it("point is found when control points aren't the same", () => {
|
||||
const c = curve(
|
||||
pointFrom(100, 0),
|
||||
pointFrom(100, 60),
|
||||
pointFrom(60, 100),
|
||||
pointFrom(0, 100),
|
||||
);
|
||||
const l = lineSegment(pointFrom(0, 0), pointFrom(200, 200));
|
||||
|
||||
expect(curveIntersectLineSegment(c, l)).toCloselyEqualPoints([
|
||||
[72.5, 72.5],
|
||||
]);
|
||||
});
|
||||
|
||||
it("points are found when curve is sliced at 3 points", () => {
|
||||
const c = curve(
|
||||
pointFrom(-50, -50),
|
||||
pointFrom(10, -50),
|
||||
pointFrom(10, 50),
|
||||
pointFrom(50, 50),
|
||||
);
|
||||
const l = lineSegment(pointFrom(0, 112.5), pointFrom(90, 0));
|
||||
|
||||
expect(curveIntersectLineSegment(c, l)).toCloselyEqualPoints([[50, 50]]);
|
||||
});
|
||||
|
||||
it("can be detected where the determinant is overly precise", () => {
|
||||
const c = curve(
|
||||
pointFrom(41.028864759926016, 12.226249068355052),
|
||||
pointFrom(41.028864759926016, 33.55958240168839),
|
||||
pointFrom(30.362198093259348, 44.22624906835505),
|
||||
pointFrom(9.028864759926016, 44.22624906835505),
|
||||
);
|
||||
const l = lineSegment(
|
||||
pointFrom(-82.30963544324186, -41.19949363038283),
|
||||
|
||||
pointFrom(188.2149592542487, 134.75505940984908),
|
||||
);
|
||||
|
||||
expect(curveIntersectLineSegment(c, l)).toCloselyEqualPoints([
|
||||
[34.4, 34.71],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("point closest to other", () => {
|
||||
it("point can be found", () => {
|
||||
const c = curve(
|
||||
pointFrom(-50, -50),
|
||||
pointFrom(10, -50),
|
||||
pointFrom(10, 50),
|
||||
pointFrom(50, 50),
|
||||
);
|
||||
const p = pointFrom(0, 0);
|
||||
|
||||
expect([curveClosestPoint(c, p)]).toCloselyEqualPoints([
|
||||
[5.965462100367372, -3.04104878946646],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("point shortest distance", () => {
|
||||
it("can be determined", () => {
|
||||
const c = curve(
|
||||
pointFrom(-50, -50),
|
||||
pointFrom(10, -50),
|
||||
pointFrom(10, 50),
|
||||
pointFrom(50, 50),
|
||||
);
|
||||
const p = pointFrom(0, 0);
|
||||
|
||||
expect(curvePointDistance(c, p)).toBeCloseTo(6.695873043213627);
|
||||
});
|
||||
});
|
||||
});
|
127
packages/math/tests/ellipse.test.ts
Normal file
127
packages/math/tests/ellipse.test.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
import {
|
||||
ellipse,
|
||||
ellipseSegmentInterceptPoints,
|
||||
ellipseIncludesPoint,
|
||||
ellipseTouchesPoint,
|
||||
ellipseLineIntersectionPoints,
|
||||
} from "../src/ellipse";
|
||||
import { line } from "../src/line";
|
||||
import { pointFrom } from "../src/point";
|
||||
import { lineSegment } from "../src/segment";
|
||||
|
||||
import type { Ellipse, GlobalPoint } from "../src/types";
|
||||
|
||||
describe("point and ellipse", () => {
|
||||
it("point on ellipse", () => {
|
||||
const target: Ellipse<GlobalPoint> = ellipse(pointFrom(1, 2), 2, 1);
|
||||
[
|
||||
pointFrom(1, 3),
|
||||
pointFrom(1, 1),
|
||||
pointFrom(3, 2),
|
||||
pointFrom(-1, 2),
|
||||
].forEach((p) => {
|
||||
expect(ellipseTouchesPoint(p, target)).toBe(true);
|
||||
});
|
||||
expect(ellipseTouchesPoint(pointFrom(-0.4, 2.7), target, 0.1)).toBe(true);
|
||||
expect(ellipseTouchesPoint(pointFrom(-0.4, 2.71), target, 0.01)).toBe(true);
|
||||
|
||||
expect(ellipseTouchesPoint(pointFrom(2.4, 2.7), target, 0.1)).toBe(true);
|
||||
expect(ellipseTouchesPoint(pointFrom(2.4, 2.71), target, 0.01)).toBe(true);
|
||||
|
||||
expect(ellipseTouchesPoint(pointFrom(2, 1.14), target, 0.1)).toBe(true);
|
||||
expect(ellipseTouchesPoint(pointFrom(2, 1.14), target, 0.01)).toBe(true);
|
||||
|
||||
expect(ellipseTouchesPoint(pointFrom(0, 1.14), target, 0.1)).toBe(true);
|
||||
expect(ellipseTouchesPoint(pointFrom(0, 1.14), target, 0.01)).toBe(true);
|
||||
|
||||
expect(ellipseTouchesPoint(pointFrom(0, 2.8), target)).toBe(false);
|
||||
expect(ellipseTouchesPoint(pointFrom(2, 1.2), target)).toBe(false);
|
||||
});
|
||||
|
||||
it("point in ellipse", () => {
|
||||
const target: Ellipse<GlobalPoint> = ellipse(pointFrom(0, 0), 2, 1);
|
||||
[
|
||||
pointFrom(0, 1),
|
||||
pointFrom(0, -1),
|
||||
pointFrom(2, 0),
|
||||
pointFrom(-2, 0),
|
||||
].forEach((p) => {
|
||||
expect(ellipseIncludesPoint(p, target)).toBe(true);
|
||||
});
|
||||
|
||||
expect(ellipseIncludesPoint(pointFrom(-1, 0.8), target)).toBe(true);
|
||||
expect(ellipseIncludesPoint(pointFrom(1, -0.8), target)).toBe(true);
|
||||
|
||||
// Point on outline
|
||||
expect(ellipseIncludesPoint(pointFrom(2, 0), target)).toBe(true);
|
||||
|
||||
expect(ellipseIncludesPoint(pointFrom(-1, 1), target)).toBe(false);
|
||||
expect(ellipseIncludesPoint(pointFrom(-1.4, 0.8), target)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("segment and ellipse", () => {
|
||||
it("detects outside segment", () => {
|
||||
const e = ellipse(pointFrom(0, 0), 2, 2);
|
||||
|
||||
expect(
|
||||
ellipseSegmentInterceptPoints(
|
||||
e,
|
||||
lineSegment<GlobalPoint>(pointFrom(-100, 0), pointFrom(-10, 0)),
|
||||
),
|
||||
).toEqual([]);
|
||||
expect(
|
||||
ellipseSegmentInterceptPoints(
|
||||
e,
|
||||
lineSegment<GlobalPoint>(pointFrom(-10, 0), pointFrom(10, 0)),
|
||||
),
|
||||
).toEqual([pointFrom(-2, 0), pointFrom(2, 0)]);
|
||||
expect(
|
||||
ellipseSegmentInterceptPoints(
|
||||
e,
|
||||
lineSegment<GlobalPoint>(pointFrom(-10, -2), pointFrom(10, -2)),
|
||||
),
|
||||
).toEqual([pointFrom(0, -2)]);
|
||||
expect(
|
||||
ellipseSegmentInterceptPoints(
|
||||
e,
|
||||
lineSegment<GlobalPoint>(pointFrom(0, -1), pointFrom(0, 1)),
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("line and ellipse", () => {
|
||||
const e = ellipse(pointFrom(0, 0), 2, 2);
|
||||
|
||||
it("detects outside line", () => {
|
||||
expect(
|
||||
ellipseLineIntersectionPoints(
|
||||
e,
|
||||
line<GlobalPoint>(pointFrom(-10, -10), pointFrom(10, -10)),
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
it("detects line intersecting ellipse", () => {
|
||||
expect(
|
||||
ellipseLineIntersectionPoints(
|
||||
e,
|
||||
line<GlobalPoint>(pointFrom(0, -1), pointFrom(0, 1)),
|
||||
),
|
||||
).toEqual([pointFrom(0, 2), pointFrom(0, -2)]);
|
||||
expect(
|
||||
ellipseLineIntersectionPoints(
|
||||
e,
|
||||
line<GlobalPoint>(pointFrom(-100, 0), pointFrom(-10, 0)),
|
||||
).map(([x, y]) => pointFrom(Math.round(x), Math.round(y))),
|
||||
).toEqual([pointFrom(2, 0), pointFrom(-2, 0)]);
|
||||
});
|
||||
it("detects line touching ellipse", () => {
|
||||
expect(
|
||||
ellipseLineIntersectionPoints(
|
||||
e,
|
||||
line<GlobalPoint>(pointFrom(-2, -2), pointFrom(2, -2)),
|
||||
),
|
||||
).toEqual([pointFrom(0, -2)]);
|
||||
});
|
||||
});
|
31
packages/math/tests/line.test.ts
Normal file
31
packages/math/tests/line.test.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { line, linesIntersectAt } from "../src/line";
|
||||
import { pointFrom } from "../src/point";
|
||||
|
||||
describe("line-line intersections", () => {
|
||||
it("should correctly detect intersection at origin", () => {
|
||||
expect(
|
||||
linesIntersectAt(
|
||||
line(pointFrom(-5, -5), pointFrom(5, 5)),
|
||||
line(pointFrom(5, -5), pointFrom(-5, 5)),
|
||||
),
|
||||
).toEqual(pointFrom(0, 0));
|
||||
});
|
||||
|
||||
it("should correctly detect intersection at non-origin", () => {
|
||||
expect(
|
||||
linesIntersectAt(
|
||||
line(pointFrom(0, 0), pointFrom(10, 10)),
|
||||
line(pointFrom(10, 0), pointFrom(0, 10)),
|
||||
),
|
||||
).toEqual(pointFrom(5, 5));
|
||||
});
|
||||
|
||||
it("should correctly detect parallel lines", () => {
|
||||
expect(
|
||||
linesIntersectAt(
|
||||
line(pointFrom(0, 0), pointFrom(0, 10)),
|
||||
line(pointFrom(10, 0), pointFrom(10, 10)),
|
||||
),
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
25
packages/math/tests/point.test.ts
Normal file
25
packages/math/tests/point.test.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { pointFrom, pointRotateRads } from "../src/point";
|
||||
|
||||
import type { Radians } from "../src/types";
|
||||
|
||||
describe("rotate", () => {
|
||||
it("should rotate over (x2, y2) and return the rotated coordinates for (x1, y1)", () => {
|
||||
const x1 = 10;
|
||||
const y1 = 20;
|
||||
const x2 = 20;
|
||||
const y2 = 30;
|
||||
const angle = (Math.PI / 2) as Radians;
|
||||
const [rotatedX, rotatedY] = pointRotateRads(
|
||||
pointFrom(x1, y1),
|
||||
pointFrom(x2, y2),
|
||||
angle,
|
||||
);
|
||||
expect([rotatedX, rotatedY]).toEqual([30, 20]);
|
||||
const res2 = pointRotateRads(
|
||||
pointFrom(rotatedX, rotatedY),
|
||||
pointFrom(x2, y2),
|
||||
-angle as Radians,
|
||||
);
|
||||
expect(res2).toEqual([x1, x2]);
|
||||
});
|
||||
});
|
51
packages/math/tests/range.test.ts
Normal file
51
packages/math/tests/range.test.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { rangeInclusive, rangeIntersection, rangesOverlap } from "../src/range";
|
||||
|
||||
describe("range overlap", () => {
|
||||
const range1_4 = rangeInclusive(1, 4);
|
||||
|
||||
it("should overlap when range a contains range b", () => {
|
||||
expect(rangesOverlap(range1_4, rangeInclusive(2, 3))).toBe(true);
|
||||
expect(rangesOverlap(range1_4, range1_4)).toBe(true);
|
||||
expect(rangesOverlap(range1_4, rangeInclusive(1, 3))).toBe(true);
|
||||
expect(rangesOverlap(range1_4, rangeInclusive(2, 4))).toBe(true);
|
||||
});
|
||||
|
||||
it("should overlap when range b contains range a", () => {
|
||||
expect(rangesOverlap(rangeInclusive(2, 3), range1_4)).toBe(true);
|
||||
expect(rangesOverlap(rangeInclusive(1, 3), range1_4)).toBe(true);
|
||||
expect(rangesOverlap(rangeInclusive(2, 4), range1_4)).toBe(true);
|
||||
});
|
||||
|
||||
it("should overlap when range a and b intersect", () => {
|
||||
expect(rangesOverlap(range1_4, rangeInclusive(2, 5))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("range intersection", () => {
|
||||
const range1_4 = rangeInclusive(1, 4);
|
||||
|
||||
it("should intersect completely with itself", () => {
|
||||
expect(rangeIntersection(range1_4, range1_4)).toEqual(range1_4);
|
||||
});
|
||||
|
||||
it("should intersect irrespective of order", () => {
|
||||
expect(rangeIntersection(range1_4, rangeInclusive(2, 3))).toEqual([2, 3]);
|
||||
expect(rangeIntersection(rangeInclusive(2, 3), range1_4)).toEqual([2, 3]);
|
||||
expect(rangeIntersection(range1_4, rangeInclusive(3, 5))).toEqual(
|
||||
rangeInclusive(3, 4),
|
||||
);
|
||||
expect(rangeIntersection(rangeInclusive(3, 5), range1_4)).toEqual(
|
||||
rangeInclusive(3, 4),
|
||||
);
|
||||
});
|
||||
|
||||
it("should intersect at the edge", () => {
|
||||
expect(rangeIntersection(range1_4, rangeInclusive(4, 5))).toEqual(
|
||||
rangeInclusive(4, 4),
|
||||
);
|
||||
});
|
||||
|
||||
it("should not intersect", () => {
|
||||
expect(rangeIntersection(range1_4, rangeInclusive(5, 7))).toEqual(null);
|
||||
});
|
||||
});
|
21
packages/math/tests/segment.test.ts
Normal file
21
packages/math/tests/segment.test.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { pointFrom } from "../src/point";
|
||||
import { lineSegment, lineSegmentIntersectionPoints } from "../src/segment";
|
||||
|
||||
describe("line-segment intersections", () => {
|
||||
it("should correctly detect intersection", () => {
|
||||
expect(
|
||||
lineSegmentIntersectionPoints(
|
||||
lineSegment(pointFrom(0, 0), pointFrom(5, 0)),
|
||||
lineSegment(pointFrom(2, -2), pointFrom(3, 2)),
|
||||
),
|
||||
).toEqual(pointFrom(2.5, 0));
|
||||
});
|
||||
it("should correctly detect non-intersection", () => {
|
||||
expect(
|
||||
lineSegmentIntersectionPoints(
|
||||
lineSegment(pointFrom(0, 0), pointFrom(5, 0)),
|
||||
lineSegment(pointFrom(3, 1), pointFrom(4, 4)),
|
||||
),
|
||||
).toEqual(null);
|
||||
});
|
||||
});
|
12
packages/math/tests/vector.test.ts
Normal file
12
packages/math/tests/vector.test.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { isVector } from "../src/vector";
|
||||
|
||||
describe("Vector", () => {
|
||||
test("isVector", () => {
|
||||
expect(isVector([5, 5])).toBe(true);
|
||||
expect(isVector([-5, -5])).toBe(true);
|
||||
expect(isVector([5, 0.5])).toBe(true);
|
||||
expect(isVector(null)).toBe(false);
|
||||
expect(isVector(undefined)).toBe(false);
|
||||
expect(isVector([5, NaN])).toBe(false);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue