Top

ggrocket module

ggrocket

A ggmath extensions for modeling spacecraft in planetary orbit

"""
# ggrocket 
## A ggmath extensions for modeling spacecraft in planetary orbit
"""

from math import pi, degrees, radians, atan2, sin, cos, sqrt
from ggame import LineStyle, Color
from ggmath import MathApp, Circle, ImagePoint, Timer, Label

class Rocket(ImagePoint):
    """
    Rocket is a class for simulating the motion of a projectile through space, 
    acted upon by arbitrary forces (thrust) and by gravitaitonal 
    attraction to a single planetary object.
    """

    def __init__(self, planet, **kwargs):
        """
        Initialize the Rocket object. 
        
        Example:
        
            rocket1 = Rocket(earth, altitude=400000, velocity=7670, timezoom=2)
       
        Required parameters:
        
        * **planet**:  Reference to a `Planet` object.
        
        Optional keyword parameters are supported:
        
        * **bitmap**:  url of a suitable bitmap image for the rocket (png recommended)
          default is `rocket.png`
        * **bitmapscale**:  scale factor for bitmap. Default is 0.1
        * **velocity**:  initial rocket speed. default is zero.
        * **directiond**:  initial rocket direction in degrees. Default is zero.
        * **direction**:  initial rocket direction in radians. Default is zero.
        * **tanomalyd**:  initial rocket true anomaly in degrees. Default is 90.
        * **tanomaly**:  initial rocket true anomaly in radians. Default is pi/2.
        * **altitude**:  initial rocket altitude in meters. Default is zero.
        * **showstatus**:  boolean displays flight parameters on screen. Default
          is True.
        * **statuspos**:  tuple with x,y coordinates of flight parameters. 
          Default is upper left.
        * **statuslist**: list of status names to include in flight parameters. 
          Default is all, consisting of: "velocity", "acceleration", "course",
          "altitude", "thrust", "mass", "trueanomaly", "scale", "timezoom",
          "shiptime"
        * **leftkey**: a `ggame` key identifier that will serve as the 
          "rotate left" key while controlling the ship. Default is 'left arrow'.
        * **rightkey**: a `ggame` key identifier that will serve as the 
          "rotate right" key while controlling the ship. Default is 'right arrow'.
        
        Following parameters may be set as a constant value, or pass in the
        name of a function that will return the value dynamically or the
        name of a `ggmath` UI control that will return the value.
        
        * **timezoom**  scale factor for time zoom. Factor = 10^timezoom
        * **heading**  direction to point the rocket in (must be radians)
        * **mass**  mass of the rocket (must be kg)
        * **thrust**  thrust of the rocket (must be N)

        Animation related parameters may be ignored if no sprite animation:
        
        * **bitmapframe**  ((x1,y1),(x2,y2)) tuple defines a region in the bitmap
        * **bitmapqty**  number of bitmaps -- used for animation effects
        * **bitmapdir**  "horizontal" or "vertical" use with animation effects
        * **bitmapmargin**  pixels between successive animation frames
        * **tickrate**  frequency of spacecraft dynamics calculations (Hz)
        
        """
        self._xy = (1000000,1000000)
        self.planet = planet
        self.bmurl = kwargs.get('bitmap', 'rocket.png') # default rocket png
        self.bitmapframe = kwargs.get('bitmapframe', None) #
        self.bitmapqty = kwargs.get('bitmapqty', 1) # Number of images in bitmap
        self.bitmapdir = kwargs.get('bitmapdir', 'horizontal') # animation orientation
        self.bitmapmargin = kwargs.get('bitmapmargin', 0) # bitmap spacing
        self.tickrate = kwargs.get('tickrate', 30) # dynamics calcs per sec
        # status display
        statusfuncs = [ self.velocityText,
                        self.accelerationText,
                        self.courseDegreesText,
                        self.altitudeText,
                        self.thrustText,
                        self.massText,
                        self.trueAnomalyDegreesText,
                        self.scaleText,
                        self.timeZoomText,
                        self.shipTimeText]
        statuslist = [  "velocity",
                        "acceleration",
                        "course",
                        "altitude",
                        "thrust",
                        "mass",
                        "trueanomaly",
                        "scale",
                        "timezoom",
                        "shiptime"]
        
        self.showstatus = kwargs.get('showstatus', True) # show stats
        self.statuspos = kwargs.get('statuspos', [10,10])  # position of stats
        self.statusselect = kwargs.get('statuslist', statuslist)
        self.localheading = 0
        # dynamic parameters
        self.timezoom = self.Eval(kwargs.get('timezoom', self.gettimezoom)) # 1,2,3 faster, -1, slower
        self.heading = self.Eval(kwargs.get('heading', self.getheading)) # must be radians
        self.mass = self.Eval(kwargs.get('mass', self.getmass)) # kg
        self.thrust = self.Eval(kwargs.get('thrust', self.getthrust)) # N
        # end dynamic 
        super().__init__(self.bmurl,
            self._getposition, 
            frame = self.bitmapframe, 
            qty = self.bitmapqty, 
            direction = self.bitmapdir,
            margin = self.bitmapmargin)
        self.scale = kwargs.get('bitmapscale', 0.1) # small
        initvel = kwargs.get('velocity', 0) # initial velocity
        initdird = kwargs.get('directiond', 0) # initial direction, degrees
        initdir = kwargs.get('direction', radians(initdird))
        tanomaly = kwargs.get('tanomaly', pi/2) # position angle
        tanomaly = radians(kwargs.get('tanomalyd', degrees(tanomaly))) 
        altitude = kwargs.get('altitude', 0) #
        r = altitude + self.planet.radius
        self._xy = (r*cos(tanomaly), r*sin(tanomaly))
        # default heading control if none provided by user
        leftkey = kwargs.get('leftkey', 'left arrow')
        rightkey = kwargs.get('rightkey', 'right arrow')
        if self.heading == self.getheading:
            Planet.listenKeyEvent('keydown', leftkey, self.turn)
            Planet.listenKeyEvent('keydown', rightkey, self.turn)
        self.timer = Timer()
        self.shiptime = 0  # track time on shipboard
        self.timer.callEvery(1/self.tickrate, self.dynamics)
        self.lasttime = self.timer.time
        self.V = [initvel * cos(initdir), initvel * sin(initdir)]
        self.A = [0,0]
        # set up status display
        if self.showstatus:
            self.addStatusReport(statuslist, statusfuncs, self.statusselect)

    # override or define externally!
    def getthrust(self):
        """
        User override function for dynamically determining thrust force.
        """
        return 0

    # override or define externally!
    def getmass(self):
        """
        User override function for dynamically determining rocket mass.
        """
        return 1

    # override or define externally!
    def getheading(self):
        """
        User override function for dynamically determining the heading.
        """
        return self.localheading
        
    # override or define externally!
    def gettimezoom(self):
        """
        User override function for dynamically determining the timezoom.
        """
        return 0

    # add a status reporting function to status display
    def addStatusReport(self, statuslist, statusfuncs, statusselect):
        """
        Accept list of all status names, all status text functions, and
        the list of status names that have been selected for display.
        """
        statusdict = {n:f for n, f in zip(statuslist, statusfuncs)}
        for name in statusselect:
            if name in statusdict:
                Label(self.statuspos[:], statusdict[name], size=15, positioning='physical', width=250)
                self.statuspos[1] += 25

    # functions available for reporting flight parameters to UI
    def velocityText(self):
        """
        Report the velocity in m/s as a text string.
        """
        return "Velocity:     {0:8.1f} m/s".format(self.velocity)
        
    def accelerationText(self):
        """
        Report the acceleration in m/s as a text string.
        """
        return "Acceleration: {0:8.1f} m/s²".format(self.acceleration)
        
    def courseDegreesText(self):
        """
        Report the heading in degrees (zero to the right) as a text string.
        """
        return "Course:       {0:8.1f}°".format(degrees(atan2(self.V[1], self.V[0])))

    def thrustText(self):
        """
        Report the thrust level in Newtons as a text string.
        """
        return "Thrust:       {0:8.1f} N".format(self.thrust())
        
    def massText(self):
        """
        Report the spacecraft mass in kilograms as a text string.
        """
        return "Mass:         {0:8.1f} kg".format(self.mass())
        
    def trueAnomalyDegreesText(self):
        """
        Report the true anomaly in degrees as a text string.
        """
        return "True Anomaly: {0:8.1f}°".format(self.tanomalyd)
        
    def trueAnomalyRadiansText(self):
        """
        Report the true anomaly in radians as a text string.
        """
        return "True Anomaly: {0:8.4f}".format(self.tanomaly)
        
    def altitudeText(self):
        """
        Report the altitude in meters as a text string.
        """
        return "Altitude:     {0:8.1f} m".format(self.altitude)
        
    def radiusText(self):
        """
        Report the radius (distance to planet center) in meters as a text string.
        """
        return "Radius:       {0:8.1f} m".format(self.r)
        
    def scaleText(self):
        """
        Report the view scale (pixels/meter) as a text string.
        """
        return "View Scale:   {0:8.6f} px/m".format(self.planet._scale)
    
    def timeZoomText(self):
        """
        Report the time acceleration as a text string.
        """
        return "Time Zoom:    {0:8.1f}".format(float(self.timezoom()))
        
    def shipTimeText(self):
        """
        Report the elapsed time as a text string.
        """
        return "Elapsed Time: {0:8.1f} s".format(float(self.shiptime))
    


            
    def dynamics(self, timer):
        """
        Perform one iteration of the simulation using runge-kutta 4th order method.
        """
        # set time duration equal to time since last execution
        tick = 10**self.timezoom()*(timer.time - self.lasttime)
        self.shiptime = self.shiptime + tick
        self.lasttime = timer.time
        # 4th order runge-kutta method (https://sites.temple.edu/math5061/files/2016/12/final_project.pdf)
        # and http://spiff.rit.edu/richmond/nbody/OrbitRungeKutta4.pdf  (succinct, but with a typo)
        self.A = k1v = self.ar(self._xy)
        k1r = self.V
        k2v = self.ar(self.vadd(self._xy, self.vmul(tick/2, k1r)))
        k2r = self.vadd(self.V, self.vmul(tick/2, k1v))
        k3v = self.ar(self.vadd(self._xy, self.vmul(tick/2, k2r)))
        k3r = self.vadd(self.V, self.vmul(tick/2, k2v))
        k4v = self.ar(self.vadd(self._xy, self.vmul(tick, k3r)))
        k4r = self.vadd(self.V, self.vmul(tick, k3v))
        self.V = [self.V[i] + tick/6*(k1v[i] + 2*k2v[i] + 2*k3v[i] + k4v[i]) for i in (0,1)]
        self._xy = [self._xy[i] + tick/6*(k1r[i] + 2*k2r[i] + 2*k3r[i] + k4r[i]) for i in (0,1)]
        if self.altitude < 0:
            self.V = [0,0]
            self.A = [0,0]
            self.altitude = 0

    # generic force as a function of position
    def fr(self, pos):
        """
        Compute the net force vector on the rocket, as a function of the 
        position vector.
        """
        self.rotation = self.heading()
        t = self.thrust()
        G = 6.674E-11
        r = Planet.distance((0,0), pos)
        uvec = (-pos[0]/r, -pos[1]/r)
        fg = G*self.mass()*self.planet.mass/r**2
        F = [x*fg for x in uvec]
        return [F[0] + t*cos(self.rotation), F[1] + t*sin(self.rotation)]

    # geric acceleration as a function of position
    def ar(self, pos):
        """
        Compute the acceleration vector of the rocket, as a function of the 
        position vector.
        """
        m = self.mass()
        F = self.fr(pos)
        return [F[i]/m for i in (0,1)]
        
    def vadd(self, v1, v2):
        """
        Vector add utility.
        """
        return [v1[i]+v2[i] for i in (0,1)]
    
    def vmul(self, s, v):
        """
        Scalar vector multiplication utility.
        """
        return [s*v[i] for i in (0,1)]
        
    def vmag(self, v):
        """
        Vector magnitude function.
        """
        return sqrt(v[0]**2 + v[1]**2)
    
    def fgrav(self):
        """
        Vector force due to gravity, at current position.
        """
        G = 6.674E-11
        r = self.r
        uvec = (-self._xy[0]/r, -self._xy[1]/r)
        F = G*self.mass()*self.planet.mass/r**2
        return [x*F for x in uvec]
    
    def turn(self, event):
        """
        Respond to left/right turning key events.
        """
        increment = pi/50 * (1 if event.key == "left arrow" else -1)
        self.localheading += increment
            
    def _getposition(self):
        return self._xy
    
    @property
    def xyposition(self):
        return self._xy
        
    @xyposition.setter
    def xyposition(self, pos):
        self._xy = pos
        #self._touchAsset()

    @property
    def tanomalyd(self):
        return degrees(self.tanomaly)
        
    @tanomalyd.setter
    def tanomalyd(self, angle):
        self.tanomaly = radians(angle)

    @property
    def altitude(self):
        alt = Planet.distance(self._xy, (0,0)) - self.planet.radius
        return alt
        
    @altitude.setter
    def altitude(self, alt):
        r = alt + self.planet.radius
        self._xy = (r*cos(self.tanomaly), r*sin(self.tanomaly))

    @property
    def velocity(self):
        return self.vmag(self.V)
    
    @property
    def acceleration(self):
        return self.vmag(self.A)
        
    @property
    def tanomaly(self):
        #pos = self._pos()
        return atan2(self._xy[1],self._xy[0])
        
    @tanomaly.setter
    def tanomaly(self, angle):
        r = self.r
        self._xy = (r*cos(angle), r*sin(angle))
        self._touchAsset()
            
    @property
    def r(self):
        return self.altitude + self.planet.radius

        
        
    


