// ================================ // A*航线规划模块 - 基于3D栅格索引 // ================================ import * as Cesium from 'cesium' class PathPlanning { constructor(viewer, occupancyGrid) { this.viewer = viewer; this.occupancyGrid = occupancyGrid; this.pathEntities = []; this.isEnabled = false; // 路径配置 this.config = { pathColor: Cesium.Color.YELLOW.withAlpha(0.8), // 路径颜色(黄色) pathOutlineColor: Cesium.Color.ORANGE, // 路径边框颜色 startPointColor: Cesium.Color.BLUE.withAlpha(0.9), // 起点颜色(蓝色) endPointColor: Cesium.Color.BLUE.withAlpha(0.9), // 终点颜色(蓝色) startEndOutlineColor: Cesium.Color.DARKBLUE, // 起点终点边框颜色 outlineWidth: 2, // 边框宽度 enableDiagonal: true, // 是否允许对角线移动(3D中为26连通性) verticalWeight: 1.2 // 垂直移动权重(略高于水平移动) }; // 显示控制 this.showOnlyPath = false; // 是否只显示路径单元 this.hasActivePath = false; // 是否有活跃的路径 // 3D栅格数据结构 this.grid3D = null; // 3维数组存储占用状态 this.gridDimensions = { width: 0, height: 0, depth: 0 }; // 栅格尺寸 this.gridEntityMap = new Map(); // 栅格索引(字符串)到实体的映射 this.indexToEntityMap = new Map(); // 栅格索引对象到实体的映射 // A*算法相关 this.openList = []; // 开放列表 this.closedList = []; // 关闭列表 } // ================================ // 启用/禁用路径规划功能 // ================================ setEnabled(enabled) { this.isEnabled = enabled; if (!enabled) { this.clearPath(); } } // ================================ // A*路径规划主函数 - 完全基于3D栅格索引 // ================================ async planPath() { if (!this.isEnabled || !this.occupancyGrid.gridEntities.length) { console.warn('路径规划未启用或网格未生成'); return; } try { console.log('开始基于3D栅格索引的A*路径规划...'); // 1. 构建3D栅格索引结构和占用状态数组 this.build3DGridStructure(); // 2. 通过最近邻查找获取航线起点和终点对应的3D栅格索引 const { startIndex, endIndex } = await this.findWaypointGridIndices(); if (!startIndex || !endIndex) { console.warn('无法找到航线起点或终点对应的栅格索引'); alert('无法找到航线起点或终点对应的栅格索引!'); return; } console.log('起点栅格索引:', startIndex); console.log('终点栅格索引:', endIndex); console.log('3D栅格维度:', this.gridDimensions); // 3. 首先显示起点和终点(蓝色) console.log('首先显示起点和终点...'); this.visualizeStartEndPoints(startIndex, endIndex); // 等待一小段时间让用户看到起点和终点 await new Promise(resolve => setTimeout(resolve, 1000)); // 4. 在三维数组上执行A*路径规划 console.log('开始执行A*路径规划算法...'); const pathIndices = this.aStarOnGrid3D(startIndex, endIndex); if (pathIndices && pathIndices.length > 0) { // 5. 通过索引找到对应的3D占用网格单元,复制并修改颜色(包含起点终点的蓝色) this.visualizePathByGridCopy(pathIndices, startIndex, endIndex); console.log(`路径规划成功,路径长度:${pathIndices.length}个栅格索引`); // 更新UI this.updatePathInfo(pathIndices); } else { console.warn('未找到可行路径'); alert('未找到从起点到终点的可行路径!'); // 即使没找到路径,也保持起点终点的显示 } } catch (error) { console.error('路径规划错误:', error); alert('路径规划过程中发生错误,请查看控制台日志。'); } } // ================================ // 构建3D栅格索引结构和占用状态数组 - 直接使用网格实体properties数据 // ================================ build3DGridStructure() { console.log('开始构建3D栅格索引结构,直接使用网格实体properties数据...'); if (!this.occupancyGrid.gridEntities.length) { throw new Error('没有可用的占用网格实体'); } // 第一步:分析所有网格实体,从properties中提取gridIndex,确定栅格维度 let minX = Infinity, minY = Infinity, minZ = Infinity; let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity; const validEntities = []; // 遍历所有网格实体,提取gridIndex信息 for (const entity of this.occupancyGrid.gridEntities) { // 直接从properties中获取gridIndex if (entity.properties && entity.properties.gridIndex) { const gridIndex = entity.properties.gridIndex.getValue(); if (gridIndex && typeof gridIndex.x === 'number' && typeof gridIndex.y === 'number' && typeof gridIndex.z === 'number') { // 更新维度范围 minX = Math.min(minX, gridIndex.x); maxX = Math.max(maxX, gridIndex.x); minY = Math.min(minY, gridIndex.y); maxY = Math.max(maxY, gridIndex.y); minZ = Math.min(minZ, gridIndex.z); maxZ = Math.max(maxZ, gridIndex.z); validEntities.push(entity); } else { console.warn('网格实体properties中的gridIndex格式不正确:', gridIndex); } } else { console.warn('网格实体缺少gridIndex属性'); } } if (validEntities.length === 0) { throw new Error('没有找到包含有效gridIndex的网格实体'); } // 计算栅格维度(基于实际的索引范围) this.gridDimensions = { width: maxX - minX + 1, height: maxY - minY + 1, depth: maxZ - minZ + 1 }; console.log(`从properties提取的3D栅格维度: ${this.gridDimensions.width} x ${this.gridDimensions.height} x ${this.gridDimensions.depth}`); console.log(`索引范围: X[${minX}, ${maxX}], Y[${minY}, ${maxY}], Z[${minZ}, ${maxZ}]`); // 第二步:初始化3D占用状态数组 this.grid3D = new Array(this.gridDimensions.width); for (let x = 0; x < this.gridDimensions.width; x++) { this.grid3D[x] = new Array(this.gridDimensions.height); for (let y = 0; y < this.gridDimensions.height; y++) { this.grid3D[x][y] = new Array(this.gridDimensions.depth); for (let z = 0; z < this.gridDimensions.depth; z++) { this.grid3D[x][y][z] = { occupied: false, entity: null, exists: false }; } } } // 第三步:直接使用实体properties数据填充3D数组 this.gridEntityMap.clear(); this.indexToEntityMap.clear(); let mappedCount = 0; let occupiedCount = 0; let freeCount = 0; // 记录索引偏移量,将原始索引映射到数组索引 this.indexOffset = { x: minX, y: minY, z: minZ }; for (const entity of validEntities) { const gridIndex = entity.properties.gridIndex.getValue(); const isOccupied = entity.properties.isOccupied ? entity.properties.isOccupied.getValue() : false; // 转换为数组索引(相对于最小值的偏移) const arrayX = gridIndex.x - minX; const arrayY = gridIndex.y - minY; const arrayZ = gridIndex.z - minZ; // 确保索引在有效范围内 if (arrayX >= 0 && arrayX < this.gridDimensions.width && arrayY >= 0 && arrayY < this.gridDimensions.height && arrayZ >= 0 && arrayZ < this.gridDimensions.depth) { // 存储到3D数组 this.grid3D[arrayX][arrayY][arrayZ] = { occupied: isOccupied, entity: entity, exists: true, originalIndex: { x: gridIndex.x, y: gridIndex.y, z: gridIndex.z } // 保存原始索引 }; // 建立映射关系 const gridKey = `${arrayX},${arrayY},${arrayZ}`; this.gridEntityMap.set(gridKey, entity); this.indexToEntityMap.set(`${arrayX}_${arrayY}_${arrayZ}`, entity); mappedCount++; if (isOccupied) { occupiedCount++; } else { freeCount++; } } else { console.warn(`网格索引超出范围: 原始(${gridIndex.x}, ${gridIndex.y}, ${gridIndex.z}) -> 数组(${arrayX}, ${arrayY}, ${arrayZ})`); } } console.log(`3D栅格结构构建完成:`); console.log(`- 栅格维度: ${this.gridDimensions.width} x ${this.gridDimensions.height} x ${this.gridDimensions.depth}`); console.log(`- 索引偏移: (${this.indexOffset.x}, ${this.indexOffset.y}, ${this.indexOffset.z})`); console.log(`- 映射实体数: ${mappedCount}`); console.log(`- 占用栅格: ${occupiedCount}`); console.log(`- 空闲栅格: ${freeCount}`); console.log(`- 占用率: ${mappedCount > 0 ? ((occupiedCount / mappedCount) * 100).toFixed(1) : 0}%`); } // ================================ // 通过最近邻查找获取航线起点和终点对应的3D栅格索引 // ================================ async findWaypointGridIndices() { console.log('开始通过最近邻查找航线起点和终点对应的栅格索引...'); // 获取航线航点 const waypoints = this.getFlightWaypoints(); if (!waypoints || waypoints.length < 2) { console.warn('航线航点不足,无法确定起点和终点'); return { startIndex: null, endIndex: null }; } const startWaypoint = waypoints[0]; const endWaypoint = waypoints[waypoints.length - 1]; console.log('航线起点位置:', startWaypoint.position); console.log('航线终点位置:', endWaypoint.position); // 使用最近邻搜索找到对应的栅格索引 const startIndex = this.findNearestGridIndex(startWaypoint.position); const endIndex = this.findNearestGridIndex(endWaypoint.position); if (!startIndex) { console.error('无法找到航线起点对应的栅格索引'); return { startIndex: null, endIndex: null }; } if (!endIndex) { console.error('无法找到航线终点对应的栅格索引'); return { startIndex: null, endIndex: null }; } // 验证找到的栅格索引是否可通行 if (this.grid3D[startIndex.x][startIndex.y][startIndex.z].occupied) { console.warn('起点栅格被占用,尝试寻找附近的空闲栅格...'); const freeStartIndex = this.findNearestFreeGrid(startIndex); if (freeStartIndex) { console.log(`找到起点附近的空闲栅格: (${freeStartIndex.x}, ${freeStartIndex.y}, ${freeStartIndex.z})`); startIndex.x = freeStartIndex.x; startIndex.y = freeStartIndex.y; startIndex.z = freeStartIndex.z; } else { console.error('起点附近没有可通行的栅格'); return { startIndex: null, endIndex: null }; } } if (this.grid3D[endIndex.x][endIndex.y][endIndex.z].occupied) { console.warn('终点栅格被占用,尝试寻找附近的空闲栅格...'); const freeEndIndex = this.findNearestFreeGrid(endIndex); if (freeEndIndex) { console.log(`找到终点附近的空闲栅格: (${freeEndIndex.x}, ${freeEndIndex.y}, ${freeEndIndex.z})`); endIndex.x = freeEndIndex.x; endIndex.y = freeEndIndex.y; endIndex.z = freeEndIndex.z; } else { console.error('终点附近没有可通行的栅格'); return { startIndex: null, endIndex: null }; } } console.log(`成功找到起点栅格索引: (${startIndex.x}, ${startIndex.y}, ${startIndex.z})`); console.log(`成功找到终点栅格索引: (${endIndex.x}, ${endIndex.y}, ${endIndex.z})`); return { startIndex, endIndex }; } // ================================ // 最近邻搜索:找到距离指定世界坐标最近的栅格索引 - 直接使用properties数据 // ================================ findNearestGridIndex(targetPosition) { let nearestIndex = null; let minDistance = Infinity; let nearestEntity = null; // 直接遍历所有网格实体,使用properties中的gridIndex for (const entity of this.occupancyGrid.gridEntities) { if (entity.properties && entity.properties.gridIndex && entity.position) { try { // 获取栅格实体的位置 const entityPosition = entity.position.getValue(this.viewer.clock.currentTime); // 计算距离 const distance = Cesium.Cartesian3.distance(targetPosition, entityPosition); if (distance < minDistance) { minDistance = distance; nearestEntity = entity; // 直接从properties获取gridIndex,并转换为数组索引 const originalIndex = entity.properties.gridIndex.getValue(); nearestIndex = { x: originalIndex.x - this.indexOffset.x, y: originalIndex.y - this.indexOffset.y, z: originalIndex.z - this.indexOffset.z, original: originalIndex // 保存原始索引以便调试 }; } } catch (error) { console.warn('处理网格实体时出错:', error); } } } if (nearestIndex) { console.log(`找到最近栅格索引: 数组索引(${nearestIndex.x}, ${nearestIndex.y}, ${nearestIndex.z}), 原始索引(${nearestIndex.original.x}, ${nearestIndex.original.y}, ${nearestIndex.original.z}), 距离: ${minDistance.toFixed(2)}m`); // 验证找到的索引是否有效 if (!this.isValidGridIndex(nearestIndex)) { console.error('找到的栅格索引无效'); return null; } // 移除original属性,只返回数组索引 return { x: nearestIndex.x, y: nearestIndex.y, z: nearestIndex.z }; } else { console.warn('未找到最近的栅格索引'); return null; } } // ================================ // 寻找指定栅格索引附近的空闲(可通行)栅格 // ================================ findNearestFreeGrid(centerIndex) { const searchRadius = 3; // 搜索半径 for (let radius = 1; radius <= searchRadius; radius++) { for (let dx = -radius; dx <= radius; dx++) { for (let dy = -radius; dy <= radius; dy++) { for (let dz = -radius; dz <= radius; dz++) { const x = centerIndex.x + dx; const y = centerIndex.y + dy; const z = centerIndex.z + dz; if (this.isValidGridIndex({ x, y, z })) { const cell = this.grid3D[x][y][z]; if (cell.exists && !cell.occupied) { return { x, y, z }; } } } } } } return null; // 未找到空闲栅格 } // ================================ // 获取航线航点 // ================================ getFlightWaypoints() { // 尝试从UAVApp获取航线航点 if (window.uavApp && window.uavApp.waypoints && window.uavApp.waypoints.length > 0) { return window.uavApp.waypoints; } console.warn('未找到航线航点,将无法进行基于航线的路径规划'); return []; } // ================================ // 在三维数组上执行A*路径规划算法 // ================================ aStarOnGrid3D(startIndex, endIndex) { console.log('开始在三维数组上执行A*路径规划算法...'); console.log(`起点: (${startIndex.x}, ${startIndex.y}, ${startIndex.z})`); console.log(`终点: (${endIndex.x}, ${endIndex.y}, ${endIndex.z})`); // 清理算法数据结构 this.openList = []; this.closedList = []; // 创建起始节点 const startNode = { x: startIndex.x, y: startIndex.y, z: startIndex.z, g: 0, h: this.calculateHeuristic(startIndex, endIndex), f: 0, parent: null }; startNode.f = startNode.g + startNode.h; this.openList.push(startNode); let iterations = 0; const maxIterations = 50000; let verticalMoves = 0; while (this.openList.length > 0 && iterations < maxIterations) { iterations++; // 找到f值最小的节点 this.openList.sort((a, b) => a.f - b.f); const currentNode = this.openList.shift(); // 如果到达终点 if (currentNode.x === endIndex.x && currentNode.y === endIndex.y && currentNode.z === endIndex.z) { console.log(`A*算法完成,迭代次数:${iterations}`); console.log(`垂直移动次数:${verticalMoves}`); return this.reconstructPath(currentNode); } // 将当前节点移到关闭列表 this.closedList.push(currentNode); // 获取邻居节点 const neighbors = this.getNeighbors(currentNode); for (const neighbor of neighbors) { // 跳过已在关闭列表中的节点 if (this.isInClosedList(neighbor)) { continue; } // 检查是否可通行 if (!this.isTraversable(neighbor.x, neighbor.y, neighbor.z)) { continue; } // 计算移动成本 const moveCost = this.getMoveCost(currentNode, neighbor); const tentativeG = currentNode.g + moveCost; // 统计垂直移动 if (neighbor.z !== currentNode.z) { verticalMoves++; } // 检查是否已在开放列表中 const existingNode = this.findInOpenList(neighbor); if (existingNode) { if (tentativeG < existingNode.g) { existingNode.g = tentativeG; existingNode.h = this.calculateHeuristic(neighbor, endIndex); existingNode.f = existingNode.g + existingNode.h; existingNode.parent = currentNode; } } else { const newNode = { x: neighbor.x, y: neighbor.y, z: neighbor.z, g: tentativeG, h: this.calculateHeuristic(neighbor, endIndex), f: 0, parent: currentNode }; newNode.f = newNode.g + newNode.h; this.openList.push(newNode); } } // 每1000次迭代输出一次进度 if (iterations % 1000 === 0) { const openCount = this.openList.length; const closedCount = this.closedList.length; console.log(`A*算法进度: ${iterations}次迭代, 开放列表: ${openCount}, 关闭列表: ${closedCount}`); } } console.warn(`A*算法未找到路径,迭代次数:${iterations}`); return null; } // ================================ // 重构路径 // ================================ reconstructPath(endNode) { const path = []; let currentNode = endNode; while (currentNode) { path.unshift({ x: currentNode.x, y: currentNode.y, z: currentNode.z }); currentNode = currentNode.parent; } console.log(`路径重构完成,路径长度: ${path.length}个节点`); return path; } // ================================ // 计算启发式函数(3D曼哈顿距离) // ================================ calculateHeuristic(index1, index2) { const dx = Math.abs(index1.x - index2.x); const dy = Math.abs(index1.y - index2.y); const dz = Math.abs(index1.z - index2.z); // 使用3D曼哈顿距离,给垂直移动适当权重 return dx + dy + (dz * this.config.verticalWeight); } // ================================ // 获取邻居节点(3D:26连通性) // ================================ getNeighbors(currentNode) { const neighbors = []; // 3D搜索:包括所有26个方向的邻居 for (let dx = -1; dx <= 1; dx++) { for (let dy = -1; dy <= 1; dy++) { for (let dz = -1; dz <= 1; dz++) { if (dx === 0 && dy === 0 && dz === 0) continue; // 如果不允许对角线移动,只使用6连通性 if (!this.config.enableDiagonal) { const isDirectional = (Math.abs(dx) + Math.abs(dy) + Math.abs(dz)) === 1; if (!isDirectional) continue; } const newX = currentNode.x + dx; const newY = currentNode.y + dy; const newZ = currentNode.z + dz; // 检查边界 if (this.isValidGridIndex({ x: newX, y: newY, z: newZ })) { neighbors.push({ x: newX, y: newY, z: newZ }); } } } } return neighbors; } // ================================ // A*算法辅助方法 // ================================ isInClosedList(node) { return this.closedList.some(n => n.x === node.x && n.y === node.y && n.z === node.z ); } findInOpenList(node) { return this.openList.find(n => n.x === node.x && n.y === node.y && n.z === node.z ); } // ================================ // 检查位置是否可通行 // ================================ isTraversable(x, y, z) { if (!this.isValidGridIndex({ x, y, z })) { return false; } const cell = this.grid3D[x][y][z]; // 如果该位置不存在网格实体,则不可通行 if (!cell.exists) { return false; } // 如果有实体但未被占用,可通行 return !cell.occupied; } // ================================ // 计算移动成本 // ================================ getMoveCost(fromNode, toNode) { const dx = Math.abs(toNode.x - fromNode.x); const dy = Math.abs(toNode.y - fromNode.y); const dz = Math.abs(toNode.z - fromNode.z); // 计算基础移动成本 let baseCost; if (dx && dy && dz) { baseCost = Math.sqrt(3); // 3D对角线移动 } else if ((dx && dy) || (dx && dz) || (dy && dz)) { baseCost = Math.sqrt(2); // 2D对角线移动 } else { baseCost = 1; // 直线移动 } // 如果是垂直移动,应用垂直权重 if (dz > 0) { baseCost *= this.config.verticalWeight; } return baseCost; } // ================================ // 通过索引找到对应的3D占用网格单元,复制并修改为黄色 - 使用properties数据 // ================================ visualizePathByGridCopy(pathIndices, startIndex, endIndex) { console.log('开始通过复制网格单元可视化路径...'); console.log('路径索引序列:', pathIndices.map(p => `(${p.x},${p.y},${p.z})`).join(' -> ')); console.log(`路径总长度: ${pathIndices.length}个栅格索引`); // 清除之前的路径 this.clearPath(); let copiedCount = 0; let missingCount = 0; for (let i = 0; i < pathIndices.length; i++) { const gridIndex = pathIndices[i]; // 从3D数组中获取对应的网格单元 const cell = this.grid3D[gridIndex.x][gridIndex.y][gridIndex.z]; if (cell.exists && cell.entity) { // 获取原始网格实体的信息 const sourceEntity = cell.entity; const sourcePosition = sourceEntity.position.getValue(this.viewer.clock.currentTime); const sourceDimensions = sourceEntity.box.dimensions.getValue(); // 获取原始索引(用于显示和命名) const originalIndex = cell.originalIndex || { x: gridIndex.x + this.indexOffset.x, y: gridIndex.y + this.indexOffset.y, z: gridIndex.z + this.indexOffset.z }; // 判断是否为起点或终点 const isStartPoint = (gridIndex.x === startIndex.x && gridIndex.y === startIndex.y && gridIndex.z === startIndex.z); const isEndPoint = (gridIndex.x === endIndex.x && gridIndex.y === endIndex.y && gridIndex.z === endIndex.z); // 根据点的类型选择颜色和名称 let entityColor, outlineColor, entityName, pointType; if (isStartPoint) { entityColor = this.config.startPointColor; outlineColor = this.config.startEndOutlineColor; entityName = `StartPoint_${originalIndex.x}_${originalIndex.y}_${originalIndex.z}`; pointType = '起点'; } else if (isEndPoint) { entityColor = this.config.endPointColor; outlineColor = this.config.startEndOutlineColor; entityName = `EndPoint_${originalIndex.x}_${originalIndex.y}_${originalIndex.z}`; pointType = '终点'; } else { entityColor = this.config.pathColor; outlineColor = this.config.pathOutlineColor; entityName = `PathGrid_${originalIndex.x}_${originalIndex.y}_${originalIndex.z}`; pointType = '路径'; } // 复制网格单元,修改颜色 const pathEntity = this.viewer.entities.add({ name: entityName, position: sourcePosition, box: { dimensions: sourceDimensions, material: entityColor, outline: true, outlineColor: outlineColor, outlineWidth: this.config.outlineWidth }, properties: { isPathGrid: true, gridIndex: originalIndex, // 使用原始索引 arrayIndex: { x: gridIndex.x, y: gridIndex.y, z: gridIndex.z }, // 保存数组索引 sourceEntityId: sourceEntity.id, pathStep: copiedCount, pointType: pointType } }); this.pathEntities.push(pathEntity); copiedCount++; console.log(`复制网格单元 数组索引[${gridIndex.x}, ${gridIndex.y}, ${gridIndex.z}] 原始索引[${originalIndex.x}, ${originalIndex.y}, ${originalIndex.z}] -> ${pointType}网格`); } else { missingCount++; console.warn(`网格索引 [${gridIndex.x}, ${gridIndex.y}, ${gridIndex.z}] 对应的网格单元不存在`); } } // 设置路径状态 this.hasActivePath = copiedCount > 0; // 如果启用了只显示路径模式,隐藏其他网格 if (this.showOnlyPath && this.hasActivePath) { this.updateGridVisibility(); } console.log(`路径可视化完成:`); console.log(`- 成功复制网格: ${copiedCount}个`); console.log(`- 缺失网格: ${missingCount}个`); // 显示起点和终点的原始索引 const startOriginalIndex = this.grid3D[startIndex.x][startIndex.y][startIndex.z].originalIndex || { x: startIndex.x + this.indexOffset.x, y: startIndex.y + this.indexOffset.y, z: startIndex.z + this.indexOffset.z }; const endOriginalIndex = this.grid3D[endIndex.x][endIndex.y][endIndex.z].originalIndex || { x: endIndex.x + this.indexOffset.x, y: endIndex.y + this.indexOffset.y, z: endIndex.z + this.indexOffset.z }; console.log(`- 起点: 数组索引(${startIndex.x}, ${startIndex.y}, ${startIndex.z}) 原始索引(${startOriginalIndex.x}, ${startOriginalIndex.y}, ${startOriginalIndex.z}) -> 蓝色`); console.log(`- 终点: 数组索引(${endIndex.x}, ${endIndex.y}, ${endIndex.z}) 原始索引(${endOriginalIndex.x}, ${endOriginalIndex.y}, ${endOriginalIndex.z}) -> 蓝色`); if (missingCount > 0) { console.warn(`⚠️ 有${missingCount}个路径索引对应的网格单元缺失`); } // 统计路径的3D特征 this.analyzePathCharacteristics(pathIndices); } // ================================ // 分析路径特征 // ================================ analyzePathCharacteristics(pathIndices) { if (!pathIndices || pathIndices.length === 0) { return; } console.log('=== 路径特征分析 ==='); // 计算路径的空间范围 const xValues = pathIndices.map(p => p.x); const yValues = pathIndices.map(p => p.y); const zValues = pathIndices.map(p => p.z); const xRange = [Math.min(...xValues), Math.max(...xValues)]; const yRange = [Math.min(...yValues), Math.max(...yValues)]; const zRange = [Math.min(...zValues), Math.max(...zValues)]; console.log(`X轴范围: [${xRange[0]}, ${xRange[1]}], 跨度: ${xRange[1] - xRange[0]}`); console.log(`Y轴范围: [${yRange[0]}, ${yRange[1]}], 跨度: ${yRange[1] - yRange[0]}`); console.log(`Z轴范围: [${zRange[0]}, ${zRange[1]}], 跨度: ${zRange[1] - zRange[0]}`); // 统计移动类型 let verticalMoves = 0; let horizontalMoves = 0; let diagonalMoves = 0; for (let i = 1; i < pathIndices.length; i++) { const prev = pathIndices[i - 1]; const curr = pathIndices[i]; const dx = Math.abs(curr.x - prev.x); const dy = Math.abs(curr.y - prev.y); const dz = Math.abs(curr.z - prev.z); if (dz > 0) verticalMoves++; if (dx > 0 || dy > 0) horizontalMoves++; if ((dx && dy) || (dx && dz) || (dy && dz)) diagonalMoves++; } const totalMoves = pathIndices.length - 1; console.log(`总移动步数: ${totalMoves}`); console.log(`垂直移动: ${verticalMoves} (${(verticalMoves/totalMoves*100).toFixed(1)}%)`); console.log(`水平移动: ${horizontalMoves} (${(horizontalMoves/totalMoves*100).toFixed(1)}%)`); console.log(`对角移动: ${diagonalMoves} (${(diagonalMoves/totalMoves*100).toFixed(1)}%)`); // 检查3D路径特征 const hasVerticalVariation = (zRange[1] - zRange[0]) > 0; if (hasVerticalVariation) { console.log('✅ 路径包含3D垂直变化,成功进行了立体路径规划'); } else { console.log('ℹ️ 路径没有垂直变化,为平面路径'); } console.log('=== 分析完成 ==='); } // ================================ // 清除路径 // ================================ clearPath() { // 从场景中移除路径网格实体 for (const pathEntity of this.pathEntities) { if (this.viewer.entities.contains(pathEntity)) { this.viewer.entities.remove(pathEntity); } } this.pathEntities = []; this.hasActivePath = false; // 重置路径状态 // 恢复所有网格的显示 // this.updateGridVisibility(); console.log('路径已清除,所有路径网格(包括起点、终点)已从场景中移除'); } // ================================ // 更新路径信息UI // ================================ updatePathInfo(pathIndices) { const pathLengthElement = document.getElementById('pathLength'); const pathStatusElement = document.getElementById('pathStatus'); if (pathLengthElement) { pathLengthElement.textContent = pathIndices.length; } if (pathStatusElement) { pathStatusElement.textContent = '规划完成'; } } // ================================ // 设置只显示路径模式 // ================================ setShowOnlyPath(showOnly) { this.showOnlyPath = showOnly; this.updateGridVisibility(); console.log(`只显示路径模式: ${showOnly ? '启用' : '禁用'}`); } // ================================ // 更新网格显示状态 // ================================ updateGridVisibility() { if (!this.occupancyGrid.gridEntities.length) { return; } // 控制原始占用网格的显示 for (const entity of this.occupancyGrid.gridEntities) { if (entity.show !== undefined) { if (this.showOnlyPath && this.hasActivePath) { // 只显示路径模式:隐藏原始网格 entity.show = false; } else { // 正常显示模式:显示所有原始网格 entity.show = true; } } } // 控制路径网格的显示(黄色路径网格始终显示,除非被清除) for (const pathEntity of this.pathEntities) { if (pathEntity.show !== undefined) { pathEntity.show = true; // 路径网格始终显示 } } const pathGridCount = this.pathEntities.length; const originalGridCount = this.occupancyGrid.gridEntities.length; console.log(`网格显示状态已更新: ${this.showOnlyPath ? '只显示路径' : '显示全部'}`); console.log(`当前显示: 原始网格 ${this.showOnlyPath && this.hasActivePath ? 0 : originalGridCount} 个, 路径网格 ${pathGridCount} 个`); } // ================================ // 验证栅格索引是否有效 // ================================ isValidGridIndex(index) { return index && typeof index.x === 'number' && typeof index.y === 'number' && typeof index.z === 'number' && index.x >= 0 && index.x < this.gridDimensions.width && index.y >= 0 && index.y < this.gridDimensions.height && index.z >= 0 && index.z < this.gridDimensions.depth; } // ================================ // 首先显示起点和终点(蓝色) // ================================ visualizeStartEndPoints(startIndex, endIndex) { console.log('开始显示起点和终点...'); // 清除之前的路径(如果有) this.clearPath(); // 显示起点(蓝色) this.createPointVisualization(startIndex, 'start'); // 显示终点(蓝色) this.createPointVisualization(endIndex, 'end'); console.log(`起点和终点已显示为蓝色:`); console.log(`- 起点: (${startIndex.x}, ${startIndex.y}, ${startIndex.z})`); console.log(`- 终点: (${endIndex.x}, ${endIndex.y}, ${endIndex.z})`); } // ================================ // 创建单个点的可视化(起点或终点) - 使用properties数据 // ================================ createPointVisualization(gridIndex, pointType) { // 从3D数组中获取对应的网格单元 const cell = this.grid3D[gridIndex.x][gridIndex.y][gridIndex.z]; if (cell.exists && cell.entity) { // 获取原始网格实体的信息 const sourceEntity = cell.entity; const sourcePosition = sourceEntity.position.getValue(this.viewer.clock.currentTime); const sourceDimensions = sourceEntity.box.dimensions.getValue(); // 获取原始索引(用于显示和命名) const originalIndex = cell.originalIndex || { x: gridIndex.x + this.indexOffset.x, y: gridIndex.y + this.indexOffset.y, z: gridIndex.z + this.indexOffset.z }; // 根据点类型设置属性 let entityColor, outlineColor, entityName, pointTypeChinese; if (pointType === 'start') { entityColor = this.config.startPointColor; outlineColor = this.config.startEndOutlineColor; entityName = `StartPoint_${originalIndex.x}_${originalIndex.y}_${originalIndex.z}`; pointTypeChinese = '起点'; } else if (pointType === 'end') { entityColor = this.config.endPointColor; outlineColor = this.config.startEndOutlineColor; entityName = `EndPoint_${originalIndex.x}_${originalIndex.y}_${originalIndex.z}`; pointTypeChinese = '终点'; } // 创建点的可视化实体 const pointEntity = this.viewer.entities.add({ name: entityName, position: sourcePosition, box: { dimensions: sourceDimensions, material: entityColor, outline: true, outlineColor: outlineColor, outlineWidth: this.config.outlineWidth }, properties: { isPathGrid: true, gridIndex: originalIndex, // 使用原始索引 arrayIndex: { x: gridIndex.x, y: gridIndex.y, z: gridIndex.z }, // 保存数组索引 sourceEntityId: sourceEntity.id, pointType: pointTypeChinese } }); this.pathEntities.push(pointEntity); console.log(`创建${pointTypeChinese}可视化: 数组索引[${gridIndex.x}, ${gridIndex.y}, ${gridIndex.z}] 原始索引[${originalIndex.x}, ${originalIndex.y}, ${originalIndex.z}]`); } else { console.warn(`网格索引 [${gridIndex.x}, ${gridIndex.y}, ${gridIndex.z}] 对应的网格单元不存在,无法创建${pointType}可视化`); } } // ================================ // 设置路径实体透明度 // ================================ setPathAlpha(alpha) { if (alpha < 0.1 || alpha > 1.0) { console.warn('透明度值应该在0.1到1.0之间'); return; } console.log(`设置路径透明度为: ${alpha}`); // 更新配置中的颜色透明度 this.config.pathColor = Cesium.Color.YELLOW.withAlpha(alpha); this.config.startPointColor = Cesium.Color.BLUE.withAlpha(alpha); this.config.endPointColor = Cesium.Color.BLUE.withAlpha(alpha); // 更新所有现有的路径实体透明度 let updatedCount = 0; for (const pathEntity of this.pathEntities) { if (pathEntity.box && pathEntity.box.material) { try { // 获取实体的类型 const pointType = pathEntity.properties && pathEntity.properties.pointType ? pathEntity.properties.pointType.getValue() : '路径'; // 根据实体类型设置对应的颜色 let newColor; if (pointType === '起点' || pointType === '终点') { newColor = Cesium.Color.BLUE.withAlpha(alpha); } else { newColor = Cesium.Color.YELLOW.withAlpha(alpha); } // 更新实体的材质颜色 pathEntity.box.material = newColor; updatedCount++; } catch (error) { console.warn('更新路径实体透明度时出错:', error); } } } console.log(`成功更新${updatedCount}个路径实体的透明度`); } // ================================ // 获取当前路径透明度 // ================================ getPathAlpha() { // 从配置中获取当前透明度 if (this.config.pathColor && this.config.pathColor.alpha !== undefined) { return this.config.pathColor.alpha; } return 0.8; // 默认透明度 } } // ================================ // 导出模块(如果使用模块系统) // ================================ if (typeof module !== 'undefined' && module.exports) { module.exports = PathPlanning; } export default PathPlanning