Source: maps/TopDownBVHNode.js

/**
 * Node of a top down BVH.
 *
 * @param  {?Grape2D.TopDownBVHNode} parent Parent node, or null, it it's
 *   the root node.
 * @param  {!Array.<Grape2D.Object2D>} objects Objects to be added to the
 *   node.
 *
 * @constructor
 */
Grape2D.TopDownBVHNode = function(parent, objects) {
	/**
	 * Bounding volume.
	 *
	 * @type {!Grape2D.Shape}
	 * @private
	 */
	this.bv = Grape2D.BVFactorySingleton.getPlaceHolder();
	/**
	 * True if it's a leaf (end point of a tree), false if it's a node.
	 *
	 * @type {!boolean}
	 * @private
	 */
	this.leaf = false;
	/**
	 * Objects of a leaf.
	 *
	 * @type {!Array.<Grape2D.Object2D>}
	 * @private
	 */
	this.objects = [];

	/**
	 * Parent node.
	 *
	 * @type {?Grape2D.TopDownBVHNode}
	 * @private
	 */
	this.parent = parent;
	/**
	 * Left child of a node.
	 *
	 * @type {?Grape2D.TopDownBVHNode}
	 * @private
	 */
	this.left = null;
	/**
	 * Right child of a node.
	 *
	 * @type {?Grape2D.TopDownBVHNode}
	 * @private
	 */
	this.right = null;
	/**
	 * Depth of the current node.
	 *
	 * @type {!number}
	 * @private
	 */
	this.depth = parent ? parent.getDepth() + 1 : 0;
	//computes what kind of node this is,
	//and what to do with it.
	this.compute(objects);
};

Grape2D.TopDownBVHNode.prototype = {
	constructor: Grape2D.TopDownBVHNode,
	/**
	 * Makes this instance a leaf or a node.
	 *
	 * @param  {!Array.<Grape2D.Object2D>} objects List of objects
	 *   to separate through the node.
	 * @public
	 */
	compute: function(objects) {
		var i;
		if (objects.length <= Grape2D.TopDownBVHTree.DEFAULT_PER_LEAF || this.depth >= Grape2D.TopDownBVHTree.MAX_DEPTH) {
			//this instance will be a leaf
			this.leaf = true;
			for (i = 0; i < objects.length; i++) {
				this.objects.push(objects[i]);
			}
		} else {
			//this instance will be a node
			var r = Grape2D.BVHStrategySingleton.getStrategy().solve(objects),
				factory = Grape2D.BVFactorySingleton.getFactory();

			if (r.endState) {
				//nop it's a leaf after all.
				this.leaf = true;
				for (i = 0; i < objects.length; i++) {
					this.objects.push(objects[i]);
				}
				return;
			}

			this.bv = factory.merge(objects[0].getBoundingBox(), objects[1].getBoundingBox());
			for (i = 2; i < objects.length; i++) {
				this.bv = factory.merge(this.bv, objects[i].getBoundingBox());
			}

			this.left = new Grape2D.TopDownBVHNode(this, r.left);
			this.right = new Grape2D.TopDownBVHNode(this, r.right);
		}
	},
	/**
	 * Checks if it's a leaf
	 *
	 * @return {!boolean} True if it's a leaf.
	 * @public
	 */
	isLeaf: function() {
		return this.leaf;
	},
	/**
	 * Gets the bounding volume.
	 *
	 * @return {!Grape2D.Shape} The bounding volume.
	 * @public
	 */
	getBoundingVolume: function() {
		return this.bv;
	},
	/**
	 * Gets the parent.
	 *
	 * @return {?Grape2D.TopDownBVHNode} The parent, or null if it's the root.
	 * @public
	 */
	getParent: function() {
		return this.parent;
	},
	/**
	 * Gets the objects, if it's a leaf.
	 *
	 * @return {!Array.<Grape2D.Object2D>} List of the objects.
	 * @public
	 */
	getObjects: function() {
		return this.objects;
	},
	/**
	 * Gets the left side of the node.
	 *
	 * @return {?Grape2D.TopDownBVHNode} Left side node, or null, if it's a
	 *   leaf.
	 * @public
	 */
	getLeft: function() {
		return this.left;
	},
	/**
	 * Gets the right side of the node.
	 *
	 * @return {?Grape2D.TopDownBVHNode} Right side node, or null, if it's a
	 *   leaf.
	 * @public
	 */
	getRight: function() {
		return this.right;
	},
	/**
	 * Queries a shape or a point to find if it is colliding.
	 *   If so, returns the {@link Grape2D.Object2D} that are colliding with
	 *   the shape.
	 *
	 * @param  {!(Grape2D.Shape|Grape2D.Vector)} shape Shape of to query.
	 * @return {!Array.<Grape2D.Object2D>} Objects that are colliding with
	 *   the shape.
	 */
	query: function(shape) {
		var res = [],
			i;
		if (this.leaf) {
			for (i = 0; i < this.objects.length; i++) {
				if (Grape2D.CollisionCheckerSingleton.collide(this.objects[i].getBoundingBox(), shape)) {
					res.push(this.objects[i]);
				}
			}
		} else {
			if (Grape2D.CollisionCheckerSingleton.collide(this.bv, shape)) {
				var list = this.left.query(shape);
				for (i = 0; i < list.length; i++) {
					res.push(list[i]);
				}

				list = this.right.query(shape);
				for (i = 0; i < list.length; i++) {
					res.push(list[i]);
				}

				return res;
			}
		}
		return res;
	},
	queryRay: function(ray) {
		var i;
		if (this.leaf) {
			for (i = 0; i < this.objects.length; i++) {
				if (Grape2D.CollisionCheckerSingleton.collide(this.objects[i].getBoundingBox(), ray)) {
					return this.objects[i];
				}
			}
		} else {
			if (Grape2D.CollisionCheckerSingleton.collide(this.bv, ray)) {
				var res = this.left.queryRay(ray);
				if (res) {
					return res;
				}

				res = this.right.queryRay(ray);
				if (res) {
					return res;
				}
			}
		}
		return null;
	},
	/**
	 * Gets the depth of the node, in the context of the tree.
	 *
	 * @return {!number} Depth of the node.
	 */
	getDepth: function() {
		return this.depth;
	}
};