0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-09 08:20:45 -05:00

Fix kdtree implementation.

This commit is contained in:
Andrey Antukh 2016-04-11 17:29:33 +03:00
parent c02e144012
commit 94fc4d2b88
No known key found for this signature in database
GPG key ID: 4DFEBCB8316A8B95
2 changed files with 162 additions and 29 deletions

View file

@ -8,15 +8,15 @@
* @author Mircea Pricop <pricop@ubilabs.net>, 2012 * @author Mircea Pricop <pricop@ubilabs.net>, 2012
* @author Martin Kleppe <kleppe@ubilabs.net>, 2012 * @author Martin Kleppe <kleppe@ubilabs.net>, 2012
* @author Ubilabs http://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.core");
goog.provide("kdtree.KDTree"); goog.provide("kdtree.core.KDTree");
goog.require('goog.array'); goog.require("kdtree.heap");
goog.require('goog.structs.Heap'); goog.require("goog.array");
goog.require('goog.asserts'); goog.require("goog.asserts");
goog.scope(function() { goog.scope(function() {
"use strict"; "use strict";
@ -35,6 +35,10 @@ goog.scope(function() {
} }
} }
function precision(v) {
return parseFloat(v.toFixed(6));
}
function buildTree(points, depth, parent, dimensions) { function buildTree(points, depth, parent, dimensions) {
const dim = depth % dimensions; const dim = depth % dimensions;
@ -184,7 +188,6 @@ goog.scope(function() {
node.left = null; node.left = null;
node.obj = nextObj; node.obj = nextObj;
} }
} }
nearest(point, maxNodes) { nearest(point, maxNodes) {
@ -192,29 +195,19 @@ goog.scope(function() {
maxNodes = 1; 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) => { const nearestSearch = (node) => {
let bestChild; let distance = precision(this.metric(point, node.obj));
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();
}
}
if (best.isEmpty()) { if (best.isEmpty()) {
best.insert(-distance, [node.obj, distance]); best.insert([node.obj, distance]);
} else { } else {
if (distance < best.peek()[1]) { if (distance < best.peek()[1]) {
best.insert(-distance, [node.obj, distance]); best.insert([node.obj, distance]);
if (best.getCount() > maxNodes) {
best.remove();
}
} }
} }
@ -222,12 +215,13 @@ goog.scope(function() {
return; return;
} }
let bestChild = null;
if (node.right === null) { if (node.right === null) {
bestChild = node.left; bestChild = node.left;
} else if (node.left === null) { } else if (node.left === null) {
bestChild = node.right; bestChild = node.right;
} else { } else {
if (point[dimension] < node.obj[dimension]) { if (point[node.dimension] < node.obj[node.dimension]) {
bestChild = node.left; bestChild = node.left;
} else { } else {
bestChild = node.right; bestChild = node.right;
@ -235,14 +229,41 @@ goog.scope(function() {
} }
nearestSearch(bestChild); 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) { if(this.root) {
nearestSearch(this.root); nearestSearch(this.root);
} }
result = best.getValues(); const result = [];
result.sort((x) => x[1]);
for (let i=0; i < (Math.min(maxNodes, best.size)); i++) {
result.push(best.removeHead());
}
return result; return result;
} }
@ -274,8 +295,8 @@ goog.scope(function() {
}; };
// Types // Types
kdtree.KDTree = KDTree; kdtree.core.KDTree = KDTree;
// Factory functions // Factory functions
kdtree.create2d = create2d; kdtree.core.create2d = create2d;
}); });

112
vendor/kdtree/heap.js vendored Normal file
View 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;
});