Physics: Motion Surface or conveyor simulation

Hi everybody!
I´m new with Panda3D, but not with physics engines, and I am testing Panda3d for a future project. It looks great! Great job!

I´ve been checking for a few days but it seems that the feature for modifying contact points to simulate moving surfaces or conveyors is missing in both ODE and bullets physics:

  • The only thing I have found in bullet is to assign linear velocity to static body and then a dynamic body moves on it, but not as I want… And I need something for both dynamic and static bodyes :frowning:
  • In ODE the surface parameter motion is missing :frowning:
    Is this true or I am missing something?

Thanks!

Hello, and welcome to Panda3D.

We are aware that the ability to modify contact points is required in many advanced cases (moving surfaces, advanced vehicle dynamics, advanced friction, etc…).

Here is the answer to your question for Bullet (and PhysX):

On the Python level we currently have not exposed such features. The reason for this is that it would be a performance killer if not done right (callbacks from C++ to Python are expensive). However, it would be possible to add, given that we find a filter criterion which limits the number of C+±to-Python calls.

On the C++ level it is possible, since the wrapped Bullet objects are public accessible. So you could replace the default contact callabck with your own implementation.

Thanks enn0x!
How could I help with this topic? or is it already someone fixing it? I would like to use python and not C++…

Hmm… thinking about it we might not need a callback. We already can access the manifold points in bullet. We just don’t have the public properties exposed to Python. Can you tell me which of the native Bullet properties are most important to you?

bulletphysics.com/Bullet/BulletF … Point.html

Hmm… :smiley: I´ve been thinking too…
I am not an expert in bullet, but next link shows which parameters are necesary to simulate a conveyor:
bulletphysics.org/Bullet/phpBB3/ … 441#p21441
But, this is what I did some years ago with ODE:

  • I did a quite complete contact callback function, taking care of all the possible parameters, the one that is called for every collision. But every geom had a surface data, so depending on that the contact was created differently, allowing to do nice stuff like the conveyor simulation. At any point in the main program was enought to modify the surface data, so the next callback would change the contact behaviour.

  • In bullet, it seems that the “regular” callback is made by the bullet core, and there is something called CustomMaterialCallback for doing aditional stuff… What if this Callback is also already programmed and the core only calls it if one of the touching objects or the world has this feature enabled. there can be a method to enable this callback from python (world.EnableCustomMateriallCallback()) so it connects the callback function. With this and some nice surface stuff or parameters being able to modify from python, the wrapper can be really nice, and without events to python, so maybe there is no performance problem…
    It is only a guess :D… and it is only one week since I have started with python and panda, so probably Is not as easy :frowning:
    Thanks anyway!

We already implement the Bullet contact callback in Panda3D. Calling notify_collisions(true) on a body node enables them. This method just sets the CF_CUSTOM_MATERIAL_CALLBACK flag on the Bullet collision object. The default implementation in Panda3D is:

* contact_added_callback:

  • store some persistent data on the manifold point (currently the two objects)
  • if the config variable bullet_enable_contact_events is set send an Panda3D event
    * contact_processed_callback:
  • do nothing
    * contact_destroyed_callback:
  • if the config variable bullet_enable_contact_events is set send an Panda3D event

Events are not callbacks. An event will be delivered to the user some time AFTER it has been created, i. e. after the physics simulation is done (the call to world.do_physics has returned). Events are intended to inform users, for example about contacts being created or destroyed. The data is “read only”.

We could also (if requested by a new config variable, maybe “bullet_enable_contact_callbacks”) do a callback tp Python from inside contact_added_callback. This would allow the user to modify the contact properties (lateral friction etc. like in the example by Erwin Coumans). Seems a usefull feature to me. I will give it a try this weekend.

But just for the record: BOTH events and callbacks to Python are performance critical, so users have to be very careful not to use with caution. This is why I certainly won’t offer a generic “every contact is processed by the user” callback. Panda3D Bullet integration is already dog slow (because of the way too powerful scene graph synchronisation), and I don’t want to make it completely unusable.

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()

Hi!
Thanks for the effort, but still this is not what I am looking for, I see some problems:

  • callback to python = bad performance.
  • What if different behaviours are needed for different shapes?

I think that the idea of having surface data attached to every shape/body is more efficient, so if it is necesary, special contact features are automatically done by the bullet code without callbacks, and changing the surface data we can change the behaviour, such as bounce, friction, motion (for conveyor simulation), …
The surface data can be created by default with every shape, and only if special features are set can the callback function be executed internally by bullet.
What do you think?

You can handle different behaviours for different shapes already. Even more, for each triangle of a triangle mesh if you want to. Just read the “part id” and “index” from the manifold point. Or directly from the callback data (if you pick up the latest snapshot build).

