mirror of
https://github.com/penpot/penpot.git
synced 2025-01-08 16:00:19 -05:00
Fix kdtree implementation.
This commit is contained in:
parent
c02e144012
commit
94fc4d2b88
2 changed files with 162 additions and 29 deletions
79
vendor/kdtree.js → vendor/kdtree/core.js
vendored
79
vendor/kdtree.js → vendor/kdtree/core.js
vendored
|
@ -8,15 +8,15 @@
|
|||
* @author Mircea Pricop <pricop@ubilabs.net>, 2012
|
||||
* @author Martin Kleppe <kleppe@ubilabs.net>, 2012
|
||||
* @author Ubilabs http://ubilabs.net, 2012
|
||||
* @license MIT License <http://www.opensource.org/licenses/mit-license.php>
|
||||
* @license MIT License <https://opensource.org/licenses/MIT>
|
||||
*/
|
||||
|
||||
goog.provide("kdtree");
|
||||
goog.provide("kdtree.KDTree");
|
||||
goog.provide("kdtree.core");
|
||||
goog.provide("kdtree.core.KDTree");
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.structs.Heap');
|
||||
goog.require('goog.asserts');
|
||||
goog.require("kdtree.heap");
|
||||
goog.require("goog.array");
|
||||
goog.require("goog.asserts");
|
||||
|
||||
goog.scope(function() {
|
||||
"use strict";
|
||||
|
@ -35,6 +35,10 @@ goog.scope(function() {
|
|||
}
|
||||
}
|
||||
|
||||
function precision(v) {
|
||||
return parseFloat(v.toFixed(6));
|
||||
}
|
||||
|
||||
function buildTree(points, depth, parent, dimensions) {
|
||||
const dim = depth % dimensions;
|
||||
|
||||
|
@ -184,7 +188,6 @@ goog.scope(function() {
|
|||
node.left = null;
|
||||
node.obj = nextObj;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
nearest(point, maxNodes) {
|
||||
|
@ -192,29 +195,19 @@ goog.scope(function() {
|
|||
maxNodes = 1;
|
||||
}
|
||||
|
||||
let best = new goog.structs.Heap();
|
||||
let best = new kdtree.heap.MinHeap((x, y) => {
|
||||
let res = x[1] - y[1];
|
||||
return res;
|
||||
});
|
||||
|
||||
const nearestSearch = (node) => {
|
||||
let bestChild;
|
||||
const distance = this.metric(point, node.obj);
|
||||
const dimension = node.dimension;
|
||||
|
||||
if (best.getCount() < maxNodes || distance < best.peek()[1]) {
|
||||
best.insert(-distance, [node.obj, distance]);
|
||||
if (best.getCount() > maxNodes) {
|
||||
best.remove();
|
||||
}
|
||||
}
|
||||
let distance = precision(this.metric(point, node.obj));
|
||||
|
||||
if (best.isEmpty()) {
|
||||
best.insert(-distance, [node.obj, distance]);
|
||||
best.insert([node.obj, distance]);
|
||||
} else {
|
||||
if (distance < best.peek()[1]) {
|
||||
best.insert(-distance, [node.obj, distance]);
|
||||
|
||||
if (best.getCount() > maxNodes) {
|
||||
best.remove();
|
||||
}
|
||||
best.insert([node.obj, distance]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -222,12 +215,13 @@ goog.scope(function() {
|
|||
return;
|
||||
}
|
||||
|
||||
let bestChild = null;
|
||||
if (node.right === null) {
|
||||
bestChild = node.left;
|
||||
} else if (node.left === null) {
|
||||
bestChild = node.right;
|
||||
} else {
|
||||
if (point[dimension] < node.obj[dimension]) {
|
||||
if (point[node.dimension] < node.obj[node.dimension]) {
|
||||
bestChild = node.left;
|
||||
} else {
|
||||
bestChild = node.right;
|
||||
|
@ -235,14 +229,41 @@ goog.scope(function() {
|
|||
}
|
||||
|
||||
nearestSearch(bestChild);
|
||||
|
||||
let candidate = [null, null];
|
||||
for (let i = 0; i < this.dimensions; i += 1) {
|
||||
if (i === node.dimension) {
|
||||
candidate[i] = point[i];
|
||||
} else {
|
||||
candidate[i] = node.obj[i];
|
||||
}
|
||||
}
|
||||
|
||||
distance = Math.abs(this.metric(candidate, node.obj));
|
||||
|
||||
if (best.size < maxNodes || distance < best.peek()[1]) {
|
||||
let otherChild;
|
||||
if (bestChild === node.left) {
|
||||
otherChild = node.right;
|
||||
} else {
|
||||
otherChild = node.left;
|
||||
}
|
||||
if (otherChild !== null) {
|
||||
nearestSearch(otherChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(this.root) {
|
||||
nearestSearch(this.root);
|
||||
}
|
||||
|
||||
result = best.getValues();
|
||||
result.sort((x) => x[1]);
|
||||
const result = [];
|
||||
|
||||
for (let i=0; i < (Math.min(maxNodes, best.size)); i++) {
|
||||
result.push(best.removeHead());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -274,8 +295,8 @@ goog.scope(function() {
|
|||
};
|
||||
|
||||
// Types
|
||||
kdtree.KDTree = KDTree;
|
||||
kdtree.core.KDTree = KDTree;
|
||||
|
||||
// Factory functions
|
||||
kdtree.create2d = create2d;
|
||||
kdtree.core.create2d = create2d;
|
||||
});
|
112
vendor/kdtree/heap.js
vendored
Normal file
112
vendor/kdtree/heap.js
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
/**
|
||||
* kdtree
|
||||
*
|
||||
* Is a modified and google closure adapted kdtree implementation
|
||||
* of https://github.com/ubilabs/kd-tree-javascript.
|
||||
*
|
||||
* @author Andrey Antukh <niwi@niwi.nz>, 2016
|
||||
* @license MIT License <https://opensource.org/licenses/MIT>
|
||||
*/
|
||||
|
||||
goog.provide("kdtree.heap");
|
||||
goog.provide("kdtree.heap.MinHeap");
|
||||
|
||||
goog.scope(function() {
|
||||
"use strict";
|
||||
|
||||
const compare = (x,y) => x-y;
|
||||
|
||||
class MinHeap {
|
||||
constructor(cmp) {
|
||||
this.cmp = cmp || compare;
|
||||
this.heap = [];
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
insert(item) {
|
||||
const heap = this.heap;
|
||||
|
||||
let index = this.size++;
|
||||
let parent = (index-1)>>1;
|
||||
|
||||
heap[index] = item;
|
||||
|
||||
while ((index > 0) && this.cmp(heap[parent], item) > 0) {
|
||||
const tmp = heap[parent];
|
||||
heap[parent] = heap[index];
|
||||
heap[index] = tmp;
|
||||
index = parent;
|
||||
parent = (index-1)>>1;
|
||||
}
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.size === 0;
|
||||
}
|
||||
|
||||
peek() {
|
||||
return this.heap[0];
|
||||
}
|
||||
|
||||
removeHead() {
|
||||
const heap = this.heap;
|
||||
const cmp = this.cmp;
|
||||
|
||||
if (this.size === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const head = heap[0];
|
||||
|
||||
this._bubble(0);
|
||||
return head;
|
||||
}
|
||||
|
||||
remove(item) {
|
||||
const heap = this.heap;
|
||||
|
||||
for (let i = 0; i < this.size; ++i) {
|
||||
if (heap[i] === item) {
|
||||
this._bubble(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_bubble(index) {
|
||||
const heap = this.heap;
|
||||
const cmp = this.cmp;
|
||||
|
||||
heap[index] = heap[--this.size];
|
||||
heap[this.size] = null;
|
||||
|
||||
while (true) {
|
||||
|
||||
const leftIndex = (index<<1)+1;
|
||||
const rightIndex = (index<<1)+2;
|
||||
let minIndex = index;
|
||||
|
||||
if (leftIndex < this.size && cmp(heap[leftIndex], heap[minIndex]) < 0) {
|
||||
minIndex = leftIndex;
|
||||
}
|
||||
|
||||
if (rightIndex < this.size && cmp(heap[rightIndex], heap[minIndex]) < 0) {
|
||||
minIndex = rightIndex;
|
||||
}
|
||||
|
||||
if (minIndex !== index) {
|
||||
const tmp = heap[index];
|
||||
heap[index] = heap[minIndex];
|
||||
heap[minIndex] = tmp;
|
||||
index = minIndex;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kdtree.heap.MinHeap = MinHeap;
|
||||
});
|
Loading…
Reference in a new issue