@esengine/pathfinding 11.0.0 → 12.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,50 +1,43 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
-
6
- // src/core/IPathfinding.ts
7
- function createPoint(x, y) {
8
- return {
9
- x,
10
- y
11
- };
12
- }
13
- __name(createPoint, "createPoint");
14
- var EMPTY_PATH_RESULT = {
15
- found: false,
16
- path: [],
17
- cost: 0,
18
- nodesSearched: 0
19
- };
20
- function manhattanDistance(a, b) {
21
- return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
22
- }
23
- __name(manhattanDistance, "manhattanDistance");
24
- function euclideanDistance(a, b) {
25
- const dx = a.x - b.x;
26
- const dy = a.y - b.y;
27
- return Math.sqrt(dx * dx + dy * dy);
28
- }
29
- __name(euclideanDistance, "euclideanDistance");
30
- function chebyshevDistance(a, b) {
31
- return Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y));
32
- }
33
- __name(chebyshevDistance, "chebyshevDistance");
34
- function octileDistance(a, b) {
35
- const dx = Math.abs(a.x - b.x);
36
- const dy = Math.abs(a.y - b.y);
37
- const D = 1;
38
- const D2 = Math.SQRT2;
39
- return D * (dx + dy) + (D2 - 2 * D) * Math.min(dx, dy);
40
- }
41
- __name(octileDistance, "octileDistance");
42
- var DEFAULT_PATHFINDING_OPTIONS = {
43
- maxNodes: 1e4,
44
- heuristicWeight: 1,
45
- allowDiagonal: true,
46
- avoidCorners: true
47
- };
1
+ import {
2
+ CatmullRomSmoother,
3
+ CombinedSmoother,
4
+ DEFAULT_GRID_OPTIONS,
5
+ DEFAULT_PATHFINDING_OPTIONS,
6
+ DEFAULT_PATH_CACHE_CONFIG,
7
+ DIRECTIONS_4,
8
+ DIRECTIONS_8,
9
+ EMPTY_PATH_RESULT,
10
+ GridMap,
11
+ GridNode,
12
+ IncrementalAStarPathfinder,
13
+ IndexedBinaryHeap,
14
+ LineOfSightSmoother,
15
+ ObstacleChangeManager,
16
+ PathCache,
17
+ PathValidator,
18
+ bresenhamLineOfSight,
19
+ chebyshevDistance,
20
+ createCatmullRomSmoother,
21
+ createCombinedSmoother,
22
+ createGridMap,
23
+ createIncrementalAStarPathfinder,
24
+ createLineOfSightSmoother,
25
+ createObstacleChangeManager,
26
+ createPathCache,
27
+ createPathValidator,
28
+ createPoint,
29
+ euclideanDistance,
30
+ manhattanDistance,
31
+ octileDistance,
32
+ raycastLineOfSight
33
+ } from "./chunk-TPT7Q3E3.js";
34
+ import {
35
+ DEFAULT_REPLANNING_CONFIG,
36
+ EMPTY_PROGRESS,
37
+ PathfindingState,
38
+ __name,
39
+ __publicField
40
+ } from "./chunk-GTFFYRZM.js";
48
41
 
49
42
  // src/core/BinaryHeap.ts
