@stringsync/vexml
Version:
MusicXML to Vexflow
151 lines (150 loc) • 5.48 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuadTree = void 0;
const collision_1 = require("./collision");
/**
* Represents a QuadTree data structure that can store shapes in a 2D space.
* @link https://en.wikipedia.org/wiki/Quadtree
*/
class QuadTree {
boundary;
threshold;
entries;
northeast;
northwest;
southeast;
southwest;
/**
* @param boundary The boundary of the QuadTree.
* @param threshold The number of entries in a node before it subdivides. A tree's entry size can exceed this.
*/
constructor(boundary, threshold) {
this.boundary = boundary;
this.threshold = threshold;
this.entries = [];
}
/**
* Inserts a shape into the QuadTree.
* @returns true if the point was successfully inserted, false otherwise.
*/
insert(shape, data) {
// If the shape is not completely surrounded by the boundary, do not insert it. We want to make sure that when we
// query a quad tree, we are able to find the shape.
if (!collision_1.Collision.is(shape).surrounding(this.boundary)) {
return false;
}
if (this.entries.length < this.threshold) {
this.entries.push({ shape, data });
return true;
}
if (!this.isDivided()) {
this.subdivide();
}
// Quadrants are assumed to have no overlap, so we only need to try to fit it in one of them.
if (this.northeast.insert(shape, data)) {
return true;
}
if (this.northwest.insert(shape, data)) {
return true;
}
if (this.southeast.insert(shape, data)) {
return true;
}
if (this.southwest.insert(shape, data)) {
return true;
}
// If the bounding box cannot be completely surrounded by any quadrant, store it in the current tree. This may cause
// the threshold to be exceeded, but this is expected to be rare. We expect that music sheets are O(1000) shapes in
// the most extreme cases and that the spatial distribution of the shapes should be mostly uniform.
this.entries.push({ shape, data });
return true;
}
/** Given a point, returns all the shapes that contain it. */
query(point) {
const found = new Array();
const dfs = (tree) => {
for (const { shape, data } of tree.entries) {
if (shape.contains(point)) {
found.push(data);
}
}
if (tree.isDivided()) {
dfs(tree.northeast);
dfs(tree.northwest);
dfs(tree.southwest);
dfs(tree.southeast);
}
};
dfs(this);
return found;
}
/**
* Returns the max number of entries in any tree node in the quad tree.
*
* Callers can use this to approximate the balance of the quad tree. If the tree is relatively balanced, then the max
* number of entries in any node should be less than or equal to the threshold. If the tree is unbalanced, then the
* max number of entries in any node will be much greater than the threshold.
*
* Balance of the tree indicates its performance. For example, if you insert N points, but the maxTreeEntryCount is N,
* that suggests the query performance will be O(N). At this extreme, the caller should consider a different threshold
* or a different data structure.
*/
getMaxTreeEntryCount() {
let max = this.entries.length;
const dfs = (tree) => {
max = Math.max(max, tree.entries.length);
if (tree.isDivided()) {
dfs(tree.northeast);
dfs(tree.northwest);
dfs(tree.southwest);
dfs(tree.southeast);
}
};
dfs(this);
return max;
}
/** Returns the boundaries of all the nodes. */
getBoundaries() {
const shapes = new Array();
const dfs = (tree) => {
shapes.push(tree.boundary);
if (tree.isDivided()) {
dfs(tree.northeast);
dfs(tree.northwest);
dfs(tree.southwest);
dfs(tree.southeast);
}
};
dfs(this);
return shapes;
}
/** Returns all the entries of all the nodes. */
getEntries() {
const entries = new Set();
const dfs = (tree) => {
for (const { data } of tree.entries) {
entries.add(data);
}
if (tree.isDivided()) {
dfs(tree.northeast);
dfs(tree.northwest);
dfs(tree.southwest);
dfs(tree.southeast);
}
};
dfs(this);
return Array.from(entries);
}
isDivided() {
// Assume that if northeast is defined, then all children are defined.
return typeof this.northeast !== 'undefined';
}
subdivide() {
const [northeast, northwest, southwest, southeast] = this.boundary.quadrants();
this.northeast = new QuadTree(northeast, this.threshold);
this.northwest = new QuadTree(northwest, this.threshold);
this.southwest = new QuadTree(southwest, this.threshold);
this.southeast = new QuadTree(southeast, this.threshold);
}
}
exports.QuadTree = QuadTree;