class Planet(MathApp):
    
    def __init__(self, **kwargs):
        """
        Initialize the Planet object. 

        Optional keyword parameters are supported:
        
        * **viewscale**  pixels per meter in graphics display. Default is 10.
        * **radius**  radius of the planet in meters. Default is Earth radius.
        * **planetmass** mass of the planet in kg. Default is Earth mass.
        * **color** color of the planet. Default is greenish (0x008040).
        * **viewalt** altitude of initial viewpoint in meters. Default is rocket 
          altitude.
        * **viewanom** true anomaly (angle) of initial viewpoint in radians. 
          Default is the rocket anomaly.
        * **viewanomd** true anomaly (angle) of initial viewpoing in degrees.
          Default is the rocket anomaly.
        
        """
        self.scale = kwargs.get('viewscale', 10)  # 10 pixels per meter default
        self.radius = kwargs.get('radius', 6.371E6) # Earth - meters
        self.mass = kwargs.get('planetmass', 5.9722E24) # Earth - kg
        self.color = kwargs.get('color', 0x008040)  # greenish
        self.kwargs = kwargs # save it for later..
        super().__init__(self.scale)

    def run(self, rocket=None):
        """
        Execute the Planet (and Rocket) simulation.

        Optional parameters:
        
        * **rocket** Reference to a Rocket object - sets the initial view
        """
        if rocket:
            viewalt = rocket.altitude
            viewanom = rocket.tanomaly
        else:
            viewalt = 0
            viewanom = pi/2
        self.viewaltitude = self.kwargs.get('viewalt', viewalt) # how high to look
        self.viewanomaly = self.kwargs.get('viewanom', viewanom)  # where to look
        self.viewanomalyd = self.kwargs.get('viewanomd', degrees(self.viewanomaly))
        self.planetcircle = Circle(
            (0,0), 
            self.radius, 
            style = LineStyle(1, Color(self.color,1)), 
            color = Color(self.color,0.5))
        r = self.radius + self.viewaltitude
        self.viewPosition = (r*cos(self.viewanomaly), r*sin(self.viewanomaly))
        super().run()

# test code here
if __name__ == "__main__":
    
    earth = Planet(viewscale=0.00005)
    earth.run()

    rocket1 = Rocket(earth, altitude=400000, velocity=7670, timezoom=2)
    rocket2 = Rocket(earth, altitude=440000, velocity=7670, timezoom=2, statuspos=[300,10])

Module variables

var pi

Classes

class Planet

