Alter a plane mesh programmatically with Godot 3.0.2

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()

 

Posted in 3D, Game Development and tagged , , , , .

2 Comments

    • 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).

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.