static List <ProBuilderMesh> CombineToNewMeshes(IEnumerable <ProBuilderMesh> meshes)
        {
            if (meshes == null)
            {
                throw new ArgumentNullException("meshes");
            }

            if (!meshes.Any() || meshes.Count() < 2)
            {
                return(null);
            }

            var vertices       = new List <Vertex>();
            var faces          = new List <Face>();
            var autoUvFaces    = new List <Face>();
            var sharedVertices = new List <SharedVertex>();
            var sharedTextures = new List <SharedVertex>();
            int offset         = 0;
            var materialMap    = new List <Material>();

            AccumulateMeshesInfo(
                meshes,
                offset,
                ref vertices,
                ref faces,
                ref autoUvFaces,
                ref sharedVertices,
                ref sharedTextures,
                ref materialMap
                );

            var res   = SplitByMaxVertexCount(vertices, faces, sharedVertices, sharedTextures);
            var pivot = meshes.LastOrDefault().transform.position;

            foreach (var m in res)
            {
                m.renderer.sharedMaterials = materialMap.ToArray();
                InternalMeshUtility.FilterUnusedSubmeshIndexes(m);
                m.SetPivot(pivot);
                UVEditing.SetAutoAndAlignUnwrapParamsToUVs(m, autoUvFaces);
            }

            return(res);
        }
        /// <summary>
        /// Extrude a collection of edges.
        /// </summary>
        /// <param name="mesh">The source mesh.</param>
        /// <param name="edges">The edges to extrude.</param>
        /// <param name="distance">The distance to extrude.</param>
        /// <param name="extrudeAsGroup">If true adjacent edges will be extruded retaining a shared vertex, if false the shared vertex will be split.</param>
        /// <param name="enableManifoldExtrude">Pass true to allow this function to extrude manifold edges, false to disallow.</param>
        /// <returns>The extruded edges, or null if the action failed due to manifold check or an empty edges parameter.</returns>
        public static Edge[] Extrude(this ProBuilderMesh mesh, IEnumerable <Edge> edges, float distance, bool extrudeAsGroup, bool enableManifoldExtrude)
        {
            if (mesh == null)
            {
                throw new ArgumentNullException("mesh");
            }

            if (edges == null)
            {
                throw new ArgumentNullException("edges");
            }

            SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal;

            List <Edge> validEdges = new List <Edge>();
            List <Face> edgeFaces  = new List <Face>();

            foreach (Edge e in edges)
            {
                int  faceCount = 0;
                Face fa        = null;

                foreach (Face face in mesh.facesInternal)
                {
                    if (mesh.IndexOf(face.edgesInternal, e) > -1)
                    {
                        fa = face;

                        if (++faceCount > 1)
                        {
                            break;
                        }
                    }
                }

                if (enableManifoldExtrude || faceCount < 2)
                {
                    validEdges.Add(e);
                    edgeFaces.Add(fa);
                }
            }

            if (validEdges.Count < 1)
            {
                return(null);
            }

            Vector3[] localVerts = mesh.positionsInternal;
            if (!mesh.HasArrays(MeshArrays.Normal))
            {
                mesh.Refresh(RefreshMask.Normals);
            }
            IList <Vector3> oNormals = mesh.normals;

            int[] allEdgeIndexes = new int[validEdges.Count * 2];
            int   c = 0;

            for (int i = 0; i < validEdges.Count; i++)
            {
                allEdgeIndexes[c++] = validEdges[i].a;
                allEdgeIndexes[c++] = validEdges[i].b;
            }

            List <Edge> extrudedIndexes = new List <Edge>();
            // used to set the editor selection to the newly created edges
            List <Edge> newEdges  = new List <Edge>();
            bool        hasColors = mesh.HasArrays(MeshArrays.Color);

            // build out new faces around validEdges
            for (int i = 0; i < validEdges.Count; i++)
            {
                Edge edge = validEdges[i];
                Face face = edgeFaces[i];

                // Averages the normals using only vertices that are on the edge
                Vector3 xnorm = extrudeAsGroup
                    ? InternalMeshUtility.AverageNormalWithIndexes(sharedIndexes[mesh.GetSharedVertexHandle(edge.a)], allEdgeIndexes, oNormals)
                    : Math.Normal(mesh, face);

                Vector3 ynorm = extrudeAsGroup
                    ? InternalMeshUtility.AverageNormalWithIndexes(sharedIndexes[mesh.GetSharedVertexHandle(edge.b)], allEdgeIndexes, oNormals)
                    : Math.Normal(mesh, face);

                int x_sharedIndex = mesh.GetSharedVertexHandle(edge.a);
                int y_sharedIndex = mesh.GetSharedVertexHandle(edge.b);

                var positions = new Vector3[4]
                {
                    localVerts[edge.a],
                    localVerts[edge.b],
                    localVerts[edge.a] + xnorm.normalized * distance,
                    localVerts[edge.b] + ynorm.normalized * distance
                };

                var colors = hasColors
                    ? new Color[4]
                {
                    mesh.colorsInternal[edge.a],
                    mesh.colorsInternal[edge.b],
                    mesh.colorsInternal[edge.a],
                    mesh.colorsInternal[edge.b]
                }
                    : null;

                Face newFace = mesh.AppendFace(
                    positions,
                    colors,
                    new Vector2[4],
                    new Face(new int[6] {
                    2, 1, 0, 2, 3, 1
                }, face.submeshIndex, AutoUnwrapSettings.tile, 0, -1, -1, false),
                    new int[4] {
                    x_sharedIndex, y_sharedIndex, -1, -1
                });

                newEdges.Add(new Edge(newFace.indexesInternal[3], newFace.indexesInternal[4]));

                extrudedIndexes.Add(new Edge(x_sharedIndex, newFace.indexesInternal[3]));
                extrudedIndexes.Add(new Edge(y_sharedIndex, newFace.indexesInternal[4]));
            }

            // merge extruded vertex indexes with each other
            if (extrudeAsGroup)
            {
                for (int i = 0; i < extrudedIndexes.Count; i++)
                {
                    int val = extrudedIndexes[i].a;

                    for (int n = 0; n < extrudedIndexes.Count; n++)
                    {
                        if (n == i)
                        {
                            continue;
                        }

                        if (extrudedIndexes[n].a == val)
                        {
                            mesh.SetVerticesCoincident(new int[] { extrudedIndexes[n].b, extrudedIndexes[i].b });
                            break;
                        }
                    }
                }
            }

            // todo Should only need to invalidate caches on affected faces
            foreach (Face f in mesh.facesInternal)
            {
                f.InvalidateCache();
            }

            return(newEdges.ToArray());
        }
        /// <summary>
        /// Merge a collection of <see cref="ProBuilderMesh"/> objects to as few meshes as possible. This may result in
        /// more than one mesh due to a max vertex count limit of 65535.
        /// </summary>
        /// <param name="meshes">A collection of meshes to be merged.</param>
        /// <returns>
        /// A list of merged meshes. In most cases this will be a single mesh. However it can be multiple in cases
        /// where the resulting vertex count exceeds the maximum allowable value.
        /// </returns>
        public static List <ProBuilderMesh> Combine(IEnumerable <ProBuilderMesh> meshes)
        {
            if (meshes == null)
            {
                throw new ArgumentNullException("meshes");
            }

            if (!meshes.Any() || meshes.Count() < 2)
            {
                return(null);
            }

            var vertices       = new List <Vertex>();
            var faces          = new List <Face>();
            var autoUvFaces    = new List <Face>();
            var sharedVertices = new List <SharedVertex>();
            var sharedTextures = new List <SharedVertex>();
            int offset         = 0;
            var materialMap    = new List <Material>();

            foreach (var mesh in meshes)
            {
                var meshVertexCount    = mesh.vertexCount;
                var transform          = mesh.transform;
                var meshVertices       = mesh.GetVertices();
                var meshFaces          = mesh.facesInternal;
                var meshSharedVertices = mesh.sharedVertices;
                var meshSharedTextures = mesh.sharedTextures;
                var materials          = mesh.renderer.sharedMaterials;
                var materialCount      = materials.Length;

                for (int i = 0; i < meshVertexCount; i++)
                {
                    vertices.Add(transform.TransformVertex(meshVertices[i]));
                }

                foreach (var face in meshFaces)
                {
                    var newFace = new Face(face);
                    newFace.ShiftIndexes(offset);

                    // prevents uvs from shifting when being converted from local coords to world space
                    if (!newFace.manualUV && !newFace.uv.useWorldSpace)
                    {
                        newFace.manualUV = true;
                        autoUvFaces.Add(newFace);
                    }
                    var material     = materials[Math.Clamp(face.submeshIndex, 0, materialCount - 1)];
                    var submeshIndex = materialMap.IndexOf(material);

                    if (submeshIndex > -1)
                    {
                        newFace.submeshIndex = submeshIndex;
                    }
                    else
                    {
                        if (material == null)
                        {
                            newFace.submeshIndex = 0;
                        }
                        else
                        {
                            newFace.submeshIndex = materialMap.Count;
                            materialMap.Add(material);
                        }
                    }

                    faces.Add(newFace);
                }

                foreach (var sv in meshSharedVertices)
                {
                    var nsv = new SharedVertex(sv);
                    nsv.ShiftIndexes(offset);
                    sharedVertices.Add(nsv);
                }

                foreach (var st in meshSharedTextures)
                {
                    var nst = new SharedVertex(st);
                    nst.ShiftIndexes(offset);
                    sharedTextures.Add(nst);
                }

                offset += meshVertexCount;
            }

            var res   = SplitByMaxVertexCount(vertices, faces, sharedVertices, sharedTextures);
            var pivot = meshes.LastOrDefault().transform.position;

            foreach (var m in res)
            {
                m.renderer.sharedMaterials = materialMap.ToArray();
                InternalMeshUtility.FilterUnusedSubmeshIndexes(m);
                m.SetPivot(pivot);
                UVEditing.SetAutoAndAlignUnwrapParamsToUVs(m, autoUvFaces);
            }

            return(res);
        }