PageRenderTime 71ms CodeModel.GetById 48ms app.highlight 17ms RepoModel.GetById 1ms app.codeStats 0ms

/Addons/WCell.Addons.Terrain/QuadTree.cs

https://github.com/primax/WCell
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}