| | | 1 | | using System.Collections.Generic; |
| | | 2 | | using System.Linq; |
| | | 3 | | using UnityEngine; |
| | | 4 | | |
| | | 5 | | // The agglomerative clusterer performs agglomerative clustering with the stopping condition given |
| | | 6 | | // by the size and radius constraints. |
| | | 7 | | public class AgglomerativeClusterer : SizeAndRadiusConstrainedClustererBase { |
| | 0 | 8 | | public AgglomerativeClusterer(int maxSize, float maxRadius) : base(maxSize, maxRadius) {} |
| | | 9 | | |
| | | 10 | | // Generate the clusters from the list of hierarchical objects. |
| | 0 | 11 | | public override List<Cluster> Cluster(IEnumerable<IHierarchical> hierarchicals) { |
| | 0 | 12 | | var clusters = new List<Cluster>(); |
| | 0 | 13 | | if (hierarchicals == null || !hierarchicals.Any()) { |
| | 0 | 14 | | return clusters; |
| | | 15 | | } |
| | | 16 | | |
| | 0 | 17 | | foreach (var hierarchical in hierarchicals) { |
| | 0 | 18 | | var cluster = new Cluster { Centroid = hierarchical.Position }; |
| | 0 | 19 | | cluster.AddSubHierarchical(hierarchical); |
| | 0 | 20 | | clusters.Add(cluster); |
| | 0 | 21 | | } |
| | | 22 | | |
| | | 23 | | // Create a set containing all valid cluster indices. |
| | 0 | 24 | | var validClusterIndices = new HashSet<int>(); |
| | 0 | 25 | | for (int i = 0; i < clusters.Count; ++i) { |
| | 0 | 26 | | validClusterIndices.Add(i); |
| | 0 | 27 | | } |
| | | 28 | | |
| | | 29 | | // Find the pairwise distances between all clusters. |
| | | 30 | | // Only the lower triangular part of the distances matrix is used, i.e., row index > column |
| | | 31 | | // index. |
| | 0 | 32 | | var distances = new float[clusters.Count, clusters.Count]; |
| | 0 | 33 | | for (int i = 0; i < clusters.Count; ++i) { |
| | 0 | 34 | | for (int j = 0; j < i; ++j) { |
| | 0 | 35 | | distances[i, j] = Vector3.Distance(clusters[i].Centroid, clusters[j].Centroid); |
| | 0 | 36 | | } |
| | 0 | 37 | | } |
| | | 38 | | |
| | 0 | 39 | | while (true) { |
| | | 40 | | // Find the minimum distance between two clusters. |
| | 0 | 41 | | float minDistance = Mathf.Infinity; |
| | 0 | 42 | | int clusterIndex1 = -1; |
| | 0 | 43 | | int clusterIndex2 = -1; |
| | | 44 | | // Invariant: clusterIndex1 > clusterIndex2. |
| | 0 | 45 | | foreach (int i in validClusterIndices) { |
| | 0 | 46 | | for (int j = 0; j < i; ++j) { |
| | 0 | 47 | | if (validClusterIndices.Contains(j) && distances[i, j] < minDistance) { |
| | 0 | 48 | | minDistance = distances[i, j]; |
| | 0 | 49 | | clusterIndex1 = i; |
| | 0 | 50 | | clusterIndex2 = j; |
| | 0 | 51 | | } |
| | 0 | 52 | | } |
| | 0 | 53 | | } |
| | | 54 | | |
| | | 55 | | // Check whether the minimum distance exceeds the maximum cluster radius, in which case the |
| | | 56 | | // algorithm has converged. This produces a conservative solution because the radius of a |
| | | 57 | | // merged cluster is less than or equal to the sum of the original cluster radii. |
| | 0 | 58 | | if (minDistance == Mathf.Infinity || minDistance > _maxRadius) { |
| | 0 | 59 | | break; |
| | | 60 | | } |
| | | 61 | | |
| | | 62 | | // Check whether merging the two clusters would violate the size constraint. |
| | 0 | 63 | | if (clusters[clusterIndex1].Size + clusters[clusterIndex2].Size > _maxSize) { |
| | 0 | 64 | | distances[clusterIndex1, clusterIndex2] = Mathf.Infinity; |
| | 0 | 65 | | continue; |
| | | 66 | | } |
| | | 67 | | |
| | | 68 | | // Merge the two clusters together and keep the cluster with the smaller index, i.e., |
| | | 69 | | // clusterIndex2. |
| | 0 | 70 | | clusters[clusterIndex2].Merge(clusters[clusterIndex1]); |
| | 0 | 71 | | clusters[clusterIndex2].Recenter(); |
| | 0 | 72 | | validClusterIndices.Remove(clusterIndex1); |
| | | 73 | | |
| | | 74 | | // Update the distances matrix using the distance between the cluster centroids. |
| | | 75 | | // TODO(titan): Change the distance metric to use average or maximum linkage. |
| | 0 | 76 | | foreach (int i in validClusterIndices) { |
| | 0 | 77 | | if (i == clusterIndex2) { |
| | 0 | 78 | | continue; |
| | | 79 | | } |
| | 0 | 80 | | float distance = Vector3.Distance(clusters[clusterIndex2].Centroid, clusters[i].Centroid); |
| | 0 | 81 | | if (i < clusterIndex2) { |
| | 0 | 82 | | distances[clusterIndex2, i] = distance; |
| | 0 | 83 | | } else if (i > clusterIndex2) { |
| | 0 | 84 | | distances[i, clusterIndex2] = distance; |
| | 0 | 85 | | } |
| | 0 | 86 | | } |
| | 0 | 87 | | } |
| | | 88 | | |
| | | 89 | | // Select only the valid clusters. |
| | 0 | 90 | | for (int i = clusters.Count - 1; i >= 0; --i) { |
| | 0 | 91 | | if (!validClusterIndices.Contains(i)) { |
| | 0 | 92 | | clusters.RemoveAt(i); |
| | 0 | 93 | | } |
| | 0 | 94 | | } |
| | 0 | 95 | | return clusters; |
| | 0 | 96 | | } |
| | | 97 | | } |