mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Arc code updated
This commit is contained in:
parent
fcb3060388
commit
85b4b315c7
2 changed files with 71 additions and 16 deletions
|
@ -1,4 +1,3 @@
|
||||||
import { radians } from "./angle";
|
|
||||||
import {
|
import {
|
||||||
arc,
|
arc,
|
||||||
arcIncludesPoint,
|
arcIncludesPoint,
|
||||||
|
@ -8,12 +7,18 @@ import {
|
||||||
import { line } from "./line";
|
import { line } from "./line";
|
||||||
import { pointFrom } from "./point";
|
import { pointFrom } from "./point";
|
||||||
import { segment } from "./segment";
|
import { segment } from "./segment";
|
||||||
|
import type { GlobalPoint, Radians } from "./types";
|
||||||
|
|
||||||
describe("point on arc", () => {
|
describe("point on arc", () => {
|
||||||
it("should detect point on simple arc", () => {
|
it("should detect point on simple arc", () => {
|
||||||
expect(
|
expect(
|
||||||
arcIncludesPoint(
|
arcIncludesPoint(
|
||||||
arc(pointFrom(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
arc(
|
||||||
|
pointFrom(0, 0),
|
||||||
|
1,
|
||||||
|
(-Math.PI / 4) as Radians,
|
||||||
|
(Math.PI / 4) as Radians,
|
||||||
|
),
|
||||||
pointFrom(0.92291667, 0.385),
|
pointFrom(0.92291667, 0.385),
|
||||||
),
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
|
@ -21,7 +26,12 @@ 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(
|
||||||
arcIncludesPoint(
|
arcIncludesPoint(
|
||||||
arc(pointFrom(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
arc(
|
||||||
|
pointFrom(0, 0),
|
||||||
|
1,
|
||||||
|
(-Math.PI / 4) as Radians,
|
||||||
|
(Math.PI / 4) as Radians,
|
||||||
|
),
|
||||||
pointFrom(-0.92291667, 0.385),
|
pointFrom(-0.92291667, 0.385),
|
||||||
),
|
),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
|
@ -29,7 +39,12 @@ 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(
|
||||||
arcIncludesPoint(
|
arcIncludesPoint(
|
||||||
arc(pointFrom(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
arc(
|
||||||
|
pointFrom(0, 0),
|
||||||
|
1,
|
||||||
|
(-Math.PI / 4) as Radians,
|
||||||
|
(Math.PI / 4) as Radians,
|
||||||
|
),
|
||||||
pointFrom(-0.5, 0.5),
|
pointFrom(-0.5, 0.5),
|
||||||
),
|
),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
|
@ -40,16 +55,37 @@ describe("intersection", () => {
|
||||||
it("should report correct interception point for segment", () => {
|
it("should report correct interception point for segment", () => {
|
||||||
expect(
|
expect(
|
||||||
arcSegmentInterceptPoints(
|
arcSegmentInterceptPoints(
|
||||||
arc(pointFrom(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
arc(
|
||||||
|
pointFrom(0, 0),
|
||||||
|
1,
|
||||||
|
(-Math.PI / 4) as Radians,
|
||||||
|
(Math.PI / 4) as Radians,
|
||||||
|
),
|
||||||
segment(pointFrom(2, 1), pointFrom(0, 0)),
|
segment(pointFrom(2, 1), pointFrom(0, 0)),
|
||||||
),
|
),
|
||||||
).toEqual([pointFrom(0.894427190999916, 0.447213595499958)]);
|
).toEqual([pointFrom(0.894427190999916, 0.447213595499958)]);
|
||||||
|
expect(
|
||||||
|
arcSegmentInterceptPoints(
|
||||||
|
arc(
|
||||||
|
pointFrom(0, 0),
|
||||||
|
1,
|
||||||
|
Math.PI as Radians,
|
||||||
|
((3 / 2) * Math.PI) as Radians,
|
||||||
|
),
|
||||||
|
segment(pointFrom(-10, -10), pointFrom(0, 0)),
|
||||||
|
),
|
||||||
|
).toEqual([pointFrom(-0.7071067811865479, -0.7071067811865479)]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should report both interception points when present for segment", () => {
|
it("should report both interception points when present for segment", () => {
|
||||||
expect(
|
expect(
|
||||||
arcSegmentInterceptPoints(
|
arcSegmentInterceptPoints(
|
||||||
arc(pointFrom(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
arc(
|
||||||
|
pointFrom(0, 0),
|
||||||
|
1,
|
||||||
|
(-Math.PI / 4) as Radians,
|
||||||
|
(Math.PI / 4) as Radians,
|
||||||
|
),
|
||||||
segment(pointFrom(0.9, -2), pointFrom(0.9, 2)),
|
segment(pointFrom(0.9, -2), pointFrom(0.9, 2)),
|
||||||
),
|
),
|
||||||
).toEqual([
|
).toEqual([
|
||||||
|
@ -61,7 +97,12 @@ describe("intersection", () => {
|
||||||
it("should report correct interception point for line", () => {
|
it("should report correct interception point for line", () => {
|
||||||
expect(
|
expect(
|
||||||
arcLineInterceptPoints(
|
arcLineInterceptPoints(
|
||||||
arc(pointFrom(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
arc(
|
||||||
|
pointFrom<GlobalPoint>(0, 0),
|
||||||
|
1,
|
||||||
|
(-Math.PI / 4) as Radians,
|
||||||
|
(Math.PI / 4) as Radians,
|
||||||
|
),
|
||||||
line(pointFrom(2, 1), pointFrom(0, 0)),
|
line(pointFrom(2, 1), pointFrom(0, 0)),
|
||||||
),
|
),
|
||||||
).toEqual([pointFrom(0.894427190999916, 0.447213595499958)]);
|
).toEqual([pointFrom(0.894427190999916, 0.447213595499958)]);
|
||||||
|
@ -70,7 +111,12 @@ describe("intersection", () => {
|
||||||
it("should report both interception points when present for line", () => {
|
it("should report both interception points when present for line", () => {
|
||||||
expect(
|
expect(
|
||||||
arcLineInterceptPoints(
|
arcLineInterceptPoints(
|
||||||
arc(pointFrom(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
arc(
|
||||||
|
pointFrom<GlobalPoint>(0, 0),
|
||||||
|
1,
|
||||||
|
(-Math.PI / 4) as Radians,
|
||||||
|
(Math.PI / 4) as Radians,
|
||||||
|
),
|
||||||
line(pointFrom(0.9, -2), pointFrom(0.9, 2)),
|
line(pointFrom(0.9, -2), pointFrom(0.9, 2)),
|
||||||
),
|
),
|
||||||
).toEqual([
|
).toEqual([
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { cartesian2Polar, normalizeRadians, radians } from "./angle";
|
import { cartesian2Polar, normalizeRadians } from "./angle";
|
||||||
import {
|
import {
|
||||||
ellipse,
|
ellipse,
|
||||||
ellipseDistanceFromPoint,
|
ellipseDistanceFromPoint,
|
||||||
|
@ -6,7 +6,15 @@ import {
|
||||||
ellipseSegmentInterceptPoints,
|
ellipseSegmentInterceptPoints,
|
||||||
} from "./ellipse";
|
} from "./ellipse";
|
||||||
import { pointFrom, pointDistance, isPoint } from "./point";
|
import { pointFrom, pointDistance, isPoint } from "./point";
|
||||||
import type { GenericPoint, Segment, Radians, Arc, Line } from "./types";
|
import type {
|
||||||
|
GlobalPoint,
|
||||||
|
LocalPoint,
|
||||||
|
Radians,
|
||||||
|
Arc,
|
||||||
|
Line,
|
||||||
|
GenericPoint,
|
||||||
|
Segment,
|
||||||
|
} from "./types";
|
||||||
import { PRECISION } from "./utils";
|
import { PRECISION } from "./utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,7 +68,7 @@ export function arcDistanceFromPoint<Point extends GenericPoint>(
|
||||||
p: Point,
|
p: Point,
|
||||||
) {
|
) {
|
||||||
const theta = normalizeRadians(
|
const theta = normalizeRadians(
|
||||||
radians(Math.atan2(p[0] - a.center[0], p[1] - a.center[1])),
|
Math.atan2(p[0] - a.center[0], p[1] - a.center[1]) as Radians,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (a.startAngle <= theta && a.endAngle >= theta) {
|
if (a.startAngle <= theta && a.endAngle >= theta) {
|
||||||
|
@ -98,11 +106,10 @@ export function arcSegmentInterceptPoints<Point extends GenericPoint>(
|
||||||
return ellipseSegmentInterceptPoints(
|
return ellipseSegmentInterceptPoints(
|
||||||
ellipse(a.center, a.radius, a.radius),
|
ellipse(a.center, a.radius, a.radius),
|
||||||
s,
|
s,
|
||||||
).filter((candidate) => {
|
).filter((candidate: Point) => {
|
||||||
const [candidateRadius, candidateAngle] = cartesian2Polar(
|
const [candidateRadius, candidateAngle] = cartesian2Polar(
|
||||||
pointFrom(candidate[0] - a.center[0], candidate[1] - a.center[1]),
|
pointFrom(candidate[0] - a.center[0], candidate[1] - a.center[1]),
|
||||||
);
|
);
|
||||||
|
|
||||||
return Math.abs(a.radius - candidateRadius) < PRECISION &&
|
return Math.abs(a.radius - candidateRadius) < PRECISION &&
|
||||||
a.startAngle > a.endAngle
|
a.startAngle > a.endAngle
|
||||||
? a.startAngle <= candidateAngle || a.endAngle >= candidateAngle
|
? a.startAngle <= candidateAngle || a.endAngle >= candidateAngle
|
||||||
|
@ -118,14 +125,14 @@ export function arcSegmentInterceptPoints<Point extends GenericPoint>(
|
||||||
* @param l
|
* @param l
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function arcLineInterceptPoints<Point extends GenericPoint>(
|
export function arcLineInterceptPoints<Point extends GlobalPoint>(
|
||||||
a: Readonly<Arc<Point>>,
|
a: Readonly<Arc<Point>>,
|
||||||
l: Readonly<Line<Point>>,
|
l: Readonly<Line<Point>>,
|
||||||
): Point[] {
|
): Point[] {
|
||||||
return ellipseLineIntersectionPoints(
|
return ellipseLineIntersectionPoints(
|
||||||
ellipse(a.center, a.radius, a.radius),
|
ellipse(a.center, a.radius, a.radius),
|
||||||
l,
|
l,
|
||||||
).filter((candidate) => {
|
).filter((candidate: Point) => {
|
||||||
const [candidateRadius, candidateAngle] = cartesian2Polar(
|
const [candidateRadius, candidateAngle] = cartesian2Polar(
|
||||||
pointFrom(candidate[0] - a.center[0], candidate[1] - a.center[1]),
|
pointFrom(candidate[0] - a.center[0], candidate[1] - a.center[1]),
|
||||||
);
|
);
|
||||||
|
@ -137,7 +144,9 @@ export function arcLineInterceptPoints<Point extends GenericPoint>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isArc<Point extends GenericPoint>(v: unknown): v is Arc<Point> {
|
export function isArc<Point extends GlobalPoint | LocalPoint>(
|
||||||
|
v: unknown,
|
||||||
|
): v is Arc<Point> {
|
||||||
return (
|
return (
|
||||||
v != null &&
|
v != null &&
|
||||||
typeof v === "object" &&
|
typeof v === "object" &&
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue