@esengine/pathfinding 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1826 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+
6
+ // src/core/IPathfinding.ts
7
+ function createPoint(x, y) {
8
+ return {
9
+ x,
10
+ y
11
+ };
12
+ }
13
+ __name(createPoint, "createPoint");
14
+ var EMPTY_PATH_RESULT = {
15
+ found: false,
16
+ path: [],
17
+ cost: 0,
18
+ nodesSearched: 0
19
+ };
20
+ function manhattanDistance(a, b) {
21
+ return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
22
+ }
23
+ __name(manhattanDistance, "manhattanDistance");
24
+ function euclideanDistance(a, b) {
25
+ const dx = a.x - b.x;
26
+ const dy = a.y - b.y;
27
+ return Math.sqrt(dx * dx + dy * dy);
28
+ }
29
+ __name(euclideanDistance, "euclideanDistance");
30
+ function chebyshevDistance(a, b) {
31
+ return Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y));
32
+ }
33
+ __name(chebyshevDistance, "chebyshevDistance");
34
+ function octileDistance(a, b) {
35
+ const dx = Math.abs(a.x - b.x);
36
+ const dy = Math.abs(a.y - b.y);
37
+ const D = 1;
38
+ const D2 = Math.SQRT2;
39
+ return D * (dx + dy) + (D2 - 2 * D) * Math.min(dx, dy);
40
+ }
41
+ __name(octileDistance, "octileDistance");
42
+ var DEFAULT_PATHFINDING_OPTIONS = {
43
+ maxNodes: 1e4,
44
+ heuristicWeight: 1,
45
+ allowDiagonal: true,
46
+ avoidCorners: true
47
+ };
48
+
49
+ // src/core/BinaryHeap.ts
50
+ var _BinaryHeap = class _BinaryHeap {
51
+ /**
52
+ * @zh 创建二叉堆
53
+ * @en Create binary heap
54
+ *
55
+ * @param compare - @zh 比较函数,返回负数表示 a < b @en Compare function, returns negative if a < b
56
+ */
57
+ constructor(compare) {
58
+ __publicField(this, "heap", []);
59
+ __publicField(this, "compare");
60
+ this.compare = compare;
61
+ }
62
+ /**
63
+ * @zh 堆大小
64
+ * @en Heap size
65
+ */
66
+ get size() {
67
+ return this.heap.length;
68
+ }
69
+ /**
70
+ * @zh 是否为空
71
+ * @en Is empty
72
+ */
73
+ get isEmpty() {
74
+ return this.heap.length === 0;
75
+ }
76
+ /**
77
+ * @zh 插入元素
78
+ * @en Push element
79
+ */
80
+ push(item) {
81
+ this.heap.push(item);
82
+ this.bubbleUp(this.heap.length - 1);
83
+ }
84
+ /**
85
+ * @zh 弹出最小元素
86
+ * @en Pop minimum element
87
+ */
88
+ pop() {
89
+ if (this.heap.length === 0) {
90
+ return void 0;
91
+ }
92
+ const result = this.heap[0];
93
+ const last = this.heap.pop();
94
+ if (this.heap.length > 0) {
95
+ this.heap[0] = last;
96
+ this.sinkDown(0);
97
+ }
98
+ return result;
99
+ }
100
+ /**
101
+ * @zh 查看最小元素(不移除)
102
+ * @en Peek minimum element (without removing)
103
+ */
104
+ peek() {
105
+ return this.heap[0];
106
+ }
107
+ /**
108
+ * @zh 更新元素(重新排序)
109
+ * @en Update element (re-sort)
110
+ */
111
+ update(item) {
112
+ const index = this.heap.indexOf(item);
113
+ if (index !== -1) {
114
+ this.bubbleUp(index);
115
+ this.sinkDown(index);
116
+ }
117
+ }
118
+ /**
119
+ * @zh 检查是否包含元素
120
+ * @en Check if contains element
121
+ */
122
+ contains(item) {
123
+ return this.heap.indexOf(item) !== -1;
124
+ }
125
+ /**
126
+ * @zh 清空堆
127
+ * @en Clear heap
128
+ */
129
+ clear() {
130
+ this.heap.length = 0;
131
+ }
132
+ /**
133
+ * @zh 上浮操作
134
+ * @en Bubble up operation
135
+ */
136
+ bubbleUp(index) {
137
+ const item = this.heap[index];
138
+ while (index > 0) {
139
+ const parentIndex = Math.floor((index - 1) / 2);
140
+ const parent = this.heap[parentIndex];
141
+ if (this.compare(item, parent) >= 0) {
142
+ break;
143
+ }
144
+ this.heap[index] = parent;
145
+ index = parentIndex;
146
+ }
147
+ this.heap[index] = item;
148
+ }
149
+ /**
150
+ * @zh 下沉操作
151
+ * @en Sink down operation
152
+ */
153
+ sinkDown(index) {
154
+ const length = this.heap.length;
155
+ const item = this.heap[index];
156
+ while (true) {
157
+ const leftIndex = 2 * index + 1;
158
+ const rightIndex = 2 * index + 2;
159
+ let smallest = index;
160
+ if (leftIndex < length && this.compare(this.heap[leftIndex], this.heap[smallest]) < 0) {
161
+ smallest = leftIndex;
162
+ }
163
+ if (rightIndex < length && this.compare(this.heap[rightIndex], this.heap[smallest]) < 0) {
164
+ smallest = rightIndex;
165
+ }
166
+ if (smallest === index) {
167
+ break;
168
+ }
169
+ this.heap[index] = this.heap[smallest];
170
+ this.heap[smallest] = item;
171
+ index = smallest;
172
+ }
173
+ }
174
+ };
175
+ __name(_BinaryHeap, "BinaryHeap");
176
+ var BinaryHeap = _BinaryHeap;
177
+
178
+ // src/core/AStarPathfinder.ts
179
+ var _AStarPathfinder = class _AStarPathfinder {
180
+ constructor(map) {
181
+ __publicField(this, "map");
182
+ __publicField(this, "nodeCache", /* @__PURE__ */ new Map());
183
+ __publicField(this, "openList");
184
+ this.map = map;
185
+ this.openList = new BinaryHeap((a, b) => a.f - b.f);
186
+ }
187
+ /**
188
+ * @zh 查找路径
189
+ * @en Find path
190
+ */
191
+ findPath(startX, startY, endX, endY, options) {
192
+ const opts = {
193
+ ...DEFAULT_PATHFINDING_OPTIONS,
194
+ ...options
195
+ };
196
+ this.clear();
197
+ const startNode = this.map.getNodeAt(startX, startY);
198
+ const endNode = this.map.getNodeAt(endX, endY);
199
+ if (!startNode || !endNode) {
200
+ return EMPTY_PATH_RESULT;
201
+ }
202
+ if (!startNode.walkable || !endNode.walkable) {
203
+ return EMPTY_PATH_RESULT;
204
+ }
205
+ if (startNode.id === endNode.id) {
206
+ return {
207
+ found: true,
208
+ path: [
209
+ startNode.position
210
+ ],
211
+ cost: 0,
212
+ nodesSearched: 1
213
+ };
214
+ }
215
+ const start = this.getOrCreateAStarNode(startNode);
216
+ start.g = 0;
217
+ start.h = this.map.heuristic(startNode.position, endNode.position) * opts.heuristicWeight;
218
+ start.f = start.h;
219
+ start.opened = true;
220
+ this.openList.push(start);
221
+ let nodesSearched = 0;
222
+ const endPosition = endNode.position;
223
+ while (!this.openList.isEmpty && nodesSearched < opts.maxNodes) {
224
+ const current = this.openList.pop();
225
+ current.closed = true;
226
+ nodesSearched++;
227
+ if (current.node.id === endNode.id) {
228
+ return this.buildPath(current, nodesSearched);
229
+ }
230
+ const neighbors = this.map.getNeighbors(current.node);
231
+ for (const neighborNode of neighbors) {
232
+ if (!neighborNode.walkable) {
233
+ continue;
234
+ }
235
+ const neighbor = this.getOrCreateAStarNode(neighborNode);
236
+ if (neighbor.closed) {
237
+ continue;
238
+ }
239
+ const movementCost = this.map.getMovementCost(current.node, neighborNode);
240
+ const tentativeG = current.g + movementCost;
241
+ if (!neighbor.opened) {
242
+ neighbor.g = tentativeG;
243
+ neighbor.h = this.map.heuristic(neighborNode.position, endPosition) * opts.heuristicWeight;
244
+ neighbor.f = neighbor.g + neighbor.h;
245
+ neighbor.parent = current;
246
+ neighbor.opened = true;
247
+ this.openList.push(neighbor);
248
+ } else if (tentativeG < neighbor.g) {
249
+ neighbor.g = tentativeG;
250
+ neighbor.f = neighbor.g + neighbor.h;
251
+ neighbor.parent = current;
252
+ this.openList.update(neighbor);
253
+ }
254
+ }
255
+ }
256
+ return {
257
+ found: false,
258
+ path: [],
259
+ cost: 0,
260
+ nodesSearched
261
+ };
262
+ }
263
+ /**
264
+ * @zh 清理状态
265
+ * @en Clear state
266
+ */
267
+ clear() {
268
+ this.nodeCache.clear();
269
+ this.openList.clear();
270
+ }
271
+ /**
272
+ * @zh 获取或创建 A* 节点
273
+ * @en Get or create A* node
274
+ */
275
+ getOrCreateAStarNode(node) {
276
+ let astarNode = this.nodeCache.get(node.id);
277
+ if (!astarNode) {
278
+ astarNode = {
279
+ node,
280
+ g: Infinity,
281
+ h: 0,
282
+ f: Infinity,
283
+ parent: null,
284
+ closed: false,
285
+ opened: false
286
+ };
287
+ this.nodeCache.set(node.id, astarNode);
288
+ }
289
+ return astarNode;
290
+ }
291
+ /**
292
+ * @zh 构建路径结果
293
+ * @en Build path result
294
+ */
295
+ buildPath(endNode, nodesSearched) {
296
+ const path = [];
297
+ let current = endNode;
298
+ while (current) {
299
+ path.unshift(current.node.position);
300
+ current = current.parent;
301
+ }
302
+ return {
303
+ found: true,
304
+ path,
305
+ cost: endNode.g,
306
+ nodesSearched
307
+ };
308
+ }
309
+ };
310
+ __name(_AStarPathfinder, "AStarPathfinder");
311
+ var AStarPathfinder = _AStarPathfinder;
312
+ function createAStarPathfinder(map) {
313
+ return new AStarPathfinder(map);
314
+ }
315
+ __name(createAStarPathfinder, "createAStarPathfinder");
316
+
317
+ // src/grid/GridMap.ts
318
+ var _GridNode = class _GridNode {
319
+ constructor(x, y, walkable = true, cost = 1) {
320
+ __publicField(this, "id");
321
+ __publicField(this, "position");
322
+ __publicField(this, "x");
323
+ __publicField(this, "y");
324
+ __publicField(this, "cost");
325
+ __publicField(this, "walkable");
326
+ this.x = x;
327
+ this.y = y;
328
+ this.id = `${x},${y}`;
329
+ this.position = createPoint(x, y);
330
+ this.walkable = walkable;
331
+ this.cost = cost;
332
+ }
333
+ };
334
+ __name(_GridNode, "GridNode");
335
+ var GridNode = _GridNode;
336
+ var DIRECTIONS_4 = [
337
+ {
338
+ dx: 0,
339
+ dy: -1
340
+ },
341
+ {
342
+ dx: 1,
343
+ dy: 0
344
+ },
345
+ {
346
+ dx: 0,
347
+ dy: 1
348
+ },
349
+ {
350
+ dx: -1,
351
+ dy: 0
352
+ }
353
+ // Left
354
+ ];
355
+ var DIRECTIONS_8 = [
356
+ {
357
+ dx: 0,
358
+ dy: -1
359
+ },
360
+ {
361
+ dx: 1,
362
+ dy: -1
363
+ },
364
+ {
365
+ dx: 1,
366
+ dy: 0
367
+ },
368
+ {
369
+ dx: 1,
370
+ dy: 1
371
+ },
372
+ {
373
+ dx: 0,
374
+ dy: 1
375
+ },
376
+ {
377
+ dx: -1,
378
+ dy: 1
379
+ },
380
+ {
381
+ dx: -1,
382
+ dy: 0
383
+ },
384
+ {
385
+ dx: -1,
386
+ dy: -1
387
+ }
388
+ // Up-Left
389
+ ];
390
+ var DEFAULT_GRID_OPTIONS = {
391
+ allowDiagonal: true,
392
+ diagonalCost: Math.SQRT2,
393
+ avoidCorners: true,
394
+ heuristic: octileDistance
395
+ };
396
+ var _GridMap = class _GridMap {
397
+ constructor(width, height, options) {
398
+ __publicField(this, "width");
399
+ __publicField(this, "height");
400
+ __publicField(this, "nodes");
401
+ __publicField(this, "options");
402
+ this.width = width;
403
+ this.height = height;
404
+ this.options = {
405
+ ...DEFAULT_GRID_OPTIONS,
406
+ ...options
407
+ };
408
+ this.nodes = this.createNodes();
409
+ }
410
+ /**
411
+ * @zh 创建网格节点
412
+ * @en Create grid nodes
413
+ */
414
+ createNodes() {
415
+ const nodes = [];
416
+ for (let y = 0; y < this.height; y++) {
417
+ nodes[y] = [];
418
+ for (let x = 0; x < this.width; x++) {
419
+ nodes[y][x] = new GridNode(x, y, true, 1);
420
+ }
421
+ }
422
+ return nodes;
423
+ }
424
+ /**
425
+ * @zh 获取指定位置的节点
426
+ * @en Get node at position
427
+ */
428
+ getNodeAt(x, y) {
429
+ if (!this.isInBounds(x, y)) {
430
+ return null;
431
+ }
432
+ return this.nodes[y][x];
433
+ }
434
+ /**
435
+ * @zh 检查坐标是否在边界内
436
+ * @en Check if coordinates are within bounds
437
+ */
438
+ isInBounds(x, y) {
439
+ return x >= 0 && x < this.width && y >= 0 && y < this.height;
440
+ }
441
+ /**
442
+ * @zh 检查位置是否可通行
443
+ * @en Check if position is walkable
444
+ */
445
+ isWalkable(x, y) {
446
+ const node = this.getNodeAt(x, y);
447
+ return node !== null && node.walkable;
448
+ }
449
+ /**
450
+ * @zh 设置位置是否可通行
451
+ * @en Set position walkability
452
+ */
453
+ setWalkable(x, y, walkable) {
454
+ const node = this.getNodeAt(x, y);
455
+ if (node) {
456
+ node.walkable = walkable;
457
+ }
458
+ }
459
+ /**
460
+ * @zh 设置位置的移动代价
461
+ * @en Set movement cost at position
462
+ */
463
+ setCost(x, y, cost) {
464
+ const node = this.getNodeAt(x, y);
465
+ if (node) {
466
+ node.cost = cost;
467
+ }
468
+ }
469
+ /**
470
+ * @zh 获取节点的邻居
471
+ * @en Get neighbors of a node
472
+ */
473
+ getNeighbors(node) {
474
+ const neighbors = [];
475
+ const { x, y } = node.position;
476
+ const directions = this.options.allowDiagonal ? DIRECTIONS_8 : DIRECTIONS_4;
477
+ for (let i = 0; i < directions.length; i++) {
478
+ const dir = directions[i];
479
+ const nx = x + dir.dx;
480
+ const ny = y + dir.dy;
481
+ if (!this.isInBounds(nx, ny)) {
482
+ continue;
483
+ }
484
+ const neighbor = this.nodes[ny][nx];
485
+ if (!neighbor.walkable) {
486
+ continue;
487
+ }
488
+ if (this.options.avoidCorners && dir.dx !== 0 && dir.dy !== 0) {
489
+ const horizontal = this.getNodeAt(x + dir.dx, y);
490
+ const vertical = this.getNodeAt(x, y + dir.dy);
491
+ if (!horizontal?.walkable || !vertical?.walkable) {
492
+ continue;
493
+ }
494
+ }
495
+ neighbors.push(neighbor);
496
+ }
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
+ }
506
+ /**
507
+ * @zh 计算移动代价
508
+ * @en Calculate movement cost
509
+ */
510
+ getMovementCost(from, to) {
511
+ const dx = Math.abs(from.position.x - to.position.x);
512
+ const dy = Math.abs(from.position.y - to.position.y);
513
+ if (dx !== 0 && dy !== 0) {
514
+ return to.cost * this.options.diagonalCost;
515
+ }
516
+ return to.cost;
517
+ }
518
+ /**
519
+ * @zh 从二维数组加载地图
520
+ * @en Load map from 2D array
521
+ *
522
+ * @param data - @zh 0=可通行,非0=不可通行 @en 0=walkable, non-0=blocked
523
+ */
524
+ loadFromArray(data) {
525
+ for (let y = 0; y < Math.min(data.length, this.height); y++) {
526
+ for (let x = 0; x < Math.min(data[y].length, this.width); x++) {
527
+ this.nodes[y][x].walkable = data[y][x] === 0;
528
+ }
529
+ }
530
+ }
531
+ /**
532
+ * @zh 从字符串加载地图
533
+ * @en Load map from string
534
+ *
535
+ * @param str - @zh 地图字符串,'.'=可通行,'#'=障碍 @en Map string, '.'=walkable, '#'=blocked
536
+ */
537
+ loadFromString(str) {
538
+ const lines = str.trim().split("\n");
539
+ for (let y = 0; y < Math.min(lines.length, this.height); y++) {
540
+ const line = lines[y];
541
+ for (let x = 0; x < Math.min(line.length, this.width); x++) {
542
+ this.nodes[y][x].walkable = line[x] !== "#";
543
+ }
544
+ }
545
+ }
546
+ /**
547
+ * @zh 导出为字符串
548
+ * @en Export to string
549
+ */
550
+ toString() {
551
+ let result = "";
552
+ for (let y = 0; y < this.height; y++) {
553
+ for (let x = 0; x < this.width; x++) {
554
+ result += this.nodes[y][x].walkable ? "." : "#";
555
+ }
556
+ result += "\n";
557
+ }
558
+ return result;
559
+ }
560
+ /**
561
+ * @zh 重置所有节点为可通行
562
+ * @en Reset all nodes to walkable
563
+ */
564
+ reset() {
565
+ for (let y = 0; y < this.height; y++) {
566
+ for (let x = 0; x < this.width; x++) {
567
+ this.nodes[y][x].walkable = true;
568
+ this.nodes[y][x].cost = 1;
569
+ }
570
+ }
571
+ }
572
+ /**
573
+ * @zh 设置矩形区域的通行性
574
+ * @en Set walkability for a rectangle region
575
+ */
576
+ setRectWalkable(x, y, width, height, walkable) {
577
+ for (let dy = 0; dy < height; dy++) {
578
+ for (let dx = 0; dx < width; dx++) {
579
+ this.setWalkable(x + dx, y + dy, walkable);
580
+ }
581
+ }
582
+ }
583
+ };
584
+ __name(_GridMap, "GridMap");
585
+ var GridMap = _GridMap;
586
+ function createGridMap(width, height, options) {
587
+ return new GridMap(width, height, options);
588
+ }
589
+ __name(createGridMap, "createGridMap");
590
+
591
+ // src/navmesh/NavMesh.ts
592
+ var _a;
593
+ var NavMeshNode = (_a = class {
594
+ constructor(polygon) {
595
+ __publicField(this, "id");
596
+ __publicField(this, "position");
597
+ __publicField(this, "cost");
598
+ __publicField(this, "walkable");
599
+ __publicField(this, "polygon");
600
+ this.id = polygon.id;
601
+ this.position = polygon.center;
602
+ this.cost = 1;
603
+ this.walkable = true;
604
+ this.polygon = polygon;
605
+ }
606
+ }, __name(_a, "NavMeshNode"), _a);
607
+ var _NavMesh = class _NavMesh {
608
+ constructor() {
609
+ __publicField(this, "polygons", /* @__PURE__ */ new Map());
610
+ __publicField(this, "nodes", /* @__PURE__ */ new Map());
611
+ __publicField(this, "nextId", 0);
612
+ }
613
+ /**
614
+ * @zh 添加导航多边形
615
+ * @en Add navigation polygon
616
+ *
617
+ * @returns @zh 多边形ID @en Polygon ID
618
+ */
619
+ addPolygon(vertices, neighbors = []) {
620
+ const id = this.nextId++;
621
+ const center = this.calculateCenter(vertices);
622
+ const polygon = {
623
+ id,
624
+ vertices,
625
+ center,
626
+ neighbors,
627
+ portals: /* @__PURE__ */ new Map()
628
+ };
629
+ this.polygons.set(id, polygon);
630
+ this.nodes.set(id, new NavMeshNode(polygon));
631
+ return id;
632
+ }
633
+ /**
634
+ * @zh 设置两个多边形之间的连接
635
+ * @en Set connection between two polygons
636
+ */
637
+ setConnection(polyA, polyB, portal) {
638
+ const polygonA = this.polygons.get(polyA);
639
+ const polygonB = this.polygons.get(polyB);
640
+ if (!polygonA || !polygonB) {
641
+ return;
642
+ }
643
+ const neighborsA = [
644
+ ...polygonA.neighbors
645
+ ];
646
+ const portalsA = new Map(polygonA.portals);
647
+ if (!neighborsA.includes(polyB)) {
648
+ neighborsA.push(polyB);
649
+ }
650
+ portalsA.set(polyB, portal);
651
+ this.polygons.set(polyA, {
652
+ ...polygonA,
653
+ neighbors: neighborsA,
654
+ portals: portalsA
655
+ });
656
+ const reversePortal = {
657
+ left: portal.right,
658
+ right: portal.left
659
+ };
660
+ const neighborsB = [
661
+ ...polygonB.neighbors
662
+ ];
663
+ const portalsB = new Map(polygonB.portals);
664
+ if (!neighborsB.includes(polyA)) {
665
+ neighborsB.push(polyA);
666
+ }
667
+ portalsB.set(polyA, reversePortal);
668
+ this.polygons.set(polyB, {
669
+ ...polygonB,
670
+ neighbors: neighborsB,
671
+ portals: portalsB
672
+ });
673
+ }
674
+ /**
675
+ * @zh 自动检测并建立相邻多边形的连接
676
+ * @en Auto-detect and build connections between adjacent polygons
677
+ */
678
+ build() {
679
+ const polygonList = Array.from(this.polygons.values());
680
+ for (let i = 0; i < polygonList.length; i++) {
681
+ for (let j = i + 1; j < polygonList.length; j++) {
682
+ const polyA = polygonList[i];
683
+ const polyB = polygonList[j];
684
+ const sharedEdge = this.findSharedEdge(polyA.vertices, polyB.vertices);
685
+ if (sharedEdge) {
686
+ this.setConnection(polyA.id, polyB.id, sharedEdge);
687
+ }
688
+ }
689
+ }
690
+ }
691
+ /**
692
+ * @zh 查找两个多边形的共享边
693
+ * @en Find shared edge between two polygons
694
+ */
695
+ findSharedEdge(verticesA, verticesB) {
696
+ const epsilon = 1e-4;
697
+ for (let i = 0; i < verticesA.length; i++) {
698
+ const a1 = verticesA[i];
699
+ const a2 = verticesA[(i + 1) % verticesA.length];
700
+ for (let j = 0; j < verticesB.length; j++) {
701
+ const b1 = verticesB[j];
702
+ const b2 = verticesB[(j + 1) % verticesB.length];
703
+ const match1 = Math.abs(a1.x - b2.x) < epsilon && Math.abs(a1.y - b2.y) < epsilon && Math.abs(a2.x - b1.x) < epsilon && Math.abs(a2.y - b1.y) < epsilon;
704
+ const match2 = Math.abs(a1.x - b1.x) < epsilon && Math.abs(a1.y - b1.y) < epsilon && Math.abs(a2.x - b2.x) < epsilon && Math.abs(a2.y - b2.y) < epsilon;
705
+ if (match1 || match2) {
706
+ return {
707
+ left: a1,
708
+ right: a2
709
+ };
710
+ }
711
+ }
712
+ }
713
+ return null;
714
+ }
715
+ /**
716
+ * @zh 计算多边形中心
717
+ * @en Calculate polygon center
718
+ */
719
+ calculateCenter(vertices) {
720
+ let x = 0;
721
+ let y = 0;
722
+ for (const v of vertices) {
723
+ x += v.x;
724
+ y += v.y;
725
+ }
726
+ return createPoint(x / vertices.length, y / vertices.length);
727
+ }
728
+ /**
729
+ * @zh 查找包含点的多边形
730
+ * @en Find polygon containing point
731
+ */
732
+ findPolygonAt(x, y) {
733
+ for (const polygon of this.polygons.values()) {
734
+ if (this.isPointInPolygon(x, y, polygon.vertices)) {
735
+ return polygon;
736
+ }
737
+ }
738
+ return null;
739
+ }
740
+ /**
741
+ * @zh 检查点是否在多边形内
742
+ * @en Check if point is inside polygon
743
+ */
744
+ isPointInPolygon(x, y, vertices) {
745
+ let inside = false;
746
+ const n = vertices.length;
747
+ for (let i = 0, j = n - 1; i < n; j = i++) {
748
+ const xi = vertices[i].x;
749
+ const yi = vertices[i].y;
750
+ const xj = vertices[j].x;
751
+ const yj = vertices[j].y;
752
+ if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
753
+ inside = !inside;
754
+ }
755
+ }
756
+ return inside;
757
+ }
758
+ // ==========================================================================
759
+ // IPathfindingMap 接口实现 | IPathfindingMap Interface Implementation
760
+ // ==========================================================================
761
+ getNodeAt(x, y) {
762
+ const polygon = this.findPolygonAt(x, y);
763
+ return polygon ? this.nodes.get(polygon.id) ?? null : null;
764
+ }
765
+ getNeighbors(node) {
766
+ const navNode = node;
767
+ const neighbors = [];
768
+ for (const neighborId of navNode.polygon.neighbors) {
769
+ const neighbor = this.nodes.get(neighborId);
770
+ if (neighbor) {
771
+ neighbors.push(neighbor);
772
+ }
773
+ }
774
+ return neighbors;
775
+ }
776
+ heuristic(a, b) {
777
+ return euclideanDistance(a, b);
778
+ }
779
+ getMovementCost(from, to) {
780
+ return euclideanDistance(from.position, to.position);
781
+ }
782
+ isWalkable(x, y) {
783
+ return this.findPolygonAt(x, y) !== null;
784
+ }
785
+ // ==========================================================================
786
+ // 寻路 | Pathfinding
787
+ // ==========================================================================
788
+ /**
789
+ * @zh 在导航网格上寻路
790
+ * @en Find path on navigation mesh
791
+ */
792
+ findPath(startX, startY, endX, endY, options) {
793
+ const opts = {
794
+ ...DEFAULT_PATHFINDING_OPTIONS,
795
+ ...options
796
+ };
797
+ const startPolygon = this.findPolygonAt(startX, startY);
798
+ const endPolygon = this.findPolygonAt(endX, endY);
799
+ if (!startPolygon || !endPolygon) {
800
+ return EMPTY_PATH_RESULT;
801
+ }
802
+ if (startPolygon.id === endPolygon.id) {
803
+ return {
804
+ found: true,
805
+ path: [
806
+ createPoint(startX, startY),
807
+ createPoint(endX, endY)
808
+ ],
809
+ cost: euclideanDistance(createPoint(startX, startY), createPoint(endX, endY)),
810
+ nodesSearched: 1
811
+ };
812
+ }
813
+ const polygonPath = this.findPolygonPath(startPolygon, endPolygon, opts);
814
+ if (!polygonPath.found) {
815
+ return EMPTY_PATH_RESULT;
816
+ }
817
+ const start = createPoint(startX, startY);
818
+ const end = createPoint(endX, endY);
819
+ const pointPath = this.funnelPath(start, end, polygonPath.polygons);
820
+ return {
821
+ found: true,
822
+ path: pointPath,
823
+ cost: this.calculatePathLength(pointPath),
824
+ nodesSearched: polygonPath.nodesSearched
825
+ };
826
+ }
827
+ /**
828
+ * @zh 在多边形图上寻路
829
+ * @en Find path on polygon graph
830
+ */
831
+ findPolygonPath(start, end, opts) {
832
+ const openList = new BinaryHeap((a, b) => a.f - b.f);
833
+ const closed = /* @__PURE__ */ new Set();
834
+ const states = /* @__PURE__ */ new Map();
835
+ const startState = {
836
+ polygon: start,
837
+ g: 0,
838
+ f: euclideanDistance(start.center, end.center) * opts.heuristicWeight,
839
+ parent: null
840
+ };
841
+ states.set(start.id, startState);
842
+ openList.push(startState);
843
+ let nodesSearched = 0;
844
+ while (!openList.isEmpty && nodesSearched < opts.maxNodes) {
845
+ const current = openList.pop();
846
+ nodesSearched++;
847
+ if (current.polygon.id === end.id) {
848
+ const path = [];
849
+ let state = current;
850
+ while (state) {
851
+ path.unshift(state.polygon);
852
+ state = state.parent;
853
+ }
854
+ return {
855
+ found: true,
856
+ polygons: path,
857
+ nodesSearched
858
+ };
859
+ }
860
+ closed.add(current.polygon.id);
861
+ for (const neighborId of current.polygon.neighbors) {
862
+ if (closed.has(neighborId)) {
863
+ continue;
864
+ }
865
+ const neighborPolygon = this.polygons.get(neighborId);
866
+ if (!neighborPolygon) {
867
+ continue;
868
+ }
869
+ const g = current.g + euclideanDistance(current.polygon.center, neighborPolygon.center);
870
+ let neighborState = states.get(neighborId);
871
+ if (!neighborState) {
872
+ neighborState = {
873
+ polygon: neighborPolygon,
874
+ g,
875
+ f: g + euclideanDistance(neighborPolygon.center, end.center) * opts.heuristicWeight,
876
+ parent: current
877
+ };
878
+ states.set(neighborId, neighborState);
879
+ openList.push(neighborState);
880
+ } else if (g < neighborState.g) {
881
+ neighborState.g = g;
882
+ neighborState.f = g + euclideanDistance(neighborPolygon.center, end.center) * opts.heuristicWeight;
883
+ neighborState.parent = current;
884
+ openList.update(neighborState);
885
+ }
886
+ }
887
+ }
888
+ return {
889
+ found: false,
890
+ polygons: [],
891
+ nodesSearched
892
+ };
893
+ }
894
+ /**
895
+ * @zh 使用漏斗算法优化路径
896
+ * @en Optimize path using funnel algorithm
897
+ */
898
+ funnelPath(start, end, polygons) {
899
+ if (polygons.length <= 1) {
900
+ return [
901
+ start,
902
+ end
903
+ ];
904
+ }
905
+ const portals = [];
906
+ for (let i = 0; i < polygons.length - 1; i++) {
907
+ const portal = polygons[i].portals.get(polygons[i + 1].id);
908
+ if (portal) {
909
+ portals.push(portal);
910
+ }
911
+ }
912
+ if (portals.length === 0) {
913
+ return [
914
+ start,
915
+ end
916
+ ];
917
+ }
918
+ const path = [
919
+ start
920
+ ];
921
+ let apex = start;
922
+ let leftIndex = 0;
923
+ let rightIndex = 0;
924
+ let left = portals[0].left;
925
+ let right = portals[0].right;
926
+ for (let i = 1; i <= portals.length; i++) {
927
+ const nextLeft = i < portals.length ? portals[i].left : end;
928
+ const nextRight = i < portals.length ? portals[i].right : end;
929
+ if (this.triArea2(apex, right, nextRight) <= 0) {
930
+ if (apex === right || this.triArea2(apex, left, nextRight) > 0) {
931
+ right = nextRight;
932
+ rightIndex = i;
933
+ } else {
934
+ path.push(left);
935
+ apex = left;
936
+ leftIndex = rightIndex = leftIndex;
937
+ left = right = apex;
938
+ i = leftIndex;
939
+ continue;
940
+ }
941
+ }
942
+ if (this.triArea2(apex, left, nextLeft) >= 0) {
943
+ if (apex === left || this.triArea2(apex, right, nextLeft) < 0) {
944
+ left = nextLeft;
945
+ leftIndex = i;
946
+ } else {
947
+ path.push(right);
948
+ apex = right;
949
+ leftIndex = rightIndex = rightIndex;
950
+ left = right = apex;
951
+ i = rightIndex;
952
+ continue;
953
+ }
954
+ }
955
+ }
956
+ path.push(end);
957
+ return path;
958
+ }
959
+ /**
960
+ * @zh 计算三角形面积的两倍(用于判断点的相对位置)
961
+ * @en Calculate twice the triangle area (for point relative position)
962
+ */
963
+ triArea2(a, b, c) {
964
+ return (c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y);
965
+ }
966
+ /**
967
+ * @zh 计算路径总长度
968
+ * @en Calculate total path length
969
+ */
970
+ calculatePathLength(path) {
971
+ let length = 0;
972
+ for (let i = 1; i < path.length; i++) {
973
+ length += euclideanDistance(path[i - 1], path[i]);
974
+ }
975
+ return length;
976
+ }
977
+ /**
978
+ * @zh 清空导航网格
979
+ * @en Clear navigation mesh
980
+ */
981
+ clear() {
982
+ this.polygons.clear();
983
+ this.nodes.clear();
984
+ this.nextId = 0;
985
+ }
986
+ /**
987
+ * @zh 获取所有多边形
988
+ * @en Get all polygons
989
+ */
990
+ getPolygons() {
991
+ return Array.from(this.polygons.values());
992
+ }
993
+ /**
994
+ * @zh 获取多边形数量
995
+ * @en Get polygon count
996
+ */
997
+ get polygonCount() {
998
+ return this.polygons.size;
999
+ }
1000
+ };
1001
+ __name(_NavMesh, "NavMesh");
1002
+ var NavMesh = _NavMesh;
1003
+ function createNavMesh() {
1004
+ return new NavMesh();
1005
+ }
1006
+ __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
+ export {
1782
+ AStarPathfinder,
1783
+ BinaryHeap,
1784
+ CatmullRomSmoother,
1785
+ CombinedSmoother,
1786
+ DEFAULT_GRID_OPTIONS,
1787
+ DEFAULT_PATHFINDING_OPTIONS,
1788
+ DIRECTIONS_4,
1789
+ DIRECTIONS_8,
1790
+ EMPTY_PATH_RESULT,
1791
+ FindPathExecutor,
1792
+ FindPathSmoothExecutor,
1793
+ FindPathSmoothTemplate,
1794
+ FindPathTemplate,
1795
+ GetPathDistanceExecutor,
1796
+ GetPathDistanceTemplate,
1797
+ GetPathLengthExecutor,
1798
+ GetPathLengthTemplate,
1799
+ GetPathPointExecutor,
1800
+ GetPathPointTemplate,
1801
+ GridMap,
1802
+ GridNode,
1803
+ HasLineOfSightExecutor,
1804
+ HasLineOfSightTemplate,
1805
+ IsWalkableExecutor,
1806
+ IsWalkableTemplate,
1807
+ LineOfSightSmoother,
1808
+ MoveAlongPathExecutor,
1809
+ MoveAlongPathTemplate,
1810
+ NavMesh,
1811
+ PathfindingNodeDefinitions,
1812
+ bresenhamLineOfSight,
1813
+ chebyshevDistance,
1814
+ createAStarPathfinder,
1815
+ createCatmullRomSmoother,
1816
+ createCombinedSmoother,
1817
+ createGridMap,
1818
+ createLineOfSightSmoother,
1819
+ createNavMesh,
1820
+ createPoint,
1821
+ euclideanDistance,
1822
+ manhattanDistance,
1823
+ octileDistance,
1824
+ raycastLineOfSight
1825
+ };
1826
+ //# sourceMappingURL=index.js.map