----


Nice demo David. I like the idea.

I remember how fascinated I have been when playing the only commercial space game I know that had realistic space dynamics a decade ago, I-War. It has been hell to fly around in expert mode, controlling thruster in all three axes directly, and they even had Lagrange points at the right positions!. But I also have to admit that playing with unrealistic space dynamics has been more easy and more fun, like e.g. TIE Fighter (over a decade ago, too).

Two small hints:

  • Earth and moon rotation is not frame-rate independent. One my box they spin around once per second ( v-sync disabled, ~400fps ).

  • Computation of gravitational forces is ok, but applying them should be dependent on the timestep too, to mimik zero-order integration. Perhaps like this

earthPullForce = earthPullForce * earthForceMagnitude
delta = dt * earthPullForce
self.spacecraft.model.setPos( self.spacecraft.model.getPos( ) - delta )

For more realistic result you will have to do higher order integration, which can be really nasty if the scene gets more complex or contains constraints. Even C/C++ real-time Physics engines have to find a trade-off between speed and realism here. Sometimes they allow to select which integrator should be used, for tuning performance.

One minor thing: You could replace this

( earthPullForce.length( ) * earthPullForce.length( ) )

with this

earthPullForce.lengthSquared( )

Something I don’t know and would like to ask: When computing the elapsed time there seems to be two ways: either storing task.time is a local variable (self.previous, self.last). This is what most Panda3D demos do. And then there is globalClock.getDt( ). This methods gives a much smaller time, and I don’t know what time this is exactly (measured from when to when). Somehow I didn’t find documentation on this method.

enn0x

Hmm… I’m afraid some of what I have written is rubbish. Sorry.

While gravity is dependent on both body masses (mEarth, mSpaceship), the acceleration the spaceship receives is also dependent on the spaceships mass:

F = -G * mEarth * mSpaceship / R^2
F = mSpaceship * acceleration

So actually when computing acceleration of the spaceship you have to do:

acceleration = G * mEarth / R2

Integration (linear) would give:

deltaPos = -G * mEarth / R2 * dt^2 * Runit

where Runit is the normalized distance vector R. Without normalizing gravity force would be dependent on 1/R and not 1/R^2.

But now there is another problem. The spaceship maintains a constant velocity which isn’t changed by gravity. Integration each for their own doesn’t work.

I would opt for collecting either forces or acceleration in one place, and then do the integration (sum them up for linear approach).

This is how I changed the code. First the modified computation of gravitational pull.

    def gravitationalPull( self, task ):
        self.G = 6.67
        self.earthMass = 81.0
        self.moonMass  = 10.0

        # earth
        delta = self.spacecraft.model.getPos( ) - self.earth.getPos( )
        R2 = delta.lengthSquared( )
        delta.normalize( )
        a = delta * -self.G * self.earthMass / R2
        self.spacecraft.accelerations['gravityEarth'] = a

        # moon
        delta = self.spacecraft.model.getPos( ) - self.moon.getPos( )
        R2 = delta.lengthSquared( )
        delta.normalize( )
        a = delta * -self.G * self.moonMass / R2
        self.spacecraft.accelerations['gravityMoon'] = a

        return task.cont

And then the modified spaceship. There is a dictionary for registered forces, with slots for the four thrusters and the two gravitational pulls. Once per frame the accelerations are summed up and then the spaceships velocity and position is updated.

Gravitational sling shots are possible now, within numeric precision of linear approach. Problems arise very close to the gravitational centers where numbers become large.

I hope you didn’t mind me tweaking around with your tutorial. If so please tell me and I will remove my posts. If you want I can send you a PM with the full code modifications I did.
enn0x

