UNPKG

@2112-lab/pathfinder

Version:

Pure JavaScript 3D pathfinding algorithm library for industrial plant pipe routing

189 lines (159 loc) 6.71 kB
import { Vector3 } from './Vector3.js'; /** * Manages tree path calculations and Minimum Spanning Tree (MST) operations * @class TreePathManager */ export class TreePathManager { constructor(gridSystem = null, sceneManager = null) { this.gridSystem = gridSystem; this.sceneManager = sceneManager; } /** * Calculate distance between two points * @param {Vector3} p1 - First point * @param {Vector3} p2 - Second point * @returns {number} Distance between points */ distance(p1, p2) { const dx = p2.x - p1.x; const dy = p2.y - p1.y; const dz = p2.z - p1.z; return Math.sqrt(dx * dx + dy * dy + dz * dz); } /** * Find Minimum Spanning Tree using Prim's algorithm * @param {Array<Vector3>} points - Array of points to connect * @returns {Array<Array<number>>} Array of edges in the MST */ findMST(points) { const n = points.length; const mst = []; const visited = new Set(); // Start with the first point visited.add(0); while (visited.size < n) { let minDist = Infinity; let minEdge = null; // Find the minimum edge from visited to unvisited for (const i of visited) { for (let j = 0; j < n; j++) { if (!visited.has(j)) { const dist = this.distance(points[i], points[j]); if (dist < minDist) { minDist = dist; minEdge = [i, j]; } } } } if (minEdge) { mst.push(minEdge); visited.add(minEdge[1]); } else { break; // Break if we can't find any more edges } } return mst; } /** * Find the nearest free voxel to a given position * @param {Vector3} position - Position to find free voxel near * @param {number} maxSearchRadius - Maximum search radius in voxel units * @returns {Vector3|null} Nearest free position or null if none found */ findNearestFreeVoxel(position, maxSearchRadius = 5) { if (!this.gridSystem || !this.sceneManager) { return position; // Return original position if systems not available } const originalVoxelKey = this.gridSystem.voxelKey(position); // Check if original position is free if (!this.sceneManager.isVoxelOccupiedByMesh(originalVoxelKey, this.gridSystem.gridSize)) { return position; } // Search in expanding rings around the position for (let radius = 1; radius <= maxSearchRadius; radius++) { const [x, y, z] = originalVoxelKey.split(',').map(Number); // Generate all voxels at this radius const candidates = []; // Generate all combinations of offsets at this radius for (let dx = -radius; dx <= radius; dx++) { for (let dy = -radius; dy <= radius; dy++) { for (let dz = -radius; dz <= radius; dz++) { // Only consider voxels exactly at this radius if (Math.abs(dx) + Math.abs(dy) + Math.abs(dz) === radius) { candidates.push([x + dx, y + dy, z + dz]); } } } } // Check each candidate for (const [cx, cy, cz] of candidates) { const candidateKey = `${cx},${cy},${cz}`; if (!this.sceneManager.isVoxelOccupiedByMesh(candidateKey, this.gridSystem.gridSize)) { return this.gridSystem.voxelToVec3(candidateKey); } } } return null; } /** * Find joint point * @param {Array<Vector3>} points - Array of points to connect * @returns {Vector3|null} Joint point or null if not possible */ findJointPoint(points) { if (points.length <= 2) { return null; } // Find MST first const mst = this.findMST(points); if (mst.length === 0) { return null; } // Find the best edge in MST let bestEdge = null; let maxScore = -1; mst.forEach(([i, j], edgeIndex) => { const pointA = points[i]; const pointB = points[j]; // Calculate edge components const dx = Math.abs(pointB.x - pointA.x); const dy = Math.abs(pointB.y - pointA.y); const dz = Math.abs(pointB.z - pointA.z); // Calculate total length const totalLength = dx + dy + dz; if (totalLength > 0) { // Calculate score // Higher score = more orthogonal (closer to being axis-aligned) relative to the total length of the edge const maxComponent = Math.max(dx, dy, dz); const score = (maxComponent / totalLength) / totalLength; // Check if this edge has a better orthogonality score if (score > maxScore) { maxScore = score; bestEdge = [pointA, pointB]; } } }); if (!bestEdge) { return null; } // Place joint point at the midpoint of the most orthogonal edge const jointPoint = new Vector3( (bestEdge[0].x + bestEdge[1].x) * 0.5, (bestEdge[0].y + bestEdge[1].y) * 0.5, (bestEdge[0].z + bestEdge[1].z) * 0.5 ); // Snap to 0.5 grid const snapToGrid = (value) => Math.round(value * 2) / 2; jointPoint.x = snapToGrid(jointPoint.x); jointPoint.y = snapToGrid(jointPoint.y); jointPoint.z = snapToGrid(jointPoint.z); // Check if the joint point is in an occupied voxel and move it if necessary const freeJointPoint = this.findNearestFreeVoxel(jointPoint); if (freeJointPoint) { return freeJointPoint; } else { return jointPoint; // Return original if no free position found } } }