예제 #1
0
파일: engine.py 프로젝트: Arcayik/orbitx
def _docking(e1, e2, e2_index):
    # e1 is an artificial object
    # if 2 artificial object to be docked on (spacespation)

    norm = e1.pos - e2.pos
    collision_angle = np.arctan2(norm[1], norm[0])
    collision_angle = collision_angle % (2 * np.pi)

    ANGLE_MIN = (e2.heading + 0.7 * np.pi) % (2 * np.pi)
    ANGLE_MAX = (e2.heading + 1.3 * np.pi) % (2 * np.pi)

    if collision_angle < ANGLE_MIN or collision_angle > ANGLE_MAX:
        # add damage ?
        _bounce(e1, e2)
        return

    log.info(f'Docking {e1.name} on {e2.name}')
    e1.landed_on = e2.name

    # Currently this flag has almost no effect.
    e1.broken = bool(
        calc.fastnorm(calc.rotational_speed(e1, e2) - e1.v) >
        common.craft_capabilities[e1.name].hull_strength
    )

    # set right heading for future takeoff
    e2_opposite = e2.heading + np.pi
    e1.pos = e2.pos + (e1.r + e2.r) * calc.heading_vector(e2_opposite)
    e1.heading = e2_opposite % (2 * np.pi)
    e1.throttle = 0
    e1.spin = e2.spin
    e1.v = e2.v
예제 #2
0
파일: engine.py 프로젝트: Arcayik/orbitx
def _bounce(e1, e2):
    # Resolve a collision by:
    # 1. calculating positions and velocities of the two entities
    # 2. do a 1D collision calculation along the normal between the two
    # 3. recombine the velocity vectors
    log.info(f'Bouncing {e1.name} and {e2.name}')
    norm = e1.pos - e2.pos
    unit_norm = norm / calc.fastnorm(norm)
    # The unit tangent is perpendicular to the unit normal vector
    unit_tang = np.asarray([-unit_norm[1], unit_norm[0]])

    # Calculate both normal and tangent velocities for both entities
    v1n = np.dot(unit_norm, e1.v)
    v1t = np.dot(unit_tang, e1.v)
    v2n = np.dot(unit_norm, e2.v)
    v2t = np.dot(unit_tang, e2.v)

    # Use https://en.wikipedia.org/wiki/Elastic_collision
    # to find the new normal velocities (a 1D collision)
    new_v1n = ((v1n * (e1.mass - e2.mass) + 2 * e2.mass * v2n) /
               (e1.mass + e2.mass))
    new_v2n = ((v2n * (e2.mass - e1.mass) + 2 * e1.mass * v1n) /
               (e1.mass + e2.mass))

    # Calculate new velocities
    e1.v = new_v1n * unit_norm + v1t * unit_tang
    e2.v = new_v2n * unit_norm + v2t * unit_tang
예제 #3
0
    def test_hyperbolic_orbital_parameters(self):
        # Unlike the elliptical test, this tests our favourite extra-solar
        # visitor to make sure we can calculate Keplerian orbital
        # characteristics from its orbital state vectors! That's right, we're
        # talking about Sedna! The expected values are arrived at through
        # calculation, and also
        # http://orbitsimulator.com/formulas/OrbitalElements.html
        physics_state = common.load_savefile(
            common.savefile('tests/sedna.json'))
        sun = physics_state[0]
        oumuamua = physics_state[1]

        expected_semimajor_axis = -71231070.14146987
        self.assertAlmostEqual(calc.semimajor_axis(oumuamua, sun),
                               expected_semimajor_axis,
                               delta=abs(0.01 * expected_semimajor_axis))

        expected_eccentricity = 1644.477
        self.assertAlmostEqual(calc.fastnorm(calc.eccentricity(oumuamua, sun)),
                               expected_eccentricity,
                               delta=0.01 * expected_eccentricity)

        expected_periapsis = 1.1714e11  # Through calculation
        self.assertAlmostEqual(calc.periapsis(sun, oumuamua) + oumuamua.r,
                               expected_periapsis,
                               delta=0.01 * 78989185420.15271)