class spacecraft:
    def __init__( self ):
        self.loadModel( )
        self.setPosHPR( )
        self.setVars( )
        self.last = 0.0
        taskMgr.add( self.update, 'updateSpaceship' )
    def setVars( self, mass = .01 ):
        self.mass = mass
        self.velocity = Vec3( 0, 0, 0 )
        self.thrust = 5.0
        self.accelerations = { }
        self.accelerations['forward'] = Vec3( 0, 0, 0 )
        self.accelerations['reverse'] = Vec3( 0, 0, 0 )
        self.accelerations['left'] = Vec3( 0, 0, 0 )
        self.accelerations['right'] = Vec3( 0, 0, 0 )
        self.accelerations['gravityEarth'] = Vec3( 0, 0, 0 )
        self.accelerations['gravityMoon'] = Vec3( 0, 0, 0 )
    def loadModel( self, modelSrc = 'assets/models/spacecraft.egg' ):
        self.model = loader.loadModel( modelSrc )
        self.model.reparentTo( render )
        self.model.setScale( .7 )
    def setPosHPR( self, pos = Vec3( 0, -20, 0 ), HPR = Vec3( 0, 0, 0 )  ):
        self.model.setPosHpr( pos, HPR )
    def createForwardTask( self ):
        self.accelerations['forward'] = Vec3( 0, self.thrust, 0 )
    def destroyForwardTask( self ):
        self.accelerations['forward'] = Vec3( 0, 0, 0 )
    def createReverseTask( self ):
        self.accelerations['reverse'] = Vec3( 0, -self.thrust, 0 )
    def destroyReverseTask( self ):
        self.accelerations['reverse'] = Vec3( 0, 0, 0 )
    def createLeftTask( self ):
        self.accelerations['left'] = Vec3( self.thrust, 0, 0 )
    def destroyLeftTask( self ):
        self.accelerations['left'] = Vec3( 0, 0, 0 )
    def createRightTask( self ):
        self.accelerations['right'] = Vec3( -self.thrust, 0, 0 )
    def destroyRightTask( self ):
        self.accelerations['right'] = Vec3( 0, 0, 0 )
    def update( self, task ):
        dt = task.time - self.last
        # total acceleration
        acceleration = reduce( lambda x,y:x+y, self.accelerations.values( ) )
        # change velocity
        self.velocity += acceleration * dt
        # change position
        self.model.setPos( self.model, self.velocity * dt )
        self.last = task.time
        return task.cont
    def reset( self ):
        self.setPosHPR( )
        self.setVars( )

Hi,

Really nice example. I have over 222 fps here??? When I create a blank panda3d program its capped at 75 fps usually…
The satellite does really weird here. Is that a bug or so, or did you program it to run really weird? Or am I missing some libs? (got linux here)

he uses independent framerate counter based on the delta between 2 frames.

Doesn’t it your spacecraft ?


This example runs just fine on 1.7.0 but one would need to remove the 2

references (line 509 and 514) to make it work.

It didn’t make sense to repack it and redistribute for those 2 minor changes alone, but if someone wants the modified file let me know.

