//! \see http://www.redblobgames.com/grids/hexagons/ import { hashNumber } from '../../functional/hash/murmurhash'; import { Vector2D as Point } from '../Vector2D'; export { Point }; export class Hexagon { constructor(public q = 0, public r = 0, public s = 0) { } toHash(seed?: number) { seed = hashNumber(this.q, seed); seed = hashNumber(this.r, seed); seed = hashNumber(this.s, seed); return seed; } equals(other: Hexagon) { return this.q === other.q && this.r === other.r && this.s === other.s; } plus(other: Hexagon) { return new Hexagon(this.q + other.q, this.r + other.r, this.s + other.s); } minus(other: Hexagon) { return new Hexagon(this.q - other.q, this.r - other.r, this.s - other.s); } mul(other: number): Hexagon; mul(other: Hexagon): Hexagon; mul(other: any) { switch (other.constructor) { case Number: return new Hexagon(this.q * other, this.r * other, this.s * other); case Hexagon: return new Hexagon(this.q * other.q, this.r * other.r, this.s * other.s); default: return new Hexagon(); } } div(other: number): Hexagon; div(other: Hexagon): Hexagon; div(other: any) { switch (other.constructor) { case Number: return new Hexagon(this.q / other, this.r / other, this.s / other); case Hexagon: return new Hexagon(this.q / other.q, this.r / other.r, this.s / other.s); default: return new Hexagon(); } } rotatedLeft() { return new Hexagon(-this.s, -this.q, -this.r); } rotatedRight() { return new Hexagon(-this.r, -this.s, -this.q); } neighbor(direction: number) { return this.plus(Hexagon.directions[direction]); } diagonalNeighbor(direction: number) { return this.plus(Hexagon.diagonals[direction]); } length() { return Math.sqrt(this.q * this.q + this.r * this.r + this.s * this.s); } manhattanLength() { return Math.abs(this.q) + Math.abs(this.r) + Math.abs(this.s); } distance(other: Hexagon) { return this.minus(other).length(); } rounded() { let q = Math.round(this.q); let r = Math.round(this.r); let s = Math.round(this.s); const q_diff = Math.abs(q - this.q); const r_diff = Math.abs(r - this.r); const s_diff = Math.abs(s - this.s); if (q_diff > r_diff && q_diff > s_diff) q = -r - s; else { if (r_diff > s_diff) r = -q - s; else s = -q - r; } return new Hexagon(q, r, s); } interpolated(other: Hexagon, t: number) { return new Hexagon(this.q * (1 - t) + other.q * t, this.r * (1 - t) + other.r * t, this.s * (1 - t) + other.s * t); } static create(q?: number, r?: number, s?: number) { return new Hexagon(q, r, s); } static fromAxial(q: number, r: number) { return new Hexagon(q, r, -q - r); } static interpolate(a: Hexagon, b: Hexagon, t: number) { return a.interpolated(b, t); } static trace(a: Hexagon, b: Hexagon) { const n = a.minus(b).manhattanLength() / 2; const a_nudge = new Hexagon(a.q + 0.000001, a.r + 0.000001, a.s - 0.000002); const b_nudge = new Hexagon(b.q + 0.000001, b.r + 0.000001, b.s - 0.000002); const ret: Hexagon[] = []; const step = 1 / Math.max(n, 1); for (let i = 0; i <= n; i++) ret.push(Hexagon.interpolate(a_nudge, b_nudge, step * i).rounded()); return ret; } static hash(h: Hexagon, seed?: number) { return h.toHash(seed); } static equal(lhs: Hexagon, rhs: Hexagon) { return lhs.equals(rhs); } static compare(lhs: Hexagon, rhs: Hexagon) { return (lhs.q - rhs.q) || (lhs.r - rhs.r) || (lhs.s - rhs.s); } static readonly directions = [new Hexagon(1, 0, -1), new Hexagon(1, -1, 0), new Hexagon(0, -1, 1), new Hexagon(-1, 0, 1), new Hexagon(-1, 1, 0), new Hexagon(0, 1, -1)]; static readonly diagonals = [new Hexagon(2, -1, -1), new Hexagon(1, -2, 1), new Hexagon(-1, -1, 2), new Hexagon(-2, 1, 1), new Hexagon(-1, 2, -1), new Hexagon(1, 1, -2)]; } export class OffsetCoord { constructor(public col = 0, public row = 0) { } equals(other: OffsetCoord) { return this.col === other.col && this.row === other.row; } static qoffsetFromCube(offset: number, h: Hexagon) { const col = h.q; const row = h.r + (h.q + offset * (h.q & 1)) / 2; return new OffsetCoord(col, row); } static qoffsetToCube(offset: number, h: OffsetCoord) { const q = h.col; const r = h.row - (h.col + offset * (h.col & 1)) / 2; const s = -q - r; return new Hexagon(q, r, s); } static roffsetFromCube(offset: number, h: Hexagon) { const col = h.q + (h.r + offset * (h.r & 1)) / 2; const row = h.r; return new OffsetCoord(col, row); } static roffsetToCube(offset: number, h: OffsetCoord) { const q = h.col - (h.row + offset * (h.row & 1)) / 2; const r = h.row; const s = -q - r; return new Hexagon(q, r, s); } static readonly EVEN = 1; static readonly ODD = -1; } export class DoubledCoord { constructor(public col = 0, public row = 0) { } equals(other: DoubledCoord) { return this.col === other.col && this.row === other.row; } qdoubledToCube() { const q = this.col; const r = (this.row - this.col) / 2; const s = -q - r; return new Hexagon(q, r, s); } rdoubledToCube() { const q = (this.col - this.row) / 2; const r = this.row; const s = -q - r; return new Hexagon(q, r, s); } static qdoubledFromCube(h: Hexagon): DoubledCoord { const col = h.q; const row = 2 * h.r + h.q; return new DoubledCoord(col, row); } static rdoubledFromCube(h: Hexagon) { const col = 2 * h.q + h.r; const row = h.r; return new DoubledCoord(col, row); } } export class Orientation { constructor(public f0: number, public f1: number, public f2: number, public f3: number, public b0: number, public b1: number, public b2: number, public b3: number, public startAngle: number) { } } export class Layout { constructor(public orientation: Orientation, public size: Point, public origin: Point = new Point()) { } hexToPoint(h: Hexagon) { const m = this.orientation; const size = this.size; const origin = this.origin; const x = (m.f0 * h.q + m.f1 * h.r) * size.x; const y = (m.f2 * h.q + m.f3 * h.r) * size.y; return new Point(x + origin.x, y + origin.y); } pointToHex(p: Point) { const m = this.orientation; const size = this.size; const origin = this.origin; const pt = new Point((p.x - origin.x) / size.x, (p.y - origin.y) / size.y); const q = m.b0 * pt.x + m.b1 * pt.y; const r = m.b2 * pt.x + m.b3 * pt.y; return new Hexagon(q, r, -q - r); } hexCornerOffset(corner: number) { const m = this.orientation; const size = this.size; const angle = 2.0 * Math.PI * (m.startAngle - corner) / 6; return new Point(size.x * Math.cos(angle), size.y * Math.sin(angle)); } polygonCorners(h: Hexagon) { const corners = new Array(6); const center = this.hexToPoint(h); for (let i = 0; i < 6; i++) { const offset = this.hexCornerOffset(i); corners[i] = new Point(center.x + offset.x, center.y + offset.y); } return corners; } static readonly pointy = new Orientation(Math.sqrt(3.0), Math.sqrt(3.0) / 2.0, 0.0, 3.0 / 2.0, Math.sqrt(3.0) / 3.0, -1.0 / 3.0, 0.0, 2.0 / 3.0, 0.5); static readonly flat = new Orientation(3.0 / 2.0, 0.0, Math.sqrt(3.0) / 2.0, Math.sqrt(3.0), 2.0 / 3.0, 0.0, -1.0 / 3.0, Math.sqrt(3.0) / 3.0, 0.0); }