import { fuzzyIsNull, fuzzyEqual } from '../../math/float'; import { M_2PI } from '../../math/constants'; import { Vector2D } from '../Vector2D'; import { Matrix } from '../Matrix'; import { intersectCircleCircle } from '../intersections'; import { Shape } from './Shape'; import { Point } from './Point'; import { Line } from './Line'; import { Rectangle } from './Rectangle'; import { Polygon } from './Polygon'; import { Polyline } from './Polyline'; import { intersectPointCircleHelper, intersectLineCircleHelper, intersectRectangleCircleHelper, intersectPolygonCircleHelper, intersectPolylineCircleHelper } from './internal/intersections'; export class Circle extends Shape { c: Vector2D; r: number; constructor(c = new Vector2D(), r = 0) { super(); this.c = c; this.r = r; } get cx() { return this.c.x; } set cx(cx: number) { this.c.x = cx; } get cy() { return this.c.y; } set cy(cy: number) { this.c.y = cy; } clone() { return new Circle(this.c.clone(), this.r); } isNull() { return this.r === 0; } fuzzyIsNull(epsilon?: number) { return fuzzyIsNull(this.r, epsilon); } equals(other: Circle) { return this.c.equals(other.c) && this.r === other.r; } fuzzyEquals(other: Circle, epsilon?: number) { return this.c.fuzzyEquals(other.c, epsilon) && fuzzyEqual(this.r, other.r, epsilon); } translate(dx: number, dy: number) { this.c.translate(dx, dy); return this; } translated(dx: number, dy: number) { return new Circle(this.c.translated(dx, dy), this.r); } transform(matrix: Matrix) { const rx = this.c.translated(this.r, 0).transform(matrix); const ry = this.c.translated(0, this.r).transform(matrix); this.c.transform(matrix); this.r = Math.max(rx.minus(this.c).length(), ry.minus(this.c).length()); return this; } transformed(matrix: Matrix) { const rx = this.c.translated(this.r, 0).transform(matrix); const ry = this.c.translated(0, this.r).transform(matrix); const c = this.c.transformed(matrix); const r = Math.max(rx.minus(c).length(), ry.minus(c).length()); return new Circle(c, r); } draw(context: CanvasRenderingContext2D, stroked?: boolean, filled?: boolean) { context.beginPath(); context.arc(this.cx, this.cy, this.r, 0, M_2PI, false); if (filled) context.fill(); if (stroked || !filled) context.stroke(); } map(callbackfn: (v: Vector2D, index: number, circle: Circle) => Vector2D, thisArg?: any) { return new Circle(callbackfn.call(thisArg, this.c, 0, this), callbackfn.call(thisArg, new Vector2D(this.r, this.r), 1, this).x); } boundingRectangle() { return new Rectangle(new Vector2D(this.cx - this.r, this.cy - this.r), new Vector2D(2 * this.r, 2 * this.r)); } containsPoint(other: Point, epsilon?: number) { return containsPointHelper(other.toVector2D(), this.c, this.r); } containsLine(other: Line, epsilon?: number) { return containsPointHelper(other.p1, this.c, this.r) && containsPointHelper(other.p2, this.c, this.r); } //! \see https://stackoverflow.com/a/33490701/2895579 containsCircle(other: Circle, epsilon?: number) { return (this.r - other.r) > other.c.minus(this.c).length(); } containsRectangle(other: Rectangle, epsilon?: number) { return other.center().minus(this.c).length() + other.s.mul(0.5).length() < this.r; } containsPolygon(other: Polygon, epsilon?: number) { if (other.isEmpty()) return false; const c = this.c; const r = this.r; for (let i = 0; i < other.points.length; i++) { const p = other.points[i]; if (!containsPointHelper(p, c, r)) return false; } return true; } containsPolyline(other: Polyline, epsilon?: number) { if (other.isEmpty()) return false; const c = this.c; const r = this.r; for (let i = 0; i < other.points.length; i++) { const p = other.points[i]; if (!containsPointHelper(p, c, r)) return false; } return true; } contains(other: Shape, epsilon?: number) { return other.containsCircle(this, epsilon); } intersectsPoint(other: Point, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectPointCircleHelper(this, other, other.toVector2D(), this.c, this.r, callbackfn, thisArg, epsilon); } intersectsLine(other: Line, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectLineCircleHelper(this, other, other.p1, other.p2, this.c, this.r, callbackfn, thisArg, epsilon); } intersectsCircle(other: Circle, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { const points = intersectCircleCircle(this.c, this.r, other.c, other.r, epsilon); return points !== undefined && (callbackfn === undefined || callbackfn.call(thisArg, points, this, other)); } intersectsRectangle(other: Rectangle, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectRectangleCircleHelper(this, other, other.topLeft(), other.topRight(), other.bottomRight(), other.bottomLeft(), this.c, this.r, callbackfn, thisArg, epsilon); } intersectsPolygon(other: Polygon, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectPolygonCircleHelper(this, other, other.points, this.c, this.r, callbackfn, thisArg, epsilon); } intersectsPolyline(other: Polyline, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectPolylineCircleHelper(this, other, other.points, this.c, this.r, callbackfn, thisArg, epsilon); } intersects(other: Shape, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number) { return other.intersectsCircle(this, callbackfn, thisArg, epsilon); } static create(center?: Vector2D, radius?: number) { return new Circle(center, radius); } static readonly className: string = 'Circle'; } function containsPointHelper(p: Vector2D, c: Vector2D, r: number) { const dx = p.x - c.x; const dy = p.y - c.y; return dx * dx + dy * dy < r * r; }