panda3d's ode

NOTE: for extended versions of this script look at he posts below

i put together a small script using the “newly” integrated ode, it will probably be available from 1.5.3.

# old version of the sample, look below for more features

import sys

from pandac.PandaModules import *
from direct.showbase.DirectObject import DirectObject


class odeGeomect( NodePath ):
  def __init__( self, _odeSpace, _odeWorld ):
    NodePath.__init__( self, 'myPhysicalObject3' )
    self.model = loader.loadModel( 'pandaPhysicsTest/data/models/block/block.egg' )
    
    self.odeMass = OdeMass()
    self.odeMass.setBox( 1, Vec3(1,1,1) )
    
    self.odeBody = OdeBody( _odeWorld )
    self.odeBody.setMass( self.odeMass )
    
    # adding a geom to the space here _and_ later (using space.add(geom)) will crash panda
    self.odeGeom = OdeBoxGeom( _odeSpace, Vec3(1,1,1) ) 
    self.odeGeom.setBody( self.odeBody )
    
    self.model.reparentTo( self )
  
  def step( self ):
    self.model.setPos( self.odeBody.getPosition() )
    self.model.setQuat( Quat(self.odeBody.getQuaternion()) )

class mainClass( DirectObject ):
  def __init__( self ):
    self.odeWorld = OdeWorld()
    self.odeWorld.setGravity( 0, 0, -9.81 )
    self.odeWorld.setCfm(0.01)
    self.odeWorld.setErp(0.01)
    self.odeWorld.setContactSurfaceLayer(.0001)
    
    self.odeSpace = OdeSimpleSpace()
    
    self.floor = OdePlaneGeom(self.odeSpace, 0, 0, 1, 0)
    
    self.objects = list()
    for i in xrange(5):
      obj = odeGeomect( self.odeSpace, self.odeWorld )
      obj.reparentTo( render )
      obj.odeBody.setPosition( Vec3(0,5,i*2+2))
      self.objects.append( obj )
      self.odeWorld.addBodyDampening( obj.odeBody, 0 )
    
    # i dont understand how correct this is
    self.odeWorld.initSurfaceTable(self.odeSpace.getNumGeoms())
    for i in xrange(self.odeSpace.getNumGeoms()):
      for j in xrange(self.odeSpace.getNumGeoms()):
        #  pos1, pos2, mu, bounce, bounce_vel, soft_erp, soft_cfm, slip, dampen
        self.odeWorld.setSurfaceEntry( i, j, 0.5, 0.1, 0, 0.01, 0.01, 0, 0 )
    
    self.odeSpace.enable()
    self.odeContactGroup = OdeJointGroup()
    self.odeSpace.setAutoCollideWorld( self.odeWorld )
    self.odeSpace.setAutoCollideJointGroup( self.odeContactGroup )
    
    self.accept( 'a', self.left )
    self.accept( 'd', self.right )
    self.accept( 'w', self.front )
    self.accept( 's', self.back )
    self.accept( 'space', self.up )
    self.accept( 'esc', sys.exit )
  
  def step( self ):
    # is required so the autoCollide will not crash after a few frames
    self.odeContactGroup.empty()
    # can crash if the initSurfaceTable & setSurface is not done properly
    self.odeSpace.setAutoCollideWorld( self.odeWorld )
    contactCount = self.odeSpace.autoCollide()
    print "I: %i contacts" % contactCount
    dt = globalClock.getDt()
    if dt > 0.2:
      dt = 0.001
    self.odeWorld.quickStep( dt )
    #self.odeWorld.step( dt )  # crashes sometimes?
    
    for obj in self.objects:
      obj.step()
    
  def up( self ):
    self.objects[0].odeBody.addForce( Vec3(0,0,1000) )
  def front( self ):
    self.objects[0].odeBody.addForce( Vec3(0,500,0) )
  def back( self ):
    self.objects[0].odeBody.addForce( Vec3(0,-500,0) )
  def left( self ):
    self.objects[0].odeBody.addForce( Vec3(-500,0,0) )
  def right( self ):
    self.objects[0].odeBody.addForce( Vec3(500,0,0) )

if __name__ == '__main__':
  import direct.directbase.DirectStart
  main = mainClass()
  while True:
    main.step()
    taskMgr.step()

I would very much like to test this out but the .egg files are needed.

