0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-06 12:01:19 -05:00

🎉 Add QuadTree implementation.

This commit is contained in:
Andrey Antukh 2020-04-17 15:04:08 +02:00 committed by Alonso Torres
parent 5d6d855562
commit f7dce00c1f

View file

@ -0,0 +1,213 @@
/**
* Copyright © 2012-2020 Timo Hausmann
* Copyright © 2020 Andrey Antukh
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software. THE
* SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
**/
/**
* Changes to the original code:
* - Use ES6+.
* - Add the Node class for manage childs.
* - Use generators where is posible.
**/
"use strict";
goog.provide("uxbox.util.quadtree");
goog.scope(function() {
const self = uxbox.util.quadtree;
class Node {
constructor(bounds, data) {
this.bounds = bounds;
this.data = data;
}
}
class Quadtree {
constructor(bounds, maxObjects, maxLevels, level) {
this.maxObjects = maxObjects || 10;
this.maxLevels = maxLevels || 4;
this.level = level || 0;
this.bounds = bounds;
this.objects = [];
this.indexes = [];
}
split() {
const nextLevel = this.level + 1;
const subWidth = this.bounds.width/2;
const subHeight = this.bounds.height/2;
const x = this.bounds.x;
const y = this.bounds.y;
//top right node
this.indexes[0] = new Quadtree({
x : x + subWidth,
y : y,
width : subWidth,
height : subHeight
}, this.maxObjects, this.maxLevels, nextLevel);
//top left node
this.indexes[1] = new Quadtree({
x : x,
y : y,
width : subWidth,
height : subHeight
}, this.maxObjects, this.maxLevels, nextLevel);
//bottom left node
this.indexes[2] = new Quadtree({
x : x,
y : y + subHeight,
width : subWidth,
height : subHeight
}, this.maxObjects, this.maxLevels, nextLevel);
//bottom right node
this.indexes[3] = new Quadtree({
x : x + subWidth,
y : y + subHeight,
width : subWidth,
height : subHeight
}, this.maxObjects, this.maxLevels, nextLevel);
}
*getIndexes(rect) {
const verticalMidpoint = this.bounds.x + (this.bounds.width/2);
const horizontalMidpoint = this.bounds.y + (this.bounds.height/2);
const startIsNorth = rect.y < horizontalMidpoint;
const startIsWest = rect.x < verticalMidpoint;
const endIsEast = rect.x + rect.width > verticalMidpoint;
const endIsSouth = rect.y + rect.height > horizontalMidpoint;
//top-right quad
if (startIsNorth && endIsEast) {
yield this.indexes[0];
}
//top-left quad
if (startIsWest && startIsNorth) {
yield this.indexes[1]
}
//bottom-left quad
if (startIsWest && endIsSouth) {
yield this.indexes[2];
}
//bottom-right quad
if (endIsEast && endIsSouth) {
yield this.indexes[3];
}
}
insert(node) {
//if we have subindexes, call insert on matching subindexes
if (this.indexes.length > 0) {
for (const index of this.getIndexes(node.bounds)) {
index.insert(node);
}
} else {
//otherwise, store object here
this.objects.push(node);
// max objects reached
if (this.objects.length > this.maxObjects
&& this.level < this.maxLevels) {
//split if we don't already have subindexes
if (this.indexes.length === 0) {
this.split();
}
//add all objects to their corresponding subnode
for (const obj of this.objects) {
for (const index of this.getIndexes(obj.bounds)) {
index.insert(obj);
}
}
this.objects = [];
}
}
}
count() {
if (this.indexes.length === 0) {
return this.objects.length;
} else {
let sum = 0;
for (const index of this.indexes) {
sum += index.count();
}
return sum;
}
}
*search(rect) {
if (this.indexes.length === 0) {
yield* this.objects;
} else {
for (const index of this.getIndexes(rect)) {
yield* index.search(rect);
}
}
}
clear() {
this.objects = [];
this.indexes = [];
}
}
self.create = function(rect) {
return new Quadtree(rect, 10, 4, 0);
};
self.insert = function(index, bounds, data) {
const node = new Node(bounds, data);
index.insert(node);
return index;
};
self.clear = function(index) {
index.clear();
return index;
};
self.search = function*(index, rect) {
const tmp = new Set();
for (const item of index.search(rect)) {
if (!tmp.has(item)) {
tmp.add(item);
yield item;
}
}
};
});