예제 #4
0
파일: engine.py 프로젝트: Arcayik/orbitx
def _reconcile_entity_dynamics(y: PhysicsState) -> PhysicsState:
    """Idempotent helper that sets velocities and spins of some entities.
    This is in its own function because it has a couple calling points."""
    # Navmode auto-rotation
    if y.navmode != Navmode['Manual']:
        craft = y.craft_entity()
        craft.spin = calc.navmode_spin(y)

    # Keep landed entities glued together
    landed_on = y.LandedOn
    for index in landed_on:
        # If we're landed on something, make sure we move in lockstep.
        lander = y[index]
        ground = y[landed_on[index]]

        if ground.name == AYSE and lander.name == HABITAT:
            # Always put the Habitat at the docking port.
            lander.pos = (
                    ground.pos
                    - calc.heading_vector(ground.heading)
                    * (lander.r + ground.r))
        else:
            norm = lander.pos - ground.pos
            unit_norm = norm / calc.fastnorm(norm)
            lander.pos = ground.pos + unit_norm * (ground.r + lander.r)

        lander.spin = ground.spin
        lander.v = calc.rotational_speed(lander, ground)

    return y
예제 #5
0
    def test_elliptical_orbital_parameters(self):
        # Again, see
        # https://www.wolframalpha.com/input/?i=International+Space+Station
        # For these expected values
        physics_state = common.load_savefile(
            common.savefile('tests/gui-test.json'))
        iss = physics_state[0]
        earth = physics_state[1]

        # The semiaxes are relatively close to expected.
        self.assertAlmostEqual(calc.semimajor_axis(iss, earth),
                               6785e3,
                               delta=0.01 * earth.r)

        # The eccentricity is within 1e-6 of the expected.
        self.assertAlmostEqual(calc.fastnorm(calc.eccentricity(iss, earth)),
                               5.893e-4,
                               delta=1e-3)

        # The apoapsis is relatively close to expected.
        self.assertAlmostEqual(calc.apoapsis(iss, earth),
                               418.3e3,
                               delta=0.01 * earth.r)

        # The periapsis is relatively close to expected.
        self.assertAlmostEqual(calc.periapsis(iss, earth),
                               410.3e3,
                               delta=0.01 * earth.r)
예제 #6
0
 def test_longterm_stable_landing(self):
     """Test that landed ships have stable altitude in the long term."""
     savestate = common.load_savefile(common.savefile('OCESS.json'))
     initial_t = savestate.timestamp
     with PhysicsEngine('OCESS.json') as physics_engine:
         initial = physics_engine.get_state(initial_t + 10)
         physics_engine.handle_requests([
             network.Request(ident=network.Request.TIME_ACC_SET,
                             time_acc_set=common.TIME_ACCS[-1].value)
         ],
                                        requested_t=initial_t + 10)
         final = physics_engine.get_state(initial_t + 100_000)
         self.assertAlmostEqual(calc.fastnorm(initial['Earth'].pos -
                                              initial['Habitat'].pos),
                                initial['Earth'].r + initial['Habitat'].r,
                                delta=1)
         self.assertAlmostEqual(calc.fastnorm(final['Earth'].pos -
                                              final['Habitat'].pos),
                                final['Earth'].r + final['Habitat'].r,
                                delta=1)
예제 #7
0
    def test_drag(self):
        """Test that drag is small but noticeable during unpowered flight."""
        atmosphere_save = common.load_savefile(
            common.savefile('tests/atmosphere.json'))
        # The habitat starts 1 km in the air, the same speed as the Earth.

        hab = atmosphere_save.craft_entity()
        hab.vy += 10
        atmosphere_save[atmosphere_save.craft] = hab
        drag = calc.fastnorm(calc.drag(atmosphere_save))
        self.assertLess(59, drag)
        self.assertGreater(60, drag)