class Planet(MathApp):
    
    def __init__(self, **kwargs):
        """
        Initialize the Planet object. 

        Optional keyword parameters are supported:
        
        * **viewscale**  pixels per meter in graphics display. Default is 10.
        * **radius**  radius of the planet in meters. Default is Earth radius.
        * **planetmass** mass of the planet in kg. Default is Earth mass.
        * **color** color of the planet. Default is greenish (0x008040).
        * **viewalt** altitude of initial viewpoint in meters. Default is rocket 
          altitude.
        * **viewanom** true anomaly (angle) of initial viewpoint in radians. 
          Default is the rocket anomaly.
        * **viewanomd** true anomaly (angle) of initial viewpoing in degrees.
          Default is the rocket anomaly.
        
        """
        self.scale = kwargs.get('viewscale', 10)  # 10 pixels per meter default
        self.radius = kwargs.get('radius', 6.371E6) # Earth - meters
        self.mass = kwargs.get('planetmass', 5.9722E24) # Earth - kg
        self.color = kwargs.get('color', 0x008040)  # greenish
        self.kwargs = kwargs # save it for later..
        super().__init__(self.scale)

    def run(self, rocket=None):
        """
        Execute the Planet (and Rocket) simulation.

        Optional parameters:
        
        * **rocket** Reference to a Rocket object - sets the initial view
        """
        if rocket:
            viewalt = rocket.altitude
            viewanom = rocket.tanomaly
        else:
            viewalt = 0
            viewanom = pi/2
        self.viewaltitude = self.kwargs.get('viewalt', viewalt) # how high to look
        self.viewanomaly = self.kwargs.get('viewanom', viewanom)  # where to look
        self.viewanomalyd = self.kwargs.get('viewanomd', degrees(self.viewanomaly))
        self.planetcircle = Circle(
            (0,0), 
            self.radius, 
            style = LineStyle(1, Color(self.color,1)), 
            color = Color(self.color,0.5))
        r = self.radius + self.viewaltitude
        self.viewPosition = (r*cos(self.viewanomaly), r*sin(self.viewanomaly))
        super().run()

Ancestors (in MRO)

  • Planet
  • ggmath.MathApp
  • ggame.App
  • builtins.object

Class variables

var spritelist

var time

Static methods

def __init__(

self, **kwargs)

Initialize the Planet object.

Optional keyword parameters are supported:

  • viewscale pixels per meter in graphics display. Default is 10.
  • radius radius of the planet in meters. Default is Earth radius.
  • planetmass mass of the planet in kg. Default is Earth mass.
  • color color of the planet. Default is greenish (0x008040).
  • viewalt altitude of initial viewpoint in meters. Default is rocket altitude.
  • viewanom true anomaly (angle) of initial viewpoint in radians. Default is the rocket anomaly.
  • viewanomd true anomaly (angle) of initial viewpoing in degrees. Default is the rocket anomaly.
def __init__(self, **kwargs):
    """
    Initialize the Planet object. 
    Optional keyword parameters are supported:
    
    * **viewscale**  pixels per meter in graphics display. Default is 10.
    * **radius**  radius of the planet in meters. Default is Earth radius.
    * **planetmass** mass of the planet in kg. Default is Earth mass.
    * **color** color of the planet. Default is greenish (0x008040).
    * **viewalt** altitude of initial viewpoint in meters. Default is rocket 
      altitude.
    * **viewanom** true anomaly (angle) of initial viewpoint in radians. 
      Default is the rocket anomaly.
    * **viewanomd** true anomaly (angle) of initial viewpoing in degrees.
      Default is the rocket anomaly.
    
    """
    self.scale = kwargs.get('viewscale', 10)  # 10 pixels per meter default
    self.radius = kwargs.get('radius', 6.371E6) # Earth - meters
    self.mass = kwargs.get('planetmass', 5.9722E24) # Earth - kg
    self.color = kwargs.get('color', 0x008040)  # greenish
    self.kwargs = kwargs # save it for later..
    super().__init__(self.scale)

def handleMouseClick(

self, event)

def handleMouseClick(self, event):
    found = False
    for obj in self._mathSelectableList:
        if obj.physicalPointTouching((event.x, event.y)):
            found = True
            if not obj.selected: 
                obj.select()
                self.selectedObj = obj
    if not found and self.selectedObj:
        self.selectedObj.unselect()
        self.selectedObj = None

def handleMouseDown(

self, event)

def handleMouseDown(self, event):
    self.mouseDown = True
    self.mouseCapturedObject = None
    self.mouseStrokedObject = None
    for obj in self._mathSelectableList:
        if obj.physicalPointTouching((event.x, event.y)):
            obj.mousedown()
            self.mouseDownObject = obj
            break
    for obj in self._mathMovableList:
        if obj.physicalPointTouching((event.x, event.y)) and not (obj.strokable and obj.canstroke((event.x,event.y))):
            self.mouseCapturedObject = obj
            break
    if not self.mouseCapturedObject:
        for obj in self._mathStrokableList:
            if obj.canstroke((event.x, event.y)):
                self.mouseStrokedObject = obj
                break

def handleMouseMove(

self, event)

def handleMouseMove(self, event):
    if not self.mouseX:
        self.mouseX = event.x
        self.mouseY = event.y
    dx = event.x - self.mouseX
    dy = event.y - self.mouseY
    self.mouseX = event.x
    self.mouseY = event.y
    if self.mouseDown:
        if self.mouseCapturedObject:
            self.mouseCapturedObject.translate((dx, dy))
        elif self.mouseStrokedObject:
            self.mouseStrokedObject.stroke((self.mouseX,self.mouseY), (dx,dy))
        else:
            lmove = self.translatePhysicalToLogical((dx, dy))
            MathApp._xcenter -= lmove[0]
            MathApp._ycenter -= lmove[1]
            self._touchAllVisuals()
            self._viewNotify("translate")

def handleMouseUp(

self, event)

def handleMouseUp(self, event):
    if self.mouseDownObject:
        self.mouseDownObject.mouseup()
        self.mouseDownObject = None
    self.mouseDown = False
    self.mouseCapturedObject = None
    self.mouseStrokedObject = None

def handleMouseWheel(

self, event)

def handleMouseWheel(self, event):
    zoomfactor = event.wheelDelta/100
    zoomfactor = 1+zoomfactor if zoomfactor > 0 else 1+zoomfactor
    if zoomfactor > 1.2:
        zoomfactor = 1.2
    elif zoomfactor < 0.8:
        zoomfactor = 0.8
    MathApp._scale *= zoomfactor
    self._touchAllVisuals()
    self._viewNotify("zoom")

def run(

self, rocket=None)

Execute the Planet (and Rocket) simulation.

Optional parameters:

  • rocket Reference to a Rocket object - sets the initial view
def run(self, rocket=None):
    """
    Execute the Planet (and Rocket) simulation.
    Optional parameters:
    
    * **rocket** Reference to a Rocket object - sets the initial view
    """
    if rocket:
        viewalt = rocket.altitude
        viewanom = rocket.tanomaly
    else:
        viewalt = 0
        viewanom = pi/2
    self.viewaltitude = self.kwargs.get('viewalt', viewalt) # how high to look
    self.viewanomaly = self.kwargs.get('viewanom', viewanom)  # where to look
    self.viewanomalyd = self.kwargs.get('viewanomd', degrees(self.viewanomaly))
    self.planetcircle = Circle(
        (0,0), 
        self.radius, 
        style = LineStyle(1, Color(self.color,1)), 
        color = Color(self.color,0.5))
    r = self.radius + self.viewaltitude
    self.viewPosition = (r*cos(self.viewanomaly), r*sin(self.viewanomaly))
    super().run()

def step(

self)

def step(self):
    MathApp.time = time()
    for spr in self._mathDynamicList:
        spr.step()

Instance variables

var color

var kwargs

var mass

var radius

var scale

var viewPosition

var width

Methods

def addViewNotification(

cls, handler)

@classmethod   
def addViewNotification(cls, handler):
    cls._viewNotificationList.append(handler)

def distance(

cls, pos1, pos2)

@classmethod   
def distance(cls, pos1, pos2):
    return sqrt((pos2[0]-pos1[0])**2 + (pos2[1]-pos1[1])**2)

def getSpritesbyClass(

cls, sclass)

Returns a list of all active sprites of a given class.

@classmethod
def getSpritesbyClass(cls, sclass):
    """
    Returns a list of all active sprites of a given class.
    """
    return App._spritesdict.get(sclass, [])

def listenKeyEvent(

cls, eventtype, key, callback)

Register to receive keyboard events. The eventtype parameter is a string that indicates what type of key event to receive (value is one of: 'keydown', 'keyup' or 'keypress'). The key parameter is a string indicating which key (e.g. 'space', 'left arrow', etc.) to receive events for. The callback parameter is a reference to a function or method that will be called with the ggame.KeyEvent object when the event occurs.

See the source for ggame.KeyEvent.keys for a list of key names to use with the key paramter.

@classmethod
def listenKeyEvent(cls, eventtype, key, callback):
    """
    Register to receive keyboard events. The `eventtype` parameter is a 
    string that indicates what type of key event to receive (value is one
    of: `'keydown'`, `'keyup'` or `'keypress'`). The `key` parameter is a 
    string indicating which key (e.g. `'space'`, `'left arrow'`, etc.) to 
    receive events for. The `callback` parameter is a reference to a 
    function or method that will be called with the `ggame.KeyEvent` object
    when the event occurs.
    See the source for `ggame.KeyEvent.keys` for a list of key names
    to use with the `key` paramter.
    """
    evtlist = App._eventdict.get((eventtype, key), [])
    if not callback in evtlist:
        evtlist.append(callback)
    App._eventdict[(eventtype, key)] = evtlist

