def test_step(priority_queue: PriorityQueue) -> None: original = deepcopy(priority_queue) result = priority_queue.pop() assert all(not item < priority_queue._item_factory(result) for item in priority_queue._items) assert len(priority_queue) == len(original) - 1
def test_base_case(priority_queue: PriorityQueue) -> None: with pytest.raises(IndexError): priority_queue.pop()
class ShapedEventsQueue(Generic[LeftShapedEvent]): __slots__ = 'context', 'event_cls', '_queue' def __init__(self, event_cls: Type[LeftShapedEvent], context: Context) -> None: self.event_cls, self.context = event_cls, context self._queue = PriorityQueue(key=partial(BinaryEventsQueueKey, context.angle_orientation)) __repr__ = generate_repr(__init__) def __bool__(self) -> bool: return bool(self._queue) @property def key(self) -> Callable[[ShapedEvent], BinaryEventsQueueKey]: return self._queue.key def detect_intersection(self, below_event: LeftShapedEvent, event: LeftShapedEvent) -> bool: relation = self.context.segments_relation(below_event, event) if relation is Relation.CROSS or relation is Relation.TOUCH: if (event.start != below_event.start and event.end != below_event.end): # segments do not intersect_multipolygons at endpoints point = self.context.segments_intersection(below_event, event) if point != below_event.start and point != below_event.end: self._divide_segment(below_event, point) if point != event.start and point != event.end: self._divide_segment(event, point) elif relation is not Relation.DISJOINT: # segments overlap if below_event.from_first is event.from_first: raise ValueError('Edges of the same geometry ' 'should not overlap.') starts_equal = below_event.start == event.start if starts_equal: start_min = start_max = None elif self.key(event) < self.key(below_event): start_min, start_max = event, below_event else: start_min, start_max = below_event, event ends_equal = event.end == below_event.end if ends_equal: end_min = end_max = None elif self.key(event.opposite) < self.key(below_event.opposite): end_min, end_max = event.opposite, below_event.opposite else: end_min, end_max = below_event.opposite, event.opposite if starts_equal: # both line segments are equal or share the left endpoint below_event.overlap_kind = event.overlap_kind = ( OverlapKind.SAME_ORIENTATION if event.interior_to_left is below_event.interior_to_left else OverlapKind.DIFFERENT_ORIENTATION) if not ends_equal: self._divide_segment(end_max.opposite, end_min.start) return True elif ends_equal: # the line segments share the right endpoint self._divide_segment(start_min, start_max.start) elif start_min is end_max.opposite: # one line segment includes the other one self._divide_segment(start_min, end_min.start) self._divide_segment(start_min, start_max.start) else: # no line segment includes the other one self._divide_segment(start_max, end_min.start) self._divide_segment(start_min, start_max.start) return False def pop(self) -> ShapedEvent: return self._queue.pop() def register(self, segments_endpoints: Iterable[SegmentEndpoints], from_first: bool) -> None: event_cls, push = self.event_cls, self._queue.push for segment_endpoints in segments_endpoints: event = event_cls.from_endpoints(segment_endpoints, from_first) push(event) push(event.opposite) def _divide_segment(self, event: LeftShapedEvent, point: Point) -> None: tail = event.divide(point) self._queue.push(tail) self._queue.push(event.opposite)
class Builder: __slots__ = ('index', '_beach_line', '_circle_events', '_end_points', 'site_events', '_site_event_index') def __init__(self, index: int = 0, site_events: Optional[List[SiteEvent]] = None) -> None: self.index = index self._beach_line = red_black.Tree.from_components([]) self._circle_events = PriorityQueue(key=itemgetter(0)) self._end_points = PriorityQueue(key=itemgetter(0)) self.site_events = [] if site_events is None else site_events self._site_event_index = None __repr__ = generate_repr(__init__) @property def beach_line(self) -> List[Tuple[BeachLineKey, BeachLineValue]]: return [node.item for node in self._beach_line] @property def site_event(self) -> SiteEvent: return self.site_events[self._site_event_index] @property def site_event_index(self) -> int: return (len(self.site_events) if self._site_event_index is None else self._site_event_index) def activate_circle_event(self, first_site: SiteEvent, second_site: SiteEvent, third_site: SiteEvent, bisector_node: red_black.Node) -> None: event = CircleEvent(0., 0., 0.) # check if the three input sites create a circle event if compute_circle_event(event, first_site, second_site, third_site): # add the new circle event to the circle events queue; # update bisector's circle event iterator to point # to the new circle event in the circle event queue self._circle_events.push((event, bisector_node)) bisector_node.value.circle_event = event def construct(self, output: 'Diagram') -> None: self.init_sites_queue() self.init_beach_line(output) while (self._circle_events or self._site_event_index < len(self.site_events)): if (not self._circle_events or (self._site_event_index < len(self.site_events) and self.site_event < self._circle_events.peek()[0])): self.process_site_event(output) else: self.process_circle_event(output) while (self._circle_events and not self._circle_events.peek()[0].is_active): self._circle_events.pop() self._beach_line.clear() output._build() @staticmethod def deactivate_circle_event(value: BeachLineValue) -> None: if value.circle_event is not None: value.circle_event.deactivate() value.circle_event = None def init_beach_line(self, diagram: 'Diagram') -> None: if not self.site_events: return elif len(self.site_events) == 1: # handle single site event case diagram._process_single_site(self.site_events[0]) self._site_event_index += 1 else: skip = 0 while (self._site_event_index < len(self.site_events) and are_vertical_endpoints(self.site_event.start, self.site_events[0].start) and self.site_event.is_vertical): self._site_event_index += 1 skip += 1 if skip == 1: # init beach line with the first two sites self.init_beach_line_default(diagram) else: # init beach line with collinear vertical sites self.init_beach_line_collinear_sites(diagram) def init_beach_line_collinear_sites(self, diagram: 'Diagram') -> None: first_index, second_index = 0, 1 while second_index != self._site_event_index: # create a new beach line node first_site, second_site = (self.site_events[first_index], self.site_events[second_index]) new_node = BeachLineKey(first_site, second_site) # update the diagram edge, _ = diagram._insert_new_edge(first_site, second_site) # insert a new bisector into the beach line self._beach_line.insert(new_node, BeachLineValue(edge)) first_index += 1 second_index += 1 def init_beach_line_default(self, diagram: 'Diagram') -> None: # get the first and the second site event self.insert_new_arc(self.site_events[0], self.site_events[0], self.site_events[1], diagram) # the second site was already processed, move the position self._site_event_index += 1 def insert_new_arc(self, first_arc_site: SiteEvent, second_arc_site: SiteEvent, site_event: SiteEvent, output: 'Diagram') -> red_black.Node: # create two new bisectors with opposite directions new_left_node = BeachLineKey(first_arc_site, site_event) new_right_node = BeachLineKey(site_event, second_arc_site) # set correct orientation for the first site of the second node if site_event.is_segment: new_right_node.left_site.inverse() # update the output edges = output._insert_new_edge(second_arc_site, site_event) self._beach_line.insert(new_right_node, BeachLineValue(edges[1])) if site_event.is_segment: # update the beach line with temporary bisector, # that will # disappear after processing site event # corresponding to the second endpoint of the segment site new_node = BeachLineKey(site_event, site_event) new_node.right_site.inverse() node = self._beach_line.insert(new_node, BeachLineValue(None)) # update the data structure that holds temporary bisectors self._end_points.push((site_event.end, node)) return self._beach_line.insert(new_left_node, BeachLineValue(edges[0])) def init_sites_queue(self) -> None: self.site_events.sort() self.site_events = to_unique_just_seen(self.site_events) for index, event in enumerate(self.site_events): event.sorted_index = index self._site_event_index = 0 def insert_point(self, point: Point) -> int: index = self.index self.site_events.append( SiteEvent.from_point(point, initial_index=index, source_category=SourceCategory.SINGLE_POINT)) self.index += 1 return index def insert_segment(self, segment: Segment) -> int: site_events = self.site_events index = self.index start, end = segment.start, segment.end site_events.append( SiteEvent.from_point( start, initial_index=index, source_category=SourceCategory.SEGMENT_START_POINT)) site_events.append( SiteEvent.from_point( end, initial_index=index, source_category=SourceCategory.SEGMENT_END_POINT)) site_events.append( SiteEvent(start, end, source_category=SourceCategory.INITIAL_SEGMENT, initial_index=index) if start < end else SiteEvent( end, start, source_category=SourceCategory.REVERSE_SEGMENT, initial_index=index)) self.index += 1 return index def process_circle_event(self, output: 'Diagram') -> None: circle_event, first_node = self._circle_events.pop() last_node = first_node second_site = copy(first_node.key.right_site) second_bisector = first_node.value.edge first_node = self._beach_line.predecessor(first_node) first_bisector = first_node.value.edge first_site = copy(first_node.key.left_site) if (not first_site.is_segment and second_site.is_segment and second_site.end == first_site.start): second_site.inverse() first_node.key.right_site = second_site first_node.value.edge, _ = output._insert_new_edge_from_intersection( first_site, second_site, circle_event, first_bisector, second_bisector) self._beach_line.remove(last_node) last_node = first_node if first_node is not self._beach_line.min(): self.deactivate_circle_event(first_node.value) first_node = self._beach_line.predecessor(first_node) self.activate_circle_event(first_node.key.left_site, first_site, second_site, last_node) last_node = self._beach_line.successor(last_node) if last_node is not red_black.NIL: self.deactivate_circle_event(last_node.value) self.activate_circle_event(first_site, second_site, last_node.key.right_site, last_node) def process_site_event(self, output: 'Diagram') -> None: last_index = self._site_event_index + 1 if not self.site_event.is_segment: while (self._end_points and self._end_points.peek()[0] == self.site_event.start): _, node = self._end_points.pop() self._beach_line.remove(node) else: while (last_index < len(self.site_events) and self.site_events[last_index].is_segment and (self.site_events[last_index].start == self.site_event.start)): last_index += 1 # find the node in the binary search tree # with left arc lying above the new site point new_key = BeachLineKey(self.site_event, self.site_event) right_node = self._beach_line.supremum(new_key) while self._site_event_index < last_index: site_event = copy(self.site_event) left_node = right_node if right_node is red_black.NIL: # the above arc corresponds to the second arc of the last node, # move the iterator to the last node left_node = self._beach_line.max() # get the second site of the last node arc_site = left_node.key.right_site # insert new nodes into the beach line, update the output right_node = self.insert_new_arc(arc_site, arc_site, site_event, output) # add a candidate circle to the circle event queue; # there could be only one new circle event # formed by a new bisector and the one on the left self.activate_circle_event(left_node.key.left_site, left_node.key.right_site, site_event, right_node) elif right_node is self._beach_line.min(): # the above arc corresponds to the first site of the first node arc_site = right_node.key.left_site # Insert new nodes into the beach line. Update the output. left_node = self.insert_new_arc(arc_site, arc_site, site_event, output) # if the site event is a segment, update its direction if site_event.is_segment: site_event.inverse() # add a candidate circle to the circle event queue; # there could be only one new circle event # formed by a new bisector and the one on the right self.activate_circle_event(site_event, right_node.key.left_site, right_node.key.right_site, right_node) right_node = left_node else: # the above arc corresponds neither to the first, # nor to the last site in the beach line second_arc_site = right_node.key.left_site third_site = right_node.key.right_site # remove the candidate circle from the event queue self.deactivate_circle_event(right_node.value) left_node = self._beach_line.predecessor(left_node) first_arc_site = left_node.key.right_site first_site = left_node.key.left_site # insert new nodes into the beach line. Update the output new_node = self.insert_new_arc(first_arc_site, second_arc_site, site_event, output) # add candidate circles to the circle event queue; # there could be up to two circle events # formed by a new bisector and the one on the left or right self.activate_circle_event(first_site, first_arc_site, site_event, new_node) # if the site event is a segment, update its direction if site_event.is_segment: site_event.inverse() self.activate_circle_event(site_event, second_arc_site, third_site, right_node) right_node = new_node self._site_event_index += 1
class NaryEventsQueue: __slots__ = 'context', '_queue' def __init__(self, context: Context) -> None: self.context = context self._queue = PriorityQueue(key=partial(NaryEventsQueueKey, context.angle_orientation)) __repr__ = generate_repr(__init__) def __bool__(self) -> bool: return bool(self._queue) @property def key(self) -> Callable[[NaryEvent], NaryEventsQueueKey]: return self._queue.key def detect_intersection(self, below_event: LeftNaryEvent, event: LeftNaryEvent) -> None: relation = self.context.segments_relation(below_event, event) if relation is Relation.CROSS or relation is Relation.TOUCH: if (event.start != below_event.start and event.end != below_event.end): # segments do not intersect_multipolygons at endpoints point = self.context.segments_intersection(below_event, event) if point != below_event.start and point != below_event.end: self._divide_segment(below_event, point) if point != event.start and point != event.end: self._divide_segment(event, point) elif relation is not Relation.DISJOINT: # segments overlap starts_equal = below_event.start == event.start if starts_equal: start_min = start_max = None elif self.key(event) < self.key(below_event): start_min, start_max = event, below_event else: start_min, start_max = below_event, event ends_equal = event.end == below_event.end if ends_equal: end_min = end_max = None elif self.key(event.opposite) < self.key(below_event.opposite): end_min, end_max = event.opposite, below_event.opposite else: end_min, end_max = below_event.opposite, event.opposite if starts_equal: # both line segments are equal or share the left endpoint if not ends_equal: self._divide_segment(end_max.opposite, end_min.start) elif ends_equal: # the line segments share the right endpoint self._divide_segment(start_min, start_max.start) elif start_min is end_max.opposite: # one line segment includes the other one self._divide_segment(start_min, end_min.start) self._divide_segment(start_min, start_max.start) else: # no line segment includes the other one self._divide_segment(start_max, end_min.start) self._divide_segment(start_min, start_max.start) def pop(self) -> NaryEvent: return self._queue.pop() def register(self, segments_endpoints: Iterable[SegmentEndpoints]) -> None: push = self._queue.push for segment_endpoints in segments_endpoints: event = LeftNaryEvent.from_segment_endpoints(segment_endpoints) push(event) push(event.opposite) def _divide_segment(self, event: LeftNaryEvent, point: Point) -> None: tail = LeftNaryEvent.divide(event, point) self._queue.push(tail) self._queue.push(event.opposite)
class MixedEventsQueue: __slots__ = 'context', '_queue' def __init__(self, context: Context) -> None: self.context = context self._queue = PriorityQueue(key=partial(BinaryEventsQueueKey, context.angle_orientation)) @property def key(self) -> Callable[[MixedEvent], BinaryEventsQueueKey]: return self._queue.key __repr__ = generate_repr(__init__) def __bool__(self) -> bool: return bool(self._queue) def detect_intersection(self, below_event: LeftMixedEvent, event: LeftMixedEvent) -> bool: relation = self.context.segments_relation(below_event, event) if relation is Relation.CROSS or relation is Relation.TOUCH: if (event.start != below_event.start and event.end != below_event.end): # segments do not intersect_multipolygons at endpoints point = self.context.segments_intersection(below_event, event) if point != below_event.start and point != below_event.end: self._divide_segment(below_event, point) if point != event.start and point != event.end: self._divide_segment(event, point) elif relation is not Relation.DISJOINT: # segments overlap if below_event.from_first is event.from_first: raise ValueError('Edges of the {geometry} ' 'should not overlap.' .format(geometry=('multisegment' if event.from_first else 'multipolygon'))) event.is_overlap = below_event.is_overlap = True starts_equal = below_event.start == event.start if starts_equal: start_min = start_max = None elif self.key(event) < self.key(below_event): start_min, start_max = event, below_event else: start_min, start_max = below_event, event ends_equal = event.end == below_event.end if ends_equal: end_min = end_max = None elif self.key(event.opposite) < self.key(below_event.opposite): end_min, end_max = event.opposite, below_event.opposite else: end_min, end_max = below_event.opposite, event.opposite if starts_equal: # both line segments are equal or share the left endpoint if not ends_equal: self._divide_segment(end_max.opposite, end_min.start) return True elif ends_equal: # the line segments share the right endpoint self._divide_segment(start_min, start_max.start) elif start_min is end_max.opposite: # one line segment includes the other one self._divide_segment(start_min, end_min.start) self._divide_segment(start_min, start_max.start) else: # no line segment includes the other one self._divide_segment(start_max, end_min.start) self._divide_segment(start_min, start_max.start) return False def pop(self) -> MixedEvent: return self._queue.pop() def register(self, segments_endpoints: Iterable[SegmentEndpoints], from_first: bool) -> None: push = self._queue.push for segment_endpoints in segments_endpoints: event = LeftMixedEvent.from_endpoints(segment_endpoints, from_first) push(event) push(event.opposite) def _divide_segment(self, event: LeftMixedEvent, point: Point) -> None: tail = event.divide(point) self._queue.push(tail) self._queue.push(event.opposite)
class EventsQueue: __slots__ = 'context', '_queue' def __init__(self, context: Context) -> None: self.context = context self._queue = PriorityQueue(key=EventsQueueKey) __repr__ = generate_repr(__init__) def __bool__(self) -> bool: return bool(self._queue) def detect_intersection(self, below_event: Event, event: Event) -> None: relation = self.context.segments_relation(below_event.start, below_event.end, event.start, event.end) if relation is Relation.TOUCH or relation is Relation.CROSS: # segments touch or cross point = self.context.segments_intersection(below_event.start, below_event.end, event.start, event.end) if point != below_event.start and point != below_event.end: self._divide_segment(below_event, point) if point != event.start and point != event.end: self._divide_segment(event, point) event.set_both_relations(max(event.relation, relation)) below_event.set_both_relations(max(below_event.relation, relation)) elif relation is not Relation.DISJOINT: # segments overlap starts_equal = event.start == below_event.start start_min, start_max = ((None, None) if starts_equal else ( (event, below_event) if EventsQueueKey(event) < EventsQueueKey(below_event) else (below_event, event))) ends_equal = event.end == below_event.end end_min, end_max = ((None, None) if ends_equal else ((event.complement, below_event.complement) if (EventsQueueKey(event.complement) < EventsQueueKey(below_event.complement)) else (below_event.complement, event.complement))) if starts_equal: if ends_equal: # segments are equal event.set_both_relations(relation) below_event.set_both_relations(relation) else: # segments share the left endpoint end_min.set_both_relations(relation) end_max.complement.relation = relation self._divide_segment(end_max.complement, end_min.start) elif ends_equal: # segments share the right endpoint start_max.set_both_relations(relation) start_min.complement.relation = relation self._divide_segment(start_min, start_max.start) elif start_min is end_max.complement: # one line segment includes the other one start_max.set_both_relations(relation) start_min_original_relationship = start_min.relation start_min.relation = relation self._divide_segment(start_min, end_min.start) start_min.relation = start_min_original_relationship start_min.complement.relation = relation self._divide_segment(start_min, start_max.start) else: # no line segment includes the other one start_max.relation = relation self._divide_segment(start_max, end_min.start) start_min.complement.relation = relation self._divide_segment(start_min, start_max.start) def peek(self) -> Event: return self._queue.peek() def pop(self) -> Event: return self._queue.pop() def push(self, event: Event) -> None: if event.start == event.end: raise ValueError('Degenerate segment found ' 'with both endpoints being: {}.'.format( event.start)) self._queue.push(event) def _divide_segment(self, event: Event, break_point: Point) -> None: left_event = event.complement.complement = Event( start=break_point, complement=event.complement, is_left_endpoint=True, relation=event.complement.relation, segments_ids=event.segments_ids) right_event = event.complement = Event( start=break_point, complement=event, is_left_endpoint=False, relation=event.relation, segments_ids=event.complement.segments_ids) self.push(left_event) self.push(right_event)
class EventsQueue: @classmethod def from_segments(cls, segments: Sequence[Segment], *, context: Context) -> 'EventsQueue': result = cls(context) for index, segment in enumerate(segments): event = LeftEvent.from_segment(segment, index) result.push(event) result.push(event.right) return result __slots__ = 'context', '_queue' def __init__(self, context: Context) -> None: self.context = context self._queue = PriorityQueue(key=EventsQueueKey) __repr__ = generate_repr(__init__) def __bool__(self) -> bool: return bool(self._queue) def detect_intersection(self, below_event: LeftEvent, event: LeftEvent, sweep_line: SweepLine) -> None: relation = self.context.segments_relation(below_event, event) if relation is Relation.DISJOINT: return elif relation is Relation.TOUCH or relation is Relation.CROSS: # segments touch or cross point = self.context.segments_intersection(below_event, event) assert event.segments_ids.isdisjoint(below_event.segments_ids) if point != below_event.start and point != below_event.end: below_below = sweep_line.below(below_event) assert not (below_below is not None and below_below.start == below_event.start and below_below.end == point) self.push(below_event.divide(point)) self.push(below_event.right) if point != event.start and point != event.end: above_event = sweep_line.above(event) if (above_event is not None and above_event.start == event.start and above_event.end == point): sweep_line.remove(above_event) self.push(event.divide(point)) self.push(event.right) event.merge_with(above_event) else: self.push(event.divide(point)) self.push(event.right) else: # segments overlap starts_equal = event.start == below_event.start start_min, start_max = ((event, below_event) if ( starts_equal or EventsQueueKey(event) < EventsQueueKey(below_event)) else (below_event, event)) ends_equal = event.end == below_event.end end_min, end_max = ((event.right, below_event.right) if ends_equal or (EventsQueueKey(event.right) < EventsQueueKey( below_event.right)) else (below_event.right, event.right)) if starts_equal: assert not ends_equal # segments share the left endpoint sweep_line.remove(end_max.left) self.push(end_max.left.divide(end_min.start)) event.merge_with(below_event) elif ends_equal: # segments share the right endpoint start_max.merge_with(start_min.divide(start_max.start)) self.push(start_min.right) elif start_min is end_max.left: # one line segment includes the other one self.push(start_min.divide(end_min.start)) self.push(start_min.right) start_max.merge_with(start_min.divide(start_max.start)) self.push(start_min.right) else: # no line segment includes the other one self.push(start_max.divide(end_min.start)) start_max.merge_with(start_min.divide(start_max.start)) self.push(start_max.right) self.push(start_min.right) def peek(self) -> Event: return self._queue.peek() def pop(self) -> Event: return self._queue.pop() def push(self, event: Event) -> None: if event.start == event.end: raise ValueError('Degenerate segment found ' 'with both endpoints being: {}.'.format( event.start)) self._queue.push(event)
class EventsQueue: __slots__ = '_queue', def __init__(self) -> None: self._queue = PriorityQueue(key=EventsQueueKey) @staticmethod def compute_position(below_event: Optional[Event], event: Event) -> None: if below_event is not None: event.other_interior_to_left = ( below_event.other_interior_to_left if (event.from_left is below_event.from_left) else below_event.interior_to_left) def detect_intersection(self, below_event: Event, event: Event) -> bool: """ Populates events queue with intersection events. Checks if events' segments overlap and have the same start. """ below_segment, segment = below_event.segment, event.segment relationship = segments_relationship(below_segment, segment) if relationship is SegmentsRelationship.OVERLAP: # segments overlap if below_event.from_left is event.from_left: raise ValueError('Edges of the same polygon ' 'should not overlap.') starts_equal = below_event.start == event.start ends_equal = below_event.end == event.end start_min, start_max = ((None, None) if starts_equal else ( (event, below_event) if (EventsQueueKey(event) < EventsQueueKey(below_event)) else (below_event, event))) end_min, end_max = ((None, None) if ends_equal else ((event.complement, below_event.complement) if (EventsQueueKey(event.complement) < EventsQueueKey(below_event.complement)) else (below_event.complement, event.complement))) if starts_equal: # both line segments are equal or share the left endpoint event.is_overlap = below_event.is_overlap = True if not ends_equal: self.divide_segment(end_max.complement, end_min.start) return True elif ends_equal: # the line segments share the right endpoint self.divide_segment(start_min, start_max.start) else: self.divide_segment( start_min # one line segment includes the other one if start_min is end_max.complement # no line segment includes the other one else start_max, end_min.start) self.divide_segment(start_min, start_max.start) elif (relationship is not SegmentsRelationship.NONE and below_event.start != event.start and below_event.end != event.end): # segments do not intersect at endpoints point = segments_intersection(below_segment, segment) if point != below_event.start and point != below_event.end: self.divide_segment(below_event, point) if point != event.start and point != event.end: self.divide_segment(event, point) return False def divide_segment(self, event: Event, point: Point) -> None: left_event = Event(True, point, event.complement, event.from_left, event.interior_to_left, event.edge) right_event = Event(False, point, event, event.from_left, event.interior_to_left, event.edge) event.complement.complement, event.complement = left_event, right_event self._queue.push(left_event) self._queue.push(right_event) def register_edge(self, edge: QuadEdge, *, from_left: bool, is_counterclockwise_contour: bool) -> None: start, end = edge.start, edge.end interior_to_left = is_counterclockwise_contour if start > end: start, end = end, start interior_to_left = not interior_to_left start_event = Event(True, start, None, from_left, interior_to_left, edge) end_event = Event(False, end, start_event, from_left, interior_to_left, edge) start_event.complement = end_event self._queue.push(start_event) self._queue.push(end_event) def register_segment(self, segment: Segment, *, from_left: bool, is_counterclockwise_contour: bool) -> None: start, end = segment interior_to_left = is_counterclockwise_contour if start > end: start, end = end, start interior_to_left = not interior_to_left start_event = Event(True, start, None, from_left, interior_to_left) end_event = Event(False, end, start_event, from_left, interior_to_left) start_event.complement = end_event self._queue.push(start_event) self._queue.push(end_event) def sweep(self) -> Iterable[Event]: sweep_line = SweepLine() while self._queue: event = self._queue.pop() if event.is_left_endpoint: sweep_line.add(event) above_event, below_event = (sweep_line.above(event), sweep_line.below(event)) self.compute_position(below_event, event) if (above_event is not None and self.detect_intersection(event, above_event)): self.compute_position(event, above_event) if (below_event is not None and self.detect_intersection(below_event, event)): self.compute_position(sweep_line.below(below_event), below_event) else: event = event.complement if event in sweep_line: above_event, below_event = (sweep_line.above(event), sweep_line.below(event)) sweep_line.remove(event) if above_event is not None and below_event is not None: self.detect_intersection(below_event, above_event) yield event