using UnityEngine; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine.Serialization; using System.Collections.ObjectModel; namespace UnityEngine.ProBuilder { /// /// A face is composed of a set of triangles, and a material. /// /// Triangle indices may point to the same vertex index as long as the vertices are unique to the face. That is, /// every vertex that a face references should only be used by that face's indices. To associate vertices that /// share common attributes (usually position), use the sharedIndexes property. /// /// ProBuilder automatically manages condensing common vertices in the /// EditorMeshUtility.Optimize function. /// [Serializable] public sealed class Face { [FormerlySerializedAs("_indices")] [SerializeField] int[] m_Indexes; /// /// Stores the ID of the smoothing group that this Face is part of. ProBuilder averages the edge /// normals for all faces that share a [Smoothing Group](../manual/workflow-edit-smoothing.md). /// [SerializeField] [FormerlySerializedAs("_smoothingGroup")] int m_SmoothingGroup; /// /// Determines how ProBuilder projects this face's vertices 2D space when is false. /// [SerializeField] [FormerlySerializedAs("_uv")] AutoUnwrapSettings m_Uv; /// /// Stores the material for this face to use. /// [SerializeField] [FormerlySerializedAs("_mat")] Material m_Material; [SerializeField] int m_SubmeshIndex; [SerializeField] [FormerlySerializedAs("manualUV")] bool m_ManualUV; /// /// Gets or sets whether to map this face's UV coordinates manually or automatically. See /// [Mapping Textures with UVs](../manual/workflow-uvs.html) for an overview of the differences. /// /// /// True to set UV coordinates manually; false to use . /// public bool manualUV { get { return m_ManualUV; } set { m_ManualUV = value; } } /// /// UV element group. Used by the UV editor to group faces. /// [SerializeField] internal int elementGroup; [SerializeField] int m_TextureGroup; /// /// Gets or sets which texture group this face belongs to. ProBuilder uses texture groups when /// projecting Auto UVs. See [Continuous tiling](../manual/workflow-uvs.html#continuous-tiling). /// /// /// ID of the texture group for this face. /// public int textureGroup { get { return m_TextureGroup;} set { m_TextureGroup = value; } } /// /// Gets or sets a reference to the array of triangle indices that make up this face. /// internal int[] indexesInternal { get { return m_Indexes; } set { if (m_Indexes == null) throw new ArgumentNullException("value"); if (m_Indexes.Length % 3 != 0) throw new ArgumentException("Face indexes must be a multiple of 3."); m_Indexes = value; InvalidateCache(); } } /// /// Gets the triangle indices that compose this face. /// /// /// The array of indices representing this face's triangles. /// public ReadOnlyCollection indexes { get { return new ReadOnlyCollection(m_Indexes); } } /// /// Sets the triangles that compose this face. /// /// The new triangle array. public void SetIndexes(IEnumerable indices) { if (indices == null) throw new ArgumentNullException("indices"); var array = indices.ToArray(); int len = array.Length; if (len % 3 != 0) throw new ArgumentException("Face indexes must be a multiple of 3."); m_Indexes = array; InvalidateCache(); } [NonSerialized] int[] m_DistinctIndexes; [NonSerialized] Edge[] m_Edges; /// /// Returns a reference to the cached distinct indexes (each vertex index is only referenced once in m_DistinctIndexes). /// internal int[] distinctIndexesInternal { get { return m_DistinctIndexes == null ? CacheDistinctIndexes() : m_DistinctIndexes; } } /// /// Gets a collection of the vertex indexes that the indexes array references, made distinct. /// /// /// A unique collection of vertices. /// public ReadOnlyCollection distinctIndexes { get { return new ReadOnlyCollection(distinctIndexesInternal); } } internal Edge[] edgesInternal { get { return m_Edges == null ? CacheEdges() : m_Edges; } } /// /// Gets the perimeter edges that compose this face. /// /// /// The collection of edges on this face. /// public ReadOnlyCollection edges { get { return new ReadOnlyCollection(edgesInternal); } } /// /// Gets or sets which smoothing group this face belongs to, if any. This is used to calculate vertex normals. /// /// /// The ID of this smoothing group as an integer. /// public int smoothingGroup { get { return m_SmoothingGroup; } set { m_SmoothingGroup = value; } } /// /// Gets the material that this face uses. /// [Obsolete("Face.material is deprecated. Please use submeshIndex instead.")] public Material material { get { return m_Material; } set { m_Material = value; } } /// /// Gets or sets the index of the submesh that this face belongs to. /// /// /// The ID of the submesh as an integer. /// public int submeshIndex { get { return m_SubmeshIndex; } set { m_SubmeshIndex = value; } } /// /// Gets or sets a reference to the [Auto UV](../manual/workflow-uvs.html#auto-uv-mode-features) mapping parameters. /// /// /// The ID of this submesh as an integer. /// public AutoUnwrapSettings uv { get { return m_Uv; } set { m_Uv = value; } } /// /// Gets the index for the specified triangle in this face's array of triangle indices. /// /// The triangle to access /// /// The index of the specified triangle. /// public int this[int i] { get { return indexesInternal[i]; } } /// /// Creates a Face with an empty triangles array. /// public Face() { m_SubmeshIndex = 0; } /// /// Creates a face with default values and the specified set of triangles. /// /// The new triangles array. public Face(IEnumerable indices) { SetIndexes(indices); m_Uv = AutoUnwrapSettings.tile; m_Material = BuiltinMaterials.defaultMaterial; m_SmoothingGroup = Smoothing.smoothingGroupNone; m_SubmeshIndex = 0; textureGroup = -1; elementGroup = 0; } [Obsolete("Face.material is deprecated. Please use \"submeshIndex\" instead.")] internal Face(int[] triangles, Material m, AutoUnwrapSettings u, int smoothing, int texture, int element, bool manualUVs) { SetIndexes(triangles); m_Uv = new AutoUnwrapSettings(u); m_Material = m; m_SmoothingGroup = smoothing; textureGroup = texture; elementGroup = element; manualUV = manualUVs; m_SubmeshIndex = 0; } internal Face(IEnumerable triangles, int submeshIndex, AutoUnwrapSettings u, int smoothing, int texture, int element, bool manualUVs) { SetIndexes(triangles); m_Uv = new AutoUnwrapSettings(u); m_SmoothingGroup = smoothing; textureGroup = texture; elementGroup = element; manualUV = manualUVs; m_SubmeshIndex = submeshIndex; } /// /// Creates a new Face as a copy of another face. /// /// The Face from which to copy properties and triangles. public Face(Face other) { CopyFrom(other); } /// /// Copies properties and triangles from the specified face to this face. /// /// The Face from which to copy properties and triangles. public void CopyFrom(Face other) { if (other == null) throw new ArgumentNullException("other"); int len = other.indexesInternal.Length; m_Indexes = new int[len]; Array.Copy(other.indexesInternal, m_Indexes, len); m_SmoothingGroup = other.smoothingGroup; m_Uv = new AutoUnwrapSettings(other.uv); #pragma warning disable 618 m_Material = other.material; #pragma warning restore 618 manualUV = other.manualUV; m_TextureGroup = other.textureGroup; elementGroup = other.elementGroup; m_SubmeshIndex = other.m_SubmeshIndex; InvalidateCache(); } internal void InvalidateCache() { m_Edges = null; m_DistinctIndexes = null; } Edge[] CacheEdges() { if (m_Indexes == null) return null; HashSet dist = new HashSet(); List dup = new List(); for (int i = 0; i < indexesInternal.Length; i += 3) { Edge a = new Edge(indexesInternal[i + 0], indexesInternal[i + 1]); Edge b = new Edge(indexesInternal[i + 1], indexesInternal[i + 2]); Edge c = new Edge(indexesInternal[i + 2], indexesInternal[i + 0]); if (!dist.Add(a)) dup.Add(a); if (!dist.Add(b)) dup.Add(b); if (!dist.Add(c)) dup.Add(c); } dist.ExceptWith(dup); m_Edges = dist.ToArray(); return m_Edges; } int[] CacheDistinctIndexes() { if (m_Indexes == null) return null; m_DistinctIndexes = m_Indexes.Distinct().ToArray(); return distinctIndexesInternal; } /// /// Tests whether a triangle matches one of the triangles of this face. /// /// First index in the triangle /// Second index in the triangle /// Third index in the triangle /// True if {a,b,c} is found in this face's list of triangles; otherwise false. public bool Contains(int a, int b, int c) { for (int i = 0, cnt = indexesInternal.Length; i < cnt; i += 3) { if (a == indexesInternal[i + 0] && b == indexesInternal[i + 1] && c == indexesInternal[i + 2]) return true; } return false; } /// /// Returns whether this face can be converted to a quad (a polygon with four sides). /// /// True if this face is divisible by 4; false otherwise. public bool IsQuad() { return edgesInternal != null && edgesInternal.Length == 4; } /// /// Converts a two-triangle face to a quad representation. /// /// A quad (an array of four indices); or null if indices are not able to be represented as a quad. public int[] ToQuad() { if (!IsQuad()) throw new InvalidOperationException("Face is not representable as a quad. Use Face.IsQuad to check for validity."); int[] quad = new int[4] { edgesInternal[0].a, edgesInternal[0].b, -1, -1 }; if (edgesInternal[1].a == quad[1]) quad[2] = edgesInternal[1].b; else if (edgesInternal[2].a == quad[1]) quad[2] = edgesInternal[2].b; else if (edgesInternal[3].a == quad[1]) quad[2] = edgesInternal[3].b; if (edgesInternal[1].a == quad[2]) quad[3] = edgesInternal[1].b; else if (edgesInternal[2].a == quad[2]) quad[3] = edgesInternal[2].b; else if (edgesInternal[3].a == quad[2]) quad[3] = edgesInternal[3].b; return quad; } /// /// Returns a string representation of the face. /// /// String formatted as `[a, b, c], ...`. public override string ToString() { System.Text.StringBuilder sb = new System.Text.StringBuilder(); for (int i = 0; i < indexesInternal.Length; i += 3) { sb.Append("["); sb.Append(indexesInternal[i]); sb.Append(", "); sb.Append(indexesInternal[i + 1]); sb.Append(", "); sb.Append(indexesInternal[i + 2]); sb.Append("]"); if (i < indexesInternal.Length - 3) sb.Append(", "); } return sb.ToString(); } /// /// Adds an offset to each value in the indices array. /// /// The value to add to each index. public void ShiftIndexes(int offset) { for (int i = 0, c = m_Indexes.Length; i < c; i++) m_Indexes[i] += offset; InvalidateCache(); } /// /// Finds the smallest value in the triangles array. /// /// The smallest value in the indices array. int SmallestIndexValue() { int smallest = m_Indexes[0]; for (int i = 1; i < m_Indexes.Length; i++) { if (m_Indexes[i] < smallest) smallest = m_Indexes[i]; } return smallest; } /// /// Finds the smallest value in the indices array, then offsets by subtracting that value from each index. /// /// /// ``` /// // sets the indexes array to `{0, 1, 2}`. /// new Face(3,4,5).ShiftIndexesToZero(); /// ``` /// public void ShiftIndexesToZero() { int offset = SmallestIndexValue(); for (int i = 0; i < m_Indexes.Length; i++) m_Indexes[i] -= offset; InvalidateCache(); } /// /// Reverses the order of the triangle array. This has the effect of reversing the direction that this face renders. /// public void Reverse() { Array.Reverse(m_Indexes); InvalidateCache(); } internal static void GetIndices(IEnumerable faces, List indices) { indices.Clear(); foreach (var face in faces) { for (int i = 0, c = face.indexesInternal.Length; i < c; ++i) indices.Add(face.indexesInternal[i]); } } internal static void GetDistinctIndices(IEnumerable faces, List indices) { indices.Clear(); foreach (var face in faces) { for (int i = 0, c = face.distinctIndexesInternal.Length; i < c; ++i) indices.Add(face.distinctIndexesInternal[i]); } } /// /// Advances to the next connected edge given a source edge and the index connect. /// internal bool TryGetNextEdge(Edge source, int index, ref Edge nextEdge, ref int nextIndex) { for (int i = 0, c = edgesInternal.Length; i < c; i++) { if (edgesInternal[i] == source) continue; nextEdge = edgesInternal[i]; if (nextEdge.Contains(index)) { nextIndex = nextEdge.a == index ? nextEdge.b : nextEdge.a; return true; } } return false; } } }