/// <summary>
        /// Attempts to find edges along an Edge loop.
        ///
        /// http://wiki.blender.org/index.php/Doc:2.4/Manual/Modeling/Meshes/Selecting/Edges says:
        /// First check to see if the selected element connects to only 3 other edges.
        /// If the edge in question has already been added to the list, the selection ends.
        /// Of the 3 edges that connect to the current edge, the ones that share a face with the current edge are eliminated
        /// and the remaining edge is added to the list and is made the current edge.
        /// </summary>
        /// <param name="mesh"></param>
        /// <param name="edges"></param>
        /// <param name="loop"></param>
        /// <returns></returns>
        internal static bool GetEdgeLoop(ProBuilderMesh mesh, IEnumerable <Edge> edges, out Edge[] loop)
        {
            List <WingedEdge>        wings        = WingedEdge.GetWingedEdges(mesh);
            IEnumerable <EdgeLookup> m_edgeLookup = EdgeLookup.GetEdgeLookup(edges, mesh.sharedVertexLookup);
            HashSet <EdgeLookup>     sources      = new HashSet <EdgeLookup>(m_edgeLookup);
            HashSet <EdgeLookup>     used         = new HashSet <EdgeLookup>();

            for (int i = 0; i < wings.Count; i++)
            {
                if (used.Contains(wings[i].edge) || !sources.Contains(wings[i].edge))
                {
                    continue;
                }

                bool completeLoop = GetEdgeLoopInternal(wings[i], wings[i].edge.common.b, used);

                // loop didn't close
                if (!completeLoop)
                {
                    GetEdgeLoopInternal(wings[i], wings[i].edge.common.a, used);
                }
            }

            loop = used.Select(x => x.local).ToArray();

            return(true);
        }
        void ExtrudeEdge()
        {
            // fetch a random perimeter edge connected to the last face extruded
            List <WingedEdge>        wings       = WingedEdge.GetWingedEdges(m_Mesh);
            IEnumerable <WingedEdge> sourceWings = wings.Where(x => x.face == m_LastExtrudedFace);
            List <Edge> nonManifoldEdges         = sourceWings.Where(x => x.opposite == null).Select(y => y.edge.local).ToList();
            int         rand       = (int)Random.Range(0, nonManifoldEdges.Count);
            Edge        sourceEdge = nonManifoldEdges[rand];

            // get the direction this edge should extrude in
            var     edgeCenter = Math.Average(m_Mesh.positions, new[] { sourceEdge.a, sourceEdge.b });
            var     faceCenter = Math.Average(m_Mesh.positions, m_LastExtrudedFace.distinctIndexes);
            Vector3 dir        = (edgeCenter - faceCenter).normalized;

            // this will be populated with the extruded edge
            Edge[] extrudedEdges;

            // perform extrusion
            extrudedEdges = m_Mesh.Extrude(new Edge[] { sourceEdge }, 0f, false, true);

            // get the last extruded face
            m_LastExtrudedFace = m_Mesh.faces.Last();

            // translate the vertices
            m_Mesh.TranslateVertices(extrudedEdges, dir * distance);

            // rebuild mesh with new geometry added by extrude
            m_Mesh.ToMesh();

            // rebuild mesh normals, textures, collisions, etc
            m_Mesh.Refresh();
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Provided two faces, this method will attempt to project @f2 and align its size, rotation, and position to match
        /// the shared edge on f1.  Returns true on success, false otherwise.
        /// </summary>
        /// <param name="mesh"></param>
        /// <param name="f1"></param>
        /// <param name="f2"></param>
        /// <param name="channel"></param>
        /// <returns></returns>
        public static bool AutoStitch(ProBuilderMesh mesh, Face f1, Face f2, int channel)
        {
            var wings = WingedEdge.GetWingedEdges(mesh, new [] { f1, f2 });

            var sharedEdge = wings.FirstOrDefault(x => x.face == f1 && x.opposite != null && x.opposite.face == f2);

            if (sharedEdge == null)
            {
                return(false);
            }

            if (f1.manualUV)
            {
                f2.manualUV = true;
            }

            f1.textureGroup = -1;
            f2.textureGroup = -1;

            Projection.PlanarProject(mesh, f2);

            if (AlignEdges(mesh, f2, sharedEdge.edge.local, sharedEdge.opposite.edge.local, channel))
            {
                if (!f2.manualUV)
                {
                    UvUnwrapping.SetAutoAndAlignUnwrapParamsToUVs(mesh, new [] { f2 });
                }

                return(true);
            }

            return(false);
        }
Ejemplo n.º 4
0
        public static List <List <Edge> > FindHoles(ProBuilderMesh mesh, HashSet <int> common)
        {
            List <List <Edge> > holes = new List <List <Edge> >();
            List <WingedEdge>   wings = WingedEdge.GetWingedEdges(mesh);

            foreach (List <WingedEdge> hole in FindHoles(wings, common))
            {
                holes.Add(hole.Select(x => x.edge.local).ToList());
            }

            return(holes);
        }
        /// <summary>
        /// Find any holes touching one of the passed vertex indexes.
        /// </summary>
        /// <param name="mesh"></param>
        /// <param name="indexes"></param>
        /// <returns></returns>
        internal static List <List <Edge> > FindHoles(ProBuilderMesh mesh, IEnumerable <int> indexes)
        {
            HashSet <int>       common = mesh.GetSharedVertexHandles(indexes);
            List <List <Edge> > holes  = new List <List <Edge> >();
            List <WingedEdge>   wings  = WingedEdge.GetWingedEdges(mesh);

            foreach (List <WingedEdge> hole in FindHoles(wings, common))
            {
                holes.Add(hole.Select(x => x.edge.local).ToList());
            }

            return(holes);
        }
        /// <summary>
        /// Grow faces to include any face touching the perimeter edges.
        /// </summary>
        /// <param name="mesh">The source mesh.</param>
        /// <param name="faces">The faces to grow out from.</param>
        /// <param name="maxAngleDiff">If provided, adjacent faces must have a normal that is within maxAngleDiff (in degrees) difference of the perimeter face.</param>
        /// <returns>The original faces selection, plus any new faces added as a result the grow operation.</returns>
        public static HashSet <Face> GrowSelection(ProBuilderMesh mesh, IEnumerable <Face> faces, float maxAngleDiff = -1f)
        {
            List <WingedEdge> wings       = WingedEdge.GetWingedEdges(mesh, true);
            HashSet <Face>    source      = new HashSet <Face>(faces);
            HashSet <Face>    neighboring = new HashSet <Face>();

            Vector3 srcNormal  = Vector3.zero;
            bool    checkAngle = maxAngleDiff > 0f;

            for (int i = 0; i < wings.Count; i++)
            {
                if (!source.Contains(wings[i].face))
                {
                    continue;
                }

                if (checkAngle)
                {
                    srcNormal = Math.Normal(mesh, wings[i].face);
                }

                using (var it = new WingedEdgeEnumerator(wings[i]))
                {
                    while (it.MoveNext())
                    {
                        var w = it.Current;

                        if (w.opposite != null && !source.Contains(w.opposite.face))
                        {
                            if (checkAngle)
                            {
                                Vector3 oppNormal = Math.Normal(mesh, w.opposite.face);

                                if (Vector3.Angle(srcNormal, oppNormal) < maxAngleDiff)
                                {
                                    neighboring.Add(w.opposite.face);
                                }
                            }
                            else
                            {
                                neighboring.Add(w.opposite.face);
                            }
                        }
                    }
                }
            }

            return(neighboring);
        }
        /// <summary>
        /// Iterates through face edges and builds a list using the opposite edge, iteratively.
        /// </summary>
        /// <param name="pb">The probuilder mesh</param>
        /// <param name="edges">The edges already selected</param>
        /// <returns>The new selected edges</returns>
        internal static IEnumerable <Edge> GetEdgeRingIterative(ProBuilderMesh pb, IEnumerable <Edge> edges)
        {
            List <WingedEdge> wings      = WingedEdge.GetWingedEdges(pb);
            List <EdgeLookup> edgeLookup = EdgeLookup.GetEdgeLookup(edges, pb.sharedVertexLookup).ToList();

            edgeLookup = edgeLookup.Distinct().ToList();

            Dictionary <Edge, WingedEdge> wings_dic = new Dictionary <Edge, WingedEdge>();

            for (int i = 0; i < wings.Count; i++)
            {
                if (!wings_dic.ContainsKey(wings[i].edge.common))
                {
                    wings_dic.Add(wings[i].edge.common, wings[i]);
                }
            }

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

            for (int i = 0, c = edgeLookup.Count; i < c; i++)
            {
                WingedEdge we;

                if (!wings_dic.TryGetValue(edgeLookup[i].common, out we))
                {
                    continue;
                }

                WingedEdge cur = we;

                if (!used.Contains(cur.edge))
                {
                    used.Add(cur.edge);
                }
                var next = EdgeRingNext(cur);
                if (next != null && next.opposite != null && !used.Contains(next.edge))
                {
                    used.Add(next.edge);
                }
                var prev = EdgeRingNext(cur.opposite);
                if (prev != null && prev.opposite != null && !used.Contains(prev.edge))
                {
                    used.Add(prev.edge);
                }
            }

            return(used.Select(x => x.local));
        }
Ejemplo n.º 8
0
        /// <summary>
        /// Ensure that all adjacent face normals are pointing in a uniform direction. This function supports multiple islands of connected faces, but it may not unify each island the same way.
        /// </summary>
        /// <param name="mesh">The mesh that the faces belong to.</param>
        /// <param name="faces">The faces to make uniform.</param>
        /// <returns>The state of the action.</returns>
        public static ActionResult ConformNormals(this ProBuilderMesh mesh, IEnumerable <Face> faces)
        {
            List <WingedEdge> wings = WingedEdge.GetWingedEdges(mesh, faces);
            HashSet <Face>    used  = new HashSet <Face>();
            int count = 0;

            // this loop adds support for multiple islands of grouped selections
            for (int i = 0; i < wings.Count; i++)
            {
                if (used.Contains(wings[i].face))
                {
                    continue;
                }

                Dictionary <Face, bool> flags = new Dictionary <Face, bool>();

                GetWindingFlags(wings[i], true, flags);

                int flip = 0;

                foreach (var kvp in flags)
                {
                    flip += kvp.Value ? 1 : -1;
                }

                bool direction = flip > 0;

                foreach (var kvp in flags)
                {
                    if (direction != kvp.Value)
                    {
                        count++;
                        kvp.Key.Reverse();
                    }
                }

                used.UnionWith(flags.Keys);
            }

            if (count > 0)
            {
                return(new ActionResult(ActionResult.Status.Success, count > 1 ? string.Format("Flipped {0} faces", count) : "Flipped 1 face"));
            }
            else
            {
                return(new ActionResult(ActionResult.Status.NoChange, "Faces Uniform"));
            }
        }
        /// <summary>
        /// Recursively add all faces touching any of the selected faces.
        /// </summary>
        /// <param name="mesh">The source mesh.</param>
        /// <param name="faces">The starting faces.</param>
        /// <param name="maxAngleDiff">Faces must have a normal that is within maxAngleDiff (in degrees) difference of the perimeter face to be added to the collection.</param>
        /// <returns>A collection of faces that are connected by shared edges to the original faces.</returns>
        public static HashSet <Face> FloodSelection(ProBuilderMesh mesh, IList <Face> faces, float maxAngleDiff)
        {
            List <WingedEdge> wings  = WingedEdge.GetWingedEdges(mesh, true);
            HashSet <Face>    source = new HashSet <Face>(faces);
            HashSet <Face>    flood  = new HashSet <Face>();

            for (int i = 0; i < wings.Count; i++)
            {
                if (!flood.Contains(wings[i].face) && source.Contains(wings[i].face))
                {
                    flood.Add(wings[i].face);
                    Flood(mesh, wings[i], maxAngleDiff > 0f ? Math.Normal(mesh, wings[i].face) : Vector3_Zero, maxAngleDiff, flood);
                }
            }
            return(flood);
        }
        static IEnumerable <List <Face> > GetFaceSelectionGroups(ProBuilderMesh mesh)
        {
            var wings  = WingedEdge.GetWingedEdges(mesh, mesh.selectedFacesInternal, true);
            var filter = new HashSet <Face>();
            var groups = new List <List <Face> >();

            foreach (var wing in wings)
            {
                var group = new List <Face>()
                {
                };
                CollectAdjacentFaces(wing, filter, group);
                if (group.Count > 0)
                {
                    groups.Add(group);
                }
            }

            return(groups);
        }
        /// <summary>
        /// Fetch a face loop.
        /// </summary>
        /// <param name="mesh">The source mesh.</param>
        /// <param name="faces">The faces to scan for loops.</param>
        /// <param name="ring">Toggles between loop and ring. Ring and loop are arbritary with faces, so this parameter just toggles between which gets scanned first.</param>
        /// <returns>A collection of faces gathered by extending a ring or loop,</returns>
        public static HashSet <Face> GetFaceLoop(ProBuilderMesh mesh, Face[] faces, bool ring = false)
        {
            if (mesh == null)
            {
                throw new ArgumentNullException("mesh");
            }

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

            HashSet <Face>    loops = new HashSet <Face>();
            List <WingedEdge> wings = WingedEdge.GetWingedEdges(mesh);

            foreach (Face face in faces)
            {
                loops.UnionWith(GetFaceLoop(wings, face, ring));
            }

            return(loops);
        }
Ejemplo n.º 12
0
        static IEnumerable <List <Face> > GetFaceSelectionGroups(ProBuilderMesh mesh)
        {
            var wings    = WingedEdge.GetWingedEdges(mesh, mesh.selectedFacesInternal, true);
            var filter   = new HashSet <Face>();
            var groups   = new List <List <Face> >();
            var groupIdx = -1;
            var i        = -1;

            foreach (var wing in wings)
            {
                var group = new List <Face>()
                {
                };
                CollectAdjacentFaces(wing, filter, group);
                if (group.Count > 0)
                {
                    i++;
                    // Make sure the last selected face is the last in the group
                    var idx = group.IndexOf(mesh.selectedFacesInternal[mesh.selectedFacesInternal.Length - 1]);
                    if (idx != -1 && idx < group.Count)
                    {
                        var item = group[idx];
                        groupIdx               = i;
                        group[idx]             = group[group.Count - 1];
                        group[group.Count - 1] = item;
                    }
                    groups.Add(group);
                }
            }
            // Make sure the last selected face's group is the last in the groups
            if (groupIdx != -1 && groupIdx < groups.Count)
            {
                var item = groups[groupIdx];
                groups[groupIdx]         = groups[groups.Count - 1];
                groups[groups.Count - 1] = item;
            }

            return(groups);
        }
Ejemplo n.º 13
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);
        }
        public void FindSpecialSides()
        {
            if (this.pb.faces.Count < 5)
            {
                return;
            }

            this.topFace = this.pb.faces[0];
            var topWingedEdges = WingedEdge.GetWingedEdges(this.pb, new Face[] { this.topFace }, false);

            // For now, this only works on blocks with a building with 4 sides.
            if (this.topFace.edges.Count != 4)
            {
                return;
            }

            this.fourSides = true;
            float shortestDistance = 0f;

            // find shortest side:
            foreach (var wingedEdge in topWingedEdges)
            {
                var vertices = this.pb.GetVertices(new List <int>()
                {
                    wingedEdge.edge.local.a, wingedEdge.edge.local.b
                });
                var edgeLength = Vector3.Distance(vertices[0].position, vertices[1].position);
                if (shortestDistance == 0 || edgeLength < shortestDistance)
                {
                    shortestDistance               = edgeLength;
                    this.topFaceShortestEdge       = wingedEdge.edge.local;
                    this.topFaceShortestEdgeCommon = wingedEdge.edge.common;
                }
            }


            // search the opposite edge (i.e. the edge that shares no corners / vertices with the shortest edge)
            foreach (var wingedEdge in topWingedEdges)
            {
                var edge = wingedEdge.edge.local;

                if (edge.a != this.topFaceShortestEdge.a && edge.a != this.topFaceShortestEdge.b && edge.b != this.topFaceShortestEdge.a && edge.b != this.topFaceShortestEdge.b)
                {
                    this.topFaceOppositeEdge       = edge;
                    this.topFaceOppositeEdgeCommon = wingedEdge.edge.common;
                    break;
                }
            }

            var wingedEdges = WingedEdge.GetWingedEdges(this.pb, this.pb.faces, false);

            foreach (var wingedEdge in wingedEdges)
            {
                if (wingedEdge.edge.common == this.topFaceShortestEdgeCommon && wingedEdge.face != this.topFace)
                {
                    this.frontFace        = wingedEdge.face;
                    this.frontFaceTopEdge = wingedEdge.edge.local;
                }
                if (wingedEdge.edge.common == this.topFaceOppositeEdgeCommon && wingedEdge.face != this.topFace)
                {
                    this.backFace        = wingedEdge.face;
                    this.backFaceTopEdge = wingedEdge.edge.local;
                }
            }
        }
