mirror of
https://github.com/penpot/penpot.git
synced 2025-08-07 14:38:33 +02:00
Simplify kdtree impl removing unused code.
This commit is contained in:
parent
ed6417f6db
commit
2fbd3f6007
1 changed files with 101 additions and 199 deletions
300
vendor/kdtree/core.js
vendored
300
vendor/kdtree/core.js
vendored
|
@ -11,6 +11,8 @@
|
||||||
* @license MIT License <https://opensource.org/licenses/MIT>
|
* @license MIT License <https://opensource.org/licenses/MIT>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
goog.provide("kdtree.core");
|
goog.provide("kdtree.core");
|
||||||
goog.provide("kdtree.core.KDTree");
|
goog.provide("kdtree.core.KDTree");
|
||||||
|
|
||||||
|
@ -18,10 +20,10 @@ goog.require("kdtree.heap");
|
||||||
goog.require("goog.asserts");
|
goog.require("goog.asserts");
|
||||||
|
|
||||||
goog.scope(function() {
|
goog.scope(function() {
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const assert = goog.asserts.assert;
|
const assert = goog.asserts.assert;
|
||||||
const assertNumber = goog.asserts.assertNumber;
|
|
||||||
|
// Hardcoded dimensions value;
|
||||||
|
const dimensions = 2;
|
||||||
|
|
||||||
class Node {
|
class Node {
|
||||||
constructor(obj, dimension, parent) {
|
constructor(obj, dimension, parent) {
|
||||||
|
@ -33,11 +35,43 @@ goog.scope(function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class KDTree {
|
||||||
|
constructor() {
|
||||||
|
this.root = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize(points) {
|
||||||
|
assert(goog.isArray(points));
|
||||||
|
this.root = buildTree(null, points, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
isInitialized() {
|
||||||
|
return this.root !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.root = null
|
||||||
|
}
|
||||||
|
|
||||||
|
nearest(point, maxNodes) {
|
||||||
|
assert(goog.isArray(point));
|
||||||
|
assert(maxNodes >= 1);
|
||||||
|
assert(this.isInitialized())
|
||||||
|
return searchNearest(this.root, point, maxNodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Private Api (implementation)
|
||||||
|
|
||||||
function precision(v) {
|
function precision(v) {
|
||||||
return parseFloat(v.toFixed(6));
|
return parseFloat(v.toFixed(6));
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTree(points, depth, parent, dimensions) {
|
function calculateDistance(a, b){
|
||||||
|
return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTree(parent, points, depth) {
|
||||||
const dim = depth % dimensions;
|
const dim = depth % dimensions;
|
||||||
|
|
||||||
if (points.length === 0) {
|
if (points.length === 0) {
|
||||||
|
@ -54,229 +88,97 @@ goog.scope(function() {
|
||||||
|
|
||||||
const median = Math.floor(points.length / 2);
|
const median = Math.floor(points.length / 2);
|
||||||
const node = new Node(points[median], dim, parent);
|
const node = new Node(points[median], dim, parent);
|
||||||
node.left = buildTree(points.slice(0, median), depth + 1, node, dimensions);
|
node.left = buildTree(node, points.slice(0, median), depth + 1);
|
||||||
node.right = buildTree(points.slice(median + 1), depth + 1, node, dimensions);
|
node.right = buildTree(node, points.slice(median + 1), depth + 1);
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findMin(node, dim) {
|
function searchNearest(root, point, maxNodes) {
|
||||||
let dimension, own, left, right, min;
|
const search = (best, node) => {
|
||||||
|
if (best === null) {
|
||||||
if (node === null) {
|
best = new kdtree.heap.MinHeap((x, y) => x[1] - y[1]);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.dimension === dim) {
|
|
||||||
if (node.left !== null) {
|
|
||||||
return findMin(node.left, dim);
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
own = node.obj[dim];
|
|
||||||
left = findMin(node.left, dim);
|
|
||||||
right = findMin(node.right, dim);
|
|
||||||
min = node;
|
|
||||||
|
|
||||||
if (left !== null && left.obj[dim] < own) {
|
|
||||||
min = left;
|
|
||||||
}
|
|
||||||
if (right !== null && right.obj[dim] < min.obj[dim]) {
|
|
||||||
min = right;
|
|
||||||
}
|
|
||||||
return min;
|
|
||||||
}
|
|
||||||
|
|
||||||
function innerSearch(point, node, parent) {
|
|
||||||
if (node === null) {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (point[dim] < node.obj[dim]) {
|
|
||||||
return innerSearch(point, node.left, node);
|
|
||||||
} else {
|
|
||||||
return innerSearch(point, node.right, node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function nodeSearch(point, node) {
|
|
||||||
if (node === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.obj === point) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (point[node.dimension] < node.obj[node.dimension]) {
|
|
||||||
return nodeSearch(point, node.left);
|
|
||||||
} else {
|
|
||||||
return nodeSearch(point, node.right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class KDTree {
|
|
||||||
constructor(points, metric, dimensions) {
|
|
||||||
assert(points.length !== 0);
|
|
||||||
assertNumber(dimensions);
|
|
||||||
|
|
||||||
this.root = buildTree(points, 0, null, dimensions);
|
|
||||||
this.metric = metric;
|
|
||||||
this.dimensions = dimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
insert(point) {
|
|
||||||
const insertPosition = innerSearch(point, this.root, null);
|
|
||||||
|
|
||||||
if (insertPosition === null) {
|
|
||||||
this.root = new Node(point, 0, null);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const newNode = new Node(point,
|
let distance = precision(calculateDistance(point, node.obj));
|
||||||
(insertPosition.dimension + 1) % this.dimensions,
|
|
||||||
insertPosition);
|
|
||||||
|
|
||||||
const dimension = insertPosition.dimension;
|
if (best.isEmpty()) {
|
||||||
if (point[dimension] < insertPosition.obj[dimension]) {
|
best.insert([node.obj, distance]);
|
||||||
insertPosition.left = newNode;
|
|
||||||
} else {
|
} else {
|
||||||
insertPosition.right = newNode;
|
if (distance < best.peek()[1]) {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(point) {
|
|
||||||
const node = nodeSearch(point, this.root);
|
|
||||||
if (node === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.left === null && node.right === null) {
|
|
||||||
if (node.parent === null) {
|
|
||||||
this.root = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pdim = node.parent.dimension;
|
|
||||||
|
|
||||||
if (node.obj[pdim] < node.parent.obj[pdim]) {
|
|
||||||
node.parent.left = null;
|
|
||||||
} else {
|
|
||||||
node.parent.right = null;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the right subtree is not empty, swap with the minimum element on the
|
|
||||||
// node's dimension. If it is empty, we swap the left and right subtrees and
|
|
||||||
// do the same.
|
|
||||||
let nextNode, nextObj;
|
|
||||||
|
|
||||||
if (node.right !== null) {
|
|
||||||
nextNode = findMin(node.right, node.dimension);
|
|
||||||
nextObj = nextNode.obj;
|
|
||||||
removeNode(nextNode);
|
|
||||||
node.obj = nextObj;
|
|
||||||
} else {
|
|
||||||
nextNode = findMin(node.left, node.dimension);
|
|
||||||
nextObj = nextNode.obj;
|
|
||||||
removeNode(nextNode);
|
|
||||||
node.right = node.left;
|
|
||||||
node.left = null;
|
|
||||||
node.obj = nextObj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nearest(point, maxNodes) {
|
|
||||||
if (maxNodes === undefined) {
|
|
||||||
maxNodes = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let best = new kdtree.heap.MinHeap((x, y) => {
|
|
||||||
let res = x[1] - y[1];
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
|
|
||||||
const nearestSearch = (node) => {
|
|
||||||
let distance = precision(this.metric(point, node.obj));
|
|
||||||
|
|
||||||
if (best.isEmpty()) {
|
|
||||||
best.insert([node.obj, distance]);
|
best.insert([node.obj, distance]);
|
||||||
} else {
|
|
||||||
if (distance < best.peek()[1]) {
|
|
||||||
best.insert([node.obj, distance]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (node.right === null && node.left === null) {
|
if (node.right === null && node.left === null) {
|
||||||
return;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bestChild = null;
|
let bestChild = null;
|
||||||
if (node.right === null) {
|
if (node.right === null) {
|
||||||
|
bestChild = node.left;
|
||||||
|
} else if (node.left === null) {
|
||||||
|
bestChild = node.right;
|
||||||
|
} else {
|
||||||
|
if (point[node.dimension] < node.obj[node.dimension]) {
|
||||||
bestChild = node.left;
|
bestChild = node.left;
|
||||||
} else if (node.left === null) {
|
|
||||||
bestChild = node.right;
|
|
||||||
} else {
|
} else {
|
||||||
if (point[node.dimension] < node.obj[node.dimension]) {
|
bestChild = node.right;
|
||||||
bestChild = node.left;
|
|
||||||
} else {
|
|
||||||
bestChild = node.right;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = [];
|
best = search(best, bestChild);
|
||||||
|
|
||||||
for (let i=0; i < (Math.min(maxNodes, best.size)); i++) {
|
let candidate = [null, null];
|
||||||
result.push(best.removeHead());
|
for (let i = 0; i < dimensions; i += 1) {
|
||||||
|
if (i === node.dimension) {
|
||||||
|
candidate[i] = point[i];
|
||||||
|
} else {
|
||||||
|
candidate[i] = node.obj[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
distance = Math.abs(calculateDistance(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) {
|
||||||
|
best = search(best, otherChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best;
|
||||||
|
};
|
||||||
|
|
||||||
|
const best = search(null, root);
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (let i=0; i < (Math.min(maxNodes, best.size)); i++) {
|
||||||
|
result.push(best.removeHead());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function distance2d(a, b){
|
// --- Public Api
|
||||||
return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
function create2d(points) {
|
function create(points) {
|
||||||
return new KDTree(points, distance2d, 2);
|
const tree = new KDTree();
|
||||||
|
if (goog.isArray(points)) {
|
||||||
|
tree.initialize(points);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
kdtree.core.KDTree = KDTree;
|
kdtree.core.KDTree = KDTree;
|
||||||
|
|
||||||
// Factory functions
|
// Factory functions
|
||||||
kdtree.core.create2d = create2d;
|
kdtree.core.create = create;
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue