import { BC_TERRAIN_SETTINGS } from "../GlobalVariables/GlobalVariables"; import { ChunkStorageTypes } from "../WorldGeneration/WorldChunk/WorldGenChunk"; import { getTileAt } from "../WorldGeneration/WorldGen"; import { PointInt2D } from "./Math.utils"; import { SceneObject } from "../SceneObjects/SceneObject"; import { getNavigationGridTile } from "../World/NavigationGrid/NavigationGrid"; import { TerrainTile } from "../WorldGeneration/WorldObjects/TerrainTile/TerrainTile"; class PathFinderNode { position; fScore = 1e16; gScore = 1e16; dScore = 1; // hScore = 1e16; id; /** * @type {TerrainTile} */ ref; /** * * @param {PointInt2D} position * @param {Number} gScore */ constructor(position, gScore = 1e16, fScore = 1e16, dScore) { this.position = position; this.id = position.getX() + "_" + position.getY(); this.gScore = gScore; this.fScore = fScore; this.dScore = dScore; } } export class NavigationPath { path; /** * @param {Array} path */ constructor(path = []) { this.path = path; } } export class NavigationResult { /** * @type {Boolean} */ error; /** * @type {String} */ errorText; /** * @type {NavigationPath | undefined} */ result; /** * 0 - point is unreachable * 1 - path found * 2 - error * @type {Number} */ state; constructor(error, errorText, result, state) { this.error = error; this.errorText = errorText; this.result = result; this.state = state; } } export class PathFinder { /** * @type Array */ _openSet = new Array(); _closedSet = new Array(); _goal = new PointInt2D(); _start = new PointInt2D(); #MAX_PATH_ITERATIONS = 10000; #iterationsCounter = 0; // findPathIfExist(start, goal) // { // let resultFromGoal = this.search(goal, start); // if(resultFromGoal.state !== 1) // { // return resultFromGoal; // } // else // { // resultFromGoal.result.path.reverse(); // return resultFromGoal; // } // } /** * * @param {PointInt2D} start * @param {PointInt2D} goal */ search(start, goal) { this._openSet = []; this._closedSet = []; this.#iterationsCounter = 0; start.divideBy(BC_TERRAIN_SETTINGS.totalSize); start.multiplyBy(BC_TERRAIN_SETTINGS.totalSize); this._start = start; goal.divideBy(BC_TERRAIN_SETTINGS.totalSize); goal.multiplyBy(BC_TERRAIN_SETTINGS.totalSize); this._goal = goal; // getNavigationGridTile(goal.getX(), goal.getY()).optionalTerrainTileRef.drawObject.tint = 0x0000FF; this._openSet.push(new PathFinderNode(this._start, 0, this._heuristic(this._start, 0))); //code /** * @type PathFinderNode */ let currentNode; // /** // * @type Map // */ // let gScores = new Map(); // /** // * @type Map // */ // let fScores = new Map(); /** * @type Map */ let cameFrom = new Map(); let minFScoreNodeIndex = 0; //START LOOP WOOOOAAAW while (this._openSet.length > 0) { this.#iterationsCounter++; if(this.#iterationsCounter >= this.#MAX_PATH_ITERATIONS) { return new NavigationResult(true, "reached limit", new NavigationPath(), 2); } //find node with min f score //better to rewrite it to priority queue minFScoreNodeIndex = 0; for (let i = 0; i < this._openSet.length; i++) { if (this._openSet[i].fScore < this._openSet[minFScoreNodeIndex].fScore) minFScoreNodeIndex = i; } //wow! node is found and set!! currentNode = this._openSet[minFScoreNodeIndex]; // if (currentNode.ref) currentNode.ref.drawObject.tint = 0xff0000; if (PointInt2D.isEqual(currentNode.position, this._goal)) { //wow!!! we have found an end of the path!!! this is so cool!!! return new NavigationResult(false, "", new NavigationPath(this._reconstructPath(cameFrom, currentNode)), 1); //return something weird stuff } //and now we must delete this node... what a sad situation... this._openSet.splice(minFScoreNodeIndex, 1); //but wait! we add it to closed set! nice!!! this._closedSet.push(currentNode); /** * @type Array */ let currentNeighbors = this._getNeighbors(currentNode.position); for (const neighbor of currentNeighbors) { if (this._existsInClosedSet(neighbor)) continue; let tentativeGScore = currentNode.gScore + this._manhattanDistance(currentNode.position, neighbor.position); if (tentativeGScore < neighbor.gScore && !this._existsInOpenSet(neighbor)) { cameFrom.set(neighbor.id, currentNode); neighbor.gScore = tentativeGScore; neighbor.fScore = tentativeGScore + this._heuristic(neighbor.position, neighbor.dScore); if (!this._existsInOpenSet(neighbor)) { this._openSet.push(neighbor); } } } } return new NavigationResult(false, "", new NavigationPath(), 0); } /** * * @param {Map} cameFrom * @param {PathFinderNode} current */ _reconstructPath(cameFrom, current) { let totalPath = [current.position]; // console.log(cameFrom); let keys = [...cameFrom.keys()]; while (keys.includes(current.id)) { if (current.id === cameFrom.get(current.id).id) break; current = cameFrom.get(current.id); totalPath.push(current.position); } return totalPath; } /** * * @param {PointInt2D} root */ _getNeighbors(root) { /** * @type Array */ let neighbors = new Array(); //north let currentTile = getNavigationGridTile(root.getX(), root.getY() + BC_TERRAIN_SETTINGS.totalSize); let node; if (currentTile && !currentTile.isObstacle) { // currentTile.optionalTerrainTileRef.drawObject.tint = 0x0000ff; node = new PathFinderNode(currentTile.position, 1e16, 1e16, currentTile.movementCost); node.ref = currentTile.optionalTerrainTileRef; neighbors.push(node); } //north-e // currentTile = getNavigationGridTile(root.getX() + BC_TERRAIN_SETTINGS.totalSize, root.getY() + BC_TERRAIN_SETTINGS.totalSize); // if (currentTile && !currentTile.isObstacle) { // // currentTile.optionalTerrainTileRef.drawObject.tint = 0x0000ff; // node = new PathFinderNode(currentTile.position, 1e16, 1e16, currentTile.movementCost); // node.ref = currentTile.optionalTerrainTileRef; // neighbors.push(node); // } //north-w // currentTile = getNavigationGridTile(root.getX() - BC_TERRAIN_SETTINGS.totalSize, root.getY() + BC_TERRAIN_SETTINGS.totalSize); // if (currentTile && !currentTile.isObstacle) { // // currentTile.optionalTerrainTileRef.drawObject.tint = 0x0000ff; // node = new PathFinderNode(currentTile.position, 1e16, 1e16, currentTile.movementCost); // node.ref = currentTile.optionalTerrainTileRef; // neighbors.push(node); // } //south currentTile = getNavigationGridTile(root.getX(), root.getY() - BC_TERRAIN_SETTINGS.totalSize); if (currentTile && !currentTile.isObstacle) { // currentTile.optionalTerrainTileRef.drawObject.tint = 0x0000ff; node = new PathFinderNode(currentTile.position, 1e16, 1e16, currentTile.movementCost); node.ref = currentTile.optionalTerrainTileRef; neighbors.push(node); } //south-e // currentTile = getNavigationGridTile(root.getX() + BC_TERRAIN_SETTINGS.totalSize, root.getY() - BC_TERRAIN_SETTINGS.totalSize); // if (currentTile && !currentTile.isObstacle) { // // currentTile.optionalTerrainTileRef.drawObject.tint = 0x0000ff; // node = new PathFinderNode(currentTile.position, 1e16, 1e16, currentTile.movementCost); // node.ref = currentTile.optionalTerrainTileRef; // neighbors.push(node); // } //south-w // currentTile = getNavigationGridTile(root.getX() - BC_TERRAIN_SETTINGS.totalSize, root.getY() - BC_TERRAIN_SETTINGS.totalSize); // if (currentTile && !currentTile.isObstacle) { // // currentTile.optionalTerrainTileRef.drawObject.tint = 0x0000ff; // node = new PathFinderNode(currentTile.position, 1e16, 1e16, currentTile.movementCost); // node.ref = currentTile.optionalTerrainTileRef; // neighbors.push(node); // } //east currentTile = getNavigationGridTile(root.getX() + BC_TERRAIN_SETTINGS.totalSize, root.getY()); if (currentTile && !currentTile.isObstacle) { // currentTile.optionalTerrainTileRef.drawObject.tint = 0x0000ff; node = new PathFinderNode(currentTile.position, 1e16, 1e16, currentTile.movementCost); node.ref = currentTile.optionalTerrainTileRef; neighbors.push(node); } //west currentTile = getNavigationGridTile(root.getX() - BC_TERRAIN_SETTINGS.totalSize, root.getY()); if (currentTile && !currentTile.isObstacle) { // currentTile.optionalTerrainTileRef.drawObject.tint = 0x0000ff; node = new PathFinderNode(currentTile.position, 1e16, 1e16, currentTile.movementCost); node.ref = currentTile.optionalTerrainTileRef; neighbors.push(node); } return neighbors; } /** * * @param {PointInt2D} start * @param {PointInt2D} goal * @returns {Number} */ _manhattanDistance(start, goal) { return Math.abs(start.getX() - goal.getX()) + Math.abs(start.getY() - goal.getY()); } /** * * @param {PointInt2D} start * @param {PointInt2D} goal * @returns {Number} */ _euclideanDistance(start, goal) { let dx = Math.abs(start.getX() - goal.getX()); let dy = Math.abs(start.getY() - goal.getY()); return Math.sqrt(dx * dx + dy * dy); } /** * * @param {PointInt2D} current * @param {Number} d */ _heuristic(current, d) { let x1 = current.getX() - this._goal.getX(); let y1 = current.getY() - this._goal.getY(); let x2 = this._start.getX() - this._goal.getX(); let y2 = this._start.getY() - this._goal.getY(); let cross = Math.abs(x1 * y2 - x2 * y1); // console.log("cross", cross); return this._manhattanDistance(current, this._goal) * d + cross * 0.00001; } /** * * @param {PathFinderNode} node */ _existsInClosedSet(node) { for (const i of this._closedSet) { if (i.id === node.id) return true; } return false; } /** * * @param {PathFinderNode} node */ _existsInOpenSet(node) { for (const i of this._openSet) { if (i.id === node.id) return true; } return false; } }