static ConnectFaceRebuildData InsertVertices(Face face, List <WingedEdge> edges, List <Vertex> vertices)
        {
            List <Edge>    perimeter        = WingedEdge.SortEdgesByAdjacency(face);
            List <Vertex>  n_vertices       = new List <Vertex>();
            List <int>     newVertexIndexes = new List <int>();
            HashSet <Edge> affected         = new HashSet <Edge>(edges.Select(x => x.edge.local));

            for (int i = 0; i < perimeter.Count; i++)
            {
                n_vertices.Add(vertices[perimeter[i].a]);

                if (affected.Contains(perimeter[i]))
                {
                    newVertexIndexes.Add(n_vertices.Count);
                    n_vertices.Add(Vertex.Mix(vertices[perimeter[i].a], vertices[perimeter[i].b], .5f));
                }
            }

            FaceRebuildData res = AppendElements.FaceWithVertices(n_vertices, false);

            res.face.textureGroup   = face.textureGroup;
            res.face.uv             = new AutoUnwrapSettings(face.uv);
            res.face.smoothingGroup = face.smoothingGroup;
            res.face.manualUV       = face.manualUV;
            res.face.submeshIndex   = face.submeshIndex;

            return(new ConnectFaceRebuildData(res, newVertexIndexes));
        }
Example #2
0
        /// <summary>
        /// Insert a new vertex at the center of a face and connect the center of all edges to it.
        /// </summary>
        /// <param name="face"></param>
        /// <param name="edges"></param>
        /// <param name="vertices"></param>
        /// <returns></returns>
        static List <ConnectFaceRebuildData> ConnectEdgesInFace(
            Face face,
            List <WingedEdge> edges,
            List <Vertex> vertices)
        {
            List <Edge> perimeter  = WingedEdge.SortEdgesByAdjacency(face);
            int         splitCount = edges.Count;

            Vertex centroid = Vertex.Average(vertices, face.distinctIndexesInternal);

            List <List <Vertex> > n_vertices = ArrayUtility.Fill <List <Vertex> >(x => { return(new List <Vertex>()); }, splitCount);
            List <List <int> >    n_indexes  = ArrayUtility.Fill <List <int> >(x => { return(new List <int>()); }, splitCount);

            HashSet <Edge> edgesToSplit = new HashSet <Edge>(edges.Select(x => x.edge.local));

            int index = 0;

            // creates two new polygon perimeter lines by stepping the current face perimeter and inserting new vertices where edges match
            for (int i = 0; i < perimeter.Count; i++)
            {
                n_vertices[index % splitCount].Add(vertices[perimeter[i].a]);

                if (edgesToSplit.Contains(perimeter[i]))
                {
                    Vertex mix = Vertex.Mix(vertices[perimeter[i].a], vertices[perimeter[i].b], .5f);

                    // split current poly line
                    n_indexes[index].Add(n_vertices[index].Count);
                    n_vertices[index].Add(mix);

                    // add the centroid vertex
                    n_indexes[index].Add(n_vertices[index].Count);
                    n_vertices[index].Add(centroid);

                    // advance the poly line index
                    index = (index + 1) % splitCount;

                    // then add the edge center vertex and move on
                    n_vertices[index].Add(mix);
                }
            }

            List <ConnectFaceRebuildData> faces = new List <ConnectFaceRebuildData>();

            for (int i = 0; i < n_vertices.Count; i++)
            {
                FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false);
                if (f == null)
                {
                    faces.Clear();
                    return(null);
                }
                faces.Add(new ConnectFaceRebuildData(f, n_indexes[i]));
            }

            return(faces);
        }
Example #3
0
        /// <summary>
        /// Accepts a face and set of edges to split on.
        /// </summary>
        /// <param name="face"></param>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="vertices"></param>
        /// <returns></returns>
        static List <ConnectFaceRebuildData> ConnectEdgesInFace(
            Face face,
            WingedEdge a,
            WingedEdge b,
            List <Vertex> vertices)
        {
            List <Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face);

            List <Vertex>[] n_vertices = new List <Vertex>[2]
            {
                new List <Vertex>(),
                new List <Vertex>()
            };

            List <int>[] n_indexes = new List <int>[2]
            {
                new List <int>(),
                new List <int>()
            };

            int index = 0;

            // creates two new polygon perimeter lines by stepping the current face perimeter and inserting new vertices where edges match
            for (int i = 0; i < perimeter.Count; i++)
            {
                n_vertices[index % 2].Add(vertices[perimeter[i].a]);

                if (perimeter[i].Equals(a.edge.local) || perimeter[i].Equals(b.edge.local))
                {
                    Vertex mix = Vertex.Mix(vertices[perimeter[i].a], vertices[perimeter[i].b], .5f);

                    n_indexes[index % 2].Add(n_vertices[index % 2].Count);
                    n_vertices[index % 2].Add(mix);
                    index++;
                    n_indexes[index % 2].Add(n_vertices[index % 2].Count);
                    n_vertices[index % 2].Add(mix);
                }
            }

            List <ConnectFaceRebuildData> faces = new List <ConnectFaceRebuildData>();

            for (int i = 0; i < n_vertices.Length; i++)
            {
                FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false);
                faces.Add(new ConnectFaceRebuildData(f, n_indexes[i]));
            }

            return(faces);
        }
