| | | 1 | | using System.Collections; |
| | | 2 | | using System.Collections.Generic; |
| | | 3 | | using UnityEngine; |
| | | 4 | | |
| | | 5 | | // The agglomerative clusterer class performs agglomerative clustering with the stopping condition |
| | | 6 | | // given by the size and radius constraints. |
| | | 7 | | public class AgglomerativeClusterer : ISizeAndRadiusConstrainedClusterer { |
| | | 8 | | public AgglomerativeClusterer(List<GameObject> objects, int maxSize, float maxRadius) |
| | 0 | 9 | | : base(objects, maxSize, maxRadius) {} |
| | | 10 | | public AgglomerativeClusterer(List<Agent> agents, int maxSize, float maxRadius) |
| | 30 | 11 | | : base(agents, maxSize, maxRadius) {} |
| | | 12 | | |
| | | 13 | | // Cluster the game objects. |
| | 10 | 14 | | public override void Cluster() { |
| | | 15 | | // Add a cluster for every game object. |
| | 2295 | 16 | | foreach (var obj in _objects) { |
| | 755 | 17 | | Cluster cluster = new Cluster(obj); |
| | 755 | 18 | | cluster.AddObject(obj); |
| | 755 | 19 | | _clusters.Add(cluster); |
| | 755 | 20 | | } |
| | | 21 | | |
| | | 22 | | // Create a set containing all valid cluster indices. |
| | 10 | 23 | | HashSet<int> validClusterIndices = new HashSet<int>(); |
| | 2285 | 24 | | for (int i = 0; i < _clusters.Count; ++i) { |
| | 755 | 25 | | validClusterIndices.Add(i); |
| | 755 | 26 | | } |
| | | 27 | | |
| | | 28 | | // Find the pairwise distances between all clusters. |
| | | 29 | | // The upper triangular half of the distances matrix is unused. |
| | 10 | 30 | | float[,] distances = new float[_clusters.Count, _clusters.Count]; |
| | 2285 | 31 | | for (int i = 0; i < _clusters.Count; ++i) { |
| | 396910 | 32 | | for (int j = 0; j < i; ++j) { |
| | 131800 | 33 | | distances[i, j] = Vector3.Distance(_clusters[i].Coordinates, _clusters[j].Coordinates); |
| | 131800 | 34 | | } |
| | 755 | 35 | | } |
| | | 36 | | |
| | 1762 | 37 | | while (true) { |
| | | 38 | | // Find the minimum distance between two clusters. |
| | 881 | 39 | | float minDistance = Mathf.Infinity; |
| | 881 | 40 | | int clusterIndex1 = -1; |
| | 881 | 41 | | int clusterIndex2 = -1; |
| | 1089826 | 42 | | for (int i = 0; i < _clusters.Count; ++i) { |
| | 265229673 | 43 | | for (int j = 0; j < i; ++j) { |
| | 88175089 | 44 | | if (distances[i, j] < minDistance) { |
| | 6990 | 45 | | minDistance = distances[i, j]; |
| | 6990 | 46 | | clusterIndex1 = i; |
| | 6990 | 47 | | clusterIndex2 = j; |
| | 6990 | 48 | | } |
| | 88168099 | 49 | | } |
| | 362688 | 50 | | } |
| | | 51 | | |
| | | 52 | | // Check whether the minimum distance exceeds the maximum cluster radius, in which case the |
| | | 53 | | // algorithm has converged. This produces a conservative solution because the radius of a |
| | | 54 | | // merged cluster is less than or equal to the sum of the original cluster radii. |
| | 891 | 55 | | if (minDistance >= _maxRadius) { |
| | 10 | 56 | | break; |
| | | 57 | | } |
| | | 58 | | |
| | | 59 | | // Check whether merging the two clusters would violate the size constraint. |
| | 1164 | 60 | | if (_clusters[clusterIndex1].Size() + _clusters[clusterIndex2].Size() > _maxSize) { |
| | 293 | 61 | | distances[clusterIndex1, clusterIndex2] = Mathf.Infinity; |
| | 293 | 62 | | continue; |
| | | 63 | | } |
| | | 64 | | |
| | | 65 | | // Merge the two clusters together. |
| | 578 | 66 | | int minClusterIndex = Mathf.Min(clusterIndex1, clusterIndex2); |
| | 578 | 67 | | int maxClusterIndex = Mathf.Max(clusterIndex1, clusterIndex2); |
| | 578 | 68 | | _clusters[minClusterIndex].Merge(_clusters[maxClusterIndex]); |
| | 578 | 69 | | _clusters[minClusterIndex].Recenter(); |
| | 578 | 70 | | validClusterIndices.Remove(maxClusterIndex); |
| | | 71 | | |
| | | 72 | | // Update the distances matrix using the distance between the cluster centroids. |
| | | 73 | | // TODO(titan): Change the distance metric to use average or maximum linkage. |
| | 295216 | 74 | | for (int i = 0; i < minClusterIndex; ++i) { |
| | 157612 | 75 | | if (distances[minClusterIndex, i] < Mathf.Infinity) { |
| | 59592 | 76 | | distances[minClusterIndex, i] = |
| | | 77 | | Vector3.Distance(_clusters[minClusterIndex].Coordinates, _clusters[i].Coordinates); |
| | 59592 | 78 | | } |
| | 98020 | 79 | | } |
| | 358861 | 80 | | for (int i = minClusterIndex + 1; i < _clusters.Count; ++i) { |
| | 187167 | 81 | | if (distances[i, minClusterIndex] < Mathf.Infinity) { |
| | 67932 | 82 | | distances[i, minClusterIndex] = |
| | | 83 | | Vector3.Distance(_clusters[minClusterIndex].Coordinates, _clusters[i].Coordinates); |
| | 67932 | 84 | | } |
| | 119235 | 85 | | } |
| | 336340 | 86 | | for (int i = 0; i < maxClusterIndex; ++i) { |
| | 111728 | 87 | | distances[maxClusterIndex, i] = Mathf.Infinity; |
| | 111728 | 88 | | } |
| | 317737 | 89 | | for (int i = maxClusterIndex + 1; i < _clusters.Count; ++i) { |
| | 105527 | 90 | | distances[i, maxClusterIndex] = Mathf.Infinity; |
| | 105527 | 91 | | } |
| | 578 | 92 | | } |
| | | 93 | | |
| | | 94 | | // Select only the valid clusters. |
| | 2285 | 95 | | for (int i = _clusters.Count - 1; i >= 0; --i) { |
| | 1333 | 96 | | if (!validClusterIndices.Contains(i)) { |
| | 578 | 97 | | _clusters.RemoveAt(i); |
| | 578 | 98 | | } |
| | 755 | 99 | | } |
| | 10 | 100 | | } |
| | | 101 | | } |