Skip to content

Commit ce74c76

Browse files
authored
improved AABB iterator speed, added BFS, added overlap_aabbs method (#6)
1 parent c0a3dac commit ce74c76

File tree

9 files changed

+192
-62
lines changed

9 files changed

+192
-62
lines changed

LICENSE.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2019 Georgia Tech Research Corporation
3+
Copyright (c) 2020 Georgia Tech Research Corporation
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

MANIFEST.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
graft docs
22
graft tests
33

4-
include aabb.py
4+
include aabbtree.py
55

66
include .bumpversion.cfg
77
include .coveragerc
@@ -15,6 +15,7 @@ include tox.ini
1515
prune docs/source/_static
1616
prune docs/build
1717

18+
exclude CONTRIBUTING.rst
1819
exclude plot_incremental.py
1920

2021
global-exclude *.py[cod] __pycache__ *.so *.dylib .DS_Store *.log Icon*

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ create a pull request, and submit issues.
103103
License and Copyright Notice
104104
============================
105105

106-
Copyright |copy| 2019, Georgia Tech Research Corporation
106+
Copyright |copy| 2020, Georgia Tech Research Corporation
107107

108108
AABBTree is open source and freely available under the terms of
109109
the MIT license.

aabbtree.py

Lines changed: 124 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import copy
2+
from collections import deque
23

34
__all__ = ['AABB', 'AABBTree']
45
__author__ = 'Kenneth (Kip) Hart'
@@ -66,10 +67,10 @@ def __eq__(self, aabb):
6667
return True
6768
if (self.limits is None) or (aabb.limits is None):
6869
return False
69-
if len(self) != len(aabb):
70+
if len(self.limits) != len(aabb.limits):
7071
return False
7172

72-
for i, lims1 in enumerate(self):
73+
for i, lims1 in enumerate(self.limits):
7374
lims2 = aabb[i]
7475
if (lims1[0] != lims2[0]) or (lims1[1] != lims2[1]):
7576
return False
@@ -98,18 +99,12 @@ def merge(cls, aabb1, aabb2):
9899
if aabb2.limits is None:
99100
return cls(aabb1.limits)
100101

101-
if len(aabb1) != len(aabb2):
102+
if len(aabb1.limits) != len(aabb2.limits):
102103
e_str = 'AABBs of different dimensions: ' + str(len(aabb1))
103104
e_str += ' and ' + str(len(aabb2))
104105
raise ValueError(e_str)
105106

106-
merged_limits = []
107-
n = len(aabb1)
108-
for i in range(n):
109-
lower = min(aabb1[i][0], aabb2[i][0])
110-
upper = max(aabb1[i][1], aabb2[i][1])
111-
merged_limits.append((lower, upper))
112-
return cls(merged_limits)
107+
return cls([_merge(*lims) for lims in zip(aabb1.limits, aabb2.limits)])
113108

114109
@property
115110
def perimeter(self):
@@ -126,11 +121,11 @@ def perimeter(self):
126121
p_n &= 2 \sum_{i=1}^n \prod_{j=1\neq i}^n l_j
127122
128123
"""
129-
if len(self) == 1:
124+
if len(self.limits) == 1:
130125
return 0
131126

132127
perim = 0
133-
side_lens = [ub - lb for lb, ub in self]
128+
side_lens = [ub - lb for lb, ub in self.limits]
134129
n_dim = len(side_lens)
135130
for i in range(n_dim):
136131
p_edge = 1
@@ -156,7 +151,7 @@ def volume(self):
156151
157152
"""
158153
vol = 1
159-
for lb, ub in self:
154+
for lb, ub in self.limits:
160155
vol *= ub - lb
161156
return vol
162157

@@ -187,12 +182,10 @@ def overlaps(self, aabb):
187182
if (self.limits is None) or (aabb.limits is None):
188183
return False
189184

190-
for lims1, lims2 in zip(self, aabb):
191-
min1, max1 = lims1
192-
min2, max2 = lims2
193-
194-
overlaps = (max1 >= min2) and (min1 <= max2)
195-
if not overlaps:
185+
for (min1, max1), (min2, max2) in zip(self.limits, aabb.limits):
186+
if min1 >= max2:
187+
return False
188+
if min2 >= max1:
196189
return False
197190
return True
198191

@@ -219,10 +212,7 @@ def overlap_volume(self, aabb):
219212
""" # NOQA: E501
220213

221214
volume = 1
222-
for lims1, lims2 in zip(self, aabb):
223-
min1, max1 = lims1
224-
min2, max2 = lims2
225-
215+
for (min1, max1), (min2, max2) in zip(self.limits, aabb.limits):
226216
overlap_min = max(min1, min2)
227217
overlap_max = min(max1, max2)
228218
if overlap_min >= overlap_max:
@@ -435,50 +425,141 @@ def add(self, aabb, value=None, method='volume'):
435425
self.right.add(aabb, value)
436426
self.aabb = AABB.merge(self.left.aabb, self.right.aabb)
437427

438-
def does_overlap(self, aabb):
428+
def does_overlap(self, aabb, method='DFS'):
439429
"""Check for overlap
440430
441431
This function checks if the limits overlap any leaf nodes in the tree.
442432
It returns true if there is an overlap.
443433
444434
Args:
445435
aabb (AABB): The AABB to check.
436+
method (str): {'DFS'|'BFS'} Method for traversing the tree.
437+
Setting 'DFS' performs a depth-first search and 'BFS' performs
438+
a breadth-first search. Defaults to 'DFS'.
446439
447440
Returns:
448441
bool: True if overlaps with a leaf node of tree.
449442
"""
450-
if self.is_leaf:
451-
return self.aabb.overlaps(aabb)
443+
if method == 'DFS':
444+
if self.is_leaf:
445+
return self.aabb.overlaps(aabb)
452446

453-
left_aabb_over = self.left.aabb.overlaps(aabb)
454-
right_aabb_over = self.right.aabb.overlaps(aabb)
447+
left_aabb_over = self.left.aabb.overlaps(aabb)
448+
right_aabb_over = self.right.aabb.overlaps(aabb)
455449

456-
if left_aabb_over and self.left.does_overlap(aabb):
457-
return True
458-
if right_aabb_over and self.right.does_overlap(aabb):
459-
return True
460-
return False
450+
if left_aabb_over and self.left.does_overlap(aabb):
451+
return True
452+
if right_aabb_over and self.right.does_overlap(aabb):
453+
return True
454+
return False
455+
456+
if method == 'BFS':
457+
q = deque()
458+
q.append(self)
459+
while len(q) > 0:
460+
node = q.popleft()
461+
overlaps = node.aabb.overlaps(aabb)
462+
if overlaps and node.is_leaf:
463+
return True
464+
if overlaps:
465+
q.append(node.left)
466+
q.append(node.right)
467+
return False
468+
469+
e_str = "method should be 'DFS' or 'BFS', not " + str(method)
470+
raise ValueError(e_str)
471+
472+
def overlap_aabbs(self, aabb, method='DFS'):
473+
"""Get overlapping AABBs
474+
475+
This function gets each overlapping AABB.
476+
477+
Args:
478+
aabb (AABB): The AABB to check.
479+
method (str): {'DFS'|'BFS'} Method for traversing the tree.
480+
Setting 'DFS' performs a depth-first search and 'BFS' performs
481+
a breadth-first search. Defaults to 'DFS'.
461482
462-
def overlap_values(self, aabb):
483+
Returns:
484+
list: AABB objects in AABBTree that overlap with the input.
485+
"""
486+
aabbs = []
487+
488+
if method == 'DFS':
489+
is_leaf = self.is_leaf
490+
if is_leaf and self.does_overlap(aabb):
491+
aabbs.append(self.aabb)
492+
elif is_leaf:
493+
pass
494+
else:
495+
if self.left.aabb.overlaps(aabb):
496+
aabbs.extend(self.left.overlap_aabbs(aabb))
497+
498+
if self.right.aabb.overlaps(aabb):
499+
aabbs.extend(self.right.overlap_aabbs(aabb))
500+
elif method == 'BFS':
501+
q = deque()
502+
q.append(self)
503+
while len(q) > 0:
504+
node = q.popleft()
505+
if node.aabb.overlaps(aabb):
506+
if node.is_leaf:
507+
aabbs.append(node.aabb)
508+
else:
509+
q.append(node.left)
510+
q.append(node.right)
511+
else:
512+
e_str = "method should be 'DFS' or 'BFS', not " + str(method)
513+
raise ValueError(e_str)
514+
return aabbs
515+
516+
def overlap_values(self, aabb, method='DFS'):
463517
"""Get values of overlapping AABBs
464518
465519
This function gets the value field of each overlapping AABB.
466520
467521
Args:
468522
aabb (AABB): The AABB to check.
523+
method (str): {'DFS'|'BFS'} Method for traversing the tree.
524+
Setting 'DFS' performs a depth-first search and 'BFS' performs
525+
a breadth-first search. Defaults to 'DFS'.
469526
470527
Returns:
471528
list: Value fields of each node that overlaps.
472529
"""
473530
values = []
474-
if self.is_leaf and self.does_overlap(aabb):
475-
values.append(self.value)
476-
elif self.is_leaf:
477-
pass
478-
else:
479-
if self.left.aabb.overlaps(aabb):
480-
values.extend(self.left.overlap_values(aabb))
481531

482-
if self.right.aabb.overlaps(aabb):
483-
values.extend(self.right.overlap_values(aabb))
532+
if method == 'DFS':
533+
is_leaf = self.is_leaf
534+
if is_leaf and self.does_overlap(aabb):
535+
values.append(self.value)
536+
elif is_leaf:
537+
pass
538+
else:
539+
if self.left.aabb.overlaps(aabb):
540+
values.extend(self.left.overlap_values(aabb))
541+
542+
if self.right.aabb.overlaps(aabb):
543+
values.extend(self.right.overlap_values(aabb))
544+
elif method == 'BFS':
545+
q = deque()
546+
q.append(self)
547+
while len(q) > 0:
548+
node = q.popleft()
549+
if node.aabb.overlaps(aabb):
550+
if node.is_leaf:
551+
values.append(node.value)
552+
else:
553+
q.append(node.left)
554+
q.append(node.right)
555+
else:
556+
e_str = "method should be 'DFS' or 'BFS', not " + str(method)
557+
raise ValueError(e_str)
484558
return values
559+
560+
561+
def _merge(lims1, lims2):
562+
lb = min(lims1[0], lims2[0])
563+
ub = max(lims1[1], lims2[1])
564+
565+
return (lb, ub)

docs/source/conf.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
# -- Project information -----------------------------------------------------
2121

2222
project = 'AABBTree'
23-
copyright = '2019, Georgia Tech Research Corporation'
23+
copyright = '2020, Georgia Tech Research Corporation'
2424
author = 'Kenneth Hart'
2525

2626
# The short X.Y version
27-
version = '2.4'
27+
version = '2.5'
2828
# The full version, including alpha/beta/rc tags
29-
release = '2.4.0'
29+
release = '2.5.0'
3030

3131

3232
# -- General configuration ---------------------------------------------------

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def read(fname):
1515

1616
setup(
1717
name='aabbtree',
18-
version='2.4.0',
18+
version='2.5.0',
1919
license='MIT',
2020
description='Pure Python implementation of d-dimensional AABB tree.',
2121
long_description=read('README.rst'),

tests/test_aabb.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,21 @@ def test_overlaps():
106106
assert aabb2.overlaps(aabb1)
107107
assert not aabb3.overlaps(aabb2)
108108
assert not aabb2.overlaps(aabb3)
109+
110+
111+
def test_corners():
112+
lims = [(0, 10), (5, 10)]
113+
aabb_corners = [
114+
[lims[0][0], lims[1][0]],
115+
[lims[0][1], lims[1][0]],
116+
[lims[0][0], lims[1][1]],
117+
[lims[0][1], lims[1][1]]
118+
]
119+
120+
out_corners = AABB(lims).corners
121+
for c in aabb_corners:
122+
assert c in out_corners
123+
124+
for c in out_corners:
125+
assert c in aabb_corners
126+

0 commit comments

Comments
 (0)