UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

96 lines (74 loc) 3.69 kB
import { BinaryDataType } from "../binary/type/BinaryDataType.js"; import { Deque } from "../collection/queue/Deque.js"; import { SquareMatrix } from "../math/matrix/SquareMatrix.js"; /** * Produce a distance matrix from an input graph, tracing distances from each * node to every node specified in the target vector. * * Traversal is BFS and each neighbour is only visited once. As a consequence * this implementation gives correct shortest-path distances only when every * edge has the same weight (typically `edge.weight === 1`). On graphs with * heterogeneous weights the returned distances are the weight sum along the * BFS tree, which is not the shortest-path distance in general — use * Dijkstra or Floyd–Warshall for those cases instead. * * Edge costs are read from `edge.weight`, so uniform non-1 weights still * produce distances scaled by the common weight, but this remains correct * only because every path of the same hop count has the same total weight. * * Output layout: `m[row, col]` is the distance from the node at index `row` * to the target node at index `col`. Columns whose index is not in `targets` * are left at `POSITIVE_INFINITY` (except the diagonal, which is 0). * * @see "A Fast Algorithm to Find All-Pairs Shortest Paths in Complex Networks" by Wei Peng et Al. 2012 * @template T * @param {Graph<T>} graph * @param {T[]} node_array graph nodes as an array * @param {number[]} targets node indices, distances to which need to be calculated * @param {Map<T, number>} node_index_map * @returns {SquareMatrix} */ export function graph_compute_distance_matrix(graph, node_array, targets, node_index_map) { const node_count = node_array.length; const m_distances = new SquareMatrix(node_count, BinaryDataType.Float32); // initialize distances m_distances.fill(Number.POSITIVE_INFINITY); // fill diagonal for (let i = 0; i < node_count; i++) { // Distance to self is 0 m_distances.setCellValue(i, i, 0); } /** * * @type {Deque<number>} */ const queue = new Deque(); for (const target_index of targets) { queue.addLast(target_index); while (!queue.isEmpty()) { const index_1 = queue.removeFirst(); const node_1 = node_array[index_1]; const node_1_container = graph.getNodeContainer(node_1); const edges = node_1_container.getEdges(); const edge_count = edges.length; for (let i = 0; i < edge_count; i++) { const edge = edges[i]; const node_0 = edge.other(node_1); if (!edge.validateTransition(node_0, node_1)) { continue; } const index_0 = node_index_map.get(node_0); // If we haven't visited this neighbor yet, its distance is the current distance + 1 if (m_distances.getCellValue(index_0, target_index) === Number.POSITIVE_INFINITY) { // Fall back to unit weight when the edge has no explicit weight, so BFS // produces meaningful hop-count distances on plain (non-weighted) edges. const transition_cost = edge.weight !== undefined ? edge.weight : 1; const new_distance = m_distances.getCellValue(index_1, target_index) + transition_cost; m_distances.setCellValue(index_0, target_index, new_distance); queue.addLast(index_0); } } } } return m_distances; }