/// <summary>
    /// Combine the given mesh instances into a single mesh and return it.
    /// </summary>
    /// <param name="instances">The mesh instances to combine.</param>
    /// <returns>A combined mesh.</returns>
    public static Mesh Combine(IEnumerable <MeshInstance> instances)
    {
        var processor = new FVMeshCombineUtility();

        processor.AddMeshInstances(instances);
        return(processor.CreateCombinedMesh());
    }
    public void Combine()
    {
        if (!Application.isPlaying)
        {
            Undo.RegisterSceneUndo("Combine meshes");
        }

        Component[] filters            = GetComponentsInChildren(typeof(MeshFilter));
        Matrix4x4   myTransform        = transform.worldToLocalMatrix;
        Hashtable   matNameToInst      = new Hashtable();
        Hashtable   matNameToMaterials = new Hashtable();

        for (int i = 0; i < filters.Length; ++i)
        {
            MeshFilter filter = (MeshFilter)filters[i];

            if (filter.name.Contains("$Combined"))
            {
                Debug.LogFormat("{0} is already combined", filter.name);
                continue;
            }

            if (filter.sharedMesh == null)
            {
                Debug.LogFormat("{0} has null mesh", filter.name);
                continue;
            }

            if (filter.transform == null)
            {
                Debug.LogFormat("{0} has null transform", filter.name);
                continue;
            }

            Renderer curRenderer = filters[i].GetComponent <Renderer>();
            FVMeshCombineUtility.MeshInstance inst = new FVMeshCombineUtility.MeshInstance();

            // collect filters based on materials
            if (curRenderer != null)
            {
                inst.transform = myTransform * filter.transform.localToWorldMatrix;

                string     mname   = MaterialsKey(curRenderer.sharedMaterials);
                ArrayList  objects = (ArrayList)matNameToInst [mname];
                Material[] mats    = (Material[])matNameToMaterials [mname];
                inst.subMeshCount = filter.sharedMesh.subMeshCount;
                inst.go           = filter.gameObject;
                inst.filter       = filter;

                if (objects != null)
                {
                    objects.Add(inst);
                }
                else
                {
                    objects = new ArrayList();
                    objects.Add(inst);
                    matNameToInst.Add(mname, objects);

                    mats = curRenderer.sharedMaterials;
                    matNameToMaterials.Add(mname, mats);
                }

                curRenderer.enabled = false;
            }
        }

        foreach (DictionaryEntry de in matNameToInst)
        {
            ArrayList elements = (ArrayList)de.Value;
            FVMeshCombineUtility.MeshInstance[] instances = (FVMeshCombineUtility.MeshInstance[])elements.ToArray(typeof(FVMeshCombineUtility.MeshInstance));
            if (instances.Length == 0)
            {
                continue;
            }

            // group all instances to enforce max vertices number per GameObject
            List <List <FVMeshCombineUtility.MeshInstance> > groupInstances = new List <List <FVMeshCombineUtility.MeshInstance> > ();
            groupInstances.Add(new List <FVMeshCombineUtility.MeshInstance>());
            int vn = 0;
            foreach (var instance in instances)
            {
                List <FVMeshCombineUtility.MeshInstance> gi = groupInstances.Last();
                int vc = instance.filter.sharedMesh.vertexCount;
                if (vn + vc > maxVertices)
                {
                    if (gi.Count == 0)
                    {
                        gi.Add(instance);
                        groupInstances.Add(new List <FVMeshCombineUtility.MeshInstance> ());
                        vn = 0;
                    }
                    else
                    {
                        groupInstances.Add(new List <FVMeshCombineUtility.MeshInstance> ());
                        gi = groupInstances.Last();
                        gi.Add(instance);
                        vn = vc;
                    }
                    continue;
                }
                gi.Add(instance);
                vn += vc;
            }

            // combine all the gameobjects have same materials
            foreach (var instancesList in groupInstances)
            {
                var gis = instancesList.ToArray();

                FVMeshCombineUtility.MeshInstance inst = gis [0];
                string     key  = (string)de.Key;
                Material[] mats = (Material[])matNameToMaterials [key];
                GameObject go   = new GameObject(inst.go.name + "$Combined");
                if (keepLayer)
                {
                    go.layer = gameObject.layer;
                }
                go.transform.parent        = transform;
                go.transform.localScale    = Vector3.one;
                go.transform.localRotation = Quaternion.identity;
                go.transform.localPosition = Vector3.zero;
                go.AddComponent(typeof(MeshFilter));
                go.AddComponent <MeshRenderer>();
                go.GetComponent <Renderer>().sharedMaterials = mats;
                MeshFilter filter = (MeshFilter)go.GetComponent(typeof(MeshFilter));
                filter.sharedMesh = FVMeshCombineUtility.Combine(gis);
                go.GetComponent <Renderer>().castShadows    = castShadow;
                go.GetComponent <Renderer>().receiveShadows = receiveShadow;
                if (addMeshCollider)
                {
                    go.AddComponent <MeshCollider>();
                }
            }
        }

        if (!destroyAfterOptimized)
        {
            return;
        }

        for (int i = 0; i < filters.Length; ++i)
        {
            MeshFilter filter = (MeshFilter)filters[i];
            if (filter.name.Contains("$Combined"))
            {
                Debug.LogFormat("{0} is already combined", filter.name);
                continue;
            }
            if (filter.gameObject == null)
            {
                Debug.LogFormat("{0} is already deleted", filter.name);
                continue;
            }
            DestroyImmediate(filter.gameObject);
        }
    }