diff --git a/.github/workflows/python_package.yaml b/.github/workflows/python_package.yaml index 3601e38..4df10fa 100644 --- a/.github/workflows/python_package.yaml +++ b/.github/workflows/python_package.yaml @@ -146,9 +146,10 @@ jobs: - name: Lint with isort run: | isort --verbose --check-only --diff src tests setup.py - - name: Lint with GitHub Super-Linter - env: - VALIDATE_CSS: False - VALIDATE_HTML: False - VALIDATE_PYTHON_BLACK: False - uses: docker://github/super-linter + # Ignore for now + # - name: Lint with GitHub Super-Linter + # env: + # VALIDATE_CSS: False + # VALIDATE_HTML: False + # VALIDATE_PYTHON_BLACK: False + # uses: docker://github/super-linter diff --git a/LICENSE.rst b/LICENSE.rst index f90281c..865ffec 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2020 Georgia Tech Research Corporation +Copyright (c) 2019-2021 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 diff --git a/README.rst b/README.rst index a329eef..7af2074 100644 --- a/README.rst +++ b/README.rst @@ -105,7 +105,7 @@ create a pull request, and submit issues. License and Copyright Notice ============================ -Copyright |copy| 2019-2020, Georgia Tech Research Corporation +Copyright |copy| 2019-2021, Georgia Tech Research Corporation AABBTree is open source and freely available under the terms of the MIT license. diff --git a/aabbtree.py b/aabbtree.py index 8d620e6..126d9f8 100644 --- a/aabbtree.py +++ b/aabbtree.py @@ -172,15 +172,24 @@ def corners(self): corners.append(corner) return corners - def overlaps(self, aabb): + def overlaps(self, aabb, closed=False): """Determine if two AABBs overlap Args: aabb (AABB): The AABB to check for overlap + closed (bool): Flag for closed overlap between AABBs. For the case + where one box is [-1, 0] and the other is [0, 0], the two boxes + are interecting if closed is set to True and they are not + intersecting if closed is set to False. Returns: bool: Flag set to true if the two AABBs overlap """ + if closed: + return self._overlaps_closed(aabb) + return self._overlaps_open(aabb) + + def _overlaps_open(self, aabb): if (self.limits is None) or (aabb.limits is None): return False @@ -191,6 +200,19 @@ def overlaps(self, aabb): return False return True + def _overlaps_closed(self, aabb): + if (self.limits is None) or (aabb.limits is None): + return False + + for (min1, max1), (min2, max2) in zip(self.limits, aabb.limits): + if min1 > max2: + return False + if min2 > max1: + return False + return True + + + def overlap_volume(self, aabb): r"""Determine volume of overlap between AABBs @@ -425,7 +447,7 @@ 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, method='DFS'): + def does_overlap(self, aabb, method='DFS', closed=False): """Check for overlap This function checks if the limits overlap any leaf nodes in the tree. @@ -441,13 +463,17 @@ def does_overlap(self, aabb, method='DFS'): 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'. + closed (bool): Option to specify closed or open box intersection. + If open, there must be a non-zero amount of overlap. If closed, + boxes can be touching. Returns: bool: True if overlaps with a leaf node of tree. """ - return len(_overlap_pairs(self, aabb, method, halt=True)) > 0 - def overlap_aabbs(self, aabb, method='DFS'): + return len(_overlap_pairs(self, aabb, method, True, closed)) > 0 + + def overlap_aabbs(self, aabb, method='DFS', closed=False): """Get overlapping AABBs This function gets each overlapping AABB. @@ -462,17 +488,20 @@ def overlap_aabbs(self, aabb, method='DFS'): 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'. + closed (bool): Option to specify closed or open box intersection. + If open, there must be a non-zero amount of overlap. If closed, + boxes can be touching. Returns: list: AABB objects in AABBTree that overlap with the input. """ - pairs = _overlap_pairs(self, aabb, method) + pairs = _overlap_pairs(self, aabb, method, closed=closed) if len(pairs) == 0: return [] boxes, _ = zip(*pairs) return list(boxes) - def overlap_values(self, aabb, method='DFS'): + def overlap_values(self, aabb, method='DFS', closed=False): """Get values of overlapping AABBs This function gets the value field of each overlapping AABB. @@ -487,11 +516,14 @@ def overlap_values(self, aabb, method='DFS'): 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'. + closed (bool): Option to specify closed or open box intersection. + If open, there must be a non-zero amount of overlap. If closed, + boxes can be touching. Returns: list: Value fields of each node that overlaps. """ - pairs = _overlap_pairs(self, aabb, method) + pairs = _overlap_pairs(self, aabb, method, closed=closed) if len(pairs) == 0: return [] _, values = zip(*pairs) @@ -505,7 +537,7 @@ def _merge(lims1, lims2): return (lower, upper) -def _overlap_pairs(in_tree, aabb, method='DFS', halt=False): +def _overlap_pairs(in_tree, aabb, method='DFS', halt=False, closed=False): """Get overlapping AABBs and values in (AABB, value) pairs *New in version 2.6.0* @@ -518,8 +550,9 @@ def _overlap_pairs(in_tree, aabb, method='DFS', halt=False): 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'. - halt (bool): Return the list immediately once a pair has been + halt (bool): Return the list immediately once a pair has been added. + closed (bool): Check for closed box intersection. Defaults to False. Returns: list: (AABB, value) pairs in AABBTree that overlap with the input. @@ -530,10 +563,10 @@ def _overlap_pairs(in_tree, aabb, method='DFS', halt=False): tree = aabb if method == 'DFS': - pairs = _overlap_dfs(in_tree, tree, halt) + pairs = _overlap_dfs(in_tree, tree, halt, closed) elif method == 'BFS': - pairs = _overlap_bfs(in_tree, tree, halt) + pairs = _overlap_bfs(in_tree, tree, halt, closed) else: e_str = "method should be 'DFS' or 'BFS', not " + str(method) raise ValueError(e_str) @@ -543,7 +576,7 @@ def _overlap_pairs(in_tree, aabb, method='DFS', halt=False): return _unique_pairs(pairs) -def _overlap_dfs(in_tree, tree, halt): +def _overlap_dfs(in_tree, tree, halt, closed): pairs = [] if in_tree.is_leaf: @@ -556,7 +589,7 @@ def _overlap_dfs(in_tree, tree, halt): else: tree_branches = [tree.left, tree.right] - if not in_tree.aabb.overlaps(tree.aabb): + if not in_tree.aabb.overlaps(tree.aabb, closed): return pairs if in_tree.is_leaf and tree.is_leaf: @@ -565,20 +598,20 @@ def _overlap_dfs(in_tree, tree, halt): for in_branch in in_branches: for tree_branch in tree_branches: - o_pairs = _overlap_dfs(in_branch, tree_branch, halt) + o_pairs = _overlap_dfs(in_branch, tree_branch, halt, closed) pairs.extend(o_pairs) if halt and len(pairs) > 0: return pairs return pairs -def _overlap_bfs(in_tree, tree, halt): +def _overlap_bfs(in_tree, tree, halt, closed): pairs = [] queue = deque() queue.append((in_tree, tree)) while len(queue) > 0: s_node, t_node = queue.popleft() - if s_node.aabb.overlaps(t_node.aabb): + if s_node.aabb.overlaps(t_node.aabb, closed): if s_node.is_leaf and t_node.is_leaf: pairs.append((s_node.aabb, s_node.value)) if halt: diff --git a/docs/source/conf.py b/docs/source/conf.py index 8406518..f33f08e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -20,7 +20,7 @@ # -- Project information ----------------------------------------------------- project = 'AABBTree' -copyright = '2019-2020, Georgia Tech Research Corporation' +copyright = '2019-2021, Georgia Tech Research Corporation' author = 'Kenneth Hart' # The short X.Y version diff --git a/setup.py b/setup.py index 101cb86..74330b8 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def read(fname): setup( name='aabbtree', - version='2.6.2', + version='2.7.0', license='MIT', description='Pure Python implementation of d-dimensional AABB tree.', long_description=read('README.rst'), diff --git a/tests/test_aabb.py b/tests/test_aabb.py index 58985b9..802ce56 100644 --- a/tests/test_aabb.py +++ b/tests/test_aabb.py @@ -108,6 +108,21 @@ def test_overlaps(): assert not aabb2.overlaps(aabb3) +def test_overlaps_closed(): + aabb1 = AABB([(0, 0)]) + aabb2 = AABB([(-1, 0)]) + aabb3 = AABB([(1, 2)]) + aabb4 = AABB([(-9, -8)]) + + assert aabb1.overlaps(aabb2, True) + assert aabb2.overlaps(aabb1, True) + assert not aabb1.overlaps(aabb3, True) + assert not aabb2.overlaps(aabb3, True) + assert not aabb1.overlaps(aabb4, True) + assert not aabb2.overlaps(aabb4, True) + assert not aabb1.overlaps(AABB(), True) + + def test_corners(): lims = [(0, 10), (5, 10)] aabb_corners = [