Example #4
0
        /// <summary>
        /// Apply a bevel to a set of edges.
        /// </summary>
        /// <param name="mesh">Target mesh.</param>
        /// <param name="edges">A set of edges to apply bevelling to.</param>
        /// <param name="amount">A value from 0 (bevel not at all) to 1 (bevel entire face).</param>
        /// <returns>The new faces created to form the bevel.</returns>
        public static List <Face> BevelEdges(ProBuilderMesh mesh, IList <Edge> edges, float amount)
        {
            if (mesh == null)
            {
                throw new ArgumentNullException("mesh");
            }

            Dictionary <int, int>  lookup      = mesh.sharedVertexLookup;
            List <Vertex>          vertices    = new List <Vertex>(mesh.GetVertices());
            List <EdgeLookup>      m_edges     = EdgeLookup.GetEdgeLookup(edges, lookup).Distinct().ToList();
            List <WingedEdge>      wings       = WingedEdge.GetWingedEdges(mesh);
            List <FaceRebuildData> appendFaces = new List <FaceRebuildData>();

            Dictionary <Face, List <int> > ignore = new Dictionary <Face, List <int> >();
            HashSet <int> slide   = new HashSet <int>();
            int           beveled = 0;

            Dictionary <int, List <SimpleTuple <FaceRebuildData, List <int> > > > holes = new Dictionary <int, List <SimpleTuple <FaceRebuildData, List <int> > > >();

            // test every edge that will be moved along to make sure the bevel distance is appropriate.  if it's not, adjust the max bevel amount
            // to suit.
            Dictionary <int, List <WingedEdge> > spokes = WingedEdge.GetSpokes(wings);
            HashSet <int> tested_common = new HashSet <int>();

            foreach (EdgeLookup e in m_edges)
            {
                if (tested_common.Add(e.common.a))
                {
                    foreach (WingedEdge w in spokes[e.common.a])
                    {
                        Edge le = w.edge.local;
                        amount = Mathf.Min(Vector3.Distance(vertices[le.a].position, vertices[le.b].position) - .001f, amount);
                    }
                }

                if (tested_common.Add(e.common.b))
                {
                    foreach (WingedEdge w in spokes[e.common.b])
                    {
                        Edge le = w.edge.local;
                        amount = Mathf.Min(Vector3.Distance(vertices[le.a].position, vertices[le.b].position) - .001f, amount);
                    }
                }
            }

            if (amount < .001f)
            {
                Log.Info("Bevel Distance > Available Surface");
                return(null);
            }

            // iterate selected edges and move each leading edge back along it's direction
            // storing information about adjacent faces in the process
            foreach (EdgeLookup lup in m_edges)
            {
                WingedEdge we = wings.FirstOrDefault(x => x.edge.Equals(lup));

                if (we == null || we.opposite == null)
                {
                    continue;
                }

                beveled++;

                ignore.AddOrAppend(we.face, we.edge.common.a);
                ignore.AddOrAppend(we.face, we.edge.common.b);
                ignore.AddOrAppend(we.opposite.face, we.edge.common.a);
                ignore.AddOrAppend(we.opposite.face, we.edge.common.b);

                // after initial slides go back and split indirect triangles at the intersecting index into two vertices
                slide.Add(we.edge.common.a);
                slide.Add(we.edge.common.b);

                SlideEdge(vertices, we, amount);
                SlideEdge(vertices, we.opposite, amount);

                appendFaces.AddRange(GetBridgeFaces(vertices, we, we.opposite, holes));
            }

            if (beveled < 1)
            {
                Log.Info("Cannot Bevel Open Edges");
                return(null);
            }

            // grab the "createdFaces" array now so that the selection returned is just the bridged faces
            // then add holes later
            var createdFaces = new List <Face>(appendFaces.Select(x => x.face));

            Dictionary <Face, List <SimpleTuple <WingedEdge, int> > > sorted = new Dictionary <Face, List <SimpleTuple <WingedEdge, int> > >();

            // sort the adjacent but affected faces into winged edge groups where each group contains a set of
            // unique winged edges pointing to the same face
            foreach (int c in slide)
            {
                IEnumerable <WingedEdge> matches = wings.Where(x => x.edge.common.Contains(c) && !(ignore.ContainsKey(x.face) && ignore[x.face].Contains(c)));

                HashSet <Face> used = new HashSet <Face>();

                foreach (WingedEdge match in matches)
                {
                    if (!used.Add(match.face))
                    {
                        continue;
                    }

                    sorted.AddOrAppend(match.face, new SimpleTuple <WingedEdge, int>(match, c));
                }
            }

            // now go through those sorted faces and apply the vertex exploding, keeping track of any holes created
            foreach (KeyValuePair <Face, List <SimpleTuple <WingedEdge, int> > > kvp in sorted)
            {
                // common index & list of vertices it was split into
                Dictionary <int, List <int> > appended;

                FaceRebuildData f = VertexEditing.ExplodeVertex(vertices, kvp.Value, amount, out appended);

                if (f == null)
                {
                    continue;
                }

                appendFaces.Add(f);

                foreach (var apv in appended)
                {
                    // organize holes by new face so that later we can compare the winding of the new face to the hole face
                    // holes are sorted by key: common index value: face, vertex list
                    holes.AddOrAppend(apv.Key, new SimpleTuple <FaceRebuildData, List <int> >(f, apv.Value));
                }
            }

            FaceRebuildData.Apply(appendFaces, mesh, vertices);
            int removed = mesh.DeleteFaces(sorted.Keys).Length;

            mesh.sharedTextures = new SharedVertex[0];
            mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal);

            // @todo don't rebuild indexes, keep 'em cached
            SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal;
            lookup = mesh.sharedVertexLookup;
            List <HashSet <int> > holesCommonIndexes = new List <HashSet <int> >();

            // offset the indexes of holes and cull any potential holes that are less than 3 indexes (not a hole :)
            foreach (KeyValuePair <int, List <SimpleTuple <FaceRebuildData, List <int> > > > hole in holes)
            {
                // less than 3 indexes in hole path; ain't a hole
                if (hole.Value.Sum(x => x.item2.Count) < 3)
                {
                    continue;
                }

                HashSet <int> holeCommon = new HashSet <int>();

                foreach (SimpleTuple <FaceRebuildData, List <int> > path in hole.Value)
                {
                    int offset = path.item1.Offset() - removed;

                    for (int i = 0; i < path.item2.Count; i++)
                    {
                        holeCommon.Add(lookup[path.item2[i] + offset]);
                    }
                }

                holesCommonIndexes.Add(holeCommon);
            }

            List <WingedEdge> modified = WingedEdge.GetWingedEdges(mesh, appendFaces.Select(x => x.face));

            // now go through the holes and create faces for them
            vertices = new List <Vertex>(mesh.GetVertices());

            List <FaceRebuildData> holeFaces = new List <FaceRebuildData>();

            foreach (HashSet <int> h in holesCommonIndexes)
            {
                // even if a set of hole indexes made it past the initial culling, the distinct part
                // may have reduced the index count
                if (h.Count < 3)
                {
                    continue;
                }
                // skip sorting the path if it's just a triangle
                if (h.Count < 4)
                {
                    List <Vertex> v = new List <Vertex>(mesh.GetVertices(h.Select(x => sharedIndexes[x][0]).ToList()));
                    holeFaces.Add(AppendElements.FaceWithVertices(v));
                }
                // if this hole has > 3 indexes, it needs a tent pole triangulation, which requires sorting into the perimeter order
                else
                {
                    List <int>    holePath = WingedEdge.SortCommonIndexesByAdjacency(modified, h);
                    List <Vertex> v        = new List <Vertex>(mesh.GetVertices(holePath.Select(x => sharedIndexes[x][0]).ToList()));
                    holeFaces.AddRange(AppendElements.TentCapWithVertices(v));
                }
            }

            FaceRebuildData.Apply(holeFaces, mesh, vertices);
            mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal);

            // go through new faces and conform hole normals
            // get a hash of just the adjacent and bridge faces
            // HashSet<pb_Face> adjacent = new HashSet<pb_Face>(appendFaces.Select(x => x.face));
            // and also just the filled holes
            HashSet <Face> newHoles = new HashSet <Face>(holeFaces.Select(x => x.face));

            // now append filled holes to the full list of added faces
            appendFaces.AddRange(holeFaces);

            List <WingedEdge> allNewFaceEdges = WingedEdge.GetWingedEdges(mesh, appendFaces.Select(x => x.face));

            for (int i = 0; i < allNewFaceEdges.Count && newHoles.Count > 0; i++)
            {
                WingedEdge wing = allNewFaceEdges[i];

                if (newHoles.Contains(wing.face))
                {
                    newHoles.Remove(wing.face);

                    // find first edge whose opposite face isn't a filled hole* then
                    // conform normal by that.
                    // *or is a filled hole but has already been conformed
                    using (var it = new WingedEdgeEnumerator(wing))
                    {
                        while (it.MoveNext())
                        {
                            var w = it.Current;

                            if (!newHoles.Contains(w.opposite.face))
                            {
                                w.face.submeshIndex = w.opposite.face.submeshIndex;
                                w.face.uv           = new AutoUnwrapSettings(w.opposite.face.uv);
                                SurfaceTopology.ConformOppositeNormal(w.opposite);
                                break;
                            }
                        }
                    }
                }
            }

            mesh.ToMesh();

            return(createdFaces);
        }
