js-subpcd
Version:
🌟 High-performance JavaScript/TypeScript QuadTree point cloud filtering and processing library. Published on npm as js-subpcd with PCL.js compatible API for spatial filtering, subsampling, and nearest neighbor search.
215 lines (214 loc) • 6.91 kB
JavaScript
"use strict";
/**
* Simple QuadTree implementation for Node.js fallback
* Used when WebAssembly is not available or for smaller datasets
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SimpleQuadTree = void 0;
/**
* Simple QuadTree implementation
*/
class SimpleQuadTree {
constructor(boundary, capacity = 50, maxDepth = 12, depth = 0) {
this.boundary = boundary;
this.capacity = capacity;
this.maxDepth = maxDepth;
this.depth = depth;
this.points = [];
this.divided = false;
this.northeast = null;
this.northwest = null;
this.southeast = null;
this.southwest = null;
}
/**
* Insert a point into the quadtree
*/
insert(point) {
if (!this.contains(point)) {
return false;
}
// Force insertion at max depth to prevent infinite recursion
if (this.depth >= this.maxDepth) {
this.points.push(point);
return true;
}
if (this.points.length < this.capacity) {
this.points.push(point);
return true;
}
if (!this.divided) {
this.subdivide();
}
return (this.northeast.insert(point) ||
this.northwest.insert(point) ||
this.southeast.insert(point) ||
this.southwest.insert(point));
}
/**
* Check if a point is within the boundary
*/
contains(point) {
return (point.x >= this.boundary.x &&
point.x <= this.boundary.x + this.boundary.width &&
point.y >= this.boundary.y &&
point.y <= this.boundary.y + this.boundary.height);
}
/**
* Subdivide the quadtree into four quadrants
*/
subdivide() {
// Prevent subdivision if area becomes too small (prevents infinite recursion)
const minSize = 0.001;
if (this.boundary.width <= minSize || this.boundary.height <= minSize) {
return;
}
const x = this.boundary.x;
const y = this.boundary.y;
const w = this.boundary.width / 2;
const h = this.boundary.height / 2;
const neRect = { x: x + w, y: y, width: w, height: h };
const nwRect = { x: x, y: y, width: w, height: h };
const seRect = { x: x + w, y: y + h, width: w, height: h };
const swRect = { x: x, y: y + h, width: w, height: h };
this.northeast = new SimpleQuadTree(neRect, this.capacity, this.maxDepth, this.depth + 1);
this.northwest = new SimpleQuadTree(nwRect, this.capacity, this.maxDepth, this.depth + 1);
this.southeast = new SimpleQuadTree(seRect, this.capacity, this.maxDepth, this.depth + 1);
this.southwest = new SimpleQuadTree(swRect, this.capacity, this.maxDepth, this.depth + 1);
this.divided = true;
// Redistribute existing points
const pointsToRedistribute = [...this.points];
this.points = [];
for (const point of pointsToRedistribute) {
// Try to insert in child nodes, keep in parent if failed
if (!(this.northeast.insert(point) ||
this.northwest.insert(point) ||
this.southeast.insert(point) ||
this.southwest.insert(point))) {
this.points.push(point);
}
}
}
/**
* Query points within a rectangular range
*/
query(range, found = []) {
if (!this.intersects(range)) {
return found;
}
for (const point of this.points) {
if (this.inRange(point, range)) {
found.push(point);
}
}
if (this.divided) {
this.northeast.query(range, found);
this.northwest.query(range, found);
this.southeast.query(range, found);
this.southwest.query(range, found);
}
return found;
}
/**
* Check if the range intersects with the boundary
*/
intersects(range) {
return !(range.x >= this.boundary.x + this.boundary.width ||
range.x + range.width <= this.boundary.x ||
range.y >= this.boundary.y + this.boundary.height ||
range.y + range.height <= this.boundary.y);
}
/**
* Check if a point is within the range
*/
inRange(point, range) {
return (point.x >= range.x &&
point.x <= range.x + range.width &&
point.y >= range.y &&
point.y <= range.y + range.height);
}
/**
* Get all points in the quadtree
*/
getAllPoints(found = []) {
found.push(...this.points);
if (this.divided) {
this.northeast.getAllPoints(found);
this.northwest.getAllPoints(found);
this.southeast.getAllPoints(found);
this.southwest.getAllPoints(found);
}
return found;
}
/**
* Clear all points from the quadtree
*/
clear() {
this.points = [];
this.divided = false;
this.northeast = null;
this.northwest = null;
this.southeast = null;
this.southwest = null;
}
/**
* Get the total number of points
*/
size() {
let count = this.points.length;
if (this.divided) {
count += this.northeast.size();
count += this.northwest.size();
count += this.southeast.size();
count += this.southwest.size();
}
return count;
}
/**
* Get statistics about the quadtree
*/
getStatistics() {
const stats = {
totalPoints: this.size(),
depth: this.getMaxDepth(),
nodes: this.getNodeCount(),
leafNodes: this.getLeafNodeCount()
};
return stats;
}
/**
* Get the maximum depth of the quadtree
*/
getMaxDepth() {
if (!this.divided) {
return this.depth;
}
return Math.max(this.northeast.getMaxDepth(), this.northwest.getMaxDepth(), this.southeast.getMaxDepth(), this.southwest.getMaxDepth());
}
/**
* Get the total number of nodes
*/
getNodeCount() {
let count = 1; // This node
if (this.divided) {
count += this.northeast.getNodeCount();
count += this.northwest.getNodeCount();
count += this.southeast.getNodeCount();
count += this.southwest.getNodeCount();
}
return count;
}
/**
* Get the number of leaf nodes
*/
getLeafNodeCount() {
if (!this.divided) {
return 1;
}
return (this.northeast.getLeafNodeCount() +
this.northwest.getLeafNodeCount() +
this.southeast.getLeafNodeCount() +
this.southwest.getLeafNodeCount());
}
}
exports.SimpleQuadTree = SimpleQuadTree;