@esengine/pathfinding 13.2.0 → 13.3.1

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.
@@ -1,1650 +0,0 @@
1
- import {
2
- EMPTY_PROGRESS,
3
- PathfindingState
4
- } from "./chunk-YKA3PWU3.js";
5
- import {
6
- __name,
7
- __publicField
8
- } from "./chunk-T626JPC7.js";
9
-
10
- // src/core/IPathfinding.ts
11
- function createPoint(x, y) {
12
- return {
13
- x,
14
- y
15
- };
16
- }
17
- __name(createPoint, "createPoint");
18
- var EMPTY_PATH_RESULT = {
19
- found: false,
20
- path: [],
21
- cost: 0,
22
- nodesSearched: 0
23
- };
24
- function manhattanDistance(a, b) {
25
- return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
26
- }
27
- __name(manhattanDistance, "manhattanDistance");
28
- function euclideanDistance(a, b) {
29
- const dx = a.x - b.x;
30
- const dy = a.y - b.y;
31
- return Math.sqrt(dx * dx + dy * dy);
32
- }
33
- __name(euclideanDistance, "euclideanDistance");
34
- function chebyshevDistance(a, b) {
35
- return Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y));
36
- }
37
- __name(chebyshevDistance, "chebyshevDistance");
38
- function octileDistance(a, b) {
39
- const dx = Math.abs(a.x - b.x);
40
- const dy = Math.abs(a.y - b.y);
41
- const D = 1;
42
- const D2 = Math.SQRT2;
43
- return D * (dx + dy) + (D2 - 2 * D) * Math.min(dx, dy);
44
- }
45
- __name(octileDistance, "octileDistance");
46
- var DEFAULT_PATHFINDING_OPTIONS = {
47
- maxNodes: 1e4,
48
- heuristicWeight: 1,
49
- allowDiagonal: true,
50
- avoidCorners: true
51
- };
52
-
53
- // src/core/IndexedBinaryHeap.ts
54
- var _IndexedBinaryHeap = class _IndexedBinaryHeap {
55
- /**
56
- * @zh 创建带索引追踪的二叉堆
57
- * @en Create indexed binary heap
58
- *
59
- * @param compare - @zh 比较函数,返回负数表示 a < b @en Compare function, returns negative if a < b
60
- */
61
- constructor(compare) {
62
- __publicField(this, "heap", []);
63
- __publicField(this, "compare");
64
- this.compare = compare;
65
- }
66
- /**
67
- * @zh 堆大小
68
- * @en Heap size
69
- */
70
- get size() {
71
- return this.heap.length;
72
- }
73
- /**
74
- * @zh 是否为空
75
- * @en Is empty
76
- */
77
- get isEmpty() {
78
- return this.heap.length === 0;
79
- }
80
- /**
81
- * @zh 插入元素
82
- * @en Push element
83
- */
84
- push(item) {
85
- item.heapIndex = this.heap.length;
86
- this.heap.push(item);
87
- this.bubbleUp(this.heap.length - 1);
88
- }
89
- /**
90
- * @zh 弹出最小元素
91
- * @en Pop minimum element
92
- */
93
- pop() {
94
- if (this.heap.length === 0) {
95
- return void 0;
96
- }
97
- const result = this.heap[0];
98
- result.heapIndex = -1;
99
- const last = this.heap.pop();
100
- if (this.heap.length > 0) {
101
- last.heapIndex = 0;
102
- this.heap[0] = last;
103
- this.sinkDown(0);
104
- }
105
- return result;
106
- }
107
- /**
108
- * @zh 查看最小元素(不移除)
109
- * @en Peek minimum element (without removing)
110
- */
111
- peek() {
112
- return this.heap[0];
113
- }
114
- /**
115
- * @zh 更新元素
116
- * @en Update element
117
- */
118
- update(item) {
119
- const index = item.heapIndex;
120
- if (index >= 0 && index < this.heap.length && this.heap[index] === item) {
121
- this.bubbleUp(index);
122
- this.sinkDown(item.heapIndex);
123
- }
124
- }
125
- /**
126
- * @zh 检查是否包含元素
127
- * @en Check if contains element
128
- */
129
- contains(item) {
130
- const index = item.heapIndex;
131
- return index >= 0 && index < this.heap.length && this.heap[index] === item;
132
- }
133
- /**
134
- * @zh 从堆中移除指定元素
135
- * @en Remove specific element from heap
136
- */
137
- remove(item) {
138
- const index = item.heapIndex;
139
- if (index < 0 || index >= this.heap.length || this.heap[index] !== item) {
140
- return false;
141
- }
142
- item.heapIndex = -1;
143
- if (index === this.heap.length - 1) {
144
- this.heap.pop();
145
- return true;
146
- }
147
- const last = this.heap.pop();
148
- last.heapIndex = index;
149
- this.heap[index] = last;
150
- this.bubbleUp(index);
151
- this.sinkDown(last.heapIndex);
152
- return true;
153
- }
154
- /**
155
- * @zh 清空堆
156
- * @en Clear heap
157
- */
158
- clear() {
159
- for (const item of this.heap) {
160
- item.heapIndex = -1;
161
- }
162
- this.heap.length = 0;
163
- }
164
- /**
165
- * @zh 上浮操作
166
- * @en Bubble up operation
167
- */
168
- bubbleUp(index) {
169
- const item = this.heap[index];
170
- while (index > 0) {
171
- const parentIndex = index - 1 >> 1;
172
- const parent = this.heap[parentIndex];
173
- if (this.compare(item, parent) >= 0) {
174
- break;
175
- }
176
- parent.heapIndex = index;
177
- this.heap[index] = parent;
178
- index = parentIndex;
179
- }
180
- item.heapIndex = index;
181
- this.heap[index] = item;
182
- }
183
- /**
184
- * @zh 下沉操作
185
- * @en Sink down operation
186
- */
187
- sinkDown(index) {
188
- const length = this.heap.length;
189
- const item = this.heap[index];
190
- const halfLength = length >> 1;
191
- while (index < halfLength) {
192
- const leftIndex = (index << 1) + 1;
193
- const rightIndex = leftIndex + 1;
194
- let smallest = index;
195
- let smallestItem = item;
196
- const left = this.heap[leftIndex];
197
- if (this.compare(left, smallestItem) < 0) {
198
- smallest = leftIndex;
199
- smallestItem = left;
200
- }
201
- if (rightIndex < length) {
202
- const right = this.heap[rightIndex];
203
- if (this.compare(right, smallestItem) < 0) {
204
- smallest = rightIndex;
205
- smallestItem = right;
206
- }
207
- }
208
- if (smallest === index) {
209
- break;
210
- }
211
- smallestItem.heapIndex = index;
212
- this.heap[index] = smallestItem;
213
- index = smallest;
214
- }
215
- item.heapIndex = index;
216
- this.heap[index] = item;
217
- }
218
- };
219
- __name(_IndexedBinaryHeap, "IndexedBinaryHeap");
220
- var IndexedBinaryHeap = _IndexedBinaryHeap;
221
-
222
- // src/core/PathCache.ts
223
- var DEFAULT_PATH_CACHE_CONFIG = {
224
- maxEntries: 1e3,
225
- ttlMs: 5e3,
226
- enableApproximateMatch: false,
227
- approximateRange: 2
228
- };
229
- var _PathCache = class _PathCache {
230
- constructor(config = {}) {
231
- __publicField(this, "config");
232
- __publicField(this, "cache");
233
- __publicField(this, "accessOrder");
234
- this.config = {
235
- ...DEFAULT_PATH_CACHE_CONFIG,
236
- ...config
237
- };
238
- this.cache = /* @__PURE__ */ new Map();
239
- this.accessOrder = [];
240
- }
241
- /**
242
- * @zh 获取缓存的路径
243
- * @en Get cached path
244
- *
245
- * @param startX - @zh 起点 X 坐标 @en Start X coordinate
246
- * @param startY - @zh 起点 Y 坐标 @en Start Y coordinate
247
- * @param endX - @zh 终点 X 坐标 @en End X coordinate
248
- * @param endY - @zh 终点 Y 坐标 @en End Y coordinate
249
- * @param mapVersion - @zh 地图版本号 @en Map version number
250
- * @returns @zh 缓存的路径结果或 null @en Cached path result or null
251
- */
252
- get(startX, startY, endX, endY, mapVersion) {
253
- const key = this.generateKey(startX, startY, endX, endY);
254
- const entry = this.cache.get(key);
255
- if (!entry) {
256
- if (this.config.enableApproximateMatch) {
257
- return this.getApproximate(startX, startY, endX, endY, mapVersion);
258
- }
259
- return null;
260
- }
261
- if (!this.isValid(entry, mapVersion)) {
262
- this.cache.delete(key);
263
- this.removeFromAccessOrder(key);
264
- return null;
265
- }
266
- this.updateAccessOrder(key);
267
- return entry.result;
268
- }
269
- /**
270
- * @zh 设置缓存路径
271
- * @en Set cached path
272
- *
273
- * @param startX - @zh 起点 X 坐标 @en Start X coordinate
274
- * @param startY - @zh 起点 Y 坐标 @en Start Y coordinate
275
- * @param endX - @zh 终点 X 坐标 @en End X coordinate
276
- * @param endY - @zh 终点 Y 坐标 @en End Y coordinate
277
- * @param result - @zh 路径结果 @en Path result
278
- * @param mapVersion - @zh 地图版本号 @en Map version number
279
- */
280
- set(startX, startY, endX, endY, result, mapVersion) {
281
- if (this.cache.size >= this.config.maxEntries) {
282
- this.evictLRU();
283
- }
284
- const key = this.generateKey(startX, startY, endX, endY);
285
- const entry = {
286
- result,
287
- timestamp: Date.now(),
288
- mapVersion
289
- };
290
- this.cache.set(key, entry);
291
- this.updateAccessOrder(key);
292
- }
293
- /**
294
- * @zh 使所有缓存失效
295
- * @en Invalidate all cache
296
- */
297
- invalidateAll() {
298
- this.cache.clear();
299
- this.accessOrder.length = 0;
300
- }
301
- /**
302
- * @zh 使指定区域的缓存失效
303
- * @en Invalidate cache for specified region
304
- *
305
- * @param minX - @zh 最小 X 坐标 @en Minimum X coordinate
306
- * @param minY - @zh 最小 Y 坐标 @en Minimum Y coordinate
307
- * @param maxX - @zh 最大 X 坐标 @en Maximum X coordinate
308
- * @param maxY - @zh 最大 Y 坐标 @en Maximum Y coordinate
309
- */
310
- invalidateRegion(minX, minY, maxX, maxY) {
311
- const keysToDelete = [];
312
- for (const [key, entry] of this.cache) {
313
- const path = entry.result.path;
314
- if (path.length === 0) continue;
315
- for (const point of path) {
316
- if (point.x >= minX && point.x <= maxX && point.y >= minY && point.y <= maxY) {
317
- keysToDelete.push(key);
318
- break;
319
- }
320
- }
321
- }
322
- for (const key of keysToDelete) {
323
- this.cache.delete(key);
324
- this.removeFromAccessOrder(key);
325
- }
326
- }
327
- /**
328
- * @zh 获取缓存统计信息
329
- * @en Get cache statistics
330
- */
331
- getStats() {
332
- return {
333
- size: this.cache.size,
334
- maxSize: this.config.maxEntries
335
- };
336
- }
337
- /**
338
- * @zh 清理过期条目
339
- * @en Clean up expired entries
340
- */
341
- cleanup() {
342
- if (this.config.ttlMs === 0) return;
343
- const now = Date.now();
344
- const keysToDelete = [];
345
- for (const [key, entry] of this.cache) {
346
- if (now - entry.timestamp > this.config.ttlMs) {
347
- keysToDelete.push(key);
348
- }
349
- }
350
- for (const key of keysToDelete) {
351
- this.cache.delete(key);
352
- this.removeFromAccessOrder(key);
353
- }
354
- }
355
- // =========================================================================
356
- // 私有方法 | Private Methods
357
- // =========================================================================
358
- generateKey(startX, startY, endX, endY) {
359
- return `${startX},${startY}->${endX},${endY}`;
360
- }
361
- isValid(entry, mapVersion) {
362
- if (entry.mapVersion !== mapVersion) {
363
- return false;
364
- }
365
- if (this.config.ttlMs > 0) {
366
- const age = Date.now() - entry.timestamp;
367
- if (age > this.config.ttlMs) {
368
- return false;
369
- }
370
- }
371
- return true;
372
- }
373
- getApproximate(startX, startY, endX, endY, mapVersion) {
374
- const range = this.config.approximateRange;
375
- for (let sx = startX - range; sx <= startX + range; sx++) {
376
- for (let sy = startY - range; sy <= startY + range; sy++) {
377
- for (let ex = endX - range; ex <= endX + range; ex++) {
378
- for (let ey = endY - range; ey <= endY + range; ey++) {
379
- const key = this.generateKey(sx, sy, ex, ey);
380
- const entry = this.cache.get(key);
381
- if (entry && this.isValid(entry, mapVersion)) {
382
- this.updateAccessOrder(key);
383
- return this.adjustPathForApproximate(entry.result, startX, startY, endX, endY);
384
- }
385
- }
386
- }
387
- }
388
- }
389
- return null;
390
- }
391
- adjustPathForApproximate(result, newStartX, newStartY, newEndX, newEndY) {
392
- if (result.path.length === 0) {
393
- return result;
394
- }
395
- const newPath = [];
396
- const oldStart = result.path[0];
397
- const oldEnd = result.path[result.path.length - 1];
398
- if (newStartX !== oldStart.x || newStartY !== oldStart.y) {
399
- newPath.push({
400
- x: newStartX,
401
- y: newStartY
402
- });
403
- }
404
- newPath.push(...result.path);
405
- if (newEndX !== oldEnd.x || newEndY !== oldEnd.y) {
406
- newPath.push({
407
- x: newEndX,
408
- y: newEndY
409
- });
410
- }
411
- return {
412
- ...result,
413
- path: newPath
414
- };
415
- }
416
- updateAccessOrder(key) {
417
- this.removeFromAccessOrder(key);
418
- this.accessOrder.push(key);
419
- }
420
- removeFromAccessOrder(key) {
421
- const index = this.accessOrder.indexOf(key);
422
- if (index !== -1) {
423
- this.accessOrder.splice(index, 1);
424
- }
425
- }
426
- evictLRU() {
427
- const lruKey = this.accessOrder.shift();
428
- if (lruKey) {
429
- this.cache.delete(lruKey);
430
- }
431
- }
432
- };
433
- __name(_PathCache, "PathCache");
434
- var PathCache = _PathCache;
435
- function createPathCache(config) {
436
- return new PathCache(config);
437
- }
438
- __name(createPathCache, "createPathCache");
439
-
440
- // src/core/IncrementalAStarPathfinder.ts
441
- var _IncrementalAStarPathfinder = class _IncrementalAStarPathfinder {
442
- /**
443
- * @zh 创建增量 A* 寻路器
444
- * @en Create incremental A* pathfinder
445
- *
446
- * @param map - @zh 寻路地图实例 @en Pathfinding map instance
447
- * @param config - @zh 配置选项 @en Configuration options
448
- */
449
- constructor(map, config) {
450
- __publicField(this, "map");
451
- __publicField(this, "sessions", /* @__PURE__ */ new Map());
452
- __publicField(this, "nextRequestId", 0);
453
- __publicField(this, "affectedRegions", []);
454
- __publicField(this, "maxRegionAge", 5e3);
455
- __publicField(this, "cache");
456
- __publicField(this, "enableCache");
457
- __publicField(this, "mapVersion", 0);
458
- __publicField(this, "cacheHits", 0);
459
- __publicField(this, "cacheMisses", 0);
460
- this.map = map;
461
- this.enableCache = config?.enableCache ?? false;
462
- this.cache = this.enableCache ? new PathCache(config?.cacheConfig) : null;
463
- }
464
- /**
465
- * @zh 请求寻路(非阻塞)
466
- * @en Request pathfinding (non-blocking)
467
- */
468
- requestPath(startX, startY, endX, endY, options) {
469
- const id = this.nextRequestId++;
470
- const priority = options?.priority ?? 50;
471
- const opts = {
472
- ...DEFAULT_PATHFINDING_OPTIONS,
473
- ...options
474
- };
475
- const request = {
476
- id,
477
- startX,
478
- startY,
479
- endX,
480
- endY,
481
- options: opts,
482
- priority,
483
- createdAt: Date.now()
484
- };
485
- if (this.cache) {
486
- const cached = this.cache.get(startX, startY, endX, endY, this.mapVersion);
487
- if (cached) {
488
- this.cacheHits++;
489
- const session2 = {
490
- request,
491
- state: cached.found ? PathfindingState.Completed : PathfindingState.Failed,
492
- options: opts,
493
- openList: new IndexedBinaryHeap((a, b) => a.f - b.f),
494
- nodeCache: /* @__PURE__ */ new Map(),
495
- startNode: this.map.getNodeAt(startX, startY),
496
- endNode: this.map.getNodeAt(endX, endY),
497
- endPosition: {
498
- x: endX,
499
- y: endY
500
- },
501
- nodesSearched: cached.nodesSearched,
502
- framesUsed: 0,
503
- initialDistance: 0,
504
- result: {
505
- requestId: id,
506
- found: cached.found,
507
- path: [
508
- ...cached.path
509
- ],
510
- cost: cached.cost,
511
- nodesSearched: cached.nodesSearched,
512
- framesUsed: 0,
513
- isPartial: false
514
- },
515
- affectedByChange: false
516
- };
517
- this.sessions.set(id, session2);
518
- return request;
519
- }
520
- this.cacheMisses++;
521
- }
522
- const startNode = this.map.getNodeAt(startX, startY);
523
- const endNode = this.map.getNodeAt(endX, endY);
524
- if (!startNode || !endNode || !startNode.walkable || !endNode.walkable) {
525
- const session2 = {
526
- request,
527
- state: PathfindingState.Failed,
528
- options: opts,
529
- openList: new IndexedBinaryHeap((a, b) => a.f - b.f),
530
- nodeCache: /* @__PURE__ */ new Map(),
531
- startNode,
532
- endNode,
533
- endPosition: endNode?.position ?? {
534
- x: endX,
535
- y: endY
536
- },
537
- nodesSearched: 0,
538
- framesUsed: 0,
539
- initialDistance: 0,
540
- result: this.createEmptyResult(id),
541
- affectedByChange: false
542
- };
543
- this.sessions.set(id, session2);
544
- return request;
545
- }
546
- if (startNode.id === endNode.id) {
547
- const session2 = {
548
- request,
549
- state: PathfindingState.Completed,
550
- options: opts,
551
- openList: new IndexedBinaryHeap((a, b) => a.f - b.f),
552
- nodeCache: /* @__PURE__ */ new Map(),
553
- startNode,
554
- endNode,
555
- endPosition: endNode.position,
556
- nodesSearched: 1,
557
- framesUsed: 0,
558
- initialDistance: 0,
559
- result: {
560
- requestId: id,
561
- found: true,
562
- path: [
563
- startNode.position
564
- ],
565
- cost: 0,
566
- nodesSearched: 1,
567
- framesUsed: 0,
568
- isPartial: false
569
- },
570
- affectedByChange: false
571
- };
572
- this.sessions.set(id, session2);
573
- return request;
574
- }
575
- const initialDistance = this.map.heuristic(startNode.position, endNode.position);
576
- const openList = new IndexedBinaryHeap((a, b) => a.f - b.f);
577
- const nodeCache = /* @__PURE__ */ new Map();
578
- const startAStarNode = {
579
- node: startNode,
580
- g: 0,
581
- h: initialDistance * opts.heuristicWeight,
582
- f: initialDistance * opts.heuristicWeight,
583
- parent: null,
584
- closed: false,
585
- opened: true,
586
- heapIndex: -1
587
- };
588
- nodeCache.set(startNode.id, startAStarNode);
589
- openList.push(startAStarNode);
590
- const session = {
591
- request,
592
- state: PathfindingState.InProgress,
593
- options: opts,
594
- openList,
595
- nodeCache,
596
- startNode,
597
- endNode,
598
- endPosition: endNode.position,
599
- nodesSearched: 0,
600
- framesUsed: 0,
601
- initialDistance,
602
- result: null,
603
- affectedByChange: false
604
- };
605
- this.sessions.set(id, session);
606
- return request;
607
- }
608
- /**
609
- * @zh 执行一步搜索
610
- * @en Execute one step of search
611
- */
612
- step(requestId, maxIterations) {
613
- const session = this.sessions.get(requestId);
614
- if (!session) {
615
- return EMPTY_PROGRESS;
616
- }
617
- if (session.state !== PathfindingState.InProgress) {
618
- return this.createProgress(session);
619
- }
620
- session.framesUsed++;
621
- let iterations = 0;
622
- while (!session.openList.isEmpty && iterations < maxIterations) {
623
- const current = session.openList.pop();
624
- current.closed = true;
625
- session.nodesSearched++;
626
- iterations++;
627
- if (current.node.id === session.endNode.id) {
628
- session.state = PathfindingState.Completed;
629
- session.result = this.buildResult(session, current);
630
- if (this.cache && session.result.found) {
631
- const req = session.request;
632
- this.cache.set(req.startX, req.startY, req.endX, req.endY, {
633
- found: true,
634
- path: session.result.path,
635
- cost: session.result.cost,
636
- nodesSearched: session.result.nodesSearched
637
- }, this.mapVersion);
638
- }
639
- return this.createProgress(session);
640
- }
641
- this.expandNeighbors(session, current);
642
- if (session.nodesSearched >= session.options.maxNodes) {
643
- session.state = PathfindingState.Failed;
644
- session.result = this.createEmptyResult(requestId);
645
- return this.createProgress(session);
646
- }
647
- }
648
- if (session.openList.isEmpty && session.state === PathfindingState.InProgress) {
649
- session.state = PathfindingState.Failed;
650
- session.result = this.createEmptyResult(requestId);
651
- }
652
- return this.createProgress(session);
653
- }
654
- /**
655
- * @zh 暂停寻路
656
- * @en Pause pathfinding
657
- */
658
- pause(requestId) {
659
- const session = this.sessions.get(requestId);
660
- if (session && session.state === PathfindingState.InProgress) {
661
- session.state = PathfindingState.Paused;
662
- }
663
- }
664
- /**
665
- * @zh 恢复寻路
666
- * @en Resume pathfinding
667
- */
668
- resume(requestId) {
669
- const session = this.sessions.get(requestId);
670
- if (session && session.state === PathfindingState.Paused) {
671
- session.state = PathfindingState.InProgress;
672
- }
673
- }
674
- /**
675
- * @zh 取消寻路
676
- * @en Cancel pathfinding
677
- */
678
- cancel(requestId) {
679
- const session = this.sessions.get(requestId);
680
- if (session && (session.state === PathfindingState.InProgress || session.state === PathfindingState.Paused)) {
681
- session.state = PathfindingState.Cancelled;
682
- session.result = this.createEmptyResult(requestId);
683
- }
684
- }
685
- /**
686
- * @zh 获取寻路结果
687
- * @en Get pathfinding result
688
- */
689
- getResult(requestId) {
690
- const session = this.sessions.get(requestId);
691
- return session?.result ?? null;
692
- }
693
- /**
694
- * @zh 获取当前进度
695
- * @en Get current progress
696
- */
697
- getProgress(requestId) {
698
- const session = this.sessions.get(requestId);
699
- return session ? this.createProgress(session) : null;
700
- }
701
- /**
702
- * @zh 清理已完成的请求
703
- * @en Clean up completed request
704
- */
705
- cleanup(requestId) {
706
- const session = this.sessions.get(requestId);
707
- if (session) {
708
- session.openList.clear();
709
- session.nodeCache.clear();
710
- this.sessions.delete(requestId);
711
- }
712
- }
713
- /**
714
- * @zh 通知障碍物变化
715
- * @en Notify obstacle change
716
- */
717
- notifyObstacleChange(minX, minY, maxX, maxY) {
718
- this.mapVersion++;
719
- if (this.cache) {
720
- this.cache.invalidateRegion(minX, minY, maxX, maxY);
721
- }
722
- const region = {
723
- minX,
724
- minY,
725
- maxX,
726
- maxY,
727
- timestamp: Date.now()
728
- };
729
- this.affectedRegions.push(region);
730
- for (const session of this.sessions.values()) {
731
- if (session.state === PathfindingState.InProgress || session.state === PathfindingState.Paused) {
732
- if (this.sessionAffectedByRegion(session, region)) {
733
- session.affectedByChange = true;
734
- }
735
- }
736
- }
737
- this.cleanupOldRegions();
738
- }
739
- /**
740
- * @zh 清理所有请求
741
- * @en Clear all requests
742
- */
743
- clear() {
744
- for (const session of this.sessions.values()) {
745
- session.openList.clear();
746
- session.nodeCache.clear();
747
- }
748
- this.sessions.clear();
749
- this.affectedRegions.length = 0;
750
- }
751
- /**
752
- * @zh 清空路径缓存
753
- * @en Clear path cache
754
- */
755
- clearCache() {
756
- if (this.cache) {
757
- this.cache.invalidateAll();
758
- this.cacheHits = 0;
759
- this.cacheMisses = 0;
760
- }
761
- }
762
- /**
763
- * @zh 获取缓存统计信息
764
- * @en Get cache statistics
765
- */
766
- getCacheStats() {
767
- if (!this.cache) {
768
- return {
769
- enabled: false,
770
- hits: 0,
771
- misses: 0,
772
- hitRate: 0,
773
- size: 0
774
- };
775
- }
776
- const total = this.cacheHits + this.cacheMisses;
777
- const hitRate = total > 0 ? this.cacheHits / total : 0;
778
- return {
779
- enabled: true,
780
- hits: this.cacheHits,
781
- misses: this.cacheMisses,
782
- hitRate,
783
- size: this.cache.getStats().size
784
- };
785
- }
786
- /**
787
- * @zh 检查会话是否被障碍物变化影响
788
- * @en Check if session is affected by obstacle change
789
- */
790
- isAffectedByChange(requestId) {
791
- const session = this.sessions.get(requestId);
792
- return session?.affectedByChange ?? false;
793
- }
794
- /**
795
- * @zh 清除会话的变化标记
796
- * @en Clear session's change flag
797
- */
798
- clearChangeFlag(requestId) {
799
- const session = this.sessions.get(requestId);
800
- if (session) {
801
- session.affectedByChange = false;
802
- }
803
- }
804
- // =========================================================================
805
- // 私有方法 | Private Methods
806
- // =========================================================================
807
- /**
808
- * @zh 展开邻居节点
809
- * @en Expand neighbor nodes
810
- */
811
- expandNeighbors(session, current) {
812
- const neighbors = this.map.getNeighbors(current.node);
813
- for (const neighborNode of neighbors) {
814
- if (!neighborNode.walkable) {
815
- continue;
816
- }
817
- let neighbor = session.nodeCache.get(neighborNode.id);
818
- if (!neighbor) {
819
- neighbor = {
820
- node: neighborNode,
821
- g: Infinity,
822
- h: 0,
823
- f: Infinity,
824
- parent: null,
825
- closed: false,
826
- opened: false,
827
- heapIndex: -1
828
- };
829
- session.nodeCache.set(neighborNode.id, neighbor);
830
- }
831
- if (neighbor.closed) {
832
- continue;
833
- }
834
- const movementCost = this.map.getMovementCost(current.node, neighborNode);
835
- const tentativeG = current.g + movementCost;
836
- if (!neighbor.opened) {
837
- neighbor.g = tentativeG;
838
- neighbor.h = this.map.heuristic(neighborNode.position, session.endPosition) * session.options.heuristicWeight;
839
- neighbor.f = neighbor.g + neighbor.h;
840
- neighbor.parent = current;
841
- neighbor.opened = true;
842
- session.openList.push(neighbor);
843
- } else if (tentativeG < neighbor.g) {
844
- neighbor.g = tentativeG;
845
- neighbor.f = neighbor.g + neighbor.h;
846
- neighbor.parent = current;
847
- session.openList.update(neighbor);
848
- }
849
- }
850
- }
851
- /**
852
- * @zh 创建进度对象
853
- * @en Create progress object
854
- */
855
- createProgress(session) {
856
- let estimatedProgress = 0;
857
- if (session.state === PathfindingState.Completed) {
858
- estimatedProgress = 1;
859
- } else if (session.state === PathfindingState.InProgress && session.initialDistance > 0) {
860
- const bestNode = session.openList.peek();
861
- if (bestNode) {
862
- const currentDistance = bestNode.h / session.options.heuristicWeight;
863
- estimatedProgress = Math.max(0, Math.min(1, 1 - currentDistance / session.initialDistance));
864
- }
865
- }
866
- return {
867
- state: session.state,
868
- nodesSearched: session.nodesSearched,
869
- openListSize: session.openList.size,
870
- estimatedProgress
871
- };
872
- }
873
- /**
874
- * @zh 构建路径结果
875
- * @en Build path result
876
- */
877
- buildResult(session, endNode) {
878
- const path = [];
879
- let current = endNode;
880
- while (current) {
881
- path.push(current.node.position);
882
- current = current.parent;
883
- }
884
- path.reverse();
885
- return {
886
- requestId: session.request.id,
887
- found: true,
888
- path,
889
- cost: endNode.g,
890
- nodesSearched: session.nodesSearched,
891
- framesUsed: session.framesUsed,
892
- isPartial: false
893
- };
894
- }
895
- /**
896
- * @zh 创建空结果
897
- * @en Create empty result
898
- */
899
- createEmptyResult(requestId) {
900
- return {
901
- requestId,
902
- found: false,
903
- path: [],
904
- cost: 0,
905
- nodesSearched: 0,
906
- framesUsed: 0,
907
- isPartial: false
908
- };
909
- }
910
- /**
911
- * @zh 检查会话是否被区域影响
912
- * @en Check if session is affected by region
913
- */
914
- sessionAffectedByRegion(session, region) {
915
- for (const astarNode of session.nodeCache.values()) {
916
- if (astarNode.opened || astarNode.closed) {
917
- const pos = astarNode.node.position;
918
- if (pos.x >= region.minX && pos.x <= region.maxX && pos.y >= region.minY && pos.y <= region.maxY) {
919
- return true;
920
- }
921
- }
922
- }
923
- const start = session.request;
924
- const end = session.endPosition;
925
- if (start.startX >= region.minX && start.startX <= region.maxX && start.startY >= region.minY && start.startY <= region.maxY || end.x >= region.minX && end.x <= region.maxX && end.y >= region.minY && end.y <= region.maxY) {
926
- return true;
927
- }
928
- return false;
929
- }
930
- /**
931
- * @zh 清理过期的变化区域
932
- * @en Clean up expired change regions
933
- */
934
- cleanupOldRegions() {
935
- const now = Date.now();
936
- let i = 0;
937
- while (i < this.affectedRegions.length) {
938
- if (now - this.affectedRegions[i].timestamp > this.maxRegionAge) {
939
- this.affectedRegions.splice(i, 1);
940
- } else {
941
- i++;
942
- }
943
- }
944
- }
945
- };
946
- __name(_IncrementalAStarPathfinder, "IncrementalAStarPathfinder");
947
- var IncrementalAStarPathfinder = _IncrementalAStarPathfinder;
948
- function createIncrementalAStarPathfinder(map) {
949
- return new IncrementalAStarPathfinder(map);
950
- }
951
- __name(createIncrementalAStarPathfinder, "createIncrementalAStarPathfinder");
952
-
953
- // src/core/PathValidator.ts
954
- var _PathValidator = class _PathValidator {
955
- /**
956
- * @zh 验证路径段的有效性
957
- * @en Validate path segment validity
958
- *
959
- * @param path - @zh 要验证的路径 @en Path to validate
960
- * @param fromIndex - @zh 起始索引 @en Start index
961
- * @param toIndex - @zh 结束索引 @en End index
962
- * @param map - @zh 地图实例 @en Map instance
963
- * @returns @zh 验证结果 @en Validation result
964
- */
965
- validatePath(path, fromIndex, toIndex, map) {
966
- const end = Math.min(toIndex, path.length);
967
- for (let i = fromIndex; i < end; i++) {
968
- const point = path[i];
969
- const x = Math.floor(point.x);
970
- const y = Math.floor(point.y);
971
- if (!map.isWalkable(x, y)) {
972
- return {
973
- valid: false,
974
- invalidIndex: i
975
- };
976
- }
977
- if (i > fromIndex) {
978
- const prev = path[i - 1];
979
- if (!this.checkLineOfSight(prev.x, prev.y, point.x, point.y, map)) {
980
- return {
981
- valid: false,
982
- invalidIndex: i
983
- };
984
- }
985
- }
986
- }
987
- return {
988
- valid: true,
989
- invalidIndex: -1
990
- };
991
- }
992
- /**
993
- * @zh 检查两点之间的视线(使用 Bresenham 算法)
994
- * @en Check line of sight between two points (using Bresenham algorithm)
995
- *
996
- * @param x1 - @zh 起点 X @en Start X
997
- * @param y1 - @zh 起点 Y @en Start Y
998
- * @param x2 - @zh 终点 X @en End X
999
- * @param y2 - @zh 终点 Y @en End Y
1000
- * @param map - @zh 地图实例 @en Map instance
1001
- * @returns @zh 是否有视线 @en Whether there is line of sight
1002
- */
1003
- checkLineOfSight(x1, y1, x2, y2, map) {
1004
- const ix1 = Math.floor(x1);
1005
- const iy1 = Math.floor(y1);
1006
- const ix2 = Math.floor(x2);
1007
- const iy2 = Math.floor(y2);
1008
- let dx = Math.abs(ix2 - ix1);
1009
- let dy = Math.abs(iy2 - iy1);
1010
- let x = ix1;
1011
- let y = iy1;
1012
- const sx = ix1 < ix2 ? 1 : -1;
1013
- const sy = iy1 < iy2 ? 1 : -1;
1014
- if (dx > dy) {
1015
- let err = dx / 2;
1016
- while (x !== ix2) {
1017
- if (!map.isWalkable(x, y)) {
1018
- return false;
1019
- }
1020
- err -= dy;
1021
- if (err < 0) {
1022
- y += sy;
1023
- err += dx;
1024
- }
1025
- x += sx;
1026
- }
1027
- } else {
1028
- let err = dy / 2;
1029
- while (y !== iy2) {
1030
- if (!map.isWalkable(x, y)) {
1031
- return false;
1032
- }
1033
- err -= dx;
1034
- if (err < 0) {
1035
- x += sx;
1036
- err += dy;
1037
- }
1038
- y += sy;
1039
- }
1040
- }
1041
- return map.isWalkable(ix2, iy2);
1042
- }
1043
- };
1044
- __name(_PathValidator, "PathValidator");
1045
- var PathValidator = _PathValidator;
1046
- var _ObstacleChangeManager = class _ObstacleChangeManager {
1047
- constructor() {
1048
- __publicField(this, "changes", /* @__PURE__ */ new Map());
1049
- __publicField(this, "epoch", 0);
1050
- }
1051
- /**
1052
- * @zh 记录障碍物变化
1053
- * @en Record obstacle change
1054
- *
1055
- * @param x - @zh X 坐标 @en X coordinate
1056
- * @param y - @zh Y 坐标 @en Y coordinate
1057
- * @param wasWalkable - @zh 变化前是否可通行 @en Was walkable before change
1058
- */
1059
- recordChange(x, y, wasWalkable) {
1060
- const key = `${x},${y}`;
1061
- this.changes.set(key, {
1062
- x,
1063
- y,
1064
- wasWalkable,
1065
- timestamp: Date.now()
1066
- });
1067
- }
1068
- /**
1069
- * @zh 获取影响区域
1070
- * @en Get affected region
1071
- *
1072
- * @returns @zh 影响区域或 null(如果没有变化)@en Affected region or null if no changes
1073
- */
1074
- getAffectedRegion() {
1075
- if (this.changes.size === 0) {
1076
- return null;
1077
- }
1078
- let minX = Infinity;
1079
- let minY = Infinity;
1080
- let maxX = -Infinity;
1081
- let maxY = -Infinity;
1082
- for (const change of this.changes.values()) {
1083
- minX = Math.min(minX, change.x);
1084
- minY = Math.min(minY, change.y);
1085
- maxX = Math.max(maxX, change.x);
1086
- maxY = Math.max(maxY, change.y);
1087
- }
1088
- return {
1089
- minX,
1090
- minY,
1091
- maxX,
1092
- maxY
1093
- };
1094
- }
1095
- /**
1096
- * @zh 获取所有变化
1097
- * @en Get all changes
1098
- *
1099
- * @returns @zh 变化列表 @en List of changes
1100
- */
1101
- getChanges() {
1102
- return Array.from(this.changes.values());
1103
- }
1104
- /**
1105
- * @zh 检查是否有变化
1106
- * @en Check if there are changes
1107
- *
1108
- * @returns @zh 是否有变化 @en Whether there are changes
1109
- */
1110
- hasChanges() {
1111
- return this.changes.size > 0;
1112
- }
1113
- /**
1114
- * @zh 获取当前 epoch
1115
- * @en Get current epoch
1116
- *
1117
- * @returns @zh 当前 epoch @en Current epoch
1118
- */
1119
- getEpoch() {
1120
- return this.epoch;
1121
- }
1122
- /**
1123
- * @zh 清空变化记录并推进 epoch
1124
- * @en Clear changes and advance epoch
1125
- */
1126
- flush() {
1127
- this.changes.clear();
1128
- this.epoch++;
1129
- }
1130
- /**
1131
- * @zh 清空所有状态
1132
- * @en Clear all state
1133
- */
1134
- clear() {
1135
- this.changes.clear();
1136
- this.epoch = 0;
1137
- }
1138
- };
1139
- __name(_ObstacleChangeManager, "ObstacleChangeManager");
1140
- var ObstacleChangeManager = _ObstacleChangeManager;
1141
- function createPathValidator() {
1142
- return new PathValidator();
1143
- }
1144
- __name(createPathValidator, "createPathValidator");
1145
- function createObstacleChangeManager() {
1146
- return new ObstacleChangeManager();
1147
- }
1148
- __name(createObstacleChangeManager, "createObstacleChangeManager");
1149
-
1150
- // src/grid/GridMap.ts
1151
- var _GridNode = class _GridNode {
1152
- constructor(x, y, width, walkable = true, cost = 1) {
1153
- __publicField(this, "id");
1154
- __publicField(this, "position");
1155
- __publicField(this, "x");
1156
- __publicField(this, "y");
1157
- __publicField(this, "cost");
1158
- __publicField(this, "walkable");
1159
- this.x = x;
1160
- this.y = y;
1161
- this.id = y * width + x;
1162
- this.position = createPoint(x, y);
1163
- this.walkable = walkable;
1164
- this.cost = cost;
1165
- }
1166
- };
1167
- __name(_GridNode, "GridNode");
1168
- var GridNode = _GridNode;
1169
- var DIRECTIONS_4 = [
1170
- {
1171
- dx: 0,
1172
- dy: -1
1173
- },
1174
- {
1175
- dx: 1,
1176
- dy: 0
1177
- },
1178
- {
1179
- dx: 0,
1180
- dy: 1
1181
- },
1182
- {
1183
- dx: -1,
1184
- dy: 0
1185
- }
1186
- // Left
1187
- ];
1188
- var DIRECTIONS_8 = [
1189
- {
1190
- dx: 0,
1191
- dy: -1
1192
- },
1193
- {
1194
- dx: 1,
1195
- dy: -1
1196
- },
1197
- {
1198
- dx: 1,
1199
- dy: 0
1200
- },
1201
- {
1202
- dx: 1,
1203
- dy: 1
1204
- },
1205
- {
1206
- dx: 0,
1207
- dy: 1
1208
- },
1209
- {
1210
- dx: -1,
1211
- dy: 1
1212
- },
1213
- {
1214
- dx: -1,
1215
- dy: 0
1216
- },
1217
- {
1218
- dx: -1,
1219
- dy: -1
1220
- }
1221
- // Up-Left
1222
- ];
1223
- var DEFAULT_GRID_OPTIONS = {
1224
- allowDiagonal: true,
1225
- diagonalCost: Math.SQRT2,
1226
- avoidCorners: true,
1227
- heuristic: octileDistance
1228
- };
1229
- var _GridMap = class _GridMap {
1230
- constructor(width, height, options) {
1231
- __publicField(this, "width");
1232
- __publicField(this, "height");
1233
- __publicField(this, "nodes");
1234
- __publicField(this, "options");
1235
- this.width = width;
1236
- this.height = height;
1237
- this.options = {
1238
- ...DEFAULT_GRID_OPTIONS,
1239
- ...options
1240
- };
1241
- this.nodes = this.createNodes();
1242
- }
1243
- /**
1244
- * @zh 创建网格节点
1245
- * @en Create grid nodes
1246
- */
1247
- createNodes() {
1248
- const nodes = [];
1249
- for (let y = 0; y < this.height; y++) {
1250
- nodes[y] = [];
1251
- for (let x = 0; x < this.width; x++) {
1252
- nodes[y][x] = new GridNode(x, y, this.width, true, 1);
1253
- }
1254
- }
1255
- return nodes;
1256
- }
1257
- /**
1258
- * @zh 获取指定位置的节点
1259
- * @en Get node at position
1260
- */
1261
- getNodeAt(x, y) {
1262
- if (!this.isInBounds(x, y)) {
1263
- return null;
1264
- }
1265
- return this.nodes[y][x];
1266
- }
1267
- /**
1268
- * @zh 检查坐标是否在边界内
1269
- * @en Check if coordinates are within bounds
1270
- */
1271
- isInBounds(x, y) {
1272
- return x >= 0 && x < this.width && y >= 0 && y < this.height;
1273
- }
1274
- /**
1275
- * @zh 检查位置是否可通行
1276
- * @en Check if position is walkable
1277
- */
1278
- isWalkable(x, y) {
1279
- const node = this.getNodeAt(x, y);
1280
- return node !== null && node.walkable;
1281
- }
1282
- /**
1283
- * @zh 设置位置是否可通行
1284
- * @en Set position walkability
1285
- */
1286
- setWalkable(x, y, walkable) {
1287
- const node = this.getNodeAt(x, y);
1288
- if (node) {
1289
- node.walkable = walkable;
1290
- }
1291
- }
1292
- /**
1293
- * @zh 设置位置的移动代价
1294
- * @en Set movement cost at position
1295
- */
1296
- setCost(x, y, cost) {
1297
- const node = this.getNodeAt(x, y);
1298
- if (node) {
1299
- node.cost = cost;
1300
- }
1301
- }
1302
- /**
1303
- * @zh 获取节点的邻居
1304
- * @en Get neighbors of a node
1305
- */
1306
- getNeighbors(node) {
1307
- const neighbors = [];
1308
- const { x, y } = node.position;
1309
- const directions = this.options.allowDiagonal ? DIRECTIONS_8 : DIRECTIONS_4;
1310
- for (let i = 0; i < directions.length; i++) {
1311
- const dir = directions[i];
1312
- const nx = x + dir.dx;
1313
- const ny = y + dir.dy;
1314
- if (nx < 0 || nx >= this.width || ny < 0 || ny >= this.height) {
1315
- continue;
1316
- }
1317
- const neighbor = this.nodes[ny][nx];
1318
- if (!neighbor.walkable) {
1319
- continue;
1320
- }
1321
- if (this.options.avoidCorners && dir.dx !== 0 && dir.dy !== 0) {
1322
- const hNode = this.nodes[y][x + dir.dx];
1323
- const vNode = this.nodes[y + dir.dy][x];
1324
- if (!hNode.walkable || !vNode.walkable) {
1325
- continue;
1326
- }
1327
- }
1328
- neighbors.push(neighbor);
1329
- }
1330
- return neighbors;
1331
- }
1332
- /**
1333
- * @zh 遍历节点的邻居(零分配)
1334
- * @en Iterate over neighbors (zero allocation)
1335
- */
1336
- forEachNeighbor(node, callback) {
1337
- const { x, y } = node.position;
1338
- const directions = this.options.allowDiagonal ? DIRECTIONS_8 : DIRECTIONS_4;
1339
- for (let i = 0; i < directions.length; i++) {
1340
- const dir = directions[i];
1341
- const nx = x + dir.dx;
1342
- const ny = y + dir.dy;
1343
- if (nx < 0 || nx >= this.width || ny < 0 || ny >= this.height) {
1344
- continue;
1345
- }
1346
- const neighbor = this.nodes[ny][nx];
1347
- if (!neighbor.walkable) {
1348
- continue;
1349
- }
1350
- if (this.options.avoidCorners && dir.dx !== 0 && dir.dy !== 0) {
1351
- const hNode = this.nodes[y][x + dir.dx];
1352
- const vNode = this.nodes[y + dir.dy][x];
1353
- if (!hNode.walkable || !vNode.walkable) {
1354
- continue;
1355
- }
1356
- }
1357
- if (callback(neighbor) === false) {
1358
- return;
1359
- }
1360
- }
1361
- }
1362
- /**
1363
- * @zh 计算启发式距离
1364
- * @en Calculate heuristic distance
1365
- */
1366
- heuristic(a, b) {
1367
- return this.options.heuristic(a, b);
1368
- }
1369
- /**
1370
- * @zh 计算移动代价
1371
- * @en Calculate movement cost
1372
- */
1373
- getMovementCost(from, to) {
1374
- const dx = Math.abs(from.position.x - to.position.x);
1375
- const dy = Math.abs(from.position.y - to.position.y);
1376
- if (dx !== 0 && dy !== 0) {
1377
- return to.cost * this.options.diagonalCost;
1378
- }
1379
- return to.cost;
1380
- }
1381
- /**
1382
- * @zh 从二维数组加载地图
1383
- * @en Load map from 2D array
1384
- *
1385
- * @param data - @zh 0=可通行,非0=不可通行 @en 0=walkable, non-0=blocked
1386
- */
1387
- loadFromArray(data) {
1388
- for (let y = 0; y < Math.min(data.length, this.height); y++) {
1389
- for (let x = 0; x < Math.min(data[y].length, this.width); x++) {
1390
- this.nodes[y][x].walkable = data[y][x] === 0;
1391
- }
1392
- }
1393
- }
1394
- /**
1395
- * @zh 从字符串加载地图
1396
- * @en Load map from string
1397
- *
1398
- * @param str - @zh 地图字符串,'.'=可通行,'#'=障碍 @en Map string, '.'=walkable, '#'=blocked
1399
- */
1400
- loadFromString(str) {
1401
- const lines = str.trim().split("\n");
1402
- for (let y = 0; y < Math.min(lines.length, this.height); y++) {
1403
- const line = lines[y];
1404
- for (let x = 0; x < Math.min(line.length, this.width); x++) {
1405
- this.nodes[y][x].walkable = line[x] !== "#";
1406
- }
1407
- }
1408
- }
1409
- /**
1410
- * @zh 导出为字符串
1411
- * @en Export to string
1412
- */
1413
- toString() {
1414
- let result = "";
1415
- for (let y = 0; y < this.height; y++) {
1416
- for (let x = 0; x < this.width; x++) {
1417
- result += this.nodes[y][x].walkable ? "." : "#";
1418
- }
1419
- result += "\n";
1420
- }
1421
- return result;
1422
- }
1423
- /**
1424
- * @zh 重置所有节点为可通行
1425
- * @en Reset all nodes to walkable
1426
- */
1427
- reset() {
1428
- for (let y = 0; y < this.height; y++) {
1429
- for (let x = 0; x < this.width; x++) {
1430
- this.nodes[y][x].walkable = true;
1431
- this.nodes[y][x].cost = 1;
1432
- }
1433
- }
1434
- }
1435
- /**
1436
- * @zh 设置矩形区域的通行性
1437
- * @en Set walkability for a rectangle region
1438
- */
1439
- setRectWalkable(x, y, width, height, walkable) {
1440
- for (let dy = 0; dy < height; dy++) {
1441
- for (let dx = 0; dx < width; dx++) {
1442
- this.setWalkable(x + dx, y + dy, walkable);
1443
- }
1444
- }
1445
- }
1446
- };
1447
- __name(_GridMap, "GridMap");
1448
- var GridMap = _GridMap;
1449
- function createGridMap(width, height, options) {
1450
- return new GridMap(width, height, options);
1451
- }
1452
- __name(createGridMap, "createGridMap");
1453
-
1454
- // src/smoothing/PathSmoother.ts
1455
- function bresenhamLineOfSight(x1, y1, x2, y2, map) {
1456
- let ix1 = Math.floor(x1);
1457
- let iy1 = Math.floor(y1);
1458
- const ix2 = Math.floor(x2);
1459
- const iy2 = Math.floor(y2);
1460
- const dx = Math.abs(ix2 - ix1);
1461
- const dy = Math.abs(iy2 - iy1);
1462
- const sx = ix1 < ix2 ? 1 : -1;
1463
- const sy = iy1 < iy2 ? 1 : -1;
1464
- let err = dx - dy;
1465
- while (true) {
1466
- if (!map.isWalkable(ix1, iy1)) {
1467
- return false;
1468
- }
1469
- if (ix1 === ix2 && iy1 === iy2) {
1470
- break;
1471
- }
1472
- const e2 = 2 * err;
1473
- if (e2 > -dy) {
1474
- err -= dy;
1475
- ix1 += sx;
1476
- }
1477
- if (e2 < dx) {
1478
- err += dx;
1479
- iy1 += sy;
1480
- }
1481
- }
1482
- return true;
1483
- }
1484
- __name(bresenhamLineOfSight, "bresenhamLineOfSight");
1485
- function raycastLineOfSight(x1, y1, x2, y2, map, stepSize = 0.5) {
1486
- const dx = x2 - x1;
1487
- const dy = y2 - y1;
1488
- const distance = Math.sqrt(dx * dx + dy * dy);
1489
- if (distance === 0) {
1490
- return map.isWalkable(Math.floor(x1), Math.floor(y1));
1491
- }
1492
- const steps = Math.ceil(distance / stepSize);
1493
- const stepX = dx / steps;
1494
- const stepY = dy / steps;
1495
- let x = x1;
1496
- let y = y1;
1497
- for (let i = 0; i <= steps; i++) {
1498
- if (!map.isWalkable(Math.floor(x), Math.floor(y))) {
1499
- return false;
1500
- }
1501
- x += stepX;
1502
- y += stepY;
1503
- }
1504
- return true;
1505
- }
1506
- __name(raycastLineOfSight, "raycastLineOfSight");
1507
- var _LineOfSightSmoother = class _LineOfSightSmoother {
1508
- constructor(lineOfSight = bresenhamLineOfSight) {
1509
- __publicField(this, "lineOfSight");
1510
- this.lineOfSight = lineOfSight;
1511
- }
1512
- smooth(path, map) {
1513
- if (path.length <= 2) {
1514
- return [
1515
- ...path
1516
- ];
1517
- }
1518
- const result = [
1519
- path[0]
1520
- ];
1521
- let current = 0;
1522
- while (current < path.length - 1) {
1523
- let furthest = current + 1;
1524
- for (let i = path.length - 1; i > current + 1; i--) {
1525
- if (this.lineOfSight(path[current].x, path[current].y, path[i].x, path[i].y, map)) {
1526
- furthest = i;
1527
- break;
1528
- }
1529
- }
1530
- result.push(path[furthest]);
1531
- current = furthest;
1532
- }
1533
- return result;
1534
- }
1535
- };
1536
- __name(_LineOfSightSmoother, "LineOfSightSmoother");
1537
- var LineOfSightSmoother = _LineOfSightSmoother;
1538
- var _CatmullRomSmoother = class _CatmullRomSmoother {
1539
- /**
1540
- * @param segments - @zh 每段之间的插值点数 @en Number of interpolation points per segment
1541
- * @param tension - @zh 张力 (0-1) @en Tension (0-1)
1542
- */
1543
- constructor(segments = 5, tension = 0.5) {
1544
- __publicField(this, "segments");
1545
- __publicField(this, "tension");
1546
- this.segments = segments;
1547
- this.tension = tension;
1548
- }
1549
- smooth(path, _map) {
1550
- if (path.length <= 2) {
1551
- return [
1552
- ...path
1553
- ];
1554
- }
1555
- const result = [];
1556
- const points = [
1557
- path[0],
1558
- ...path,
1559
- path[path.length - 1]
1560
- ];
1561
- for (let i = 1; i < points.length - 2; i++) {
1562
- const p0 = points[i - 1];
1563
- const p1 = points[i];
1564
- const p2 = points[i + 1];
1565
- const p3 = points[i + 2];
1566
- for (let j = 0; j < this.segments; j++) {
1567
- const t = j / this.segments;
1568
- const point = this.interpolate(p0, p1, p2, p3, t);
1569
- result.push(point);
1570
- }
1571
- }
1572
- result.push(path[path.length - 1]);
1573
- return result;
1574
- }
1575
- /**
1576
- * @zh Catmull-Rom 插值
1577
- * @en Catmull-Rom interpolation
1578
- */
1579
- interpolate(p0, p1, p2, p3, t) {
1580
- const t2 = t * t;
1581
- const t3 = t2 * t;
1582
- const tension = this.tension;
1583
- 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);
1584
- 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);
1585
- return createPoint(x, y);
1586
- }
1587
- };
1588
- __name(_CatmullRomSmoother, "CatmullRomSmoother");
1589
- var CatmullRomSmoother = _CatmullRomSmoother;
1590
- var _CombinedSmoother = class _CombinedSmoother {
1591
- constructor(curveSegments = 5, tension = 0.5) {
1592
- __publicField(this, "simplifier");
1593
- __publicField(this, "curveSmoother");
1594
- this.simplifier = new LineOfSightSmoother();
1595
- this.curveSmoother = new CatmullRomSmoother(curveSegments, tension);
1596
- }
1597
- smooth(path, map) {
1598
- const simplified = this.simplifier.smooth(path, map);
1599
- return this.curveSmoother.smooth(simplified, map);
1600
- }
1601
- };
1602
- __name(_CombinedSmoother, "CombinedSmoother");
1603
- var CombinedSmoother = _CombinedSmoother;
1604
- function createLineOfSightSmoother(lineOfSight) {
1605
- return new LineOfSightSmoother(lineOfSight);
1606
- }
1607
- __name(createLineOfSightSmoother, "createLineOfSightSmoother");
1608
- function createCatmullRomSmoother(segments, tension) {
1609
- return new CatmullRomSmoother(segments, tension);
1610
- }
1611
- __name(createCatmullRomSmoother, "createCatmullRomSmoother");
1612
- function createCombinedSmoother(curveSegments, tension) {
1613
- return new CombinedSmoother(curveSegments, tension);
1614
- }
1615
- __name(createCombinedSmoother, "createCombinedSmoother");
1616
-
1617
- export {
1618
- createPoint,
1619
- EMPTY_PATH_RESULT,
1620
- manhattanDistance,
1621
- euclideanDistance,
1622
- chebyshevDistance,
1623
- octileDistance,
1624
- DEFAULT_PATHFINDING_OPTIONS,
1625
- IndexedBinaryHeap,
1626
- DEFAULT_PATH_CACHE_CONFIG,
1627
- PathCache,
1628
- createPathCache,
1629
- IncrementalAStarPathfinder,
1630
- createIncrementalAStarPathfinder,
1631
- PathValidator,
1632
- ObstacleChangeManager,
1633
- createPathValidator,
1634
- createObstacleChangeManager,
1635
- GridNode,
1636
- DIRECTIONS_4,
1637
- DIRECTIONS_8,
1638
- DEFAULT_GRID_OPTIONS,
1639
- GridMap,
1640
- createGridMap,
1641
- bresenhamLineOfSight,
1642
- raycastLineOfSight,
1643
- LineOfSightSmoother,
1644
- CatmullRomSmoother,
1645
- CombinedSmoother,
1646
- createLineOfSightSmoother,
1647
- createCatmullRomSmoother,
1648
- createCombinedSmoother
1649
- };
1650
- //# sourceMappingURL=chunk-VNC2YAAL.js.map