How to scatter multimesh instances randomly over a mesh surface that is generated in game?

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

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?

:bust_in_silhouette: Reply From: DaddyMonster

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():
    for i in objs.multimesh.instance_count:
        var v = Vector3(rand_range(-plane_size/2, plane_size/2), 
                        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.

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

Macryc | 2022-06-03 14:57