mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-07-23 02:25:52 -04:00
609 lines
22 KiB
JavaScript
609 lines
22 KiB
JavaScript
/**
|
|
GDevelop - Pathfinding Behavior Extension
|
|
Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
|
|
*/
|
|
|
|
/**
|
|
* PathfindingRuntimeBehavior represents a behavior allowing objects to
|
|
* follow a path computed to avoid obstacles.
|
|
*
|
|
* @class PathfindingRuntimeBehavior
|
|
* @constructor
|
|
*/
|
|
gdjs.PathfindingRuntimeBehavior = function(runtimeScene, behaviorData, owner)
|
|
{
|
|
gdjs.RuntimeBehavior.call(this, runtimeScene, behaviorData, owner);
|
|
|
|
if (this._path === undefined)
|
|
this._path = []; //The path computed and followed by the object (Array of arrays containing x and y position)
|
|
else
|
|
this._path.length = 0;
|
|
|
|
//Behavior configuration:
|
|
this._allowDiagonals = behaviorData.allowDiagonals;
|
|
this._acceleration = behaviorData.acceleration;
|
|
this._maxSpeed = behaviorData.maxSpeed;
|
|
this._angularMaxSpeed = behaviorData.angularMaxSpeed;
|
|
this._rotateObject = behaviorData.rotateObject;
|
|
this._angleOffset = behaviorData.angleOffset;
|
|
this._cellWidth = behaviorData.cellWidth;
|
|
this._cellHeight = behaviorData.cellHeight;
|
|
this._extraBorder = behaviorData.extraBorder;
|
|
|
|
//Attributes used for traveling on the path:
|
|
this._pathFound = false;
|
|
this._speed = 0;
|
|
this._angularSpeed = 0;
|
|
this._timeOnSegment = 0;
|
|
this._totalSegmentTime = 0;
|
|
this._currentSegment = 0;
|
|
this._reachedEnd = false;
|
|
|
|
this._manager = gdjs.PathfindingObstaclesManager.getManager(runtimeScene);
|
|
|
|
if ( this._searchContext === undefined )
|
|
this._searchContext = new gdjs.PathfindingRuntimeBehavior.SearchContext();
|
|
};
|
|
|
|
gdjs.PathfindingRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
|
|
gdjs.PathfindingRuntimeBehavior.thisIsARuntimeBehaviorConstructor = "PathfindingBehavior::PathfindingBehavior";
|
|
|
|
gdjs.PathfindingRuntimeBehavior.prototype.setCellWidth = function(width) {
|
|
this._cellWidth = width;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getCellWidth = function() {
|
|
return this._cellWidth;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.setCellHeight = function(height) {
|
|
this._cellHeight = height;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getCellHeight = function() {
|
|
return this._cellHeight;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.setAcceleration = function(acceleration) {
|
|
this._acceleration = acceleration;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getAcceleration = function() {
|
|
return this._acceleration;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.setMaxSpeed = function(maxSpeed) {
|
|
this._maxSpeed = maxSpeed;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getMaxSpeed = function() {
|
|
return this._maxSpeed;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.setSpeed = function(speed) {
|
|
this._speed = speed;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getSpeed = function() {
|
|
return this._speed;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.setAngularMaxSpeed = function(angularMaxSpeed) {
|
|
this._angularMaxSpeed = angularMaxSpeed;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getAngularMaxSpeed = function() {
|
|
return this._angularMaxSpeed;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.setAngleOffset = function(angleOffset) {
|
|
this._angleOffset = angleOffset;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getAngleOffset = function() {
|
|
return this._angleOffset;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.setExtraBorder = function(extraBorder) {
|
|
this._extraBorder = extraBorder;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getExtraBorder = function() {
|
|
return this._extraBorder;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.allowDiagonals = function(allow) {
|
|
this._allowDiagonals = allow;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.diagonalsAllowed = function() {
|
|
return this._allowDiagonals;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.setRotateObject = function(allow) {
|
|
this._rotateObject = allow;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.isObjectRotated = function() {
|
|
return this._rotateObject;
|
|
};
|
|
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getNodeX = function(index) {
|
|
if (index<this._path.length) return this._path[index][0];
|
|
return 0;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getNodeY = function(index) {
|
|
if (index<this._path.length) return this._path[index][1];
|
|
return 0;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getNextNodeIndex = function() {
|
|
if (this._currentSegment+1 < this._path.length)
|
|
return this._currentSegment+1;
|
|
else
|
|
return this._path.length-1;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getNodeCount = function() {
|
|
return this._path.length;
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getNextNodeX = function() {
|
|
if ( this._path.length === 0 ) return 0;
|
|
|
|
if (this._currentSegment+1 < this._path.length)
|
|
return this._path[this._currentSegment+1][0];
|
|
else
|
|
return this._path.back()[0];
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getNextNodeY = function() {
|
|
if ( this._path.length === 0 ) return 0;
|
|
|
|
if (this._currentSegment+1 < this._path.length)
|
|
return this._path[this._currentSegment+1][1];
|
|
else
|
|
return this._path.back()[1];
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getLastNodeX = function() {
|
|
if ( this._path.length < 2 ) return 0;
|
|
|
|
if (this._currentSegment < this._path.length-1)
|
|
return this._path[this._currentSegment][0];
|
|
else
|
|
return this._path[this._path.length-1][0];
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getLastNodeY = function() {
|
|
if ( this._path.length < 2 ) return 0;
|
|
|
|
if (this._currentSegment < this._path.length-1)
|
|
return this._path[this._currentSegment][1];
|
|
else
|
|
return this._path[this._path.length-1][1];
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getDestinationX = function() {
|
|
if ( this._path.length === 0 ) return 0;
|
|
return this._path.back()[0];
|
|
};
|
|
gdjs.PathfindingRuntimeBehavior.prototype.getDestinationY = function() {
|
|
if ( this._path.length === 0 ) return 0;
|
|
return this._path.back()[1];
|
|
};
|
|
|
|
/**
|
|
* Return true if the latest call to moveTo succeeded.
|
|
* @method pathFound
|
|
*/
|
|
gdjs.PathfindingRuntimeBehavior.prototype.pathFound = function() {
|
|
return this._pathFound;
|
|
};
|
|
|
|
/**
|
|
* Return true if the object reached its destination.
|
|
* @method destinationReached
|
|
*/
|
|
gdjs.PathfindingRuntimeBehavior.prototype.destinationReached = function() {
|
|
return this._reachedEnd;
|
|
};
|
|
|
|
/**
|
|
* Compute and move on the path to the specified destination.
|
|
* @method moveTo
|
|
*/
|
|
gdjs.PathfindingRuntimeBehavior.prototype.moveTo = function(runtimeScene, x, y)
|
|
{
|
|
var owner = this.owner;
|
|
|
|
//First be sure that there is a path to compute.
|
|
var targetCellX = Math.round(x/this._cellWidth);
|
|
var targetCellY = Math.round(y/this._cellHeight);
|
|
var startCellX = Math.round(owner.getX()/this._cellWidth);
|
|
var startCellY = Math.round(owner.getY()/this._cellHeight);
|
|
if ( startCellX == targetCellX && startCellY == targetCellY ) {
|
|
while (this._path.length < 2) this._path.unshift([0, 0]);
|
|
this._path.length = 2;
|
|
|
|
this._path.push([owner.getX(), owner.getY()]);
|
|
this._path.push([x, y]);
|
|
this._enterSegment(0);
|
|
this._pathFound = true;
|
|
|
|
return;
|
|
}
|
|
|
|
//Start searching for a path
|
|
this._searchContext.allowDiagonals(this._allowDiagonals);
|
|
this._searchContext.setObstacles(this._manager);
|
|
this._searchContext.setCellSize(this._cellWidth, this._cellHeight);
|
|
this._searchContext.setStartPosition(owner.getX(), owner.getY());
|
|
this._searchContext.setObjectSize(owner.getX()-owner.getDrawableX()+this._extraBorder,
|
|
owner.getY()-owner.getDrawableY()+this._extraBorder,
|
|
owner.getWidth()-(owner.getX()-owner.getDrawableX())+this._extraBorder,
|
|
owner.getHeight()-(owner.getY()-owner.getDrawableY())+this._extraBorder);
|
|
|
|
if (this._searchContext.computePathTo(x, y))
|
|
{
|
|
//Path found: memorize it
|
|
var node = this._searchContext.getFinalNode();
|
|
var finalPathLength = 0;
|
|
while (node) {
|
|
if ( finalPathLength === this._path.length)
|
|
this._path.push([0, 0]);
|
|
|
|
this._path[finalPathLength][0] = node.pos[0]*this._cellWidth;
|
|
this._path[finalPathLength][1] = node.pos[1]*this._cellHeight;
|
|
|
|
node = node.parent;
|
|
finalPathLength++;
|
|
}
|
|
this._path.length = finalPathLength;
|
|
|
|
this._path.reverse();
|
|
this._path[0][0] = owner.getX();
|
|
this._path[0][1] = owner.getY();
|
|
|
|
this._enterSegment(0);
|
|
this._pathFound = true;
|
|
return;
|
|
}
|
|
|
|
//Not path found
|
|
this._pathFound = false;
|
|
};
|
|
|
|
gdjs.PathfindingRuntimeBehavior.prototype._enterSegment = function(segmentNumber)
|
|
{
|
|
if (this._path.length === 0) return;
|
|
|
|
this._currentSegment = segmentNumber;
|
|
if (this._currentSegment < this._path.length-1) {
|
|
var pathX = this._path[this._currentSegment + 1][0] - this._path[this._currentSegment][0];
|
|
var pathY = this._path[this._currentSegment + 1][1] - this._path[this._currentSegment][1];
|
|
this._totalSegmentTime = Math.sqrt(pathX*pathX+pathY*pathY);
|
|
this._timeOnSegment = 0;
|
|
this._reachedEnd = false;
|
|
}
|
|
else {
|
|
this._reachedEnd = true;
|
|
this._speed = 0;
|
|
}
|
|
};
|
|
|
|
gdjs.PathfindingRuntimeBehavior.prototype.doStepPreEvents = function(runtimeScene)
|
|
{
|
|
if (this._path.length === 0 || this._reachedEnd) return;
|
|
|
|
//Update the speed of the object
|
|
var timeDelta = this.owner.getElapsedTime(runtimeScene)/1000;
|
|
this._speed += this._acceleration*timeDelta;
|
|
if ( this._speed > this._maxSpeed ) this._speed = this._maxSpeed;
|
|
this._angularSpeed = this._angularMaxSpeed;
|
|
|
|
//Update the time on the segment and change segment if needed
|
|
this._timeOnSegment += this._speed*timeDelta;
|
|
if (this._timeOnSegment >= this._totalSegmentTime && this._currentSegment < this._path.length)
|
|
this._enterSegment(this._currentSegment + 1);
|
|
|
|
//Position object on the segment and update its angle
|
|
var newPos = [0, 0];
|
|
var pathAngle = this.owner.getAngle();
|
|
if ( this._currentSegment < this._path.length-1 ) {
|
|
newPos[0] = gdjs.evtTools.common.lerp(this._path[this._currentSegment][0],
|
|
this._path[this._currentSegment + 1][0], this._timeOnSegment / this._totalSegmentTime);
|
|
newPos[1] = gdjs.evtTools.common.lerp(this._path[this._currentSegment][1],
|
|
this._path[this._currentSegment + 1][1], this._timeOnSegment / this._totalSegmentTime);
|
|
|
|
pathAngle = gdjs.toDegrees(Math.atan2(this._path[this._currentSegment+1][1] - this._path[this._currentSegment][1],
|
|
this._path[this._currentSegment+1][0] - this._path[this._currentSegment][0]))+this._angleOffset;
|
|
}
|
|
else
|
|
newPos = this._path[this._path.length-1];
|
|
|
|
this.owner.setX(newPos[0]);
|
|
this.owner.setY(newPos[1]);
|
|
|
|
if ( this._rotateObject ) {
|
|
this.owner.rotateTowardAngle(pathAngle, this._angularSpeed, runtimeScene);
|
|
}
|
|
};
|
|
|
|
gdjs.PathfindingRuntimeBehavior.prototype.doStepPostEvents = function(runtimeScene) {
|
|
//Scene change is not supported
|
|
/*
|
|
if ( parentScene != &scene ) //Parent scene has changed
|
|
{
|
|
parentScene = &scene;
|
|
sceneManager = parentScene ? &ScenePathfindingObstaclesManager::managers[&scene] : null;
|
|
floorPlatform = null;
|
|
}
|
|
*/
|
|
};
|
|
|
|
/**
|
|
* Internal tool class representing a node when looking for a path
|
|
* @private
|
|
* @namespace gdjs.PathfindingRuntimeBehavior
|
|
* @class Node
|
|
*/
|
|
gdjs.PathfindingRuntimeBehavior.Node = function(xPos, yPos)
|
|
{
|
|
if ( this.pos === undefined)
|
|
this.pos = [xPos, yPos];
|
|
else {
|
|
this.pos[0] = xPos;
|
|
this.pos[1] = yPos;
|
|
}
|
|
|
|
this.cost = 0;
|
|
this.smallestCost = -1;
|
|
this.estimateCost = -1;
|
|
this.parent = null;
|
|
this.open = true;
|
|
};
|
|
|
|
/**
|
|
* Compute the euclidean distance between two positions.
|
|
* @private
|
|
* @namespace gdjs.PathfindingRuntimeBehavior
|
|
* @method euclideanDistance
|
|
*/
|
|
gdjs.PathfindingRuntimeBehavior.euclideanDistance = function(a, b)
|
|
{
|
|
return Math.sqrt((a[0]-b[0])*(a[0]-b[0])+(a[1]-b[1])*(a[1]-b[1]));
|
|
};
|
|
|
|
/**
|
|
* Compute the taxi distance between two positions.
|
|
* @private
|
|
* @namespace gdjs.PathfindingRuntimeBehavior
|
|
* @method manhattanDistance
|
|
*/
|
|
gdjs.PathfindingRuntimeBehavior.manhattanDistance = function(a, b)
|
|
{
|
|
return Math.abs(a[0]-b[0])+Math.abs(a[1]-b[1]);
|
|
};
|
|
|
|
/**
|
|
* \brief Internal tool class containing the structures used by A* and members functions related
|
|
* to them.
|
|
*/
|
|
gdjs.PathfindingRuntimeBehavior.SearchContext = function()
|
|
{
|
|
this._obstacles = null;
|
|
this._finalNode = null;
|
|
this._destination = [0,0];
|
|
this._start = [0,0];
|
|
this._startX = 0;
|
|
this._startY = 0;
|
|
this._allowDiagonals = true;
|
|
this._maxComplexityFactor = 50;
|
|
this._cellWidth = 20;
|
|
this._cellHeight = 20;
|
|
this._leftBorder = 0;
|
|
this._rightBorder = 0;
|
|
this._topBorder = 0;
|
|
this._bottomBorder = 0;
|
|
this._distanceFunction = gdjs.PathfindingRuntimeBehavior.euclideanDistance;
|
|
this._allNodes = []; //An array of array. Nodes are indexed by their x position, and then by their y position.
|
|
this._openNodes = []; //An array of nodes sorted by their estimate cost (First node = Lower estimate cost).
|
|
|
|
this._closeObstacles = []; //Used by getNodes to temporarily store obstacles near a position.
|
|
this._nodeCache = []; //Old nodes constructed in a previous search are stored here to avoid temporary objects (see _freeAllNodes method).
|
|
};
|
|
|
|
gdjs.PathfindingRuntimeBehavior.SearchContext.prototype.setObstacles = function(obstacles)
|
|
{
|
|
this._obstacles = obstacles;
|
|
return this;
|
|
};
|
|
|
|
gdjs.PathfindingRuntimeBehavior.SearchContext.prototype.getFinalNode = function()
|
|
{
|
|
return this._finalNode;
|
|
};
|
|
|
|
gdjs.PathfindingRuntimeBehavior.SearchContext.prototype.allowDiagonals = function(allowDiagonals)
|
|
{
|
|
this._allowDiagonals = allowDiagonals;
|
|
this._distanceFunction = allowDiagonals ? gdjs.PathfindingRuntimeBehavior.euclideanDistance :
|
|
gdjs.PathfindingRuntimeBehavior.manhattanDistance;
|
|
return this;
|
|
};
|
|
|
|
gdjs.PathfindingRuntimeBehavior.SearchContext.prototype.setStartPosition = function(x, y)
|
|
{
|
|
this._startX = x;
|
|
this._startY = y;
|
|
return this;
|
|
};
|
|
|
|
gdjs.PathfindingRuntimeBehavior.SearchContext.prototype.setObjectSize = function(leftBorder, topBorder, rightBorder, bottomBorder)
|
|
{
|
|
this._leftBorder = leftBorder;
|
|
this._rightBorder = rightBorder;
|
|
this._topBorder = topBorder;
|
|
this._bottomBorder = bottomBorder;
|
|
return this;
|
|
};
|
|
|
|
gdjs.PathfindingRuntimeBehavior.SearchContext.prototype.setCellSize = function(cellWidth, cellHeight)
|
|
{
|
|
this._cellWidth = cellWidth;
|
|
this._cellHeight = cellHeight;
|
|
return this;
|
|
};
|
|
|
|
gdjs.PathfindingRuntimeBehavior.SearchContext.prototype.computePathTo = function(targetX, targetY)
|
|
{
|
|
if (this._obstacles === null) {
|
|
console.log("You tried to compute a path without specifying the obstacles");
|
|
return;
|
|
}
|
|
|
|
this._destination[0] = Math.round(targetX/this._cellWidth);
|
|
this._destination[1] = Math.round(targetY/this._cellHeight);
|
|
this._start[0] = Math.round(this._startX/this._cellWidth);
|
|
this._start[1] = Math.round(this._startY/this._cellHeight);
|
|
|
|
//Initialize the algorithm
|
|
this._freeAllNodes();
|
|
var startNode = this._getNode(this._start[0], this._start[1]);
|
|
startNode.smallestCost = 0;
|
|
startNode.estimateCost = 0 + this._distanceFunction(this._start, this._destination);
|
|
this._openNodes.length = 0;
|
|
this._openNodes.push(startNode);
|
|
|
|
//A* algorithm main loop
|
|
var iterationCount = 0;
|
|
var maxIterationCount = startNode.estimateCost*this._maxComplexityFactor;
|
|
while (this._openNodes.length !== 0)
|
|
{
|
|
if (iterationCount++ > maxIterationCount) return false; //Make sure we do not search forever.
|
|
|
|
var n = this._openNodes.shift(); //Get the most promising node...
|
|
n.open = false; //...and flag it as explored
|
|
|
|
//Check if we reached destination?
|
|
if ( n.pos[0] == this._destination[0] && n.pos[1] == this._destination[1] )
|
|
{
|
|
this._finalNode = n;
|
|
return true;
|
|
}
|
|
|
|
//No, so add neighbors to the nodes to explore.
|
|
this._insertNeighbors(n);
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
gdjs.PathfindingRuntimeBehavior.SearchContext.prototype._freeAllNodes = function()
|
|
{
|
|
if ( this._nodeCache.length <= 32000 ) {
|
|
for( var i in this._allNodes ) {
|
|
if ( this._allNodes.hasOwnProperty(i) ) {
|
|
var nodeArray = this._allNodes[i];
|
|
for( var j in nodeArray ) {
|
|
if ( nodeArray.hasOwnProperty(j) ) {
|
|
this._nodeCache.push(nodeArray[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this._allNodes = {};
|
|
};
|
|
|
|
/**
|
|
* Insert the neighbors of the current node in the open list
|
|
* (Only if they are not closed, and if the cost is better than the already existing smallest cost).
|
|
*/
|
|
gdjs.PathfindingRuntimeBehavior.SearchContext.prototype._insertNeighbors = function(currentNode)
|
|
{
|
|
this._addOrUpdateNode(currentNode.pos[0]+1, currentNode.pos[1], currentNode, 1);
|
|
this._addOrUpdateNode(currentNode.pos[0]-1, currentNode.pos[1], currentNode, 1);
|
|
this._addOrUpdateNode(currentNode.pos[0], currentNode.pos[1]+1, currentNode, 1);
|
|
this._addOrUpdateNode(currentNode.pos[0], currentNode.pos[1]-1, currentNode, 1);
|
|
if ( this._allowDiagonals )
|
|
{
|
|
this._addOrUpdateNode(currentNode.pos[0]+1, currentNode.pos[1]+1, currentNode, 1.414213562);
|
|
this._addOrUpdateNode(currentNode.pos[0]+1, currentNode.pos[1]-1, currentNode, 1.414213562);
|
|
this._addOrUpdateNode(currentNode.pos[0]-1, currentNode.pos[1]-1, currentNode, 1.414213562);
|
|
this._addOrUpdateNode(currentNode.pos[0]-1, currentNode.pos[1]+1, currentNode, 1.414213562);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get (or dynamically construct) a node.
|
|
*
|
|
* *All* nodes should be created using this method: The cost of the node is computed thanks
|
|
* to the objects flagged as obstacles.
|
|
*/
|
|
gdjs.PathfindingRuntimeBehavior.SearchContext.prototype._getNode = function(xPos, yPos)
|
|
{
|
|
//First check if their is a node a the specified position.
|
|
if (this._allNodes.hasOwnProperty(xPos)) {
|
|
if ( this._allNodes[xPos].hasOwnProperty(yPos) )
|
|
return this._allNodes[xPos][yPos];
|
|
}
|
|
else
|
|
this._allNodes[xPos] = [];
|
|
|
|
//No so construct a new node (or get it from the cache)...
|
|
var newNode = null;
|
|
if ( this._nodeCache.length !== 0) {
|
|
newNode = this._nodeCache.shift();
|
|
gdjs.PathfindingRuntimeBehavior.Node.call(newNode, xPos, yPos);
|
|
}
|
|
else
|
|
newNode = new gdjs.PathfindingRuntimeBehavior.Node(xPos, yPos);
|
|
|
|
//...and update its cost according to obstacles
|
|
var objectsOnCell = false;
|
|
var radius = this._cellHeight > this._cellWidth ? this._cellHeight*2 : this._cellWidth*2;
|
|
this._obstacles.getAllObstaclesAround(xPos*this._cellWidth, yPos*this._cellHeight,
|
|
radius, this._closeObstacles);
|
|
|
|
for(var k = 0; k < this._closeObstacles.length; ++k ) {
|
|
var obj = this._closeObstacles[k].owner;
|
|
var topLeftCellX = Math.floor((obj.getDrawableX()-this._rightBorder)/this._cellWidth);
|
|
var topLeftCellY = Math.floor((obj.getDrawableY()-this._bottomBorder)/this._cellHeight);
|
|
var bottomRightCellX = Math.ceil((obj.getDrawableX()+obj.getWidth()+this._leftBorder)/this._cellWidth);
|
|
var bottomRightCellY = Math.ceil((obj.getDrawableY()+obj.getHeight()+this._topBorder)/this._cellHeight);
|
|
if ( topLeftCellX < xPos && xPos < bottomRightCellX
|
|
&& topLeftCellY < yPos && yPos < bottomRightCellY) {
|
|
|
|
objectsOnCell = true;
|
|
if ( this._closeObstacles[k].isImpassable() ) {
|
|
newNode.cost = -1;
|
|
break; //The cell is impassable, stop here.
|
|
}
|
|
else //Superimpose obstacles
|
|
newNode.cost += this._closeObstacles[k].getCost();
|
|
}
|
|
}
|
|
|
|
if (!objectsOnCell) newNode.cost = 1; //Default cost when no objects put on the cell.
|
|
|
|
this._allNodes[xPos][yPos] = newNode;
|
|
return newNode;
|
|
};
|
|
|
|
/**
|
|
* Add a node to the openNodes (only if the cost to reach it is less than the existing cost, if any).
|
|
*/
|
|
gdjs.PathfindingRuntimeBehavior.SearchContext.prototype._addOrUpdateNode = function(newNodeX, newNodeY, currentNode, factor)
|
|
{
|
|
var neighbor = this._getNode(newNodeX, newNodeY);
|
|
if (!neighbor.open || neighbor.cost < 0 ) //cost < 0 means impassable obstacle
|
|
return;
|
|
|
|
//Update the node costs and parent if the path coming from currentNode is better:
|
|
if (neighbor.smallestCost === -1
|
|
|| neighbor.smallestCost > currentNode.smallestCost + (currentNode.cost+neighbor.cost)/2.0*factor)
|
|
{
|
|
if (neighbor.smallestCost != -1) { //The node is already in the open list..
|
|
for (var i =0;i<this._openNodes.length;++i) {
|
|
if (this._openNodes[i].pos[0] == neighbor.pos[0] && this._openNodes[i].pos[1] == neighbor.pos[1]) {
|
|
this._openNodes.splice(i, 1); //..so remove it as its estimate cost will be updated.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
neighbor.smallestCost = currentNode.smallestCost + (currentNode.cost+neighbor.cost)/2.0*factor;
|
|
neighbor.parent = currentNode;
|
|
neighbor.estimateCost = neighbor.smallestCost + this._distanceFunction(neighbor.pos, this._destination);
|
|
|
|
//Add the neighbor to open nodes, which are sorted by their estimate cost:
|
|
if (this._openNodes.length === 0 || this._openNodes[this._openNodes.length-1].estimateCost < neighbor.estimateCost )
|
|
this._openNodes.push(neighbor);
|
|
else {
|
|
for (var i = 0;i<this._openNodes.length;++i) {
|
|
if (this._openNodes[i].estimateCost >= neighbor.estimateCost) {
|
|
this._openNodes.splice(i, 0, neighbor);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|