Exemplo n.º 1
0
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
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
    def _derive(self, t: float, y_1d: np.ndarray,
                pass_through_state: PhysicalState) -> np.ndarray:
        """
        y_1d =
         [X, Y, VX, VY, Heading, Spin, Fuel, Throttle, LandedOn, Broken] +
         SRB_time_left + time_acc (these are both single values) +
         [ComponentData, CoolantLoopData, RadiatorData]
        returns the derivative of y_1d, i.e.
        [VX, VY, AX, AY, Spin, 0, Fuel consumption, 0, 0, 0] + -constant + 0
        (zeroed-out fields are changed elsewhere)

        !!!!!!!!!!! IMPORTANT !!!!!!!!!!!
        This function should return a DERIVATIVE. The numpy.solve_ivp function
        will do the rest of the work of the simulation, this function just
        describes how things _move_.
        At its most basic level, this function takes in the _position_ of
        everything (plus some random stuff), and returns the _velocity_ of
        everything (plus some random stuff).
        Essentially, numpy.solve_ivp does this calculation:
        new_positions_of_system = t_delta * _derive(
                                                current_t_of_system,
                                                current_y_of_system)
        """
        # Note: we create this y as a PhysicsState for convenience, but if you
        # set any values of y, the changes will be discarded! The only way they
        # will be propagated out of this function is by numpy using the return
        # value of this function as a derivative, as explained above.
        # If you want to set values in y, look at _reconcile_entity_dynamics.
        y = PhysicsState(y_1d, pass_through_state)
        acc_matrix = calc.grav_acc(y.X, y.Y, self.M, y.Fuel)
        zeros = np.zeros(y._n)
        fuel_cons = np.zeros(y._n)

        # Engine thrust and fuel consumption
        for artif_index in self._artificials:
            if y[artif_index].fuel > 0 and y[artif_index].throttle > 0:
                # We have fuel remaining, calculate thrust
                entity = y[artif_index]
                capability = common.craft_capabilities[entity.name]

                fuel_cons[artif_index] = \
                    -abs(capability.fuel_cons * entity.throttle)
                eng_thrust = (capability.thrust * entity.throttle
                              * calc.heading_vector(entity.heading))
                mass = entity.mass + entity.fuel

                if entity.name == AYSE and \
                        y[HABITAT].landed_on == AYSE:
                    # It's bad that this is hardcoded, but it's also the only
                    # place that this comes up so IMO it's not too bad.
                    hab = y[HABITAT]
                    mass += hab.mass + hab.fuel

                eng_acc = eng_thrust / mass
                acc_matrix[artif_index] += eng_acc

        # And SRB thrust
        srb_usage = 0
        try:
            if y.srb_time >= 0:
                hab_index = y._name_to_index(HABITAT)
                hab = y[hab_index]
                srb_acc = common.SRB_THRUST / (hab.mass + hab.fuel)
                srb_acc_vector = srb_acc * calc.heading_vector(hab.heading)
                acc_matrix[hab_index] += srb_acc_vector
                srb_usage = -1
        except PhysicsState.NoEntityError:
            # The Habitat doesn't exist.
            pass

        # Engineering values
        R = y.engineering.components.Resistance()
        I = y.engineering.components.Current()  # noqa: E741

        # Eventually radiators will affect the temperature.
        T_deriv = common.eta * np.square(I) * R
        R_deriv = common.alpha * T_deriv
        # Voltage we set to be constant. Since I = V/R, I' = V'/R' = 0/R' = 0
        V_deriv = I_deriv = np.zeros(data_structures._N_COMPONENTS)

        # Drag effects
        craft = y.craft
        if craft is not None:
            craft_index = y._name_to_index(y.craft)
            drag_acc = calc.drag(y)
            acc_matrix[craft_index] -= drag_acc

        # Centripetal acceleration to keep landed entities glued to each other.
        landed_on = y.LandedOn
        for landed_i in landed_on:
            lander = y[landed_i]
            ground = y[landed_on[landed_i]]

            centripetal_acc = (lander.pos - ground.pos) * ground.spin ** 2
            acc_matrix[landed_i] = \
                acc_matrix[landed_on[landed_i]] - centripetal_acc

        # Sets velocity and spin of a couple more entities.
        # If you want to set the acceleration of an entity, do it above and
        # keep that logic in _derive. If you want to set the velocity and spin
        # or any other fields that an Entity has, you should put that logic in
        # this _reconcile_entity_dynamics helper.
        y = _reconcile_entity_dynamics(y)

        return np.concatenate((
            y.VX, y.VY, np.hsplit(acc_matrix, 2), y.Spin,
            zeros, fuel_cons, zeros, zeros, zeros, np.array([srb_usage, 0]),
            np.zeros(data_structures._N_COMPONENTS),  # connected field doesn't change
            T_deriv, R_deriv, V_deriv, I_deriv,
            np.zeros(data_structures._N_COMPONENTS),  # attached_to_coolant_loop field doesn't change
            np.zeros(data_structures._N_COOLANT_LOOPS * data_structures._N_COOLANT_FIELDS),
            np.zeros(data_structures._N_RADIATORS * data_structures._N_RADIATOR_FIELDS)
        ), axis=None)
