prismarine-world
Version:
The core implementation of the world for prismarine
283 lines (252 loc) • 8.23 kB
JavaScript
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
}