201 lines
6.5 KiB
C#
201 lines
6.5 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Text;
|
|||
|
using UnityEngine;
|
|||
|
|
|||
|
namespace DigitalOpus.MB.Core{
|
|||
|
public class MB3_KMeansClustering {
|
|||
|
|
|||
|
class DataPoint
|
|||
|
{
|
|||
|
public Vector3 center;
|
|||
|
public GameObject gameObject;
|
|||
|
public int Cluster;
|
|||
|
|
|||
|
public DataPoint(GameObject go)
|
|||
|
{
|
|||
|
gameObject = go;
|
|||
|
center = go.transform.position;
|
|||
|
if (go.GetComponent<Renderer>() == null) Debug.LogError("Object does not have a renderer " + go);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
List<DataPoint> _normalizedDataToCluster = new List<DataPoint>();
|
|||
|
Vector3[] _clusters = new Vector3[0];
|
|||
|
private int _numberOfClusters = 0;
|
|||
|
|
|||
|
public MB3_KMeansClustering(List<GameObject> gos, int numClusters)
|
|||
|
{
|
|||
|
for (int i = 0; i < gos.Count; i++)
|
|||
|
{
|
|||
|
if (gos[i] != null)
|
|||
|
{
|
|||
|
DataPoint dp = new DataPoint(gos[i]);
|
|||
|
_normalizedDataToCluster.Add(dp);
|
|||
|
} else
|
|||
|
{
|
|||
|
Debug.LogWarning(String.Format("Object {0} in list of objects to cluster was null.", i));
|
|||
|
}
|
|||
|
}
|
|||
|
if (numClusters <= 0)
|
|||
|
{
|
|||
|
Debug.LogError("Number of clusters must be posititve.");
|
|||
|
numClusters = 1;
|
|||
|
}
|
|||
|
if (_normalizedDataToCluster.Count <= numClusters)
|
|||
|
{
|
|||
|
Debug.LogError("There must be fewer clusters than objects to cluster");
|
|||
|
numClusters = _normalizedDataToCluster.Count - 1;
|
|||
|
}
|
|||
|
_numberOfClusters = numClusters;
|
|||
|
if (_numberOfClusters <= 0) _numberOfClusters = 1;
|
|||
|
|
|||
|
_clusters = new Vector3[_numberOfClusters];
|
|||
|
}
|
|||
|
|
|||
|
private void InitializeCentroids()
|
|||
|
{
|
|||
|
//todo error if more clusters than objs
|
|||
|
for (int i = 0; i < _numberOfClusters; ++i)
|
|||
|
{
|
|||
|
_normalizedDataToCluster[i].Cluster = i;
|
|||
|
}
|
|||
|
for (int i = _numberOfClusters; i < _normalizedDataToCluster.Count; i++)
|
|||
|
{
|
|||
|
_normalizedDataToCluster[i].Cluster = UnityEngine.Random.Range(0, _numberOfClusters);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private bool UpdateDataPointMeans(bool force)
|
|||
|
{
|
|||
|
if (AnyAreEmpty(_normalizedDataToCluster) && !force) return false;
|
|||
|
Vector3[] means = new Vector3[_numberOfClusters];
|
|||
|
int[] numInCluster = new int[_numberOfClusters];
|
|||
|
|
|||
|
for (int i = 0; i < _normalizedDataToCluster.Count; i++)
|
|||
|
{
|
|||
|
int idx = _normalizedDataToCluster[i].Cluster;
|
|||
|
means[idx] += _normalizedDataToCluster[i].center;
|
|||
|
numInCluster[idx]++;
|
|||
|
}
|
|||
|
for (int i = 0; i < _numberOfClusters; i++)
|
|||
|
{
|
|||
|
_clusters[i] = means[i] / numInCluster[i];
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
private bool AnyAreEmpty(List<DataPoint> data)
|
|||
|
{
|
|||
|
int[] numInCluster = new int[_numberOfClusters];
|
|||
|
for (int i = 0; i < _normalizedDataToCluster.Count; i++)
|
|||
|
{
|
|||
|
numInCluster[_normalizedDataToCluster[i].Cluster]++;
|
|||
|
}
|
|||
|
|
|||
|
for (int i = 0; i < numInCluster.Length; i++)
|
|||
|
{
|
|||
|
if (numInCluster[i] == 0)
|
|||
|
{
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
private bool UpdateClusterMembership()
|
|||
|
{
|
|||
|
bool changed = false;
|
|||
|
|
|||
|
float[] distances = new float[_numberOfClusters];
|
|||
|
|
|||
|
for (int i = 0; i < _normalizedDataToCluster.Count; ++i)
|
|||
|
{
|
|||
|
|
|||
|
for (int k = 0; k < _numberOfClusters; ++k)
|
|||
|
{
|
|||
|
distances[k] = ElucidanDistance(_normalizedDataToCluster[i], _clusters[k]);
|
|||
|
}
|
|||
|
int newClusterId = MinIndex(distances);
|
|||
|
if (newClusterId != _normalizedDataToCluster[i].Cluster)
|
|||
|
{
|
|||
|
changed = true;
|
|||
|
_normalizedDataToCluster[i].Cluster = newClusterId;
|
|||
|
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
if (changed == false) return false;
|
|||
|
//if (AnyAreEmpty(_normalizedDataToCluster)) return false;
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
private float ElucidanDistance(DataPoint dataPoint, Vector3 mean)
|
|||
|
{
|
|||
|
return Vector3.Distance(dataPoint.center, mean);
|
|||
|
}
|
|||
|
|
|||
|
private int MinIndex(float[] distances)
|
|||
|
{
|
|||
|
int _indexOfMin = 0;
|
|||
|
double _smallDist = distances[0];
|
|||
|
for (int k = 0; k < distances.Length; ++k)
|
|||
|
{
|
|||
|
if (distances[k] < _smallDist)
|
|||
|
{
|
|||
|
_smallDist = distances[k];
|
|||
|
_indexOfMin = k;
|
|||
|
}
|
|||
|
}
|
|||
|
return _indexOfMin;
|
|||
|
}
|
|||
|
|
|||
|
public List<Renderer> GetCluster(int idx, out Vector3 mean, out float size)
|
|||
|
{
|
|||
|
if (idx < 0 || idx >= _numberOfClusters)
|
|||
|
{
|
|||
|
Debug.LogError("idx is out of bounds");
|
|||
|
mean = Vector3.zero;
|
|||
|
size = 1;
|
|||
|
return new List<Renderer>();
|
|||
|
}
|
|||
|
UpdateDataPointMeans(true);
|
|||
|
List<Renderer> gos = new List<Renderer>();
|
|||
|
mean = _clusters[idx];
|
|||
|
float longestDist = 0;
|
|||
|
for (int i = 0; i < _normalizedDataToCluster.Count; i++)
|
|||
|
{
|
|||
|
if (_normalizedDataToCluster[i].Cluster == idx)
|
|||
|
{
|
|||
|
float dist = Vector3.Distance(mean, _normalizedDataToCluster[i].center);
|
|||
|
if (dist > longestDist) longestDist = dist;
|
|||
|
gos.Add(_normalizedDataToCluster[i].gameObject.GetComponent<Renderer>());
|
|||
|
}
|
|||
|
}
|
|||
|
mean = _clusters[idx];
|
|||
|
size = longestDist; //todo should be greatest distance to mean
|
|||
|
return gos;
|
|||
|
}
|
|||
|
|
|||
|
public void Cluster()
|
|||
|
{
|
|||
|
bool _changed = true;
|
|||
|
bool _success = true;
|
|||
|
InitializeCentroids();
|
|||
|
|
|||
|
int maxIteration = _normalizedDataToCluster.Count * 1000;
|
|||
|
int _threshold = 0;
|
|||
|
while (_success == true && _changed == true && _threshold < maxIteration)
|
|||
|
{
|
|||
|
++_threshold;
|
|||
|
_success = UpdateDataPointMeans(false);
|
|||
|
_changed = UpdateClusterMembership();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|