def listenMouseEvent(

cls, eventtype, callback)

Register to receive mouse events. The eventtype parameter is a string that indicates what type of mouse event to receive ( value is one of: 'mousemove', 'mousedown', 'mouseup', 'click', 'dblclick' or 'mousewheel'). The callback parameter is a reference to a function or method that will be called with the ggame.MouseEvent object when the event occurs.

@classmethod
def listenMouseEvent(cls, eventtype, callback):
    """
    Register to receive mouse events. The `eventtype` parameter is
    a string that indicates what type of mouse event to receive (
    value is one of: `'mousemove'`, `'mousedown'`, `'mouseup'`, `'click'`, 
    `'dblclick'` or `'mousewheel'`). The `callback` parameter is a 
    reference to a function or method that will be called with the 
    `ggame.MouseEvent` object when the event occurs.
    """
    evtlist = App._eventdict.get(eventtype, [])
    if not callback in evtlist:
        evtlist.append(callback)
    App._eventdict[eventtype] = evtlist

def logicalToPhysical(

cls, lp)

@classmethod
def logicalToPhysical(cls, lp):
    xxform = lambda xvalue, xscale, xcenter, physwidth: int((xvalue-xcenter)*xscale + physwidth/2)
    yxform = lambda yvalue, yscale, ycenter, physheight: int(physheight/2 - (yvalue-ycenter)*yscale)
    try:
        return (xxform(lp[0], cls._scale, cls._xcenter, cls._win.width),
            yxform(lp[1], cls._scale, cls._ycenter, cls._win.height))
    except AttributeError:
        return lp

def physicalToLogical(

cls, pp)

@classmethod
def physicalToLogical(cls, pp):
    xxform = lambda xvalue, xscale, xcenter, physwidth: (xvalue - physwidth/2)/xscale + xcenter
    yxform = lambda yvalue, yscale, ycenter, physheight: (physheight/2 - yvalue)/yscale + ycenter
    try:
        return (xxform(pp[0], cls._scale, cls._xcenter, cls._win.width),
            yxform(pp[1], cls._scale, cls._ycenter, cls._win.height))
    except AttributeError:
        return pp

def removeViewNotification(

cls, handler)

@classmethod   
def removeViewNotification(cls, handler):
    cls._viewNotificationList.remove(handler)

def translateLogicalToPhysical(

cls, pp)

@classmethod
def translateLogicalToPhysical(cls, pp):
    xxform = lambda xvalue, xscale: xvalue*xscale
    yxform = lambda yvalue, yscale: -yvalue*yscale
    try:
        return (xxform(pp[0], cls._scale), yxform(pp[1], cls._scale))
    except AttributeError:
        return pp

def translatePhysicalToLogical(

cls, pp)

@classmethod
def translatePhysicalToLogical(cls, pp):
    xxform = lambda xvalue, xscale: xvalue/xscale
    yxform = lambda yvalue, yscale: -yvalue/yscale
    try:
        return (xxform(pp[0], cls._scale), yxform(pp[1], cls._scale))
    except AttributeError:
        return pp

def unlistenKeyEvent(

cls, eventtype, key, callback)

Use this method to remove a registration to receive a particular keyboard event. Arguments must exactly match those used when registering for the event.

@classmethod
def unlistenKeyEvent(cls, eventtype, key, callback):
    """
    Use this method to remove a registration to receive a particular
    keyboard event. Arguments must exactly match those used when
    registering for the event.
    """
    App._eventdict[(eventtype,key)].remove(callback)

def unlistenMouseEvent(

cls, eventtype, callback)

Use this method to remove a registration to receive a particular mouse event. Arguments must exactly match those used when registering for the event.

@classmethod
def unlistenMouseEvent(cls, eventtype, callback):
    """
    Use this method to remove a registration to receive a particular
    mouse event. Arguments must exactly match those used when
    registering for the event.
    """
    App._eventdict[eventtype].remove(callback)

class Rocket

Rocket is a class for simulating the motion of a projectile through space, acted upon by arbitrary forces (thrust) and by gravitaitonal attraction to a single planetary object.

