src/grid/Grid3D.js

import Grid from "./Grid.js"

/** A class containing (mostly static) utility functions for dealing with 3D
 * 	grids. Extends the Grid class but implements functions specific to the 3D
 * 	grid (such as neighborhoods).
 *
 * @example <caption>Neighborhoods on a 3D grid</caption>
 *
 * let CPM = require("path/to/build")
 *
 * // Make a grid with a torus, and find the neighborhood of the upper left pixel [0,0,0]
 * let grid = new CPM.Grid3D( [100,100,100], [true,true,true] )
 * console.log( grid.neigh( [0,0,0] ) ) // returns coordinates of 26 neighbors (9+8+9)
 *
 * // Now try a grid without torus; the corner now has fewer neighbors.
 * let grid2 = new CPM.Grid3D( [100,100,100], [false,false,false] )
 * console.log( grid2.neigh( [0,0,0] ) ) // returns only 7 neighbors
 *
 * // Or try a torus in one dimension
 * let grid2 = new CPM.Grid3D( [100,100,100], [true,false,false] )
 * console.log( grid2.neigh( [0,0,0] ) )
 */
class Grid3D extends Grid {

	/** Constructor of the Grid3D object.
	 * @param {GridSize} extents - the size of the grid in each dimension
	 * @param {boolean[]} [torus = [true,true,true]] - should the borders of
	 * the grid be linked, so that a cell moving out on the left reappears on
	 * the right?
	 * Warning: setting the torus to false can give artifacts if not done
	 * properly, see {@link Grid#torus}.*/
	constructor( extents, torus = [true,true,true] ){
		super( extents, torus )
		// Check that the grid size is not too big to store pixel ID in 32-bit number,
		// and allow fast conversion of coordinates to unique ID numbers.
		/** @ignore */
		this.Z_BITS = 1+Math.floor( Math.log2( this.extents[2] - 1 ) )
		if( this.X_BITS + this.Y_BITS + this.Z_BITS > 32 ){
			throw("Field size too large -- field cannot be represented as 32-bit number")
		}
		/** @ignore */
		this.Z_MASK = (1 << this.Z_BITS)-1
		/** @ignore */
		this.Z_STEP = 1
		/** @ignore */
		this.Y_STEP = 1 << (this.Z_BITS)
		/** @ignore */
		this.X_STEP = 1 << (this.Z_BITS +this.Y_BITS)
		/** Array with values for each pixel stored at the position of its
		 * {@link IndexCoordinate}. E.g. the value of pixel with coordinate i
		 * is stored as this._pixelArray[i].
		 * 	Note that this array is accessed indirectly via the
		 * {@link _pixels} set- and get methods.
		 * @private
		 * @type {Uint16Array} */
		this._pixelArray = new Uint16Array(this.p2i(extents))
		this.datatype = "Uint16"
	}
	/** Method for conversion from an {@link ArrayCoordinate} to an
	 * {@link IndexCoordinate}.
	 *
	 * See also {@link Grid3D#i2p} for the backward conversion.
	 *
	 * @param {ArrayCoordinate} p - the coordinate of the pixel to convert
	 * @return {IndexCoordinate} the converted coordinate.
	 *
	 * @example
	 * let grid = new CPM.Grid3D( [100,100,100], [true,true,true] )
	 * let p = grid.i2p( 5 )
	 * console.log( p )
	 * console.log( grid.p2i( p ))
	 */
	p2i( p ){
		return ( p[0] << ( this.Z_BITS + this.Y_BITS ) ) + 
			( p[1] << this.Z_BITS ) + 
			p[2]
	}
	/** Method for conversion from an {@link IndexCoordinate} to an
	 * {@link ArrayCoordinate}.
	 *
	 * See also {@link Grid3D#p2i} for the backward conversion.
	 *
	 * @param {IndexCoordinate} i - the coordinate of the pixel to convert
	 * @return {ArrayCoordinate} the converted coordinate.
	 *
	 * @example
	 * let grid = new CPM.Grid3D( [100,100,100], [true,true,true] )
	 * let p = grid.i2p( 5 )
	 * console.log( p )
	 * console.log( grid.p2i( p ))
	 */
	i2p( i ){
		return [i >> (this.Y_BITS + this.Z_BITS), 
			( i >> this.Z_BITS ) & this.Y_MASK, i & this.Z_MASK ]
	}
	
