UNPKG

fqtree

Version:

a flexible quadtree for JavaScript/TypeScript

350 lines 14 kB
"use strict"; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.QuadTree = void 0; var onsreo_1 = require("onsreo"); var evjkit_1 = require("evjkit"); var range_1 = require("./range"); var caches_1 = require("./caches"); var _next_qt_id = 0; var QuadTree = /** @class */ (function () { /** * A Quadtree for spatial partitioning and searching. * @param boundary - this QuadTree<Spatial>'s bounds * @param capacity - how many points this quadtree can hold * @param root - QuadTree<Spatial>'s root Instance (only used for subdivisions) */ function QuadTree(boundary, capacity, maxDepth, root) { if (capacity === void 0) { capacity = 4; } if (maxDepth === void 0) { maxDepth = 10; } this.id = -1; this.capacity = 4; this.divided = false; this.boundary = boundary; this.capacity = capacity; this.id = _next_qt_id++; this.root = root !== null && root !== void 0 ? root : this.id; this.maxDepth = maxDepth; this.data = []; var qt_cache = (0, caches_1.get_qt_cache)(); qt_cache.add(this, this.id); } /** * clears this quadtree and its children (if any). */ QuadTree.prototype._clear = function () { var _this = this; var _a, _b, _c, _d; this.data.forEach(function (id) { return _this.data_cache.remove(id); }); this.data = []; this.qt_cache.remove(this.id); this.root = null; if (this.divided) { (_a = this.qt_cache.get(this.ne)) === null || _a === void 0 ? void 0 : _a._clear(); (_b = this.qt_cache.get(this.nw)) === null || _b === void 0 ? void 0 : _b._clear(); (_c = this.qt_cache.get(this.se)) === null || _c === void 0 ? void 0 : _c._clear(); (_d = this.qt_cache.get(this.sw)) === null || _d === void 0 ? void 0 : _d._clear(); } this.ne = null; this.nw = null; this.se = null; this.sw = null; this.divided = false; (0, evjkit_1.nullify)(this, 'boundary'); }; QuadTree.prototype.destroy = function () { this._clear(); _next_qt_id = 0; this.data_cache.destroy(); this.qt_cache.destroy(); }; Object.defineProperty(QuadTree.prototype, "qt_cache", { get: function () { return (0, caches_1.get_qt_cache)(); }, enumerable: false, configurable: true }); Object.defineProperty(QuadTree.prototype, "data_cache", { get: function () { return (0, caches_1.get_data_cache)(); }, enumerable: false, configurable: true }); // /** // * resets all the internal data for this tree and its children. // */ // _reset(): void { // this.data.forEach(id => this.data_cache.remove(id)); // this.data = []; // this.qt_cache.remove(this.id); // this.root = null; // if (this.divided) { // this.qt_cache.get(this.ne)?._reset(); // this.qt_cache.get(this.nw)?._reset(); // this.qt_cache.get(this.se)?._reset(); // this.qt_cache.get(this.sw)?._reset(); // } // this.ne = null; // this.nw = null; // this.se = null; // this.sw = null; // this.divided = false; // nullify(this, 'boundary'); // } /** * resets the quadtree and optionally sets new bounds. * @param boundary - this QuadTree<Spatial>'s bounds */ QuadTree.prototype.reset = function (bounds) { if (bounds === void 0) { bounds = this.boundary; } // store bounds temporarily so they can be persisted var tempBounds = bounds; this._clear(); _next_qt_id = 0; this.id = _next_qt_id++; this.root = this.id; this.qt_cache.add(this, this.id); this.boundary = tempBounds; }; /** * subdivide this tree into 4 new quadrants. */ QuadTree.prototype.subdivide = function () { var _this = this; var _a = this.boundary.bounds, x = _a.x, y = _a.y, w = _a.w, h = _a.h; var hw = Math.ceil(w / 2), hh = Math.ceil(h / 2); var ne = new range_1.RectangleRange(x + hw, y - hh, hw, hh); var nw = new range_1.RectangleRange(x - hw, y - hh, hw, hh); var se = new range_1.RectangleRange(x + hw, y + hh, hw, hh); var sw = new range_1.RectangleRange(x - hw, y + hh, hw, hh); // we insert things this way so that the points will cause the // appropriate number of subdivisions, that way ranges and lines // will be inserted most efficiently. We don't do this separation // in insertMany as it would have too large of a performance // penalty this.ne = new QuadTree(ne, this.capacity, this.maxDepth - 1, this.root).id; this.nw = new QuadTree(nw, this.capacity, this.maxDepth - 1, this.root).id; this.se = new QuadTree(se, this.capacity, this.maxDepth - 1, this.root).id; this.sw = new QuadTree(sw, this.capacity, this.maxDepth - 1, this.root).id; // now we've successfully divided this.divided = true; // now collect and re-insert this quadrants data // into its sub-quadrants this.getData().forEach(function (spatial) { _this.data_cache.remove(spatial.id); _this.insert(spatial); }); // clear this node's internal data as we only want it // to exist on the leaves this.data = []; }; QuadTree.prototype._insert = function (spatial) { // insert and divide based on type of spatial this.data.push(spatial.id); // if we've exceeded capacity, subdivide if (this.data.length > this.capacity && this.maxDepth) { this.subdivide(); } else { this.data_cache.add([spatial, this.id], spatial.id); } }; /** * insert a spatial into this tree. * @param spatial - spatial to insert. */ QuadTree.prototype.insert = function (spatial) { var _a, _b, _c, _d; if (!this.contains(spatial)) return; if (this.divided) { (_a = this.qt_cache.get(this.ne)) === null || _a === void 0 ? void 0 : _a.insert(spatial); (_b = this.qt_cache.get(this.nw)) === null || _b === void 0 ? void 0 : _b.insert(spatial); (_c = this.qt_cache.get(this.se)) === null || _c === void 0 ? void 0 : _c.insert(spatial); (_d = this.qt_cache.get(this.sw)) === null || _d === void 0 ? void 0 : _d.insert(spatial); } else { this._insert(spatial); } }; /** * insert all the provided spatials. * @param spatials - spatials to insert. */ QuadTree.prototype.insertMany = function (spatials) { var _this = this; spatials.forEach(function (spatial) { return _this.insert(spatial); }); }; /** * tests if this quadtree's boundary completely contains the spatial. * @param spatial - spatial to test. * @returns true if contained within the bounds, else false. */ QuadTree.prototype.contains = function (spatial) { return this.boundary.containsPoint(spatial); }; /** * tests if this quadtree's boundary intersects with the boundary. * @param spatial - spatial to test. * @returns true if intersects, else false. */ QuadTree.prototype.intersects = function (range) { return this.boundary.intersects(range); }; QuadTree.prototype.getData = function () { var _this = this; return this.data.reduce(function (data, id) { var maybe_data = _this.data_cache.get(id); if ((0, onsreo_1.is_none)(maybe_data)) return data; return data.addItem(maybe_data[0]); }, []); }; /** * query insertain spatial using provided range. * @param range - range for query * @param found - set to fill [default: empty Set]. * @returns query results as a set */ QuadTree.prototype.query = function (range, found) { var _a, _b, _c, _d; if (found === void 0) { found = new Set(); } if (!this.intersects(range)) return found; if (this.divided) { (_a = this.qt_cache.get(this.ne)) === null || _a === void 0 ? void 0 : _a.query(range, found); (_b = this.qt_cache.get(this.nw)) === null || _b === void 0 ? void 0 : _b.query(range, found); (_c = this.qt_cache.get(this.se)) === null || _c === void 0 ? void 0 : _c.query(range, found); (_d = this.qt_cache.get(this.sw)) === null || _d === void 0 ? void 0 : _d.query(range, found); } else { this.getData().forEach(function (spatial) { return range.containsPoint(spatial) && found.add(spatial); }); } return found; }; QuadTree.prototype._remove = function (id) { var _a; var maybe_data = this.data_cache.get(id); if ((0, onsreo_1.is_none)(maybe_data)) return; var _b = __read(maybe_data, 2), data = _b[0], parent_id = _b[1]; (_a = this.qt_cache.get(parent_id)) === null || _a === void 0 ? void 0 : _a.remove(data); }; /** * remove the given spatial from the quadtree. * @param spatial - spatial to remove. */ QuadTree.prototype.remove = function (spatial) { this._remove(spatial.id); }; /** * remove a spatial by id. * @param id - id of spatial to remove. */ QuadTree.prototype.removeById = function (id) { this._remove(id); }; /** * remove all spatials with the provided IDs. * @param removedIds - array of uuids to remove. */ QuadTree.prototype.removeByIds = function (removedIds) { var _this = this; removedIds.forEach(function (id) { return _this._remove(id); }); }; /** * remove many spatials from the quadtree. * @param spatials - spatials to remove. */ QuadTree.prototype.removeMany = function (spatials) { var _this = this; spatials.forEach(function (spatial) { return _this._remove(spatial.id); }); }; /** * update a spatial point. * @param spatial - spatial point to update. */ QuadTree.prototype.update = function (spatial) { var _a, _b; var maybe_data = this.data_cache.get(spatial.id); if ((0, onsreo_1.is_none)(maybe_data)) { // if no parents, then we only need to insert at root (_a = this.qt_cache.get(this.root)) === null || _a === void 0 ? void 0 : _a.insert(spatial); return; } var _c = __read(maybe_data, 2), _data = _c[0], parent_id = _c[1]; var maybe_parent = this.qt_cache.get(parent_id); var invalid = (0, onsreo_1.is_none)(maybe_parent) || !maybe_parent.boundary || !maybe_parent.boundary.containsPoint(spatial); // IF parent is NONE // OR has a bad boundary // OR is not contained within this boundary // THEN remove the point from its parent // AND reinsert from root // ELSE do nothing, since no change requried if (invalid) { this._remove(spatial.id); (_b = this.qt_cache.get(this.root)) === null || _b === void 0 ? void 0 : _b.insert(spatial); } }; /** * returns all spatials within the tree. * @param spatials - optional starting set [default: empty Set]. * @returns All contained spatials within the tree. */ QuadTree.prototype.getAllSpatials = function (spatials) { var _a, _b, _c, _d, _e, _f, _g, _h; if (spatials === void 0) { spatials = new Set(); } if (this.divided) { spatials = (_b = (_a = this.qt_cache.get(this.ne)) === null || _a === void 0 ? void 0 : _a.getAllSpatials(spatials)) !== null && _b !== void 0 ? _b : spatials; spatials = (_d = (_c = this.qt_cache.get(this.nw)) === null || _c === void 0 ? void 0 : _c.getAllSpatials(spatials)) !== null && _d !== void 0 ? _d : spatials; spatials = (_f = (_e = this.qt_cache.get(this.se)) === null || _e === void 0 ? void 0 : _e.getAllSpatials(spatials)) !== null && _f !== void 0 ? _f : spatials; spatials = (_h = (_g = this.qt_cache.get(this.sw)) === null || _g === void 0 ? void 0 : _g.getAllSpatials(spatials)) !== null && _h !== void 0 ? _h : spatials; } else { this.getData().forEach(function (spatial) { return spatials.add(spatial); }); } return spatials; }; /** * update the bounds of the current quadtree * @param newBounds - the bounds for the quadtree. * @param spatials - optional spatials to insert [default: all previously inserted spatials] */ QuadTree.prototype.updateBounds = function (newBounds, spatials) { var _this = this; if (spatials === void 0) { spatials = this.getAllSpatials(); } // clear structure, update bounds, and insert all the exising or // provided spatials while also clearing their parents this.reset(newBounds); spatials.forEach(function (spatial) { _this.remove(spatial); _this.insert(spatial); }); }; return QuadTree; }()); exports.QuadTree = QuadTree; //# sourceMappingURL=quadtree.js.map