예제 #8
0
    def update(self, state: PhysicsState, origin: Entity):
        if not self._visible:
            self._hyperbola.visible = False
            self._ring.visible = False
            return

        orb_params = calc.orbit_parameters(state.reference_entity(),
                                           state.craft_entity())
        thickness = min(
            self.PROJECTION_THICKNESS * vpython.canvas.get_selected().range,
            abs(0.1 * max(orb_params.major_axis, orb_params.minor_axis)))
        pos = orb_params.centre - origin.pos
        direction = vpython.vector(*orb_params.eccentricity, 0)
        e_mag = calc.fastnorm(orb_params.eccentricity)

        if e_mag < 1:
            # Project an elliptical orbit
            self._hyperbola.visible = False
            self._ring.pos = vpython.vector(*pos, 0)
            # size.x is actually the thickness, confusingly.
            # size.y and size.z are the width and length of the ellipse.
            self._ring.size.y = orb_params.major_axis
            self._ring.size.z = orb_params.minor_axis
            self._ring.up = direction
            self._ring.size.x = thickness
            self._ring.visible = True

        elif e_mag > 1:
            # Project a hyperbolic orbit
            self._ring.visible = False
            self._hyperbola.origin = vpython.vector(*pos, 0)

            # For a vpython.curve object, setting the axis is confusing.
            # To control direction, set the .up attribute (magnitude doesn't
            # matter for .up)
            # To control size, set the .size attribute (magnitude matters).
            # Setting .axis will also change .size, not sure how.

            self._hyperbola.size = vpython.vector(orb_params.minor_axis / 2,
                                                  orb_params.major_axis / 2, 1)
            self._hyperbola.up = -direction
            self._hyperbola.radius = thickness
            self._hyperbola.visible = True
            assert self._hyperbola.axis.z == 0, self._hyperbola.axis
            assert self._hyperbola.up.z == 0, self._hyperbola.up

        else:
            # e == 1 pretty much never happens, and if it does we'll just wait
            # until it goes away
            return
예제 #9
0
파일: engine.py 프로젝트: Arcayik/orbitx
 def __call__(self, t: float, y_1d: np.ndarray) -> float:
     """Return positive if the current time acceleration is accurate, zero
     then negative otherwise."""
     if self.current_acc == 1:
         # If we can't lower the time acc, don't bother doing any work.
         return np.inf
     derive_result = self.derive(t, y_1d)
     max_acc_mag = 0.0005  # A small nonzero value.
     for artif_index in self.artificials:
         accel = (derive_result[self.ax_offset] + artif_index,
                  derive_result[self.ay_offset] + artif_index)
         acc_mag = calc.fastnorm(accel)
         if acc_mag > max_acc_mag:
             max_acc_mag = acc_mag
     return max(self.acc_bound - acc_mag, 0)
예제 #10
0
def _land(e1, e2):
    # e1 is an artificial object
    # if 2 artificial object collide (habitat, spacespation)
    # or small astroid collision (need deletion), handle later

    log.info(f'Landing {e1.name} on {e2.name}')
    assert e2.artificial is False
    e1.landed_on = e2.name

    # Currently does nothing
    e1.broken = bool(
        calc.fastnorm(calc.rotational_speed(e1, e2) -
                      e1.v) > common.craft_capabilities[e1.name].hull_strength)

    # set right heading for future takeoff
    norm = e1.pos - e2.pos
    e1.heading = np.arctan2(norm[1], norm[0])
    e1.throttle = 0
    e1.spin = e2.spin
    e1.v = calc.rotational_speed(e1, e2)
