@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
96 lines (74 loc) • 3.69 kB
JavaScript
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;
}