Skip to content

Commit 14e6b0f

Browse files
authored
Merge pull request OmkarPathak#64 from OmkarPathak/dev
Completed implementation of quad tree
2 parents 96ab6f3 + 8179371 commit 14e6b0f

File tree

3 files changed

+742
-2
lines changed

3 files changed

+742
-2
lines changed

docs/Data_Structure.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Features
4141
- Check cycle in Undirected Graph (data_structures.graph.CheckCycleUndirectedGraph)
4242
- **Heap**
4343
- Heap (data_structures.heap.Heap)
44+
- **QuadTree**
45+
- QuadTree (data_structures.quadtree.QuadTree)
4446

4547
* Get the code used for any of the implementation
4648

@@ -214,3 +216,21 @@ Trie
214216
-----
215217
.. autoclass:: Trie
216218
:members:
219+
220+
QuadTree
221+
--------
222+
223+
.. automodule:: pygorithm.data_structures.quadtree
224+
225+
QuadTreeEntity
226+
--------------
227+
.. autoclass:: QuadTreeEntity
228+
:members:
229+
:special-members:
230+
231+
QuadTree
232+
--------
233+
.. autoclass:: QuadTree
234+
:members:
235+
:special-members:
236+

pygorithm/data_structures/quadtree.py

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,351 @@
99

1010
from pygorithm.geometry import (vector2, polygon2, rect2)
1111

