import { fuzzyEqual } from '../../math/float'; import { M_2PI } from '../../math/constants'; import { Vector2D } from '../Vector2D'; import { Matrix } from '../Matrix'; import { Shape } from './Shape'; import { Point } from './Point'; import { Circle } from './Circle'; import { Rectangle } from './Rectangle'; import { Polygon } from './Polygon'; import { Polyline } from './Polyline'; import { intersectPointLineHelper, intersectLineLineHelper, intersectLineCircleHelper, intersectLineRectangleHelper, intersectLinePolygonHelper, intersectLinePolylineHelper } from './internal/intersections'; export class Line extends Shape { p1: Vector2D; p2: Vector2D; constructor(p1 = new Vector2D(), p2 = new Vector2D()) { super(); this.p1 = p1; this.p2 = p2; } get x1() { return this.p1.x; } set x1(x1: number) { this.p1.x = x1; } get x2() { return this.p2.x; } set x2(x2: number) { this.p2.x = x2; } get y1() { return this.p1.y; } set y1(y1: number) { this.p1.y = y1; } get y2() { return this.p2.y; } set y2(y2: number) { this.p2.y = y2; } clone() { return new Line(this.p1.clone(), this.p2.clone()); } isNull() { return this.x1 === this.x2 && this.y1 === this.y2; } fuzzyIsNull(epsilon?: number) { return fuzzyEqual(this.x1, this.x2, epsilon) && fuzzyEqual(this.y1, this.y2, epsilon); } equals(other: Line) { return this.p1.equals(other.p1) && this.p2.equals(other.p2); } fuzzyEquals(other: Line, epsilon?: number) { return this.p1.fuzzyEquals(other.p1, epsilon) && this.p2.fuzzyEquals(other.p2, epsilon); } translate(dx: number, dy: number) { this.p1.translate(dx, dy); this.p2.translate(dx, dy); return this; } translated(dx: number, dy: number) { return new Line(this.p1.translated(dx, dy), this.p2.translated(dx, dy)); } transform(matrix: Matrix) { this.p1.transform(matrix); this.p2.transform(matrix); return this; } transformed(matrix: Matrix) { return new Line(this.p1.transformed(matrix), this.p2.transformed(matrix)); } draw(context: CanvasRenderingContext2D, stroked?: boolean, filled?: boolean) { context.beginPath(); context.moveTo(this.x1, this.y1); context.lineTo(this.x2, this.y2); context.stroke(); } dx() { return this.x2 - this.x1; } dy() { return this.y2 - this.y1; } center() { return new Vector2D(0.5 * this.x1 + 0.5 * this.x2, 0.5 * this.y1 + 0.5 * this.y2); } normalVector() { return new Vector2D(this.dy(), -this.dx()); } unitVector() { const dx = this.dx(); const dy = this.dy(); const length = Math.sqrt(dx * dx + dy * dy); return new Vector2D(dx / length, dy / length); } length() { const dx = this.dx(); const dy = this.dy(); return Math.sqrt(dx * dx + dy * dy); } setLength(length: number) { const v = this.unitVector(); this.p2 = new Vector2D(this.x1 + v.x * length, this.y1 + v.y * length); } pointAt(t: number) { return new Vector2D(this.x1 + this.dx() * t, this.y1 + this.dy() * t); } angle(epsilon?: number) { const theta = Math.atan2(-this.dy(), this.dx()); const thetaNormalized = theta < 0 ? theta + M_2PI : theta; return fuzzyEqual(thetaNormalized, M_2PI, epsilon) ? 0 : thetaNormalized; } angleTo(other: Line, epsilon?: number) { if (this.fuzzyIsNull(epsilon) || other.fuzzyIsNull(epsilon)) return 0; const delta = other.angle() - this.angle(); // if (fuzzyEqual(delta, M_2PI, epsilon)) // return 0; return delta < 0 ? delta + M_2PI : delta; } setAngle(rad: number) { const len = this.length(); this.x2 = this.x1 + Math.cos(rad) * len; this.y2 = this.y1 + -Math.sin(rad) * len; } map(callbackfn: (value: Vector2D, index: number, line: Line) => Vector2D, thisArg?: any) { return new Line(callbackfn.call(thisArg, this.p1, 0, this), callbackfn.call(thisArg, this.p2, 1, this)); } boundingRectangle() { return (new Rectangle(this.p1, this.p2.minus(this.p1))).normalized(); } containsPoint(other: Point, epsilon?: number) { return false; } containsLine(other: Line, epsilon?: number) { return false; } containsCircle(other: Circle, epsilon?: number) { return false; } containsRectangle(other: Rectangle, epsilon?: number) { return false; } containsPolygon(other: Polygon, epsilon?: number) { return false; } containsPolyline(other: Polyline, epsilon?: number) { return false; } contains(other: Shape, epsilon?: number) { return other.containsLine(this, epsilon); } intersectsPoint(other: Point, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectPointLineHelper(this, other, other.toVector2D(), this.p1, this.p2, callbackfn, thisArg, epsilon); } intersectsLine(other: Line, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectLineLineHelper(this, other, this.p1, this.p2, other.p1, other.p2, callbackfn, thisArg, epsilon); } intersectsCircle(other: Circle, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectLineCircleHelper(this, other, this.p1, this.p2, other.c, other.r, callbackfn, thisArg, epsilon); } intersectsRectangle(other: Rectangle, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectLineRectangleHelper(this, other, this.p1, this.p2, other.topLeft(), other.topRight(), other.bottomRight(), other.bottomLeft(), callbackfn, thisArg, epsilon); } intersectsPolygon(other: Polygon, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectLinePolygonHelper(this, other, this.p1, this.p2, other.points, callbackfn, thisArg, epsilon); } intersectsPolyline(other: Polyline, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectLinePolylineHelper(this, other, this.p1, this.p2, other.points, callbackfn, thisArg, epsilon); } intersects(other: Shape, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number) { return other.intersectsLine(this, callbackfn, thisArg, epsilon); } static create(p1?: Vector2D, p2?: Vector2D) { return new Line(p1, p2); } static fromPolar(len: number, rad: number) { return new Line(new Vector2D(0, 0), new Vector2D(Math.cos(rad) * len, -Math.sin(rad) * len)); } static readonly className: string = 'Line'; }