	/** This iterator returns locations and values of all non-zero pixels.
	 * Whereas the {@link pixels} generator yields only non-background pixels
	 * and specifies both their {@link ArrayCoordinate} and value, this
	 * generator yields all pixels by {@link IndexCoordinate} and does not
	 * report value.
	 *
	 * @return {IndexCoordinate} for each pixel, return its
	 * {@link IndexCoordinate}.
	 *
	 *
	 * @example
	 * let CPM = require( "path/to/build" )
	 * // make a grid and set some values
	 * let grid = new CPM.Grid3D( [100,100,100], [true,true,true] )
	 * grid.setpixi( 0, 1 )
	 * grid.setpixi( 1, 5 )
	 *
	 * // iterator
	 * for( let i of grid.pixelsi() ){
	 * 	console.log( i )
	 * }
	 */
	* pixelsi() {
		let ii = 0, c = 0
		for( let i = 0 ; i < this.extents[0] ; i ++ ){
			let d = 0
			for( let j = 0 ; j < this.extents[1] ; j ++ ){
				for( let k = 0 ; k < this.extents[2] ; k ++ ){
					yield ii
					ii++
				}
				d += this.Y_STEP
				ii = c + d
			}
			c += this.X_STEP
			ii = c
		}
	}

	/** This iterator returns locations and values of all non-zero pixels.
	 * @return {Pixel} for each pixel, return an array [p,v] where p are
	 * the pixel's array coordinates on the grid, and v its value.
	 *
	 * @example
	 * let CPM = require( "path/to/build" )
	 * // make a grid and set some values
	 * let grid = new CPM.Grid3D( [100,100,100], [true,true,true] )
	 * grid.setpix( [0,0,0], 1 )
	 * grid.setpix( [0,0,1], 5 )
	 *
	 * // iterator
	 * for( let p of grid.pixels() ){
	 * 	console.log( p )
	 * }
	 */
	* pixels() {
		let ii = 0, c = 0
		for( let i = 0 ; i < this.extents[0] ; i ++ ){
			let d = 0
			for( let j = 0 ; j < this.extents[1] ; j ++ ){
				for( let k = 0 ; k < this.extents[2] ; k ++ ){
					//noinspection JSUnresolvedVariable
					let pixels = this._pixels
					if( pixels[ii] > 0 ){
						yield [[i,j,k], pixels[ii]]
					}
					ii++
				}
				d += this.Y_STEP
				ii = c + d
			}
			c += this.X_STEP
			ii = c
		}
	}

	/**	Return array of {@link IndexCoordinate} of the Moore neighbor pixels
	 * of the pixel at coordinate i. This function takes the 3D equivalent of
	 * the 2D Moore-8 neighborhood, excluding the pixel itself.
	 * @see https://en.wikipedia.org/wiki/Moore_neighborhood
	 * @param {IndexCoordinate} i - location of the pixel to get neighbors of.
	 * @param {boolean[]} [torus=[true,true,true]] - does the grid have linked
	 * borders? Defaults to the setting on this grid, see {@link torus}
	 * @return {IndexCoordinate[]} - an array of coordinates for all the
	 * neighbors of i.
	*/
	neighi( i, torus = this.torus ){
		let p = this.i2p(i)

		let xx = []
		for( let d = 0 ; d <= 2 ; d ++ ){
			if( p[d] === 0 ){
				if( torus[d] ){
					xx[d] = [p[d],this.extents[d]-1,p[d]+1]
				} else {
					xx[d] = [p[d],p[d]+1]
				}
			} else if( p[d] === this.extents[d]-1 ){
				if( torus[d] ){
					xx[d] = [p[d],p[d]-1,0]
				} else {
					xx[d] = [p[d],p[d]-1]
				}
			} else {
				xx[d] = [p[d],p[d]-1,p[d]+1]
			}
		}
		let r = [], first=true
		for( let x of xx[0] ){
			for( let y of xx[1] ){
				for( let z of xx[2] ){
					if( first ){
						first = false 
					} else {
						r.push( this.p2i( [x,y,z] ) )
					}
				}
			}
		}
		return r
	}
}

export default Grid3D