Improved MeshCombineUtility

I recently hit a problem where I needed to combine meshes into a single renderer, but the meshes were using different materials. The built-in MeshCombineUtility doesn’t support multiple submeshes. So, I rewrote it.

It’s also now set up as an accumulating object, so you can create a MeshCombineUtility and add instances to it one by one, instead of needing to collect them all up in a list and passing them in in one go. Though it’s a little more efficient if you can do that.

It uses LINQ, so I guess it doesn’t work on iOS - but this is the kind of thing that you should probably be doing in the editor or in AssetPostProcessors anyway.

569390–20262–$MeshCombineUtility.cs (9.88 KB)

To go with this, I also amended Neodrop’s Combine Children Extended to combine everything into a single mesh with multiple materials:

569390–20263–$CombineChildren.cs (4.62 KB)

Hope you find it useful. Let me know if you come up with any improvements or can point out any bugs. I think that the triangle strip handling is a little more fragile than the original - if you tell it you’re using strips, and then you give it something that doesn’t have strips, then it’ll crash, while UT’s original version would just silently drop back to using triangle lists. I’m not convinced that their way is better though.

why submesh support?
How can you handle more than 64k verts with your script?

I needed submesh support because the Unity tree renderer needs trees to be single meshes. The trees generated by e.g. Treepad use separate materials for the branches and the leaves, and they were coming in as separate subobjects. (Obviously, in the long run, it’d be better to atlas textures and combine the materials into one, but I needed something fast, and combining the materials might not always be an option for everyone).

The combiner is still limited to 64k vertices, just like the old one.

1 Like

I’m getting 3 errors in my project:

Assets/MeshCombineUtility.cs(93,51): error CS0411: The type arguments for method `System.Linq.Enumerable.Select<TSource,TResult>(this System.Collections.Generic.IEnumerable, System.Func<TSource,int,TResult>)’ cannot be inferred from the usage. Try specifying the type arguments explicitly

Assets/MeshCombineUtility.cs(93,19): error CS1502: The best overloaded method match for `System.Collections.Generic.List<UnityEngine.Vector3>.AddRange(System.Collections.Generic.IEnumerable<UnityEngine.Vector3>)’ has some invalid arguments

Assets/MeshCombineUtility.cs(93,19): error CS1503: Argument #1' cannot convert object’ expression to type `System.Collections.Generic.IEnumerable<UnityEngine.Vector3>’

Try changing that line (line 93) from:

_vertices.AddRange(instance.mesh.vertices.Select(instance.transform.MultiplyPoint));

to

_vertices.AddRange(instance.mesh.vertices.Select(v => instance.transform.MultiplyPoint(v)));

