예제 #1
0
        /// <summary>
        ///
        /// </summary>
        /// <returns>
        /// 0 if mesh is valid, or greater than 0 if corrections were made. If this method returns greater than 0, you will need to
        /// rebuild the mesh using <see cref="ProBuilderMesh.ToMesh"/> and <see cref="ProBuilderMesh.Refresh"/>.
        /// </returns>


        /// <summary>
        /// Check a mesh for degenerate triangles or unused vertices, and remove them if necessary.
        /// </summary>
        /// <param name="mesh">The mesh to test.</param>
        /// <param name="removedVertices">If fixes were made, this will be set to the number of vertices removed during that process.</param>
        /// <returns>Returns true if no problems were found, false if topology issues were discovered and fixed.</returns>
        internal static bool EnsureMeshIsValid(ProBuilderMesh mesh, out int removedVertices)
        {
            removedVertices = 0;

            if (ContainsDegenerateTriangles(mesh))
            {
                var faces   = mesh.selectedFacesInternal;
                var edges   = mesh.selectedEdgesInternal;
                var indices = mesh.selectedIndexesInternal;

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

                if (RemoveDegenerateTriangles(mesh, removed))
                {
                    mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal);

                    RebuildSelectionIndexes(mesh, ref faces, ref edges, ref indices, removed);
                    mesh.selectedFacesInternal   = faces;
                    mesh.selectedEdgesInternal   = edges;
                    mesh.selectedIndexesInternal = indices;
                    removedVertices = removed.Count;
                    return(false);
                }
            }

            return(true);
        }
예제 #2
0
        /// <summary>
        /// Condense co-incident vertex positions per-face. vertices must already be marked as shared in the sharedIndexes
        /// array to be considered. This method is really only useful after merging faces.
        /// </summary>
        /// <param name="mesh"></param>
        /// <param name="faces"></param>
        internal static void CollapseCoincidentVertices(ProBuilderMesh mesh, IEnumerable <Face> faces)
        {
            Dictionary <int, int> lookup = new Dictionary <int, int>();

            SharedVertex.GetSharedVertexLookup(mesh.sharedVertices, lookup);
            Dictionary <int, int> matches = new Dictionary <int, int>();

            foreach (Face face in faces)
            {
                matches.Clear();

                int[] indexes = face.indexes.ToArray();
                for (int i = 0; i < indexes.Length; i++)
                {
                    int common = lookup[face.indexes[i]];

                    if (matches.ContainsKey(common))
                    {
                        indexes[i] = matches[common];
                    }
                    else
                    {
                        matches.Add(common, indexes[i]);
                    }
                }
                face.SetIndexes(indexes);

                face.Reverse();
                face.Reverse();
            }

            mesh.RemoveUnusedVertices();
        }
        /// <summary>
        /// Rebuild targets if they can't be refreshed.
        /// </summary>
        /// <param name="targets"></param>
        static void RebuildSharedIndexes(ProBuilderMesh[] targets)
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < targets.Length; i++)
            {
                UnityEditor.EditorUtility.DisplayProgressBar(
                    "Refreshing ProBuilder Objects",
                    "Rebuilding mesh " + targets[i].id + ".",
                    ((float)i / targets.Length));

                ProBuilderMesh pb = targets[i];

                try
                {
                    pb.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(pb.positionsInternal);

                    pb.ToMesh();
                    pb.Refresh();
                    pb.Optimize();
                }
                catch (System.Exception e)
                {
                    sb.AppendLine("Failed rebuilding " + pb.name + " shared indexes cache.\n" + e.ToString());
                }
            }

            if (sb.Length > 0)
            {
                Log.Error(sb.ToString());
            }

            UnityEditor.EditorUtility.ClearProgressBar();
            UnityEditor.EditorUtility.DisplayDialog("Rebuild Shared Index Cache", "Successfully rebuilt " + targets.Length + " shared index caches", "Okay");
        }
        /// <summary>
        /// Break a ProBuilder mesh into multiple meshes if it's vertex count is greater than maxVertexCount.
        /// </summary>
        /// <returns></returns>
        internal static List <ProBuilderMesh> SplitByMaxVertexCount(IList <Vertex> vertices, IList <Face> faces, IList <SharedVertex> sharedVertices, IList <SharedVertex> sharedTextures, uint maxVertexCount = ProBuilderMesh.maxVertexCount)
        {
            uint vertexCount  = (uint)vertices.Count;
            uint meshCount    = System.Math.Max(1u, vertexCount / maxVertexCount);
            var  submeshCount = faces.Max(x => x.submeshIndex) + 1;

            if (meshCount < 2)
            {
                return new List <ProBuilderMesh>()
                       {
                           ProBuilderMesh.Create(vertices, faces, sharedVertices, sharedTextures, new Material[submeshCount])
                       }
            }
            ;

            var sharedVertexLookup = new Dictionary <int, int>();

            SharedVertex.GetSharedVertexLookup(sharedVertices, sharedVertexLookup);

            var sharedTextureLookup = new Dictionary <int, int>();

            SharedVertex.GetSharedVertexLookup(sharedTextures, sharedTextureLookup);

            var meshes = new List <ProBuilderMesh>();
            var mv     = new List <Vertex>();
            var mf     = new List <Face>();
            var remap  = new Dictionary <int, int>();

            foreach (var face in faces)
            {
                if (mv.Count + face.distinctIndexes.Count > maxVertexCount)
                {
                    // finalize mesh
                    meshes.Add(CreateMeshFromSplit(mv, mf, sharedVertexLookup, sharedTextureLookup, remap, new Material[submeshCount]));
                    mv.Clear();
                    mf.Clear();
                    remap.Clear();
                }

                foreach (int i in face.distinctIndexes)
                {
                    mv.Add(vertices[i]);
                    remap.Add(i, mv.Count - 1);
                }

                mf.Add(face);
            }

            if (mv.Any())
            {
                meshes.Add(CreateMeshFromSplit(mv, mf, sharedVertexLookup, sharedTextureLookup, remap, new Material[submeshCount]));
            }

            return(meshes);
        }
    }