50
43
  var _BinaryHeap = class _BinaryHeap {
@@ -182,7 +175,7 @@ var _AStarPathfinder = class _AStarPathfinder {
182
175
  __publicField(this, "nodeCache", /* @__PURE__ */ new Map());
183
176
  __publicField(this, "openList");
184
177
  this.map = map;
185
- this.openList = new BinaryHeap((a, b) => a.f - b.f);
178
+ this.openList = new IndexedBinaryHeap((a, b) => a.f - b.f);
186
179
  }
187
180
  /**
188
181
  * @zh 查找路径
@@ -282,7 +275,8 @@ var _AStarPathfinder = class _AStarPathfinder {
282
275
  f: Infinity,
283
276
  parent: null,
284
277
  closed: false,
285
- opened: false
278
+ opened: false,
279
+ heapIndex: -1
286
280
  };
287
281
  this.nodeCache.set(node.id, astarNode);
288
282
  }
@@ -296,9 +290,10 @@ var _AStarPathfinder = class _AStarPathfinder {
296
290
  const path = [];
297
291
  let current = endNode;
298
292
  while (current) {
299
- path.unshift(current.node.position);
293
+ path.push(current.node.position);
300
294
  current = current.parent;
301
295
  }
296
+ path.reverse();
302
297
  return {
303
298
  found: true,
304
299
  path,
@@ -314,283 +309,1643 @@ function createAStarPathfinder(map) {
314
309
  }
315
310
  __name(createAStarPathfinder, "createAStarPathfinder");
316
311
 
317
- // src/grid/GridMap.ts
318
- var _GridNode = class _GridNode {
319
- constructor(x, y, walkable = true, cost = 1) {
320
- __publicField(this, "id");
321
- __publicField(this, "position");
322
- __publicField(this, "x");
323
- __publicField(this, "y");
324
- __publicField(this, "cost");
325
- __publicField(this, "walkable");
326
- this.x = x;
327
- this.y = y;
328
- this.id = `${x},${y}`;
329
- this.position = createPoint(x, y);
330
- this.walkable = walkable;
331
- this.cost = cost;
312
+ // src/core/GridPathfinder.ts
313
+ var CLOSED_FLAG = 1;
314
+ var OPENED_FLAG = 2;
315
+ var BACKWARD_CLOSED = 4;
316
+ var BACKWARD_OPENED = 8;
317
+ var _a;
318
+ var GridState = (_a = class {
319
+ constructor(width, height, bidirectional = false) {
320
+ __publicField(this, "size");
321
+ __publicField(this, "width");
322
+ __publicField(this, "g");
323
+ __publicField(this, "f");
324
+ __publicField(this, "flags");
325
+ __publicField(this, "parent");
326
+ __publicField(this, "heapIndex");
327
+ __publicField(this, "version");
328
+ __publicField(this, "currentVersion", 1);
329
+ // 双向搜索额外状态
330
+ __publicField(this, "gBack", null);
331
+ __publicField(this, "fBack", null);
332
+ __publicField(this, "parentBack", null);
333
+ __publicField(this, "heapIndexBack", null);
334
+ this.width = width;
335
+ this.size = width * height;
336
+ this.g = new Float32Array(this.size);
337
+ this.f = new Float32Array(this.size);
338
+ this.flags = new Uint8Array(this.size);
339
+ this.parent = new Int32Array(this.size);
340
+ this.heapIndex = new Int32Array(this.size);
341
+ this.version = new Uint32Array(this.size);
342
+ if (bidirectional) {
343
+ this.gBack = new Float32Array(this.size);
344
+ this.fBack = new Float32Array(this.size);
345
+ this.parentBack = new Int32Array(this.size);
346
+ this.heapIndexBack = new Int32Array(this.size);
347
+ }
348
+ }
349
+ reset() {
350
+ this.currentVersion++;
351
+ if (this.currentVersion > 4294967295) {
352
+ this.version.fill(0);
353
+ this.currentVersion = 1;
354
+ }
355
+ }
356
+ isInit(i) {
357
+ return this.version[i] === this.currentVersion;
358
+ }
359
+ init(i) {
360
+ if (!this.isInit(i)) {
361
+ this.g[i] = Infinity;
362
+ this.f[i] = Infinity;
363
+ this.flags[i] = 0;
364
+ this.parent[i] = -1;
365
+ this.heapIndex[i] = -1;
366
+ if (this.gBack) {
367
+ this.gBack[i] = Infinity;
368
+ this.fBack[i] = Infinity;
369
+ this.parentBack[i] = -1;
370
+ this.heapIndexBack[i] = -1;
371
+ }
372
+ this.version[i] = this.currentVersion;
373
+ }
374
+ }
375
+ // Forward
376
+ getG(i) {
377
+ return this.isInit(i) ? this.g[i] : Infinity;
378
+ }
379
+ setG(i, v) {
380
+ this.init(i);
381
+ this.g[i] = v;
382
+ }
383
+ getF(i) {
384
+ return this.isInit(i) ? this.f[i] : Infinity;
385
+ }
386
+ setF(i, v) {
387
+ this.init(i);
388
+ this.f[i] = v;
389
+ }
390
+ getParent(i) {
391
+ return this.isInit(i) ? this.parent[i] : -1;
392
+ }
393
+ setParent(i, v) {
394
+ this.init(i);
395
+ this.parent[i] = v;
396
+ }
397
+ getHeapIndex(i) {
398
+ return this.isInit(i) ? this.heapIndex[i] : -1;
399
+ }
400
+ setHeapIndex(i, v) {
401
+ this.init(i);
402
+ this.heapIndex[i] = v;
403
+ }
404
+ isClosed(i) {
405
+ return this.isInit(i) && (this.flags[i] & CLOSED_FLAG) !== 0;
406
+ }
407
+ setClosed(i) {
408
+ this.init(i);
409
+ this.flags[i] |= CLOSED_FLAG;
410
+ }
411
+ isOpened(i) {
412
+ return this.isInit(i) && (this.flags[i] & OPENED_FLAG) !== 0;
413
+ }
414
+ setOpened(i) {
415
+ this.init(i);
416
+ this.flags[i] |= OPENED_FLAG;
417
+ }
418
+ // Backward
419
+ getGBack(i) {
420
+ return this.isInit(i) ? this.gBack[i] : Infinity;
421
+ }
422
+ setGBack(i, v) {
423
+ this.init(i);
424
+ this.gBack[i] = v;
425
+ }
426
+ getFBack(i) {
427
+ return this.isInit(i) ? this.fBack[i] : Infinity;
428
+ }
429
+ setFBack(i, v) {
430
+ this.init(i);
431
+ this.fBack[i] = v;
432
+ }
433
+ getParentBack(i) {
434
+ return this.isInit(i) ? this.parentBack[i] : -1;
435
+ }
436
+ setParentBack(i, v) {
437
+ this.init(i);
438
+ this.parentBack[i] = v;
439
+ }
440
+ getHeapIndexBack(i) {
441
+ return this.isInit(i) ? this.heapIndexBack[i] : -1;
442
+ }
443
+ setHeapIndexBack(i, v) {
444
+ this.init(i);
445
+ this.heapIndexBack[i] = v;
446
+ }
447
+ isClosedBack(i) {
448
+ return this.isInit(i) && (this.flags[i] & BACKWARD_CLOSED) !== 0;
449
+ }
450
+ setClosedBack(i) {
451
+ this.init(i);
452
+ this.flags[i] |= BACKWARD_CLOSED;
453
+ }
454
+ isOpenedBack(i) {
455
+ return this.isInit(i) && (this.flags[i] & BACKWARD_OPENED) !== 0;
456
+ }
457
+ setOpenedBack(i) {
458
+ this.init(i);
459
+ this.flags[i] |= BACKWARD_OPENED;
460
+ }
461
+ }, __name(_a, "GridState"), _a);
462
+ var _a2;
463
+ var GridHeap = (_a2 = class {
464
+ constructor(state, isBack = false) {
465
+ __publicField(this, "heap", []);
466
+ __publicField(this, "state");
467
+ __publicField(this, "isBack");
468
+ this.state = state;
469
+ this.isBack = isBack;
470
+ }
471
+ get size() {
472
+ return this.heap.length;
473
+ }
474
+ get isEmpty() {
475
+ return this.heap.length === 0;
476
+ }
477
+ getF(i) {
478
+ return this.isBack ? this.state.getFBack(i) : this.state.getF(i);
479
+ }
480
+ getHeapIndex(i) {
481
+ return this.isBack ? this.state.getHeapIndexBack(i) : this.state.getHeapIndex(i);
482
+ }
483
+ setHeapIndex(i, v) {
484
+ if (this.isBack) this.state.setHeapIndexBack(i, v);
485
+ else this.state.setHeapIndex(i, v);
486
+ }
487
+ push(i) {
488
+ this.setHeapIndex(i, this.heap.length);
489
+ this.heap.push(i);
490
+ this.bubbleUp(this.heap.length - 1);
491
+ }
492
+ pop() {
493
+ if (this.heap.length === 0) return -1;
494
+ const result = this.heap[0];
495
+ this.setHeapIndex(result, -1);
496
+ const last = this.heap.pop();
497
+ if (this.heap.length > 0) {
498
+ this.heap[0] = last;
499
+ this.setHeapIndex(last, 0);
500
+ this.sinkDown(0);
501
+ }
502
+ return result;
503
+ }
504
+ update(i) {
505
+ const pos = this.getHeapIndex(i);
506
+ if (pos >= 0 && pos < this.heap.length) {
507
+ this.bubbleUp(pos);
508
+ this.sinkDown(this.getHeapIndex(i));
509
+ }
510
+ }
511
+ clear() {
512
+ this.heap.length = 0;
513
+ }
514
+ bubbleUp(pos) {
515
+ const idx = this.heap[pos];
516
+ const f = this.getF(idx);
517
+ while (pos > 0) {
518
+ const pp = pos - 1 >> 1;
519
+ const pi = this.heap[pp];
520
+ if (f >= this.getF(pi)) break;
521
+ this.heap[pos] = pi;
522
+ this.setHeapIndex(pi, pos);
523
+ pos = pp;
524
+ }
525
+ this.heap[pos] = idx;
526
+ this.setHeapIndex(idx, pos);
527
+ }
528
+ sinkDown(pos) {
529
+ const len = this.heap.length;
530
+ const idx = this.heap[pos];
531
+ const f = this.getF(idx);
532
+ const half = len >> 1;
533
+ while (pos < half) {
534
+ const left = (pos << 1) + 1;
535
+ const right = left + 1;
536
+ let smallest = pos, smallestF = f;
537
+ const lf = this.getF(this.heap[left]);
538
+ if (lf < smallestF) {
539
+ smallest = left;
540
+ smallestF = lf;
541
+ }
542
+ if (right < len) {
543
+ const rf = this.getF(this.heap[right]);
544
+ if (rf < smallestF) smallest = right;
545
+ }
546
+ if (smallest === pos) break;
547
+ const si = this.heap[smallest];
548
+ this.heap[pos] = si;
549
+ this.setHeapIndex(si, pos);
550
+ pos = smallest;
551
+ }
552
+ this.heap[pos] = idx;
553
+ this.setHeapIndex(idx, pos);
554
+ }
555
+ }, __name(_a2, "GridHeap"), _a2);
556
+ var _GridPathfinder = class _GridPathfinder {
557
+ constructor(map, config) {
558
+ __publicField(this, "map");
559
+ __publicField(this, "mode");
560
+ __publicField(this, "state");
561
+ __publicField(this, "openList");
562
+ __publicField(this, "openListBack");
563
+ this.map = map;
564
+ this.mode = config?.mode ?? "fast";
565
+ const isBidir = this.mode === "bidirectional";
566
+ this.state = new GridState(map.width, map.height, isBidir);
567
+ this.openList = new GridHeap(this.state, false);
568
+ this.openListBack = isBidir ? new GridHeap(this.state, true) : null;
569
+ }
570
+ findPath(startX, startY, endX, endY, options) {
571
+ if (this.mode === "bidirectional") {
572
+ return this.findPathBidirectional(startX, startY, endX, endY, options);
573
+ }
574
+ return this.findPathUnidirectional(startX, startY, endX, endY, options);
575
+ }
576
+ findPathUnidirectional(startX, startY, endX, endY, options) {
577
+ const opts = options ? {
578
+ ...DEFAULT_PATHFINDING_OPTIONS,
579
+ ...options
580
+ } : DEFAULT_PATHFINDING_OPTIONS;
581
+ const { width, height } = this.map;
582
+ this.state.reset();
583
+ this.openList.clear();
584
+ if (!this.validate(startX, startY, endX, endY)) return EMPTY_PATH_RESULT;
585
+ const startIdx = startY * width + startX;
586
+ const endIdx = endY * width + endX;
587
+ if (startIdx === endIdx) {
588
+ return {
589
+ found: true,
590
+ path: [
591
+ {
592
+ x: startX,
593
+ y: startY
594
+ }
595
+ ],
596
+ cost: 0,
597
+ nodesSearched: 1
598
+ };
599
+ }
600
+ const hw = opts.heuristicWeight;
601
+ const h0 = this.map.heuristic({
602
+ x: startX,
603
+ y: startY
604
+ }, {
605
+ x: endX,
606
+ y: endY
607
+ }) * hw;
608
+ this.state.setG(startIdx, 0);
609
+ this.state.setF(startIdx, h0);
610
+ this.state.setOpened(startIdx);
611
+ this.openList.push(startIdx);
612
+ let searched = 0;
613
+ const maxNodes = opts.maxNodes;
614
+ const { allowDiagonal, avoidCorners, diagonalCost } = this.map["options"];
615
+ const nodes = this.map["nodes"];
616
+ const dx = allowDiagonal ? [
617
+ 0,
618
+ 1,
619
+ 1,
620
+ 1,
621
+ 0,
622
+ -1,
623
+ -1,
624
+ -1
625
+ ] : [
626
+ 0,
627
+ 1,
628
+ 0,
629
+ -1
630
+ ];
631
+ const dy = allowDiagonal ? [
632
+ -1,
633
+ -1,
634
+ 0,
635
+ 1,
636
+ 1,
637
+ 1,
638
+ 0,
639
+ -1
640
+ ] : [
641
+ -1,
642
+ 0,
643
+ 1,
644
+ 0
645
+ ];
646
+ const dirCount = dx.length;
647
+ while (!this.openList.isEmpty && searched < maxNodes) {
648
+ const cur = this.openList.pop();
649
+ this.state.setClosed(cur);
650
+ searched++;
651
+ if (cur === endIdx) {
652
+ return this.buildPath(startIdx, endIdx, searched);
653
+ }
654
+ const cx = cur % width, cy = cur / width | 0;
655
+ const curG = this.state.getG(cur);
656
+ for (let d = 0; d < dirCount; d++) {
657
+ const nx = cx + dx[d], ny = cy + dy[d];
658
+ if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue;
659
+ const neighbor = nodes[ny][nx];
660
+ if (!neighbor.walkable) continue;
661
+ if (avoidCorners && dx[d] !== 0 && dy[d] !== 0) {
662
+ if (!nodes[cy][cx + dx[d]].walkable || !nodes[cy + dy[d]][cx].walkable) continue;
663
+ }
664
+ const ni = ny * width + nx;
665
+ if (this.state.isClosed(ni)) continue;
666
+ const isDiag = dx[d] !== 0 && dy[d] !== 0;
667
+ const cost = isDiag ? neighbor.cost * diagonalCost : neighbor.cost;
668
+ const tentG = curG + cost;
669
+ if (!this.state.isOpened(ni)) {
670
+ const h = this.map.heuristic({
671
+ x: nx,
672
+ y: ny
673
+ }, {
674
+ x: endX,
675
+ y: endY
676
+ }) * hw;
677
+ this.state.setG(ni, tentG);
678
+ this.state.setF(ni, tentG + h);
679
+ this.state.setParent(ni, cur);
680
+ this.state.setOpened(ni);
681
+ this.openList.push(ni);
682
+ } else if (tentG < this.state.getG(ni)) {
683
+ const h = this.state.getF(ni) - this.state.getG(ni);
684
+ this.state.setG(ni, tentG);
685
+ this.state.setF(ni, tentG + h);
686
+ this.state.setParent(ni, cur);
687
+ this.openList.update(ni);
688
+ }
689
+ }
690
+ }
691
+ return {
692
+ found: false,
693
+ path: [],
694
+ cost: 0,
695
+ nodesSearched: searched
696
+ };
697
+ }
698
+ findPathBidirectional(startX, startY, endX, endY, options) {
699
+ const opts = options ? {
700
+ ...DEFAULT_PATHFINDING_OPTIONS,
701
+ ...options
702
+ } : DEFAULT_PATHFINDING_OPTIONS;
703
+ const { width, height } = this.map;
704
+ this.state.reset();
705
+ this.openList.clear();
706
+ this.openListBack.clear();
707
+ if (!this.validate(startX, startY, endX, endY)) return EMPTY_PATH_RESULT;
708
+ const startIdx = startY * width + startX;
709
+ const endIdx = endY * width + endX;
710
+ if (startIdx === endIdx) {
711
+ return {
712
+ found: true,
713
+ path: [
714
+ {
715
+ x: startX,
716
+ y: startY
717
+ }
718
+ ],
719
+ cost: 0,
720
+ nodesSearched: 1
721
+ };
722
+ }
723
+ const hw = opts.heuristicWeight;
724
+ const startPos = {
725
+ x: startX,
726
+ y: startY
727
+ };
728
+ const endPos = {
729
+ x: endX,
730
+ y: endY
731
+ };
732
+ const hf = this.map.heuristic(startPos, endPos) * hw;
733
+ this.state.setG(startIdx, 0);
734
+ this.state.setF(startIdx, hf);
735
+ this.state.setOpened(startIdx);
736
+ this.openList.push(startIdx);
737
+ const hb = this.map.heuristic(endPos, startPos) * hw;
738
+ this.state.setGBack(endIdx, 0);
739
+ this.state.setFBack(endIdx, hb);
740
+ this.state.setOpenedBack(endIdx);
741
+ this.openListBack.push(endIdx);
742
+ let searched = 0;
743
+ const maxNodes = opts.maxNodes;
744
+ let meetIdx = -1, bestCost = Infinity;
745
+ const { allowDiagonal, avoidCorners, diagonalCost } = this.map["options"];
746
+ const nodes = this.map["nodes"];
747
+ const dx = allowDiagonal ? [
748
+ 0,
749
+ 1,
750
+ 1,
751
+ 1,
752
+ 0,
753
+ -1,
754
+ -1,
755
+ -1
756
+ ] : [
757
+ 0,
758
+ 1,
759
+ 0,
760
+ -1
761
+ ];
762
+ const dy = allowDiagonal ? [
763
+ -1,
764
+ -1,
765
+ 0,
766
+ 1,
767
+ 1,
768
+ 1,
769
+ 0,
770
+ -1
771
+ ] : [
772
+ -1,
773
+ 0,
774
+ 1,
775
+ 0
776
+ ];
777
+ const dirCount = dx.length;
778
+ while ((!this.openList.isEmpty || !this.openListBack.isEmpty) && searched < maxNodes) {
779
+ if (!this.openList.isEmpty) {
780
+ const cur = this.openList.pop();
781
+ this.state.setClosed(cur);
782
+ searched++;
783
+ const curG = this.state.getG(cur);
784
+ if (this.state.isClosedBack(cur)) {
785
+ const total = curG + this.state.getGBack(cur);
786
+ if (total < bestCost) {
787
+ bestCost = total;
788
+ meetIdx = cur;
789
+ }
790
+ }
791
+ if (meetIdx !== -1 && curG >= bestCost) break;
792
+ const cx = cur % width, cy = cur / width | 0;
793
+ for (let d = 0; d < dirCount; d++) {
794
+ const nx = cx + dx[d], ny = cy + dy[d];
795
+ if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue;
796
+ const neighbor = nodes[ny][nx];
797
+ if (!neighbor.walkable) continue;
798
+ if (avoidCorners && dx[d] !== 0 && dy[d] !== 0) {
799
+ if (!nodes[cy][cx + dx[d]].walkable || !nodes[cy + dy[d]][cx].walkable) continue;
800
+ }
801
+ const ni = ny * width + nx;
802
+ if (this.state.isClosed(ni)) continue;
803
+ const isDiag = dx[d] !== 0 && dy[d] !== 0;
804
+ const cost = isDiag ? neighbor.cost * diagonalCost : neighbor.cost;
805
+ const tentG = curG + cost;
806
+ if (!this.state.isOpened(ni)) {
807
+ const h = this.map.heuristic({
808
+ x: nx,
809
+ y: ny
810
+ }, endPos) * hw;
811
+ this.state.setG(ni, tentG);
812
+ this.state.setF(ni, tentG + h);
813
+ this.state.setParent(ni, cur);
814
+ this.state.setOpened(ni);
815
+ this.openList.push(ni);
816
+ } else if (tentG < this.state.getG(ni)) {
817
+ const h = this.state.getF(ni) - this.state.getG(ni);
818
+ this.state.setG(ni, tentG);
819
+ this.state.setF(ni, tentG + h);
820
+ this.state.setParent(ni, cur);
821
+ this.openList.update(ni);
822
+ }
823
+ }
824
+ }
825
+ if (!this.openListBack.isEmpty) {
826
+ const cur = this.openListBack.pop();
827
+ this.state.setClosedBack(cur);
828
+ searched++;
829
+ const curG = this.state.getGBack(cur);
830
+ if (this.state.isClosed(cur)) {
831
+ const total = curG + this.state.getG(cur);
832
+ if (total < bestCost) {
833
+ bestCost = total;
834
+ meetIdx = cur;
835
+ }
836
+ }
837
+ if (meetIdx !== -1 && curG >= bestCost) break;
838
+ const cx = cur % width, cy = cur / width | 0;
839
+ for (let d = 0; d < dirCount; d++) {
840
+ const nx = cx + dx[d], ny = cy + dy[d];
841
+ if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue;
842
+ const neighbor = nodes[ny][nx];
843
+ if (!neighbor.walkable) continue;
844
+ if (avoidCorners && dx[d] !== 0 && dy[d] !== 0) {
845
+ if (!nodes[cy][cx + dx[d]].walkable || !nodes[cy + dy[d]][cx].walkable) continue;
846
+ }
847
+ const ni = ny * width + nx;
848
+ if (this.state.isClosedBack(ni)) continue;
849
+ const isDiag = dx[d] !== 0 && dy[d] !== 0;
850
+ const cost = isDiag ? neighbor.cost * diagonalCost : neighbor.cost;
851
+ const tentG = curG + cost;
852
+ if (!this.state.isOpenedBack(ni)) {
853
+ const h = this.map.heuristic({
854
+ x: nx,
855
+ y: ny
856
+ }, startPos) * hw;
857
+ this.state.setGBack(ni, tentG);
858
+ this.state.setFBack(ni, tentG + h);
859
+ this.state.setParentBack(ni, cur);
860
+ this.state.setOpenedBack(ni);
861
+ this.openListBack.push(ni);
862
+ } else if (tentG < this.state.getGBack(ni)) {
863
+ const h = this.state.getFBack(ni) - this.state.getGBack(ni);
864
+ this.state.setGBack(ni, tentG);
865
+ this.state.setFBack(ni, tentG + h);
866
+ this.state.setParentBack(ni, cur);
867
+ this.openListBack.update(ni);
868
+ }
869
+ }
870
+ }
871
+ }
872
+ if (meetIdx === -1) {
873
+ return {
874
+ found: false,
875
+ path: [],
876
+ cost: 0,
877
+ nodesSearched: searched
878
+ };
879
+ }
880
+ return this.buildPathBidirectional(startIdx, endIdx, meetIdx, searched);
881
+ }
882
+ validate(startX, startY, endX, endY) {
883
+ const { width, height } = this.map;
884
+ if (startX < 0 || startX >= width || startY < 0 || startY >= height) return false;
885
+ if (endX < 0 || endX >= width || endY < 0 || endY >= height) return false;
886
+ return this.map.isWalkable(startX, startY) && this.map.isWalkable(endX, endY);
887
+ }
888
+ buildPath(startIdx, endIdx, searched) {
889
+ const w = this.state.width;
890
+ const path = [];
891
+ let cur = endIdx;
892
+ while (cur !== -1) {
893
+ path.push({
894
+ x: cur % w,
895
+ y: cur / w | 0
896
+ });
897
+ cur = cur === startIdx ? -1 : this.state.getParent(cur);
898
+ }
899
+ path.reverse();
900
+ return {
901
+ found: true,
902
+ path,
903
+ cost: this.state.getG(endIdx),
904
+ nodesSearched: searched
905
+ };
906
+ }
907
+ buildPathBidirectional(startIdx, endIdx, meetIdx, searched) {
908
+ const w = this.state.width;
909
+ const path = [];
910
+ let cur = meetIdx;
911
+ while (cur !== -1 && cur !== startIdx) {
912
+ path.push({
913
+ x: cur % w,
914
+ y: cur / w | 0
915
+ });
916
+ cur = this.state.getParent(cur);
917
+ }
918
+ path.push({
919
+ x: startIdx % w,
920
+ y: startIdx / w | 0
921
+ });
922
+ path.reverse();
923
+ cur = this.state.getParentBack(meetIdx);
924
+ while (cur !== -1 && cur !== endIdx) {
925
+ path.push({
926
+ x: cur % w,
927
+ y: cur / w | 0
928
+ });
929
+ cur = this.state.getParentBack(cur);
930
+ }
931
+ if (meetIdx !== endIdx) {
932
+ path.push({
933
+ x: endIdx % w,
934
+ y: endIdx / w | 0
935
+ });
936
+ }
937
+ const cost = this.state.getG(meetIdx) + this.state.getGBack(meetIdx);
938
+ return {
939
+ found: true,
940
+ path,
941
+ cost,
942
+ nodesSearched: searched
943
+ };
944
+ }
945
+ clear() {
946
+ this.state.reset();
947
+ this.openList.clear();
948
+ this.openListBack?.clear();
949
+ }
950
+ };
951
+ __name(_GridPathfinder, "GridPathfinder");
952
+ var GridPathfinder = _GridPathfinder;
953
+ function createGridPathfinder(map, config) {
954
+ return new GridPathfinder(map, config);
955
+ }
956
+ __name(createGridPathfinder, "createGridPathfinder");
957
+
958
+ // src/core/JPSPathfinder.ts
959
+ var _JPSPathfinder = class _JPSPathfinder {
960
+ constructor(map) {
961
+ __publicField(this, "map");
962
+ __publicField(this, "width");
963
+ __publicField(this, "height");
964
+ __publicField(this, "openList");
965
+ __publicField(this, "nodeGrid");
966
+ this.map = map;
967
+ const bounds = this.getMapBounds();
968
+ this.width = bounds.width;
969
+ this.height = bounds.height;
970
+ this.openList = new BinaryHeap((a, b) => a.f - b.f);
971
+ this.nodeGrid = [];
972
+ }
973
+ /**
974
+ * @zh 寻找路径
975
+ * @en Find path
976
+ */
977
+ findPath(startX, startY, endX, endY, options) {
978
+ const opts = {
979
+ ...DEFAULT_PATHFINDING_OPTIONS,
980
+ ...options
981
+ };
982
+ if (!this.map.isWalkable(startX, startY) || !this.map.isWalkable(endX, endY)) {
983
+ return EMPTY_PATH_RESULT;
984
+ }
985
+ if (startX === endX && startY === endY) {
986
+ return {
987
+ found: true,
988
+ path: [
989
+ {
990
+ x: startX,
991
+ y: startY
992
+ }
993
+ ],
994
+ cost: 0,
995
+ nodesSearched: 1
996
+ };
997
+ }
998
+ this.initGrid();
999
+ this.openList.clear();
1000
+ const startNode = this.getOrCreateNode(startX, startY);
1001
+ startNode.g = 0;
1002
+ startNode.h = this.heuristic(startX, startY, endX, endY) * opts.heuristicWeight;
1003
+ startNode.f = startNode.h;
1004
+ this.openList.push(startNode);
1005
+ let nodesSearched = 0;
1006
+ while (!this.openList.isEmpty && nodesSearched < opts.maxNodes) {
1007
+ const current = this.openList.pop();
1008
+ current.closed = true;
1009
+ nodesSearched++;
1010
+ if (current.x === endX && current.y === endY) {
1011
+ return {
1012
+ found: true,
1013
+ path: this.buildPath(current),
1014
+ cost: current.g,
1015
+ nodesSearched
1016
+ };
1017
+ }
1018
+ this.identifySuccessors(current, endX, endY, opts);
1019
+ }
1020
+ return {
1021
+ found: false,
1022
+ path: [],
1023
+ cost: 0,
1024
+ nodesSearched
1025
+ };
1026
+ }
1027
+ /**
1028
+ * @zh 清理状态
1029
+ * @en Clear state
1030
+ */
1031
+ clear() {
1032
+ this.openList.clear();
1033
+ this.nodeGrid = [];
1034
+ }
1035
+ // =========================================================================
1036
+ // 私有方法 | Private Methods
1037
+ // =========================================================================
1038
+ /**
1039
+ * @zh 获取地图边界
1040
+ * @en Get map bounds
1041
+ */
1042
+ getMapBounds() {
1043
+ const mapAny = this.map;
1044
+ if (typeof mapAny.width === "number" && typeof mapAny.height === "number") {
1045
+ return {
1046
+ width: mapAny.width,
1047
+ height: mapAny.height
1048
+ };
1049
+ }
1050
+ return {
1051
+ width: 1e3,
1052
+ height: 1e3
1053
+ };
1054
+ }
1055
+ /**
1056
+ * @zh 初始化节点网格
1057
+ * @en Initialize node grid
1058
+ */
1059
+ initGrid() {
1060
+ this.nodeGrid = [];
1061
+ for (let i = 0; i < this.width; i++) {
1062
+ this.nodeGrid[i] = [];
1063
+ }
1064
+ }
1065
+ /**
1066
+ * @zh 获取或创建节点
1067
+ * @en Get or create node
1068
+ */
1069
+ getOrCreateNode(x, y) {
1070
+ const xi = x | 0;
1071
+ const yi = y | 0;
1072
+ if (xi < 0 || xi >= this.width || yi < 0 || yi >= this.height) {
1073
+ throw new Error("[JPSPathfinder] Invalid grid coordinates");
1074
+ }
1075
+ if (!this.nodeGrid[xi]) {
1076
+ this.nodeGrid[xi] = [];
1077
+ }
1078
+ if (!this.nodeGrid[xi][yi]) {
1079
+ this.nodeGrid[xi][yi] = {
1080
+ x: xi,
1081
+ y: yi,
1082
+ g: Infinity,
1083
+ h: 0,
1084
+ f: Infinity,
1085
+ parent: null,
1086
+ closed: false
1087
+ };
1088
+ }
1089
+ return this.nodeGrid[xi][yi];
1090
+ }
1091
+ /**
1092
+ * @zh 启发式函数(八方向距离)
1093
+ * @en Heuristic function (octile distance)
1094
+ */
1095
+ heuristic(x1, y1, x2, y2) {
1096
+ const dx = Math.abs(x1 - x2);
1097
+ const dy = Math.abs(y1 - y2);
1098
+ return dx + dy + (Math.SQRT2 - 2) * Math.min(dx, dy);
1099
+ }
1100
+ /**
1101
+ * @zh 识别后继节点(跳跃点)
1102
+ * @en Identify successors (jump points)
1103
+ */
1104
+ identifySuccessors(node, endX, endY, opts) {
1105
+ const neighbors = this.findNeighbors(node);
1106
+ for (const neighbor of neighbors) {
1107
+ const jumpPoint = this.jump(neighbor.x, neighbor.y, node.x, node.y, endX, endY);
1108
+ if (jumpPoint) {
1109
+ const jx = jumpPoint.x;
1110
+ const jy = jumpPoint.y;
1111
+ const jpNode = this.getOrCreateNode(jx, jy);
1112
+ if (jpNode.closed) continue;
1113
+ const dx = Math.abs(jx - node.x);
1114
+ const dy = Math.abs(jy - node.y);
1115
+ const distance = Math.sqrt(dx * dx + dy * dy);
1116
+ const tentativeG = node.g + distance;
1117
+ if (tentativeG < jpNode.g) {
1118
+ jpNode.g = tentativeG;
1119
+ jpNode.h = this.heuristic(jx, jy, endX, endY) * opts.heuristicWeight;
1120
+ jpNode.f = jpNode.g + jpNode.h;
1121
+ jpNode.parent = node;
1122
+ if (!this.openList.contains(jpNode)) {
1123
+ this.openList.push(jpNode);
1124
+ } else {
1125
+ this.openList.update(jpNode);
1126
+ }
1127
+ }
1128
+ }
1129
+ }
1130
+ }
1131
+ /**
1132
+ * @zh 查找邻居(根据父节点方向剪枝)
1133
+ * @en Find neighbors (pruned based on parent direction)
1134
+ */
1135
+ findNeighbors(node) {
1136
+ const { x, y, parent } = node;
1137
+ const neighbors = [];
1138
+ if (!parent) {
1139
+ for (let dx2 = -1; dx2 <= 1; dx2++) {
1140
+ for (let dy2 = -1; dy2 <= 1; dy2++) {
1141
+ if (dx2 === 0 && dy2 === 0) continue;
1142
+ const nx = x + dx2;
1143
+ const ny = y + dy2;
1144
+ if (this.isWalkableAt(nx, ny)) {
1145
+ if (dx2 !== 0 && dy2 !== 0) {
1146
+ if (this.isWalkableAt(x + dx2, y) || this.isWalkableAt(x, y + dy2)) {
1147
+ neighbors.push({
1148
+ x: nx,
1149
+ y: ny
1150
+ });
1151
+ }
1152
+ } else {
1153
+ neighbors.push({
1154
+ x: nx,
1155
+ y: ny
1156
+ });
1157
+ }
1158
+ }
1159
+ }
1160
+ }
1161
+ return neighbors;
1162
+ }
1163
+ const dx = Math.sign(x - parent.x);
1164
+ const dy = Math.sign(y - parent.y);
1165
+ if (dx !== 0 && dy !== 0) {
1166
+ if (this.isWalkableAt(x, y + dy)) {
1167
+ neighbors.push({
1168
+ x,
1169
+ y: y + dy
1170
+ });
1171
+ }
1172
+ if (this.isWalkableAt(x + dx, y)) {
1173
+ neighbors.push({
1174
+ x: x + dx,
1175
+ y
1176
+ });
1177
+ }
1178
+ if (this.isWalkableAt(x, y + dy) || this.isWalkableAt(x + dx, y)) {
1179
+ if (this.isWalkableAt(x + dx, y + dy)) {
1180
+ neighbors.push({
1181
+ x: x + dx,
1182
+ y: y + dy
1183
+ });
1184
+ }
1185
+ }
1186
+ if (!this.isWalkableAt(x - dx, y) && this.isWalkableAt(x, y + dy)) {
1187
+ if (this.isWalkableAt(x - dx, y + dy)) {
1188
+ neighbors.push({
1189
+ x: x - dx,
1190
+ y: y + dy
1191
+ });
1192
+ }
1193
+ }
1194
+ if (!this.isWalkableAt(x, y - dy) && this.isWalkableAt(x + dx, y)) {
1195
+ if (this.isWalkableAt(x + dx, y - dy)) {
1196
+ neighbors.push({
1197
+ x: x + dx,
1198
+ y: y - dy
1199
+ });
1200
+ }
1201
+ }
1202
+ } else if (dx !== 0) {
1203
+ if (this.isWalkableAt(x + dx, y)) {
1204
+ neighbors.push({
1205
+ x: x + dx,
1206
+ y
1207
+ });
1208
+ if (!this.isWalkableAt(x, y + 1) && this.isWalkableAt(x + dx, y + 1)) {
1209
+ neighbors.push({
1210
+ x: x + dx,
1211
+ y: y + 1
1212
+ });
1213
+ }
1214
+ if (!this.isWalkableAt(x, y - 1) && this.isWalkableAt(x + dx, y - 1)) {
1215
+ neighbors.push({
1216
+ x: x + dx,
1217
+ y: y - 1
1218
+ });
1219
+ }
1220
+ }
1221
+ } else if (dy !== 0) {
1222
+ if (this.isWalkableAt(x, y + dy)) {
1223
+ neighbors.push({
1224
+ x,
1225
+ y: y + dy
1226
+ });
1227
+ if (!this.isWalkableAt(x + 1, y) && this.isWalkableAt(x + 1, y + dy)) {
1228
+ neighbors.push({
1229
+ x: x + 1,
1230
+ y: y + dy
1231
+ });
1232
+ }
1233
+ if (!this.isWalkableAt(x - 1, y) && this.isWalkableAt(x - 1, y + dy)) {
1234
+ neighbors.push({
1235
+ x: x - 1,
1236
+ y: y + dy
1237
+ });
1238
+ }
1239
+ }
1240
+ }
1241
+ return neighbors;
1242
+ }
1243
+ /**
1244
+ * @zh 跳跃函数(迭代版本,避免递归开销)
1245
+ * @en Jump function (iterative version to avoid recursion overhead)
1246
+ */
1247
+ jump(startX, startY, px, py, endX, endY) {
1248
+ const dx = startX - px;
1249
+ const dy = startY - py;
1250
+ let x = startX;
1251
+ let y = startY;
1252
+ while (true) {
1253
+ if (!this.isWalkableAt(x, y)) {
1254
+ return null;
1255
+ }
1256
+ if (x === endX && y === endY) {
1257
+ return {
1258
+ x,
1259
+ y
1260
+ };
1261
+ }
1262
+ if (dx !== 0 && dy !== 0) {
1263
+ if (this.isWalkableAt(x - dx, y + dy) && !this.isWalkableAt(x - dx, y) || this.isWalkableAt(x + dx, y - dy) && !this.isWalkableAt(x, y - dy)) {
1264
+ return {
1265
+ x,
1266
+ y
1267
+ };
1268
+ }
1269
+ if (this.jumpStraight(x + dx, y, dx, 0, endX, endY) || this.jumpStraight(x, y + dy, 0, dy, endX, endY)) {
1270
+ return {
1271
+ x,
1272
+ y
1273
+ };
1274
+ }
1275
+ if (!this.isWalkableAt(x + dx, y) && !this.isWalkableAt(x, y + dy)) {
1276
+ return null;
1277
+ }
1278
+ } else if (dx !== 0) {
1279
+ if (this.isWalkableAt(x + dx, y + 1) && !this.isWalkableAt(x, y + 1) || this.isWalkableAt(x + dx, y - 1) && !this.isWalkableAt(x, y - 1)) {
1280
+ return {
1281
+ x,
1282
+ y
1283
+ };
1284
+ }
1285
+ } else if (dy !== 0) {
1286
+ if (this.isWalkableAt(x + 1, y + dy) && !this.isWalkableAt(x + 1, y) || this.isWalkableAt(x - 1, y + dy) && !this.isWalkableAt(x - 1, y)) {
1287
+ return {
1288
+ x,
1289
+ y
1290
+ };
1291
+ }
1292
+ }
1293
+ x += dx;
1294
+ y += dy;
1295
+ }
1296
+ }
1297
+ /**
1298
+ * @zh 直线跳跃(水平或垂直方向)
1299
+ * @en Straight jump (horizontal or vertical direction)
1300
+ */
1301
+ jumpStraight(startX, startY, dx, dy, endX, endY) {
1302
+ let x = startX;
1303
+ let y = startY;
1304
+ while (true) {
1305
+ if (!this.isWalkableAt(x, y)) {
1306
+ return false;
1307
+ }
1308
+ if (x === endX && y === endY) {
1309
+ return true;
1310
+ }
1311
+ if (dx !== 0) {
1312
+ if (this.isWalkableAt(x + dx, y + 1) && !this.isWalkableAt(x, y + 1) || this.isWalkableAt(x + dx, y - 1) && !this.isWalkableAt(x, y - 1)) {
1313
+ return true;
1314
+ }
1315
+ } else if (dy !== 0) {
1316
+ if (this.isWalkableAt(x + 1, y + dy) && !this.isWalkableAt(x + 1, y) || this.isWalkableAt(x - 1, y + dy) && !this.isWalkableAt(x - 1, y)) {
1317
+ return true;
1318
+ }
1319
+ }
1320
+ x += dx;
1321
+ y += dy;
1322
+ }
1323
+ }
1324
+ /**
1325
+ * @zh 检查位置是否可通行
1326
+ * @en Check if position is walkable
1327
+ */
1328
+ isWalkableAt(x, y) {
1329
+ if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
1330
+ return false;
1331
+ }
1332
+ return this.map.isWalkable(x, y);
1333
+ }
1334
+ /**
1335
+ * @zh 构建路径
1336
+ * @en Build path
1337
+ */
1338
+ buildPath(endNode) {
1339
+ const path = [];
1340
+ let current = endNode;
1341
+ while (current) {
1342
+ path.unshift({
1343
+ x: current.x,
1344
+ y: current.y
1345
+ });
1346
+ current = current.parent;
1347
+ }
1348
+ return this.interpolatePath(path);
1349
+ }
1350
+ /**
1351
+ * @zh 插值路径(在跳跃点之间填充中间点)
1352
+ * @en Interpolate path (fill intermediate points between jump points)
1353
+ */
1354
+ interpolatePath(jumpPoints) {
1355
+ if (jumpPoints.length < 2) {
1356
+ return jumpPoints;
1357
+ }
1358
+ const path = [
1359
+ jumpPoints[0]
1360
+ ];
1361
+ for (let i = 1; i < jumpPoints.length; i++) {
1362
+ const prev = jumpPoints[i - 1];
1363
+ const curr = jumpPoints[i];
1364
+ const dx = curr.x - prev.x;
1365
+ const dy = curr.y - prev.y;
1366
+ const steps = Math.max(Math.abs(dx), Math.abs(dy));
1367
+ const stepX = dx === 0 ? 0 : dx / Math.abs(dx);
1368
+ const stepY = dy === 0 ? 0 : dy / Math.abs(dy);
1369
+ let x = prev.x;
1370
+ let y = prev.y;
1371
+ for (let j = 0; j < steps; j++) {
1372
+ if (x !== curr.x && y !== curr.y) {
1373
+ x += stepX;
1374
+ y += stepY;
1375
+ } else if (x !== curr.x) {
1376
+ x += stepX;
1377
+ } else if (y !== curr.y) {
1378
+ y += stepY;
1379
+ }
1380
+ if (x !== prev.x || y !== prev.y) {
1381
+ path.push({
1382
+ x,
1383
+ y
1384
+ });
1385
+ }
1386
+ }
1387
+ }
1388
+ return path;
332
1389
  }
