def test_step(context: Context, contour: Contour) -> None: first_vertex, rest_contour = pop_left_vertex(contour) rest_vertices = rest_contour.vertices result = contour_self_intersects(rest_contour) next_result = contour_self_intersects(contour) first_edge = context.segment_cls(first_vertex, rest_vertices[0]) last_edge = context.segment_cls(rest_vertices[-1], first_vertex) rest_edges = contour_to_edges(rest_contour) overlap_relations = (Relation.COMPONENT, Relation.COMPOSITE, Relation.EQUAL, Relation.OVERLAP) assert (next_result is (result and len(rest_vertices) > 2 and (any( context.segments_relation(rest_edges[index], rest_edges[other_index]) is not Relation.DISJOINT for index in range(len(rest_edges) - 1) for other_index in chain(range(index - 1), range(index + 2, len(rest_edges) - 1)) ) or any( context.segments_relation(edge, other_edge) in overlap_relations for edge, other_edge in combinations(rest_edges[:-1], 2) )) or any( context.segments_relation(first_edge, edge) is not Relation.DISJOINT for edge in rest_edges[1:-1]) or any( context.segments_relation(last_edge, edge) is not Relation.DISJOINT for edge in rest_edges[:-2]) or len(rest_vertices) > 1 and ( context.segments_relation(first_edge, rest_edges[0]) in overlap_relations or context.segments_relation( first_edge, last_edge) in overlap_relations or context.segments_relation( last_edge, rest_edges[0]) in overlap_relations)))
def _symmetric_subtract_segments_overlap(minuend: Segment, subtrahend: Segment, context: Context) -> Multisegment: left_start, left_end, right_start, right_end = sorted([ minuend.start, minuend.end, subtrahend.start, subtrahend.end]) return context.multisegment_cls([context.segment_cls(left_start, left_end), context.segment_cls(right_start, right_end)])
def _subtract_segments_overlap(minuend: Segment, subtrahend: Segment, context: Context) -> Segment: left_start, left_end, right_start, right_end = sorted([ minuend.start, minuend.end, subtrahend.start, subtrahend.end]) return (context.segment_cls(left_start, left_end) if left_start == minuend.start or left_start == minuend.end else context.segment_cls(right_start, right_end))
def _relate_segment_to_contour(contour: Contour, segment: Segment, context: Context) -> Relation: # similar to segment-in-contour check # but cross has higher priority over overlap # because cross with contour will be considered as cross with region # whereas overlap with contour can't be an overlap with region # and should be classified by further analysis has_no_touch = has_no_overlap = True last_touched_edge_index = last_touched_edge_start = None start, end = segment.start, segment.end for index, edge in enumerate(context.contour_segments(contour)): edge_start, edge_end = edge_endpoints = edge.start, edge.end relation_with_edge = relate_segments(edge, segment, context) if (relation_with_edge is Relation.COMPONENT or relation_with_edge is Relation.EQUAL): return Relation.COMPONENT elif (relation_with_edge is Relation.OVERLAP or relation_with_edge is Relation.COMPOSITE): if has_no_overlap: has_no_overlap = False elif relation_with_edge is Relation.TOUCH: if has_no_touch: has_no_touch = False elif (index - last_touched_edge_index == 1 and start not in edge_endpoints and end not in edge_endpoints and (context.angle_orientation( start, end, edge_start) is Orientation.COLLINEAR) and point_vertex_line_divides_angle( start, last_touched_edge_start, edge_start, edge_end, context)): return Relation.CROSS last_touched_edge_index = index last_touched_edge_start = edge_start elif relation_with_edge is Relation.CROSS: return Relation.CROSS vertices = contour.vertices if not has_no_touch and last_touched_edge_index == len(vertices) - 1: first_edge_endpoints = first_edge_start, first_edge_end = ( vertices[-1], vertices[0]) if (relate_segments( context.segment_cls(first_edge_start, first_edge_end), segment, context) is Relation.TOUCH and start not in first_edge_endpoints and end not in first_edge_endpoints and (context.angle_orientation(start, end, first_edge_start) is Orientation.COLLINEAR) and point_vertex_line_divides_angle( start, vertices[-2], first_edge_start, first_edge_end, context)): return Relation.CROSS return ((Relation.DISJOINT if has_no_touch else Relation.TOUCH) if has_no_overlap else Relation.OVERLAP)
def relate_segment(contour: Contour, segment: Segment, context: Context) -> Relation: angle_orientation = context.angle_orientation has_no_touch = has_no_cross = True last_touched_edge_index = last_touched_edge_start = None start, end = segment.start, segment.end for index, sub_segment in enumerate(context.contour_segments(contour)): sub_segment_start, sub_segment_end = sub_segment_endpoints = ( sub_segment.start, sub_segment.end) relation = relate_segments(sub_segment, segment, context) if relation is Relation.COMPONENT or relation is Relation.EQUAL: return Relation.COMPONENT elif relation is Relation.OVERLAP or relation is Relation.COMPOSITE: return Relation.OVERLAP elif relation is Relation.TOUCH: if has_no_touch: has_no_touch = False elif (has_no_cross and index - last_touched_edge_index == 1 and start not in sub_segment_endpoints and end not in sub_segment_endpoints and (angle_orientation( start, end, sub_segment_start) is Orientation.COLLINEAR) and point_vertex_line_divides_angle( start, last_touched_edge_start, sub_segment_start, sub_segment_end, context)): has_no_cross = False last_touched_edge_index = index last_touched_edge_start = sub_segment_start elif has_no_cross and relation is Relation.CROSS: has_no_cross = False vertices = contour.vertices if (has_no_cross and not has_no_touch and last_touched_edge_index == len(vertices) - 1): first_sub_segment_endpoints = (first_sub_segment_start, first_sub_segment_end) = (vertices[-1], vertices[0]) if (relate_segments( context.segment_cls(first_sub_segment_start, first_sub_segment_end), segment, context) is Relation.TOUCH and start not in first_sub_segment_endpoints and end not in first_sub_segment_endpoints and (angle_orientation(start, end, first_sub_segment_start) is Orientation.COLLINEAR) and point_vertex_line_divides_angle( start, vertices[-2], first_sub_segment_start, first_sub_segment_end, context)): has_no_cross = False return ((Relation.DISJOINT if has_no_touch else Relation.TOUCH) if has_no_cross else Relation.CROSS)
def _unite_segments_touch(first: Segment, second: Segment, context: Context) -> Union_[Multisegment, Segment]: return (context.multisegment_cls([first, second]) if ((first.start != second.start or (context.angle_orientation(first.start, first.end, second.end) is not Orientation.COLLINEAR)) and (first.start != second.end or (context.angle_orientation(first.start, first.end, second.start) is not Orientation.COLLINEAR)) and (first.end != second.start or (context.angle_orientation(first.end, first.start, second.end) is not Orientation.COLLINEAR)) and (first.end != second.end or (context.angle_orientation(first.end, first.start, second.start) is not Orientation.COLLINEAR))) else context.segment_cls(min(first.start, first.end, second.start, second.end), max(first.start, first.end, second.start, second.end)))
def _subtract_segments_composite(minuend: Segment, subtrahend: Segment, context: Context ) -> Union_[Multisegment, Segment]: left_start, left_end, right_start, right_end = sorted([ minuend.start, minuend.end, subtrahend.start, subtrahend.end]) return (context.segment_cls(right_start, right_end) if left_start == subtrahend.start or left_start == subtrahend.end else (((context.segment_cls(left_start, left_end) if right_start == right_end else context.multisegment_cls([context.segment_cls(left_start, left_end), context.segment_cls(right_start, right_end)])) if (right_start == subtrahend.start or right_start == subtrahend.end) else context.segment_cls(left_start, left_end)) if left_end == subtrahend.start or left_end == subtrahend.end else context.segment_cls(left_start, right_start)))
def _intersect_segments_overlap(first: Segment, second: Segment, context: Context) -> Segment: _, start, end, _ = sorted([first.start, first.end, second.start, second.end]) return context.segment_cls(start, end)
def _unite_segments_overlap(first: Segment, second: Segment, context: Context) -> Segment: start, _, _, end = sorted([first.start, first.end, second.start, second.end]) return context.segment_cls(start, end)
def test_base_case(context: Context, contour: Contour) -> None: result = contour_self_intersects(contour) left_vertex, mid_vertex, right_vertex = sorted(contour.vertices) assert result is context.segment_contains_point( context.segment_cls(left_vertex, right_vertex), mid_vertex)