Ejemplo n.º 15
0
        public override void FillHoles()
        {
            int filled = 0;

            foreach (ProBuilderMesh mesh in m_edgeSelection.Meshes)
            {
                //MeshSelection selection = new MeshSelection();
                //selection.SelectedEdges.Add(mesh, m_edgeSelection.GetEdges(mesh));
                //selection.EdgesToVertices(false);

                HashSet <int> indexes = new HashSet <int>();
                for (int i = 0; i < mesh.vertexCount; ++i)
                {
                    indexes.Add(i);
                }
                List <List <Edge> > holes = PBElementSelection.FindHoles(mesh, indexes);

                mesh.ToMesh();

                List <WingedEdge> wings         = WingedEdge.GetWingedEdges(mesh);
                HashSet <Face>    appendedFaces = new HashSet <Face>();

                // const bool wholePath = false;

                foreach (List <Edge> hole in holes)
                {
                    List <int> holeIndexes;
                    Face       face;

                    if (!hole.All(e => m_edgeSelection.IsSelected(mesh, e)))
                    {
                        continue;
                    }

                    //if (wholePath)
                    //{
                    //    // if selecting whole path and in edge mode, make sure the path contains
                    //    // at least one complete edge from the selection.
                    //    if (!hole.Any(x => common.Contains(x.edge.common.a) &&
                    //            common.Contains(x.edge.common.b)))
                    //        continue;

                    //    holeIndexes = hole.Select(x => x.edge.local.a).ToList();
                    //    face = AppendElements.CreatePolygon(mesh, holeIndexes, false);
                    //}
                    //else
                    {
                        //IEnumerable<WingedEdge> selected = hole.Where(x => common.Contains(x.edge.common.a));
                        //holeIndexes = selected.Select(x => x.edge.local.a).ToList();

                        //holeIndexes = hole.Select(x => x.edge.local.a).ToList();
                        //face = AppendElements.CreatePolygon(mesh, holeIndexes, true);

                        holeIndexes = hole.Select(x => x.a).ToList();
                        face        = AppendElements.CreatePolygon(mesh, holeIndexes, true);
                    }

                    if (face != null)
                    {
                        filled++;
                        appendedFaces.Add(face);
                    }
                }

                mesh.SetSelectedFaces(appendedFaces);

                wings = WingedEdge.GetWingedEdges(mesh);

                // make sure the appended faces match the first adjacent face found
                // both in winding and face properties
                foreach (var appendedFace in appendedFaces)
                {
                    var wing = wings.FirstOrDefault(x => x.face == appendedFace);

                    if (wing == null)
                    {
                        continue;
                    }

                    using (var it = new WingedEdgeEnumerator(wing))
                    {
                        while (it.MoveNext())
                        {
                            if (it.Current == null)
                            {
                                continue;
                            }

                            var currentWing  = it.Current;
                            var oppositeFace = it.Current.opposite != null ? it.Current.opposite.face : null;

                            if (oppositeFace != null && !appendedFaces.Contains(oppositeFace))
                            {
                                currentWing.face.submeshIndex = oppositeFace.submeshIndex;
                                currentWing.face.uv           = new AutoUnwrapSettings(oppositeFace.uv);
                                PBSurfaceTopology.ConformOppositeNormal(currentWing.opposite);
                                break;
                            }
                        }
                    }
                }

                mesh.ToMesh();
                mesh.Refresh();
                //mesh.Optimize();
            }
        }
