Home Reference Source

src/OctreeHelper.js

import { edges, layout } from "sparse-octree";

import {
	BufferAttribute,
	BufferGeometry,
	Group,
	LineSegments,
	LineBasicMaterial
} from "three";

/**
 * An octree helper.
 */

export class OctreeHelper extends Group {

	/**
	 * Constructs a new octree helper.
	 *
	 * @param {Tree} [octree=null] - An octree.
	 */

	constructor(octree = null) {

		super();

		/**
		 * The name of this object.
		 *
		 * @type {String}
		 * @protected
		 */

		this.name = "OctreeHelper";

		/**
		 * The octree.
		 *
		 * @type {Tree}
		 */

		this.octree = octree;

		this.update();

	}

	/**
	 * Creates octant geometry.
	 *
	 * @private
	 * @param {Iterator} octants - An octant iterator.
	 * @param {Number} octantCount - The size of the given sequence.
	 */

	createLineSegments(octants, octantCount) {

		const maxOctants = (Math.pow(2, 16) / 8) - 1;
		const group = new Group();

		const material = new LineBasicMaterial({
			color: 0xffffff * Math.random()
		});

		let result;
		let vertexCount;
		let length;

		let indices, positions;
		let octant, min, max;
		let geometry;

		let i, j, c, d, n;
		let corner, edge;

		// Create geometry in multiple runs to limit the amount of vertices.
		for(i = 0, length = 0, n = Math.ceil(octantCount / maxOctants); n > 0; --n) {

			length += (octantCount < maxOctants) ? octantCount : maxOctants;
			octantCount -= maxOctants;

			vertexCount = length * 8;
			indices = new Uint16Array(vertexCount * 3);
			positions = new Float32Array(vertexCount * 3);

			// Continue where the previous run left off.
			for(c = 0, d = 0, result = octants.next(); !result.done && i < length;) {

				octant = result.value;
				min = octant.min;
				max = octant.max;

				// Create line connections based on the current vertex count.
				for(j = 0; j < 12; ++j) {

					edge = edges[j];

					indices[d++] = c + edge[0];
					indices[d++] = c + edge[1];

				}

				// Create the vertices.
				for(j = 0; j < 8; ++j, ++c) {

					corner = layout[j];

					positions[c * 3] = (corner[0] === 0) ? min.x : max.x;
					positions[c * 3 + 1] = (corner[1] === 0) ? min.y : max.y;
					positions[c * 3 + 2] = (corner[2] === 0) ? min.z : max.z;

				}

				if(++i < length) {

					result = octants.next();

				}

			}

			geometry = new BufferGeometry();
			geometry.setIndex(new BufferAttribute(indices, 1));
			geometry.setAttribute("position", new BufferAttribute(positions, 3));

			group.add(new LineSegments(geometry, material));

		}

		this.add(group);

	}

	/**
	 * Updates the helper geometry.
	 */

	update() {

		const depth = (this.octree !== null) ? this.octree.getDepth() : -1;

		let level = 0;
		let result;

		// Remove existing geometry.
		this.dispose();

		while(level <= depth) {

			result = this.octree.findNodesByLevel(level);

			this.createLineSegments(
				result[Symbol.iterator](),
				(typeof result.size === "number") ? result.size : result.length
			);

			++level;

		}

	}

	/**
	 * Destroys this helper.
	 */

	dispose() {

		const groups = this.children;

		let group, children;
		let i, j, il, jl;

		for(i = 0, il = groups.length; i < il; ++i) {

			group = groups[i];
			children = group.children;

			for(j = 0, jl = children.length; j < jl; ++j) {

				children[j].geometry.dispose();
				children[j].material.dispose();

			}

			while(children.length > 0) {

				group.remove(children[0]);

			}

		}

		while(groups.length > 0) {

			this.remove(groups[0]);

		}

	}

}