Confusing strange crash with Bullet manifold along with python list and dict

I have a Bullet physics shape that moves into another shape. I am querying any attribute of a contact manifold.
If all this is True Panda3D will crash with no error:

  • Using Dynamic Bullet Triangle mesh shape on the objects.
  • I store the contact.manifold_point in a dictionary and then a list.
  • I then try and query an attribute of the manifold that is inside the dict which is stored in a list.

So looking at this section of the code:

        result = self.world.contact_test(self.b1.node())
        for contact in result.get_contacts():
            mpoint = contact.get_manifold_point()
            print("A1")
            print(mpoint.distance)
            print("A2")

        hits = []
        h = None
        for contact in result.get_contacts():
            hit = {}
            hit['MPOINT'] = contact.manifold_point
            print("B1")
            print(hit['MPOINT'].distance)
            print("B2")
            hits.append(hit)
            h = hit

        if h:
            print("C1")
            print(h['MPOINT'].distance)
            print("C2")

        if hits:
            for hit in hits:
                print("E1")
                # Crash happens here if using triangle hull:
                print(hit['MPOINT'].distance)
                print("E2")

Panda3D will crash after “E1” when trying to do:

    print(hit['MPOINT'].distance)

Here is the full source code:

from direct.showbase.ShowBase import ShowBase
from panda3d.core import NodePath
from panda3d.bullet import BulletWorld
from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletConvexHullShape, BulletTriangleMeshShape, BulletTriangleMesh


