using UnityEngine; using System.Collections.Generic; using System.Linq; using UnityEngine.Serialization; using System; using System.Collections.ObjectModel; namespace UnityEngine.ProBuilder { /// /// This component is responsible for storing all the data necessary for editing and compiling UnityEngine.Mesh objects. /// [AddComponentMenu("")] [DisallowMultipleComponent] [RequireComponent(typeof(MeshFilter))] [RequireComponent(typeof(MeshRenderer))] [ExecuteInEditMode] public sealed partial class ProBuilderMesh : MonoBehaviour { /// /// Max number of UV channels that ProBuilderMesh format supports. /// const int k_UVChannelCount = 4; /// /// The current mesh format version. This is used to run expensive upgrade functions once in ToMesh(). /// const int k_MeshFormatVersion = 1; const int k_MeshFormatVersionSubmeshMaterialRefactor = 1; /// /// The maximum number of vertices that a ProBuilderMesh can accomodate. /// public const uint maxVertexCount = ushort.MaxValue; [SerializeField] int m_MeshFormatVersion; [SerializeField] [FormerlySerializedAs("_quads")] Face[] m_Faces; [SerializeField] [FormerlySerializedAs("_sharedIndices")] [FormerlySerializedAs("m_SharedVertexes")] SharedVertex[] m_SharedVertices; [Flags] enum CacheValidState : byte { SharedVertex = 1 << 0, SharedTexture = 1 << 1 } [NonSerialized] CacheValidState m_CacheValid; [NonSerialized] Dictionary m_SharedVertexLookup; [SerializeField] [FormerlySerializedAs("_sharedIndicesUV")] SharedVertex[] m_SharedTextures; [NonSerialized] Dictionary m_SharedTextureLookup; [SerializeField] [FormerlySerializedAs("_vertices")] Vector3[] m_Positions; [SerializeField] [FormerlySerializedAs("_uv")] Vector2[] m_Textures0; [SerializeField] [FormerlySerializedAs("_uv3")] List m_Textures2; [SerializeField] [FormerlySerializedAs("_uv4")] List m_Textures3; [SerializeField] [FormerlySerializedAs("_tangents")] Vector4[] m_Tangents; [NonSerialized] Vector3[] m_Normals; [SerializeField] [FormerlySerializedAs("_colors")] Color[] m_Colors; /// /// If false, ProBuilder will automatically create and scale colliders. /// public bool userCollisions { get; set; } [FormerlySerializedAs("unwrapParameters")] [SerializeField] UnwrapParameters m_UnwrapParameters; /// /// UV2 generation parameters. /// public UnwrapParameters unwrapParameters { get { return m_UnwrapParameters; } set { m_UnwrapParameters = value; } } [FormerlySerializedAs("dontDestroyMeshOnDelete")] [SerializeField] bool m_PreserveMeshAssetOnDestroy; /// /// If "Meshes are Assets" feature is enabled, this is used to relate pb_Objects to stored meshes. /// [SerializeField] internal string assetGuid; [NonSerialized] MeshRenderer m_MeshRenderer; #pragma warning disable 109 internal new MeshRenderer renderer { get { if (m_MeshRenderer == null) m_MeshRenderer = GetComponent(); return m_MeshRenderer; } } #pragma warning restore 109 [NonSerialized] MeshFilter m_MeshFilter; #pragma warning disable 109 internal new MeshFilter filter { get { if (m_MeshFilter == null) m_MeshFilter = GetComponent(); return m_MeshFilter; } } #pragma warning restore 109 /// /// In the editor, when you delete a ProBuilderMesh you usually also want to destroy the mesh asset. /// However, there are situations you'd want to keep the mesh around, like when stripping probuilder scripts. /// public bool preserveMeshAssetOnDestroy { get { return m_PreserveMeshAssetOnDestroy; } set { m_PreserveMeshAssetOnDestroy = value; } } /// /// Check if the mesh contains the requested arrays. /// /// A flag containing the array types that a ProBuilder mesh stores. /// True if all arrays in the flag are present, false if not. public bool HasArrays(MeshArrays channels) { bool missing = false; int vc = vertexCount; missing |= (channels & MeshArrays.Position) == MeshArrays.Position && m_Positions == null; missing |= (channels & MeshArrays.Normal) == MeshArrays.Normal && (m_Normals == null || m_Normals.Length != vc); missing |= (channels & MeshArrays.Texture0) == MeshArrays.Texture0 && (m_Textures0 == null || m_Textures0.Length != vc); missing |= (channels & MeshArrays.Texture2) == MeshArrays.Texture2 && (m_Textures2 == null || m_Textures2.Count != vc); missing |= (channels & MeshArrays.Texture3) == MeshArrays.Texture3 && (m_Textures3 == null || m_Textures3.Count != vc); missing |= (channels & MeshArrays.Color) == MeshArrays.Color && (m_Colors == null || m_Colors.Length != vc); missing |= (channels & MeshArrays.Tangent) == MeshArrays.Tangent && (m_Tangents == null || m_Tangents.Length != vc); // UV2 is a special case. It is not stored in ProBuilderMesh, does not necessarily match the vertex count, // at it has a cost to check. if ((channels & MeshArrays.Texture1) == MeshArrays.Texture1) { var m_Textures1 = mesh != null ? mesh.uv2 : null; missing |= (m_Textures1 == null || m_Textures1.Length < 3); } return !missing; } internal Face[] facesInternal { get { return m_Faces; } set { m_Faces = value; } } /// /// Meshes are composed of vertices and faces. Faces primarily contain triangles and material information. With these components, ProBuilder will compile a mesh. /// /// /// A collection of the @"UnityEngine.ProBuilder.Face"'s that make up this mesh. /// public IList faces { get { return new ReadOnlyCollection(m_Faces); } set { if (value == null) throw new ArgumentNullException("value"); m_Faces = value.ToArray(); } } internal void InvalidateSharedVertexLookup() { if (m_SharedVertexLookup == null) m_SharedVertexLookup = new Dictionary(); m_SharedVertexLookup.Clear(); m_CacheValid &= ~CacheValidState.SharedVertex; } internal void InvalidateSharedTextureLookup() { if (m_SharedTextureLookup == null) m_SharedTextureLookup = new Dictionary(); m_SharedTextureLookup.Clear(); m_CacheValid &= ~CacheValidState.SharedTexture; } internal void InvalidateCaches() { InvalidateSharedVertexLookup(); InvalidateSharedTextureLookup(); foreach (var face in faces) face.InvalidateCache(); m_SelectedCacheDirty = true; } internal SharedVertex[] sharedVerticesInternal { get { return m_SharedVertices; } set { m_SharedVertices = value; InvalidateSharedVertexLookup(); } } /// /// ProBuilder makes the assumption that no @"UnityEngine.ProBuilder.Face" references a vertex used by another. /// However, we need a way to associate vertices in the editor for many operations. These vertices are usually /// called coincident, or shared vertices. ProBuilder manages these associations with the sharedIndexes array. /// Each array contains a list of triangles that point to vertices considered to be coincident. When ProBuilder /// compiles a UnityEngine.Mesh from the ProBuilderMesh, these vertices will be condensed to a single vertex /// where possible. /// /// /// The shared (or common) index array for this mesh. /// public IList sharedVertices { get { return new ReadOnlyCollection(m_SharedVertices); } set { if (value == null) throw new ArgumentNullException("value"); int len = value.Count; m_SharedVertices = new SharedVertex[len]; for (var i = 0; i < len; i++) m_SharedVertices[i] = new SharedVertex(value[i]); InvalidateSharedVertexLookup(); } } internal Dictionary sharedVertexLookup { get { if ((m_CacheValid & CacheValidState.SharedVertex) != CacheValidState.SharedVertex) { SharedVertex.GetSharedVertexLookup(m_SharedVertices, m_SharedVertexLookup); m_CacheValid |= CacheValidState.SharedVertex; } return m_SharedVertexLookup; } } /// /// Set the sharedIndexes array for this mesh with a lookup dictionary. /// /// /// The new sharedIndexes array. /// /// internal void SetSharedVertices(IEnumerable> indexes) { if (indexes == null) throw new ArgumentNullException("indexes"); m_SharedVertices = SharedVertex.ToSharedVertices(indexes); InvalidateSharedVertexLookup(); } internal SharedVertex[] sharedTextures { get { return m_SharedTextures; } set { m_SharedTextures = value; InvalidateSharedTextureLookup(); } } internal Dictionary sharedTextureLookup { get { if ((m_CacheValid & CacheValidState.SharedTexture) != CacheValidState.SharedTexture) { m_CacheValid |= CacheValidState.SharedTexture; SharedVertex.GetSharedVertexLookup(m_SharedTextures, m_SharedTextureLookup); } return m_SharedTextureLookup; } } internal void SetSharedTextures(IEnumerable> indexes) { if (indexes == null) throw new ArgumentNullException("indexes"); m_SharedTextures = SharedVertex.ToSharedVertices(indexes); InvalidateSharedTextureLookup(); } internal Vector3[] positionsInternal { get { return m_Positions; } set { m_Positions = value; } } /// /// The vertex positions that make up this mesh. /// public IList positions { get { return new ReadOnlyCollection(m_Positions); } set { if (value == null) throw new ArgumentNullException("value"); m_Positions = value.ToArray(); } } /// /// Creates a new array of vertices with values from a @"UnityEngine.ProBuilder.ProBuilderMesh" component. /// /// An optional list of indexes pointing to the mesh attribute indexes to include in the returned Vertex array. /// An array of vertices. public Vertex[] GetVertices(IList indexes = null) { int meshVertexCount = vertexCount; int vc = indexes != null ? indexes.Count : vertexCount; Vertex[] v = new Vertex[vc]; Vector3[] positions = positionsInternal; Color[] colors = colorsInternal; Vector2[] uv0s = texturesInternal; Vector4[] tangents = GetTangents(); Vector3[] normals = GetNormals(); Vector2[] uv2s = mesh != null ? mesh.uv2 : null; List uv3s = new List(); List uv4s = new List(); GetUVs(2, uv3s); GetUVs(3, uv4s); bool _hasPositions = positions != null && positions.Count() == meshVertexCount; bool _hasColors = colors != null && colors.Count() == meshVertexCount; bool _hasNormals = normals != null && normals.Count() == meshVertexCount; bool _hasTangents = tangents != null && tangents.Count() == meshVertexCount; bool _hasUv0 = uv0s != null && uv0s.Count() == meshVertexCount; bool _hasUv2 = uv2s != null && uv2s.Count() == meshVertexCount; bool _hasUv3 = uv3s.Count() == meshVertexCount; bool _hasUv4 = uv4s.Count() == meshVertexCount; for (int i = 0; i < vc; i++) { v[i] = new Vertex(); int ind = indexes == null ? i : indexes[i]; if (_hasPositions) v[i].position = positions[ind]; if (_hasColors) v[i].color = colors[ind]; if (_hasNormals) v[i].normal = normals[ind]; if (_hasTangents) v[i].tangent = tangents[ind]; if (_hasUv0) v[i].uv0 = uv0s[ind]; if (_hasUv2) v[i].uv2 = uv2s[ind]; if (_hasUv3) v[i].uv3 = uv3s[ind]; if (_hasUv4) v[i].uv4 = uv4s[ind]; } return v; } /// /// Set the vertex element arrays on this mesh. /// /// The new vertex array. /// An optional parameter that will apply elements to the MeshFilter.sharedMesh. Note that this should only be used when the mesh is in its original state, not optimized (meaning it won't affect triangles which can be modified by Optimize). public void SetVertices(IList vertices, bool applyMesh = false) { if (vertices == null) throw new ArgumentNullException("vertices"); var first = vertices.FirstOrDefault(); if (first == null || !first.HasArrays(MeshArrays.Position)) { Clear(); return; } Vector3[] position; Color[] color; Vector3[] normal; Vector4[] tangent; Vector2[] uv0; Vector2[] uv2; List uv3; List uv4; Vertex.GetArrays(vertices, out position, out color, out uv0, out normal, out tangent, out uv2, out uv3, out uv4); m_Positions = position; m_Colors = color; m_Tangents = tangent; m_Textures0 = uv0; m_Textures2 = uv3; m_Textures3 = uv4; if (applyMesh) { Mesh umesh = mesh; if (first.HasArrays(MeshArrays.Position)) umesh.vertices = position; if (first.HasArrays(MeshArrays.Color)) umesh.colors = color; if (first.HasArrays(MeshArrays.Texture0)) umesh.uv = uv0; if (first.HasArrays(MeshArrays.Normal)) umesh.normals = normal; if (first.HasArrays(MeshArrays.Tangent)) umesh.tangents = tangent; if (first.HasArrays(MeshArrays.Texture1)) umesh.uv2 = uv2; if (first.HasArrays(MeshArrays.Texture2)) umesh.SetUVs(2, uv3); if (first.HasArrays(MeshArrays.Texture3)) umesh.SetUVs(3, uv4); } } /// /// The mesh normals. /// /// /// public IList normals { get { return m_Normals != null ? new ReadOnlyCollection(m_Normals) : null; } } internal Vector3[] normalsInternal { get { return m_Normals; } set { m_Normals = value; } } /// /// Get the normals array for this mesh. /// /// /// Returns the normals for this mesh. /// public Vector3[] GetNormals() { if (!HasArrays(MeshArrays.Normal)) Normals.CalculateNormals(this); return normals.ToArray(); } internal Color[] colorsInternal { get { return m_Colors; } set { m_Colors = value; } } /// /// Vertex colors array for this mesh. When setting, the value must match the length of positions. /// public IList colors { get { return m_Colors != null ? new ReadOnlyCollection(m_Colors) : null; } set { if (value == null) m_Colors = null; else if (value.Count() != vertexCount) throw new ArgumentOutOfRangeException("value", "Array length must match vertex count."); else m_Colors = value.ToArray(); } } /// /// Get an array of Color values from the mesh. /// /// The colors array for this mesh. If mesh does not contain colors, a new array is returned filled with the default value (Color.white). public Color[] GetColors() { if (HasArrays(MeshArrays.Color)) return colors.ToArray(); return ArrayUtility.Fill(Color.white, vertexCount); } /// /// Get the user-set tangents array for this mesh. If tangents have not been explicitly set, this value will be null. /// /// /// To get the generated tangents that are applied to the mesh through Refresh(), use GetTangents(). /// /// public IList tangents { get { return m_Tangents == null || m_Tangents.Length != vertexCount ? null : new ReadOnlyCollection(m_Tangents); } set { if (value == null) m_Tangents = null; else if (value.Count() != vertexCount) throw new ArgumentOutOfRangeException("value", "Tangent array length must match vertex count"); else m_Tangents = value.ToArray(); } } internal Vector4[] tangentsInternal { get { return m_Tangents; } set { m_Tangents = value; } } /// /// Get the tangents applied to the mesh, or create and cache them if not yet initialized. /// /// The tangents applied to the MeshFilter.sharedMesh. If the tangents array length does not match the vertex count, null is returned. public Vector4[] GetTangents() { if (!HasArrays(MeshArrays.Tangent)) Normals.CalculateTangents(this); return tangents.ToArray(); } internal Vector2[] texturesInternal { get { return m_Textures0; } set { m_Textures0 = value; } } /// /// The UV0 channel. Null if not present. /// /// public IList textures { get { return m_Textures0 != null ? new ReadOnlyCollection(m_Textures0) : null; } set { if (value == null) m_Textures0 = null; else if (value.Count() != vertexCount) throw new ArgumentOutOfRangeException("value"); else m_Textures0 = value.ToArray(); } } /// /// Copy values in a UV channel to uvs. /// /// The index of the UV channel to fetch values from. The valid range is `{0, 1, 2, 3}`. /// A list that will be cleared and populated with the UVs copied from this mesh. public void GetUVs(int channel, List uvs) { if (uvs == null) throw new ArgumentNullException("uvs"); if (channel < 0 || channel > 3) throw new ArgumentOutOfRangeException("channel"); uvs.Clear(); switch (channel) { case 0: for (int i = 0; i < vertexCount; i++) uvs.Add((Vector4)m_Textures0[i]); break; case 1: if (mesh != null && mesh.uv2 != null) { Vector2[] uv2 = mesh.uv2; for (int i = 0; i < uv2.Length; i++) uvs.Add((Vector4)uv2[i]); } break; case 2: if (m_Textures2 != null) uvs.AddRange(m_Textures2); break; case 3: if (m_Textures3 != null) uvs.AddRange(m_Textures3); break; } } internal ReadOnlyCollection GetUVs(int channel) { if (channel == 0) return new ReadOnlyCollection(m_Textures0); if (channel == 1) return new ReadOnlyCollection(mesh.uv2); if (channel == 2) return m_Textures2 == null ? null : new ReadOnlyCollection(m_Textures2.Cast().ToList()); if (channel == 3) return m_Textures3 == null ? null : new ReadOnlyCollection(m_Textures3.Cast().ToList()); return null; } /// /// Set the mesh UVs per-channel. Channels 0 and 1 are cast to Vector2, where channels 2 and 3 are kept Vector4. /// /// Does not apply to mesh (use Refresh to reflect changes after application). /// The index of the UV channel to fetch values from. The valid range is `{0, 1, 2, 3}`. /// The new UV values. public void SetUVs(int channel, List uvs) { switch (channel) { case 0: m_Textures0 = uvs != null ? uvs.Select(x => (Vector2)x).ToArray() : null; break; case 1: mesh.uv2 = uvs != null ? uvs.Select(x => (Vector2)x).ToArray() : null; break; case 2: m_Textures2 = uvs != null ? new List(uvs) : null; break; case 3: m_Textures3 = uvs != null ? new List(uvs) : null; break; } } /// /// How many faces does this mesh have? /// public int faceCount { get { return m_Faces == null ? 0 : m_Faces.Length; } } /// /// How many vertices are in the positions array. /// public int vertexCount { get { return m_Positions == null ? 0 : m_Positions.Length; } } /// /// How many edges compose this mesh. /// public int edgeCount { get { return m_Faces.Sum(x => x.edgesInternal.Length); } } /// /// How many vertex indexes make up this mesh. /// public int indexCount { get { return m_Faces == null ? 0 : m_Faces.Sum(x => x.indexesInternal.Length); } } /// /// How many triangles make up this mesh. /// public int triangleCount { get { return m_Faces == null ? 0 : m_Faces.Sum(x => x.indexesInternal.Length) / 3; } } /// /// In the editor, when a ProBuilderMesh is destroyed it will also destroy the MeshFilter.sharedMesh that is found with the parent GameObject. You may override this behaviour by subscribing to onDestroyObject. /// /// /// If onDestroyObject has a subscriber ProBuilder will invoke it instead of cleaning up unused meshes by itself. /// /// public static event Action meshWillBeDestroyed; /// /// Invoked when the element selection changes on any ProBuilderMesh. /// /// /// /// public static event Action elementSelectionChanged; /// /// Convenience property for getting the mesh from the MeshFilter component. /// internal Mesh mesh { get { return filter.sharedMesh; } set { filter.sharedMesh = value; } } internal int id { get { return gameObject.GetInstanceID(); } } /// /// Ensure that the UnityEngine.Mesh is in sync with the ProBuilderMesh. /// /// A flag describing the state of the synchronicity between the MeshFilter.sharedMesh and ProBuilderMesh components. public MeshSyncState meshSyncState { get { if (mesh == null) return MeshSyncState.Null; int meshNo; int.TryParse(mesh.name.Replace("pb_Mesh", ""), out meshNo); if (meshNo != id) return MeshSyncState.InstanceIDMismatch; return mesh.uv2 == null ? MeshSyncState.Lightmap : MeshSyncState.InSync; } } } }