@caveworld/honeycomb-grid
Version:
Create hexagon grids easily
630 lines (629 loc) • 16.9 kB
JavaScript
const m = (t) => typeof t == "object" && t !== null, At = (t) => m(t) && Number.isFinite(t.q) && Number.isFinite(t.r), U = (t) => typeof t == "function", d = (t) => m(t) && Number.isFinite(t.col) && Number.isFinite(t.row), rt = (t) => m(t) && Number.isFinite(t.x) && Number.isFinite(t.y), N = (t) => Array.isArray(t) && Number.isFinite(t[0]) && Number.isFinite(t[1]), I = (t, r) => r + t * (r & 1) >> 1;
function et(t, r) {
return (t % r + r) % r;
}
const T = ([t, r, e = -t - r]) => ({ q: t, r, s: e });
var C = /* @__PURE__ */ ((t) => (t[t.N = 0] = "N", t[t.E = 2] = "E", t[t.S = 4] = "S", t[t.W = 6] = "W", t))(C || {}), Q = /* @__PURE__ */ ((t) => (t[t.NE = 1] = "NE", t[t.SE = 3] = "SE", t[t.SW = 5] = "SW", t[t.NW = 7] = "NW", t))(Q || {}), h = /* @__PURE__ */ ((t) => (t[t.N = 0] = "N", t[t.NE = 1] = "NE", t[t.E = 2] = "E", t[t.SE = 3] = "SE", t[t.S = 4] = "S", t[t.SW = 5] = "SW", t[t.W = 6] = "W", t[t.NW = 7] = "NW", t))(h || {});
class y {
static N = 0;
static NE = 1;
static E = 2;
static SE = 3;
static S = 4;
static SW = 5;
static W = 6;
static NW = 7;
static Cardinal;
static Ordinal;
static of(r = 0) {
return new y(r);
}
static isCardinal(r) {
return !!C[r];
}
static isOrdinal(r) {
return !!Q[r];
}
static rotate(r, e) {
return et(r + e, 8);
}
direction;
constructor(r = 0) {
this.direction = typeof r == "number" ? r : h[r];
}
isCardinal() {
return y.isCardinal(this.direction);
}
isOrdinal() {
return y.isOrdinal(this.direction);
}
rotate(r) {
return y.rotate(this.direction, r);
}
}
const k = (t, r, e) => {
const n = t - I(e, r), s = r, o = -n - s;
return { q: n, r: s, s: o };
}, p = (t, r, e) => {
const n = t, s = r - I(e, t), o = -n - s;
return { q: n, r: s, s: o };
}, H = ({ offset: t, isPointy: r }, { col: e, row: n }) => r ? k(e, n, t) : p(e, n, t);
function W(t, r) {
const {
q: e,
r: n,
s = -e - n
} = d(r) ? H(t, r) : N(r) ? T(r) : r;
return { q: e, r: n, s };
}
const B = (t) => m(t) && !!Object.getPrototypeOf(t).__isHoneycombHex;
function nt(t) {
const { width: r, height: e } = t, { x: n, y: s } = B(t) ? t : t.origin;
return { x: r / 2 - n, y: e / 2 - s };
}
const st = (t, r = {}) => {
if (d(r)) {
const { col: e, row: n, ...s } = r, o = H(t, { col: e, row: n });
return Object.assign(Object.create(Object.getPrototypeOf(t)), t, o, s);
}
return r = N(r) ? T(r) : r, Object.assign(Object.create(Object.getPrototypeOf(t)), t, r);
};
var b = /* @__PURE__ */ ((t) => (t.FLAT = "FLAT", t.POINTY = "POINTY", t))(b || {});
const V = (t) => t * 2, Z = (t) => t * Math.sqrt(3), ot = ({ orientation: t, dimensions: { yRadius: r } }) => t === b.POINTY ? V(r) : Z(r), A = ({ orientation: t, dimensions: { xRadius: r, yRadius: e }, origin: { x: n, y: s }, q: o, r: i }) => t === b.POINTY ? {
x: r * Math.sqrt(3) * (o + i / 2) - n,
y: e * 3 / 2 * i - s
} : {
x: r * 3 / 2 * o - n,
y: e * Math.sqrt(3) * (i + o / 2) - s
}, X = (t) => t * Math.sqrt(3), G = (t) => t * 2, it = ({ orientation: t, dimensions: { xRadius: r } }) => t === b.POINTY ? X(r) : G(r), ct = (t, r, { x: e, y: n }) => [
{ x: e + t * 0.5, y: n - r * 0.25 },
{ x: e + t * 0.5, y: n + r * 0.25 },
{ x: e, y: n + r * 0.5 },
{ x: e - t * 0.5, y: n + r * 0.25 },
{ x: e - t * 0.5, y: n - r * 0.25 },
{ x: e, y: n - r * 0.5 }
], ut = (t, r, { x: e, y: n }) => [
{ x: e + t * 0.25, y: n - r * 0.5 },
{ x: e + t * 0.5, y: n },
{ x: e + t * 0.25, y: n + r * 0.5 },
{ x: e - t * 0.25, y: n + r * 0.5 },
{ x: e - t * 0.5, y: n },
{ x: e - t * 0.25, y: n - r * 0.5 }
];
function ft(t) {
const {
orientation: r,
dimensions: { xRadius: e, yRadius: n }
} = t, s = B(t) ? A(t) : t.origin;
return r === b.POINTY ? ct(X(e), V(n), s) : ut(G(e), Z(n), s);
}
const J = (t, r = { q: 0, r: 0 }) => {
if (B(t))
return t.clone(r);
if (d(r)) {
const { col: e, row: n, ...s } = r, o = H(t, { col: e, row: n });
return Object.assign(Object.create(t), o, s);
}
return r = N(r) ? T(r) : r, Object.assign(Object.create(t), r);
};
function ht(t, r) {
if (d(t) && d(r))
return t.col === r.col && t.row === r.row;
if (Object.prototype.hasOwnProperty.call(t, "col") || Object.prototype.hasOwnProperty.call(r, "col"))
throw new Error(
`Can't compare coordinates where one are offset coordinates. Either pass two offset coordinates or two axial/cube coordinates. Received: ${JSON.stringify(
t
)} and ${JSON.stringify(r)}`
);
const e = N(t) ? T(t) : t, n = N(r) ? T(r) : r;
return e.q === n.q && e.r === n.r;
}
const at = (t, r, e) => ({
col: t + I(e, r),
row: r
}), lt = (t, r, e) => ({
col: t,
row: r + I(e, t)
}), j = ({ q: t, r, offset: e, isPointy: n }) => n ? at(t, r, e) : lt(t, r, e), gt = ({ orientation: t }) => t === b.FLAT, bt = ({ orientation: t }) => t === b.POINTY, xt = ({ q: t, r }) => `${t},${r}`, Ot = {
dimensions: { xRadius: 1, yRadius: 1 },
orientation: b.POINTY,
origin: { x: 0, y: 0 },
offset: -1
}, dt = (t) => {
const r = /* @__PURE__ */ new WeakMap(), e = {
...Ot,
clone(n) {
return st(this, n);
},
equals(n) {
return ht(this, d(n) ? H(this, n) : n);
},
toString() {
return xt(this);
},
...t
};
return Object.defineProperties(e, {
[Symbol.toStringTag]: { value: "Hex" },
__isHoneycombHex: { value: !0, writable: !1 },
center: {
get() {
return nt(this);
}
},
col: {
get() {
return j(this).col;
}
},
corners: {
get() {
return ft(this);
}
},
dimensions: { value: qt(e) },
height: {
get() {
return ot(this);
}
},
isFlat: {
get() {
return gt(this);
}
},
isPointy: {
get() {
return bt(this);
}
},
orientation: { value: D(e) },
offset: { value: yt(e) },
row: {
get() {
return j(this).row;
}
},
s: {
get() {
const n = r.get(this);
return Number.isFinite(n) ? n : -this.q - this.r;
},
set(n) {
r.set(this, n);
}
},
width: {
get() {
return it(this);
}
},
x: {
get() {
return A(this).x;
}
},
y: {
get() {
return A(this).y;
}
}
}), Object.defineProperties(e, {
origin: { value: St(e) }
});
};
function qt(t) {
const { dimensions: r } = t;
if (m(r)) {
if (r.xRadius > 0 && r.yRadius > 0)
return { ...r };
const { width: e, height: n } = r;
if (e > 0 && n > 0)
return D(t) === b.POINTY ? { xRadius: e / Math.sqrt(3), yRadius: n / 2 } : { xRadius: e / 2, yRadius: n / Math.sqrt(3) };
}
if (r > 0)
return { xRadius: r, yRadius: r };
throw new TypeError(
`Invalid dimensions: ${JSON.stringify(
r
)}. Dimensions must be expressed as an Ellipse ({ xRadius: number, yRadius: number }), a Rectangle ({ width: number, height: number }) or a number.`
);
}
function D({ orientation: t }) {
return t.toUpperCase();
}
function yt({ offset: t }) {
if (!Number.isFinite(t))
throw new TypeError(`Invalid offset: ${t}. Offset must be a number.`);
return t;
}
function St(t) {
const { origin: r } = t;
if (rt(r))
return { ...r };
if (r === "topLeft")
return { x: t.width * -0.5, y: t.height * -0.5 };
if (U(r))
return r(t);
throw new TypeError(
`Invalid origin: ${JSON.stringify(
r
)}. Origin must be expressed as a Point ({ x: number, y: number }), 'topLeft' or a function that returns a Point.`
);
}
const v = ({ q: t, r, s: e = -t - r }) => {
let n = Math.round(t), s = Math.round(r), o = Math.round(e);
const i = Math.abs(t - n), c = Math.abs(r - s), u = Math.abs(e - o);
return i > c && i > u ? n = -s - o : c > u ? s = -n - o : o = -n - s, { q: n, r: s, s: o };
}, Nt = ({ dimensions: { xRadius: t, yRadius: r }, origin: e, isPointy: n }, { x: s, y: o }) => (s += e.x, o += e.y, v(n ? { q: Math.sqrt(3) * s / (3 * t) - o / (3 * r), r: 2 / 3 * (o / r) } : { q: 2 / 3 * (s / t), r: Math.sqrt(3) * o / (3 * r) - s / (3 * t) }));
function F(t, r, e) {
const { q: n, r: s, s: o = -n - s } = W(t, r), { q: i, r: c, s: u = -i - c } = W(t, e);
return Math.max(Math.abs(n - i), Math.abs(s - c), Math.abs(o - u));
}
const Tt = [
null,
{ q: 1, r: -1 },
{ q: 1, r: 0 },
{ q: 0, r: 1 },
null,
{ q: -1, r: 1 },
{ q: -1, r: 0 },
{ q: 0, r: -1 }
], wt = [
{ q: 0, r: -1 },
{ q: 1, r: -1 },
null,
{ q: 1, r: 0 },
{ q: 0, r: 1 },
{ q: -1, r: 1 },
null,
{ q: -1, r: 0 }
], Et = ({ offset: t, q: r, r: e, col: n, row: s }, o) => {
if (o === h.S || o === h.N) {
const c = o === h.S ? s + 1 : s - 1;
return k(n, c, t);
}
const i = Tt[o];
return { q: r + i.q, r: e + i.r };
}, mt = ({ offset: t, q: r, r: e, col: n, row: s }, o) => {
if (o === h.E || o === h.W) {
const c = o === h.E ? n + 1 : n - 1;
return p(c, s, t);
}
const i = wt[o];
return { q: r + i.q, r: e + i.r };
}, S = (t, r) => t.clone(t.isPointy ? Et(t, r) : mt(t, r));
function E(t) {
return Array.isArray(t) ? function(e, n) {
const s = [];
let o = n;
for (const i of t)
for (const c of i(e, o))
s.push(o = c);
return s;
} : t;
}
const jt = (...t) => (r) => t.map(r);
function L(t) {
return Pt(t) ? Wt(t) : Ft(t);
}
function Pt(t) {
return t.direction in h;
}
function Wt({ start: t, direction: r, length: e }) {
return function(s, o) {
const i = [];
let u = s(t ?? o);
!t && o && (u = S(u, r));
for (let f = 0; f < e; f++)
i.push(u), u = S(u, r);
return i;
};
}
function Ft({ start: t, stop: r }) {
return function(n, s) {
const o = [], i = n(t ?? s), c = z(i), u = z(W(i, r)), f = It(c, u), l = F(i, i, r), q = 1 / Math.max(l, 1);
let x = !t && s ? 1 : 0;
for (x; x <= l; x++) {
const a = v(f(q * x));
o.push(n(a));
}
return o;
};
}
function z({ q: t, r, s: e }) {
return { q: t + 1e-6, r: r + 1e-6, s: e + -2e-6 };
}
function It(t, r) {
return (e) => {
const n = t.q * (1 - e) + r.q * e, s = t.r * (1 - e) + r.r * e;
return { q: n, r: s };
};
}
const vt = (t) => (r, e) => [S(r(e), t)];
function tt(t, r, { includeSource: e = !0 } = {}) {
return function(s, o) {
const i = [];
for (const c of E(t)(s, o)) {
e && i.push(c);
for (const u of E(r)(s, c))
i.push(u);
}
return i;
};
}
function Lt(t, r) {
return function(n, s) {
const {
width: o,
height: i,
start: c,
direction: u = h.E
} = r ? Ht(t, r, n()) : t, f = n(c ?? s), l = tt(
L({ start: f, direction: y.rotate(u, 2), length: i }),
L({ direction: u, length: o - 1 })
)(n, f);
return !c && s ? l.slice(1) : l;
};
}
function Ht(t, r, { isPointy: e, offset: n }) {
const { col: s, row: o } = K(t, e, n), { col: i, row: c } = K(r, e, n), u = s < i ? "A" : "B", f = o < c ? "A" : "B", l = u + f, { swapWidthHeight: q, direction: x } = Mt[l], a = Math.abs(s - i) + 1, P = Math.abs(o - c) + 1;
return {
width: q ? P : a,
height: q ? a : P,
start: t,
direction: x
};
}
function K(t, r, e) {
if (d(t))
return t;
const { q: n, r: s } = N(t) ? T(t) : t;
return j({ q: n, r: s, isPointy: r, offset: e });
}
const Mt = {
AA: {
swapWidthHeight: !1,
direction: h.E
},
AB: {
swapWidthHeight: !0,
direction: h.N
},
BA: {
swapWidthHeight: !0,
direction: h.S
},
BB: {
swapWidthHeight: !1,
direction: h.W
}
};
function Yt(t, r) {
return E(Array.from({ length: t }, () => E(r)));
}
var Y = /* @__PURE__ */ ((t) => (t.CLOCKWISE = "CLOCKWISE", t.COUNTERCLOCKWISE = "COUNTERCLOCKWISE", t))(Y || {});
function Rt(t) {
const { center: r, rotation: e = Y.CLOCKWISE } = t;
return function(s, o) {
const i = e.toUpperCase(), c = [];
let { radius: u } = t, f;
Number.isFinite(u) ? (f = s(r), f.q += u) : (f = s(t.start ?? o), u = F(f, r, f));
const { q: l, r: q, s: x } = W(f, r);
let a = s({ q: l, r: q - u, s: x + u });
if (i === Y.CLOCKWISE)
for (let g = 0; g < 6; g++)
for (let w = 0; w < u; w++) {
const { q: M, r: R } = _[g];
a = s({ q: a.q + M, r: a.r + R }), c.push(a);
}
else
for (let g = 5; g >= 0; g--)
for (let w = 0; w < u; w++) {
const { q: M, r: R } = _[g];
a = s({ q: a.q - M, r: a.r - R }), c.push(a);
}
const P = !t.start && o, $ = c.findIndex((g) => g.equals(f));
return c.slice($ + (P ? 1 : 0)).concat(c.slice(0, $));
};
}
const _ = [
{ q: 1, r: 0 },
{ q: 0, r: 1 },
{ q: -1, r: 1 },
{ q: -1, r: 0 },
{ q: 0, r: -1 },
{ q: 1, r: -1 }
];
function Bt({ radius: t, start: r, rotation: e }) {
return function(s, o) {
const i = s(r ?? o);
return tt(L({ start: r, direction: h.N, length: t }), Rt({ center: i, rotation: e }))(
s,
o
);
};
}
class O {
static fromIterable(r) {
const e = r[Symbol.iterator]().next().value;
if (!e)
throw new Error(`Can't create grid from empty iterable: ${JSON.stringify(r)}`);
return new O(Object.getPrototypeOf(e), r);
}
static fromJSON({ hexSettings: r, coordinates: e }) {
const n = dt(r);
return new O(
n,
e.map((s) => J(n, s))
);
}
[Symbol.toStringTag] = "Grid";
get size() {
return this.#t.size;
}
[Symbol.iterator]() {
return this.#t.values();
}
hexPrototype;
#t = /* @__PURE__ */ new Map();
constructor(r, e = []) {
if (r instanceof O) {
this.hexPrototype = r.hexPrototype, this.setHexes(r);
return;
}
this.hexPrototype = r, this.setHexes(this.#e(e));
}
createHex(r) {
return J(this.hexPrototype, r);
}
getHex(r) {
const e = this.createHex(r);
return this.#t.get(e.toString());
}
hasHex(r) {
return this.#t.has(r.toString());
}
setHexes(r) {
for (const e of r)
this.#r(e);
return this;
}
filter(r) {
const e = new O(this.hexPrototype);
for (const n of this)
r(n) && e.#r(n);
return e;
}
map(r) {
const e = new O(this.hexPrototype);
for (const n of this)
e.#r(r(n));
return e;
}
traverse(r, { bail: e = !1 } = {}) {
const n = new O(this.hexPrototype);
for (const s of this.#e(r)) {
const o = this.getHex(s);
if (o)
n.#r(o);
else if (!e)
return n;
}
return n;
}
forEach(r) {
for (const e of this)
r(e);
return this;
}
reduce(r, e) {
if (e === void 0) {
let s, o, i;
for (const c of this)
o = i, i = c, o && (s = r(o, i));
return s;
}
let n = e;
for (const s of this)
n = r(n, s);
return n;
}
toArray() {
return Array.from(this);
}
toJSON() {
return { hexSettings: this.hexPrototype, coordinates: this.toArray() };
}
toString() {
return `Grid(${this.size})`;
}
pointToHex(r, { allowOutside: e = !0 } = {}) {
const n = Nt(this.hexPrototype, r);
if (e)
return this.createHex(n);
const s = this.getHex(n);
return s || null;
}
distance(r, e, { allowOutside: n = !0 } = {}) {
if (n)
return F(this.hexPrototype, r, e);
const s = this.getHex(r), o = this.getHex(e);
return !s || !o ? null : F(this.hexPrototype, s, o);
}
neighborOf(r, e, { allowOutside: n = !0 } = {}) {
if (n)
return S(r, e);
const s = this.getHex(r), o = S(r, e);
return !s || !o ? null : S(r, e);
}
#r(r) {
this.#t.set(r.toString(), r);
}
#e(r) {
return this.#n(r) ? this.#s(r) : Array.isArray(r) && this.#n(r[0]) ? this.#s(E(r)) : r;
}
#n(r) {
return U(r);
}
#s(r) {
return r(this.createHex.bind(this));
}
}
export {
C as CardinalCompassDirection,
y as Compass,
h as CompassDirection,
O as Grid,
Q as OrdinalCompassDirection,
b as Orientation,
Y as Rotation,
W as assertCubeCoordinates,
nt as center,
st as cloneHex,
E as concat,
ft as corners,
ut as cornersFlat,
ct as cornersPointy,
J as createHex,
dt as createHexPrototype,
Ot as defaultHexSettings,
F as distance,
ht as equals,
jt as fromCoordinates,
ot as height,
Z as heightFlat,
V as heightPointy,
j as hexToOffset,
lt as hexToOffsetFlat,
at as hexToOffsetPointy,
A as hexToPoint,
At as isAxial,
gt as isFlat,
B as isHex,
d as isOffset,
rt as isPoint,
bt as isPointy,
N as isTuple,
L as line,
vt as move,
S as neighborOf,
mt as neighborOfFlat,
Et as neighborOfPointy,
I as offsetFromZero,
H as offsetToCube,
p as offsetToCubeFlat,
k as offsetToCubePointy,
Nt as pointToCube,
Lt as rectangle,
Yt as repeat,
tt as repeatWith,
Rt as ring,
v as round,
Bt as spiral,
xt as toString,
T as tupleToCube,
it as width,
G as widthFlat,
X as widthPointy
};