Bullet behaves strangely

I decided to try ghost objects to create triggers. However, I’m getting a few problems, let’s start with a strange message when running the simulation.

from direct.showbase.ShowBase import ShowBase
from panda3d.bullet import BulletRigidBodyNode, BulletBoxShape, BulletWorld, BulletGhostNode, BulletDebugNode, BulletPlaneShape
from panda3d.core import Vec3, TransformState, Point3, ClockObject, NodePath

class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        self.bullet_world = BulletWorld()
        self.bullet_world.set_gravity(Vec3(0, 0, -9.8))

        debug_node = BulletDebugNode('debug')
        debug_bullet = NodePath(debug_node)
        debug_bullet.reparent_to(render)
        debug_bullet.show()
        self.bullet_world.set_debug_node(debug_node)

        body = BulletRigidBodyNode('body_1')
        body.add_shape(BulletBoxShape(Vec3(1, 1, 1)))
        body.set_mass(1)
        body.set_transform(TransformState.make_pos(Point3(0, 0, 20)))
        self.bullet_world.attach_rigid_body(body)

        self.trigger = BulletGhostNode('trigger')
        shape = BulletPlaneShape(Vec3(0, 0, 1), 0)
        self.trigger.add_shape(shape)
        self.bullet_world.attach_ghost(self.trigger)

        taskMgr.add(self.update, "update")

    def update(self, task):
        dt = globalClock.get_dt()

        self.bullet_world.do_physics(dt)

        if self.trigger.get_num_overlapping_nodes() > 0:
            pass
            #return task.done

        return task.cont

app = MyApp()
app.run()

out:

D:\TestBullet>D:\Panda3D-1.10.12-x64\python\python.exe main.py
Known pipe types:
  wglGraphicsPipe
(all display modules loaded.)
:bullet(error): Overflow in AABB, object removed from simulation
:bullet(error): If you can reproduce this, please email bugs@continuousphysics.com

:bullet(error): Please include above information, your Platform, version of OS.

:bullet(error): Thanks.

There is another problem, the ghostly object always shows a collision. Of course, this is not all, there are still surprises, but more about them later…

I think I found that this strange message is related to the debugging node.

I’m not sure about the error-message–that is an odd one–but if I recall correctly, detection of contact with ghost-nodes in Bullet requires not only checking the number of overlapping nodes, but also the checking of contact-points between the ghost and objects detected to be overlapping.

Looking at some old code of mine, what I had was essentially something like this:

for node in colliderNode.getOverlappingNodes():
    if bulletWorld.filterTest(colliderNode, node) and bulletWorld.contactTestPair(node, colliderNode).getNumContacts() > 0:
        # Treat as within the trigger

(You might not require the “filterTest”, depending on your situation.)

Thank you, it seems that an additional contact test is needed for concave forms.

Now we can go back to the main problem, since I now have a test example working.
I have set a limited mode for clock. The essence of the problem is that the bullet slows down when the frame rate drops, which logically should not happen.

from direct.showbase.ShowBase import ShowBase
from panda3d.bullet import BulletRigidBodyNode, BulletBoxShape, BulletWorld, BulletGhostNode, BulletDebugNode, BulletPlaneShape
from panda3d.core import Vec3, TransformState, Point3, NodePath, ClockObject

class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        self.bullet_world = BulletWorld()
        self.bullet_world.set_gravity(Vec3(0, 0, -9.8))

        debug_node = BulletDebugNode('debug')
        debug_bullet = NodePath(debug_node)
        debug_bullet.reparent_to(render)
        debug_bullet.show()

        self.clock = ClockObject.get_global_clock()
        self.clock.set_mode(ClockObject.MLimited)
        self.clock.set_frame_rate(60)

        self.bullet_world.set_debug_node(debug_node)

        body = BulletRigidBodyNode('body_1')
        body.add_shape(BulletBoxShape(Vec3(1, 1, 1)))
        body.set_mass(1)
        body.set_transform(TransformState.make_pos(Point3(0, 0, 20)))
        self.bullet_world.attach_rigid_body(body)

        self.trigger = BulletGhostNode('trigger')
        shape = BulletPlaneShape(Vec3(0, 0, 1), 0)
        self.trigger.add_shape(shape)
        self.bullet_world.attach_ghost(self.trigger)

        taskMgr.add(self.update, "update")

    def update(self, task):
        dt = globalClock.get_dt()

        self.bullet_world.do_physics(dt)

        if self.bullet_world.contact_test(self.trigger).get_num_contacts() > 0:
            print(task.time)
            return task.done

        return task.cont

app = MyApp()
app.run()

The time for which the cube falls from a height of 20 meters will be 1.9500038999999998 seconds at 60 frames.

If you limit the frame rate to 20 frames, then the time will already be 5.850002399999999. It’s counterintuitive, maybe I forgot something simple.

Hi serega-kkz,

Apologies for reviving this topic, being 11 months since the last reply.
I figure it might be helpful for people searching in the future if they come across this thread.

Out of curiosity I ran the numbers through this calculator to see what the accurate values should be.
With gravity at 9.8m/s^2, the cube falling 20m should take 2.0203s to fall to the ground, so the ~1.95s from the simulation is pretty good for the 60fps simulation. (Final velocity of 19.8m/s from the calculator vs ~19.27m/s I was getting from a 60fps simulation.)

The issue with the cube taking significantly longer to fall with the lower frame rate is due to the default values of the do_physics call.
self.bullet_world.do_physics(dt) is the same as calling with the defaults 1 for max_substeps and 1.0 / 60.0 for the stepsize.
So because you’re limiting the framerate to 20fps, you’re only running 20 simulation steps of ~0.016667 each second. Effectively simulating 1/3 of normal speed.

From all of this, we’ve got some options for tweaking, one option is to simulate with a stepsize value of 1.0 / fps so each frame is simulating an accurate time, this will have the effect of reducing the accuracy of the simulation significantly as the fps drops (If we INCREASE the fps here instead of reducing, we’ll get a more accurate simulation with the smaller steps, but will cost more in processing time).
Another option is to do multiple substeps (60 / fps), this will produce a more accurate simulation (than a low fps with adjusted stepsize), but at the cost of extra processing (we’re effectively running the physics simulation at 60fps here independently of the rendering fps, so 3 physics simulation substeps at 20fps).

This Stack Overflow link was an interesting and helpful read.
It shows us how we can calculate the amount of simulation time we’ve lost if we don’t adjust the stepsize/max_substeps and just drop the fps.

# if this is true we're simulating less than the actual elapsed time!
losingTime = dt > max_substeps * stepsize
# If we sum this over the frames we can see how much we've lost over the entire simulation time.
lostTime = dt - (max_substeps * stepsize) if losingTime else 0.0

At 20fps the simulation runs in ~6s vs the ~2 that we’re expecting.
Summing the lostTime each frame gives ~4s which is the difference between our simulation time and the time it should have taken.

Hope this little write-up is helpful to someone!

Cheers,
H3LLB0Y.

P.S. I found the first update run had a silly dt, so skipping that is probably a good idea.

1 Like