using System; using System.Collections.Generic; using System.Linq; using Unity.UIWidgets.ui; namespace Unity.UIWidgets.external { public interface ISpatialData { uiRect bounds { get; } } public class IndexedRect : ISpatialData { private uiRect _bounds; public uiRect bounds { get { return _bounds; } } public readonly int index; public IndexedRect(uiRect bounds, int index) { this._bounds = bounds; this.index = index; } } /// /// Non-generic class to produce instances of the generic class, /// optionally using type inference. /// public static class ProjectionComparer { /// /// Creates an instance of ProjectionComparer using the specified projection. /// /// Type parameter for the elements to be compared /// Type parameter for the keys to be compared, after being projected from the elements /// Projection to use when determining the key of an element /// A comparer which will compare elements by projecting each element to its key, and comparing keys public static ProjectionComparer Create(Func projection) { return new ProjectionComparer(projection); } /// /// Creates an instance of ProjectionComparer using the specified projection. /// The ignored parameter is solely present to aid type inference. /// /// Type parameter for the elements to be compared /// Type parameter for the keys to be compared, after being projected from the elements /// Value is ignored - type may be used by type inference /// Projection to use when determining the key of an element /// A comparer which will compare elements by projecting each element to its key, and comparing keys public static ProjectionComparer Create (TSource ignored, Func projection) { return new ProjectionComparer(projection); } } /// /// Class generic in the source only to produce instances of the /// doubly generic class, optionally using type inference. /// public static class ProjectionComparer { /// /// Creates an instance of ProjectionComparer using the specified projection. /// /// Type parameter for the keys to be compared, after being projected from the elements /// Projection to use when determining the key of an element /// A comparer which will compare elements by projecting each element to its key, and comparing keys public static ProjectionComparer Create(Func projection) { return new ProjectionComparer(projection); } } /// /// Comparer which projects each element of the comparison to a key, and then compares /// those keys using the specified (or default) comparer for the key type. /// /// Type of elements which this comparer will be asked to compare /// Type of the key projected from the element public class ProjectionComparer : IComparer { private readonly IComparer comparer; private readonly Func projection; /// /// Creates a new instance using the specified projection, which must not be null. /// The default comparer for the projected type is used. /// /// Projection to use during comparisons public ProjectionComparer(Func projection) : this(projection, null) { } /// /// Creates a new instance using the specified projection, which must not be null. /// /// Projection to use during comparisons /// /// The comparer to use on the keys. May be null, in /// which case the default comparer will be used. /// public ProjectionComparer(Func projection, IComparer comparer) { this.comparer = comparer ?? Comparer.Default; this.projection = projection; } /// /// Compares x and y by projecting them to keys and then comparing the keys. /// Null values are not projected; they obey the /// standard comparer contract such that two null values are equal; any null value is /// less than any non-null value. /// public int Compare(TSource x, TSource y) { // Don't want to project from nullity if (x == null && y == null) return 0; if (x == null) return -1; if (y == null) return 1; return comparer.Compare(projection(x), projection(y)); } } public interface BBoxHierarchy where T : ISpatialData { IReadOnlyList Search(in uiRect boundingBox); void BulkLoad(IEnumerable items); void Insert(T data); void Clear(); } public class RTree : BBoxHierarchy where T : ISpatialData { public class RTreeNode : ISpatialData { internal readonly List children; private uiRect _Rect; internal RTreeNode(List items, int height) { Height = height; children = items; ResetRect(); } public IReadOnlyList Children => children; public int Height { get; } public bool IsLeaf => Height == 1; public uiRect bounds => _Rect; internal void Add(ISpatialData node) { children.Add(node); _Rect = bounds.expandToInclude(node.bounds); } internal void Remove(ISpatialData node) { children.Remove(node); ResetRect(); } internal void RemoveRange(int index, int count) { children.RemoveRange(index, count); ResetRect(); } internal void ResetRect() { _Rect = GetEnclosingRect(children); } } #region Search private List DoSearch(in uiRect boundingBox) { if (!uiRectHelper.overlaps(Root.bounds, boundingBox)) return new List(); var intersections = new List(); var queue = new Queue(); queue.Enqueue(Root); while (queue.Count != 0) { var item = queue.Dequeue(); if (item.IsLeaf) { foreach (var leafChildItem in item.children.Cast()) if (uiRectHelper.overlaps(leafChildItem.bounds, boundingBox)) intersections.Add(leafChildItem); } else { foreach (var child in item.children.Cast()) if (uiRectHelper.overlaps(child.bounds, boundingBox)) queue.Enqueue(child); } } return intersections; } #endregion private static uiRect GetEnclosingRect(IEnumerable items) { var uiRect = uiRectHelper.zero; foreach (var data in items) uiRect = uiRect.expandToInclude(data.bounds); return uiRect; } private List GetAllChildren(List list, RTreeNode n) { if (n.IsLeaf) list.AddRange( n.children.Cast()); else foreach (var node in n.children.Cast()) GetAllChildren(list, node); return list; } #region Sort Functions private static readonly IComparer CompareMinX = ProjectionComparer.Create(d => d.bounds.left); private static readonly IComparer CompareMinY = ProjectionComparer.Create(d => d.bounds.top); #endregion #region Insert private List FindCoveringArea(in uiRect area, int depth) { var path = new List(); var node = Root; var _area = area; //FIX CS1628 while (true) { path.Add(node); if (node.IsLeaf || path.Count == depth) return path; node = node.children .Select(c => new {EnlargedArea = c.bounds.expandToInclude(_area).area, c.bounds.area, Node = c as RTreeNode}) .OrderBy(x => x.EnlargedArea) .ThenBy(x => x.area) .Select(x => x.Node) .First(); } } private void Insert(ISpatialData data, int depth) { var path = FindCoveringArea(data.bounds, depth); var insertNode = path.Last(); insertNode.Add(data); while (--depth >= 0) if (path[depth].children.Count > maxEntries) { var newNode = SplitNode(path[depth]); if (depth == 0) SplitRoot(newNode); else path[depth - 1].Add(newNode); } else { path[depth].ResetRect(); } } #region SplitNode private void SplitRoot(RTreeNode newRTreeNode) { Root = new RTreeNode(new List {Root, newRTreeNode}, Root.Height + 1); } private RTreeNode SplitNode(RTreeNode rTreeNode) { SortChildren(rTreeNode); var splitPoint = GetBestSplitIndex(rTreeNode.children); var newChildren = rTreeNode.children.Skip(splitPoint).ToList(); rTreeNode.RemoveRange(splitPoint, rTreeNode.children.Count - splitPoint); return new RTreeNode(newChildren, rTreeNode.Height); } #region SortChildren private void SortChildren(RTreeNode rTreeNode) { rTreeNode.children.Sort(CompareMinX); var splitsByX = GetPotentialSplitMargins(rTreeNode.children); rTreeNode.children.Sort(CompareMinY); var splitsByY = GetPotentialSplitMargins(rTreeNode.children); if (splitsByX < splitsByY) rTreeNode.children.Sort(CompareMinX); } private float GetPotentialSplitMargins(List children) { return GetPotentialEnclosingMargins(children) + GetPotentialEnclosingMargins(children.AsEnumerable().Reverse().ToList()); } private float GetPotentialEnclosingMargins(List children) { var uiRect = uiRectHelper.zero; var i = 0; for (; i < minEntries; i++) uiRect = uiRect.expandToInclude(children[i].bounds); var totalMargin = uiRect.margin; for (; i < children.Count - minEntries; i++) { uiRect = uiRect.expandToInclude(children[i].bounds); totalMargin += uiRect.margin; } return totalMargin; } #endregion private int GetBestSplitIndex(List children) { return Enumerable.Range(minEntries, children.Count - minEntries) .Select(i => { var leftRect = GetEnclosingRect(children.Take(i)); var rightRect = GetEnclosingRect(children.Skip(i)); var overlap = leftRect.intersect(rightRect).area; var totalArea = leftRect.area + rightRect.area; return new {i, overlap, totalArea}; }) .OrderBy(x => x.overlap) .ThenBy(x => x.totalArea) .Select(x => x.i) .First(); } #endregion #endregion #region BuildTree private RTreeNode BuildTree(List data) { var treeHeight = GetDepth(data.Count); var rootMaxEntries = (int) Math.Ceiling(data.Count / Math.Pow(maxEntries, treeHeight - 1)); return BuildNodes(data, 0, data.Count - 1, treeHeight, rootMaxEntries); } private int GetDepth(int numNodes) { return (int) Math.Ceiling(Math.Log(numNodes) / Math.Log(maxEntries)); } private RTreeNode BuildNodes(List data, int left, int right, int height, int maxEntries) { var num = right - left + 1; if (num <= maxEntries) return height == 1 ? new RTreeNode(data.GetRange(left, num), height) : new RTreeNode( new List { BuildNodes(data, left, right, height - 1, this.maxEntries) }, height); data.Sort(left, num, CompareMinX); var nodeSize = (num + (maxEntries - 1)) / maxEntries; var subSortLength = nodeSize * (int) Math.Ceiling(Math.Sqrt(maxEntries)); var children = new List(maxEntries); for (var subCounter = left; subCounter <= right; subCounter += subSortLength) { var subRight = Math.Min(subCounter + subSortLength - 1, right); data.Sort(subCounter, subRight - subCounter + 1, CompareMinY); for (var nodeCounter = subCounter; nodeCounter <= subRight; nodeCounter += nodeSize) children.Add( BuildNodes( data, nodeCounter, Math.Min(nodeCounter + nodeSize - 1, subRight), height - 1, this.maxEntries)); } return new RTreeNode(children, height); } #endregion private const int DefaultMaxEntries = 9; private const int MinimumMaxEntries = 4; private const int MinimumMinEntries = 2; private const float DefaultFillFactor = 0.4f; private readonly EqualityComparer comparer; private readonly int maxEntries; private readonly int minEntries; public RTree() : this(DefaultMaxEntries) { } public RTree(int maxEntries) : this(maxEntries, EqualityComparer.Default) { } public RTree(int maxEntries, EqualityComparer comparer) { this.comparer = comparer; this.maxEntries = Math.Max(MinimumMaxEntries, maxEntries); minEntries = Math.Max(MinimumMinEntries, (int) Math.Ceiling(this.maxEntries * DefaultFillFactor)); Clear(); } public RTreeNode Root { get; private set; } public uiRect uiRect => Root.bounds; public int Count { get; private set; } public void Clear() { Root = new RTreeNode(new List(), 1); Count = 0; } public IReadOnlyList Search() { return GetAllChildren(new List(), Root); } public IReadOnlyList Search(in uiRect boundingBox) { return DoSearch(boundingBox); } public void Insert(T item) { Insert(item, Root.Height); Count++; } public void BulkLoad(IEnumerable items) { var data = items.Cast().ToList(); if (data.Count == 0) return; if (Root.IsLeaf && Root.children.Count + data.Count < maxEntries) { foreach (var i in data) Insert((T) i); return; } if (data.Count < minEntries) { foreach (var i in data) Insert((T) i); return; } var dataRoot = BuildTree(data); Count += data.Count; if (Root.children.Count == 0) { Root = dataRoot; } else if (Root.Height == dataRoot.Height) { if (Root.children.Count + dataRoot.children.Count <= maxEntries) foreach (var isd in dataRoot.children) Root.Add(isd); else SplitRoot(dataRoot); } else { if (Root.Height < dataRoot.Height) { var tmp = Root; Root = dataRoot; dataRoot = tmp; } Insert(dataRoot, Root.Height - dataRoot.Height); } } } }