예제 #5
0
        /// <summary>
        /// Deletes a list of faces from a mesh.
        ///
        /// This is the equivalent of the [Delete Faces](../manual/Face_Delete.html) action.
        /// </summary>
        /// <param name="mesh">The source mesh.</param>
        /// <param name="faceIndexes">The indices of faces to remove (corresponding to the <see cref="ProBuilderMesh.faces"/> collection.</param>
        /// <returns>An array of vertex indices that ProBuilder deleted when it removed the specified faces.</returns>
        public static int[] DeleteFaces(this ProBuilderMesh mesh, IList <int> faceIndexes)
        {
            if (mesh == null)
            {
                throw new ArgumentNullException("mesh");
            }

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

            Face[] faces = new Face[faceIndexes.Count];

            for (int i = 0; i < faces.Length; i++)
            {
                faces[i] = mesh.facesInternal[faceIndexes[i]];
            }

            List <int> indexesToRemove = faces.SelectMany(x => x.distinctIndexesInternal).Distinct().ToList();

            indexesToRemove.Sort();

            int vertexCount = mesh.positionsInternal.Length;

            Face[] nFaces   = mesh.facesInternal.RemoveAt(faceIndexes);
            var    vertices = mesh.GetVertices().SortedRemoveAt(indexesToRemove);

            Dictionary <int, int> shiftmap = new Dictionary <int, int>();

            for (var i = 0; i < vertexCount; i++)
            {
                shiftmap.Add(i, ArrayUtility.NearestIndexPriorToValue <int>(indexesToRemove, i) + 1);
            }

            // shift all other face indexes down to account for moved vertex positions
            for (var i = 0; i < nFaces.Length; i++)
            {
                int[] tris = nFaces[i].indexesInternal;

                for (var n = 0; n < tris.Length; n++)
                {
                    tris[n] -= shiftmap[tris[n]];
                }

                nFaces[i].indexesInternal = tris;
            }

            mesh.SetVertices(vertices);
            mesh.sharedVerticesInternal = SharedVertex.SortedRemoveAndShift(mesh.sharedVertexLookup, indexesToRemove);
            mesh.sharedTextures         = SharedVertex.SortedRemoveAndShift(mesh.sharedTextureLookup, indexesToRemove);
            mesh.facesInternal          = nFaces;
            int[] array = indexesToRemove.ToArray();

            return(array);
        }
예제 #6
0
 public static void Rebuild(this ProBuilderMesh mesh, IList <Vector3> positions, IList <Face> faces, IList <Vector2> textures)
 {
     mesh.Clear();
     mesh.positions      = positions;
     mesh.faces          = faces;
     mesh.textures       = textures;
     mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(positions);
     mesh.ToMesh();
     mesh.Refresh();
 }
예제 #7
0
        static string GetCommonVertexString(SharedVertex shared)
        {
            var str     = "";
            var indexes = shared.arrayInternal;

            for (int i = 0, c = indexes.Length - 1; i < c; i++)
            {
                str += indexes[i] + ", ";
            }
            str += indexes[indexes.Length - 1];
            return(str);
        }
예제 #8
0
        /// <summary>
        /// Collapses all passed indexes to a single shared index.
        /// </summary>
        /// <remarks>
        /// Retains vertex normals.
        /// </remarks>
        /// <param name="mesh">Target mesh.</param>
        /// <param name="indexes">The indexes to merge to a single shared vertex.</param>
        /// <param name="collapseToFirst">If true, instead of merging all vertices to the average position, the vertices will be collapsed onto the first vertex position.</param>
        /// <returns>The first available local index created as a result of the merge. -1 if action is unsuccessfull.</returns>
        public static int MergeVertices(this ProBuilderMesh mesh, int[] indexes, bool collapseToFirst = false)
        {
            if (mesh == null)
            {
                throw new ArgumentNullException("mesh");
            }

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

            Vertex[] vertices = mesh.GetVertices();
            Vertex   cen      = collapseToFirst ? vertices[indexes[0]] : Vertex.Average(vertices, indexes);

            mesh.SetVerticesCoincident(indexes);
            UVEditing.SplitUVs(mesh, indexes);
            int sharedVertexHandle = mesh.GetSharedVertexHandle(indexes.First());

            mesh.SetSharedVertexValues(sharedVertexHandle, cen);

            SharedVertex merged         = mesh.sharedVerticesInternal[sharedVertexHandle];
            List <int>   removedIndexes = new List <int>();

            MeshValidation.RemoveDegenerateTriangles(mesh, removedIndexes);

            // get a non-deleted index to work with
            int ind = -1;

            for (int i = 0; i < merged.Count; i++)
            {
                if (!removedIndexes.Contains(merged[i]))
                {
                    ind = merged[i];
                }
            }

            int res = ind;

            for (int i = 0; i < removedIndexes.Count; i++)
            {
                if (ind > removedIndexes[i])
                {
                    res--;
                }
            }

            return(res);
        }
