Adding trimesh collision to a triangle strip mesh

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Ninfur

I’m working on a procedurally generated terrain that is created with an ArrayMesh populated with triangle strip data. How do I go about adding collisions to the mesh?

I’ve tried using MeshInstance.create_trimesh_collision(). But as noted in this question, create_trimesh_collision does not work with triangle fan/strip. Neither does Mesh.create_trimesh_shape() as it returns null. Altough the functions do work when using Mesh.PRIMITIVE_TRIANGLES.

I’ve also noticed that triangle strip meshes does not show a wireframe in the wireframe view, and Mesh.get_faces() returns an empty list (which is likely why none of the above is working properly).

Related questions that does not answer this:

:bust_in_silhouette: Reply From: DaddyMonster

The way I tackled this was to make the procedural terrain generation run separately, save the mesh to file and load it on start up. This brings a couple of advantages:

  1. You use the same mesh for collision
  2. You can implement lod and have a lower poly collision mesh if you want
  3. It’s much quicker to launch
  4. It works well with chucks
  5. Simplicity

That said, I don’t have experience with tri strips, maybe that changes thing, not sure.

Thanks for the suggestion.
The terrain I’m working on is endless, so it wont be possible to generate the entire thing beforehand unfortunately. I guess it would be possible to save the mesh of each chunk that has been built. But I’m afraid I don’t see how that would help with the collisions unless the export or import step somehow rebuilds the mesh differently.

Ninfur | 2022-01-10 16:20

Ah, I see. No, my suggestion is not suitable for an endless terrain scenario.

Hmm, this is genuinely tricky. Maybe try something like this:

func _ready():
	make_col_shape(CubeMesh.new())

func make_col_shape(mesh):
	var col = CollisionShape.new()
	var cps = ConvexPolygonShape.new()
	cps.points = mesh.get_mesh_arrays()
	col.shape = cps
	add_child(col)

Replace the CubeMesh with your MeshInstance mesh chunk.

Huge caveat: I’ve not tested any of this. I’m worried we may have to separate the verts from the normals, UVs and so forth so I’m not confident about this as is… Try it, you never know. We want to avoid more expensive looping…

I assume you’re using MeshDataTool to loop as you make the mesh? Maybe you can grab a vert only PoolVector3Array and just apply that??

DaddyMonster | 2022-01-10 18:14

I’m manually creating the verts, UVs, indices and normals and adding them to the ArrayMesh, so accessing them won’t be a problem.

I’ve tried something similar to what you are suggesting, and it kind of works if you throw in a StaticBody node in the mix. The problem I’m facing with this approach is that I don’t think a convex shape will be accurate enough for a terrain that can be both convex and concave in places. This results in objects flying in the air above the concave parts of the terrain.

I thought it would be easy enough to just use a ConcavePolygonShape instead, but it needs to be populated with faces rather than vertices. And so far I haven’t found a way to get the mesh faces besides creating them myself. But I feel like doing so would kind of defeat the purpose of using PRIMITIVE_TRIANGLE_STRIP over PRIMITIVE_TRIANGLES. Normally you would be able to get the faces using mesh.get_faces(), but this does not work for triangle strips for some reason. I’m not sure if this is a bug or if there is some logical explanation as to why.

Perhaps ConcavePolygonShape’s and PRIMITIVE_TRIANGLE_STRIP’s are simply not compatible, since the shape seems to expect faces with 3 unique vertices and the strip is built to allow faces with shared vertices.

Ninfur | 2022-01-10 20:15

Ah, concave… Impressive sounding project. Ok, so we need to effectively construct a tres along these lines:

[gd_resource type="ConcavePolygonShape" format=2]

[resource]
data = PoolVector3Array( 0.4536, 0.8618, 0.6113 )

How about this: when you’re done with the procedural looping with MeshDataTool, grab faces = mdt.get_vertex_faces()

I’d try it first with a CubeMesh for simplicity and see how the data looks. In theory you can just make an obj and add it to the shape.

DaddyMonster | 2022-01-11 10:54

I should clarify that I’m not using MeshDataTool for the mesh generation. My understanding is that MeshDataTool is mainly used for modifying existing geometry, rather than creating new.

My mesh creation looks something like this

var data = []
data.resize(ArrayMesh.ARRAY_MAX)
data[ArrayMesh.ARRAY_VERTEX] = vertices
data[ArrayMesh.ARRAY_TEX_UV] = UVs
data[ArrayMesh.ARRAY_INDEX] = indices
data[ArrayMesh.ARRAY_NORMAL] = normals

var terrain_mesh = ArrayMesh.new()
terrain_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, data)

However, I tried adding my mesh to a MeshDataTool’s instance. But when calling mdt.create_from_surface() it fails with the error ERR_INVALID_PARAMETER. Looking at the documentation for create_from_surface(), it actually states that it requires a mesh with primitive type Mesh.PRIMITIVE_TRIANGLES.

Ninfur | 2022-01-11 16:47

Ah ok, I work a little differently and start with a primitive, subdivide, then loop - hence MeshDataTool.

I’ve been playing around with it but, honestly, I’m not sure what form the face data takes (as opposed to vertex data)… The vector maths is different for concave but I’m just not sure what form they want it. I mean, a face is just three verts with an index…

I’ll copy my musings below so you can see my train of thought if it’s any help:

func make_col_shape(mesh):
	var am = ArrayMesh.new()
	am.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh.get_mesh_arrays())
	var mdt = MeshDataTool.new()
	mdt.create_from_surface(am, 0)
	print("face count: ", mdt.get_vertex_count())
	var data = []
	for face in range(mdt.get_face_count()):
		data.append(mdt.get_face_vertex(0, face))
		data.append(mdt.get_face_vertex(1, face))
		data.append(mdt.get_face_vertex(2, face))
	print(data)

There’s almost certainly a really easy way to do this… The editor has a Create Trimesh Static Body tool but there’s no api sadly. Maybe just make a CollisionShape and add your vertices (or verts & indices) variable above as a PoolVector3Array.

Btw, the docs explicitly say: You can only use concave shapes within StaticBodies which I wasn’t aware of until now.

Collision shapes (3D) — Godot Engine (stable) documentation in English

DaddyMonster | 2022-01-11 21:33

:bust_in_silhouette: Reply From: Ninfur

I found one approach of adding collisions specifically to a height map based terrain, while still using triangle strips. But it doesn’t quite answer my original question.

This makes use of the HeightMapShape

var shape = HeightMapShape.new()
shape.map_width = chunk_size + 1
shape.map_depth = chunk_size + 1
var height_data = PoolRealArray()
for z in range(shape.map_depth):
	for x in range(shape.map_width):
		height_data.append(height_at(x - chunk_size/2, z - chunk_size/2))
shape.map_data = height_data

var collision_shape = CollisionShape.new()
collision_shape.shape = shape
add_child(collision_shape)

The HeightMapShape can also be created from a height map texture, see this thread.

:bust_in_silhouette: Reply From: Ninfur

From what I’ve gathered there seems to be no good way of doing this that works for an arbitrary shape. ConcavePolygonShape must be populated with faces consisting of 3 vertices each. However, since the point of a triangle strip is to only define unique vertices and connect them using indices, there is no good way of populating the ConcavePolygonShape. You can of course generate the faces based on the vertices + indices, but that kind of defeats the purpose of using triangle strips in the first place.

It is however possible to add convex collisions and height map based collisions, as described in the other answers.

In the end I decided to move away from triangle strips and use indexed triangles instead. This is supposedly as fast, or faster than using triangle strips. At the cost of slightly higher memory usage.