UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

124 lines (97 loc) 3.56 kB
// import { randomIntegerBetween } from "../math/random/randomIntegerBetween.js"; import { seededRandom } from "../math/random/seededRandom.js"; import { graph_compute_distance_matrix } from "./graph_compute_distance_matrix.js"; /** * @param {MultiNode<T>[]} node_array * @param {number} k * @param {number} random_seed * @param {number[]|Uint8Array|Uint16Array|Uint32Array} node_cluster_assignments * @param {Map<T, number>} node_index_map * @param {Graph<MultiNode<T>>} graph * @returns {number[][]} */ export function graph_k_means_cluster_detailed( node_array, k, random_seed, node_cluster_assignments, node_index_map, graph ) { const total_node_count = node_array.length; if (k > total_node_count) { throw new Error(`Not enough nodes in the graph, K(=${k}) > |V| (=${total_node_count})`); } const random = seededRandom(random_seed); //seeds const clusters_seeds = []; /** * * @type {number[][]} */ const clusters = []; const UNASSIGNED_CLUSTER = 255; // seed clusters for (let i = 0; i < k; i++) { // const node_index = randomIntegerBetween(random, 0, total_node_count); if (node_cluster_assignments[node_index] !== UNASSIGNED_CLUSTER) { // node taken, retry i--; continue; } node_cluster_assignments[node_index] = i; clusters_seeds[i] = node_index; clusters[i] = [node_index]; } const m_distances = graph_compute_distance_matrix(graph, node_array, clusters_seeds, node_index_map); for (let node_index = 0; node_index < total_node_count; node_index++) { if (node_cluster_assignments[node_index] !== UNASSIGNED_CLUSTER) { // already assigned continue; } let closest_cluster = 0; let closest_distance = Number.POSITIVE_INFINITY; for (let cluster_index = 0; cluster_index < k; cluster_index++) { const cluster_seed = clusters_seeds[cluster_index]; const distance = m_distances.getCellValue(cluster_seed, node_index); if (distance < closest_distance) { closest_distance = distance; closest_cluster = cluster_index; } } // assign node to closest cluster node_cluster_assignments[node_index] = closest_cluster; clusters[closest_cluster].push(node_index); } return clusters; } /** * Partition graph into K parts using K-means algorithm * @template T * @param {Graph<T>} graph * @param {number} k number of desired parts * @param {number} random_seed seed for random number generator, useful for restarting partitioning * @returns {number[][]} */ export function graph_k_means_cluster(graph, k, random_seed) { const node_array = Array.from(graph.getNodes()); const node_count = node_array.length; /** * * @type {Uint8Array} */ const node_cluster_assignments = new Uint8Array(node_count); // fill with "UNASSIGNED" value node_cluster_assignments.fill(255); /** * build node index * @type {Map<T, number>} */ const node_index_map = new Map(); for (let i = 0; i < node_count; i++) { node_index_map.set(node_array[i], i); } return graph_k_means_cluster_detailed(node_array, k, random_seed, node_cluster_assignments, node_index_map, graph); }