using UnityEngine; using System.Collections.Generic; using System.Linq; using UnityEngine.Polybrush; namespace UnityEditor.Polybrush { /// /// Vertex painter brush mode. /// internal class BrushModePaint : BrushModeMesh { [System.Serializable] struct VertexColorsPaintInfo { /// /// Copy of colors array loaded from active mesh. /// public Color32[] OriginalColors; /// /// Colors array used when applying brush. /// public Color32[] TargetColors; /// /// Colors array used when erasing color. /// Only used in "brush" mode. "Flood" and "Fill" modes will use white color. /// public Color32[] EraseColors; /// /// Current active colors applied on active mesh. /// public Color32[] Colors; /// /// Refresh this instance with new informations based on a given mesh vertex colors. /// /// Vertex colors array from a given mesh. It'll be used to initialize every fields of this struct. public void Build(Color32[] baseColors) { OriginalColors = baseColors; Colors = new Color32[baseColors.Length]; TargetColors = new Color32[baseColors.Length]; EraseColors = new Color32[baseColors.Length]; } /// /// Refresh brush fields TargetColors and EraseColors based on a given color. /// /// New colors to apply in TargetColors and EraseColors fields. /// Brush strength to apply on . /// Selected channels on which we will apply . public void RebuildColorTargets(Color color, float strength, ColorMask mask) { if (OriginalColors == null || TargetColors == null || OriginalColors.Length != TargetColors.Length) return; for (int i = 0; i < OriginalColors.Length; i++) { TargetColors[i] = Util.Lerp(OriginalColors[i], color, mask, strength); EraseColors[i] = Util.Lerp(OriginalColors[i], s_WhiteColor, mask, strength); } } } class EditableObjectData { public VertexColorsPaintInfo MeshVertexColors; public bool LikelySupportsVertexColors; // used for fill mode public Dictionary> TriangleLookup; } // how many applications it should take to reach the full strength const float k_StrengthModifier = 1f/8f; static readonly Color32 s_WhiteColor = new Color32(255, 255, 255, 255); [SerializeField] internal PaintMode paintMode = PaintMode.Brush; Dictionary m_EditableObjectsData = new Dictionary(); [SerializeField] Color32 m_BrushColor = Color.green; // The current color palette. [SerializeField] ColorPalette m_ColorPalette = null; internal ColorMask mask = new ColorMask(true, true, true, true); ColorPalette[] m_AvailablePalettes = null; string[] m_AvailablePalettesAsString = null; int m_CurrentPaletteIndex = -1; // temp vars PolyEdge[] m_FillModeEdges = new PolyEdge[3]; List m_FillModeAdjacentTriangles = null; internal GUIContent[] modeIcons = new GUIContent[] { new GUIContent("Brush", "Brush" ), new GUIContent("Fill", "Fill" ), new GUIContent("Flood", "Flood" ) }; internal ColorPalette colorPalette { get { if(m_ColorPalette == null) colorPalette = PolyEditorUtility.GetFirstOrNew(); return m_ColorPalette; } set { m_ColorPalette = value; } } internal override bool SetDefaultSettings() { RefreshAvailablePalettes(); ColorPalette defaultPalette = m_AvailablePalettes.FirstOrDefault(x => x.name.Contains("Default")); if (defaultPalette == null) { return false; } SetColorPalette(defaultPalette); //other settings paintMode = PaintMode.Brush; SetBrushColor(Color.red, 1f); mask = new ColorMask(true, true, true, true); return true; } // An Editor for the colorPalette. ColorPaletteEditor m_ColorPaletteEditor = null; ColorPaletteEditor colorPaletteEditor { get { if(m_ColorPaletteEditor == null || m_ColorPaletteEditor.target != colorPalette) { m_ColorPaletteEditor = (ColorPaletteEditor) Editor.CreateEditor(colorPalette); m_ColorPaletteEditor.hideFlags = HideFlags.HideAndDontSave; } return m_ColorPaletteEditor; } } /// /// The message that will accompany Undo commands for this brush. Undo/Redo is handled by PolyEditor. /// internal override string UndoMessage { get { return "Paint Brush"; } } protected override string ModeSettingsHeader { get { return "Color Paint Settings"; } } protected override string DocsLink { get { return PrefUtility.documentationColorBrushLink; } } internal override void OnEnable() { base.OnEnable(); RefreshAvailablePalettes(); m_BrushColor = colorPalette.current; } internal override void OnDisable() { base.OnDisable(); if(m_ColorPaletteEditor != null) Object.DestroyImmediate(m_ColorPaletteEditor); } /// /// Inspector GUI shown in the Editor window. Base class shows BrushSettings by default /// /// Current brush settings internal override void DrawGUI(BrushSettings brushSettings) { base.DrawGUI(brushSettings); using (new GUILayout.HorizontalScope()) { if (colorPalette == null) RefreshAvailablePalettes(); EditorGUI.BeginChangeCheck(); m_CurrentPaletteIndex = EditorGUILayout.Popup(m_CurrentPaletteIndex, m_AvailablePalettesAsString); if (EditorGUI.EndChangeCheck()) { if (m_CurrentPaletteIndex >= m_AvailablePalettes.Length) SetColorPalette(ColorPaletteEditor.AddNew()); else SetColorPalette(m_AvailablePalettes[m_CurrentPaletteIndex]); } paintMode = (PaintMode)GUILayout.Toolbar((int)paintMode, modeIcons); } bool likelySupportsVertexColors = m_EditableObjectsData.Count == 0; foreach(var kvp in m_EditableObjectsData) likelySupportsVertexColors |= kvp.Value.LikelySupportsVertexColors; if(!likelySupportsVertexColors) EditorGUILayout.HelpBox("It doesn't look like any of the materials on this object support vertex colors!", MessageType.Warning); colorPaletteEditor.onSelectIndex = (color) => { SetBrushColor(color, brushSettings.strength); }; colorPaletteEditor.onSaveAs = SetColorPalette; mask = PolyGUILayout.ColorMaskField("Color Mask", mask); colorPaletteEditor.OnInspectorGUI(); } internal void SetBrushColor(Color color, float strength) { m_BrushColor = color; foreach(var kvp in m_EditableObjectsData) kvp.Value.MeshVertexColors.RebuildColorTargets(color, strength, mask); } internal void SetColorPalette(ColorPalette palette) { colorPalette = palette; m_BrushColor = colorPalette.current; RefreshAvailablePalettes(); } internal override void OnBrushSettingsChanged(BrushTarget target, BrushSettings settings) { base.OnBrushSettingsChanged(target, settings); foreach(var kvp in m_EditableObjectsData) kvp.Value.MeshVertexColors.RebuildColorTargets(m_BrushColor, settings.strength, mask); } // Called when the mouse begins hovering an editable object. internal override void OnBrushEnter(EditableObject target, BrushSettings settings) { base.OnBrushEnter(target, settings); if(target.graphicsMesh == null) return; EditableObjectData data; if(!m_EditableObjectsData.TryGetValue(target, out data)) { data = new EditableObjectData(); m_EditableObjectsData.Add(target, data); } RebuildCaches(target, settings); data.TriangleLookup = PolyMeshUtility.GetAdjacentTriangles(target.editMesh); MeshRenderer mr = target.gameObjectAttached.GetComponent(); if(mr != null && mr.sharedMaterials != null) data.LikelySupportsVertexColors = mr.sharedMaterials.Any(x => x != null && x.shader != null && PolyShaderUtil.SupportsVertexColors(x.shader)); else data.LikelySupportsVertexColors = false; } // Called whenever the brush is moved. Note that @target may have a null editableObject. internal override void OnBrushMove(BrushTarget target, BrushSettings settings) { base.OnBrushMove(target, settings); if(!Util.IsValid(target) || !m_EditableObjectsData.ContainsKey(target.editableObject)) return; bool invert = settings.isUserHoldingControl; PolyMesh mesh = target.editableObject.editMesh; int vertexCount = mesh.vertexCount; float[] weights = target.GetAllWeights(); var data = m_EditableObjectsData[target.editableObject]; var vertexColorInfo = data.MeshVertexColors; switch(paintMode) { case PaintMode.Flood: for(int i = 0; i < vertexCount; i++) vertexColorInfo.Colors[i] = invert? s_WhiteColor : vertexColorInfo.TargetColors[i]; break; case PaintMode.Fill: System.Array.Copy(vertexColorInfo.OriginalColors, vertexColorInfo.Colors, vertexCount); int[] indices = target.editableObject.editMesh.GetTriangles(); int index = 0; foreach(PolyRaycastHit hit in target.raycastHits) { if(hit.triangle > -1) { index = hit.triangle * 3; vertexColorInfo.Colors[indices[index + 0]] = invert ? s_WhiteColor : vertexColorInfo.TargetColors[indices[index + 0]]; vertexColorInfo.Colors[indices[index + 1]] = invert ? s_WhiteColor : vertexColorInfo.TargetColors[indices[index + 1]]; vertexColorInfo.Colors[indices[index + 2]] = invert ? s_WhiteColor : vertexColorInfo.TargetColors[indices[index + 2]]; m_FillModeEdges[0].x = indices[index+0]; m_FillModeEdges[0].y = indices[index+1]; m_FillModeEdges[1].x = indices[index+1]; m_FillModeEdges[1].y = indices[index+2]; m_FillModeEdges[2].x = indices[index+2]; m_FillModeEdges[2].y = indices[index+0]; for(int i = 0; i < 3; i++) { if(data.TriangleLookup.TryGetValue(m_FillModeEdges[i], out m_FillModeAdjacentTriangles)) { for(int n = 0; n < m_FillModeAdjacentTriangles.Count; n++) { index = m_FillModeAdjacentTriangles[n] * 3; vertexColorInfo.Colors[indices[index + 0]] = invert ? s_WhiteColor : vertexColorInfo.TargetColors[indices[index + 0]]; vertexColorInfo.Colors[indices[index + 1]] = invert ? s_WhiteColor : vertexColorInfo.TargetColors[indices[index + 1]]; vertexColorInfo.Colors[indices[index + 2]] = invert ? s_WhiteColor : vertexColorInfo.TargetColors[indices[index + 2]]; } } } } } break; default: { for (int i = 0; i < vertexCount; i++) { vertexColorInfo.Colors[i] = Util.Lerp(vertexColorInfo.OriginalColors[i], invert ? vertexColorInfo.EraseColors[i] : vertexColorInfo.TargetColors[i], mask, weights[i]); } break; } } target.editableObject.editMesh.colors = vertexColorInfo.Colors; target.editableObject.ApplyMeshAttributes(MeshChannel.Color); } // Called when the mouse exits hovering an editable object. internal override void OnBrushExit(EditableObject target) { base.OnBrushExit(target); var data = m_EditableObjectsData[target]; if(target.editMesh != null) { target.editMesh.colors = data.MeshVertexColors.OriginalColors; target.ApplyMeshAttributes(MeshChannel.Color); } data.LikelySupportsVertexColors = true; m_EditableObjectsData.Remove(target); } // Called every time the brush should apply itself to a valid target. Default is on mouse move. internal override void OnBrushApply(BrushTarget target, BrushSettings settings) { var vertexColorInfo = m_EditableObjectsData[target.editableObject].MeshVertexColors; System.Array.Copy(vertexColorInfo.Colors, vertexColorInfo.OriginalColors, vertexColorInfo.Colors.Length); target.editableObject.editMesh.colors = vertexColorInfo.OriginalColors; target.editableObject.modifiedChannels |= MeshChannel.Color; base.OnBrushApply(target, settings); } /// /// set mesh colors back to their original state before registering for undo /// /// Target object of the brush internal override void RegisterUndo(BrushTarget brushTarget) { brushTarget.editableObject.editMesh.colors = m_EditableObjectsData[brushTarget.editableObject].MeshVertexColors.OriginalColors; brushTarget.editableObject.ApplyMeshAttributes(MeshChannel.Color); base.RegisterUndo(brushTarget); } internal override void DrawGizmos(BrushTarget target, BrushSettings settings) { if(Util.IsValid(target) && paintMode == PaintMode.Fill) { Vector3[] vertices = target.editableObject.editMesh.vertices; int[] indices = target.editableObject.editMesh.GetTriangles(); int index = 0; using(new Handles.DrawingScope(target.transform.localToWorldMatrix)) { var data = m_EditableObjectsData[target.editableObject]; foreach (PolyRaycastHit hit in target.raycastHits) { if (hit.triangle > -1) { Handles.color = data.MeshVertexColors.TargetColors[indices[index]]; index = hit.triangle * 3; Handles.DrawLine(vertices[indices[index + 0]] + hit.normal * .1f, vertices[indices[index + 1]] + hit.normal * .1f); Handles.DrawLine(vertices[indices[index + 1]] + hit.normal * .1f, vertices[indices[index + 2]] + hit.normal * .1f); Handles.DrawLine(vertices[indices[index + 2]] + hit.normal * .1f, vertices[indices[index + 0]] + hit.normal * .1f); m_FillModeEdges[0].x = indices[index + 0]; m_FillModeEdges[0].y = indices[index + 1]; m_FillModeEdges[1].x = indices[index + 1]; m_FillModeEdges[1].y = indices[index + 2]; m_FillModeEdges[2].x = indices[index + 2]; m_FillModeEdges[2].y = indices[index + 0]; for (int i = 0; i < 3; i++) { if (data.TriangleLookup.TryGetValue(m_FillModeEdges[i], out m_FillModeAdjacentTriangles)) { for (int n = 0; n < m_FillModeAdjacentTriangles.Count; n++) { index = m_FillModeAdjacentTriangles[n] * 3; Handles.DrawLine(vertices[indices[index + 0]] + hit.normal * .1f, vertices[indices[index + 1]] + hit.normal * .1f); Handles.DrawLine(vertices[indices[index + 1]] + hit.normal * .1f, vertices[indices[index + 2]] + hit.normal * .1f); Handles.DrawLine(vertices[indices[index + 2]] + hit.normal * .1f, vertices[indices[index + 0]] + hit.normal * .1f); } } } } } } } else { base.DrawGizmos(target, settings); } } void RefreshAvailablePalettes() { m_AvailablePalettes = PolyEditorUtility.GetAll().ToArray(); if (m_AvailablePalettes.Length < 1) colorPalette = PolyEditorUtility.GetFirstOrNew(); m_AvailablePalettesAsString = m_AvailablePalettes.Select(x => x.name).ToArray(); ArrayUtility.Add(ref m_AvailablePalettesAsString, string.Empty); ArrayUtility.Add(ref m_AvailablePalettesAsString, "Add Palette..."); m_CurrentPaletteIndex = System.Array.IndexOf(m_AvailablePalettes, colorPalette); } void RebuildCaches(EditableObject target, BrushSettings settings) { PolyMesh m = target.editMesh; int vertexCount = m.vertexCount; Color32[] newBaseColors = null; if(m.colors != null && m.colors.Length == vertexCount) newBaseColors = Util.Duplicate(m.colors); else newBaseColors = Util.Fill(x => { return Color.white; }, vertexCount); EditableObjectData data; if(m_EditableObjectsData.TryGetValue(target, out data)) { data.MeshVertexColors.Build(newBaseColors); data.MeshVertexColors.RebuildColorTargets(m_BrushColor, settings.strength, mask); } } } }