예제 #9
0
        /// <summary>
        /// Averages shared normals with the mask of all (indexes contained in perimeter edge)
        /// </summary>
        internal static Vector3 AverageNormalWithIndexes(SharedVertex shared, int[] all, IList <Vector3> norm)
        {
            Vector3 n     = Vector3.zero;
            int     count = 0;

            for (int i = 0; i < all.Length; i++)
            {
                // this is a point in the perimeter, add it to the average
                if (shared.Contains(all[i]))
                {
                    n += norm[all[i]];
                    count++;
                }
            }
            return(n / (float)count);
        }
예제 #10
0
        public void RefreshLookups()
        {
            if (sharedVertexLookup == null)
            {
                sharedVertexLookup = new Dictionary <int, int>();
            }
            if (faceLookup == null)
            {
                faceLookup = new Dictionary <int, List <int> >();
            }

            sharedVertexLookup.Clear();
            faceLookup.Clear();

            SharedVertex.GetSharedVertexLookup(Mesh.sharedVertices, sharedVertexLookup);
            GetFaceLookup(Mesh.faces, sharedVertexLookup, faceLookup);
        }
예제 #11
0
        public override MeshSelection Select(Camera camera, Rect rect, Rect uiRootRect, GameObject[] gameObjects, bool depthTest, MeshEditorSelectionMode mode)
        {
            Dictionary <ProBuilderMesh, HashSet <int> > pickResult = PBUtility.PickVertices(camera, rect, uiRootRect, gameObjects, depthTest);

            if (pickResult.Count == 0)
            {
                return(null);
            }

            MeshSelection selection = new MeshSelection();

            foreach (KeyValuePair <ProBuilderMesh, HashSet <int> > kvp in pickResult)
            {
                ProBuilderMesh       mesh           = kvp.Key;
                HashSet <int>        sharedIndexes  = kvp.Value;
                IList <SharedVertex> sharedVertices = mesh.sharedVertices;

                HashSet <int> indices = new HashSet <int>();
                foreach (int sharedIndex in sharedIndexes)
                {
                    SharedVertex sharedVertex = sharedVertices[sharedIndex];
                    for (int j = 0; j < sharedVertex.Count; ++j)
                    {
                        if (!indices.Contains(sharedVertex[j]))
                        {
                            indices.Add(sharedVertex[j]);
                        }
                    }
                }

                if (mode == MeshEditorSelectionMode.Substract || mode == MeshEditorSelectionMode.Difference)
                {
                    IList <int> selected = mesh.GetCoincidentVertices(indices).Where(index => m_vertexSelection.IsSelected(mesh, index)).ToArray();
                    selection.UnselectedIndices.Add(mesh.gameObject, selected);
                    m_vertexSelection.Remove(mesh, selected);
                }

                if (mode == MeshEditorSelectionMode.Add || mode == MeshEditorSelectionMode.Difference)
                {
                    IList <int> notSelected = mesh.GetCoincidentVertices(indices).Where(index => !m_vertexSelection.IsSelected(mesh, index)).ToArray();
                    selection.SelectedIndices.Add(mesh.gameObject, notSelected);
                    m_vertexSelection.Add(mesh, notSelected);
                }
            }
            return(selection);
        }
예제 #12
0
        static ProBuilderMesh CreateMeshFromSplit(List <Vertex> vertices,
                                                  List <Face> faces,
                                                  Dictionary <int, int> sharedVertexLookup,
                                                  Dictionary <int, int> sharedTextureLookup,
                                                  Dictionary <int, int> remap,
                                                  Material[] materials)
        {
            // finalize mesh
            var sv = new Dictionary <int, int>();
            var st = new Dictionary <int, int>();

            foreach (var f in faces)
            {
                for (int i = 0, c = f.indexesInternal.Length; i < c; i++)
                {
                    f.indexesInternal[i] = remap[f.indexesInternal[i]];
                }

                f.InvalidateCache();
            }

            foreach (var kvp in remap)
            {
                int v;

                if (sharedVertexLookup.TryGetValue(kvp.Key, out v))
                {
                    sv.Add(kvp.Value, v);
                }

                if (sharedTextureLookup.TryGetValue(kvp.Key, out v))
                {
                    st.Add(kvp.Value, v);
                }
            }

            return(ProBuilderMesh.Create(
                       vertices,
                       faces,
                       SharedVertex.ToSharedVertices(sv),
                       st.Any() ? SharedVertex.ToSharedVertices(st) : null,
                       materials));
        }
예제 #13
0
 public void SetupIndices(FaceOrientation face)
 {
     if (face == FaceOrientation.Front || face == FaceOrientation.Top || face == FaceOrientation.Right)
     {
         this.i0 = this.V0;
         this.i1 = this.V1;
         this.i2 = this.V2;
         this.i3 = this.V0;
         this.i4 = this.V2;
         this.i5 = this.V3;
     }
     else
     {
         this.i0 = this.V0;
         this.i1 = this.V2;
         this.i2 = this.V1;
         this.i3 = this.V0;
         this.i4 = this.V3;
         this.i5 = this.V2;
     }
 }
예제 #14
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)));
        }
