-
Notifications
You must be signed in to change notification settings - Fork 146
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
create a quadtree implementation adapted for svg paths and segments
- Loading branch information
Showing
1 changed file
with
140 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
from path import Path | ||
|
||
|
||
class Point: | ||
def __init__(self, point: complex, path: Path = None): | ||
self.x = point.real | ||
self.y = point.imag | ||
self.path = path | ||
|
||
def translated(self, translation: complex): | ||
return Point(complex(self.x + translation.real, | ||
self.y + translation.imag), | ||
self.path) | ||
|
||
def to_tuple(self): | ||
return self.x, self.y | ||
|
||
|
||
class Rect: | ||
def __init__(self, origin: Point, width: float, height: float): | ||
self.origin = origin | ||
self.width = width | ||
self.height = height | ||
|
||
def in_bounds(self, p: Point): | ||
return self.origin.x <= p.x < self.origin.x + self.width and self.origin.y <= p.y < self.origin.y + self.height | ||
|
||
def overlaps(self, other): | ||
if other.origin.x > self.origin.x + self.width or \ | ||
other.origin.y > self.origin.y + self.height or \ | ||
other.origin.x + other.width < self.origin.x or \ | ||
other.origin.y + other.height < self.origin.y: | ||
return False | ||
return True | ||
|
||
def to_tuple(self): | ||
return self.origin.x, self.origin.y, self.width, self.height | ||
|
||
|
||
class QuadTree: | ||
def __init__(self, boundary: Rect, capacity: int): | ||
self.boundary = boundary | ||
self.capacity = capacity | ||
self.segments = set() | ||
self.is_split = False | ||
self.subtreeNE = None | ||
self.subtreeNW = None | ||
self.subtreeSE = None | ||
self.subtreeSW = None | ||
|
||
def split(self): | ||
self.subtreeNE = QuadTree( | ||
Rect(self.boundary.origin, | ||
int(self.boundary.width / 2), int(self.boundary.height / 2)), | ||
self.capacity | ||
) | ||
self.subtreeNW = QuadTree( | ||
Rect(self.boundary.origin.translated(complex(self.boundary.width / 2, 0)), | ||
int(self.boundary.width / 2), int(self.boundary.height / 2)), | ||
self.capacity | ||
) | ||
self.subtreeSE = QuadTree( | ||
Rect(self.boundary.origin.translated(complex(0, self.boundary.height / 2)), | ||
int(self.boundary.width / 2), int(self.boundary.height / 2)), | ||
self.capacity | ||
) | ||
self.subtreeSW = QuadTree( | ||
Rect(self.boundary.origin.translated(complex(self.boundary.width / 2, self.boundary.height / 2)), | ||
int(self.boundary.width / 2), int(self.boundary.height / 2)), | ||
self.capacity | ||
) | ||
self.is_split = True | ||
|
||
def get_all_unique_segments(self, unique_segments=None) -> set: | ||
if unique_segments is None: | ||
unique_segments = set() | ||
|
||
unique_segments = unique_segments.union(self.segments) | ||
if not self.is_split: | ||
return unique_segments | ||
|
||
unique_segments = unique_segments.union(self.subtreeNE.get_all_unique_segments(unique_segments)) | ||
unique_segments = unique_segments.union(self.subtreeNW.get_all_unique_segments(unique_segments)) | ||
unique_segments = unique_segments.union(self.subtreeSE.get_all_unique_segments(unique_segments)) | ||
unique_segments = unique_segments.union(self.subtreeSW.get_all_unique_segments(unique_segments)) | ||
|
||
return unique_segments | ||
|
||
def insert_segment(self, segment: Path): | ||
if not self.boundary.overlaps(bbox_to_rect(*segment.bbox())): | ||
return | ||
|
||
if len(self.segments) < self.capacity: | ||
self.segments.add(segment) | ||
else: | ||
if not self.is_split: | ||
self.split() | ||
|
||
self.subtreeNE.insert_segment(segment) | ||
self.subtreeNW.insert_segment(segment) | ||
self.subtreeSE.insert_segment(segment) | ||
self.subtreeSW.insert_segment(segment) | ||
|
||
def insert_path(self, path: Path): | ||
for segment in path: | ||
self.insert_segment(Path(segment)) | ||
|
||
def get_segments_in_area(self, area: Rect, out=None): | ||
if out is None: | ||
out = set() | ||
|
||
if not self.boundary.overlaps(area): | ||
return out | ||
|
||
out = out.union(self.segments) | ||
if self.is_split: | ||
out = out.union(self.subtreeNE.get_segments_in_area(area, out)) | ||
out = out.union(self.subtreeNW.get_segments_in_area(area, out)) | ||
out = out.union(self.subtreeSE.get_segments_in_area(area, out)) | ||
out = out.union(self.subtreeSW.get_segments_in_area(area, out)) | ||
|
||
return out | ||
|
||
def get_path_collisions(self, collision_path: Path, found_paths=None): | ||
if found_paths is None: | ||
found_paths = set() | ||
|
||
for segment in collision_path: | ||
segment = Path(segment) | ||
segments_in_area = self.get_segments_in_area(bbox_to_rect(*segment.bbox())) | ||
found_paths = found_paths.union(segments_in_area) | ||
|
||
return None, found_paths | ||
|
||
|
||
def bbox_to_rect(xmin: float, xmax: float, ymin: float, ymax: float, expansion=5) -> Rect: | ||
origin = Point(complex(xmin-expansion, ymin-expansion)) | ||
width = (xmax - xmin) + expansion | ||
height = (ymax - ymin) + expansion | ||
return Rect(origin, width, height) |