Regenerate normals after MeshDataTool vertex adjustments

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

For initial generation of mesh using SurfaceTool (and adding smoothing group above add_vertex calls, and adding ‘generate_normals’ after adding the verts / indices works quite nicely.

However, if I make adjustments to the mesh using MeshDataTool, then use SurfaceTool to commit that (arraymesh) to the object being updated, the normals no longer seem to work even when calling ‘generate_normals’ again on the surfacetool.

Has anyone else seen this issue – could very well be an issue with my code. I’ll paste it here later on today if this doesn’t sound familiar to anyone as to why this may occur. Any thoughts?

So, I took a lot at SurfaceTool source, and it appears at the end of “SurfaceTool::generate_normals”, there’s a call to clear the smooth groups

if (was_indexed) {
	index();
	smooth_groups.clear();
}

Not sure why this is cleared here, but sure there’s some reason. If I add the call to " back before calling generate_normals, it doesn’t seem to help though, so not sure this is my issue…

My update code is basically this this:

func update_mesh(delta):
      if _result_mesh == null:
            _result_mesh = self.mesh     
      _mesh_data_tool.create_from_surface(_result_mesh, 0)
     for i in range(_mesh_data_tool.get_vertex_count()):
          _vertex = _mesh_data_tool.get_vertex(i)
         
            #... some code to perform vertex adjustment and call _mesh_data_tool.set_vertex

      _result_mesh.surface_remove(0)
      _mesh_data_tool.commit_to_surface(_result_mesh)
      _surface_tool.create_from(_result_mesh, 0)
      #_surface_tool.add_smooth_group(true)       # gave this a try - didn't work
      _surface_tool.generate_normals(true)
      _result_mesh = _surface_tool.commit()
      self.mesh = _result_mesh

  The initial programmatic mesh looks smooth, but as soon as the first update to it runs, the shading is no longer smooth.  Anyone happen to have any thoughts or suggestions? 

GodotRobot | 2020-05-02 22:18

:bust_in_silhouette: Reply From: btuso

I had the same issue when using the MeshDataTool to modify a mesh.

The problem is that the MeshDataTool does not generate the corresponding ArrayMesh.ARRAY_NORMAL when creating the ArrayMesh.

The workaround I found is to recreate the mesh using the MeshDataTool faces and setting the normal for each vertex accordingly.

For example:

var vertices = PoolVector3Array()
var normals = PoolVector3Array()
var uvs = PoolVector2Array()
for face in meshData.get_face_count():
	var normal = meshData.get_face_normal(face)
	for vertex in range(0, 3):
		var faceVertex = meshData.get_face_vertex(face, vertex)
		vertices.push_back(meshData.get_vertex(faceVertex))
		uvs.push_back(meshData.get_vertex_uv(faceVertex))
		normals.push_back(normal)

var arrays = []
arrays.resize(ArrayMesh.ARRAY_MAX)
arrays[ArrayMesh.ARRAY_VERTEX] = vertices
arrays[ArrayMesh.ARRAY_NORMAL] = normals
arrays[ArrayMesh.ARRAY_TEX_UV] = uvs
var arr_mesh := ArrayMesh.new()
arr_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)

self.mesh = arr_mesh

As you can see, the problem with this solution is that you need to re-create everything else the MeshDataTool preserves, such as the UVs.

Before:
Mesh commited from mesh data tool
After:
Using face normals to shade each triangle

Be aware that Mesh.PRIMITIVE_TRIANGLES and Mesh.PRIMITIVE_TRIANGLE_STRIP behave different, since vertices (and their normals) are shared between faces for the latter. So make sure you’re using the correct approach for your base mesh.

Another option would be to call set_vertex_normal for each vertex in your MeshDataTool, but you’d have to figure out which normal to use if your base mesh uses PRIMITIVE_TRIANGLE_STRIP as the primitive type, or you could get some weird results, like this:

for face in meshData.get_face_count():
            # Set the face's normal as the normal for one of its points
	var vertex = meshData.get_face_vertex(face, 0)
	var normal = meshData.get_face_normal(face)
	meshData.set_vertex_normal(vertex, normal)

Which results in:

Normals per vertex