0 votes

Hi All. I'm looking for a way to populate a surface mesh with multimesh instances. Trouble is, my mesh is generated in code so I can't use MultimeshInstance3d's editor tools.

I suppose I could obtain vertex positions from the generated mesh and set instance transform of each multimesh instance to a vertex position but:
- this makes multimeshes be stuck to the mesh vertices as opposed to bein scattered randomly over the surface
- randomizing the instance transforms causes the instances to appear under or over the surface (which is a terrain, ie. not flat)
- mesh doesn't have a collision shape so casting rays from instances to get the y position isn't an option...

I am looking for a way to do this in code that works exactly like the editor tool: surface populate.

Anybody knows how?

Godot version 4.0
in Engine by (480 points)

1 Answer

0 votes

Yeah, I've done that before. By the sounds of it, your terrain mesh is procedural. The solution is to put the multimesh instance through the same algorithm as the terrain vertex placement.

You didn't say how you're doing this, say you're using OpenSimplexNoise. You set the seed, period, octaves and persistence. Say we start with a plane which we subdivide. Then with SurfaceTool and MeshDataTool we loop through each vertex offsetting the position. When we do this we call a method set_noise_position(v) which returns the repositioned vertex. Something like:

func set_noise_position(v):
    return v.y *= 1 + (noise.get_noise_3d(v.x, v.y, v.z) * scalar)

Now when you make your MultiMeshInstance you can just set the loop the MultiMesh setting the x and y randomly / according to whatever criteria you have and call set_noise_position(v) on the y. Remember to position the vector within the plane. Something like this:

func position_objs():
    randomize()
    for i in objs.multimesh.instance_count:
        var v = Vector3(rand_range(-plane_size/2, plane_size/2), 
                        0.0, 
                        rand_range(-plane_size/2, plane_size/2))
        v = set_noise_position(v)
        objs.multimesh.set_instance_transform(i, Transform(Basis.IDENTITY, v)

Done. Just make sure the origin for the multimesh is at the bottom so it sits nicely on the ground. Now on slopes you might need to recalculate the basis with a y on the normal if its a wide object.

Now I did mine on a cube-sphere with chunks so I have to take the xform / xform_inv but hopefully you can just use a simple basis identity.

by (1,877 points)

Hi. I'm actually using an imported heightmap (from Gaea) but I guess I can just use getpixel() instead of getnoise_3d just like I do when creating a collision shape from this heightmap image. Positioning the multimesh instances is easy.

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.