diff --git a/src/engine/Math/Graph.ts b/src/engine/Math/Graph.ts index c18ee93e..7332c901 100644 --- a/src/engine/Math/Graph.ts +++ b/src/engine/Math/Graph.ts @@ -3,7 +3,7 @@ import { Vector } from './vector'; /** * A unique identifier for a graph node or edge. */ -type G_UUID = string & { readonly __brand: unique symbol }; +export type G_UUID = string & { readonly __brand: unique symbol }; /** * Options for creating a new edge in the graph. @@ -398,97 +398,132 @@ export class Graph { * be `Infinity`. */ - dijkstra( - startNode: Node, - endNode: Node - ): { - path: Node[] | null; - distance: number; - } { - // Initialize data structures - const distances: Map = new Map(); - const previous: Map = new Map(); - const unvisited: Set = new Set(); - - // Set initial distances - for (const [nodeId] of this._nodes) { - distances.set(nodeId, nodeId === startNode.id ? 0 : Infinity); - previous.set(nodeId, null); - unvisited.add(nodeId); + dijkstra(sourcenode: Node): Array<{ node: Node; distance: number; previous: Node | null }> { + const visited: Node[] = []; + const unvisited: Node[] = []; + const resultArray: Array<{ node: Node; distance: number; previous: Node | null }> = []; + + //fill unvisited + this.nodes.forEach((node) => unvisited.push(node)); + + //fill resultArray + this.nodes.forEach((node) => resultArray.push({ node, distance: Infinity, previous: null })); + + //start with starting node + //add startingnode to result array + const startingNodeIndex = resultArray.findIndex((node) => node.node === sourcenode); + if (startingNodeIndex === -1) { + return []; } + resultArray[startingNodeIndex].distance = 0; - // Continue until we've visited all nodes or found the target - while (unvisited.size > 0) { - // Find closest unvisited node - let currentId: G_UUID | null = null; - let shortestDistance = Infinity; + visited.push(sourcenode); + unvisited.splice(unvisited.indexOf(sourcenode), 1); - for (const nodeId of unvisited) { - const distance = distances.get(nodeId) || Infinity; - if (distance < shortestDistance) { - shortestDistance = distance; - currentId = nodeId; - } - } + let current = sourcenode; + const currentEdges = current.edges; + const filteredCurrentEdges: Edge[] = Array.from(currentEdges).filter((edge: Edge) => edge.target !== current); - // If we can't find a node, there's no path - if (currentId === null || shortestDistance === Infinity) { - break; + //update result array with distances, which is edge values + + for (const edge of filteredCurrentEdges) { + const index = resultArray.findIndex((node) => node.node === edge.target); + + if (index === -1) { + return []; } + resultArray[index].distance = edge.weight as number; + resultArray[index].previous = current; + } - // If we found the target, we're done - if (currentId === endNode.id) { - break; + while (unvisited.length > 0) { + //get list of unvisited available nodes + let listOfAvailableNodes: Node[] = []; + let listofAvailableEntries: Array<{ node: Node; distance: number; previous: Node | null }> = []; + listofAvailableEntries = resultArray.filter((node) => unvisited.includes(node.node)); + listOfAvailableNodes = listofAvailableEntries.map((node) => node.node); + + //loop through available nodes and find lowest distance to sourcenode + let lowestDistance = Infinity; + let lowestDistanceIndex = -1; + + if (listOfAvailableNodes.length > 0) { + for (let i = 0; i < listOfAvailableNodes.length; i++) { + const unVisitiedNode = listOfAvailableNodes[i]; + + const index = resultArray.findIndex((node) => node.node === unVisitiedNode); + if (resultArray[index].distance < lowestDistance) { + lowestDistance = resultArray[index].distance; + lowestDistanceIndex = index; + } + } + } else { + //manage exception + //choose node from unvisited list that has lowest distance to source node + + lowestDistance = Infinity; + lowestDistanceIndex = -1; + for (let i = 0; i < unvisited.length; i++) { + const unVisitiedNode = unvisited[i]; + const index = resultArray.findIndex((node) => node.node === unVisitiedNode); + if (resultArray[index].distance < lowestDistance) { + lowestDistance = resultArray[index].distance; + lowestDistanceIndex = index; + } + } } - // Remove from unvisited - unvisited.delete(currentId); + current = resultArray[lowestDistanceIndex].node; + let currentEdgesArray = Array.from(current.edges); - // Get all edges from this node - const currentNode = this._nodes.get(currentId)!; - const outgoingEdges = Array.from(currentNode.edges).filter((edge) => edge.source.id === currentId); + //remove visited from currentEdges + currentEdgesArray = currentEdgesArray.filter((edge: Edge) => { + return !visited.includes(edge.source) && !visited.includes(edge.target); + }); - // Update distances to neighbors - for (const edge of outgoingEdges) { - const neighborId = edge.target.id; + visited.push(current); + unvisited.splice(unvisited.indexOf(current), 1); - // Skip if neighbor has been visited - if (!unvisited.has(neighborId)) { - continue; - } + //update result array with distances, which is edge values + for (let i = 0; i < currentEdgesArray.length; i++) { + const edge = currentEdgesArray[i]; + const index = resultArray.findIndex((node) => node.node === edge.target); - const newDistance = (distances.get(currentId) || 0) + edge.weight; - const currentDistance = distances.get(neighborId) || Infinity; + //update cumulative distances + const previousIndex = resultArray.findIndex((node) => node.node === edge.source); - if (newDistance < currentDistance) { - distances.set(neighborId, newDistance); - previous.set(neighborId, currentId); + const previousDistance = resultArray[previousIndex].distance; + const cumDistance = (previousDistance + edge.weight!) as number; + + if (cumDistance < resultArray[index].distance) { + resultArray[index].distance = cumDistance; + resultArray[index].previous = current; } } } - // Reconstruct path + return resultArray; + } + + shortestPathDijkstra(sourcenode: Node, endnode: Node): { path: Node[]; distance: number } { + const dAnalysis = this.dijkstra(sourcenode); + + //iterate through dAnalysis to plot shortest path to endnode const path: Node[] = []; - let current: G_UUID | null = endNode.id; + let current: Node | null | undefined = endnode; + const distance = dAnalysis.find((node) => node.node === endnode)?.distance as number; - // No path found - if (previous.get(endNode.id) === null && startNode.id !== endNode.id) { - return { path: null, distance: Infinity }; - } + while (current != null) { + path.push(current); - // Build the path - while (current !== null) { - //get currentNode + current = dAnalysis.find((node) => node.node === current)?.previous; - const nextNode: Node = this._nodes.get(current)!; - path.unshift(nextNode); - current = previous.get(current)!; + if (current == null) { + break; + } } - - return { - path, - distance: distances.get(endNode.id) || Infinity - }; + path.reverse(); + return { path, distance }; } /**