예제 #11
0
파일: engine.py 프로젝트: Arcayik/orbitx
def _one_request(request: Request, y0: PhysicsState) \
        -> PhysicsState:
    """Interface to set habitat controls.

    Use an argument to change habitat throttle or spinning, and simulation
    will restart with this new information."""
    log.info(f'At simtime={y0.timestamp}, '
             f'Got command {MessageToString(request, as_one_line=True)}')

    if request.ident != Request.TIME_ACC_SET:
        # Reveal the type of y0.craft as str (not None).
        assert y0.craft is not None

    if request.ident == Request.HAB_SPIN_CHANGE:
        if y0.navmode != Navmode['Manual']:
            # We're in autopilot, ignore this command
            return y0
        craft = y0.craft_entity()
        if not craft.landed():
            craft.spin += request.spin_change
    elif request.ident == Request.HAB_THROTTLE_CHANGE:
        y0.craft_entity().throttle += request.throttle_change
    elif request.ident == Request.HAB_THROTTLE_SET:
        y0.craft_entity().throttle = request.throttle_set
    elif request.ident == Request.TIME_ACC_SET:
        assert request.time_acc_set >= 0
        y0.time_acc = request.time_acc_set
    elif request.ident == Request.ENGINEERING_UPDATE:
        # Multiply this value by 100, because OrbitV considers engines at
        # 100% to be 100x the maximum thrust.
        common.craft_capabilities[HABITAT] = \
            common.craft_capabilities[HABITAT]._replace(
                thrust=100 * request.engineering_update.max_thrust)
        hab = y0[HABITAT]
        ayse = y0[AYSE]
        hab.fuel = request.engineering_update.hab_fuel
        ayse.fuel = request.engineering_update.ayse_fuel
        y0[HABITAT] = hab
        y0[AYSE] = ayse

        if request.engineering_update.module_state == \
                Request.DETACHED_MODULE and \
                MODULE not in y0._entity_names and \
                not hab.landed():
            # If the Habitat is freely floating and engineering asks us to
            # detach the Module, spawn in the Module.
            module = Entity(protos.Entity(
                name=MODULE, mass=100, r=10, artificial=True))
            module.pos = (hab.pos - (module.r + hab.r) *
                          calc.heading_vector(hab.heading))
            module.v = calc.rotational_speed(module, hab)

            y0_proto = y0.as_proto()
            y0_proto.entities.extend([module.proto])
            y0 = PhysicsState(None, y0_proto)

    elif request.ident == Request.UNDOCK:
        habitat = y0[HABITAT]

        if habitat.landed_on == AYSE:
            ayse = y0[AYSE]
            habitat.landed_on = ''

            norm = habitat.pos - ayse.pos
            unit_norm = norm / calc.fastnorm(norm)
            habitat.v += unit_norm * common.UNDOCK_PUSH
            habitat.spin = ayse.spin

            y0[HABITAT] = habitat

    elif request.ident == Request.REFERENCE_UPDATE:
        y0.reference = request.reference
    elif request.ident == Request.TARGET_UPDATE:
        y0.target = request.target
    elif request.ident == Request.LOAD_SAVEFILE:
        y0 = common.load_savefile(common.savefile(request.loadfile))
    elif request.ident == Request.NAVMODE_SET:
        y0.navmode = Navmode(request.navmode)
        if y0.navmode == Navmode['Manual']:
            y0.craft_entity().spin = 0
    elif request.ident == Request.PARACHUTE:
        y0.parachute_deployed = request.deploy_parachute
    elif request.ident == Request.IGNITE_SRBS:
        if round(y0.srb_time) == common.SRB_FULL:
            y0.srb_time = common.SRB_BURNTIME
    elif request.ident == Request.TOGGLE_COMPONENT:
        component = y0.engineering.components[request.component_to_toggle]
        component.connected = not component.connected
    elif request.ident == Request.TOGGLE_RADIATOR:
        radiator = y0.engineering.radiators[request.radiator_to_toggle]
        radiator.functioning = not radiator.functioning
    elif request.ident == Request.CONNECT_RADIATOR_TO_LOOP:
        radiator = y0.engineering.radiators[request.radiator_to_loop.rad]
        radiator.attached_to_coolant_loop = request.radiator_to_loop.loop
    elif request.ident == Request.TOGGLE_COMPONENT_COOLANT:
        component = y0.engineering.components[request.component_to_loop.component]
        # See comments on _component_coolant_cnxn_transitions for more info.
        component.coolant_connection = (
            _component_coolant_cnxn_transitions
            [request.component_to_loop.loop]
            [component.coolant_connection]
        )

    return y0