class Game(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        base.disableMouse()
        camera.setPos(0,-20,0)
        world = BulletWorld()

        self.world = world

        self.node = NodePath('ph')
        self.node.reparent_to(render)

        self.b1 = self.make_ball(-2, 0, 0)
        self.b2 = self.make_ball(2, 0, 0)
        self.b2.node().set_kinematic(True)
        self.b1.node().set_kinematic(True)

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




    def make_ball(self, x, y, z):
        smiley = loader.loadModel("smiley")

        geomNodes = smiley.findAllMatches('**/+GeomNode')
        geomNode = geomNodes.getPath(0).node()
        geom = geomNode.getGeom(0)

        # Make Convex Shape:
        #shape = BulletConvexHullShape()
        #shape.addGeom(geom)

        # Make TriangleMesh Shape:
        shape = BulletTriangleMesh()
        shape.add_geom(geom)
        shape = BulletTriangleMeshShape(shape, dynamic=True)


        body = BulletRigidBodyNode()
        body.add_shape(shape)

        body.set_mass(1.0)

        self.ball = NodePath(body)

        smiley.reparentTo(self.ball)
        self.ball.set_pos( (x, y, z) )

        self.world.attach_rigid_body(body)
        self.ball.reparent_to(self.node)

        return self.ball


    def update(self, task):
        self.world.do_physics(globalClock.get_dt())


        result = self.world.contact_test(self.b1.node())
        for contact in result.get_contacts():
            mpoint = contact.get_manifold_point()
            print("A1")
            print(mpoint.distance)
            print("A2")

        hits = []
        h = None
        for contact in result.get_contacts():
            hit = {}
            hit['MPOINT'] = contact.manifold_point
            print("B1")
            print(hit['MPOINT'].distance)
            print("B2")
            hits.append(hit)
            h = hit

        if h:
            print("C1")
            print(h['MPOINT'].distance)
            print("C2")

        if hits:
            for hit in hits:
                print("E1")
                # Crash happens here if using triangle hull:
                print(hit['MPOINT'].distance)
                print("E2")





        pos = self.b1.get_pos()
        pos.x += 0.01
        self.b1.set_pos(pos)

        #self.b1.node().set_linear_velocity((1,0,0))
        return task.cont




game = Game()
game.run()

I run your code and there was no crash.

Thanks,
I will try and do some more test.
Here is what I am using:
Manjaro Linux
Panda3D 1.10.10 installed with PIP.
Python 3.9.7

Have another computer using same software and it also crashes.

I can add my own configuration, for completeness of the analysis.

Windows 10
Panda3D 1.10.10 SDK.
Python 3.7.9

Trying the code on my end, I find that it does crash–but not immediately.

Specifically, I seem to get a segmentation fault. (If your output included the line “Process finished with exit code 139”, this is, I believe, what “exit code 139” signifies.)

Hmm… I do notice that you’re using the geom from the “ball” model as the shape for your triangle meshes. I wonder whether the problem doesn’t lie there, in some way. I’m not sure of what way, specifically, but it seems like a potential source of issues, to me.

So, what happens then if you model a separate triangle-mesh shape, loaded via the “BulletHelper” class (specifically, the “fromCollisionSolids” method), and use that as your Bullet-shape instead?

The thing that is strange is if I only comment out this part of the code it does not crash:
(all the other parts of code that query a attribute from the manifold don’t cause a crash)

        if hits:
            for hit in hits:
                print("E1")
                # Crash happens here if using triangle hull:
                print(hit['MPOINT'].distance)
                print("E2")

I have tested different models and it does the same thing.

The Manual mentions how to create triangle shapes from geoms:

from panda3d.bullet import BulletTriangleMesh
mesh = BulletTriangleMesh()
mesh.addGeom(geom)

What is the proper way to use fromCollisionSolids?

" static fromCollisionSolids(np: panda3d.core.NodePath , clear: bool )→ panda3d.core.NodePathCollection"

When I use it I just seem to get a empty NodePaths

Different models generated using the same approach? Or models generated in other ways (such as via collision-geometry specified in the model-file)?

I’ll confess that I haven’t used that approach myself–perhaps it is reliable! My suspicion against is just a hunch.

In short, it assumes that you have a model or node-hierarchy that includes collision-geometry for Panda’s built-in collision system, and converts that geometry to Bullet collision-geometry, I believe.

I’m guessing that you’re passing into it models that don’t have Panda-built-in-style collision geometry, hence the empty NodePaths.

I was thinking that, for testing purposes, you might create a model of your own in a 3D modelling package, specify that the model be collision geometry, and then use BulletHelper to convert it to Bullet collision geometry.

That might allow us to get an idea as to whether the problem lies with the way that the Bullet collision-geometry is being generated, or with some other element.

It was different models with the same approach.

Ok I understand the BulletHelper.fromCollisionSolids now.

I created a collision egg then converted it to a rigid body using BulletHelper.fromCollisionSolids.
The make_ball function looks like this now:

    def make_ball(self, x, y, z):
        smiley = loader.loadModel("smiley")

        collider = loader.loadModel("col.egg")
        collider_paths = BulletHelper.fromCollisionSolids(collider)
        body = collider_paths.getPath(0).node()

        self.ball = NodePath(body)

        smiley.reparentTo(self.ball)
        self.ball.set_pos( (x, y, z) )

        self.world.attach_rigid_body(body)
        self.ball.reparent_to(self.node)

        return self.ball

However I am getting the exact same result.

Using a convex hull shape and there is no crash, this code works fine:

    def make_ball(self, x, y, z):
        smiley = loader.loadModel("smiley")

        geomNodes = smiley.findAllMatches('**/+GeomNode')
        geomNode = geomNodes.getPath(0).node()
        geom = geomNode.getGeom(0)

        # Make Convex Shape:
        shape = BulletConvexHullShape()
        shape.addGeom(geom)

        body = BulletRigidBodyNode()
        body.add_shape(shape)

        body.set_mass(1.0)

        self.ball = NodePath(body)

        smiley.reparentTo(self.ball)
        self.ball.set_pos( (x, y, z) )

        self.world.attach_rigid_body(body)
        self.ball.reparent_to(self.node)

        return self.ball

Here most likely the problem is inside Bullet. Physics engine does not allow direct impact on the body. Perhaps this is the problem with calculations.

Given the above, I’m inclined to agree with serega, above: my guess now is that this is likely an issue within Bullet. (Or perhaps our wrapper to it.)

What I don’t understand is why would querying the manifold work for these:

        result = self.world.contact_test(self.b1.node())
        for contact in result.get_contacts():
            mpoint = contact.get_manifold_point()
            print("A1")
            print(mpoint.distance)
            print("A2")

        hits = []
        h = None
        for contact in result.get_contacts():
            hit = {}
            hit['MPOINT'] = contact.manifold_point
            print("B1")
            print(hit['MPOINT'].distance)
            print("B2")
            hits.append(hit)
            h = hit

        if h:
            print("C1")
            print(h['MPOINT'].distance)
            print("C2")

But querying it this way will then crash:

        if hits:
            for hit in hits:
                print("E1")
                # Crash happens here if using triangle hull:
                print(hit['MPOINT'].distance)
                print("E2")

It is strange! Given that the error is a segmentation fault, it’s tempting to think that perhaps the relevant memory is being cleared at some stage, thus leaving the attempt to access it invalid.

However, I wouldn’t expect that to happen, given that “result” doesn’t seem to go out of scope…

[edit]
Interestingly, I can get it to crash with a call to "hit[“MPOINT”].getLifeTime(), too, which reinforces my feeling that the manifold-object is being invalidated somehow.

Perhaps there’s something about the internal handling of manifold points that causes them to be unreliable when stored in external structures like dictionaries or lists…?

I’m thinking, then, that it might be better to not store them, and to either use their data immediately or store the desired data by itself.

Yeah that is what it is looking like.
I Installed same version of python and Panda3D on a Windows machine and it did not seem to have any issue.
Thanks for going over this with me.
I will go file this as a bug on Github.

1 Like