Skip to content

Commit

Permalink
Merge 3331f08 into c0a3dac
Browse files Browse the repository at this point in the history
  • Loading branch information
kip-hart committed Jun 13, 2020
2 parents c0a3dac + 3331f08 commit 7e611b7
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 61 deletions.
2 changes: 1 addition & 1 deletion LICENSE.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2019 Georgia Tech Research Corporation
Copyright (c) 2020 Georgia Tech Research Corporation

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ include tox.ini
prune docs/source/_static
prune docs/build

exclude CONTRIBUTING.rst
exclude plot_incremental.py

global-exclude *.py[cod] __pycache__ *.so *.dylib .DS_Store *.log Icon*
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ create a pull request, and submit issues.
License and Copyright Notice
============================

Copyright |copy| 2019, Georgia Tech Research Corporation
Copyright |copy| 2020, Georgia Tech Research Corporation

AABBTree is open source and freely available under the terms of
the MIT license.
Expand Down
167 changes: 124 additions & 43 deletions aabbtree.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import copy
from collections import deque

__all__ = ['AABB', 'AABBTree']
__author__ = 'Kenneth (Kip) Hart'
Expand Down Expand Up @@ -66,10 +67,10 @@ def __eq__(self, aabb):
return True
if (self.limits is None) or (aabb.limits is None):
return False
if len(self) != len(aabb):
if len(self.limits) != len(aabb.limits):
return False

for i, lims1 in enumerate(self):
for i, lims1 in enumerate(self.limits):
lims2 = aabb[i]
if (lims1[0] != lims2[0]) or (lims1[1] != lims2[1]):
return False
Expand Down Expand Up @@ -98,18 +99,12 @@ def merge(cls, aabb1, aabb2):
if aabb2.limits is None:
return cls(aabb1.limits)

if len(aabb1) != len(aabb2):
if len(aabb1.limits) != len(aabb2.limits):
e_str = 'AABBs of different dimensions: ' + str(len(aabb1))
e_str += ' and ' + str(len(aabb2))
raise ValueError(e_str)

merged_limits = []
n = len(aabb1)
for i in range(n):
lower = min(aabb1[i][0], aabb2[i][0])
upper = max(aabb1[i][1], aabb2[i][1])
merged_limits.append((lower, upper))
return cls(merged_limits)
return cls([_merge(*lims) for lims in zip(aabb1.limits, aabb2.limits)])

@property
def perimeter(self):
Expand All @@ -126,11 +121,11 @@ def perimeter(self):
p_n &= 2 \sum_{i=1}^n \prod_{j=1\neq i}^n l_j
"""
if len(self) == 1:
if len(self.limits) == 1:
return 0

perim = 0
side_lens = [ub - lb for lb, ub in self]
side_lens = [ub - lb for lb, ub in self.limits]
n_dim = len(side_lens)
for i in range(n_dim):
p_edge = 1
Expand All @@ -156,7 +151,7 @@ def volume(self):
"""
vol = 1
for lb, ub in self:
for lb, ub in self.limits:
vol *= ub - lb
return vol

Expand Down Expand Up @@ -187,12 +182,10 @@ def overlaps(self, aabb):
if (self.limits is None) or (aabb.limits is None):
return False

for lims1, lims2 in zip(self, aabb):
min1, max1 = lims1
min2, max2 = lims2

overlaps = (max1 >= min2) and (min1 <= max2)
if not overlaps:
for (min1, max1), (min2, max2) in zip(self.limits, aabb.limits):
if min1 >= max2:
return False
if min2 >= max1:
return False
return True

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

volume = 1
for lims1, lims2 in zip(self, aabb):
min1, max1 = lims1
min2, max2 = lims2

for (min1, max1), (min2, max2) in zip(self.limits, aabb.limits):
overlap_min = max(min1, min2)
overlap_max = min(max1, max2)
if overlap_min >= overlap_max:
Expand Down Expand Up @@ -435,50 +425,141 @@ def add(self, aabb, value=None, method='volume'):
self.right.add(aabb, value)
self.aabb = AABB.merge(self.left.aabb, self.right.aabb)

def does_overlap(self, aabb):
def does_overlap(self, aabb, method='DFS'):
"""Check for overlap
This function checks if the limits overlap any leaf nodes in the tree.
It returns true if there is an overlap.
Args:
aabb (AABB): The AABB to check.
method (str): {'DFS'|'BFS'} Method for traversing the tree.
Setting 'DFS' performs a depth-first search and 'BFS' performs
a breadth-first search. Defaults to 'DFS'.
Returns:
bool: True if overlaps with a leaf node of tree.
"""
if self.is_leaf:
return self.aabb.overlaps(aabb)
if method == 'DFS':
if self.is_leaf:
return self.aabb.overlaps(aabb)

left_aabb_over = self.left.aabb.overlaps(aabb)
right_aabb_over = self.right.aabb.overlaps(aabb)
left_aabb_over = self.left.aabb.overlaps(aabb)
right_aabb_over = self.right.aabb.overlaps(aabb)