예제 #15
0
        static void AccumulateMeshesInfo(
            IEnumerable <ProBuilderMesh> meshes,
            int offset,
            ref List <Vertex> vertices,
            ref List <Face> faces,
            ref List <Face> autoUvFaces,
            ref List <SharedVertex> sharedVertices,
            ref List <SharedVertex> sharedTextures,
            ref List <Material> materialMap,
            Transform targetTransform = null
            )
        {
            foreach (var mesh in meshes)
            {
                var meshVertexCount    = mesh.vertexCount;
                var transform          = mesh.transform;
                var meshVertices       = mesh.GetVertices();
                var meshFaces          = mesh.facesInternal;
                var meshSharedVertices = mesh.sharedVertices;
                var meshSharedTextures = mesh.sharedTextures;
                var materials          = mesh.renderer.sharedMaterials;
                var materialCount      = materials.Length;

                for (int i = 0; i < meshVertexCount; i++)
                {
                    var worldVertex = transform.TransformVertex(meshVertices[i]);
                    if (targetTransform != null)
                    {
                        vertices.Add(targetTransform.InverseTransformVertex(worldVertex));
                    }
                    else
                    {
                        vertices.Add(worldVertex);
                    }
                }


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

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

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

                    faces.Add(newFace);
                }

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

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

                offset += meshVertexCount;
            }
        }
        /// <summary>
        /// Rebuild a mesh from an ordered set of points.
        /// </summary>
        /// <param name="mesh">The target mesh. The mesh values will be cleared and repopulated with the shape extruded from points.</param>
        /// <param name="points">A path of points to triangulate and extrude.</param>
        /// <param name="extrude">The distance to extrude.</param>
        /// <param name="flipNormals">If true the faces will be inverted at creation.</param>
        /// <returns>An ActionResult with the status of the operation.</returns>
        public static ActionResult CreateShapeFromPolygon(this ProBuilderMesh mesh, IList <Vector3> points, float extrude, bool flipNormals)
        {
            if (mesh == null)
            {
                throw new ArgumentNullException("mesh");
            }

            if (points == null || points.Count < 3)
            {
                mesh.Clear();
                mesh.ToMesh();
                mesh.Refresh();
                return(new ActionResult(ActionResult.Status.NoChange, "Too Few Points"));
            }

            Vector3[]  vertices = points.ToArray();
            List <int> triangles;

            Log.PushLogLevel(LogLevel.Error);

            if (Triangulation.TriangulateVertices(vertices, out triangles, false))
            {
                int[] indexes = triangles.ToArray();

                if (Math.PolygonArea(vertices, indexes) < Mathf.Epsilon)
                {
                    mesh.Clear();
                    Log.PopLogLevel();
                    return(new ActionResult(ActionResult.Status.Failure, "Polygon Area < Epsilon"));
                }

                mesh.Clear();

                mesh.positionsInternal      = vertices;
                mesh.facesInternal          = new[] { new Face(indexes) };
                mesh.sharedVerticesInternal = SharedVertex.GetSharedVerticesWithPositions(vertices);
                mesh.InvalidateCaches();

                Vector3 nrm = Math.Normal(mesh, mesh.facesInternal[0]);

                if (Vector3.Dot(Vector3.up, nrm) > 0f)
                {
                    mesh.facesInternal[0].Reverse();
                }

                mesh.DuplicateAndFlip(mesh.facesInternal);

                mesh.Extrude(new Face[] { mesh.facesInternal[1] }, ExtrudeMethod.IndividualFaces, extrude);

                if ((extrude < 0f && !flipNormals) || (extrude > 0f && flipNormals))
                {
                    foreach (var face in mesh.facesInternal)
                    {
                        face.Reverse();
                    }
                }

                mesh.ToMesh();
                mesh.Refresh();
            }
            else
            {
                Log.PopLogLevel();
                return(new ActionResult(ActionResult.Status.Failure, "Failed Triangulating Points"));
            }

            Log.PopLogLevel();

            return(new ActionResult(ActionResult.Status.Success, "Create Polygon Shape"));
        }
예제 #17
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);
            }
        }
