As I’m playing around with Godot and meshes (programmatically generated meshes to be precise).
I’ve found something that I believe is worth sharing.
I have to mention that I haven’t found a real life use of this technique, say to generate a heightmap. Yet I believe it may exist a way to do it.
How to use SurfaceTool and MeshDataTool
I’ve created a simple script that should allow to understand it just by reading the code, it’s quite simple once you know how to use those tools.
tool extends Spatial export(int, 1, 512) onready var size setget _setPlaneSize func _setPlaneSize(_newval): size = _newval print([size, _newval]) var startt = float(OS.get_ticks_msec()) var mdt = MeshDataTool.new() var st = SurfaceTool.new() var plane_mesh = PlaneMesh.new() plane_mesh.subdivide_width = _newval plane_mesh.subdivide_depth = _newval plane_mesh.size = Vector2(_newval, _newval) st.create_from(plane_mesh, 0) var array_plane = st.commit() var error = mdt.create_from_surface(array_plane, 0) for i in range(mdt.get_vertex_count()): var vtx = mdt.get_vertex(i) vtx.y = randf() * 1 mdt.set_vertex(i, vtx) for s in range(array_plane.get_surface_count()): array_plane.surface_remove(s) mdt.commit_to_surface(array_plane) st.create_from(array_plane, 0) st.generate_normals() $MeshInstance.mesh = st.commit() var endtt = float(OS.get_ticks_msec()) print("Execution time: %.2f" % ((endtt - startt)/1000)) #func _ready(): # _setPlaneSize(size)
You have to place the script in a scene with the following elements (same names, or change the code accordingly):
Spatial (attach script here)
|- DirectionalLight
|- MeshInstance
The script should be attached to the root node, and actually, the directional light is needed just to see the correct creation of the normals.
Once done you should see the new property “size”, when you set it the code will generate a “heightmap”, the size you have established.
Please note that depending on your machine it may take a while as you go further than 256 (on my slow ION pc it takes in seconds per size: 0.5 x 64, 2 x 128, 13 x 256).
The role of SurfaceTool
So, to go deeper, st (the surface tool), can create an array mesh starting from a previously generated mesh (it can be any type, in this case i have used the plane mesh), this way:
var st = SurfaceTool.new() # create a plane mesh of _newval size # and same amount of subdivisons var plane_mesh = PlaneMesh.new() plane_mesh.subdivide_width = _newval plane_mesh.subdivide_depth = _newval plane_mesh.size = Vector2(_newval, _newval) # create an arrayMesh to be used by the MeshDataTool st.create_from(plane_mesh, 0) var array_plane = st.commit()
The Mesh Data Tool
Once converted into an ArrayMesh, it can be used by the MeshDataTool, as the other one, the PlaneTool, cannot modify meshes, just create them.
Now that the MeshDataTool can place its hands on the plane we can work this way to alter some vertex (the y axis in this case)
# In practice we copy the array mesh into the MeshDataTool var error = mdt.create_from_surface(array_plane, 0) for i in range(mdt.get_vertex_count()): # Here we run through all the vertices of the plane var vtx = mdt.get_vertex(i) # A random value to modify the y position of our vertex vtx.y = randf() * 1 # We tell the MeshDataTool to modify the vertex accordingly mdt.set_vertex(i, vtx)
Generating normals and place the mesh in the nodetree
Good, almos done, now the next part consists in removing the previous meshes (if any), and generate the normals for our modified plane.
How do you create the normals, well, godot have this nice feature that allow us to avoid using vector math, ad does it all for us, but you have to move the mesh back to the SurfaceTool (yeah, I wonder why it’s not all in the same object, instead of having 2, but well) generate_normals() does it all.
Then, finally, we take our newly created mesh and we want to put it somewhere, so that anyone can see it, as we are proud parents (commit() returns the final mesh):
# To avoid adding new surfaces to the mesh instance # I had to run through all the existing surfaces and remove them for s in range(array_plane.get_surface_count()): array_plane.surface_remove(s) # Then we are ready to place the modified plane # back into the original arraymesh mdt.commit_to_surface(array_plane) # This last step is needed in order to generate the normals # without the need of doing it manually st.create_from(array_plane, 0) st.generate_normals() #finally, attach the mesh to the MeshInstance node $MeshInstance.mesh = st.commit()
Thanks for taking the time to document the process.
Very useful and aplicable.
Hahaha… LOL, not really applicable, at least not for me, when I realized that it’s quite hard to forsee where a vertex is positioned in the plane mesh given the index only..
Anyhow I’ll try to find the time to better understand the usage of the Mesh Data Tool.. hopefully by the time the official documentation will be improved (last version I checked was much better than a year ago).