class Rocket(ImagePoint):
    """
    Rocket is a class for simulating the motion of a projectile through space, 
    acted upon by arbitrary forces (thrust) and by gravitaitonal 
    attraction to a single planetary object.
    """

    def __init__(self, planet, **kwargs):
        """
        Initialize the Rocket object. 
        
        Example:
        
            rocket1 = Rocket(earth, altitude=400000, velocity=7670, timezoom=2)
       
        Required parameters:
        
        * **planet**:  Reference to a `Planet` object.
        
        Optional keyword parameters are supported:
        
        * **bitmap**:  url of a suitable bitmap image for the rocket (png recommended)
          default is `rocket.png`
        * **bitmapscale**:  scale factor for bitmap. Default is 0.1
        * **velocity**:  initial rocket speed. default is zero.
        * **directiond**:  initial rocket direction in degrees. Default is zero.
        * **direction**:  initial rocket direction in radians. Default is zero.
        * **tanomalyd**:  initial rocket true anomaly in degrees. Default is 90.
        * **tanomaly**:  initial rocket true anomaly in radians. Default is pi/2.
        * **altitude**:  initial rocket altitude in meters. Default is zero.
        * **showstatus**:  boolean displays flight parameters on screen. Default
          is True.
        * **statuspos**:  tuple with x,y coordinates of flight parameters. 
          Default is upper left.
        * **statuslist**: list of status names to include in flight parameters. 
          Default is all, consisting of: "velocity", "acceleration", "course",
          "altitude", "thrust", "mass", "trueanomaly", "scale", "timezoom",
          "shiptime"
        * **leftkey**: a `ggame` key identifier that will serve as the 
          "rotate left" key while controlling the ship. Default is 'left arrow'.
        * **rightkey**: a `ggame` key identifier that will serve as the 
          "rotate right" key while controlling the ship. Default is 'right arrow'.
        
        Following parameters may be set as a constant value, or pass in the
        name of a function that will return the value dynamically or the
        name of a `ggmath` UI control that will return the value.
        
        * **timezoom**  scale factor for time zoom. Factor = 10^timezoom
        * **heading**  direction to point the rocket in (must be radians)
        * **mass**  mass of the rocket (must be kg)
        * **thrust**  thrust of the rocket (must be N)

        Animation related parameters may be ignored if no sprite animation:
        
        * **bitmapframe**  ((x1,y1),(x2,y2)) tuple defines a region in the bitmap
        * **bitmapqty**  number of bitmaps -- used for animation effects
        * **bitmapdir**  "horizontal" or "vertical" use with animation effects
        * **bitmapmargin**  pixels between successive animation frames
        * **tickrate**  frequency of spacecraft dynamics calculations (Hz)
        
        """
        self._xy = (1000000,1000000)
        self.planet = planet
        self.bmurl = kwargs.get('bitmap', 'rocket.png') # default rocket png
        self.bitmapframe = kwargs.get('bitmapframe', None) #
        self.bitmapqty = kwargs.get('bitmapqty', 1) # Number of images in bitmap
        self.bitmapdir = kwargs.get('bitmapdir', 'horizontal') # animation orientation
        self.bitmapmargin = kwargs.get('bitmapmargin', 0) # bitmap spacing
        self.tickrate = kwargs.get('tickrate', 30) # dynamics calcs per sec
        # status display
        statusfuncs = [ self.velocityText,
                        self.accelerationText,
                        self.courseDegreesText,
                        self.altitudeText,
                        self.thrustText,
                        self.massText,
                        self.trueAnomalyDegreesText,
                        self.scaleText,
                        self.timeZoomText,
                        self.shipTimeText]
        statuslist = [  "velocity",
                        "acceleration",
                        "course",
                        "altitude",
                        "thrust",
                        "mass",
                        "trueanomaly",
                        "scale",
                        "timezoom",
                        "shiptime"]
        
        self.showstatus = kwargs.get('showstatus', True) # show stats
        self.statuspos = kwargs.get('statuspos', [10,10])  # position of stats
        self.statusselect = kwargs.get('statuslist', statuslist)
        self.localheading = 0
        # dynamic parameters
        self.timezoom = self.Eval(kwargs.get('timezoom', self.gettimezoom)) # 1,2,3 faster, -1, slower
        self.heading = self.Eval(kwargs.get('heading', self.getheading)) # must be radians
        self.mass = self.Eval(kwargs.get('mass', self.getmass)) # kg
        self.thrust = self.Eval(kwargs.get('thrust', self.getthrust)) # N
        # end dynamic 
        super().__init__(self.bmurl,
            self._getposition, 
            frame = self.bitmapframe, 
            qty = self.bitmapqty, 
            direction = self.bitmapdir,
            margin = self.bitmapmargin)
        self.scale = kwargs.get('bitmapscale', 0.1) # small
        initvel = kwargs.get('velocity', 0) # initial velocity
        initdird = kwargs.get('directiond', 0) # initial direction, degrees
        initdir = kwargs.get('direction', radians(initdird))
        tanomaly = kwargs.get('tanomaly', pi/2) # position angle
        tanomaly = radians(kwargs.get('tanomalyd', degrees(tanomaly))) 
        altitude = kwargs.get('altitude', 0) #
        r = altitude + self.planet.radius
        self._xy = (r*cos(tanomaly), r*sin(tanomaly))
        # default heading control if none provided by user
        leftkey = kwargs.get('leftkey', 'left arrow')
        rightkey = kwargs.get('rightkey', 'right arrow')
        if self.heading == self.getheading:
            Planet.listenKeyEvent('keydown', leftkey, self.turn)
            Planet.listenKeyEvent('keydown', rightkey, self.turn)
        self.timer = Timer()
        self.shiptime = 0  # track time on shipboard
        self.timer.callEvery(1/self.tickrate, self.dynamics)
        self.lasttime = self.timer.time
        self.V = [initvel * cos(initdir), initvel * sin(initdir)]
        self.A = [0,0]
        # set up status display
        if self.showstatus:
            self.addStatusReport(statuslist, statusfuncs, self.statusselect)

    # override or define externally!
    def getthrust(self):
        """
        User override function for dynamically determining thrust force.
        """
        return 0

    # override or define externally!
    def getmass(self):
        """
        User override function for dynamically determining rocket mass.
        """
        return 1

    # override or define externally!
    def getheading(self):
        """
        User override function for dynamically determining the heading.
        """
        return self.localheading
        
    # override or define externally!
    def gettimezoom(self):
        """
        User override function for dynamically determining the timezoom.
        """
        return 0

    # add a status reporting function to status display
    def addStatusReport(self, statuslist, statusfuncs, statusselect):
        """
        Accept list of all status names, all status text functions, and
        the list of status names that have been selected for display.
        """
        statusdict = {n:f for n, f in zip(statuslist, statusfuncs)}
        for name in statusselect:
            if name in statusdict:
                Label(self.statuspos[:], statusdict[name], size=15, positioning='physical', width=250)
                self.statuspos[1] += 25

    # functions available for reporting flight parameters to UI
    def velocityText(self):
        """
        Report the velocity in m/s as a text string.
        """
        return "Velocity:     {0:8.1f} m/s".format(self.velocity)
        
    def accelerationText(self):
        """
        Report the acceleration in m/s as a text string.
        """
        return "Acceleration: {0:8.1f} m/s²".format(self.acceleration)
        
    def courseDegreesText(self):
        """
        Report the heading in degrees (zero to the right) as a text string.
        """
        return "Course:       {0:8.1f}°".format(degrees(atan2(self.V[1], self.V[0])))

    def thrustText(self):
        """
        Report the thrust level in Newtons as a text string.
        """
        return "Thrust:       {0:8.1f} N".format(self.thrust())
        
    def massText(self):
        """
        Report the spacecraft mass in kilograms as a text string.
        """
        return "Mass:         {0:8.1f} kg".format(self.mass())
        
    def trueAnomalyDegreesText(self):
        """
        Report the true anomaly in degrees as a text string.
        """
        return "True Anomaly: {0:8.1f}°".format(self.tanomalyd)
        
    def trueAnomalyRadiansText(self):
        """
        Report the true anomaly in radians as a text string.
        """
        return "True Anomaly: {0:8.4f}".format(self.tanomaly)
        
    def altitudeText(self):
        """
        Report the altitude in meters as a text string.
        """
        return "Altitude:     {0:8.1f} m".format(self.altitude)
        
    def radiusText(self):
        """
        Report the radius (distance to planet center) in meters as a text string.
        """
        return "Radius:       {0:8.1f} m".format(self.r)
        
    def scaleText(self):
        """
        Report the view scale (pixels/meter) as a text string.
        """
        return "View Scale:   {0:8.6f} px/m".format(self.planet._scale)
    
    def timeZoomText(self):
        """
        Report the time acceleration as a text string.
        """
        return "Time Zoom:    {0:8.1f}".format(float(self.timezoom()))
        
    def shipTimeText(self):
        """
        Report the elapsed time as a text string.
        """
        return "Elapsed Time: {0:8.1f} s".format(float(self.shiptime))
    


            
    def dynamics(self, timer):
        """
        Perform one iteration of the simulation using runge-kutta 4th order method.
        """
        # set time duration equal to time since last execution
        tick = 10**self.timezoom()*(timer.time - self.lasttime)
        self.shiptime = self.shiptime + tick
        self.lasttime = timer.time
        # 4th order runge-kutta method (https://sites.temple.edu/math5061/files/2016/12/final_project.pdf)
        # and http://spiff.rit.edu/richmond/nbody/OrbitRungeKutta4.pdf  (succinct, but with a typo)
        self.A = k1v = self.ar(self._xy)
        k1r = self.V
        k2v = self.ar(self.vadd(self._xy, self.vmul(tick/2, k1r)))
        k2r = self.vadd(self.V, self.vmul(tick/2, k1v))
        k3v = self.ar(self.vadd(self._xy, self.vmul(tick/2, k2r)))
        k3r = self.vadd(self.V, self.vmul(tick/2, k2v))
        k4v = self.ar(self.vadd(self._xy, self.vmul(tick, k3r)))
        k4r = self.vadd(self.V, self.vmul(tick, k3v))
        self.V = [self.V[i] + tick/6*(k1v[i] + 2*k2v[i] + 2*k3v[i] + k4v[i]) for i in (0,1)]
        self._xy = [self._xy[i] + tick/6*(k1r[i] + 2*k2r[i] + 2*k3r[i] + k4r[i]) for i in (0,1)]
        if self.altitude < 0:
            self.V = [0,0]
            self.A = [0,0]
            self.altitude = 0

    # generic force as a function of position
    def fr(self, pos):
        """
        Compute the net force vector on the rocket, as a function of the 
        position vector.
        """
        self.rotation = self.heading()
        t = self.thrust()
        G = 6.674E-11
        r = Planet.distance((0,0), pos)
        uvec = (-pos[0]/r, -pos[1]/r)
        fg = G*self.mass()*self.planet.mass/r**2
        F = [x*fg for x in uvec]
        return [F[0] + t*cos(self.rotation), F[1] + t*sin(self.rotation)]

    # geric acceleration as a function of position
    def ar(self, pos):
        """
        Compute the acceleration vector of the rocket, as a function of the 
        position vector.
        """
        m = self.mass()
        F = self.fr(pos)
        return [F[i]/m for i in (0,1)]
        
    def vadd(self, v1, v2):
        """
        Vector add utility.
        """
        return [v1[i]+v2[i] for i in (0,1)]
    
    def vmul(self, s, v):
        """
        Scalar vector multiplication utility.
        """
        return [s*v[i] for i in (0,1)]
        
    def vmag(self, v):
        """
        Vector magnitude function.
        """
        return sqrt(v[0]**2 + v[1]**2)
    
    def fgrav(self):
        """
        Vector force due to gravity, at current position.
        """
        G = 6.674E-11
        r = self.r
        uvec = (-self._xy[0]/r, -self._xy[1]/r)
        F = G*self.mass()*self.planet.mass/r**2
        return [x*F for x in uvec]
    
    def turn(self, event):
        """
        Respond to left/right turning key events.
        """
        increment = pi/50 * (1 if event.key == "left arrow" else -1)
        self.localheading += increment
            
    def _getposition(self):
        return self._xy
    
    @property
    def xyposition(self):
        return self._xy
        
    @xyposition.setter
    def xyposition(self, pos):
        self._xy = pos
        #self._touchAsset()

    @property
    def tanomalyd(self):
        return degrees(self.tanomaly)
        
    @tanomalyd.setter
    def tanomalyd(self, angle):
        self.tanomaly = radians(angle)

    @property
    def altitude(self):
        alt = Planet.distance(self._xy, (0,0)) - self.planet.radius
        return alt
        
    @altitude.setter
    def altitude(self, alt):
        r = alt + self.planet.radius
        self._xy = (r*cos(self.tanomaly), r*sin(self.tanomaly))

    @property
    def velocity(self):
        return self.vmag(self.V)
    
    @property
    def acceleration(self):
        return self.vmag(self.A)
        
    @property
    def tanomaly(self):
        #pos = self._pos()
        return atan2(self._xy[1],self._xy[0])
        
    @tanomaly.setter
    def tanomaly(self, angle):
        r = self.r
        self._xy = (r*cos(angle), r*sin(angle))
        self._touchAsset()
            
    @property
    def r(self):
        return self.altitude + self.planet.radius