12+
class QuadTreeEntity(object):
13+
"""
14+
This is the minimum information required for an object to
15+
be usable in a quadtree as an entity. Entities are the
16+
things that you are trying to compare in a quadtree.
17+
18+
:ivar aabb: the axis-aligned bounding box of this entity
19+
:type aabb: :class:`pygorithm.geometry.rect2.Rect2`
20+
"""
21+
def __init__(self, aabb):
22+
"""
23+
Create a new quad tree entity with the specified aabb
24+
25+
:param aabb: axis-aligned bounding box
26+
:type aabb: :class:`pygorithm.geometry.rect2.Rect2`
27+
"""
28+
pass
29+
30+
def __repr__(self):
31+
"""
32+
Create an unambiguous representation of this entity.
33+
34+
Example:
35+
36+
.. code-block:: python
37+
38+
from pygorithm.geometry import (vector2, rect2)
39+
from pygorithm.data_structures import quadtree
40+
41+
_ent = quadtree.QuadTreeEntity(rect2.Rect2(5, 5))
42+
43+
# prints quadtreeentity(aabb=rect2(width=5, height=5, mincorner=vector2(x=0, y=0)))
44+
print(repr(_ent))
45+
46+
:returns: unambiguous representation of this quad tree entity
47+
:rtype: string
48+
"""
49+
pass
50+
51+
def __str__(self):
52+
"""
53+
Create a human readable representation of this entity
54+
55+
Example:
56+
57+
.. code-block:: python
58+
59+
from pygorithm.geometry import (vector2, rect2)
60+
from pygorithm.data_structures import quadtree
61+
62+
_ent = quadtree.QuadTreeEntity(rect2.Rect2(5, 5))
63+
64+
# prints entity(at rect(5x5 at <0, 0>))
65+
print(str(_ent))
66+
67+
:returns: human readable representation of this entity
68+
:rtype: string
69+
"""
70+
pass
71+
1272
class QuadTree(object):
1373
"""
1474
A quadtree is a sorting tool for two-dimensional space, most
1575
commonly used to reduce the number of required collision
1676
calculations in a two-dimensional scene. In this context,
1777
the scene is stepped without collision detection, then a
1878
quadtree is constructed from all of the boundaries
79+
80+
.. caution::
81+
82+
Just because a quad tree has split does not mean entities will be empty. Any
83+
entities which overlay any of the lines of the split will be included in the
84+
parent of the quadtree.
85+
86+
.. tip::
87+
88+
It is important to tweak bucket size and depth to the problem, but a common error
89+
is too small a bucket size. It is typically not reasonable to have a bucket size
90+
smaller than 16; A good starting point is 64, then modify as appropriate. Larger
91+
buckets reduce the overhead of the quad tree which could easily exceed the improvement
92+
from reduced collision checks. The max depth is typically just a sanity check since
93+
depth greater than 4 or 5 would either indicate a badly performing quadtree (too
94+
dense objects, use an r-tree or kd-tree) or a very large world (where an iterative
95+
quadtree implementation would be appropriate).
96+
97+
:ivar bucket_size: maximum number objects per bucket (before :py:attr:`.max_depth`)
98+
:type bucket_size: int
99+
:ivar max_depth: maximum depth of the quadtree
100+
:type max_depth: int
101+
:ivar depth: the depth of this node (0 being the topmost)
102+
:type depth: int
103+
:ivar location: where this quad tree node is situated
104+
:type location: :class:`pygorithm.geometry.rect2.Rect2`
105+
:ivar entities: the entities in this quad tree and in NO OTHER related quad tree
106+
:type entities: list of :class:`.QuadTreeEntity`
107+
:ivar children: either None or the 4 :class:`.QuadTree` children of this node
108+
:type children: None or list of :class:`.QuadTree`
19109
"""
110+
111+
def __init__(self, bucket_size, max_depth, location, depth = 0, entities = None):
112+
"""
113+
Initialize a new quad tree.
114+
115+
.. warning::
116+
117+
Passing entities to this quadtree will NOT cause it to split automatically!
118+
You must call :py:meth:`.think` for that. This allows for more predictable
119+
performance per line.
120+
121+
:param bucket_size: the number of entities in this quadtree
122+
:type bucket_size: int
123+
:param max_depth: the maximum depth for automatic splitting
124+
:type max_depth: int
125+
:param location: where this quadtree is located
126+
:type location: :class:`pygorithm.geometry.rect2.Rect2`
127+
:param depth: the depth of this node
128+
:type depth: int
129+
:param entities: the entities to initialize this quadtree with
130+
:type entities: list of :class:`.QuadTreeEntity` or None for empty list
131+
"""
132+
pass
133+
134+
def think(self, recursive = False):
135+
"""
136+
Call :py:meth:`.split` if appropriate
137+
138+
Split this quad tree if it has not split already and it has more
139+
entities than :py:attr:`.bucket_size` and :py:attr:`.depth` is
140+
less than :py:attr:`.max_depth`.
141+
142+
If `recursive` is True, think is called on the :py:attr:`.children` with
143+
recursive set to True after splitting.
144+
145+
:param recursive: if `think(True)` should be called on :py:attr:`.children` (if there are any)
146+
:type recursive: bool
147+
"""
148+
pass
149+
150+
def split(self):
151+
"""
152+
Split this quadtree.
153+
154+
.. caution::
155+
156+
A call to split will always split the tree or raise an error. Use
157+
:py:meth:`.think` if you want to ensure the quadtree is operating
158+
efficiently.
159+
160+
.. caution::
161+
162+
This function will not respect :py:attr:`.bucket_size` or
163+
:py:attr:`.max_depth`.
164+
165+
:raises ValueError: if :py:attr:`.children` is not empty
166+
"""
167+
pass
168+
169+
def get_quadrant(self, entity):
170+
"""
171+
Calculate the quadrant that the specified entity belongs to.
172+
173+
Quadrants are:
174+
175+
- -1: None (it overlaps 2 or more quadrants)
176+
- 0: Top-left
177+
- 1: Top-right
178+
- 2: Bottom-right
179+
- 3: Bottom-left
180+
181+
.. caution::
182+
183+
This function does not verify the entity is contained in this quadtree.
184+
185+
This operation takes O(1) time.
186+
187+
:param entity: the entity to place
188+
:type entity: :class:`.QuadTreeEntity`
189+
:returns: quadrant
190+
:rtype: int
191+
"""
192+
pass
193+
194+
def insert_and_think(self, entity):
195+
"""
196+
Insert the entity into this or the appropriate child.
197+
198+
This also acts as thinking (recursively). Using :py:meth:`.insert_and_think`
199+
iteratively is slightly less efficient but has more predictable performance
200+
than initializing with a large number of entities then thinking is slightly
201+
faster but may hang. Both may exceed recursion depth if :py:attr:`.max_depth`
202+
is too large.
203+
204+
:param entity: the entity to insert
205+
:type entity: :class:`.QuadTreeEntity`
206+
"""
207+
pass
208+
209+
def retrieve_collidables(self, entity, predicate = None):
210+
"""
211+
Find all entities that could collide with the specified entity.
212+
213+
.. warning::
214+
215+
If entity is, itself, in the quadtree, it will be returned. The
216+
predicate may be used to prevent this using your preferred equality
217+
method.
218+
219+
The predicate takes 1 positional argument (the entity being considered)
220+
and returns `False` if the entity should never be returned, even if it
221+
might collide with the entity. It should return `True` otherwise.
222+
223+
:param entity: the entity to find collidables for
224+
:type entity: :class:`.QuadTreeEntity`
225+
:param predicate: the predicate
226+
:type predicate: :class:`types.FunctionType` or None
227+
:returns: potential collidables (never `None)
228+
:rtype: list of :class:`.QuadTreeEntity`
229+
"""
230+
pass
231+
232+
def find_entities_per_depth(self):
233+
"""
234+
Calculate the number of nodes and entities at each depth level in this
235+
quad tree. Only returns for depth levels at or equal to this node.
236+
237+
This is implemented iteratively. See :py:meth:`.__str__` for usage example.
238+
239+
:returns: dict of depth level to (number of nodes, number of entities)
240+
:rtype: dict int: (int, int)
241+
"""
242+
pass
243+
244+
def sum_entities(self, entities_per_depth=None):
245+
"""
246+
Sum the number of entities in this quad tree and all lower quad trees.
247+
248+
If `entities_per_depth` is not None, that array is used to calculate the sum
249+
of entities rather than traversing the tree. Either way, this is implemented
250+
iteratively. See :py:meth:`.__str__` for usage example.
251+
252+
:param entities_per_depth: the result of :py:meth:`.find_entities_per_depth`
253+
:type entities_per_depth: `dict int: (int, int)` or None
254+
:returns: number of entities in this and child nodes
255+
:rtype: int
256+
"""
257+
pass
258+
259+
def calculate_avg_ents_per_leaf(self):
260+
"""
261+
Calculate the average number of entities per leaf node on this and child
262+
quad trees.
263+
264+
In the ideal case, the average entities per leaf is equal to the bucket size,
265+
implying maximum efficiency. Note that, as always with averages, this might
266+
be misleading if this tree has reached its max depth.
267+
268+
This is implemented iteratively. See :py:meth:`.__str__` for usage example.
269+
270+
:returns: average number of entities at each leaf node
271+
:rtype: :class:`numbers.Number`
272+
"""
273+
pass
274+
275+
def calculate_weight_misplaced_ents(self, sum_entities=None):
276+
"""
277+
Calculate a rating for misplaced entities.
278+
279+
A misplaced entity is one that is not on a leaf node. That weight is multiplied
280+
by 4*remaining maximum depth of that node, to indicate approximately how
281+
many additional calculations are required.
282+
283+
The result is then divided by the total number of entities on this node (either
284+
calculated using :py:meth:`.sum_entities` or provided) to get the approximate
285+
cost of the misplaced nodes in comparison with the placed nodes. A value greater
286+
than 1 implies a different tree type (such as r-tree or kd-tree) should probably be
287+
used.
288+
289+
This is implemented iteratively. See :py:meth:`.__str__` for usage example.
290+
291+
:param sum_entities: the number of entities on this node
292+
:type sum_entities: int or None
293+
:returns: weight of misplaced entities
294+
:rtype: :class:`numbers.Number`
295+
"""
296+
pass
297+
298+
def __repr__(self):
299+
"""
300+
Create an unambiguous, recursive representation of this quad tree.
301+
302+
Example:
303+
304+
.. code-block:: python
305+
306+
from pygorithm.geometry import (vector2, rect2)
307+
from pygorithm.data_structures import quadtree
308+
309+
# create a tree with a up to 2 entities in a bucket that
310+
# can have a depth of up to 5.
311+
_tree = quadtree.QuadTree(2, 5, rect2.Rect2(100, 100))
312+
313+
# add a few entities to the tree
314+
_tree.insert_and_think(quadtree.QuadTreeEntity(rect2.Rect2(2, 2, vector2.Vector2(5, 5))))
315+
_tree.insert_and_think(quadtree.QuadTreeEntity(rect2.Rect2(2, 2, vector2.Vector2(95, 5))))
316+
317+
# prints quadtree(bucket_size=2, max_depth=5, location=rect2(width=100, height=100, mincorner=vector2(x=0, y=0)), depth=0, entities=[], children=[ quadtree(bucket_size=2, max_depth=5, location=rect2(width=50, height=50, mincorner=vector2(x=0, y=0)), depth=1, entities=[ quadtreeentity(aabb=rect2(width=2, height=2, mincorner=vector2(x=5, y=5))) ], children=[]), quadtree(bucket_size=2, max_depth=5, location=rect2(width=50, height=50, mincorner=vector2(x=50, y=0)), depth=1, entities=[ quadtreeentity(aabb=rect2(width=2, height=2, mincorner=vector2(x=95, y=5))) ], children=[]), quadtree(bucket_size=2, max_depth=5, location=rect2(width=50, height=50, mincorner=vector2(x=50, y=50)), depth=1, entities=[], children=[]), quadtree(bucket_size=2, max_depth=5, location=rect2(width=50, height=50, mincorner=vector2(x=0, y=50)), depth=1, entities=[], children=[]) ])
318+
print(repr(_tree))
319+
320+
:returns: unambiguous, recursive representation of this quad tree
321+
:rtype: string
322+
"""
323+
pass
324+
325+
def __str__(self):
326+
"""
327+
Create a human-readable representation of this quad tree
328+
329+
.. caution::
330+
331+
Because of the complexity of quadtrees it takes a fair amount of calculation to
332+
produce something somewhat legible. All returned statistics have paired functions.
333+
This uses only iterative algorithms to calculate statistics.
334+
335+
Example:
336+
337+
.. code-block:: python
338+
339+
from pygorithm.geometry import (vector2, rect2)
340+
from pygorithm.data_structures import quadtree
341+
342+
# create a tree with a up to 2 entities in a bucket that
343+
# can have a depth of up to 5.
344+
_tree = quadtree.QuadTree(2, 5, rect2.Rect2(100, 100))
345+
346+
# add a few entities to the tree
347+
_tree.insert_and_think(quadtree.QuadTreeEntity(rect2.Rect2(2, 2, vector2.Vector2(5, 5))))
348+
_tree.insert_and_think(quadtree.QuadTreeEntity(rect2.Rect2(2, 2, vector2.Vector2(95, 5))))
349+
350+
# prints quadtree(at rect(100x100 at <0, 0>) with 0 entities here (2 in total); (nodes, entities) per depth: [ 0: (1, 0), 1: (4, 2) ] (max depth: 5), avg ent/leaf: 0.5 (target 2), misplaced weight = 0 (0 best, >1 bad))
351+
352+
:returns: human-readable representation of this quad tree
353+
:rtype: string
354+
"""
355+
pass
356+
20357
@staticmethod
21358
def get_code():
22359
"""

0 commit comments

Comments
 (0)