Search Unity

Improved MeshCombineUtility

Discussion in 'Made With Unity' started by superpig, May 5, 2011.

  1. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    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.

    View attachment $MeshCombineUtility.cs

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

    View attachment $CombineChildren.cs

    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.
     
  2. Cominu

    Cominu

    Joined:
    Feb 18, 2010
    Posts:
    149
    why submesh support?
    How can you handle more than 64k verts with your script?
     
  3. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    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.
     
    eelstork likes this.
  4. Zavior

    Zavior

    Joined:
    May 21, 2010
    Posts:
    4
    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<TSource>, 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>'
     
  5. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    Try changing that line (line 93) from:

    Code (csharp):
    1. _vertices.AddRange(instance.mesh.vertices.Select(instance.transform.MultiplyPoint));
    to

    Code (csharp):
    1. _vertices.AddRange(instance.mesh.vertices.Select(v => instance.transform.MultiplyPoint(v)));
     
  6. keithsoulasa

    keithsoulasa

    Joined:
    Feb 15, 2012
    Posts:
    2,126
    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
     
  7. liszto

    liszto

    Joined:
    Dec 22, 2011
    Posts:
    135
    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
     
    Last edited: Jun 13, 2012
  8. augasur

    augasur

    Joined:
    Mar 4, 2012
    Posts:
    133
    Is it possible tho call Combine Mesh Utility with JS (UnityScript)?
     
  9. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    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.
     
  10. joticarroll

    joticarroll

    Joined:
    Jan 30, 2013
    Posts:
    4
    I've converted this to work as an AssetPostprocessor.
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System.Linq;
    6. using System;
    7.  
    8. // This script require the MeshCombineUtility.cs
    9. public class ImportTerrainModels : AssetPostprocessor
    10. {
    11.    
    12.     public static bool importPostprocessEnabled = true;
    13.    
    14.     public static bool generateTriangleStrips = true;    
    15.     public bool addMeshColliderToCombinedMesh = true;
    16.     public bool useEditorImportSettingsForCollisionGeneration = true;    
    17.     public static string ignoreAssetPathContaining = "/.";
    18.     public static string includeAssetPathContaining = "Terrain-Models";
    19.    
    20.     public bool castShadow = false;
    21.     public bool recieveShadow = false;
    22.     public bool playAnimationAutomatic = false;
    23.    
    24.     private ModelImporter modelImporter;
    25.    
    26.     // Apply this postprocessor early
    27.     public override int GetPostprocessOrder()
    28.     {
    29.         return 0;
    30.     }
    31.    
    32.     void OnPostprocessModel(GameObject g)
    33.     {
    34.  
    35.         modelImporter = assetImporter as ModelImporter;
    36.         bool assetPathFilter =
    37.             (includeAssetPathContaining == "" || modelImporter.assetPath.ToLower().Contains(includeAssetPathContaining.ToLower())) &&
    38.                 (ignoreAssetPathContaining == "" || !modelImporter.assetPath.ToLower().Contains(ignoreAssetPathContaining.ToLower()));
    39.        
    40.         if (importPostprocessEnabled && assetPathFilter)
    41.         {
    42.  
    43.             var filters = g.GetComponentsInChildren<MeshFilter>();
    44.             Matrix4x4 myTransform = g.transform.worldToLocalMatrix;
    45.             var materialToMesh = new Dictionary<Material, List<MeshCombineUtility.MeshInstance>>();
    46.            
    47.             foreach (var sourceFilter in filters)
    48.             {
    49.                 Renderer curRenderer = sourceFilter.renderer;
    50.                 if (curRenderer == null || !curRenderer.enabled) continue;
    51.                
    52.                 var instance = new MeshCombineUtility.MeshInstance
    53.                 {
    54.                     mesh = sourceFilter.sharedMesh,
    55.                     transform = myTransform * sourceFilter.transform.localToWorldMatrix
    56.                 };
    57.                 if(instance.mesh == null) continue;
    58.                
    59.                 Material[] materials = curRenderer.sharedMaterials;
    60.                 for (int m = 0; m < materials.Length; m++)
    61.                 {
    62.                     instance.subMeshIndex = Math.Min(m, instance.mesh.subMeshCount - 1);
    63.                    
    64.                     List<MeshCombineUtility.MeshInstance> objects;
    65.                     if (!materialToMesh.TryGetValue(materials[m], out objects))
    66.                     {
    67.                         objects = new List<MeshCombineUtility.MeshInstance>();
    68.                         materialToMesh.Add(materials[m], objects);  
    69.                     }
    70.                     objects.Add(instance);
    71.                 }
    72.             }
    73.            
    74.             int targetMeshIndex = 0;
    75.             foreach(var de in materialToMesh)
    76.             {
    77.                 foreach (var instance in de.Value)
    78.                     instance.targetSubMeshIndex = targetMeshIndex;
    79.                 ++targetMeshIndex;
    80.             }
    81.  
    82.             Mesh mesh = MeshCombineUtility.Combine(materialToMesh.SelectMany(kvp => kvp.Value), generateTriangleStrips);
    83.             bool meshAssigned = false;
    84.  
    85.             foreach (MeshFilter filter in filters)
    86.             {
    87.                 if (!meshAssigned)
    88.                 {
    89.                     Debug.Log("Add to mesh: "+filter.sharedMesh.name);
    90.                     DeepCopyMeshInto(mesh, filter.sharedMesh);
    91.                     //filter.sharedMesh = mesh;
    92.  
    93.                     if (!g.GetComponent<MeshFilter>()) g.gameObject.AddComponent<MeshFilter>();
    94.                     var rootFilter = g.GetComponent<MeshFilter>();
    95.                     if (!g.GetComponent<MeshRenderer>()) g.gameObject.AddComponent<MeshRenderer>();
    96.                
    97.                     rootFilter.sharedMesh = filter.sharedMesh;
    98.                     g.renderer.materials = materialToMesh.Keys.ToArray();
    99.                     g.renderer.enabled = true;
    100.  
    101.                     UnityEngine.Object.DestroyImmediate(filter.gameObject, true);
    102.                
    103.                     meshAssigned = true;
    104.                  
    105.                 }
    106.                 else
    107.                 {
    108.                     Mesh.DestroyImmediate(filter.sharedMesh);
    109.                     filter.sharedMesh = null;
    110.                     // Remove unused free meshes
    111.                     UnityEngine.Object.DestroyImmediate(filter.gameObject, true);
    112.                 }
    113.             }
    114.         }
    115.     }
    116.  
    117.     void DeepCopyMeshInto(Mesh fromMesh, Mesh toMesh)
    118.     {    
    119.         int vertexCount = fromMesh.vertexCount;
    120.         int triangleCount = fromMesh.triangles.Length;
    121.  
    122.         toMesh.triangles = new int[triangleCount];
    123.         toMesh.vertices = new Vector3[vertexCount];
    124.         toMesh.normals = new Vector3[vertexCount];
    125.         toMesh.colors = new Color[vertexCount];
    126.         toMesh.uv = new Vector2[vertexCount];
    127.         toMesh.uv1 = new Vector2[vertexCount];
    128.         toMesh.tangents = new Vector4[vertexCount];
    129.  
    130.     //    toMesh.triangles = fromMesh.triangles;
    131.         toMesh.vertices = fromMesh.vertices;
    132.         toMesh.normals = fromMesh.normals;
    133.         toMesh.colors = fromMesh.colors;
    134.         toMesh.uv = fromMesh.uv;
    135.         toMesh.uv1 = fromMesh.uv1;
    136.         toMesh.tangents = fromMesh.tangents;
    137.  
    138.         toMesh.subMeshCount = fromMesh.subMeshCount;
    139.  
    140.  
    141.         for(int i=0; i < fromMesh.subMeshCount; i++){
    142.             toMesh.SetTriangles(fromMesh.GetTriangles(i),i);
    143.         }
    144.  
    145.         toMesh.RecalculateBounds ();
    146.         toMesh.name = fromMesh.name;
    147.     }
    148.  
    149. }
     
    Last edited: Aug 13, 2014
  11. idurvesh

    idurvesh

    Joined:
    Jun 9, 2014
    Posts:
    495
    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<int>)' has some invalid arguments



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



    (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?​
     
    Pilltech101 likes this.
  12. KEMBL

    KEMBL

    Joined:
    Apr 16, 2009
    Posts:
    181
    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.
     
  13. Master-Tulz

    Master-Tulz

    Joined:
    Sep 1, 2015
    Posts:
    1
    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<TSource>, 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<Color> _colors = new List<Color>();
    private readonly bool _generateStrips;
    private readonly List<Vector3> _normals = new List<Vector3>();
    private readonly Dictionary<int, List<int>> _strip = new Dictionary<int, List<int>>();
    private readonly List<Vector4> _tangents = new List<Vector4>();
    private readonly Dictionary<int, List<int>> _triangles = new Dictionary<int, List<int>>();
    private readonly List<Vector2> _uv = new List<Vector2>();
    private readonly List<Vector2> _uv1 = new List<Vector2>();
    private readonly List<Vector3> _vertices = new List<Vector3>();

    /// <summary>
    /// Creates a new, empty MeshCombineUtility object for combining meshes.
    /// </summary>
    /// <param name="generateStrips">true if the meshes you're going to combine are all triangle-strip based; false if you just want to use triangle lists.</param>
    public MeshCombineUtility(bool generateStrips)
    {
    _generateStrips = generateStrips;
    }

    /// <summary>
    /// Allocate space for adding a load more vertex data.
    /// </summary>
    /// <param name="numVertices">The number of vertices you're about to add.</param>
    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;
    }

    /// <summary>
    /// Allocate space for adding a load more triangles to the triangle list.
    /// </summary>
    /// <param name="targetSubmeshIndex">The index of the submesh that you're going to add triangles to.</param>
    /// <param name="numIndices">The number of triangle indicies (number of triangles * 3) that you want to reserve space for.</param>
    public void PrepareForAddingTriangles(int targetSubmeshIndex, int numIndices)
    {
    if (!_triangles.ContainsKey(targetSubmeshIndex))
    _triangles.Add(targetSubmeshIndex, new List<int>());

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

    /// <summary>
    /// Allocate space for adding a load more triangle strips to the combiner.
    /// </summary>
    /// <param name="targetSubmeshIndex">The index of the submesh you're going to add strips to.</param>
    /// <param name="stripLengths">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.</param>
    public void PrepareForAddingStrips(int targetSubmeshIndex, IEnumerable<int> stripLengths)
    {
    if (!_strip.ContainsKey(targetSubmeshIndex))
    _strip.Add(targetSubmeshIndex, new List<int>());

    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;
    }

    /// <summary>
    /// Add a mesh instance to the combiner.
    /// </summary>
    /// <param name="instance">The mesh instance to add.</param>
    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<int> 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));
    }
    }

    /// <summary>
    /// Add multiple mesh instances to the combiner, allocating space for them all up-front.
    /// </summary>
    /// <param name="instances">The instances to add.</param>
    public void AddMeshInstances(IEnumerable<MeshInstance> 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);
    }

    /// <summary>
    /// Generate a single mesh from the instances that have been added to the combiner so far.
    /// </summary>
    /// <returns>A combined mesh.</returns>
    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;
    }

    /// <summary>
    /// Combine the given mesh instances into a single mesh and return it.
    /// </summary>
    /// <param name="instances">The mesh instances to combine.</param>
    /// <param name="generateStrips">true to use triangle strips, false to use triangle lists.</param>
    /// <returns>A combined mesh.</returns>
    public static Mesh Combine(IEnumerable<MeshInstance> instances, bool generateStrips)
    {
    var processor = new MeshCombineUtility(generateStrips);
    processor.AddMeshInstances(instances);
    return processor.CreateCombinedMesh();
    }

    #region Nested type: MeshInstance

    public class MeshInstance
    {
    /// <summary>
    /// The source mesh.
    /// </summary>
    public Mesh mesh;

    /// <summary>
    /// The submesh from the source mesh that you want to add to the combiner.
    /// </summary>
    public int subMeshIndex;

    /// <summary>
    /// The submesh that you want this instance to be combined into. Group instances that should
    /// share a material into the same target submesh index.
    /// </summary>
    public int targetSubMeshIndex;

    /// <summary>
    /// The instance transform.
    /// </summary>
    public Matrix4x4 transform;
    }

    #endregion
    }
     
  14. KEMBL

    KEMBL

    Joined:
    Apr 16, 2009
    Posts:
    181
    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
    Code (csharp):
    1. _vertices.AddRange(instance.mesh.vertices.Select(instance.transform.MultiplyPoint));

    NEW
    Code (csharp):
    1. _vertices.AddRange(instance.mesh.vertices.Select(var => instance.transform.MultiplyPoint(var)));
     
    Last edited: Sep 28, 2015
  15. NextWorldVR

    NextWorldVR

    Joined:
    Dec 30, 2017
    Posts:
    14
    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!