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