diff --git a/packages/morton/src/zcurve.ts b/packages/morton/src/zcurve.ts index 83cd076bb2..2a214446c7 100644 --- a/packages/morton/src/zcurve.ts +++ b/packages/morton/src/zcurve.ts @@ -11,8 +11,8 @@ type Range2_64 = Exclude; /** * Z-Curve encoder/decoder and optimized bbox range extraction for - * arbitrary dimensions. Supports max. 32bit per-component value range - * and resulting Morton codes encoded as `BigInt`. + * arbitrary dimensions (>= 2). Supports max. 32bit per-component value + * range and resulting Morton codes encoded as `BigInt`. */ export class ZCurve { /** @@ -64,39 +64,26 @@ export class ZCurve { dim: T; bits: number; - pattern: ArrayLike; - masks: bigint[]; - fullMask: bigint; + order: ArrayLike; + masks!: bigint[]; + wipeMasks!: bigint[]; /** * @param dim - dimensions * @param bits - number of bits per component - * @param pattern - component order + * @param order - component ordering */ - constructor( - dim: T, - bits: Range1_32, - pattern?: ArrayLike - ) { - assert(dim >= 1, `unsupported dimensions`); + constructor(dim: T, bits: Range1_32, order?: ArrayLike) { + assert(dim >= 2, `unsupported dimensions`); assert(bits >= 1 && bits <= 32, `unsupported bits per component`); this.dim = dim; this.bits = bits; - if (!pattern) { - pattern = []; - for (let i = 0; i < dim; i++) (pattern)[i] = i; - } - this.pattern = pattern; - this.masks = []; - this.fullMask = (1n << BigInt(dim * bits)) - 1n; - for (let i = dim; --i >= 0; ) { - this.masks[i] = ZCurve.encodeComponent( - MASKS[bits], - bits, - dim, - pattern[i] - ); + if (!order) { + order = []; + for (let i = 0; i < dim; i++) (order)[i] = i; } + this.order = order; + this.initMasks(); } /** @@ -106,9 +93,9 @@ export class ZCurve { */ encode(p: ArrayLike) { let res = 0n; - const { dim, bits, pattern } = this; + const { dim, bits, order } = this; for (let i = dim; --i >= 0; ) { - res = ZCurve.encodeComponent(p[i], bits, dim, pattern[i], res); + res = ZCurve.encodeComponent(p[i], bits, dim, order[i], res); } return res; } @@ -120,9 +107,9 @@ export class ZCurve { * @param out - optional result array */ decode(z: bigint, out: NumericArray = []) { - const { dim, bits, pattern } = this; + const { dim, bits, order } = this; for (let i = dim; --i >= 0; ) { - out[i] = ZCurve.decodeComponent(z, bits, dim, pattern[i]); + out[i] = ZCurve.decodeComponent(z, bits, dim, order[i]); } return out; } @@ -206,22 +193,25 @@ export class ZCurve { const zmaxBit = zmax & mask; const currBit = zcurr & mask; const bitMask = 1 << ((bitPos / dim) | 0); - const offset = bitPos % dim; if (!currBit) { if (!zminBit && zmaxBit) { - bigmin = this.loadBits(bitMask, bitPos, zmin, offset); - zmax = this.loadBits(bitMask - 1, bitPos, zmax, offset); - } else if (zminBit && zmaxBit) { - return zmin; - } else if (zminBit && !zmaxBit) { - throw new Error("illegal BIGMIN state"); + bigmin = this.loadBits(bitMask, bitPos, zmin); + zmax = this.loadBits(bitMask - 1, bitPos, zmax); + } else if (zminBit) { + if (zmaxBit) { + return zmin; + } else { + throw new Error("illegal BIGMIN state"); + } } } else { - if (!zminBit && !zmaxBit) { - return bigmin; - } else if (!zminBit && zmaxBit) { - zmin = this.loadBits(bitMask, bitPos, zmin, offset); - } else if (zminBit && !zmaxBit) { + if (!zminBit) { + if (zmaxBit) { + zmin = this.loadBits(bitMask, bitPos, zmin); + } else { + return bigmin; + } + } else if (!zmaxBit) { throw new Error("illegal BIGMIN state"); } } @@ -243,21 +233,35 @@ export class ZCurve { return true; } - protected loadBits( - mask: number, - bitPos: number, - z: bigint, - offset: number - ) { - const { bits, dim } = this; - const wipeMask = - ZCurve.encodeComponent( - MASKS[bits] >>> (bits - (((bitPos / dim) | 0) + 1)), + protected initMasks() { + const { bits, dim, order } = this; + this.masks = []; + for (let i = dim; --i >= 0; ) { + this.masks[i] = ZCurve.encodeComponent( + MASKS[bits], bits, dim, - offset - ) ^ this.fullMask; - const bitPattern = ZCurve.encodeComponent(mask, bits, dim, offset); - return (z & wipeMask) | bitPattern; + order[i] + ); + } + this.wipeMasks = []; + const fullMask = (1n << BigInt(dim * bits)) - 1n; + for (let i = dim * bits; --i >= 0; ) { + this.wipeMasks[i] = + ZCurve.encodeComponent( + MASKS[bits] >>> (bits - (((i / dim) | 0) + 1)), + bits, + dim, + i % dim + ) ^ fullMask; + } + } + + protected loadBits(mask: number, bitPos: number, z: bigint) { + const dim = this.dim; + return ( + (z & this.wipeMasks[bitPos]) | + ZCurve.encodeComponent(mask, this.bits, dim, bitPos % dim) + ); } } diff --git a/packages/morton/test/zcurve.ts b/packages/morton/test/zcurve.ts index 9181454cfa..dbee94fe21 100644 --- a/packages/morton/test/zcurve.ts +++ b/packages/morton/test/zcurve.ts @@ -2,6 +2,12 @@ import * as assert from "assert"; import { ZCurve } from "../src"; describe("ZCurve", () => { + it("ctor", () => { + assert.throws(() => new ZCurve(1, 8)); + assert.throws(() => new ZCurve(2, 0)); + assert.throws(() => new ZCurve(2, 33)); + }); + it("range 2d (1)", () => { const z = new ZCurve(2, 8); assert.deepEqual( @@ -47,4 +53,58 @@ describe("ZCurve", () => { ] ); }); + + it("range 3d (2)", () => { + const z = new ZCurve(3, 8); + assert.deepEqual( + [...z.range([3, 2, 0], [4, 6, 1])], + [ + 25n, + 27n, + 29n, + 31n, + 80n, + 82n, + 84n, + 86n, + 137n, + 139n, + 141n, + 143n, + 153n, + 157n, + 192n, + 194n, + 196n, + 198n, + 208n, + 212n + ] + ); + assert.deepEqual( + [...z.range([3, 2, 0], [4, 6, 1])].map((i) => z.decode(i)), + [ + [3, 2, 0], + [3, 3, 0], + [3, 2, 1], + [3, 3, 1], + [4, 2, 0], + [4, 3, 0], + [4, 2, 1], + [4, 3, 1], + [3, 4, 0], + [3, 5, 0], + [3, 4, 1], + [3, 5, 1], + [3, 6, 0], + [3, 6, 1], + [4, 4, 0], + [4, 5, 0], + [4, 4, 1], + [4, 5, 1], + [4, 6, 0], + [4, 6, 1] + ] + ); + }); });