Skip to content

Commit 56d9750

Browse files
committed
QuadTree documentation
Add documentation for quadtree (next up tests, then implementation) * docs/Data_Structure.rst - add quadtree * pygorithm/data_structures/quadtree.py - add skeleton, documentation * tests/test_data_structure.py - add skeleton for quadtree tests
1 parent 96ab6f3 commit 56d9750

File tree

3 files changed

+390
-1
lines changed

3 files changed

+390
-1
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: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,346 @@
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 class 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 insert_and_think
199+
iteratively is slightly less efficient but more predictable performance,
200+
whereas initializing with a large number of entities then thinking is slightly
201+
faster but may hang. Both may exceed recursion depth if 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.
250+
251+
:param entities_per_depth: the result of :py:meth:`.find_entities_per_depth`
252+
:type entities_per_depth: `dict int: (int, int)` or None
253+
:returns: number of entities in this and child nodes
254+
:rtype: int
255+
"""
256+
pass
257+
258+
def calculate_avg_ents_per_leaf(self):
259+
"""
260+
Calculate the average number of entities per leaf node on this and child
261+
quad trees.
262+
263+
In the ideal case, the average entities per leaf is equal to the bucket size,
264+
implying maximum efficiency.
265+
266+
This is implemented iteratively. See :py:meth:`.__str__` for usage example.
267+
268+
:returns: average number of entities at each leaf node
269+
:rtype: :class:`numbers.Number`
270+
"""
271+
pass
272+
273+
def calculate_weight_misplaced_ents(self, sum_entities=None):
274+
"""
275+
Calculate a rating for misplaced entities.
276+
277+
A misplaced entity is one that is not on a leaf node. That weight is multiplied
278+
by 4*remaining maximum depth of that node, to indicate approximately how
279+
many additional calculations are required.
280+
281+
The result is then divided by the total number of entities on this node (either
282+
calculated using :py:meth:`.sum_entities` or provided) to get the approximate
283+
cost of the misplaced nodes in comparison with the placed nodes. A value greater
284+
than 1 implies a different tree type (such as r-tree or kd-tree) should probably be
285+
used.
286+
287+
:param sum_entities: the number of entities on this node
288+
:type sum_entities: int or None
289+
:returns: weight of misplaced entities
290+
:rtype: :class:`numbers.Number`
291+
"""
292+
pass
293+
294+
def __repr__(self):
295+
"""
296+
Create an unambiguous, recursive representation of this quad tree.
297+
298+
Example:
299+
300+
.. code-block:: python
301+
302+
from pygorithm.geometry import (vector2, rect2)
303+
from pygorithm.data_structures import quadtree
304+
305+
# create a tree with a up to 2 entities in a bucket that
306+
# can have a depth of up to 5.
307+
_tree = quadtree.QuadTree(2, 5, rect2.Rect2(100, 100))
308+
309+
# add a few entities to the tree
310+
_tree.insert_and_think(quadtree.QuadTreeEntity(rect2.Rect2(2, 2, vector2.Vector2(5, 5))))
311+
_tree.insert_and_think(quadtree.QuadTreeEntity(rect2.Rect2(2, 2, vector2.Vector2(95, 5))))
312+
313+
# 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=[]) ])
314+
print(repr(_tree))
315+
316+
:returns: unambiguous, recursive representation of this quad tree
317+
:rtype: string
318+
"""
319+
pass
320+
321+
def __str__(self):
322+
"""
323+
Create a human-readable representation of this quad tree
324+
325+
.. caution::
326+
327+
Because of the complexity of quadtrees it takes a fair amount of calculation to
328+
produce something somewhat legible. All returned statistics have paired functions
329+
330+
Example:
331+
332+
.. code-block:: python
333+
334+
from pygorithm.geometry import (vector2, rect2)
335+
from pygorithm.data_structures import quadtree
336+
337+
# create a tree with a up to 2 entities in a bucket that
338+
# can have a depth of up to 5.
339+
_tree = quadtree.QuadTree(2, 5, rect2.Rect2(100, 100))
340+
341+
# add a few entities to the tree
342+
_tree.insert_and_think(quadtree.QuadTreeEntity(rect2.Rect2(2, 2, vector2.Vector2(5, 5))))
343+
_tree.insert_and_think(quadtree.QuadTreeEntity(rect2.Rect2(2, 2, vector2.Vector2(95, 5))))
344+
345+
# 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))
346+
347+
:returns: human-readable representation of this quad tree
348+
:rtype: string
349+
"""
350+
pass
351+
20352
@staticmethod
21353
def get_code():
22354
"""

0 commit comments

Comments
 (0)