无人机管理后台前端(已迁走)
罗广辉
2025-10-11 02409bfbe15f22fc3b5dccadabfd860a660a49d9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
// ================================
// 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