+8 votes

As the title suggests, I'm trying to create a mesh at runtime through code (GDScript). However I'm not having much luck.

I'm starting with a simple quad for now, with the intention to move on to more interesting things once I understand how this works. The code looks like this:

extends StaticBody

func make_quad(): #mesh
    var array = Array()
    array.push_back(Vector3(0,0,0))
    array.push_back(Vector3(0,1,0))
    array.push_back(Vector3(1,1,0))
    array.push_back(Vector3(1,0,0))

    print(array.size()) #expected: 4, result: 4
var mesh = Mesh.new()
mesh.add_surface(Mesh.PRIMITIVE_TRIANGLE_STRIP, array)
print(mesh.surface_get_array_len(0)) #expected: 4, result: -1
    return mesh

func _init():
    var mesh = make_quad()
    var meshinst = MeshInstance.new()
    meshinst.set_mesh(mesh)
    add_child(meshinst)
    print("surface count ", mesh.get_surface_count()) #expected: 1, result: 0
    print("format ", mesh.surface_get_format(0)) #expected: ???, result: 0
    print("primitive ", mesh.surface_get_primitive_type(0)) #expected: 5, result: 1

Then in the root Spatial node's _ready() function I load the script file, instantiate an object and add_child() it to the scene.

The docs aren't clear on what format the array parameter to add_surface() takes; I've tried an Array of Vector3s as in the above code, a Vector3Array and an Array of [x, y, z] triplets. All give the same result.

The code appears to be quietly failing on the add_surface() function, as the debugger shows an error at that line and also on the subsequent surface_* calls, but it doesn't give any details of the error which makes it hard to debug!

The doc page for Mesh talks a lot about "format" and the Mesh class contains a number of flags related to format, but there is no way to pass a format parameter so I don't see how to set this.

in Engine by (30 points)

3 Answers

+2 votes
Best answer

Hello,
I also messed around with procedural meshes. And I also tried to use the add_surface() function with no success ;(
But there are some Classes provided wich make creating a mesh even easier and also give you the power to access UV's and so on..
You use the Surface tool which allows you to create a surface pretty easy and with a lot of control.
Here is some simple example code:

var surface = SurfaceTool.new()
var datatool = MeshDataTool.new()
var mesh = Mesh.new()

var array = Array()
array.push_back(Vector3(0,0,0))
array.push_back(Vector3(0,1,0))
array.push_back(Vector3(1,1,0))
array.push_back(Vector3(1,0,0))

wg.surface.begin(VisualServer.PRIMITIVE_TRIANGLES)
for v in vertex_array
    #surface.set_material(material)
    #surface.add_uv(Vector2())
    #surface.add_normal(Vector3())
    surface.add_vertex(v)
datatool.create_from_surface(surface.commit(), 0)
datatool.commit_to_surface(mesh)

I think you also can skip the part with the MehsDatatTool and just use the commit() function like that:

Mesh surfaceTool.commit( Mesh existing=Object() )

I think when I tried it, it didn't worked for me but it was some time ago so maybe I made another mistake.
If you are interested you also can have a look at a voxel game I created with Godot "a minecraft clone" there I use the surface tool too.
Godot-Voxel-Game-MineCraftClone

by (331 points)
edited by
Thanks for the reply! I would at some point like to get to the bottom of how to create meshes directly, simply because I feel it's useful to have a deeper understanding of such things, but that surface tool looks great. My next question would have been how to set up uv arrays but you've already answered that so thanks again!
If you are messing around next time could you try if it works without the MeshDataTool?
Because I have no time today. And I am really interested if it does work!
If you don't i may try it tomorrow. Than I will tell you about the results.
Yeah I had a go and it seems to work fine without using MeshDataTool. I was able to use the Mesh returned by SurfaceTool.commit() directly.
Ah I spoke too soon about the uv question being answered. I can set up the vertices fine now so I'm rendering a black quad, but the code is silently failing on the calls to `SurfaceTool.add_uv()` so I'm unable to render textures on my mesh.

Here's what I've got so far:

    func make_quad(): #mesh
        var vert_array = Array()
        var uv_array = Array()
        var st = SurfaceTool.new()
       
        vert_array.push_back(Vector3(0,0,0))
        vert_array.push_back(Vector3(0,1,0))
        vert_array.push_back(Vector3(1,1,0))
       
        vert_array.push_back(Vector3(0,0,0))
        vert_array.push_back(Vector3(1,1,0))
        vert_array.push_back(Vector3(1,0,0))
       
        uv_array.push_back(Vector2(0,0))
        uv_array.push_back(Vector2(0,1))
        uv_array.push_back(Vector2(1,1))
       
        uv_array.push_back(Vector2(0,0))
        uv_array.push_back(Vector2(1,1))
        uv_array.push_back(Vector2(1,0))
       
        st.begin(Mesh.PRIMITIVE_TRIANGLES)
        for i in range(6):
            st.add_vertex(vert_array[i])
            st.add_uv(uv_array[i])
        return st.commit()

Debugger is reporting 6 errors, one for each call to `add_uv()`. Any ideas what I'm doing wrong here?
I think the way it works is that you first set up the properties for one vertex than the next added vertex will have these settings:

surface.set_material(material)
surface.add_uv(Vector2())
surface.add_normal(Vector3())
surface.add_vertex(v)

So you have to do it in this order.
Yes! You're right, all I had to do was reverse the calls to add_vertex() and add_uv() to eliminate the errors and make it render correctly. Thanks dude.
+2 votes

So, I dug through the Godot source code a bit, and finally managed to get your example to work and display a black triangle. The three undocumented "gotchas" are that #1 it expects an array of length 9 (VisualSurface::ARRAY_MAX), and #2, the vertices actually go into a slot based on a flag (e.g. ARRAY_VERTEX), and #3, it must be a Vector3Array.

Working example based on above example:

const ARRAY_MAX = 9

func make_quad():
    var vertices = Vector3Array()
    vertices.push_back(Vector3(0,0,0))
    vertices.push_back(Vector3(0,1,0))
    vertices.push_back(Vector3(1,1,0))
    vertices.push_back(Vector3(1,0,0))

    var array = Array()
    array.resize(ARRAY_MAX)
    array[Mesh.ARRAY_VERTEX] = vertices

    var mesh = Mesh.new()
    mesh.add_surface(Mesh.PRIMITIVE_TRIANGLE_STRIP, array)
    print(mesh.surface_get_format(0))
    print(mesh.surface_get_array_len(0))
    return mesh

func _init():
    var mesh = make_quad()
    var meshinst = MeshInstance.new()
    meshinst.set_mesh(mesh)
    add_child(meshinst)
    print("surface count ", mesh.get_surface_count())
by (18 points)

Thank you for the 'gotchas"!

And one more thing, the vertices[2] should be swaped with vertices[3] if we want to draw quad because the Clock-Wise orientation.

+1 vote

Also, you can use ImmediateGeometry class for procedural meshes, as explained in this video tutorial: https://youtu.be/y0mat87aWTk?t=11m30s

by (16 points)

I'm downloading the tutorial to watch it later on media player, by the way do you know whether one can resize the arrays on the fly and change the vertices position without having to recreate some new ones.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.

Categories