Regarding performance: the callback approach implemented now is usable from Python and C++ via the same API, and C++ callbacks are fast. So actually if performance becomes critical you can move just the critical part to C++. Actually you just need to implement your own callback object derived from Panda3D’s CallbackObject, and you are good.

So both problems are solved already, kindof.

I think you make a mistake which is quite common around here, by assuming the there is a one-size-fits-all solution. I urge you to make up your mind about what surface data is actually needed, in order to solve all possible usages of contact callbacks. And then post a structure here which holds all the needed data. No need to make it valid C++ code, just pseudocode. My expectation is that this data structure quickly becomes vary large, the longer you think about what data is needed.

Hi enn0x!
I probably made a mistake :(…
But still do not see the point on iterating over all the objects in my scene to find the ones colliding (or maybe bullet has a function for that), then get somehow surface related data, and finally modify the collision contact… I guess that for a big scene (500 objects) this can low performance…
would it be possible to get the collision objects pointers directly in the callback function, so maybe I can create a new class inherited from dat with surface data, so I can create a standard callback function in C++ as you said.
Hope I do not continue with mistakes :D!
You are doing a great work!
Thansk again!

Sorry, I have seen that the contact callback has already the objects pointers, so maybe can fix it…
In bullet c++:

typedef bool (ContactAddedCallback)(
btManifoldPoint& cp,
const btCollisionObject
colObj0,
int partId0,
int index0,
const btCollisionObject* colObj1,
int partId1,
int index1);

Is it BulletBodyNode the equivalent to btCollisionObject?
mmmm… I could create a new class BulletRigidSurfaceBodyNode, for example, inherited from BulletRigidBodyNode plus the surface data, witch activates automatically notifyCollisions, and has the implementation of the callback function (static function?)…
Doing something like this once the world is created would work:

Callback

self.world.setContactAddedCallback(PythonCallbackObject(BulletRigidSurfaceBodyNode.Callback)) 

or not??? :frowning:
:smiley:

No need to be sorry. Iterating over all bodies is too much of a performance hit, for sure. I think the callback approach which I added after your first post is a good trade-off between flexibility and performance.

PythonCallbackObject is derived from CallbackObject. On C++ you can define your own derived classes. for Python this is not possible. So Panda3D offers a generic class which takes a Python function as argument. Part of the framework. Just make sure you pass a Python function in the constructor. The Python function has to take one argument, which carries the callback data.

By now (latest buildbot version) the callback data (type: BulletContactCallbackData) provides access to all data of the C++ callback.

The PandaNode pointers are equivalent to the Bullet btCollisionObject Pointers. I’m not sure if modifying the collision objects from within the callback causes problems in Bullet itself, since the Bullet documentation makes no statement here. The manifold point is what you should modify, i. e. setting lateral friction and so on.

Don’t forget to call mp.setLateralFrictionInitialized(True) on the manifold point. Otherwise Bullet will assume you did not modify the data and initialize the data itself, effectively overwriting your modifications.

class Foo:
  def f(self, data):
    print dir(data)

    mp = data.getManifold()
    node0 = data.getNode0()
    node1 = data.getNode1()
    id0 = data.getPartId0()
    id1 = data.getPartId1()
    index0 = data.getIndex0()
    index1 = data.getIndex1()

    mp.setLateralFrictionInitialized(True)

  def setup(...):
    ...
    cbo = PythonCallbackObject(self.f)
    self.world.setContactAddedCallback(cbo)

A small warning: I have not been working with part ids and (triangle) indeces myself so far. So I don’t know for sure what exactly Bullet provides here.

Ok! Let me see if I understood…
In the Callback function I can get both colliding node pointers. So, in your example, node0 and node1 got from the callback data would be BulletRigidBodyNodes? or I need to do nodeX.node() to get them?

To implement a C++ Callback function and use it when programming the rest of the code in python sounds quite advanced for now :(…

I will try to test it this weekend and see what happends :smiley:
Thansk a lot enn0x!

Yes, in the above example node0 and node1 would be the BulletRigidBodyNodes. Since it is the node itself (and node a NodePath) you don’t hav to use np.node().

Hi enn0x!
I had not much time to test the bullet Panda3D wrapper last weekend, but I already have seen some stuff I need help with :frowning:
In my application the next points are most important:

  • The simulation should be real time, so one bullet second should take as one real second, of course, if the computer can compute the physics in less time than the simulation step time . And it will be an application for educational purpose, so I do not expect a student to have a NASA computer…
  • The simulation quality is more important than the visual frames per second.
  • I will use a large amount of static objects, and always, less dynamic objects.

So, after modifying an example program I get next conclusions, but I need your help to confirm or not them:

  • In Panda3D, bullet and the visual world are connected in a way that is not easy to disconnect. I mean, I can´t easily decide when I want to paint or not my objects, and the example programs I have seen use visual world update to do bullet physics. I guess is dophysics() witch updates position and rotation to the visual nodes… 
  • Doing the physics steps every visual update, means that unless I do some calculation (see program bellow) I get different result in simulation speed depending on the simulation world scale.
  • It looks like maybe I could reach reasonable performance with python when simulating dynamic objects, but I don´t know why, and sounds strange for me… when using static objects the performance goes down. (check it setting the mass to 0 to all the boxes in the example).

I know that I started the post with a specific issue, but I guess it would be easy to fix that once I decide to use Panda3D because the performance is good enough for my project.

  • You can test performance changing the num of boxes.

Thanks!

import sys
import time

from direct.showbase.ShowBase import ShowBase

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(ShowBase): 

  def __init__(self):
    # Viewer
    ShowBase.__init__(self)  
    base.setFrameRateMeter(True) 
    base.cam.setPos(0, -50, 5) 
    base.cam.lookAt(0, 0, 10) 

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

    # Physics 
    self.setup()

    self.RealTime = 0.0
    self.BulletTime = 0.0
    
    self.MaxSimStep = 0.05
    self.MinSimStep = 0.005
    self.SimStep = (self.MaxSimStep + self.MinSimStep) / 2
    
    # Task 
    taskMgr.add(self.update, 'updateWorld') 

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

  def update(self, task):
    # Real Time
    self.RealTime += globalClock.getDt()

    # Get next simulation total step time
    dt = self.RealTime - self.BulletTime
    if (dt > self.SimStep*10):
      dt = self.SimStep*10

    # Get actual time
    pt = time.time()

    # Bullet simulation
    self.world.doPhysics(dt, 10, self.SimStep)
    self.BulletTime += dt

    # Calc Bullet processing time
    pt = time.time() - pt

    # Adjust Sim step
    if (pt > dt) and (self.SimStep < self.MaxSimStep):
      self.SimStep += 0.001
    elif (self.SimStep > self.MinSimStep):
      self.SimStep -= 0.001

    # Print
    print (self.RealTime, self.BulletTime, pt, dt)
    return task.cont 

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

  def setup(self): 

    # World 
    self.world = BulletWorld() 
    self.world.setGravity(Vec3(0, 0, -9.81)) 

    # Debug Node
    debugnode = render.attachNewNode(BulletDebugNode('Debug')) 
    debugnode.show() 
    debugnode.node().showWireframe(True) 
    self.world.setDebugNode(debugnode.node()) 

    # Floor 
    node = BulletRigidBodyNode('Ground')
    shape = BulletPlaneShape(Vec3(0, 0, 1), 1) 
    node.addShape(shape)
    self.world.attachRigidBody(node) 
 
    # Boxes
    num = 10
    cont = 0
    shape = BulletBoxShape(Vec3(0.5, 0.5, 0.5))
    for i in range(num):
        for j in range(num):
            for k in range(num):
                node = BulletRigidBodyNode()
                node.setMass(1.0)
                node.addShape(shape)
                self.world.attachRigidBody(node) 
                np = render.attachNewNode(node)
                np.setPos(i*10,j*10,(k*3+5))
                cont += 1
    print (cont)
    
game = Game() 
game.run()

Is there anyone else who can help me with the previous questions?
Thanks!

I don’t know what to say here. We get this quite often. Let me exaggerate a bit: “I want highest quality (ultra-realisitic) at best performance (real-time at least), and of course at no cost (open-source) and right now”. I’m not good at customer expectation management, so I will use my no-so-polite way to answer this: We don’t offer this. No need to look elsewhere, since you won’t find anything which fits the bill. But you are still lucky: Santa Clause will make his roundtrip soon, and you could put this on your wishlist for Christmas.

Sorry for the rant. Now let’s move on to serious answers.

Bullet/PhysX/ODE are engines for GAME physics, not for technical simulation. This means they do have tradeoffs between performance and realism. Performance is almost everywhere winning over realism. Actually most games don’t want to have realism at all. Nobody wants to play a racing car game if the physics would be realistic. Same for character movement. Wouldn’t be fun, and games are intended to be entertaining.

Actually it is very easy to disconnect both. You don’t want objects to be renderer: then just do not parent the bullet nodes below render. Or stash them.

About the second part: Yes, do_physics is the call which advances the simulation state.

By the way: the debug renderer you use in your example is not intended for rendering your objects. It is very slow, by intention. It is a tool to help with finding bugs in your code, e. g. misplaced physics objects.

Yes and no. The only way to get perfectly deterministic physics is to leave rendering away. This is a fact. Still, you make a major mistake in your example: you use a different substep size each frame!

Stepping works likes this:

  • Choose a CONSTANT substep size, which is smaller than your highest framerate. 1/120 seconds is usually a good value. The smaller the substep is the better the qualtity, but the performance will drop.
  • Now you increase the simulation by a delta time. Bullet will check how many substeps fit into the delta time, and simulate those substeps. The remaning time (dt - n * substep) is interpolated by Bullet.

In other words: the time step calculations you do in your example are useless.

Of course the best results are created when calling Bullet with multiples of the substep size. This could be done from a separate thread, but now you run into problems with synchronizing the simulation state with the scene graph. We don’t offer this option because it is beyound the skill level of most Panda3D users.

To minimize those interpolation times the best approach is to limit your framerate, and pay a little attention to those cases where the delta time is a tiny bit below n*substep. Interpolation effects will be reduced to those moments when the framerate drops below your limit - which is ok since you have a problem with rendering/AI/… anyway at this moment, and don’t want physics to make it even worse (interpolation is cheaper than simulation!).

This is due to the debug renderer, and not because of the static/dynamic state of bodies. Don’t complain about performance if debug rendering is on.

A tip for Panda3D newbies: use pstats to check which parts of Panda3D need the most processor time.

I checked in an optimization for the physics/scenegraph synchronisation a few days ago, which makes it possible to disable the expensive synchronisation mechanism I used so far. If you pick up a recent snapshot build and disable the “bullet-full-sync f” config variable then you should see much better performance for static objects and also for sleeping dynamic objects.

Finally another physics tip: if you have many static bodies with only one shape each then try combining them into few static bodies with many shapes each. You could even use only one static object (the environment), and maybe a few others for things like signs or other static objects you want to interact with.

To make a simulation real time is quite easy, I’ve been doing it for 10 years, and as you know, it is only about steping the physics engine properly. In the other hand, I have not say that I spect/need a high quality simulation, but I prefer to use the processor for physics than for rendering work, that is important for me.

I have to complain on that… and at least ODE, the one I have used for many years, is not only for games. There are lots of projects using those libraries for technical simulations, films, etc… and not only for gaming. But, If you are saying that Panda3D can be only use for making games, I probably need to find something else. I hope not.

That sounds good. Is there any event I can get before panda3D wants to render the scene? Or is already Update called before and not after as I thought? I could update then the position/rotation of the objects that I want. The only question is that the code inside dophysics() witch updates the pandanodes is going to be skiped efficientlly or not. You probably know. How can I see the code inside that method? It would be nice to see how it gets the position and rotation of the bullet objects and then set them to the Pandanodes.

Yes, you are right! I have noticed that during this week, when I have started to paint with models instead. This is not a problem for me, I understand what debug means.

The example I wrote for changing the simulation step had some mistakes, I know. That was only to test if it was possible to fix the real time issue. Anyway, I will try to run the simulation and the visual world independently, using a timer/interruption to do the physics with fixed step. I will see how can I do it…

Finally, the tips you give me about static objects are not an option for my application… And I can still see a big performance drop using static objects, both using the debugger node and panda3d models for visualization. Have you tried it?

Thanks for your answers!

Just browse the code via the Sourceforge CVS viewer:
panda3d.cvs.sourceforge.net/view … rc/bullet/

You want to look at the following functions:

  • BulletWorld.cxx: do_physics, sync_p2b, sync_b2p
  • BulletRigidBodyNode.cxx: transform_changed, sync_p2b, sync_b2p
  • BulletRigidBodyNode.h: inner class MotionState

Yes, I have.

Are you certain that you have done the following:
(1) you picked up the lates snapshot build?
(2) you have set bullet-full-sync to FALSE?

Have you checked with PStats that the Bullet module is eating the performance? Rendering lots of GeomNodes each with simple geometry can be quite expensive. It’s better to have less models with more triangles per model. This is what the Panda3D RigidBodyCombiner is used for (despite the name this class has nothing to do with physics).

If so I want to see a screenshot of the following pstats category: “App:Bullet:DoPhysics”, along with the updated test driver code.

Oh, and you might want to use the Panda3D ODE module. It’s old, but if you are familiar with ODE then you might have better productivity.

Sorry, I was very bussy this week…
I had only 5 minutes today to test the Pstats app, and with the Panda3d 1.8.0 release, the same program with imported model rendering (not debug) a floor and 160 boxes, gives me this result:

  • Boxes mass = 0.5: 65fps, Total time 16ms, Bullet:DoPhysics time 3ms.
  • Boxes mass = 0.0: 15fps, Total time 74ms, Bullet:DoPhysics time 69ms.

Those values are the ones I’ve got ones the world is stable (after 10 sec) and no box is moving in the first case. I can send you a jpg file if you want.

I will try the same with the latest snapshot and see how it looks when I have more time.
Thanks anyway!

Merry Christmas!