// Copyright 2008 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview Datastructure: A point Quad Tree for representing 2D data. Each * region has the same ratio as the bounds for the tree. * * The implementation currently requires pre-determined bounds for data as it * can not rebalance itself to that degree. * * @see ../demos/quadtree.html */ goog.provide('goog.structs.QuadTree'); goog.provide('goog.structs.QuadTree.Node'); goog.provide('goog.structs.QuadTree.Point'); goog.require('goog.math.Coordinate'); /** * Constructs a new quad tree. * @param {number} minX Minimum x-value that can be held in tree. * @param {number} minY Minimum y-value that can be held in tree. * @param {number} maxX Maximum x-value that can be held in tree. * @param {number} maxY Maximum y-value that can be held in tree. * @constructor * @final */ goog.structs.QuadTree = function(minX, minY, maxX, maxY) { /** * Count of the number of items in the tree. * @private {number} */ this.count_ = 0; /** * The root node for the quad tree. * @private {goog.structs.QuadTree.Node} */ this.root_ = new goog.structs.QuadTree.Node(minX, minY, maxX - minX, maxY - minY); }; /** * Returns a reference to the tree's root node. Callers shouldn't modify nodes, * directly. This is a convenience for visualization and debugging purposes. * @return {goog.structs.QuadTree.Node} The root node. */ goog.structs.QuadTree.prototype.getRootNode = function() { return this.root_; }; /** * Sets the value of an (x, y) point within the quad-tree. * @param {number} x The x-coordinate. * @param {number} y The y-coordinate. * @param {*} value The value associated with the point. */ goog.structs.QuadTree.prototype.set = function(x, y, value) { var root = this.root_; if (x < root.x || y < root.y || x > root.x + root.w || y > root.y + root.h) { throw Error('Out of bounds : (' + x + ', ' + y + ')'); } if (this.insert_(root, new goog.structs.QuadTree.Point(x, y, value))) { this.count_++; } }; /** * Gets the value of the point at (x, y) or null if the point is empty. * @param {number} x The x-coordinate. * @param {number} y The y-coordinate. * @param {*=} opt_default The default value to return if the node doesn't * exist. * @return {*} The value of the node, the default value if the node * doesn't exist, or undefined if the node doesn't exist and no default * has been provided. */ goog.structs.QuadTree.prototype.get = function(x, y, opt_default) { var node = this.find_(this.root_, x, y); return node ? node.point.value : opt_default; }; /** * Removes a point from (x, y) if it exists. * @param {number} x The x-coordinate. * @param {number} y The y-coordinate. * @return {*} The value of the node that was removed, or null if the * node doesn't exist. */ goog.structs.QuadTree.prototype.remove = function(x, y) { var node = this.find_(this.root_, x, y); if (node) { var value = node.point.value; node.point = null; node.nodeType = goog.structs.QuadTree.NodeType.EMPTY; this.balance_(node); this.count_--; return value; } else { return null; } }; /** * Returns true if the point at (x, y) exists in the tree. * @param {number} x The x-coordinate. * @param {number} y The y-coordinate. * @return {boolean} Whether the tree contains a point at (x, y). */ goog.structs.QuadTree.prototype.contains = function(x, y) { return this.get(x, y) != null; }; /** * @return {boolean} Whether the tree is empty. */ goog.structs.QuadTree.prototype.isEmpty = function() { return this.root_.nodeType == goog.structs.QuadTree.NodeType.EMPTY; }; /** * @return {number} The number of items in the tree. */ goog.structs.QuadTree.prototype.getCount = function() { return this.count_; }; /** * Removes all items from the tree. */ goog.structs.QuadTree.prototype.clear = function() { this.root_.nw = this.root_.ne = this.root_.sw = this.root_.se = null; this.root_.nodeType = goog.structs.QuadTree.NodeType.EMPTY; this.root_.point = null; this.count_ = 0; }; /** * Returns an array containing the coordinates of each point stored in the tree. * @return {!Array} Array of coordinates. */ goog.structs.QuadTree.prototype.getKeys = function() { var arr = []; this.traverse_(this.root_, function(node) { arr.push(new goog.math.Coordinate(node.point.x, node.point.y)); }); return arr; }; /** * Returns an array containing all values stored within the tree. * @return {!Array} The values stored within the tree. */ goog.structs.QuadTree.prototype.getValues = function() { var arr = []; this.traverse_(this.root_, function(node) { // Must have a point because it's a leaf. arr.push(node.point.value); }); return arr; }; /** * Clones the quad-tree and returns the new instance. * @return {!goog.structs.QuadTree} A clone of the tree. */ goog.structs.QuadTree.prototype.clone = function() { var x1 = this.root_.x; var y1 = this.root_.y; var x2 = x1 + this.root_.w; var y2 = y1 + this.root_.h; var clone = new goog.structs.QuadTree(x1, y1, x2, y2); // This is inefficient as the clone needs to recalculate the structure of the // tree, even though we know it already. But this is easier and can be // optimized when/if needed. this.traverse_(this.root_, function(node) { clone.set(node.point.x, node.point.y, node.point.value); }); return clone; }; /** * Traverses the tree and calls a function on each node. * @param {function(?, goog.math.Coordinate, goog.structs.QuadTree)} fn * The function to call for every value. This function takes 3 arguments * (the value, the coordinate, and the tree itself) and the return value is * irrelevant. * @param {Object=} opt_obj The object to be used as the value of 'this' * within {@ code fn}. */ goog.structs.QuadTree.prototype.forEach = function(fn, opt_obj) { this.traverse_(this.root_, function(node) { var coord = new goog.math.Coordinate(node.point.x, node.point.y); fn.call(opt_obj, node.point.value, coord, this); }); }; /** * Traverses the tree depth-first, with quadrants being traversed in clockwise * order (NE, SE, SW, NW). The provided function will be called for each * leaf node that is encountered. * @param {goog.structs.QuadTree.Node} node The current node. * @param {function(goog.structs.QuadTree.Node)} fn The function to call * for each leaf node. This function takes the node as an argument, and its * return value is irrelevant. * @private */ goog.structs.QuadTree.prototype.traverse_ = function(node, fn) { switch (node.nodeType) { case goog.structs.QuadTree.NodeType.LEAF: fn.call(this, node); break; case goog.structs.QuadTree.NodeType.POINTER: this.traverse_(node.ne, fn); this.traverse_(node.se, fn); this.traverse_(node.sw, fn); this.traverse_(node.nw, fn); break; } }; /** * Finds a leaf node with the same (x, y) coordinates as the target point, or * null if no point exists. * @param {goog.structs.QuadTree.Node} node The node to search in. * @param {number} x The x-coordinate of the point to search for. * @param {number} y The y-coordinate of the point to search for. * @return {goog.structs.QuadTree.Node} The leaf node that matches the target, * or null if it doesn't exist. * @private */ goog.structs.QuadTree.prototype.find_ = function(node, x, y) { switch (node.nodeType) { case goog.structs.QuadTree.NodeType.EMPTY: return null; case goog.structs.QuadTree.NodeType.LEAF: return node.point.x == x && node.point.y == y ? node : null; case goog.structs.QuadTree.NodeType.POINTER: return this.find_(this.getQuadrantForPoint_(node, x, y), x, y); default: throw Error('Invalid nodeType'); } }; /** * Inserts a point into the tree, updating the tree's structure if necessary. * @param {goog.structs.QuadTree.Node} parent The parent to insert the point * into. * @param {goog.structs.QuadTree.Point} point The point to insert. * @return {boolean} True if a new node was added to the tree; False if a node * already existed with the correpsonding coordinates and had its value * reset. * @private */ goog.structs.QuadTree.prototype.insert_ = function(parent, point) { switch (parent.nodeType) { case goog.structs.QuadTree.NodeType.EMPTY: this.setPointForNode_(parent, point); return true; case goog.structs.QuadTree.NodeType.LEAF: if (parent.point.x == point.x && parent.point.y == point.y) { this.setPointForNode_(parent, point); return false; } else { this.split_(parent); return this.insert_(parent, point); } case goog.structs.QuadTree.NodeType.POINTER: return this.insert_( this.getQuadrantForPoint_(parent, point.x, point.y), point); default: throw Error('Invalid nodeType in parent'); } }; /** * Converts a leaf node to a pointer node and reinserts the node's point into * the correct child. * @param {goog.structs.QuadTree.Node} node The node to split. * @private */ goog.structs.QuadTree.prototype.split_ = function(node) { var oldPoint = node.point; node.point = null; node.nodeType = goog.structs.QuadTree.NodeType.POINTER; var x = node.x; var y = node.y; var hw = node.w / 2; var hh = node.h / 2; node.nw = new goog.structs.QuadTree.Node(x, y, hw, hh, node); node.ne = new goog.structs.QuadTree.Node(x + hw, y, hw, hh, node); node.sw = new goog.structs.QuadTree.Node(x, y + hh, hw, hh, node); node.se = new goog.structs.QuadTree.Node(x + hw, y + hh, hw, hh, node); this.insert_(node, oldPoint); }; /** * Attempts to balance a node. A node will need balancing if all its children * are empty or it contains just one leaf. * @param {goog.structs.QuadTree.Node} node The node to balance. * @private */ goog.structs.QuadTree.prototype.balance_ = function(node) { switch (node.nodeType) { case goog.structs.QuadTree.NodeType.EMPTY: case goog.structs.QuadTree.NodeType.LEAF: if (node.parent) { this.balance_(node.parent); } break; case goog.structs.QuadTree.NodeType.POINTER: var nw = node.nw, ne = node.ne, sw = node.sw, se = node.se; var firstLeaf = null; // Look for the first non-empty child, if there is more than one then we // break as this node can't be balanced. if (nw.nodeType != goog.structs.QuadTree.NodeType.EMPTY) { firstLeaf = nw; } if (ne.nodeType != goog.structs.QuadTree.NodeType.EMPTY) { if (firstLeaf) { break; } firstLeaf = ne; } if (sw.nodeType != goog.structs.QuadTree.NodeType.EMPTY) { if (firstLeaf) { break; } firstLeaf = sw; } if (se.nodeType != goog.structs.QuadTree.NodeType.EMPTY) { if (firstLeaf) { break; } firstLeaf = se; } if (!firstLeaf) { // All child nodes are empty: so make this node empty. node.nodeType = goog.structs.QuadTree.NodeType.EMPTY; node.nw = node.ne = node.sw = node.se = null; } else if (firstLeaf.nodeType == goog.structs.QuadTree.NodeType.POINTER) { // Only child was a pointer, therefore we can't rebalance. break; } else { // Only child was a leaf: so update node's point and make it a leaf. node.nodeType = goog.structs.QuadTree.NodeType.LEAF; node.nw = node.ne = node.sw = node.se = null; node.point = firstLeaf.point; } // Try and balance the parent as well. if (node.parent) { this.balance_(node.parent); } break; } }; /** * Returns the child quadrant within a node that contains the given (x, y) * coordinate. * @param {goog.structs.QuadTree.Node} parent The node. * @param {number} x The x-coordinate to look for. * @param {number} y The y-coordinate to look for. * @return {goog.structs.QuadTree.Node} The child quadrant that contains the * point. * @private */ goog.structs.QuadTree.prototype.getQuadrantForPoint_ = function(parent, x, y) { var mx = parent.x + parent.w / 2; var my = parent.y + parent.h / 2; if (x < mx) { return y < my ? parent.nw : parent.sw; } else { return y < my ? parent.ne : parent.se; } }; /** * Sets the point for a node, as long as the node is a leaf or empty. * @param {goog.structs.QuadTree.Node} node The node to set the point for. * @param {goog.structs.QuadTree.Point} point The point to set. * @private */ goog.structs.QuadTree.prototype.setPointForNode_ = function(node, point) { if (node.nodeType == goog.structs.QuadTree.NodeType.POINTER) { throw Error('Can not set point for node of type POINTER'); } node.nodeType = goog.structs.QuadTree.NodeType.LEAF; node.point = point; }; /** * Enumeration of node types. * @enum {number} */ goog.structs.QuadTree.NodeType = { EMPTY: 0, LEAF: 1, POINTER: 2 }; /** * Constructs a new quad tree node. * @param {number} x X-coordiate of node. * @param {number} y Y-coordinate of node. * @param {number} w Width of node. * @param {number} h Height of node. * @param {goog.structs.QuadTree.Node=} opt_parent Optional parent node. * @constructor * @final */ goog.structs.QuadTree.Node = function(x, y, w, h, opt_parent) { /** * The x-coordinate of the node. * @type {number} */ this.x = x; /** * The y-coordinate of the node. * @type {number} */ this.y = y; /** * The width of the node. * @type {number} */ this.w = w; /** * The height of the node. * @type {number} */ this.h = h; /** * The parent node. * @type {goog.structs.QuadTree.Node?} */ this.parent = opt_parent || null; }; /** * The node's type. * @type {goog.structs.QuadTree.NodeType} */ goog.structs.QuadTree.Node.prototype.nodeType = goog.structs.QuadTree.NodeType.EMPTY; /** * The child node in the North-West quadrant. * @type {goog.structs.QuadTree.Node?} */ goog.structs.QuadTree.Node.prototype.nw = null; /** * The child node in the North-East quadrant. * @type {goog.structs.QuadTree.Node?} */ goog.structs.QuadTree.Node.prototype.ne = null; /** * The child node in the South-West quadrant. * @type {goog.structs.QuadTree.Node?} */ goog.structs.QuadTree.Node.prototype.sw = null; /** * The child node in the South-East quadrant. * @type {goog.structs.QuadTree.Node?} */ goog.structs.QuadTree.Node.prototype.se = null; /** * The point for the node, if it is a leaf node. * @type {goog.structs.QuadTree.Point?} */ goog.structs.QuadTree.Node.prototype.point = null; /** * Creates a new point object. * @param {number} x The x-coordinate of the point. * @param {number} y The y-coordinate of the point. * @param {*=} opt_value Optional value associated with the point. * @constructor * @final */ goog.structs.QuadTree.Point = function(x, y, opt_value) { /** * The x-coordinate for the point. * @type {number} */ this.x = x; /** * The y-coordinate for the point. * @type {number} */ this.y = y; /** * Optional value associated with the point. * @type {*} */ this.value = goog.isDef(opt_value) ? opt_value : null; };