Ejemplo n.º 1
0
        /**
         *	Splits face per vertex.
         *	Todo - Could implement more sanity checks - namely testing for edges before sending to Split_Internal.  However,
         *	the split method is smart enough to fail on those cases, so ignore for now.
         */
        public static bool ConnectVertices(this pb_Object pb, List <pb_VertexConnection> vertexConnectionsUnfiltered, out int[] triangles)
        {
            List <pb_VertexConnection> vertexConnections = new List <pb_VertexConnection>();
            List <int> inds = new List <int>();

            int i = 0;

            for (i = 0; i < vertexConnectionsUnfiltered.Count; i++)
            {
                pb_VertexConnection vc = vertexConnectionsUnfiltered[i];
                vc.indices = vc.indices.Distinct().ToList();

                if (vc.isValid)
                {
                    inds.AddRange(vc.indices);
                    vertexConnections.Add(vc);
                }
            }

            if (vertexConnections.Count < 1)
            {
                triangles = null;
                return(false);
            }

            List <Vector3> selectedVertices = pb.GetVertices(pb_VertexConnection.AllTriangles(vertexConnections));

            int len = vertexConnections.Count;

            // new faces will be built from successfull split ops
            List <pb_Face> successfullySplitFaces = new List <pb_Face>();
            List <pb_Face> all_splitFaces         = new List <pb_Face>();

            List <Vector3[]> all_splitVertices = new List <Vector3[]>();
            List <Color[]>   all_splitColors   = new List <Color[]>();
            List <Vector2[]> all_splitUVs      = new List <Vector2[]>();

            List <int[]> all_splitSharedIndices = new List <int[]>();

            bool[] success = new bool[len];

            pb_IntArray[] sharedIndices = pb.sharedIndices;

            i = 0;
            foreach (pb_VertexConnection vc in vertexConnections)
            {
                pb_Face[]   splitFaces    = null;
                Vector3[][] splitVertices = null;
                Color[][]   splitColors   = null;
                Vector2[][] splitUVs;
                int[][]     splitSharedIndices = null;

                if (vc.indices.Count < 3)
                {
                    int indA = vc.face.indices.IndexOf(vc.indices[0], sharedIndices);
                    int indB = vc.face.indices.IndexOf(vc.indices[1], sharedIndices);

                    if (indA < 0 || indB < 0)
                    {
                        success[i] = false;
                        continue;
                    }

                    indA = vc.face.indices[indA];
                    indB = vc.face.indices[indB];

                    success[i] = SplitFace_Internal(
                        new SplitSelection(pb, vc.face, pb.vertices[indA], pb.vertices[indB], pb.colors[indA], pb.colors[indB], true, true, new int[] { indA }, new int[] { indB }),
                        out splitFaces,
                        out splitVertices,
                        out splitColors,
                        out splitUVs,
                        out splitSharedIndices);

                    if (success[i])
                    {
                        successfullySplitFaces.Add(vc.face);
                    }
                }
                else
                {
                    Vector3 pokedVertex;

                    success[i] = PokeFace_Internal(pb, vc.face, vc.indices.ToArray(),
                                                   out pokedVertex,
                                                   out splitFaces,
                                                   out splitVertices,
                                                   out splitColors,
                                                   out splitUVs,
                                                   out splitSharedIndices);

                    if (success[i])
                    {
                        selectedVertices.Add(pokedVertex);
                        successfullySplitFaces.Add(vc.face);
                    }
                }

                if (success[i])
                {
                    int texGroup = pb.UnusedTextureGroup(i + 1);

                    for (int j = 0; j < splitFaces.Length; j++)
                    {
                        splitFaces[j].textureGroup = texGroup;

                        all_splitFaces.Add(splitFaces[j]);
                        all_splitVertices.Add(splitVertices[j]);
                        all_splitColors.Add(splitColors[j]);
                        all_splitUVs.Add(splitUVs[j]);

                        all_splitSharedIndices.Add(splitSharedIndices[j]);
                    }
                }

                i++;
            }

            if (all_splitFaces.Count < 1)
            {
                triangles = null;
                return(false);
            }

            pb_Face[] appendedFaces = pb.AppendFaces(all_splitVertices.ToArray(),
                                                     all_splitColors.ToArray(),
                                                     all_splitUVs.ToArray(),
                                                     all_splitFaces.ToArray(),
                                                     all_splitSharedIndices.ToArray());

            inds.AddRange(pb_Face.AllTriangles(appendedFaces));

            int[] welds;
            pb.WeldVertices(inds.ToArray(), Mathf.Epsilon, out welds);

            pb.DeleteFaces(successfullySplitFaces.ToArray());

            List <int> seltris = new List <int>();

            for (i = 0; i < selectedVertices.Count; i++)
            {
                int ind = System.Array.IndexOf(pb.vertices, selectedVertices[i]);
                if (ind > -1)
                {
                    seltris.Add(ind);
                }
            }

            triangles = seltris.Distinct().ToArray();
            return(true);
        }
Ejemplo n.º 2
0
        private static bool ConnectEdges(this pb_Object pb, List <pb_EdgeConnection> pb_edgeConnectionsUnfiltered, out pb_Face[] faces)
        {
            List <pb_EdgeConnection> pb_edgeConnections = new List <pb_EdgeConnection>();

            foreach (pb_EdgeConnection ec in pb_edgeConnectionsUnfiltered)
            {
                if (ec.isValid)
                {
                    pb_edgeConnections.Add(ec);
                }
            }

            int len = pb_edgeConnections.Count;

            if (len < 1)
            {
                faces = null;
                return(false);
            }

            Vector3[] vertices = pb.vertices;
            Color[]   colors   = pb.colors;

            List <pb_Face> successfullySplitFaces = new List <pb_Face>();

            List <pb_Face>   all_splitFaces         = new List <pb_Face>();
            List <Vector3[]> all_splitVertices      = new List <Vector3[]>();
            List <Color[]>   all_splitColors        = new List <Color[]>();
            List <Vector2[]> all_splitUVs           = new List <Vector2[]>();
            List <int[]>     all_splitSharedIndices = new List <int[]>();

            bool[] success = new bool[len];

            // use a nullable type because in order for the adjacent face triangulation
            // code to work, it needs to know what dangling vert belongs to which edge,
            // if we out a vector3[] with each index corresponding to the passed edges
            // in pb_EdgeConnection, it's easy to maintain the relationship.
            DanglingVertex?[][] danglingVertices = new DanglingVertex?[len][];

            // profiler.BeginSample("foreach(edge connection)");
            int i = 0;

            foreach (pb_EdgeConnection fc in pb_edgeConnections)
            {
                pb_Face[]   splitFaces         = null;
                Vector3[][] splitVertices      = null;
                Color[][]   splitColors        = null;
                Vector2[][] splitUVs           = null;
                int[][]     splitSharedIndices = null;

                if (fc.edges.Count < 3)
                {
                    Vector3 edgeACen = (vertices[fc.edges[0].x] + vertices[fc.edges[0].y]) / 2f;
                    Vector3 edgeBCen = (vertices[fc.edges[1].x] + vertices[fc.edges[1].y]) / 2f;

                    Color cola = (colors[fc.edges[0].x] + colors[fc.edges[0].y]) / 2f;
                    Color colb = (colors[fc.edges[1].x] + colors[fc.edges[1].y]) / 2f;

                    danglingVertices[i] = new DanglingVertex?[2] {
                        new DanglingVertex(edgeACen, cola), new DanglingVertex(edgeBCen, colb)
                    };

                    success[i] = SplitFace_Internal(
                        new SplitSelection(pb, fc.face, edgeACen, edgeBCen, cola, colb, false, false, new int[] { fc.edges[0].x, fc.edges[0].y }, new int[] { fc.edges[1].x, fc.edges[1].y }),
                        out splitFaces,
                        out splitVertices,
                        out splitColors,
                        out splitUVs,
                        out splitSharedIndices);

                    if (success[i])
                    {
                        successfullySplitFaces.Add(fc.face);
                    }
                }
                else
                {
                    DanglingVertex?[] appendedVertices = null;

                    success[i] = SubdivideFace_Internal(pb, fc,
                                                        out appendedVertices,
                                                        out splitFaces,
                                                        out splitVertices,
                                                        out splitColors,
                                                        out splitUVs,
                                                        out splitSharedIndices);

                    if (success[i])
                    {
                        successfullySplitFaces.Add(fc.face);
                    }

                    danglingVertices[i] = appendedVertices;
                }

                if (success[i])
                {
                    int texGroup = fc.face.textureGroup < 0 ? pb.UnusedTextureGroup(i + 1) : fc.face.textureGroup;

                    for (int j = 0; j < splitFaces.Length; j++)
                    {
                        splitFaces[j].textureGroup = texGroup;
                        all_splitFaces.Add(splitFaces[j]);
                        all_splitVertices.Add(splitVertices[j]);
                        all_splitColors.Add(splitColors[j]);
                        all_splitUVs.Add(splitUVs[j]);
                        all_splitSharedIndices.Add(splitSharedIndices[j]);
                    }
                }

                i++;
            }
            // profiler.EndSample();


            // profiler.BeginSample("Retrianguate");

            /**
             *	Figure out which faces need to be re-triangulated
             */
            pb_Edge[][] tedges = new pb_Edge[pb_edgeConnections.Count][];
            int         n      = 0;

            for (i = 0; i < pb_edgeConnections.Count; i++)
            {
                tedges[n++] = pb_edgeConnections[i].edges.ToArray();
            }

            List <pb_Face>[][] allConnects = pbMeshUtils.GetNeighborFacesJagged(pb, tedges);


            Dictionary <pb_Face, List <DanglingVertex> > addVertex = new Dictionary <pb_Face, List <DanglingVertex> >();
            List <pb_Face> temp = new List <pb_Face>();

            for (int j = 0; j < pb_edgeConnections.Count; j++)
            {
                if (!success[j])
                {
                    continue;
                }

                // check that this edge has a buddy that it welded it's new vertex to, and if not,
                // create one
                for (i = 0; i < pb_edgeConnections[j].edges.Count; i++)
                {
                    if (danglingVertices[j][i] == null)
                    {
                        continue;
                    }

                    List <pb_Face> connected = allConnects[j][i];

                    foreach (pb_Face face in connected)
                    {
                        int ind = successfullySplitFaces.IndexOf(face);

                        if (ind < 0)
                        {
                            if (addVertex.ContainsKey(face))
                            {
                                addVertex[face].Add((DanglingVertex)danglingVertices[j][i]);
                            }
                            else
                            {
                                temp.Add(face);
                                addVertex.Add(face, new List <DanglingVertex>(1)
                                {
                                    (DanglingVertex)danglingVertices[j][i]
                                });
                            }
                        }
                    }
                }
            }
            // profiler.EndSample();

            // profiler.BeginSample("Append vertices to faces");
            pb_Face[] appendedFaces = pb.AppendFaces(all_splitVertices.ToArray(), all_splitColors.ToArray(), all_splitUVs.ToArray(), all_splitFaces.ToArray(), all_splitSharedIndices.ToArray());

            List <pb_Face> triangulatedFaces = new List <pb_Face>();

            foreach (KeyValuePair <pb_Face, List <DanglingVertex> > add in addVertex)
            {
                pb_Face newFace;

                if (pb.AppendVerticesToFace(add.Key, add.Value.Select(x => x.position).ToArray(), add.Value.Select(x => x.color).ToArray(), out newFace))
                {
                    triangulatedFaces.Add(newFace);
                }
                else
                {
                    Debug.LogError("Mesh re-triangulation failed.");            //  Specifically, AppendVerticesToFace(" + add.Key + " : " + add.Value.ToFormattedString(", "));
                }
            }
            // profiler.EndSample();

            // profiler.BeginSample("rebuild mesh");

            // Re-triangulate any faces left with dangling verts at edges
            // Weld verts, including those added in re-triangu
            int[] splitFaceTris        = pb_Face.AllTriangles(appendedFaces);
            int[] triangulatedFaceTris = pb_Face.AllTriangles(triangulatedFaces);
            int[] allModifiedTris      = new int[splitFaceTris.Length + triangulatedFaceTris.Length];

            System.Array.Copy(splitFaceTris, 0, allModifiedTris, 0, splitFaceTris.Length);
            System.Array.Copy(triangulatedFaceTris, 0, allModifiedTris, splitFaceTris.Length, triangulatedFaceTris.Length);

            // safe to assume that we probably didn't delete anything :/
            int[] welds;

            // profiler.BeginSample("weld vertices");
            pb.WeldVertices(allModifiedTris, Mathf.Epsilon, out welds);

            // profiler.EndSample();
            // pb.SetSharedIndices( pb_IntArrayUtility.ExtractSharedIndices(pb.vertices) );

            // Now that we're done screwing with geo, delete all the old faces (that were successfully split)
            // profiler.BeginSample("delete faces");
            pb.DeleteFaces(successfullySplitFaces.ToArray());
            faces = appendedFaces;
            // profiler.EndSample();
            // profiler.EndSample();
            // profiler.EndSample();
            // Debug.Log(profiler.ToString());

            return(true);
        }
