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);
}
}
}
}