Example #5
0
        static List <ConnectFaceRebuildData> ConnectIndexesPerFace(
            Face face,
            List <int> indexes,
            List <Vertex> vertices,
            Dictionary <int, int> lookup,
            int sharedIndexOffset)
        {
            if (indexes.Count < 3)
            {
                return(null);
            }

            List <Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face);

            int splitCount = indexes.Count;

            List <List <Vertex> > n_vertices      = ArrayUtility.Fill <List <Vertex> >(x => { return(new List <Vertex>()); }, splitCount);
            List <List <int> >    n_sharedIndexes = ArrayUtility.Fill <List <int> >(x => { return(new List <int>()); }, splitCount);
            List <List <int> >    n_indexes       = ArrayUtility.Fill <List <int> >(x => { return(new List <int>()); }, splitCount);

            Vertex  center = Vertex.Average(vertices, indexes);
            Vector3 nrm    = Math.Normal(vertices, face.indexesInternal);

            int index = 0;

            for (int i = 0; i < perimeter.Count; i++)
            {
                int cur = perimeter[i].a;

                n_vertices[index].Add(vertices[cur]);
                n_sharedIndexes[index].Add(lookup[cur]);

                if (indexes.Contains(cur))
                {
                    n_indexes[index].Add(n_vertices[index].Count);
                    n_vertices[index].Add(center);
                    n_sharedIndexes[index].Add(sharedIndexOffset);

                    index = (index + 1) % splitCount;

                    n_indexes[index].Add(n_vertices[index].Count);
                    n_vertices[index].Add(vertices[cur]);
                    n_sharedIndexes[index].Add(lookup[cur]);
                }
            }

            List <ConnectFaceRebuildData> faces = new List <ConnectFaceRebuildData>();

            for (int i = 0; i < n_vertices.Count; i++)
            {
                if (n_vertices[i].Count < 3)
                {
                    continue;
                }

                FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false);
                f.sharedIndexes = n_sharedIndexes[i];

                Vector3 fn = Math.Normal(n_vertices[i], f.face.indexesInternal);

                if (Vector3.Dot(nrm, fn) < 0)
                {
                    f.face.Reverse();
                }

                faces.Add(new ConnectFaceRebuildData(f, n_indexes[i]));
            }

            return(faces);
        }
