程序代码:
http://www.codeproject.com/Articles/30535/A-Simple-QuadTree-Implementation-in-C
四叉树:
using System; using System.Drawing; using System.Collections.Generic; using System.Diagnostics; namespace QuadTreeLib { /// <summary> /// A Quadtree is a structure designed to partition space so /// that it's faster to find out what is inside or outside a given /// area. See http://en.wikipedia.org/wiki/Quadtree /// This QuadTree contains items that have an area (RectangleF) /// it will store a reference to the item in the quad /// that is just big enough to hold it. Each quad has a bucket that /// contain multiple items. /// </summary> public class QuadTree<T> where T : IHasRect { /// <summary> /// The root QuadTreeNode /// 根节点 /// </summary> QuadTreeNode<T> m_root; /// <summary> /// The bounds of this QuadTree /// 四叉树的包围盒,根节点的范围 /// </summary> RectangleF m_rectangle; /// <summary> /// An delegate that performs an action on a QuadTreeNode /// </summary> /// <param name="obj"></param> public delegate void QTAction(QuadTreeNode<T> obj); /// <summary> /// /// </summary> /// <param name="rectangle"></param> public QuadTree(RectangleF rectangle) { m_rectangle = rectangle; m_root = new QuadTreeNode<T>(m_rectangle);//初始化根节点 } /// <summary> /// Get the count of items in the QuadTree /// 四叉树节点的数目 /// </summary> public int Count { get { return m_root.Count; } } /// <summary> /// Insert the feature into the QuadTree /// 插入数据项 /// </summary> /// <param name="item"></param> public void Insert(T item) { m_root.Insert(item);//往四叉树插入数据项,其实是通过根节点插入数据项 } /// <summary> /// Query the QuadTree, returning the items that are in the given area /// 查询四叉树,返回给定区域的数据项 /// </summary> /// <param name="area"></param> /// <returns></returns> public List<T> Query(RectangleF area) { return m_root.Query(area); } /// <summary> /// Do the specified action for each item in the quadtree /// 执行四叉树中特定的行为 /// </summary> /// <param name="action"></param> public void ForEach(QTAction action) { m_root.ForEach(action); } } } QuadTree
四叉树节点:
using System; using System.Collections.Generic; using System.Drawing; using System.Diagnostics; namespace QuadTreeLib { /// <summary> /// The QuadTreeNode /// 四叉树节点 /// </summary> /// <typeparam name="T"></typeparam> public class QuadTreeNode<T> where T : IHasRect { /// <summary> /// Construct a quadtree node with the given bounds /// 根据给定的范围构建四叉树节点 /// </summary> /// <param name="area"></param> public QuadTreeNode(RectangleF bounds) { m_bounds = bounds; } /// <summary> /// The area of this node /// </summary> RectangleF m_bounds; /// <summary> /// The contents of this node. /// Note that the contents have no limit: this is not the standard way to impement a QuadTree /// </summary> List<T> m_contents = new List<T>(); /// <summary> /// The child nodes of the QuadTree /// 四叉树的子节点 /// </summary> List<QuadTreeNode<T>> m_nodes = new List<QuadTreeNode<T>>(4); /// <summary> /// Is the node empty /// </summary> public bool IsEmpty { get { return m_bounds.IsEmpty || m_nodes.Count == 0; } } /// <summary> /// Area of the quadtree node /// 四叉树节点的范围 /// </summary> public RectangleF Bounds { get { return m_bounds; } } /// <summary> /// Total number of nodes in the this node and all SubNodes /// /// </summary> public int Count { get { int count = 0; foreach (QuadTreeNode<T> node in m_nodes) count += node.Count; count += this.Contents.Count; return count; } } /// <summary> /// Return the contents of this node and all subnodes in the true below this one. /// </summary> public List<T> SubTreeContents { get { List<T> results = new List<T>(); foreach (QuadTreeNode<T> node in m_nodes) results.AddRange(node.SubTreeContents); results.AddRange(this.Contents); return results; } } public List<T> Contents { get { return m_contents; } } /// <summary> /// Query the QuadTree for items that are in the given area /// 查询给定范围的数据项 /// </summary> /// <param name="queryArea"></pasram> /// <returns></returns> public List<T> Query(RectangleF queryArea) { // create a list of the items that are found List<T> results = new List<T>(); // this quad contains items that are not entirely contained by // it's four sub-quads. Iterate through the items in this quad // to see if they intersect. foreach (T item in this.Contents) { if (queryArea.IntersectsWith(item.Rectangle)) results.Add(item); } foreach (QuadTreeNode<T> node in m_nodes) { if (node.IsEmpty) continue; // Case 1: search area completely contained by sub-quad // if a node completely contains the query area, go down that branch // and skip the remaining nodes (break this loop) if (node.Bounds.Contains(queryArea)) { results.AddRange(node.Query(queryArea)); break; } // Case 2: Sub-quad completely contained by search area // if the query area completely contains a sub-quad, // just add all the contents of that quad and it's children // to the result set. You need to continue the loop to test // the other quads if (queryArea.Contains(node.Bounds)) { results.AddRange(node.SubTreeContents); continue; } // Case 3: search area intersects with sub-quad // traverse into this quad, continue the loop to search other // quads if (node.Bounds.IntersectsWith(queryArea)) { results.AddRange(node.Query(queryArea)); } } return results; } /// <summary> /// Insert an item to this node /// 将数据项递归插入该四叉树节点 /// </summary> /// <param name="item"></param> public void Insert(T item) { // if the item is not contained in this quad, there's a problem //数据项不在当前四叉树节点范围内,返回 if (!m_bounds.Contains(item.Rectangle)) { Trace.TraceWarning("feature is out of the bounds of this quadtree node"); return; } // if the subnodes are null create them. may not be sucessfull: see below // we may be at the smallest allowed size in which case the subnodes will not be created if (m_nodes.Count == 0) CreateSubNodes();//分割四叉树,将当前节点分为四个子节点 // for each subnode: // if the node contains the item, add the item to that node and return // this recurses into the node that is just large enough to fit this item foreach (QuadTreeNode<T> node in m_nodes) { if (node.Bounds.Contains(item.Rectangle))//四叉树在当前节点范围内,插入 { node.Insert(item); return; } } // if we make it to here, either // 1) none of the subnodes completely contained the item. or // 2) we're at the smallest subnode size allowed // add the item to this node's contents. //考虑这一块,如果所有的子节点都不完全包含本数据项,或者达到了子节点的最小限制,将数据项添加到本节点 this.Contents.Add(item); } //递归遍历本节点的子节点 public void ForEach(QuadTree<T>.QTAction action) { action(this); // draw the child quads foreach (QuadTreeNode<T> node in this.m_nodes) node.ForEach(action); } /// <summary> /// Internal method to create the subnodes (partitions space) /// 私有方法,创建子节点 /// </summary> private void CreateSubNodes() { // the smallest subnode has an area if ((m_bounds.Height * m_bounds.Width) <= 10) return; float halfWidth = (m_bounds.Width / 2f); float halfHeight = (m_bounds.Height / 2f); m_nodes.Add(new QuadTreeNode<T>(new RectangleF(m_bounds.Location, new SizeF(halfWidth, halfHeight)))); m_nodes.Add(new QuadTreeNode<T>(new RectangleF(new PointF(m_bounds.Left, m_bounds.Top + halfHeight), new SizeF(halfWidth, halfHeight)))); m_nodes.Add(new QuadTreeNode<T>(new RectangleF(new PointF(m_bounds.Left + halfWidth, m_bounds.Top), new SizeF(halfWidth, halfHeight)))); m_nodes.Add(new QuadTreeNode<T>(new RectangleF(new PointF(m_bounds.Left + halfWidth, m_bounds.Top + halfHeight), new SizeF(halfWidth, halfHeight)))); } } } QuadTreeNode
数据项,作为T传入:
namespace QuadTreeDemoApp { /// <summary> /// An item with a position, a size and a random colour to test /// the QuadTree structure. /// 数据项 /// </summary> class Item: IHasRect { /// <summary> /// Create an item at the given location with the given size. /// 数据项,在给定的位置构建特定大小的数据项 /// </summary> /// <param name="p"></param> /// <param name="size"></param> public Item(Point p, int size) { m_size = size; m_rectangle = new RectangleF(p.X, p.Y, m_size, m_size); m_color = Utility.RandomColor; } /// <summary> /// Bounds of this item /// 数据项的范围 /// </summary> RectangleF m_rectangle; /// <summary> ///默认大小 /// </summary> int m_size = 2; /// <summary> /// 颜色 /// </summary> Color m_color; /// <summary> /// Colour to draw the item for the QuadTree demo /// </summary> public Color Color { get { return m_color; } } #region IHasRect Members /// <summary> /// The rectangular bounds of this item /// 数据项的范围矩形 /// </summary> public RectangleF Rectangle { get { return m_rectangle; } } #endregion } } Item
包围盒接口:
namespace QuadTreeLib { /// <summary> /// An interface that defines and object with a rectangle /// 接口定义了对象的包围盒 /// </summary> public interface IHasRect { RectangleF Rectangle { get; } } } IHasRect
渲染四叉树:
using System; using System.Collections.Generic; using System.Drawing; using QuadTreeLib; namespace QuadTreeDemoApp { /// <summary> /// Class draws a QuadTree /// 绘制四叉树类 /// </summary> class QuadTreeRenderer { /// <summary> /// Create the renderer, give the QuadTree to render. /// 渲染四叉树 /// </summary> /// <param name="quadTree"></param> public QuadTreeRenderer(QuadTree<Item> quadTree) { m_quadTree = quadTree; } QuadTree<Item> m_quadTree; /// <summary> /// Hashtable contains a colour for every node in the quad tree so that they are /// rendered with a consistant colour. /// </summary> Dictionary<QuadTreeNode<Item>, Color> m_dictionary = new Dictionary<QuadTreeNode<Item>, Color>(); /// <summary> /// Get the colour for a QuadTreeNode from the hash table or else create a new colour /// </summary> /// <param name="node"></param> /// <returns></returns> Color GetColor(QuadTreeNode<Item> node) { if (m_dictionary.ContainsKey(node)) return m_dictionary[node]; Color color = Utility.RandomColor; m_dictionary.Add(node, color); return color; } /// <summary> /// Render the QuadTree into the given Graphics context /// 在给定的图形设备渲染四叉树 /// </summary> /// <param name="graphics"></param> internal void Render(Graphics graphics) { //遍历节点触发委托方法 m_quadTree.ForEach(delegate(QuadTreeNode<Item> node) { // draw the contents of this quad if (node.Contents != null) { foreach (Item item in node.Contents) { using (Brush b = new SolidBrush(item.Color)) graphics.FillEllipse(b, Rectangle.Round(item.Rectangle)); } } // draw this quad // Draw the border //绘制包围盒 Color color = GetColor(node); graphics.DrawRectangle(Pens.Black, Rectangle.Round(node.Bounds)); // draw the inside of the border in a distinct colour using (Pen p = new Pen(color)) { Rectangle inside = Rectangle.Round(node.Bounds); inside.Inflate(-1, -1); graphics.DrawRectangle(p, inside); } }); } } } QuadTreeRenderer
主窗体调用:
public partial class MainForm : Form { QuadTree<Item> m_quadTree; QuadTreeRenderer m_renderer; public MainForm() { InitializeComponent(); } private void MainForm_Load(object sender, EventArgs e) { Init(); } /// <summary> /// Resize the window re-initializes the QuadTree to the new size /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MainForm_Resize(object sender, EventArgs e) { Init(); } /// <summary> /// Draw the QuadTree and the selection rectangle /// Also highlight the selecte items. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MainForm_Paint(object sender, PaintEventArgs e) { // draw the QuadTree m_renderer.Render(e.Graphics); // draw the selection rectangle if (!m_selectionRect.IsEmpty) { // draw the selection rect in semi-transparent yellow using (Brush b = new SolidBrush(Color.FromArgb(128, Color.Yellow))) e.Graphics.FillRectangle(b, Rectangle.Round(m_selectionRect)); } // draw the selected items with a red ring around them if (m_selectedItems != null) { foreach (Item obj in m_selectedItems) { Rectangle selectedRect = Rectangle.Round(obj.Rectangle); selectedRect.Inflate(1, 1); using (Pen p = new Pen(Color.Red, 2)) e.Graphics.DrawEllipse(p, selectedRect); } } } /// <summary> /// Initialize the QuadTree to the size of the window. /// Initialize the QuadTreeRenderer /// </summary> private void Init() { m_quadTree = new QuadTree<Item>(this.ClientRectangle);//构造客户区范围大小的四叉树 m_renderer = new QuadTreeRenderer(m_quadTree); } #region mouse interaction code bool m_dragging = false; RectangleF m_selectionRect; Point m_startPoint; List<Item> m_selectedItems; /// <summary> /// MouseUp: /// - if you're using the left mouse button insert a new item into /// the QuadTree at the click point /// - if you're dragging with the right mouse button, query the /// QuadTree with the selection rectangle defined by the current /// point and the point when the mouseDown event happened. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MainForm_MouseUp(object sender, MouseEventArgs e) { if (m_dragging && e.Button== MouseButtons.Right) { m_selectedItems = m_quadTree.Query(m_selectionRect); m_dragging = false; } else { Random rand = new Random(DateTime.Now.Millisecond); m_quadTree.Insert(new Item(e.Location, rand.Next(25) + 4)); } Invalidate(); } /// <summary> /// If the it's a right click, record the start point and start drag operation /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MainForm_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) { m_dragging = true; m_startPoint = e.Location; } } /// <summary> /// MouseMove: if we're dragging the update the area of the selection /// rectangle using the start point and the current point. /// Invalidate causes the form to redraw. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MainForm_MouseMove(object sender, MouseEventArgs e) { if (m_dragging) { m_selectionRect = RectangleF.FromLTRB( Math.Min(e.Location.X, m_startPoint.X), Math.Min(e.Location.Y, m_startPoint.Y), Math.Max(e.Location.X, m_startPoint.X), Math.Max(e.Location.Y, m_startPoint.Y)); Invalidate(); } } #endregion } MainForm
运行结果: