UNPKG

peng-pathfinding

Version:

Comprehensive pathfinding library for grid based games

1,363 lines (1,293 loc) 143 kB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pengPathfinding = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ module.exports = require('./lib/heap'); },{"./lib/heap":2}],2:[function(require,module,exports){ // Generated by CoffeeScript 1.8.0 (function() { var Heap, defaultCmp, floor, heapify, heappop, heappush, heappushpop, heapreplace, insort, min, nlargest, nsmallest, updateItem, _siftdown, _siftup; floor = Math.floor, min = Math.min; /* Default comparison function to be used */ defaultCmp = function(x, y) { if (x < y) { return -1; } if (x > y) { return 1; } return 0; }; /* Insert item x in list a, and keep it sorted assuming a is sorted. If x is already in a, insert it to the right of the rightmost x. Optional args lo (default 0) and hi (default a.length) bound the slice of a to be searched. */ insort = function(a, x, lo, hi, cmp) { var mid; if (lo == null) { lo = 0; } if (cmp == null) { cmp = defaultCmp; } if (lo < 0) { throw new Error('lo must be non-negative'); } if (hi == null) { hi = a.length; } while (lo < hi) { mid = floor((lo + hi) / 2); if (cmp(x, a[mid]) < 0) { hi = mid; } else { lo = mid + 1; } } return ([].splice.apply(a, [lo, lo - lo].concat(x)), x); }; /* Push item onto heap, maintaining the heap invariant. */ heappush = function(array, item, cmp) { if (cmp == null) { cmp = defaultCmp; } array.push(item); return _siftdown(array, 0, array.length - 1, cmp); }; /* Pop the smallest item off the heap, maintaining the heap invariant. */ heappop = function(array, cmp) { var lastelt, returnitem; if (cmp == null) { cmp = defaultCmp; } lastelt = array.pop(); if (array.length) { returnitem = array[0]; array[0] = lastelt; _siftup(array, 0, cmp); } else { returnitem = lastelt; } return returnitem; }; /* Pop and return the current smallest value, and add the new item. This is more efficient than heappop() followed by heappush(), and can be more appropriate when using a fixed size heap. Note that the value returned may be larger than item! That constrains reasonable use of this routine unless written as part of a conditional replacement: if item > array[0] item = heapreplace(array, item) */ heapreplace = function(array, item, cmp) { var returnitem; if (cmp == null) { cmp = defaultCmp; } returnitem = array[0]; array[0] = item; _siftup(array, 0, cmp); return returnitem; }; /* Fast version of a heappush followed by a heappop. */ heappushpop = function(array, item, cmp) { var _ref; if (cmp == null) { cmp = defaultCmp; } if (array.length && cmp(array[0], item) < 0) { _ref = [array[0], item], item = _ref[0], array[0] = _ref[1]; _siftup(array, 0, cmp); } return item; }; /* Transform list into a heap, in-place, in O(array.length) time. */ heapify = function(array, cmp) { var i, _i, _j, _len, _ref, _ref1, _results, _results1; if (cmp == null) { cmp = defaultCmp; } _ref1 = (function() { _results1 = []; for (var _j = 0, _ref = floor(array.length / 2); 0 <= _ref ? _j < _ref : _j > _ref; 0 <= _ref ? _j++ : _j--){ _results1.push(_j); } return _results1; }).apply(this).reverse(); _results = []; for (_i = 0, _len = _ref1.length; _i < _len; _i++) { i = _ref1[_i]; _results.push(_siftup(array, i, cmp)); } return _results; }; /* Update the position of the given item in the heap. This function should be called every time the item is being modified. */ updateItem = function(array, item, cmp) { var pos; if (cmp == null) { cmp = defaultCmp; } pos = array.indexOf(item); if (pos === -1) { return; } _siftdown(array, 0, pos, cmp); return _siftup(array, pos, cmp); }; /* Find the n largest elements in a dataset. */ nlargest = function(array, n, cmp) { var elem, result, _i, _len, _ref; if (cmp == null) { cmp = defaultCmp; } result = array.slice(0, n); if (!result.length) { return result; } heapify(result, cmp); _ref = array.slice(n); for (_i = 0, _len = _ref.length; _i < _len; _i++) { elem = _ref[_i]; heappushpop(result, elem, cmp); } return result.sort(cmp).reverse(); }; /* Find the n smallest elements in a dataset. */ nsmallest = function(array, n, cmp) { var elem, i, los, result, _i, _j, _len, _ref, _ref1, _results; if (cmp == null) { cmp = defaultCmp; } if (n * 10 <= array.length) { result = array.slice(0, n).sort(cmp); if (!result.length) { return result; } los = result[result.length - 1]; _ref = array.slice(n); for (_i = 0, _len = _ref.length; _i < _len; _i++) { elem = _ref[_i]; if (cmp(elem, los) < 0) { insort(result, elem, 0, null, cmp); result.pop(); los = result[result.length - 1]; } } return result; } heapify(array, cmp); _results = []; for (i = _j = 0, _ref1 = min(n, array.length); 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) { _results.push(heappop(array, cmp)); } return _results; }; _siftdown = function(array, startpos, pos, cmp) { var newitem, parent, parentpos; if (cmp == null) { cmp = defaultCmp; } newitem = array[pos]; while (pos > startpos) { parentpos = (pos - 1) >> 1; parent = array[parentpos]; if (cmp(newitem, parent) < 0) { array[pos] = parent; pos = parentpos; continue; } break; } return array[pos] = newitem; }; _siftup = function(array, pos, cmp) { var childpos, endpos, newitem, rightpos, startpos; if (cmp == null) { cmp = defaultCmp; } endpos = array.length; startpos = pos; newitem = array[pos]; childpos = 2 * pos + 1; while (childpos < endpos) { rightpos = childpos + 1; if (rightpos < endpos && !(cmp(array[childpos], array[rightpos]) < 0)) { childpos = rightpos; } array[pos] = array[childpos]; pos = childpos; childpos = 2 * pos + 1; } array[pos] = newitem; return _siftdown(array, startpos, pos, cmp); }; Heap = (function() { Heap.push = heappush; Heap.pop = heappop; Heap.replace = heapreplace; Heap.pushpop = heappushpop; Heap.heapify = heapify; Heap.updateItem = updateItem; Heap.nlargest = nlargest; Heap.nsmallest = nsmallest; function Heap(cmp) { this.cmp = cmp != null ? cmp : defaultCmp; this.nodes = []; } Heap.prototype.push = function(x) { return heappush(this.nodes, x, this.cmp); }; Heap.prototype.pop = function() { return heappop(this.nodes, this.cmp); }; Heap.prototype.peek = function() { return this.nodes[0]; }; Heap.prototype.contains = function(x) { return this.nodes.indexOf(x) !== -1; }; Heap.prototype.replace = function(x) { return heapreplace(this.nodes, x, this.cmp); }; Heap.prototype.pushpop = function(x) { return heappushpop(this.nodes, x, this.cmp); }; Heap.prototype.heapify = function() { return heapify(this.nodes, this.cmp); }; Heap.prototype.updateItem = function(x) { return updateItem(this.nodes, x, this.cmp); }; Heap.prototype.clear = function() { return this.nodes = []; }; Heap.prototype.empty = function() { return this.nodes.length === 0; }; Heap.prototype.size = function() { return this.nodes.length; }; Heap.prototype.clone = function() { var heap; heap = new Heap(); heap.nodes = this.nodes.slice(0); return heap; }; Heap.prototype.toArray = function() { return this.nodes.slice(0); }; Heap.prototype.insert = Heap.prototype.push; Heap.prototype.top = Heap.prototype.peek; Heap.prototype.front = Heap.prototype.peek; Heap.prototype.has = Heap.prototype.contains; Heap.prototype.copy = Heap.prototype.clone; return Heap; })(); (function(root, factory) { if (typeof define === 'function' && define.amd) { return define([], factory); } else if (typeof exports === 'object') { return module.exports = factory(); } else { return root.Heap = factory(); } })(this, function() { return Heap; }); }).call(this); },{}],3:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var AStarFinder_1 = require("../finder/AStarFinder"); var BestFirstFinder_1 = require("../finder/BestFirstFinder"); //代价函数 var Heuristic_1 = require("../core/Heuristic"); //斜线运动方式 var ClassDef_1 = require("../core/ClassDef"); var DynamicFinder = /** @class */ (function () { /** * 动态寻路者 * @constructor */ function DynamicFinder() { var optA = { allowDiagonal: true, dontCrossCorners: true, diagonalMovement: ClassDef_1.DiagonalMovement.IfAtMostOneObstacle, heuristic: Heuristic_1.default.octile, weight: 1 }; var optB = { allowDiagonal: true, dontCrossCorners: true, diagonalMovement: ClassDef_1.DiagonalMovement.IfAtMostOneObstacle, weight: 1 }; this.aFinder = new AStarFinder_1.default(optA); this.bFinder = new BestFirstFinder_1.default(optB); } /** * 同步寻路方法 */ DynamicFinder.prototype.findPathSync = function (startX, startY, endX, endY, grid) { return this.aFinder.findPath(startX, startY, endX, endY, grid); }; ; return DynamicFinder; }()); exports.default = DynamicFinder; },{"../core/ClassDef":5,"../core/Heuristic":7,"../finder/AStarFinder":9,"../finder/BestFirstFinder":10}],4:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * A node in grid. * This class holds some basic information about a node and custom * attributes may be added, depending on the algorithms' needs. * @constructor * @param {number} g - 当前节点到起始点的代价 * @param {number} h - 当前节点到终点的估价 * @param {number} f - 代价总和 * @param {boolean} opened - 节点是否已经开启 * @param {boolean} closed - 节点是否被关闭了 * @param {number} x - 网格上节点的x坐标。. * @param {number} y - 网格上节点的y坐标。. * @param {number} r - 代价系数 * @param {boolean} [walkable] - 这个节点是否可以行走. */ var Cell = /** @class */ (function () { function Cell(x, y, walkable, r) { /** * The x coordinate of the node on the grid. * @type number */ this.x = x; /** * The y coordinate of the node on the grid. * @type number */ this.y = y; /** * Whether this node can be walked through. * @type boolean */ this.walkable = (walkable === undefined ? true : walkable); /** * 默认的系数为1,这里指示达到目标点的难易程度 */ this.r = (r === undefined ? 1 : r); } return Cell; }()); exports.default = Cell; },{}],5:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); //对角运动 var DiagonalMovement; (function (DiagonalMovement) { //总是 DiagonalMovement[DiagonalMovement["Always"] = 1] = "Always"; //绝不 DiagonalMovement[DiagonalMovement["Never"] = 2] = "Never"; //最多只有一个障碍时 DiagonalMovement[DiagonalMovement["IfAtMostOneObstacle"] = 3] = "IfAtMostOneObstacle"; //仅在没有任何障碍时 DiagonalMovement[DiagonalMovement["OnlyWhenNoObstacles"] = 4] = "OnlyWhenNoObstacles"; })(DiagonalMovement = exports.DiagonalMovement || (exports.DiagonalMovement = {})); ; },{}],6:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var Cell_1 = require("./Cell"); var ClassDef_1 = require("./ClassDef"); /**代价网格 */ var Grid = /** @class */ (function () { /** * The Grid class, which serves as the encapsulation of the layout of the nodes. * @constructor * @param {number|Array<Array<(number|boolean)>>} width_or_matrix Number of columns of the grid, or matrix * @param {number} height Number of rows of the grid. * @param {Array<Array<(number|boolean)>>} [matrix] - A 0-1 matrix * representing the walkable status of the nodes(0 or false for walkable). * If the matrix is not supplied, all the nodes will be walkable. */ function Grid(x, y, size, width_or_matrix, height, matrix) { var width; if (typeof width_or_matrix !== 'object') { width = width_or_matrix; } else { height = width_or_matrix.length; width = width_or_matrix[0].length; matrix = width_or_matrix; } /** * The number of columns of the grid. * @type number */ this.width = width; /** * The number of rows of the grid. * @type number */ this.height = height; this.x = x; this.y = y; this.size = size; /** * A 2D array of nodes. */ this.nodes = this.buildNodes(width, height, matrix); } /** * Build and return the nodes. * @private * @param {number} width * @param {number} height * @param {Array<Array<number|boolean>>} [matrix] - A 0-1 matrix representing * the walkable status of the nodes. * @see Grid */ Grid.prototype.buildNodes = function (width, height, matrix) { var i, j, nodes = new Array(height); for (i = 0; i < height; ++i) { nodes[i] = new Array(width); for (j = 0; j < width; ++j) { nodes[i][j] = new Cell_1.default(j, i); } } if (matrix === undefined) { return nodes; } if (matrix.length !== height || matrix[0].length !== width) { throw new Error('Matrix size does not fit'); } for (i = 0; i < height; ++i) { for (j = 0; j < width; ++j) { if (matrix[i][j]) { // 0, false, null will be walkable // while others will be un-walkable nodes[i][j].walkable = false; } } } return nodes; }; ; Grid.prototype.getNodeAt = function (x, y) { return this.nodes[y][x]; }; ; /** * Determine whether the node at the given position is walkable. * (Also returns false if the position is outside the grid.) * @param {number} x - The x coordinate of the node. * @param {number} y - The y coordinate of the node. * @return {boolean} - The walkability of the node. */ Grid.prototype.isWalkableAt = function (x, y) { return this.isInside(x, y) && this.nodes[y][x].walkable; }; ; /** * Determine whether the position is inside the grid. * XXX: `grid.isInside(x, y)` is wierd to read. * It should be `(x, y) is inside grid`, but I failed to find a better * name for this method. * @param {number} x * @param {number} y * @return {boolean} */ Grid.prototype.isInside = function (x, y) { return (x >= 0 && x < this.width) && (y >= 0 && y < this.height); }; ; /** * Set whether the node on the given position is walkable. * NOTE: throws exception if the coordinate is not inside the grid. * @param {number} x - The x coordinate of the node. * @param {number} y - The y coordinate of the node. * @param {boolean} walkable - Whether the position is walkable. */ Grid.prototype.setWalkableAt = function (x, y, walkable) { if (this.isInside(x, y)) this.nodes[y][x].walkable = walkable; }; ; /** * 设置代价系数,计算代价时将会乘以该值 * @param x * @param y * @param r */ Grid.prototype.setCostRate = function (x, y, r) { if (this.isInside(x, y)) this.nodes[y][x].r = r; }; /** * 增加代价系数,计算代价时将会乘以该值 * @param x * @param y * @param r */ Grid.prototype.addCostRate = function (x, y, r) { if (this.isInside(x, y)) this.nodes[y][x].r += r; }; /** * Get the neighbors of the given node. * * offsets diagonalOffsets: * +---+---+---+ +---+---+---+ * | | 0 | | | 0 | | 1 | * +---+---+---+ +---+---+---+ * | 3 | | 1 | | | | | * +---+---+---+ +---+---+---+ * | | 2 | | | 3 | | 2 | * +---+---+---+ +---+---+---+ * * When allowDiagonal is true, if offsets[i] is valid, then * diagonalOffsets[i] and * diagonalOffsets[(i + 1) % 4] is valid. * @param {Cell} node * @param {DiagonalMovement} diagonalMovement */ Grid.prototype.getNeighbors = function (node, diagonalMovement) { var x = node.x, y = node.y, neighbors = [], s0 = false, d0 = false, s1 = false, d1 = false, s2 = false, d2 = false, s3 = false, d3 = false, nodes = this.nodes; // ↑ if (this.isWalkableAt(x, y - 1)) { neighbors.push(nodes[y - 1][x]); s0 = true; } // → if (this.isWalkableAt(x + 1, y)) { neighbors.push(nodes[y][x + 1]); s1 = true; } // ↓ if (this.isWalkableAt(x, y + 1)) { neighbors.push(nodes[y + 1][x]); s2 = true; } // ← if (this.isWalkableAt(x - 1, y)) { neighbors.push(nodes[y][x - 1]); s3 = true; } if (diagonalMovement === ClassDef_1.DiagonalMovement.Never) { return neighbors; } if (diagonalMovement === ClassDef_1.DiagonalMovement.OnlyWhenNoObstacles) { d0 = s3 && s0; d1 = s0 && s1; d2 = s1 && s2; d3 = s2 && s3; } else if (diagonalMovement === ClassDef_1.DiagonalMovement.IfAtMostOneObstacle) { d0 = s3 || s0; d1 = s0 || s1; d2 = s1 || s2; d3 = s2 || s3; } else if (diagonalMovement === ClassDef_1.DiagonalMovement.Always) { d0 = true; d1 = true; d2 = true; d3 = true; } else { throw new Error('Incorrect value of diagonalMovement'); } // ↖ if (d0 && this.isWalkableAt(x - 1, y - 1)) { neighbors.push(nodes[y - 1][x - 1]); } // ↗ if (d1 && this.isWalkableAt(x + 1, y - 1)) { neighbors.push(nodes[y - 1][x + 1]); } // ↘ if (d2 && this.isWalkableAt(x + 1, y + 1)) { neighbors.push(nodes[y + 1][x + 1]); } // ↙ if (d3 && this.isWalkableAt(x - 1, y + 1)) { neighbors.push(nodes[y + 1][x - 1]); } return neighbors; }; ; /** * Get a clone of this grid. * @return {Grid} Cloned grid. */ Grid.prototype.clone = function () { var i, j, node, x = this.x, y = this.y, width = this.width, height = this.height, size = this.size, thisNodes = this.nodes, newGrid = new Grid(x, y, size, width, height), newNodes = new Array(height); for (i = 0; i < height; ++i) { newNodes[i] = new Array(width); for (j = 0; j < width; ++j) { node = thisNodes[i][j]; if (!node) continue; newNodes[i][j] = new Cell_1.default(j, i, node.walkable, node.r); } } newGrid.nodes = newNodes; return newGrid; }; ; /**合并两个代价网格成为一个新的代价网格 */ Grid.prototype.merge = function (grid) { var create; var origin; var target; //填充的长度 var copyX, copyY; var mergeX, mergeY; var startX, startY; var endX, endY; //构建新的网格 var newX, newY, newW, newH, newSize; //原网格覆盖的宽度和高度 var originW = this.width * this.size; var originH = this.height * this.size; //原网格最远端的坐标 var originX = this.x + originW; var originY = this.y + originH; //目标网格覆盖的宽度和高度 var targetW = grid.width * grid.size; var targetH = grid.height * grid.size; //目标网格最远端的坐标 var targetX = grid.x + targetW; var targetY = grid.y + targetH; //确定新网格的大小和位置 newSize = this.size; newX = Math.min(this.x, grid.x); newY = Math.min(this.y, grid.y); newW = (Math.max(originX, targetX) - newX) / newSize; newH = (Math.max(originY, targetY) - newY) / newSize; create = new Grid(newX, newY, newSize, newW, newH); origin = grid; target = this; mergeX = origin.width; mergeY = origin.height; copyX = target.width * (newSize / target.size); copyY = target.height * (newSize / target.size); startX = (origin.x - newX) / newSize; startY = (origin.y - newY) / newSize; endX = (target.x - newX) / newSize; endY = (target.y - newY) / newSize; //合并网格 for (var i = 0; i < mergeY; i++) { for (var j = 0; j < mergeX; j++) { var tmp = origin.nodes[i][j]; tmp.x = j + startX; tmp.y = i + startY; create.nodes[i + startY][j + startX] = tmp; } } for (var i = 0; i < copyY; i++) { for (var j = 0; j < copyX; j++) { var tmp = target.nodes[i][j]; tmp.x = j + endX; tmp.y = i + endY; create.nodes[i + endY][j + endX] = tmp; } } return create; }; /**合并网格数组返回新的网格 */ Grid.Merge = function (arr) { if (!arr || arr.length == 0) return null; if (arr.length == 1) return arr[0].clone(); var create; //构建新的网格 var x, y, w, h, size; var grid; grid = arr[0]; size = grid.size; x = grid.x; y = grid.y; w = grid.x + grid.width * grid.size; h = grid.y + grid.height * grid.size; for (var i = 1; i < arr.length; i++) { grid = arr[i]; x = Math.min(x, grid.x); y = Math.min(y, grid.y); w = Math.max(w, grid.x + grid.width * grid.size); h = Math.max(h, grid.y + grid.height * grid.size); } w = (w - x) / size; h = (h - y) / size; create = new Grid(x, y, size, w, h); for (var i = 0; i < arr.length; i++) { grid = arr[i]; x = (grid.x - create.x) / size; y = (grid.y - create.y) / size; w = grid.width * (size / grid.size); h = grid.height * (size / grid.size); for (var i_1 = 0; i_1 < h; i_1++) { for (var j = 0; j < w; j++) { var tmp = grid.nodes[i_1][j]; tmp.x = j + x; tmp.y = i_1 + y; create.nodes[i_1 + y][j + x] = tmp; } } } return create; }; return Grid; }()); exports.default = Grid; },{"./Cell":4,"./ClassDef":5}],7:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * 代价函数 * @namespace pfg.Heuristic * @description A collection of heuristic functions. */ var Heuristic = /** @class */ (function () { function Heuristic() { } /** * 曼哈顿距离,指的是直角折线的距离,即 X + Y * Manhattan distance. * @param {number} dx - Difference in x. * @param {number} dy - Difference in y. * @return {number} dx + dy */ Heuristic.manhattan = function (dx, dy) { return dx + dy; }; ; /** * 欧氏距离,也就是直线距离 * Euclidean distance. * @param {number} dx - Difference in x. * @param {number} dy - Difference in y. * @return {number} sqrt(dx * dx + dy * dy) */ Heuristic.euclidean = function (dx, dy) { return Math.sqrt(dx * dx + dy * dy); }; ; /** * 八进制距离,斜线距离的特殊形式,允许存在折线的直线距离 * Octile distance. * @param {number} dx - Difference in x. * @param {number} dy - Difference in y. * @return {number} (dx < dy) ? F * dx + dy : F * dy + dx; */ Heuristic.octile = function (dx, dy) { //返归根号2 var F = Math.SQRT2 - 1; return (dx < dy) ? F * dx + dy : F * dy + dx; }; ; /** * 切比雪夫距离,斜线距离的特殊形式,即X或者Y中取最大值 * Chebyshev distance. * @param {number} dx - Difference in x. * @param {number} dy - Difference in y. * @return {number} max(dx, dy) */ Heuristic.chebyshev = function (dx, dy) { return Math.max(dx, dy); }; return Heuristic; }()); exports.default = Heuristic; ; },{}],8:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var Util = /** @class */ (function () { function Util() { } /** * 根据父记录进行回溯跟踪并返回路径。 * (包括开始节点和结束节点) * Backtrace according to the parent records and return the path. * (including both start and end nodes) * @param {Node} node End node * @return {Array<Array<number>>} the path */ Util.backtrace = function (node) { var path = [[node.x, node.y]]; while (node.parent) { node = node.parent; path.push([node.x, node.y]); } //翻转路径数组 return path.reverse(); }; /** * 从开始和结束节点回溯跟踪,并返回路径。 * (包括开始节点和结束节点) * Backtrace from start and end node, and return the path. * (including both start and end nodes) * @param {Node} * @param {Node} */ Util.biBacktrace = function (nodeA, nodeB) { var pathA = this.backtrace(nodeA), pathB = this.backtrace(nodeB); return pathA.concat(pathB.reverse()); }; /** * 计算路径的长度。 * Compute the length of the path. * @param {Array<Array<number>>} path The path * @return {number} The length of the path */ Util.pathLength = function (path) { var i, sum = 0, a, b, dx, dy; for (i = 1; i < path.length; ++i) { a = path[i - 1]; b = path[i]; dx = a[0] - b[0]; dy = a[1] - b[1]; sum += Math.sqrt(dx * dx + dy * dy); } return sum; }; /** * 给定开始和结束坐标,根据Bresenham的算法返回由这些坐标构成的直线上的所有坐标。 * Given the start and end coordinates, return all the coordinates lying * on the line formed by these coordinates, based on Bresenham's algorithm. * http://en.wikipedia.org/wiki/Bresenham's_line_algorithm#Simplification * @param {number} x0 Start x coordinate * @param {number} y0 Start y coordinate * @param {number} x1 End x coordinate * @param {number} y1 End y coordinate * @return {Array<Array<number>>} The coordinates on the line */ Util.interpolate = function (x0, y0, x1, y1) { var abs = Math.abs, line = [], sx, sy, dx, dy, err, e2; dx = abs(x1 - x0); dy = abs(y1 - y0); sx = (x0 < x1) ? 1 : -1; sy = (y0 < y1) ? 1 : -1; err = dx - dy; while (true) { line.push([x0, y0]); if (x0 === x1 && y0 === y1) { break; } e2 = 2 * err; if (e2 > -dy) { err = err - dy; x0 = x0 + sx; } if (e2 < dx) { err = err + dx; y0 = y0 + sy; } } return line; }; /** * 给定一个压缩路径,返回一个新路径,该路径中包含所有插值段。 * Given a compressed path, return a new path that has all the segments * in it interpolated. * @param {Array<Array<number>>} path The path * @return {Array<Array<number>>} expanded path */ Util.expandPath = function (path) { var expanded = [], len = path.length, coord0, coord1, interpolated, interpolatedLen, i, j; if (len < 2) { return expanded; } for (i = 0; i < len - 1; ++i) { coord0 = path[i]; coord1 = path[i + 1]; interpolated = this.interpolate(coord0[0], coord0[1], coord1[0], coord1[1]); interpolatedLen = interpolated.length; for (j = 0; j < interpolatedLen - 1; ++j) { expanded.push(interpolated[j]); } } expanded.push(path[len - 1]); return expanded; }; /** * 平滑给出的路径。 * 原始路径不会被修改;将返回一条新路径。 * Smoothen the give path. * The original path will not be modified; a new path will be returned. * @param {pfg.Grid} grid * @param {Array<Array<number>>} path The path */ Util.smoothenPath = function (grid, path) { var len = path.length, x0 = path[0][0], // path start x y0 = path[0][1], // path start y x1 = path[len - 1][0], // path end x y1 = path[len - 1][1], // path end y sx, sy, // current start coordinate ex, ey, // current end coordinate newPath, i, j, coord, line, testCoord, blocked; sx = x0; sy = y0; newPath = [[sx, sy]]; for (i = 2; i < len; ++i) { coord = path[i]; ex = coord[0]; ey = coord[1]; line = this.interpolate(sx, sy, ex, ey); blocked = false; for (j = 1; j < line.length; ++j) { testCoord = line[j]; if (!grid.isWalkableAt(testCoord[0], testCoord[1])) { blocked = true; break; } } if (blocked) { var lastValidCoord = path[i - 1]; newPath.push(lastValidCoord); sx = lastValidCoord[0]; sy = lastValidCoord[1]; } } newPath.push([x1, y1]); return newPath; }; /** * 压缩路径,删除冗余节点,不改变形状,不修改原始路径,将返回一条新路径。 * Compress a path, remove redundant nodes without altering the shape * The original path is not modified * @param {Array<Array<number>>} path The path * @return {Array<Array<number>>} The compressed path */ Util.compressPath = function (path) { // nothing to compress if (path.length < 3) { return path; } var compressed = [], sx = path[0][0], // start x sy = path[0][1], // start y px = path[1][0], // second point x py = path[1][1], // second point y dx = px - sx, // direction between the two points dy = py - sy, // direction between the two points lx, ly, ldx, ldy, sq, i; // normalize the direction sq = Math.sqrt(dx * dx + dy * dy); dx /= sq; dy /= sq; // start the new path compressed.push([sx, sy]); for (i = 2; i < path.length; i++) { // store the last point lx = px; ly = py; // store the last direction ldx = dx; ldy = dy; // next point px = path[i][0]; py = path[i][1]; // next direction dx = px - lx; dy = py - ly; // normalize sq = Math.sqrt(dx * dx + dy * dy); dx /= sq; dy /= sq; // if the direction has changed, store the point if (dx !== ldx || dy !== ldy) { compressed.push([lx, ly]); } } // store the last point compressed.push([px, py]); return compressed; }; return Util; }()); exports.default = Util; },{}],9:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); //堆 var Heap = require("heap"); //工具 var Util_1 = require("../core/Util"); //代价函数 var Heuristic_1 = require("../core/Heuristic"); //斜线运动方式 var ClassDef_1 = require("../core/ClassDef"); var AStarFinder = /** @class */ (function () { /** * A* path-finder. Based upon https://github.com/bgrins/javascript-astar * @constructor * @param {Object} opt * @param {boolean} opt.allowDiagonal Whether diagonal movement is allowed. * Deprecated, use diagonalMovement instead. * @param {boolean} opt.dontCrossCorners Disallow diagonal movement touching * block corners. Deprecated, use diagonalMovement instead. * @param {DiagonalMovement} opt.diagonalMovement Allowed diagonal movement. * @param {function} opt.heuristic Heuristic function to estimate the distance * (defaults to manhattan). * @param {number} opt.weight Weight to apply to the heuristic to allow for * suboptimal paths, in order to speed up the search. */ function AStarFinder(opt) { opt = opt || {}; this.allowDiagonal = opt.allowDiagonal; this.dontCrossCorners = opt.dontCrossCorners; this.heuristic = opt.heuristic || Heuristic_1.default.manhattan; this.weight = opt.weight || 1; this.diagonalMovement = opt.diagonalMovement; if (!this.diagonalMovement) { if (!this.allowDiagonal) { this.diagonalMovement = ClassDef_1.DiagonalMovement.Never; } else { if (this.dontCrossCorners) { this.diagonalMovement = ClassDef_1.DiagonalMovement.OnlyWhenNoObstacles; } else { this.diagonalMovement = ClassDef_1.DiagonalMovement.IfAtMostOneObstacle; } } } //是否运行使用对角线移动 // When diagonal movement is allowed the manhattan heuristic is not //admissible. It should be octile instead if (this.diagonalMovement === ClassDef_1.DiagonalMovement.Never) { this.heuristic = opt.heuristic || Heuristic_1.default.manhattan; } else { this.heuristic = opt.heuristic || Heuristic_1.default.octile; } } /** * 寻找并返回一条路径 * Find and return the the path. * @param {number} startX - 起点X坐标 * @param {number} startY - 起点Y坐标 * @param {number} endX - 终点X坐标 * @param {number} endY - 终点Y坐标 * @param {Grid} grid - 网格地图 * @return {Array<Array<number>>} The path, including both start and * end positions. */ AStarFinder.prototype.findPath = function (startX, startY, endX, endY, grid) { startX = Math.round((startX - grid.x) / grid.size); startY = Math.round((startY - grid.y) / grid.size); endX = Math.round((endX - grid.x) / grid.size); endY = Math.round((endY - grid.y) / grid.size); if (startX == -1) startX = 0; if (startX == grid.width) startX = grid.width - 1; if (startY == -1) startY = 0; if (startY == grid.height) startY = grid.height - 1; if (endX == -1) endX = 0; if (endX == grid.width) endX = grid.width - 1; if (endY == -1) endY = 0; if (endY == grid.height) endY = grid.height - 1; //let width = grid.x + grid.width * grid.size; //let height = grid.y + grid.height * grid.size; //排除超出范围的寻路 if (startX < 0 || startX >= grid.width || startY < 0 || startY >= grid.height || endX < 0 || endX >= grid.width || endY < 0 || endY >= grid.height) return; //创建堆,交换条件为节点的代价总和 var openList = new Heap(function (nodeA, nodeB) { return nodeA.f - nodeB.f; }), //创建开始节点 startNode = grid.getNodeAt(startX, startY), //创建结束节点 endNode = grid.getNodeAt(endX, endY), //设置代价函数 heuristic = this.heuristic, //是否运行斜线运动 diagonalMovement = this.diagonalMovement, //宽度 weight = this.weight, //取绝对值函数 abs = Math.abs, SQRT2 = Math.SQRT2, //临时变量 node, neighbors, neighbor, i, l, x, y, r, ng; //设置起始节点的代价为0 // set the `g` and `f` value of the start node to be 0 startNode.g = 0; startNode.f = 0; // push the start node into the open list //放入初始节点 openList.push(startNode); //设置初始节点已经开启 startNode.opened = true; //直到无法找到可以开启的格子 // while the open list is not empty while (!openList.empty()) { //弹出具有最小代价总和 f 值的节点位置。 // pop the position of node which has the minimum `f` value. node = openList.pop(); node.closed = true; //如果到达了结束节点 // if reached the end position, construct the path and return it if (node === endNode) { //根据父节点回溯跟踪返回路径 return Util_1.default.backtrace(endNode); } //获取附近的节点并遍历 // get neigbours of the current node neighbors = grid.getNeighbors(node, diagonalMovement); for (i = 0, l = neighbors.length; i < l; ++i) { neighbor = neighbors[i]; //如果节点是关闭的则跳过 if (neighbor.closed) { continue; } //相邻节点的坐标 x = neighbor.x; y = neighbor.y; //相邻节点的代价比率 r = neighbor.r; //得到当前节点与邻近节点之间的距离,计算下一个代价 g // get the distance between current node and the neighbor // and calculate the next g score //console.log("[P]"+x+","+y+"[系数]"+r); ng = node.g + r * ((x - node.x === 0 || y - node.y === 0) ? 1 : SQRT2); // 检查邻居是否还没有被检查,或者是否可以从当前节点以更小的成本到达 // 即为寻找代价最小的节点 // check if the neighbor has not been inspected yet, or // can be reached with smaller cost from the current node if (!neighbor.opened || ng < neighbor.g) { //为临近节点设置到起点的代价 neighbor.g = ng; //为临近节点设置到终点的估价 neighbor.h = neighbor.h || weight * heuristic(abs(x - endX), abs(y - endY)); //为临近节点设置新的代价总和 neighbor.f = neighbor.g + neighbor.h; //设置临近节点的父节点为当前节点 neighbor.parent = node; //临近节点是否已经开启 if (!neighbor.opened) { //开启列表中放入临近节点 openList.push(neighbor); //设置临近节点为开启 neighbor.opened = true; } else { //发现可以用更少的成本到达临近节点。 //因为它的 f 值已经更新了,所以我们不得不更新其在开启列表中的位置 // the neighbor can be reached with smaller cost. // Since its f value has been updated, we have to // update its position in the open list openList.updateItem(neighbor); } } } // end for each neighbor } // end while not open list empty // fail to find the path return []; }; ; return AStarFinder; }()); exports.default = AStarFinder; },{"../core/ClassDef":5,"../core/Heuristic":7,"../core/Util":8,"heap":1}],10:[function(require,module,exports){ "use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); var AStarFinder_1 = require("./AStarFinder"); var BestFirstFinder = /** @class */ (function (_super) { __extends(BestFirstFinder, _super); /** * Best-First-Search path-finder. * @constructor * @extends AStarFinder * @param {Object} opt 配置信息 * @param {boolean} opt.allowDiagonal Whether diagonal movement is allowed. * Deprecated, use diagonalMovement instead. * @param {boolean} opt.dontCrossCorners Disallow diagonal movement touching * block corners. Deprecated, use diagonalMovement instead. * @param {DiagonalMovement} opt.diagonalMovement Allowed diagonal movement. * @param {function} opt.heuristic Heuristic function to estimate the distance * (defaults to manhattan). */ function BestFirstFinder(opt) { var _this = _super.call(this, opt) || this; var orig = _this.heuristic; //放大原本的代价函数 _this.heuristic = function (dx, dy) { return orig(dx, dy) * 1000000; }; return _this; } return BestFirstFinder; }(AStarFinder_1.default)); exports.default = BestFirstFinder; },{"./AStarFinder":9}],11:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var Grid_1 = require("./core/Grid"); exports.Grid = Grid_1.default; var ClassDef_1 = require("./core/ClassDef"); exports.DiagonalMovement = ClassDef_1.DiagonalMovement; var DynamicFinder_1 = require("./app/DynamicFinder"); exports.DynamicFinder = DynamicFinder_1.default; var Heuristic_1 = require("./core/Heuristic"); exports.Heuristic = Heuristic_1.default; var AStarFinder_1 = require("./finder/AStarFinder"); exports.AStarFinder = AStarFinder_1.default; var BestFirstFinder_1 = require("./finder/BestFirstFinder"); exports.BestFirstFinder = BestFirstFinder_1.default; var Cell_1 = require("./core/Cell"); exports.Cell = Cell_1.default; var Util_1 = require("./core/Util"); exports.Util = Util_1.default; var Heap = require("heap"); exports.Heap = Heap; function version() { return "pfg@0.5.0"; } exports.version = version; },{"./app/DynamicFinder":3,"./core/Cell":4,"./core/ClassDef":5,"./core/Grid":6,"./core/Heuristic":7,"./core/Util":8,"./finder/AStarFinder":9,"./finder/BestFirstFinder":10,"heap":1}]},{},[11])(11) }); //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJub2RlX21vZHVsZXMvaGVhcC9pbmRleC5qcyIsIm5vZGVfbW9kdWxlcy9oZWFwL2xpYi9oZWFwLmpzIiwic3JjL2FwcC9EeW5hbWljRmluZGVyLnRzIiwic3JjL2NvcmUvQ2VsbC50cyIsInNyYy9jb3JlL0NsYXNzRGVmLnRzIiwic3JjL2NvcmUvR3JpZC50cyIsInNyYy9jb3JlL0hldXJpc3RpYy50cyIsInNyYy9jb3JlL1V0aWwudHMiLCJzcmMvZmluZGVyL0FTdGFyRmluZGVyLnRzIiwic3JjL2ZpbmRlci9CZXN0Rmlyc3RGaW5kZXIudHMiLCJzcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBOztBQ0RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNB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