예제 #1
0
    def move_the_rocket(self):

        timestep = self.parameters.time.timestep

        rocket_hist = self.history.rocket
        rocket_params = self.parameters.rocket

        vel = self.physics.calc_rocket_velocity()
        acc_relative = self.parameters.rocket.calc_acc_relative2rocket(
            rocket_hist.engine_forces
        )

        acc = acc_relative.rotate_by(rocket_hist.angle - math.pi / 2)

        updated_vel = vel + acc * timestep

        rocket_hist.locations.append(rocket_hist.location + updated_vel * timestep)

        angular_vel = self.physics.calc_rocket_angular_velocity()
        angular_acc = rocket_params.calc_angular_acc(rocket_hist.thruster_forces)

        updated_angular_vel = angular_vel + angular_acc * timestep

        updated_angle = normalise_angle(
            rocket_hist.angle + updated_angular_vel * timestep
        )
        rocket_hist.angles.append(updated_angle)
예제 #2
0
    def _calc_single_obstacle_shadow_attraction(
            self, obstacle: Coordinate) -> Coordinate:

        # TODO: Make this more efficient by storing it between cycles
        # TODO: Deal with perfectly vertical gradients
        angle_turret2obstacle = (obstacle.location -
                                 self.parameters.turret.location).angle

        edge_gradients = self.calc_shadow_edge_gradients(obstacle)
        shadow_attractions = []
        rocket_location = self.history.rocket.location
        for gradient in edge_gradients:
            y_intercept = Line.calculate_y_intercept(
                gradient, self.parameters.turret.location)
            shadow_edge_line = Line(gradient, y_intercept)
            closest_point = shadow_edge_line.closest_point_on_line(
                rocket_location)
            strength = 1 / rocket_location.distance2(closest_point)
            angle_turret2closest_point = (
                closest_point - self.parameters.turret.location).angle
            if normalise_angle(angle_turret2closest_point -
                               angle_turret2obstacle) > 0:
                rotation = -1
            else:
                rotation = 1
            direction_angle = angle_turret2closest_point + rotation * math.pi / 2
            shadow_attractions.append(
                PolarCoordinate(strength, direction_angle).pol2cart())

        return sum(shadow_attractions)
예제 #3
0
    def _calc_projectile_path_avoidance(self):

        rocket_location = self.history.rocket.location
        turret_location = self.parameters.turret.location

        projectile_path_avoidance = []
        for projectile in self.history.active_projectiles:
            gradient = math.tan(projectile.firing_angle)
            y_intercept = turret_location.y - gradient * turret_location.x
            y_value = gradient * rocket_location.x + y_intercept
            avoidance_angle = normalise_angle(
                projectile.firing_angle +
                (1 if rocket_location.y > y_value else -1) * math.pi / 2)
            min_dist_rocket2path = self.calc_minimum_distance_from_location2line(
                rocket_location, gradient, y_intercept)
            projectile_location = self.helpers.calc_projectile_location(
                projectile)
            dist_rockt2projectile = rocket_location.distance2(
                projectile_location)
            try:
                avoidance_strength = 1 / (
                    min_dist_rocket2path *
                    (dist_rockt2projectile -
                     self.parameters.rocket.target_radius))
            except ZeroDivisionError:
                avoidance_strength = float("inf")
            projectile_path_avoidance.append(
                PolarCoordinate(avoidance_strength,
                                avoidance_angle).pol2cart())

        return (average(projectile_path_avoidance)
                if projectile_path_avoidance else Coordinate(0.0, 0.0))
    def firing_angle2hit_rocket(self) -> Optional[float]:
        """
        Calculates the firing angle to hit the rocket.

        If the turret was currently at said angle and fired a projectile, and the rocket continued at its current velocity,
        then the projectile would hit the target.
        https://math.stackexchange.com/questions/213545/solving-trigonometric-equations-of-the-form-a-sin-x-b-cos-x-c

        return:
            firing angle (rad): The angle as described above
                Will return None if no firing angle is possible due to high rocket velocity
        """
        projectile_speed = self.parameters.turret.projectile_speed
        turret_location = self.parameters.turret.location

        rocket_velocity = self.physics.calc_rocket_velocity()
        rocket_location = self.history.rocket.location

        try:
            k = (turret_location.y - rocket_location.y) / (turret_location.x -
                                                           rocket_location.x)
        except ZeroDivisionError:  # k = inf
            try:
                m = math.asin(rocket_velocity.x / projectile_speed)
            except ValueError:  # Intercept is not possible due to rocket velocity
                return None

            beta = math.pi / 2
        else:
            try:
                a = -projectile_speed
                b = k * projectile_speed
                c = k * rocket_velocity.x - rocket_velocity.y
                m = math.asin(c / math.sqrt(a**2 + b**2))
            except ValueError:  # Intercept is not possible due to rocket velocity
                return None

            A = a / math.sqrt(a**2 + b**2)
            B = b / math.sqrt(a**2 + b**2)
            beta = math.atan2(B, A)

        firing_angle = normalise_angle(m - beta)
        if self.will_firing_angle_hit(firing_angle):
            return firing_angle
        return normalise_angle(math.pi - m - beta)