how would i run this in the editor ?
I have an andriod game that is ready to go , aside from 2 enviromental assets which just take up too much processing power( game runs at full speed if I delete the assets

When I combine the mesh with combine children extend and your mesh combine utility script, my meshrenderer is false and if I active it, no draw call win maybe I misunderstood something ?

All my combine mesh got vertices but no triangles :x

Is it possible tho call Combine Mesh Utility with JS (UnityScript)?

Yes just drop the .cs file into Plugins, then it’ll get compiled first and be available. Or switch to C# - not that hard really.

I’ve converted this to work as an AssetPostprocessor.

using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;

// This script require the MeshCombineUtility.cs
public class ImportTerrainModels : AssetPostprocessor
{
   
    public static bool importPostprocessEnabled = true;
   
    public static bool generateTriangleStrips = true;    
    public bool addMeshColliderToCombinedMesh = true;
    public bool useEditorImportSettingsForCollisionGeneration = true;    
    public static string ignoreAssetPathContaining = "/.";
    public static string includeAssetPathContaining = "Terrain-Models";
   
    public bool castShadow = false;
    public bool recieveShadow = false;
    public bool playAnimationAutomatic = false;
   
    private ModelImporter modelImporter;
   
    // Apply this postprocessor early
    public override int GetPostprocessOrder()
    {
        return 0;
    }
   
    void OnPostprocessModel(GameObject g)
    {

        modelImporter = assetImporter as ModelImporter;
        bool assetPathFilter =
            (includeAssetPathContaining == "" || modelImporter.assetPath.ToLower().Contains(includeAssetPathContaining.ToLower())) &&
                (ignoreAssetPathContaining == "" || !modelImporter.assetPath.ToLower().Contains(ignoreAssetPathContaining.ToLower()));
       
        if (importPostprocessEnabled && assetPathFilter)
        {

            var filters = g.GetComponentsInChildren<MeshFilter>();
            Matrix4x4 myTransform = g.transform.worldToLocalMatrix;
            var materialToMesh = new Dictionary<Material, List<MeshCombineUtility.MeshInstance>>();
           
            foreach (var sourceFilter in filters)
            {
                Renderer curRenderer = sourceFilter.renderer;
                if (curRenderer == null || !curRenderer.enabled) continue;
               
                var instance = new MeshCombineUtility.MeshInstance
                {
                    mesh = sourceFilter.sharedMesh,
                    transform = myTransform * sourceFilter.transform.localToWorldMatrix
                };
                if(instance.mesh == null) continue;
               
                Material[] materials = curRenderer.sharedMaterials;
                for (int m = 0; m < materials.Length; m++)
                {
                    instance.subMeshIndex = Math.Min(m, instance.mesh.subMeshCount - 1);
                   
                    List<MeshCombineUtility.MeshInstance> objects;
                    if (!materialToMesh.TryGetValue(materials[m], out objects))
                    {
                        objects = new List<MeshCombineUtility.MeshInstance>();
                        materialToMesh.Add(materials[m], objects);  
                    }
                    objects.Add(instance);
                }
            }
           
            int targetMeshIndex = 0;
            foreach(var de in materialToMesh)
            {
                foreach (var instance in de.Value)
                    instance.targetSubMeshIndex = targetMeshIndex;
                ++targetMeshIndex;
            }

            Mesh mesh = MeshCombineUtility.Combine(materialToMesh.SelectMany(kvp => kvp.Value), generateTriangleStrips);
            bool meshAssigned = false;

            foreach (MeshFilter filter in filters)
            {
                if (!meshAssigned)
                {
                    Debug.Log("Add to mesh: "+filter.sharedMesh.name);
                    DeepCopyMeshInto(mesh, filter.sharedMesh);
                    //filter.sharedMesh = mesh;

                    if (!g.GetComponent<MeshFilter>()) g.gameObject.AddComponent<MeshFilter>();
                    var rootFilter = g.GetComponent<MeshFilter>();
                    if (!g.GetComponent<MeshRenderer>()) g.gameObject.AddComponent<MeshRenderer>();
               
                    rootFilter.sharedMesh = filter.sharedMesh;
                    g.renderer.materials = materialToMesh.Keys.ToArray();
                    g.renderer.enabled = true;

                    UnityEngine.Object.DestroyImmediate(filter.gameObject, true);
               
                    meshAssigned = true;
                 
                }
                else
                {
                    Mesh.DestroyImmediate(filter.sharedMesh);
                    filter.sharedMesh = null;
                    // Remove unused free meshes
                    UnityEngine.Object.DestroyImmediate(filter.gameObject, true);
                }
            }
        }
    }

    void DeepCopyMeshInto(Mesh fromMesh, Mesh toMesh)
    {    
        int vertexCount = fromMesh.vertexCount;
        int triangleCount = fromMesh.triangles.Length;

        toMesh.triangles = new int[triangleCount];
        toMesh.vertices = new Vector3[vertexCount];
        toMesh.normals = new Vector3[vertexCount];
        toMesh.colors = new Color[vertexCount];
        toMesh.uv = new Vector2[vertexCount];
        toMesh.uv1 = new Vector2[vertexCount];
        toMesh.tangents = new Vector4[vertexCount];

    //    toMesh.triangles = fromMesh.triangles;
        toMesh.vertices = fromMesh.vertices;
        toMesh.normals = fromMesh.normals;
        toMesh.colors = fromMesh.colors;
        toMesh.uv = fromMesh.uv;
        toMesh.uv1 = fromMesh.uv1;
        toMesh.tangents = fromMesh.tangents;

        toMesh.subMeshCount = fromMesh.subMeshCount;


        for(int i=0; i < fromMesh.subMeshCount; i++){
            toMesh.SetTriangles(fromMesh.GetTriangles(i),i);
        }

        toMesh.RecalculateBounds ();
        toMesh.name = fromMesh.name; 
    }

}

Got the following errors in Unity 5

(110/46) error CS1061: Type UnityEngine.Mesh' does not contain a definition for GetTriangleStrip’ and no extension method GetTriangleStrip' of type UnityEngine.Mesh’ could be found (are you missing a using directive or an assembly reference?)

(153,87) error CS1061: Type UnityEngine.Mesh' does not contain a definition for GetTriangleStrip’ and no extension method GetTriangleStrip' of type UnityEngine.Mesh’ could be found (are you missing a using directive or an assembly reference?)

(152,17) error CS1502: The best overloaded method match for `MeshCombineUtility.PrepareForAddingStrips(int, System.Collections.Generic.IEnumerable)’ has some invalid arguments

(152,17) error CS1503: Argument #2' cannot convert object’ expression to type `System.Collections.Generic.IEnumerable’

(187,22) error CS1061: Type UnityEngine.Mesh' does not contain a definition for SetTriangleStrip’ and no extension method SetTriangleStrip' of type UnityEngine.Mesh’ could be found (are you missing a using directive or an assembly reference?)

Any one with the fix?

1 Like

Hello idurvesh,

To fix it please change

  • GetTriangleStrip to GetTriangles and
  • SetTriangleStrip to SetTriangles

Probable it will be enough for other errors, if not - feel free to post your script here in the thread and I will check it for you.

Errors moving it to unity 5

Assets/Scripts/3rd Party/$MeshCombineUtility.cs(93,51): error CS0411: The type arguments for method `System.Linq.Enumerable.Select<TSource,TResult>(this System.Collections.Generic.IEnumerable, System.Func<TSource,int,TResult>)’ cannot be inferred from the usage. Try specifying the type arguments explicitly

Assets/Scripts/3rd Party/$MeshCombineUtility.cs(93,19): error CS1502: The best overloaded method match for `System.Collections.Generic.List<UnityEngine.Vector3>.AddRange(System.Collections.Generic.IEnumerable<UnityEngine.Vector3>)’ has some invalid arguments

Assets/Scripts/3rd Party/$MeshCombineUtility.cs(93,19): error CS1503: Argument #1' cannot convert object’ expression to type `System.Collections.Generic.IEnumerable<UnityEngine.Vector3>’

code:
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class MeshCombineUtility
{
private readonly List _colors = new List();
private readonly bool _generateStrips;
private readonly List _normals = new List();
private readonly Dictionary<int, List> _strip = new Dictionary<int, List>();
private readonly List _tangents = new List();
private readonly Dictionary<int, List> _triangles = new Dictionary<int, List>();
private readonly List _uv = new List();
private readonly List _uv1 = new List();
private readonly List _vertices = new List();

///


/// Creates a new, empty MeshCombineUtility object for combining meshes.
///

/// true if the meshes you’re going to combine are all triangle-strip based; false if you just want to use triangle lists.
public MeshCombineUtility(bool generateStrips)
{
_generateStrips = generateStrips;
}

///


/// Allocate space for adding a load more vertex data.
///

/// The number of vertices you’re about to add.
public void PrepareForAddingVertices(int numVertices)
{
int shortfall = numVertices - (_vertices.Capacity - _vertices.Count);

_vertices.Capacity += shortfall;
_normals.Capacity += shortfall;
_tangents.Capacity += shortfall;
_uv.Capacity += shortfall;
_uv1.Capacity += shortfall;
_colors.Capacity += shortfall;
}

///


/// Allocate space for adding a load more triangles to the triangle list.
///

/// The index of the submesh that you’re going to add triangles to.
/// The number of triangle indicies (number of triangles * 3) that you want to reserve space for.
public void PrepareForAddingTriangles(int targetSubmeshIndex, int numIndices)
{
if (!_triangles.ContainsKey(targetSubmeshIndex))
_triangles.Add(targetSubmeshIndex, new List());

int shortfall = numIndices - (_triangles[targetSubmeshIndex].Capacity - _triangles[targetSubmeshIndex].Count);
_triangles[targetSubmeshIndex].Capacity += shortfall;
}

///


/// Allocate space for adding a load more triangle strips to the combiner.
///

/// The index of the submesh you’re going to add strips to.
/// A sequence of strip lengths (in number-of-indices). These numbers will be used to automatically calculate how many bridging indices need to be added.
public void PrepareForAddingStrips(int targetSubmeshIndex, IEnumerable stripLengths)
{
if (!_strip.ContainsKey(targetSubmeshIndex))
_strip.Add(targetSubmeshIndex, new List());

int requiredCapacity = _strip[targetSubmeshIndex].Count;

foreach (int srcStripLength in stripLengths)
{
int adjStripLength = srcStripLength;
if (requiredCapacity > 0)
if ((requiredCapacity & 1) == 1)
adjStripLength += 3;
else
adjStripLength += 2;
requiredCapacity += adjStripLength;
}

if (_strip[targetSubmeshIndex].Capacity < requiredCapacity)
_strip[targetSubmeshIndex].Capacity = requiredCapacity;
}

///


/// Add a mesh instance to the combiner.
///

/// The mesh instance to add.
public void AddMeshInstance(MeshInstance instance)
{
int baseVertexIndex = _vertices.Count;

PrepareForAddingVertices(instance.mesh.vertexCount);

_vertices.AddRange(instance.mesh.vertices.Select(instance.transform.MultiplyPoint));
_normals.AddRange(
instance.mesh.normals.Select(n => instance.transform.inverse.transpose.MultiplyVector(n).normalized));
_tangents.AddRange(instance.mesh.tangents.Select(t =>
{
var p = new Vector3(t.x, t.y, t.z);
p =
instance.transform.inverse.transpose.
MultiplyVector(p).normalized;
return new Vector4(p.x, p.y, p.z, t.w);
}));
_uv.AddRange(instance.mesh.uv);
_uv1.AddRange(instance.mesh.uv1);
_colors.AddRange(instance.mesh.colors);

if (_generateStrips)
{
int[ ] inputstrip = instance.mesh.GetTriangles(instance.subMeshIndex);
PrepareForAddingStrips(instance.targetSubMeshIndex, new[ ] {inputstrip.Length});
List outputstrip = _strip[instance.targetSubMeshIndex];
if (outputstrip.Count != 0)
{
if ((outputstrip.Count & 1) == 1)
{
outputstrip.Add(outputstrip[outputstrip.Count - 1]);
outputstrip.Add(inputstrip[0] + baseVertexIndex);
outputstrip.Add(inputstrip[0] + baseVertexIndex);
}
else
{
outputstrip.Add(outputstrip[outputstrip.Count - 1]);
outputstrip.Add(inputstrip[0] + baseVertexIndex);
}
}

outputstrip.AddRange(inputstrip.Select(s => s + baseVertexIndex));
}
else
{
int[ ] inputtriangles = instance.mesh.GetTriangles(instance.subMeshIndex);
PrepareForAddingTriangles(instance.targetSubMeshIndex, inputtriangles.Length);
_triangles[instance.targetSubMeshIndex].AddRange(inputtriangles.Select(t => t + baseVertexIndex));
}
}

///


/// Add multiple mesh instances to the combiner, allocating space for them all up-front.
///

/// The instances to add.
public void AddMeshInstances(IEnumerable instances)
{
instances = instances.Where(instance => instance.mesh);

PrepareForAddingVertices(instances.Sum(instance => instance.mesh.vertexCount));

foreach (var targetSubmesh in instances.GroupBy(instance => instance.targetSubMeshIndex))
{
if (_generateStrips)
{
PrepareForAddingStrips(targetSubmesh.Key,
targetSubmesh.Select(instance => instance.mesh.GetTriangles(instance.subMeshIndex).Length));
}
else
{
PrepareForAddingTriangles(targetSubmesh.Key,
targetSubmesh.Sum(instance => instance.mesh.GetTriangles(instance.subMeshIndex).Length));
}
}

foreach (MeshInstance instance in instances)
AddMeshInstance(instance);
}

///


/// Generate a single mesh from the instances that have been added to the combiner so far.
///

/// A combined mesh.
public Mesh CreateCombinedMesh()
{
var mesh = new Mesh
{
name = “Combined Mesh”,
vertices = _vertices.ToArray(),
normals = _normals.ToArray(),
colors = _colors.ToArray(),
uv = _uv.ToArray(),
uv1 = _uv1.ToArray(),
tangents = _tangents.ToArray(),
subMeshCount = (_generateStrips) ? _strip.Count : _triangles.Count
};

if (_generateStrips)
{
foreach (var targetSubmesh in _strip)
mesh.SetTriangles(targetSubmesh.Value.ToArray(), targetSubmesh.Key);
}
else
{
foreach (var targetSubmesh in _triangles)
mesh.SetTriangles(targetSubmesh.Value.ToArray(), targetSubmesh.Key);
}

return mesh;
}

///


/// Combine the given mesh instances into a single mesh and return it.
///

/// The mesh instances to combine.
/// true to use triangle strips, false to use triangle lists.
/// A combined mesh.
public static Mesh Combine(IEnumerable instances, bool generateStrips)
{
var processor = new MeshCombineUtility(generateStrips);
processor.AddMeshInstances(instances);
return processor.CreateCombinedMesh();
}

#region Nested type: MeshInstance

public class MeshInstance
{
///


/// The source mesh.
///

public Mesh mesh;

///


/// The submesh from the source mesh that you want to add to the combiner.
///

public int subMeshIndex;

///


/// The submesh that you want this instance to be combined into. Group instances that should
/// share a material into the same target submesh index.
///

public int targetSubMeshIndex;

///


/// The instance transform.
///

public Matrix4x4 transform;
}

#endregion
}

Master-Tulz it seems you did not convert your script when unity prompted about it. I see uv1 on line 105 and 179. This value deprecated at least in 5.1.1f1 change it to uv2.

For the rest of errors I should say that it is mono problem that seems not appear in VS2013 .Net 3.5. Try to rewrite line 93 like this:

OLD

_vertices.AddRange(instance.mesh.vertices.Select(instance.transform.MultiplyPoint));

NEW

_vertices.AddRange(instance.mesh.vertices.Select(var => instance.transform.MultiplyPoint(var)));

Any info at all on how to use it? What do you DO with it? You have to add objects one at a time? Add objects to what?
THANKS!