예제 #12
0
파일: flight_gui.py 프로젝트: OCESS/orbitx
    def _create_wtexts(self):
        vpython.canvas.get_selected().caption += "<table>\n"
        self._wtexts: List[TableText] = []

        self._wtexts.append(
            TableText("Simulation time",
                      lambda state: datetime.fromtimestamp(
                          state.timestamp, common.TIMEZONE).strftime('%x %X'),
                      "Current time in simulation",
                      new_section=False))
        self._wtexts.append(
            TableText("Orbit speed",
                      lambda state: common.format_num(
                          calc.orb_speed(state.craft_entity(),
                                         state.reference_entity()), " m/s"),
                      "Speed required for circular orbit at current altitude",
                      new_section=False))

        self._wtexts.append(
            TableText(
                "Periapsis",
                lambda state: common.format_num(calc.periapsis(
                    state.craft_entity(), state.reference_entity()) / 1000,
                                                " km",
                                                decimals=3),
                "Lowest altitude in naïve orbit around reference",
                new_section=False))

        self._wtexts.append(
            TableText(
                "Apoapsis",
                lambda state: common.format_num(calc.apoapsis(
                    state.craft_entity(), state.reference_entity()) / 1000,
                                                " km",
                                                decimals=3),
                "Highest altitude in naïve orbit around reference",
                new_section=False))

        self._wtexts.append(
            TableText(
                # The H in HRT stands for Habitat, even though craft is more
                # general and covers Ayse, but HRT is the familiar triple name and
                # the Hawking III says trans rights.
                "HRT phase θ",
                lambda state: common.format_num(
                    calc.phase_angle(
                        state.craft_entity(), state.reference_entity(),
                        state.target_entity()), "°") or "Same ref and targ",
                "Angle between Habitat, Reference, and Target",
                new_section=False))

        self._wtexts.append(
            TableText(
                "Throttle",
                lambda state: "{:.1%}".format(state.craft_entity().throttle),
                "Percentage of habitat's maximum rated engines",
                new_section=True))

        self._wtexts.append(
            TableText("Engine Acceleration",
                      lambda state: common.format_num(
                          calc.engine_acceleration(state), " m/s/s") +
                      (' [SRB]' if state.srb_time > 0 else ''),
                      "Acceleration due to craft's engine thrust",
                      new_section=False))

        self._wtexts.append(
            TableText("Drag",
                      lambda state: common.format_num(
                          calc.fastnorm(calc.drag(state)), " m/s/s"),
                      "Atmospheric drag acting on the craft",
                      new_section=False))

        self._wtexts.append(
            TableText("Fuel",
                      lambda state: common.format_num(
                          state.craft_entity().fuel, " kg"),
                      "Remaining fuel of craft",
                      new_section=False))

        def rotation_formatter(state: PhysicsState) -> str:
            deg_spin = round(np.degrees(state.craft_entity().spin), ndigits=1)
            if deg_spin < 0:
                return f"{-deg_spin} °/s cw"
            elif deg_spin > 0:
                return f"{deg_spin} °/s ccw"
            else:
                return f"{deg_spin} °/s"

        self._wtexts.append(
            TableText("Rotation",
                      rotation_formatter,
                      "Rotation speed of craft",
                      new_section=False))

        self._wtexts.append(
            TableText(
                "Ref altitude",
                lambda state: common.format_num(calc.altitude(
                    state.craft_entity(), state.reference_entity()) / 1000,
                                                " km",
                                                decimals=3),
                "Altitude of habitat above reference surface",
                new_section=True))

        self._wtexts.append(
            TableText("Ref speed",
                      lambda state: common.format_num(
                          calc.speed(state.craft_entity(),
                                     state.reference_entity()), " m/s"),
                      "Speed of habitat above reference surface",
                      new_section=False))

        self._wtexts.append(
            TableText(
                "Vertical speed",
                lambda state: common.format_num(
                    calc.v_speed(state.craft_entity(), state.reference_entity(
                    )), " m/s "),
                "Vertical speed of habitat towards/away reference surface",
                new_section=False))

        self._wtexts.append(
            TableText("Horizontal speed",
                      lambda state: common.format_num(
                          calc.h_speed(state.craft_entity(),
                                       state.reference_entity()), " m/s "),
                      "Horizontal speed of habitat across reference surface",
                      new_section=False))

        self._wtexts.append(
            TableText("Pitch θ",
                      lambda state: common.format_num(
                          np.degrees(
                              calc.pitch(state.craft_entity(
                              ), state.reference_entity())) % 360, "°"),
                      "Horizontal speed of habitat across reference surface",
                      new_section=False))

        self._wtexts.append(
            TableText("Targ altitude",
                      lambda state: common.format_num(calc.altitude(
                          state.craft_entity(), state.target_entity()) / 1000,
                                                      " km",
                                                      decimals=3),
                      "Altitude of habitat above reference surface",
                      new_section=True))

        self._wtexts.append(
            TableText("Targ speed",
                      lambda state: common.format_num(
                          calc.speed(state.craft_entity(), state.target_entity(
                          )), " m/s"),
                      "Speed of habitat above target surface",
                      new_section=False))

        self._wtexts.append(
            TableText(
                "Landing acc",
                lambda state: common.format_num(
                    calc.landing_acceleration(state.craft_entity(),
                                              state.target_entity()), " m/s/s"
                ) or "no vertical landing",
                "Constant engine acc to land during vertical descent to target",
                new_section=False))

        vpython.canvas.get_selected().caption += "</table>"