You can use the ‘models/misc/rgbCube.bam’ or any other cube model with size (1,1,1), (the object pivot is afaik in the center).

Just make sure you really have ode compiled into panda3d, which is afaik not the case for the installers of 1.5.2.

Nicely done! I am indeed running 1.5.3 from pro-soft’s linux build. Maybe the PyODE tutorials should be ported to Panda? I’ll take a crack at it soon.

i have played around with the ode system a bit more and have created a updated version including a joint and a trimesh example.

while working on the trimesh example i have found a problem of panda’s ode system when using the darwinports ode. The trimeshdate is converted wrong and results in a 1d object (only the y coorinate is != 0).

to fix this error edit panda/ode/src/odeTriMeshData.h and make it looking like this:

...
protected:
  struct StridedVertex{
    float Vertex[3];
  };  
  struct StridedTri{
...

the new example:
it not well documented but at least it shows how to use the ode system.

# -----
# example for the integrated ode of panda3d
# -----
# by reto spoerri (Hypnos)
# rspoerri@@@nouser...org
# -----
# panda3d can be crashed using ode, this should hopefully be fixed some time
# -----
# possible sources of problems:
# - self.odeWorld.initSurfaceTable( x )
#   i dont know what x should be, but if you dont call this function and use
#   odeSpace.autoCollide() it will crash
# - self.odeContactGroup.empty()
#   panda will add up collisions every frame, it will slow down until nothing
#   works anymore
# - self.odeWorld.step( dt ) crashed for me, quickStep never did
#   (might not be true accurate)
# -----
# usage:
# w/a/s/d/space:   move to bottom block
# -----

import sys, random

from pandac.PandaModules import *
from direct.showbase.DirectObject import DirectObject

Infinity = 1e10000
NaN = Infinity / Infinity

class odeObj( NodePath ):
  # a ode controlled object
  def __init__( self ):
    NodePath.__init__( self, 'odeObj' )
  
  def step( self ):
    pos = self.odeBody.getPosition()
    quat = Quat(self.odeBody.getQuaternion())
    if pos.length() == Infinity or pos.length() == NaN:
      # object got out of control, might happen if something goes wrong with
      # the collision (or a bug)
      self.stop()
      # define a reset pos and rotation for the object
      pos = Vec3(0,0,0)
      quat = Quat(0,0,0,0)
    self.setPos( render, pos )
    self.setQuat( render, quat )
  
  def stop( self ):
    # stop a objects motion and rotation
    # position
    self.odeBody.setLinearVel( Vec3(0,0,0) )
    self.odeBody.setForce( Vec3(0,0,0) )
    # rotation
    self.odeBody.setAngularVel( Vec3(0,0,0) )
    self.odeBody.setTorque( Vec3(0,0,0) )

class odeGeom( odeObj ):
  # a simple ode box object
  def __init__( self, _odeSpace, _odeWorld ):
    odeObj.__init__( self )
    self.model = loader.loadModel( 'models/misc/rgbCube.bam' )
    
    self.odeMass = OdeMass()
    self.odeMass.setBox( 1, Vec3(1,1,1) )
    
    self.odeBody = OdeBody( _odeWorld )
    self.odeBody.setMass( self.odeMass )
    
    # adding a geom to the space here _and_ later (using space.add(geom)) will crash panda
    self.odeGeom = OdeBoxGeom( _odeSpace, Vec3(1,1,1) ) 
    self.odeGeom.setBody( self.odeBody )
    
    self.model.reparentTo( self )
  

class odeJointBox( odeObj ):
  # a ode box conntected to attachTo with a joint
  def __init__( self, _odeSpace, _odeWorld, attachTo ):
    odeObj.__init__( self )
    self.model = loader.loadModel( 'models/misc/rgbCube.bam' )
    
    self.odeMass = OdeMass()
    self.odeMass.setBox( 1, Vec3(1,1,1) )
    
    self.odeBody = OdeBody( _odeWorld )
    self.odeBody.setMass( self.odeMass )
    
    # adding a geom to the space here _and_ later (using space.add(geom)) will crash panda
    self.odeGeom = OdeBoxGeom( _odeSpace, Vec3(1,1,1) ) 
    self.odeGeom.setBody( self.odeBody )
    
    self.model.reparentTo( self )
    
    # Connect body1 with the static environment
    self.joint = OdeBallJoint( _odeWorld )
    self.joint.attach( self.odeBody, attachTo )
    self.joint.setAnchor( Vec3(2,2,0) )

class odeTrimesh( odeObj ):
  def __init__( self, _odeSpace, _odeWorld ):
    odeObj.__init__( self )
    self.model = loader.loadModel( 'models/panda-model.egg' )
    self.model.setScale( 0.01 )
    
    self.odeMass = OdeMass()
    self.odeMass.setBox( 1, Vec3(1,1,1) )
    
    self.odeBody = OdeBody( _odeWorld )
    self.odeBody.setMass( self.odeMass )
    
    # if a model is scaled you need to call flattenstrong on the data
    # else the collision object will stay the same size as before
    # (at least that's what i suspect)
    self.model.flattenStrong()
    # get the models polygons
    trimeshData = OdeTriMeshData( self.model, True )
    # crete a trimesh object from the trimeshData
    self.odeGeom = OdeTriMeshGeom( _odeSpace, trimeshData )
    
    if False: # checking the data
      print "self.odeGeom.getNumTriangles()", self.odeGeom.getNumTriangles()
      for i in xrange(self.odeGeom.getNumTriangles()):
        x = Point3(0)
        y = Point3(0)
        z = Point3(0)
        self.odeGeom.getTriangle( i, x, y, z )
        print x,y,z
    self.odeGeom.setBody( self.odeBody )
    
    self.model.reparentTo( self )

class mainClass( DirectObject ):
  def __init__( self ):
    self.odeWorld = OdeWorld()
    self.odeWorld.setGravity( 0, 0, -9.81 )
    self.odeWorld.setCfm(0.01)
    self.odeWorld.setErp(0.01)
    self.odeWorld.setContactSurfaceLayer(.0001)
    
    self.odeSpace = OdeSimpleSpace()
    self.odeSpace.enable()
    
    self.floor = OdePlaneGeom(self.odeSpace, 0, 0, 1, 0)
    # visual object for cardmaker
    cm = CardMaker('card')
    cm.setFrame( -1000, 1000, -1000, 1000 )
    card = render.attachNewNode(cm.generate())
    card.setHpr(0,-90,0)
    
    self.objects = list()
    for i in xrange(5):
      obj = odeGeom( self.odeSpace, self.odeWorld )
      obj.reparentTo( render )
      obj.odeBody.setPosition( Vec3(0,5,i*2+2))
      self.objects.append( obj )
      self.odeWorld.addBodyDampening( obj.odeBody, 0 )
    
    # 2 objects connected by a joint
    obj = odeGeom( self.odeSpace, self.odeWorld )
    obj.reparentTo( render )
    obj.odeBody.setPosition( Vec3(2,5,20))
    self.objects.append( obj )
    obj = odeJointBox( self.odeSpace, self.odeWorld, obj.odeBody )
    obj.reparentTo( render )
    obj.odeBody.setPosition( Vec3(4,5,20))
    self.objects.append( obj )
    
    # a trimesh object
    obj = odeTrimesh( self.odeSpace, self.odeWorld )
    obj.reparentTo( render )
    obj.odeBody.setPosition( Vec3(20,20,10))
    self.objects.append( obj )
    
    # im not sure how to use this settings actually
    # if initSurfaceTable is not defined panda will segfault
    # but already a initSurfaceTable(0) will work
    self.odeWorld.initSurfaceTable(self.odeSpace.getNumGeoms())
    for i in xrange(self.odeSpace.getNumGeoms()):
      for j in xrange(self.odeSpace.getNumGeoms()):
        #  pos1, pos2, mu, bounce, bounce_vel, soft_erp, soft_cfm, slip, dampen
        self.odeWorld.setSurfaceEntry( i, j, 0.1, 0.1, 0, 0.01, 0.01, 0, 0 )
    
    # create a joint ground for the collision joints
    self.odeSpace.setAutoCollideWorld( self.odeWorld )
    self.odeContactGroup = OdeJointGroup()
    self.odeSpace.setAutoCollideJointGroup( self.odeContactGroup )
    
    self.accept( 'a', self.left )
    self.accept( 'd', self.right )
    self.accept( 'w', self.front )
    self.accept( 's', self.back )
    self.accept( 'e', self.up )
    self.accept( 'u', self.push )
    self.accept( 'j', self.pull )
    self.accept( 'space', self.random )
    self.accept( 'escape', sys.exit )
  
  def step( self ):
    # is required, else the collisions will increase until you have 0fps
    self.odeContactGroup.empty()
    
    # panda's internal method to handle collisions
    contactCount = self.odeSpace.autoCollide()
    
    dt = globalClock.getDt()
    # the first frame of panda3d usually has a pretty large lag, causing objects
    # to fall trough the ground
    if dt > 0.2:
      dt = 0.001
    self.odeWorld.quickStep( dt )
    
    for obj in self.objects:
      obj.step()
  
  def random( self ):
    for obj in self.objects:
      r = random.randint(0,1000)-500
      obj.odeBody.addForce( Vec3(r,r,r) )
  def pull( self ):
    for obj in self.objects:
      force = -obj.getPos( render ) * 50 + Vec3(0,0,50)
      obj.odeBody.addForce( force )
  def push( self ):
    for obj in self.objects:
      obj.odeBody.addForce( obj.getPos( render ) * 50 )
  
  def up( self ):
    obj = self.objects[-1]
    obj.odeBody.addForce( Vec3(0,0,1000) )
  def front( self ):
    obj = self.objects[-1]
    obj.odeBody.addForce( Vec3(0,500,0) )
  def back( self ):
    obj = self.objects[-1]
    obj.odeBody.addForce( Vec3(0,-500,0) )
  def left( self ):
    obj = self.objects[-1]
    obj.odeBody.addForce( Vec3(-500,0,0) )
  def right( self ):
    obj = self.objects[-1]
    obj.odeBody.addForce( Vec3(500,0,0) )

if __name__ == '__main__':
  import direct.directbase.DirectStart
  main = mainClass()
  while True:
    main.step()
    taskMgr.step()

based on a question in irc from chozabu (thanks for the hint) i updated the sample to include immovable’s (like a terrain using a trimesh):

# -----
# example for the integrated ode of panda3d
# -----
# by reto spoerri (Hypnos)
# rspoerri@@@nouser...org
# -----
# panda3d can be crashed using ode, this should hopefully be fixed some time
# -----
# license: you must! modify change and do with this script whatever you want.
# no requirement, but it would be nice if you have good idea's to put samples of it on the forums.
# -----
# possible sources of problems:
# - self.odeWorld.initSurfaceTable( x )
#   i dont know what x should be, but if you dont call this function and use
#   odeSpace.autoCollide() it will crash
# - self.odeContactGroup.empty()
#   panda will add up collisions every frame, it will slow down until nothing
#   works anymore
# - self.odeWorld.step( dt ) crashed for me, quickStep never did
#   (might not be true accurate)
# -----
# usage:
# w/a/s/d/space:   move to bottom block
# -----

import sys, random

from pandac.PandaModules import *
from direct.showbase.DirectObject import DirectObject

Infinity = 1e10000
NaN = Infinity / Infinity

class odeObj( NodePath ):
  # a ode controlled object
  def __init__( self ):
    NodePath.__init__( self, 'odeObj' )
  
  def step( self ):
    pos = self.odeBody.getPosition()
    quat = Quat(self.odeBody.getQuaternion())
    if pos.length() == Infinity or pos.length() == NaN:
      # object got out of control, might happen if something goes wrong with
      # the collision (or a bug)
      self.stop()
      # define a reset pos and rotation for the object
      pos = Vec3(0,0,0)
      quat = Quat(0,0,0,0)
    self.setPos( render, pos )
    self.setQuat( render, quat )
  
  def stop( self ):
    # stop a objects motion and rotation
    # position
    self.odeBody.setLinearVel( Vec3(0,0,0) )
    self.odeBody.setForce( Vec3(0,0,0) )
    # rotation
    self.odeBody.setAngularVel( Vec3(0,0,0) )
    self.odeBody.setTorque( Vec3(0,0,0) )
  
  def setPos( self, *args ):
    NodePath.setPos( self, *args )
    self.updateOde()
  
  def setHpr( self, *args ):
    NodePath.setHpr( self, *args )
    self.updateOde()
  
  def updateOde( self ):
    # moves the ode object, accordingly to the panda object
    # usually the ode object moves the panda object
    # this is not useful if you want to move a object which should collide
    # during the moving time, you will need to apply forces to do that
    self.odeBody.setPosition( self.getPos( render ) )
    self.odeBody.setQuaternion( self.getQuat( render ) )

class odeGeom( odeObj ):
  # a simple ode box object
  def __init__( self, _odeSpace, _odeWorld ):
    odeObj.__init__( self )
    self.model = loader.loadModel( 'models/misc/rgbCube.bam' )
    
    self.odeMass = OdeMass()
    self.odeMass.setBox( 1, Vec3(1,1,1) )
    
    self.odeBody = OdeBody( _odeWorld )
    self.odeBody.setMass( self.odeMass )
    
    # adding a geom to the space here _and_ later (using space.add(geom)) will crash panda
    self.odeGeom = OdeBoxGeom( _odeSpace, Vec3(1,1,1) ) 
    self.odeGeom.setBody( self.odeBody )
    
    self.model.reparentTo( self )

class odeJointBox( odeObj ):
  # a ode box conntected to attachTo with a joint
  def __init__( self, _odeSpace, _odeWorld, attachTo ):
    odeObj.__init__( self )
    self.model = loader.loadModel( 'models/misc/rgbCube.bam' )
    
    self.odeMass = OdeMass()
    self.odeMass.setBox( 1, Vec3(1,1,1) )
    
    self.odeBody = OdeBody( _odeWorld )
    self.odeBody.setMass( self.odeMass )
    
    # adding a geom to the space here _and_ later (using space.add(geom)) will crash panda
    self.odeGeom = OdeBoxGeom( _odeSpace, Vec3(1,1,1) ) 
    self.odeGeom.setBody( self.odeBody )
    
    self.model.reparentTo( self )
    
    # Connect body1 with the static environment
    self.joint = OdeBallJoint( _odeWorld )
    self.joint.attach( self.odeBody, attachTo )
    self.joint.setAnchor( Vec3(2,2,0) )

class odeTrimesh( odeObj ):
  def __init__( self, _odeSpace, _odeWorld ):
    odeObj.__init__( self )
    self.model = loader.loadModel( 'models/panda-model.egg' )
    self.model.setScale( 0.01 )
    
    self.odeMass = OdeMass()
    self.odeMass.setBox( 1, Vec3(1,1,1) )
    
    self.odeBody = OdeBody( _odeWorld )
    self.odeBody.setMass( self.odeMass )
    
    # if a model is scaled you need to call flattenstrong on the data
    # else the collision object will stay the same size as before
    # (at least that's what i suspect)
    self.model.flattenStrong()
    # get the models polygons
    trimeshData = OdeTriMeshData( self.model, True )
    # crete a trimesh object from the trimeshData
    self.odeGeom = OdeTriMeshGeom( _odeSpace, trimeshData )
    
    if False: # checking the data
      print "self.odeGeom.getNumTriangles()", self.odeGeom.getNumTriangles()
      for i in xrange(self.odeGeom.getNumTriangles()):
        x = Point3(0)
        y = Point3(0)
        z = Point3(0)
        self.odeGeom.getTriangle( i, x, y, z )
        print x,y,z
    self.odeGeom.setBody( self.odeBody )
    
    self.model.reparentTo( self )

class odeTerrain( odeObj ):
  # a object that does not move (like a terrain)
  def __init__( self, _odeSpace, _odeWorld ):
    odeObj.__init__( self )
    self.model = loader.loadModel( 'models/panda-model.egg' )
    self.model.setScale( 0.1 )
    #self.model.setPos( Vec3(0,0,-20) )
    
    self.model.flattenStrong()
    
    # get the models polygons
    trimeshData = OdeTriMeshData( self.model, True )
    # crete a trimesh object from the trimeshData
    self.odeGeom = OdeTriMeshGeom( _odeSpace, trimeshData )
    
    self.model.reparentTo( self )
  
  def updateOde( self ):
    # moves the ode object, accordingly to the panda object
    # usually the ode object moves the panda object
    # this is not useful if you want to move a object which should collide
    # during the moving time, you will need to apply forces to do that
    self.odeGeom.setPosition( self.getPos( render ) )
    self.odeGeom.setQuaternion( self.getQuat( render ) )

class mainClass( DirectObject ):
  def __init__( self ):
    self.odeWorld = OdeWorld()
    self.odeWorld.setGravity( 0, 0, -9.81 )
    self.odeWorld.setCfm(0.01)
    self.odeWorld.setErp(0.01)
    self.odeWorld.setContactSurfaceLayer(.0001)
    
    self.odeSpace = OdeSimpleSpace()
    self.odeSpace.enable()
    
    self.floor = OdePlaneGeom(self.odeSpace, 0, 0, 1, 0)
    # visual object for cardmaker
    cm = CardMaker('card')
    cm.setFrame( -1000, 1000, -1000, 1000 )
    card = render.attachNewNode(cm.generate())
    card.setHpr(0,-90,0)
    
    self.objects = list()
    for i in xrange(5):
      obj = odeGeom( self.odeSpace, self.odeWorld )
      obj.reparentTo( render )
      obj.setPos( Vec3(0,5,i*2+2) )
      self.objects.append( obj )
      self.odeWorld.addBodyDampening( obj.odeBody, 0 )
    
    # 2 objects connected by a joint
    obj = odeGeom( self.odeSpace, self.odeWorld )
    obj.reparentTo( render )
    obj.setPos( Vec3(2,5,20))
    self.objects.append( obj )
    obj = odeJointBox( self.odeSpace, self.odeWorld, obj.odeBody )
    obj.reparentTo( render )
    obj.setPos( Vec3(4,5,20))
    self.objects.append( obj )
    
    # a trimesh object
    obj = odeTrimesh( self.odeSpace, self.odeWorld )
    obj.reparentTo( render )
    obj.setPos( Vec3(20,20,10))
    self.objects.append( obj )
    
    # im not sure how to use this settings actually
    # if initSurfaceTable is not defined panda will segfault
    # but already a initSurfaceTable(0) will work
    self.odeWorld.initSurfaceTable(self.odeSpace.getNumGeoms())
    for i in xrange(self.odeSpace.getNumGeoms()):
      for j in xrange(self.odeSpace.getNumGeoms()):
        #  pos1, pos2, mu, bounce, bounce_vel, soft_erp, soft_cfm, slip, dampen
        self.odeWorld.setSurfaceEntry( i, j, 0.1, 0.1, 0, 0.01, 0.01, 0, 0 )
    
    obj = odeTerrain( self.odeSpace, self.odeWorld )
    obj.setPos( Vec3(20,20,0) )
    obj.reparentTo( render )
    
    # create a joint ground for the collision joints
    self.odeSpace.setAutoCollideWorld( self.odeWorld )
    self.odeContactGroup = OdeJointGroup()
    self.odeSpace.setAutoCollideJointGroup( self.odeContactGroup )
    
    self.accept( 'a', self.left )
    self.accept( 'd', self.right )
    self.accept( 'w', self.front )
    self.accept( 's', self.back )
    self.accept( 'e', self.up )
    self.accept( 'u', self.push )
    self.accept( 'j', self.pull )
    self.accept( 'space', self.random )
    self.accept( 'escape', sys.exit )
  
  def step( self ):
    # is required, else the collisions will increase until you have 0fps
    self.odeContactGroup.empty()
    
    # panda's internal method to handle collisions
    contactCount = self.odeSpace.autoCollide()
    
    dt = globalClock.getDt()
    # the first frame of panda3d usually has a pretty large lag, causing objects
    # to fall trough the ground
    if dt > 0.2:
      dt = 0.001
    self.odeWorld.quickStep( dt )
    
    for obj in self.objects:
      obj.step()
  
  def random( self ):
    for obj in self.objects:
      r = random.randint(0,1000)-500
      obj.odeBody.addForce( Vec3(r,r,r) )
  def pull( self ):
    for obj in self.objects:
      force = -obj.getPos( render ) * 50 + Vec3(0,0,50)
      obj.odeBody.addForce( force )
  def push( self ):
    for obj in self.objects:
      obj.odeBody.addForce( obj.getPos( render ) * 50 )
  
  def up( self ):
    obj = self.objects[-1]
    obj.odeBody.addForce( Vec3(0,0,1000) )
  def front( self ):
    obj = self.objects[-1]
    obj.odeBody.addForce( Vec3(0,500,0) )
  def back( self ):
    obj = self.objects[-1]
    obj.odeBody.addForce( Vec3(0,-500,0) )
  def left( self ):
    obj = self.objects[-1]
    obj.odeBody.addForce( Vec3(-500,0,0) )
  def right( self ):
    obj = self.objects[-1]
    obj.odeBody.addForce( Vec3(500,0,0) )

if __name__ == '__main__':
  import direct.directbase.DirectStart
  main = mainClass()
  while True:
    main.step()
    taskMgr.step()