forked from BilalY/Rasagar
1084 lines
42 KiB
C#
1084 lines
42 KiB
C#
// Procedural Terrain Painter by Staggart Creations http://staggart.xyz
|
|
// Copyright protected under Unity Asset Store EULA
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
#if __MICROSPLAT__
|
|
using System.Reflection;
|
|
using JBooth.MicroSplat;
|
|
#endif
|
|
using UnityEditor;
|
|
using UnityEditor.AnimatedValues;
|
|
using UnityEditorInternal;
|
|
using UnityEngine;
|
|
|
|
namespace sc.terrain.proceduralpainter
|
|
{
|
|
[CustomEditor(typeof(TerrainPainter))]
|
|
public class TerrainPainterInspector : Editor
|
|
{
|
|
TerrainPainter script;
|
|
private SerializedProperty layerSettings;
|
|
private SerializedProperty resolution;
|
|
private SerializedProperty colorMapResolution;
|
|
private SerializedProperty terrains;
|
|
private bool hasMissingTerrains;
|
|
private SerializedProperty autoRepaint;
|
|
private Dictionary<LayerSettings, ReorderableList> m_modifierList = new Dictionary<LayerSettings, ReorderableList>();
|
|
private ReorderableList curModList;
|
|
|
|
#if VEGETATION_STUDIO_PRO
|
|
private SerializedProperty refreshVegetationOnPaint;
|
|
#endif
|
|
#if __MICROSPLAT__
|
|
private SerializedProperty msTexArray;
|
|
private Editor msTexArrayEditor;
|
|
private bool requiresConfigRebuild;
|
|
#endif
|
|
private bool requiresRepaint;
|
|
private bool heatmapEnabled
|
|
{
|
|
get => SessionState.GetBool("PTP_HEATMAP", false);
|
|
set => SessionState.SetBool("PTP_HEATMAP", value);
|
|
}
|
|
private bool visualizeContour;
|
|
private bool visualizeTiling;
|
|
|
|
private Editor layerEditor;
|
|
private bool editLayerSettings;
|
|
private AnimBool editLayerSettingsAnim;
|
|
|
|
private RenderTexture[] heatmaps;
|
|
|
|
private Vector2 scrollview;
|
|
|
|
private int selectedLayerID
|
|
{
|
|
get { return SessionState.GetInt("PTP_SELECTED_LAYER", -1); }
|
|
set { SessionState.SetInt("PTP_SELECTED_LAYER", value);}
|
|
}
|
|
private int selectedModifierIndex;
|
|
ReorderableList m_LayerList;
|
|
|
|
private enum Tab
|
|
{
|
|
Layers,
|
|
Settings
|
|
}
|
|
|
|
private static Tab CurrentTab
|
|
{
|
|
get { return (Tab)SessionState.GetInt("PTP_TAB", 0); }
|
|
set { SessionState.SetInt("PTP_TAB", (int)value); }
|
|
}
|
|
|
|
TerrainLayer m_PickedLayer;
|
|
Texture2D m_PickedTexture;
|
|
Texture2D m_layerTexture;
|
|
|
|
int m_layerPickerWindowID = -1;
|
|
int m_texturePickerWindowID = -1;
|
|
|
|
// layer list view
|
|
const int kElementHeight = 40;
|
|
const int kElementObjectFieldHeight = 16;
|
|
const int kElementPadding = 2;
|
|
const int kElementObjectFieldWidth = 140;
|
|
const int kElementToggleWidth = 20;
|
|
const int kElementThumbSize = 40;
|
|
|
|
private string iconPrefix => EditorGUIUtility.isProSkin ? "d_" : "";
|
|
|
|
private void OnEnable()
|
|
{
|
|
script = (TerrainPainter) target;
|
|
TerrainPainter.Current = script;
|
|
|
|
if(script.terrains != null) script.RecalculateBounds();
|
|
|
|
terrains = serializedObject.FindProperty("terrains");
|
|
autoRepaint = serializedObject.FindProperty("autoRepaint");
|
|
layerSettings = serializedObject.FindProperty("layerSettings");
|
|
resolution = serializedObject.FindProperty("splatmapResolution");
|
|
colorMapResolution = serializedObject.FindProperty("colorMapResolution");
|
|
#if VEGETATION_STUDIO_PRO
|
|
refreshVegetationOnPaint = serializedObject.FindProperty("refreshVegetationOnPaint");
|
|
#endif
|
|
#if __MICROSPLAT__
|
|
msTexArray = serializedObject.FindProperty("msTexArray");
|
|
#endif
|
|
ModifierEditor.RefreshModifiers();
|
|
|
|
RefreshLayerList();
|
|
RefreshModifierLists();
|
|
|
|
editLayerSettingsAnim = new AnimBool(editLayerSettings);
|
|
editLayerSettingsAnim.valueChanged.AddListener(this.Repaint);
|
|
editLayerSettingsAnim.speed = 4f;
|
|
|
|
if(script.terrains != null) hasMissingTerrains = Utilities.HasMissingTerrain(script.terrains);
|
|
|
|
#if UNITY_2019_1_OR_NEWER
|
|
SceneView.duringSceneGui += OnSceneRepaint;
|
|
#else
|
|
SceneView.onSceneGUIDelegate += OnSceneRepaint;
|
|
#endif
|
|
}
|
|
|
|
private void RefreshLayerList()
|
|
{
|
|
m_LayerList = null;
|
|
|
|
if (m_LayerList == null)
|
|
{
|
|
m_LayerList = new ReorderableList(script.layerSettings, typeof(LayerSettings), true,
|
|
false, false, false);
|
|
m_LayerList.elementHeight = kElementHeight;
|
|
//m_LayerList.drawHeaderCallback = DrawHeader;
|
|
m_LayerList.drawElementCallback = DrawLayerElement;
|
|
m_LayerList.onSelectCallback = OnSelectLayerElement;
|
|
m_LayerList.drawElementBackgroundCallback = DrawLayerBackground;
|
|
m_LayerList.onReorderCallbackWithDetails = OnReorderLayerElement;
|
|
//m_LayerList.onAddDropdownCallback = OnLayerAddButton;
|
|
//m_LayerList.onRemoveCallback = OnLayerRemoveButton;
|
|
|
|
m_LayerList.headerHeight = 0f;
|
|
m_LayerList.footerHeight = 0f;
|
|
|
|
m_LayerList.index = selectedLayerID;
|
|
}
|
|
|
|
m_LayerList.showDefaultBackground = false;
|
|
}
|
|
|
|
private void RefreshModifierLists()
|
|
{
|
|
m_modifierList.Clear();
|
|
|
|
foreach (LayerSettings s in script.layerSettings)
|
|
{
|
|
ReorderableList layerModifiers = new ReorderableList(s.modifierStack, typeof(Modifier));
|
|
layerModifiers.draggable = true;
|
|
layerModifiers.elementHeight = 25;
|
|
layerModifiers.drawHeaderCallback = DrawModifierHeader;
|
|
layerModifiers.displayAdd = true;
|
|
layerModifiers.displayRemove = true;
|
|
layerModifiers.drawElementCallback = DrawModifierElement;
|
|
layerModifiers.onSelectCallback = OnSelectModifier;
|
|
layerModifiers.drawElementBackgroundCallback = DrawModifierBackground;
|
|
layerModifiers.onRemoveCallback = OnRemoveModifier;
|
|
layerModifiers.onReorderCallbackWithDetails = OnReorderModifier;
|
|
layerModifiers.onAddDropdownCallback = OnAddModifierDropDown;
|
|
m_modifierList.Add(s, layerModifiers);
|
|
}
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
#if UNITY_2019_1_OR_NEWER
|
|
SceneView.duringSceneGui -= OnSceneRepaint;
|
|
#else
|
|
SceneView.onSceneGUIDelegate -= OnSceneRepaint;
|
|
#endif
|
|
|
|
//This is necessary, since painting data is serialized asynchronously.
|
|
//- If the scene were to be closed the TerrainAPI looses reference to the terrains
|
|
//- Doing manual painting after using the Terrain Painter, then saving, will loose those modifications
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
EditorGUILayout.LabelField("Version " + TerrainPainter.Version, EditorStyles.centeredGreyMiniLabel);
|
|
GUILayout.Space(5f);
|
|
|
|
//Terrains not yet assigned? Force settings tab
|
|
if (terrains.arraySize > 0 && !hasMissingTerrains)
|
|
{
|
|
CurrentTab = (Tab)GUILayout.Toolbar((int)CurrentTab, new GUIContent[]
|
|
{
|
|
#if UNITY_2019_3_OR_NEWER
|
|
new GUIContent("Layers", EditorGUIUtility.IconContent(iconPrefix + "Terrain Icon").image),
|
|
#else
|
|
//Old UI does not have a dark skin version for the terrain icon
|
|
new GUIContent("Layers", EditorGUIUtility.IconContent("Terrain Icon").image),
|
|
#endif
|
|
new GUIContent("Settings", EditorGUIUtility.IconContent(iconPrefix + "SettingsIcon").image)
|
|
}, GUILayout.Height(30f));
|
|
}
|
|
else
|
|
{
|
|
CurrentTab = Tab.Settings;
|
|
}
|
|
|
|
//Default
|
|
requiresRepaint = false;
|
|
#if __MICROSPLAT__
|
|
requiresConfigRebuild = false;
|
|
#endif
|
|
|
|
if(hasMissingTerrains) EditorGUILayout.HelpBox("One or more terrains are missing", MessageType.Error);
|
|
|
|
serializedObject.Update();
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
switch (CurrentTab)
|
|
{
|
|
case Tab.Layers:
|
|
DrawLayers();
|
|
break;
|
|
case Tab.Settings:
|
|
DrawSettings();
|
|
break;
|
|
}
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
|
|
#if __MICROSPLAT__
|
|
if(requiresConfigRebuild && script.msTexArray) TextureArrayConfigEditor.CompileConfig(script.msTexArray);
|
|
#endif
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
using (new EditorGUILayout.HorizontalScope())
|
|
{
|
|
GUILayout.FlexibleSpace();
|
|
if(GUILayout.Button(new GUIContent(" Force Repaint ", "Trigger a complete repaint operation. Typically needed if the terrain was modified in some way, yet not changes are made in the Terrain Painter component")))
|
|
{
|
|
requiresRepaint = true;
|
|
}
|
|
GUILayout.FlexibleSpace();
|
|
}
|
|
|
|
if (requiresRepaint)
|
|
{
|
|
script.RepaintAll();
|
|
UpdateHeatmap();
|
|
}
|
|
|
|
EditorGUILayout.LabelField("- Staggart Creations -", EditorStyles.centeredGreyMiniLabel);
|
|
}
|
|
|
|
private void DrawSettings()
|
|
{
|
|
if (terrains.arraySize == 0 || hasMissingTerrains)
|
|
{
|
|
terrains.isExpanded = true;
|
|
}
|
|
|
|
using (new EditorGUILayout.VerticalScope("Box"))
|
|
{
|
|
serializedObject.Update();
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUILayout.PropertyField(terrains, new GUIContent("Terrains (" + terrains.arraySize + ")"));
|
|
if (EditorGUI.EndChangeCheck()) serializedObject.ApplyModifiedProperties();
|
|
|
|
using (new EditorGUILayout.HorizontalScope())
|
|
{
|
|
GUILayout.FlexibleSpace();
|
|
|
|
if (GUILayout.Button("Add active terrains"))
|
|
{
|
|
//Important! Don't repaint of the terrains are new, current splatmaps would be wiped without user warning!
|
|
if(terrains.arraySize > 0) requiresRepaint = true;
|
|
|
|
script.SetTargetTerrains(Terrain.activeTerrains);
|
|
|
|
hasMissingTerrains = false;
|
|
|
|
EditorUtility.SetDirty(target);
|
|
}
|
|
|
|
if (terrains.arraySize > 0)
|
|
{
|
|
if (GUILayout.Button("Clear"))
|
|
{
|
|
script.terrains = new Terrain[0];
|
|
|
|
hasMissingTerrains = false;
|
|
|
|
EditorUtility.SetDirty(target);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (terrains.arraySize == 0)
|
|
{
|
|
EditorGUILayout.HelpBox("Assign terrains to paint on first", MessageType.Info);
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.Space();
|
|
|
|
serializedObject.Update();
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
EditorGUILayout.PropertyField(autoRepaint);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
serializedObject.ApplyModifiedProperties();
|
|
script.SetAutoRepaint(autoRepaint.boolValue);
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
EditorGUILayout.PropertyField(resolution);
|
|
EditorGUILayout.PropertyField(colorMapResolution);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
requiresRepaint = true;
|
|
}
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
#if VEGETATION_STUDIO_PRO
|
|
EditorGUILayout.LabelField("Vegetation Studio Pro", EditorStyles.boldLabel);
|
|
EditorGUILayout.PropertyField(refreshVegetationOnPaint);
|
|
#endif
|
|
|
|
using (new EditorGUILayout.HorizontalScope())
|
|
{
|
|
EditorGUILayout.LabelField("Bounds");
|
|
|
|
if (GUILayout.Button(new GUIContent("Recalculate bounds",
|
|
"If the terrain size has changed, the bounds must be recalculated. The white box must encapsulate all terrains"),
|
|
GUILayout.MaxWidth(150f)))
|
|
{
|
|
script.RecalculateBounds();
|
|
requiresRepaint = true;
|
|
EditorUtility.SetDirty(target);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnSceneRepaint(SceneView sceneView)
|
|
{
|
|
if (layerSettings.arraySize != 0 && terrains.arraySize != 0) //Not yet initialized OnEnable
|
|
{
|
|
if (heatmapEnabled && selectedLayerID >= 0)
|
|
{
|
|
if (heatmaps == null || script.terrains.Length != heatmaps.Length) UpdateHeatmap();
|
|
|
|
for (int i = 0; i < script.terrains.Length; i++)
|
|
{
|
|
HeatmapPreview.Draw(script.terrains[i], script.layerSettings[m_LayerList.index].layer, heatmaps[i], visualizeContour, visualizeTiling);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!heatmapEnabled && heatmaps != null)
|
|
{
|
|
for (int i = 0; i < heatmaps.Length; i++)
|
|
{
|
|
DestroyImmediate(heatmaps[i]);
|
|
}
|
|
|
|
heatmaps = null;
|
|
}
|
|
|
|
if (CurrentTab == Tab.Settings)
|
|
{
|
|
Handles.DrawWireCube(script.bounds.center, script.bounds.size);
|
|
}
|
|
}
|
|
|
|
private void UpdateHeatmap()
|
|
{
|
|
if(heatmapEnabled) HeatmapPreview.CreateHeatmaps(script.terrains, (m_LayerList.count-1) - selectedLayerID, ref heatmaps);
|
|
}
|
|
|
|
#region Layers
|
|
private float previewScaleMultiplier
|
|
{
|
|
get => EditorPrefs.GetFloat("PTP_UI_LAYER_COUNT", 4f);
|
|
set => EditorPrefs.SetFloat("PTP_UI_LAYER_COUNT", value);
|
|
}
|
|
private Rect sliderRect;
|
|
|
|
private void DrawLayers()
|
|
{
|
|
#if __MICROSPLAT__
|
|
DrawMicroSplatField();
|
|
#else
|
|
EditorGUILayout.Space();
|
|
#endif
|
|
|
|
sliderRect = EditorGUILayout.GetControlRect();
|
|
sliderRect.x += (EditorGUIUtility.currentViewWidth * 0.75f);
|
|
sliderRect.width *= 0.2f;
|
|
|
|
previewScaleMultiplier = GUI.HorizontalSlider(sliderRect, previewScaleMultiplier, 4f, m_LayerList.count);
|
|
|
|
scrollview = EditorGUILayout.BeginScrollView(scrollview, GUILayout.Height((kElementHeight * previewScaleMultiplier) + 3f));
|
|
|
|
m_LayerList.DoLayoutList();
|
|
|
|
EditorGUILayout.EndScrollView();
|
|
|
|
// Control buttons
|
|
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
|
|
{
|
|
EditorGUI.BeginDisabledGroup(m_LayerList.index < 0 || m_LayerList.count == 0);
|
|
{
|
|
heatmapEnabled = GUILayout.Toggle(heatmapEnabled, new GUIContent(" Heatmap", EditorGUIUtility.IconContent(iconPrefix + "winbtn_mac_close").image), EditorStyles.toolbarButton, GUILayout.MaxWidth(110f));
|
|
if (heatmapEnabled)
|
|
{
|
|
EditorGUILayout.LabelField("Countour", GUILayout.MaxWidth(60f));
|
|
visualizeContour = EditorGUILayout.Toggle(visualizeContour, GUILayout.MaxWidth(30f));
|
|
EditorGUILayout.LabelField("Tiling", GUILayout.MaxWidth(35f));
|
|
visualizeTiling = EditorGUILayout.Toggle(visualizeTiling, GUILayout.MaxWidth(30f));
|
|
}
|
|
|
|
editLayerSettings = GUILayout.Toggle(editLayerSettings, new GUIContent(" Edit layer", EditorGUIUtility.IconContent(iconPrefix + "editicon.sml").image), EditorStyles.toolbarButton, GUILayout.MaxWidth(90f));
|
|
}
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
|
|
GUILayout.FlexibleSpace();
|
|
|
|
EditorGUI.BeginDisabledGroup(layerSettings.arraySize >= 32); //Maximum realistic number of terrain layers
|
|
#if UNITY_2019_3_OR_NEWER
|
|
var newIcon = EditorGUIUtility.IconContent(iconPrefix + "DefaultAsset Icon").image;
|
|
#else
|
|
var newIcon = EditorGUIUtility.IconContent("DefaultAsset Icon").image;
|
|
#endif
|
|
if (GUILayout.Button(new GUIContent("", newIcon, "New terrain layer from texture"), EditorStyles.toolbarButton, GUILayout.MaxWidth(32f)))
|
|
{
|
|
m_texturePickerWindowID = EditorGUIUtility.GetControlID(FocusType.Passive) + 201;
|
|
EditorGUIUtility.ShowObjectPicker<Texture2D>(null, false, "", m_texturePickerWindowID);
|
|
}
|
|
|
|
if (GUILayout.Button(new GUIContent("",
|
|
EditorGUIUtility.IconContent(iconPrefix + "Toolbar Plus More")
|
|
.image, "Add terrain layer from project"), EditorStyles.toolbarButton))
|
|
{
|
|
m_layerPickerWindowID = EditorGUIUtility.GetControlID(FocusType.Passive) + 200;
|
|
EditorGUIUtility.ShowObjectPicker<TerrainLayer>(null, false, "", m_layerPickerWindowID);
|
|
}
|
|
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
EditorGUI.BeginDisabledGroup(m_LayerList.index < 0 || m_LayerList.count == 0);
|
|
if (GUILayout.Button(new GUIContent("", EditorGUIUtility.IconContent(iconPrefix + "TreeEditor.Trash").image,
|
|
"Remove selected layer"), EditorStyles.toolbarButton))
|
|
{
|
|
if (!EditorUtility.DisplayDialog("Terrain Painter",
|
|
"Removing a layer cannot be undone, settings will be lost",
|
|
"Ok","Cancel")) return;
|
|
|
|
RemoveLayerElement(m_LayerList.index);
|
|
}
|
|
EditorGUI.EndDisabledGroup();
|
|
}
|
|
|
|
if (script.layerSettings.ElementAtOrDefault(selectedLayerID) != null)
|
|
{
|
|
editLayerSettingsAnim.target = editLayerSettings;
|
|
//TODO: Fix error about mismatching layout
|
|
if (EditorGUILayout.BeginFadeGroup(editLayerSettingsAnim.faded))
|
|
{
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.LabelField("Layer settings", EditorStyles.boldLabel);
|
|
|
|
#if UNITY_2019_2_OR_NEWER
|
|
Editor.CreateCachedEditor(script.layerSettings.ElementAtOrDefault(selectedLayerID).layer, typeof(TerrainLayerInspector), ref layerEditor);
|
|
#else
|
|
//TerrainLayerInspector is internal
|
|
layerEditor = Editor.CreateEditor(script.layerSettings.ElementAtOrDefault(selectedLayerID).layer);
|
|
#endif
|
|
layerEditor.OnInspectorGUI();
|
|
}
|
|
EditorGUILayout.EndFadeGroup();
|
|
}
|
|
|
|
if (m_LayerList.count == 0)
|
|
{
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.HelpBox("All existing terrain layers and painting will be cleared when adding the first layer!", MessageType.Warning);
|
|
}
|
|
|
|
ObjectPickerActions();
|
|
|
|
GUILayout.Space(17f);
|
|
|
|
DrawLayerModifierStack();
|
|
}
|
|
|
|
private void DrawMicroSplatField()
|
|
{
|
|
#if __MICROSPLAT__
|
|
using (new EditorGUILayout.HorizontalScope())
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUILayout.PropertyField(msTexArray, new GUIContent("MicroSplat texture array", msTexArray.tooltip));
|
|
if (EditorGUI.EndChangeCheck()) serializedObject.ApplyModifiedProperties();
|
|
|
|
EditorGUI.BeginDisabledGroup(msTexArray.objectReferenceValue == null);
|
|
if (GUILayout.Button(new GUIContent("Copy to", "Copy all the terrain layers to the MicroSplat array." +
|
|
"\n\nThis also removes any textures from the array that aren't configured here" +
|
|
"\n\nNote that additional maps (AO, Height, smoothness) aren't taken into account because regular terrain layers don't have them"), EditorStyles.miniButton, GUILayout.MaxWidth(70f)))
|
|
{
|
|
MicroSplatCopyLayersToArray();
|
|
}
|
|
EditorGUI.EndDisabledGroup();
|
|
}
|
|
|
|
if (msTexArray.objectReferenceValue && script.msTexArray.sourceTextures.Count != script.layerSettings.Count)
|
|
{
|
|
EditorGUILayout.HelpBox("The amount of textures in the array, and layers configured here doesn't match up.\n\nYou can choose to copy all the current layers into the array", MessageType.Error);
|
|
if (GUILayout.Button("Apply layers to array"))
|
|
{
|
|
MicroSplatCopyLayersToArray();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private void ObjectPickerActions()
|
|
{
|
|
// Add existing layer
|
|
if (Event.current.commandName == "ObjectSelectorClosed" &&
|
|
EditorGUIUtility.GetObjectPickerControlID() == m_layerPickerWindowID)
|
|
{
|
|
m_PickedLayer = (TerrainLayer) EditorGUIUtility.GetObjectPickerObject();
|
|
m_layerPickerWindowID = -1;
|
|
|
|
if (m_PickedLayer)
|
|
{
|
|
|
|
var exists = false;
|
|
|
|
foreach (LayerSettings s in script.layerSettings)
|
|
{
|
|
if (s.layer == m_PickedLayer) exists = true;
|
|
}
|
|
|
|
if (exists)
|
|
{
|
|
EditorUtility.DisplayDialog("Terrain Painter", "Terrain layer already exists", "Ok");
|
|
return;
|
|
}
|
|
}
|
|
|
|
script.CreateSettingsForLayer(m_PickedLayer);
|
|
|
|
MicroSplatAdd(m_PickedLayer);
|
|
|
|
RefreshLayerList();
|
|
RefreshModifierLists();
|
|
|
|
//Auto-select new layer
|
|
m_LayerList.index = 0;
|
|
m_LayerList.onSelectCallback.Invoke(m_LayerList);
|
|
scrollview.y = 0;
|
|
|
|
EditorUtility.SetDirty(target);
|
|
|
|
requiresRepaint = true;
|
|
}
|
|
|
|
// New layer creation
|
|
if (Event.current.commandName == "ObjectSelectorClosed" &&
|
|
EditorGUIUtility.GetObjectPickerControlID() == m_texturePickerWindowID)
|
|
{
|
|
m_PickedTexture = (Texture2D) EditorGUIUtility.GetObjectPickerObject();
|
|
m_texturePickerWindowID = -1;
|
|
|
|
if (m_PickedTexture == null) return;
|
|
|
|
TerrainLayer newLayer = CreateLayerFromTexture(m_PickedTexture);
|
|
|
|
if (newLayer == null) return;
|
|
|
|
script.CreateSettingsForLayer(newLayer);
|
|
|
|
MicroSplatAdd(newLayer);
|
|
|
|
RefreshLayerList();
|
|
RefreshModifierLists();
|
|
|
|
//Auto-select new layer
|
|
m_LayerList.index = 0;
|
|
m_LayerList.onSelectCallback.Invoke(m_LayerList);
|
|
scrollview.y = 0;
|
|
|
|
EditorUtility.SetDirty(target);
|
|
|
|
requiresRepaint = true;
|
|
}
|
|
}
|
|
|
|
private void MicroSplatCopyLayersToArray()
|
|
{
|
|
#if __MICROSPLAT__
|
|
if (script.msTexArray)
|
|
{
|
|
script.msTexArray.sourceTextures.Clear();
|
|
|
|
//For safety, may have switched scene with an entirely different TerrainPainter component
|
|
script.SetTerrainLayers();
|
|
|
|
TextureArrayConfigEditor.GetFromTerrain(script.msTexArray, script.terrains[0]);
|
|
|
|
requiresConfigRebuild = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private void MicroSplatAdd(TerrainLayer newLayer)
|
|
{
|
|
#if __MICROSPLAT__
|
|
if (script.msTexArray)
|
|
{
|
|
var entry = new TextureArrayConfig.TextureEntry();
|
|
if (script.msTexArray.sourceTextures.Count > 0)
|
|
{
|
|
entry.aoChannel = script.msTexArray.sourceTextures[0].aoChannel;
|
|
entry.heightChannel = script.msTexArray.sourceTextures[0].heightChannel;
|
|
entry.smoothnessChannel = script.msTexArray.sourceTextures[0].smoothnessChannel;
|
|
}
|
|
else
|
|
{
|
|
entry.aoChannel = TextureArrayConfig.TextureChannel.G;
|
|
entry.heightChannel = TextureArrayConfig.TextureChannel.G;
|
|
entry.smoothnessChannel = TextureArrayConfig.TextureChannel.G;
|
|
}
|
|
|
|
entry.diffuse = newLayer.diffuseTexture;
|
|
//Layer will be inserted at index 0, so for the "real" terrain layers, append to end
|
|
script.msTexArray.sourceTextures.Add(entry);
|
|
|
|
requiresConfigRebuild = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private TerrainLayer CreateLayerFromTexture(Texture2D tex)
|
|
{
|
|
TerrainLayer newLayer = new TerrainLayer();
|
|
newLayer.diffuseTexture = m_PickedTexture;
|
|
newLayer.name = newLayer.diffuseTexture.name;
|
|
|
|
string assetPath =string.Empty;
|
|
assetPath = EditorUtility.SaveFilePanel("Asset destination folder", "Assets/", "New Layer", "asset");
|
|
if (assetPath.Length == 0) return null;
|
|
|
|
//Relative path in project
|
|
assetPath = assetPath.Substring(assetPath.IndexOf("Assets/"));
|
|
|
|
AssetDatabase.CreateAsset(newLayer, assetPath);
|
|
|
|
newLayer = (TerrainLayer)AssetDatabase.LoadAssetAtPath(assetPath, typeof(TerrainLayer));
|
|
|
|
return newLayer;
|
|
}
|
|
|
|
private void DrawLayerBackground(Rect rect, int index, bool isactive, bool selected)
|
|
{
|
|
var prevColor = GUI.color;
|
|
var prevBgColor = GUI.backgroundColor;
|
|
|
|
GUI.color = index % 2 == 0
|
|
? Color.grey * (EditorGUIUtility.isProSkin ? 1f : 1.7f)
|
|
: Color.grey * (EditorGUIUtility.isProSkin ? 1.05f : 1.66f);
|
|
|
|
if (m_LayerList.index == index) GUI.color = EditorGUIUtility.isProSkin ? Color.grey * 1.1f : Color.grey * 1.5f;
|
|
|
|
//Selection outline
|
|
if (m_LayerList.index == index)
|
|
{
|
|
Rect outline = rect;
|
|
EditorGUI.DrawRect(outline, EditorGUIUtility.isProSkin ? Color.gray * 1.5f : Color.gray);
|
|
|
|
rect.x += 1;
|
|
rect.y += 1;
|
|
rect.width -= 2;
|
|
rect.height -= 2;
|
|
}
|
|
|
|
EditorGUI.DrawRect(rect, GUI.color);
|
|
|
|
GUI.color = prevColor;
|
|
GUI.backgroundColor = prevBgColor;
|
|
}
|
|
|
|
void DrawLayerElement(Rect rect, int index, bool selected, bool focused)
|
|
{
|
|
rect.y = rect.y + kElementPadding;
|
|
var rectButton = new Rect((rect.x + kElementPadding), rect.y + (kElementHeight / 4), kElementToggleWidth,
|
|
kElementToggleWidth);
|
|
var rectImage = new Rect((rectButton.x + kElementToggleWidth) + 5f, rect.y, kElementThumbSize, kElementThumbSize);
|
|
var rectObject = new Rect((rectImage.x + kElementThumbSize + 10), rect.y + (kElementHeight / 4),
|
|
kElementObjectFieldWidth, kElementObjectFieldHeight);
|
|
|
|
if (script.layerSettings.Count > 0 && script.layerSettings.ElementAtOrDefault(index) != null)
|
|
{
|
|
if (index < layerSettings.arraySize-1)
|
|
{
|
|
#if UNITY_2019_1_OR_NEWER
|
|
EditorGUI.Toggle(rectButton, new GUIContent(EditorGUIUtility.IconContent(script.layerSettings[index].enabled ? iconPrefix + "scenevis_visible_hover" : iconPrefix + "scenevis_hidden_hover").image), script.layerSettings[index].enabled, GUIStyle.none);
|
|
if (rectButton.Contains(Event.current.mousePosition) && Event.current.type == EventType.MouseDown &&
|
|
Event.current.button == 0)
|
|
{
|
|
script.layerSettings[index].enabled = !script.layerSettings[index].enabled;
|
|
|
|
requiresRepaint = true;
|
|
}
|
|
|
|
#else
|
|
EditorGUI.BeginChangeCheck();
|
|
script.layerSettings[index].enabled = EditorGUI.Toggle(rectButton, script.layerSettings[index].enabled);
|
|
if (EditorGUI.EndChangeCheck()) requiresRepaint = true;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
//Base layer is always enabled
|
|
script.layerSettings[index].enabled = true;
|
|
}
|
|
|
|
Texture2D icon = null;
|
|
if (script.layerSettings[index].layer != null)
|
|
{
|
|
icon = AssetPreview.GetAssetPreview(script.layerSettings[index].layer.diffuseTexture);
|
|
}
|
|
GUI.Box(rectImage, icon);
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
script.layerSettings[index].layer = EditorGUI.ObjectField(rectObject, script.layerSettings[index].layer, typeof(TerrainLayer), false) as TerrainLayer;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
OnChangeLayer(script.layerSettings[index].layer, index);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnSelectLayerElement(ReorderableList list)
|
|
{
|
|
selectedLayerID = list.index;
|
|
|
|
LayerSettings settings = script.layerSettings.ElementAtOrDefault(selectedLayerID);
|
|
m_modifierList.TryGetValue(settings, out curModList);
|
|
|
|
SelectModifier(curModList, 0);
|
|
|
|
//Refresh for current layer
|
|
UpdateHeatmap();
|
|
}
|
|
|
|
void OnChangeLayer(TerrainLayer terrainLayer, int index)
|
|
{
|
|
requiresRepaint = true;
|
|
|
|
script.SetTerrainLayers();
|
|
RefreshLayerList();
|
|
|
|
#if __MICROSPLAT__
|
|
if (script.msTexArray)
|
|
{
|
|
script.msTexArray.sourceTextures[index].diffuse = terrainLayer.diffuseTexture;
|
|
script.msTexArray.sourceTextures[index].normal = terrainLayer.normalMapTexture;
|
|
|
|
script.msTexArray.sourceTextures2[index].diffuse = terrainLayer.diffuseTexture;
|
|
script.msTexArray.sourceTextures2[index].normal = terrainLayer.normalMapTexture;
|
|
|
|
script.msTexArray.sourceTextures3[index].diffuse = terrainLayer.diffuseTexture;
|
|
script.msTexArray.sourceTextures3[index].normal = terrainLayer.normalMapTexture;
|
|
|
|
requiresConfigRebuild = true;
|
|
//other maps (AO, height, smoothness) aren't changed, touch luck
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void DrawLayerModifierStack()
|
|
{
|
|
if (layerSettings.arraySize == 0) return;
|
|
|
|
if (selectedLayerID == layerSettings.arraySize -1)
|
|
{
|
|
EditorGUILayout.HelpBox("Base layer has no configurable options, it fills the entire terrain." + (layerSettings.arraySize == 1 ? " \n\nAdd an additional terrain layer" : ""), MessageType.Info);
|
|
return;
|
|
}
|
|
|
|
if (script.layerSettings.ElementAtOrDefault(selectedLayerID) == null)
|
|
{
|
|
EditorGUILayout.HelpBox("Select a layer to modify its spawn rules", MessageType.Info);
|
|
return;
|
|
}
|
|
|
|
LayerSettings settings = script.layerSettings.ElementAtOrDefault(selectedLayerID);
|
|
m_modifierList.TryGetValue(settings, out curModList);
|
|
|
|
//Draw all modifierStack for the current layer
|
|
using (new EditorGUI.DisabledGroupScope(settings.enabled == false))
|
|
{
|
|
if (curModList != null)
|
|
{
|
|
curModList.DoLayoutList();
|
|
|
|
if(curModList.index < 0 && curModList.count > 0) EditorGUILayout.HelpBox("Select a modifier from the stack to edit its settings", MessageType.Info);
|
|
if(curModList.count == 0) EditorGUILayout.HelpBox("Add a modifier to create painting rules", MessageType.Info);
|
|
|
|
DrawModifierSettings(curModList.index);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void OnReorderLayerElement(ReorderableList list, int oldIndex, int newIndex)
|
|
{
|
|
script.SetTerrainLayers();
|
|
RefreshLayerList();
|
|
|
|
requiresRepaint = true;
|
|
|
|
UpdateHeatmap();
|
|
|
|
#if __MICROSPLAT__
|
|
if (script.msTexArray)
|
|
{
|
|
MethodInfo SwapEntryInfo = typeof(TextureArrayConfigEditor).GetMethod("SwapEntry", BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Editor.CreateCachedEditor(script.msTexArray, typeof(TextureArrayConfigEditor), ref msTexArrayEditor);
|
|
//Because layers are from bottom to top, reverse indices
|
|
SwapEntryInfo.Invoke(msTexArrayEditor, new object[] { script.msTexArray, (list.count-1) - oldIndex, (list.count-1) - newIndex });
|
|
|
|
requiresConfigRebuild = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void RemoveLayerElement(int index)
|
|
{
|
|
if (script.layerSettings.ElementAtOrDefault(index) == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
script.layerSettings.RemoveAt(index);
|
|
|
|
script.SetTerrainLayers();
|
|
RefreshLayerList();
|
|
|
|
EditorUtility.SetDirty(target);
|
|
|
|
requiresRepaint = true;
|
|
|
|
UpdateHeatmap();
|
|
|
|
#if __MICROSPLAT__
|
|
if (script.msTexArray)
|
|
{
|
|
MethodInfo RemoveInfo = typeof(TextureArrayConfigEditor).GetMethod("Remove", BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Editor.CreateCachedEditor(script.msTexArray, typeof(TextureArrayConfigEditor), ref msTexArrayEditor);
|
|
RemoveInfo.Invoke(msTexArrayEditor, new object[] { script.msTexArray, (script.layerSettings.Count) - index});
|
|
|
|
requiresConfigRebuild = true;
|
|
}
|
|
#endif
|
|
}
|
|
#endregion
|
|
|
|
#region Modifiers
|
|
|
|
private void SelectModifier(ReorderableList list, int index)
|
|
{
|
|
selectedModifierIndex = index;
|
|
list.index = index;
|
|
list.onSelectCallback.Invoke(curModList);
|
|
//list.GrabKeyboardFocus();
|
|
}
|
|
private void OnRemoveModifier(ReorderableList list)
|
|
{
|
|
if (!EditorUtility.DisplayDialog("Remove modifier", "This operation cannot be undone, settings will be lost",
|
|
"Ok", "Cancel")) return;
|
|
|
|
//get the related layer
|
|
LayerSettings layer = script.layerSettings.ElementAtOrDefault(selectedLayerID);
|
|
|
|
layer.modifierStack.RemoveAt(list.index);
|
|
RefreshModifierLists();
|
|
|
|
EditorUtility.SetDirty(target);
|
|
|
|
requiresRepaint = true;
|
|
}
|
|
|
|
private void DrawModifierHeader(Rect rect)
|
|
{
|
|
EditorGUI.LabelField(rect, "Modifier stack");
|
|
}
|
|
|
|
private void OnAddModifierDropDown(Rect buttonrect, ReorderableList list)
|
|
{
|
|
List<Modifier> currentModifierList = script.layerSettings.ElementAtOrDefault(selectedLayerID).modifierStack;
|
|
|
|
GenericMenu menu = new GenericMenu();
|
|
|
|
foreach (string item in ModifierEditor.ModifierNames)
|
|
{
|
|
menu.AddItem(new GUIContent(item), false, () => AddModifier(currentModifierList, list, item));
|
|
}
|
|
|
|
menu.ShowAsContext();
|
|
}
|
|
|
|
private void AddModifier(List<Modifier> currentModifierList, ReorderableList list, string typeName)
|
|
{
|
|
Type type = ModifierEditor.GetType(typeName);
|
|
|
|
Modifier m = (Modifier)CreateInstance(type);
|
|
m.label = typeName;
|
|
currentModifierList.Insert(0, m);
|
|
|
|
RefreshModifierLists();
|
|
|
|
LayerSettings settings = script.layerSettings.ElementAtOrDefault(selectedLayerID);
|
|
m_modifierList.TryGetValue(settings, out curModList);
|
|
//Auto select new
|
|
SelectModifier(curModList, 0);
|
|
|
|
requiresRepaint = true;
|
|
|
|
EditorUtility.SetDirty(target);
|
|
}
|
|
|
|
|
|
void OnSelectModifier(ReorderableList list)
|
|
{
|
|
selectedModifierIndex = list.index;
|
|
}
|
|
|
|
private void DrawModifierBackground(Rect rect, int index, bool isactive, bool isfocused)
|
|
{
|
|
var prevColor = GUI.color;
|
|
var prevBgColor = GUI.backgroundColor;
|
|
|
|
GUI.color = index % 2 == 0
|
|
? Color.grey * (EditorGUIUtility.isProSkin ? 1f : 1.7f)
|
|
: Color.grey * (EditorGUIUtility.isProSkin ? 1.05f : 1.66f);
|
|
|
|
|
|
//Selection outline (note: can't rely on isfocused. Focus and selection aren't the same thing)
|
|
if (index == selectedModifierIndex)
|
|
{
|
|
GUI.color = EditorGUIUtility.isProSkin ? Color.grey * 1.1f : Color.grey * 1.5f;
|
|
Rect outline = rect;
|
|
EditorGUI.DrawRect(outline, EditorGUIUtility.isProSkin ? Color.gray * 1.5f : Color.gray);
|
|
|
|
rect.x += 1;
|
|
rect.y += 1;
|
|
rect.width -= 2;
|
|
rect.height -= 2;
|
|
}
|
|
|
|
|
|
EditorGUI.DrawRect(rect, GUI.color);
|
|
|
|
GUI.color = prevColor;
|
|
GUI.backgroundColor = prevBgColor;
|
|
}
|
|
|
|
private void DrawModifierElement(Rect rect, int index, bool isactive, bool isfocused)
|
|
{
|
|
//Get modifierStack for current layer
|
|
List<Modifier> currentModifierList = script.layerSettings.ElementAtOrDefault(m_LayerList.index).modifierStack;
|
|
|
|
if (currentModifierList.ElementAtOrDefault(index) == null)
|
|
{
|
|
EditorGUILayout.LabelField("NULL!");
|
|
return;
|
|
}
|
|
|
|
Modifier m = currentModifierList[index];
|
|
|
|
rect.y = rect.y;
|
|
var rectButton = new Rect(10 + (rect.x + kElementPadding), rect.y + kElementPadding, kElementToggleWidth,
|
|
kElementToggleWidth);
|
|
var labelRect = new Rect(rect.x + rectButton.x - 10, rect.y+kElementPadding, 120, 17);
|
|
var blendModeRect = new Rect((labelRect.x + 120 + 10), rect.y+ kElementPadding, 80, 27);
|
|
var opacityRect = new Rect(blendModeRect.x + blendModeRect.width + kElementPadding + 10, rect.y+ kElementPadding, 0f, 17);
|
|
opacityRect.width = EditorGUIUtility.currentViewWidth - opacityRect.x - 30f;
|
|
|
|
m.label = EditorGUI.TextField(labelRect, m.label);
|
|
|
|
#if UNITY_2019_1_OR_NEWER
|
|
EditorGUI.Toggle(rectButton, new GUIContent(EditorGUIUtility.IconContent(m.enabled ? iconPrefix + "scenevis_visible_hover" : iconPrefix + "scenevis_hidden_hover").image, "Toggle visibility"), m.enabled, GUIStyle.none);
|
|
if (rectButton.Contains(Event.current.mousePosition) && Event.current.type == EventType.MouseDown &&
|
|
Event.current.button == 0)
|
|
{
|
|
m.enabled = !m.enabled;
|
|
|
|
requiresRepaint = true;
|
|
}
|
|
#else
|
|
EditorGUI.BeginChangeCheck();
|
|
m.enabled = EditorGUI.Toggle(rectButton, m.enabled);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
requiresRepaint = true;
|
|
}
|
|
#endif
|
|
|
|
serializedObject.Update();
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
m.blendMode = (Modifier.BlendMode) EditorGUI.Popup(blendModeRect, (int)m.blendMode, Enum.GetNames((typeof(Modifier.BlendMode))));
|
|
m.opacity = EditorGUI.Slider(opacityRect, m.opacity, 0f, 100f);
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
requiresRepaint = true;
|
|
}
|
|
}
|
|
|
|
private void DrawModifierSettings(int index)
|
|
{
|
|
//None selected
|
|
if (index < 0) return;
|
|
|
|
serializedObject.Update();
|
|
|
|
SerializedProperty settingsElement = serializedObject.FindProperty("layerSettings").GetArrayElementAtIndex(m_LayerList.index);
|
|
SerializedProperty modifiersElement = settingsElement.FindPropertyRelative("modifierStack");
|
|
if (index >= modifiersElement.arraySize) return;
|
|
SerializedProperty modifierProp = modifiersElement.GetArrayElementAtIndex(index);
|
|
|
|
//Can't draw the properties of the serializedproperty itself
|
|
var editor = Editor.CreateEditor(modifierProp.objectReferenceValue);
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
#if ODIN_INSPECTOR
|
|
editor.DrawDefaultInspector();
|
|
#else
|
|
//If Odin is installed, this doesn't draw!
|
|
editor.OnInspectorGUI();
|
|
#endif
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
requiresRepaint = true;
|
|
}
|
|
}
|
|
|
|
void OnReorderModifier(ReorderableList list, int oldIndex, int newIndex)
|
|
{
|
|
RefreshModifierLists();
|
|
|
|
UpdateHeatmap();
|
|
|
|
requiresRepaint = true;
|
|
}
|
|
#endregion
|
|
}
|
|
} |