Rasagar/Library/PackageCache/com.unity.terrain-tools/Editor/TerrainToolbox/TerrainToolboxUtilities.cs
2024-08-26 23:07:20 +03:00

2294 lines
102 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor.SceneManagement;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.TerrainTools;
namespace UnityEditor.TerrainTools
{
[Serializable]
internal class UtilitySettings : ScriptableObject
{
// Terrain Split
public int TileSplit = 2;
public bool AutoUpdateSettings = true;
public string TerrainAssetDir = "Assets/Terrain";
// Layers
public string PalettePath = string.Empty;
public bool ApplyAllTerrains = true;
// Replace splatmap
public Terrain SplatmapTerrain;
public Texture2D SplatmapOld0;
public Texture2D SplatmapNew0;
public Texture2D SplatmapOld1;
public Texture2D SplatmapNew1;
public ImageFormat SelectedFormat = ImageFormat.TGA;
public RotationAdjustment RotationAdjust = RotationAdjustment.Clockwise;
public FlipAdjustment FlipAdjust = FlipAdjustment.Horizontal;
public string SplatFolderPath = "Assets/Splatmaps/";
public bool AdjustAllSplats = false;
// Import heightmap
public Texture2D HeightmapImport;
public int HeightmapResolution;
public float ImportHeightRemapMin;
public float ImportHeightRemapMax;
public Heightmap.Flip HeightmapFlipMode = Heightmap.Flip.None;
// Export heightmaps
public string HeightmapFolderPath = "Assets/Heightmaps/";
public Heightmap.Format HeightFormat = Heightmap.Format.RAW;
public Heightmap.Depth HeightmapDepth = Heightmap.Depth.Bit16;
public ToolboxHelper.ByteOrder HeightmapByteOrder = ToolboxHelper.ByteOrder.Windows;
public float ExportHeightRemapMin = 0.0f;
public float ExportHeightRemapMax = 1.0f;
public bool FlipVertically = false;
// Remap heightmaps
public int LowerBound = 0;
public int UpperBound = 512;
public float BottomPad = 0.05f;
public float TopPad = 0.05f;
// Enums
public enum ImageFormat { TGA, PNG }
public enum SplatmapChannel { R, G, B, A }
public enum RotationAdjustment { Clockwise, Counterclockwise }
public enum FlipAdjustment { Horizontal, Vertical }
// GUI
public bool ShowTerrainEdit = false;
public bool ShowTerrainLayers = false;
public bool ShowReplaceSplatmaps = false;
public bool ShowExportSplatmaps = false;
public bool ShowExportHeightmaps = false;
public bool ShowRemapHeightmaps = false;
}
internal class TerrainToolboxUtilities
{
internal UtilitySettings m_Settings = ScriptableObject.CreateInstance<UtilitySettings>();
// Splatmaps
int m_SplatmapResolution = 0;
Terrain[] m_SplatExportTerrains;
// Terrain Edit
Terrain[] m_Terrains;
List<Material> m_TerrainMaterials = new List<Material>();
// Terrain Split
Terrain[] m_SplitTerrains;
// Layers
List<TerrainLayer> m_CopiedLayers = new List<TerrainLayer>();
internal List<Layer> m_PaletteLayers = new List<Layer>();
ReorderableList m_LayerList;
TerrainPalette m_SelectedLayerPalette = ScriptableObject.CreateInstance<TerrainPalette>();
bool m_LayersAlreadyAdded = false;
// Heightmap export
Dictionary<string, Heightmap.Depth> m_DepthOptions = new Dictionary<string, Heightmap.Depth>()
{
{ "16 bit", Heightmap.Depth.Bit16 },
{ "8 bit", Heightmap.Depth.Bit8 }
};
internal int m_SelectedDepth = 0;
// Splatmaps
internal List<Texture2D> m_Splatmaps = new List<Texture2D>();
HashSet<Texture2D> m_SplatmapHasCopy = new HashSet<Texture2D>();
internal ReorderableList m_SplatmapList;
int m_SelectedSplatMap = 0;
bool m_PreviewIsDirty = false;
MaterialPropertyBlock m_PreviewMaterialPropBlock = new MaterialPropertyBlock();
ToolboxHelper.RenderPipeline m_ActiveRenderPipeline = ToolboxHelper.RenderPipeline.None;
//Visualization
Material m_PreviewMaterial;
bool m_ShowSplatmapPreview = false;
#if UNITY_2019_2_OR_NEWER
#else
Terrain.MaterialType m_TerrainMaterialType;
float m_TerrainLegacyShininess;
Color m_TerrainLegacySpecular;
#endif
int m_MaxLayerCount = 0;
int m_MaxSplatmapCount = 0;
const int k_MaxLayerHD = 8; // HD allows up to 8 layers with 2 splat alpha maps
const int k_MaxSplatmapHD = 2;
const int k_MaxNoLimit = 20;
const int k_MinHeightmapRes = 32;
const int k_MinDetailResPerPatch = 8;
static class Styles
{
public static readonly GUIContent TerrainLayers = EditorGUIUtility.TrTextContent("Terrain Layers");
public static readonly GUIContent ImportSplatmaps = EditorGUIUtility.TrTextContent("Terrain Splatmaps");
public static readonly GUIContent ExportSplatmaps = EditorGUIUtility.TrTextContent("Export Splatmaps");
public static readonly GUIContent ExportHeightmaps = EditorGUIUtility.TrTextContent("Export Heightmaps");
public static readonly GUIContent TerrainEdit = EditorGUIUtility.TrTextContent("Terrain Edit");
public static readonly GUIContent DuplicateTerrain = EditorGUIUtility.TrTextContent("Duplicate");
public static readonly GUIContent RemoveTerrain = EditorGUIUtility.TrTextContent("Clean Remove");
public static readonly GUIContent SplitTerrain = EditorGUIUtility.TrTextContent("Split");
public static readonly GUIContent DuplicateTerrainBtn = EditorGUIUtility.TrTextContent("Duplicate", "Start duplicating selected terrain(s) and create new terrain data.");
public static readonly GUIContent RemoveTerrainBtn = EditorGUIUtility.TrTextContent("Remove", "Start removing selected terrain(s) and delete terrain data asset files.");
public static readonly GUIContent PalettePreset = EditorGUIUtility.TrTextContent("Layer Palette Profile", "Select or make a Terrain Layer palette profile asset.");
public static readonly GUIContent SavePalette = EditorGUIUtility.TrTextContent("Save", "Save the current palette asset file on disk.");
public static readonly GUIContent SaveAsPalette = EditorGUIUtility.TrTextContent("Save As", "Save the current palette asset as a new file on disk.");
public static readonly GUIContent RefreshPalette = EditorGUIUtility.TrTextContent("Refresh", "Load selected palette and apply to list of layers.");
public static readonly GUIContent ApplyToAllTerrains = EditorGUIUtility.TrTextContent("All Terrains in Scene", "When unchecked only apply layer changes to selected terrain(s).");
public static readonly GUIContent ImportTerrainLayersBtn = EditorGUIUtility.TrTextContent("Import From Terrain", "Import layers from the selected terrain.");
public static readonly GUIContent AddLayersBtn = EditorGUIUtility.TrTextContent("Add Layers", "Add layers to the selected terrain(s) that aren't already in the palette.");
public static readonly GUIContent ReplacesLayersBtn = EditorGUIUtility.TrTextContent("Replace Layers", "Replace layers assigned to selected terrain(s). Removed layers will not alter the splatmap data.");
public static readonly GUIContent RemoveLayersBtn = EditorGUIUtility.TrTextContent("Remove All Layers", "Remove all layers from selected terrain(s)");
public static readonly GUIContent TerrainToReplaceSplatmap = EditorGUIUtility.TrTextContent("Terrain", "Select a terrain to replace splatmaps on.");
public static readonly GUIContent SplatmapResolution = EditorGUIUtility.TrTextContent("Splatmap Resolution: ", "The control texture resolution setting of selected terrain.");
public static readonly GUIContent SplatAlpha0 = EditorGUIUtility.TrTextContent("Old SplatAlpha0", "The SplatAlpha 0 texture from selected terrain.");
public static readonly GUIContent SplatAlpha1 = EditorGUIUtility.TrTextContent("Old SplatAlpha1", "The SplatAlpha 1 texture from selected terrain.");
public static readonly GUIContent SplatAlpha0New = EditorGUIUtility.TrTextContent("New SplatAlpha0", "Select a texture to replace the SplatAlpha 0 texture on selected terrain.");
public static readonly GUIContent SplatAlpha1New = EditorGUIUtility.TrTextContent("New SplatAlpha1", "Select a texture to replace the SplatAlpha 1 texture on selected terrain.");
public static readonly GUIContent ImportFromSplatmapBtn = EditorGUIUtility.TrTextContent("Import from Selected Terrain", "Import splatmaps from the selected terrain.");
public static readonly GUIContent ReplaceSplatmapsBtn = EditorGUIUtility.TrTextContent("Replace Splatmaps", "Replace splatmaps with new splatmaps on selected terrain.");
public static readonly GUIContent ResetSplatmapsBtn = EditorGUIUtility.TrTextContent("Reset Splatmaps", "Clear splatmap textures on selected terrain(s).");
public static readonly GUIContent ExportToTerrSplatmapBtn = EditorGUIUtility.TrTextContent("Apply to Terrain", "Export splatmaps to selected terrains.");
public static readonly GUIContent PreviewSplatMapTogg = EditorGUIUtility.TrTextContent("Preview Splatmap", "Preview a splatmap on selected terrains.");
public static readonly GUIContent RotateSplatmapLabel = EditorGUIUtility.TrTextContent("Rotate Adjustment", "Select whether to rotate clockwise or counterclockwise.");
public static readonly GUIContent FlipSplatmapLabel = EditorGUIUtility.TrTextContent("Flip Adjustment", "Select whether to flip horizontally or vertically.");
public static readonly GUIContent RotateSplatmapBtn = EditorGUIUtility.TrTextContent("Rotate", "Rotate splatmap in the selected direction.");
public static readonly GUIContent FlipSplatmapBtn = EditorGUIUtility.TrTextContent("Flip", "Flip splatmaps in the selected direction.");
public static readonly GUIContent MultiAdjustTogg = EditorGUIUtility.TrTextContent("Adjust All Splatmaps", "Make changes to all splatmaps.");
public static readonly GUIContent ExportSplatmapFolderPath = EditorGUIUtility.TrTextContent("Export Folder Path", "Select or input a folder path where splatmap textures will be saved.");
public static readonly GUIContent ExportSplatmapFormat = EditorGUIUtility.TrTextContent("Splatmap Format", "Texture format of exported splatmap(s).");
public static readonly GUIContent ExportSplatmapsBtn = EditorGUIUtility.TrTextContent("Export Splatmaps", "Start exporting splatmaps into textures as selected format from selected terrain(s).");
public static readonly GUIContent OriginalTerrain = EditorGUIUtility.TrTextContent("Original Terrain", "Select a terrain to split into smaller tiles.");
public static readonly GUIContent TileSplit = EditorGUIUtility.TrTextContent("Split Tiles", "The number of tiles along the X and Z axes after splitting.");
public static readonly GUIContent AutoUpdateSetting = EditorGUIUtility.TrTextContent("Auto Update Terrain Settings", "Automatically copy terrain settings to new tiles from original tiles upon create.");
public static readonly GUIContent KeepOldTerrains = EditorGUIUtility.TrTextContent("Keep Original Terrain", "Keep original terrain while splitting.");
public static readonly GUIContent SplitTerrainBtn = EditorGUIUtility.TrTextContent("Split", "Start splitting original terrain into small tiles.");
public static readonly GUIContent ExportHeightmapsBtn = EditorGUIUtility.TrTextContent("Export Heightmaps", "Start exporting raw heightmaps for selected terrain(s).");
public static readonly GUIContent HeightmapSelectedFormat = EditorGUIUtility.TrTextContent("Heightmap Format", "Select the image format for exported heightmaps.");
public static readonly GUIContent ExportHeightmapFolderPath = EditorGUIUtility.TrTextContent("Export Folder Path", "Select or input a folder path where heightmaps will be saved.");
public static readonly GUIContent HeightmapBitDepth = EditorGUIUtility.TrTextContent("Heightmap Depth", "Select heightmap depth option from 8 bit or 16 bit.");
public static readonly GUIContent HeightmapByteOrder = EditorGUIUtility.TrTextContent("Heightmap Byte Order", "Select heightmap byte order from Windows or Mac.");
public static readonly GUIContent HeightmapRemap = EditorGUIUtility.TrTextContent("Levels Correction", "Remap the height range before export.");
public static readonly GUIContent HeightmapRemapMin = EditorGUIUtility.TrTextContent("Min", "Minimum input height");
public static readonly GUIContent HeightmapRemapMax = EditorGUIUtility.TrTextContent("Max", "Maximum input height");
public static readonly GUIContent FlipVertically = EditorGUIUtility.TrTextContent("Flip Vertically", "Flip heights vertically when export. Enable this if using heightmap in external program like World Machine. Or use the Flip Y Axis option in World Machine instead.");
public static readonly GUIContent RemapHeightMaps = EditorGUIUtility.TrTextContent("Remap Height Maps", "Remap height values while preserving the visual appearance");
public static readonly GUIContent RemapLabel = EditorGUIUtility.TrTextContent("Remap Terrain Height Range");
public static readonly GUIContent OptimizeLabel = EditorGUIUtility.TrTextContent("Optimize Height Range Usage");
public static readonly GUIContent RemapButtonLabel = EditorGUIUtility.TrTextContent("Remap", "Remap heightmap between supplied height values, preserving the existing position");
public static readonly GUIContent OptimizeButtonLabel = EditorGUIUtility.TrTextContent("Optimize", "Remap the heightmap so that it fills the height values efficiently");
public static readonly GUIStyle ToggleButtonStyle = "LargeButton";
// for the large buttons
public static int ButtonWidth = 140;
public static int ButtonHeight = 25;
}
public static void DrawSeperatorLine()
{
Rect rect = EditorGUILayout.GetControlRect(GUILayout.Height(12));
rect.height = 1;
rect.y = rect.y + 5;
rect.x = 2;
rect.width += 6;
EditorGUI.DrawRect(rect, new Color(0.35f, 0.35f, 0.35f));
}
public void OnLoad()
{
if (m_Settings.PalettePath != string.Empty && File.Exists(m_Settings.PalettePath))
{
LoadPalette();
}
}
public void OnGUI()
{
// scroll view of settings
EditorGUIUtility.hierarchyMode = true;
DrawSeperatorLine();
// Terrain Edit
m_Settings.ShowTerrainEdit = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.TerrainEdit, m_Settings.ShowTerrainEdit);
++EditorGUI.indentLevel;
if (m_Settings.ShowTerrainEdit)
{
ShowTerrainEditGUI();
}
--EditorGUI.indentLevel;
DrawSeperatorLine();
// Terrain Layers
m_Settings.ShowTerrainLayers = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.TerrainLayers, m_Settings.ShowTerrainLayers);
++EditorGUI.indentLevel;
if (m_Settings.ShowTerrainLayers)
{
ShowTerrainLayerGUI();
}
--EditorGUI.indentLevel;
DrawSeperatorLine();
// Terrain Splatmaps
m_Settings.ShowReplaceSplatmaps = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.ImportSplatmaps, m_Settings.ShowReplaceSplatmaps);
++EditorGUI.indentLevel;
if (m_Settings.ShowReplaceSplatmaps)
{
ShowSplatmapImportGUI();
}
--EditorGUI.indentLevel;
DrawSeperatorLine();
// Terrain Heightmaps
m_Settings.ShowRemapHeightmaps =
TerrainToolGUIHelper.DrawHeaderFoldout(Styles.RemapHeightMaps, m_Settings.ShowRemapHeightmaps);
++EditorGUI.indentLevel;
if (m_Settings.ShowRemapHeightmaps)
{
ShowHeightRemapGUI();
}
--EditorGUI.indentLevel;
DrawSeperatorLine();
// Export Spaltmaps
m_Settings.ShowExportSplatmaps = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.ExportSplatmaps, m_Settings.ShowExportSplatmaps);
++EditorGUI.indentLevel;
if (m_Settings.ShowExportSplatmaps)
{
ShowExportSplatmapGUI();
}
--EditorGUI.indentLevel;
DrawSeperatorLine();
// Export Heightmaps
m_Settings.ShowExportHeightmaps = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.ExportHeightmaps, m_Settings.ShowExportHeightmaps);
++EditorGUI.indentLevel;
if (m_Settings.ShowExportHeightmaps)
{
ShowExportHeightmapGUI();
}
--EditorGUI.indentLevel;
EditorGUILayout.Space();
}
void ShowTerrainEditGUI()
{
// style for labels
GUIStyle labelStyle = new GUIStyle(EditorStyles.label);
labelStyle.wordWrap = true;
// Duplicate Terrain
EditorGUILayout.LabelField(Styles.DuplicateTerrain, EditorStyles.boldLabel);
++EditorGUI.indentLevel;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Select terrain(s) to make a copy from with new terrain data assets: ", labelStyle);
if (GUILayout.Button(Styles.DuplicateTerrainBtn, GUILayout.Height(Styles.ButtonHeight), GUILayout.Width(Styles.ButtonWidth)))
{
DuplicateTerrains();
}
EditorGUILayout.EndHorizontal();
// Clean Delete
--EditorGUI.indentLevel;
EditorGUILayout.LabelField(Styles.RemoveTerrain, EditorStyles.boldLabel);
++EditorGUI.indentLevel;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Select terrain components to remove and delete associated terrain data assets: ", labelStyle);
if (GUILayout.Button(Styles.RemoveTerrainBtn, GUILayout.Height(Styles.ButtonHeight), GUILayout.Width(Styles.ButtonWidth)))
{
RemoveTerrains();
}
EditorGUILayout.EndHorizontal();
// Split Terrain
--EditorGUI.indentLevel;
EditorGUILayout.LabelField(Styles.SplitTerrain, EditorStyles.boldLabel);
++EditorGUI.indentLevel;
EditorGUILayout.LabelField("Select terrain(s) to split: ");
m_Settings.TileSplit = EditorGUILayout.IntField(Styles.TileSplit, m_Settings.TileSplit);
m_Settings.AutoUpdateSettings = EditorGUILayout.Toggle(Styles.AutoUpdateSetting, m_Settings.AutoUpdateSettings);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button(Styles.SplitTerrainBtn, GUILayout.Height(Styles.ButtonHeight), GUILayout.Width(Styles.ButtonWidth)))
{
SplitTerrains();
}
EditorGUILayout.EndHorizontal();
--EditorGUI.indentLevel;
}
void ShowTerrainLayerGUI()
{
// Layer Palette preset
EditorGUILayout.LabelField(Styles.PalettePreset);
EditorGUI.BeginChangeCheck();
m_SelectedLayerPalette = (TerrainPalette)EditorGUILayout.ObjectField(m_SelectedLayerPalette, typeof(TerrainPalette), false);
if (EditorGUI.EndChangeCheck() && m_SelectedLayerPalette != null)
{
if (EditorUtility.DisplayDialog("Confirm", "Load palette from selected?", "OK", "Cancel"))
{
LoadPalette();
}
}
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button(Styles.SavePalette))
{
if (GetPalette())
{
m_SelectedLayerPalette.PaletteLayers.Clear();
foreach (var layer in m_PaletteLayers)
{
m_SelectedLayerPalette.PaletteLayers.Add(layer.AssignedLayer);
}
AssetDatabase.SaveAssets();
}
}
if (GUILayout.Button(Styles.SaveAsPalette))
{
CreateNewPalette();
}
if (GUILayout.Button(Styles.RefreshPalette))
{
if (GetPalette())
{
LoadPalette();
}
}
EditorGUILayout.EndHorizontal();
// layer reorderable list
ShowLayerListGUI();
// Apply button
m_Settings.ApplyAllTerrains = EditorGUILayout.Toggle(Styles.ApplyToAllTerrains, m_Settings.ApplyAllTerrains);
if (m_LayersAlreadyAdded)
{
EditorGUILayout.HelpBox("One or more of the layers are already in your terrain palette.", MessageType.Info);
}
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button(Styles.AddLayersBtn, GUILayout.Height(Styles.ButtonHeight), GUILayout.MaxWidth(Styles.ButtonWidth)))
{
AddLayersToSelectedTerrains(false);
}
if (GUILayout.Button(Styles.ReplacesLayersBtn, GUILayout.Height(Styles.ButtonHeight), GUILayout.MaxWidth(Styles.ButtonWidth)))
{
AddLayersToSelectedTerrains(true);
}
// Clear button
if (GUILayout.Button(Styles.RemoveLayersBtn, GUILayout.Height(Styles.ButtonHeight), GUILayout.MaxWidth(Styles.ButtonWidth)))
{
RemoveLayersFromSelectedTerrains();
}
EditorGUILayout.EndHorizontal();
}
// 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 ShowLayerListGUI()
{
EditorGUILayout.BeginVertical("Box");
if (GUILayout.Button(Styles.ImportTerrainLayersBtn, GUILayout.Width(Styles.ButtonWidth)))
{
ImportLayersFromTerrain();
}
// List View
if (m_LayerList == null)
{
m_LayerList = new ReorderableList(m_PaletteLayers, typeof(Layer), true, true, true, true);
}
m_LayerList.elementHeight = kElementHeight;
m_LayerList.drawHeaderCallback = (Rect rect) => EditorGUI.LabelField(rect, "Layer Palette");
m_LayerList.drawElementCallback = DrawLayerElement;
m_LayerList.onAddCallback = OnAddLayerElement;
m_LayerList.onRemoveCallback = OnRemoveLayerElement;
m_LayerList.onCanAddCallback = OnCanAddLayerElement;
m_LayerList.DoLayoutList();
LayerCreation();
EditorGUILayout.EndVertical();
}
void DrawLayerElement(Rect rect, int index, bool selected, bool focused)
{
rect.y = rect.y + kElementPadding;
var rectImage = new Rect((rect.x + kElementPadding), rect.y, kElementImageWidth, kElementImageHeight);
var rectObject = new Rect((rectImage.x + kElementImageWidth), rect.y, kElementObjectFieldWidth, kElementObjectFieldHeight);
if (m_PaletteLayers.Count > 0 && m_PaletteLayers[index] != null)
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.BeginHorizontal();
List<TerrainLayer> 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);
m_PaletteLayers[index].AssignedLayer = EditorGUI.ObjectField(rectObject, m_PaletteLayers[index].AssignedLayer, typeof(TerrainLayer), false) as TerrainLayer;
EditorGUILayout.EndHorizontal();
if (EditorGUI.EndChangeCheck())
{
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;
}
}
}
}
bool OnCanAddLayerElement(ReorderableList list)
{
return list.count < m_MaxLayerCount;
}
// adding layers
int m_LayerPickerWindowID = -1;
int m_ObjPickerWindowID = -1;
string m_LayerName = "NewLayer";
TerrainLayer m_PickedLayer;
Texture2D m_LayerTexture;
void OnAddLayerElement(ReorderableList list)
{
m_LayerPickerWindowID = EditorGUIUtility.GetControlID(FocusType.Passive) + 200; // had to bump this to make it unique
EditorGUIUtility.ShowObjectPicker<TerrainLayer>(null, false, "", m_LayerPickerWindowID);
}
void LayerCreation()
{
// this code grabs the layer from the ObjectSelector, and creates a new layer with the texture and adds
// it to the list
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);
}
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);
}
RemoveEmptyLayers();
}
void AddLayerElement(TerrainLayer layer)
{
if (LayerExists(layer))
{
return;
}
Layer newLayer = new Layer();
newLayer.AssignedLayer = layer;
newLayer.IsSelected = true;
m_PaletteLayers.Add(newLayer);
m_LayerList.index = m_PaletteLayers.Count - 1;
}
bool LayerExists(TerrainLayer layer)
{
List<TerrainLayer> 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 RemoveEmptyLayers()
{
m_PaletteLayers.RemoveAll(layer => layer.AssignedLayer == null);
}
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;
AssetDatabase.CreateAsset(newLayer.AssignedLayer, AssetDatabase.GenerateUniqueAssetPath($"{path}/{m_LayerName}.terrainlayer"));
}
void OnRemoveLayerElement(ReorderableList list)
{
m_PaletteLayers.RemoveAt(list.index);
list.index = list.index - 1;
}
void ShowSplatmapImportGUI()
{
EditorGUILayout.BeginVertical("Box");
if (GUILayout.Button(Styles.ImportFromSplatmapBtn, GUILayout.Width(Styles.ButtonWidth + 35)))
{
ImportSplatmapsFromTerrain();
}
if (m_SplatmapList == null)
{
m_SplatmapList = new ReorderableList(m_Splatmaps, typeof(Texture2D), true, false, true, true);
}
m_SplatmapList.elementHeight = 70;
m_SplatmapList.drawHeaderCallback = (Rect rect) => EditorGUI.LabelField(rect, "Splatmaps");
m_SplatmapList.drawElementCallback = DrawSplatmapElement;
m_SplatmapList.onAddCallback = OnAddSplatmapElement;
m_SplatmapList.onRemoveCallback = OnRemoveSplatmapElement;
m_SplatmapList.onCanAddCallback = OnCanAddSplatmapElement;
m_SplatmapList.onSelectCallback = OnSelectSplatmapElement;
EditorGUI.BeginChangeCheck();
m_SplatmapList.DoLayoutList();
if (EditorGUI.EndChangeCheck())
{
// check to see if any of the splatmaps in the list need to be copied
for (int i = 0; i < m_Splatmaps.Count; i++)
{
var splatmap = m_Splatmaps[i];
if (splatmap != null && !m_SplatmapHasCopy.Contains(splatmap))
{
var textureCopy = ToolboxHelper.GetTextureCopy(splatmap);
m_Splatmaps[i] = textureCopy;
m_SplatmapHasCopy.Add(textureCopy);
}
}
// clean up the hashset
m_SplatmapHasCopy.Clear();
foreach (var splatmap in m_Splatmaps)
{
if (splatmap != null)
{
m_SplatmapHasCopy.Add(splatmap);
}
}
}
//Splatmap Preview and Adjustment
EditorStyles.label.fontStyle = FontStyle.Bold;
EditorGUI.BeginChangeCheck();
m_ShowSplatmapPreview = EditorGUILayout.Toggle(Styles.PreviewSplatMapTogg, m_ShowSplatmapPreview);
if (EditorGUI.EndChangeCheck())
{
m_PreviewIsDirty = true;
if (m_ShowSplatmapPreview)
{
GetAndSetActiveRenderPipelineSettings();
}
}
EditorStyles.label.fontStyle = FontStyle.Normal;
++EditorGUI.indentLevel;
m_Settings.AdjustAllSplats = EditorGUILayout.Toggle(Styles.MultiAdjustTogg, m_Settings.AdjustAllSplats);
EditorGUILayout.BeginHorizontal();
m_Settings.RotationAdjust = (UtilitySettings.RotationAdjustment)EditorGUILayout.EnumPopup(Styles.RotateSplatmapLabel, m_Settings.RotationAdjust);
if (GUILayout.Button(Styles.RotateSplatmapBtn, GUILayout.Width(150)))
{
RotateSplatmap();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
m_Settings.FlipAdjust = (UtilitySettings.FlipAdjustment)EditorGUILayout.EnumPopup(Styles.FlipSplatmapLabel, m_Settings.FlipAdjust);
if (GUILayout.Button(Styles.FlipSplatmapBtn, GUILayout.Width(150)))
{
FlipSplatmap();
}
EditorGUILayout.EndHorizontal();
--EditorGUI.indentLevel;
EditorGUILayout.EndVertical();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button(Styles.ExportToTerrSplatmapBtn, GUILayout.Height(Styles.ButtonHeight), GUILayout.Width(Styles.ButtonWidth)))
{
ExportSplatmapsToTerrain();
}
if (GUILayout.Button(Styles.ResetSplatmapsBtn, GUILayout.Height(Styles.ButtonHeight), GUILayout.Width(Styles.ButtonWidth)))
{
ResetSplatmaps();
}
EditorGUILayout.EndHorizontal();
if (m_PreviewIsDirty)
{
if (!m_ShowSplatmapPreview)
{
RevertPreviewMaterial();
}
else if (ValidatePreviewTexture())
{
m_PreviewMaterial.DisableKeyword("_HEATMAP");
m_PreviewMaterial.EnableKeyword("_SPLATMAP_PREVIEW");
UpdateAdjustedSplatmaps();
}
m_PreviewIsDirty = false;
}
}
bool OnCanAddSplatmapElement(ReorderableList list)
{
return list.count < m_MaxSplatmapCount;
}
void OnAddSplatmapElement(ReorderableList list)
{
Texture2D newSplatmap = null;
m_Splatmaps.Add(newSplatmap);
m_SplatmapList.index = m_Splatmaps.Count - 1;
}
void OnRemoveSplatmapElement(ReorderableList list)
{
m_Splatmaps.RemoveAt(list.index);
list.index = 0;
m_SelectedSplatMap = 0;
if (list.count == 0)
{
RevertPreviewMaterial();
}
}
void OnSelectSplatmapElement(ReorderableList list)
{
m_SelectedSplatMap = list.index;
m_PreviewIsDirty = true;
}
const int kSplatmapElementHeight = 64;
const int kSplatmapLabelWidth = 100;
const int kSplatmapFieldWidth = 75;
void DrawSplatmapElement(Rect rect, int index, bool selected, bool focused)
{
rect.height = rect.height + kElementPadding;
var rectLabel = new Rect(rect.x, rect.y, kSplatmapLabelWidth, kSplatmapElementHeight);
var rectObject = new Rect((rectLabel.x + kSplatmapLabelWidth), rect.y + kElementPadding, kSplatmapFieldWidth, kSplatmapElementHeight);
if (m_Splatmaps.Count > 0)
{
// label is the built-in splatmap name that gets auto assigned
string label = "SplatAlpha " + index;
EditorGUI.LabelField(rectLabel, label);
m_Splatmaps[index] = EditorGUI.ObjectField(rectObject, m_Splatmaps[index], typeof(Texture2D), false) as Texture2D;
}
}
void ShowReplaceSplatmapGUI()
{
// Replace Splatmap
EditorGUI.BeginChangeCheck();
m_Settings.SplatmapTerrain = EditorGUILayout.ObjectField(Styles.TerrainToReplaceSplatmap, m_Settings.SplatmapTerrain, typeof(Terrain), true) as Terrain;
if (EditorGUI.EndChangeCheck())
{
if (m_Settings.SplatmapTerrain != null)
{
TerrainData terrainData = m_Settings.SplatmapTerrain.terrainData;
if (terrainData.alphamapTextureCount == 1)
{
m_Settings.SplatmapOld0 = terrainData.alphamapTextures[0];
m_Settings.SplatmapOld1 = null;
}
if (terrainData.alphamapTextureCount == 2)
{
m_Settings.SplatmapOld0 = terrainData.alphamapTextures[0];
m_Settings.SplatmapOld1 = terrainData.alphamapTextures[1];
}
m_SplatmapResolution = terrainData.alphamapResolution;
}
else
{
m_Settings.SplatmapOld0 = null;
m_Settings.SplatmapOld1 = null;
m_SplatmapResolution = 0;
}
}
EditorGUILayout.Separator();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(Styles.SplatmapResolution.text + m_SplatmapResolution.ToString());
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
m_Settings.SplatmapOld0 = EditorGUILayout.ObjectField(Styles.SplatAlpha0, m_Settings.SplatmapOld0, typeof(Texture2D), false) as Texture2D;
m_Settings.SplatmapNew0 = EditorGUILayout.ObjectField(Styles.SplatAlpha0New, m_Settings.SplatmapNew0, typeof(Texture2D), false) as Texture2D;
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
m_Settings.SplatmapOld1 = EditorGUILayout.ObjectField(Styles.SplatAlpha1, m_Settings.SplatmapOld1, typeof(Texture2D), false) as Texture2D;
m_Settings.SplatmapNew1 = EditorGUILayout.ObjectField(Styles.SplatAlpha1New, m_Settings.SplatmapNew1, typeof(Texture2D), false) as Texture2D;
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button(Styles.ReplaceSplatmapsBtn, GUILayout.Height(Styles.ButtonHeight), GUILayout.Width(Styles.ButtonWidth)))
{
ReplaceSplatmaps();
}
if (GUILayout.Button(Styles.ResetSplatmapsBtn, GUILayout.Height(Styles.ButtonHeight), GUILayout.Width(Styles.ButtonWidth)))
{
ResetSplatmaps();
}
EditorGUILayout.EndHorizontal();
}
void ShowExportSplatmapGUI()
{
// Export Splatmaps
EditorGUILayout.BeginHorizontal();
m_Settings.SplatFolderPath = EditorGUILayout.TextField(Styles.ExportSplatmapFolderPath, m_Settings.SplatFolderPath);
if (GUILayout.Button("...", GUILayout.Width(25)))
{
// clear the keyboard focus so we can update the value
GUIUtility.keyboardControl = -1;
var newSplatmapPath = EditorUtility.OpenFolderPanel("Select a folder...", m_Settings.SplatFolderPath, "");
// confirm that the user didn't cancel
if (newSplatmapPath != "")
{
m_Settings.SplatFolderPath = newSplatmapPath;
}
}
EditorGUILayout.EndHorizontal();
if (m_Settings.SplatFolderPath == "")
{
EditorGUILayout.HelpBox("Empty folder path. Be sure to assign a folder path before exporting.", MessageType.Warning);
}
m_Settings.SelectedFormat = (UtilitySettings.ImageFormat)EditorGUILayout.EnumPopup(Styles.ExportSplatmapFormat, m_Settings.SelectedFormat);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button(Styles.ExportSplatmapsBtn, GUILayout.Height(Styles.ButtonHeight), GUILayout.Width(Styles.ButtonWidth)))
{
var selectedTerrains = Selection.GetFiltered(typeof(Terrain), SelectionMode.Unfiltered);
ExportSplatmaps(selectedTerrains);
}
EditorGUILayout.EndHorizontal();
}
void ShowExportHeightmapGUI()
{
EditorGUILayout.BeginHorizontal();
m_Settings.HeightmapFolderPath = EditorGUILayout.TextField(Styles.ExportHeightmapFolderPath, m_Settings.HeightmapFolderPath);
if (GUILayout.Button("...", GUILayout.Width(25)))
{
// clear the keyboard focus so we can update the value
GUIUtility.keyboardControl = -1;
var newHeightmapPath = EditorUtility.OpenFolderPanel("Select a folder...", m_Settings.HeightmapFolderPath, "");
// handle if the user pressed cancel
if (newHeightmapPath != "")
{
m_Settings.HeightmapFolderPath = newHeightmapPath;
}
}
EditorGUILayout.EndHorizontal();
if (m_Settings.HeightmapFolderPath == "")
{
EditorGUILayout.HelpBox("Empty folder path. Be sure to assign a folder path before exporting.", MessageType.Warning);
}
//EditorGUILayout.LabelField("Heightmap Format: .raw");
//EditorGUILayout.BeginHorizontal();
//m_SelectedDepth = EditorGUILayout.Popup(Styles.HeightmapBitDepth, m_SelectedDepth, m_DepthOptions.Keys.ToArray());
//EditorGUILayout.EndHorizontal();
//EditorGUILayout.BeginHorizontal();
//m_Settings.HeightmapByteOrder = (ToolboxHelper.ByteOrder)EditorGUILayout.EnumPopup(Styles.HeightmapByteOrder, m_Settings.HeightmapByteOrder);
//EditorGUILayout.EndHorizontal();
//Future to support PNG and TGA.
m_Settings.HeightFormat = (Heightmap.Format)EditorGUILayout.EnumPopup(Styles.HeightmapSelectedFormat, m_Settings.HeightFormat);
if (m_Settings.HeightFormat == Heightmap.Format.RAW)
{
EditorGUILayout.BeginHorizontal();
m_Settings.HeightmapDepth = (Heightmap.Depth)EditorGUILayout.EnumPopup(Styles.HeightmapBitDepth, m_Settings.HeightmapDepth);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
m_Settings.HeightmapByteOrder = (ToolboxHelper.ByteOrder)EditorGUILayout.EnumPopup(Styles.HeightmapByteOrder, m_Settings.HeightmapByteOrder);
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.BeginHorizontal();
EditorGUILayout.MinMaxSlider(Styles.HeightmapRemap, ref m_Settings.ExportHeightRemapMin, ref m_Settings.ExportHeightRemapMax, 0f, 1.0f);
EditorGUILayout.LabelField(Styles.HeightmapRemapMin, GUILayout.Width(40.0f));
m_Settings.ExportHeightRemapMin = EditorGUILayout.FloatField(m_Settings.ExportHeightRemapMin, GUILayout.Width(75.0f));
EditorGUILayout.LabelField(Styles.HeightmapRemapMax, GUILayout.Width(40.0f));
m_Settings.ExportHeightRemapMax = EditorGUILayout.FloatField(m_Settings.ExportHeightRemapMax, GUILayout.Width(75.0f));
EditorGUILayout.EndHorizontal();
m_Settings.FlipVertically = EditorGUILayout.Toggle(Styles.FlipVertically, m_Settings.FlipVertically);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button(Styles.ExportHeightmapsBtn, GUILayout.Height(Styles.ButtonHeight), GUILayout.Width(Styles.ButtonWidth)))
{
var selectedTerrains = Selection.GetFiltered(typeof(Terrain), SelectionMode.Unfiltered);
ExportHeightmaps(selectedTerrains);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Separator();
}
void ShowHeightRemapGUI()
{
EditorGUILayout.LabelField(Styles.RemapLabel, EditorStyles.boldLabel);
// ---- remap section
++EditorGUI.indentLevel;
EditorGUILayout.BeginHorizontal();
// min-max fields
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Heightmap lower boundary");
m_Settings.LowerBound = EditorGUILayout.IntField(m_Settings.LowerBound);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Heightmap upper boundary");
m_Settings.UpperBound = EditorGUILayout.IntField(m_Settings.UpperBound);
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
// end min-max fields
// button line
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button(Styles.RemapButtonLabel, GUILayout.Width(Styles.ButtonWidth), GUILayout.Height(36)))
{
OptimizeHeightRange.RemapSelectedTerrains(m_Settings.LowerBound, m_Settings.UpperBound);
}
EditorGUILayout.EndHorizontal();
// end button line
EditorGUILayout.EndHorizontal();
--EditorGUI.indentLevel;
// ---- end remap section
EditorGUILayout.Separator();
EditorGUILayout.Separator();
// ---- optimize section
EditorGUILayout.LabelField(Styles.OptimizeLabel, EditorStyles.boldLabel);
++EditorGUI.indentLevel;
EditorGUILayout.BeginHorizontal();
// padding fields
EditorGUILayout.BeginVertical();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Lower boundary padding");
m_Settings.BottomPad = Mathf.Clamp(EditorGUILayout.FloatField(m_Settings.BottomPad), 0, 1f);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Upper boundary padding");
m_Settings.TopPad = Mathf.Clamp(EditorGUILayout.FloatField(m_Settings.TopPad), 0, 1f);
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
// end padding fields
EditorGUILayout.Space();
if (GUILayout.Button(Styles.OptimizeButtonLabel, GUILayout.Width(Styles.ButtonWidth), GUILayout.Height(36)))
{
OptimizeHeightRange.OptimizeSelectedTerrains(m_Settings.BottomPad, m_Settings.TopPad);
}
EditorGUILayout.EndHorizontal();
--EditorGUI.indentLevel;
// ---- end optimize section
EditorGUILayout.Separator();
}
void UpdateAdjustedSplatmaps()
{
List<Terrain> terrains = m_Terrains.ToList();
if (terrains.Count == 0)
{
EditorUtility.DisplayDialog("Error", "Select a terrain before previewing the splatmap.", "OK");
RevertPreviewMaterial();
return;
}
List<Terrain> sortedTerrains = terrains.OrderBy(t => t.gameObject.transform.position.x).ThenBy(t => t.gameObject.transform.position.z).ToList();
Texture2D splatMap = m_Splatmaps[m_SelectedSplatMap];
Vector2Int tileOffset = Vector2Int.zero;
int tilesX = terrains.Select(t => t.gameObject.transform.position.x).Distinct().Count();
int tilesZ = terrains.Select(t => t.gameObject.transform.position.z).Distinct().Count();
int expectedCount = tilesX * tilesZ;
int tilesCount = terrains.Count;
int index = 0;
Vector2Int resolution = new Vector2Int(splatMap.width / tilesX, splatMap.height / tilesZ);
Texture2D texture = new Texture2D(resolution.x, resolution.y);
if (!ValidateSplatmap(terrains, resolution, expectedCount, tilesCount))
{
RevertPreviewMaterial();
return;
}
for (int x = 0; x < tilesX; x++, tileOffset.x += resolution.x)
{
tileOffset.y = 0;
for (int y = 0; y < tilesZ; y++, tileOffset.y += resolution.y)
{
texture = new Texture2D(resolution.x, resolution.y);
var newPixels = splatMap.GetPixels(tileOffset.x, tileOffset.y, resolution.x, resolution.y);
#if UNITY_2019_2_OR_NEWER
#else
sortedTerrains[index].materialType = Terrain.MaterialType.Custom;
#endif
sortedTerrains[index].materialTemplate = m_PreviewMaterial;
texture.SetPixels(newPixels);
texture.Apply();
m_PreviewMaterialPropBlock.Clear();
m_PreviewMaterialPropBlock.SetTexture("_SplatmapTex", texture);
sortedTerrains[index].SetSplatMaterialPropertyBlock(m_PreviewMaterialPropBlock);
index++;
}
}
}
void DrawLayerIcon(Texture icon, int index)
{
if (icon == null)
return;
int width = icon.width;
Rect position = new Rect(0, width * index, width, width);
int size = Mathf.Min((int)position.width, (int)position.height);
if (size >= icon.width * 2)
size = icon.width * 2;
FilterMode filterMode = icon.filterMode;
icon.filterMode = FilterMode.Point;
EditorGUILayout.BeginVertical("Box", GUILayout.Width(140));
GUILayout.Label(icon);
if (m_PaletteLayers[index] != null && m_PaletteLayers[index].AssignedLayer != null)
{
GUILayout.BeginHorizontal();
GUILayout.Label(m_PaletteLayers[index].AssignedLayer.name, GUILayout.Width(90));
GUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
icon.filterMode = filterMode;
}
public void AddLayersToSelectedTerrains(bool clearExistingLayers)
{
Terrain[] terrains;
if (m_Settings.ApplyAllTerrains)
{
terrains = ToolboxHelper.GetAllTerrainsInScene();
}
else
{
terrains = ToolboxHelper.GetSelectedTerrainsInScene();
}
if (terrains == null || terrains.Length == 0)
{
EditorUtility.DisplayDialog("Warning", "No selected terrain found. Please select to continue.", "OK");
return;
}
int index = 0;
if (terrains.Length > 0 && m_PaletteLayers.Count > 0)
{
foreach (var terrain in terrains)
{
if (!terrain || !terrain.terrainData)
{
continue;
}
EditorUtility.DisplayProgressBar("Applying terrain layers", string.Format("Updating terrain tile ({0})", terrain.name), ((float)index / (terrains.Count())));
m_LayersAlreadyAdded = TerrainToolboxLayer.AddLayersToTerrain(terrain.terrainData, m_PaletteLayers.Select(l => l.AssignedLayer).ToList(), clearExistingLayers);
index++;
}
AssetDatabase.SaveAssets();
EditorUtility.ClearProgressBar();
}
else
{
m_LayersAlreadyAdded = false;
}
}
void RemoveLayersFromSelectedTerrains()
{
Terrain[] terrains;
if (m_Settings.ApplyAllTerrains)
{
terrains = ToolboxHelper.GetAllTerrainsInScene();
}
else
{
terrains = ToolboxHelper.GetSelectedTerrainsInScene();
}
if (terrains == null || terrains.Length == 0)
{
EditorUtility.DisplayDialog("Warning", "No selected terrain found. Please select to continue.", "OK");
return;
}
if (EditorUtility.DisplayDialog("Confirm", "Are you sure you want to remove all existing layers from terrain(s)?", "Continue", "Cancel"))
{
int index = 0;
if (terrains.Length > 0)
{
foreach (var terrain in terrains)
{
EditorUtility.DisplayProgressBar("Removing terrain layers", string.Format("Updating terrain tile ({0})", terrain.name), ((float)index / (terrains.Count())));
if (!terrain || !terrain.terrainData)
{
continue;
}
var layers = terrain.terrainData.terrainLayers;
if (layers == null || layers.Length == 0)
{
continue;
}
TerrainToolboxLayer.RemoveAllLayers(terrain.terrainData);
index++;
}
AssetDatabase.SaveAssets();
EditorUtility.ClearProgressBar();
}
}
}
public void ImportLayersFromTerrain()
{
m_Terrains = ToolboxHelper.GetSelectedTerrainsInScene();
if (m_Terrains.Length != 1)
{
EditorUtility.DisplayDialog("Warning", "Layers can only be imported from 1 terrain.", "OK");
}
else
{
Terrain terrain = m_Terrains[0];
m_PaletteLayers.Clear();
m_CopiedLayers.Clear();
foreach (TerrainLayer layer in terrain.terrainData.terrainLayers)
{
Layer paletteLayer = new Layer();
paletteLayer.AssignedLayer = layer;
m_PaletteLayers.Add(paletteLayer);
}
m_CopiedLayers.AddRange(terrain.terrainData.terrainLayers);
}
}
internal void ImportSplatmapsFromTerrain(bool bypassDisplays = false)
{
m_Terrains = ToolboxHelper.GetSelectedTerrainsInScene();
if (m_Terrains.Length != 1)
{
if (!bypassDisplays)
EditorUtility.DisplayDialog("Warning", "Splatmaps can only be imported from 1 terrain.", "OK");
}
else
{
Terrain terrain = m_Terrains[0];
m_Splatmaps.Clear();
foreach (Texture2D alphamap in terrain.terrainData.alphamapTextures)
{
var textureCopy = ToolboxHelper.GetTextureCopy(alphamap);
m_SplatmapHasCopy.Add(textureCopy);
m_Splatmaps.Add(textureCopy);
}
UpdateCachedTerrainMaterials();
}
}
void DuplicateTerrains()
{
m_Terrains = ToolboxHelper.GetSelectedTerrainsInScene(SelectionMode.TopLevel);
if (m_Terrains == null || m_Terrains.Length == 0)
{
EditorUtility.DisplayDialog("Error", "No terrain(s) selected. Please select and try again.", "OK");
return;
}
foreach (var rootTerrain in m_Terrains)
{
// Duplicate the root terrain object, which will also duplicate any child objects.
GameObject newGO = UnityEngine.Object.Instantiate(rootTerrain.gameObject);
// Make sure the clone matches Unity Editor naming preferences for duplicates.
newGO.name = rootTerrain.gameObject.name;
GameObjectUtility.EnsureUniqueNameForSibling(newGO);
// Find every child with a terrain and duplicate the terrain data.
foreach(var terrain in newGO.GetComponentsInChildren<Terrain>())
{
var dataPath = AssetDatabase.GetAssetPath(terrain.terrainData);
var dataPathNew = AssetDatabase.GenerateUniqueAssetPath(dataPath);
AssetDatabase.CopyAsset(dataPath, dataPathNew);
TerrainData terrainData = AssetDatabase.LoadAssetAtPath<TerrainData>(dataPathNew);
terrain.GetComponent<Terrain>().terrainData = terrainData;
// Update terrain data reference in terrain collider.
TerrainCollider collider = terrain.gameObject.GetComponent<TerrainCollider>();
collider.terrainData = terrainData;
}
Undo.RegisterCreatedObjectUndo(newGO, "Duplicate terrain");
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
internal void RemoveTerrains(bool isTest = false)
{
m_Terrains = ToolboxHelper.GetSelectedTerrainsInScene();
if (!isTest && m_Terrains == null || m_Terrains.Length == 0)
{
EditorUtility.DisplayDialog("Error", "No terrain components selected. Please select and try again.", "OK");
return;
}
if (isTest || EditorUtility.DisplayDialog("Confirm", $"Are you sure you want to delete the selected terrain components and their data assets? This process cannot be undone.", "Continue", "Cancel"))
{
foreach (var terrain in m_Terrains)
{
if (terrain.terrainData)
{
var path = AssetDatabase.GetAssetPath(terrain.terrainData);
AssetDatabase.MoveAssetToTrash(path);
}
// If the gameobject only has terrain components and no children, delete the whole thing.
// Otherwise, only delete the terrain components.
int numComponents = terrain.gameObject.GetComponents<Component>().Select(x => x.GetType())
.Count(x => x != typeof(Terrain) && x != typeof(TerrainCollider) && x != typeof(Transform));
if (numComponents == 0 && terrain.transform.childCount == 0)
{
UnityEngine.Object.DestroyImmediate(terrain.gameObject);
}
else
{
var collider = terrain.GetComponent<TerrainCollider>();
UnityEngine.Object.DestroyImmediate(terrain);
if (collider != null)
{
UnityEngine.Object.DestroyImmediate(collider);
}
}
}
AssetDatabase.Refresh();
}
}
bool MultipleIDExist(List<Terrain> terrains)
{
int[] ids = terrains.Select(t => t.groupingID).ToArray();
if (ids.Distinct().ToArray().Length > 1)
{
return true;
}
else
{
return false;
}
}
internal void SplitTerrains(bool isTest = false)
{
var terrainsFrom = ToolboxHelper.GetSelectedTerrainsInScene();
if (terrainsFrom == null || terrainsFrom.Length == 0)
{
EditorUtility.DisplayDialog("Error", "No terrain(s) selected. Please select and try again.", "OK");
return;
}
// check if multiple grouping ids selected
if (MultipleIDExist(terrainsFrom.ToList()))
{
EditorUtility.DisplayDialog("Error", "The terrains selected have inconsistent Grouping IDs.", "OK");
return;
}
int new_id = GetGroupIDForSplittedNewTerrain(terrainsFrom);
try
{
foreach (var terrain in terrainsFrom)
{
SplitTerrain(terrain, new_id, isTest);
}
}
finally
{
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.ClearProgressBar();
}
}
internal void SplitTerrain(Terrain terrain, int new_id, bool isTest = false)
{
if (m_Settings.TileSplit <= 1)
{
m_Settings.TileSplit = 2;
if (!isTest)
{
EditorUtility.DisplayDialog("Warning", "Invalid split value. Resetting to 2.", "OK");
return;
}
}
TerrainData terrainData = terrain.terrainData;
Vector3 startPosition = terrain.transform.position;
float tileWidth = terrainData.size.x / m_Settings.TileSplit;
float tileLength = terrainData.size.z / m_Settings.TileSplit;
float tileHeight = terrainData.size.y;
Vector2Int heightOffset = Vector2Int.zero;
Vector2Int detailOffset = Vector2Int.zero;
Vector2Int controlOffset = Vector2Int.zero;
Vector3 tilePosition = terrain.transform.position;
// get parent gameobject (the original terrain if it's not under a terrain group, otherwise the terrain group)
GameObject parentGO = terrain.gameObject;
if (terrain.transform.parent != null && terrain.transform.parent.gameObject != null)
{
var parent = terrain.transform.parent.gameObject;
var groupComp = parent.GetComponent<TerrainGroup>();
if (parent != null && groupComp != null)
{
parentGO = parent;
}
}
int originalHeightmapRes = terrainData.heightmapResolution;
int newHeightmapRes = (originalHeightmapRes - 1) / m_Settings.TileSplit;
int newDetailmapRes = terrainData.detailResolution / m_Settings.TileSplit;
if (!ToolboxHelper.IsPowerOfTwo(newHeightmapRes))
{
EditorUtility.DisplayDialog("Error", "Heightmap resolution of new tiles is not power of 2 with current settings.", "OK");
return;
}
if (newHeightmapRes < k_MinHeightmapRes)
{
if (!isTest && !EditorUtility.DisplayDialog("Warning",
$"The heightmap resolution of the newly split tiles is {newHeightmapRes + 1}; " +
$"this is smaller than the minimum supported value of {k_MinHeightmapRes + 1}.\n\n" +
$"Would you like to split terrain into {m_Settings.TileSplit}x{m_Settings.TileSplit} " +
$"tiles of heightmap resolution {k_MinHeightmapRes + 1}?",
"OK",
"Cancel"))
{
return;
}
ToolboxHelper.ResizeHeightmap(terrainData, k_MinHeightmapRes * m_Settings.TileSplit);
newHeightmapRes = k_MinHeightmapRes;
}
// control map resolution
int newControlRes = terrainData.alphamapResolution / m_Settings.TileSplit;
if (!ToolboxHelper.IsPowerOfTwo(newControlRes))
{
EditorUtility.DisplayDialog("Error", "Splat control map resolution of new tiles is not power of 2 with current settings.", "OK");
return;
}
int tileIndex = 0;
int tileCount = m_Settings.TileSplit * m_Settings.TileSplit;
Terrain[] terrainsNew = new Terrain[tileCount];
#if UNITY_2019_3_OR_NEWER
// holes render texture
RenderTexture rt = RenderTexture.GetTemporary(terrainData.holesTexture.width, terrainData.holesTexture.height);
Graphics.Blit(terrainData.holesTexture, rt);
rt.filterMode = FilterMode.Point;
#endif
for (int x = 0; x < m_Settings.TileSplit; x++, heightOffset.x += newHeightmapRes, detailOffset.x += newDetailmapRes, controlOffset.x += newControlRes, tilePosition.x += tileWidth)
{
heightOffset.y = 0;
detailOffset.y = 0;
controlOffset.y = 0;
tilePosition.z = startPosition.z;
for (int y = 0; y < m_Settings.TileSplit; y++, heightOffset.y += newHeightmapRes, detailOffset.y += newDetailmapRes, controlOffset.y += newControlRes, tilePosition.z += tileLength)
{
EditorUtility.DisplayProgressBar("Creating terrains", string.Format("Updating terrain tile ({0}, {1})", x, y), ((float)tileIndex / tileCount));
TerrainData terrainDataNew = new TerrainData();
GameObject newGO = Terrain.CreateTerrainGameObject(terrainDataNew);
Terrain newTerrain = newGO.GetComponent<Terrain>();
Guid newGuid = Guid.NewGuid();
string terrainName = $"Terrain_{x}_{y}_{newGuid}";
newGO.name = terrainName;
newTerrain.transform.position = tilePosition;
newTerrain.groupingID = new_id;
newTerrain.allowAutoConnect = true;
newTerrain.drawInstanced = terrain.drawInstanced;
newTerrain.transform.SetParent(parentGO.transform);
// get and set heights
terrainDataNew.heightmapResolution = newHeightmapRes + 1;
var heightData = terrainData.GetHeights(heightOffset.x, heightOffset.y, (newHeightmapRes + 1), (newHeightmapRes + 1));
terrainDataNew.SetHeights(0, 0, heightData);
terrainDataNew.size = new Vector3(tileWidth, tileHeight, tileLength);
string assetPath = $"{m_Settings.TerrainAssetDir}/{terrainName}.asset";
if (!Directory.Exists(m_Settings.TerrainAssetDir))
{
Directory.CreateDirectory(m_Settings.TerrainAssetDir);
}
AssetDatabase.CreateAsset(terrainDataNew, assetPath);
// note that add layers and alphamap operations need to happen after terrain data asset being created, so cached splat 0 and 1 data gets cleared to avoid bumping to splat 2 map.
// get and set terrain layers
TerrainToolboxLayer.AddLayersToTerrain(terrainDataNew, terrainData.terrainLayers.ToList(), true);
// get and set alphamaps
float[,,] alphamap = terrainData.GetAlphamaps(controlOffset.x, controlOffset.y, newControlRes, newControlRes);
terrainDataNew.alphamapResolution = newControlRes;
terrainDataNew.SetAlphamaps(0, 0, alphamap);
// get and set detailmap
int newDetailPatch = Mathf.Max(k_MinDetailResPerPatch, terrainData.detailResolutionPerPatch / m_Settings.TileSplit);
terrainDataNew.SetDetailResolution(newDetailmapRes, newDetailPatch);
terrainDataNew.detailPrototypes = terrainData.detailPrototypes;
for (int i = 0; i < terrainDataNew.detailPrototypes.Length; i++)
{
int[,] detailLayer = terrainData.GetDetailLayer(detailOffset.x, detailOffset.y, newDetailmapRes, newDetailmapRes, i);
terrainDataNew.SetDetailLayer(0, 0, i, detailLayer);
}
// get and set treemap
float treeOffsetXMin = x / (float)m_Settings.TileSplit;
float treeOffsetZMin = y / (float)m_Settings.TileSplit;
float treeOffsetXMAX = treeOffsetXMin + (1 / (float)m_Settings.TileSplit);
float treeOffsetZMAX = treeOffsetZMin + (1 / (float)m_Settings.TileSplit);
terrainDataNew.treePrototypes = terrainData.treePrototypes;
List<TreeInstance> treeInstances = new List<TreeInstance>();
TreeInstance[] treeData = terrainData.treeInstances;
for (int i = 0; i < treeData.Length; i++)
{
TreeInstance tree = treeData[i];
if (treeOffsetXMin <= tree.position.x && tree.position.x <= treeOffsetXMAX &&
treeOffsetZMin <= tree.position.z && tree.position.z <= treeOffsetZMAX)
{
tree.position.x = (tree.position.x - treeOffsetXMin) * m_Settings.TileSplit;
tree.position.z = (tree.position.z - treeOffsetZMin) * m_Settings.TileSplit;
treeInstances.Add(tree);
}
}
terrainDataNew.SetTreeInstances(treeInstances.ToArray(), true);
#if UNITY_2019_3_OR_NEWER
// get and set holes, however there's currently a bug in GetHoles() so using render texture blit instead
//var holes = terrainData.GetHoles(heightOffset.x, heightOffset.y, newHeightmapRes, newHeightmapRes);
//terrainDataNew.SetHoles(0, 0, holes);
float divX = 1f / m_Settings.TileSplit;
float divZ = 1f / m_Settings.TileSplit;
Vector2 scale = new Vector2(divX, divZ);
Vector2 offset = new Vector2(divX * x, divZ * y);
Graphics.Blit(rt, (RenderTexture)terrainDataNew.holesTexture, scale, offset);
terrainDataNew.DirtyTextureRegion(TerrainData.HolesTextureName, new RectInt(0, 0, terrainDataNew.holesTexture.width, terrainDataNew.holesTexture.height), false);
#endif
// update other terrain settings
if (m_Settings.AutoUpdateSettings)
{
ApplySettingsFromSourceToTargetTerrain(terrain, newTerrain);
}
terrainsNew[tileIndex] = newTerrain;
tileIndex++;
Undo.RegisterCreatedObjectUndo(newGO, "Split terrain");
}
}
m_SplitTerrains = terrainsNew;
ToolboxHelper.CalculateAdjacencies(m_SplitTerrains, m_Settings.TileSplit, m_Settings.TileSplit);
#if UNITY_2019_3_OR_NEWER
RenderTexture.ReleaseTemporary(rt);
#endif
if (terrainData.heightmapResolution != originalHeightmapRes)
{
ToolboxHelper.ResizeHeightmap(terrainData, originalHeightmapRes);
}
// Remove terrain components from the root gameobject.
var terrainCollider = terrain.GetComponent<TerrainCollider>();
if (terrainCollider != null)
{
Undo.DestroyObjectImmediate(terrainCollider);
}
Undo.DestroyObjectImmediate(terrain);
}
int GetGroupIDForSplittedNewTerrain(Terrain[] exclude_terrains)
{
// check all other terrains in scene to see if group ID exists
Terrain[] all_terrains = ToolboxHelper.GetAllTerrainsInScene();
Terrain[] remaining_terrains = all_terrains.Except(exclude_terrains).ToArray();
List<int> ids = new List<int>();
int original_id = exclude_terrains[0].groupingID;
ids.Add(original_id);
bool exist = false;
foreach (var terrain in remaining_terrains)
{
if (terrain.groupingID == original_id)
{
exist = true;
}
ids.Add(terrain.groupingID);
}
List<int> unique_ids = ids.Distinct().ToList();
int max_id = unique_ids.Max();
// if found id exist in scene, give a new id with largest id + 1, otherwise use original terrain's id
if (exist)
{
return max_id + 1;
}
else
{
return original_id;
}
}
void ApplySettingsFromSourceToTargetTerrain(Terrain sourceTerrain, Terrain targetTerrain)
{
targetTerrain.allowAutoConnect = sourceTerrain.allowAutoConnect;
targetTerrain.drawHeightmap = sourceTerrain.drawHeightmap;
targetTerrain.drawInstanced = sourceTerrain.drawInstanced;
targetTerrain.heightmapPixelError = sourceTerrain.heightmapPixelError;
targetTerrain.basemapDistance = sourceTerrain.basemapDistance;
targetTerrain.shadowCastingMode = sourceTerrain.shadowCastingMode;
targetTerrain.materialTemplate = sourceTerrain.materialTemplate;
targetTerrain.reflectionProbeUsage = sourceTerrain.reflectionProbeUsage;
#if UNITY_2019_2_OR_NEWER
#else
targetTerrain.materialType = sourceTerrain.materialType;
targetTerrain.legacySpecular = sourceTerrain.legacySpecular;
targetTerrain.legacyShininess = sourceTerrain.legacyShininess;
#endif
targetTerrain.terrainData.baseMapResolution = sourceTerrain.terrainData.baseMapResolution;
targetTerrain.drawTreesAndFoliage = sourceTerrain.drawTreesAndFoliage;
targetTerrain.bakeLightProbesForTrees = sourceTerrain.bakeLightProbesForTrees;
targetTerrain.deringLightProbesForTrees = sourceTerrain.deringLightProbesForTrees;
targetTerrain.preserveTreePrototypeLayers = sourceTerrain.preserveTreePrototypeLayers;
targetTerrain.detailObjectDistance = sourceTerrain.detailObjectDistance;
targetTerrain.collectDetailPatches = sourceTerrain.collectDetailPatches;
targetTerrain.detailObjectDensity = sourceTerrain.detailObjectDistance;
targetTerrain.treeDistance = sourceTerrain.treeDistance;
targetTerrain.treeBillboardDistance = sourceTerrain.treeBillboardDistance;
targetTerrain.treeCrossFadeLength = sourceTerrain.treeCrossFadeLength;
targetTerrain.treeMaximumFullLODCount = sourceTerrain.treeMaximumFullLODCount;
targetTerrain.terrainData.wavingGrassStrength = sourceTerrain.terrainData.wavingGrassStrength;
targetTerrain.terrainData.wavingGrassSpeed = sourceTerrain.terrainData.wavingGrassSpeed;
targetTerrain.terrainData.wavingGrassAmount = sourceTerrain.terrainData.wavingGrassAmount;
targetTerrain.terrainData.wavingGrassTint = sourceTerrain.terrainData.wavingGrassTint;
targetTerrain.lightmapIndex = sourceTerrain.lightmapIndex;
targetTerrain.renderingLayerMask = sourceTerrain.renderingLayerMask;
targetTerrain.lightmapScaleOffset = sourceTerrain.lightmapScaleOffset;
targetTerrain.realtimeLightmapIndex = sourceTerrain.realtimeLightmapIndex;
targetTerrain.realtimeLightmapScaleOffset = sourceTerrain.realtimeLightmapScaleOffset;
targetTerrain.editorRenderFlags = sourceTerrain.editorRenderFlags;
}
void ReplaceSplatmaps()
{
if (m_Settings.SplatmapNew0 == null && m_Settings.SplatmapNew1 == null)
{
if (EditorUtility.DisplayDialog("Confirm", "You don't have new splatmaps assigned. Would you like to reset splatmaps to defaults on selected terrain?", "OK", "Cancel"))
{
// reset splatmaps
ResetSplatmapsOnTerrain(m_Settings.SplatmapTerrain);
return;
}
return;
}
if (m_Settings.SplatmapOld0 != null && m_Settings.SplatmapNew0 != null)
{
ReplaceSplatmapTexture(m_Settings.SplatmapOld0, m_Settings.SplatmapNew0);
}
if (m_Settings.SplatmapOld1 != null && m_Settings.SplatmapNew1 != null)
{
ReplaceSplatmapTexture(m_Settings.SplatmapOld1, m_Settings.SplatmapNew1);
}
AssetDatabase.SaveAssets();
}
void ReplaceSplatmapTexture(Texture2D oldTexture, Texture2D newTexture)
{
if (newTexture.width != newTexture.height)
{
EditorUtility.DisplayDialog("Error", "Could not replace splatmap. Non-square sized splatmap found.", "OK");
return;
}
var undoObjects = new List<UnityEngine.Object>();
undoObjects.Add(m_Settings.SplatmapTerrain.terrainData);
undoObjects.AddRange(m_Settings.SplatmapTerrain.terrainData.alphamapTextures);
Undo.RegisterCompleteObjectUndo(undoObjects.ToArray(), "Replace splatmaps");
// set new texture to be readable through Import Settings, so we can use GetPixels() later
if (!newTexture.isReadable)
{
var newPath = AssetDatabase.GetAssetPath(newTexture);
var newImporter = AssetImporter.GetAtPath(newPath) as TextureImporter;
if (newImporter != null)
{
newImporter.isReadable = true;
AssetDatabase.ImportAsset(newPath);
AssetDatabase.Refresh();
}
}
if (newTexture.width != oldTexture.width)
{
if (EditorUtility.DisplayDialog("Confirm", "Mismatched splatmap resolution found.", "Use New Resolution", "Use Old Resolution"))
{
// resize to new texture size
oldTexture.Reinitialize(newTexture.width, newTexture.height, oldTexture.format, true);
// update splatmap resolution on terrain settings as well
m_Settings.SplatmapTerrain.terrainData.alphamapResolution = newTexture.width;
m_SplatmapResolution = newTexture.width;
}
else
{
// resize to old texture size
newTexture.Reinitialize(oldTexture.width, oldTexture.height, newTexture.format, true);
}
}
var pixelsNew = newTexture.GetPixels();
oldTexture.SetPixels(pixelsNew);
oldTexture.Apply();
}
internal void ExportSplatmapsToTerrain(bool bypassDisplays = false)
{
// validate settings
// all splatmaps same resolution
// terrains same control map resolution
// get selected tiles and sort by position along X and Z
List<Terrain> terrains = ToolboxHelper.GetSelectedTerrainsInScene().ToList();
List<Terrain> sortedTerrains = terrains.OrderBy(t => t.gameObject.transform.position.x).ThenBy(t => t.gameObject.transform.position.z).ToList();
var undoObjects = new List<UnityEngine.Object>();
foreach (var terrain in terrains)
{
undoObjects.Add(terrain.terrainData);
undoObjects.AddRange(terrain.terrainData.alphamapTextures);
}
Undo.RegisterCompleteObjectUndo(undoObjects.ToArray(), "Reset terrains");
int tilesX = terrains.Select(t => t.gameObject.transform.position.x).Distinct().Count();
int tilesZ = terrains.Select(t => t.gameObject.transform.position.z).Distinct().Count();
int expectedCount = tilesX * tilesZ;
if (expectedCount == 0)
{
if (!bypassDisplays)
{
EditorUtility.DisplayDialog("Error", "No terrain(s) selected. Please select terrain tile(s) to continue.", "OK");
}
return;
}
int tilesCount = terrains.Count;
Vector2Int tileOffset = Vector2Int.zero;
int index = 0;
try
{
for (int z = 0; z < m_Splatmaps.Count; z++)
{
index = 0;
tileOffset = Vector2Int.zero;
if (m_Splatmaps[z] != null)
{
Vector2Int resolution = new Vector2Int(m_Splatmaps[z].width / tilesX, m_Splatmaps[z].height / tilesZ);
if (ValidateSplatmap(terrains, resolution, expectedCount, tilesCount))
{
RenderTexture oldRT = RenderTexture.active;
RenderTexture[] rts = new RenderTexture[m_Splatmaps.Count];
rts[z] = RenderTexture.GetTemporary(resolution.x, resolution.x, 0, SystemInfo.GetGraphicsFormat(UnityEngine.Experimental.Rendering.DefaultFormat.HDR));
Graphics.Blit(m_Splatmaps[z], rts[z]);
RenderTexture.active = rts[z];
for (int x = 0; x < tilesX; x++, tileOffset.x += resolution.x)
{
tileOffset.y = 0;
for (int y = 0; y < tilesZ; y++, tileOffset.y += resolution.y)
{
if (!bypassDisplays)
{
EditorUtility.DisplayProgressBar("Applying splatmaps", string.Format("Updating terrain tile {0}", sortedTerrains[index].name), ((float)index / tilesCount));
}
ToolboxHelper.ResizeControlTexture(sortedTerrains[index].terrainData, resolution.x);
if (sortedTerrains[index].terrainData.alphamapTextures[z] != null)
{
ToolboxHelper.CopyActiveRenderTextureToTexture(sortedTerrains[index].terrainData.alphamapTextures[z], new RectInt(tileOffset.x, tileOffset.y, resolution.x, resolution.x), Vector2Int.zero, false);
}
index++;
}
}
RenderTexture.active = oldRT;
for (int i = 0; i < m_Splatmaps.Count; i++)
{
RenderTexture.ReleaseTemporary(rts[i]);
}
}
}
}
}
finally
{
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.ClearProgressBar();
}
}
bool ValidateSplatmap(List<Terrain> terrains, Vector2Int resolution, int expectedCount, int tilesCount)
{
foreach (Terrain terrain in terrains)
{
if (terrain.terrainData.alphamapTextures.Length < m_SplatmapList.count)
{
EditorUtility.DisplayDialog("Error", "You've selected more splatmaps to import than each terrain can hold. Either select less splatmaps or add more to your terrain.", "OK"); //string.Format("The selected amount of {0} splatmap textures, dosen't match that of the average splatmaps of {1:0.##} per terrain ", m_SplatmapList.count, averageSplatmaps)
return false;
}
}
if (!ToolboxHelper.IsPowerOfTwo(resolution.x))
{
EditorUtility.DisplayDialog("Error", "The selected splatmap resolutions aren't a power of two.", "OK");
return false;
}
else if (resolution.x != resolution.y)
{
EditorUtility.DisplayDialog("Error", "The selected splatmaps resolution isn't square.", "OK");
return false;
}
else if (expectedCount > tilesCount)
{
EditorUtility.DisplayDialog("Error", "The terrains selected aren't square.", "OK");
return false;
}
return true;
}
bool ValidatePreviewTexture()
{
if (m_Splatmaps.Count == 0)
{
EditorUtility.DisplayDialog("Error", "Add and select a splatmap before previewing the splatmap.", "OK");
RevertPreviewMaterial();
return false;
}
else if (m_Splatmaps[m_SelectedSplatMap] == null)
{
EditorUtility.DisplayDialog("Error", "Select a splatmap before previewing the splatmap.", "OK");
if (m_Splatmaps[0] == null)
{
RevertPreviewMaterial();
return false;
}
m_SelectedSplatMap = 0;
}
Texture2D texture = m_Splatmaps[m_SelectedSplatMap];
TextureFormat format = texture.format;
if ((format != TextureFormat.RGBA32 && format != TextureFormat.ARGB32 && format != TextureFormat.RGB24) || !texture.isReadable)
{
EditorUtility.DisplayDialog("Error", "The Texture format isn't compatable. Please change it to either RGBA32, ARGB32, or RGB24 and enable Read/Write.", "OK");
RevertPreviewMaterial();
return false;
}
return true;
}
void ResetSplatmaps()
{
var terrains = ToolboxHelper.GetSelectedTerrainsInScene();
int index = 0;
foreach (var terrain in terrains)
{
EditorUtility.DisplayProgressBar("Resetting Splatmaps", string.Format("Resetting splatmaps on terrain {0}", terrain.name), (index / (terrains.Count())));
ResetSplatmapsOnTerrain(terrain);
index++;
}
EditorUtility.ClearProgressBar();
}
void ResetSplatmapsOnTerrain(Terrain terrain)
{
TerrainData terrainData = terrain.terrainData;
if (terrainData.alphamapTextureCount < 1)
return;
var undoObjects = new List<UnityEngine.Object>();
undoObjects.Add(terrainData);
undoObjects.AddRange(terrainData.alphamapTextures);
Undo.RegisterCompleteObjectUndo(undoObjects.ToArray(), "Reset splatmaps");
Color splatDefault = new Color(1, 0, 0, 0); // red
Color splatZero = new Color(0, 0, 0, 0);
var pixelsFirst = terrainData.alphamapTextures[0].GetPixels();
for (int p = 0; p < pixelsFirst.Length; p++)
{
pixelsFirst[p] = splatDefault;
}
terrainData.alphamapTextures[0].SetPixels(pixelsFirst);
terrainData.alphamapTextures[0].Apply();
for (int i = 1; i < terrainData.alphamapTextureCount; i++)
{
var pixels = terrainData.alphamapTextures[i].GetPixels();
for (int j = 0; j < pixels.Length; j++)
{
pixels[j] = splatZero;
}
terrainData.alphamapTextures[i].SetPixels(pixels);
terrainData.alphamapTextures[i].Apply();
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
void ExportSplatmaps(UnityEngine.Object[] terrains)
{
if (terrains == null || terrains.Length == 0)
{
EditorUtility.DisplayDialog("Error", "No terrain(s) selected. Please select terrain tile(s) to continue.", "OK");
return;
}
if (m_Settings.SplatFolderPath == "")
{
EditorUtility.DisplayDialog("Error", "Empty export folder path. Please assign a path and try again.", "OK");
return;
}
if (!Directory.Exists(m_Settings.SplatFolderPath))
{
Directory.CreateDirectory(m_Settings.SplatFolderPath);
}
var fileExtension = m_Settings.SelectedFormat == UtilitySettings.ImageFormat.TGA ? ".tga" : ".png";
int index = 0;
foreach (var t in terrains)
{
var terrain = t as Terrain;
EditorUtility.DisplayProgressBar("Exporting Splatmaps", string.Format("Exporting splatmaps on terrain {0}", terrain.name), (index / (terrains.Count())));
TerrainData data = terrain.terrainData;
for (var i = 0; i < data.alphamapTextureCount; i++)
{
Texture2D tex = data.alphamapTextures[i];
byte[] bytes;
if (m_Settings.SelectedFormat == UtilitySettings.ImageFormat.TGA)
{
bytes = tex.EncodeToTGA();
}
else
{
bytes = tex.EncodeToPNG();
}
string filename = terrain.name + "_splatmap_" + i + fileExtension;
File.WriteAllBytes($"{m_Settings.SplatFolderPath}/{filename}", bytes);
}
index++;
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.ClearProgressBar();
}
internal void ExportHeightmaps(UnityEngine.Object[] terrains)
{
if (terrains == null || terrains.Length == 0)
{
EditorUtility.DisplayDialog("Error", "No terrain(s) selected. Please select terrain tile(s) to continue.", "OK");
return;
}
if (m_Settings.HeightmapFolderPath == "")
{
EditorUtility.DisplayDialog("Error", "Empty export folder path. Please assign a path and try again.", "OK");
return;
}
if (!Directory.Exists(m_Settings.HeightmapFolderPath))
{
Directory.CreateDirectory(m_Settings.HeightmapFolderPath);
}
int index = 0;
foreach (var t in terrains)
{
var terrain = t as Terrain;
EditorUtility.DisplayProgressBar("Exporting Heightmaps", string.Format("Exporting heightmap on terrain {0}", terrain.name), (index / (terrains.Count())));
TerrainData terrainData = terrain.terrainData;
string fileName = terrain.name + "_heightmap";
string path = Path.Combine(m_Settings.HeightmapFolderPath, fileName);
switch (m_Settings.HeightFormat)
{
case Heightmap.Format.RAW:
ToolboxHelper.ExportTerrainHeightsToRawFile(terrainData, path, m_Settings.HeightmapDepth, m_Settings.FlipVertically, m_Settings.HeightmapByteOrder, new Vector2(m_Settings.ExportHeightRemapMin, m_Settings.ExportHeightRemapMax));
break;
default:
ToolboxHelper.ExportTerrainHeightsToTexture(terrainData, m_Settings.HeightFormat, path, m_Settings.FlipVertically, new Vector2(m_Settings.ExportHeightRemapMin, m_Settings.ExportHeightRemapMax));
break;
}
index++;
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.ClearProgressBar();
}
internal void RotateSplatmap()
{
if (!ValidatePreviewTexture())
return;
if (m_Settings.AdjustAllSplats)
{
for (int i = 0; i < m_SplatmapList.count; i++)
{
RotateTexture(m_Splatmaps[i]);
}
}
else
{
RotateTexture(m_Splatmaps[m_SelectedSplatMap]);
}
if (m_ShowSplatmapPreview)
m_PreviewIsDirty = true;
}
void RotateTexture(Texture2D texture)
{
Undo.RegisterCompleteObjectUndo(texture, "Rotate Texture");
Color32[] originalPixels;
Color32[] rotatedPixels;
originalPixels = texture.GetPixels32();
rotatedPixels = new Color32[originalPixels.Length];
//bool clockwise = m_Settings.RotationAdjust == UtilitySettings.RotationAdjustment.Clockwise ? true : false;
int width = texture.width;
int height = texture.height;
int rotatedIndex, originalIndex;
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
rotatedIndex = (col + 1) * height - row - 1;
if (m_Settings.RotationAdjust == UtilitySettings.RotationAdjustment.Clockwise)
{
originalIndex = originalPixels.Length - 1 - (row * width + col);
}
else
{
originalIndex = row * width + col;
}
//originalIndex = clockwise ? originalPixels.Length - 1 - (row * width + col) : row * width + col;
rotatedPixels[rotatedIndex] = originalPixels[originalIndex];
}
}
texture.SetPixels32(rotatedPixels);
texture.Apply();
}
internal void FlipSplatmap()
{
if (!ValidatePreviewTexture())
return;
bool horizontal = m_Settings.FlipAdjust == UtilitySettings.FlipAdjustment.Horizontal ? true : false;
if (m_Settings.AdjustAllSplats)
{
for (int i = 0; i < m_SplatmapList.count; i++)
{
ToolboxHelper.FlipTexture(m_Splatmaps[i], horizontal);
}
}
else
{
ToolboxHelper.FlipTexture(m_Splatmaps[m_SelectedSplatMap], horizontal);
}
if (m_ShowSplatmapPreview)
m_PreviewIsDirty = true;
}
void FlipTexture(Texture2D texture)
{
Undo.RegisterCompleteObjectUndo(texture, "Flip Texture");
Color32[] originalPixels;
Color32[] flippedPixels;
bool horizontal = m_Settings.FlipAdjust == UtilitySettings.FlipAdjustment.Horizontal ? true : false;
int difference;
int width;
int height;
int flippedIndex, originalIndex;
originalPixels = texture.GetPixels32();
flippedPixels = new Color32[originalPixels.Length];
width = texture.width;
height = texture.height;
difference = width - height;
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
flippedIndex = horizontal ? (((width - 1) - row) * height + col) - difference : (height - 1) + (row * width) - col - difference;
originalIndex = row * width + col;
if (flippedIndex < 0)
continue;
flippedPixels[flippedIndex] = originalPixels[originalIndex];
}
}
texture.SetPixels32(flippedPixels);
texture.Apply();
}
internal void RevertPreviewMaterial()
{
if (m_PreviewMaterial == null)
{
GetAndSetActiveRenderPipelineSettings();
}
m_PreviewMaterial.DisableKeyword("_SPLATMAP_PREVIEW");
for (int i = 0; i < m_Terrains.Length; i++)
{
if (m_Terrains[i] != null)
{
#if UNITY_2019_2_OR_NEWER
m_Terrains[i].materialTemplate = m_TerrainMaterials[i];
#else
m_Terrains[i].materialType = m_TerrainMaterialType;
if (m_TerrainMaterialType == Terrain.MaterialType.Custom)
{
m_Terrains[i].materialTemplate = m_TerrainMaterials[i];
}
else if (m_TerrainMaterialType == Terrain.MaterialType.BuiltInLegacySpecular)
{
m_Terrains[i].legacyShininess = m_TerrainLegacyShininess;
m_Terrains[i].legacySpecular = m_TerrainLegacySpecular;
m_Terrains[i].materialTemplate = null;
}
else
{
m_Terrains[i].materialTemplate = null;
}
#endif
}
}
m_ShowSplatmapPreview = false;
}
void GetAndSetActiveRenderPipelineSettings()
{
m_Terrains = ToolboxHelper.GetSelectedTerrainsInScene();
UpdateCachedTerrainMaterials();
ToolboxHelper.RenderPipeline currentPipeline = ToolboxHelper.GetRenderPipeline();
if (m_ActiveRenderPipeline == currentPipeline)
return;
m_ActiveRenderPipeline = currentPipeline;
Shader vizShader = Shader.Find("Hidden/Builtin_TerrainVisualization");
switch (m_ActiveRenderPipeline)
{
case ToolboxHelper.RenderPipeline.HD:
m_MaxLayerCount = k_MaxLayerHD;
m_MaxSplatmapCount = k_MaxSplatmapHD;
vizShader = Shader.Find("Hidden/HDRP_TerrainVisualization");
break;
case ToolboxHelper.RenderPipeline.LW:
// this is a temp setting, in LW if height based blending or opacity as density enabled,
// we only support 4 layers and 1 splatmap
// this will get checked when applying changes to each terrain
// To-do: update max allowance check once LW terrain checked in
m_MaxLayerCount = k_MaxNoLimit;
m_MaxSplatmapCount = k_MaxNoLimit;
vizShader = Shader.Find("Hidden/LWRP_TerrainVisualization");
break;
case ToolboxHelper.RenderPipeline.Universal:
m_MaxLayerCount = k_MaxNoLimit;
m_MaxSplatmapCount = k_MaxNoLimit;
vizShader = Shader.Find("Hidden/Universal_TerrainVisualization");
break;
default:
m_MaxLayerCount = k_MaxNoLimit;
m_MaxSplatmapCount = k_MaxNoLimit;
if (m_Terrains == null || m_Terrains.Length == 0)
{
break;
}
#if UNITY_2019_2_OR_NEWER
#else
m_TerrainMaterialType = m_Terrains[0].materialType;
if (m_TerrainMaterialType == Terrain.MaterialType.BuiltInLegacySpecular)
{
m_TerrainLegacyShininess = m_Terrains[0].legacyShininess;
m_TerrainLegacySpecular = m_Terrains[0].legacySpecular;
}
#endif
break;
}
m_PreviewMaterial = TerrainToolboxVisualization.GetVizMaterial(vizShader);
}
/// <summary>
/// Updates an array of materials used to revert the selected terrain material from
/// the preview material back to its original Terrain material.
/// </summary>
void UpdateCachedTerrainMaterials()
{
m_TerrainMaterials.Clear();
foreach (Terrain terrain in m_Terrains)
{
m_TerrainMaterials.Add(terrain.materialTemplate);
}
}
void CreateNewPalette()
{
string filePath = EditorUtility.SaveFilePanelInProject("Create New Palette", "New Layer Palette.asset", "asset", "");
if (string.IsNullOrEmpty(filePath))
{
return;
}
m_SelectedLayerPalette = null;
var newPalette = ScriptableObject.CreateInstance<TerrainPalette>();
foreach (var layer in m_PaletteLayers)
{
newPalette.PaletteLayers.Add(layer.AssignedLayer);
}
AssetDatabase.CreateAsset(newPalette, filePath);
m_SelectedLayerPalette = newPalette;
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
void LoadPalette()
{
if (!GetPalette())
return;
m_PaletteLayers.Clear();
foreach (var layer in m_SelectedLayerPalette.PaletteLayers)
{
Layer newLayer = new Layer();
newLayer.AssignedLayer = layer;
newLayer.IsSelected = true;
m_PaletteLayers.Add(newLayer);
}
}
bool GetPalette()
{
if (m_SelectedLayerPalette == null)
{
if (EditorUtility.DisplayDialog("Error", "No layer palette found, create a new one?", "OK", "Cancel"))
{
CreateNewPalette();
return true;
}
else
{
return false;
}
}
return true;
}
public void SaveSettings()
{
if (m_SelectedLayerPalette != null)
{
m_Settings.PalettePath = AssetDatabase.GetAssetPath(m_SelectedLayerPalette);
}
else
{
m_Settings.PalettePath = string.Empty;
}
string filePath = ToolboxHelper.GetPrefFilePath(ToolboxHelper.ToolboxPrefsUtility);
string utilitySettings = JsonUtility.ToJson(m_Settings);
File.WriteAllText(filePath, utilitySettings);
RevertPreviewMaterial();
SceneView.RepaintAll();
}
public void LoadSettings()
{
string filePath = ToolboxHelper.GetPrefFilePath(ToolboxHelper.ToolboxPrefsUtility);
if (File.Exists(filePath))
{
string utilitySettingsData = File.ReadAllText(filePath);
JsonUtility.FromJsonOverwrite(utilitySettingsData, m_Settings);
}
if (m_Settings.PalettePath == string.Empty)
{
m_SelectedLayerPalette = null;
}
else
{
m_SelectedLayerPalette = AssetDatabase.LoadAssetAtPath(m_Settings.PalettePath, typeof(TerrainPalette)) as TerrainPalette;
}
GetAndSetActiveRenderPipelineSettings();
EditorSceneManager.sceneSaving += OnSceneSaving;
EditorSceneManager.sceneOpened += OnSceneOpened;
EditorApplication.playModeStateChanged += OnPlayModeChanged;
}
public void OnLostFocus()
{
if (!m_ShowSplatmapPreview)
return;
string mouseOverWindow;
try
{
mouseOverWindow = EditorWindow.mouseOverWindow.ToString();
}
catch
{
mouseOverWindow = null;
}
if (mouseOverWindow == null
|| mouseOverWindow != " (UnityEditor.TerrainTools.TerrainToolboxWindow)"
&& mouseOverWindow != " (UnityEditor.SceneView)")
{
RevertPreviewMaterial();
}
}
void OnSceneSaving(UnityEngine.SceneManagement.Scene scene, string path)
{
if (m_ShowSplatmapPreview)
{
RevertPreviewMaterial();
}
}
void OnSceneOpened(UnityEngine.SceneManagement.Scene scene, OpenSceneMode open)
{
m_PaletteLayers.Clear();
}
void OnPlayModeChanged(PlayModeStateChange state)
{
RevertPreviewMaterial();
}
}
}