UNPKG

37.9 kBPlain TextView Raw
1/*
2* Copyright (C) 1998-2021 by Northwoods Software Corporation. All Rights Reserved.
3*/
4import * as go from '../release/go-module.js';
5
6/**
7 * @hidden @internal
8 */
9class QuadNode<T> {
10 public bounds: go.Rect;
11 public parent: QuadNode<T> | null;
12 public level: number;
13 public objects: Array<T> = [];
14 public treeObjects: Array<QuadObj<T>> = [];
15 public totalObjects = 0; // total in this node + in all children (recursively)
16 public nodes: Array<QuadNode<T> | null> = [null, null, null, null];
17
18 constructor(bounds: go.Rect, parent: QuadNode<T> | null, level: number) {
19 this.bounds = bounds;
20 this.parent = parent;
21 this.level = level;
22 }
23
24 public split(): void {
25 const w2 = this.bounds.width / 2;
26 const h2 = this.bounds.height / 2;
27 const x = this.bounds.x;
28 const y = this.bounds.y;
29
30 this.nodes[0] = new QuadNode<T>(new go.Rect(x + w2, y, w2, h2), this, this.level + 1);
31 this.nodes[1] = new QuadNode<T>(new go.Rect(x, y, w2, h2), this, this.level + 1);
32 this.nodes[2] = new QuadNode<T>(new go.Rect(x, y + h2, w2, h2), this, this.level + 1);
33 this.nodes[3] = new QuadNode<T>(new go.Rect(x + w2, y + h2, w2, h2), this, this.level + 1);
34 }
35
36 public clear(): void {
37 this.treeObjects = [];
38 this.objects = [];
39 this.totalObjects = 0;
40 for (let i = 0; i < this.nodes.length; i++) {
41 const n = this.nodes[i];
42 if (n !== null) {
43 n.clear();
44 this.nodes[i] = null;
45 }
46 }
47 }
48
49}
50
51/**
52 * @internal @hidden
53 * Object to be contained by the {@link Quadtree} class. This object needs
54 * to have rectangular bounds (described by an {@link Rect} object), as well
55 * as something (of any type) associated with it.
56 */
57class QuadObj<T> {
58 public bounds: go.Rect;
59 public obj: T;
60
61 constructor(bounds: go.Rect, obj: T) {
62 this.bounds = bounds;
63 this.obj = obj;
64 }
65}
66
67/**
68 * Implementation of the quadtree data structure using the {@link Rect} class.
69 * Each Quadtree has defined bounds found at {@link #bounds}, an array
70 * of member rectangles, and an array of child nodes
71 * (Quadtrees themselves). If the Quadtree has no
72 * children, the nodes array will have four nulls. To construct a Quadtree, you
73 * can call its constructor with no arguments. Then, to insert a rectangle, call
74 * {@link #add}. This tree supports adding points (rectangles with 0
75 * width and height), segments (rectangles with either 0 width or 0 height), and
76 * rectangles with nonzero widths and heights.
77 *
78 * Quadtrees can be used to calculate intersections extremely quickly between a
79 * given rectangle and all of the rectangles in the quadtree. Use of this data
80 * structure prevents having to do precise intersection calculations for every
81 * rectangle in the tree. To calculate all of the rectangular intersections for
82 * a given rectangle, use {@link #intersecting}.
83 *
84 * Other common operations are detailed below.
85 * @category Layout Extension
86 */
87export class Quadtree<T> {
88 /** @hidden @internal */ private _root: QuadNode<T>;
89
90 /** @hidden @internal */ private readonly _nodeCapacity: number = 1;
91 /** @hidden @internal */ private readonly _maxLevels: number = Infinity;
92 /** @hidden @internal */ private _treeObjectMap: go.Map<T, QuadObj<T>> = new go.Map<T, QuadObj<T>>();
93
94 // we can avoid unnecessary work when adding objects if there are no objects with 0 width or height.
95 // Note that after being set to true, these flags are not ever set again to false, even if all objects
96 // with zero width/height are removed (assumption was made that this should almost never matter)
97 /** @hidden @internal */ private _hasZeroWidthObject = false;
98 /** @hidden @internal */ private _hasZeroHeightObject = false;
99
100 /**
101 * Gets the node capacity of this quadtree. This is the number of objects a node can contain before it splits.
102 */
103 get nodeCapacity(): number { return this._nodeCapacity; }
104 /**
105 * Gets the maximum depth the Quadtree will allow before it will no longer split..
106 */
107 get maxLevels(): number { return this._maxLevels; }
108 /**
109 * Gets the boundaries of the node. All nodes should be square.
110 */
111 get bounds(): go.Rect { return this._root.bounds; }
112 /**
113 * Gets the root node of the tree
114 */
115 get root(): QuadNode<T> { return this._root; }
116
117
118 /**
119 * In most cases, simply calling this constructor with no arguments will produce the desired behaviour.
120 * @constructor
121 * @param {number=} nodeCapacity The node capacity of this quadtree. This is the number of objects a node can contain before it splits. Defaults to 1.
122 * @param {number=} maxLevel The maximum depth the Quadtree will allow before it will no longer split. Defaults to Infinity (no maximum depth).
123 * @param {Rect=} bounds The bounding box surrounding the entire Quadtree. If the bounds are unset or a node is inserted outside of the bounds, the tree will automatically grow.
124 */
125 constructor(nodeCapacity?: number, maxLevel?: number, bounds?: go.Rect) {
126 if (nodeCapacity) {
127 this._nodeCapacity = nodeCapacity;
128 }
129 if (maxLevel) {
130 this._maxLevels = maxLevel;
131 }
132 if (bounds === undefined) {
133 bounds = new go.Rect();
134 }
135
136 this._root = new QuadNode<T>(bounds, null, 0);
137 }
138
139 /**
140 * Clears the Quadtree, removing all objects and children nodes. Keeps the current bounds of the root node.
141 * @this {Quadtree}
142 * @return {void}
143 */
144 public clear(): void {
145 this._root.clear();
146 this._treeObjectMap.clear();
147 }
148
149 /**
150 * @hidden @internal
151 * Returns a list of possible quadrants that the given rect could be in
152 * @this {Quadtree}
153 * @param {Rect} rect the rectangle to test
154 * @return {Array<number>}
155 */
156 private _getQuadrants(rect: go.Rect, node: QuadNode<T>): Array<number> {
157 const quadrants: Array<number> = [];
158 const horizontalMidpoint = node.bounds.x + (node.bounds.width / 2);
159 const verticalMidpoint = node.bounds.y + (node.bounds.height / 2);
160
161 const topQuadrant = rect.y <= verticalMidpoint;
162 const bottomQuadrant = rect.y + rect.height >= verticalMidpoint;
163
164 if (rect.x <= horizontalMidpoint) {
165 if (topQuadrant) {
166 quadrants.push(1);
167 }
168 if (bottomQuadrant) {
169 quadrants.push(2);
170 }
171 }
172 if (rect.x + rect.width >= horizontalMidpoint) {
173 if (topQuadrant) {
174 quadrants.push(0);
175 }
176 if (bottomQuadrant) {
177 quadrants.push(3);
178 }
179 }
180 return quadrants;
181 }
182
183 /**
184 * @hidden @internal
185 * Determine which node the rect belongs to. -1 means rect
186 * cannot completely fit within a child node and is part of
187 * the parent node. This function avoids some additional
188 * calculations by assuming that the rect is contained entirely
189 * within the parent node's bounds.
190 * @this {Quadtree}
191 * @param {Rect} rect the rect to test
192 * @return {number}
193 */
194 private _getIndex(rect: go.Rect, node: QuadNode<T>): number {
195 let index = -1;
196 if (node.bounds === undefined) { // the quadtree is empty
197 return index;
198 }
199
200 const horizontalMidpoint = node.bounds.x + (node.bounds.width / 2);
201 const verticalMidpoint = node.bounds.y + (node.bounds.height / 2);
202
203 const topQuadrant = rect.y <= verticalMidpoint && rect.y + rect.height <= verticalMidpoint;
204 const bottomQuadrant = rect.y >= verticalMidpoint;
205
206 if (rect.x + rect.width <= horizontalMidpoint) {
207 if (topQuadrant) {
208 index = 1;
209 } else if (bottomQuadrant) {
210 index = 2;
211 }
212 } else if (rect.x >= horizontalMidpoint) {
213 if (topQuadrant) {
214 index = 0;
215 } else if (bottomQuadrant) {
216 index = 3;
217 }
218 }
219
220 return index;
221 }
222
223 /**
224 * Insert the object into the quadtree. If the node
225 * exceeds the capacity, it will split and add all
226 * objects to their corresponding nodes. If the object is
227 * outside the bounds of the tree's root node, the tree
228 * will grow to accomodate it. Possibly restructures the
229 * tree if a more efficient configuration can be found with
230 * the new dimensions. Bounds can be given either as a
231 * single {@link Rect} or as any combination of arguments
232 * which is valid for the {@link Rect} constructor.
233 * @this {Quadtree}
234 * @param {T} obj the object to insert
235 * @param {Rect|Point|number} x The Rect bounds of the object, or top-left Point, or x value.
236 * @param {Point|Size|number} y Bottom-right Point or Size or y value.
237 * @param {number} w Width to be used if x,y are specified;
238 * must be non-negative.
239 * @param {number} h Height to be used if x,y are specified;
240 * @return {void}
241 */
242 public add(obj: T | QuadObj<T>, x?: go.Rect | go.Point | number, y?: go.Point | go.Size | number, w?: number, h?: number): void {
243 let bounds: go.Rect;
244 if (!(obj instanceof QuadObj) && (x === undefined || x === null)) {
245 throw new Error('Invalid bounds for added object');
246 }
247 if (x instanceof go.Rect) {
248 bounds = x.copy();
249 } else {
250 bounds = new go.Rect(x, y, w, h);
251 }
252
253 let treeObj: QuadObj<T>;
254 if (obj instanceof QuadObj) {
255 treeObj = obj;
256 obj = treeObj.obj;
257 bounds = treeObj.bounds;
258 } else {
259 treeObj = new QuadObj<T>(bounds, obj);
260 }
261
262 if (isNaN(bounds.x) || bounds.x === Infinity ||
263 isNaN(bounds.y) || bounds.y === Infinity ||
264 isNaN(bounds.width) || bounds.width === Infinity ||
265 isNaN(bounds.height) || bounds.height === Infinity) {
266 throw new Error('Invalid rectangle, contains NaN or Infinity properties');
267 }
268
269 this._hasZeroWidthObject = this._hasZeroWidthObject || bounds.width === 0;
270 this._hasZeroHeightObject = this._hasZeroHeightObject || bounds.height === 0;
271
272 // initialize bounds of tree as the max width or height of the first object added
273 if (this._root.bounds.width === 0 || this._root.bounds.height === 0) {
274 const len = Math.max(bounds.width, bounds.height);
275 this._root.bounds = new go.Rect(bounds.x, bounds.y, len, len);
276 }
277
278 // fixes quadtree having a width and height of 0 if the first object added is a point
279 // this will only be called after a second object is added, the new width/height is the maximum distance between them
280 if (this._root.bounds !== undefined && (this._root.bounds.width === 0 || this._root.bounds.height === 0)) {
281 const len = Math.max(Math.abs(bounds.x - this._root.bounds.x), Math.abs(bounds.y - this._root.bounds.y));
282 this._root.bounds = new go.Rect(Math.min(this._root.bounds.x, bounds.x), Math.min(this._root.bounds.y, bounds.y), len, len);
283 }
284
285 // map the object to its corresponding QuadObj (so that the bounds of this object can be retrieved later)
286 this._treeObjectMap.add(obj, treeObj);
287
288 // grow as many times as necessary to fit the new object
289 while (!this._root.bounds.containsRect(bounds)) {
290 const old = this._root;
291 this.walk(n => n.level++, old);
292
293 const intersectsTopBound = bounds.y < this._root.bounds.y;
294 const intersectsBottomBound = bounds.y + bounds.height > this._root.bounds.y + this._root.bounds.height;
295 const intersectsRightBound = bounds.x + bounds.width > this._root.bounds.x + this._root.bounds.width;
296 const intersectsLeftBound = bounds.x < this._root.bounds.x;
297
298 if ((intersectsTopBound && intersectsRightBound) || (intersectsTopBound && !intersectsLeftBound)) {
299 /* _______
300 * | 1 | 0 |
301 * |___|___|
302 * |old| 3 |
303 * |___|___|
304 */
305 const newBounds = new go.Rect(this._root.bounds.x,
306 this._root.bounds.y - this._root.bounds.height,
307 this._root.bounds.width * 2,
308 this._root.bounds.height * 2);
309 this._root = new QuadNode<T>(newBounds, null, 0);
310 this._root.split();
311 this._root.nodes[2] = old;
312 this._root.totalObjects = old.totalObjects;
313 old.parent = this._root;
314 this._restructure(old);
315 this._restructureLevels(old);
316 if (this._hasZeroHeightObject) {
317 this._fixTopObjectPlacement(old);
318 }
319 } else if (intersectsTopBound && intersectsLeftBound) {
320 /* _______
321 * | 1 | 0 |
322 * |___|___|
323 * | 2 |old|
324 * |___|___|
325 */
326 const newBounds = new go.Rect(this._root.bounds.x - this._root.bounds.width,
327 this._root.bounds.y - this._root.bounds.height,
328 this._root.bounds.width * 2,
329 this._root.bounds.height * 2);
330 this._root = new QuadNode<T>(newBounds, null, 0);
331 this._root.split();
332 this._root.nodes[3] = old;
333 this._root.totalObjects = old.totalObjects;
334 old.parent = this._root;
335 this._restructure(old);
336 this._restructureLevels(old);
337 if (this._hasZeroWidthObject) {
338 this._fixLeftObjectPlacement(old);
339 }
340 if (this._hasZeroHeightObject) {
341 this._fixTopObjectPlacement(old);
342 }
343 } else if ((intersectsBottomBound && intersectsRightBound) || ((intersectsRightBound || intersectsBottomBound) && !intersectsLeftBound)) {
344 /* _______
345 * |old| 0 |
346 * |___|___|
347 * | 2 | 3 |
348 * |___|___|
349 */
350 const newBounds = new go.Rect(this._root.bounds.x,
351 this._root.bounds.y,
352 this._root.bounds.width * 2,
353 this._root.bounds.height * 2);
354 this._root = new QuadNode<T>(newBounds, null, 0);
355 this._root.split();
356 this._root.nodes[1] = old;
357 this._root.totalObjects = old.totalObjects;
358 old.parent = this._root;
359 this._restructure(old);
360 this._restructureLevels(old);
361 } else if ((intersectsBottomBound && intersectsLeftBound) || intersectsLeftBound) {
362 /* _______
363 * | 1 |old|
364 * |___|___|
365 * | 2 | 3 |
366 * |___|___|
367 */
368 const newBounds = new go.Rect(this._root.bounds.x - this._root.bounds.width,
369 this._root.bounds.y,
370 this._root.bounds.width * 2,
371 this._root.bounds.height * 2);
372 this._root = new QuadNode<T>(newBounds, null, 0);
373 this._root.split();
374 this._root.nodes[0] = old;
375 this._root.totalObjects = old.totalObjects;
376 old.parent = this._root;
377 this._restructure(old);
378 this._restructureLevels(old);
379 if (this._hasZeroWidthObject) {
380 this._fixLeftObjectPlacement(old);
381 }
382 }
383 }
384
385 // add the object to the tree
386 this._addHelper(this._root, treeObj);
387 }
388
389 /**
390 * @hidden @internal
391 * Helper function to recursively perform the add operation
392 * on the tree.
393 * @this {Quadtree}
394 * @param {QuadNode<T>} root the current node being operated on
395 * @param {QuadObj<T>} treeObj the object being added
396 * @return {void}
397 */
398 private _addHelper(root: QuadNode<T>, treeObj: QuadObj<T>): void {
399 root.totalObjects++;
400
401 if (root.nodes[0]) {
402 const index = this._getIndex(treeObj.bounds, root);
403 if (index !== -1) {
404 const selected = root.nodes[index];
405 if (selected !== null) {
406 this._addHelper(selected, treeObj);
407 return;
408 }
409 }
410 }
411
412 root.treeObjects.push(treeObj);
413 root.objects.push(treeObj.obj);
414 if (root.treeObjects.length > this._nodeCapacity && root.level < this._maxLevels) {
415 if (!root.nodes[0]) {
416 root.split();
417 }
418
419 let i = 0;
420 while (i < root.treeObjects.length) {
421 const index = this._getIndex(root.treeObjects[i].bounds, root);
422 if (index !== -1 && !(root.treeObjects[i].bounds.width === 0 || root.treeObjects[i].bounds.height === 0)) {
423 root.objects.splice(i, 1);
424 const selected = root.nodes[index];
425 if (selected !== null) {
426 this._addHelper(selected, root.treeObjects.splice(i, 1)[0]);
427 }
428 } else {
429 i++;
430 }
431 }
432 }
433 }
434
435 /**
436 * @hidden @internal
437 * Recursively moves objects placed on the right side of a vertical border
438 * between two nodes to the left side of the vertical border. This allows
439 * them to be located by {@link #_getIndex}. This function is called
440 * after an {@link #add} call grows the Quadtree, but only if there
441 * are 0 width objects in the tree.
442 * @this {Quadtree}
443 * @param {QuadNode<T>} root the current root node being operated on
444 * @return {void}
445 */
446 private _fixLeftObjectPlacement(root: QuadNode<T>): void {
447 const nw = root.nodes[1];
448 if (nw !== null) { // if root is split
449 this._fixLeftObjectPlacement(nw); // NW
450 const sw = root.nodes[2];
451 if (sw !== null) {
452 this._fixLeftObjectPlacement(sw); // SW
453 }
454 }
455
456 const toRemove: Array<number> = [];
457 const toAdd: Array<QuadObj<T>> = [];
458 for (let i = 0; i < root.objects.length; i++) {
459 const obj = root.treeObjects[i];
460 if (obj.bounds.width === 0 && obj.bounds.x === root.bounds.x) {
461 toRemove.push(i);
462 toAdd.push(obj);
463 }
464 }
465 this._removeFromOwner(root, toRemove);
466 for (const obj of toAdd) {
467 this.add(obj.obj, obj.bounds);
468 }
469 }
470
471 /**
472 * @hidden @internal
473 * Recursively moves objects placed on the bottom side of a horizontal border
474 * between two nodes to the top side of the vertical border. This allows
475 * them to be located by {@link #_getIndex}. This function is called
476 * after an {@link #add} call grows the Quadtree, but only if there
477 * are 0 height objects in the tree.
478 * @this {Quadtree}
479 * @param {QuadNode<T>} root the current root node being operated on
480 * @return {void}
481 */
482 private _fixTopObjectPlacement(root: QuadNode<T>): void {
483 const ne = root.nodes[0];
484 if (ne !== null) { // if root is split
485 this._fixTopObjectPlacement(ne); // NE
486 const nw = root.nodes[1];
487 if (nw !== null) {
488 this._fixTopObjectPlacement(nw); // NW
489 }
490 }
491
492 const toRemove: Array<number> = [];
493 const toAdd: Array<QuadObj<T>> = [];
494 for (let i = 0; i < root.objects.length; i++) {
495 const obj = root.treeObjects[i];
496 if (obj.bounds.height === 0 && obj.bounds.y === root.bounds.y) {
497 toRemove.push(i);
498 toAdd.push(obj);
499 }
500 }
501 this._removeFromOwner(root, toRemove);
502 for (const obj of toAdd) {
503 this.add(obj);
504 }
505 }
506
507 /**
508 * @hidden @internal
509 * Moves all objects from a leaf node to its parent and unsplits.
510 * Used after growing the tree when level>max level.
511 * @this {Quadtree}
512 * @param {QuadNode<T>} node the leaf node to restructure
513 * @return {void}
514 */
515 private _restructureLevels(node: QuadNode<T>): void {
516 if (node && this._maxLevels < Infinity && node.nodes[0] !== null) {
517 if (node.level >= this._maxLevels) {
518 for (let i = 0; i < node.nodes.length; i++) {
519 const selected = node.nodes[i];
520 if (selected !== null) {
521 node.objects.push.apply(node.objects, selected.objects);
522 node.treeObjects.push.apply(node.treeObjects, selected.treeObjects);
523 selected.clear();
524 node.nodes[i] = null;
525 }
526 }
527 } else {
528 for (let i = 0; i < node.nodes.length; i++) {
529 const selected = node.nodes[i];
530 if (selected !== null) {
531 this._restructureLevels(selected);
532 }
533 }
534 }
535 }
536 }
537
538 /**
539 * Return the node that contains the given object.
540 * @this {Quadtree}
541 * @param {T} obj the object to find
542 * @return {QuadNode<T>} the node containing the given object, null if the object is not found
543 */
544 public find(obj: T): QuadNode<T> | null {
545 const treeObj = this._treeObjectMap.get(obj);
546 if (treeObj) {
547 return this._findHelper(this._root, treeObj);
548 }
549
550 return null;
551 }
552
553 private _findHelper(root: QuadNode<T>, treeObj: QuadObj<T>): QuadNode<T> | null {
554 for (const object of root.treeObjects) {
555 if (object === treeObj) {
556 return root;
557 }
558 }
559
560 const index = this._getIndex(treeObj.bounds, root);
561 const selected = index === -1 ? null : root.nodes[index];
562 if (selected !== null) {
563 const result = this._findHelper(selected, treeObj);
564 if (result) {
565 return result;
566 }
567 }
568
569 return null;
570 }
571
572 /**
573 * Convenience method, calls {@link #find} and returns a boolean
574 * indicating whether or not the tree contains the given object
575 * @this {Quadtree}
576 * @param {T} obj the object to check for
577 * @return {boolean} whether or not the given object is present in the tree
578 */
579 public has(obj: T): boolean {
580 return !!this.find(obj);
581 }
582
583 /**
584 * Checks if any of the objects in the tree have the given boundaries
585 * @this {Quadtree}
586 * @param {Rect} bounds the rectangle to check for
587 * @return {Rect} the actual bounds object stored in the tree
588 */
589 public findBounds(bounds: go.Rect): go.Rect | null {
590 if (bounds) {
591 return this._findBoundsHelper(this._root, bounds);
592 }
593
594 return null;
595 }
596
597 private _findBoundsHelper(root: QuadNode<T>, bounds: go.Rect): go.Rect | null {
598 for (const object of root.treeObjects) {
599 if (bounds.equalsApprox(object.bounds)) {
600 return bounds;
601 }
602 }
603
604 const index = this._getIndex(bounds, root);
605 const selected = index === -1 ? null : root.nodes[index];
606 if (selected !== null) {
607 return this._findBoundsHelper(selected, bounds);
608 }
609
610 return null;
611 }
612
613 /**
614 * Remove the given object from the tree, restructuring to
615 * get rid of empty nodes that are unneeded.
616 * @this {Quadtree}
617 * @param {T} obj the object to remove
618 * @return {boolean} whether or not the deletion was successful. False when the object is not in the tree.
619 */
620 public remove(obj: T): boolean {
621 const treeObj = this._treeObjectMap.get(obj);
622 if (treeObj) {
623 const owner = this._findHelper(this._root, treeObj);
624
625 if (owner) {
626 owner.treeObjects.splice(owner.treeObjects.indexOf(treeObj), 1);
627 owner.objects.splice(owner.objects.indexOf(obj), 1);
628 owner.totalObjects--;
629 this._treeObjectMap.remove(obj);
630 let parent = owner.parent;
631 while (parent) {
632 parent.totalObjects--;
633 parent = parent.parent;
634 }
635 if (owner.nodes[0] && owner.totalObjects <= this._nodeCapacity) {
636 this._addChildObjectsToNode(owner, owner);
637 for (let i = 0; i < owner.nodes.length; i++) {
638 const selected = owner.nodes[i];
639 if (selected !== null) {
640 selected.clear();
641 }
642 owner.nodes[i] = null;
643 }
644 }
645 this._restructure(owner);
646 return true;
647 }
648 }
649
650 return false;
651 }
652
653 /**
654 * Removes multiple objects at the given indices from the given owner. Similar
655 * to the normal remove function, but much faster when the owner and indices are
656 * already known.
657 * @this {Quadtree}
658 * @param {QuadNode<T>} owner the node to remove objects from
659 * @param {Array<number>} indexes the indices to remove. Should be given in ascending order.
660 */
661 private _removeFromOwner(owner: QuadNode<T>, indexes: Array<number>): void {
662 if (indexes.length === 0) {
663 return;
664 }
665
666 for (let i = indexes.length - 1; i >= 0; i--) {
667 this._treeObjectMap.remove(owner.objects[indexes[i]]);
668 owner.treeObjects.splice(indexes[i], 1);
669 owner.objects.splice(indexes[i], 1);
670 }
671
672 owner.totalObjects -= indexes.length;
673 let parent = owner.parent;
674 while (parent) {
675 parent.totalObjects -= indexes.length;
676 parent = parent.parent;
677 }
678 if (owner.nodes[0] && owner.totalObjects <= this._nodeCapacity) {
679 this._addChildObjectsToNode(owner, owner);
680 for (let i = 0; i < owner.nodes.length; i++) {
681 const selected = owner.nodes[i];
682 if (selected !== null) {
683 selected.clear();
684 }
685 owner.nodes[i] = null;
686 }
687 }
688 this._restructure(owner);
689 }
690
691 /**
692 * @hidden @internal
693 * Recursively adds all objects from children of the given
694 * root tree to the given owner tree
695 * Used internally by {@link #remove}
696 * @this {Quadtree}
697 * @param {Quadtree} owner the tree to add objects to
698 * @return {void}
699 */
700 private _addChildObjectsToNode(owner: QuadNode<T>, root: QuadNode<T>): void {
701 for (const node of root.nodes) {
702 if (node) {
703 owner.treeObjects.push.apply(owner.treeObjects, node.treeObjects);
704 owner.objects.push.apply(owner.objects, node.objects);
705 this._addChildObjectsToNode(owner, node);
706 }
707 }
708 }
709
710 /**
711 * @hidden @internal
712 * Recursively combines parent nodes that should be split, all the way
713 * up the tree. Starts from the given node.
714 * @this {Quadtree}
715 * @return {void}
716 */
717 private _restructure(root: QuadNode<T>): void {
718 const parent = root.parent;
719 if (parent) {
720 // if none of the child nodes have any objects, the parent should not be split
721 let childrenHaveNoObjects = true;
722 for (const node of parent.nodes) {
723 if (node !== null && node.totalObjects > 0) {
724 childrenHaveNoObjects = false;
725 break;
726 }
727 }
728
729 // unsplit children and move nodes to parent
730 if (parent.totalObjects <= this._nodeCapacity || childrenHaveNoObjects) {
731 for (let i = 0; i < parent.nodes.length; i++) {
732 const selected = parent.nodes[i];
733 if (selected !== null) {
734 parent.objects.push.apply(parent.objects, selected.objects);
735 parent.treeObjects.push.apply(parent.treeObjects, selected.treeObjects);
736 selected.clear();
737 parent.nodes[i] = null;
738 }
739 }
740 this._restructure(parent);
741 }
742
743 }
744 }
745
746 /**
747 * Can be called as either (obj, x, y) or (obj, point). Translate
748 * the given object to given x and y coordinates or to a given {@link Point}.
749 * @this {Quadtree}
750 * @param {T} obj the object to move
751 * @param {number|Point} x the x coordinate or Point to move the object to
752 * @param {number} y the y coordinate to move the object to
753 * @return {boolean} whether or not the move was successful. False if the object was not in the tree.
754 */
755 public move(obj: T, x: number | go.Point, y?: number): boolean {
756 const treeObj = this._treeObjectMap.get(obj);
757 if (treeObj && this.remove(obj)) {
758 if (x instanceof go.Point) {
759 treeObj.bounds.x = x.x;
760 treeObj.bounds.y = x.y;
761 } else if (y !== undefined) {
762 treeObj.bounds.x = x;
763 treeObj.bounds.y = y;
764 } else {
765 throw new Error('Please provide the position as either a Point or two numbers');
766 }
767 this.add(treeObj);
768 return true;
769 }
770 return false;
771 }
772
773 /**
774 * Can be called as either (obj, width, height) or (obj, size). Resize
775 * the given object to given width and height or to a given {@link Size}.
776 * @this {Quadtree}
777 * @param {T} obj the object to resize
778 * @param {number|Size} width the width or Size to resize the object to
779 * @param {number} height the height to resize the object to
780 * @return {boolean} whether or not the resize was successful. False if the object was not in the tree.
781 */
782 public resize(obj: T, width: number | go.Size, height?: number): boolean {
783 const treeObj = this._treeObjectMap.get(obj);
784 if (treeObj && this.remove(obj)) {
785 if (width instanceof go.Size) {
786 treeObj.bounds.width = width.width;
787 treeObj.bounds.height = width.height;
788 } else if (height !== undefined) {
789 treeObj.bounds.width = width;
790 treeObj.bounds.height = height;
791 } else {
792 throw new Error('Please provide the size as either a Size or two numbers');
793 }
794 this.add(treeObj);
795 return true;
796 }
797 return false;
798 }
799
800 /**
801 * Updates the given object to have the bounds given, provided as either a
802 * {@link Rect} or x, y, width, and height.
803 * @this {Quadtree}
804 * @param obj the object to change the bounds of
805 * @param x the x-coordinate or Rect to set the object to
806 * @param y the y-coordinate to set the object to, unnecessary if a Rect was given
807 * @param width the width to set the object to, unnecessary if a Rect was given
808 * @param height the height to set the object to, unnecessary if a Rect was given
809 */
810 public setTo(obj: T, x: number | go.Rect, y?: number, width?: number, height?: number): boolean {
811 const treeObj = this._treeObjectMap.get(obj);
812 if (treeObj && this.remove(obj)) {
813 if (x instanceof go.Rect) {
814 treeObj.bounds.set(x);
815 } else if (y !== undefined && width !== undefined && height !== undefined) {
816 treeObj.bounds.setTo(x, y, width, height);
817 } else {
818 throw new Error('Please provide new bounds as either a Rect or combination of four numbers (x, y, width, height)');
819 }
820 this.add(treeObj);
821 return true;
822 }
823 return false;
824 }
825
826 /**
827 * Return all objects that intersect (wholly or partially) with
828 * the given {@link Rect} or {@link Point}. Touching edges and
829 * objects overlapping by 1e-7 or less (to account for floating
830 * point error) are both not considered intersections.
831 * @this {Quadtree}
832 * @param {Rect|Point} rect the Rect or Point to check intersections for. If a point is given, a Rect with size (0, 0) is created for intersection calculations.
833 * @return {Array<T>} array containing all intersecting objects
834 */
835 public intersecting(rect: go.Rect | go.Point): Array<T> {
836 if (rect instanceof go.Point) {
837 rect = new go.Rect(rect.x, rect.y, 0, 0);
838 }
839 const returnObjects: Array<T> = [];
840 this._intersectingHelper(rect, this._root, returnObjects);
841 return returnObjects;
842 }
843
844 private _intersectingHelper(rect: go.Rect, root: QuadNode<T>, returnObjects: Array<T>) {
845 const index = this._getIndex(rect, root);
846 const selected = index === -1 ? null : root.nodes[index];
847 if (selected !== null) {
848 this._intersectingHelper(rect, selected, returnObjects);
849 } else if (root.nodes[0] !== null) {
850 const quadrants = this._getQuadrants(rect, root);
851 for (const quadrant of quadrants) {
852 const node = root.nodes[quadrant];
853 if (node !== null) {
854 this._intersectingHelper(rect, node, returnObjects);
855 }
856 }
857 }
858
859 for (const obj of root.treeObjects) {
860 if (Quadtree._rectsIntersect(obj.bounds, rect)) {
861 returnObjects.push(obj.obj);
862 }
863 }
864 }
865
866 /**
867 * @hidden @internal
868 * Similar as {@link Rect.intersectsRect}, but doesn't count edges as intersections.
869 * Also accounts for floating error (by returning false more often) up to an error of 1e-7.
870 * Used by {@link #intersecting}.
871 * @this {Quadtree}
872 * @param {Rect} r1 first rectangle
873 * @param {Rect} r2 second rectangle
874 * @return {boolean} whether or not the two rectangles intersect
875 */
876 private static _rectsIntersect(r1: go.Rect, r2: go.Rect): boolean {
877 return !(r2.left + 1e-7 >= r1.right || r2.right - 1e-7 <= r1.left || r2.top + 1e-7 >= r1.bottom || r2.bottom - 1e-7 <= r1.top);
878 }
879
880 /**
881 * Return all objects that fully contain the given {@link Rect} or {@link Point}.
882 * @this {Quadtree}
883 * @param {Rect|Point} rect the Rect or Point to check containing for. If a point is given, a Rect with size (0, 0) is created for containment calculations.
884 * @return {Array<T>} array containing all containing objects
885 */
886 public containing(rect: go.Rect | go.Point): Array<T> {
887 if (rect instanceof go.Point) {
888 rect = new go.Rect(rect.x, rect.y, 0, 0);
889 }
890 const returnObjects: Array<T> = [];
891 this._containingHelper(rect, this._root, returnObjects);
892 return returnObjects;
893 }
894
895 private _containingHelper(rect: go.Rect, root: QuadNode<T>, returnObjects: Array<T>) {
896 const index = this._getIndex(rect, root);
897 const selected = index === -1 ? null : root.nodes[index];
898 if (selected !== null) {
899 this._containingHelper(rect, selected, returnObjects);
900 } else if (root.nodes[0]) {
901 const quadrants = this._getQuadrants(rect, root);
902 for (const quadrant of quadrants) {
903 const node = root.nodes[quadrant];
904 if (node !== null) {
905 this._containingHelper(rect, node, returnObjects);
906 }
907 }
908 }
909
910 for (const obj of root.treeObjects) {
911 if (obj.bounds.containsRect(rect)) {
912 returnObjects.push(obj.obj);
913 }
914 }
915 }
916
917 /**
918 * Returns the square of the distance from the centers of the given objects
919 * @this {Quadtree}
920 * @param {T} obj1
921 * @param {T} obj2
922 * @return {number} square of the distance between the centers of obj1 and obj2
923 */
924 public distanceSquared(obj1: T, obj2: T): number {
925 const owner1 = this.find(obj1);
926 const owner2 = this.find(obj2);
927 if (owner1 !== null && owner2 !== null) {
928 const treeObj1 = this._treeObjectMap.get(obj1);
929 const treeObj2 = this._treeObjectMap.get(obj2);
930 if (treeObj1 !== null && treeObj2 !== null) {
931 return treeObj1.bounds.center.distanceSquaredPoint(treeObj2.bounds.center);
932 }
933 }
934 return -1;
935 }
936
937 /**
938 * Recursively traverses the tree (depth first) and executes the
939 * given callback on each node.
940 * @this {Quadtree}
941 * @param {function} callback the callback to execute on each node. Takes the form of (n: Quadtree) => void
942 * @param {boolean} root whether or not to execute the callback on the root node as well. Defaults to true
943 * @return {void}
944 */
945 public walk(callback: (n: QuadNode<T>) => void, node: QuadNode<T> = this._root, root: boolean = true): void {
946 if (root) {
947 root = false;
948 callback(node);
949 }
950 for (const n of node.nodes) {
951 if (n) {
952 callback(n);
953 this.walk(callback, n, root);
954 }
955 }
956 }
957
958 /**
959 * Visits every object stored in the tree (depth first)
960 * @this {Quadtree}
961 * @param {function} callback the callback to execute on each object.
962 * @return {void}
963 */
964 public forEach(callback: (obj: T) => void): void {
965 this.walk((n) => {
966 for (const obj of n.objects) {
967 callback(obj);
968 }
969 });
970 }
971
972 /**
973 * Finds the most furthest object in each direction stored in the tree.
974 * Bounds are tested using the center x and y coordinate.
975 * @this {Quadtree}
976 * @return {Array<T>} maximum and minimum objects in the tree, in the format [min x, max x, min y, max y].
977 */
978 public findExtremeObjects(): [T | null, T | null, T | null, T | null] {
979 const [extremes0, extremes1, extremes2, extremes3] = this._findExtremeObjectsHelper();
980 return [
981 extremes0 !== null ? extremes0.obj : null,
982 extremes1 !== null ? extremes1.obj : null,
983 extremes2 !== null ? extremes2.obj : null,
984 extremes3 !== null ? extremes3.obj : null
985 ];
986 }
987
988 /**
989 * @hidden @internal
990 * Recursive helper function for {@link #findExtremeObjects}
991 * @this {Quadtree}
992 * @param {QuadNode<T>} root the current root node being searched
993 * @return {Array<QuadObj<T>>} maximum and minimum objects in the tree, in the format [min x, max x, min y, max y].
994 */
995 private _findExtremeObjectsHelper(root = this._root): [QuadObj<T> | null, QuadObj<T> | null, QuadObj<T> | null, QuadObj<T> | null] {
996 let minX: QuadObj<T> | null = null;
997 let maxX: QuadObj<T> | null = null;
998 let minY: QuadObj<T> | null = null;
999 let maxY: QuadObj<T> | null = null;
1000 if (root.nodes[0]) { // if root is split
1001 for (const node of root.nodes) {
1002 if (node !== null) {
1003 const [extremes0, extremes1, extremes2, extremes3] = this._findExtremeObjectsHelper(node);
1004 if (minX == null || (extremes0 !== null && extremes0.bounds.centerX < minX.bounds.centerX)) {
1005 minX = extremes0;
1006 }
1007 if (maxX === null || (extremes1 !== null && extremes1.bounds.centerX > maxX.bounds.centerX)) {
1008 maxX = extremes1;
1009 }
1010 if (minY === null || (extremes2 !== null && extremes2.bounds.centerY < minY.bounds.centerY)) {
1011 minY = extremes2;
1012 }
1013 if (maxY === null || (extremes3 !== null && extremes3.bounds.centerY > maxY.bounds.centerY)) {
1014 maxY = extremes3;
1015 }
1016 }
1017 }
1018 }
1019
1020 for (const obj of root.treeObjects) {
1021 if (!minX || obj.bounds.centerX < minX.bounds.centerX) {
1022 minX = obj;
1023 }
1024 if (!maxX || obj.bounds.centerX > maxX.bounds.centerX) {
1025 maxX = obj;
1026 }
1027 if (!minY || obj.bounds.centerY < minY.bounds.centerY) {
1028 minY = obj;
1029 }
1030 if (!maxY || obj.bounds.centerY > maxY.bounds.centerY) {
1031 maxY = obj;
1032 }
1033 }
1034
1035 return [minX, maxX, minY, maxY];
1036 }
1037
1038}