Collision mesh from loaded model for built-in collision system

Hi all,

This code snippet creates a collision mesh from a loaded model, suitable for the built-in collision detection and handling system:

from panda3d.core import *
from direct.showbase.ShowBase import ShowBase


base = ShowBase()

# set up a light source
p_light = PointLight("point_light")
p_light.set_color((1., 1., 1., 1.))
base.light = base.camera.attach_new_node(p_light)
base.light.set_pos(5., -100., 7.)
base.render.set_light(base.light)

# load model(s)
path_to_model = "smiley"
model_root = base.loader.load_model(path_to_model)
model_root.reparent_to(base.render)

# create a temporary copy to generate the collision meshes from
model_copy = model_root.copy_to(base.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(model_root)
# 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)
    # collision nodes are hidden by default
    collision_mesh.show()

    for geom in model_node.modify_geoms():

        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)


base.run()

Please note that this is far from efficient, especially for models with complex geometry!
It is meant to be used in cases where exact collisions take priority over performance and for testing purposes.
In other cases, a simpler collision shape approximating the model geometry is strongly recommended.

Hopefully some of you will find it useful :slight_smile: .

3 Likes

Does this create a collision solid around any object in a model?

It creates a solid for each GeomNode in the model with the exact same shape as that GeomNode.

This is very neat! :slight_smile:

I would like to note one caveat–unless I’ve missed something obviating it, or am mistaken: If the source-model has rather detailed geometry, this may have performance implications when the geometry is used for collision purposes.

Where feasible, I do still urge the use of dedicated, simplified geometry for the purposes of collision.

That said, this remains a technique that could potentially be useful in a number of cases, indeed! :slight_smile:

1 Like

And you are right, of course! In my hurry to post the snippet, I totally forgot to mention this :grin: . The first post has now been amended to include this advice. Thanks :slight_smile: !

1 Like

When I am trying it with “models/smiley”, It is not working

Hey. I want to use this in a c++ project. I am trying to port it over but I need some help. Basically I’m trying to make a function that takes the geometry of a terrain built in blender and make it into a mesh for collisions so the player can walk on it. I’m new to panda3d and do not know of a way to do that so I’m trying to write one. If there is a way, please let me know.

This is what I have so far and I have no idea if I’m on the right track; any help would be great!

    NodePath Constructor::BuildSimpleCollider(const NodePath& ground_geom_node, NodePath& attach_collider_to)
	{
		// ------------------------------------------------
		// Create collision shape from geometry of model
		// ------------------------------------------------
		NodePath collision_root = NodePath(ground_geom_node.get_name() + "_collision_root");

		collision_root.reparent_to(attach_collider_to);
		CollisionNode* collision_node = new CollisionNode(ground_geom_node.get_name() + "_collider");
		NodePath collision_mesh = collision_root.attach_new_node(collision_node);
		collision_mesh.show();

		PT(GeomNode) geomNode = DCAST(GeomNode, ground_geom_node.node());
		CPT(Geom) geom = geomNode->get_geom(0);
		CPT(GeomVertexData) vdata = geom->get_vertex_data();
		
		std::vector<LVector3> vertices;
		GeomVertexReader vertex(vdata, "vertex");
		while (!vertex.is_at_end())
		{
			LVector3 p = vertex.get_data3();
			vertices.push_back(p);
		}

		for (int i = 0; i < (int)vertices.size(); i+=3)
		{
			LVecBase3 a = vertices[i];
			LVecBase3 b = vertices[i + 1];
			LVecBase3 c = vertices[i + 2];

			PT(CollisionPolygon) poly = new CollisionPolygon(a, b, c);
			collision_node->add_solid(poly);
		}

		// -----------------------------------------------

		return collision_root;
	}

I tihnk you are on the right track, if you are running into issues with this code you should share the error message.