/Addons/WCell.Addons.Terrain/QuadTree.cs
C# | 331 lines | 181 code | 49 blank | 101 comment | 20 complexity | 499f8d92aa858dc3c97f2920156a9659 MD5 | raw file
1using System;
2using System.Collections.Generic;
3using System.Diagnostics;
4using System.Linq;
5using System.Text;
6using WCell.Constants;
7using WCell.Util.Graphics;
8
9namespace WCell.Addons.Terrain
10{
11 public class zzQuadTree<T> where T : IBounded
12 {
13 /// <summary>
14 /// The root zzQuadTreeNode
15 /// </summary>
16 private readonly zzQuadTreeNode<T> m_root;
17
18 /// <summary>
19 /// The bounds of this QuadTree
20 /// </summary>
21 private readonly BoundingBox m_rectangle;
22
23 /// <summary>
24 /// An delegate that performs an action on a zzQuadTreeNode
25 /// </summary>
26 /// <param name="obj"></param>
27 public delegate void QTAction(zzQuadTreeNode<T> obj);
28
29 public zzQuadTree(BoundingBox rectangle)
30 {
31 m_rectangle = rectangle;
32 m_root = new zzQuadTreeNode<T>(m_rectangle);
33 }
34
35 /// <summary>
36 /// Get the count of items in the QuadTree
37 /// </summary>
38 public int Count { get { return m_root.Count; } }
39
40 /// <summary>
41 /// Insert the feature into the QuadTree
42 /// </summary>
43 /// <param name="item"></param>
44 public void Insert(T item)
45 {
46 m_root.Insert(item);
47 }
48
49 /// <summary>
50 /// Query the QuadTree, returning the items that are in the given area
51 /// </summary>
52 /// <param name="area"></param>
53 /// <returns></returns>
54 public List<T> Query(BoundingBox area)
55 {
56 return m_root.Query(area);
57 }
58
59 /// <summary>
60 /// Do the specified action for each item in the quadtree
61 /// </summary>
62 /// <param name="action"></param>
63 public void ForEach(QTAction action)
64 {
65 m_root.ForEach(action);
66 }
67
68 }
69
70 public class zzQuadTreeNode<T> where T : IBounded
71 {
72 /// <summary>
73 /// The contents of this node.
74 /// Note that the contents have no limit: this is not the standard way to impement a QuadTree
75 /// </summary>
76 private readonly List<T> m_contents = new List<T>();
77
78 /// <summary>
79 /// The child nodes of the QuadTree
80 /// </summary>
81 private readonly List<zzQuadTreeNode<T>> m_nodes = new List<zzQuadTreeNode<T>>(4);
82
83 /// <summary>
84 /// Construct a quadtree node with the given bounds
85 /// </summary>
86 public zzQuadTreeNode(BoundingBox bounds)
87 {
88 Bounds = bounds;
89 }
90
91 /// <summary>
92 /// Is the node empty
93 /// </summary>
94 public bool IsEmpty
95 {
96 get
97 {
98 return (m_nodes.Count == 0 || Bounds.Height == 0 || Bounds.Width == 0);
99 }
100 }
101
102 /// <summary>
103 /// Area of the quadtree node
104 /// </summary>
105 public BoundingBox Bounds
106 {
107 get;
108 private set;
109 }
110
111 /// <summary>
112 /// Total number of nodes in this node and all SubNodes
113 /// </summary>
114 public int Count
115 {
116 get
117 {
118 var count = 0;
119
120 foreach (var node in m_nodes)
121 count += node.Count;
122
123 count += Contents.Count;
124
125 return count;
126 }
127 }
128
129 /// <summary>
130 /// Return the contents of this node and all subnodes in the tree below this one.
131 /// </summary>
132 public List<T> SubTreeContents
133 {
134 get
135 {
136 var results = new List<T>();
137
138 foreach (var node in m_nodes)
139 results.AddRange(node.SubTreeContents);
140
141 results.AddRange(this.Contents);
142 return results;
143 }
144 }
145
146 public List<T> Contents { get { return m_contents; } }
147
148 /// <summary>
149 /// Query the QuadTree for items that are in the given area
150 /// </summary>
151 /// <param name="queryArea"></pasram>
152 /// <returns></returns>
153 public List<T> Query(BoundingBox queryArea)
154 {
155 // create a list of the items that are found
156 var results = new List<T>();
157
158 // this quad contains items that are not entirely contained by
159 // it's four sub-quads. Iterate through the items in this quad
160 // to see if they intersect.
161 foreach (var item in Contents)
162 {
163 var bounds = item.Bounds;
164 if (queryArea.Intersects(ref bounds) != IntersectionType.NoIntersection)
165 {
166 results.Add(item);
167 }
168 }
169
170 foreach (var node in m_nodes)
171 {
172 if (node.IsEmpty)
173 continue;
174
175 // Case 1: search area completely contained by sub-quad
176 // if a node completely contains the query area, go down that branch
177 // and skip the remaining nodes (break this loop)
178 if (node.Bounds.Contains(ref queryArea))
179 {
180 results.AddRange(node.Query(queryArea));
181 break;
182 }
183
184 // Case 2: Sub-quad completely contained by search area
185 // if the query area completely contains a sub-quad,
186 // just add all the contents of that quad and it's children
187 // to the result set. You need to continue the loop to test
188 // the other quads
189 var bounds = node.Bounds;
190 if (queryArea.Contains(ref bounds))
191 {
192 results.AddRange(node.SubTreeContents);
193 continue;
194 }
195
196 // Case 3: search area intersects with sub-quad
197 // traverse into this quad, continue the loop to search other
198 // quads
199 if (node.Bounds.Intersects(ref queryArea) == IntersectionType.Intersects)
200 {
201 results.AddRange(node.Query(queryArea));
202 }
203 }
204
205
206 return results;
207 }
208
209 /// <summary>
210 /// Insert an item to this node
211 /// </summary>
212 public void Insert(T item)
213 {
214 // if the item is not contained in this quad, there's a problem
215 var bounds = item.Bounds;
216 if (!Bounds.Contains(ref bounds))
217 {
218 Trace.TraceWarning("feature is out of the bounds of this quadtree node");
219 return;
220 }
221
222 // if the subnodes are null create them. may not be sucessfull: see below
223 // we may be at the smallest allowed size in which case the subnodes will not be created
224 if (m_nodes.Count == 0)
225 CreateSubNodes();
226
227 // for each subnode:
228 // if the node contains the item, add the item to that node and return
229 // this recurses into the node that is just large enough to fit this item
230 foreach (var node in m_nodes)
231 {
232 if (!node.Bounds.Contains(ref bounds)) continue;
233 node.Insert(item);
234 return;
235 }
236
237 // if we make it to here, either
238 // 1) none of the subnodes completely contained the item. or
239 // 2) we're at the smallest subnode size allowed
240 // add the item to this node's contents.
241 Contents.Add(item);
242 }
243
244 /// <summary>
245 /// Remove an item from this node
246 /// </summary>
247 public void Remove(T item)
248 {
249 // if the item is not contained in this quad, there's a problem
250 var bounds = item.Bounds;
251 if (!Bounds.Contains(ref bounds))
252 {
253 Trace.TraceWarning("feature is out of the bounds of this quadtree node");
254 return;
255 }
256
257 // for each subnode:
258 // if the node contains the item, add the item to that node and return
259 // this recurses into the node that is just large enough to fit this item
260 foreach (var node in m_nodes)
261 {
262 if (!node.Bounds.Contains(ref bounds)) continue;
263
264 node.Remove(item);
265 return;
266 }
267
268 // if we make it to here, either
269 // 1) none of the subnodes completely contained the item. or
270 // 2) we're at the smallest subnode size allowed
271 // remove the item from this node's contents.
272 Contents.Remove(item);
273 }
274
275 public void ForEach(zzQuadTree<T>.QTAction action)
276 {
277 action(this);
278
279 // draw the child quads
280 foreach (var node in m_nodes)
281 node.ForEach(action);
282 }
283
284 /// <summary>
285 /// Internal method to create the subnodes (partitions space)
286 /// </summary>
287 private void CreateSubNodes()
288 {
289 // the smallest subnode will be chunk-sized
290 if (Bounds.Height <= TerrainConstants.ChunkSize)
291 return;
292 if (Bounds.Width <= TerrainConstants.ChunkSize)
293 return;
294
295 var halfWidth = (Bounds.Width / 2.0f);
296 var halfHeight = (Bounds.Height / 2.0f);
297
298 var centerPoint = new Vector2(Bounds.Min.X + halfWidth, Bounds.Min.Y + halfHeight);
299 var bottomMiddle = new Vector2(Bounds.Min.X + halfWidth, Bounds.Min.Y);
300 var rightMiddle = new Vector2(Bounds.Max.X, Bounds.Max.Y - halfHeight);
301 var leftMiddle = new Vector2(Bounds.Min.X, Bounds.Min.Y + halfHeight);
302 var topMiddle = new Vector2(Bounds.Max.X - halfWidth, Bounds.Max.Y);
303
304 // Bottom Left
305 m_nodes.Add(new zzQuadTreeNode<T>(new BoundingBox(Bounds.Min, new Vector3(centerPoint, Bounds.Max.Z))));
306
307 // Bottom Right
308 m_nodes.Add(
309 new zzQuadTreeNode<T>(new BoundingBox(new Vector3(bottomMiddle, Bounds.Min.Z),
310 new Vector3(rightMiddle, Bounds.Max.Z))));
311
312 // Top Left
313 m_nodes.Add(
314 new zzQuadTreeNode<T>(new BoundingBox(new Vector3(leftMiddle, Bounds.Min.Z),
315 new Vector3(topMiddle, Bounds.Max.Z))));
316
317 // Top Right
318 m_nodes.Add(new zzQuadTreeNode<T>(new BoundingBox(new Vector3(centerPoint, Bounds.Min.Z), Bounds.Max)));
319 }
320
321 }
322
323 public interface IBounded
324 {
325 BoundingBox Bounds
326 {
327 get;
328 set;
329 }
330 }
331}