Ejemplo n.º 16
0
        /// <summary>
        /// Inserts new edges connecting the passed edges, optionally restricting new edge insertion to faces in faceMask.
        /// </summary>
        /// <param name="mesh"></param>
        /// <param name="edges"></param>
        /// <param name="addedFaces"></param>
        /// <param name="connections"></param>
        /// <param name="returnFaces"></param>
        /// <param name="returnEdges"></param>
        /// <param name="faceMask"></param>
        /// <returns></returns>
        internal static ActionResult Connect(
            this ProBuilderMesh mesh,
            IEnumerable <Edge> edges,
            out Face[] addedFaces,
            out Edge[] connections,
            bool returnFaces        = false,
            bool returnEdges        = false,
            HashSet <Face> faceMask = null)
        {
            Dictionary <int, int> lookup        = mesh.sharedVertexLookup;
            Dictionary <int, int> lookupUV      = mesh.sharedTextureLookup;
            HashSet <EdgeLookup>  distinctEdges = new HashSet <EdgeLookup>(EdgeLookup.GetEdgeLookup(edges, lookup));
            List <WingedEdge>     wings         = WingedEdge.GetWingedEdges(mesh);

            // map each edge to a face so that we have a list of all touched faces with their to-be-subdivided edges
            Dictionary <Face, List <WingedEdge> > touched = new Dictionary <Face, List <WingedEdge> >();

            foreach (WingedEdge wing in wings)
            {
                if (distinctEdges.Contains(wing.edge))
                {
                    List <WingedEdge> faceEdges;
                    if (touched.TryGetValue(wing.face, out faceEdges))
                    {
                        faceEdges.Add(wing);
                    }
                    else
                    {
                        touched.Add(wing.face, new List <WingedEdge>()
                        {
                            wing
                        });
                    }
                }
            }

            Dictionary <Face, List <WingedEdge> > affected = new Dictionary <Face, List <WingedEdge> >();

            // weed out edges that won't actually connect to other edges (if you don't play ya' can't stay)
            foreach (KeyValuePair <Face, List <WingedEdge> > kvp in touched)
            {
                if (kvp.Value.Count <= 1)
                {
                    WingedEdge opp = kvp.Value[0].opposite;

                    if (opp == null)
                    {
                        continue;
                    }

                    List <WingedEdge> opp_list;

                    if (!touched.TryGetValue(opp.face, out opp_list))
                    {
                        continue;
                    }

                    if (opp_list.Count <= 1)
                    {
                        continue;
                    }
                }

                affected.Add(kvp.Key, kvp.Value);
            }

            List <Vertex> vertices = new List <Vertex>(mesh.GetVertices());
            List <ConnectFaceRebuildData> results = new List <ConnectFaceRebuildData>();
            // just the faces that where connected with > 1 edge
            List <Face> connectedFaces = new List <Face>();

            HashSet <int> usedTextureGroups    = new HashSet <int>(mesh.facesInternal.Select(x => x.textureGroup));
            int           newTextureGroupIndex = 1;

            // do the splits
            foreach (KeyValuePair <Face, List <WingedEdge> > split in affected)
            {
                Face face = split.Key;
                List <WingedEdge> targetEdges = split.Value;
                int     inserts = targetEdges.Count;
                Vector3 nrm     = Math.Normal(vertices, face.indexesInternal);

                if (inserts == 1 || (faceMask != null && !faceMask.Contains(face)))
                {
                    ConnectFaceRebuildData c;

                    if (InsertVertices(face, targetEdges, vertices, out c))
                    {
                        Vector3 fn = Math.Normal(c.faceRebuildData.vertices, c.faceRebuildData.face.indexesInternal);

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

                        results.Add(c);
                    }
                }
                else if (inserts > 1)
                {
                    List <ConnectFaceRebuildData> res = inserts == 2 ?
                                                        ConnectEdgesInFace(face, targetEdges[0], targetEdges[1], vertices) :
                                                        ConnectEdgesInFace(face, targetEdges, vertices);

                    if (face.textureGroup < 0)
                    {
                        while (usedTextureGroups.Contains(newTextureGroupIndex))
                        {
                            newTextureGroupIndex++;
                        }

                        usedTextureGroups.Add(newTextureGroupIndex);
                    }


                    if (res == null)
                    {
                        connections = null;
                        addedFaces  = null;
                        return(new ActionResult(ActionResult.Status.Failure, "Unable to connect faces"));
                    }
                    else
                    {
                        foreach (ConnectFaceRebuildData c in res)
                        {
                            connectedFaces.Add(c.faceRebuildData.face);

                            Vector3 fn = Math.Normal(c.faceRebuildData.vertices,
                                                     c.faceRebuildData.face.indexesInternal);

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

                            c.faceRebuildData.face.textureGroup =
                                face.textureGroup < 0 ? newTextureGroupIndex : face.textureGroup;
                            c.faceRebuildData.face.uv             = new AutoUnwrapSettings(face.uv);
                            c.faceRebuildData.face.submeshIndex   = face.submeshIndex;
                            c.faceRebuildData.face.smoothingGroup = face.smoothingGroup;
                            c.faceRebuildData.face.manualUV       = face.manualUV;
                        }

                        results.AddRange(res);
                    }
                }
            }

            FaceRebuildData.Apply(results.Select(x => x.faceRebuildData), mesh, vertices, null);

            mesh.sharedTextures = new SharedVertex[0];
            int removedVertexCount = mesh.DeleteFaces(affected.Keys).Length;

            mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal);
            mesh.ToMesh();

            // figure out where the new edges where inserted
            if (returnEdges)
            {
                // offset the newVertexIndexes by whatever the FaceRebuildData did so we can search for the new edges by index
                var appended = new HashSet <int>();

                for (int n = 0; n < results.Count; n++)
                {
                    for (int i = 0; i < results[n].newVertexIndexes.Count; i++)
                    {
                        appended.Add((results[n].newVertexIndexes[i] + results[n].faceRebuildData.Offset()) - removedVertexCount);
                    }
                }

                Dictionary <int, int>    lup          = mesh.sharedVertexLookup;
                IEnumerable <Edge>       newEdges     = results.SelectMany(x => x.faceRebuildData.face.edgesInternal).Where(x => appended.Contains(x.a) && appended.Contains(x.b));
                IEnumerable <EdgeLookup> distNewEdges = EdgeLookup.GetEdgeLookup(newEdges, lup);

                connections = distNewEdges.Distinct().Select(x => x.local).ToArray();
            }
            else
            {
                connections = null;
            }

            if (returnFaces)
            {
                addedFaces = connectedFaces.ToArray();
            }
            else
            {
                addedFaces = null;
            }

            return(new ActionResult(ActionResult.Status.Success, string.Format("Connected {0} Edges", results.Count / 2)));
        }
        /// <summary>
        /// Iterates through face edges and builds a list using the opposite edge.
        /// </summary>
        /// <param name="pb"></param>
        /// <param name="edges"></param>
        /// <returns></returns>
        internal static IEnumerable <Edge> GetEdgeRing(ProBuilderMesh pb, IEnumerable <Edge> edges)
        {
            List <WingedEdge> wings      = WingedEdge.GetWingedEdges(pb);
            List <EdgeLookup> edgeLookup = EdgeLookup.GetEdgeLookup(edges, pb.sharedVertexLookup).ToList();

            edgeLookup = edgeLookup.Distinct().ToList();

            Dictionary <Edge, WingedEdge> wings_dic = new Dictionary <Edge, WingedEdge>();

            for (int i = 0; i < wings.Count; i++)
            {
                if (!wings_dic.ContainsKey(wings[i].edge.common))
                {
                    wings_dic.Add(wings[i].edge.common, wings[i]);
                }
            }

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

            for (int i = 0, c = edgeLookup.Count; i < c; i++)
            {
                WingedEdge we;

                if (!wings_dic.TryGetValue(edgeLookup[i].common, out we) || used.Contains(we.edge))
                {
                    continue;
                }

                WingedEdge cur = we;

                while (cur != null)
                {
                    if (!used.Add(cur.edge))
                    {
                        break;
                    }
                    cur = EdgeRingNext(cur);
                    if (cur != null && cur.opposite != null)
                    {
                        cur = cur.opposite;
                    }
                }

                cur = EdgeRingNext(we.opposite);
                if (cur != null && cur.opposite != null)
                {
                    cur = cur.opposite;
                }

                // run in both directions
                while (cur != null)
                {
                    if (!used.Add(cur.edge))
                    {
                        break;
                    }
                    cur = EdgeRingNext(cur);
                    if (cur != null && cur.opposite != null)
                    {
                        cur = cur.opposite;
                    }
                }
            }

            return(used.Select(x => x.local));
        }
