fast-astar 1.0.5 → 2.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/Grid.js ADDED
@@ -0,0 +1,585 @@
1
+ // 🌐 通用网格类 - 内存优化版本
2
+ // 支持所有环境:Node.js、浏览器、WebWorker等
3
+
4
+ // 四叉树节点 - 用于超大地图的障碍物查询优化
5
+ class QuadTreeNode {
6
+ constructor(x, y, width, height) {
7
+ this.x = x;
8
+ this.y = y;
9
+ this.width = width;
10
+ this.height = height;
11
+ this.children = null; // [NW, NE, SW, SE]
12
+ this.hasObstacle = false; // 是否包含障碍物
13
+ this.isLeaf = true;
14
+ }
15
+
16
+ // 检查点是否在区域内
17
+ contains(px, py) {
18
+ return px >= this.x && px < this.x + this.width &&
19
+ py >= this.y && py < this.y + this.height;
20
+ }
21
+
22
+ // 分割节点
23
+ subdivide() {
24
+ const halfWidth = Math.floor(this.width / 2);
25
+ const halfHeight = Math.floor(this.height / 2);
26
+
27
+ this.children = [
28
+ new QuadTreeNode(this.x, this.y, halfWidth, halfHeight), // NW
29
+ new QuadTreeNode(this.x + halfWidth, this.y, this.width - halfWidth, halfHeight), // NE
30
+ new QuadTreeNode(this.x, this.y + halfHeight, halfWidth, this.height - halfHeight), // SW
31
+ new QuadTreeNode(this.x + halfWidth, this.y + halfHeight,
32
+ this.width - halfWidth, this.height - halfHeight) // SE
33
+ ];
34
+ this.isLeaf = false;
35
+ }
36
+
37
+ // 检查区域是否包含障碍物
38
+ hasObstacleInRegion(grid, isObstacleFunc) {
39
+ if (this.isLeaf) {
40
+ // 叶子节点:检查所有点
41
+ for (let y = this.y; y < this.y + this.height; y++) {
42
+ for (let x = this.x; x < this.x + this.width; x++) {
43
+ if (isObstacleFunc(x, y)) {
44
+ return true;
45
+ }
46
+ }
47
+ }
48
+ return false;
49
+ } else {
50
+ // 非叶子节点:递归检查子节点
51
+ return this.children.some(child => child.hasObstacleInRegion(grid, isObstacleFunc));
52
+ }
53
+ }
54
+
55
+ // 构建四叉树
56
+ build(grid, isObstacleFunc, minSize = 16) {
57
+ // 如果区域太小,不再分割
58
+ if (this.width <= minSize && this.height <= minSize) {
59
+ this.hasObstacle = this.hasObstacleInRegion(grid, isObstacleFunc);
60
+ return;
61
+ }
62
+
63
+ // 检查当前区域是否包含障碍物
64
+ this.hasObstacle = this.hasObstacleInRegion(grid, isObstacleFunc);
65
+
66
+ if (this.hasObstacle) {
67
+ // 如果包含障碍物,继续分割
68
+ this.subdivide();
69
+ for (const child of this.children) {
70
+ child.build(grid, isObstacleFunc, minSize);
71
+ }
72
+ }
73
+ }
74
+
75
+ // 查询点是否为障碍物(使用四叉树加速)
76
+ queryObstacle(px, py, grid, isObstacleFunc) {
77
+ if (!this.contains(px, py)) {
78
+ return false;
79
+ }
80
+
81
+ if (this.isLeaf) {
82
+ // 叶子节点:直接查询
83
+ return isObstacleFunc(px, py);
84
+ }
85
+
86
+ // 非叶子节点:递归查询
87
+ for (const child of this.children) {
88
+ if (child.contains(px, py)) {
89
+ return child.queryObstacle(px, py, grid, isObstacleFunc);
90
+ }
91
+ }
92
+
93
+ return false;
94
+ }
95
+
96
+ // 查询区域是否包含障碍物
97
+ queryRegion(x, y, width, height, grid, isObstacleFunc) {
98
+ // 检查区域是否与当前节点重叠
99
+ if (x + width <= this.x || x >= this.x + this.width ||
100
+ y + height <= this.y || y >= this.y + this.height) {
101
+ return false; // 不重叠
102
+ }
103
+
104
+ if (!this.hasObstacle) {
105
+ return false; // 当前区域无障碍物
106
+ }
107
+
108
+ if (this.isLeaf) {
109
+ // 叶子节点:检查区域内的所有点
110
+ const endX = Math.min(x + width, this.x + this.width);
111
+ const endY = Math.min(y + height, this.y + this.height);
112
+ for (let py = Math.max(y, this.y); py < endY; py++) {
113
+ for (let px = Math.max(x, this.x); px < endX; px++) {
114
+ if (isObstacleFunc(px, py)) {
115
+ return true;
116
+ }
117
+ }
118
+ }
119
+ return false;
120
+ }
121
+
122
+ // 非叶子节点:递归查询子节点
123
+ return this.children.some(child =>
124
+ child.queryRegion(x, y, width, height, grid, isObstacleFunc)
125
+ );
126
+ }
127
+ }
128
+
129
+ class Grid {
130
+ constructor(options = {}) {
131
+ // 支持多种初始化方式
132
+ if (typeof options === 'number') {
133
+ // 兼容: new Grid(100)
134
+ this.col = options;
135
+ this.row = options;
136
+ } else if (Array.isArray(options)) {
137
+ // 数组: new Grid([100, 100])
138
+ this.col = options[0] || 10;
139
+ this.row = options[1] || 10;
140
+ } else {
141
+ // 对象: new Grid({col: 100, row: 100})
142
+ this.col = options.col || options.width || 10;
143
+ this.row = options.row || options.height || 10;
144
+ }
145
+
146
+ // 性能优化:预计算常用值
147
+ this.size = this.col * this.row;
148
+ this.maxX = this.col - 1;
149
+ this.maxY = this.row - 1;
150
+
151
+ // 根据网格大小选择存储策略
152
+ if (this.size > 1000000) { // 大于100万节点使用内存高效模式
153
+ this.useMemoryEfficientMode = true;
154
+ this.initMemoryEfficientGrid();
155
+ } else {
156
+ this.useMemoryEfficientMode = false;
157
+ this.grid = this.createGrid();
158
+ }
159
+
160
+ // 对于超大地图(大于1000万节点),启用四叉树优化
161
+ this.useQuadTree = this.size > 10000000;
162
+ this.quadTree = null;
163
+ this.quadTreeBuilt = false;
164
+
165
+ // 可选的 render 回调函数,用于可视化搜索过程
166
+ this.render = (typeof options === 'object' && options !== null && typeof options.render === 'function')
167
+ ? options.render
168
+ : null;
169
+ }
170
+
171
+ // 内存高效模式:使用位图存储障碍物,按需创建节点对象
172
+ initMemoryEfficientGrid() {
173
+ // 使用位图存储障碍物信息,每个位代表一个节点
174
+ this.obstacles = new Uint8Array(Math.ceil(this.size / 8));
175
+
176
+ // 节点缓存:只为访问过的节点创建对象
177
+ this.nodeCache = new Map();
178
+
179
+ console.log(`🚀 使用内存高效模式: ${this.col}x${this.row} (${(this.size/1000000).toFixed(1)}M节点)`);
180
+ console.log(`💾 障碍物位图: ${(this.obstacles.length/1024).toFixed(1)}KB vs 传统模式: ${(this.size * 150 / 1024 / 1024).toFixed(1)}MB`);
181
+ }
182
+
183
+ createGrid() {
184
+ const grid = new Array(this.row);
185
+
186
+ for (let y = 0; y < this.row; y++) {
187
+ grid[y] = new Array(this.col);
188
+ for (let x = 0; x < this.col; x++) {
189
+ grid[y][x] = {
190
+ x: x,
191
+ y: y,
192
+ value: 0, // 0: 可通行, 1: 障碍物
193
+ g: 0, // 从起点到当前点的实际代价
194
+ h: 0, // 从当前点到终点的启发式代价
195
+ f: 0, // f = g + h
196
+ parent: null,
197
+ key: [x, y] // 用于兼容原始 API
198
+ };
199
+ }
200
+ }
201
+
202
+ return grid;
203
+ }
204
+
205
+ // 内存高效模式的障碍物操作
206
+ setObstacleBit(x, y, isObstacle) {
207
+ const index = y * this.col + x;
208
+ const byteIndex = Math.floor(index / 8);
209
+ const bitIndex = index % 8;
210
+
211
+ if (isObstacle) {
212
+ this.obstacles[byteIndex] |= (1 << bitIndex);
213
+ } else {
214
+ this.obstacles[byteIndex] &= ~(1 << bitIndex);
215
+ }
216
+ }
217
+
218
+ getObstacleBit(x, y) {
219
+ const index = y * this.col + x;
220
+ const byteIndex = Math.floor(index / 8);
221
+ const bitIndex = index % 8;
222
+ return (this.obstacles[byteIndex] & (1 << bitIndex)) !== 0;
223
+ }
224
+
225
+ // 按需创建节点对象
226
+ getOrCreateNode(x, y) {
227
+ const key = `${x},${y}`;
228
+
229
+ if (!this.nodeCache.has(key)) {
230
+ this.nodeCache.set(key, {
231
+ x: x,
232
+ y: y,
233
+ value: this.getObstacleBit(x, y) ? 1 : 0,
234
+ g: 0,
235
+ h: 0,
236
+ f: 0,
237
+ parent: null,
238
+ key: [x, y] // 用于兼容原始 API
239
+ });
240
+ }
241
+
242
+ return this.nodeCache.get(key);
243
+ }
244
+
245
+ // 设置障碍物
246
+ setWalkAt(x, y, walkable) {
247
+ if (!this.isValidCoordinate(x, y)) return;
248
+
249
+ if (this.useMemoryEfficientMode) {
250
+ this.setObstacleBit(x, y, !walkable);
251
+
252
+ // 如果节点已缓存,更新其值
253
+ const key = `${x},${y}`;
254
+ if (this.nodeCache.has(key)) {
255
+ this.nodeCache.get(key).value = walkable ? 0 : 1;
256
+ }
257
+ } else {
258
+ this.grid[y][x].value = walkable ? 0 : 1;
259
+ }
260
+
261
+ // 如果使用四叉树,标记需要重新构建
262
+ if (this.useQuadTree && this.quadTreeBuilt) {
263
+ this.quadTreeBuilt = false;
264
+ this.quadTree = null;
265
+ }
266
+ }
267
+
268
+ // 检查坐标是否有效
269
+ isValidCoordinate(x, y) {
270
+ return x >= 0 && x < this.col && y >= 0 && y < this.row;
271
+ }
272
+
273
+ // 检查位置是否可通行(支持四叉树优化)
274
+ isWalkableAt(x, y) {
275
+ if (!this.isValidCoordinate(x, y)) {
276
+ return false;
277
+ }
278
+
279
+ // 如果使用四叉树且已构建,使用四叉树查询
280
+ if (this.useQuadTree && this.quadTree && this.quadTreeBuilt) {
281
+ const isObstacleFunc = this.useMemoryEfficientMode
282
+ ? (px, py) => this.getObstacleBit(px, py)
283
+ : (px, py) => this.grid[py] && this.grid[py][px] && this.grid[py][px].value >= 1;
284
+ return !this.quadTree.queryObstacle(x, y, this, isObstacleFunc);
285
+ }
286
+
287
+ if (this.useMemoryEfficientMode) {
288
+ return !this.getObstacleBit(x, y);
289
+ } else {
290
+ return this.grid[y][x].value === 0;
291
+ }
292
+ }
293
+
294
+ // 构建四叉树(用于超大地图的障碍物查询优化)
295
+ buildQuadTree() {
296
+ if (!this.useQuadTree || this.quadTreeBuilt) {
297
+ return;
298
+ }
299
+
300
+ console.log(`🌳 开始构建四叉树优化 (${this.col}x${this.row})...`);
301
+ const startTime = Date.now();
302
+
303
+ // 创建根节点
304
+ this.quadTree = new QuadTreeNode(0, 0, this.col, this.row);
305
+
306
+ // 定义障碍物检查函数
307
+ const isObstacleFunc = this.useMemoryEfficientMode
308
+ ? (x, y) => this.getObstacleBit(x, y)
309
+ : (x, y) => this.grid[y] && this.grid[y][x] && this.grid[y][x].value >= 1;
310
+
311
+ // 构建四叉树(最小节点大小为32x32)
312
+ const minNodeSize = Math.max(16, Math.floor(Math.sqrt(this.size) / 100));
313
+ this.quadTree.build(this, isObstacleFunc, minNodeSize);
314
+
315
+ this.quadTreeBuilt = true;
316
+ const buildTime = Date.now() - startTime;
317
+ console.log(`✅ 四叉树构建完成 (${buildTime}ms)`);
318
+ }
319
+
320
+ // 批量检查区域是否可通行(使用四叉树优化)
321
+ isRegionWalkable(x, y, width, height) {
322
+ if (!this.isValidCoordinate(x, y) ||
323
+ !this.isValidCoordinate(x + width - 1, y + height - 1)) {
324
+ return false;
325
+ }
326
+
327
+ // 如果使用四叉树且已构建,使用四叉树查询
328
+ if (this.useQuadTree && this.quadTree && this.quadTreeBuilt) {
329
+ const isObstacleFunc = this.useMemoryEfficientMode
330
+ ? (px, py) => this.getObstacleBit(px, py)
331
+ : (px, py) => this.grid[py] && this.grid[py][px] && this.grid[py][px].value >= 1;
332
+
333
+ // 如果区域包含障碍物,返回false
334
+ return !this.quadTree.queryRegion(x, y, width, height, this, isObstacleFunc);
335
+ }
336
+
337
+ // 回退到逐个检查
338
+ for (let py = y; py < y + height; py++) {
339
+ for (let px = x; px < x + width; px++) {
340
+ if (!this.isWalkableAt(px, py)) {
341
+ return false;
342
+ }
343
+ }
344
+ }
345
+ return true;
346
+ }
347
+
348
+ // 获取节点(兼容原始 API:get([x, y]))
349
+ get(point) {
350
+ if (!Array.isArray(point) || point.length < 2) {
351
+ return null;
352
+ }
353
+ return this.getNodeAt(point[0], point[1]);
354
+ }
355
+
356
+ // 获取节点
357
+ getNodeAt(x, y) {
358
+ if (!this.isValidCoordinate(x, y)) {
359
+ return null;
360
+ }
361
+
362
+ let node;
363
+ if (this.useMemoryEfficientMode) {
364
+ node = this.getOrCreateNode(x, y);
365
+ } else {
366
+ node = this.grid[y][x];
367
+ }
368
+
369
+ // 确保节点有 key 属性(用于兼容原始 API)
370
+ if (node && !node.key) {
371
+ node.key = [x, y];
372
+ }
373
+
374
+ // 如果设置了 render 回调,确保节点有 render 属性
375
+ if (node && this.render) {
376
+ node.render = this.render;
377
+ }
378
+
379
+ return node;
380
+ }
381
+
382
+ // 设置节点属性(兼容原始 API:set([x, y], 'key', value))
383
+ set(point, key, val) {
384
+ if (!Array.isArray(point) || point.length < 2) {
385
+ return;
386
+ }
387
+
388
+ const node = this.getNodeAt(point[0], point[1]);
389
+ if (!node) {
390
+ return;
391
+ }
392
+
393
+ // 设置节点属性
394
+ node[key] = val;
395
+
396
+ // 如果设置的是 value(障碍物),同步到网格
397
+ if (key === 'value') {
398
+ this.setWalkAt(point[0], point[1], val === 0);
399
+ }
400
+
401
+ // 触发 render 回调
402
+ if (this.render && typeof this.render === 'function') {
403
+ // render 回调的 this 指向节点本身
404
+ this.render.call(node, { key, val });
405
+ }
406
+ }
407
+
408
+ // 重置网格(清除路径查找数据)
409
+ reset() {
410
+ if (this.useMemoryEfficientMode) {
411
+ // 清空节点缓存
412
+ this.nodeCache.clear();
413
+ } else {
414
+ for (let y = 0; y < this.row; y++) {
415
+ for (let x = 0; x < this.col; x++) {
416
+ const node = this.grid[y][x];
417
+ node.g = 0;
418
+ node.h = 0;
419
+ node.f = 0;
420
+ node.parent = null;
421
+ }
422
+ }
423
+ }
424
+ }
425
+
426
+ // 批量设置障碍物
427
+ setObstacles(obstacles) {
428
+ for (const obstacle of obstacles) {
429
+ if (Array.isArray(obstacle) && obstacle.length >= 2) {
430
+ this.setWalkAt(obstacle[0], obstacle[1], false);
431
+ } else if (obstacle.x !== undefined && obstacle.y !== undefined) {
432
+ this.setWalkAt(obstacle.x, obstacle.y, false);
433
+ }
434
+ }
435
+
436
+ // 如果使用四叉树,需要重新构建
437
+ if (this.useQuadTree && this.quadTreeBuilt) {
438
+ this.quadTreeBuilt = false;
439
+ this.quadTree = null;
440
+ }
441
+ }
442
+
443
+ // 从二维数组创建网格
444
+ static fromArray(array) {
445
+ const rows = array.length;
446
+ const cols = array[0] ? array[0].length : 0;
447
+
448
+ const grid = new Grid({ col: cols, row: rows });
449
+
450
+ for (let y = 0; y < rows; y++) {
451
+ for (let x = 0; x < cols; x++) {
452
+ if (array[y] && array[y][x] !== undefined) {
453
+ grid.setWalkAt(x, y, array[y][x] === 0);
454
+ }
455
+ }
456
+ }
457
+
458
+ return grid;
459
+ }
460
+
461
+ // 转换为二维数组
462
+ toArray() {
463
+ const array = new Array(this.row);
464
+
465
+ for (let y = 0; y < this.row; y++) {
466
+ array[y] = new Array(this.col);
467
+ for (let x = 0; x < this.col; x++) {
468
+ if (this.useMemoryEfficientMode) {
469
+ array[y][x] = this.getObstacleBit(x, y) ? 1 : 0;
470
+ } else {
471
+ array[y][x] = this.grid[y][x].value;
472
+ }
473
+ }
474
+ }
475
+
476
+ return array;
477
+ }
478
+
479
+ // 获取网格统计信息
480
+ getStats() {
481
+ let walkable = 0;
482
+ let obstacles = 0;
483
+
484
+ if (this.useMemoryEfficientMode) {
485
+ // 遍历位图统计
486
+ for (let i = 0; i < this.size; i++) {
487
+ const byteIndex = Math.floor(i / 8);
488
+ const bitIndex = i % 8;
489
+ if (this.obstacles[byteIndex] & (1 << bitIndex)) {
490
+ obstacles++;
491
+ } else {
492
+ walkable++;
493
+ }
494
+ }
495
+ } else {
496
+ for (let y = 0; y < this.row; y++) {
497
+ for (let x = 0; x < this.col; x++) {
498
+ if (this.grid[y][x].value === 0) {
499
+ walkable++;
500
+ } else {
501
+ obstacles++;
502
+ }
503
+ }
504
+ }
505
+ }
506
+
507
+ return {
508
+ total: this.size,
509
+ walkable,
510
+ obstacles,
511
+ walkablePercent: (walkable / this.size * 100).toFixed(2),
512
+ obstaclePercent: (obstacles / this.size * 100).toFixed(2),
513
+ memoryMode: this.useMemoryEfficientMode ? 'Memory Efficient' : 'Standard',
514
+ cachedNodes: this.useMemoryEfficientMode ? this.nodeCache.size : this.size
515
+ };
516
+ }
517
+
518
+ // 随机生成障碍物
519
+ generateRandomObstacles(density = 0.3) {
520
+ const obstacleCount = Math.floor(this.size * density);
521
+
522
+ for (let i = 0; i < obstacleCount; i++) {
523
+ const x = Math.floor(Math.random() * this.col);
524
+ const y = Math.floor(Math.random() * this.row);
525
+ this.setWalkAt(x, y, false);
526
+ }
527
+ }
528
+
529
+ // 获取内存使用情况
530
+ getMemoryUsage() {
531
+ if (this.useMemoryEfficientMode) {
532
+ const obstacleBytes = this.obstacles.length;
533
+ const cacheBytes = this.nodeCache.size * 100; // 估算每个节点对象100字节
534
+
535
+ return {
536
+ obstacles: obstacleBytes,
537
+ nodeCache: cacheBytes,
538
+ total: obstacleBytes + cacheBytes,
539
+ mode: 'Memory Efficient',
540
+ savings: Math.max(0, this.size * 150 - (obstacleBytes + cacheBytes))
541
+ };
542
+ } else {
543
+ const gridBytes = this.size * 150; // 估算每个节点对象150字节
544
+
545
+ return {
546
+ grid: gridBytes,
547
+ total: gridBytes,
548
+ mode: 'Standard'
549
+ };
550
+ }
551
+ }
552
+
553
+ // 生成迷宫(简化版本,避免在大地图上耗时过长)
554
+ generateMaze() {
555
+ // 对于大地图,使用简化的迷宫生成算法
556
+ if (this.useMemoryEfficientMode && this.size > 10000000) {
557
+ console.log('⚠️ 大地图跳过迷宫生成以节省时间');
558
+ return;
559
+ }
560
+
561
+ // 首先填充所有位置为障碍物
562
+ for (let y = 0; y < this.row; y++) {
563
+ for (let x = 0; x < this.col; x++) {
564
+ this.setWalkAt(x, y, false);
565
+ }
566
+ }
567
+
568
+ // 简化的迷宫生成:创建通道
569
+ for (let y = 1; y < this.row - 1; y += 2) {
570
+ for (let x = 1; x < this.col - 1; x += 2) {
571
+ this.setWalkAt(x, y, true);
572
+
573
+ // 随机创建连接
574
+ if (Math.random() > 0.5 && x + 1 < this.col - 1) {
575
+ this.setWalkAt(x + 1, y, true);
576
+ }
577
+ if (Math.random() > 0.5 && y + 1 < this.row - 1) {
578
+ this.setWalkAt(x, y + 1, true);
579
+ }
580
+ }
581
+ }
582
+ }
583
+ }
584
+
585
+ export default Grid;