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 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 m_Entries = new List(); readonly List m_Nodes = new List(); /// /// Whether the tree will be rebuilt on the next query /// public bool dirty { get; internal set; } /// /// Add an IInterval to the tree /// public void Add(T item) { if (item == null) return; m_Entries.Add( new Entry() { intervalStart = item.intervalStart, intervalEnd = item.intervalEnd, item = item } ); dirty = true; } /// /// Query the tree at a particular time /// /// /// public void IntersectsWith(Int64 value, List results) { if (m_Entries.Count == 0) return; if (dirty) { Rebuild(); dirty = false; } if (m_Nodes.Count > 0) Query(m_Nodes[0], value, results); } /// /// Query the tree at a particular range of time /// /// /// /// public void IntersectsWithRange(Int64 start, Int64 end, List 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); } /// /// Updates the intervals from their source. Use this to detect if the data in the tree /// has changed. /// 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 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 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(); } } }