Ejemplo n.º 18
0
        /// <summary>
        /// Extrude faces as groups.
        /// </summary>
        /// <param name="mesh"></param>
        /// <param name="faces"></param>
        /// <param name="compensateAngleVertexDistance"></param>
        /// <param name="distance"></param>
        /// <returns></returns>
        static Face[] ExtrudeAsGroups(ProBuilderMesh mesh, IEnumerable <Face> faces, bool compensateAngleVertexDistance, float distance)
        {
            if (faces == null || !faces.Any())
            {
                return(null);
            }

            List <Vertex>         vertices          = new List <Vertex>(mesh.GetVertices());
            int                   sharedIndexMax    = mesh.sharedVerticesInternal.Length;
            int                   sharedIndexOffset = 0;
            Dictionary <int, int> lookup            = mesh.sharedVertexLookup;
            Dictionary <int, int> lookupUV          = mesh.sharedTextureLookup;

            List <Face> newFaces = new List <Face>();
            // old triangle index -> old shared index
            Dictionary <int, int> oldSharedMap = new Dictionary <int, int>();
            // old shared index -> new shared index
            Dictionary <int, int> newSharedMap = new Dictionary <int, int>();
            // bridge face extruded edges, maps vertex index to new extruded vertex position
            Dictionary <int, int> delayPosition = new Dictionary <int, int>();
            // used to average the direction of vertices shared by perimeter edges
            // key[shared index], value[normal count, normal sum]
            Dictionary <int, SimpleTuple <Vector3, Vector3, List <int> > > extrudeMap = new Dictionary <int, SimpleTuple <Vector3, Vector3, List <int> > >();

            List <WingedEdge>      wings  = WingedEdge.GetWingedEdges(mesh, faces, true);
            List <HashSet <Face> > groups = GetFaceGroups(wings);

            foreach (HashSet <Face> group in groups)
            {
                Dictionary <EdgeLookup, Face> perimeter = GetPerimeterEdges(group, lookup);

                newSharedMap.Clear();
                oldSharedMap.Clear();

                foreach (var edgeAndFace in perimeter)
                {
                    EdgeLookup edge = edgeAndFace.Key;
                    Face       face = edgeAndFace.Value;

                    int vc = vertices.Count;
                    int x = edge.local.a, y = edge.local.b;

                    if (!oldSharedMap.ContainsKey(x))
                    {
                        oldSharedMap.Add(x, lookup[x]);
                        int newSharedIndex = -1;

                        if (newSharedMap.TryGetValue(lookup[x], out newSharedIndex))
                        {
                            lookup[x] = newSharedIndex;
                        }
                        else
                        {
                            newSharedIndex = sharedIndexMax + (sharedIndexOffset++);
                            newSharedMap.Add(lookup[x], newSharedIndex);
                            lookup[x] = newSharedIndex;
                        }
                    }

                    if (!oldSharedMap.ContainsKey(y))
                    {
                        oldSharedMap.Add(y, lookup[y]);
                        int newSharedIndex = -1;

                        if (newSharedMap.TryGetValue(lookup[y], out newSharedIndex))
                        {
                            lookup[y] = newSharedIndex;
                        }
                        else
                        {
                            newSharedIndex = sharedIndexMax + (sharedIndexOffset++);
                            newSharedMap.Add(lookup[y], newSharedIndex);
                            lookup[y] = newSharedIndex;
                        }
                    }

                    lookup.Add(vc + 0, oldSharedMap[x]);
                    lookup.Add(vc + 1, oldSharedMap[y]);
                    lookup.Add(vc + 2, lookup[x]);
                    lookup.Add(vc + 3, lookup[y]);

                    delayPosition.Add(vc + 2, x);
                    delayPosition.Add(vc + 3, y);

                    vertices.Add(new Vertex(vertices[x]));
                    vertices.Add(new Vertex(vertices[y]));

                    // extruded edge will be positioned later
                    vertices.Add(null);
                    vertices.Add(null);

                    Face bridge = new Face(
                        new int[6] {
                        vc + 0, vc + 1, vc + 2, vc + 1, vc + 3, vc + 2
                    },
                        face.submeshIndex,
                        new AutoUnwrapSettings(face.uv),
                        Smoothing.smoothingGroupNone,
                        -1,
                        -1,
                        false
                        );

                    newFaces.Add(bridge);
                }

                foreach (Face face in group)
                {
                    // @todo keep together if possible
                    face.textureGroup = -1;

                    Vector3 normal = Math.Normal(mesh, face);

                    for (int i = 0; i < face.distinctIndexesInternal.Length; i++)
                    {
                        int idx = face.distinctIndexesInternal[i];

                        // If this vertex is on the perimeter but not part of a perimeter edge
                        // move the sharedIndex to match it's new value.
                        if (!oldSharedMap.ContainsKey(idx) && newSharedMap.ContainsKey(lookup[idx]))
                        {
                            lookup[idx] = newSharedMap[lookup[idx]];
                        }

                        int com = lookup[idx];

                        // Break any UV shared connections
                        if (lookupUV != null && lookupUV.ContainsKey(face.distinctIndexesInternal[i]))
                        {
                            lookupUV.Remove(face.distinctIndexesInternal[i]);
                        }

                        // add the normal to the list of normals for this shared vertex
                        SimpleTuple <Vector3, Vector3, List <int> > dir;

                        if (extrudeMap.TryGetValue(com, out dir))
                        {
                            dir.item1 += normal;
                            dir.item3.Add(idx);
                            extrudeMap[com] = dir;
                        }
                        else
                        {
                            extrudeMap.Add(com, new SimpleTuple <Vector3, Vector3, List <int> >(normal, normal, new List <int>()
                            {
                                idx
                            }));
                        }
                    }
                }
            }

            foreach (var kvp in extrudeMap)
            {
                Vector3 direction = (kvp.Value.item1 / kvp.Value.item3.Count);
                direction.Normalize();

                // If extruding by face normal extend vertices on seams by the hypotenuse
                float modifier = compensateAngleVertexDistance ? Math.Secant(Vector3.Angle(direction, kvp.Value.item2) * Mathf.Deg2Rad) : 1f;

                direction.x *= distance * modifier;
                direction.y *= distance * modifier;
                direction.z *= distance * modifier;

                foreach (int i in kvp.Value.item3)
                {
                    vertices[i].position += direction;
                }
            }

            foreach (var kvp in delayPosition)
            {
                vertices[kvp.Key] = new Vertex(vertices[kvp.Value]);
            }

            mesh.SetVertices(vertices);

            var fc       = mesh.faceCount;
            var nc       = newFaces.Count;
            var appended = new Face[fc + nc];

            Array.Copy(mesh.facesInternal, 0, appended, 0, fc);
            for (int i = fc, c = fc + nc; i < c; i++)
            {
                appended[i] = newFaces[i - fc];
            }
            mesh.faces = appended;
            mesh.SetSharedVertices(lookup);
            mesh.SetSharedTextures(lookupUV);

            return(newFaces.ToArray());
        }
        public static List <Face> ToQuads(this ProBuilderMesh mesh, IList <Face> faces, bool smoothing = true)
        {
            HashSet <Face> processed = new HashSet <Face>();

            List <WingedEdge> wings = WingedEdge.GetWingedEdges(mesh, faces, true);

            // build a lookup of the strength of edge connections between triangle faces
            Dictionary <EdgeLookup, float> connections = new Dictionary <EdgeLookup, float>();

            for (int i = 0; i < wings.Count; i++)
            {
                using (var it = new WingedEdgeEnumerator(wings[i]))
                {
                    while (it.MoveNext())
                    {
                        var border = it.Current;

                        if (border.opposite != null && !connections.ContainsKey(border.edge))
                        {
                            float score = mesh.GetQuadScore(border, border.opposite);
                            connections.Add(border.edge, score);
                        }
                    }
                }
            }

            List <SimpleTuple <Face, Face> > quads = new List <SimpleTuple <Face, Face> >();

            // move through each face and find it's best quad neighbor
            foreach (WingedEdge face in wings)
            {
                if (!processed.Add(face.face))
                {
                    continue;
                }

                float bestScore = 0f;
                Face  buddy     = null;

                using (var it = new WingedEdgeEnumerator(face))
                {
                    while (it.MoveNext())
                    {
                        var border = it.Current;

                        if (border.opposite != null && processed.Contains(border.opposite.face))
                        {
                            continue;
                        }

                        float borderScore;

                        // only add it if the opposite face's best score is also this face
                        if (connections.TryGetValue(border.edge, out borderScore) &&
                            borderScore > bestScore &&
                            face.face == GetBestQuadConnection(border.opposite, connections))
                        {
                            bestScore = borderScore;
                            buddy     = border.opposite.face;
                        }
                    }
                }

                if (buddy != null)
                {
                    processed.Add(buddy);
                    quads.Add(new SimpleTuple <Face, Face>(face.face, buddy));
                }
            }

            // don't collapse coincident vertices if smoothing is enabled, we need the original normals intact
            return(MergeElements.MergePairs(mesh, quads, smoothing));
        }