Ancestors (in MRO)

  • Rocket
  • ggmath.ImagePoint
  • ggmath._Point
  • ggmath._MathVisual
  • ggame.Sprite
  • ggmath._MathDynamic
  • builtins.object

Class variables

var defaultcolor

var defaultsize

var defaultstyle

var defaultwidth

var nonposinputsdef

var posinputsdef

Static methods

def __init__(

self, planet, **kwargs)

Initialize the Rocket object.

Example:

rocket1 = Rocket(earth, altitude=400000, velocity=7670, timezoom=2)

Required parameters:

  • planet: Reference to a Planet object.

Optional keyword parameters are supported:

  • bitmap: url of a suitable bitmap image for the rocket (png recommended) default is rocket.png
  • bitmapscale: scale factor for bitmap. Default is 0.1
  • velocity: initial rocket speed. default is zero.
  • directiond: initial rocket direction in degrees. Default is zero.
  • direction: initial rocket direction in radians. Default is zero.
  • tanomalyd: initial rocket true anomaly in degrees. Default is 90.
  • tanomaly: initial rocket true anomaly in radians. Default is pi/2.
  • altitude: initial rocket altitude in meters. Default is zero.
  • showstatus: boolean displays flight parameters on screen. Default is True.
  • statuspos: tuple with x,y coordinates of flight parameters. Default is upper left.
  • statuslist: list of status names to include in flight parameters. Default is all, consisting of: "velocity", "acceleration", "course", "altitude", "thrust", "mass", "trueanomaly", "scale", "timezoom", "shiptime"
  • leftkey: a ggame key identifier that will serve as the "rotate left" key while controlling the ship. Default is 'left arrow'.
  • rightkey: a ggame key identifier that will serve as the "rotate right" key while controlling the ship. Default is 'right arrow'.

Following parameters may be set as a constant value, or pass in the name of a function that will return the value dynamically or the name of a ggmath UI control that will return the value.

  • timezoom scale factor for time zoom. Factor = 10^timezoom
  • heading direction to point the rocket in (must be radians)
  • mass mass of the rocket (must be kg)
  • thrust thrust of the rocket (must be N)

Animation related parameters may be ignored if no sprite animation:

  • bitmapframe ((x1,y1),(x2,y2)) tuple defines a region in the bitmap
  • bitmapqty number of bitmaps -- used for animation effects
  • bitmapdir "horizontal" or "vertical" use with animation effects
  • bitmapmargin pixels between successive animation frames
  • tickrate frequency of spacecraft dynamics calculations (Hz)
def __init__(self, planet, **kwargs):
    """
    Initialize the Rocket object. 
    
    Example:
    
        rocket1 = Rocket(earth, altitude=400000, velocity=7670, timezoom=2)
   
    Required parameters:
    
    * **planet**:  Reference to a `Planet` object.
    
    Optional keyword parameters are supported:
    
    * **bitmap**:  url of a suitable bitmap image for the rocket (png recommended)
      default is `rocket.png`
    * **bitmapscale**:  scale factor for bitmap. Default is 0.1
    * **velocity**:  initial rocket speed. default is zero.
    * **directiond**:  initial rocket direction in degrees. Default is zero.
    * **direction**:  initial rocket direction in radians. Default is zero.
    * **tanomalyd**:  initial rocket true anomaly in degrees. Default is 90.
    * **tanomaly**:  initial rocket true anomaly in radians. Default is pi/2.
    * **altitude**:  initial rocket altitude in meters. Default is zero.
    * **showstatus**:  boolean displays flight parameters on screen. Default
      is True.
    * **statuspos**:  tuple with x,y coordinates of flight parameters. 
      Default is upper left.
    * **statuslist**: list of status names to include in flight parameters. 
      Default is all, consisting of: "velocity", "acceleration", "course",
      "altitude", "thrust", "mass", "trueanomaly", "scale", "timezoom",
      "shiptime"
    * **leftkey**: a `ggame` key identifier that will serve as the 
      "rotate left" key while controlling the ship. Default is 'left arrow'.
    * **rightkey**: a `ggame` key identifier that will serve as the 
      "rotate right" key while controlling the ship. Default is 'right arrow'.
    
    Following parameters may be set as a constant value, or pass in the
    name of a function that will return the value dynamically or the
    name of a `ggmath` UI control that will return the value.
    
    * **timezoom**  scale factor for time zoom. Factor = 10^timezoom
    * **heading**  direction to point the rocket in (must be radians)
    * **mass**  mass of the rocket (must be kg)
    * **thrust**  thrust of the rocket (must be N)
    Animation related parameters may be ignored if no sprite animation:
    
    * **bitmapframe**  ((x1,y1),(x2,y2)) tuple defines a region in the bitmap
    * **bitmapqty**  number of bitmaps -- used for animation effects
    * **bitmapdir**  "horizontal" or "vertical" use with animation effects
    * **bitmapmargin**  pixels between successive animation frames
    * **tickrate**  frequency of spacecraft dynamics calculations (Hz)
    
    """
    self._xy = (1000000,1000000)
    self.planet = planet
    self.bmurl = kwargs.get('bitmap', 'rocket.png') # default rocket png
    self.bitmapframe = kwargs.get('bitmapframe', None) #
    self.bitmapqty = kwargs.get('bitmapqty', 1) # Number of images in bitmap
    self.bitmapdir = kwargs.get('bitmapdir', 'horizontal') # animation orientation
    self.bitmapmargin = kwargs.get('bitmapmargin', 0) # bitmap spacing
    self.tickrate = kwargs.get('tickrate', 30) # dynamics calcs per sec
    # status display
    statusfuncs = [ self.velocityText,
                    self.accelerationText,
                    self.courseDegreesText,
                    self.altitudeText,
                    self.thrustText,
                    self.massText,
                    self.trueAnomalyDegreesText,
                    self.scaleText,
                    self.timeZoomText,
                    self.shipTimeText]
    statuslist = [  "velocity",
                    "acceleration",
                    "course",
                    "altitude",
                    "thrust",
                    "mass",
                    "trueanomaly",
                    "scale",
                    "timezoom",
                    "shiptime"]
    
    self.showstatus = kwargs.get('showstatus', True) # show stats
    self.statuspos = kwargs.get('statuspos', [10,10])  # position of stats
    self.statusselect = kwargs.get('statuslist', statuslist)
    self.localheading = 0
    # dynamic parameters
    self.timezoom = self.Eval(kwargs.get('timezoom', self.gettimezoom)) # 1,2,3 faster, -1, slower
    self.heading = self.Eval(kwargs.get('heading', self.getheading)) # must be radians
    self.mass = self.Eval(kwargs.get('mass', self.getmass)) # kg
    self.thrust = self.Eval(kwargs.get('thrust', self.getthrust)) # N
    # end dynamic 
    super().__init__(self.bmurl,
        self._getposition, 
        frame = self.bitmapframe, 
        qty = self.bitmapqty, 
        direction = self.bitmapdir,
        margin = self.bitmapmargin)
    self.scale = kwargs.get('bitmapscale', 0.1) # small
    initvel = kwargs.get('velocity', 0) # initial velocity
    initdird = kwargs.get('directiond', 0) # initial direction, degrees
    initdir = kwargs.get('direction', radians(initdird))
    tanomaly = kwargs.get('tanomaly', pi/2) # position angle
    tanomaly = radians(kwargs.get('tanomalyd', degrees(tanomaly))) 
    altitude = kwargs.get('altitude', 0) #
    r = altitude + self.planet.radius
    self._xy = (r*cos(tanomaly), r*sin(tanomaly))
    # default heading control if none provided by user
    leftkey = kwargs.get('leftkey', 'left arrow')
    rightkey = kwargs.get('rightkey', 'right arrow')
    if self.heading == self.getheading:
        Planet.listenKeyEvent('keydown', leftkey, self.turn)
        Planet.listenKeyEvent('keydown', rightkey, self.turn)
    self.timer = Timer()
    self.shiptime = 0  # track time on shipboard
    self.timer.callEvery(1/self.tickrate, self.dynamics)
    self.lasttime = self.timer.time
    self.V = [initvel * cos(initdir), initvel * sin(initdir)]
    self.A = [0,0]
    # set up status display
    if self.showstatus:
        self.addStatusReport(statuslist, statusfuncs, self.statusselect)

