@esengine/pathfinding 12.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/IIncrementalPathfinding-3qs7e_pO.d.ts +450 -0
- package/dist/chunk-GTFFYRZM.js +36 -0
- package/dist/chunk-GTFFYRZM.js.map +1 -0
- package/dist/chunk-TPT7Q3E3.js +1648 -0
- package/dist/chunk-TPT7Q3E3.js.map +1 -0
- package/dist/ecs.d.ts +503 -0
- package/dist/ecs.js +1033 -0
- package/dist/ecs.js.map +1 -0
- package/dist/index.d.ts +886 -192
- package/dist/index.js +1650 -1066
- package/dist/index.js.map +1 -1
- package/dist/nodes.d.ts +143 -0
- package/dist/nodes.js +1174 -0
- package/dist/nodes.js.map +1 -0
- package/package.json +23 -7
package/dist/index.js
CHANGED
|
@@ -1,50 +1,43 @@
|
|
|
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
|
-
__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
|
|
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.
|
|
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/
|
|
318
|
-
var
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
__publicField(this, "
|
|
326
|
-
this
|
|
327
|
-
this
|
|
328
|
-
this
|
|
329
|
-
this
|
|
330
|
-
this
|
|
331
|
-
this
|
|
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(
|
|
335
|
-
var
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
|
397
|
-
constructor(
|
|
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, "
|
|
401
|
-
__publicField(this, "
|
|
402
|
-
this
|
|
403
|
-
this
|
|
404
|
-
this
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
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
|
|
1430
|
+
* @zh 预处理地图(构建抽象图)
|
|
1431
|
+
* @en Preprocess map (build abstract graph)
|
|
1432
|
+
*
|
|
1433
|
+
* @zh 在地图变化后需要重新调用
|
|
1434
|
+
* @en Need to call again after map changes
|
|
413
1435
|
*/
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
|
1450
|
+
* @zh 寻找路径
|
|
1451
|
+
* @en Find path
|
|
427
1452
|
*/
|
|
428
|
-
|
|
429
|
-
if (!this.
|
|
430
|
-
|
|
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
|
-
|
|
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
|
|
1496
|
+
* @zh 清理状态
|
|
1497
|
+
* @en Clear state
|
|
437
1498
|
*/
|
|
438
|
-
|
|
439
|
-
|
|
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
|
|
1508
|
+
* @zh 通知地图区域变化
|
|
1509
|
+
* @en Notify map region change
|
|
444
1510
|
*/
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
1511
|
+
notifyRegionChange(minX, minY, maxX, maxY) {
|
|
1512
|
+
this.preprocessed = false;
|
|
1513
|
+
this.internalPathCache.clear();
|
|
448
1514
|
}
|
|
449
1515
|
/**
|
|
450
|
-
* @zh
|
|
451
|
-
* @en
|
|
1516
|
+
* @zh 获取预处理统计信息
|
|
1517
|
+
* @en Get preprocessing statistics
|
|
452
1518
|
*/
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
|
1840
|
+
return null;
|
|
559
1841
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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(
|
|
585
|
-
var
|
|
586
|
-
function
|
|
587
|
-
return new
|
|
1939
|
+
__name(_HPAPathfinder, "HPAPathfinder");
|
|
1940
|
+
var HPAPathfinder = _HPAPathfinder;
|
|
1941
|
+
function createHPAPathfinder(map, config) {
|
|
1942
|
+
return new HPAPathfinder(map, config);
|
|
588
1943
|
}
|
|
589
|
-
__name(
|
|
1944
|
+
__name(createHPAPathfinder, "createHPAPathfinder");
|
|
590
1945
|
|
|
591
1946
|
// src/navmesh/NavMesh.ts
|
|
592
|
-
var
|
|
593
|
-
var NavMeshNode = (
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
2378
|
+
GridPathfinder,
|
|
2379
|
+
HPAPathfinder,
|
|
2380
|
+
IncrementalAStarPathfinder,
|
|
2381
|
+
IndexedBinaryHeap,
|
|
2382
|
+
JPSPathfinder,
|
|
1807
2383
|
LineOfSightSmoother,
|
|
1808
|
-
MoveAlongPathExecutor,
|
|
1809
|
-
MoveAlongPathTemplate,
|
|
1810
2384
|
NavMesh,
|
|
1811
|
-
|
|
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,
|