using UnityEngine; using System.Collections.Generic; using System.Linq; using UnityEngine.Serialization; using System; using System.Collections.ObjectModel; using UnityEngine.Rendering; namespace UnityEngine.ProBuilder { /// /// Represents the ProBuilder MeshFilter component, which is responsible for storing all /// the data necessary for editing and compiling objects. /// // The double "//" sets this component as hidden in the menu, but is used by ObjectNames.cs to get the component name. [AddComponentMenu("//ProBuilder MeshFilter")] // Don't include MeshFilter in the required components because it gets registered with serialization before we have a // chance to mark it with the correct HideFlags. [RequireComponent(typeof(MeshRenderer))] [DisallowMultipleComponent, ExecuteInEditMode, ExcludeFromPreset, ExcludeFromObjectFactory] // [MonoBehaviourIcon("Packages/com.unity.probuilder/Content/Icons/Scripts/ProBuilderMesh@64.png")] public sealed partial class ProBuilderMesh : MonoBehaviour { #if ENABLE_DRIVEN_PROPERTIES internal const HideFlags k_MeshFilterHideFlags = HideFlags.DontSave | HideFlags.HideInInspector | HideFlags.NotEditable; #else internal const HideFlags k_MeshFilterHideFlags = HideFlags.HideInInspector | HideFlags.NotEditable; #endif /// /// Stores the maximum number of UV channels that the ProBuilderMesh format supports. /// const int k_UVChannelCount = 4; /// /// Represents the current mesh format version. This is used to run expensive upgrade functions once in ToMesh(). /// internal const int k_MeshFormatVersion = k_MeshFormatVersionAutoUVScaleOffset; internal const int k_MeshFormatVersionSubmeshMaterialRefactor = 1; internal const int k_MeshFormatVersionAutoUVScaleOffset = 2; /// /// The maximum number of vertices that a ProBuilderMesh can accomodate. /// public const uint maxVertexCount = ushort.MaxValue; // MeshFormatVersion is used to deprecate and upgrade serialized data. [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; [SerializeField] Mesh m_Mesh; [NonSerialized] MeshRenderer m_MeshRenderer; #pragma warning disable 109 internal new MeshRenderer renderer { get { if (!gameObject.TryGetComponent(out m_MeshRenderer)) return null; return m_MeshRenderer; } } #pragma warning restore 109 [NonSerialized] MeshFilter m_MeshFilter; #pragma warning disable 109 internal new MeshFilter filter { get { if (m_MeshFilter == null) { if (!gameObject.TryGetComponent(out m_MeshFilter)) return null; #if UNITY_EDITOR m_MeshFilter.hideFlags = k_MeshFilterHideFlags; #endif } return m_MeshFilter; } } #pragma warning restore 109 internal const ushort k_UnitializedVersionIndex = 0; /// /// Tracks each time the ToMesh() and Refresh() functions are called to modify the mesh. /// This is a simple uint number used to check whether two versions of the ProBuilderMesh are the same or not. /// [SerializeField] ushort m_VersionIndex = k_UnitializedVersionIndex; // k_NewMeshVersionIndex is a reserved value indicating that this is a new mesh instance. It is used to // differentiate between mesh creation and duplication. [NonSerialized] ushort m_InstanceVersionIndex = k_UnitializedVersionIndex; internal ushort versionIndex => m_VersionIndex; internal ushort nonSerializedVersionIndex => m_InstanceVersionIndex; internal struct NonVersionedEditScope : IDisposable { readonly ProBuilderMesh m_Mesh; readonly ushort m_VersionIndex, m_InstanceVersionIndex; public NonVersionedEditScope(ProBuilderMesh mesh) { m_Mesh = mesh; m_VersionIndex = mesh.versionIndex; m_InstanceVersionIndex = mesh.m_InstanceVersionIndex; } public void Dispose() { m_Mesh.m_VersionIndex = m_VersionIndex; m_Mesh.m_InstanceVersionIndex = m_InstanceVersionIndex; } } /// /// Gets or sets whether to destroy the mesh asset if this ProBuilderMesh is deleted. /// /// 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. /// /// True to keep the mesh asset; false to destroy it. public bool preserveMeshAssetOnDestroy { get { return m_PreserveMeshAssetOnDestroy; } set { m_PreserveMeshAssetOnDestroy = value; } } /// /// Tests whether 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, // and it has a cost to check. if ((channels & MeshArrays.Texture1) == MeshArrays.Texture1 && mesh != null) { #if UNITY_2019_3_OR_NEWER missing |= !mesh.HasVertexAttribute(VertexAttribute.TexCoord1); #else var m_Textures1 = m_Mesh.uv2; missing |= (m_Textures1 == null || m_Textures1.Length < 3); #endif } return !missing; } internal Face[] facesInternal { get { return m_Faces; } set { m_Faces = value; } } /// /// Gets or sets the faces that ProBuilder uses to compile a mesh. /// /// Meshes are composed of vertices and faces. Faces primarily contain triangles and material information. /// /// A collection of the Face objects 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 InvalidateFaces() { if (m_Faces == null) { m_Faces = new Face[0]; return; } foreach (var face in faces) face.InvalidateCache(); } internal void InvalidateCaches() { InvalidateSharedVertexLookup(); InvalidateSharedTextureLookup(); InvalidateFaces(); m_SelectedCacheDirty = true; } internal SharedVertex[] sharedVerticesInternal { get { return m_SharedVertices; } set { m_SharedVertices = value; InvalidateSharedVertexLookup(); } } /// /// Gets or sets the sharedVertices for this ProBuilderMesh object. /// /// ProBuilder makes the assumption that no references a vertex used by another. /// However, ProBuilder needs to be able to associate vertices in the Editor for many operations. /// These vertices are usually called [coincident](../manual/gloss.html#coincident), or "shared" vertices. /// ProBuilder manages these associations with the sharedIndexes array. /// /// Each array contains a list of triangles that points to vertices considered to be coincident. /// When ProBuilder compiles a from the ProBuilderMesh, it condenses /// these vertices 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) { if (m_SharedVertexLookup == null) m_SharedVertexLookup = new Dictionary(); SharedVertex.GetSharedVertexLookup(m_SharedVertices, m_SharedVertexLookup); m_CacheValid |= CacheValidState.SharedVertex; } return m_SharedVertexLookup; } } /// /// Sets 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; if (m_SharedTextureLookup == null) m_SharedTextureLookup = new Dictionary(); 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; } } /// /// Gets or sets the vertex positions that compose 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 ProBuilderMesh component. /// /// An optional list of indices used to designate the subset of vertices values to retrieve from the mesh in the array. /// An array of vertices matching either the specified list in the `indexes` parameter. If the `indexes` parameter contains no list, this returns all mesh 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.Length == meshVertexCount; bool _hasColors = colors != null && colors.Length == meshVertexCount; bool _hasNormals = normals != null && normals.Length == meshVertexCount; bool _hasTangents = tangents != null && tangents.Length == meshVertexCount; bool _hasUv0 = uv0s != null && uv0s.Length == meshVertexCount; bool _hasUv2 = uv2s != null && uv2s.Length == 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; } /// /// Returns a list of vertices from a ProBuilderMesh component. /// /// The list to populate. internal void GetVerticesInList(IList vertices) { int vc = vertexCount; vertices.Clear(); 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.Length == vc; bool _hasColors = colors != null && colors.Length == vc; bool _hasNormals = normals != null && normals.Length == vc; bool _hasTangents = tangents != null && tangents.Length == vc; bool _hasUv0 = uv0s != null && uv0s.Length == vc; bool _hasUv2 = uv2s != null && uv2s.Length == vc; bool _hasUv3 = uv3s.Count == vc; bool _hasUv4 = uv4s.Count == vc; for (int i = 0; i < vc; i++) { vertices.Add(new Vertex()); if (_hasPositions) vertices[i].position = positions[i]; if (_hasColors) vertices[i].color = colors[i]; if (_hasNormals) vertices[i].normal = normals[i]; if (_hasTangents) vertices[i].tangent = tangents[i]; if (_hasUv0) vertices[i].uv0 = uv0s[i]; if (_hasUv2) vertices[i].uv2 = uv2s[i]; if (_hasUv3) vertices[i].uv3 = uv3s[i]; if (_hasUv4) vertices[i].uv4 = uv4s[i]; } } /// /// Sets the vertex element arrays on this mesh. /// /// The new vertex array. /// /// Optionally indicate whether to apply elements to the . /// Note that you should only use this when the mesh is in its original state, not optimized /// (that is, when it won't affect triangles which can be /// optimized). /// 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_Normals = normal; 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); IncrementVersionIndex(); } } /// /// Gets the normals for this mesh. /// /// /// /// A collection of normals for this mesh. public IList normals { get { return m_Normals != null ? new ReadOnlyCollection(m_Normals) : null; } } internal Vector3[] normalsInternal { get { return m_Normals; } set { m_Normals = value; } } /// /// Gets the normals array for this mesh. /// /// An array of 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; } } /// /// Gets or sets a collecton of the vertex colors for this mesh. /// When setting, the value must match the length of the positions. /// /// A collection of colors for this mesh. public IList colors { get { return m_Colors != null ? new ReadOnlyCollection(m_Colors) : null; } set { if (value == null || value.Count == 0) m_Colors = null; else if (value.Count != vertexCount) throw new ArgumentOutOfRangeException("value", "Array length must match vertex count."); else m_Colors = value.ToArray(); } } /// /// Returns the Color values from the mesh. /// /// /// An array of colors for this mesh. If the mesh does not contain colors, it returns a new array /// filled with the default value (). /// public Color[] GetColors() { if (HasArrays(MeshArrays.Color)) return colors.ToArray(); return ArrayUtility.Fill(Color.white, vertexCount); } /// /// Gets or sets the array of tangents that the user explicitly set for this mesh. /// /// A collection of tangents for this mesh or null if the user hasn't set any tangents yet. /// /// 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; } } /// /// Returns the tangents applied to the mesh. If they haven't been initialized yet, it creates and caches them. /// /// /// The tangents applied to /// or null if the tangents array length doesn't match the vertex count. /// public Vector4[] GetTangents() { if (!HasArrays(MeshArrays.Tangent)) Normals.CalculateTangents(this); return tangents.ToArray(); } internal Vector2[] texturesInternal { get { return m_Textures0; } set { m_Textures0 = value; } } internal List textures2Internal { get { return m_Textures2; } set { m_Textures2 = value; } } internal List textures3Internal { get { return m_Textures3; } set { m_Textures3 = value; } } /// /// Gets or sets the UV0 channel. /// /// The list of texture UVs for this mesh or null if there are none. /// 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(); } } /// /// Copies values from the specified UV channel to the list of texture UVs. /// /// The index of the UV channel to fetch values from. The valid range is `{0, 1, 2, 3}`. /// The list of texture UVs to clear and populate with the copied UVs. 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; } /// /// Sets the mesh UVs per channel. Channels 0 and 1 are cast to Vector2, but channels 2 and 3 remain Vector4. /// /// Does not apply to mesh (use Refresh to reflect changes after application). /// The index of the UV channel to copy values to. The valid range is `{0, 1, 2, 3}`. /// The list of 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; } } /// /// Gets the number of faces that this mesh has. /// /// The number of faces on this mesh. public int faceCount { get { return m_Faces == null ? 0 : m_Faces.Length; } } /// /// Gets the number of vertices in the positions array. /// /// The number of vertex positions for this mesh. public int vertexCount { get { return m_Positions == null ? 0 : m_Positions.Length; } } /// /// Gets the number of edges that compose this mesh. /// /// The number of edges in this mesh. public int edgeCount { get { int count = 0; for (int i = 0, c = faceCount; i < c; i++) count += facesInternal[i].edgesInternal.Length; return count; } } /// /// Gets the number of vertex indices that compose this mesh. /// /// The number of vertices in this mesh. public int indexCount { get { return m_Faces == null ? 0 : m_Faces.Sum(x => x.indexesInternal.Length); } } /// /// Gets the number of triangles that compose this mesh. /// /// The number of triangles in this mesh. public int triangleCount { get { return m_Faces == null ? 0 : m_Faces.Sum(x => x.indexesInternal.Length) / 3; } } /// /// Invoked when this ProBuilderMesh is deleted. /// /// In the Editor, when a ProBuilderMesh is destroyed it also destroys the /// that it finds with the parent GameObject. /// To override the default behavior, subscribe to onDestroyObject. When onDestroyObject has a /// subscriber, ProBuilder invokes it instead of cleaning up unused meshes by itself. /// /// /// public static event Action meshWillBeDestroyed; /// /// Mesh was rebuilt from Awake call. /// internal static event Action meshWasInitialized; /// /// Invoked from ProBuilderMesh.OnDestroy before any cleanup is performed. /// internal static event Action componentWillBeDestroyed; /// /// Invoked from ProBuilderMesh.Reset after component is rebuilt. /// internal static event Action componentHasBeenReset; /// /// Invoked when the element selection changes on any ProBuilderMesh. /// /// /// /// public static event Action elementSelectionChanged; internal Mesh mesh { get { if (m_Mesh == null && filter != null) m_Mesh = filter.sharedMesh; return m_Mesh; } set { m_Mesh = value; #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(this); UnityEditor.PrefabUtility.RecordPrefabInstancePropertyModifications(this); #endif } } [Obsolete("InstanceID is not used to track mesh references as of 2023/04/12")] internal int id => gameObject.GetInstanceID(); /// /// Gets a flag that indicates whether the is in sync with the ProBuilderMesh. /// /// /// A flag that describes the state of the synchronicity between the /// and ProBuilderMesh components. /// public MeshSyncState meshSyncState { get { if (mesh == null) return MeshSyncState.Null; // if the local version index is uninitialized, that means no edits have occurred since loading this // mesh. it could mean a new mesh was created and ToMesh has not been called yet, or that this mesh was // copy/pasted from an existing instance. // in the latter case, it is handled by listening to the ObjectChangeEvent stream in HierarchyListener. // this function only cares about the case where modifications have been made during the current session. if (m_VersionIndex != m_InstanceVersionIndex && m_InstanceVersionIndex != k_UnitializedVersionIndex) return MeshSyncState.NeedsRebuild; return mesh.uv2 == null ? MeshSyncState.Lightmap : MeshSyncState.InSync; } } internal int meshFormatVersion => m_MeshFormatVersion; } }