Physics: Motion Surface or conveyor simulation

I have added the feature to set a user-defined callback when a contact is added (Bullet’s gContactAddedCallback). Contact information can be modified from within the callback. In oder to use the callback you have to do the following:

1.) Call node.notifyCollisions(True) for every RigidBodyNode which should create callbacks.
2.) Create a callback object: cbo = PythonCallbackObject(func)
3.) Set the callback object: world.setContactAddedCallback(cbo)

A few things to notice:

  • Callbacks ruin performance if overdone!
  • There is only one global Bullet callback function. So you can not use different callbacks if you have multiple worlds.
  • Callbacks can be created in addition to contact added events (both, only events, only callbacks, neither)

Here is a short sample for the conveyor belt:

import sys
import direct.directbase.DirectStart

from direct.showbase.DirectObject import DirectObject

from panda3d.core import Vec3
from panda3d.core import Point3
from panda3d.core import TransformState
from panda3d.core import BitMask32
from panda3d.core import PythonCallbackObject

from panda3d.bullet import BulletWorld
from panda3d.bullet import BulletPlaneShape
from panda3d.bullet import BulletBoxShape
from panda3d.bullet import BulletRigidBodyNode
from panda3d.bullet import BulletDebugNode

class Game(DirectObject):

  def __init__(self):
    base.setBackgroundColor(0.1, 0.1, 0.8, 1)
    base.setFrameRateMeter(True)

    base.cam.setPos(0, -20, 4)
    base.cam.lookAt(0, 0, 0)

    # Input
    self.accept('escape', self.doExit)
    self.accept('r', self.doReset)

    # Task
    taskMgr.add(self.update, 'updateWorld')

    # Physics
    self.setup()

  # _____HANDLER_____

  def doExit(self):
    self.cleanup()
    sys.exit(1)

  def doReset(self):
    self.cleanup()
    self.setup()

  # ____TASK___

  def update(self, task):
    dt = globalClock.getDt()
    self.world.doPhysics(dt)
    return task.cont

  def cleanup(self):
    self.world.clearContactAddedCallback()
    self.world = None
    self.worldNP.removeNode()

  def setup(self):
    self.worldNP = render.attachNewNode('World')

    # World
    self.debugNP = self.worldNP.attachNewNode(BulletDebugNode('Debug'))
    self.debugNP.show()
    self.debugNP.node().showWireframe(True)
    self.debugNP.node().showConstraints(True)
    self.debugNP.node().showBoundingBoxes(False)
    self.debugNP.node().showNormals(True)

    self.world = BulletWorld()
    self.world.setGravity(Vec3(0, 0, -9.81))
    self.world.setDebugNode(self.debugNP.node())

    # Conveyor (static)
    shape = BulletBoxShape(Vec3(4, 4, 0.4))

    np = self.worldNP.attachNewNode(BulletRigidBodyNode('Conveyor'))
    np.node().addShape(shape)
    np.node().notifyCollisions(True)
    np.setPos(0, 0, -1)
    np.setCollideMask(BitMask32.allOn())

    self.world.attachRigidBody(np.node())

    # Crate (dynamic)
    shape = BulletBoxShape(Vec3(0.5, 0.5, 0.5))

    np = self.worldNP.attachNewNode(BulletRigidBodyNode('Box'))
    np.node().setMass(1.0)
    np.node().addShape(shape)
    np.setPos(2, 0, 4)
    np.setCollideMask(BitMask32.allOn())

    self.world.attachRigidBody(np.node())

    # Callback
    self.world.setContactAddedCallback(PythonCallbackObject(self.added))

  def added(self, data):
    mp = data.getManifold()

    v1 = Vec3(1, 0, 1)
    v2 = v1.cross(mp.getNormalWorldOnB())

    mp.setLateralFrictionInitialized(True)
    mp.setLateralFrictionDir1(v1)
    mp.setLateralFrictionDir2(v2)
    mp.setContactMotion1(1.6)
    #mp.setContactCfm2(0.6)
    #mp.setCombinedFriction(0.2)
    #mp.setCombinedRestitution(0.0)

game = Game()
run()