272 lines
8.3 KiB
C#
272 lines
8.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace UnityEngine.Timeline
|
|
{
|
|
interface IInterval
|
|
{
|
|
Int64 intervalStart { get; }
|
|
Int64 intervalEnd { get; }
|
|
}
|
|
|
|
struct IntervalTreeNode // interval node,
|
|
{
|
|
public Int64 center; // midpoint for this node
|
|
public int first; // index of first element of this node in m_Entries
|
|
public int last; // index of the last element of this node in m_Entries
|
|
public int left; // index in m_Nodes of the left subnode
|
|
public int right; // index in m_Nodes of the right subnode
|
|
}
|
|
|
|
class IntervalTree<T> where T : IInterval
|
|
{
|
|
internal struct Entry
|
|
{
|
|
public Int64 intervalStart;
|
|
public Int64 intervalEnd;
|
|
public T item;
|
|
}
|
|
|
|
const int kMinNodeSize = 10; // the minimum number of entries to have subnodes
|
|
const int kInvalidNode = -1;
|
|
const Int64 kCenterUnknown = Int64.MaxValue; // center hasn't been calculated. indicates no children
|
|
|
|
readonly List<Entry> m_Entries = new List<Entry>();
|
|
readonly List<IntervalTreeNode> m_Nodes = new List<IntervalTreeNode>();
|
|
|
|
/// <summary>
|
|
/// Whether the tree will be rebuilt on the next query
|
|
/// </summary>
|
|
public bool dirty { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// Add an IInterval to the tree
|
|
/// </summary>
|
|
public void Add(T item)
|
|
{
|
|
if (item == null)
|
|
return;
|
|
|
|
m_Entries.Add(
|
|
new Entry()
|
|
{
|
|
intervalStart = item.intervalStart,
|
|
intervalEnd = item.intervalEnd,
|
|
item = item
|
|
}
|
|
);
|
|
dirty = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Query the tree at a particular time
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
/// <param name="results"></param>
|
|
public void IntersectsWith(Int64 value, List<T> results)
|
|
{
|
|
if (m_Entries.Count == 0)
|
|
return;
|
|
|
|
if (dirty)
|
|
{
|
|
Rebuild();
|
|
dirty = false;
|
|
}
|
|
|
|
if (m_Nodes.Count > 0)
|
|
Query(m_Nodes[0], value, results);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Query the tree at a particular range of time
|
|
/// </summary>
|
|
/// <param name="start"></param>
|
|
/// <param name="end"></param>
|
|
/// <param name="results"></param>
|
|
public void IntersectsWithRange(Int64 start, Int64 end, List<T> results)
|
|
{
|
|
if (start > end)
|
|
return;
|
|
|
|
if (m_Entries.Count == 0)
|
|
return;
|
|
|
|
if (dirty)
|
|
{
|
|
Rebuild();
|
|
dirty = false;
|
|
}
|
|
|
|
if (m_Nodes.Count > 0)
|
|
QueryRange(m_Nodes[0], start, end, results);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the intervals from their source. Use this to detect if the data in the tree
|
|
/// has changed.
|
|
/// </summary>
|
|
public void UpdateIntervals()
|
|
{
|
|
bool isDirty = false;
|
|
for (int i = 0; i < m_Entries.Count; i++)
|
|
{
|
|
var n = m_Entries[i];
|
|
var s = n.item.intervalStart;
|
|
var e = n.item.intervalEnd;
|
|
|
|
isDirty |= n.intervalStart != s;
|
|
isDirty |= n.intervalEnd != e;
|
|
|
|
m_Entries[i] = new Entry()
|
|
{
|
|
intervalStart = s,
|
|
intervalEnd = e,
|
|
item = n.item
|
|
};
|
|
}
|
|
|
|
dirty |= isDirty;
|
|
}
|
|
|
|
private void Query(IntervalTreeNode intervalTreeNode, Int64 value, List<T> results)
|
|
{
|
|
for (int i = intervalTreeNode.first; i <= intervalTreeNode.last; i++)
|
|
{
|
|
var entry = m_Entries[i];
|
|
if (value >= entry.intervalStart && value < entry.intervalEnd)
|
|
{
|
|
results.Add(entry.item);
|
|
}
|
|
}
|
|
|
|
if (intervalTreeNode.center == kCenterUnknown)
|
|
return;
|
|
if (intervalTreeNode.left != kInvalidNode && value < intervalTreeNode.center)
|
|
Query(m_Nodes[intervalTreeNode.left], value, results);
|
|
if (intervalTreeNode.right != kInvalidNode && value > intervalTreeNode.center)
|
|
Query(m_Nodes[intervalTreeNode.right], value, results);
|
|
}
|
|
|
|
private void QueryRange(IntervalTreeNode intervalTreeNode, Int64 start, Int64 end, List<T> results)
|
|
{
|
|
for (int i = intervalTreeNode.first; i <= intervalTreeNode.last; i++)
|
|
{
|
|
var entry = m_Entries[i];
|
|
if (end >= entry.intervalStart && start < entry.intervalEnd)
|
|
{
|
|
results.Add(entry.item);
|
|
}
|
|
}
|
|
|
|
if (intervalTreeNode.center == kCenterUnknown)
|
|
return;
|
|
if (intervalTreeNode.left != kInvalidNode && start < intervalTreeNode.center)
|
|
QueryRange(m_Nodes[intervalTreeNode.left], start, end, results);
|
|
if (intervalTreeNode.right != kInvalidNode && end > intervalTreeNode.center)
|
|
QueryRange(m_Nodes[intervalTreeNode.right], start, end, results);
|
|
}
|
|
|
|
private void Rebuild()
|
|
{
|
|
m_Nodes.Clear();
|
|
m_Nodes.Capacity = m_Entries.Capacity;
|
|
Rebuild(0, m_Entries.Count - 1);
|
|
}
|
|
|
|
private int Rebuild(int start, int end)
|
|
{
|
|
IntervalTreeNode intervalTreeNode = new IntervalTreeNode();
|
|
|
|
// minimum size, don't subdivide
|
|
int count = end - start + 1;
|
|
if (count < kMinNodeSize)
|
|
{
|
|
intervalTreeNode = new IntervalTreeNode() {center = kCenterUnknown, first = start, last = end, left = kInvalidNode, right = kInvalidNode};
|
|
m_Nodes.Add(intervalTreeNode);
|
|
return m_Nodes.Count - 1;
|
|
}
|
|
|
|
var min = Int64.MaxValue;
|
|
var max = Int64.MinValue;
|
|
|
|
for (int i = start; i <= end; i++)
|
|
{
|
|
var o = m_Entries[i];
|
|
min = Math.Min(min, o.intervalStart);
|
|
max = Math.Max(max, o.intervalEnd);
|
|
}
|
|
|
|
var center = (max + min) / 2;
|
|
intervalTreeNode.center = center;
|
|
|
|
// first pass, put every thing left of center, left
|
|
int x = start;
|
|
int y = end;
|
|
while (true)
|
|
{
|
|
while (x <= end && m_Entries[x].intervalEnd < center)
|
|
x++;
|
|
|
|
while (y >= start && m_Entries[y].intervalEnd >= center)
|
|
y--;
|
|
|
|
if (x > y)
|
|
break;
|
|
|
|
var nodeX = m_Entries[x];
|
|
var nodeY = m_Entries[y];
|
|
|
|
m_Entries[y] = nodeX;
|
|
m_Entries[x] = nodeY;
|
|
}
|
|
|
|
intervalTreeNode.first = x;
|
|
|
|
// second pass, put every start passed the center right
|
|
y = end;
|
|
while (true)
|
|
{
|
|
while (x <= end && m_Entries[x].intervalStart <= center)
|
|
x++;
|
|
|
|
while (y >= start && m_Entries[y].intervalStart > center)
|
|
y--;
|
|
|
|
if (x > y)
|
|
break;
|
|
|
|
var nodeX = m_Entries[x];
|
|
var nodeY = m_Entries[y];
|
|
|
|
m_Entries[y] = nodeX;
|
|
m_Entries[x] = nodeY;
|
|
}
|
|
|
|
intervalTreeNode.last = y;
|
|
|
|
// reserve a place
|
|
m_Nodes.Add(new IntervalTreeNode());
|
|
int index = m_Nodes.Count - 1;
|
|
|
|
intervalTreeNode.left = kInvalidNode;
|
|
intervalTreeNode.right = kInvalidNode;
|
|
|
|
if (start < intervalTreeNode.first)
|
|
intervalTreeNode.left = Rebuild(start, intervalTreeNode.first - 1);
|
|
|
|
if (end > intervalTreeNode.last)
|
|
intervalTreeNode.right = Rebuild(intervalTreeNode.last + 1, end);
|
|
|
|
m_Nodes[index] = intervalTreeNode;
|
|
return index;
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
m_Entries.Clear();
|
|
m_Nodes.Clear();
|
|
}
|
|
}
|
|
}
|