Ejemplo n.º 20
0
        /// <summary>
        /// Import mesh data from a GameObject's MeshFilter.sharedMesh and MeshRenderer.sharedMaterials.
        /// </summary>
        /// <param name="originalMesh">The UnityEngine.Mesh to extract attributes from.</param>
        /// <param name="materials">The materials array corresponding to the originalMesh submeshes.</param>
        /// <param name="importSettings">Optional settings parameter defines import customization properties.</param>
        /// <exception cref="NotSupportedException">Import only supports triangle and quad mesh topologies.</exception>
        public void Import(MeshImportSettings importSettings = null)
        {
            if (importSettings == null)
            {
                importSettings = k_DefaultImportSettings;
            }

            // When importing the mesh is always split into triangles with no vertices shared
            // between faces. In a later step co-incident vertices are collapsed (eg, before
            // leaving the Import function).
            Vertex[]      sourceVertices = m_SourceMesh.GetVertices();
            List <Vertex> splitVertices  = new List <Vertex>();
            List <Face>   faces          = new List <Face>();

            // Fill in Faces array with just the position indexes. In the next step we'll
            // figure out smoothing groups & merging
            int vertexIndex   = 0;
            int materialCount = m_SourceMaterials != null ? m_SourceMaterials.Length : 0;

            for (int submeshIndex = 0; submeshIndex < m_SourceMesh.subMeshCount; submeshIndex++)
            {
                switch (m_SourceMesh.GetTopology(submeshIndex))
                {
                case MeshTopology.Triangles:
                {
                    int[] indexes = m_SourceMesh.GetIndices(submeshIndex);

                    for (int tri = 0; tri < indexes.Length; tri += 3)
                    {
                        faces.Add(new Face(
                                      new int[] { vertexIndex, vertexIndex + 1, vertexIndex + 2 },
                                      Math.Clamp(submeshIndex, 0, materialCount - 1),
                                      AutoUnwrapSettings.tile,
                                      Smoothing.smoothingGroupNone,
                                      -1,
                                      -1,
                                      true));

                        splitVertices.Add(sourceVertices[indexes[tri]]);
                        splitVertices.Add(sourceVertices[indexes[tri + 1]]);
                        splitVertices.Add(sourceVertices[indexes[tri + 2]]);

                        vertexIndex += 3;
                    }
                }
                break;

                case MeshTopology.Quads:
                {
                    int[] indexes = m_SourceMesh.GetIndices(submeshIndex);

                    for (int quad = 0; quad < indexes.Length; quad += 4)
                    {
                        faces.Add(new Face(new int[]
                            {
                                vertexIndex, vertexIndex + 1, vertexIndex + 2,
                                vertexIndex + 2, vertexIndex + 3, vertexIndex + 0
                            },
                                           Math.Clamp(submeshIndex, 0, materialCount - 1),
                                           AutoUnwrapSettings.tile,
                                           Smoothing.smoothingGroupNone,
                                           -1,
                                           -1,
                                           true));

                        splitVertices.Add(sourceVertices[indexes[quad]]);
                        splitVertices.Add(sourceVertices[indexes[quad + 1]]);
                        splitVertices.Add(sourceVertices[indexes[quad + 2]]);
                        splitVertices.Add(sourceVertices[indexes[quad + 3]]);

                        vertexIndex += 4;
                    }
                }
                break;

                default:
                    throw new NotSupportedException("ProBuilder only supports importing triangle and quad meshes.");
                }
            }

            m_Vertices = splitVertices.ToArray();

            m_Destination.Clear();
            m_Destination.SetVertices(m_Vertices);
            m_Destination.faces          = faces;
            m_Destination.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(m_Destination.positionsInternal);
            m_Destination.sharedTextures = new SharedVertex[0];

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

            if (importSettings.quads)
            {
                List <WingedEdge> wings = WingedEdge.GetWingedEdges(m_Destination, m_Destination.facesInternal, true);

                // build a lookup of the strength of edge connections between triangle faces
                Dictionary <EdgeLookup, float> connections = new Dictionary <EdgeLookup, float>();

                for (int i = 0; i < wings.Count; i++)
                {
                    using (var it = new WingedEdgeEnumerator(wings[i]))
                    {
                        while (it.MoveNext())
                        {
                            var border = it.Current;

                            if (border.opposite != null && !connections.ContainsKey(border.edge))
                            {
                                float score = GetQuadScore(border, border.opposite);
                                connections.Add(border.edge, score);
                            }
                        }
                    }
                }

                List <SimpleTuple <Face, Face> > quads = new List <SimpleTuple <Face, Face> >();

                // move through each face and find it's best quad neighbor
                foreach (WingedEdge face in wings)
                {
                    if (!processed.Add(face.face))
                    {
                        continue;
                    }

                    float bestScore = 0f;
                    Face  buddy     = null;

                    using (var it = new WingedEdgeEnumerator(face))
                    {
                        while (it.MoveNext())
                        {
                            var border = it.Current;

                            if (border.opposite != null && processed.Contains(border.opposite.face))
                            {
                                continue;
                            }

                            float borderScore;

                            // only add it if the opposite face's best score is also this face
                            if (connections.TryGetValue(border.edge, out borderScore) &&
                                borderScore > bestScore &&
                                face.face == GetBestQuadConnection(border.opposite, connections))
                            {
                                bestScore = borderScore;
                                buddy     = border.opposite.face;
                            }
                        }
                    }

                    if (buddy != null)
                    {
                        processed.Add(buddy);
                        quads.Add(new SimpleTuple <Face, Face>(face.face, buddy));
                    }
                }

                // don't collapse coincident vertices if smoothing is enabled, we need the original normals intact
                MergeElements.MergePairs(m_Destination, quads, !importSettings.smoothing);
            }

            if (importSettings.smoothing)
            {
                Smoothing.ApplySmoothingGroups(m_Destination, m_Destination.facesInternal, importSettings.smoothingAngle, m_Vertices.Select(x => x.normal).ToArray());
                // After smoothing has been applied go back and weld coincident vertices created by MergePairs.
                MergeElements.CollapseCoincidentVertices(m_Destination, m_Destination.facesInternal);
            }
        }
