Arc code updated

This commit is contained in:
Mark Tolmacs 2025-02-11 15:17:04 +01:00
parent fcb3060388
commit 85b4b315c7
No known key found for this signature in database
2 changed files with 71 additions and 16 deletions

View file

@ -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([

View file

@ -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" &&