def hit_polygon_boundary(p: Union[PolygonPoint, EdgePoint], q: Point, polygon: Polygon) -> EdgePoint: """O(n): Return the index of the edge we hit when shooting from p to q.""" assert isinstance(p, (PolygonPoint, EdgePoint)) assert isinstance(q, Point) assert isinstance(polygon, Polygon) # We save some edge indices which are ignored when testing edge intersections. # This is to take rounding errors into account. # The forbidden indices are either both edges left and right of the starting point (which in this case is a vertex) # or the edge index. If a starting point has a "None" index we get (None, ) but this is ok for us. forbidden_indices = (p.index, ) if isinstance(p, PolygonPoint) and p.index is not None: forbidden_indices = (p.index, polygon.prev(p.index)) # We now construct a ray from p through q and check for intersection with every polygon edge. # We take the intersection point with the shortest distance to p since this is the only points which can be seen # from p. ray = Ray(p, q) hit_point = None distance = None for ix in polygon.indices(): edge = polygon.edge(ix) if ix not in forbidden_indices and ray.properly_intersects(edge): tmp_hit = ray.intersection_point(edge) tmp_distance = p.squared_distance_to(tmp_hit) if distance is None or tmp_distance < distance: hit_point = EdgePoint(tmp_hit, edge.a.index) distance = tmp_distance # TODO: Remember what exactly this was for. if hit_point is None: hit_point = q return hit_point
def in_subpolygon(polygon: Polygon, q1: Point, q2: Point, t: Point, t_trapezoid: Trapezoid) -> bool: """O(1): Return whether `t` lies inside the subpolygon of `polygon` to the right of `q1`,`q2`.""" assert isinstance(q1, (PolygonPoint, EdgePoint)) assert isinstance(q2, (PolygonPoint, EdgePoint)) assert isinstance(t, Point) assert isinstance(t_trapezoid, Trapezoid) assert isinstance(polygon, Polygon) # Because our line (q1,q2) can start and/or end on edges we need to be careful in taking decisions. # Find the vertex indices for q1,q2. If q1 lies on an edge we need to increase the index by one to have the smaller # subpolygon. ix1 = q1.index if isinstance(q1, EdgePoint): ix1 = polygon.next(ix1) # The second index does not need special treatment since the index already shrinks the polygon. ix2 = q2.index if ix1 == ix2: assert isinstance(q1, EdgePoint) return Point.turn(q1, q2, t) != Point.CCW_TURN # First we check in which part of the (possibly) smaller subpolygon our trapezoid lies. small_polygon_position = trapezoid_subpolygon_position( polygon, ix1, ix2, t_trapezoid) # If the trapezoid lies right to the line it is safe. if small_polygon_position == 1: return True # In the other cases we should have a closer look. # Now we widen the subpolygon s.t. we can check whether t lies completely outside our range. if isinstance(q1, EdgePoint): ix1 = polygon.prev(ix1) if isinstance(q2, EdgePoint): ix2 = polygon.next(ix2) # If the point does not lie inside this bigger subpolygon we can safely say it does not lie in the actual # subpolygon if ix1 != ix2: big_polygon_position = trapezoid_subpolygon_position( polygon, ix1, ix2, t_trapezoid) if big_polygon_position == small_polygon_position == -1: return False # Now the trapezoid lies somewhere between the small and the big subpolygon. # First we can decide with respect to the x-coordinates -- just checking where t lies with respect to line(q1,q2) # does not work! if t.is_right_of(q1) and t.is_right_of(q2): if ((isinstance(q1, EdgePoint) and q1.index == t_trapezoid.bot_edge_ix) or (isinstance(q2, EdgePoint) and q2.index == t_trapezoid.top_edge_ix)): return True if ((isinstance(q1, EdgePoint) and q1.index == t_trapezoid.top_edge_ix) or (isinstance(q2, EdgePoint) and q2.index == t_trapezoid.bot_edge_ix)): return False if t.left_of(q1) and t.left_of(q2): if ((isinstance(q1, EdgePoint) and q1.index == t_trapezoid.top_edge_ix) or (isinstance(q2, EdgePoint) and q2.index == t_trapezoid.bot_edge_ix)): return True if ((isinstance(q1, EdgePoint) and q1.index == t_trapezoid.bot_edge_ix) or (isinstance(q2, EdgePoint) and q2.index == t_trapezoid.top_edge_ix)): return False return Point.turn(q1, q2, t) != Point.CCW_TURN