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.

297 lines (296 loc) • 11.6 kB
"use strict"; /** * WebAssembly QuadTree Wrapper for Node.js * Provides seamless integration between WASM and JavaScript implementations */ Object.defineProperty(exports, "__esModule", { value: true }); exports.QuadTreeWASMWrapper = void 0; const simple_quadtree_1 = require("./simple-quadtree"); /** * QuadTree WASM Wrapper for Node.js environment */ class QuadTreeWASMWrapper { constructor() { this.wasmModule = null; this.wasmInstance = null; this.isWasmReady = false; this.isLoading = false; this.fallbackQuadTree = null; this.useFallback = false; this.wasmTreeData = null; // Performance thresholds this.WASM_THRESHOLD = 1000; // Use WASM for datasets larger than this this.loadWasm(); } /** * Load WebAssembly module */ async loadWasm() { if (this.isLoading || this.isWasmReady) { return; } this.isLoading = true; try { // Check if WebAssembly is supported if (typeof WebAssembly === 'undefined') { console.warn('WebAssembly not supported, using JavaScript fallback'); this.useFallback = true; this.isLoading = false; return; } // Load the WASM module (Node.js way) // eslint-disable-next-line @typescript-eslint/no-var-requires const QuadTreeModule = require('../quadtree.js'); this.wasmModule = await QuadTreeModule(); this.isWasmReady = true; console.log('WebAssembly QuadTree module loaded successfully'); } catch (error) { console.warn('Failed to load WebAssembly module, using JavaScript fallback:', error); this.useFallback = true; } finally { this.isLoading = false; } } /** * Wait for WASM to load with timeout */ async waitForWasm() { if (this.isWasmReady || this.useFallback) { return; } // Wait for WASM to load (with timeout) const timeout = 5000; // 5 seconds const startTime = Date.now(); while (!this.isWasmReady && !this.useFallback && (Date.now() - startTime) < timeout) { await new Promise(resolve => setTimeout(resolve, 50)); } if (!this.isWasmReady && !this.useFallback) { console.warn('WASM loading timeout, using JavaScript fallback'); this.useFallback = true; } } /** * Determine if WASM should be used based on point count */ shouldUseWasm(pointCount) { return this.isWasmReady && !this.useFallback && pointCount >= this.WASM_THRESHOLD; } /** * Build tree using either WASM or JavaScript fallback */ async buildTree(points, bounds) { await this.waitForWasm(); const useWasm = this.shouldUseWasm(points.length); if (useWasm) { console.log(`Using WebAssembly for ${points.length} points`); return this.buildTreeWasm(points, bounds); } else { console.log(`Using JavaScript fallback for ${points.length} points`); return this.buildTreeJS(points, bounds); } } /** * Build tree using WebAssembly */ buildTreeWasm(points, bounds) { try { console.log('Attempting to use WASM QuadTree implementation...'); // Check if WASM module has the expected functions if (!this.wasmModule || typeof this.wasmModule.ccall !== 'function') { console.warn('WASM module does not have ccall interface, falling back to JavaScript'); return this.buildTreeJS(points, bounds); } // Convert points to format expected by WASM (flatten arrays) const xCoords = new Float32Array(points.length); const yCoords = new Float32Array(points.length); const zCoords = new Float32Array(points.length); for (let i = 0; i < points.length; i++) { xCoords[i] = points[i].x; yCoords[i] = points[i].y; zCoords[i] = points[i].z || 0; } // Try to call WASM function for tree building const result = this.wasmModule.ccall('buildQuadTree', 'number', ['number', 'number', 'number', 'number', 'number', 'number', 'number'], [points.length, xCoords, yCoords, zCoords, bounds.minX, bounds.minY, bounds.maxX, bounds.maxY]); if (result !== 0) { console.log(`WASM QuadTree built successfully for ${points.length} points`); // Create a wrapper that mimics the expected interface this.wasmTreeData = { points: points, bounds: bounds, wasmHandle: result }; return { type: 'wasm', instance: this.wasmTreeData, pointCount: points.length }; } else { throw new Error('WASM tree building returned null/failed'); } } catch (error) { console.warn('WASM tree building failed, falling back to JavaScript:', error.message); return this.buildTreeJS(points, bounds); } } /** * Build tree using JavaScript fallback */ buildTreeJS(points, bounds) { // Use the existing SimpleQuadTree implementation const boundary = { x: bounds.minX, y: bounds.minY, width: bounds.maxX - bounds.minX, height: bounds.maxY - bounds.minY }; this.fallbackQuadTree = new simple_quadtree_1.SimpleQuadTree(boundary); for (let i = 0; i < points.length; i++) { this.fallbackQuadTree.insert({ x: points[i].x, y: points[i].y, z: points[i].z || 0, index: i }); } return { type: 'javascript', instance: this.fallbackQuadTree, pointCount: points.length }; } /** * Filter points by depth using grid-based subsampling */ filterByDepth(treeInfo, targetDepth) { if (treeInfo.type === 'wasm' && treeInfo.instance) { try { console.log(`WASM filtering at depth ${targetDepth}...`); // Call WASM function for grid-based subsampling const gridSize = Math.pow(2, targetDepth); if (!this.wasmModule || typeof this.wasmModule.ccall !== 'function') { console.warn('WASM module not available for filtering'); return []; } // Allocate memory for result points const resultPtr = this.wasmModule.ccall('filterByDepth', 'number', ['number', 'number'], [treeInfo.instance.wasmHandle, targetDepth]); if (resultPtr === 0) { console.warn('WASM filtering returned null'); return []; } // Read results from WASM memory const resultCount = this.wasmModule.getValue(resultPtr, 'i32'); const pointsPtr = this.wasmModule.getValue(resultPtr + 4, 'i32'); const filteredPoints = []; for (let i = 0; i < resultCount; i++) { const pointPtr = pointsPtr + (i * 12); // 3 floats * 4 bytes const x = this.wasmModule.getValue(pointPtr, 'float'); const y = this.wasmModule.getValue(pointPtr + 4, 'float'); const z = this.wasmModule.getValue(pointPtr + 8, 'float'); filteredPoints.push({ x, y, z }); } // Free WASM memory this.wasmModule.ccall('freeFilterResult', 'void', ['number'], [resultPtr]); console.log(`WASM filtered to ${filteredPoints.length} points (${gridSize}x${gridSize} grid)`); return filteredPoints; } catch (error) { // WASM filtering failed, using fallback // Fallback to JavaScript implementation using the original points return this.fallbackGridSubsampling(treeInfo.instance.points, treeInfo.instance.bounds, targetDepth); } } else if (treeInfo.type === 'javascript' && treeInfo.instance) { // JavaScript QuadTree filtering (already implemented in main code) const allPoints = treeInfo.instance.getAllPoints(); const gridSize = Math.pow(2, targetDepth); // Simple grid-based subsampling return this.fallbackGridSubsampling(allPoints, treeInfo.instance.boundary, targetDepth); } return []; } /** * Fallback JavaScript grid-based subsampling */ fallbackGridSubsampling(points, bounds, targetDepth) { const gridSize = Math.pow(2, targetDepth); const cellWidth = (bounds.maxX - bounds.minX) / gridSize; const cellHeight = (bounds.maxY - bounds.minY) / gridSize; const gridCells = new Map(); for (const point of points) { const cellX = Math.floor((point.x - bounds.minX) / cellWidth); const cellY = Math.floor((point.y - bounds.minY) / cellHeight); const cellKey = `${cellX},${cellY}`; // Keep only first point in each cell if (!gridCells.has(cellKey)) { gridCells.set(cellKey, point); } } return Array.from(gridCells.values()); } /** * Query range using appropriate implementation */ queryRange(treeInfo, x, y, width, height) { if (treeInfo.type === 'wasm' && treeInfo.instance) { try { // WASM range query would be implemented here return this.convertWasmArrayToJS([]); } catch (error) { // WASM range query failed, using fallback return []; } } else if (treeInfo.type === 'javascript' && treeInfo.instance) { const range = { x, y, width, height }; return treeInfo.instance.query(range); } return []; } /** * Convert WASM array to JavaScript array */ convertWasmArrayToJS(wasmArray) { const result = []; const length = wasmArray.length || 0; for (let i = 0; i < length; i++) { const point = wasmArray[i]; result.push({ x: point.x, y: point.y, z: point.z }); } return result; } /** * Clear tree data */ clear(treeInfo) { if (treeInfo.type === 'wasm' && treeInfo.instance) { // WASM cleanup would be implemented here } else if (treeInfo.type === 'javascript' && treeInfo.instance) { treeInfo.instance.clear(); } } /** * Get performance information */ getPerformanceInfo() { return { wasmReady: this.isWasmReady, usingFallback: this.useFallback, wasmThreshold: this.WASM_THRESHOLD, wasmSupported: typeof WebAssembly !== 'undefined' }; } } exports.QuadTreeWASMWrapper = QuadTreeWASMWrapper; // Export for CommonJS compatibility exports.default = QuadTreeWASMWrapper;