using UnityEngine; using System.IO; using System.Linq; using System.Collections.Generic; using UnityEngine.Experimental.TerrainAPI; using UnityEditor.ShortcutManagement; using UnityEditorInternal; namespace UnityEditor.Experimental.TerrainAPI { //[FilePathAttribute("Library/TerrainTools/PaintTexture", FilePathAttribute.Location.ProjectFolder)] public class PaintTextureTool : TerrainPaintTool { #if UNITY_2019_1_OR_NEWER [Shortcut("Terrain/Select Paint Texture Tool", typeof(TerrainToolShortcutContext), KeyCode.F2)] // tells shortcut manager what to call the shortcut and what to pass as args static void SelectShortcut(ShortcutArguments args) { TerrainToolShortcutContext context = (TerrainToolShortcutContext)args.context; // gets interface to modify state of TerrainTools context.SelectPaintTool(); // set active tool TerrainToolsAnalytics.OnShortcutKeyRelease("Select Paint Texture Tool"); } [ClutchShortcut("Terrain/Layer Eyedropper", KeyCode.A, ShortcutModifiers.Shift)] static void LayerShortcut(ShortcutArguments args) { m_EyedropperSelected = args.stage == ShortcutStage.Begin ? true : false; if (m_EyedropperSelected && m_PaintToolSelected) { Cursor.SetCursor(m_CursorTexture, m_CursorOffset, CursorMode.Auto); } else { Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto); TerrainToolsAnalytics.OnShortcutKeyRelease("Layer Eyedropper"); } } #endif [SerializeField] IBrushUIGroup m_commonUI; private IBrushUIGroup commonUI { get { if( m_commonUI == null ) { m_commonUI = new DefaultBrushUIGroup("PaintTexture", UpdateAnalyticParameters); m_commonUI.OnEnterToolMode(); } return m_commonUI; } } const string toolName = "Paint Texture"; const int kMaxLayerHD = 8; const int kMaxNoLimit = 20; [SerializeField] TerrainPalette m_SelectedLayerPalette; ReorderableList m_LayerList; List m_PaletteLayers = new List(); Texture2D m_layerTexture; Vector2 m_ScrollPos; TerrainLayer m_PickedLayer; [SerializeField] string m_LayerName = "NewLayer"; int m_objPickerWindowID = -1; int m_layerPickerWindowID = -1; int m_MaxLayerCount; [SerializeField] float m_TargetStrength = 1.0f; [SerializeField] Terrain m_SelectedTerrain = null; TerrainLayer m_SelectedTerrainLayer = null; [SerializeField] bool m_ToggleAllElements = false; #if UNITY_2019_2_OR_NEWER Editor m_TemplateMaterialEditor = null; Editor m_SelectedTerrainLayerInspector = null; [SerializeField] bool m_ShowMaterialEditor = false; [SerializeField] bool m_ShowLayerProperties = false; bool m_LayerRepaintFlag = false; #endif #if UNITY_2019_1_OR_NEWER static bool m_EyedropperSelected = false; static Texture2D m_CursorTexture; static Vector2 m_CursorOffset = new Vector2(0, 30); static bool m_PaintToolSelected = false; #endif [SerializeField] bool m_ShowLayerInspector = false; #region Resources Material m_Material = null; Material GetPaintMaterial() { if (m_Material == null) m_Material = new Material(Shader.Find("PaintTextureAdvance")); return m_Material; } Material m_BlendMat = null; Material GetBlendMaterial() { if (m_BlendMat == null) { m_BlendMat = new Material(Shader.Find("Hidden/TerrainTools/BlendModes")); } return m_BlendMat; } Material m_BrushPreviewMat = null; Material GetBrushPreviewMaterial() { if (m_BrushPreviewMat == null) { m_BrushPreviewMat = new Material(Shader.Find("Hidden/TerrainEngine/PaintMaterialBrushPreview")); } return m_BrushPreviewMat; } #endregion public override string GetName() { return toolName; } public override string GetDesc() { return Styles.description.text; } public override void OnEnterToolMode() { base.OnEnterToolMode(); commonUI.OnEnterToolMode(); #if UNITY_2019_1_OR_NEWER m_PaintToolSelected = true; #endif } public override void OnExitToolMode() { base.OnExitToolMode(); commonUI.OnExitToolMode(); #if UNITY_2019_1_OR_NEWER m_PaintToolSelected = false; #endif } public override void OnEnable() { base.OnEnable(); GetAndSetActiveRenderPipelineSettings(); #if UNITY_2019_1_OR_NEWER m_CursorTexture = (Texture2D)AssetDatabase.LoadAssetAtPath("Packages/com.unity.terrain-tools/Editor/Resources/Icons/LayersEyedropper.png", typeof(Texture2D)); #endif } #region Paint public override bool OnPaint(Terrain terrain, IOnPaint editContext) { #if UNITY_2019_1_OR_NEWER if (m_EyedropperSelected && m_LayerList != null) { Texture2D[] splatmaps = terrain.terrainData.alphamapTextures; int splatOffset = 0; foreach(Texture2D splatmap in splatmaps) { Color pixel = splatmap.GetPixelBilinear(editContext.uv.x, editContext.uv.y); if (pixel.r > .5f) { m_SelectedTerrainLayer = terrain.terrainData.terrainLayers[0 + splatOffset]; m_LayerList.index = 0 + splatOffset; break; } else if (pixel.g > .5f) { m_SelectedTerrainLayer = terrain.terrainData.terrainLayers[1 + splatOffset]; m_LayerList.index = 1 + splatOffset; break; } else if (pixel.b > .5f) { m_SelectedTerrainLayer = terrain.terrainData.terrainLayers[2 + splatOffset]; m_LayerList.index = 2 + splatOffset; break; } else if (pixel.a > .5f) { m_SelectedTerrainLayer = terrain.terrainData.terrainLayers[3 + splatOffset]; m_LayerList.index = 3 + splatOffset; break; } splatOffset += 4; } return true; } #endif commonUI.OnPaint(terrain, editContext); if (commonUI.allowPaint) { Texture brushTexture = editContext.brushTexture; using (IBrushRenderUnderCursor brushRender = new BrushRenderUIGroupUnderCursor(commonUI, "PaintTextureTool", brushTexture)) { if (brushRender.CalculateBrushTransform(out BrushTransform brushTransform)) { Rect brushRect = brushTransform.GetBrushXYBounds(); PaintContext paintContext = brushRender.AcquireTexture(true, brushRect, m_SelectedTerrainLayer); if (paintContext != null) { PaintContext heightContext = brushRender.AcquireHeightmap(false, brushRect); // Evaluate the brush mask filter stack Material mat = GetPaintMaterial(); var brushMask = RTUtils.GetTempHandle(heightContext.sourceRenderTexture.width, heightContext.sourceRenderTexture.height, 0, FilterUtility.defaultFormat); Utility.SetFilterRT(commonUI, heightContext.sourceRenderTexture, brushMask, mat); // apply brush float targetAlpha = m_TargetStrength; float s = commonUI.InvertStrength ? -commonUI.brushStrength : commonUI.brushStrength; Vector4 brushParams = new Vector4(s, targetAlpha, 0.0f, 0.0f); mat.SetTexture("_BrushTex", editContext.brushTexture); mat.SetVector("_BrushParams", brushParams); brushRender.SetupTerrainToolMaterialProperties(paintContext, brushTransform, mat); brushRender.RenderBrush(paintContext, mat, 0); RTUtils.Release(brushMask); } } } } return true; } #endregion #region GUI public override void OnSceneGUI(Terrain terrain, IOnSceneGUI editContext) { commonUI.OnSceneGUI2D(terrain, editContext); #if UNITY_2019_1_OR_NEWER // Don't paint if eyedropper is selected if (m_EyedropperSelected) { EditorGUIUtility.AddCursorRect(new Rect(0, 0, Screen.width, Screen.height), MouseCursor.CustomCursor); editContext.Repaint(); return; } #endif // We're only doing painting operations, early out if it's not a repaint if (!editContext.hitValidTerrain && !commonUI.isInUse) { return; } // update brush UI group commonUI.OnSceneGUI(terrain, editContext); // Don't render preview if this isn't a repaint. losing performance if we do if (Event.current.type != EventType.Repaint) { return; } Texture brushTexture = editContext.brushTexture; using (IBrushRenderPreviewUnderCursor brushRender = new BrushRenderPreviewUIGroupUnderCursor(commonUI, "PaintTextureTool", brushTexture)) { if (brushRender.CalculateBrushTransform(out BrushTransform brushTransform)) //cute. { RenderTexture tmpRT = RenderTexture.active; Rect brushBounds = brushTransform.GetBrushXYBounds(); PaintContext heightmapContext = brushRender.AcquireHeightmap(false, brushBounds, 1); Material brushMaterial = GetBrushPreviewMaterial(); var defaultPreviewMaterial = Utility.GetDefaultPreviewMaterial(); if (commonUI.brushMaskFilterStack.filters.Count > 0) { // Evaluate the brush mask filter stack Vector3 brushPos = new Vector3( commonUI.raycastHitUnderCursor.point.x, 0, commonUI.raycastHitUnderCursor.point.z ); var brushMask = RTUtils.GetTempHandle(heightmapContext.sourceRenderTexture.width, heightmapContext.sourceRenderTexture.height, 0, FilterUtility.defaultFormat); commonUI.GetBrushMask(heightmapContext.sourceRenderTexture, brushMask); //Composite the brush texture onto the filter stack result var compRT = RTUtils.GetTempHandle(brushMask.Desc); Material blendMat = GetBlendMaterial(); blendMat.SetTexture("_BlendTex", editContext.brushTexture); blendMat.SetVector("_BlendParams", new Vector4(0.0f, 0.0f, -(commonUI.brushRotation * Mathf.Deg2Rad), 0.0f)); TerrainPaintUtility.SetupTerrainToolMaterialProperties(heightmapContext, brushTransform, blendMat); Graphics.Blit(brushMask, compRT, blendMat, 0); RenderTexture.active = tmpRT; BrushTransform identityBrushTransform = TerrainPaintUtility.CalculateBrushTransform(commonUI.terrainUnderCursor, commonUI.raycastHitUnderCursor.textureCoord, commonUI.brushSize, 0.0f); var texelCtx = Utility.CollectTexelValidity(heightmapContext.originTerrain, identityBrushTransform.GetBrushXYBounds()); Utility.SetupMaterialForPaintingWithTexelValidityContext(heightmapContext, texelCtx, identityBrushTransform, brushMaterial); Utility.SetupMaterialForPaintingWithTexelValidityContext(heightmapContext, texelCtx, identityBrushTransform, defaultPreviewMaterial); TerrainPaintUtilityEditor.DrawBrushPreview(heightmapContext, TerrainPaintUtilityEditor.BrushPreview.SourceRenderTexture, compRT, identityBrushTransform, brushMaterial, 0); RTUtils.Release(compRT); RTUtils.Release(brushMask); texelCtx.Cleanup(); } brushRender.RenderBrushPreview(heightmapContext, TerrainPaintUtilityEditor.BrushPreview.SourceRenderTexture, brushTransform, defaultPreviewMaterial, 0); } } } public override void OnInspectorGUI(Terrain terrain, IOnInspectorGUI editContext) { EditorGUI.BeginChangeCheck(); commonUI.OnInspectorGUI(terrain, editContext); m_TargetStrength = EditorGUILayout.Slider(Styles.targetStrengthTxt, m_TargetStrength, 0.0f, 1.0f); #if UNITY_2019_2_OR_NEWER // Material GUI m_ShowMaterialEditor = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.materialControls, m_ShowMaterialEditor); if (m_ShowMaterialEditor) { Editor.DrawFoldoutInspector(terrain.materialTemplate, ref m_TemplateMaterialEditor); EditorGUILayout.Space(); } #endif // Layers GUI UpdateLayerPalette(terrain); m_ShowLayerInspector = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.layerControls, m_ShowLayerInspector); if (m_ShowLayerInspector) { LayersGUI(); #if UNITY_2019_2_OR_NEWER m_ShowLayerProperties = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.layerProperties, m_ShowLayerProperties); if (m_ShowLayerProperties) { if(!m_LayerRepaintFlag) { TerrainLayerUtility.ShowTerrainLayerGUI(terrain, m_SelectedTerrainLayer, ref m_SelectedTerrainLayerInspector, (m_TemplateMaterialEditor as MaterialEditor)?.customShaderGUI as ITerrainLayerCustomUI); } else { m_LayerRepaintFlag = false; // flag to skip layer property repaint when layer list modified } } #endif } m_SelectedTerrain = terrain; if (EditorGUI.EndChangeCheck()) { TerrainToolsAnalytics.OnParameterChange(); } } #endregion private static class Styles { public static readonly GUIContent description = EditorGUIUtility.TrTextContent("Add material layers to use on the terrain. Left click to paint the selected material layer onto the terrain. \n\nHold Shift and A to pick a material layer from the terrain."); #if UNITY_2019_2_OR_NEWER public static readonly GUIContent materialControls = EditorGUIUtility.TrTextContent("Material"); public static readonly GUIContent layerProperties = EditorGUIUtility.TrTextContent("Layer Properties"); #endif public static readonly GUIContent layerControls = EditorGUIUtility.TrTextContent("Layers"); public static readonly GUIContent PalettePreset = EditorGUIUtility.TrTextContent("Layer Palette Profile", "Select an existing layer palette asset or create a new palette asset from the layer list."); public static readonly GUIContent NewLayer = EditorGUIUtility.TrTextContent("Create New Layer", "Create a new layer with provided name and add to the layer palette."); public static readonly GUIContent CreateLayersBtn = EditorGUIUtility.TrTextContent("Create...", "Create a new layer."); public static readonly GUIContent SavePaletteBtn = EditorGUIUtility.TrTextContent("Save", "Save the current layer list into selected palette asset file on disk."); public static readonly GUIContent SaveAsPaletteBtn = EditorGUIUtility.TrTextContent("Save As", "Save the current palette asset as a new file on disk."); public static readonly GUIContent RefreshPaletteBtn = EditorGUIUtility.TrTextContent("Refresh", "Load selected palette and apply to the layer list."); public static readonly GUIContent RemoveLayersBtn = EditorGUIUtility.TrTextContent("Remove Selected Layers", "Removes layers that are selected within the Layer Palette."); public static readonly GUIContent targetStrengthTxt = EditorGUIUtility.TrTextContent("Target Strength", "Maximum opacity this brush will paint to."); public static readonly string LayersWarning = "The selected terrain doesn't contain any layers. Add layer(s) to paint on the terrain."; } // layer list view const int kElementHeight = 70; const int kElementObjectFieldHeight = 16; const int kElementPadding = 2; const int kElementObjectFieldWidth = 240; const int kElementToggleWidth = 20; const int kElementImageWidth = 64; const int kElementImageHeight = 64; void LayersGUI() { if (m_SelectedTerrain != null && m_SelectedTerrain.terrainData.terrainLayers.Length == 0) { EditorGUILayout.HelpBox(Styles.LayersWarning, MessageType.Warning); } // Layer Palette preset EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(Styles.PalettePreset, GUILayout.Width(EditorGUIUtility.labelWidth - 4.5f)); EditorGUI.BeginChangeCheck(); m_SelectedLayerPalette = (TerrainPalette)EditorGUILayout.ObjectField(m_SelectedLayerPalette, typeof(TerrainPalette), false, GUILayout.MinWidth(130)); if (EditorGUI.EndChangeCheck() && m_SelectedLayerPalette != null) { if (EditorUtility.DisplayDialog("Confirm", "Load palette from selected?", "OK", "Cancel")) { LoadPalette(); } } if (GUILayout.Button(Styles.SavePaletteBtn)) { if (GetPalette()) { m_SelectedLayerPalette.PaletteLayers.Clear(); foreach (var layer in m_PaletteLayers) { m_SelectedLayerPalette.PaletteLayers.Add(layer.AssignedLayer); } AssetDatabase.SaveAssets(); } } if (GUILayout.Button(Styles.SaveAsPaletteBtn)) { CreateNewPalette(); } if (GUILayout.Button(Styles.RefreshPaletteBtn)) { if (GetPalette()) { LoadPalette(); } } EditorGUILayout.EndHorizontal(); // Reorderable list view EditorGUILayout.BeginVertical("Box"); if (m_LayerList == null) { m_LayerList = new ReorderableList(m_PaletteLayers, typeof(Layer), true, true, false, false); m_LayerList.elementHeight = kElementHeight; m_LayerList.drawHeaderCallback = DrawHeader; m_LayerList.drawElementCallback = DrawLayerElement; m_LayerList.onSelectCallback = OnSelectLayerElement; m_LayerList.onReorderCallbackWithDetails = OnReorderLayerElement; } CreateLayersIfNeeded(); m_LayerList.DoLayoutList(); // Layer creation if (Event.current.commandName == "ObjectSelectorClosed" && EditorGUIUtility.GetObjectPickerControlID() == m_layerPickerWindowID) { m_PickedLayer = (TerrainLayer)EditorGUIUtility.GetObjectPickerObject(); } if (m_PickedLayer != null && Event.current.type == EventType.Repaint) { TerrainLayer tempLayer = m_PickedLayer; m_PickedLayer = null; if (!LayerExists(tempLayer)) { AddLayerElement(tempLayer); } } if (Event.current.commandName == "ObjectSelectorClosed" && EditorGUIUtility.GetObjectPickerControlID() == m_objPickerWindowID) { m_layerTexture = (Texture2D)EditorGUIUtility.GetObjectPickerObject(); } if (m_layerTexture != null && Event.current.type == EventType.Repaint) { Texture2D tempTexture = m_layerTexture; m_layerTexture = null; CreateNewLayerWithTexture(tempTexture); } // Control buttons EditorGUILayout.BeginHorizontal(); EditorGUI.BeginDisabledGroup(!CanAddLayerElement()); if (GUILayout.Button("Add Layer", GUILayout.Height(20))) { m_layerPickerWindowID = EditorGUIUtility.GetControlID(FocusType.Passive) + 200; // had to bump this to make it unique EditorGUIUtility.ShowObjectPicker(null, false, "", m_layerPickerWindowID); } EditorGUI.EndDisabledGroup(); if (GUILayout.Button("Remove Layer", GUILayout.Height(20)) && EditorUtility.DisplayDialog("Warning", "Splatmap data changed by this layer will be lost.", "OK", "Cancel")) { RemoveLayerElement(); } if (GUILayout.Button(Styles.RemoveLayersBtn, GUILayout.Height(20)) && EditorUtility.DisplayDialog("Error", "Splatmap data changed by these layers will be lost.", "OK", "Cancel")) { RemoveSelectedLayerElements(); } EditorGUILayout.EndHorizontal(); // Create new layer EditorGUILayout.Space(); EditorGUILayout.BeginHorizontal(); m_LayerName = EditorGUILayout.TextField(Styles.NewLayer, m_LayerName); if (GUILayout.Button(Styles.CreateLayersBtn, GUILayout.Width(85), GUILayout.Height(20))) { m_objPickerWindowID = EditorGUIUtility.GetControlID(FocusType.Passive) + 100; // had to bump this to make it unique EditorGUIUtility.ShowObjectPicker(null, false, "", m_objPickerWindowID); } EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); EditorGUILayout.EndVertical(); } private void CreateLayersIfNeeded() { if( m_SelectedTerrain == null ) { return; } for( int i = 0; i < m_PaletteLayers.Count && i < m_SelectedTerrain.terrainData.terrainLayers.Length; ++i ) { if( m_PaletteLayers[ i ] == null ) { m_PaletteLayers[ i ] = ScriptableObject.CreateInstance< Layer >(); m_PaletteLayers[ i ].AssignedLayer = m_SelectedTerrain.terrainData.terrainLayers[ i ]; } } } void DrawHeader(Rect rect) { var rectToggle = new Rect(rect.x + 16, rect.y, rect.width, rect.height); EditorGUI.BeginChangeCheck(); m_ToggleAllElements = EditorGUI.Toggle(rectToggle, m_ToggleAllElements); if (EditorGUI.EndChangeCheck()) { foreach (var layerElement in m_PaletteLayers) { layerElement.IsSelected = m_ToggleAllElements; } } var rectLabel = new Rect(rectToggle.x + kElementToggleWidth + kElementPadding, rect.y, kElementObjectFieldWidth, kElementToggleWidth); EditorGUI.LabelField(rectLabel, "Layer Palette", EditorStyles.boldLabel); } void DrawLayerElement(Rect rect, int index, bool selected, bool focused) { rect.y = rect.y + kElementPadding; var rectButton= new Rect((rect.x + kElementPadding), rect.y, kElementToggleWidth, kElementToggleWidth); var rectImage = new Rect((rectButton.x + kElementToggleWidth), rect.y, kElementImageWidth, kElementImageHeight); var rectObject = new Rect((rectImage.x + kElementImageWidth + 10), rect.y, kElementObjectFieldWidth, kElementObjectFieldHeight); if (m_PaletteLayers.Count > 0 && m_PaletteLayers.ElementAtOrDefault(index) != null) { m_PaletteLayers[index].IsSelected = EditorGUI.Toggle(rectButton, m_PaletteLayers[index].IsSelected); EditorGUILayout.BeginHorizontal(); List existLayers = m_PaletteLayers.Select(l => l.AssignedLayer).ToList(); TerrainLayer oldLayer = m_PaletteLayers[index].AssignedLayer; Texture2D icon = null; if (m_PaletteLayers[index].AssignedLayer != null) { icon = AssetPreview.GetAssetPreview(m_PaletteLayers[index].AssignedLayer.diffuseTexture); } GUI.Box(rectImage, icon); EditorGUI.BeginChangeCheck(); m_PaletteLayers[index].AssignedLayer = EditorGUI.ObjectField(rectObject, m_PaletteLayers[index].AssignedLayer, typeof(TerrainLayer), false) as TerrainLayer; EditorGUILayout.EndHorizontal(); if (EditorGUI.EndChangeCheck()) { if(m_PaletteLayers[index].AssignedLayer == null) { m_PaletteLayers.RemoveAt(index); TerrainToolboxLayer.RemoveLayerFromTerrain(m_SelectedTerrain.terrainData, index); m_SelectedTerrainLayer = null; } else if (existLayers.Contains(m_PaletteLayers[index].AssignedLayer) && m_PaletteLayers[index].AssignedLayer != oldLayer) { EditorUtility.DisplayDialog("Error", "Layer exists. Please select a different layer.", "OK"); m_PaletteLayers[index].AssignedLayer = oldLayer; } else { if (m_SelectedTerrain.terrainData.terrainLayers.Length < m_PaletteLayers.Count) { TerrainToolboxLayer.AddLayerToTerrain(m_SelectedTerrain.terrainData, m_PaletteLayers[index].AssignedLayer); } else { int layersLength = m_SelectedTerrain.terrainData.terrainLayers.Length; TerrainLayer[] layers = m_SelectedTerrain.terrainData.terrainLayers; layers[index] = m_PaletteLayers[index].AssignedLayer; m_SelectedTerrainLayer = m_PaletteLayers[index].AssignedLayer; m_SelectedTerrain.terrainData.terrainLayers = layers; } } } } } void AddLayerElement(TerrainLayer layer) { if (LayerExists(layer)) return; Layer newLayer = CreateInstance(); newLayer.AssignedLayer = layer; newLayer.IsSelected = m_ToggleAllElements; m_PaletteLayers.Add(newLayer); TerrainToolboxLayer.AddLayerToTerrain(m_SelectedTerrain.terrainData, newLayer.AssignedLayer); m_SelectedTerrainLayer = newLayer.AssignedLayer; m_LayerList.index = m_PaletteLayers.Count - 1; #if UNITY_2019_2_OR_NEWER m_LayerRepaintFlag = true; #endif } bool LayerExists(TerrainLayer layer) { List existLayers = m_PaletteLayers.Select(l => l.AssignedLayer).ToList(); if (existLayers.Count > 0 && existLayers.Contains(layer)) { EditorUtility.DisplayDialog("Error", "Layer exists. Please select a different layer.", "OK"); return true; } else { return false; } } void CreateNewLayerWithTexture(Texture2D texture) { Layer newLayer = ScriptableObject.CreateInstance(); newLayer.AssignedLayer = new TerrainLayer(); newLayer.AssignedLayer.diffuseTexture = texture; m_PaletteLayers.Add(newLayer); m_LayerList.index = m_PaletteLayers.Count - 1; string path = "Assets"; foreach (UnityEngine.Object obj in Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets)) { path = AssetDatabase.GetAssetPath(obj); if (!string.IsNullOrEmpty(path) && File.Exists(path)) { path = Path.GetDirectoryName(path); break; } } newLayer.AssignedLayer.name = m_LayerName; TerrainToolboxLayer.AddLayerToTerrain(m_SelectedTerrain.terrainData, newLayer.AssignedLayer); AssetDatabase.CreateAsset(newLayer.AssignedLayer, AssetDatabase.GenerateUniqueAssetPath($"{path}/{m_LayerName}.terrainlayer")); } void RemoveLayerElement() { if (m_PaletteLayers.ElementAtOrDefault(m_LayerList.index) == null) { return; } m_PaletteLayers.RemoveAt(m_LayerList.index); if (m_SelectedTerrain.terrainData.terrainLayers.Length > m_LayerList.index) { TerrainToolboxLayer.RemoveLayerFromTerrain(m_SelectedTerrain.terrainData, m_LayerList.index); } m_LayerList.index = m_PaletteLayers.Count - 1; if (m_LayerList.index >= 0 && m_LayerList.index < m_PaletteLayers.Count) { m_SelectedTerrainLayer = m_PaletteLayers[m_LayerList.index].AssignedLayer; } else { m_SelectedTerrainLayer = null; } } void RemoveSelectedLayerElements() { for (int i = m_PaletteLayers.Count - 1; i >= 0; i--) { if (m_PaletteLayers[i].IsSelected) { if (m_PaletteLayers[i].AssignedLayer != null) { TerrainToolboxLayer.RemoveLayerFromTerrain(m_SelectedTerrain.terrainData, i); } m_PaletteLayers.RemoveAt(i); } } m_SelectedTerrainLayer = null; AssetDatabase.Refresh(); } void OnSelectLayerElement(ReorderableList list) { if(m_SelectedTerrain.terrainData.terrainLayers.Length > list.index) { m_SelectedTerrainLayer = m_SelectedTerrain.terrainData.terrainLayers[list.index]; } } void OnReorderLayerElement(ReorderableList list, int oldIndex, int newIndex) { TerrainLayer[] layers = m_SelectedTerrain.terrainData.terrainLayers; if(layers[oldIndex] != null) { TerrainLayer temp = layers[oldIndex]; layers[oldIndex] = layers[newIndex]; layers[newIndex] = temp; for (int i = 0; i < m_PaletteLayers.Count; i++) { layers[i] = m_PaletteLayers[i].AssignedLayer; } m_SelectedTerrain.terrainData.SetTerrainLayersRegisterUndo(layers, "Reorder Terrain Layers"); } } bool CanAddLayerElement() { return m_PaletteLayers.Count < m_MaxLayerCount; } void UpdateLayerPalette(Terrain terrain) { if (terrain == null || m_LayerList == null) { return; } bool[] selectedList = new bool[m_PaletteLayers.Count]; for(int i = 0; i < m_PaletteLayers.Count; i++) { selectedList[i] = m_PaletteLayers[i].IsSelected; } m_PaletteLayers.Clear(); m_LayerList.index = -1; List terrainLayers = new List(); int index = 0; foreach (TerrainLayer layer in terrain.terrainData.terrainLayers) { if(layer != null) { Layer paletteLayer = ScriptableObject.CreateInstance(); paletteLayer.AssignedLayer = layer; paletteLayer.IsSelected = selectedList.ElementAtOrDefault(index); m_PaletteLayers.Add(paletteLayer); terrainLayers.Add(layer); if (layer == m_SelectedTerrainLayer) m_LayerList.index = index; index++; } } if(m_LayerList.index == -1) { m_SelectedTerrainLayer = null; } terrain.terrainData.terrainLayers = terrainLayers.ToArray(); } bool GetPalette() { if (m_SelectedLayerPalette == null) { if (EditorUtility.DisplayDialog("Error", "No layer palette found, create a new one?", "OK", "Cancel")) { return CreateNewPalette(); } else { return false; } } return true; } void LoadPalette() { if (!GetPalette()) return; m_PaletteLayers.Clear(); List terrainLayers = new List(); foreach (var layer in m_SelectedLayerPalette.PaletteLayers) { if(layer != null) { Layer newLayer = ScriptableObject.CreateInstance(); newLayer.AssignedLayer = layer; m_PaletteLayers.Add(newLayer); terrainLayers.Add(layer); } } m_SelectedTerrain.terrainData.SetTerrainLayersRegisterUndo(terrainLayers.ToArray(), "Load Palette"); } bool CreateNewPalette() { string filePath = EditorUtility.SaveFilePanelInProject("Create New Palette", "New Layer Palette.asset", "asset", ""); if (string.IsNullOrEmpty(filePath)) { return false; } m_SelectedLayerPalette = CreateInstance(); foreach (var layer in m_PaletteLayers) { m_SelectedLayerPalette.PaletteLayers.Add(layer.AssignedLayer); } AssetDatabase.CreateAsset(m_SelectedLayerPalette, filePath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); return true; } void GetAndSetActiveRenderPipelineSettings() { ToolboxHelper.RenderPipeline m_ActiveRenderPipeline = ToolboxHelper.GetRenderPipeline(); switch (m_ActiveRenderPipeline) { case ToolboxHelper.RenderPipeline.HD: m_MaxLayerCount = kMaxLayerHD; break; case ToolboxHelper.RenderPipeline.LW: m_MaxLayerCount = kMaxNoLimit; break; default: m_MaxLayerCount = kMaxNoLimit; break; } } #region Analytics private TerrainToolsAnalytics.IBrushParameter[] UpdateAnalyticParameters() => new TerrainToolsAnalytics.IBrushParameter[]{ new TerrainToolsAnalytics.BrushParameter{Name = Styles.targetStrengthTxt.text, Value = m_TargetStrength}, new TerrainToolsAnalytics.BrushParameter{Name = "Layers Count", Value = m_PaletteLayers.Count}, }; #endregion } }