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