|
| 1 | +# Credits to magwo (Magnus Wolffelt) |
| 2 | +import math |
| 3 | + |
| 4 | +from core.utils.helper import DictWrapper |
| 5 | +from .trigonometric_carrier_cruise import get_ship_course_and_speed |
| 6 | +from .me_utils import Speed, Distance, Heading, knots, HeadingAndSpeed |
| 7 | + |
| 8 | +__all__ = ['relocate_carrier'] |
| 9 | + |
| 10 | + |
| 11 | +# |
| 12 | +# TAKEN FROM PyDCS! >>> START |
| 13 | +# |
| 14 | +def point_from_heading(_x: float, _y: float, heading: float, distance: float) -> tuple[float, float]: |
| 15 | + """Calculates a point from a given point, heading and distance. |
| 16 | +
|
| 17 | + :param _x: source point x |
| 18 | + :param _y: source point y |
| 19 | + :param heading: heading in degrees from source point |
| 20 | + :param distance: distance from source point |
| 21 | + :return: returns a tuple (x, y) of the calculated point |
| 22 | + """ |
| 23 | + while heading < 0: |
| 24 | + heading += 360 |
| 25 | + heading %= 360 |
| 26 | + rad_heading = math.radians(heading) |
| 27 | + x = _x + math.cos(rad_heading) * distance |
| 28 | + y = _y + math.sin(rad_heading) * distance |
| 29 | + |
| 30 | + return x, y |
| 31 | + |
| 32 | + |
| 33 | +def heading_between_points(x1: float, y1: float, x2: float, y2: float) -> float: |
| 34 | + """Returns the angle between 2 points in degrees. |
| 35 | +
|
| 36 | + :param x1: x coordinate of point 1 |
| 37 | + :param y1: y coordinate of point 1 |
| 38 | + :param x2: x coordinate of point 2 |
| 39 | + :param y2: y coordinate of point 2 |
| 40 | + :return: angle in degrees |
| 41 | + """ |
| 42 | + def angle_trunc(a): |
| 43 | + while a < 0.0: |
| 44 | + a += math.pi * 2 |
| 45 | + return a |
| 46 | + deltax = x2 - x1 |
| 47 | + deltay = y2 - y1 |
| 48 | + return math.degrees(angle_trunc(math.atan2(deltay, deltax))) |
| 49 | + |
| 50 | + |
| 51 | +def distance_to_point(x1: float, y1: float, x2: float, y2: float) -> float: |
| 52 | + """Returns the distance between 2 points |
| 53 | +
|
| 54 | + :param x1: x coordinate of point 1 |
| 55 | + :param y1: y coordinate of point 1 |
| 56 | + :param x2: x coordinate of point 2 |
| 57 | + :param y2: y coordinate of point 2 |
| 58 | + :return: distance in point units(m) |
| 59 | + """ |
| 60 | + return math.hypot(x2 - x1, y2 - y1) |
| 61 | +# |
| 62 | +# TAKEN FROM PyDCS <<<< END |
| 63 | +# |
| 64 | + |
| 65 | +def get_carrier_cruise(wind: dict, deck_angle: float, desired_apparent_wind: Speed) -> HeadingAndSpeed: |
| 66 | + wind_speed = Speed.from_meters_per_second(wind.get('speed', 0)) |
| 67 | + heading, speed, apparent_wind_angle = get_ship_course_and_speed( |
| 68 | + wind.get('dir', 0), wind_speed.knots, desired_apparent_wind.knots |
| 69 | + ) |
| 70 | + # Quick hack for Tarawa |
| 71 | + if deck_angle == 0: |
| 72 | + wind_heading = Heading( wind.get('dir', 0)) |
| 73 | + heading = wind_heading.opposite.degrees |
| 74 | + |
| 75 | + solution = HeadingAndSpeed( |
| 76 | + Heading.from_degrees(heading), Speed.from_knots(speed) |
| 77 | + ) |
| 78 | + return solution |
| 79 | + |
| 80 | + |
| 81 | +def rotate_group_around(group: DictWrapper, pivot: tuple[float, float], degrees_change: float): |
| 82 | + # My fear was that using sin/cos would result in incorrect |
| 83 | + # transforms when not near the equator. Polar coordinates (heading + distance) |
| 84 | + # should resolve that, if the Point functions are implemented correctly. |
| 85 | + # I think they're not, but this code at least doesn't prevent correct transform. |
| 86 | + # Maybe DCS doesn't even use mercator projection. |
| 87 | + for unit in group.units: |
| 88 | + distance = distance_to_point(pivot[0], pivot[1], unit.x, unit.y) |
| 89 | + heading = heading_between_points(pivot[0], pivot[1], unit.x, unit.y) |
| 90 | + new_heading = Heading.from_degrees(heading + degrees_change).degrees |
| 91 | + |
| 92 | + unit.x, unit.y = point_from_heading(pivot[0], pivot[1], new_heading, distance) |
| 93 | + unit.heading = Heading.from_degrees(unit.heading + degrees_change).degrees |
| 94 | + |
| 95 | + |
| 96 | +def relocate_carrier(_: dict, reference: dict, **kwargs): |
| 97 | + # create a wrapper, to make it easier (and to mainly keep the old code) |
| 98 | + group = DictWrapper(reference) |
| 99 | + route = group.route |
| 100 | + |
| 101 | + wind = kwargs.get('wind', {}) |
| 102 | + carrier = group.units[0] |
| 103 | + deck_angle = 0 if carrier.type == 'LHA_Tarawa' else -9.12 |
| 104 | + cruise = get_carrier_cruise(wind, deck_angle, Speed.from_knots(25)) |
| 105 | + |
| 106 | + radius = Distance.from_nautical_miles(50) |
| 107 | + carrier_start_pos = point_from_heading( |
| 108 | + group['x'], group['y'], cruise.heading.opposite.degrees, radius.meters |
| 109 | + ) |
| 110 | + carrier_end_pos = point_from_heading(reference['x'], reference['y'], cruise.heading.degrees, radius.meters) |
| 111 | + |
| 112 | + group_heading_before_change = heading_between_points( |
| 113 | + route.points[0].x, route.points[0].y, route.points[1].x, route.points[1].y |
| 114 | + ) |
| 115 | + group_position_before_change = (route.points[0].x, route.points[0].y) |
| 116 | + |
| 117 | + if len(route.points) < 4: |
| 118 | + print(f"Carrier group {reference['name']} missing waypoint") |
| 119 | + return |
| 120 | + # TODO |
| 121 | + |
| 122 | + route.points[0].x = carrier_start_pos[0] |
| 123 | + route.points[0].y = carrier_start_pos[1] |
| 124 | + route.points[0].speed = cruise.speed.meters_per_second |
| 125 | + |
| 126 | + route.points[1].x = carrier_end_pos[0] |
| 127 | + route.points[1].y = carrier_end_pos[1] |
| 128 | + route.points[1].ETA_locked = False |
| 129 | + route.points[1].speed = cruise.speed.meters_per_second |
| 130 | + route.points[1].speed_locked = True |
| 131 | + |
| 132 | + route.points[2].x = carrier_start_pos[0] |
| 133 | + route.points[2].y = carrier_start_pos[1] |
| 134 | + route.points[2].speed = knots(50).meters_per_second |
| 135 | + route.points[2].ETA_locked = False |
| 136 | + route.points[2].speed_locked = True |
| 137 | + |
| 138 | + route.points[3].x = carrier_end_pos[0] |
| 139 | + route.points[3].y = carrier_end_pos[1] |
| 140 | + route.points[3].ETA_locked = False |
| 141 | + route.points[3].speed = cruise.speed.meters_per_second |
| 142 | + route.points[3].speed_locked = True |
| 143 | + |
| 144 | + position_change = ( |
| 145 | + carrier_start_pos[0] - group_position_before_change[0], |
| 146 | + carrier_start_pos[1] - group_position_before_change[1] |
| 147 | + ) |
| 148 | + for unit in group.units: |
| 149 | + unit.x = unit.x + position_change[0] |
| 150 | + unit.y = unit.y + position_change[1] |
| 151 | + |
| 152 | + heading_change = cruise.heading.degrees - group_heading_before_change |
| 153 | + rotate_group_around( |
| 154 | + group, (route.points[0].x, route.points[0].y), heading_change |
| 155 | + ) |
| 156 | + |
| 157 | + # change the real thing |
| 158 | + reference['route'] = route.to_dict() |
| 159 | + reference['units'] = [unit.to_dict() for unit in group.units] |
0 commit comments