Ejemplo n.º 21
0
        protected override ActionResult PerformActionImplementation()
        {
            if (MeshSelection.selectedObjectCount < 1)
            {
                return(ActionResult.NoSelection);
            }

            UndoUtility.RecordSelection("Fill Hole");

            ActionResult res       = new ActionResult(ActionResult.Status.NoChange, "No Holes Found");
            int          filled    = 0;
            bool         wholePath = m_SelectEntirePath;

            foreach (ProBuilderMesh mesh in MeshSelection.topInternal)
            {
                bool selectAll            = mesh.selectedIndexesInternal == null || mesh.selectedIndexesInternal.Length < 1;
                IEnumerable <int> indexes = selectAll ? mesh.facesInternal.SelectMany(x => x.indexes) : mesh.selectedIndexesInternal;

                mesh.ToMesh();

                List <WingedEdge>         wings  = WingedEdge.GetWingedEdges(mesh);
                HashSet <int>             common = mesh.GetSharedVertexHandles(indexes);
                List <List <WingedEdge> > holes  = ElementSelection.FindHoles(wings, common);

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

                foreach (List <WingedEdge> hole in holes)
                {
                    List <int> holeIndexes;
                    Face       face;

                    if (wholePath)
                    {
                        // if selecting whole path and in edge mode, make sure the path contains
                        // at least one complete edge from the selection.
                        if (ProBuilderEditor.selectMode == SelectMode.Edge &&
                            !hole.Any(x => common.Contains(x.edge.common.a) &&
                                      common.Contains(x.edge.common.b)))
                        {
                            continue;
                        }

                        holeIndexes = hole.Select(x => x.edge.local.a).ToList();
                        face        = AppendElements.CreatePolygon(mesh, holeIndexes, false);
                    }
                    else
                    {
                        IEnumerable <WingedEdge> selected = hole.Where(x => common.Contains(x.edge.common.a));
                        holeIndexes = selected.Select(x => x.edge.local.a).ToList();
                        face        = AppendElements.CreatePolygon(mesh, holeIndexes, true);
                    }

                    if (face != null)
                    {
                        filled++;
                        appendedFaces.Add(face);
                    }
                }

                mesh.SetSelectedFaces(appendedFaces);

                wings = WingedEdge.GetWingedEdges(mesh);

                // make sure the appended faces match the first adjacent face found
                // both in winding and face properties
                foreach (var appendedFace in appendedFaces)
                {
                    var wing = wings.FirstOrDefault(x => x.face == appendedFace);

                    if (wing == null)
                    {
                        continue;
                    }

                    using (var it = new WingedEdgeEnumerator(wing))
                    {
                        while (it.MoveNext())
                        {
                            if (it.Current == null)
                            {
                                continue;
                            }

                            var currentWing  = it.Current;
                            var oppositeFace = it.Current.opposite != null ? it.Current.opposite.face : null;

                            if (oppositeFace != null && !appendedFaces.Contains(oppositeFace))
                            {
                                currentWing.face.submeshIndex = oppositeFace.submeshIndex;
                                currentWing.face.uv           = new AutoUnwrapSettings(oppositeFace.uv);
                                SurfaceTopology.ConformOppositeNormal(currentWing.opposite);
                                break;
                            }
                        }
                    }
                }

                mesh.ToMesh();
                mesh.Refresh();
                mesh.Optimize();
            }

            ProBuilderEditor.Refresh();

            if (filled > 0)
            {
                res = new ActionResult(ActionResult.Status.Success, filled > 1 ? string.Format("Filled {0} Holes", filled) : "Fill Hole");
            }
            return(res);
        }