예제 #5
0
    def rotate_the_turret(self):

        rotation_velocity = self.history.turret.rotation_velocity
        timestep = self.parameters.time.timestep

        d_theta = rotation_velocity * timestep

        angles = self.history.turret.angles
        angles.append(normalise_angle(angles[-1] + d_theta))
예제 #6
0
    def _plot_engine_thrust(
        self, force, projection_location, relative_engine_angle, engine_plot
    ):

        rocket_angle = self.history.rocket.angle
        projection_angle = normalise_angle(rocket_angle + relative_engine_angle)

        max_main_engine_force = self.parameters.rocket.max_main_engine_force
        rocket_length = self.parameters.rocket.length
        thrust_ratio = force / max_main_engine_force
        edge_length = (
            self.visual.rocket_length_max_thrust_length_ratio
            * rocket_length
            * thrust_ratio
        )

        left_angle = normalise_angle(projection_angle + self.visual.thrust_cone_angle)
        relative_thrust_left_location = PolarCoordinate(
            edge_length, left_angle
        ).pol2cart()
        thrust_left_location = projection_location + relative_thrust_left_location

        right_angle = normalise_angle(projection_angle - self.visual.thrust_cone_angle)
        relative_thrust_right_location = PolarCoordinate(
            edge_length, right_angle
        ).pol2cart()
        thrust_right_location = projection_location + relative_thrust_right_location

        engine_plot.set_data(
            [
                projection_location.x,
                thrust_left_location.x,
                thrust_right_location.x,
                projection_location.x,
            ],
            [
                projection_location.y,
                thrust_left_location.y,
                thrust_right_location.y,
                projection_location.y,
            ],
        )
예제 #7
0
    def _calc_rotation_factor(self):
        """1 if aligned with intercept angle, 0 if pointing in the opposite direction"""

        intercept_angle = self.controller_helpers.firing_angle2hit_rocket()
        if intercept_angle is None:  # Moving too fast to hit
            return 0.0

        angle_turret_barrel2rocket = normalise_angle(intercept_angle -
                                                     self.history.turret.angle)

        return 1 - abs(angle_turret_barrel2rocket) / math.pi
예제 #8
0
    def calc_rotation_velocity(self):

        firing_angle = self.controller_helpers.firing_angle2hit_rocket()
        angle2rocket = self.calc_angle2rocket()
        if firing_angle is None or abs(firing_angle -
                                       angle2rocket) > math.pi / 2:
            firing_angle = (
                angle2rocket  # If firing angle is at > 90 deg, aim for the rocket
            )

        delta_angle = normalise_angle(firing_angle - self.history.turret.angle)
        abs_rotation_speed = min(
            self.parameters.turret.max_rotation_speed,
            abs(delta_angle) / self.parameters.time.timestep,
        )  # Take a smaller step if a full move would carry past the rocket

        return (1 if delta_angle >= 0 else -1) * abs_rotation_speed
예제 #9
0
    def get_engine_bridge_ends(self):

        _, rocket_rear_location = self.get_rocket_ends()

        rocket_length = self.parameters.rocket.length
        engine_bridge_width = (
            self.visual.rocket_length_engine_bridge_width_ratio * rocket_length
        )

        rocket_angle = self.history.rocket.angle

        perpendicular_angle = normalise_angle(rocket_angle + math.pi / 2)
        relative_engine_end = PolarCoordinate(
            engine_bridge_width / 2, perpendicular_angle
        ).pol2cart()

        engine_bridge_left_location = rocket_rear_location + relative_engine_end
        engine_bridge_right_location = rocket_rear_location - relative_engine_end

        return engine_bridge_left_location, engine_bridge_right_location