Exemplo n.º 5
0
def _write_state_to_osbackup(orbitx_state: PhysicsState, osbackup_path: Path,
                             orbitv_names: List[str]):
    """Writes information to OSbackup.RND. orbitv_names should be the result
    of entity_list_from_starsr.

    Currently only writes information relevant to engineering, but if we
    want to communicate piloting state to other legacy orbit programs we
    can write other kinds of information. I found out what information I
    should be writing here by reading the source for engineering
    (enghabv.bas) and seeing what information it reads from OSbackup.RND"""
    assert orbitx_state.craft

    hab = orbitx_state[common.HABITAT]
    ayse = orbitx_state[common.AYSE]
    craft = orbitx_state.craft_entity()
    max_thrust = common.craft_capabilities[craft.name].thrust
    timestamp = datetime.fromtimestamp(orbitx_state.timestamp)
    check_byte = random.choice(string.ascii_letters).encode('ascii')
    target = (orbitx_state.target
              if orbitx_state.target in orbitv_names else "AYSE")
    reference = (orbitx_state.reference
                 if orbitx_state.reference in orbitv_names else "Earth")
    atmosphere = calc.relevant_atmosphere(orbitx_state)
    if atmosphere is None:
        pressure = 0.0
    else:
        pressure = calc.pressure(craft, atmosphere)
    craft_thrust = (max_thrust * craft.throttle *
                    calc.heading_vector(craft.heading))
    craft_drag = calc.drag(orbitx_state)
    artificial_gravity = numpy.linalg.norm(craft_thrust - craft_drag)

    with open(osbackup_path, 'r+b') as osbackup:
        # We replicate the logic of orbit5v.bas:1182 (look for label 800)
        # in the below code. OSBACKUP.RND is a binary, fixed-width field
        # file. orbit5v.bas writes to it every second, and it's created in
        # the same way as any OrbitV save file.
        # If you're reading the orbit5v.bas source code, note that arrays
        # and strings in QB are 1-indexed not 0-indexed.

        osbackup.write(check_byte)
        osbackup.write("ORBIT5S        ".encode('ascii'))
        # OrbitX keeps throttle as a float, where 100% = 1.0
        # OrbitV expects 100% = 100.0
        _write_single(100 * max(hab.throttle, ayse.throttle), osbackup)

        # This is Vflag and Aflag, dunno what these are.
        _write_int(0, osbackup)
        _write_int(0, osbackup)

        _write_int(NAVMODE_TO_SFLAG[orbitx_state.navmode], osbackup)
        _write_double(numpy.linalg.norm(calc.drag(orbitx_state)), osbackup)
        _write_double(25, osbackup)  # A sane value for OrbitV zoom.
        # TODO: check if this is off by 90 degrees or something.
        _write_single(hab.heading, osbackup)
        _write_int(orbitv_names.index("Habitat"), osbackup)  # Centre
        _write_int(orbitv_names.index(target), osbackup)
        _write_int(orbitv_names.index(reference), osbackup)

        _write_int(0, osbackup)  # Set trails to off.
        _write_single(0.006 if orbitx_state.parachute_deployed else 0.002,
                      osbackup)
        _write_single(
            (common.SRB_THRUST / 100) if orbitx_state.srb_time > 0 else 0,
            osbackup)
        _write_int(0, osbackup)  # No colour-coded trails.

        # Valid values of this are 0, 1, 2.
        # A value of 1 gets OrbitV to display EST timestamps.
        _write_int(1, osbackup)
        _write_double(0.125, osbackup)  # Set timestep to the slowest.
        _write_double(0.125, osbackup)  # This is OLDts, also slowest value
        _write_single(0, osbackup)  # Something to do with RSCP, zero it.
        _write_int(0, osbackup)  # Eflag? Something that gassimv.bas sets.

        _write_int(timestamp.year, osbackup)
        # QB uses day-of-the-year instead of day-of-the-month.
        _write_int(int(timestamp.strftime('%j')), osbackup)
        _write_int(timestamp.hour, osbackup)
        _write_int(timestamp.minute, osbackup)
        _write_double(timestamp.second, osbackup)

        _write_single(ayse.heading, osbackup)
        _write_int(0, osbackup)  # AYSEscrape seemingly does nothing.

        # TODO: Once we implement wind, fill in these fields.
        _write_single(0, osbackup)
        _write_single(0, osbackup)

        _write_single(numpy.degrees(hab.spin), osbackup)
        _write_int(150 if hab.landed_on == common.AYSE else 0, osbackup)
        _write_single(0, osbackup)  # Rad shields, written by enghabv.bas.
        _write_int(
            MODSTATE_TO_MODFLAG[network.Request.DETACHED_MODULE]
            if common.MODULE in orbitx_state._entity_names else
            MODSTATE_TO_MODFLAG[network.Request.NO_MODULE], osbackup)

        # These next two variables are technically AYSEdist and OCESSdist
        # but they're used for engineering to determine fueld loading from
        # OCESS or AYSE. Also, if you can dock with AYSE. So we just tell
        # engineering if we're landed, but don't tell it any more deets.
        _write_single(150 if hab.landed_on == common.AYSE else 1000, osbackup)
        _write_single(150 if hab.landed_on == common.EARTH else 1000, osbackup)

        _write_int(hab.broken, osbackup)  # I think an explosion flag.
        _write_int(ayse.broken, osbackup)  # Same as above.

        # TODO: Once we implement nav malfunctions, set this field.
        _write_single(0, osbackup)

        _write_single(max_thrust, osbackup)

        # TODO: Once we implement nav malfunctions, set this field.
        # Also, apparently this is the same field as two fields ago?
        _write_single(0, osbackup)

        # TODO: Once we implement wind, fill in these fields.
        _write_long(0, osbackup)

        # TODO: Some information required from MST.rnd needed to implement.
        # this field (LONGtarg).
        _write_single(0, osbackup)

        _write_single(pressure, osbackup)
        _write_single(artificial_gravity, osbackup)

        for i in range(0, 39):
            try:
                entity = orbitx_state[orbitv_names[i]]
                x, y = entity.pos
                vx, vy = entity.v
            except ValueError:
                x = y = vx = vy = 0
            _write_double(x, osbackup)
            _write_double(y, osbackup)
            _write_double(vx, osbackup)
            _write_double(vy, osbackup)

        _write_single(hab.fuel, osbackup)
        _write_single(ayse.fuel, osbackup)

        osbackup.write(check_byte)