Keith M
Keith M

Reputation: 1249

Modify vertices at runtime

For a small set of custom terrain tools I'm making to be used at runtime in Unity I need to be able to "paint" areas of the terrain to raise and lower it.

To do that I have this function:

public void PaintRaise(Vector3 center, float radius, float power) {
    Mesh mesh = this.gameObject.GetComponent<MeshFilter>().sharedMesh;
    Vector3[] verts = new Vector3[mesh.vertices.Length];

    for (int i = 0; i < mesh.vertices.Length; ++i) {
        // method for getting distance, faster then Vector3.Distance
        var heading = mesh.vertices[i] - center;
        var distance = heading.magnitude;
        var direction = heading / distance;
        if (heading.sqrMagnitude < radius * radius) {
            verts[i] = new Vector3(
                mesh.vertices[i].x,
                mesh.vertices[i].y + power,
                mesh.vertices[i].z);
        } else {
            verts[i] = mesh.vertices[i];
        }

    }
    mesh.vertices = verts;
}

In theory, it should work perfectly applied on a GameObject with a mesh, and it does! But unfortunately it is very, very slow. It takes around 10 seconds to process for a radius of 5 units, even with this improved distance finding method.

It runs just as slow in both the editor and in a compiled build.

enter image description here

As you can see the profiler shows the Update() function that calls the PaintRaise function taking ~13 seconds to execute a single frame, most of it being the garabage collector!

Why is it running slow and how can I speed it up (so it ideally takes a few milliseconds to execute)?

EDIT

Enabling deep profiling makes it even more confusing! Why is getting the mesh vertices taking 13 seconds due to the GC?

enter image description here

Upvotes: 1

Views: 4800

Answers (1)

Keith M
Keith M

Reputation: 1249

After doing some research I found out the vertices is not actually a variable like it looks like.

There are several reasons for the new SetVertices / SetIndices / SetUVs / methods. First of all the old vertices property is not a variable. This fact has confused many users. It's a property. So actually there's a get method and a set method but the usage just looks like a variable.

What most did not understand is that the getter does not return a reference to an internal array but returns a copy. This is necessary since the actual vertex data is stored in the native C++ side of Unity.

Changing my function to use Get() and Set() eliminated the extra garbage collection created by using the vertices property and made it run buttery smooth.

public void PaintRaise(Vector3 center, float radius, float power) {
    Vector3 localPoint = transform.InverseTransformPoint(center);
    Mesh mesh = this.gameObject.GetComponent<MeshFilter>().sharedMesh;
    List<Vector3> verts = new List<Vector3>(); 
    mesh.GetVertices(verts);

    for (int i = 0; i < verts.Count; ++i) {
        var heading = verts[i] - center;
        var distance = heading.magnitude;
        var direction = heading / distance;
        if (heading.sqrMagnitude < radius * radius) {
            verts[i] = new Vector3(
                verts[i].x,
                verts[i].y + power,
                verts[i].z);
        }
    }
    mesh.SetVertices(verts);
}

Upvotes: 1

Related Questions