public void CreateShapeFromPolygon_ExtrudeOrNot()
        {
            m_Poly.m_Points.Add(new Vector3(0, 0, 0));
            m_Poly.m_Points.Add(new Vector3(0, 0, 2));
            m_Poly.m_Points.Add(new Vector3(2, 0, 0));
            m_Poly.extrude     = 0.0f;
            m_Poly.flipNormals = false;
            m_pb.CreateShapeFromPolygon(m_Poly.m_Points, m_Poly.extrude, m_Poly.flipNormals);

            Assert.That(m_pb.faceCount, Is.EqualTo(1));
            m_pb.Clear();
            Assert.That(m_pb.faceCount, Is.EqualTo(0));
            m_Poly.extrude = 1.0f;
            m_pb.CreateShapeFromPolygon(m_Poly.m_Points, m_Poly.extrude, m_Poly.flipNormals);
            Assert.That(m_pb.faceCount, Is.EqualTo(5));
        }
Exemple #2
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();
 }
Exemple #3
0
        public void Clear()
        {
            m_pbMesh.Clear();
            m_pbMesh.Refresh();
            m_pbMesh.ToMesh();

            MeshFilter filter = m_pbMesh.GetComponent <MeshFilter>();

            filter.sharedMesh.bounds = new Bounds(Vector3.zero, Vector3.zero);

            RaiseChanged(false, true);
        }
Exemple #4
0
        public override Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
        {
            var meshSize = Math.Abs(size);

            if (meshSize.x < float.Epsilon || meshSize.z < float.Epsilon)
            {
                mesh.Clear();
                if (mesh.mesh != null)
                {
                    mesh.mesh.Clear();
                }
                return(new Bounds());
            }

            var width  = meshSize.x;
            var height = meshSize.z;

            Vector2[] p = new Vector2[4];
            Vector3[] v = new Vector3[4];
            Face[]    f = new Face[1];

            float x0 = -(width / 2f);
            float x1 = (width / 2f);

            float y0 = -(height / 2f);
            float y1 = (height / 2f);

            p[0] = new Vector2(x0, y0);
            p[1] = new Vector2(x1, y0);
            p[2] = new Vector2(x0, y1);
            p[3] = new Vector2(x1, y1);

            f[0] = new Face(new int[6]
            {
                0,
                1,
                2,
                1,
                3,
                2
            });

            for (int i = 0; i < v.Length; i++)
            {
                v[i] = new Vector3(p[i].y, 0, p[i].x);
            }

            mesh.RebuildWithPositionsAndFaces(v, f);

            return(mesh.mesh.bounds);
        }
Exemple #5
0
        public override Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
        {
            mesh.Clear();

            Vector3[] points = new Vector3[k_CubeTriangles.Length];

            for (int i = 0; i < k_CubeTriangles.Length; i++)
            {
                points[i] = rotation * Vector3.Scale(k_CubeVertices[k_CubeTriangles[i]], Math.Abs(size));
            }

            mesh.GeometryWithPoints(points);

            return(mesh.mesh.bounds);
        }
        /// <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"));
        }
 /// <summary>
 /// Clear and refresh mesh in case of failure to create a shape.
 /// </summary>
 /// <param name="mesh"></param>
 internal static void ClearAndRefreshMesh(this ProBuilderMesh mesh)
 {
     mesh.Clear();
     mesh.ToMesh();
     mesh.Refresh();
 }
        /// <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"));
        }
        /// <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);
            }
        }
        /// <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"));
        }
Exemple #11
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);
            }
        }
 public void Clear()
 {
     m_pbMesh.Clear();
     m_pbMesh.Refresh();
     m_pbMesh.ToMesh();
 }