@esengine/pathfinding
Version:
寻路算法库,支持A*、广度优先等算法,适用于Cocos Creator、Laya等游戏引擎
573 lines (563 loc) • 22.7 kB
JavaScript
/**
* @esengine/pathfinding v1.0.8
* 高性能寻路算法库 - 支持A*、广度优先等算法,适用于Cocos Creator、Laya等游戏引擎
*
* @author yhh
* @license MIT
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Pathfinding = {}));
})(this, (function (exports) { 'use strict';
var Vector2Utils = (function () {
function Vector2Utils() {
}
Vector2Utils.equals = function (a, b) {
if (a.equals) {
return a.equals(b);
}
return a.x === b.x && a.y === b.y;
};
Vector2Utils.create = function (x, y) {
return { x: x, y: y };
};
Vector2Utils.clone = function (vector) {
return { x: vector.x, y: vector.y };
};
Vector2Utils.add = function (a, b) {
return { x: a.x + b.x, y: a.y + b.y };
};
Vector2Utils.manhattanDistance = function (a, b) {
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
};
Vector2Utils.distance = function (a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
};
Vector2Utils.toHash = function (vector) {
var x = (vector.x + this.MAX_COORD) | 0;
var y = (vector.y + this.MAX_COORD) | 0;
return (x << 16) | y;
};
Vector2Utils.toKey = function (vector) {
return "".concat(vector.x, ",").concat(vector.y);
};
Vector2Utils.fromHash = function (hash) {
var x = (hash >> 16) - this.MAX_COORD;
var y = (hash & 0xFFFF) - this.MAX_COORD;
return { x: x, y: y };
};
Vector2Utils.HASH_MULTIPLIER = 73856093;
Vector2Utils.MAX_COORD = 32767;
return Vector2Utils;
}());
var PriorityQueue = (function () {
function PriorityQueue() {
this._heap = [];
this._size = 0;
}
Object.defineProperty(PriorityQueue.prototype, "size", {
get: function () {
return this._size;
},
enumerable: false,
configurable: true
});
Object.defineProperty(PriorityQueue.prototype, "isEmpty", {
get: function () {
return this._size === 0;
},
enumerable: false,
configurable: true
});
PriorityQueue.prototype.clear = function () {
this._heap.length = 0;
this._size = 0;
};
PriorityQueue.prototype.enqueue = function (item) {
this._heap[this._size] = item;
this._bubbleUp(this._size);
this._size++;
};
PriorityQueue.prototype.dequeue = function () {
if (this._size === 0) {
return undefined;
}
var result = this._heap[0];
this._size--;
if (this._size > 0) {
this._heap[0] = this._heap[this._size];
this._bubbleDown(0);
}
return result;
};
PriorityQueue.prototype.peek = function () {
return this._size > 0 ? this._heap[0] : undefined;
};
PriorityQueue.prototype._bubbleUp = function (index) {
while (index > 0) {
var parentIndex = Math.floor((index - 1) / 2);
if (this._heap[index].priority >= this._heap[parentIndex].priority) {
break;
}
this._swap(index, parentIndex);
index = parentIndex;
}
};
PriorityQueue.prototype._bubbleDown = function (index) {
while (true) {
var minIndex = index;
var leftChild = 2 * index + 1;
var rightChild = 2 * index + 2;
if (leftChild < this._size &&
this._heap[leftChild].priority < this._heap[minIndex].priority) {
minIndex = leftChild;
}
if (rightChild < this._size &&
this._heap[rightChild].priority < this._heap[minIndex].priority) {
minIndex = rightChild;
}
if (minIndex === index) {
break;
}
this._swap(index, minIndex);
index = minIndex;
}
};
PriorityQueue.prototype._swap = function (i, j) {
var temp = this._heap[i];
this._heap[i] = this._heap[j];
this._heap[j] = temp;
};
return PriorityQueue;
}());
var __spreadArray = (globalThis && globalThis.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
var AStarNode = (function () {
function AStarNode(node, gCost, hCost, parent) {
if (gCost === void 0) { gCost = 0; }
if (hCost === void 0) { hCost = 0; }
if (parent === void 0) { parent = null; }
this.priority = 0;
this.gCost = 0;
this.hCost = 0;
this.parent = null;
this.hash = 0;
this.node = node;
this.gCost = gCost;
this.hCost = hCost;
this.priority = gCost + hCost;
this.parent = parent;
this.hash = Vector2Utils.toHash(node);
}
AStarNode.prototype.updateCosts = function (gCost, hCost, parent) {
if (parent === void 0) { parent = null; }
this.gCost = gCost;
this.hCost = hCost;
this.priority = gCost + hCost;
this.parent = parent;
};
AStarNode.prototype.updateNode = function (node, gCost, hCost, parent) {
if (gCost === void 0) { gCost = 0; }
if (hCost === void 0) { hCost = 0; }
if (parent === void 0) { parent = null; }
this.node = node;
this.gCost = gCost;
this.hCost = hCost;
this.priority = gCost + hCost;
this.parent = parent;
this.hash = Vector2Utils.toHash(node);
};
AStarNode.prototype.reset = function () {
this.node = null;
this.priority = 0;
this.gCost = 0;
this.hCost = 0;
this.parent = null;
this.hash = 0;
};
return AStarNode;
}());
var AStarPathfinder = (function () {
function AStarPathfinder() {
}
AStarPathfinder._getNode = function (node, gCost, hCost, parent) {
if (gCost === void 0) { gCost = 0; }
if (hCost === void 0) { hCost = 0; }
if (parent === void 0) { parent = null; }
var astarNode = this._nodePool.pop();
if (!astarNode) {
astarNode = new AStarNode(node, gCost, hCost, parent);
}
else {
astarNode.updateNode(node, gCost, hCost, parent);
}
return astarNode;
};
AStarPathfinder._recycleNode = function (node) {
if (this._nodePool.length < 1000) {
node.reset();
this._nodePool.push(node);
}
};
AStarPathfinder.search = function (graph, start, goal) {
var openSet = new PriorityQueue();
var closedSet = new Set();
var openSetMap = new Map();
var startHash = Vector2Utils.toHash(start);
var goalHash = Vector2Utils.toHash(goal);
if (startHash === goalHash) {
return { found: true, goalNode: this._getNode(start, 0, 0) };
}
if (!graph.isNodePassable(start)) {
return { found: false, openSetNodes: [] };
}
var startNode = this._getNode(start, 0, graph.heuristic(start, goal));
openSet.enqueue(startNode);
openSetMap.set(startHash, startNode);
var goalNode;
var processedNodes = [];
while (!openSet.isEmpty) {
var current = openSet.dequeue();
var currentHash = current.hash;
openSetMap.delete(currentHash);
if (currentHash === goalHash) {
goalNode = current;
break;
}
closedSet.add(currentHash);
for (var _i = 0, _a = graph.getNeighbors(current.node); _i < _a.length; _i++) {
var neighbor = _a[_i];
var neighborHash = Vector2Utils.toHash(neighbor);
if (closedSet.has(neighborHash)) {
continue;
}
var tentativeGScore = current.gCost + graph.cost(current.node, neighbor);
var existingNode = openSetMap.get(neighborHash);
if (existingNode) {
if (tentativeGScore < existingNode.gCost) {
var hCost = existingNode.hCost;
existingNode.updateCosts(tentativeGScore, hCost, current);
}
}
else {
var hCost = graph.heuristic(neighbor, goal);
var neighborNode = this._getNode(neighbor, tentativeGScore, hCost, current);
openSet.enqueue(neighborNode);
openSetMap.set(neighborHash, neighborNode);
}
}
processedNodes.push(current);
}
var remainingNodes = [];
while (!openSet.isEmpty) {
remainingNodes.push(openSet.dequeue());
}
var allNodesToRecycle = __spreadArray(__spreadArray([], processedNodes, true), remainingNodes, true);
return { found: !!goalNode, goalNode: goalNode, openSetNodes: allNodesToRecycle };
};
AStarPathfinder.searchPath = function (graph, start, goal) {
var result = this.search(graph, start, goal);
if (!result.found || !result.goalNode) {
if (result.openSetNodes) {
for (var _i = 0, _a = result.openSetNodes; _i < _a.length; _i++) {
var node = _a[_i];
this._recycleNode(node);
}
}
return [];
}
var path = this.reconstructPathFromNode(result.goalNode);
if (result.openSetNodes) {
for (var _b = 0, _c = result.openSetNodes; _b < _c.length; _b++) {
var node = _c[_b];
this._recycleNode(node);
}
}
return path;
};
AStarPathfinder.reconstructPathFromNode = function (goalNode) {
this._tempPath.length = 0;
var current = goalNode;
while (current) {
this._tempPath.unshift(current.node);
current = current.parent;
}
return __spreadArray([], this._tempPath, true);
};
AStarPathfinder.hasPath = function (graph, start, goal) {
var result = this.search(graph, start, goal);
if (result.goalNode) {
this._recycleNode(result.goalNode);
}
if (result.openSetNodes) {
for (var _i = 0, _a = result.openSetNodes; _i < _a.length; _i++) {
var node = _a[_i];
if (node !== result.goalNode) {
this._recycleNode(node);
}
}
}
return result.found;
};
AStarPathfinder.clearPool = function () {
this._nodePool.length = 0;
this._tempPath.length = 0;
};
AStarPathfinder.getPoolStats = function () {
return {
poolSize: this._nodePool.length,
maxPoolSize: 1000
};
};
AStarPathfinder._nodePool = [];
AStarPathfinder._tempPath = [];
return AStarPathfinder;
}());
var AstarGridGraph = (function () {
function AstarGridGraph(width, height) {
this.dirs = [
Vector2Utils.create(1, 0),
Vector2Utils.create(0, -1),
Vector2Utils.create(-1, 0),
Vector2Utils.create(0, 1)
];
this.walls = [];
this.weightedNodes = [];
this.defaultWeight = 1;
this.weightedNodeWeight = 5;
this._neighbors = new Array(4);
this._wallsSet = new Set();
this._weightedNodesSet = new Set();
this._wallsDirty = true;
this._weightedNodesDirty = true;
this._width = width;
this._height = height;
}
AstarGridGraph.prototype.addWall = function (wall) {
this.walls.push(wall);
this._wallsDirty = true;
};
AstarGridGraph.prototype.addWalls = function (walls) {
var _a;
(_a = this.walls).push.apply(_a, walls);
this._wallsDirty = true;
};
AstarGridGraph.prototype.clearWalls = function () {
this.walls.length = 0;
this._wallsSet.clear();
this._wallsDirty = false;
};
AstarGridGraph.prototype.addWeightedNode = function (node) {
this.weightedNodes.push(node);
this._weightedNodesDirty = true;
};
AstarGridGraph.prototype.addWeightedNodes = function (nodes) {
var _a;
(_a = this.weightedNodes).push.apply(_a, nodes);
this._weightedNodesDirty = true;
};
AstarGridGraph.prototype.clearWeightedNodes = function () {
this.weightedNodes.length = 0;
this._weightedNodesSet.clear();
this._weightedNodesDirty = false;
};
AstarGridGraph.prototype._updateHashSets = function () {
if (this._wallsDirty) {
this._wallsSet.clear();
for (var _i = 0, _a = this.walls; _i < _a.length; _i++) {
var wall = _a[_i];
this._wallsSet.add(Vector2Utils.toHash(wall));
}
this._wallsDirty = false;
}
if (this._weightedNodesDirty) {
this._weightedNodesSet.clear();
for (var _b = 0, _c = this.weightedNodes; _b < _c.length; _b++) {
var node = _c[_b];
this._weightedNodesSet.add(Vector2Utils.toHash(node));
}
this._weightedNodesDirty = false;
}
};
AstarGridGraph.prototype.isNodeInBounds = function (node) {
return 0 <= node.x && node.x < this._width && 0 <= node.y && node.y < this._height;
};
AstarGridGraph.prototype.isNodePassable = function (node) {
this._updateHashSets();
return !this._wallsSet.has(Vector2Utils.toHash(node));
};
AstarGridGraph.prototype.search = function (start, goal) {
return AStarPathfinder.hasPath(this, start, goal);
};
AstarGridGraph.prototype.searchPath = function (start, goal) {
return AStarPathfinder.searchPath(this, start, goal);
};
AstarGridGraph.prototype.getNeighbors = function (node) {
this._neighbors.length = 0;
for (var _i = 0, _a = this.dirs; _i < _a.length; _i++) {
var dir = _a[_i];
var next = Vector2Utils.add(node, dir);
if (this.isNodeInBounds(next) && this.isNodePassable(next)) {
this._neighbors.push(next);
}
}
return this._neighbors;
};
AstarGridGraph.prototype.cost = function (from, to) {
this._updateHashSets();
return this._weightedNodesSet.has(Vector2Utils.toHash(to)) ? this.weightedNodeWeight : this.defaultWeight;
};
AstarGridGraph.prototype.heuristic = function (node, goal) {
return Vector2Utils.manhattanDistance(node, goal);
};
AstarGridGraph.prototype.getStats = function () {
this._updateHashSets();
return {
walls: this.walls.length,
weightedNodes: this.weightedNodes.length,
gridSize: "".concat(this._width, "x").concat(this._height),
wallsSetSize: this._wallsSet.size,
weightedNodesSetSize: this._weightedNodesSet.size
};
};
return AstarGridGraph;
}());
var BreadthFirstPathfinder = (function () {
function BreadthFirstPathfinder() {
}
BreadthFirstPathfinder.search = function (graph, start, goal, cameFrom) {
var frontier = [];
var visited = new Set();
var pathMap = cameFrom || new Map();
var startHash = Vector2Utils.toHash(start);
var goalHash = Vector2Utils.toHash(goal);
if (startHash === goalHash) {
return true;
}
frontier.push(start);
visited.add(startHash);
while (frontier.length > 0) {
var current = frontier.shift();
var currentHash = Vector2Utils.toHash(current);
if (currentHash === goalHash) {
return true;
}
for (var _i = 0, _a = graph.getNeighbors(current); _i < _a.length; _i++) {
var neighbor = _a[_i];
var neighborHash = Vector2Utils.toHash(neighbor);
if (visited.has(neighborHash)) {
continue;
}
visited.add(neighborHash);
pathMap.set(neighborHash, current);
frontier.push(neighbor);
}
}
return false;
};
BreadthFirstPathfinder.searchPath = function (graph, start, goal) {
var cameFrom = new Map();
if (this.search(graph, start, goal, cameFrom)) {
return this.reconstructPath(cameFrom, start, goal);
}
return [];
};
BreadthFirstPathfinder.reconstructPath = function (cameFrom, start, goal) {
var path = [];
var current = goal;
var startHash = Vector2Utils.toHash(start);
while (Vector2Utils.toHash(current) !== startHash) {
path.unshift(current);
var currentHash = Vector2Utils.toHash(current);
var parent_1 = cameFrom.get(currentHash);
if (!parent_1)
break;
current = parent_1;
}
path.unshift(start);
return path;
};
return BreadthFirstPathfinder;
}());
var UnweightedGraph = (function () {
function UnweightedGraph() {
this.edges = new Map();
}
UnweightedGraph.prototype.addEdgesForNode = function (node, neighbors) {
this.edges.set(node, neighbors);
return this;
};
UnweightedGraph.prototype.getNeighbors = function (node) {
return this.edges.get(node) || [];
};
return UnweightedGraph;
}());
var UnweightedGridGraph = (function () {
function UnweightedGridGraph(width, height, allowDiagonalSearch) {
if (allowDiagonalSearch === void 0) { allowDiagonalSearch = false; }
this.walls = [];
this._neighbors = [];
this._width = width;
this._height = height;
this._dirs = allowDiagonalSearch ? UnweightedGridGraph.COMPASS_DIRS : UnweightedGridGraph.CARDINAL_DIRS;
}
UnweightedGridGraph.prototype.isNodeInBounds = function (node) {
return 0 <= node.x && node.x < this._width && 0 <= node.y && node.y < this._height;
};
UnweightedGridGraph.prototype.isNodePassable = function (node) {
return !this.walls.find(function (wall) { return Vector2Utils.equals(wall, node); });
};
UnweightedGridGraph.prototype.getNeighbors = function (node) {
this._neighbors.length = 0;
for (var _i = 0, _a = this._dirs; _i < _a.length; _i++) {
var dir = _a[_i];
var next = Vector2Utils.add(node, dir);
if (this.isNodeInBounds(next) && this.isNodePassable(next)) {
this._neighbors.push(next);
}
}
return this._neighbors;
};
UnweightedGridGraph.prototype.searchPath = function (start, goal) {
return BreadthFirstPathfinder.searchPath(this, start, goal);
};
UnweightedGridGraph.prototype.hasPath = function (start, goal) {
return BreadthFirstPathfinder.search(this, start, goal);
};
UnweightedGridGraph.CARDINAL_DIRS = [
Vector2Utils.create(1, 0),
Vector2Utils.create(0, -1),
Vector2Utils.create(-1, 0),
Vector2Utils.create(0, 1)
];
UnweightedGridGraph.COMPASS_DIRS = [
Vector2Utils.create(1, 0),
Vector2Utils.create(1, -1),
Vector2Utils.create(0, -1),
Vector2Utils.create(-1, -1),
Vector2Utils.create(-1, 0),
Vector2Utils.create(-1, 1),
Vector2Utils.create(0, 1),
Vector2Utils.create(1, 1),
];
return UnweightedGridGraph;
}());
exports.AStarPathfinder = AStarPathfinder;
exports.AstarGridGraph = AstarGridGraph;
exports.BreadthFirstPathfinder = BreadthFirstPathfinder;
exports.PriorityQueue = PriorityQueue;
exports.UnweightedGraph = UnweightedGraph;
exports.UnweightedGridGraph = UnweightedGridGraph;
exports.Vector2Utils = Vector2Utils;
}));
//# sourceMappingURL=pathfinding.js.map