def Eval(

self, val)

def Eval(self, val):
    if callable(val):
        self._setDynamic() # dynamically defined .. must step
        return val
    else:
        return lambda : val  

def accelerationText(

self)

Report the acceleration in m/s as a text string.

def accelerationText(self):
    """
    Report the acceleration in m/s as a text string.
    """
    return "Acceleration: {0:8.1f} m/s²".format(self.acceleration)

def addStatusReport(

self, statuslist, statusfuncs, statusselect)

Accept list of all status names, all status text functions, and the list of status names that have been selected for display.

def addStatusReport(self, statuslist, statusfuncs, statusselect):
    """
    Accept list of all status names, all status text functions, and
    the list of status names that have been selected for display.
    """
    statusdict = {n:f for n, f in zip(statuslist, statusfuncs)}
    for name in statusselect:
        if name in statusdict:
            Label(self.statuspos[:], statusdict[name], size=15, positioning='physical', width=250)
            self.statuspos[1] += 25

def altitudeText(

self)

Report the altitude in meters as a text string.

def altitudeText(self):
    """
    Report the altitude in meters as a text string.
    """
    return "Altitude:     {0:8.1f} m".format(self.altitude)

def ar(

self, pos)

Compute the acceleration vector of the rocket, as a function of the position vector.

def ar(self, pos):
    """
    Compute the acceleration vector of the rocket, as a function of the 
    position vector.
    """
    m = self.mass()
    F = self.fr(pos)
    return [F[i]/m for i in (0,1)]

def canstroke(

self, ppos)

def canstroke(self, ppos):
    return False

def circularCollisionModel(

self)

Obsolete. No op.

def circularCollisionModel(self):
    """
    Obsolete. No op.
    """
    pass

def collidingPolyWithPoly(

self, obj)

def collidingPolyWithPoly(self, obj):
    return True

def collidingWith(

self, obj)

Return a boolean True if this sprite is currently overlapping the sprite referenced by obj. Returns False if checking for collision with itself. Returns False if extents of object make it impossible for collision to occur. Returns True if sprite's edgedef parameter overlaps with other sprite's edgedef parameter, taking into consideration both sprites' center, rotation and scale settings.

def collidingWith(self, obj):
    """
    Return a boolean True if this sprite is currently overlapping the sprite 
    referenced by `obj`. Returns False if checking for collision with 
    itself. Returns False if extents of object make it impossible for
    collision to occur. Returns True if sprite's `edgedef` parameter overlaps
    with other sprite's `edgedef` parameter, taking into consideration both
    sprites' center, rotation and scale settings.
    """
    if self is obj:
        return False
    else:
        self._setExtents()
        obj._setExtents()
        # Gross check for overlap will usually rule out a collision
        if (self.xmin > obj.xmax
            or self.xmax < obj.xmin
            or self.ymin > obj.ymax
            or self.ymax < obj.ymin):
            return False
        # Otherwise, perform a careful overlap determination
        elif type(self.asset) is CircleAsset:
            if type(obj.asset) is CircleAsset:
                # two circles .. check distance between
                sx = (self.xmin + self.xmax) / 2
                sy = (self.ymin + self.ymax) / 2
                ox = (obj.xmin + obj.xmax) / 2
                oy = (obj.ymin + obj.ymax) / 2
                d = math.sqrt((sx-ox)**2 + (sy-oy)**2)
                return d <= self.width/2 + obj.width/2
            else:
                return self.collidingCircleWithPoly(self, obj)
        else:
            if type(obj.asset) is CircleAsset:
                return self.collidingCircleWithPoly(obj, self)
            else:
                return self.collidingPolyWithPoly(obj)

def collidingWithSprites(

self, sclass=None)

Return a list of sprite objects identified by the sclass parameter that are currently colliding with (that is, with which the ggame.Sprite.collidingWith method returns True) this sprite. If sclass is set to None (default), then all other sprites are checked for collision, otherwise, only sprites whose class matches sclass are checked.

def collidingWithSprites(self, sclass = None):
    """
    Return a list of sprite objects identified by the `sclass` parameter
    that are currently colliding with (that is, with which the `ggame.Sprite.collidingWith`
    method returns True) this sprite. If `sclass` is set to `None` (default), then
    all other sprites are checked for collision, otherwise, only sprites whose
    class matches `sclass` are checked.
    """
    if sclass is None:
        slist = App.spritelist
    else:
        slist = App.getSpritesbyClass(sclass)
    return list(filter(self.collidingWith, slist))

def courseDegreesText(

self)

Report the heading in degrees (zero to the right) as a text string.

def courseDegreesText(self):
    """
    Report the heading in degrees (zero to the right) as a text string.
    """
    return "Course:       {0:8.1f}°".format(degrees(atan2(self.V[1], self.V[0])))

def destroy(

self)

def destroy(self):
    MathApp._removeVisual(self)
    MathApp._removeMovable(self)
    MathApp._removeStrokable(self)
    _MathDynamic.destroy(self)
    Sprite.destroy(self)

def distanceTo(

self, otherpoint)

def distanceTo(self, otherpoint):
    try:
        pos = self.posinputs.pos
        opos = otherpoint.posinputs.pos
        return MathApp.distance(pos, opos())
    except AttributeError:
        return otherpoint  # presumably a scalar - use this distance

def dynamics(

self, timer)

Perform one iteration of the simulation using runge-kutta 4th order method.

def dynamics(self, timer):
    """
    Perform one iteration of the simulation using runge-kutta 4th order method.
    """
    # set time duration equal to time since last execution
    tick = 10**self.timezoom()*(timer.time - self.lasttime)
    self.shiptime = self.shiptime + tick
    self.lasttime = timer.time
    # 4th order runge-kutta method (https://sites.temple.edu/math5061/files/2016/12/final_project.pdf)
    # and http://spiff.rit.edu/richmond/nbody/OrbitRungeKutta4.pdf  (succinct, but with a typo)
    self.A = k1v = self.ar(self._xy)
    k1r = self.V
    k2v = self.ar(self.vadd(self._xy, self.vmul(tick/2, k1r)))
    k2r = self.vadd(self.V, self.vmul(tick/2, k1v))
    k3v = self.ar(self.vadd(self._xy, self.vmul(tick/2, k2r)))
    k3r = self.vadd(self.V, self.vmul(tick/2, k2v))
    k4v = self.ar(self.vadd(self._xy, self.vmul(tick, k3r)))
    k4r = self.vadd(self.V, self.vmul(tick, k3v))
    self.V = [self.V[i] + tick/6*(k1v[i] + 2*k2v[i] + 2*k3v[i] + k4v[i]) for i in (0,1)]
    self._xy = [self._xy[i] + tick/6*(k1r[i] + 2*k2r[i] + 2*k3r[i] + k4r[i]) for i in (0,1)]
    if self.altitude < 0:
        self.V = [0,0]
        self.A = [0,0]
        self.altitude = 0

def fgrav(

self)

Vector force due to gravity, at current position.

def fgrav(self):
    """
    Vector force due to gravity, at current position.
    """
    G = 6.674E-11
    r = self.r
    uvec = (-self._xy[0]/r, -self._xy[1]/r)
    F = G*self.mass()*self.planet.mass/r**2
    return [x*F for x in uvec]

def firstImage(

self)

Select and display the first image used by this sprite.

def firstImage(self):
    """
    Select and display the *first* image used by this sprite.
    """
    self.GFX.texture = self.asset[0]

def fr(

self, pos)

Compute the net force vector on the rocket, as a function of the position vector.

def fr(self, pos):
    """
    Compute the net force vector on the rocket, as a function of the 
    position vector.
    """
    self.rotation = self.heading()
    t = self.thrust()
    G = 6.674E-11
    r = Planet.distance((0,0), pos)
    uvec = (-pos[0]/r, -pos[1]/r)
    fg = G*self.mass()*self.planet.mass/r**2
    F = [x*fg for x in uvec]
    return [F[0] + t*cos(self.rotation), F[1] + t*sin(self.rotation)]

def getheading(

self)

User override function for dynamically determining the heading.

def getheading(self):
    """
    User override function for dynamically determining the heading.
    """
    return self.localheading

def getmass(

self)

User override function for dynamically determining rocket mass.

def getmass(self):
    """
    User override function for dynamically determining rocket mass.
    """
    return 1

def getthrust(

self)

User override function for dynamically determining thrust force.

def getthrust(self):
    """
    User override function for dynamically determining thrust force.
    """
    return 0

def gettimezoom(

self)

User override function for dynamically determining the timezoom.

def gettimezoom(self):
    """
    User override function for dynamically determining the timezoom.
    """
    return 0

