import { fuzzyIsNull, fuzzyEqual } from '../math/float'; import { Matrix } from './Matrix'; export class Vector2D { x: number; y: number; constructor(x = 0, y = 0) { this.x = x; this.y = y; } clone() { return new Vector2D(this.x, this.y); } isNull() { return this.x === 0 && this.y === 0; } fuzzyIsNull(epsilon?: number) { return fuzzyIsNull(this.x, epsilon) && fuzzyIsNull(this.y, epsilon); } equals(other: Vector2D) { return this.x === other.x && this.y === other.y; } fuzzyEquals(other: Vector2D, epsilon?: number) { return fuzzyEqual(this.x, other.x, epsilon) && fuzzyEqual(this.y, other.y, epsilon); } plus(other: Vector2D) { return new Vector2D(this.x + other.x, this.y + other.y); } minus(other: Vector2D) { return new Vector2D(this.x - other.x, this.y - other.y); } mul(other: number): Vector2D; mul(other: Vector2D): Vector2D; mul(other: Matrix): Vector2D; mul(other: any) { switch (other.constructor) { case Number: return new Vector2D(this.x * other, this.y * other); case Vector2D: return new Vector2D(this.x * other.x, this.y * other.y); case Matrix: return new Vector2D(this.x * other.a + this.y * other.c + other.e, this.x * other.b + this.y * other.d + other.f); default: return new Vector2D(); } } div(other: number): Vector2D; div(other: Vector2D): Vector2D; div(other: any) { switch (other.constructor) { case Number: return new Vector2D(this.x / other, this.y / other); case Vector2D: return new Vector2D(this.x / other.x, this.y / other.y); default: return new Vector2D(); } } length() { return Math.sqrt(this.x * this.x + this.y * this.y); } lengthSquared() { return this.x * this.x + this.y * this.y; } manhattanLength() { return Math.abs(this.x) + Math.abs(this.y); } distanceToPoint(point: Vector2D) { return this.minus(point).length(); } dotProduct(other: Vector2D) { return this.x * other.x + this.y * other.y; } crossProduct(other: Vector2D) { return this.x * other.y - this.y * other.x; } distanceToLine(point: Vector2D, direction: Vector2D, epsilon?: number) { if (direction.fuzzyIsNull(epsilon)) return this.minus(point).length(); const p = point.plus(direction.mul(this.minus(point).dotProduct(direction))); return this.minus(p).length(); } normalized(epsilon?: number) { const len = this.x * this.x + this.y * this.y; if (fuzzyIsNull(len - 1, epsilon)) return new Vector2D(this.x, this.y); if (!fuzzyIsNull(len, epsilon)) { const sqrtLen = Math.sqrt(len); return new Vector2D(this.x / sqrtLen, this.y / sqrtLen); } return new Vector2D(); } projected(other: Vector2D, epsilon?: number) { const d = other.normalized(epsilon); return d.mul(this.dotProduct(d)); } rejected(other: Vector2D, epsilon?: number) { return this.minus(this.projected(other, epsilon)); } interpolated(other: Vector2D, t: number) { return new Vector2D(this.x + (other.x - this.x) * t, this.y + (other.y - this.y) * t); } invert() { this.x = -this.x; this.y = -this.y; return this; } inverted() { return new Vector2D(-this.x, -this.y); } translate(dx: number, dy: number) { this.x += dx; this.y += dy; return this; } translated(dx: number, dy: number) { return new Vector2D(this.x + dx, this.y + dy); } scale(sx: number, sy = sx) { this.x *= sx; this.y *= sy; return this; } scaled(sx: number, sy = sx) { return new Vector2D(this.x * sx, this.y * sy); } rotated(rad: number) { const sina = Math.sin(rad); const cosa = Math.cos(rad); return new Vector2D(this.x * cosa - this.y * sina, this.x * sina + this.y * cosa); } rotate(rad: number) { const sina = Math.sin(rad); const cosa = Math.cos(rad); const px = this.x; const py = this.y; this.x = px * cosa - py * sina; this.y = px * sina + py * cosa; return this; } transform(other: Matrix) { const px = this.x; const py = this.y; this.x = other.a * px + other.c * py + other.e; this.y = other.b * px + other.d * py + other.f; return this; } transformed(other: Matrix) { return new Vector2D(this.x * other.a + this.y * other.c + other.e, this.x * other.b + this.y * other.d + other.f); } map(mapfn: (value: number, index: number, vector: Vector2D) => number, thisArg?: any) { return new Vector2D(mapfn.call(thisArg, this.x, 0, this), mapfn.call(thisArg, this.y, 1, this)); } static create(x?: number, y?: number) { return new Vector2D(x, y); } static compare(lhs: Vector2D, rhs: Vector2D) { return (lhs.x - rhs.x) || (lhs.y - rhs.y); } static equal(lhs: Vector2D, rhs: Vector2D) { return lhs.equals(rhs); } static fuzzyEqual(lhs: Vector2D, rhs: Vector2D, epsilon?: number) { return lhs.fuzzyEquals(rhs, epsilon); } static interpolate(from: Vector2D, to: Vector2D, progress: number) { return from.interpolated(to, progress); } }