0 votes

My goal is to distribute grass mesh instances on a terrain mesh, created in Blender, using MultiMeshInstance.
Script gets all the triangles of a terrain mesh through MeshDataTool and places a MultiMeshInstance for each triangle. How to evenly distribute meshes inside a given triangle with respect to height and a normal direction of a triangle?
I have these 3 points, from documentation.

for i in mesh_data_tool.get_face_count():

    # Get the index in the vertex array.
    var a = mesh_data_tool.get_face_vertex(i, 0)
    var b = mesh_data_tool.get_face_vertex(i, 1)
    var c = mesh_data_tool.get_face_vertex(i, 2)

    # Get vertex position using vertex index.
    var ap = mesh_data_tool.get_vertex(a)
    var bp = mesh_data_tool.get_vertex(b)
    var cp = mesh_data_tool.get_vertex(c)

I set the instance's transform, like this:

    #Random scale and rotation
    var t = Transform().scaled(Vector3(rand_scale, rand_scale, rand_scale)).rotated(Vector3.UP, randf() * TAU)

    # This gives a point within a rectangle
    # Actually this doesn't work
    var rand_x = randf() * ap.length()
    var rand_z = randf() * bp.length()

    # Setting the transform
    t.origin = Vector3(rand_x, 0, rand_z)
    mm.set_instance_transform(i, t)

Why I don't use Populate Surface, because this way I can hide multi meshes on triangles which player can't see.

in Engine by (81 points)
edited by

1 Answer

+1 vote
Best answer

So I was trying to solve the exact same problem and also limit mesh instances based on vertex color.

I ended up looking into the Populate function used in the editor to get some ideas.
https://github.com/godotengine/godot/blob/07025e607dda2ab3b35bee65614347dc39f6f236/editor/plugins/multimesh_editor_plugin.cpp

I noticed that it uses a function on line 190 Vector3 pos = face.get_random_point_inside();

Unfortunately the face3 math library isn't exposed as far as I am aware. But looking in https://github.com/godotengine/godot/blob/07025e607dda2ab3b35bee65614347dc39f6f236/core/math/face3.cpp

We can find the function on line 153.

Using your bit of code I was able to figure it all out.

func get_valid_verts():
for i in mdt.get_face_count():
    # Get the index in the vertex array.
    var a = mdt.get_face_vertex(i, 0)
    var b = mdt.get_face_vertex(i, 1)
    var c = mdt.get_face_vertex(i, 2)
    # Get vertex position using vertex index.
    var ap = mdt.get_vertex(a)
    var bp = mdt.get_vertex(b)
    var cp = mdt.get_vertex(c)
    if mdt.get_vertex_color(a).r > 0.1:
        verts.append({"verts":[ap,bp,cp],"vertcol":mdt.get_vertex_color(a)})

So here for my approach I get all the face verts, but at the end I filter out those whose vertex colour is less than 0.1. This allows one to paint vertex colours in Blender for example or potentially also make use of the VPainter tool I imagine.

func get_random_point_inside(vertices) -> Vector3:
var a
var b
a = rand_range(0.0,1.0)
b = rand_range(0.0,1.0)
return vertices[0] * a + vertices[1] * (b - a) + vertices[2] * (1.0 - b)

Rewrote the Face3 function to return a random point given a set of 3 vertices.

func addGrass(a):
get_valid_verts()
multimesh.multimesh.instance_count = 5000
for i in multimesh.multimesh.instance_count:
    var xform = Transform()
    verts.shuffle()
    var randpos = get_random_point_inside(verts[0].verts)
    var pos = global_transform.xform(randpos)
    #print(pos)
    var basis_scale = Vector3(verts[0].vertcol.r,verts[0].vertcol.g,verts[0].vertcol.b)
    var basis = Basis(Vector3.UP, deg2rad(rand_range(0,359)))
    multimesh.multimesh.set_instance_custom_data(i,Color(
        basis_scale.x,
        basis_scale.y,
        basis_scale.z
    ))
    xform.origin = pos
    xform.basis = basis
    multimesh.multimesh.set_instance_transform(i,xform)

Put it all together.

Sorry code is a bit of a mess as I was tinkering to get this to work. Hope this helps.
enter image description here

by (48 points)
selected by

Just noticed there is a slight issue with the height as some are floating. Think this might be because they aren't being aligned to the normal of the face.

Figured out the floating instances. Turns out the "swap" is fairly important to ensure that the point is within the range of the triangle. I also flipped the test to be less than rather than greater than, so that was also causing issues.

As Godot doesn't have a swap function, I came up with this:

func get_random_point_inside(vertices,amount) -> Vector3:
var a
var b
a = rand_range(0.0,amount)
b = rand_range(0.0,amount)
if  a > b:
    var x
    var y
    x = a
    y = b
    b = x
    a = y
return vertices[0] * a + vertices[1] * (b - a) + vertices[2] * (1.0 - b)

enter image description here

This is cool! Sorry for the late reply.

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.