if left_aabb_over and self.left.does_overlap(aabb):
return True
if right_aabb_over and self.right.does_overlap(aabb):
return True
return False
if left_aabb_over and self.left.does_overlap(aabb):
return True
if right_aabb_over and self.right.does_overlap(aabb):
return True
return False

if method == 'BFS':
q = deque()
q.append(self)
while len(q) > 0:
node = q.popleft()
overlaps = node.aabb.overlaps(aabb)
if overlaps and node.is_leaf:
return True
if overlaps:
q.append(node.left)
q.append(node.right)
return False

e_str = "method should be 'DFS' or 'BFS', not " + str(method)
raise ValueError(e_str)

def overlap_aabbs(self, aabb, method='DFS'):
"""Get overlapping AABBs
This function gets each overlapping AABB.
Args:
aabb (AABB): The AABB to check.
method (str): {'DFS'|'BFS'} Method for traversing the tree.
Setting 'DFS' performs a depth-first search and 'BFS' performs
a breadth-first search. Defaults to 'DFS'.
def overlap_values(self, aabb):
Returns:
list: AABB objects in AABBTree that overlap with the input.
"""
aabbs = []

if method == 'DFS':
is_leaf = self.is_leaf
if is_leaf and self.does_overlap(aabb):
aabbs.append(self.aabb)
elif is_leaf:
pass
else:
if self.left.aabb.overlaps(aabb):
aabbs.extend(self.left.overlap_aabbs(aabb))

if self.right.aabb.overlaps(aabb):
aabbs.extend(self.right.overlap_aabbs(aabb))
elif method == 'BFS':
q = deque()
q.append(self)
while len(q) > 0:
node = q.popleft()
if node.aabb.overlaps(aabb):
if node.is_leaf:
aabbs.append(node.aabb)
else:
q.append(node.left)
q.append(node.right)
else:
e_str = "method should be 'DFS' or 'BFS', not " + str(method)
raise ValueError(e_str)
return aabbs

def overlap_values(self, aabb, method='DFS'):
"""Get values of overlapping AABBs
This function gets the value field of each overlapping AABB.
Args:
aabb (AABB): The AABB to check.
method (str): {'DFS'|'BFS'} Method for traversing the tree.
Setting 'DFS' performs a depth-first search and 'BFS' performs
a breadth-first search. Defaults to 'DFS'.
Returns:
list: Value fields of each node that overlaps.
"""
values = []
if self.is_leaf and self.does_overlap(aabb):
values.append(self.value)
elif self.is_leaf:
pass
else:
if self.left.aabb.overlaps(aabb):
values.extend(self.left.overlap_values(aabb))

if self.right.aabb.overlaps(aabb):
values.extend(self.right.overlap_values(aabb))
if method == 'DFS':
is_leaf = self.is_leaf
if is_leaf and self.does_overlap(aabb):
values.append(self.value)
elif is_leaf:
pass
else:
if self.left.aabb.overlaps(aabb):
values.extend(self.left.overlap_values(aabb))

if self.right.aabb.overlaps(aabb):
values.extend(self.right.overlap_values(aabb))
elif method == 'BFS':
q = deque()
q.append(self)
while len(q) > 0:
node = q.popleft()
if node.aabb.overlaps(aabb):
if node.is_leaf:
values.append(node.value)
else:
q.append(node.left)
q.append(node.right)
else:
e_str = "method should be 'DFS' or 'BFS', not " + str(method)
raise ValueError(e_str)
return values


def _merge(lims1, lims2):
lb = min(lims1[0], lims2[0])
ub = max(lims1[1], lims2[1])

return (lb, ub)
6 changes: 3 additions & 3 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
# -- Project information -----------------------------------------------------

project = 'AABBTree'
copyright = '2019, Georgia Tech Research Corporation'
copyright = '2020, Georgia Tech Research Corporation'
author = 'Kenneth Hart'

# The short X.Y version
version = '2.4'
version = '2.5'
# The full version, including alpha/beta/rc tags
release = '2.4.0'
release = '2.5.0'


# -- General configuration ---------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def read(fname):

setup(
name='aabbtree',
version='2.4.0',
version='2.5.0',
license='MIT',
description='Pure Python implementation of d-dimensional AABB tree.',
long_description=read('README.rst'),
Expand Down
18 changes: 18 additions & 0 deletions tests/test_aabb.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,21 @@ def test_overlaps():
assert aabb2.overlaps(aabb1)
assert not aabb3.overlaps(aabb2)
assert not aabb2.overlaps(aabb3)


def test_corners():
lims = [(0, 10), (5, 10)]
aabb_corners = [
[lims[0][0], lims[1][0]],
[lims[0][1], lims[1][0]],
[lims[0][0], lims[1][1]],
[lims[0][1], lims[1][1]]
]

out_corners = AABB(lims).corners
for c in aabb_corners:
assert c in out_corners

for c in out_corners:
assert c in aabb_corners

0 comments on commit 7e611b7

Please sign in to comment.