예제 #18
0
        /// <summary>
        /// Similar to Merge vertices, expect that this method only collapses vertices within a specified distance of one another (typically Mathf.Epsilon is used).
        /// </summary>
        /// <param name="mesh">Target pb_Object.</param>
        /// <param name="indexes">The vertex indexes to be scanned for inclusion. To weld the entire object for example, pass pb.faces.SelectMany(x => x.indexes).</param>
        /// <param name="neighborRadius">The minimum distance from another vertex to be considered within welding distance.</param>
        /// <returns>The indexes of any new vertices created by a weld.</returns>
        public static int[] WeldVertices(this ProBuilderMesh mesh, IEnumerable <int> indexes, float neighborRadius)
        {
            if (mesh == null)
            {
                throw new ArgumentNullException("mesh");
            }

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

            Vertex[]       vertices      = mesh.GetVertices();
            SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal;

            HashSet <int> common      = mesh.GetSharedVertexHandles(indexes);
            int           vertexCount = common.Count;

            // Make assumption that there will rarely be a time when a single weld encompasses more than 32 vertices.
            // If a radial search returns neighbors matching the max count, the search is re-done and maxNearestNeighbors
            // is set to the resulting length. This will be slow, but in most cases shouldn't happen ever, or if it does,
            // should only happen once or twice.
            int maxNearestNeighbors = System.Math.Min(32, common.Count());

            // 3 dimensions, duplicate entries allowed
            KdTree <float, int> tree = new KdTree <float, int>(3, new FloatMath(), AddDuplicateBehavior.Collect);

            foreach (int i in common)
            {
                Vector3 v = vertices[sharedIndexes[i][0]].position;
                tree.Add(new float[] { v.x, v.y, v.z }, i);
            }

            float[] point = new float[3] {
                0, 0, 0
            };
            Dictionary <int, int>     remapped = new Dictionary <int, int>();
            Dictionary <int, Vector3> averages = new Dictionary <int, Vector3>();
            int index = sharedIndexes.Length;

            foreach (int commonIndex in common)
            {
                // already merged with another
                if (remapped.ContainsKey(commonIndex))
                {
                    continue;
                }

                Vector3 v = vertices[sharedIndexes[commonIndex][0]].position;

                point[0] = v.x;
                point[1] = v.y;
                point[2] = v.z;

                // Radial search at each point
                KdTreeNode <float, int>[] neighbors = tree.RadialSearch(point, neighborRadius, maxNearestNeighbors);

                // if first radial search filled the entire allotment reset the max neighbor count to 1.5x.
                // the result hopefully preventing double-searches in the next iterations.
                if (maxNearestNeighbors < vertexCount && neighbors.Length >= maxNearestNeighbors)
                {
                    neighbors           = tree.RadialSearch(point, neighborRadius, vertexCount);
                    maxNearestNeighbors = System.Math.Min(vertexCount, neighbors.Length + neighbors.Length / 2);
                }

                Vector3 avg   = Vector3.zero;
                float   count = 0;

                for (int neighborIndex = 0; neighborIndex < neighbors.Length; neighborIndex++)
                {
                    // common index of this neighbor
                    int c = neighbors[neighborIndex].Value;

                    // if it's already been added to another, skip it
                    if (remapped.ContainsKey(c))
                    {
                        continue;
                    }

                    avg.x += neighbors[neighborIndex].Point[0];
                    avg.y += neighbors[neighborIndex].Point[1];
                    avg.z += neighbors[neighborIndex].Point[2];

                    remapped.Add(c, index);

                    count++;

                    if (neighbors[neighborIndex].Duplicates != null)
                    {
                        for (int duplicateIndex = 0; duplicateIndex < neighbors[neighborIndex].Duplicates.Count; duplicateIndex++)
                        {
                            remapped.Add(neighbors[neighborIndex].Duplicates[duplicateIndex], index);
                        }
                    }
                }

                avg.x /= count;
                avg.y /= count;
                avg.z /= count;

                averages.Add(index, avg);

                index++;
            }

            var welds  = new int[remapped.Count];
            int n      = 0;
            var lookup = mesh.sharedVertexLookup;

            foreach (var kvp in remapped)
            {
                SharedVertex tris = sharedIndexes[kvp.Key];

                welds[n++] = tris[0];

                for (int i = 0; i < tris.Count; i++)
                {
                    lookup[tris[i]]            = kvp.Value;
                    vertices[tris[i]].position = averages[kvp.Value];
                }
            }

            mesh.SetSharedVertices(lookup);
            mesh.SetVertices(vertices);
            mesh.ToMesh();
            return(welds);
        }
예제 #19
0
        /// <summary>
        /// Imports mesh data from a GameObject's <see cref="UnityEngine.MeshFilter.sharedMesh"/> and
        /// <see cref="UnityEngine.Renderer.sharedMaterials"/> properties.
        /// </summary>
        /// <param name="importSettings">Optional import customization settings.</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];

            if (importSettings.quads)
            {
                var newFaces = m_Destination.ToQuads(m_Destination.facesInternal, !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);
            }
        }
예제 #20
0
        /// <summary>
        /// Rebuild a mesh from an ordered set of points.
        /// </summary>
        /// <param name="mesh">The target mesh. The mesh values will be cleared and repopulated with the shape extruded from points.</param>
        /// <param name="points">A path of points to triangulate and extrude.</param>
        /// <param name="extrude">The distance to extrude.</param>
        /// <param name="flipNormals">If true the faces will be inverted at creation.</param>
        /// <param name="cameraLookAt">If the normal of the polygon of the first face is facing in the same direction of the camera lookat it will be inverted at creation, so it is facing the camera.</param>
        /// <returns>An ActionResult with the status of the operation.</returns>
        public static ActionResult CreateShapeFromPolygon(this ProBuilderMesh mesh, IList <Vector3> points, float extrude, bool flipNormals, Vector3 cameraLookAt)
        {
            if (mesh == null)
            {
                throw new ArgumentNullException("mesh");
            }

            if (points == null || points.Count < 3)
            {
                ClearAndRefreshMesh(mesh);
                return(new ActionResult(ActionResult.Status.NoChange, "Too Few Points"));
            }

            Vector3[]  vertices = points.ToArray();
            List <int> triangles;

            Log.PushLogLevel(LogLevel.Error);

            if (Triangulation.TriangulateVertices(vertices, out triangles, false))
            {
                int[] indexes = triangles.ToArray();

                if (Math.PolygonArea(vertices, indexes) < Mathf.Epsilon)
                {
                    ClearAndRefreshMesh(mesh);
                    Log.PopLogLevel();
                    return(new ActionResult(ActionResult.Status.Failure, "Polygon Area < Epsilon"));
                }

                mesh.Clear();

                mesh.positionsInternal = vertices;
                var newFace = new Face(indexes);
                mesh.facesInternal          = new[] { newFace };
                mesh.sharedVerticesInternal = SharedVertex.GetSharedVerticesWithPositions(vertices);
                mesh.InvalidateCaches();

                // check that all points are represented in the triangulation
                if (newFace.distinctIndexesInternal.Length != vertices.Length)
                {
                    ClearAndRefreshMesh(mesh);
                    Log.PopLogLevel();
                    return(new ActionResult(ActionResult.Status.Failure, "Triangulation missing points"));
                }

                Vector3 nrm = Math.Normal(mesh, mesh.facesInternal[0]);
                cameraLookAt.Normalize();
                if ((flipNormals ? Vector3.Dot(cameraLookAt, nrm) < 0f : Vector3.Dot(cameraLookAt, nrm) > 0f))
                {
                    mesh.facesInternal[0].Reverse();
                }

                if (extrude != 0.0f)
                {
                    mesh.DuplicateAndFlip(mesh.facesInternal);

                    mesh.Extrude(new Face[] { (flipNormals ? mesh.facesInternal[1] : mesh.facesInternal[0]) }, ExtrudeMethod.IndividualFaces, extrude);

                    if ((extrude < 0f && !flipNormals) || (extrude > 0f && flipNormals))
                    {
                        foreach (var face in mesh.facesInternal)
                        {
                            face.Reverse();
                        }
                    }
                }

                mesh.ToMesh();
                mesh.Refresh();
            }
            else
            {
                // clear mesh instead of showing an invalid one
                ClearAndRefreshMesh(mesh);
                Log.PopLogLevel();
                return(new ActionResult(ActionResult.Status.Failure, "Failed Triangulating Points"));
            }

            Log.PopLogLevel();

            return(new ActionResult(ActionResult.Status.Success, "Create Polygon Shape"));
        }
