using UnityEngine; using System.IO; using System.Linq; using System.Collections.Generic; using UnityEngine.TerrainTools; using UnityEditor.ShortcutManagement; using UnityEditorInternal; namespace UnityEditor.TerrainTools { //[FilePathAttribute("Library/TerrainTools/PaintTexture", FilePathAttribute.Location.ProjectFolder)] internal class PaintTextureToolOvl : TerrainToolsPaintTool { #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.SelectPaintToolWithOverlays(); // 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 public override string OnIcon => "TerrainOverlays/PaintMaterials_On.png"; public override string OffIcon => "TerrainOverlays/PaintMaterials.png"; private IBrushUIGroup commonUI { get { if (m_commonUI == null) { m_commonUI = new DefaultBrushUIGroup( "PaintTexture", UpdateAnalyticParameters, DefaultBrushUIGroup.Feature.All, new DefaultBrushUIGroup.FeatureDefaults { Strength = 0.71f } ); m_commonUI.OnEnterToolMode(); } return m_commonUI; } } const string k_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 = true; 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; } internal void SetSelectedTerrainLayer(TerrainLayer terrainLayer) { m_SelectedTerrainLayer = terrainLayer; } /// /// Allows overriding for unit testing purposes /// /// internal void ChangeCommonUI(IBrushUIGroup uiGroup) { m_commonUI = uiGroup; } public override int IconIndex { get { return (int) MaterialIndex.PaintTexture; } } public override TerrainCategory Category { get { return TerrainCategory.Materials; } } public override string GetName() { return k_toolName; } public override string GetDescription() { return Styles.description.text; } public override bool HasToolSettings => true; public override bool HasBrushFilters => true; public override bool HasBrushMask => true; public override bool HasBrushAttributes => true; 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/Icons/LayersEyedropper.png", typeof(Texture2D)); #endif } 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) { SelectEyedroppedLayer(terrain, splatOffset); break; } else if (pixel.g > .5f) { SelectEyedroppedLayer(terrain, 1 + splatOffset); break; } else if (pixel.b > .5f) { SelectEyedroppedLayer(terrain, 2 + splatOffset); break; } else if (pixel.a > .5f) { SelectEyedroppedLayer(terrain, 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.GenerateAndSetFilterRT(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; } void SelectEyedroppedLayer(Terrain terrain, int offset) { TerrainLayer[] layers = terrain.terrainData.terrainLayers; if (layers.Length > offset) { m_SelectedTerrainLayer = layers[offset]; m_LayerList.index = offset; } } 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; } // Only render preview if this is a repaint. losing performance if we do if (Event.current.type == EventType.Repaint) { Texture brushTexture = editContext.brushTexture; using (IBrushRenderPreviewUnderCursor brushRender = new BrushRenderPreviewUIGroupUnderCursor(commonUI, "PaintTextureTool", brushTexture)) { if (brushRender.CalculateBrushTransform(out BrushTransform brushTransform)) { RenderTexture tmpRT = RenderTexture.active; Rect brushBounds = brushTransform.GetBrushXYBounds(); PaintContext heightmapContext = brushRender.AcquireHeightmap(false, brushBounds, 1); var previewMaterial = Utility.GetDefaultPreviewMaterial(commonUI.hasEnabledFilters); var texelCtx = Utility.CollectTexelValidity(heightmapContext.originTerrain, brushTransform.GetBrushXYBounds()); Utility.SetupMaterialForPaintingWithTexelValidityContext(heightmapContext, texelCtx, brushTransform, previewMaterial); var filterRT = RTUtils.GetTempHandle(heightmapContext.sourceRenderTexture.width, heightmapContext.sourceRenderTexture.height, 0, FilterUtility.defaultFormat); Utility.GenerateAndSetFilterRT(commonUI, heightmapContext.sourceRenderTexture, filterRT, previewMaterial); brushRender.RenderBrushPreview(heightmapContext, TerrainBrushPreviewMode.SourceRenderTexture, brushTransform, previewMaterial, 0); texelCtx.Cleanup(); RTUtils.Release(filterRT); brushRender.Release(heightmapContext); } } } // update brush UI group commonUI.OnSceneGUI(terrain, editContext); } public override void OnToolSettingsGUI(Terrain terrain, IOnInspectorGUI editContext, bool overlays) { TextureToolSettingsGUI(terrain, editContext, true); } private void TextureToolSettingsGUI(Terrain terrain, IOnInspectorGUI editContext, bool overlays) { m_TargetStrength = EditorGUILayout.Slider(Styles.targetStrengthTxt, m_TargetStrength, 0.0f, 1.0f); if (m_TemplateMaterialEditor == null) m_TemplateMaterialEditor = Editor.CreateEditor(terrain.materialTemplate); // fix - 1306604 #if UNITY_2019_2_OR_NEWER // Material GUI if (!overlays) { m_ShowMaterialEditor = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.materialControls, m_ShowMaterialEditor); if (m_ShowMaterialEditor) { Editor.DrawFoldoutInspector(terrain.materialTemplate, ref m_TemplateMaterialEditor); #if UNITY_2021_2_OR_NEWER TerrainInspectorUtility.TerrainShaderValidationGUI(terrain.materialTemplate); #endif EditorGUILayout.Space(); } } #endif // Layers GUI m_ShowLayerInspector = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.layerControls, m_ShowLayerInspector); if (m_ShowLayerInspector) { LayersGUI(terrain, editContext); #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 } } public override void OnInspectorGUI(Terrain terrain, IOnInspectorGUI editContext) { EditorGUI.BeginChangeCheck(); commonUI.OnInspectorGUI(terrain, editContext); TextureToolSettingsGUI(terrain, editContext, false); if (EditorGUI.EndChangeCheck()) { TerrainToolsAnalytics.OnParameterChange(); } } private static class Styles { public static readonly GUIContent description = EditorGUIUtility.TrTextContent("Applies tiled Textures onto the Terrain.\n\n" + "Hold Shift + A + Click to sample layers 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 RevertPaletteBtn = EditorGUIUtility.TrTextContent("Revert", "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 or create 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(Terrain terrain, IOnInspectorGUI editContext) { if (terrain != null && terrain.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.RevertPaletteBtn)) { 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; } if (!terrain.terrainData.terrainLayers.Equals(m_PaletteLayers)) { RemoveEmptyLayers(terrain); UpdateLayerPalette(terrain); m_SelectedTerrain = terrain; } 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; AddLayerElement(tempLayer); editContext.Repaint(); } 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(); GUIUtility.ExitGUI(); } if (GUILayout.Button(Styles.RemoveLayersBtn, GUILayout.Height(20)) && EditorUtility.DisplayDialog("Error", "Splatmap data changed by these layers will be lost.", "OK", "Cancel")) { RemoveSelectedLayerElements(); m_ToggleAllElements = false; GUIUtility.ExitGUI(); } 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] = new 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) { 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)) { m_SelectedTerrainLayer = layer; return; } Layer newLayer = new Layer(); 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 existingLayers = m_PaletteLayers.Select(l => l.AssignedLayer).ToList(); if (existingLayers.Count > 0 && existingLayers.Contains(layer)) { m_LayerList.index = existingLayers.IndexOf(layer); return true; } return false; } void CreateNewLayerWithTexture(Texture2D texture) { Layer newLayer = new Layer(); 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 RemoveEmptyLayers(Terrain terrain) { List nonNullLayers = new List(); foreach (TerrainLayer layer in terrain.terrainData.terrainLayers) { if (layer != null) { nonNullLayers.Add(layer); } } terrain.terrainData.terrainLayers = nonNullLayers.ToArray(); } 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; int index = 0; foreach (TerrainLayer layer in terrain.terrainData.terrainLayers) { if (layer != null) { Layer paletteLayer = new Layer(); paletteLayer.AssignedLayer = layer; paletteLayer.IsSelected = selectedList.ElementAtOrDefault(index); m_PaletteLayers.Add(paletteLayer); if (layer == m_SelectedTerrainLayer) m_LayerList.index = index; index++; } } if (m_LayerList.index == -1) { m_SelectedTerrainLayer = null; } } 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 = new Layer(); 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; } } //Analytics Setup 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}, }; } }