UNPKG

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
"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;