/* * @author Valentin Simonov / http://va.lent.in/ */ using System.Collections.Generic; using TouchScript.Utils; using TouchScript.Pointers; using UnityEngine; namespace TouchScript.Clusters { /// /// Represents a pool of points separated into two clusters. /// public sealed class Clusters2D { #region Constants /// /// The first cluster. /// public const int CLUSTER1 = 0; /// /// The second cluster. /// public const int CLUSTER2 = 1; #endregion #region Public properties /// /// Gets the total number of points in clusters represented by this object. /// public int PointsCount { get { return points.Count; } } /// /// Gets or sets minimum distance in pixels between clusters to treat them as two separate clusters. /// /// Minimum distance in pixels which must be between cluster centers to consider them as separate clusters. /// This value is used to set the limit of how close cluster can be. Sometimes very close points shouldn't be treated as being in separate clusters. public float MinPointsDistance { get { return minPointDistance; } set { minPointDistance = value; minPointDistanceSqr = value * value; } } /// /// Indicates that this cluster instance has two valid clusters. /// /// true if this instance has clusters; otherwise, false. public bool HasClusters { get { if (dirty) distributePoints(); return hasClusters; } } #endregion #region Private variables private List points = new List(); private bool dirty; private List cluster1 = new List(); private List cluster2 = new List(); private float minPointDistance, minPointDistanceSqr; private bool hasClusters = false; #endregion /// /// Initializes a new instance of the class. /// public Clusters2D() { MinPointsDistance = 0; markDirty(); } #region Public methods /// /// Calculates the center position of one of the clusters. /// /// Cluster id. Either or . /// Cluster's centroid position or if cluster contains no pointers. public Vector2 GetCenterPosition(int id) { if (!HasClusters) return TouchManager.INVALID_POSITION; Vector2 result; switch (id) { case CLUSTER1: result = ClusterUtils.Get2DCenterPosition(cluster1); break; case CLUSTER2: result = ClusterUtils.Get2DCenterPosition(cluster2); break; default: return TouchManager.INVALID_POSITION; } return result; } /// /// Calculates previous center position of one of the clusters. /// /// Cluster id. Either or . /// Cluster's centroid previous position or if cluster contains no pointers. public Vector2 GetPreviousCenterPosition(int id) { if (!HasClusters) return TouchManager.INVALID_POSITION; Vector2 result; switch (id) { case CLUSTER1: result = ClusterUtils.GetPrevious2DCenterPosition(cluster1); break; case CLUSTER2: result = ClusterUtils.GetPrevious2DCenterPosition(cluster2); break; default: return TouchManager.INVALID_POSITION; } return result; } /// /// Adds a pointer to cluster. /// A pointer. public void AddPoint(Pointer pointer) { if (points.Contains(pointer)) return; points.Add(pointer); markDirty(); } /// /// Adds a list of pointers to cluster. /// /// List of pointers. public void AddPoints(IList pointers) { var count = pointers.Count; for (var i = 0; i < count; i++) AddPoint(pointers[i]); } /// /// Removes a pointer from cluster. /// /// A pointer. public void RemovePoint(Pointer pointer) { if (!points.Contains(pointer)) return; points.Remove(pointer); markDirty(); } /// /// Removes a list of pointers from cluster. /// /// List of pointers. public void RemovePoints(IList points) { var count = points.Count; for (var i = 0; i < count; i++) RemovePoint(points[i]); } /// /// Removes all pointers from cluster. /// public void RemoveAllPoints() { points.Clear(); markDirty(); } /// /// Invalidates cluster state. Call this method to recalculate cluster properties. /// public void Invalidate() { markDirty(); } #endregion #region Private functions private void distributePoints() { cluster1.Clear(); cluster2.Clear(); hasClusters = checkClusters(); if (!hasClusters) return; cluster1.Add(points[0]); cluster2.Add(points[1]); var total = points.Count; if (total == 2) return; var oldHash1 = ""; var oldHash2 = ""; var hash1 = "#"; var hash2 = "#"; while (oldHash1 != hash1 || oldHash2 != hash2) { var center1 = ClusterUtils.Get2DCenterPosition(cluster1); var center2 = ClusterUtils.Get2DCenterPosition(cluster2); Pointer obj1 = null; Pointer obj2 = null; // Take most distant points from cluster1 and cluster2 var maxDist1 = -float.MaxValue; var maxDist2 = -float.MaxValue; for (var i = 0; i < total; i++) { var obj = points[i]; var dist = (center1 - obj.Position).sqrMagnitude; if (dist > maxDist2) { maxDist2 = dist; obj2 = obj; } dist = (center2 - obj.Position).sqrMagnitude; if (dist > maxDist1) { maxDist1 = dist; obj1 = obj; } } // If it is the same point it means that this point is too far away from both clusters and has to be in a separate cluster if (obj1 == obj2) { center1 = (center1 + center2) * .5f; center2 = obj2.Position; } else { center1 = obj1.Position; center2 = obj2.Position; } cluster1.Clear(); cluster2.Clear(); for (var i = 0; i < total; i++) { var obj = points[i]; if ((center1 - obj.Position).sqrMagnitude < (center2 - obj.Position).sqrMagnitude) { cluster1.Add(obj); } else { cluster2.Add(obj); } } oldHash1 = hash1; oldHash2 = hash2; hash1 = ClusterUtils.GetPointsHash(cluster1); hash2 = ClusterUtils.GetPointsHash(cluster2); } markClean(); } private bool checkClusters() { var length = points.Count - 1; if (length < 1) return false; if (length == 1) { var p1 = points[0].Position; var p2 = points[1].Position; var dx = p1.x - p2.x; var dy = p1.y - p2.y; if (dx * dx + dy * dy >= minPointDistanceSqr) return true; return false; } for (var i = 0; i < length; i++) { for (var j = i + 1; j <= length; j++) { var p1 = points[i].Position; var p2 = points[j].Position; var dx = p1.x - p2.x; var dy = p1.y - p2.y; if (dx * dx + dy * dy >= minPointDistanceSqr) return true; } } return false; } private void markDirty() { dirty = true; } private void markClean() { dirty = false; } #endregion } }