import { Vector2D } from '../Vector2D'; import { Matrix } from '../Matrix'; import { intersectLineLine } from '../intersections'; import { Shape } from './Shape'; import { Point } from './Point'; import { Line } from './Line'; import { Circle } from './Circle'; import { Rectangle } from './Rectangle'; import { Polygon } from './Polygon'; import { intersectPointPolylineHelper, intersectLinePolylineHelper, intersectPolylineCircleHelper, intersectPolylineRectangleHelper, intersectPolylinePolygonHelper, } from './internal/intersections'; export class Polyline extends Shape { points: Vector2D[]; constructor(points: Vector2D[] = []) { super(); this.points = points; } get size() { return this.points.length; } set size(size: number) { this.points.length = size; } clone() { return new Polyline(this.points.map(p => p.clone())); } isEmpty() { return this.points.length === 0; } first() { return this.points[0]; } last() { return this.points[this.points.length - 1]; } isNull() { if (this.points.length === 0) return true; let p1 = this.points[0]; for (let i = 1; i < this.points.length; i++) { const p2 = this.points[i]; if (!p1.equals(p2)) return false; p1 = p2; } return true; } fuzzyIsNull(epsilon?: number) { if (this.points.length === 0) return true; let p1 = this.points[0]; for (let i = 1; i < this.points.length; i++) { const p2 = this.points[i]; if (!p1.fuzzyEquals(p2, epsilon)) return false; p1 = p2; } return true; } equals(other: Polyline) { if (this.points.length !== other.points.length) return false; for (let i = 0; i < this.points.length; i++) if (!this.points[i].equals(other.points[i])) return false; return true; } fuzzyEquals(other: Polyline, epsilon?: number) { if (this.points.length !== other.points.length) return false; for (let i = 0; i < this.points.length; i++) if (!this.points[i].fuzzyEquals(other.points[i], epsilon)) return false; return true; } translate(dx: number, dy: number) { for (let i = 0; i < this.points.length; i++) this.points[i].translate(dx, dy); return this; } translated(dx: number, dy: number) { return new Polyline(this.points.map(p => p.translated(dx, dy))); } transform(matrix: Matrix) { for (let i = 0; i < this.points.length; i++) this.points[i].transform(matrix); return this; } transformed(matrix: Matrix) { return new Polyline(this.points.map(p => p.transformed(matrix))); } draw(context: CanvasRenderingContext2D, stroked?: boolean, filled?: boolean) { if (this.points.length < 2) return; context.beginPath(); context.moveTo(this.points[0].x, this.points[0].y); const lastIndex = this.points.length - 1; for (let i = 1; i < lastIndex; i++) context.lineTo(this.points[i].x, this.points[i].y); context.lineTo(this.points[lastIndex].x, this.points[lastIndex].y); context.stroke(); } forEach(callbackfn: (point: Vector2D, index: number, polyline: Polyline) => void, thisArg?: any) { for (let i = 0; i < this.points.length; i++) callbackfn.call(thisArg, this.points[i], i, this); } map(callbackfn: (point: Vector2D, index: number, polyline: Polyline) => Vector2D, thisArg?: any) { return new Polyline(this.points.map((point, index) => callbackfn.call(thisArg, point, index, this))); } get(index: number): Vector2D { return this.points[index]; } set(index: number, point: Vector2D) { this.points[index] = point; } length() { if (this.points.length < 2) return 0; let result = 0; let p1 = this.points[0]; for (let i = 1; i < this.points.length; i++) { const p2 = this.points[i]; result += p2.minus(p1).length(); p1 = p2; } return result; } boundingRectangle() { if (this.points.length === 0) return new Rectangle(); const p = this.points[0]; let minx, maxx, miny, maxy; minx = maxx = p.x; miny = maxy = p.y; for (let i = 1; i < this.points.length; i++) { const p = this.points[i]; if (p.x < minx) minx = p.x; else if (p.x > maxx) maxx = p.x; if (p.y < miny) miny = p.y; else if (p.y > maxy) maxy = p.y; } return new Rectangle(new Vector2D(minx, miny), new Vector2D(maxx - minx, maxy - miny)); } 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.containsPolyline(this, epsilon); } intersectsPoint(other: Point, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectPointPolylineHelper(this, other, other.toVector2D(), this.points, callbackfn, thisArg, epsilon); } intersectsLine(other: Line, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectLinePolylineHelper(this, other, other.p1, other.p2, this.points, callbackfn, thisArg, epsilon); } intersectsCircle(other: Circle, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectPolylineCircleHelper(this, other, this.points, other.c, other.r, callbackfn, thisArg, epsilon); } intersectsRectangle(other: Rectangle, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number): boolean { return intersectPolylineRectangleHelper(this, other, this.points, 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 intersectPolylinePolygonHelper(this, other, this.points, other.points, callbackfn, thisArg, epsilon); } intersectsPolyline(other: Polyline, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number) { if (this.points.length === 0 || other.points.length === 0) return false; let p1 = this.points[0]; for (let i = 1; i < this.points.length; i++) { const p2 = this.points[i]; if (intersectLinePolylineHelper(this, other, p1, p2, other.points, callbackfn, thisArg, epsilon)) return true; p1 = p2; } return false; } intersects(other: Shape, callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number) { return other.intersectsPolyline(this, callbackfn, thisArg, epsilon); } intersectsSelf(callbackfn?: (points: Vector2D[], thisShape: Shape, otherShape: Shape) => any, thisArg?: any, epsilon?: number) { if (this.points.length === 0) return false; let p1 = this.points[0]; for (let i = 1; i < this.points.length; i++) { const p2 = this.points[i]; let p3 = p2; for (let j = i + 1; j < this.points.length; j++) { const p4 = this.points[j]; const points = intersectLineLine(p1, p2, p3, p4, epsilon); if (points !== undefined) { if (j - 1 === i // joint case && points[0].fuzzyEquals(p2, epsilon)) { points.shift(); p3 = p4; continue; } if (callbackfn === undefined || callbackfn.call(thisArg, points, this, this)) return true; } p3 = p4; } p1 = p2; } return false; } static create(points?: Vector2D[]) { return new Polyline(points); } static readonly className: string = 'Polyline'; }