333
1390
  };
334
- __name(_GridNode, "GridNode");
335
- var GridNode = _GridNode;
336
- var DIRECTIONS_4 = [
337
- {
338
- dx: 0,
339
- dy: -1
340
- },
341
- {
342
- dx: 1,
343
- dy: 0
344
- },
345
- {
346
- dx: 0,
347
- dy: 1
348
- },
349
- {
350
- dx: -1,
351
- dy: 0
352
- }
353
- // Left
354
- ];
355
- var DIRECTIONS_8 = [
356
- {
357
- dx: 0,
358
- dy: -1
359
- },
360
- {
361
- dx: 1,
362
- dy: -1
363
- },
364
- {
365
- dx: 1,
366
- dy: 0
367
- },
368
- {
369
- dx: 1,
370
- dy: 1
371
- },
372
- {
373
- dx: 0,
374
- dy: 1
375
- },
376
- {
377
- dx: -1,
378
- dy: 1
379
- },
380
- {
381
- dx: -1,
382
- dy: 0
383
- },
384
- {
385
- dx: -1,
386
- dy: -1
387
- }
388
- // Up-Left
389
- ];
390
- var DEFAULT_GRID_OPTIONS = {
391
- allowDiagonal: true,
392
- diagonalCost: Math.SQRT2,
393
- avoidCorners: true,
394
- heuristic: octileDistance
1391
+ __name(_JPSPathfinder, "JPSPathfinder");
1392
+ var JPSPathfinder = _JPSPathfinder;
1393
+ function createJPSPathfinder(map) {
1394
+ return new JPSPathfinder(map);
1395
+ }
1396
+ __name(createJPSPathfinder, "createJPSPathfinder");
1397
+
1398
+ // src/core/HPAPathfinder.ts
1399
+ var DEFAULT_HPA_CONFIG = {
1400
+ clusterSize: 10,
1401
+ maxEntranceWidth: 6,
1402
+ cacheInternalPaths: true
395
1403
  };
396
- var _GridMap = class _GridMap {
397
- constructor(width, height, options) {
1404
+ var _HPAPathfinder = class _HPAPathfinder {
1405
+ constructor(map, config) {
1406
+ __publicField(this, "map");
1407
+ __publicField(this, "config");
398
1408
  __publicField(this, "width");
399
1409
  __publicField(this, "height");
400
- __publicField(this, "nodes");
401
- __publicField(this, "options");
402
- this.width = width;
403
- this.height = height;
404
- this.options = {
405
- ...DEFAULT_GRID_OPTIONS,
406
- ...options
1410
+ __publicField(this, "clusters", []);
1411
+ __publicField(this, "entrances", []);
1412
+ __publicField(this, "abstractNodes", /* @__PURE__ */ new Map());
1413
+ __publicField(this, "clusterGrid", []);
1414
+ __publicField(this, "nextEntranceId", 0);
1415
+ __publicField(this, "nextNodeId", 0);
1416
+ __publicField(this, "internalPathCache", /* @__PURE__ */ new Map());
1417
+ __publicField(this, "localPathfinder");
1418
+ __publicField(this, "preprocessed", false);
1419
+ this.map = map;
1420
+ this.config = {
1421
+ ...DEFAULT_HPA_CONFIG,
1422
+ ...config
407
1423
  };
408
- this.nodes = this.createNodes();
1424
+ const bounds = this.getMapBounds();
1425
+ this.width = bounds.width;
1426
+ this.height = bounds.height;
1427
+ this.localPathfinder = new AStarPathfinder(map);
409
1428
  }
410
1429
  /**
411
- * @zh 创建网格节点
412
- * @en Create grid nodes
1430
+ * @zh 预处理地图(构建抽象图)
1431
+ * @en Preprocess map (build abstract graph)
1432
+ *
1433
+ * @zh 在地图变化后需要重新调用
1434
+ * @en Need to call again after map changes
413
1435
  */
414
- createNodes() {
415
- const nodes = [];
416
- for (let y = 0; y < this.height; y++) {
417
- nodes[y] = [];
418
- for (let x = 0; x < this.width; x++) {
419
- nodes[y][x] = new GridNode(x, y, true, 1);
420
- }
421
- }
422
- return nodes;
1436
+ preprocess() {
1437
+ this.clusters = [];
1438
+ this.entrances = [];
1439
+ this.abstractNodes.clear();
1440
+ this.clusterGrid = [];
1441
+ this.internalPathCache.clear();
1442
+ this.nextEntranceId = 0;
1443
+ this.nextNodeId = 0;
1444
+ this.buildClusters();
1445
+ this.findEntrances();
1446
+ this.buildAbstractGraph();
1447
+ this.preprocessed = true;
423
1448
  }
424
1449
  /**
425
- * @zh 获取指定位置的节点
426
- * @en Get node at position
1450
+ * @zh 寻找路径
1451
+ * @en Find path
427
1452
  */
428
- getNodeAt(x, y) {
429
- if (!this.isInBounds(x, y)) {
430
- return null;
1453
+ findPath(startX, startY, endX, endY, options) {
1454
+ if (!this.preprocessed) {
1455
+ this.preprocess();
1456
+ }
1457
+ const opts = {
1458
+ ...DEFAULT_PATHFINDING_OPTIONS,
1459
+ ...options
1460
+ };
1461
+ if (!this.map.isWalkable(startX, startY) || !this.map.isWalkable(endX, endY)) {
1462
+ return EMPTY_PATH_RESULT;
1463
+ }
1464
+ if (startX === endX && startY === endY) {
1465
+ return {
1466
+ found: true,
1467
+ path: [
1468
+ {
1469
+ x: startX,
1470
+ y: startY
1471
+ }
1472
+ ],
1473
+ cost: 0,
1474
+ nodesSearched: 1
1475
+ };
1476
+ }
1477
+ const startCluster = this.getClusterAt(startX, startY);
1478
+ const endCluster = this.getClusterAt(endX, endY);
1479
+ if (!startCluster || !endCluster) {
1480
+ return EMPTY_PATH_RESULT;
431
1481
  }
432
- return this.nodes[y][x];
1482
+ if (startCluster.id === endCluster.id) {
1483
+ return this.findLocalPath(startX, startY, endX, endY, opts);
1484
+ }
1485
+ const startNodes = this.insertTemporaryNode(startX, startY, startCluster);
1486
+ const endNodes = this.insertTemporaryNode(endX, endY, endCluster);
1487
+ const abstractPath = this.searchAbstractGraph(startNodes, endNodes, opts);
1488
+ this.removeTemporaryNodes(startNodes);
1489
+ this.removeTemporaryNodes(endNodes);
1490
+ if (!abstractPath || abstractPath.length === 0) {
1491
+ return EMPTY_PATH_RESULT;
1492
+ }
1493
+ return this.refinePath(abstractPath, startX, startY, endX, endY, opts);
433
1494
  }
434
1495
  /**
435
- * @zh 检查坐标是否在边界内
436
- * @en Check if coordinates are within bounds
1496
+ * @zh 清理状态
1497
+ * @en Clear state
437
1498
  */
438
- isInBounds(x, y) {
439
- return x >= 0 && x < this.width && y >= 0 && y < this.height;
1499
+ clear() {
1500
+ this.clusters = [];
1501
+ this.entrances = [];
1502
+ this.abstractNodes.clear();
1503
+ this.clusterGrid = [];
1504
+ this.internalPathCache.clear();
1505
+ this.preprocessed = false;
440
1506
  }
441
1507
  /**
442
- * @zh 检查位置是否可通行
443
- * @en Check if position is walkable
1508
+ * @zh 通知地图区域变化
1509
+ * @en Notify map region change
444
1510
  */
445
- isWalkable(x, y) {
446
- const node = this.getNodeAt(x, y);
447
- return node !== null && node.walkable;
1511
+ notifyRegionChange(minX, minY, maxX, maxY) {
1512
+ this.preprocessed = false;
1513
+ this.internalPathCache.clear();
448
1514
  }
449
1515
  /**
450
- * @zh 设置位置是否可通行
451
- * @en Set position walkability
1516
+ * @zh 获取预处理统计信息
1517
+ * @en Get preprocessing statistics
452
1518
  */
453
- setWalkable(x, y, walkable) {
454
- const node = this.getNodeAt(x, y);
455
- if (node) {
456
- node.walkable = walkable;
1519
+ getStats() {
1520
+ return {
1521
+ clusters: this.clusters.length,
1522
+ entrances: this.entrances.length,
1523
+ abstractNodes: this.abstractNodes.size,
1524
+ cacheSize: this.internalPathCache.size
1525
+ };
1526
+ }
1527
+ // =========================================================================
1528
+ // 预处理方法 | Preprocessing Methods
1529
+ // =========================================================================
1530
+ getMapBounds() {
1531
+ const mapAny = this.map;
1532
+ if (typeof mapAny.width === "number" && typeof mapAny.height === "number") {
1533
+ return {
1534
+ width: mapAny.width,
1535
+ height: mapAny.height
1536
+ };
457
1537
  }
1538
+ return {
1539
+ width: 1e3,
1540
+ height: 1e3
1541
+ };
458
1542
  }
459
- /**
460
- * @zh 设置位置的移动代价
461
- * @en Set movement cost at position
462
- */
463
- setCost(x, y, cost) {
464
- const node = this.getNodeAt(x, y);
465
- if (node) {
466
- node.cost = cost;
1543
+ buildClusters() {
1544
+ const clusterSize = this.config.clusterSize;
1545
+ const clustersX = Math.ceil(this.width / clusterSize);
1546
+ const clustersY = Math.ceil(this.height / clusterSize);
1547
+ this.clusterGrid = [];
1548
+ for (let x = 0; x < clustersX; x++) {
1549
+ this.clusterGrid[x] = [];
1550
+ }
1551
+ let clusterId = 0;
1552
+ for (let cy = 0; cy < clustersY; cy++) {
1553
+ for (let cx = 0; cx < clustersX; cx++) {
1554
+ const cluster = {
1555
+ id: clusterId++,
1556
+ x: cx * clusterSize,
1557
+ y: cy * clusterSize,
1558
+ width: Math.min(clusterSize, this.width - cx * clusterSize),
1559
+ height: Math.min(clusterSize, this.height - cy * clusterSize),
1560
+ entrances: []
1561
+ };
1562
+ this.clusters.push(cluster);
1563
+ this.clusterGrid[cx][cy] = cluster;
1564
+ }
467
1565
  }
468
1566
  }
469
- /**
470
- * @zh 获取节点的邻居
471
- * @en Get neighbors of a node
472
- */
473
- getNeighbors(node) {
474
- const neighbors = [];
475
- const { x, y } = node.position;
476
- const directions = this.options.allowDiagonal ? DIRECTIONS_8 : DIRECTIONS_4;
477
- for (let i = 0; i < directions.length; i++) {
478
- const dir = directions[i];
479
- const nx = x + dir.dx;
480
- const ny = y + dir.dy;
481
- if (!this.isInBounds(nx, ny)) {
482
- continue;
1567
+ findEntrances() {
1568
+ const clusterSize = this.config.clusterSize;
1569
+ const clustersX = Math.ceil(this.width / clusterSize);
1570
+ const clustersY = Math.ceil(this.height / clusterSize);
1571
+ for (let cy = 0; cy < clustersY; cy++) {
1572
+ for (let cx = 0; cx < clustersX; cx++) {
1573
+ const cluster = this.clusterGrid[cx][cy];
1574
+ if (!cluster) continue;
1575
+ if (cx < clustersX - 1) {
1576
+ const rightCluster = this.clusterGrid[cx + 1]?.[cy];
1577
+ if (rightCluster) {
1578
+ this.findEntrancesBetween(cluster, rightCluster, "horizontal");
1579
+ }
1580
+ }
1581
+ if (cy < clustersY - 1) {
1582
+ const bottomCluster = this.clusterGrid[cx]?.[cy + 1];
1583
+ if (bottomCluster) {
1584
+ this.findEntrancesBetween(cluster, bottomCluster, "vertical");
1585
+ }
1586
+ }
483
1587
  }
484
- const neighbor = this.nodes[ny][nx];
485
- if (!neighbor.walkable) {
486
- continue;
1588
+ }
1589
+ }
1590
+ findEntrancesBetween(cluster1, cluster2, direction) {
1591
+ const maxWidth = this.config.maxEntranceWidth;
1592
+ let entranceStart = null;
1593
+ let entranceLength = 0;
1594
+ if (direction === "horizontal") {
1595
+ const x1 = cluster1.x + cluster1.width - 1;
1596
+ const x2 = cluster2.x;
1597
+ const startY = Math.max(cluster1.y, cluster2.y);
1598
+ const endY = Math.min(cluster1.y + cluster1.height, cluster2.y + cluster2.height);
1599
+ for (let y = startY; y < endY; y++) {
1600
+ const walkable1 = this.map.isWalkable(x1, y);
1601
+ const walkable2 = this.map.isWalkable(x2, y);
1602
+ if (walkable1 && walkable2) {
1603
+ if (entranceStart === null) {
1604
+ entranceStart = y;
1605
+ entranceLength = 1;
1606
+ } else {
1607
+ entranceLength++;
1608
+ }
1609
+ if (entranceLength >= maxWidth || y === endY - 1) {
1610
+ this.createEntrance(cluster1, cluster2, x1, x2, entranceStart, entranceStart + entranceLength - 1, "horizontal");
1611
+ entranceStart = null;
1612
+ entranceLength = 0;
1613
+ }
1614
+ } else if (entranceStart !== null) {
1615
+ this.createEntrance(cluster1, cluster2, x1, x2, entranceStart, entranceStart + entranceLength - 1, "horizontal");
1616
+ entranceStart = null;
1617
+ entranceLength = 0;
1618
+ }
487
1619
  }
488
- if (this.options.avoidCorners && dir.dx !== 0 && dir.dy !== 0) {
489
- const horizontal = this.getNodeAt(x + dir.dx, y);
490
- const vertical = this.getNodeAt(x, y + dir.dy);
491
- if (!horizontal?.walkable || !vertical?.walkable) {
492
- continue;
1620
+ } else {
1621
+ const y1 = cluster1.y + cluster1.height - 1;
1622
+ const y2 = cluster2.y;
1623
+ const startX = Math.max(cluster1.x, cluster2.x);
1624
+ const endX = Math.min(cluster1.x + cluster1.width, cluster2.x + cluster2.width);
1625
+ for (let x = startX; x < endX; x++) {
1626
+ const walkable1 = this.map.isWalkable(x, y1);
1627
+ const walkable2 = this.map.isWalkable(x, y2);
1628
+ if (walkable1 && walkable2) {
1629
+ if (entranceStart === null) {
1630
+ entranceStart = x;
1631
+ entranceLength = 1;
1632
+ } else {
1633
+ entranceLength++;
1634
+ }
1635
+ if (entranceLength >= maxWidth || x === endX - 1) {
1636
+ this.createEntrance(cluster1, cluster2, entranceStart, entranceStart + entranceLength - 1, y1, y2, "vertical");
1637
+ entranceStart = null;
1638
+ entranceLength = 0;
1639
+ }
1640
+ } else if (entranceStart !== null) {
1641
+ this.createEntrance(cluster1, cluster2, entranceStart, entranceStart + entranceLength - 1, y1, y2, "vertical");
1642
+ entranceStart = null;
1643
+ entranceLength = 0;
493
1644
  }
494
1645
  }
495
- neighbors.push(neighbor);
496
1646
  }
497
- return neighbors;
498
- }
499
- /**
500
- * @zh 计算启发式距离
501
- * @en Calculate heuristic distance
502
- */
503
- heuristic(a, b) {
504
- return this.options.heuristic(a, b);
505
1647
  }
506
- /**
507
- * @zh 计算移动代价
508
- * @en Calculate movement cost
509
- */
510
- getMovementCost(from, to) {
511
- const dx = Math.abs(from.position.x - to.position.x);
512
- const dy = Math.abs(from.position.y - to.position.y);
513
- if (dx !== 0 && dy !== 0) {
514
- return to.cost * this.options.diagonalCost;
1648
+ createEntrance(cluster1, cluster2, coord1Start, coord1End, coord2Start, coord2End, direction) {
1649
+ let point1;
1650
+ let point2;
1651
+ let center;
1652
+ if (direction === "horizontal") {
1653
+ const midY = Math.floor((coord1Start + coord1End) / 2);
1654
+ point1 = {
1655
+ x: coord1Start,
1656
+ y: midY
1657
+ };
1658
+ point2 = {
1659
+ x: coord2Start,
1660
+ y: midY
1661
+ };
1662
+ center = {
1663
+ x: coord1Start,
1664
+ y: midY
1665
+ };
1666
+ } else {
1667
+ const midX = Math.floor((coord1Start + coord1End) / 2);
1668
+ point1 = {
1669
+ x: midX,
1670
+ y: coord2Start
1671
+ };
1672
+ point2 = {
1673
+ x: midX,
1674
+ y: coord2End
1675
+ };
1676
+ center = {
1677
+ x: midX,
1678
+ y: coord2Start
1679
+ };
1680
+ }
1681
+ const entrance = {
1682
+ id: this.nextEntranceId++,
1683
+ cluster1Id: cluster1.id,
1684
+ cluster2Id: cluster2.id,
1685
+ point1,
1686
+ point2,
1687
+ center
1688
+ };
1689
+ this.entrances.push(entrance);
1690
+ cluster1.entrances.push(entrance);
1691
+ cluster2.entrances.push(entrance);
1692
+ }
1693
+ buildAbstractGraph() {
1694
+ for (const entrance of this.entrances) {
1695
+ const node1 = this.createAbstractNode(entrance.point1, entrance.cluster1Id, entrance.id);
1696
+ const node2 = this.createAbstractNode(entrance.point2, entrance.cluster2Id, entrance.id);
1697
+ node1.edges.push({
1698
+ targetNodeId: node2.id,
1699
+ cost: 1,
1700
+ isInterEdge: true
1701
+ });
1702
+ node2.edges.push({
1703
+ targetNodeId: node1.id,
1704
+ cost: 1,
1705
+ isInterEdge: true
1706
+ });
1707
+ }
1708
+ for (const cluster of this.clusters) {
1709
+ this.connectIntraClusterNodes(cluster);
1710
+ }
1711
+ }
1712
+ createAbstractNode(position, clusterId, entranceId) {
1713
+ const node = {
1714
+ id: this.nextNodeId++,
1715
+ position,
1716
+ clusterId,
1717
+ entranceId,
1718
+ edges: []
1719
+ };
1720
+ this.abstractNodes.set(node.id, node);
1721
+ return node;
1722
+ }
1723
+ connectIntraClusterNodes(cluster) {
1724
+ const nodesInCluster = [];
1725
+ for (const node of this.abstractNodes.values()) {
1726
+ if (node.clusterId === cluster.id) {
1727
+ nodesInCluster.push(node);
1728
+ }
1729
+ }
1730
+ for (let i = 0; i < nodesInCluster.length; i++) {
1731
+ for (let j = i + 1; j < nodesInCluster.length; j++) {
1732
+ const node1 = nodesInCluster[i];
1733
+ const node2 = nodesInCluster[j];
1734
+ const cost = this.heuristic(node1.position, node2.position);
1735
+ node1.edges.push({
1736
+ targetNodeId: node2.id,
1737
+ cost,
1738
+ isInterEdge: false
1739
+ });
1740
+ node2.edges.push({
1741
+ targetNodeId: node1.id,
1742
+ cost,
1743
+ isInterEdge: false
1744
+ });
1745
+ }
515
1746
  }
516
- return to.cost;
517
1747
  }
518
- /**
519
- * @zh 从二维数组加载地图
520
- * @en Load map from 2D array
521
- *
522
- * @param data - @zh 0=可通行,非0=不可通行 @en 0=walkable, non-0=blocked
523
- */
524
- loadFromArray(data) {
525
- for (let y = 0; y < Math.min(data.length, this.height); y++) {
526
- for (let x = 0; x < Math.min(data[y].length, this.width); x++) {
527
- this.nodes[y][x].walkable = data[y][x] === 0;
1748
+ // =========================================================================
1749
+ // 搜索方法 | Search Methods
1750
+ // =========================================================================
1751
+ getClusterAt(x, y) {
1752
+ const clusterSize = this.config.clusterSize;
1753
+ const cx = Math.floor(x / clusterSize);
1754
+ const cy = Math.floor(y / clusterSize);
1755
+ return this.clusterGrid[cx]?.[cy] ?? null;
1756
+ }
1757
+ insertTemporaryNode(x, y, cluster) {
1758
+ const tempNodes = [];
1759
+ const tempNode = this.createAbstractNode({
1760
+ x,
1761
+ y
1762
+ }, cluster.id, -1);
1763
+ tempNodes.push(tempNode);
1764
+ for (const node of this.abstractNodes.values()) {
1765
+ if (node.clusterId === cluster.id && node.id !== tempNode.id) {
1766
+ const cost = this.heuristic({
1767
+ x,
1768
+ y
1769
+ }, node.position);
1770
+ tempNode.edges.push({
1771
+ targetNodeId: node.id,
1772
+ cost,
1773
+ isInterEdge: false
1774
+ });
1775
+ node.edges.push({
1776
+ targetNodeId: tempNode.id,
1777
+ cost,
1778
+ isInterEdge: false
1779
+ });
528
1780
  }
529
1781
  }
1782
+ return tempNodes;
530
1783
  }
531
- /**
532
- * @zh 从字符串加载地图
533
- * @en Load map from string
534
- *
535
- * @param str - @zh 地图字符串,'.'=可通行,'#'=障碍 @en Map string, '.'=walkable, '#'=blocked
536
- */
537
- loadFromString(str) {
538
- const lines = str.trim().split("\n");
539
- for (let y = 0; y < Math.min(lines.length, this.height); y++) {
540
- const line = lines[y];
541
- for (let x = 0; x < Math.min(line.length, this.width); x++) {
542
- this.nodes[y][x].walkable = line[x] !== "#";
1784
+ removeTemporaryNodes(nodes) {
1785
+ for (const node of nodes) {
1786
+ for (const edge of node.edges) {
1787
+ const targetNode = this.abstractNodes.get(edge.targetNodeId);
1788
+ if (targetNode) {
1789
+ targetNode.edges = targetNode.edges.filter((e) => e.targetNodeId !== node.id);
1790
+ }
543
1791
  }
1792
+ this.abstractNodes.delete(node.id);
544
1793
  }
545
1794
  }
546
- /**
547
- * @zh 导出为字符串
548
- * @en Export to string
549
- */
550
- toString() {
551
- let result = "";
552
- for (let y = 0; y < this.height; y++) {
553
- for (let x = 0; x < this.width; x++) {
554
- result += this.nodes[y][x].walkable ? "." : "#";
1795
+ searchAbstractGraph(startNodes, endNodes, opts) {
1796
+ if (startNodes.length === 0 || endNodes.length === 0) {
1797
+ return null;
1798
+ }
1799
+ const endNodeIds = new Set(endNodes.map((n) => n.id));
1800
+ const openList = new BinaryHeap((a, b) => a.f - b.f);
1801
+ const closedSet = /* @__PURE__ */ new Set();
1802
+ for (const startNode of startNodes) {
1803
+ const h = this.heuristic(startNode.position, endNodes[0].position);
1804
+ openList.push({
1805
+ abstractNode: startNode,
1806
+ g: 0,
1807
+ h: h * opts.heuristicWeight,
1808
+ f: h * opts.heuristicWeight,
1809
+ parent: null
1810
+ });
1811
+ }
1812
+ let nodesSearched = 0;
1813
+ while (!openList.isEmpty && nodesSearched < opts.maxNodes) {
1814
+ const current = openList.pop();
1815
+ nodesSearched++;
1816
+ if (endNodeIds.has(current.abstractNode.id)) {
1817
+ return this.reconstructAbstractPath(current);
1818
+ }
1819
+ if (closedSet.has(current.abstractNode.id)) {
1820
+ continue;
1821
+ }
1822
+ closedSet.add(current.abstractNode.id);
1823
+ for (const edge of current.abstractNode.edges) {
1824
+ if (closedSet.has(edge.targetNodeId)) {
1825
+ continue;
1826
+ }
1827
+ const neighbor = this.abstractNodes.get(edge.targetNodeId);
1828
+ if (!neighbor) continue;
1829
+ const tentativeG = current.g + edge.cost;
1830
+ const h = this.heuristic(neighbor.position, endNodes[0].position) * opts.heuristicWeight;
1831
+ openList.push({
1832
+ abstractNode: neighbor,
1833
+ g: tentativeG,
1834
+ h,
1835
+ f: tentativeG + h,
1836
+ parent: current
1837
+ });
555
1838
  }
556
- result += "\n";
557
1839
  }
558
- return result;
1840
+ return null;
559
1841
  }
560
- /**
561
- * @zh 重置所有节点为可通行
562
- * @en Reset all nodes to walkable
563
- */
564
- reset() {
565
- for (let y = 0; y < this.height; y++) {
566
- for (let x = 0; x < this.width; x++) {
567
- this.nodes[y][x].walkable = true;
568
- this.nodes[y][x].cost = 1;
1842
+ reconstructAbstractPath(endNode) {
1843
+ const path = [];
1844
+ let current = endNode;
1845
+ while (current) {
1846
+ path.unshift(current.abstractNode);
1847
+ current = current.parent;
1848
+ }
1849
+ return path;
1850
+ }
1851
+ refinePath(abstractPath, startX, startY, endX, endY, opts) {
1852
+ const fullPath = [];
1853
+ let totalCost = 0;
1854
+ let nodesSearched = abstractPath.length;
1855
+ let currentX = startX;
1856
+ let currentY = startY;
1857
+ for (let i = 0; i < abstractPath.length; i++) {
1858
+ const node = abstractPath[i];
1859
+ const targetX = i === abstractPath.length - 1 ? endX : node.position.x;
1860
+ const targetY = i === abstractPath.length - 1 ? endY : node.position.y;
1861
+ if (currentX !== targetX || currentY !== targetY) {
1862
+ const segment = this.localPathfinder.findPath(currentX, currentY, targetX, targetY, opts);
1863
+ if (!segment.found) {
1864
+ if (fullPath.length > 0) {
1865
+ return {
1866
+ found: true,
1867
+ path: fullPath,
1868
+ cost: totalCost,
1869
+ nodesSearched
1870
+ };
1871
+ }
1872
+ return EMPTY_PATH_RESULT;
1873
+ }
1874
+ for (let j = fullPath.length === 0 ? 0 : 1; j < segment.path.length; j++) {
1875
+ fullPath.push(segment.path[j]);
1876
+ }
1877
+ totalCost += segment.cost;
1878
+ nodesSearched += segment.nodesSearched;
1879
+ }
1880
+ currentX = targetX;
1881
+ currentY = targetY;
1882
+ }
1883
+ if (currentX !== endX || currentY !== endY) {
1884
+ const finalSegment = this.localPathfinder.findPath(currentX, currentY, endX, endY, opts);
1885
+ if (finalSegment.found) {
1886
+ for (let j = 1; j < finalSegment.path.length; j++) {
1887
+ fullPath.push(finalSegment.path[j]);
1888
+ }
1889
+ totalCost += finalSegment.cost;
1890
+ nodesSearched += finalSegment.nodesSearched;
569
1891
  }
570
1892
  }
1893
+ return {
1894
+ found: fullPath.length > 0,
1895
+ path: fullPath,
1896
+ cost: totalCost,
1897
+ nodesSearched
1898
+ };
571
1899
  }
572
- /**
573
- * @zh 设置矩形区域的通行性
574
- * @en Set walkability for a rectangle region
575
- */
576
- setRectWalkable(x, y, width, height, walkable) {
577
- for (let dy = 0; dy < height; dy++) {
578
- for (let dx = 0; dx < width; dx++) {
579
- this.setWalkable(x + dx, y + dy, walkable);
1900
+ // =========================================================================
1901
+ // 辅助方法 | Helper Methods
1902
+ // =========================================================================
1903
+ findLocalPath(startX, startY, endX, endY, opts) {
1904
+ return this.localPathfinder.findPath(startX, startY, endX, endY, opts);
1905
+ }
1906
+ findInternalPath(startX, startY, endX, endY, cluster) {
1907
+ const cacheKey = `${cluster.id}:${startX},${startY}->${endX},${endY}`;
1908
+ if (this.config.cacheInternalPaths) {
1909
+ const cached = this.internalPathCache.get(cacheKey);
1910
+ if (cached) {
1911
+ return cached;
580
1912
  }
581
1913
  }
1914
+ const result = this.localPathfinder.findPath(startX, startY, endX, endY);
1915
+ if (result.found && this.config.cacheInternalPaths) {
1916
+ this.internalPathCache.set(cacheKey, [
1917
+ ...result.path
1918
+ ]);
1919
+ }
1920
+ return result.found ? [
1921
+ ...result.path
1922
+ ] : null;
1923
+ }
1924
+ calculatePathCost(path) {
1925
+ let cost = 0;
1926
+ for (let i = 1; i < path.length; i++) {
1927
+ const dx = Math.abs(path[i].x - path[i - 1].x);
1928
+ const dy = Math.abs(path[i].y - path[i - 1].y);
1929
+ cost += dx !== 0 && dy !== 0 ? Math.SQRT2 : 1;
1930
+ }
1931
+ return cost;
1932
+ }
1933
+ heuristic(a, b) {
1934
+ const dx = Math.abs(a.x - b.x);
1935
+ const dy = Math.abs(a.y - b.y);
1936
+ return dx + dy + (Math.SQRT2 - 2) * Math.min(dx, dy);
582
1937
  }
583
1938
  };
584
- __name(_GridMap, "GridMap");
585
- var GridMap = _GridMap;
586
- function createGridMap(width, height, options) {
587
- return new GridMap(width, height, options);
1939
+ __name(_HPAPathfinder, "HPAPathfinder");
1940
+ var HPAPathfinder = _HPAPathfinder;
1941
+ function createHPAPathfinder(map, config) {
1942
+ return new HPAPathfinder(map, config);
588
1943
  }
589
- __name(createGridMap, "createGridMap");
1944
+ __name(createHPAPathfinder, "createHPAPathfinder");
590
1945
 
591
1946
  // src/navmesh/NavMesh.ts
592
- var _a;
593
- var NavMeshNode = (_a = class {
1947
+ var _a3;
1948
+ var NavMeshNode = (_a3 = class {
594
1949
  constructor(polygon) {
595
1950
  __publicField(this, "id");
596
1951
  __publicField(this, "position");
@@ -603,7 +1958,7 @@ var NavMeshNode = (_a = class {
603
1958
  this.walkable = true;
604
1959
  this.polygon = polygon;
605
1960
  }
606
- }, __name(_a, "NavMeshNode"), _a);
1961
+ }, __name(_a3, "NavMeshNode"), _a3);
607
1962
  var _NavMesh = class _NavMesh {
608
1963
  constructor() {
609
1964
  __publicField(this, "polygons", /* @__PURE__ */ new Map());
@@ -1004,819 +2359,48 @@ function createNavMesh() {
1004
2359
  return new NavMesh();
1005
2360
  }
1006
2361
  __name(createNavMesh, "createNavMesh");
1007
-
1008
- // src/smoothing/PathSmoother.ts
1009
- function bresenhamLineOfSight(x1, y1, x2, y2, map) {
1010
- let ix1 = Math.floor(x1);
1011
- let iy1 = Math.floor(y1);
1012
- const ix2 = Math.floor(x2);
1013
- const iy2 = Math.floor(y2);
1014
- const dx = Math.abs(ix2 - ix1);
1015
- const dy = Math.abs(iy2 - iy1);
1016
- const sx = ix1 < ix2 ? 1 : -1;
1017
- const sy = iy1 < iy2 ? 1 : -1;
1018
- let err = dx - dy;
1019
- while (true) {
1020
- if (!map.isWalkable(ix1, iy1)) {
1021
- return false;
1022
- }
1023
- if (ix1 === ix2 && iy1 === iy2) {
1024
- break;
1025
- }
1026
- const e2 = 2 * err;
1027
- if (e2 > -dy) {
1028
- err -= dy;
1029
- ix1 += sx;
1030
- }
1031
- if (e2 < dx) {
1032
- err += dx;
1033
- iy1 += sy;
1034
- }
1035
- }
1036
- return true;
1037
- }
1038
- __name(bresenhamLineOfSight, "bresenhamLineOfSight");
1039
- function raycastLineOfSight(x1, y1, x2, y2, map, stepSize = 0.5) {
1040
- const dx = x2 - x1;
1041
- const dy = y2 - y1;
1042
- const distance = Math.sqrt(dx * dx + dy * dy);
1043
- if (distance === 0) {
1044
- return map.isWalkable(Math.floor(x1), Math.floor(y1));
1045
- }
1046
- const steps = Math.ceil(distance / stepSize);
1047
- const stepX = dx / steps;
1048
- const stepY = dy / steps;
1049
- let x = x1;
1050
- let y = y1;
1051
- for (let i = 0; i <= steps; i++) {
1052
- if (!map.isWalkable(Math.floor(x), Math.floor(y))) {
1053
- return false;
1054
- }
1055
- x += stepX;
1056
- y += stepY;
1057
- }
1058
- return true;
1059
- }
1060
- __name(raycastLineOfSight, "raycastLineOfSight");
1061
- var _LineOfSightSmoother = class _LineOfSightSmoother {
1062
- constructor(lineOfSight = bresenhamLineOfSight) {
1063
- __publicField(this, "lineOfSight");
1064
- this.lineOfSight = lineOfSight;
1065
- }
1066
- smooth(path, map) {
1067
- if (path.length <= 2) {
1068
- return [
1069
- ...path
1070
- ];
1071
- }
1072
- const result = [
1073
- path[0]
1074
- ];
1075
- let current = 0;
1076
- while (current < path.length - 1) {
1077
- let furthest = current + 1;
1078
- for (let i = path.length - 1; i > current + 1; i--) {
1079
- if (this.lineOfSight(path[current].x, path[current].y, path[i].x, path[i].y, map)) {
1080
- furthest = i;
1081
- break;
1082
- }
1083
- }
1084
- result.push(path[furthest]);
1085
- current = furthest;
1086
- }
1087
- return result;
1088
- }
1089
- };
1090
- __name(_LineOfSightSmoother, "LineOfSightSmoother");
1091
- var LineOfSightSmoother = _LineOfSightSmoother;
1092
- var _CatmullRomSmoother = class _CatmullRomSmoother {
1093
- /**
1094
- * @param segments - @zh 每段之间的插值点数 @en Number of interpolation points per segment
1095
- * @param tension - @zh 张力 (0-1) @en Tension (0-1)
1096
- */
1097
- constructor(segments = 5, tension = 0.5) {
1098
- __publicField(this, "segments");
1099
- __publicField(this, "tension");
1100
- this.segments = segments;
1101
- this.tension = tension;
1102
- }
1103
- smooth(path, _map) {
1104
- if (path.length <= 2) {
1105
- return [
1106
- ...path
1107
- ];
1108
- }
1109
- const result = [];
1110
- const points = [
1111
- path[0],
1112
- ...path,
1113
- path[path.length - 1]
1114
- ];
1115
- for (let i = 1; i < points.length - 2; i++) {
1116
- const p0 = points[i - 1];
1117
- const p1 = points[i];
1118
- const p2 = points[i + 1];
1119
- const p3 = points[i + 2];
1120
- for (let j = 0; j < this.segments; j++) {
1121
- const t = j / this.segments;
1122
- const point = this.interpolate(p0, p1, p2, p3, t);
1123
- result.push(point);
1124
- }
1125
- }
1126
- result.push(path[path.length - 1]);
1127
- return result;
1128
- }
1129
- /**
1130
- * @zh Catmull-Rom 插值
1131
- * @en Catmull-Rom interpolation
1132
- */
1133
- interpolate(p0, p1, p2, p3, t) {
1134
- const t2 = t * t;
1135
- const t3 = t2 * t;
1136
- const tension = this.tension;
1137
- const x = 0.5 * (2 * p1.x + (-p0.x + p2.x) * t * tension + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 * tension + (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3 * tension);
1138
- const y = 0.5 * (2 * p1.y + (-p0.y + p2.y) * t * tension + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 * tension + (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3 * tension);
1139
- return createPoint(x, y);
1140
- }
1141
- };
1142
- __name(_CatmullRomSmoother, "CatmullRomSmoother");
1143
- var CatmullRomSmoother = _CatmullRomSmoother;
1144
- var _CombinedSmoother = class _CombinedSmoother {
1145
- constructor(curveSegments = 5, tension = 0.5) {
1146
- __publicField(this, "simplifier");
1147
- __publicField(this, "curveSmoother");
1148
- this.simplifier = new LineOfSightSmoother();
1149
- this.curveSmoother = new CatmullRomSmoother(curveSegments, tension);
1150
- }
1151
- smooth(path, map) {
1152
- const simplified = this.simplifier.smooth(path, map);
1153
- return this.curveSmoother.smooth(simplified, map);
1154
- }
1155
- };
1156
- __name(_CombinedSmoother, "CombinedSmoother");
1157
- var CombinedSmoother = _CombinedSmoother;
1158
- function createLineOfSightSmoother(lineOfSight) {
1159
- return new LineOfSightSmoother(lineOfSight);
1160
- }
1161
- __name(createLineOfSightSmoother, "createLineOfSightSmoother");
1162
- function createCatmullRomSmoother(segments, tension) {
1163
- return new CatmullRomSmoother(segments, tension);
1164
- }
1165
- __name(createCatmullRomSmoother, "createCatmullRomSmoother");
1166
- function createCombinedSmoother(curveSegments, tension) {
1167
- return new CombinedSmoother(curveSegments, tension);
1168
- }
1169
- __name(createCombinedSmoother, "createCombinedSmoother");
1170
-
1171
- // src/nodes/PathfindingNodes.ts
1172
- var FindPathTemplate = {
1173
- type: "FindPath",
1174
- title: "Find Path",
1175
- category: "custom",
1176
- description: "Find path from start to end / \u4ECE\u8D77\u70B9\u5230\u7EC8\u70B9\u5BFB\u8DEF",
1177
- keywords: [
1178
- "path",
1179
- "pathfinding",
1180
- "astar",
1181
- "navigate",
1182
- "route"
1183
- ],
1184
- menuPath: [
1185
- "Pathfinding",
1186
- "Find Path"
1187
- ],
1188
- inputs: [
1189
- {
1190
- name: "exec",
1191
- displayName: "",
1192
- type: "exec"
1193
- },
1194
- {
1195
- name: "startX",
1196
- displayName: "Start X",
1197
- type: "float"
1198
- },
1199
- {
1200
- name: "startY",
1201
- displayName: "Start Y",
1202
- type: "float"
1203
- },
1204
- {
1205
- name: "endX",
1206
- displayName: "End X",
1207
- type: "float"
1208
- },
1209
- {
1210
- name: "endY",
1211
- displayName: "End Y",
1212
- type: "float"
1213
- }
1214
- ],
1215
- outputs: [
1216
- {
1217
- name: "exec",
1218
- displayName: "",
1219
- type: "exec"
1220
- },
1221
- {
1222
- name: "found",
1223
- displayName: "Found",
1224
- type: "bool"
1225
- },
1226
- {
1227
- name: "path",
1228
- displayName: "Path",
1229
- type: "array"
1230
- },
1231
- {
1232
- name: "cost",
1233
- displayName: "Cost",
1234
- type: "float"
1235
- }
1236
- ],
1237
- color: "#4caf50"
1238
- };
1239
- var _FindPathExecutor = class _FindPathExecutor {
1240
- execute(node, context) {
1241
- const ctx = context;
1242
- const startX = ctx.evaluateInput(node.id, "startX", 0);
1243
- const startY = ctx.evaluateInput(node.id, "startY", 0);
1244
- const endX = ctx.evaluateInput(node.id, "endX", 0);
1245
- const endY = ctx.evaluateInput(node.id, "endY", 0);
1246
- const result = ctx.findPath(startX, startY, endX, endY);
1247
- return {
1248
- outputs: {
1249
- found: result.found,
1250
- path: result.path,
1251
- cost: result.cost
1252
- },
1253
- nextExec: "exec"
1254
- };
1255
- }
1256
- };
1257
- __name(_FindPathExecutor, "FindPathExecutor");
1258
- var FindPathExecutor = _FindPathExecutor;
1259
- var FindPathSmoothTemplate = {
1260
- type: "FindPathSmooth",
1261
- title: "Find Path (Smooth)",
1262
- category: "custom",
1263
- description: "Find path with smoothing / \u5BFB\u8DEF\u5E76\u5E73\u6ED1\u8DEF\u5F84",
1264
- keywords: [
1265
- "path",
1266
- "pathfinding",
1267
- "smooth",
1268
- "navigate"
1269
- ],
1270
- menuPath: [
1271
- "Pathfinding",
1272
- "Find Path (Smooth)"
1273
- ],
1274
- inputs: [
1275
- {
1276
- name: "exec",
1277
- displayName: "",
1278
- type: "exec"
1279
- },
1280
- {
1281
- name: "startX",
1282
- displayName: "Start X",
1283
- type: "float"
1284
- },
1285
- {
1286
- name: "startY",
1287
- displayName: "Start Y",
1288
- type: "float"
1289
- },
1290
- {
1291
- name: "endX",
1292
- displayName: "End X",
1293
- type: "float"
1294
- },
1295
- {
1296
- name: "endY",
1297
- displayName: "End Y",
1298
- type: "float"
1299
- }
1300
- ],
1301
- outputs: [
1302
- {
1303
- name: "exec",
1304
- displayName: "",
1305
- type: "exec"
1306
- },
1307
- {
1308
- name: "found",
1309
- displayName: "Found",
1310
- type: "bool"
1311
- },
1312
- {
1313
- name: "path",
1314
- displayName: "Path",
1315
- type: "array"
1316
- },
1317
- {
1318
- name: "cost",
1319
- displayName: "Cost",
1320
- type: "float"
1321
- }
1322
- ],
1323
- color: "#4caf50"
1324
- };
1325
- var _FindPathSmoothExecutor = class _FindPathSmoothExecutor {
1326
- execute(node, context) {
1327
- const ctx = context;
1328
- const startX = ctx.evaluateInput(node.id, "startX", 0);
1329
- const startY = ctx.evaluateInput(node.id, "startY", 0);
1330
- const endX = ctx.evaluateInput(node.id, "endX", 0);
1331
- const endY = ctx.evaluateInput(node.id, "endY", 0);
1332
- const result = ctx.findPathSmooth(startX, startY, endX, endY);
1333
- return {
1334
- outputs: {
1335
- found: result.found,
1336
- path: result.path,
1337
- cost: result.cost
1338
- },
1339
- nextExec: "exec"
1340
- };
1341
- }
1342
- };
1343
- __name(_FindPathSmoothExecutor, "FindPathSmoothExecutor");
1344
- var FindPathSmoothExecutor = _FindPathSmoothExecutor;
1345
- var IsWalkableTemplate = {
1346
- type: "IsWalkable",
1347
- title: "Is Walkable",
1348
- category: "custom",
1349
- description: "Check if position is walkable / \u68C0\u67E5\u4F4D\u7F6E\u662F\u5426\u53EF\u901A\u884C",
1350
- keywords: [
1351
- "walkable",
1352
- "obstacle",
1353
- "blocked",
1354
- "terrain"
1355
- ],
1356
- menuPath: [
1357
- "Pathfinding",
1358
- "Is Walkable"
1359
- ],
1360
- isPure: true,
1361
- inputs: [
1362
- {
1363
- name: "x",
1364
- displayName: "X",
1365
- type: "float"
1366
- },
1367
- {
1368
- name: "y",
1369
- displayName: "Y",
1370
- type: "float"
1371
- }
1372
- ],
1373
- outputs: [
1374
- {
1375
- name: "walkable",
1376
- displayName: "Walkable",
1377
- type: "bool"
1378
- }
1379
- ],
1380
- color: "#4caf50"
1381
- };
1382
- var _IsWalkableExecutor = class _IsWalkableExecutor {
1383
- execute(node, context) {
1384
- const ctx = context;
1385
- const x = ctx.evaluateInput(node.id, "x", 0);
1386
- const y = ctx.evaluateInput(node.id, "y", 0);
1387
- const walkable = ctx.isWalkable(x, y);
1388
- return {
1389
- outputs: {
1390
- walkable
1391
- }
1392
- };
1393
- }
1394
- };
1395
- __name(_IsWalkableExecutor, "IsWalkableExecutor");
1396
- var IsWalkableExecutor = _IsWalkableExecutor;
1397
- var GetPathLengthTemplate = {
1398
- type: "GetPathLength",
1399
- title: "Get Path Length",
1400
- category: "custom",
1401
- description: "Get the number of points in path / \u83B7\u53D6\u8DEF\u5F84\u70B9\u6570\u91CF",
1402
- keywords: [
1403
- "path",
1404
- "length",
1405
- "count",
1406
- "waypoints"
1407
- ],
1408
- menuPath: [
1409
- "Pathfinding",
1410
- "Get Path Length"
1411
- ],
1412
- isPure: true,
1413
- inputs: [
1414
- {
1415
- name: "path",
1416
- displayName: "Path",
1417
- type: "array"
1418
- }
1419
- ],
1420
- outputs: [
1421
- {
1422
- name: "length",
1423
- displayName: "Length",
1424
- type: "int"
1425
- }
1426
- ],
1427
- color: "#4caf50"
1428
- };
1429
- var _GetPathLengthExecutor = class _GetPathLengthExecutor {
1430
- execute(node, context) {
1431
- const ctx = context;
1432
- const path = ctx.evaluateInput(node.id, "path", []);
1433
- return {
1434
- outputs: {
1435
- length: path.length
1436
- }
1437
- };
1438
- }
1439
- };
1440
- __name(_GetPathLengthExecutor, "GetPathLengthExecutor");
1441
- var GetPathLengthExecutor = _GetPathLengthExecutor;
1442
- var GetPathDistanceTemplate = {
1443
- type: "GetPathDistance",
1444
- title: "Get Path Distance",
1445
- category: "custom",
1446
- description: "Get total path distance / \u83B7\u53D6\u8DEF\u5F84\u603B\u8DDD\u79BB",
1447
- keywords: [
1448
- "path",
1449
- "distance",
1450
- "length",
1451
- "travel"
1452
- ],
1453
- menuPath: [
1454
- "Pathfinding",
1455
- "Get Path Distance"
1456
- ],
1457
- isPure: true,
1458
- inputs: [
1459
- {
1460
- name: "path",
1461
- displayName: "Path",
1462
- type: "array"
1463
- }
1464
- ],
1465
- outputs: [
1466
- {
1467
- name: "distance",
1468
- displayName: "Distance",
1469
- type: "float"
1470
- }
1471
- ],
1472
- color: "#4caf50"
1473
- };
1474
- var _GetPathDistanceExecutor = class _GetPathDistanceExecutor {
1475
- execute(node, context) {
1476
- const ctx = context;
1477
- const path = ctx.evaluateInput(node.id, "path", []);
1478
- const distance = ctx.getPathDistance(path);
1479
- return {
1480
- outputs: {
1481
- distance
1482
- }
1483
- };
1484
- }
1485
- };
1486
- __name(_GetPathDistanceExecutor, "GetPathDistanceExecutor");
1487
- var GetPathDistanceExecutor = _GetPathDistanceExecutor;
1488
- var GetPathPointTemplate = {
1489
- type: "GetPathPoint",
1490
- title: "Get Path Point",
1491
- category: "custom",
1492
- description: "Get point at index in path / \u83B7\u53D6\u8DEF\u5F84\u4E2D\u6307\u5B9A\u7D22\u5F15\u7684\u70B9",
1493
- keywords: [
1494
- "path",
1495
- "point",
1496
- "waypoint",
1497
- "index"
1498
- ],
1499
- menuPath: [
1500
- "Pathfinding",
1501
- "Get Path Point"
1502
- ],
1503
- isPure: true,
1504
- inputs: [
1505
- {
1506
- name: "path",
1507
- displayName: "Path",
1508
- type: "array"
1509
- },
1510
- {
1511
- name: "index",
1512
- displayName: "Index",
1513
- type: "int"
1514
- }
1515
- ],
1516
- outputs: [
1517
- {
1518
- name: "x",
1519
- displayName: "X",
1520
- type: "float"
1521
- },
1522
- {
1523
- name: "y",
1524
- displayName: "Y",
1525
- type: "float"
1526
- },
1527
- {
1528
- name: "valid",
1529
- displayName: "Valid",
1530
- type: "bool"
1531
- }
1532
- ],
1533
- color: "#4caf50"
1534
- };
1535
- var _GetPathPointExecutor = class _GetPathPointExecutor {
1536
- execute(node, context) {
1537
- const ctx = context;
1538
- const path = ctx.evaluateInput(node.id, "path", []);
1539
- const index = ctx.evaluateInput(node.id, "index", 0);
1540
- if (index >= 0 && index < path.length) {
1541
- return {
1542
- outputs: {
1543
- x: path[index].x,
1544
- y: path[index].y,
1545
- valid: true
1546
- }
1547
- };
1548
- }
1549
- return {
1550
- outputs: {
1551
- x: 0,
1552
- y: 0,
1553
- valid: false
1554
- }
1555
- };
1556
- }
1557
- };
1558
- __name(_GetPathPointExecutor, "GetPathPointExecutor");
1559
- var GetPathPointExecutor = _GetPathPointExecutor;
1560
- var MoveAlongPathTemplate = {
1561
- type: "MoveAlongPath",
1562
- title: "Move Along Path",
1563
- category: "custom",
1564
- description: "Get position along path at progress / \u83B7\u53D6\u8DEF\u5F84\u4E0A\u6307\u5B9A\u8FDB\u5EA6\u7684\u4F4D\u7F6E",
1565
- keywords: [
1566
- "path",
1567
- "move",
1568
- "lerp",
1569
- "progress",
1570
- "interpolate"
1571
- ],
1572
- menuPath: [
1573
- "Pathfinding",
1574
- "Move Along Path"
1575
- ],
1576
- isPure: true,
1577
- inputs: [
1578
- {
1579
- name: "path",
1580
- displayName: "Path",
1581
- type: "array"
1582
- },
1583
- {
1584
- name: "progress",
1585
- displayName: "Progress (0-1)",
1586
- type: "float"
1587
- }
1588
- ],
1589
- outputs: [
1590
- {
1591
- name: "x",
1592
- displayName: "X",
1593
- type: "float"
1594
- },
1595
- {
1596
- name: "y",
1597
- displayName: "Y",
1598
- type: "float"
1599
- }
1600
- ],
1601
- color: "#4caf50"
1602
- };
1603
- var _MoveAlongPathExecutor = class _MoveAlongPathExecutor {
1604
- execute(node, context) {
1605
- const ctx = context;
1606
- const path = ctx.evaluateInput(node.id, "path", []);
1607
- let progress = ctx.evaluateInput(node.id, "progress", 0);
1608
- if (path.length === 0) {
1609
- return {
1610
- outputs: {
1611
- x: 0,
1612
- y: 0
1613
- }
1614
- };
1615
- }
1616
- if (path.length === 1) {
1617
- return {
1618
- outputs: {
1619
- x: path[0].x,
1620
- y: path[0].y
1621
- }
1622
- };
1623
- }
1624
- progress = Math.max(0, Math.min(1, progress));
1625
- let totalDistance = 0;
1626
- const segmentDistances = [];
1627
- for (let i = 1; i < path.length; i++) {
1628
- const dx = path[i].x - path[i - 1].x;
1629
- const dy = path[i].y - path[i - 1].y;
1630
- const dist = Math.sqrt(dx * dx + dy * dy);
1631
- segmentDistances.push(dist);
1632
- totalDistance += dist;
1633
- }
1634
- if (totalDistance === 0) {
1635
- return {
1636
- outputs: {
1637
- x: path[0].x,
1638
- y: path[0].y
1639
- }
1640
- };
1641
- }
1642
- const targetDistance = progress * totalDistance;
1643
- let accumulatedDistance = 0;
1644
- for (let i = 0; i < segmentDistances.length; i++) {
1645
- const segmentDist = segmentDistances[i];
1646
- if (accumulatedDistance + segmentDist >= targetDistance) {
1647
- const segmentProgress = (targetDistance - accumulatedDistance) / segmentDist;
1648
- const x = path[i].x + (path[i + 1].x - path[i].x) * segmentProgress;
1649
- const y = path[i].y + (path[i + 1].y - path[i].y) * segmentProgress;
1650
- return {
1651
- outputs: {
1652
- x,
1653
- y
1654
- }
1655
- };
1656
- }
1657
- accumulatedDistance += segmentDist;
1658
- }
1659
- const last = path[path.length - 1];
1660
- return {
1661
- outputs: {
1662
- x: last.x,
1663
- y: last.y
1664
- }
1665
- };
1666
- }
1667
- };
1668
- __name(_MoveAlongPathExecutor, "MoveAlongPathExecutor");
1669
- var MoveAlongPathExecutor = _MoveAlongPathExecutor;
1670
- var HasLineOfSightTemplate = {
1671
- type: "HasLineOfSight",
1672
- title: "Has Line of Sight",
1673
- category: "custom",
1674
- description: "Check if there is a clear line between two points / \u68C0\u67E5\u4E24\u70B9\u4E4B\u95F4\u662F\u5426\u6709\u6E05\u6670\u7684\u89C6\u7EBF",
1675
- keywords: [
1676
- "line",
1677
- "sight",
1678
- "los",
1679
- "visibility",
1680
- "raycast"
1681
- ],
1682
- menuPath: [
1683
- "Pathfinding",
1684
- "Has Line of Sight"
1685
- ],
1686
- isPure: true,
1687
- inputs: [
1688
- {
1689
- name: "startX",
1690
- displayName: "Start X",
1691
- type: "float"
1692
- },
1693
- {
1694
- name: "startY",
1695
- displayName: "Start Y",
1696
- type: "float"
1697
- },
1698
- {
1699
- name: "endX",
1700
- displayName: "End X",
1701
- type: "float"
1702
- },
1703
- {
1704
- name: "endY",
1705
- displayName: "End Y",
1706
- type: "float"
1707
- }
1708
- ],
1709
- outputs: [
1710
- {
1711
- name: "hasLOS",
1712
- displayName: "Has LOS",
1713
- type: "bool"
1714
- }
1715
- ],
1716
- color: "#4caf50"
1717
- };
1718
- var _HasLineOfSightExecutor = class _HasLineOfSightExecutor {
1719
- execute(node, context) {
1720
- const ctx = context;
1721
- const startX = ctx.evaluateInput(node.id, "startX", 0);
1722
- const startY = ctx.evaluateInput(node.id, "startY", 0);
1723
- const endX = ctx.evaluateInput(node.id, "endX", 0);
1724
- const endY = ctx.evaluateInput(node.id, "endY", 0);
1725
- const hasLOS = ctx.hasLineOfSight?.(startX, startY, endX, endY) ?? true;
1726
- return {
1727
- outputs: {
1728
- hasLOS
1729
- }
1730
- };
1731
- }
1732
- };
1733
- __name(_HasLineOfSightExecutor, "HasLineOfSightExecutor");
1734
- var HasLineOfSightExecutor = _HasLineOfSightExecutor;
1735
- var PathfindingNodeDefinitions = {
1736
- templates: [
1737
- FindPathTemplate,
1738
- FindPathSmoothTemplate,
1739
- IsWalkableTemplate,
1740
- GetPathLengthTemplate,
1741
- GetPathDistanceTemplate,
1742
- GetPathPointTemplate,
1743
- MoveAlongPathTemplate,
1744
- HasLineOfSightTemplate
1745
- ],
1746
- executors: /* @__PURE__ */ new Map([
1747
- [
1748
- "FindPath",
1749
- new FindPathExecutor()
1750
- ],
1751
- [
1752
- "FindPathSmooth",
1753
- new FindPathSmoothExecutor()
1754
- ],
1755
- [
1756
- "IsWalkable",
1757
- new IsWalkableExecutor()
1758
- ],
1759
- [
1760
- "GetPathLength",
1761
- new GetPathLengthExecutor()
1762
- ],
1763
- [
1764
- "GetPathDistance",
1765
- new GetPathDistanceExecutor()
1766
- ],
1767
- [
1768
- "GetPathPoint",
1769
- new GetPathPointExecutor()
1770
- ],
1771
- [
1772
- "MoveAlongPath",
1773
- new MoveAlongPathExecutor()
1774
- ],
1775
- [
1776
- "HasLineOfSight",
1777
- new HasLineOfSightExecutor()
1778
- ]
1779
- ])
1780
- };
1781
2362
  export {
1782
2363
  AStarPathfinder,
1783
2364
  BinaryHeap,
1784
2365
  CatmullRomSmoother,
1785
2366
  CombinedSmoother,
1786
2367
  DEFAULT_GRID_OPTIONS,
2368
+ DEFAULT_HPA_CONFIG,
1787
2369
  DEFAULT_PATHFINDING_OPTIONS,
2370
+ DEFAULT_PATH_CACHE_CONFIG,
2371
+ DEFAULT_REPLANNING_CONFIG,
1788
2372
  DIRECTIONS_4,
1789
2373
  DIRECTIONS_8,
1790
2374
  EMPTY_PATH_RESULT,
1791
- FindPathExecutor,
1792
- FindPathSmoothExecutor,
1793
- FindPathSmoothTemplate,
1794
- FindPathTemplate,
1795
- GetPathDistanceExecutor,
1796
- GetPathDistanceTemplate,
1797
- GetPathLengthExecutor,
1798
- GetPathLengthTemplate,
1799
- GetPathPointExecutor,
1800
- GetPathPointTemplate,
2375
+ EMPTY_PROGRESS,
1801
2376
  GridMap,
1802
2377
  GridNode,
1803
- HasLineOfSightExecutor,
1804
- HasLineOfSightTemplate,
1805
- IsWalkableExecutor,
1806
- IsWalkableTemplate,
2378
+ GridPathfinder,
2379
+ HPAPathfinder,
2380
+ IncrementalAStarPathfinder,
2381
+ IndexedBinaryHeap,
2382
+ JPSPathfinder,
1807
2383
  LineOfSightSmoother,
1808
- MoveAlongPathExecutor,
1809
- MoveAlongPathTemplate,
1810
2384
  NavMesh,
1811
- PathfindingNodeDefinitions,
2385
+ ObstacleChangeManager,
2386
+ PathCache,
2387
+ PathValidator,
2388
+ PathfindingState,
1812
2389
  bresenhamLineOfSight,
1813
2390
  chebyshevDistance,
1814
2391
  createAStarPathfinder,
1815
2392
  createCatmullRomSmoother,
1816
2393
  createCombinedSmoother,
1817
2394
  createGridMap,
2395
+ createGridPathfinder,
2396
+ createHPAPathfinder,
2397
+ createIncrementalAStarPathfinder,
2398
+ createJPSPathfinder,
1818
2399
  createLineOfSightSmoother,
1819
2400
  createNavMesh,
2401
+ createObstacleChangeManager,
2402
+ createPathCache,
2403
+ createPathValidator,
1820
2404
  createPoint,
1821
2405
  euclideanDistance,
1822
2406
  manhattanDistance,