From fa2660e00b5eb7e7b0a49f701c3717d413dcf1ab Mon Sep 17 00:00:00 2001 From: James Brown Date: Fri, 29 Sep 2017 14:30:44 -0600 Subject: [PATCH 1/5] Added support to grid / node for weights per node. Added usage to AStarFinder to use the new weights. Weights above 1 (the default) make a node less desirable (cost more expensive) to use but not unwalkable. Weights less than 1 make a node more desirable (cost less expensive) to use. --- src/core/Grid.js | 514 +++++++++++++++++++------------------ src/core/Node.js | 61 +++-- src/finders/AStarFinder.js | 254 +++++++++--------- 3 files changed, 430 insertions(+), 399 deletions(-) diff --git a/src/core/Grid.js b/src/core/Grid.js index 3f45d84f..13b4ae7d 100644 --- a/src/core/Grid.js +++ b/src/core/Grid.js @@ -1,245 +1,269 @@ -var Node = require('./Node'); -var DiagonalMovement = require('./DiagonalMovement'); - -/** - * The Grid class, which serves as the encapsulation of the layout of the nodes. - * @constructor - * @param {number|Array>} width_or_matrix Number of columns of the grid, or matrix - * @param {number} height Number of rows of the grid. - * @param {Array>} [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(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; - - /** - * 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>} [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 Node(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) { - this.nodes[y][x].walkable = walkable; -}; - - -/** - * 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 {Node} 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 === DiagonalMovement.Never) { - return neighbors; - } - - if (diagonalMovement === DiagonalMovement.OnlyWhenNoObstacles) { - d0 = s3 && s0; - d1 = s0 && s1; - d2 = s1 && s2; - d3 = s2 && s3; - } else if (diagonalMovement === DiagonalMovement.IfAtMostOneObstacle) { - d0 = s3 || s0; - d1 = s0 || s1; - d2 = s1 || s2; - d3 = s2 || s3; - } else if (diagonalMovement === 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, - - width = this.width, - height = this.height, - thisNodes = this.nodes, - - newGrid = new Grid(width, height), - newNodes = new Array(height); - - for (i = 0; i < height; ++i) { - newNodes[i] = new Array(width); - for (j = 0; j < width; ++j) { - newNodes[i][j] = new Node(j, i, thisNodes[i][j].walkable); - } - } - - newGrid.nodes = newNodes; - - return newGrid; -}; - -module.exports = Grid; +var Node = require('./Node'); +var DiagonalMovement = require('./DiagonalMovement'); + +/** + * The Grid class, which serves as the encapsulation of the layout of the nodes. + * @constructor + * @param {number|Array>} width_or_matrix Number of columns of the grid, or matrix + * @param {number} height Number of rows of the grid. + * @param {Array>} [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(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; + + /** + * 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>} [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 Node(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) { + this.nodes[y][x].walkable = walkable; +}; + + +/** + * Set the weight multiplier of a given node + * NOTE: if it is unset, we will return 1 since this will do nothing + * @param {number} x - The x coordinate of the node. + * @param {number} y - The y coordinate of the node. + * @param {number} weight - Weight multiplier of the node + */ +Grid.prototype.setWeightAt = function(x, y, weight) { + if (this.isInside(x, y)) + this.nodes[y][x].weight = weight; +}; + +/** + * Determine the weight mulitpler of a given node. + * (Also returns 1 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 {number} - The weight multiplier of the node. + */ +Grid.prototype.getWeightAt = function(x, y) { + return this.isInside(x, y) ? this.nodes[y][x].weight : 1; +}; + + +/** + * 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 {Node} 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 === DiagonalMovement.Never) { + return neighbors; + } + + if (diagonalMovement === DiagonalMovement.OnlyWhenNoObstacles) { + d0 = s3 && s0; + d1 = s0 && s1; + d2 = s1 && s2; + d3 = s2 && s3; + } else if (diagonalMovement === DiagonalMovement.IfAtMostOneObstacle) { + d0 = s3 || s0; + d1 = s0 || s1; + d2 = s1 || s2; + d3 = s2 || s3; + } else if (diagonalMovement === 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, + + width = this.width, + height = this.height, + thisNodes = this.nodes, + + newGrid = new Grid(width, height), + newNodes = new Array(height); + + for (i = 0; i < height; ++i) { + newNodes[i] = new Array(width); + for (j = 0; j < width; ++j) { + newNodes[i][j] = new Node(j, i, thisNodes[i][j].walkable, thisNodes[i][j].weight); + } + } + + newGrid.nodes = newNodes; + + return newGrid; +}; + +module.exports = Grid; diff --git a/src/core/Node.js b/src/core/Node.js index 8df8ba50..c4a32085 100644 --- a/src/core/Node.js +++ b/src/core/Node.js @@ -1,28 +1,33 @@ -/** - * 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} x - The x coordinate of the node on the grid. - * @param {number} y - The y coordinate of the node on the grid. - * @param {boolean} [walkable] - Whether this node is walkable. - */ -function Node(x, y, walkable) { - /** - * 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); -} - -module.exports = Node; +/** + * 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} x - The x coordinate of the node on the grid. + * @param {number} y - The y coordinate of the node on the grid. + * @param {boolean} [walkable] - Whether this node is walkable. + */ +function Node(x, y, walkable, weight) { + /** + * 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); + /** + * weight multiplier of this node. + * @type number (defaults to 1) + */ + this.weight = (weight === undefined ? 1 : weight); +} + +module.exports = Node; diff --git a/src/finders/AStarFinder.js b/src/finders/AStarFinder.js index d7e63430..4d5d3d4c 100644 --- a/src/finders/AStarFinder.js +++ b/src/finders/AStarFinder.js @@ -1,126 +1,128 @@ -var Heap = require('heap'); -var Util = require('../core/Util'); -var Heuristic = require('../core/Heuristic'); -var DiagonalMovement = require('../core/DiagonalMovement'); - -/** - * 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.manhattan; - this.weight = opt.weight || 1; - this.diagonalMovement = opt.diagonalMovement; - - if (!this.diagonalMovement) { - if (!this.allowDiagonal) { - this.diagonalMovement = DiagonalMovement.Never; - } else { - if (this.dontCrossCorners) { - this.diagonalMovement = DiagonalMovement.OnlyWhenNoObstacles; - } else { - this.diagonalMovement = DiagonalMovement.IfAtMostOneObstacle; - } - } - } - - // When diagonal movement is allowed the manhattan heuristic is not - //admissible. It should be octile instead - if (this.diagonalMovement === DiagonalMovement.Never) { - this.heuristic = opt.heuristic || Heuristic.manhattan; - } else { - this.heuristic = opt.heuristic || Heuristic.octile; - } -} - -/** - * Find and return the the path. - * @return {Array>} The path, including both start and - * end positions. - */ -AStarFinder.prototype.findPath = function(startX, startY, endX, endY, grid) { - 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, ng; - - // 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()) { - // 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.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; - - // get the distance between current node and the neighbor - // and calculate the next g score - ng = node.g + ((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 { - // 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 []; -}; - -module.exports = AStarFinder; +var Heap = require('heap'); +var Util = require('../core/Util'); +var Heuristic = require('../core/Heuristic'); +var DiagonalMovement = require('../core/DiagonalMovement'); + +/** + * 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.manhattan; + this.weight = opt.weight || 1; + this.diagonalMovement = opt.diagonalMovement; + + if (!this.diagonalMovement) { + if (!this.allowDiagonal) { + this.diagonalMovement = DiagonalMovement.Never; + } else { + if (this.dontCrossCorners) { + this.diagonalMovement = DiagonalMovement.OnlyWhenNoObstacles; + } else { + this.diagonalMovement = DiagonalMovement.IfAtMostOneObstacle; + } + } + } + + // When diagonal movement is allowed the manhattan heuristic is not + //admissible. It should be octile instead + if (this.diagonalMovement === DiagonalMovement.Never) { + this.heuristic = opt.heuristic || Heuristic.manhattan; + } else { + this.heuristic = opt.heuristic || Heuristic.octile; + } +} + +/** + * Find and return the the path. + * @return {Array>} The path, including both start and + * end positions. + */ +AStarFinder.prototype.findPath = function(startX, startY, endX, endY, grid) { + 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, ng; + + // 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()) { + // 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.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; + + // get the distance between current node and the neighbor + // and calculate the next g score + ng = node.g + ((x - node.x === 0 || y - node.y === 0) ? 1 : SQRT2); + + ng *= neighbor.weight; + + // 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 { + // 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 []; +}; + +module.exports = AStarFinder; From 8bfc4763bb3701347d3196ea766c914eeabd1e7d Mon Sep 17 00:00:00 2001 From: James Brown Date: Fri, 29 Sep 2017 15:41:47 -0600 Subject: [PATCH 2/5] Added weight support to BiAStarFinder and JumpPointFinderBase as well. --- src/finders/BiAStarFinder.js | 2 ++ src/finders/JumpPointFinderBase.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/finders/BiAStarFinder.js b/src/finders/BiAStarFinder.js index 46e03952..81771c7c 100644 --- a/src/finders/BiAStarFinder.js +++ b/src/finders/BiAStarFinder.js @@ -107,6 +107,8 @@ BiAStarFinder.prototype.findPath = function(startX, startY, endX, endY, grid) { // and calculate the next g score ng = node.g + ((x - node.x === 0 || y - node.y === 0) ? 1 : SQRT2); + ng *= neighbor.weight; + // 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) { diff --git a/src/finders/JumpPointFinderBase.js b/src/finders/JumpPointFinderBase.js index e6375879..29bf9bad 100644 --- a/src/finders/JumpPointFinderBase.js +++ b/src/finders/JumpPointFinderBase.js @@ -94,6 +94,8 @@ JumpPointFinderBase.prototype._identifySuccessors = function(node) { d = Heuristic.octile(abs(jx - x), abs(jy - y)); ng = node.g + d; // next `g` value + ng *= neighbor.weight; + if (!jumpNode.opened || ng < jumpNode.g) { jumpNode.g = ng; jumpNode.h = jumpNode.h || heuristic(abs(jx - endX), abs(jy - endY)); From 55bfad14655e1f1a6956ec550f307cdde6e2f2fc Mon Sep 17 00:00:00 2001 From: James Brown Date: Fri, 29 Sep 2017 15:56:01 -0600 Subject: [PATCH 3/5] Added documentation on weights to the readme under advanced usage --- README.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 13b8a640..2aec08db 100644 --- a/README.md +++ b/README.md @@ -60,13 +60,13 @@ Basic Usage To build a grid-map of width 5 and height 3: ```javascript -var grid = new PF.Grid(5, 3); +var grid = new PF.Grid(5, 3); ``` By default, all the nodes in the grid will be able to be walked through. To set whether a node at a given coordinate is walkable or not, use the `setWalkableAt` method. -For example, to set the node at (0, 1) to be un-walkable, where 0 is the x coordinate (from left to right), and +For example, to set the node at (0, 1) to be un-walkable, where 0 is the x coordinate (from left to right), and 1 is the y coordinate (from up to down): ```javascript @@ -205,6 +205,19 @@ you may use `PF.Util.expandPath`. var newPath = PF.Util.expandPath(path); ``` +There is an optional weight that can be assigned to each node in the grid. This +functions as a multiplier to the next g value calculated for each neighbor. By +setting the weight for a given node higher than 1 you make it less likely that +the finder will examine that node. By setting the weight lower than 1 you make +it more likely the finder will examine that node. The default value is 1 which will not impact the next g calculation at all. + +There are two calls, a getter and a setter to examine or set the weight for a given node. +These are accessed via the grid object. + +```javascript +var w = grid.getWeightAt(x,y); // returns the current weight value for the node at x,y +grid.setWeightAt(x,y, weight); // sets the weight for the node at x,y +``` Development ------------ @@ -219,9 +232,9 @@ Layout: |-- benchmark # benchmarks `-- visual # visualization -Make sure you have `node.js` installed, then use `npm` to install the dependencies: +Make sure you have `node.js` installed, then use `npm` to install the dependencies: - npm install -d + npm install -d The build system uses gulp, so make sure you have it installed: From 3da80ec60fb3aee96a9d4414c881bffde1df18f4 Mon Sep 17 00:00:00 2001 From: James Brown Date: Thu, 14 May 2020 17:18:57 -0600 Subject: [PATCH 4/5] modified user-guide docs to use proper path for embedded images --- docs/user-guide/diagonal-movement.md | 12 ++++++------ docs/user-guide/getting-started.md | 4 ++-- docs/user-guide/obstacles.md | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/user-guide/diagonal-movement.md b/docs/user-guide/diagonal-movement.md index 830fe2e1..ef1e8ae3 100644 --- a/docs/user-guide/diagonal-movement.md +++ b/docs/user-guide/diagonal-movement.md @@ -12,7 +12,7 @@ var finder = new PF.AStarFinder({ See that the path is straight now: -![Screenshot](user-guide/images/DiagonalMovementDisabled.png) +![Screenshot](images/DiagonalMovementDisabled.png) The `diagonalMovement` option can take any of the following values: @@ -25,28 +25,28 @@ To understand them consider the following four simple maps labelled A, B, C and D. A has no obstacles for diagonal movement from green to orange cell, B and C have one obstacle and D has two obstacles. -![Screenshot](user-guide/images/DiagonalMaps.png) +![Screenshot](images/DiagonalMaps.png) ## Always With this option PathFinding.js will always find a diagonal path, irrespective of the obstacles when moving diagonally. -![Screenshot](user-guide/images/AllMapsWithAPath.png) +![Screenshot](images/AllMapsWithAPath.png) ## Never With this option PathFinding.js will only find straight paths and will never find any diagonal paths. -![Screenshot](user-guide/images/AllMapsWithStraightPaths.png) +![Screenshot](images/AllMapsWithStraightPaths.png) ## IfAtMostOneObstacle With this option PathFinding.js will find diagonal paths only if there is at most one obstacle for the diagonal path. -![Screenshot](user-guide/images/DiagonalPathsForAtMostOneObstacle.png) +![Screenshot](images/DiagonalPathsForAtMostOneObstacle.png) ## OnlyWhenNoObstacles With this option PathFinding.js will find diagonal paths only if there are no obstacles for the diagonal path. -![Screenshot](user-guide/images/DiagonalPathsForOnlyWhenNoObstacles.png) +![Screenshot](images/DiagonalPathsForOnlyWhenNoObstacles.png) diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index 930401c0..cd8f794e 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -23,7 +23,7 @@ var grid = new PF.Grid(5, 7); ``` This will create a grid which is walkable all over. -![Screenshot](user-guide/images/5x7EmptyGrid.png) +![Screenshot](images/5x7EmptyGrid.png) In this grid, the green cell at the top left is [0, 0] and the orange cell at the bottom right is [4, 6]. @@ -49,4 +49,4 @@ This will return the following path: Which when plotted on the grid looks like: -![Screenshot](user-guide/images/5x7GridWithPath.png) \ No newline at end of file +![Screenshot](images/5x7GridWithPath.png) \ No newline at end of file diff --git a/docs/user-guide/obstacles.md b/docs/user-guide/obstacles.md index 901daeb1..6bff72ea 100644 --- a/docs/user-guide/obstacles.md +++ b/docs/user-guide/obstacles.md @@ -26,7 +26,7 @@ grid.setWalkableAt(2, 1, false); After setting the obstacles the grid should look like this. -![Screenshot](user-guide/images/5x7GridWithObstacles.png) +![Screenshot](images/5x7GridWithObstacles.png) Let us find a path now. @@ -37,7 +37,7 @@ var path = finder.findPath(0, 0, 4, 6, grid); PathFinding.js will find the following path: -![Screenshot](user-guide/images/5x7GridWithObstaclesAndPath.png) +![Screenshot](images/5x7GridWithObstaclesAndPath.png) Notice how the path moves diagonally where it can, thus making it shorter. This may not be always desirable and you may want to create a path without any From e5bc6588fff6f477327432030d3fdc1ecdf1f033 Mon Sep 17 00:00:00 2001 From: James Brown Date: Thu, 14 May 2020 17:25:47 -0600 Subject: [PATCH 5/5] added myself to authors / contributors (for the path weights addition) --- docs/contributor-guide/authors.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/contributor-guide/authors.md b/docs/contributor-guide/authors.md index 55edc771..e8fb1b86 100644 --- a/docs/contributor-guide/authors.md +++ b/docs/contributor-guide/authors.md @@ -5,6 +5,7 @@ This is the alphabetically sorted list of contributors to PathFinding.js: Anders Riutta
Chris Khoo
Gerjo
+James Brown
Juan Pablo Canepa
Mat Gadd
Murilo Pereira