def find_supporting_pair( point: TPoint, polygon: TPolygon, polygon_number: int, angles: Optional[TAngles]) -> Optional[Tuple[PointData, PointData]]: """ Find pair of supporting points from point to polygon in log time (binary search). :param point: visibility point (x, y) :param polygon: convex, given counter-clockwise, first and last points must be equal :param polygon_number: sequence number of polygon for PointData :param angles: tuple of polar angles from #0 point of polygon to all others except itself :return: pair of PointData of supporting points or None if unable to find """ polygon_size = len(polygon) - 1 assert polygon_size >= 2 assert compare_points(polygon[0], polygon[-1]) if polygon_size <= 3: return find_supporting_pair_semiplanes(point, polygon, polygon_number) assert turn(polygon[0], polygon[1], polygon[2]) >= 0 assert len(angles) == polygon_size - 1 start_to_point = localize_convex(point, polygon, angles, False) # ray polygon[0], point intersects polygon if start_to_point[1] is not None: index1 = find_supporting_point(point, polygon, 0, start_to_point[1], True) if index1 is None: return None index2 = find_supporting_point(point, polygon, start_to_point[1], polygon_size - 1, False) if index2 is None: return None else: point_to_start = localize_convex(point, polygon, angles, True) # ray polygon[0], point does not intersect polygon if point_to_start[1] is not None: index1 = find_supporting_point(point, polygon, 0, point_to_start[1], False) if index1 is None: return None index2 = find_supporting_point(point, polygon, point_to_start[1], polygon_size - 1, True) if index2 is None: return None # polygon[0] is supporting point else: index1 = 0 index2 = find_supporting_point( point, polygon, 1, polygon_size - 1, turn(polygon[0], polygon[1], point) >= 0) if index2 is None: return None return (polygon[index1], polygon_number, index1, True, 0), (polygon[index2], polygon_number, index2, True, 0)
def localize_convex_linear(point: TPoint, polygon: TPolygon) -> bool: """ Localize point inside convex polygon in linear time. :param point: point to be localized (x, y) :param polygon: convex, given counter-clockwise, first and last points must be equal """ polygon_size = len(polygon) - 1 assert polygon_size >= 2 assert compare_points(polygon[0], polygon[-1]) if polygon_size <= 2: return False polygon_cross = turn(polygon[0], polygon[1], polygon[2]) for i in range(polygon_size): if turn(polygon[i], polygon[i + 1], point) * polygon_cross <= 0: return False return True
def find_supporting_pair_brute(point, polygon, polygon_size, point_number): result = list() for i in range(polygon_size): pi = polygon[i] if point_number is not None and i == point_number: continue if turn(point, pi, polygon[(i - 1) % polygon_size]) * turn(point, pi, polygon[(i + 1) % polygon_size]) < 0: continue # check intersection with all other points for j in range(polygon_size): if j in (i - 1, i) or (point_number is not None and j in (point_number - 1, point_number)): continue if check_ray_segment_intersection(point, pi, polygon[j], polygon[j + 1], False): break else: result.append(i) if len(result) != 2: return None point1, point2 = result return point1, point2
def find_supporting_pair_semiplanes(point: TPoint, polygon: TPolygon, polygon_number: int) -> Optional[tuple]: """ Find pair of supporting points from point to polygon in linear line. :param point: visibility point (x, y) :param polygon: convex, first and last points must be equal :param polygon_number: sequence number of polygon for PointData :return: pair of PointData of supporting points or None if unable to find """ polygon_size = len(polygon) - 1 assert polygon_size >= 2 assert compare_points(polygon[0], polygon[-1]) if polygon_size == 2: return (polygon[0], polygon_number, 0, True, 0), (polygon[1], polygon_number, 1, True, 0) semiplanes = [1] * polygon_size count = 0 polygon_turn = turn(polygon[0], polygon[1], polygon[2]) # fill an array of angles containing (1) or not containing (0) point (2 subsets) for i in range(polygon_size): if turn(polygon[i % polygon_size], polygon[ (i + 1) % polygon_size], point) * polygon_turn < 0: semiplanes[i] = 0 count += 1 if count in (0, polygon_size): return None start = semiplanes.index(1) if semiplanes[0] == 0 else semiplanes.index(0) end = (len(semiplanes) - semiplanes[::-1].index(1)) % polygon_size if semiplanes[0] == 0 else \ (len(semiplanes) - semiplanes[::-1].index(0)) % polygon_size return (polygon[start], polygon_number, start, True, 0), (polygon[end], polygon_number, end, True, 0)
def get_edges_sweepline(self, point: TPoint) -> List[PointData]: # list of points sorted by angle points = list() for edge in self.__segments: points.append((edge[0], edge[1])) # keep info about pair and PointData points.append((edge[1], edge[0])) # keep info about pair and PointData points.sort(key=lambda x: polar_angle(point, x[0][0])) # list of segments intersected by 0-angle ray intersected = OrderedDict() for edge in self.__segments: if check_ray_segment_intersection(point, (point[0] + 1, point[1]), edge[0][0], edge[1][0], True): index1 = str((edge[0][1], edge[0][2], edge[0][3] | 0)) index2 = str((edge[1][1], edge[1][2], edge[1][3] | 0)) intersected[index1 + index2] = (edge[0][0], edge[1][0]) self.__segments.clear() visible_edges = dict() for p in points: # check if any of the segments in intersected list crosses current segment for segment in reversed(intersected.values()): if check_segment_intersection(point, p[0][0], segment[0], segment[1]): break else: if self.__restriction_pair is None: visible_edges[str(p[0][1]) + str(p[0][2]) + str(p[0][3] | 0)] = p[0] else: l_point, r_point = self.__restriction_pair if point_in_angle(p[0][0], l_point, self.__restriction_point, r_point) != self.__reverse_angle: visible_edges[str(p[0][1]) + str(p[0][2]) + str(p[0][3] | 0)] = p[0] # update intersected list index1 = str((p[0][1], p[0][2], p[0][3] | 0)) index2 = str((p[1][1], p[1][2], p[1][3] | 0)) if turn(point, p[0][0], p[1][0]) > 0: intersected[index1 + index2] = (p[0][0], p[1][0]) else: intersected.pop(index1 + index2, None) return list(visible_edges.values())
def find_supporting_point(point: TPoint, polygon: TPolygon, low: int, high: int, low_contains: bool) -> Optional[int]: """ Find supporting point from point to polygon (index between low and high) with binary search. :param point: visibility point (x, y) :param polygon: convex, given counter-clockwise, first and last points must be equal :param low: binary search algorithm parameter (polygon min index) :param high: binary search algorithm parameter (polygon max index) :param low_contains: angle formed by polygon[low] contains point (True) or not (False) :return: index of supporting point from point to polygon or None if unable to find """ polygon_size = len(polygon) - 1 assert polygon_size >= 3 assert compare_points(polygon[0], polygon[-1]) assert turn(polygon[0], polygon[1], polygon[2]) >= 0 mid = (high + low) // 2 while low <= high and mid < polygon_size: # supporting point separating 2 subsets is found if turn(polygon[mid], polygon[(mid + 1) % polygon_size], point) <= 0 and \ turn(polygon[(mid - 1) % polygon_size], polygon[mid], point) >= 0 or \ turn(polygon[mid], polygon[(mid + 1) % polygon_size], point) >= 0 and \ turn(polygon[(mid - 1) % polygon_size], polygon[mid], point) <= 0: return mid # update mid if low_contains and turn(polygon[mid], polygon[(mid + 1) % polygon_size], point) >= 0 or \ not low_contains and turn(polygon[mid], polygon[(mid + 1) % polygon_size], point) <= 0: low = mid + 1 else: high = mid - 1 mid = (high + low) // 2 return None
def localize_convex(point: TPoint, polygon: TPolygon, angles: Optional[TAngles], reverse_angle: bool = False) -> Tuple[bool, Optional[int]]: """ Localize point inside convex polygon in log time (Preparata-Shamos algorithm). :param point: point to be localized (x, y) :param polygon: convex, given counter-clockwise, first and last points must be equal :param angles: tuple of polar angles from #0 point of polygon to all others except itself :param reverse_angle: point angles should be turned on pi (True) or not (False) :return: tuple of 2 elements: 1. bool - point is inside polygon 2. None if point is inside polygon else int - number of polygon vertex where point_angle is located. """ polygon_size = len(polygon) - 1 assert polygon_size >= 2 assert compare_points(polygon[0], polygon[-1]) if polygon_size <= 2: return False, None assert turn(polygon[0], polygon[1], polygon[2]) >= 0 assert len(angles) == polygon_size - 1 if compare_points(point, polygon[0]): return True, None point_angle = polar_angle(point, polygon[0]) if reverse_angle else polar_angle( polygon[0], point) # point angle not between angles if angles[-1] < pi < angles[0]: if angles[-1] <= point_angle <= angles[0]: return False, None elif point_angle <= angles[0] or point_angle >= angles[-1]: return False, None angles_len = len(angles) mid = (angles_len - 1) // 2 low = 0 high = angles_len - 1 while low <= high: if mid + 1 == angles_len: return False, None angle1 = angles[mid] angle2 = angles[mid + 1] # 2 angles contain zero-angle if angle1 > pi > angle2: if point_angle >= angle1 or point_angle <= angle2: return turn(polygon[mid + 1], polygon[mid + 2], point) > 0, mid + 2 if point_angle > pi: high = mid - 1 if point_angle < pi: low = mid + 1 else: if angle1 <= point_angle <= angle2: return turn(polygon[mid + 1], polygon[mid + 2], point) > 0, mid + 2 if point_angle - pi > angle2: high = mid - 1 elif point_angle + pi < angle1: low = mid + 1 elif point_angle < angle1: high = mid - 1 else: low = mid + 1 mid = (high + low) // 2 return False, None
def check_polygon_direction(polygon: TPolygon) -> TPolygon: return tuple(reversed(polygon)) if len(polygon) >= 3 and turn( polygon[0], polygon[1], polygon[2]) < 0 else polygon