Home Reference Source

src/octree/voxel/VoxelCell.js

import { Vector3 } from "three";
import { CubicOctant } from "sparse-octree";
import { QEFData, QEFSolver } from "../../math";
import { Voxel } from "../../volume/Voxel";

/**
 * A QEF solver.
 *
 * @type {QEFSolver}
 * @private
 */

const qefSolver = new QEFSolver();

/**
 * A bias for boundary checks.
 *
 * @type {Number}
 * @private
 */

const BIAS = 1e-1;

/**
 * An error threshold for QEF-based voxel clustering.
 *
 * @type {Number}
 * @private
 */

let errorThreshold = -1;

/**
 * A voxel octant.
 */

export class VoxelCell extends CubicOctant {

	/**
	 * Constructs a new voxel octant.
	 *
	 * @param {Vector3} [min] - The lower bounds of the octant.
	 * @param {Number} [size] - The size of the octant.
	 */

	constructor(min, size) {

		super(min, size);

		/**
		 * A voxel that contains draw information.
		 *
		 * @type {Voxel}
		 */

		this.voxel = null;

	}

	/**
	 * Checks if the given point lies inside this cell.
	 *
	 * @param {Vector3} p - A point.
	 * @return {Boolean} Whether the given point lies inside this cell.
	 */

	contains(p) {

		const min = this.min;
		const size = this.size;

		return (
			p.x >= min.x - BIAS &&
			p.y >= min.y - BIAS &&
			p.z >= min.z - BIAS &&
			p.x <= min.x + size + BIAS &&
			p.y <= min.y + size + BIAS &&
			p.z <= min.z + size + BIAS
		);

	}

	/**
	 * Attempts to simplify this cell.
	 *
	 * @return {Number} The amount of removed voxels.
	 */

	collapse() {

		const children = this.children;

		const signs = [
			-1, -1, -1, -1,
			-1, -1, -1, -1
		];

		const position = new Vector3();

		let midSign = -1;
		let collapsible = (children !== null);

		let removedVoxels = 0;

		let child, sign, voxel;
		let qefData, error;

		let v, i;

		if(collapsible) {

			qefData = new QEFData();

			for(v = 0, i = 0; i < 8; ++i) {

				child = children[i];
				removedVoxels += child.collapse();
				voxel = child.voxel;

				if(child.children !== null) {

					// Couldn't simplify the child.
					collapsible = false;

				} else if(voxel !== null) {

					qefData.addData(voxel.qefData);

					midSign = (voxel.materials >> (7 - i)) & 1;
					signs[i] = (voxel.materials >> i) & 1;

					++v;

				}

			}

			if(collapsible) {

				error = qefSolver.setData(qefData).solve(position);

				if(error <= errorThreshold) {

					voxel = new Voxel();
					voxel.position.copy(this.contains(position) ? position : qefSolver.massPoint);

					for(i = 0; i < 8; ++i) {

						sign = signs[i];
						child = children[i];

						if(sign === -1) {

							// Undetermined, use mid sign instead.
							voxel.materials |= (midSign << i);

						} else {

							voxel.materials |= (sign << i);

							// Accumulate normals.
							voxel.normal.add(child.voxel.normal);

						}

					}

					voxel.normal.normalize();
					voxel.qefData = qefData;

					this.voxel = voxel;
					this.children = null;

					// Removed existing voxels and created a new one.
					removedVoxels += v - 1;

				}

			}

		}

		return removedVoxels;

	}

	/**
	 * An error threshold for QEF-based voxel clustering (mesh simplification).
	 *
	 * @type {Number}
	 */

	static get errorThreshold() {

		return errorThreshold;

	}

	/**
	 * The mesh simplification error threshold.
	 *
	 * A bigger threshold allows more voxel cells to collapse which results in
	 * less vertices being created.
	 *
	 * An error threshold of -1 disables the mesh simplification.
	 *
	 * @type {Number}
	 */

	static set errorThreshold(value) {

		errorThreshold = value;

	}

}