diff --git a/packages/math/ellipse.test.ts b/packages/math/ellipse.test.ts index 653c4c55f0..3a82cda73b 100644 --- a/packages/math/ellipse.test.ts +++ b/packages/math/ellipse.test.ts @@ -3,7 +3,9 @@ import { ellipseSegmentInterceptPoints, ellipseIncludesPoint, ellipseTouchesPoint, + ellipseIntersectsLine, } from "./ellipse"; +import { line } from "./line"; import { point } from "./point"; import { segment } from "./segment"; import type { Ellipse, GlobalPoint } from "./types"; @@ -47,7 +49,7 @@ describe("point and ellipse", () => { }); }); -describe("line and ellipse", () => { +describe("segment and ellipse", () => { it("detects outside segment", () => { const e = ellipse(point(0, 0), 2, 2); @@ -77,3 +79,32 @@ describe("line and ellipse", () => { ).toEqual([]); }); }); + +describe("line and ellipse", () => { + const e = ellipse(point(0, 0), 2, 2); + + it("detects outside line", () => { + expect( + ellipseIntersectsLine( + e, + line(point(-10, -10), point(10, -10)), + ), + ).toEqual([]); + }); + it("detects line intersecting ellipse", () => { + expect( + ellipseIntersectsLine(e, line(point(0, -1), point(0, 1))), + ).toEqual([point(0, 2), point(0, -2)]); + expect( + ellipseIntersectsLine( + e, + line(point(-100, 0), point(-10, 0)), + ).map(([x, y]) => point(Math.round(x), Math.round(y))), + ).toEqual([point(2, 0), point(-2, 0)]); + }); + it("detects line touching ellipse", () => { + expect( + ellipseIntersectsLine(e, line(point(-2, -2), point(2, -2))), + ).toEqual([point(0, -2)]); + }); +}); diff --git a/packages/math/ellipse.ts b/packages/math/ellipse.ts index dff59bcc76..787faa052b 100644 --- a/packages/math/ellipse.ts +++ b/packages/math/ellipse.ts @@ -1,5 +1,5 @@ -import { point, pointDistance, pointFromVector } from "./point"; -import type { Ellipse, GenericPoint, Segment } from "./types"; +import { point, pointDistance, pointFromVector, pointsEqual } from "./point"; +import type { Ellipse, GenericPoint, Line, Segment } from "./types"; import { PRECISION } from "./utils"; import { vector, @@ -180,3 +180,37 @@ export function ellipseSegmentInterceptPoints( return intersections; } + +export function ellipseIntersectsLine( + { center, halfWidth, halfHeight }: Ellipse, + [g, h]: Line, +): Point[] { + const [cx, cy] = center; + const x1 = g[0] - cx; + const y1 = g[1] - cy; + const x2 = h[0] - cx; + const y2 = h[1] - cy; + const a = + Math.pow(x2 - x1, 2) / Math.pow(halfWidth, 2) + + Math.pow(y2 - y1, 2) / Math.pow(halfHeight, 2); + const b = + 2 * + ((x1 * (x2 - x1)) / Math.pow(halfWidth, 2) + + (y1 * (y2 - y1)) / Math.pow(halfHeight, 2)); + const c = + Math.pow(x1, 2) / Math.pow(halfWidth, 2) + + Math.pow(y1, 2) / Math.pow(halfHeight, 2) - + 1; + const t1 = (-b + Math.sqrt(Math.pow(b, 2) - 4 * a * c)) / (2 * a); + const t2 = (-b - Math.sqrt(Math.pow(b, 2) - 4 * a * c)) / (2 * a); + const candidates = [ + point(x1 + t1 * (x2 - x1) + cx, y1 + t1 * (y2 - y1) + cy), + point(x1 + t2 * (x2 - x1) + cx, y1 + t2 * (y2 - y1) + cy), + ].filter((p) => !isNaN(p[0]) && !isNaN(p[1])); + + if (candidates.length === 2 && pointsEqual(candidates[0], candidates[1])) { + return [candidates[0]]; + } + + return candidates; +} diff --git a/packages/math/line.ts b/packages/math/line.ts index 16a0ec02f2..6dca92b2ab 100644 --- a/packages/math/line.ts +++ b/packages/math/line.ts @@ -1,3 +1,4 @@ +import { ellipseIntersectsLine } from "./ellipse"; import { point, pointCenter, pointRotateRads } from "./point"; import { segmentIncludesPoint } from "./segment"; import type { GenericPoint, Line, Radians, Segment } from "./types"; @@ -93,3 +94,5 @@ export function lineIntersectsSegment( return candidate; } + +export const lineInterceptsEllipse = ellipseIntersectsLine; diff --git a/packages/math/segment.ts b/packages/math/segment.ts index 775402ab4f..ef2a5eb7a2 100644 --- a/packages/math/segment.ts +++ b/packages/math/segment.ts @@ -6,7 +6,7 @@ import { pointRotateRads, pointsEqual, } from "./point"; -import type { GenericPoint, Segment, Radians, Line } from "./types"; +import type { GenericPoint, Segment, Radians } from "./types"; import { PRECISION } from "./utils"; import { vectorAdd, @@ -180,15 +180,10 @@ export function segmentDistanceToPoint( } /** - * Returns the intersection point between a segment and a line, if any + * Returns the intersection point of a segment and a line * - * @param s * @param l + * @param s * @returns */ -export function segmentIntersectsLine( - s: Segment, - l: Line, -): Point | null { - return lineIntersectsSegment(l, s); -} +export const segmentIntersectsLine = lineIntersectsSegment;