Ejemplo n.º 3
0
        /**
         *	Splits face per vertex.
         *	Todo - Could implement more sanity checks - namely testing for edges before sending to Split_Internal.  However,
         *	the split method is smart enough to fail on those cases, so ignore for now.
         */
        public static bool ConnectVertices(this pb_Object pb, List <VertexConnection> vertexConnectionsUnfiltered, out pb_Face[] faces)
        {
            List <VertexConnection> vertexConnections = new List <VertexConnection>();
            List <int> inds = new List <int>();

            int i = 0;

            for (i = 0; i < vertexConnectionsUnfiltered.Count; i++)
            {
                VertexConnection vc = vertexConnectionsUnfiltered[i];
                vc.indices = vc.indices.Distinct().ToList();

                if (vc.isValid)
                {
                    inds.AddRange(vc.indices);
                    vertexConnections.Add(vc);
                }
            }

            if (vertexConnections.Count < 1)
            {
                faces = null;
                return(false);
            }

            int              len = vertexConnections.Count;
            List <pb_Face>   successfullySplitFaces = new List <pb_Face>();
            List <pb_Face>   all_splitFaces         = new List <pb_Face>();
            List <Vector3[]> all_splitVertices      = new List <Vector3[]>();
            List <int[]>     all_splitSharedIndices = new List <int[]>();

            bool[] success = new bool[len];

            pb_IntArray[] sharedIndices = pb.sharedIndices;

            i = 0;
            foreach (VertexConnection vc in vertexConnections)
            {
                pb_Face[]   splitFaces         = null;
                Vector3[][] splitVertices      = null;
                int[][]     splitSharedIndices = null;

                if (vc.indices.Count < 3)
                {
                    int indA = vc.face.indices.IndexOf(vc.indices[0], sharedIndices);
                    int indB = vc.face.indices.IndexOf(vc.indices[1], sharedIndices);

                    if (indA < 0 || indB < 0)
                    {
                        success[i] = false;
                        continue;
                    }

                    indA = vc.face.indices[indA];
                    indB = vc.face.indices[indB];

                    success[i] = SplitFace_Internal(new SplitSelection(pb, vc.face, pb.vertices[indA], pb.vertices[indB], true, true, indA, indB),
                                                    out splitFaces,
                                                    out splitVertices,
                                                    out splitSharedIndices);

                    if (success[i])
                    {
                        successfullySplitFaces.Add(vc.face);
                    }
                }
                else
                {
                    success[i] = PokeFace_Internal(pb, vc.face, vc.indices.ToArray(),
                                                   out splitFaces,
                                                   out splitVertices,
                                                   out splitSharedIndices);

                    if (success[i])
                    {
                        successfullySplitFaces.Add(vc.face);
                    }
                }

                if (success[i])
                {
                    int texGroup = pb.UnusedTextureGroup(i + 1);

                    for (int j = 0; j < splitFaces.Length; j++)
                    {
                        splitFaces[j].textureGroup = texGroup;
                        all_splitFaces.Add(splitFaces[j]);
                        all_splitVertices.Add(splitVertices[j]);
                        all_splitSharedIndices.Add(splitSharedIndices[j]);
                    }
                }

                i++;
            }

            if (all_splitFaces.Count < 1)
            {
                faces = null;
                return(false);
            }

            pb_Face[] appendedFaces = pb.AppendFaces(all_splitVertices.ToArray(), all_splitFaces.ToArray(), all_splitSharedIndices.ToArray());
            inds.AddRange(pb_Face.AllTriangles(appendedFaces));

            pb.WeldVertices(inds.ToArray(), Mathf.Epsilon);

            pb.DeleteFaces(successfullySplitFaces.ToArray());

            faces = appendedFaces;
            return(true);
        }
