src/volume/EdgeData.js
import { EdgeIterator } from "./EdgeIterator";
/**
 * Stores edge data separately for each dimension.
 *
 * With a grid resolution N, there are `3 * (N + 1)² * N` edges in total, but
 * the number of edges that actually contain the volume's surface is usually
 * much lower.
 *
 * @implements {Serializable}
 * @implements {Deserializable}
 * @implements {TransferableContainer}
 */
export class EdgeData {
	/**
	 * Constructs new edge data.
	 *
	 * @param {Number} n - The material grid resolution.
	 * @param {Number} [x=0] - The amount of edges along the X-axis. If <= 0, no memory will be allocated.
	 * @param {Number} [y=x] - The amount of edges along the Y-axis. If omitted, this will be the same as x.
	 * @param {Number} [z=x] - The amount of edges along the Z-axis. If omitted, this will be the same as x.
	 */
	constructor(n, x = 0, y = x, z = x) {
		/**
		 * The material grid resolution.
		 *
		 * @type {Number}
		 */
		this.resolution = n;
		/**
		 * The edges.
		 *
		 * Edges are stored as starting grid point indices in ascending order. The
		 * ending point indices are implicitly defined through the dimension split:
		 *
		 * Given a starting point index A, the ending point index B for the X-, Y-
		 * and Z-axis is defined as `A + 1`, `A + N` and `A + N²` respectively where
		 * N is the grid resolution + 1.
		 *
		 * @type {Uint32Array[]}
		 */
		this.indices = (x <= 0) ? null : [
			new Uint32Array(x),
			new Uint32Array(y),
			new Uint32Array(z)
		];
		/**
		 * The Zero Crossing interpolation values.
		 *
		 * Each value describes the relative surface intersection position on the
		 * respective edge. The values correspond to the order of the edges.
		 *
		 * @type {Float32Array[]}
		 */
		this.zeroCrossings = (x <= 0) ? null : [
			new Float32Array(x),
			new Float32Array(y),
			new Float32Array(z)
		];
		/**
		 * The surface intersection normals.
		 *
		 * The vectors are stored as [x, y, z] float triples and correspond to the
		 * order of the edges.
		 *
		 * @type {Float32Array[]}
		 */
		this.normals = (x <= 0) ? null : [
			new Float32Array(x * 3),
			new Float32Array(y * 3),
			new Float32Array(z * 3)
		];
	}
	/**
	 * Serialises this data.
	 *
	 * @param {Boolean} [deflate=false] - Whether the data should be compressed if possible.
	 * @return {Object} The serialised data.
	 */
	serialize(deflate = false) {
		return {
			resolution: this.resolution,
			edges: this.edges,
			zeroCrossings: this.zeroCrossings,
			normals: this.normals
		};
	}
	/**
	 * Adopts the given serialised data.
	 *
	 * @param {Object} object - Serialised edge data. Can be null.
	 * @return {Deserializable} This object or null if the given serialised data was null.
	 */
	deserialize(object) {
		let result = this;
		if(object !== null) {
			this.resolution = object.resolution;
			this.edges = object.edges;
			this.zeroCrossings = object.zeroCrossings;
			this.normals = object.normals;
		} else {
			result = null;
		}
		return result;
	}
	/**
	 * Creates a list of transferable items.
	 *
	 * @param {Array} [transferList] - An optional target list. The transferable items will be added to this list.
	 * @return {Transferable[]} The transfer list.
	 */
	createTransferList(transferList = []) {
		const arrays = [
			this.edges[0],
			this.edges[1],
			this.edges[2],
			this.zeroCrossings[0],
			this.zeroCrossings[1],
			this.zeroCrossings[2],
			this.normals[0],
			this.normals[1],
			this.normals[2]
		];
		let array;
		let i, l;
		for(i = 0, l = arrays.length; i < l; ++i) {
			array = arrays[i];
			if(array !== null) {
				transferList.push(array.buffer);
			}
		}
		return transferList;
	}
	/**
	 * Returns a new edge iterator.
	 *
	 * @param {Vector3} cellPosition - The position of the volume data cell.
	 * @param {Number} cellSize - The size of the volume data cell.
	 * @return {EdgeIterator} An iterator.
	 */
	edges(cellPosition, cellSize) {
		return new EdgeIterator(this, cellPosition, cellSize);
	}
	/**
	 * Creates a new edge iterator that only returns edges along the X-axis.
	 *
	 * @param {Vector3} cellPosition - The position of the volume data cell.
	 * @param {Number} cellSize - The size of the volume data cell.
	 * @return {EdgeIterator} An iterator.
	 */
	edgesX(cellPosition, cellSize) {
		return new EdgeIterator(this, cellPosition, cellSize, 0, 1);
	}
	/**
	 * Creates a new edge iterator that only returns edges along the Y-axis.
	 *
	 * @param {Vector3} cellPosition - The position of the volume data cell.
	 * @param {Number} cellSize - The size of the volume data cell.
	 * @return {EdgeIterator} An iterator.
	 */
	edgesY(cellPosition, cellSize) {
		return new EdgeIterator(this, cellPosition, cellSize, 1, 2);
	}
	/**
	 * Creates a new edge iterator that only returns edges along the Z-axis.
	 *
	 * @param {Vector3} cellPosition - The position of the volume data cell.
	 * @param {Number} cellSize - The size of the volume data cell.
	 * @return {EdgeIterator} An iterator.
	 */
	edgesZ(cellPosition, cellSize) {
		return new EdgeIterator(this, cellPosition, cellSize, 2, 3);
	}
	/**
	 * Calculates the amount of edges for one axis based on a given resolution.
	 *
	 * @param {Number} n - The grid resolution.
	 * @return {Number} The amount of edges for a single dimension.
	 */
	static calculate1DEdgeCount(n) {
		return Math.pow((n + 1), 2) * n;
	}
}