I got a error :’(

ERROR: **** End of process output ****
DirectStart: Starting the game.
Known pipe types:
wglGraphicsPipe
(all display modules loaded.)
Traceback (most recent call last):
File “C:\Program Files\Panda3D-1.7.1\direct\showbase\EventManager.py”, line 61, in eventLoopTask
self.doEvents()
File “C:\Program Files\Panda3D-1.7.1\direct\showbase\EventManager.py”, line 55, in doEvents
processFunc(self.eventQueue.dequeueEvent())
File “C:\Program Files\Panda3D-1.7.1\direct\showbase\EventManager.py”, line 122, in processEvent
messenger.send(eventName, paramList)
File “C:\Program Files\Panda3D-1.7.1\direct\showbase\Messenger.py”, line 388, in send
self.__dispatch(acceptorDict, event, sentArgs, foundWatch)
File “C:\Program Files\Panda3D-1.7.1\direct\showbase\Messenger.py”, line 473, in __dispatch
method (*(extraArgs + sentArgs))
File “spaceFlight.py”, line 762, in mouseIntersect
applicationFSM.request( ‘GameLoop’ ) # go into the game loop
File “C:\Program Files\Panda3D-1.7.1\direct\fsm\FSM.py”, line 322, in request
self.__setState(*result)
File “C:\Program Files\Panda3D-1.7.1\direct\fsm\FSM.py”, line 457, in __setState
self.__callEnterFunc(self.newState, *args)
File “C:\Program Files\Panda3D-1.7.1\direct\fsm\FSM.py”, line 493, in __callEnterFunc
func(args)
File “spaceFlight.py”, line 890, in enterGameLoop
self.application = application( )
File “spaceFlight.py”, line 789, in init
self.createWorld( )
File “spaceFlight.py”, line 796, in createWorld
self.gameWorld = world( )
File “spaceFlight.py”, line 500, in init
self.createLight( ) # create the light for the game world
File “spaceFlight.py”, line 514, in createLight
self.dlnp = render.attachNewNode( dlight.upcastToPandaNode( ) ) # attach
AttributeError: ‘libpanda.DirectionalLight’ object has no attribute ‘upcastToPandaNode’
:task(error): Exception occurred in PythonTask eventManager
Traceback (most recent call last):
File “spaceFlight.py”, line 905, in
run( ) # if this module is not being imported run the game
File “C:\Program Files\Panda3D-1.7.1\direct\showbase\ShowBase.py”, line 2630, in run
self.taskMgr.run()
File “C:\Program Files\Panda3D-1.7.1\direct\task\Task.py”, line 502, in run
self.step()
File “C:\Program Files\Panda3D-1.7.1\direct\task\Task.py”, line 460, in step
self.mgr.poll()
File “C:\Program Files\Panda3D-1.7.1\direct\showbase\EventManager.py”, line 61, in eventLoopTask
self.doEvents()
File “C:\Program Files\Panda3D-1.7.1\direct\showbase\EventManager.py”, line 55, in doEvents
processFunc(self.eventQueue.dequeueEvent())
File “C:\Program Files\Panda3D-1.7.1\direct\showbase\EventManager.py”, line 122, in processEvent
messenger.send(eventName, paramList)
File “C:\Program Files\Panda3D-1.7.1\direct\showbase\Messenger.py”, line 388, in send
self.__dispatch(acceptorDict, event, sentArgs, foundWatch)
File “C:\Program Files\Panda3D-1.7.1\direct\showbase\Messenger.py”, line 473, in __dispatch
method (
(extraArgs + sentArgs))
File “spaceFlight.py”, line 762, in mouseIntersect
applicationFSM.request( ‘GameLoop’ ) # go into the game loop
File “C:\Program Files\Panda3D-1.7.1\direct\fsm\FSM.py”, line 322, in request
self.__setState(*result)
File “C:\Program Files\Panda3D-1.7.1\direct\fsm\FSM.py”, line 457, in __setState
self.__callEnterFunc(self.newState, *args)
File “C:\Program Files\Panda3D-1.7.1\direct\fsm\FSM.py”, line 493, in __callEnterFunc
func(*args)
File “spaceFlight.py”, line 890, in enterGameLoop
self.application = application( )
File “spaceFlight.py”, line 789, in init
self.createWorld( )
File “spaceFlight.py”, line 796, in createWorld
self.gameWorld = world( )
File “spaceFlight.py”, line 500, in init
self.createLight( ) # create the light for the game world
File “spaceFlight.py”, line 514, in createLight
self.dlnp = render.attachNewNode( dlight.upcastToPandaNode( ) ) # attach
AttributeError: ‘libpanda.DirectionalLight’ object has no attribute ‘upcastToPandaNode’
:TaskManager: TaskManager.destroy()

As the post right above yours indicates, you have to edit the program and remove the two references to upcastToPandaNode().

David

it seems i don’t have the modified file anymore and my python+panda3d installation no longer works, so i cannot test the modification but the changes needed are very simple.

just go to line 509 in the file and instead of
alight.upcastToPandaNode( )
you leave there just:
alight

same for line 514, where you will only have
dlight
instead of
dlight.upcastToPandaNode( )

so basically, line 509 and line 514 will look like:

509: self.alnp = render.attachNewNode( alight ) # attach node 
514:         self.dlnp = render.attachNewNode( dlight ) # attach 

Without the line numbers obviously.

Let me know if it worked.

works, why should it not? :smiley:

Greetz

Blaz