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"])
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
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)