Ejemplo n.º 4
0
        /**
         *
         */
        public static bool ConnectEdges(this pb_Object pb, List <EdgeConnection> edgeConnectionsUnfiltered, out pb_Face[] faces)
        {
            // first, remove any junk connections.  faces with less than two edges confuse this method.
            List <EdgeConnection> edgeConnections = new List <EdgeConnection>();

            foreach (EdgeConnection ec in edgeConnectionsUnfiltered)
            {
                if (ec.isValid)
                {
                    edgeConnections.Add(ec);
                }
            }

            int len = edgeConnections.Count;

            if (len < 1)
            {
                Debug.LogWarning("No valid split paths found.  This is most likely because you are attempting to split edges that do belong to the same face, or do not have more than one edge selected.  This is not currently supported, sorry!");
                faces = null;
                return(false);
            }


            Vector3[] vertices = pb.vertices;

            List <pb_Face> successfullySplitFaces = new List <pb_Face>();

            List <pb_Face>   all_splitFaces         = new List <pb_Face>();
            List <Vector3[]> all_splitVertices      = new List <Vector3[]>();
            List <int[]>     all_splitSharedIndices = new List <int[]>();

            bool[] success = new bool[len];

            // use a nullable type because in order for the adjacent face triangulation
            // code to work, it needs to know what dangling vert belongs to which edge,
            // if we out a vector3[] with each index corresponding to the passed edges
            // in EdgeConnection, it's easy to maintain the relationship.
            Vector3?[][] danglingVertices = new Vector3?[len][];

            int i = 0;

            foreach (EdgeConnection fc in edgeConnections)
            {
                pb_Face[]   splitFaces         = null;
                Vector3[][] splitVertices      = null;
                int[][]     splitSharedIndices = null;

                if (fc.edges.Count < 3)
                {
                    Vector3 edgeACen = (vertices[fc.edges[0].x] + vertices[fc.edges[0].y]) / 2f;
                    Vector3 edgeBCen = (vertices[fc.edges[1].x] + vertices[fc.edges[1].y]) / 2f;
                    danglingVertices[i] = new Vector3?[2] {
                        edgeACen, edgeBCen
                    };
                    success[i] = SplitFace_Internal(new SplitSelection(pb, fc.face, edgeACen, edgeBCen, false, false, -1, -1),
                                                    out splitFaces,
                                                    out splitVertices,
                                                    out splitSharedIndices);

                    if (success[i])
                    {
                        successfullySplitFaces.Add(fc.face);
                    }
                }
                else
                {
                    Vector3?[] appendedVertices = null;
                    success[i] = SubdivideFace_Internal(pb, fc,
                                                        out appendedVertices,
                                                        out splitFaces,
                                                        out splitVertices,
                                                        out splitSharedIndices);

                    if (success[i])
                    {
                        successfullySplitFaces.Add(fc.face);
                    }

                    danglingVertices[i] = appendedVertices;
                }

                if (success[i])
                {
                    int texGroup = fc.face.textureGroup < 0 ? pb.UnusedTextureGroup(i + 1) : fc.face.textureGroup;

                    for (int j = 0; j < splitFaces.Length; j++)
                    {
                        splitFaces[j].textureGroup = texGroup;
                        all_splitFaces.Add(splitFaces[j]);
                        all_splitVertices.Add(splitVertices[j]);
                        all_splitSharedIndices.Add(splitSharedIndices[j]);
                    }
                }

                i++;
            }

            /**
             *	Figure out which faces need to be re-triangulated
             */
            pb_Edge[][] tedges = new pb_Edge[edgeConnections.Count][];
            int         n      = 0;

            for (i = 0; i < edgeConnections.Count; i++)
            {
                tedges[n++] = edgeConnections[i].edges.ToArray();
            }

            List <pb_Face>[][] allConnects = pbMeshUtils.GetConnectedFacesJagged(pb, tedges);

            Dictionary <pb_Face, List <Vector3> > addVertex = new Dictionary <pb_Face, List <Vector3> >();
            List <pb_Face> temp = new List <pb_Face>();

            for (int j = 0; j < edgeConnections.Count; j++)
            {
                if (!success[j])
                {
                    continue;
                }

                // check that this edge has a buddy that it welded it's new vertex to, and if not,
                // create one
                for (i = 0; i < edgeConnections[j].edges.Count; i++)
                {
                    if (danglingVertices[j][i] == null)
                    {
                        continue;
                    }

                    List <pb_Face> connected = allConnects[j][i];

                    foreach (pb_Face face in connected)
                    {
                        int ind = successfullySplitFaces.IndexOf(face);

                        if (ind < 0)
                        {
                            if (addVertex.ContainsKey(face))
                            {
                                addVertex[face].Add((Vector3)danglingVertices[j][i]);
                            }
                            else
                            {
                                temp.Add(face);
                                addVertex.Add(face, new List <Vector3>(1)
                                {
                                    (Vector3)danglingVertices[j][i]
                                });
                            }
                        }
                    }
                }
            }

            pb_Face[] appendedFaces = pb.AppendFaces(all_splitVertices.ToArray(), all_splitFaces.ToArray(), all_splitSharedIndices.ToArray());

            List <pb_Face> triangulatedFaces = new List <pb_Face>();

            foreach (KeyValuePair <pb_Face, List <Vector3> > add in addVertex)
            {
                pb_Face newFace;
                if (pb.AppendVerticesToFace(add.Key, add.Value, out newFace))
                {
                    triangulatedFaces.Add(newFace);
                }
                else
                {
                    Debug.LogError("Mesh re-triangulation failed.  Specifically, AppendVerticesToFace(" + add.Key + " : " + add.Value.ToFormattedString(", "));
                }
            }

            // Re-triangulate any faces left with dangling verts at edges
            // Weld verts, including those added in re-triangu
            int[] splitFaceTris        = pb_Face.AllTriangles(appendedFaces);
            int[] triangulatedFaceTris = pb_Face.AllTriangles(triangulatedFaces);
            int[] allModifiedTris      = new int[splitFaceTris.Length + triangulatedFaceTris.Length];
            System.Array.Copy(splitFaceTris, 0, allModifiedTris, 0, splitFaceTris.Length);
            System.Array.Copy(triangulatedFaceTris, 0, allModifiedTris, splitFaceTris.Length, triangulatedFaceTris.Length);

            pb.WeldVertices(allModifiedTris, Mathf.Epsilon);

            // Now that we're done screwing with geo, delete all the old faces (that were successfully split)
            pb.DeleteFaces(successfullySplitFaces.ToArray());
            faces = appendedFaces;
            return(true);
        }