Example #6
0
        static List <ConnectFaceRebuildData> ConnectIndexesPerFace(
            Face face,
            int a,
            int b,
            List <Vertex> vertices,
            Dictionary <int, int> lookup)
        {
            List <Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face);

            List <Vertex>[] n_vertices = new List <Vertex>[] {
                new List <Vertex>(),
                new List <Vertex>()
            };

            List <int>[] n_sharedIndexes = new List <int>[] {
                new List <int>(),
                new List <int>()
            };

            List <int>[] n_indexes = new List <int>[] {
                new List <int>(),
                new List <int>()
            };

            int index = 0;

            for (int i = 0; i < perimeter.Count; i++)
            {
                // trying to connect two vertices that are already connected
                if (perimeter[i].Contains(a) && perimeter[i].Contains(b))
                {
                    return(null);
                }

                int cur = perimeter[i].a;

                n_vertices[index].Add(vertices[cur]);
                n_sharedIndexes[index].Add(lookup[cur]);

                if (cur == a || cur == b)
                {
                    index = (index + 1) % 2;

                    n_indexes[index].Add(n_vertices[index].Count);
                    n_vertices[index].Add(vertices[cur]);
                    n_sharedIndexes[index].Add(lookup[cur]);
                }
            }

            List <ConnectFaceRebuildData> faces = new List <ConnectFaceRebuildData>();
            Vector3 nrm = Math.Normal(vertices, face.indexesInternal);

            for (int i = 0; i < n_vertices.Length; i++)
            {
                FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false);
                f.sharedIndexes = n_sharedIndexes[i];

                Vector3 fn = Math.Normal(n_vertices[i], f.face.indexesInternal);

                if (Vector3.Dot(nrm, fn) < 0)
                {
                    f.face.Reverse();
                }

                faces.Add(new ConnectFaceRebuildData(f, n_indexes[i]));
            }

            return(faces);
        }