def lastImage(

self)

Select and display the last image used by this sprite.

def lastImage(self):
    """
    Select and display the *last* image used by this sprite.
    """
    self.GFX.texture = self.asset[-1]

def massText(

self)

Report the spacecraft mass in kilograms as a text string.

def massText(self):
    """
    Report the spacecraft mass in kilograms as a text string.
    """
    return "Mass:         {0:8.1f} kg".format(self.mass())

def mousedown(

self)

def mousedown(self):
    self.mouseisdown = True

def mouseup(

self)

def mouseup(self):
    self.mouseisdown = False

def nextImage(

self, wrap=False)

Select and display the next image used by this sprite. If the current image is already the last image, then the image is not advanced.

If the optional wrap parameter is set to True, then calling ggame.Sprite.nextImage on the last image will cause the first image to be loaded.

def nextImage(self, wrap = False):
    """
    Select and display the *next* image used by this sprite.
    If the current image is already the *last* image, then
    the image is not advanced.
    If the optional `wrap` parameter is set to `True`, then calling
    `ggame.Sprite.nextImage` on the last image will cause the *first*
    image to be loaded.
    """
    self._index += 1
    if self._index >= len(self.asset):
        if wrap:
            self._index = 0
        else:
            self._index = len(self.asset)-1
    self.GFX.texture = self.asset[self._index]

def physicalPointTouching(

self, ppos)

def physicalPointTouching(self, ppos):
    self._setExtents()  # ensure xmin, xmax are correct
    x, y = ppos
    return x >= self.xmin and x < self.xmax and y >= self.ymin and y <= self.ymax

def prevImage(

self, wrap=False)

Select and display the previous image used by this sprite. If the current image is already the first image, then the image is not changed.

If the optional wrap parameter is set to True, then calling ggame.Sprite.prevImage on the first image will cause the last image to be loaded.

def prevImage(self, wrap = False):
    """
    Select and display the *previous* image used by this sprite.
    If the current image is already the *first* image, then
    the image is not changed.
    If the optional `wrap` parameter is set to `True`, then calling
    `ggame.Sprite.prevImage` on the first image will cause the *last*
    image to be loaded.
    """
    self._index -= 1
    if self._index < 0:
        if wrap:
            self._index = len(self.asset)-1
        else:
            self._index = 0
    self.GFX.texture = self.asset[self._index]

def processEvent(

self, event)

def processEvent(self, event):
    pass

def radiusText(

self)

Report the radius (distance to planet center) in meters as a text string.

def radiusText(self):
    """
    Report the radius (distance to planet center) in meters as a text string.
    """
    return "Radius:       {0:8.1f} m".format(self.r)

def rectangularCollisionModel(

self)

Obsolete. No op.

def rectangularCollisionModel(self):
    """
    Obsolete. No op.
    """
    pass

def scaleText(

self)

Report the view scale (pixels/meter) as a text string.

def scaleText(self):
    """
    Report the view scale (pixels/meter) as a text string.
    """
    return "View Scale:   {0:8.6f} px/m".format(self.planet._scale)

def select(

self)

def select(self):
    self.selected = True

def setImage(

self, index=0)

Select the image to display by giving its index, where an index of zero represents the first image in the asset.

This is equivalent to setting the ggame.Sprite.index property directly.

def setImage(self, index=0):
    """
    Select the image to display by giving its `index`, where an index
    of zero represents the *first* image in the asset.
    This is equivalent to setting the `ggame.Sprite.index` property
    directly.
    """
    self.index = index

def shipTimeText(

self)

Report the elapsed time as a text string.

def shipTimeText(self):
    """
    Report the elapsed time as a text string.
    """
    return "Elapsed Time: {0:8.1f} s".format(float(self.shiptime))

def step(

self)

def step(self):
    pass  # FIXME
    self._touchAsset()

def stroke(

self, ppos, pdisp)

def stroke(self, ppos, pdisp):
    pass

def thrustText(

self)

Report the thrust level in Newtons as a text string.

def thrustText(self):
    """
    Report the thrust level in Newtons as a text string.
    """
    return "Thrust:       {0:8.1f} N".format(self.thrust())

def timeZoomText(

self)

Report the time acceleration as a text string.

def timeZoomText(self):
    """
    Report the time acceleration as a text string.
    """
    return "Time Zoom:    {0:8.1f}".format(float(self.timezoom()))

def translate(

self, pdisp)

def translate(self, pdisp):
    ldisp = MathApp.translatePhysicalToLogical(pdisp)
    pos = self.posinputs.pos()
    self.posinputs = self.posinputs._replace(pos=self.Eval((pos[0] + ldisp[0], pos[1] + ldisp[1])))
    self._touchAsset()

def trueAnomalyDegreesText(

self)

Report the true anomaly in degrees as a text string.

def trueAnomalyDegreesText(self):
    """
    Report the true anomaly in degrees as a text string.
    """
    return "True Anomaly: {0:8.1f}°".format(self.tanomalyd)

def trueAnomalyRadiansText(

self)

Report the true anomaly in radians as a text string.

def trueAnomalyRadiansText(self):
    """
    Report the true anomaly in radians as a text string.
    """
    return "True Anomaly: {0:8.4f}".format(self.tanomaly)

def turn(

self, event)

Respond to left/right turning key events.

def turn(self, event):
    """
    Respond to left/right turning key events.
    """
    increment = pi/50 * (1 if event.key == "left arrow" else -1)
    self.localheading += increment

def unselect(

self)

def unselect(self):
    self.selected = False

def vadd(

self, v1, v2)

Vector add utility.

def vadd(self, v1, v2):
    """
    Vector add utility.
    """
    return [v1[i]+v2[i] for i in (0,1)]

def velocityText(

self)

Report the velocity in m/s as a text string.

def velocityText(self):
    """
    Report the velocity in m/s as a text string.
    """
    return "Velocity:     {0:8.1f} m/s".format(self.velocity)

def vmag(

self, v)

Vector magnitude function.

def vmag(self, v):
    """
    Vector magnitude function.
    """
    return sqrt(v[0]**2 + v[1]**2)

def vmul(

self, s, v)

Scalar vector multiplication utility.

def vmul(self, s, v):
    """
    Scalar vector multiplication utility.
    """
    return [s*v[i] for i in (0,1)]

Instance variables

var A

var V

var acceleration

var altitude

var bitmapdir

var bitmapframe

var bitmapmargin

var bitmapqty

var bmurl

var center

This attribute represents the horizontal and vertical position of the sprite "center" as a tuple of floating point numbers. See the descriptions for ggame.Sprite.fxcenter and ggame.Sprite.fycenter for more details.

var fxcenter

This represents the horizontal position of the sprite "center", as a floating point number between 0.0 and 1.0. A value of 0.0 means that the x-coordinate of the sprite refers to its left hand edge. A value of 1.0 refers to its right hand edge. Any value in between may be specified. Values may be assigned to this attribute.

var fycenter

This represents the vertical position of the sprite "center", as a floating point number between 0.0 and 1.0. A value of 0.0 means that the x-coordinate of the sprite refers to its top edge. A value of 1.0 refers to its bottom edge. Any value in between may be specified. Values may be assigned to this attribute.

var heading

var height

This is an integer representing the display height of the sprite. Assigning a value to the height will scale the image vertically.

var index

This is an integer index in to the list of images available for this sprite.

var lasttime

var localheading

var mass

var movable

var planet

var position

This represents the (x,y) coordinates of the sprite on the screen. Assigning a value to this attribute will move the sprite to the new coordinates.

var r

var rotation

This attribute may be used to change the rotation of the sprite on the screen. Value may be a floating point number. A value of 0.0 means no rotation. A value of 1.0 means a rotation of 1 radian in a counter-clockwise direction. One radian is 180/pi or approximately 57.3 degrees.

var scale

var selectable

var shiptime

var showstatus

var statuspos

var statusselect

var strokable

var tanomaly

var tanomalyd

var thrust

var tickrate

var timer

var timezoom

var velocity

var visible

This boolean attribute may be used to change the visibility of the sprite. Setting ggame.Sprite.visible to False will prevent the sprite from rendering on the screen.

var width

This is an integer representing the display width of the sprite. Assigning a value to the width will scale the image horizontally.

var x

This represents the x-coordinate of the sprite on the screen. Assigning a value to this attribute will move the sprite horizontally.

var xyposition

var y

This represents the y-coordinate of the sprite on the screen. Assigning a value to this attribute will move the sprite vertically.

Methods

def collidingCircleWithPoly(

cls, circ, poly)

@classmethod
def collidingCircleWithPoly(cls, circ, poly):
    return True