程序代码:
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>>(); /// <summary>
/// Is the node empty
/// </summary>
public bool IsEmpty { get { return m_bounds.IsEmpty || m_nodes.Count == ; } } /// <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 = ; 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 == )
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) <= )
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 = ; /// <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(-, -);
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(, 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(, );
using (Pen p = new Pen(Color.Red, ))
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() + ));
} 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
运行结果: