• 3D
  • How do I make a custom 3D collider shape?

For my procedurally generated island I need colliders, and I'd like to have control over them specifically so no automatic magic convex hull necessary. I'd prefer to build the shape myself.

But I'm having trouble finding information about how to build it. Which node to use, and what do I use to build it since I'm not supposed to use Mesh?

Hello I've gotten a little further by digging into things a bit. It seems I'll need to make use of a PhysicsBody node, populated by a ConcavePolygonShape.

At least that's what I think.

If there are any tutorials or examples I'd love to take a look. Especially if there is a good way to see my collision shape, because this is being generated at runtime.

Second question. Is there a significant performance improvement using fewer triangles to build the shape.

@Kequc said: Hello I've gotten a little further by digging into things a bit. It seems I'll need to make use of a PhysicsBody node, populated by a ConcavePolygonShape.

Yeah, that is more or less how to do it. If the mesh that needs the collider isn’t moving, I would suggest a StaticBody node, and if it needs to move then I would try a RigidBody node with the mode set to static.

It looks like you can pass a ConcavePolygonShape an array of triangles, which is a PoolVector3Array. I have not used it myself, but it looks doable. The only thing I’m not sure on is what kind of data it wants in the triangle array. I would guess vertex positions, but in what format I have no idea.

You might want to look into using the SurfaceTool. When I have needed. To generate collision meshes at runtime, or navmeshes, that is what I’ve used. You can make a mesh with the surface tool and then convert it into a collision mesh using the CreateConvexShape or CreateTrimeshShape function.

If there are any tutorials or examples I'd love to take a look. Especially if there is a good way to see my collision shape, because this is being generated at runtime.

I do not know if there are any tutorials on generating collision meshes at runtime with Godot. I’ve made a couple tutorials on generating a voxel terrain with Godot on RandomMomentania, but it is more focused on creating the terrain rather than the collision mesh.

I think you can view the collision mesh at runtime by clicking the Debug menu/drop-down in the editor, I think it is located in the top left but I’m not sure. In the menu/drop-down there should be a option called something like visible collision shapes That you can enable. Once enabled, all of the collision shapes should have a blue line mesh around it to represent the collision mesh.

Second question. Is there a significant performance improvement using fewer triangles to build the shape.

I dunno. With game engines I have always been told the less geometry in your collision meshes the better, but I don’t know if that is because there is performance difference or not.

I suppose I was expecting to be using Vector3's for vertices and then a separate array of vertex indices to create triangles. But it seems like I just simply append Vector3's directly, three at a time, for the engine to use. I'll give it all a try in the next days and see if I can get it.

For complexity reduction, I'm wondering if there's a polygon triangulation algorithm that takes into account the possibility of holes? I've been reading a little bit about how to do polygon triangulation, the best I've planned involves triangulating each face separately as opposed to the algorithm figuring out where faces go. I haven't quite solved holes yet either.

Here is the code I've come up with. But unfortunately even though I seem to have a whole bunch of faces being generated I don't have a collider in the scene.

extends CollisionShape

var faces:PoolVector3Array

func render(data:MapData, y:int) -> void:
	faces = PoolVector3Array()
	for x in range(data.xx):
		for z in range(data.zz):
			var tile:Tile = data.tiles.get_value(x, y, z)
			if tile.ground_type == ENUM.GROUND_TYPES.EMPTY: continue
			_render_tile(tile)
	shape.set_faces(faces)

func _render_tile(tile:Tile) -> void:
	var v = tile.render_cache[0]
	_add_triangle(v[0], v[1], v[2])
	if v.size() > 3:
		_add_triangle(v[2], v[3], v[0])

func _add_triangle(v1:Location3D, v2:Location3D, v3:Location3D) -> void:
	faces.push_back(v1.to_vector())
	faces.push_back(v2.to_vector())
	faces.push_back(v3.to_vector())

My scene looks like this, before running it.

And using the remote viewer I can see this when the scene is running.

You've been helpful so far but I feel like I need more help to get this working, I am not sure what I'm doing wrong and I'm running out of resources to refer to. I'll try your suggestion to use the surfacetool, which is what I am using to generate the graphics.

Unfortunately I can't find a method called CreateTrimeshShape.

Thank you!

@Kequc said: Here is the code I've come up with. But unfortunately even though I seem to have a whole bunch of faces being generated I don't have a collider in the scene.

I tried some tests on my computer to see if I could get a ConcavePolygon Shape working through code, but then a thunderstorm cut my process short. I’ll try to take another look tomorrow.

From my initial testing, it appears that the ConcavePolygon Shape works when given a PoolVector3Array of vertices, where each three vertices makes a face, but it doesn’t draw in the editor. However, I made a RigidBody sphere and it collides as expected, so I guess it works? I dunno, will need to do more testing.

You've been helpful so far but I feel like I need more help to get this working, I am not sure what I'm doing wrong and I'm running out of resources to refer to. I'll try your suggestion to use the surfacetool, which is what I am using to generate the graphics.

I do not think you are doing anything wrong, I think it is just hard to generate collision shapes/meshes from code.

Using the surfacetool might be your best bet, especially if you want to debug your collision shape, as worse case you can just make a MeshInstance node to use to visualize the output from the surfacetool. If you need help with the surface tool, let me know and I’ll do my best to help as I can.

I’ll try to take another look at the ConcavePolygon shape. It is strange that ConcavePolygon doesn’t seem to show up in the editor, which makes it very hard to debug what shape it has.

Unfortunately I can't find a method called CreateTrimeshShape.

It is a function exposed by the Mesh class. Here is a link to the documentation for the function.

Thank you!

Happy to help! Sorry I cannot be of more assistance.

In the remote viewer it seems that the mesh block/input is empty, might have something to do with it.

Okay, I did some further testing with ConcavePolygon.

As far as I can tell, setting the triangles in ConcavePolygon does work. The visual debug shapes for ConcavePolygon shapes does not work though, which makes it hard to tell the shape of the ConcavePolygon.

I'm not sure why the debug visual doesn't work, but I'm guessing like @Megalomaniak said, the mesh block/input being empty might have something to do with it.

I found a simple workaround that uses the SurfaceTool to generate a line mesh like the visible collision mesh debug option. After writing the code below, I can now see the collision mesh and everything seems to work as expected.

Here is the code I used:

extends CollisionShape

export (bool) var show_debug_shape = false;
export (int, "lines", "triangles") var debug_shape = 0;

var triangles = PoolVector3Array();

func _ready():
	triangles = PoolVector3Array();
	
	generate_collision_cube();
	
	if (show_debug_shape == true):
		if (debug_shape == 0):
			generate_concave_visual(triangles, "lines");
		else:
			generate_concave_visual(triangles, "triangles");


func generate_collision_cube():
	
	var CES = 1; # Cube Extends Size
	
	# Top
	generate_quad(Vector3(-CES, CES, -CES), Vector3(-CES, CES, CES), Vector3(CES, CES, CES), Vector3(CES, CES, -CES));
	# Bottom
	generate_quad(Vector3(-CES, -CES, -CES), Vector3(-CES, -CES, CES), Vector3(CES, -CES, CES), Vector3(CES, -CES, -CES));
	
	# Forward
	generate_quad(Vector3(-CES, -CES, CES), Vector3(-CES, CES, CES), Vector3(CES, CES, CES), Vector3(CES, -CES, CES));
	# Backward
	generate_quad(Vector3(-CES, -CES, -CES), Vector3(-CES, CES, -CES), Vector3(CES, CES, -CES), Vector3(CES, -CES, -CES));
	
	# West
	generate_quad(Vector3(CES, -CES, -CES), Vector3(CES, -CES, CES), Vector3(CES, CES, CES), Vector3(CES, CES, -CES));
	# East
	generate_quad(Vector3(-CES, -CES, -CES), Vector3(-CES, -CES, CES), Vector3(-CES, CES, CES), Vector3(-CES, CES, -CES));
	
	if (shape is ConcavePolygonShape):
		shape.set_faces(triangles);



func generate_quad(position_one, position_two, position_three, position_four):
	# First triangle
	triangles.append(position_one);
	triangles.append(position_two);
	triangles.append(position_three);
	
	# Second triangle
	triangles.append(position_one);
	triangles.append(position_three);
	triangles.append(position_four);



func generate_concave_visual(triangles_array, mode = "lines"):
	var surface_tool = SurfaceTool.new();
	
	if (mode == "lines"):
		# Line mesh:
		surface_tool.begin(Mesh.PRIMITIVE_LINES);
		
		# Go through the array adding lines.
		# NOTE: This code should add two lines per vertex, which makes them a little easier to see.
		var i = 0;
		while (i < triangles_array.size()):
			surface_tool.add_vertex(triangles_array[i]);
			if (i > 1):
				surface_tool.add_vertex(triangles_array[i-1]);
			i += 1;
		
		# Add the last line:
		surface_tool.add_vertex(triangles_array[triangles_array.size()-3]);
		surface_tool.add_vertex(triangles_array[triangles_array.size()-1]);
	
	
	elif (mode == "triangles"):
		# Solid mesh:
		surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES);
		
		for vertex in triangles_array:
			surface_tool.add_vertex(vertex);
	
	
	var collision_mat = SpatialMaterial.new();
	collision_mat.albedo_color = Color.green;
	collision_mat.flags_unshaded = true;
	# NOTE: Disable culling because triangle order isn't quite right in the add_quad function.
	# however, this only effects normals and shouldn't matter for collision geometry.
	collision_mat.params_cull_mode = SpatialMaterial.CULL_DISABLED;
	
	surface_tool.set_material(collision_mat);
	
	var mesh_node = MeshInstance.new();
	mesh_node.mesh = surface_tool.commit();
	
	add_child(mesh_node);

I tried to write generate_concave_visual so that it is independent of the rest of the code so it can be more easily used in other projects.

Also, I should note that the generate_quad function doesn't take the order of the inputted vertices into account, which is fine for collision mesh generation, but for drawing the mesh it requires the cull mode being disabled.

Hopefully this helps!

That really helps a lot because apparently I am generating colliders correctly.

Not an efficient mesh at all! ha

But in any case I'll be able to continue development with this and optimise it later. Thank you for that. I just assumed that the debug "Visible Collision Shapes" tool was working. And that mesh that appeared at runtime was confusing me because I didn't understand what it was for.

This essentially mimics the expected behaviour and I'm happy enough with that.

I've changed this topic from discussion to a question, feel free to mark relevant replies as answers.