Bullet apply rigid body to 3d shape

Hi, i would like to know if there is a way to use the BulletRigidBodyNode with irregular shapes, such as 3d models. In the manual there are primitive shapes and some advanced ones, but i have complex world 3d models (.egg) that i would like to use. How can i do that?

Hello, you need to add the Geom class to the BulletTriangleMesh instance.

https://docs.panda3d.org/1.10/python/programming/physics/bullet/collision-shapes#triangle-mesh-shape

Here it is shown how to extract Geom from geometry.

https://docs.panda3d.org/1.10/python/programming/internal-structures/other-manipulation/reading-existing-geometry#reading-existing-geometry-data

Do note, however, that such RigidBodyNodes will not–I believe–respect skinned animation: they’ll be just the mesh in its base form.

Also, if your models are fairly high-poly, then using them may be inefficient; whether this is a problem for your program may depend on just what you’re doing. (Put another way, there’s a reason that collision geometry tends I believe to be simpler–either low-poly or primitive shapes–than visible geometry.)

Hi, thanks very much for this reply. I read through the docs you sent me, however i was unable to get it running, do you know if anyone did a tutorial about this, since i cannot find any?

I believe that there’s a code-snippet on the forum that deals with converting visible geometry to the geometry used by the built-in collision system; this could then be converted for Bullet via the “BulletHelper” class, specifically via the “fromCollisionSolids” method.

The code-snippet:

BulletHelper:
https://docs.panda3d.org/1.10/python/reference/panda3d.bullet.BulletHelper

Thanks, but after using the code snippet that you sent me with the fromCollisionSolids method, i encounter an error

"TypeError: BulletTriangleMesh.add_geom() argument 1 must be panda3d.core.Geom, not panda3d.core.NodePathCollection"

My code:

def LoadWorldGeometry(self):
                # create a temporary copy to generate the collision meshes from
        model_copy = self.environmnet.copy_to(self.render)
        model_copy.detach_node()
        # "bake" the transformations into the vertices
        model_copy.flatten_light()

        # create root node to attach collision nodes to
        collision_root = NodePath("collision_root")
        collision_root.reparent_to(self.environmnet)
        # offset the collision meshes from the model so they're easier to see
        collision_root.set_x(1.)

        # Please note that the code below will not copy the hierarchy structure of the
        # loaded `model_root` and that the resulting collision meshes will all have
        # their origins at (0., 0., 0.), an orientation of (0., 0., 0.) and a scale of 1
        # (as a result of the call to `flatten_light`).
        # If a different relationship between loaded models and their corresponding
        # collision meshes is required, feel free to alter the code as needed, but keep
        # in mind that any (especially non-uniform) scale affecting a collision mesh
        # (whether set on the mesh itself or inherited from a node at a higher level)
        # can cause problems for the built-in collision system.

        # create a collision mesh for each of the loaded models
        for model in model_copy.find_all_matches("**/+GeomNode"):

            model_node = model.node()
            collision_node = CollisionNode(model_node.name)
            collision_mesh = collision_root.attach_new_node(collision_node)

            for geom in model_node.modify_geoms():
                print("ì")

                geom.decompose_in_place()
                vertex_data = geom.modify_vertex_data()
                vertex_data.format = GeomVertexFormat.get_v3()
                view = memoryview(vertex_data.arrays[0]).cast("B").cast("f")
                index_list = geom.primitives[0].get_vertex_list()
                index_count = len(index_list)

                for indices in (index_list[i:i+3] for i in range(0, index_count, 3)):
                    points = [Point3(*view[index*3:index*3+3]) for index in indices]
                    coll_poly = CollisionPolygon(*points)
                    collision_node.add_solid(coll_poly)
        
        return collision_root


self.environmnet = self.loader.loadModel("Models/Environment/environment.egg")
self.environmnet.reparentTo(self.render)

world_collision = self.LoadWorldGeometry()
world_mesh = BulletTriangleMesh()
world_mesh.addGeom(BulletHelper.fromCollisionSolids(world_collision))

please note:
the code here has been extracted from my game class which contains a lot more code that is not relevant to this question, and that is why for example the

LoadWorldGeometry()

function is called with self

Indeed, looking at the API, it seems that “fromCollisionSolids” returns a NodePathCollection–i.e. a group of NodePaths, rather than a single NodePath–much less a Geom!

Hmm… Not being hugely familiar with the use of “fromCollisionSolids”, let me ask: how many NodePaths are in the NodePathCollection? (I think that you can treat it as a list and just use the built-in “len” function to get that number.)

It may be that it’s doing the work for you and just producing a functional collision-mesh that you can then reparent into the scene-graph.

by performing the len() function on the NodePathCollection i get a result of 17

Actually, looking again at the API and around the forum… have you tried just extracting your base Geoms after loading the model–without first constructing built-in collision-geometry from them–and then passing those in to “addGeom”…?

Something like this?

model = self.loader.loadModel("someModelFile.egg")
geomCollection = model.find("**/+GeomNode")
for geom in geomCollection:
    world_mesh.addGeom(geom)

I will confess here that I’m not hugely familiar with this sort of thing, so I may very well be off about this! ^^;

from direct.showbase.ShowBase import ShowBase
from panda3d.bullet import BulletTriangleMesh, BulletTriangleMeshShape

class MyApp(ShowBase):

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

        model = loader.load_model("panda")

        mesh = BulletTriangleMesh()

        for node_path in model.find_all_matches('**/+GeomNode'):
            for i in range(node_path.node().get_num_geoms()):
                geom = node_path.node().get_geom(i)
                mesh.add_geom(geom)

        shape = BulletTriangleMeshShape(mesh, True)

app = MyApp()
app.run()

This code will extract all the Geoms in your model and create a BulletTriangleMeshShape

1 Like