using System.Collections.Generic; namespace UnityEngine.ProBuilder.MeshOperations { /// /// Provides a helper function to manage converting triangulated polygons to [quads](../manual/gloss.html#quad). /// public static class QuadUtility { /// /// Converts the faces to quads if possible. /// /// The source mesh. /// The list of faces to process. /// True to apply smoothing. /// A list of the processed faces. public static List ToQuads(this ProBuilderMesh mesh, IList faces, bool smoothing = true) { HashSet processed = new HashSet(); List wings = WingedEdge.GetWingedEdges(mesh, faces, true); // build a lookup of the strength of edge connections between triangle faces Dictionary connections = new Dictionary(); for (int i = 0; i < wings.Count; i++) { using (var it = new WingedEdgeEnumerator(wings[i])) { while (it.MoveNext()) { var border = it.Current; if (border.opposite != null && !connections.ContainsKey(border.edge)) { float score = mesh.GetQuadScore(border, border.opposite); connections.Add(border.edge, score); } } } } List> quads = new List>(); // 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, buddy)); } } // don't collapse coincident vertices if smoothing is enabled, we need the original normals intact return MergeElements.MergePairs(mesh, quads, smoothing); } static Face GetBestQuadConnection(WingedEdge wing, Dictionary connections) { float score = 0f; Face face = null; using (var it = new WingedEdgeEnumerator(wing)) { while (it.MoveNext()) { var border = it.Current; float s = 0f; if (connections.TryGetValue(border.edge, out s) && s > score) { score = connections[border.edge]; face = border.opposite.face; } } } return face; } /** * Get a weighted value for the quality of a quad composed of two triangles. 0 is terrible, 1 is perfect. * normalThreshold will discard any quads where the dot product of their normals is less than the threshold. * @todo Abstract the quad detection to a separate class so it can be applied to pb_Objects. */ static float GetQuadScore(this ProBuilderMesh mesh, WingedEdge left, WingedEdge right, float normalThreshold = .9f) { Vertex[] vertices = mesh.GetVertices(); int[] quad = WingedEdge.MakeQuad(left, right); if (quad == null) return 0f; // first check normals Vector3 leftNormal = Math.Normal(vertices[quad[0]].position, vertices[quad[1]].position, vertices[quad[2]].position); Vector3 rightNormal = Math.Normal(vertices[quad[2]].position, vertices[quad[3]].position, vertices[quad[0]].position); float score = Vector3.Dot(leftNormal, rightNormal); if (score < normalThreshold) return 0f; // next is right-angle-ness check Vector3 a = (vertices[quad[1]].position - vertices[quad[0]].position); Vector3 b = (vertices[quad[2]].position - vertices[quad[1]].position); Vector3 c = (vertices[quad[3]].position - vertices[quad[2]].position); Vector3 d = (vertices[quad[0]].position - vertices[quad[3]].position); a.Normalize(); b.Normalize(); c.Normalize(); d.Normalize(); float da = Mathf.Abs(Vector3.Dot(a, b)); float db = Mathf.Abs(Vector3.Dot(b, c)); float dc = Mathf.Abs(Vector3.Dot(c, d)); float dd = Mathf.Abs(Vector3.Dot(d, a)); score += 1f - ((da + db + dc + dd) * .25f); // and how close to parallel the opposite sides area score += Mathf.Abs(Vector3.Dot(a, c)) * .5f; score += Mathf.Abs(Vector3.Dot(b, d)) * .5f; // the three tests each contribute 1 return score * .33f; } } }