/// <summary>
        /// Insert a face between two edges.
        /// </summary>
        /// <param name="mesh">The source mesh.</param>
        /// <param name="a">First edge.</param>
        /// <param name="b">Second edge</param>
        /// <param name="allowNonManifoldGeometry">If true, this function will allow edges to be bridged that create overlapping (non-manifold) faces.</param>
        /// <returns>The new face, or null of the action failed.</returns>
        public static Face Bridge(this ProBuilderMesh mesh, Edge a, Edge b, bool allowNonManifoldGeometry = false)
        {
            if (mesh == null)
            {
                throw new ArgumentNullException("mesh");
            }

            SharedVertex[]        sharedVertices = mesh.sharedVerticesInternal;
            Dictionary <int, int> lookup         = mesh.sharedVertexLookup;

            // Check to see if a face already exists
            if (!allowNonManifoldGeometry)
            {
                if (ElementSelection.GetNeighborFaces(mesh, a).Count > 1 ||
                    ElementSelection.GetNeighborFaces(mesh, b).Count > 1)
                {
                    return(null);
                }
            }

            foreach (Face face in mesh.facesInternal)
            {
                if (mesh.IndexOf(face.edgesInternal, a) >= 0 && mesh.IndexOf(face.edgesInternal, b) >= 0)
                {
                    Log.Warning("Face already exists between these two edges!");
                    return(null);
                }
            }

            Vector3[] positions = mesh.positionsInternal;
            bool      hasColors = mesh.HasArrays(MeshArrays.Color);

            Color[] colors = hasColors ? mesh.colorsInternal : null;

            Vector3[]          v;
            Color[]            c;
            int[]              s;
            AutoUnwrapSettings uvs = AutoUnwrapSettings.tile;
            int submeshIndex       = 0;

            // Get material and UV stuff from the first edge face
            SimpleTuple <Face, Edge> faceAndEdge;

            if (EdgeUtility.ValidateEdge(mesh, a, out faceAndEdge) ||
                EdgeUtility.ValidateEdge(mesh, b, out faceAndEdge))
            {
                uvs          = new AutoUnwrapSettings(faceAndEdge.item1.uv);
                submeshIndex = faceAndEdge.item1.submeshIndex;
            }

            // Bridge will form a triangle
            if (a.Contains(b.a, lookup) || a.Contains(b.b, lookup))
            {
                v = new Vector3[3];
                c = new Color[3];
                s = new int[3];

                bool axbx = Array.IndexOf(sharedVertices[mesh.GetSharedVertexHandle(a.a)].arrayInternal, b.a) > -1;
                bool axby = Array.IndexOf(sharedVertices[mesh.GetSharedVertexHandle(a.a)].arrayInternal, b.b) > -1;

                bool aybx = Array.IndexOf(sharedVertices[mesh.GetSharedVertexHandle(a.b)].arrayInternal, b.a) > -1;
                bool ayby = Array.IndexOf(sharedVertices[mesh.GetSharedVertexHandle(a.b)].arrayInternal, b.b) > -1;

                if (axbx)
                {
                    v[0] = positions[a.a];
                    if (hasColors)
                    {
                        c[0] = colors[a.a];
                    }
                    s[0] = mesh.GetSharedVertexHandle(a.a);
                    v[1] = positions[a.b];
                    if (hasColors)
                    {
                        c[1] = colors[a.b];
                    }
                    s[1] = mesh.GetSharedVertexHandle(a.b);
                    v[2] = positions[b.b];
                    if (hasColors)
                    {
                        c[2] = colors[b.b];
                    }
                    s[2] = mesh.GetSharedVertexHandle(b.b);
                }
                else if (axby)
                {
                    v[0] = positions[a.a];
                    if (hasColors)
                    {
                        c[0] = colors[a.a];
                    }
                    s[0] = mesh.GetSharedVertexHandle(a.a);
                    v[1] = positions[a.b];
                    if (hasColors)
                    {
                        c[1] = colors[a.b];
                    }
                    s[1] = mesh.GetSharedVertexHandle(a.b);
                    v[2] = positions[b.a];
                    if (hasColors)
                    {
                        c[2] = colors[b.a];
                    }
                    s[2] = mesh.GetSharedVertexHandle(b.a);
                }
                else if (aybx)
                {
                    v[0] = positions[a.b];
                    if (hasColors)
                    {
                        c[0] = colors[a.b];
                    }
                    s[0] = mesh.GetSharedVertexHandle(a.b);
                    v[1] = positions[a.a];
                    if (hasColors)
                    {
                        c[1] = colors[a.a];
                    }
                    s[1] = mesh.GetSharedVertexHandle(a.a);
                    v[2] = positions[b.b];
                    if (hasColors)
                    {
                        c[2] = colors[b.b];
                    }
                    s[2] = mesh.GetSharedVertexHandle(b.b);
                }
                else if (ayby)
                {
                    v[0] = positions[a.b];
                    if (hasColors)
                    {
                        c[0] = colors[a.b];
                    }
                    s[0] = mesh.GetSharedVertexHandle(a.b);
                    v[1] = positions[a.a];
                    if (hasColors)
                    {
                        c[1] = colors[a.a];
                    }
                    s[1] = mesh.GetSharedVertexHandle(a.a);
                    v[2] = positions[b.a];
                    if (hasColors)
                    {
                        c[2] = colors[b.a];
                    }
                    s[2] = mesh.GetSharedVertexHandle(b.a);
                }

                return(mesh.AppendFace(
                           v,
                           hasColors ? c : null,
                           new Vector2[v.Length],
                           new Vector4[v.Length],
                           new Vector4[v.Length],
                           new Face(axbx || axby ? new int[3] {
                    2, 1, 0
                } : new int[3] {
                    0, 1, 2
                }, submeshIndex, uvs, 0, -1, -1,
                                    false),
                           s));
            }

            // Else, bridge will form a quad

            v = new Vector3[4];
            c = new Color[4];
            s = new int[4]; // shared indexes index to add to

            v[0] = positions[a.a];
            if (hasColors)
            {
                c[0] = mesh.colorsInternal[a.a];
            }
            s[0] = mesh.GetSharedVertexHandle(a.a);
            v[1] = positions[a.b];
            if (hasColors)
            {
                c[1] = mesh.colorsInternal[a.b];
            }
            s[1] = mesh.GetSharedVertexHandle(a.b);

            Vector3 nrm = Vector3.Cross(positions[b.a] - positions[a.a], positions[a.b] - positions[a.a]).normalized;

            Vector2[] planed =
                Projection.PlanarProject(
                    new Vector3[4] {
                positions[a.a], positions[a.b], positions[b.a], positions[b.b]
            }, null, nrm);

            Vector2 ipoint     = Vector2.zero;
            bool    intersects = Math.GetLineSegmentIntersect(planed[0], planed[2], planed[1], planed[3], ref ipoint);

            if (!intersects)
            {
                v[2] = positions[b.a];
                if (hasColors)
                {
                    c[2] = mesh.colorsInternal[b.a];
                }
                s[2] = mesh.GetSharedVertexHandle(b.a);
                v[3] = positions[b.b];
                if (hasColors)
                {
                    c[3] = mesh.colorsInternal[b.b];
                }
                s[3] = mesh.GetSharedVertexHandle(b.b);
            }
            else
            {
                v[2] = positions[b.b];
                if (hasColors)
                {
                    c[2] = mesh.colorsInternal[b.b];
                }
                s[2] = mesh.GetSharedVertexHandle(b.b);
                v[3] = positions[b.a];
                if (hasColors)
                {
                    c[3] = mesh.colorsInternal[b.a];
                }
                s[3] = mesh.GetSharedVertexHandle(b.a);
            }

            return(mesh.AppendFace(
                       v,
                       hasColors ? c : null,
                       new Vector2[v.Length],
                       new Vector4[v.Length],
                       new Vector4[v.Length],
                       new Face(new int[6] {
                2, 1, 0, 2, 3, 1
            }, submeshIndex, uvs, 0, -1, -1, false),
                       s));
        }
        /// <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());
        }