UNPKG

prismarine-world

Version:

The core implementation of the world for prismarine

283 lines (252 loc) 8.23 kB
const { Vec3 } = require('vec3') const BlockFace = { UNKNOWN: -999, BOTTOM: 0, TOP: 1, NORTH: 2, SOUTH: 3, WEST: 4, EAST: 5 } // 2D spiral iterator, useful to iterate on // columns that are centered on bot position // https://en.wikipedia.org/wiki/Taxicab_geometry class ManhattanIterator { constructor (x, y, maxDistance) { this.maxDistance = Math.floor(maxDistance) this.startx = x this.starty = y this.x = 2 this.y = -1 this.layer = 1 this.leg = -1 } /** * Returns the next position. If the iterator is at the end, returns null. Position will be 2d along the x z plane and y always being 0. * @returns {Vec3 | null} */ next () { if (this.leg === -1) { // use -1 as the center this.leg = 0 return new Vec3(this.startx, 0, this.starty) } else if (this.leg === 0) { if (this.maxDistance === 1) return null this.x-- this.y++ if (this.x === 0) this.leg = 1 } else if (this.leg === 1) { this.x-- this.y-- if (this.y === 0) this.leg = 2 } else if (this.leg === 2) { this.x++ this.y-- if (this.x === 0) this.leg = 3 } else if (this.leg === 3) { this.x++ this.y++ if (this.y === 0) { this.x++ this.leg = 0 this.layer++ if (this.layer === this.maxDistance) { return null } } } return new Vec3(this.startx + this.x, 0, this.starty + this.y) } } class OctahedronIterator { constructor (start, maxDistance) { this.start = start.floored() this.maxDistance = maxDistance this.apothem = 1 this.x = -1 this.y = -1 this.z = -1 this.L = this.apothem this.R = this.L + 1 } next () { if (this.apothem > this.maxDistance) return null this.R -= 1 if (this.R < 0) { this.L -= 1 if (this.L < 0) { this.z += 2 if (this.z > 1) { this.y += 2 if (this.y > 1) { this.x += 2 if (this.x > 1) { this.apothem += 1 this.x = -1 } this.y = -1 } this.z = -1 } this.L = this.apothem } this.R = this.L } const X = this.x * this.R const Y = this.y * (this.apothem - this.L) const Z = this.z * (this.apothem - (Math.abs(X) + Math.abs(Y))) return this.start.offset(X, Y, Z) } } // This iterate along a ray starting at `pos` in `dir` direction // It steps exactly 1 block at a time, returning the block coordinates // and the face by which the ray entered the block. class RaycastIterator { constructor (pos, dir, maxDistance) { this.block = { x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z), face: BlockFace.UNKNOWN } this.pos = pos this.dir = dir this.invDirX = (dir.x === 0) ? Number.MAX_VALUE : 1 / dir.x this.invDirY = (dir.y === 0) ? Number.MAX_VALUE : 1 / dir.y this.invDirZ = (dir.z === 0) ? Number.MAX_VALUE : 1 / dir.z this.stepX = Math.sign(dir.x) this.stepY = Math.sign(dir.y) this.stepZ = Math.sign(dir.z) this.tDeltaX = (dir.x === 0) ? Number.MAX_VALUE : Math.abs(1 / dir.x) this.tDeltaY = (dir.y === 0) ? Number.MAX_VALUE : Math.abs(1 / dir.y) this.tDeltaZ = (dir.z === 0) ? Number.MAX_VALUE : Math.abs(1 / dir.z) this.tMaxX = (dir.x === 0) ? Number.MAX_VALUE : Math.abs((this.block.x + (dir.x > 0 ? 1 : 0) - pos.x) / dir.x) this.tMaxY = (dir.y === 0) ? Number.MAX_VALUE : Math.abs((this.block.y + (dir.y > 0 ? 1 : 0) - pos.y) / dir.y) this.tMaxZ = (dir.z === 0) ? Number.MAX_VALUE : Math.abs((this.block.z + (dir.z > 0 ? 1 : 0) - pos.z) / dir.z) this.maxDistance = maxDistance } // Returns null if none of the shapes is intersected, otherwise returns intersect pos and face // shapes are translated by offset intersect (shapes, offset) { // Shapes is an array of shapes, each in the form of: [x0, y0, z0, x1, y1, z1] let t = Number.MAX_VALUE let f = BlockFace.UNKNOWN const p = this.pos.minus(offset) for (const shape of shapes) { let tmin = (shape[this.invDirX > 0 ? 0 : 3] - p.x) * this.invDirX let tmax = (shape[this.invDirX > 0 ? 3 : 0] - p.x) * this.invDirX const tymin = (shape[this.invDirY > 0 ? 1 : 4] - p.y) * this.invDirY const tymax = (shape[this.invDirY > 0 ? 4 : 1] - p.y) * this.invDirY let face = this.stepX > 0 ? BlockFace.WEST : BlockFace.EAST if ((tmin > tymax) || (tymin > tmax)) continue if (tymin > tmin) { tmin = tymin face = this.stepY > 0 ? BlockFace.BOTTOM : BlockFace.TOP } if (tymax < tmax) tmax = tymax const tzmin = (shape[this.invDirZ > 0 ? 2 : 5] - p.z) * this.invDirZ const tzmax = (shape[this.invDirZ > 0 ? 5 : 2] - p.z) * this.invDirZ if ((tmin > tzmax) || (tzmin > tmax)) continue if (tzmin > tmin) { tmin = tzmin face = this.stepZ > 0 ? BlockFace.NORTH : BlockFace.SOUTH } if (tzmax < tmax) tmax = tzmax if (tmin < t) { t = tmin f = face } } if (t === Number.MAX_VALUE) return null return { pos: this.pos.plus(this.dir.scaled(t)), face: f } } next () { if (Math.min(Math.min(this.tMaxX, this.tMaxY), this.tMaxZ) > this.maxDistance) { return null } if (this.tMaxX < this.tMaxY) { if (this.tMaxX < this.tMaxZ) { this.block.x += this.stepX this.tMaxX += this.tDeltaX this.block.face = this.stepX > 0 ? BlockFace.WEST : BlockFace.EAST } else { this.block.z += this.stepZ this.tMaxZ += this.tDeltaZ this.block.face = this.stepZ > 0 ? BlockFace.NORTH : BlockFace.SOUTH } } else { if (this.tMaxY < this.tMaxZ) { this.block.y += this.stepY this.tMaxY += this.tDeltaY this.block.face = this.stepY > 0 ? BlockFace.BOTTOM : BlockFace.TOP } else { this.block.z += this.stepZ this.tMaxZ += this.tDeltaZ this.block.face = this.stepZ > 0 ? BlockFace.NORTH : BlockFace.SOUTH } } return this.block } } class SpiralIterator2d { /** * Spiral outwards from a central position in growing squares. * Every point has a constant distance to its previous and following position of 1. First point returned is the starting position. * Generates positions like this: * ```text * 16 15 14 13 12 * 17 4 3 2 11 * 18 5 0 1 10 * 19 6 7 8 9 * 20 21 22 23 24 * (maxDistance = 2; points returned = 25) * ``` * Copy and paste warrior source: https://stackoverflow.com/questions/3706219/algorithm-for-iterating-over-an-outward-spiral-on-a-discrete-2d-grid-from-the-or * @param {Vec3} pos Starting position * @param {number} maxDistance Max distance from starting position */ constructor (pos, maxDistance) { this.start = pos this.maxDistance = maxDistance this.NUMBER_OF_POINTS = Math.floor(Math.pow((Math.floor(maxDistance) - 0.5) * 2, 2)) // (di, dj) is a vector - direction in which we move right now this.di = 1 this.dj = 0 // length of current segment this.segment_length = 1 // current position (i, j) and how much of current segment we passed this.i = 0 this.j = 0 this.segment_passed = 0 // current iteration this.k = 0 } next () { if (this.k >= this.NUMBER_OF_POINTS) return null const output = this.start.offset(this.i, 0, this.j) // make a step, add 'direction' vector (di, dj) to current position (i, j) this.i += this.di this.j += this.dj this.segment_passed += 1 if (this.segment_passed === this.segment_length) { // done with current segment this.segment_passed = 0 // 'rotate' directions const buffer = this.di this.di = -this.dj this.dj = buffer // increase segment length if necessary if (this.dj === 0) { this.segment_length += 1 } } this.k += 1 return output } } module.exports = { ManhattanIterator, ManathanIterator: ManhattanIterator, // backward compatibility OctahedronIterator, RaycastIterator, SpiralIterator2d, BlockFace }