using System; using UnityEditor; namespace UnityEngine.Polybrush { /// /// Script that will be attached at all time on any polybrush object /// It will be used to store a copy of the mesh inside a polymesh object (that is serializable). /// It will also handle the advs switch mode (on/off) /// [ExecuteInEditMode] class PolybrushMesh : MonoBehaviour { internal enum Mode { Mesh, AdditionalVertexStream } internal static class Styles { internal const string k_VertexMismatchStringFormat = "Warning! The GameObject \"{0}\" cannot apply it's 'Additional Vertex Streams' mesh, because it's base mesh has changed and has a different vertex count."; } internal enum ObjectType { Mesh, SkinnedMesh } /// /// References components needed by Polybrush: MeshFilter, MeshRenderer and SkinnedMeshRenderer. /// Will cache references if they are found to avoid additional GetComponent() calls. /// internal struct MeshComponentsCache { GameObject m_Owner; MeshFilter m_MeshFilter; MeshRenderer m_MeshRenderer; SkinnedMeshRenderer m_SkinMeshRenderer; internal bool IsValid() { return m_Owner != null; } internal MeshFilter MeshFilter { get { return m_MeshFilter; } } internal MeshRenderer MeshRenderer { get { return m_MeshRenderer; } } internal SkinnedMeshRenderer SkinnedMeshRenderer { get { return m_SkinMeshRenderer; } } internal MeshComponentsCache(GameObject root) { m_Owner = root; m_MeshFilter = root.GetComponent(); m_MeshRenderer = root.GetComponent(); m_SkinMeshRenderer = root.GetComponent(); } } //seriazlied polymesh stored on the component [SerializeField] PolyMesh m_PolyMesh; //mesh ref from the skinmesh asset, if any [SerializeField] Mesh m_SkinMeshRef; [SerializeField] Mesh m_OriginalMeshObject; [SerializeField] Mode m_Mode; MeshComponentsCache m_ComponentsCache; /// /// Accessor to Unity rendering related components attached to the same GameObject as this component. /// internal MeshComponentsCache componentsCache { get { return m_ComponentsCache; } } /// /// Returns true if there are applied modification on this mesh. /// internal bool hasAppliedChanges { get { if (m_ComponentsCache.MeshFilter) return m_PolyMesh.ToUnityMesh() != m_ComponentsCache.MeshFilter.sharedMesh; if (m_ComponentsCache.SkinnedMeshRenderer) return m_PolyMesh.ToUnityMesh() != m_ComponentsCache.SkinnedMeshRenderer.sharedMesh; return false; } } internal bool hasAppliedAdditionalVertexStreams { get { if (m_ComponentsCache.MeshRenderer != null && m_ComponentsCache.MeshRenderer.additionalVertexStreams != null) return m_ComponentsCache.MeshRenderer.additionalVertexStreams == m_PolyMesh.ToUnityMesh(); return false; } } //access to the polymesh directly internal PolyMesh polyMesh { get { return m_PolyMesh; } } //get: will convert the data stored inside polymesh to a mesh, return it internal Mesh storedMesh { get { if (m_PolyMesh != null) { return m_PolyMesh.ToUnityMesh(); } else { return null; } } } internal ObjectType type { get { if (m_ComponentsCache.SkinnedMeshRenderer) return ObjectType.SkinnedMesh; return ObjectType.Mesh; } } [Obsolete()] internal static bool s_UseADVS { private get; set; } internal Mode mode { get { return m_Mode; } set { UpdateMode(value); } } internal Mesh skinMeshRef { get { return m_SkinMeshRef; } set { m_SkinMeshRef = value; } } internal Mesh sourceMesh { get { return m_OriginalMeshObject; } } bool m_Initialized = false; /// /// Returns true if internal data has been initialized. /// If returns false, use . /// internal bool isInitialized { get { return m_Initialized; } } /// /// Initializes non-serialized internal cache in the component. /// Should be called after instantiation. /// Use to check if it has already been initialized. /// internal void Initialize() { if (isInitialized) return; if (!m_ComponentsCache.IsValid()) m_ComponentsCache = new MeshComponentsCache(gameObject); if (m_PolyMesh == null) m_PolyMesh = new PolyMesh(); Mesh mesh = null; if (m_ComponentsCache.MeshFilter != null) { mesh = m_ComponentsCache.MeshFilter.sharedMesh; if (m_OriginalMeshObject == null) m_OriginalMeshObject = mesh; } else if (m_ComponentsCache.SkinnedMeshRenderer != null) mesh = m_ComponentsCache.SkinnedMeshRenderer.sharedMesh; if (!polyMesh.IsValid() && mesh) SetMesh(mesh); else if (polyMesh.IsValid()) SetMesh(polyMesh.ToUnityMesh()); m_Initialized = true; } /// /// Update Polymesh data with the given Unity Mesh information. /// /// Unity mesh. internal void SetMesh(Mesh unityMesh) { if (unityMesh == null) return; m_PolyMesh.InitializeWithUnityMesh(unityMesh); SynchronizeWithMeshRenderer(); } internal void SetAdditionalVertexStreams(Mesh vertexStreams) { m_PolyMesh.ApplyAttributesFromUnityMesh(vertexStreams, MeshChannelUtility.ToMask(vertexStreams)); SynchronizeWithMeshRenderer(); } /// /// Update GameObject's renderers with PolybrushMesh data. /// internal void SynchronizeWithMeshRenderer() { if (m_PolyMesh == null) return; m_PolyMesh.UpdateMeshFromData(); if (m_ComponentsCache.SkinnedMeshRenderer != null && skinMeshRef != null) UpdateSkinMesh(); if (mode == Mode.Mesh) { if (m_ComponentsCache.MeshFilter != null) { #if UNITY_EDITOR if (!Application.isPlaying) Undo.RecordObject(m_ComponentsCache.MeshFilter, "Assign new mesh to MeshFilter"); #endif m_ComponentsCache.MeshFilter.sharedMesh = m_PolyMesh.ToUnityMesh(); } SetAdditionalVertexStreamsOnRenderer(null); } else { if (!CanApplyAdditionalVertexStreams()) { if (hasAppliedAdditionalVertexStreams) RemoveAdditionalVertexStreams(); Debug.LogWarning(string.Format(Styles.k_VertexMismatchStringFormat, gameObject.name), this); return; } SetAdditionalVertexStreamsOnRenderer(m_PolyMesh.ToUnityMesh()); } } /// /// Checks if all conditions are met to apply Additional Vertex Streams. /// /// internal bool CanApplyAdditionalVertexStreams() { // If "Use additional vertex streams is enabled in Preferences." if (mode == Mode.AdditionalVertexStream) { if (m_ComponentsCache.MeshFilter != null && m_ComponentsCache.MeshFilter.sharedMesh != null) { if (m_ComponentsCache.MeshFilter.sharedMesh.vertexCount != polyMesh.vertexCount) return false; } } return true; } /// /// Takes the mesh from the skinmesh in the asset (saved by EditableObject during creation) and apply skinmesh information to the regular stored mesh. /// Then set it to the skinmeshrenderer. /// void UpdateSkinMesh() { Mesh mesh = skinMeshRef; Mesh generatedMesh = m_PolyMesh.ToUnityMesh(); generatedMesh.boneWeights = mesh.boneWeights; generatedMesh.bindposes = mesh.bindposes; #if UNITY_EDITOR if (!Application.isPlaying) Undo.RecordObject(m_ComponentsCache.SkinnedMeshRenderer, "Assign new mesh to SkinnedMeshRenderer"); #endif m_ComponentsCache.SkinnedMeshRenderer.sharedMesh = generatedMesh; } /// /// Set the Additional Vertex Streams on the current MeshRenderer. /// /// void SetAdditionalVertexStreamsOnRenderer(Mesh mesh) { if (m_ComponentsCache.MeshRenderer != null) m_ComponentsCache.MeshRenderer.additionalVertexStreams = mesh; } internal void RemoveAdditionalVertexStreams() { SetAdditionalVertexStreamsOnRenderer(null); } void UpdateMode(Mode newMode) { // SkinnedMesh can only work in baked mode. // It doesn't support Additional Vertex Streams. if (type == ObjectType.SkinnedMesh && m_Mode != Mode.Mesh) { m_Mode = Mode.Mesh; return; } m_Mode = newMode; if (mode == Mode.AdditionalVertexStream) { if (m_ComponentsCache.MeshFilter != null) { #if UNITY_EDITOR if (!Application.isPlaying) Undo.RecordObject(m_ComponentsCache.MeshFilter, "Assign new mesh to MeshFilter"); #endif m_ComponentsCache.MeshFilter.sharedMesh = m_OriginalMeshObject; } SetMesh(m_PolyMesh.ToUnityMesh()); } else if (mode == Mode.Mesh) { SetMesh(m_PolyMesh.ToUnityMesh()); } } void OnEnable() { m_Initialized = false; if (!isInitialized) Initialize(); } void OnDestroy() { // Time.frameCount is zero when loading scenes in the Editor. It's the only way I could figure to // differentiate between OnDestroy invoked from user delete & editor scene loading. if (Application.isEditor && !Application.isPlaying && Time.frameCount > 0) { // Re-assign source mesh only if it has changed. // Likely to happen with ProBuilder. if (type == ObjectType.Mesh && componentsCache.MeshFilter !=null && sourceMesh == componentsCache.MeshFilter.sharedMesh) return; SetMesh(sourceMesh); } } } }