@esengine/pathfinding
Version:
寻路算法库,支持A*、广度优先等算法,适用于Cocos Creator、Laya等游戏引擎
244 lines (209 loc) • 7.62 kB
text/typescript
import { IVector2, Vector2Utils } from '../../../Types/IVector2';
import { IAstarGraph } from './IAstarGraph';
import { PriorityQueue, IPriorityQueueNode } from '../../../Utils/PriorityQueue';
/**
* A*算法节点
*/
class AStarNode implements IPriorityQueueNode {
public node: IVector2;
public priority: number = 0;
public gCost: number = 0;
public hCost: number = 0;
public parent: AStarNode | null = null;
public hash: number = 0; // 缓存哈希值,避免重复计算
constructor(node: IVector2, gCost: number = 0, hCost: number = 0, parent: AStarNode | null = null) {
this.node = node;
this.gCost = gCost;
this.hCost = hCost;
this.priority = gCost + hCost;
this.parent = parent;
this.hash = Vector2Utils.toHash(node); // 预计算哈希值
}
updateCosts(gCost: number, hCost: number, parent: AStarNode | null = null): void {
this.gCost = gCost;
this.hCost = hCost;
this.priority = gCost + hCost;
this.parent = parent;
}
updateNode(node: IVector2, gCost: number = 0, hCost: number = 0, parent: AStarNode | null = null): void {
this.node = node;
this.gCost = gCost;
this.hCost = hCost;
this.priority = gCost + hCost;
this.parent = parent;
this.hash = Vector2Utils.toHash(node); // 更新哈希值
}
reset(): void {
this.node = null as any;
this.priority = 0;
this.gCost = 0;
this.hCost = 0;
this.parent = null;
this.hash = 0;
}
}
export class AStarPathfinder {
private static _nodePool: AStarNode[] = [];
private static _tempPath: IVector2[] = [];
/**
* 从对象池获取节点
*/
private static _getNode(node: IVector2, gCost: number = 0, hCost: number = 0, parent: AStarNode | null = null): AStarNode {
let astarNode = this._nodePool.pop();
if (!astarNode) {
astarNode = new AStarNode(node, gCost, hCost, parent);
} else {
astarNode.updateNode(node, gCost, hCost, parent);
}
return astarNode;
}
/**
* 回收节点到对象池
*/
private static _recycleNode(node: AStarNode): void {
if (this._nodePool.length < 1000) { // 限制池大小
node.reset();
this._nodePool.push(node);
}
}
/**
* 使用A*算法搜索路径
* @param graph 图对象
* @param start 起始节点
* @param goal 目标节点
* @returns 搜索结果
*/
static search<T extends IVector2>(
graph: IAstarGraph<T>,
start: T,
goal: T
): { found: boolean; goalNode?: AStarNode } {
const openSet = new PriorityQueue<AStarNode>();
const closedSet = new Set<number>(); // 使用数值哈希
const openSetMap = new Map<number, AStarNode>(); // 使用数值哈希
const startHash = Vector2Utils.toHash(start);
const goalHash = Vector2Utils.toHash(goal);
if (startHash === goalHash) {
return { found: true, goalNode: this._getNode(start, 0, 0) };
}
const startNode = this._getNode(start, 0, graph.heuristic(start, goal));
openSet.enqueue(startNode);
openSetMap.set(startHash, startNode);
let goalNode: AStarNode | undefined;
while (!openSet.isEmpty) {
const current = openSet.dequeue()!;
const currentHash = current.hash;
openSetMap.delete(currentHash);
if (currentHash === goalHash) {
goalNode = current;
break;
}
closedSet.add(currentHash);
for (const neighbor of graph.getNeighbors(current.node as T)) {
const neighborHash = Vector2Utils.toHash(neighbor);
if (closedSet.has(neighborHash)) {
continue;
}
const tentativeGScore = current.gCost + graph.cost(current.node as T, neighbor);
const existingNode = openSetMap.get(neighborHash);
if (existingNode) {
if (tentativeGScore < existingNode.gCost) {
const hCost = existingNode.hCost;
existingNode.updateCosts(tentativeGScore, hCost, current);
}
} else {
const hCost = graph.heuristic(neighbor, goal);
const neighborNode = this._getNode(neighbor, tentativeGScore, hCost, current);
openSet.enqueue(neighborNode);
openSetMap.set(neighborHash, neighborNode);
}
}
if (current !== goalNode) {
this._recycleNode(current);
}
}
while (!openSet.isEmpty) {
const node = openSet.dequeue()!;
if (node !== goalNode) {
this._recycleNode(node);
}
}
return { found: !!goalNode, goalNode };
}
/**
* 搜索并返回完整路径
* @param graph 图对象
* @param start 起始节点
* @param goal 目标节点
* @returns 路径数组,如果没找到则返回空数组
*/
static searchPath<T extends IVector2>(
graph: IAstarGraph<T>,
start: T,
goal: T
): T[] {
const result = this.search(graph, start, goal);
if (!result.found || !result.goalNode) {
return [];
}
return this.reconstructPathFromNode(result.goalNode, start);
}
/**
* 从目标节点重构路径
* @param goalNode 目标节点
* @param start 起始节点
* @returns 完整路径
*/
private static reconstructPathFromNode<T extends IVector2>(
goalNode: AStarNode,
start: T
): T[] {
this._tempPath.length = 0;
let current: AStarNode | null = goalNode;
const startHash = Vector2Utils.toHash(start);
while (current) {
this._tempPath.unshift(current.node as T);
const currentHash = current.hash;
const parent = current.parent;
if (currentHash !== startHash) {
this._recycleNode(current);
}
current = parent;
}
return [...this._tempPath] as T[];
}
/**
* 检查路径是否存在
* @param graph 图对象
* @param start 起始节点
* @param goal 目标节点
* @returns 是否存在路径
*/
static hasPath<T extends IVector2>(
graph: IAstarGraph<T>,
start: T,
goal: T
): boolean {
const result = this.search(graph, start, goal);
if (result.goalNode) {
this._recycleNode(result.goalNode);
}
return result.found;
}
/**
* 清理对象池(可选调用,用于内存管理)
*/
static clearPool(): void {
this._nodePool.length = 0;
this._tempPath.length = 0;
}
/**
* 获取对象池统计信息
*/
static getPoolStats(): { poolSize: number; maxPoolSize: number } {
return {
poolSize: this._nodePool.length,
maxPoolSize: 1000
};
}
}