def draw_navmesh_neighbor_line(self, scene: QGraphicsScene, poly: Polygon,
                                   begin: ShapelyPoint) -> None:
        vertex = Point(begin.x, begin.y)
        centroid = poly.centroid
        direction = Point(centroid.x, centroid.y)
        end = vertex.point_from_heading(
            vertex.heading_between_point(direction),
            nautical_miles(2).meters)

        scene.addLine(
            QLineF(QPointF(*self._transform_point(vertex)),
                   QPointF(*self._transform_point(end))),
            CONST.COLORS["yellow"])
Ejemplo n.º 2
0
    def __init__(self, target: Point, home: Point, ip: Point,
                 coalition: Coalition) -> None:
        self._target = target
        # Normal join placement is based on the path from home to the IP. If no path is
        # found it means that the target is on a direct path. In that case we instead
        # want to enforce that the join point is:
        #
        # * Not closer to the target than the IP.
        # * Not too close to the home airfield.
        # * Not threatened.
        # * A minimum distance from the IP.
        # * Not too sharp a turn at the ingress point.
        self.ip = ShapelyPoint(ip.x, ip.y)
        self.threat_zone = coalition.opponent.threat_zone.all
        self.home = ShapelyPoint(home.x, home.y)

        self.ip_bubble = self.ip.buffer(
            coalition.doctrine.join_distance.meters)

        ip_distance = ip.distance_to_point(target)
        self.target_bubble = ShapelyPoint(target.x,
                                          target.y).buffer(ip_distance)

        # The minimum distance between the home location and the IP.
        min_distance_from_home = nautical_miles(5)

        self.home_bubble = self.home.buffer(min_distance_from_home.meters)

        excluded_zones = shapely.ops.unary_union(
            [self.ip_bubble, self.target_bubble, self.threat_zone])

        if not isinstance(excluded_zones, MultiPolygon):
            excluded_zones = MultiPolygon([excluded_zones])
        self.excluded_zones = excluded_zones

        ip_heading = target.heading_between_point(ip)

        # Arbitrarily large since this is later constrained by the map boundary, and
        # we'll be picking a location close to the IP anyway. Just used to avoid real
        # distance calculations to project to the map edge.
        large_distance = nautical_miles(400).meters
        turn_limit = 40
        ip_limit_ccw = ip.point_from_heading(ip_heading - turn_limit,
                                             large_distance)
        ip_limit_cw = ip.point_from_heading(ip_heading + turn_limit,
                                            large_distance)

        ip_direction_limit_wedge = Polygon([
            (ip.x, ip.y),
            (ip_limit_ccw.x, ip_limit_ccw.y),
            (ip_limit_cw.x, ip_limit_cw.y),
        ])

        permissible_zones = ip_direction_limit_wedge.difference(
            self.excluded_zones).difference(self.home_bubble)
        if permissible_zones.is_empty:
            permissible_zones = MultiPolygon([])
        if not isinstance(permissible_zones, MultiPolygon):
            permissible_zones = MultiPolygon([permissible_zones])
        self.permissible_zones = permissible_zones

        preferred_lines = ip_direction_limit_wedge.intersection(
            self.excluded_zones.boundary).difference(self.home_bubble)

        if preferred_lines.is_empty:
            preferred_lines = MultiLineString([])
        if not isinstance(preferred_lines, MultiLineString):
            preferred_lines = MultiLineString([preferred_lines])
        self.preferred_lines = preferred_lines
Ejemplo n.º 3
0
    def __init__(
        self,
        target: Point,
        home: Point,
        ip: Point,
        join: Point,
        coalition: Coalition,
        theater: ConflictTheater,
    ) -> None:
        self._target = target
        # Hold points are placed one of two ways. Either approach guarantees:
        #
        # * Safe hold point.
        # * Minimum distance to the join point.
        # * Not closer to the target than the join point.
        #
        # 1. As near the join point as possible with a specific distance from the
        #    departure airfield. This prevents loitering directly above the airfield but
        #    also keeps the hold point close to the departure airfield.
        #
        # 2. Alternatively, if the entire home zone is excluded by the above criteria,
        #    as neat the departure airfield as possible within a minimum distance from
        #    the join point, with a restricted turn angle at the join point. This
        #    handles the case where we need to backtrack from the departure airfield and
        #    the join point to place the hold point, but the turn angle limit restricts
        #    the maximum distance of the backtrack while maintaining the direction of
        #    the flight plan.
        self.threat_zone = coalition.opponent.threat_zone.all
        self.home = ShapelyPoint(home.x, home.y)

        self.join = ShapelyPoint(join.x, join.y)

        self.join_bubble = self.join.buffer(
            coalition.doctrine.push_distance.meters)

        join_to_target_distance = join.distance_to_point(target)
        self.target_bubble = ShapelyPoint(
            target.x, target.y).buffer(join_to_target_distance)

        self.home_bubble = self.home.buffer(
            coalition.doctrine.hold_distance.meters)

        excluded_zones = shapely.ops.unary_union(
            [self.join_bubble, self.target_bubble, self.threat_zone])
        if not isinstance(excluded_zones, MultiPolygon):
            excluded_zones = MultiPolygon([excluded_zones])
        self.excluded_zones = excluded_zones

        join_heading = ip.heading_between_point(join)

        # Arbitrarily large since this is later constrained by the map boundary, and
        # we'll be picking a location close to the IP anyway. Just used to avoid real
        # distance calculations to project to the map edge.
        large_distance = nautical_miles(400).meters
        turn_limit = 40
        join_limit_ccw = join.point_from_heading(join_heading - turn_limit,
                                                 large_distance)
        join_limit_cw = join.point_from_heading(join_heading + turn_limit,
                                                large_distance)

        join_direction_limit_wedge = Polygon([
            (join.x, join.y),
            (join_limit_ccw.x, join_limit_ccw.y),
            (join_limit_cw.x, join_limit_cw.y),
        ])

        permissible_zones = (coalition.nav_mesh.map_bounds(
            theater).intersection(join_direction_limit_wedge).difference(
                self.excluded_zones).difference(self.home_bubble))
        if not isinstance(permissible_zones, MultiPolygon):
            permissible_zones = MultiPolygon([permissible_zones])
        self.permissible_zones = permissible_zones
        self.preferred_lines = self.home_bubble.boundary.difference(
            self.excluded_zones)