Ejemplo n.º 5
0
        /**
         * Extrudes passed faces on their normal axis using extrudeDistance.
         */
        public static bool Extrude(this pb_Object pb, pb_Face[] faces, float extrudeDistance, bool extrudeAsGroup, out pb_Face[] appendedFaces)
        {
            appendedFaces = null;

            if (faces == null || faces.Length < 1)
            {
                return(false);
            }

            pb_IntArray[]         sharedIndices = pb.GetSharedIndices();
            Dictionary <int, int> lookup        = sharedIndices.ToDictionary();

            Vector3[] localVerts = pb.vertices;

            pb_Edge[][] perimeterEdges = extrudeAsGroup ? new pb_Edge[1][] { pbMeshUtils.GetPerimeterEdges(pb, lookup, faces).ToArray() } : faces.Select(x => x.edges).ToArray();

            if (perimeterEdges == null || perimeterEdges.Length < 1 || (extrudeAsGroup && perimeterEdges[0].Length < 3))
            {
                Debug.LogWarning("No perimeter edges found.  Try deselecting and reselecting this object and trying again.");
                return(false);
            }

            pb_Face[][] edgeFaces      = new pb_Face[perimeterEdges.Length][];  // can't assume faces and perimiter edges will be 1:1 - so calculate perimeters then extract face information
            int[][]     allEdgeIndices = new int[perimeterEdges.Length][];
            int         c = 0;

            for (int i = 0; i < perimeterEdges.Length; i++)
            {
                c = 0;
                allEdgeIndices[i] = new int[perimeterEdges[i].Length * 2];
                edgeFaces[i]      = new pb_Face[perimeterEdges[i].Length];

                for (int n = 0; n < perimeterEdges[i].Length; n++)
                {
                    // gets the faces associated with each perimeter edge
                    foreach (pb_Face face in faces)
                    {
                        if (face.edges.Contains(perimeterEdges[i][n]))
                        {
                            edgeFaces[i][n] = face;
                            break;
                        }
                    }

                    allEdgeIndices[i][c++] = perimeterEdges[i][n].x;
                    allEdgeIndices[i][c++] = perimeterEdges[i][n].y;
                }
            }

            List <pb_Edge>[] extrudedIndices = new List <pb_Edge> [perimeterEdges.Length];
            Vector3[]        normals         = pb.msh.normals;

            List <Vector3[]> append_vertices = new List <Vector3[]>();
            List <Color[]>   append_color    = new List <Color[]>();
            List <Vector2[]> append_uv       = new List <Vector2[]>();
            List <pb_Face>   append_face     = new List <pb_Face>();
            List <int[]>     append_shared   = new List <int[]>();

            /// build out new faces around edges

            for (int i = 0; i < perimeterEdges.Length; i++)
            {
                extrudedIndices[i] = new List <pb_Edge>();

                for (int n = 0; n < perimeterEdges[i].Length; n++)
                {
                    pb_Edge edge = perimeterEdges[i][n];
                    pb_Face face = edgeFaces[i][n];

                    // Averages the normals using only vertices that are on the edge
                    Vector3 xnorm = Vector3.zero;
                    Vector3 ynorm = Vector3.zero;

                    // don't bother getting vertex normals if not auto-extruding
                    if (Mathf.Abs(extrudeDistance) > Mathf.Epsilon)
                    {
                        if (!extrudeAsGroup)
                        {
                            xnorm = pb_Math.Normal(localVerts[face.indices[0]], localVerts[face.indices[1]], localVerts[face.indices[2]]);
                            ynorm = xnorm;
                        }
                        else
                        {
                            xnorm = Norm(sharedIndices[lookup[edge.x]], allEdgeIndices[i], normals);
                            ynorm = Norm(sharedIndices[lookup[edge.y]], allEdgeIndices[i], normals);
                        }
                    }

                    int x_sharedIndex = lookup[edge.x];
                    int y_sharedIndex = lookup[edge.y];

                    // this could be condensed to a single call with an array of new faces
                    append_vertices.Add(new Vector3[]
                    {
                        localVerts [edge.x],
                        localVerts [edge.y],
                        localVerts [edge.x] + xnorm.normalized * extrudeDistance,
                        localVerts [edge.y] + ynorm.normalized * extrudeDistance
                    });

                    append_color.Add(new Color[]
                    {
                        pb.colors[edge.x],
                        pb.colors[edge.y],
                        pb.colors[edge.x],
                        pb.colors[edge.y]
                    });

                    append_uv.Add(new Vector2[4]);

                    append_face.Add(new pb_Face(
                                        new int[6] {
                        0, 1, 2, 1, 3, 2
                    },                                                                          // indices
                                        face.material,                                          // material
                                        new pb_UV(face.uv),                                     // UV material
                                        face.smoothingGroup,                                    // smoothing group
                                        -1,                                                     // texture group
                                        -1,                                                     // uv element group
                                        false)                                                  // manualUV flag
                                    );

                    append_shared.Add(new int[4]
                    {
                        x_sharedIndex,
                        y_sharedIndex,
                        -1,
                        -1
                    });

                    extrudedIndices[i].Add(new pb_Edge(x_sharedIndex, -1));
                    extrudedIndices[i].Add(new pb_Edge(y_sharedIndex, -1));
                }
            }

            appendedFaces = pb.AppendFaces(append_vertices.ToArray(), append_color.ToArray(), append_uv.ToArray(), append_face.ToArray(), append_shared.ToArray());

            // x = shared index, y = triangle (only known once faces are appended to pb_Object)
            for (int i = 0, f = 0; i < extrudedIndices.Length; i++)
            {
                for (int n = 0; n < extrudedIndices[i].Count; n += 2)
                {
                    extrudedIndices[i][n + 0].y = appendedFaces[f].indices[2];
                    extrudedIndices[i][n + 1].y = appendedFaces[f++].indices[4];
                }
            }

            pb_IntArray[]         si    = pb.sharedIndices; // leave the sharedIndices copy alone since we need the un-altered version later
            Dictionary <int, int> welds = si.ToDictionary();

            // Weld side-wall top vertices together, both grouped and non-grouped need this.
            for (int f = 0; f < extrudedIndices.Length; f++)
            {
                for (int i = 0; i < extrudedIndices[f].Count - 1; i++)
                {
                    int val = extrudedIndices[f][i].x;
                    for (int n = i + 1; n < extrudedIndices[f].Count; n++)
                    {
                        if (extrudedIndices[f][n].x == val)
                        {
                            welds[extrudedIndices[f][i].y] = welds[extrudedIndices[f][n].y];
                            break;
                        }
                    }
                }
            }

            localVerts = pb.vertices;

            // Remove smoothing and texture group flags
            foreach (pb_Face f in faces)
            {
                f.SetSmoothingGroup(-1);
                f.textureGroup = -1;
            }

            if (extrudeAsGroup)
            {
                foreach (pb_Face f in faces)
                {
                    int[] distinctIndices = f.distinctIndices;

                    // Merge in-group face seams
                    foreach (int ind in distinctIndices)
                    {
                        int oldIndex = si.IndexOf(ind);

                        for (int n = 0; n < allEdgeIndices.Length; n++)
                        {
                            for (int i = 0; i < extrudedIndices[n].Count; i++)
                            {
                                if (oldIndex == extrudedIndices[n][i].x)
                                {
                                    welds[ind] = welds[extrudedIndices[n][i].y];
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            else

            /**
             * If extruding as separate faces, weld each face to the tops of the bridging faces
             */
            {
                // Dictionary<int, int> hold = si.ToDictionary();

                for (int i = 0; i < edgeFaces.Length; i++)
                {
                    foreach (int n in pb_Face.AllTrianglesDistinct(edgeFaces[i]))
                    {
                        int old_si_index = lookup[n];
                        int match        = extrudedIndices[i].FindIndex(x => x.x == old_si_index);

                        if (match < 0)
                        {
                            continue;
                        }

                        int match_tri_index = extrudedIndices[i][match].y;

                        if (welds.ContainsKey(match_tri_index))
                        {
                            welds[n] = welds[match_tri_index];
                        }
                    }
                }
            }

            si = welds.ToSharedIndices();


            pb.SplitUVs(pb_Face.AllTriangles(faces));

            /**
             * Move the inside faces to the top of the extrusion
             *
             * This is a separate loop cause the one above this must completely merge all sharedindices prior to
             * checking the normal averages
             *
             */
            Vector3 norm = Vector3.zero;

            int[] allIndices = pb_Face.AllTrianglesDistinct(faces);
            foreach (pb_Face f in faces)
            {
                if (!extrudeAsGroup)
                {
                    norm = pb_Math.Normal(localVerts[f.indices[0]], localVerts[f.indices[1]], localVerts[f.indices[2]]);
                }

                foreach (int ind in f.distinctIndices)
                {
                    if (extrudeAsGroup)
                    {
                        norm = Norm(sharedIndices[lookup[ind]], allIndices, normals);
                    }

                    localVerts[ind] += norm.normalized * extrudeDistance;
                }
            }

            // Test the winding of the first pulled face, and reverse if it's ccw
            if (pb.GetWindingOrder(faces[0]) == WindingOrder.CounterClockwise)
            {
                foreach (pb_Face face in appendedFaces)
                {
                    face.ReverseIndices();
                }
            }

            pb.SetSharedIndices(si);
            pb.SetVertices(localVerts);


            return(true);
        }