1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
4 namespace System.Activities.Presentation.View
7 using System.Collections.Generic;
8 using System.Diagnostics;
13 using System.Windows.Controls;
14 using System.Windows.Shapes;
15 using System.Windows.Media;
21 /// This class efficiently stores and retrieves arbitrarily sized and positioned
22 /// objects in a quad-tree data structure. This can be used to do efficient hit
23 /// detection or visiblility checks on objects in a virtualized canvas.
24 /// The object does not need to implement any special interface because the Rect Bounds
25 /// of those objects is handled as a separate argument to Insert.
27 class QuadTree<T> where T : class
29 Rect bounds; // overall bounds we are indexing.
31 IDictionary<T, Quadrant> table;
36 /// This determines the overall quad-tree indexing strategy, changing this bounds
37 /// is expensive since it has to re-divide the entire thing - like a re-hash operation.
41 get { return this.bounds; }
42 set { this.bounds = value; ReIndex(); }
46 /// Insert a node with given bounds into this QuadTree.
48 /// <param name="node">The node to insert</param>
49 /// <param name="bounds">The bounds of this node</param>
50 public void Insert(T node, Rect bounds)
52 if (this.bounds.Width == 0 || this.bounds.Height == 0)
54 throw FxTrace.Exception.AsError(new ArgumentException(SR.BoundsMustBeNonZero));
56 if (bounds.Width == 0 || bounds.Height == 0)
58 throw FxTrace.Exception.AsError(new ArgumentException(SR.BoundsMustBeNonZero));
60 if (this.root == null)
62 this.root = new Quadrant(null, this.bounds);
65 Quadrant parent = this.root.Insert(node, bounds);
67 if (this.table == null)
69 this.table = new Dictionary<T, Quadrant>();
71 this.table[node] = parent;
77 /// Get a list of the nodes that intersect the given bounds.
79 /// <param name="bounds">The bounds to test</param>
80 /// <returns>List of zero or mode nodes found inside the given bounds</returns>
81 public IEnumerable<T> GetNodesInside(Rect bounds)
83 foreach (QuadNode n in GetNodes(bounds))
90 /// Get a list of the nodes that intersect the given bounds.
92 /// <param name="bounds">The bounds to test</param>
93 /// <returns>List of zero or mode nodes found inside the given bounds</returns>
94 public bool HasNodesInside(Rect bounds)
96 if (this.root == null)
100 return this.root.HasIntersectingNodes(bounds);
104 /// Get list of nodes that intersect the given bounds.
106 /// <param name="bounds">The bounds to test</param>
107 /// <returns>The list of nodes intersecting the given bounds</returns>
108 IEnumerable<QuadNode> GetNodes(Rect bounds)
110 List<QuadNode> result = new List<QuadNode>();
111 if (this.root != null)
113 this.root.GetIntersectingNodes(result, bounds);
119 /// Remove the given node from this QuadTree.
121 /// <param name="node">The node to remove</param>
122 /// <returns>True if the node was found and removed.</returns>
123 public bool Remove(T node)
125 if (this.table != null)
127 Quadrant parent = null;
128 if (this.table.TryGetValue(node, out parent))
130 parent.RemoveNode(node);
131 this.table.Remove(node);
139 /// Rebuild all the Quadrants according to the current QuadTree Bounds.
144 foreach (QuadNode n in GetNodes(this.bounds))
146 Insert(n.Node, n.Bounds);
151 /// Each node stored in the tree has a position, width & height.
153 internal class QuadNode
156 QuadNode next; // linked in a circular list.
157 T node; // the actual visual object being stored here.
160 /// Construct new QuadNode to wrap the given node with given bounds
162 /// <param name="node">The node</param>
163 /// <param name="bounds">The bounds of that node</param>
164 public QuadNode(T node, Rect bounds)
167 this.bounds = bounds;
175 get { return this.node; }
176 set { this.node = value; }
180 /// The Rect bounds of the node
184 get { return this.bounds; }
188 /// QuadNodes form a linked list in the Quadrant.
192 get { return this.next; }
193 set { this.next = value; }
199 /// The canvas is split up into four Quadrants and objects are stored in the quadrant that contains them
200 /// and each quadrant is split up into four child Quadrants recurrsively. Objects that overlap more than
201 /// one quadrant are stored in the this.nodes list for this Quadrant.
203 internal class Quadrant
206 Rect bounds; // quadrant bounds.
208 QuadNode nodes; // nodes that overlap the sub quadrant boundaries.
210 // The quadrant is subdivided when nodes are inserted that are
211 // completely contained within those subdivisions.
215 Quadrant bottomRight;
218 public void ShowQuadTree(Canvas c)
220 Rectangle r = new Rectangle();
221 r.Width = this.bounds.Width;
222 r.Height = this.bounds.Height;
223 Canvas.SetLeft(r, this.bounds.Left);
224 Canvas.SetTop(r, this.bounds.Top);
225 r.Stroke = Brushes.DarkRed;
226 r.StrokeThickness = 1;
227 r.StrokeDashArray = new DoubleCollection(new double[] { 2.0, 3.0 });
230 if (this.topLeft != null) this.topLeft.ShowQuadTree(c);
231 if (this.topRight != null) this.topRight.ShowQuadTree(c);
232 if (this.bottomLeft != null) this.bottomLeft.ShowQuadTree(c);
233 if (this.bottomRight != null) this.bottomRight.ShowQuadTree(c);
236 public void Dump(LogWriter w)
238 w.WriteAttribute("Bounds", this.bounds.ToString());
239 if (this.nodes != null)
241 QuadNode n = this.nodes;
244 n = n.Next; // first node.
246 w.WriteAttribute("Bounds", n.Bounds.ToString());
248 } while (n != this.nodes);
250 DumpQuadrant("TopLeft", this.topLeft, w);
251 DumpQuadrant("TopRight", this.topRight, w);
252 DumpQuadrant("BottomLeft", this.bottomLeft, w);
253 DumpQuadrant("BottomRight", this.bottomRight, w);
256 public void DumpQuadrant(string label, Quadrant q, LogWriter w)
261 w.WriteAttribute("Name", label);
269 /// Construct new Quadrant with a given bounds all nodes stored inside this quadrant
270 /// will fit inside this bounds.
272 /// <param name="parent">The parent quadrant (if any)</param>
273 /// <param name="bounds">The bounds of this quadrant</param>
274 public Quadrant(Quadrant parent, Rect bounds)
276 this.parent = parent;
277 Fx.Assert(bounds.Width != 0 && bounds.Height != 0, "Cannot have empty bound");
278 if (bounds.Width == 0 || bounds.Height == 0)
280 throw FxTrace.Exception.AsError(new ArgumentException(SR.BoundsMustBeNonZero));
282 this.bounds = bounds;
286 /// The parent Quadrant or null if this is the root
288 internal Quadrant Parent
290 get { return this.parent; }
294 /// The bounds of this quadrant
298 get { return this.bounds; }
302 /// Insert the given node
304 /// <param name="node">The node </param>
305 /// <param name="bounds">The bounds of that node</param>
306 /// <returns></returns>
307 internal Quadrant Insert(T node, Rect bounds)
310 Fx.Assert(bounds.Width != 0 && bounds.Height != 0, "Cannot have empty bound");
311 if (bounds.Width == 0 || bounds.Height == 0)
313 throw FxTrace.Exception.AsError(new ArgumentException(SR.BoundsMustBeNonZero));
316 Quadrant toInsert = this;
319 double w = toInsert.bounds.Width / 2;
324 double h = toInsert.bounds.Height / 2;
330 // assumption that the Rect struct is almost as fast as doing the operations
331 // manually since Rect is a value type.
333 Rect topLeft = new Rect(toInsert.bounds.Left, toInsert.bounds.Top, w, h);
334 Rect topRight = new Rect(toInsert.bounds.Left + w, toInsert.bounds.Top, w, h);
335 Rect bottomLeft = new Rect(toInsert.bounds.Left, toInsert.bounds.Top + h, w, h);
336 Rect bottomRight = new Rect(toInsert.bounds.Left + w, toInsert.bounds.Top + h, w, h);
338 Quadrant child = null;
340 // See if any child quadrants completely contain this node.
341 if (topLeft.Contains(bounds))
343 if (toInsert.topLeft == null)
345 toInsert.topLeft = new Quadrant(toInsert, topLeft);
347 child = toInsert.topLeft;
349 else if (topRight.Contains(bounds))
351 if (toInsert.topRight == null)
353 toInsert.topRight = new Quadrant(toInsert, topRight);
355 child = toInsert.topRight;
357 else if (bottomLeft.Contains(bounds))
359 if (toInsert.bottomLeft == null)
361 toInsert.bottomLeft = new Quadrant(toInsert, bottomLeft);
363 child = toInsert.bottomLeft;
365 else if (bottomRight.Contains(bounds))
367 if (toInsert.bottomRight == null)
369 toInsert.bottomRight = new Quadrant(toInsert, bottomRight);
371 child = toInsert.bottomRight;
380 QuadNode n = new QuadNode(node, bounds);
381 if (toInsert.nodes == null)
387 // link up in circular link list.
388 QuadNode x = toInsert.nodes;
399 /// Returns all nodes in this quadrant that intersect the given bounds.
400 /// The nodes are returned in pretty much random order as far as the caller is concerned.
402 /// <param name="nodes">List of nodes found in the given bounds</param>
403 /// <param name="bounds">The bounds that contains the nodes you want returned</param>
404 internal void GetIntersectingNodes(List<QuadNode> nodes, Rect bounds)
406 if (bounds.IsEmpty) return;
407 double w = this.bounds.Width / 2;
408 double h = this.bounds.Height / 2;
410 // assumption that the Rect struct is almost as fast as doing the operations
411 // manually since Rect is a value type.
413 Rect topLeft = new Rect(this.bounds.Left, this.bounds.Top, w, h);
414 Rect topRight = new Rect(this.bounds.Left + w, this.bounds.Top, w, h);
415 Rect bottomLeft = new Rect(this.bounds.Left, this.bounds.Top + h, w, h);
416 Rect bottomRight = new Rect(this.bounds.Left + w, this.bounds.Top + h, w, h);
418 // See if any child quadrants completely contain this node.
419 if (topLeft.IntersectsWith(bounds) && this.topLeft != null)
421 this.topLeft.GetIntersectingNodes(nodes, bounds);
424 if (topRight.IntersectsWith(bounds) && this.topRight != null)
426 this.topRight.GetIntersectingNodes(nodes, bounds);
429 if (bottomLeft.IntersectsWith(bounds) && this.bottomLeft != null)
431 this.bottomLeft.GetIntersectingNodes(nodes, bounds);
434 if (bottomRight.IntersectsWith(bounds) && this.bottomRight != null)
436 this.bottomRight.GetIntersectingNodes(nodes, bounds);
439 GetIntersectingNodes(this.nodes, nodes, bounds);
443 /// Walk the given linked list of QuadNodes and check them against the given bounds.
444 /// Add all nodes that intersect the bounds in to the list.
446 /// <param name="last">The last QuadNode in a circularly linked list</param>
447 /// <param name="nodes">The resulting nodes are added to this list</param>
448 /// <param name="bounds">The bounds to test against each node</param>
449 static void GetIntersectingNodes(QuadNode last, List<QuadNode> nodes, Rect bounds)
456 n = n.Next; // first node.
457 if (n.Bounds.IntersectsWith(bounds))
466 /// Return true if there are any nodes in this Quadrant that intersect the given bounds.
468 /// <param name="bounds">The bounds to test</param>
469 /// <returns>boolean</returns>
470 internal bool HasIntersectingNodes(Rect bounds)
472 if (bounds.IsEmpty) return false;
473 double w = this.bounds.Width / 2;
474 double h = this.bounds.Height / 2;
476 // assumption that the Rect struct is almost as fast as doing the operations
477 // manually since Rect is a value type.
479 Rect topLeft = new Rect(this.bounds.Left, this.bounds.Top, w, h);
480 Rect topRight = new Rect(this.bounds.Left + w, this.bounds.Top, w, h);
481 Rect bottomLeft = new Rect(this.bounds.Left, this.bounds.Top + h, w, h);
482 Rect bottomRight = new Rect(this.bounds.Left + w, this.bounds.Top + h, w, h);
486 // See if any child quadrants completely contain this node.
487 if (topLeft.IntersectsWith(bounds) && this.topLeft != null)
489 found = this.topLeft.HasIntersectingNodes(bounds);
492 if (!found && topRight.IntersectsWith(bounds) && this.topRight != null)
494 found = this.topRight.HasIntersectingNodes(bounds);
497 if (!found && bottomLeft.IntersectsWith(bounds) && this.bottomLeft != null)
499 found = this.bottomLeft.HasIntersectingNodes(bounds);
502 if (!found && bottomRight.IntersectsWith(bounds) && this.bottomRight != null)
504 found = this.bottomRight.HasIntersectingNodes(bounds);
508 found = HasIntersectingNodes(this.nodes, bounds);
514 /// Walk the given linked list and test each node against the given bounds/
516 /// <param name="last">The last node in the circularly linked list.</param>
517 /// <param name="bounds">Bounds to test</param>
518 /// <returns>Return true if a node in the list intersects the bounds</returns>
519 static bool HasIntersectingNodes(QuadNode last, Rect bounds)
526 n = n.Next; // first node.
527 if (n.Bounds.IntersectsWith(bounds))
537 /// Remove the given node from this Quadrant.
539 /// <param name="node">The node to remove</param>
540 /// <returns>Returns true if the node was found and removed.</returns>
541 internal bool RemoveNode(T node)
544 if (this.nodes != null)
546 QuadNode p = this.nodes;
547 while (p.Next.Node != node && p.Next != this.nodes)
551 if (p.Next.Node == node)
557 // list goes to empty
562 if (this.nodes == n) this.nodes = p;
572 public void ShowQuadTree(Canvas container)
574 if (this.root != null)
576 this.root.ShowQuadTree(container);
580 public void Dump(LogWriter w)
582 if (this.root != null)
591 public class LogWriter : IDisposable
597 public LogWriter(TextWriter w)
599 XmlWriterSettings s = new XmlWriterSettings();
601 this.xw = XmlWriter.Create(w, s);
608 return this.maxdepth;
612 public void Open(string label)
614 this.xw.WriteStartElement(label);
616 if (this.indent > this.maxdepth) this.maxdepth = this.indent;
622 this.xw.WriteEndElement();
624 public void WriteAttribute(string name, string value)
626 this.xw.WriteAttributeString(name, value);
629 #region IDisposable Members
631 public void Dispose()
634 GC.SuppressFinalize(this);
637 protected virtual void Dispose(bool disposing)
639 if (disposing && this.xw != null)