Iterating and Aggregating Meshes

I have a method that locates all mesh filters attached to a transform and its children and aggregates its vertices and triangles. A copy of the code is further down.

[Edit: Based on skovacs1's answer and further troubleshooting.] The below code works as expected. The mirroring that is mentioned was due to the display method used for debugging. So the only remaining question is whether there is a more efficient way of aggregating meshes for purposes other than display.

The problem I'm having is that the resulting aggregate mesh is mirrored along the x-axis. So only if I view the aggregate mesh with Scale X = -1 is everything good.

Two questions:

  1. [Edit: No longer applies] Anyone see something I'm doing wrong? E.g. Applying the transform incorrectly.
  2. Is this the most efficient way of doing this sort of thing?

Code (C#)

private void AggregateTransform(Transform source
    , List<float> aggregateVerts
    , List<int> aggregateIndices
    , List<int> indexMap)
{
    // Get all mesh filters on this transform and its children.
    MeshFilter[] filters = source.GetComponentsInChildren<MeshFilter>();

    if (filters == null)
    {
        Debug.LogWarning("No meshes found in " + source.name + ".");
    }
    else
    {
        int currentIndex = aggregateVerts.Count / 3;
        foreach (MeshFilter filter in filters)
        {
            Mesh mesh = filter.sharedMesh;
            Transform trans = filter.transform;
            if (mesh != null)
            {
                // Loop through all vertices, transform to world position,
                // and add to aggregate.
                indexMap.Clear();
                for (int i = 0; i < mesh.vertexCount; i++)
                {
                    Vector3 vert = trans.TransformPoint(mesh.vertices*);*
 *aggregateVerts.Add(vert.x);*
 *aggregateVerts.Add(vert.y);*
 *aggregateVerts.Add(vert.z);*
 *indexMap.Add(currentIndex);*
 *currentIndex++;*
 *}* 
 *// Loop through all indices.  Add to aggregate list,* 
 *// mapping from local to global index value.*
 *for (int i = 0; i < mesh.triangles.Length; i++)*
 *{*
 _aggregateIndices.Add(indexMap[mesh.triangles*]);*_
 _*}*_
 _*IncrementProgressBar();*_
 _*}*_
 _*}*_
 _*}*_
_*}*_
_*```*_

It is possible that there is a problem with that portion of code which you use to make this data viewable.

What is going on with currentIndex and indexMap? You go through a lot of work with currentIndex, but all you really want to do is add your triangles to the aggregate triangles, right? wouldn't that simply entail getting the offset for the vertices in the aggregate vertices?

Here's a simpler version of what your code does (each vertex is still split into separate components and the triangle indices index to the start index of each vertex):

private void AggregateMesh(Transform source,
                           List<float> aggregateVerts,
                           List<int> aggregateTriangles) {
    MeshFilter[] filters = source.GetComponentsInChildren<MeshFilter>();

    if(filters == null) {
        Debug.LogWarning("No meshes found in " + source.name + ".");
        return;
    }
    foreach (MeshFilter filter in filters) {
        Mesh mesh = filter.sharedMesh;
        if(mesh == null) continue;

        int currentIndex = aggregateVerts.Count;
        int i = 0;
        Transform trans = filter.transform;
        for(; i < mesh.vertices.Length; i++) {
            Vector3 vert = trans.TransformPoint(vertices*);*
 *aggregateVerts.Add(vert.x);*
 *aggregateVerts.Add(vert.y);*
 *aggregateVerts.Add(vert.z);*
 *}*
 *for(i = 0; i < mesh.triangles.Length; i++)*
 <em>aggregateTriangles.Add(mesh.triangles _* 3 + currentIndex);_</em>
 _*IncrementProgressBar();*_
 _*}*_
_*}*_
_*```*_

The answer was hiding in plain sight.

First, a review of the requirements implicit in the question:

  • The only data needed from the MeshFilters is the vertices and triangles. The uv's and normals don't matter.
  • The vertices in the aggregate mesh need to be in world space.
  • The solution needs to support more more than 64K vertices in the aggregate mesh.

Another requirement is that the vertices be a flattened array, rather than an array of Vector3 structures. But that requirement effects the detail of the implementation, not the answer.

And the answer is to use Mesh.CombineMeshes.

Most of the documentation involving the Mesh.CombineMeshes function discusses using it to combine meshes that share the same material. But, if all you need are the vertices and triangles, then it can be used to combine meshes with different materials.

Just prepare and use Mesh.CombineMeshes as shown in the function's example code, then extract the vertices and triangles from mesh.vertices and mesh.triangles.

The 64K vertices limit for a single Mesh object applies. But that can be worked around using a batching process:

  1. Get an array of MeshFitlers to combine.
  2. Batch together the filters with each batch containing less than 64K vertices.
  3. Run each batch through Mesh.CombineMeshes. (Separately)
  4. Aggregate the vertices and triangles from each batch into aggregate vertices and triangle arrays.

As far as the performance goes: The aggregation took over 2 minutes to complete using the original algorithm. (See question.) It took less than 50 milliseconds using Mesh.CombineMeshes.

A slight performance boost.