예제 #10
0
    def _calc_firing_path_avoidance(self):

        rocket_location = self.history.rocket.location
        turret_location = self.parameters.turret.location

        recharge_factor = self._calc_recharge_factor()
        rotation_factor = self._calc_rotation_factor()

        turret_angle = self.history.turret.angle
        gradient = math.tan(turret_angle)
        y_intercept = turret_location.y - gradient * turret_location.x
        y_value = gradient * rocket_location.x + y_intercept
        avoidance_angle = normalise_angle(
            turret_angle +
            (1 if rocket_location.y > y_value else -1) * math.pi / 2)
        min_dist_rocket2path = self.calc_minimum_distance_from_location2line(
            rocket_location, gradient, y_intercept)
        dist_rockt2projectile = (
            self.controller_helpers.calc_dist_between_rocket_and_turret())
        return PolarCoordinate(
            rotation_factor * recharge_factor /
            (min_dist_rocket2path * dist_rockt2projectile),
            avoidance_angle,
        ).pol2cart()
예제 #11
0
    def calc_inputs(self):

        safety_buffer = 2.0

        direction = self._calc_direction(safety_buffer)

        # Current velocity
        rocket_velocity = self.physics.calc_rocket_velocity()

        direction_velocity_ratio = 250.0

        thrust_direction = direction_velocity_ratio * direction - rocket_velocity
        thrust_angle = thrust_direction.angle

        rocket_angle = self.history.rocket.angle
        relative_thrust_angle = normalise_angle(thrust_angle - rocket_angle)

        # If facing away from the turret, spin the rocket using the thrusters
        # Use PD controller
        p_c = 0.75
        d_c = -0.35

        # Derivative
        angular_vel = self.physics.calc_rocket_angular_velocity()

        control_signal = p_c * relative_thrust_angle + d_c * angular_vel

        max_thruster_force = self.parameters.rocket.max_thruster_force
        thruster_force = min(
            abs(control_signal) * max_thruster_force, max_thruster_force)

        clockwise_thrust = thruster_force if control_signal < 0 else 0.0
        anticlockwise_thrust = thruster_force if control_signal >= 0 else 0.0

        rotational_outputs = [
            0.0,
            clockwise_thrust,
            anticlockwise_thrust,
            anticlockwise_thrust,
            clockwise_thrust,
        ]

        max_outputs = [self.parameters.rocket.max_main_engine_force
                       ] + [max_thruster_force] * 4
        remaining_outputs = [
            max_thrust - rotational_thrust for max_thrust, rotational_thrust in
            zip(max_outputs, rotational_outputs)
        ]

        if self.is_within_buffer(safety_buffer):
            if not math.isclose(relative_thrust_angle, 0) and not math.isclose(
                    relative_thrust_angle, math.pi):
                idx1, idx2 = (3, 4) if relative_thrust_angle > 0 else (1, 2)
                min_remaining_thruster_output = min(remaining_outputs[idx1],
                                                    remaining_outputs[idx2])
                for idx in (idx1, idx2):
                    rotational_outputs[idx] += min_remaining_thruster_output

            if abs(relative_thrust_angle) < 3 * math.pi / 4:
                rotational_outputs[
                    0] = self.parameters.rocket.max_main_engine_force

            return rotational_outputs

        if abs(relative_thrust_angle) > math.pi / 2:
            return rotational_outputs

        left_thrusters_active = relative_thrust_angle < 0
        right_thrusters_active = relative_thrust_angle >= 0
        thrusters_active = [left_thrusters_active
                            ] * 2 + [right_thrusters_active] * 2

        engine_translation_force_ratio = [math.cos(relative_thrust_angle)] + [
            active * abs(math.sin(relative_thrust_angle)) / 2
            for active in thrusters_active
        ]
        try:
            min_max_ratio = min(
                max_output / force_ratio for max_output, force_ratio in zip(
                    max_outputs, engine_translation_force_ratio))
        except ZeroDivisionError:
            min_max_ratio = min(
                max_output / force_ratio for max_output, force_ratio in zip(
                    max_outputs, engine_translation_force_ratio)
                if not math.isclose(force_ratio, 0.0))
        translation_engine_forces = [
            min_max_ratio * force_ratio
            for force_ratio in engine_translation_force_ratio
        ]

        try:

            max_output_ratio = max(
                output / remaining for output, remaining in zip(
                    translation_engine_forces, remaining_outputs))
        except ZeroDivisionError:  # Remaining thrust is 0; cannot manouver directionally
            return rotational_outputs

        directional_outputs = [
            output / max_output_ratio for output in translation_engine_forces
        ]

        return [
            rotation + direction for rotation, direction in zip(
                rotational_outputs, directional_outputs)
        ]