예제 #21
0
        /// <summary>
        /// ProBuilderize in-place function. You must call ToMesh() and Refresh() after
        /// returning from this function, as this only creates the pb_Object and sets its
        /// fields. This allows you to record the mesh and gameObject for Undo operations.
        /// </summary>
        /// <param name="pb"></param>
        /// <param name="preserveFaces"></param>
        /// <returns></returns>
        public static bool ResetPbObjectWithMeshFilter(ProBuilderMesh pb, bool preserveFaces)
        {
            MeshFilter mf = pb.gameObject.GetComponent <MeshFilter>();

            if (mf == null || mf.sharedMesh == null)
            {
                Log.Error(pb.name + " does not have a mesh or Mesh Filter component.");
                return(false);
            }

            Mesh m = mf.sharedMesh;

            int vertexCount = m.vertexCount;

            Vector3[] m_positions = MeshUtility.GetMeshChannel <Vector3[]>(pb.gameObject, x => x.vertices);
            Color[]   m_colors    = MeshUtility.GetMeshChannel <Color[]>(pb.gameObject, x => x.colors);
            Vector2[] m_uvs       = MeshUtility.GetMeshChannel <Vector2[]>(pb.gameObject, x => x.uv);

            List <Vector3> verts = preserveFaces ? new List <Vector3>(m.vertices) : new List <Vector3>();
            List <Color>   cols  = preserveFaces ? new List <Color>(m.colors) : new List <Color>();
            List <Vector2> uvs   = preserveFaces ? new List <Vector2>(m.uv) : new List <Vector2>();
            List <Face>    faces = new List <Face>();

            MeshRenderer mr = pb.gameObject.GetComponent <MeshRenderer>();

            if (mr == null)
            {
                mr = pb.gameObject.AddComponent <MeshRenderer>();
            }

            Material[] sharedMaterials = mr.sharedMaterials;
            int        mat_length      = sharedMaterials.Length;

            for (int n = 0; n < m.subMeshCount; n++)
            {
                int[] tris = m.GetTriangles(n);
                for (int i = 0; i < tris.Length; i += 3)
                {
                    int index = -1;
                    if (preserveFaces)
                    {
                        for (int j = 0; j < faces.Count; j++)
                        {
                            if (faces[j].distinctIndexesInternal.Contains(tris[i + 0]) ||
                                faces[j].distinctIndexesInternal.Contains(tris[i + 1]) ||
                                faces[j].distinctIndexesInternal.Contains(tris[i + 2]))
                            {
                                index = j;
                                break;
                            }
                        }
                    }

                    if (index > -1 && preserveFaces)
                    {
                        int   len = faces[index].indexesInternal.Length;
                        int[] arr = new int[len + 3];
                        System.Array.Copy(faces[index].indexesInternal, 0, arr, 0, len);
                        arr[len + 0] = tris[i + 0];
                        arr[len + 1] = tris[i + 1];
                        arr[len + 2] = tris[i + 2];
                        faces[index].indexesInternal = arr;
                    }
                    else
                    {
                        int[] faceTris;

                        if (preserveFaces)
                        {
                            faceTris = new int[3]
                            {
                                tris[i + 0],
                                tris[i + 1],
                                tris[i + 2]
                            };
                        }
                        else
                        {
                            verts.Add(m_positions[tris[i + 0]]);
                            verts.Add(m_positions[tris[i + 1]]);
                            verts.Add(m_positions[tris[i + 2]]);

                            cols.Add(m_colors != null && m_colors.Length == vertexCount ? m_colors[tris[i + 0]] : Color.white);
                            cols.Add(m_colors != null && m_colors.Length == vertexCount ? m_colors[tris[i + 1]] : Color.white);
                            cols.Add(m_colors != null && m_colors.Length == vertexCount ? m_colors[tris[i + 2]] : Color.white);

                            uvs.Add(m_uvs[tris[i + 0]]);
                            uvs.Add(m_uvs[tris[i + 1]]);
                            uvs.Add(m_uvs[tris[i + 2]]);

                            faceTris = new int[3] {
                                i + 0, i + 1, i + 2
                            };
                        }

                        faces.Add(
                            new Face(
                                faceTris,
                                Math.Clamp(n, 0, mat_length - 1),
                                AutoUnwrapSettings.tile,
                                0,   // smoothing group
                                -1,  // texture group
                                -1,  // element group
                                true // manualUV
                                ));
                    }
                }
            }

            pb.positionsInternal      = verts.ToArray();
            pb.texturesInternal       = uvs.ToArray();
            pb.facesInternal          = faces.ToArray();
            pb.sharedVerticesInternal = SharedVertex.GetSharedVerticesWithPositions(verts.ToArray());
            pb.colorsInternal         = cols.ToArray();

            return(true);
        }
        /// <summary>
        /// Add a set of points to a face and retriangulate. Points are added to the nearest edge.
        /// </summary>
        /// <param name="mesh">The source mesh.</param>
        /// <param name="face">The face to append points to.</param>
        /// <param name="points">Points to added to the face.</param>
        /// <returns>The face created by appending the points.</returns>
        public static Face AppendVerticesToFace(this ProBuilderMesh mesh, Face face, Vector3[] points)
        {
            if (mesh == null)
            {
                throw new ArgumentNullException("mesh");
            }

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

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

            List <Vertex>         vertices = mesh.GetVertices().ToList();
            List <Face>           faces    = new List <Face>(mesh.facesInternal);
            Dictionary <int, int> lookup   = mesh.sharedVertexLookup;
            Dictionary <int, int> lookupUV = null;

            if (mesh.sharedTextures != null)
            {
                lookupUV = new Dictionary <int, int>();
                SharedVertex.GetSharedVertexLookup(mesh.sharedTextures, lookupUV);
            }

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

            List <Vertex> n_vertices = new List <Vertex>();
            List <int>    n_shared   = new List <int>();
            List <int>    n_sharedUV = lookupUV != null ? new List <int>() : null;

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

                if (lookupUV != null)
                {
                    int uv;

                    if (lookupUV.TryGetValue(wound[i].a, out uv))
                    {
                        n_sharedUV.Add(uv);
                    }
                    else
                    {
                        n_sharedUV.Add(-1);
                    }
                }
            }

            // now insert the new points on the nearest edge
            for (int i = 0; i < points.Length; i++)
            {
                int     index = -1;
                float   best  = Mathf.Infinity;
                Vector3 p     = points[i];
                int     vc    = n_vertices.Count;

                for (int n = 0; n < vc; n++)
                {
                    Vector3 v = n_vertices[n].position;
                    Vector3 w = n_vertices[(n + 1) % vc].position;

                    float dist = Math.DistancePointLineSegment(p, v, w);

                    if (dist < best)
                    {
                        best  = dist;
                        index = n;
                    }
                }

                Vertex left = n_vertices[index], right = n_vertices[(index + 1) % vc];

                float x = (p - left.position).sqrMagnitude;
                float y = (p - right.position).sqrMagnitude;

                Vertex insert = Vertex.Mix(left, right, x / (x + y));

                n_vertices.Insert((index + 1) % vc, insert);
                n_shared.Insert((index + 1) % vc, -1);
                if (n_sharedUV != null)
                {
                    n_sharedUV.Insert((index + 1) % vc, -1);
                }
            }

            List <int> triangles;

            try
            {
                Triangulation.TriangulateVertices(n_vertices, out triangles, false);
            }
            catch
            {
                Debug.Log("Failed triangulating face after appending vertices.");
                return(null);
            }

            FaceRebuildData data = new FaceRebuildData();

            data.face = new Face(triangles.ToArray(), face.submeshIndex, new AutoUnwrapSettings(face.uv),
                                 face.smoothingGroup, face.textureGroup, -1, face.manualUV);
            data.vertices        = n_vertices;
            data.sharedIndexes   = n_shared;
            data.sharedIndexesUV = n_sharedUV;

            FaceRebuildData.Apply(new List <FaceRebuildData>()
            {
                data
            },
                                  vertices,
                                  faces,
                                  lookup,
                                  lookupUV);

            var newFace = data.face;

            mesh.SetVertices(vertices);
            mesh.faces = faces;
            mesh.SetSharedVertices(lookup);
            mesh.SetSharedTextures(lookupUV);

            // check old normal and make sure this new face is pointing the same direction
            Vector3 oldNrm = Math.Normal(mesh, face);
            Vector3 newNrm = Math.Normal(mesh, newFace);

            if (Vector3.Dot(oldNrm, newNrm) < 0)
            {
                newFace.Reverse();
            }

            mesh.DeleteFace(face);

            return(newFace);
        }
        /// <summary>
        /// Rebuild a mesh from an ordered set of points.
        /// </summary>
        /// <param name="mesh">The target mesh. The mesh values will be cleared and repopulated with the shape extruded from points.</param>
        /// <param name="points">A path of points to triangulate and extrude.</param>
        /// <param name="extrude">The distance to extrude.</param>
        /// <param name="flipNormals">If true the faces will be inverted at creation.</param>
        /// <param name="holePoints">Holes in the polygon. If null this will be ignored.</param>
        /// <returns>An ActionResult with the status of the operation.</returns>
        public static ActionResult CreateShapeFromPolygon(this ProBuilderMesh mesh, IList <Vector3> points,
                                                          float extrude, bool flipNormals, IList <IList <Vector3> > holePoints)
        {
            if (mesh == null)
            {
                throw new ArgumentNullException("mesh");
            }

            if (points == null || points.Count < 3)
            {
                ClearAndRefreshMesh(mesh);
                return(new ActionResult(ActionResult.Status.NoChange, "Too Few Points"));
            }

            Vector3[] vertices = points.ToArray();

            Vector3[][] holeVertices = null;
            if (holePoints != null && holePoints.Count > 0)
            {
                holeVertices = new Vector3[holePoints.Count][];
                for (int i = 0; i < holePoints.Count; i++)
                {
                    if (holePoints[i] == null || holePoints[i].Count < 3)
                    {
                        ClearAndRefreshMesh(mesh);
                        return(new ActionResult(ActionResult.Status.NoChange, "Too Few Points in hole " + i));
                    }

                    holeVertices[i] = holePoints[i].ToArray();
                }
            }

            List <int> triangles;

            Log.PushLogLevel(LogLevel.Error);

            if (Triangulation.TriangulateVertices(vertices, out triangles, holeVertices))
            {
                Vector3[] combinedVertices = null;
                if (holeVertices != null)
                {
                    combinedVertices = new Vector3[vertices.Length + holeVertices.Sum(arr => arr.Length)];
                    Array.Copy(vertices, combinedVertices, vertices.Length);
                    int destinationIndex = vertices.Length;
                    foreach (var hole in holeVertices)
                    {
                        Array.ConstrainedCopy(hole, 0, combinedVertices, destinationIndex, hole.Length);
                        destinationIndex += hole.Length;
                    }
                }
                else
                {
                    combinedVertices = vertices;
                }

                int[] indexes = triangles.ToArray();

                if (Math.PolygonArea(combinedVertices, indexes) < Mathf.Epsilon)
                {
                    ClearAndRefreshMesh(mesh);
                    Log.PopLogLevel();
                    return(new ActionResult(ActionResult.Status.Failure, "Polygon Area < Epsilon"));
                }

                mesh.Clear();

                mesh.positionsInternal = combinedVertices;
                var newFace = new Face(indexes);
                mesh.facesInternal          = new[] { newFace };
                mesh.sharedVerticesInternal = SharedVertex.GetSharedVerticesWithPositions(combinedVertices);
                mesh.InvalidateCaches();

                // check that all points are represented in the triangulation
                if (newFace.distinctIndexesInternal.Length != combinedVertices.Length)
                {
                    ClearAndRefreshMesh(mesh);
                    Log.PopLogLevel();
                    return(new ActionResult(ActionResult.Status.Failure, "Triangulation missing points"));
                }

                Vector3 nrm = Math.Normal(mesh, mesh.facesInternal[0]);
                nrm = mesh.gameObject.transform.TransformDirection(nrm);
                if ((flipNormals
                    ? Vector3.Dot(mesh.gameObject.transform.up, nrm) > 0f
                    : Vector3.Dot(mesh.gameObject.transform.up, nrm) < 0f))
                {
                    mesh.facesInternal[0].Reverse();
                }

                if (extrude != 0.0f)
                {
                    mesh.DuplicateAndFlip(mesh.facesInternal);

                    mesh.Extrude(new Face[] { (flipNormals ? mesh.facesInternal[1] : mesh.facesInternal[0]) },
                                 ExtrudeMethod.IndividualFaces, extrude);

                    if ((extrude < 0f && !flipNormals) || (extrude > 0f && flipNormals))
                    {
                        foreach (var face in mesh.facesInternal)
                        {
                            face.Reverse();
                        }
                    }
                }

                mesh.ToMesh();
                mesh.Refresh();
            }
            else
            {
                // clear mesh instead of showing an invalid one
                ClearAndRefreshMesh(mesh);
                Log.PopLogLevel();
                return(new ActionResult(ActionResult.Status.Failure, "Failed Triangulating Points"));
            }

            Log.PopLogLevel();

            return(new ActionResult(ActionResult.Status.Success, "Create Polygon Shape"));
        }
