using UnityEngine; using System.Collections.Generic; using UnityEngine.Polybrush; using UnityEditor.SettingsManagement; namespace UnityEditor.Polybrush { /// /// Base class for brush modes that move vertices around. Implements an overlay preview. /// internal abstract class BrushModeSculpt : BrushModeMesh { class EditableObjectData { public List BrushNormalOnBeginApply = new List(); public Vector3[] CachedNormals; public HashSet NonManifoldIndices; public EditableObject CacheTarget; public List CacheMaterials; public OverlayRenderer TempComponent; } internal static class Styles { /// /// List built based on PolyDirection enum. /// Make sure to properly convert values between this list and PolyDirection. /// internal static string[] s_BrushDirectionList = new string[] { "Brush Normal", "Vertex Normal", "Global Y Axis", "Global X Axis", "Global Z Axis" }; internal static GUIContent gcDirection = new GUIContent("Direction", "How vertices are moved when the brush is applied. You can explicitly set an axis, or use the vertex normal."); internal static GUIContent gcIgnoreOpenEdges = new GUIContent("Ignore Open Edges", "When on, edges that are not connected on both sides will be ignored by brush strokes."); internal static GUIContent gcBrushNormalIsSticky = new GUIContent("Brush Normal is Sticky", "If enabled, vertices will be moved only on the direction of the brush normal at the time of first application."); } [UserSetting] internal static Pref s_VertexBillboardSize = new Pref("Brush.VertexBillboardSize", 2f, SettingsScope.Project); [UserSettingBlock("General Settings")] static void HandleBrushPreferences(string searchContext) { s_VertexBillboardSize.value = SettingsGUILayout.SettingsSlider(new GUIContent("Vertex Render Size", "The size at which selected vertices will be rendered."), s_VertexBillboardSize, 0f, 10f, searchContext); } Dictionary m_EditableObjectsData = new Dictionary(); protected bool m_LikelyToSupportVertexSculpt = true; internal override string UndoMessage { get { return "Sculpt Vertices"; } } protected override string ModeSettingsHeader { get { return "Sculpt Settings"; } } internal override void OnEnable() { base.OnEnable(); foreach (GameObject go in Selection.gameObjects) { m_LikelyToSupportVertexSculpt = CheckForVertexScluptSupport(go); if (m_LikelyToSupportVertexSculpt) break; } } protected List BrushNormalsOnBeginApply(EditableObject target) { return m_EditableObjectsData[target].BrushNormalOnBeginApply; } protected bool ContainsIndexInNonManifoldIndices(EditableObject target, int index) { return m_EditableObjectsData[target].NonManifoldIndices.Contains(index); } /// /// Check if the sculpt is supported for the current selection. No support for skin meshes /// /// /// bool CheckForVertexScluptSupport(GameObject go) { return !go.GetComponentInChildren(); } internal override void OnBrushEnter(EditableObject target, BrushSettings settings) { base.OnBrushEnter(target, settings); EditableObjectData data; if(!m_EditableObjectsData.TryGetValue(target, out data)) { data = new EditableObjectData(); m_EditableObjectsData.Add(target, data); } data.NonManifoldIndices = PolyMeshUtility.GetNonManifoldIndices(target.editMesh); RefreshVertexSculptSupport(target); } // Called when the mouse exits hovering an editable object. internal override void OnBrushExit(EditableObject target) { base.OnBrushExit(target); if(m_EditableObjectsData.ContainsKey(target)) { DestroyImmediate(m_EditableObjectsData[target].TempComponent); m_EditableObjectsData.Remove(target); } } /// /// Will ensure that the check is not done everytime (inspired from BrushModeTexture script) /// /// void RefreshVertexSculptSupport(EditableObject target) { EditableObjectData data; if(m_EditableObjectsData.TryGetValue(target, out data)) { bool refresh = (data.CacheTarget != null && !data.CacheTarget.Equals(target.gameObjectAttached)) || data.CacheTarget == null; if (data.CacheTarget != null && data.CacheTarget.Equals(target.gameObjectAttached)) refresh = data.CacheMaterials != target.gameObjectAttached.GetMaterials(); if (refresh) { data.CacheTarget = target; data.CacheMaterials = target.gameObjectAttached.GetMaterials(); m_LikelyToSupportVertexSculpt = CheckForVertexScluptSupport(target.gameObjectAttached); } } } internal override void DrawGUI(BrushSettings settings) { base.DrawGUI(settings); if (!m_LikelyToSupportVertexSculpt) EditorGUILayout.HelpBox("Sculpting on skin meshes is not supported.", MessageType.Warning); } /// /// Cache the brush normals and the mesh normals /// /// Target object to cache the brush normals and mesh normals from protected void CacheBrushNormals(BrushTarget target) { EditableObjectData data; if(m_EditableObjectsData.TryGetValue(target.editableObject, out data)) { data.BrushNormalOnBeginApply.Clear(); for(int i = 0; i < target.raycastHits.Count; i++) data.BrushNormalOnBeginApply.Add(target.raycastHits[i].normal); PolyMesh mesh = target.editableObject.editMesh; data.CachedNormals = new Vector3[mesh.vertexCount]; if(mesh.normals != null && mesh.normals.Length == mesh.vertexCount) { System.Array.Copy(mesh.normals, 0, data.CachedNormals, 0, mesh.vertexCount); target.editableObject.modifiedChannels |= MeshChannel.Normal; } } } internal override void OnBrushBeginApply(BrushTarget target, BrushSettings settings) { CacheBrushNormals(target); base.OnBrushBeginApply(target, settings); } internal override void OnBrushFinishApply(BrushTarget target, BrushSettings settings) { base.OnBrushFinishApply(target, settings); if(target!= null && m_EditableObjectsData.ContainsKey(target.editableObject)) m_EditableObjectsData[target.editableObject].BrushNormalOnBeginApply.Clear(); } protected override void CreateTempComponent(EditableObject target) { if(target == null) return; RefreshVertexSculptSupport(target); if (!m_LikelyToSupportVertexSculpt) return; OverlayRenderer ren = target.gameObjectAttached.AddComponent(); ren.hideFlags = HideFlags.DontSave | HideFlags.NotEditable | HideFlags.HideInInspector | HideFlags.HideInHierarchy; ren.SetMesh(target.editMesh); ren.fullColor = s_FullStrengthColor; ren.gradient = s_BrushGradientColor; ren.vertexBillboardSize = s_VertexBillboardSize; EditableObjectData data; if(!m_EditableObjectsData.TryGetValue(target, out data)) { data = new EditableObjectData(); m_EditableObjectsData.Add(target, data); } data.TempComponent = ren; } protected override void UpdateTempComponent(BrushTarget target, BrushSettings settings) { if(!Util.IsValid(target)) return; EditableObjectData data; if(m_EditableObjectsData.TryGetValue(target.editableObject, out data)) { if(data.TempComponent != null) ((OverlayRenderer)data.TempComponent).SetWeights(target.GetAllWeights(), settings.strength); } } protected void UpdateWireframe(BrushTarget target, BrushSettings settings) { if(!Util.IsValid(target)) return; if(m_EditableObjectsData.TryGetValue(target.editableObject, out EditableObjectData data)) { if(data.TempComponent != null) data.TempComponent.OnVerticesMoved(target.editableObject.editMesh); //Might be costly to do that on every wireframe update if(ProBuilderBridge.ProBuilderExists() && target.editableObject.isProBuilderObject) ProBuilderBridge.RefreshEditor(false); } } } }