mirror of
https://github.com/penpot/penpot.git
synced 2025-05-25 19:56:11 +02:00
246 lines
6.3 KiB
JavaScript
246 lines
6.3 KiB
JavaScript
/**
|
|
* Copyright (c) 2020 Andrey Antukh <niwi@niwi.nz>
|
|
* Copyright (c) 2012-2020 Timo Hausmann
|
|
*
|
|
* 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 possible.
|
|
**/
|
|
|
|
"use strict";
|
|
|
|
goog.provide("app.util.quadtree");
|
|
goog.require("cljs.core");
|
|
|
|
goog.scope(function() {
|
|
const self = app.util.quadtree;
|
|
const eq = cljs.core._EQ_;
|
|
const contains = cljs.core.contains_QMARK_;
|
|
|
|
class Node {
|
|
constructor(id, bounds, data) {
|
|
this.id = id;
|
|
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 = [];
|
|
}
|
|
|
|
getObjects() {
|
|
return this.objects;
|
|
}
|
|
}
|
|
|
|
self.create = function(rect) {
|
|
return new Quadtree(rect, 10, 4, 0);
|
|
};
|
|
|
|
self.insert = function(index, id, bounds, data) {
|
|
const node = new Node(id, 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;
|
|
}
|
|
}
|
|
};
|
|
|
|
self.remove = function(index, id) {
|
|
const result = self.create(index.bounds);
|
|
|
|
for (let node of index.objects) {
|
|
if (!eq(id, node.id)) {
|
|
self.insert(result, node.id, node.bounds, node.data);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// FIXME: Inefficient to recreate the index. Needs to be improved
|
|
self.remove_all = function(index, ids) {
|
|
const result = self.create(index.bounds);
|
|
|
|
for (let node of self.search(index, index.bounds)) {
|
|
if (!contains(ids, node.id)) {
|
|
self.insert(result, node.id, node.bounds, node.data);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
});
|