예제 #24
0
        /// <summary>
        /// Given an array of "donors", this method returns a merged ProBuilderMesh.
        /// </summary>
        /// <param name="meshes"></param>
        /// <returns></returns>
        public static List <ProBuilderMesh> CombineObjects(IEnumerable <ProBuilderMesh> meshes)
        {
            if (meshes == null)
            {
                throw new ArgumentNullException("meshes");
            }

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

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

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

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

                foreach (var face in meshFaces)
                {
                    var newFace = new Face(face);
                    newFace.ShiftIndexes(offset);
                    // prevents uvs from shifting when being converted from local coords to world space
                    if (!newFace.manualUV && !newFace.uv.useWorldSpace)
                    {
                        newFace.manualUV = true;
                    }

                    var material     = materials[Math.Clamp(face.submeshIndex, 0, materialCount - 1)];
                    var submeshIndex = materialMap.IndexOf(material);

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

                    faces.Add(newFace);
                }

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

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

                offset += meshVertexCount;
            }

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

            foreach (var m in res)
            {
                m.renderer.sharedMaterials = materialMap.ToArray();
                FilterUnusedSubmeshIndexes(m);
                m.SetPivot(pivot);
            }
            return(res);
        }
예제 #25
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);
        }
예제 #26
0
 public static HandleRef getCPtr(SharedVertex obj)
 {
     return((obj == null) ? new HandleRef(null, IntPtr.Zero) : obj.swigCPtr);
 }