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
def __init__(self, draw_state: PhysicsState, *, title: str, running_as_mirror: bool) -> None: assert len(draw_state) >= 1 self._state = draw_state # create a vpython canvas, onto which all 3D and HTML drawing happens. self._scene = self._init_canvas(title) self._show_label: bool = True self._show_trails: bool = DEFAULT_TRAILS self._paused: bool = False self._removed_saveload_hint: bool = False self._origin: Entity self.texture_path: Path = Path('data', 'textures') self._commands: List[Request] = [] self._paused_label = vpython.label( text="Simulation paused; saving and loading enabled.\n" "When finished, unpause by setting a time acceleration.", xoffset=0, yoffset=200, line=False, height=25, border=20, opacity=1, visible=False) self._3dobjs: Dict[str, ThreeDeeObj] = {} # remove vpython ambient lighting self._scene.lights = [] # This line shouldn't be removed self._set_origin(common.DEFAULT_CENTRE) for entity in draw_state: self._3dobjs[entity.name] = self._build_threedeeobj(entity) self._orbit_projection = OrbitProjection() self._3dobjs[draw_state.reference].draw_landing_graphic( draw_state.reference_entity()) self._3dobjs[draw_state.target].draw_landing_graphic( draw_state.target_entity()) self._sidebar = Sidebar(self, running_as_mirror) self._scene.bind('keydown', self._handle_keydown) # Add an animation when launching the program to describe the solar # system and the current location while self._scene.range > 600000: vpython.rate(100) self._scene.range = self._scene.range * 0.92 self.recentre_camera(common.DEFAULT_CENTRE)
def navmode_heading(flight_state: PhysicsState) -> float: """ Returns the heading that the craft should be facing in current navmode. Don't call this with Manual navmode. If the navmode is relative to the reference, and the reference is set to the craft, this will return the craft's current heading as a sane default. """ navmode = flight_state.navmode craft = flight_state.craft_entity() ref = flight_state.reference_entity() targ = flight_state.target_entity() requested_vector: np.ndarray if navmode == Navmode['Manual']: raise ValueError('Autopilot requested for manual navmode') elif navmode.name == 'CCW Prograde': if flight_state.reference == flight_state.craft: return craft.heading normal = craft.pos - ref.pos requested_vector = np.array([-normal[1], normal[0]]) elif navmode == Navmode['CW Retrograde']: if flight_state.reference == flight_state.craft: return craft.heading normal = craft.pos - ref.pos requested_vector = np.array([normal[1], -normal[0]]) elif navmode == Navmode['Depart Reference']: if flight_state.reference == flight_state.craft: return craft.heading requested_vector = craft.pos - ref.pos elif navmode == Navmode['Approach Target']: if flight_state.target == flight_state.craft: return craft.heading requested_vector = targ.pos - craft.pos elif navmode == Navmode['Pro Targ Velocity']: if flight_state.target == flight_state.craft: return craft.heading requested_vector = craft.v - targ.v elif navmode == Navmode['Anti Targ Velocity']: if flight_state.target == flight_state.craft: return craft.heading requested_vector = targ.v - craft.v else: raise ValueError(f'Got an unexpected NAVMODE: {flight_state.navmode}') return np.arctan2(requested_vector[1], requested_vector[0])
def _update_obj(self, entity: Entity, state: PhysicsState, origin: Entity): super()._update_obj(entity, state, origin) self._obj.boosters.pos = self._obj.pos self._obj.boosters.axis = self._obj.axis # Attach the parachute to the forward cone of the habitat. self._obj.parachute.pos = ( self._obj.pos + calc.angle_to_vpy(entity.heading) * entity.r * 0.8) parachute_is_visible = ((state.craft == entity.name) and state.parachute_deployed) if parachute_is_visible: drag = calc.drag(state) drag_mag = np.inner(drag, drag) for parachute in [self._obj.parachute, self._small_habitat.parachute]: if not parachute_is_visible or drag_mag < 0.00001: # If parachute_is_visible == False, don't show the parachute. # If the drag is basically zero, don't show the parachute. parachute.visible = False continue if drag_mag > 0.1: parachute.width = self.PARACHUTE_RADIUS * 2 parachute.height = self.PARACHUTE_RADIUS * 2 else: # Below a certain threshold the parachute deflates. parachute.width = self.PARACHUTE_RADIUS parachute.height = self.PARACHUTE_RADIUS parachute.axis = -vpython.vec(*drag, 0) parachute.visible = True if not self._broken and entity.broken: # We weren't broken before, but looking at new data we realize # we're now broken. Change the habitat texture. new_texture = self._obj.texture.replace('Habitat.jpg', 'Habitat-broken.jpg') assert new_texture != self._obj.texture, \ f'{new_texture!r} == {self._obj.texture!r}' self._obj.texture = new_texture self._small_habitat.texture = new_texture self._broken = entity.broken elif self._broken and not entity.broken: # We were broken before, but we've repaired ourselves somehow. new_texture = self._obj.texture.replace('Habitat-broken.jpg', 'Habitat.jpg') assert new_texture != self._obj.texture, \ f'{new_texture!r} == {self._obj.texture!r}' self._obj.texture = new_texture self._small_habitat.texture = new_texture self._broken = entity.broken # Set reference and target arrows of the minimap habitat. same = state.reference == entity.name default = vpython.vector(0, 0, -1) ref_arrow_axis = (entity.screen_pos(state.reference_entity()).norm() * entity.r * -1.2) v = entity.v - state.reference_entity().v velocity_arrow_axis = \ vpython.vector(*v, 0).norm() * entity.r self._ref_arrow.axis = default if same else ref_arrow_axis self._velocity_arrow.axis = default if same else velocity_arrow_axis self._small_habitat.axis = self._obj.axis self._small_habitat.boosters.axis = self._obj.axis # Invisible-ize the SRBs if we ran out. if state.srb_time == common.SRB_EMPTY and self._obj.boosters.visible: self._obj.boosters.visible = False self._small_habitat.boosters.visible = False