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

1241 lines
53 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using UnityEditor.EditorTools;
using UnityEditor.ShortcutManagement;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEditor.Overlays;
using UnityEngine.UIElements;
using Cursor = UnityEngine.Cursor;
using UnityEngine.TerrainTools;
namespace UnityEditor.TerrainTools
{
/// <summary>
/// Provides methods for altering brush data.
/// </summary>
public abstract class BaseBrushUIGroup : IBrushUIGroup, IBrushEventHandler, IBrushTerrainCache
{
private bool m_ShowBrushMaskFilters = true;
private bool m_ShowModifierControls = true;
private static readonly BrushShortcutHandler<BrushShortcutType> s_ShortcutHandler = new BrushShortcutHandler<BrushShortcutType>();
private readonly string m_Name;
private readonly HashSet<Event> m_ConsumedEvents = new HashSet<Event>();
private readonly List<IBrushController> m_Controllers = new List<IBrushController>();
private IBrushSizeController m_BrushSizeController = null;
private IBrushRotationController m_BrushRotationController = null;
private IBrushStrengthController m_BrushStrengthController = null;
private IBrushSpacingController m_BrushSpacingController = null;
private IBrushScatterController m_BrushScatterController = null;
private IBrushModifierKeyController m_BrushModifierKeyController = null;
private IBrushSmoothController m_BrushSmoothController = null;
internal bool m_HasBrushSize; // tells you which of the controllers are null or not
internal bool m_HasBrushRotation;
internal bool m_HasBrushStrength;
internal bool m_HasBrushSpacing;
internal bool m_HasBrushScatter;
[ SerializeField ]
private FilterStack m_BrushMaskFilterStack = null;
/// <summary>
/// Gets the brush mask's <see cref="FilterStack"/>.
/// </summary>
public FilterStack brushMaskFilterStack
{
get
{
if( m_BrushMaskFilterStack == null )
{
if( File.Exists( getFilterStackFilePath ) )
{
m_BrushMaskFilterStack = LoadFilterStack();
}
// If there is no filter stack or if it failed to load
if( m_BrushMaskFilterStack == null )
{
// create the first filterstack if this is the first time this tool is being used
// because a save file has not been made yet for the filterstack
m_BrushMaskFilterStack = ScriptableObject.CreateInstance< FilterStack >();
}
}
return m_BrushMaskFilterStack;
}
}
private FilterStackView m_BrushMaskFilterStackView = null;
/// <summary>
/// Gets the brush mask's <see cref="FilterStackView"/>.
/// </summary>
public FilterStackView brushMaskFilterStackView
{
get
{
// need to make the UI if the view hasnt been created yet or if the reference to the FilterStack SerializedObject has
// been lost, like when entering and exiting Play Mode
if( m_BrushMaskFilterStackView == null || m_BrushMaskFilterStackView.serializedFilterStack.targetObject == null )
{
m_BrushMaskFilterStackView = new FilterStackView(new GUIContent("Brush Mask Filters"), new SerializedObject( brushMaskFilterStack ) );
m_BrushMaskFilterStackView.FilterContext = filterContext;
m_BrushMaskFilterStackView.onChanged += SaveFilterStack;
}
return m_BrushMaskFilterStackView;
}
}
FilterContext m_FilterContext;
FilterContext filterContext
{
get
{
if (m_FilterContext != null) return m_FilterContext;
m_FilterContext = new FilterContext(FilterUtility.defaultFormat, Vector3.zero, 1f, 0f);
return m_FilterContext;
}
}
/// <summary>
/// Checks if Filters are enabled.
/// </summary>
public bool hasEnabledFilters => brushMaskFilterStack.hasEnabledFilters;
private void SetFilterContext(TerrainData terrainData, Vector3 position, float scale, float rotation)
{
// set the filter context properties
filterContext.brushPos = position;
filterContext.brushSize = scale;
filterContext.brushRotation = rotation;
// bind properties for filters to read/write to
filterContext.diffuseTextures = terrainData.terrainLayers;
filterContext.floatProperties[FilterContext.Keywords.TerrainScale] = Mathf.Sqrt(terrainData.size.x * terrainData.size.x + terrainData.size.z * terrainData.size.z);
filterContext.vectorProperties["_TerrainSize"] = new Vector4(terrainData.size.x, terrainData.size.y, terrainData.size.z, 0.0f);
}
/// <summary>
/// Generates the brush mask.
/// </summary>
/// <remarks>
/// **Note:** Using this version of the overloaded method allows for the use of the Layer Filter
/// </remarks>
/// <param name="terrain">The terrain in focus.</param>
/// <param name="brushRender">The brushRender object used for acquiring the heightmap and splatmap texture to blit from.</param>
/// <param name="destinationRenderTexture">The destination render texture for bliting to.</param>
/// <param name="position">The brush's position.</param>
/// <param name="scale">The brush's scale.</param>
/// <param name="rotation">The brush's rotation.</param>
/// <seealso cref="GenerateBrushMask(Terrain terrain, RenderTexture sourceRenderTexture, RenderTexture destinationRenderTexture, Vector3 position, float scale, float rotation)"/>
public void GenerateBrushMask(Terrain terrain, IBrushRenderUnderCursor brushRender, RenderTexture destinationRenderTexture,
Vector3 position, float scale, float rotation)
{
filterContext.ReleaseRTHandles();
FilterUtility.LayerFilterActiveState = true;
if (brushRender.CalculateBrushTransform(out BrushTransform brushTransform))
{
Rect brushRect = brushTransform.GetBrushXYBounds();
using (new ActiveRenderTextureScope(null))
{
TerrainData terrainData = terrain.terrainData;
SetFilterContext(terrainData, position, scale, rotation);
// bind terrain texture data
PaintContext heightContext = brushRender.AcquireHeightmap(false, brushRect);
filterContext.rtHandleCollection.AddRTHandle(0, FilterContext.Keywords.Heightmap, heightContext.sourceRenderTexture.graphicsFormat);
List<LayerFilter> layerFilters = brushMaskFilterStack.filters.OfType<LayerFilter>().ToList();
if(layerFilters.Count > 0)
{
// bind layer filter alphamap textures
for (int i = 0; i < layerFilters.Count; i++)
{
LayerFilter layerFilter = layerFilters[i];
if (layerFilter.layerIndex >= terrainData.terrainLayers.Length)
continue;
PaintContext paintContext = brushRender.AcquireTexture(false, brushRect, terrain.terrainData.terrainLayers[layerFilter.layerIndex]);
layerFilter.splatmapKeyword = FilterContext.Keywords.Splatmap + i;
filterContext.rtHandleCollection.AddRTHandle(1 + i, layerFilter.splatmapKeyword, paintContext.sourceRenderTexture.graphicsFormat);
brushRender.Release(paintContext);
}
filterContext.rtHandleCollection.GatherRTHandles(heightContext.sourceRenderTexture.width, heightContext.sourceRenderTexture.height);
// blit the gathered rthandles
foreach(LayerFilter layerFilter in layerFilters)
{
if (layerFilter.layerIndex >= terrainData.terrainLayers.Length)
continue;
PaintContext paintContext = brushRender.AcquireTexture(false, brushRect, terrain.terrainData.terrainLayers[layerFilter.layerIndex]);
Graphics.Blit(paintContext.sourceRenderTexture, filterContext.rtHandleCollection[layerFilter.splatmapKeyword]);
brushRender.Release(paintContext);
}
Graphics.Blit(heightContext.sourceRenderTexture, filterContext.rtHandleCollection[FilterContext.Keywords.Heightmap]);
}
else
{
filterContext.rtHandleCollection.GatherRTHandles(heightContext.sourceRenderTexture.width, heightContext.sourceRenderTexture.height);
Graphics.Blit(heightContext.sourceRenderTexture, filterContext.rtHandleCollection[FilterContext.Keywords.Heightmap]);
}
brushMaskFilterStack.Eval(filterContext, heightContext.sourceRenderTexture, destinationRenderTexture);
}
filterContext.ReleaseRTHandles();
}
}
/// <summary>
/// Generates the brush mask.
/// </summary>
/// <remarks>
/// **Note:** Use <see cref="GenerateBrushMask(Terrain terrain, IBrushRenderUnderCursor brushRender, RenderTexture destinationRenderTexture, Vector3 position, float scale, float rotation)"/>
/// if Layer Filtering capabilities are desired.
/// </remarks>
/// <param name="terrain">The terrain in focus.</param>
/// <param name="sourceRenderTexture">The source render texture to blit from.</param>
/// <param name="destinationRenderTexture">The destination render texture for bliting to.</param>
/// <param name="position">The brush's position.</param>
/// <param name="scale">The brush's scale.</param>
/// <param name="rotation">The brush's rotation.</param>
/// <seealso cref="GenerateBrushMask(Terrain terrain, RenderTexture sourceRenderTexture, RenderTexture destinationRenderTexture, Vector3 position, float scale, float rotation)"/>
public void GenerateBrushMask(Terrain terrain, RenderTexture sourceRenderTexture, RenderTexture destinationRenderTexture,
Vector3 position, float scale, float rotation)
{
filterContext.ReleaseRTHandles();
FilterUtility.LayerFilterActiveState = false;
using (new ActiveRenderTextureScope(null))
{
SetFilterContext(terrain.terrainData, position, scale, rotation);
// bind terrain texture data
filterContext.rtHandleCollection.AddRTHandle(0, FilterContext.Keywords.Heightmap, sourceRenderTexture.graphicsFormat);
filterContext.rtHandleCollection.GatherRTHandles(sourceRenderTexture.width, sourceRenderTexture.height);
Graphics.Blit(sourceRenderTexture, filterContext.rtHandleCollection[FilterContext.Keywords.Heightmap]);
brushMaskFilterStack.Eval(filterContext, sourceRenderTexture, destinationRenderTexture);
}
filterContext.ReleaseRTHandles();
}
/// <summary>
/// Generates the brush mask.
/// </summary>
/// <param name="terrain">The terrain in focus.</param>
/// <param name="sourceRenderTexture">The source render texture to blit from.</param>
/// <param name="destinationRenderTexture">The destination render texture for bliting to.</param>
/// <seealso cref="GenerateBrushMask(Terrain, RenderTexture, RenderTexture, Vector3, float, float)"/>
public void GenerateBrushMask(Terrain terrain, RenderTexture sourceRenderTexture, RenderTexture destinationRenderTexture)
{
GenerateBrushMask(terrain, sourceRenderTexture, destinationRenderTexture, raycastHitUnderCursor.point, brushSize, brushRotation);
}
/// <summary>
/// Generates the brush mask.
/// </summary>
/// <param name="sourceRenderTexture">The source render texture to blit from.</param>
/// <param name="destinationRenderTexture">The destination render texture for bliting to.</param>
/// <seealso cref="GenerateBrushMask(Terrain, RenderTexture, RenderTexture, Vector3, float, float)"/>
public void GenerateBrushMask(RenderTexture sourceRenderTexture, RenderTexture destinationRenderTexture)
{
GenerateBrushMask(terrainUnderCursor, sourceRenderTexture, destinationRenderTexture);
}
/// <summary>
/// Generates the brush mask.
/// </summary>
/// <param name="brushRender">The brushRender object used for acquiring the heightmap and splatmap texture to blit from.</param>
/// <param name="destinationRenderTexture">The destination render texture for bliting to.</param>
public void GenerateBrushMask(IBrushRenderUnderCursor brushRender, RenderTexture destinationRenderTexture)
{
GenerateBrushMask(terrainUnderCursor, brushRender, destinationRenderTexture, raycastHitUnderCursor.point, brushSize, brushRotation);
}
/// <summary>
/// Returns the brush name.
/// </summary>
public string brushName => m_Name;
/// <summary>
/// Does the commonUI have a size controller?
/// </summary>
public bool hasBrushSize => m_HasBrushSize;
/// <summary>
/// Does the commonUI have a rotation controller?
/// </summary>
public bool hasBrushRotation => m_HasBrushRotation;
/// <summary>
/// Does the commonUI have a strength controller?
/// </summary>
public bool hasBrushStrength => m_HasBrushStrength;
/// <summary>
/// Does the commonUI have a spacing controller?
/// </summary>
public bool hasBrushSpacing => m_HasBrushSpacing;
/// <summary>
/// Does the commonUI have a scatter controller?
/// </summary>
public bool hasBrushScatter => m_HasBrushScatter;
/// <summary>
/// Gets and sets the brush size.
/// </summary>
/// <remarks>Gets a value of 100 if the brush size controller isn't initialized.</remarks>
public float brushSize
{
get { return m_BrushSizeController?.brushSize ?? 100.0f; }
set { m_BrushSizeController.brushSize = value; }
}
/// <summary>
/// The size of the brush without jitter.
/// </summary>
/// <remarks>Gets a value of 100 if the brush size controller isn't initialized.</remarks>
public float brushSizeVal
{
get { return m_BrushSizeController?.brushSizeVal ?? 100.0f; }
}
/// <summary>
/// Gets and sets the brush min size.
/// </summary>
/// <remarks>Gets a value of 0 if the brush size controller isn't initialized.</remarks>
public float brushSizeMin
{
get { return m_BrushSizeController?.brushSizeMin ?? 0.0f; }
set { m_BrushSizeController.brushSizeMin = value; }
}
/// <summary>
/// Gets and sets the brush max size.
/// </summary>
/// <remarks>Gets a value of 1 if the brush size controller isn't initialized.</remarks>
public float brushSizeMax
{
get { return m_BrushSizeController?.brushSizeMax ?? 1.0f; }
set { m_BrushSizeController.brushSizeMax = value; }
}
/// <summary>
/// Gets and sets the brush size jitter.
/// </summary>
/// <remarks>Gets a value of 0 if the brush size controller isn't initialized.</remarks>
public float brushSizeJitter
{
get { return m_BrushSizeController?.brushSizeJitter ?? 0.0f; }
set { m_BrushSizeController.brushSizeJitter = value; }
}
/// <summary>
/// Gets and sets the brush rotation.
/// </summary>
/// <remarks>Gets a value of 0 if the brush rotation controller isn't initialized.</remarks>
public float brushRotation
{
get { return m_BrushRotationController?.brushRotation ?? 0.0f; }
set { m_BrushRotationController.brushRotation = value; }
}
/// <summary>
/// The strength of the brush without jitter.
/// </summary>
/// <remarks>Gets a value of 0 if the brush rotation controller isn't initialized.</remarks>
public float brushRotationVal
{
get { return m_BrushRotationController?.brushRotationVal ?? 0.0f; }
}
/// <summary>
/// Gets and sets the brush rotation jitter.
/// </summary>
/// <remarks>Gets a value of 0 if the brush rotation controller isn't initialized.</remarks>
public float brushRotationJitter
{
get { return m_BrushRotationController?.brushRotationJitter ?? 0.0f; }
set { m_BrushRotationController.brushRotationJitter = value; }
}
/// <summary>
/// Gets and sets the brush strength.
/// </summary>
/// <remarks>Gets a value of 1 if the brush strength controller isn't initialized.</remarks>
public float brushStrength
{
get { return m_BrushStrengthController?.brushStrength ?? 1.0f; }
set { m_BrushStrengthController.brushStrength = value; }
}
/// <summary>
/// The strength of the brush without jitter.
/// </summary>
/// <remarks>Gets a value of 1 if the brush strength controller isn't initialized.</remarks>
public float brushStrengthVal
{
get { return m_BrushStrengthController?.brushStrengthVal ?? 1.0f; }
}
/// <summary>
/// Gets and sets the brush min strength.
/// </summary>
/// <remarks>Gets a value of 0 if the brush strength controller isn't initialized.</remarks>
public float brushStrengthMin
{
get { return m_BrushStrengthController?.brushStrengthMin ?? 0.0f; }
set { m_BrushStrengthController.brushStrengthMin = value; }
}
/// <summary>
/// Gets and sets the brush max strength.
/// </summary>
/// <remarks>Gets a value of 1 if the brush strength controller isn't initialized.</remarks>
public float brushStrengthMax
{
get { return m_BrushStrengthController?.brushStrengthMax ?? 1.0f; }
set { m_BrushStrengthController.brushStrengthMax = value; }
}
/// <summary>
/// Gets and sets the brush strength jitter.
/// </summary>
/// <remarks>Gets a value of 0 if the brush strength controller isn't initialized.</remarks>
public float brushStrengthJitter
{
get { return m_BrushStrengthController?.brushStrengthJitter ?? 0.0f; }
set { m_BrushStrengthController.brushStrengthJitter = value; }
}
/// <summary>
/// Returns the brush spacing.
/// </summary>
/// <remarks>Returns a value of 0 if the brush spacing controller isn't initialized.</remarks>
public float brushSpacing
{
get { return m_BrushSpacingController?.brushSpacing ?? 0.0f; }
set { m_BrushSpacingController.brushSpacing = value; }
}
/// <summary>
/// Returns the brush scatter.
/// </summary>
/// <remarks>Returns a value of 0 if the brush scattering controller isn't initialized.</remarks>
public float brushScatter
{
get { return m_BrushScatterController?.brushScatter ?? 0.0f; }
set { m_BrushScatterController.brushScatter = value; }
}
private bool isSmoothing
{
get
{
if (m_BrushSmoothController != null)
{
return Event.current != null && Event.current.shift;
}
return false;
}
}
/// <summary>
/// Checks if painting is allowed.
/// </summary>
public virtual bool allowPaint => (m_BrushSpacingController?.allowPaint ?? true) && !isSmoothing;
/// <summary>
/// Inverts the brush strength.
/// </summary>
public bool InvertStrength => m_BrushModifierKeyController?.ModifierActive(BrushModifierKey.BRUSH_MOD_INVERT) ?? false;
/// <summary>
/// Checks if the brush is in use.
/// </summary>
public bool isInUse
{
get
{
foreach(IBrushController c in m_Controllers)
{
if(c.isInUse)
{
return true;
}
}
return false;
}
}
private static class Styles
{
public static GUIStyle Box { get; private set; }
public static readonly GUIContent brushMask = EditorGUIUtility.TrTextContent("Brush Mask");
public static readonly GUIContent multipleControls = EditorGUIUtility.TrTextContent("Multiple Controls");
public static readonly GUIContent stroke = EditorGUIUtility.TrTextContent("Stroke");
public static readonly string kGroupBox = "GroupBox";
static Styles()
{
Box = new GUIStyle(EditorStyles.helpBox);
Box.normal.textColor = Color.white;
}
}
Func<TerrainToolsAnalytics.IBrushParameter[]> m_analyticsCallback;
/// <summary>
/// Initializes and returns an instance of BaseBrushUIGroup.
/// </summary>
/// <param name="name">The name of the brush.</param>
/// <param name="analyticsCall">The brush's analytics function.</param>
protected BaseBrushUIGroup(string name, Func<TerrainToolsAnalytics.IBrushParameter[]> analyticsCall = null)
{
m_Name = name;
m_analyticsCallback = analyticsCall;
}
#if UNITY_2019_1_OR_NEWER
[ClutchShortcut("Terrain/Adjust Brush Strength (SceneView)", typeof(TerrainToolShortcutContext), KeyCode.A)]
static void StrengthBrushShortcut(ShortcutArguments args) {
s_ShortcutHandler.HandleShortcutChanged(args, BrushShortcutType.Strength);
}
[ClutchShortcut("Terrain/Adjust Brush Size (SceneView)", typeof(TerrainToolShortcutContext), KeyCode.S)]
static void ResizeBrushShortcut(ShortcutArguments args) {
s_ShortcutHandler.HandleShortcutChanged(args, BrushShortcutType.Size);
}
[ClutchShortcut("Terrain/Adjust Brush Rotation (SceneView)", typeof(TerrainToolShortcutContext), KeyCode.D)]
private static void RotateBrushShortcut(ShortcutArguments args) {
s_ShortcutHandler.HandleShortcutChanged(args, BrushShortcutType.Rotation);
}
#endif
/// <summary>
/// Adds a generic controller of type <see cref="IBrushController"/> to the brush's controller list.
/// </summary>
/// <typeparam name="TController">A generic controller type of IBrushController.</typeparam>
/// <param name="newController">The new controller to add.</param>
/// <returns>Returns the new generic controller.</returns>
protected TController AddController<TController>(TController newController) where TController: IBrushController
{
m_Controllers.Add(newController);
return newController;
}
/// <summary>
/// Adds a rotation controller of type <see cref="IBrushRotationController"/> to the brush's controller list.
/// </summary>
/// <typeparam name="TController">A generic controller type of IBrushRotationController.</typeparam>
/// <param name="newController">The new controller to add.</param>
/// <returns>Returns the new rotation controller.</returns>
protected TController AddRotationController<TController>(TController newController) where TController : IBrushRotationController
{
m_BrushRotationController = AddController(newController);
return newController;
}
/// <summary>
/// Adds a size controller of type <see cref="IBrushSizeController"/> to the brush's controller list.
/// </summary>
/// <typeparam name="TController">A generic controller type of IBrushSizeController.</typeparam>
/// <param name="newController">The new controller to add.</param>
/// <returns>Returns the new size controller.</returns>
protected TController AddSizeController<TController>(TController newController) where TController : IBrushSizeController
{
m_BrushSizeController = AddController(newController);
return newController;
}
/// <summary>
/// Adds a strength controller of type <see cref="IBrushStrengthController"/> to the brush's controller list.
/// </summary>
/// <typeparam name="TController">A generic controller type of IBrushStrengthController.</typeparam>
/// <param name="newController">The new controller to add.</param>
/// <returns>Returns the new strength controller.</returns>
protected TController AddStrengthController<TController>(TController newController) where TController : IBrushStrengthController
{
m_BrushStrengthController = AddController(newController);
return newController;
}
/// <summary>
/// Adds a spacing controller of type <see cref="IBrushSpacingController"/> to the brush's controller list.
/// </summary>
/// <typeparam name="TController">A generic controller type of IBrushSpacingController.</typeparam>
/// <param name="newController">The new controller to add.</param>
/// <returns>Returns the new spacing controller.</returns>
protected TController AddSpacingController<TController>(TController newController) where TController : IBrushSpacingController
{
m_BrushSpacingController = AddController(newController);
return newController;
}
/// <summary>
/// Adds a scatter controller of type <see cref="IBrushScatterController"/> to the brush's controller list.
/// </summary>
/// <typeparam name="TController">A generic controller type of IBrushScatterController.</typeparam>
/// <param name="newController">The new controller to add.</param>
/// <returns>Returns the new scatter controller.</returns>
protected TController AddScatterController<TController>(TController newController) where TController : IBrushScatterController
{
m_BrushScatterController = AddController(newController);
return newController;
}
/// <summary>
/// Adds a modifier key controller of type <see cref="IBrushModifierKeyController"/> to the brush's controller list.
/// </summary>
/// <typeparam name="TController">A generic controller type of IBrushModifierKeyController.</typeparam>
/// <param name="newController">The new controller to add.</param>
/// <returns>Returns the new modifier key controller.</returns>
protected TController AddModifierKeyController<TController>(TController newController) where TController : IBrushModifierKeyController
{
m_BrushModifierKeyController = newController;
return newController;
}
/// <summary>
/// Adds a smoothing controller of type <see cref="IBrushSmoothController"/> to the brush's controller list.
/// </summary>
/// <typeparam name="TController">A generic controller type of IBrushSmoothController.</typeparam>
/// <param name="newController">The new controller to add.</param>
/// <returns>Returns the new smoothing controller.</returns>
protected TController AddSmoothingController<TController>(TController newController) where TController : IBrushSmoothController
{
m_BrushSmoothController = newController;
return newController;
}
private bool m_RepaintRequested;
/// <summary>
/// Registers a new event to be used witin <see cref="OnSceneGUI(Terrain, IOnSceneGUI)"/>.
/// </summary>
/// <param name="newEvent">The event to add.</param>
public void RegisterEvent(Event newEvent)
{
m_ConsumedEvents.Add(newEvent);
}
/// <summary>
/// Calls the Use function of the registered events.
/// </summary>
/// <param name="terrain">The terrain in focus.</param>
/// <param name="editContext">The editcontext to repaint.</param>
/// <seealso cref="RegisterEvent(Event)"/>
public void ConsumeEvents(Terrain terrain, IOnSceneGUI editContext)
{
// Consume all of the events we've handled...
foreach(Event currentEvent in m_ConsumedEvents)
{
currentEvent.Use();
}
m_ConsumedEvents.Clear();
// Repaint everything if we need to...
if(m_RepaintRequested)
{
EditorWindow view = EditorWindow.GetWindow<SceneView>();
editContext.Repaint();
view.Repaint();
m_RepaintRequested = false;
}
}
/// <summary>
/// Sets the repaint request to <c>true</c>.
/// </summary>
public void RequestRepaint()
{
m_RepaintRequested = true;
}
/// <summary>
/// Renders the brush's GUI within the inspector view.
/// </summary>
/// <param name="terrain">The terrain in focus.</param>
/// <param name="editContext">The editcontext used to show the brush GUI.</param>
/// <param name="brushFlags">The brushflags to use when displaying the brush GUI.</param>
public virtual void OnInspectorGUI(Terrain terrain, IOnInspectorGUI editContext,
BrushGUIEditFlags brushFlags = BrushGUIEditFlags.SelectAndInspect)
{
OnInspectorGUI(terrain, editContext, false);
}
/// <summary>
/// Renders the brush's GUI within the inspector view.
/// </summary>
/// <param name="terrain">The terrain in focus.</param>
/// <param name="editContext">The editcontext used to show the brush GUI.</param>
/// <param name="overlays">The bool to mark true when showing UI specific for overlays.</param>
/// <param name="brushFlags">The brushflags to use when displaying the brush GUI.</param>
/// <param name="brushOverlaysFlags"></param>
public virtual void OnInspectorGUI(Terrain terrain, IOnInspectorGUI editContext, bool overlays,
BrushGUIEditFlags brushFlags = BrushGUIEditFlags.SelectAndInspect,
BrushOverlaysGUIFlags brushOverlaysFlags = BrushOverlaysGUIFlags.All)
{
// get flag booleans for overlays
bool showNone = brushOverlaysFlags.Equals(BrushOverlaysGUIFlags.None);
bool showFilter = (brushOverlaysFlags & BrushOverlaysGUIFlags.Filter) != 0;
bool showStrength = (brushOverlaysFlags & BrushOverlaysGUIFlags.Strength) != 0;
bool showSize = (brushOverlaysFlags & BrushOverlaysGUIFlags.Size) != 0;
bool showRotation = (brushOverlaysFlags & BrushOverlaysGUIFlags.Rotation) != 0;
bool showSpacing = (brushOverlaysFlags & BrushOverlaysGUIFlags.Spacing) != 0;
bool showScatter = (brushOverlaysFlags & BrushOverlaysGUIFlags.Scatter) != 0;
if (overlays && showNone) return; // if show nothing for overlays, then return
if (brushFlags != BrushGUIEditFlags.None && !overlays)
{
editContext.ShowBrushesGUI(0, brushFlags);
}
EditorGUI.BeginChangeCheck();
if (showFilter)
{
// only show drop down for not overlays
if (!overlays) m_ShowBrushMaskFilters = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.brushMask, m_ShowBrushMaskFilters);
if (m_ShowBrushMaskFilters)
{
brushMaskFilterStackView.OnGUI();
}
}
// only show drop down for not overlays
if (!overlays) m_ShowModifierControls = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.stroke, m_ShowModifierControls);
if (m_ShowModifierControls)
{
if(m_BrushStrengthController != null && showStrength)
{
EditorGUILayout.BeginVertical(Styles.kGroupBox);
m_BrushStrengthController.OnInspectorGUI(terrain, editContext);
EditorGUILayout.EndVertical();
}
if(m_BrushSizeController != null && showSize)
{
EditorGUILayout.BeginVertical(Styles.kGroupBox);
m_BrushSizeController.OnInspectorGUI(terrain, editContext);
EditorGUILayout.EndVertical();
}
if(m_BrushRotationController != null && showRotation)
{
EditorGUILayout.BeginVertical(Styles.kGroupBox);
m_BrushRotationController?.OnInspectorGUI(terrain, editContext);
EditorGUILayout.EndVertical();
}
// if NOT overlays then draw spacing and scatter together. else draw scatter and spacing separately
if (overlays)
{
if (((m_BrushSpacingController != null) || (m_BrushScatterController != null)) && showSpacing)
{
EditorGUILayout.BeginVertical(Styles.kGroupBox);
m_BrushSpacingController?.OnInspectorGUI(terrain, editContext);
EditorGUILayout.EndVertical();
}
if (((m_BrushSpacingController != null) || (m_BrushScatterController != null)) && showScatter)
{
EditorGUILayout.BeginVertical(Styles.kGroupBox);
m_BrushScatterController?.OnInspectorGUI(terrain, editContext);
EditorGUILayout.EndVertical();
}
}
else
{
if((m_BrushSpacingController != null) || (m_BrushScatterController != null))
{
EditorGUILayout.BeginVertical(Styles.kGroupBox);
m_BrushSpacingController?.OnInspectorGUI(terrain, editContext);
m_BrushScatterController?.OnInspectorGUI(terrain, editContext);
EditorGUILayout.EndVertical();
}
}
}
if (EditorGUI.EndChangeCheck())
TerrainToolsAnalytics.OnParameterChange();
}
private string getFilterStackFilePath
{
get { return Application.persistentDataPath + "/TerrainTools_" + m_Name + "_FilterStack.filterstack"; }
}
private FilterStack LoadFilterStack()
{
UnityEngine.Object[] obs = UnityEditorInternal.InternalEditorUtility.LoadSerializedFileAndForget( getFilterStackFilePath );
if( obs != null && obs.Length > 0 )
{
return obs[ 0 ] as FilterStack;
}
return null;
}
private void SaveFilterStack( FilterStack filterStack )
{
List< UnityEngine.Object > objList = new List< UnityEngine.Object >();
objList.Add( filterStack );
objList.AddRange( filterStack.filters );
filterStack.filters.ForEach( ( f ) =>
{
var l = f.GetObjectsToSerialize();
if( l != null && l.Count > 0 )
{
objList.AddRange( l );
}
} );
// write to the file
UnityEditorInternal.InternalEditorUtility.SaveToSerializedFileAndForget(objList.ToArray(), getFilterStackFilePath, true );
}
/// <summary>
/// Defines data when the brush is selected.
/// </summary>
/// <seealso cref="OnExitToolMode"/>
public virtual void OnEnterToolMode()
{
m_BrushModifierKeyController?.OnEnterToolMode();
m_Controllers.ForEach((controller) => controller.OnEnterToolMode(s_ShortcutHandler));
TerrainToolsAnalytics.m_OriginalParameters = m_analyticsCallback?.Invoke();
}
/// <summary>
/// Defines data when the brush is deselected.
/// </summary>
/// <seealso cref="OnEnterToolMode"/>
public virtual void OnExitToolMode()
{
m_Controllers.ForEach((controller) => controller.OnExitToolMode(s_ShortcutHandler));
m_BrushModifierKeyController?.OnExitToolMode();
SaveFilterStack(brushMaskFilterStack);
}
/// <summary>
/// Checks if the brush strokes are being recorded.
/// </summary>
public static bool isRecording = false;
/// <summary>
/// Provides methods for the brush's painting.
/// </summary>
[Serializable]
public class OnPaintOccurrence
{
[NonSerialized] internal static List<OnPaintOccurrence> history = new List<OnPaintOccurrence>();
[NonSerialized] private static float prevRealTime;
/// <summary>
/// Initializes and returns an instance of OnPaintOccurrence.
/// </summary>
/// <param name="brushTexture">The brush's texture.</param>
/// <param name="brushSize">The brush's size.</param>
/// <param name="brushStrength">The brush's strength.</param>
/// <param name="brushRotation">The brush's rotation.</param>
/// <param name="uvX">The cursor's X position within UV space.</param>
/// <param name="uvY">The cursor's Y position within UV space.</param>
public OnPaintOccurrence(Texture brushTexture, float brushSize,
float brushStrength, float brushRotation,
float uvX, float uvY)
{
this.xPos = uvX;
this.yPos = uvY;
this.brushTextureAssetPath = AssetDatabase.GetAssetPath(brushTexture);
this.brushStrength = brushStrength;
this.brushSize = brushSize;
if (history.Count == 0)
{
duration = 0;
}
else
{
duration = Time.realtimeSinceStartup - prevRealTime;
}
prevRealTime = Time.realtimeSinceStartup;
}
/// <summary>
/// The cursor's X position within UV space.
/// </summary>
[SerializeField] public float xPos;
/// <summary>
/// The cursor's Y position within UV space.
/// </summary>
[SerializeField] public float yPos;
/// <summary>
/// The asset file path of the brush texture in use.
/// </summary>
[SerializeField] public string brushTextureAssetPath;
/// <summary>
/// The brush strength.
/// </summary>
[SerializeField] public float brushStrength;
/// <summary>
/// The brush rotation.
/// </summary>
[SerializeField] public float brushRotation;
/// <summary>
/// The brush size.
/// </summary>
[SerializeField] public float brushSize;
/// <summary>
/// The total duration of painting.
/// </summary>
[SerializeField] public float duration;
}
/// <summary>
/// Triggers events when painting on a terrain.
/// </summary>
/// <param name="terrain">The terrain in focus.</param>
/// <param name="editContext">The editcontext to reference.</param>
public virtual void OnPaint(Terrain terrain, IOnPaint editContext)
{
filterContext.ReleaseRTHandles();
// Manage brush capture history for playback in tests
if (isRecording)
{
OnPaintOccurrence.history.Add(new OnPaintOccurrence(editContext.brushTexture, brushSize,
brushStrength, brushRotation,
editContext.uv.x, editContext.uv.y));
}
m_Controllers.ForEach((controller) => controller.OnPaint(terrain, editContext));
if (isSmoothing)
{
Vector2 uv = editContext.uv;
m_BrushSmoothController.kernelSize = (int)Mathf.Max(1, 0.1f * m_BrushSizeController.brushSize);
m_BrushSmoothController.OnPaint(terrain, editContext, brushSize, brushRotation, brushStrength, uv);
}
/// Ensure that we re-randomize where the next scatter operation will place the brush,
/// that way we can render the preview in a representative manner.
m_BrushScatterController?.RequestRandomisation();
TerrainToolsAnalytics.UpdateAnalytics(this, m_analyticsCallback);
filterContext.ReleaseRTHandles();
}
/// <summary>
/// Triggers events to render a 2D GUI within the Scene view.
/// </summary>
/// <param name="terrain">The terrain in focus.</param>
/// <param name="editContext">The editcontext to reference.</param>
/// <seealso cref="OnSceneGUI(Terrain, IOnSceneGUI)"/>
private static string brushInfo = ""; // adding this to grab brush info
public virtual void OnSceneGUI2D(Terrain terrain, IOnSceneGUI editContext)
{
StringBuilder builder = new StringBuilder();
Handles.BeginGUI();
{
AppendBrushInfo(terrain, editContext, builder);
string text = builder.ToString();
string trimmedText = text.Trim('\n', '\r', ' ', '\t');
brushInfo = trimmedText;
BrushInfoIsAccessed();
Handles.EndGUI();
}
}
// adding this to grab brush info
public static string getBrushInfoText()
{
return brushInfo;
}
public static event Action brushInfoAccessed;
internal static void BrushInfoIsAccessed()
{
if (brushInfoAccessed == null)
return;
brushInfoAccessed();
}
[Overlay(typeof(SceneView), k_Id, "Brush Info")]
class BrushInfoOverlay : Overlay, ITransientOverlay
{
const string k_Id = "brush-info-overlay";
private Label m_Label;
public bool visible
{
get
{
var currTool = BrushesOverlay.ActiveTerrainTool as TerrainPaintToolWithOverlaysBase;
if (currTool == null)
return false;
return currTool.HasBrushAttributes && BrushesOverlay.IsSelectedObjectTerrain();
}
}
void UpdateText()
{
if (m_Label != null) m_Label.text = getBrushInfoText();
}
void EmptyText()
{
if (m_Label != null) m_Label.text = "";
}
public override VisualElement CreatePanelContent()
{
brushInfoAccessed += UpdateText;
Selection.selectionChanged += EmptyText;
return m_Label = new Label(getBrushInfoText());
}
public override void OnWillBeDestroyed()
{
base.OnWillBeDestroyed();
brushInfoAccessed -= UpdateText;
Selection.selectionChanged -= EmptyText;
}
}
/// <summary>
/// Triggers events to render objects and displays within Scene view.
/// </summary>
/// <param name="terrain">The terrain in focus.</param>
/// <param name="editContext">The editcontext to reference.</param>
/// <seealso cref="OnSceneGUI(Terrain, IOnSceneGUI)"/>
public virtual void OnSceneGUI(Terrain terrain, IOnSceneGUI editContext)
{
filterContext.ReleaseRTHandles();
Event currentEvent = Event.current;
int controlId = GUIUtility.GetControlID(TerrainToolGUIHelper.s_TerrainEditorHash, FocusType.Passive);
if(canUpdateTerrainUnderCursor)
{
isRaycastHitUnderCursorValid = editContext.hitValidTerrain;
terrainUnderCursor = terrain;
raycastHitUnderCursor = editContext.raycastHit;
}
m_Controllers.ForEach((controller) => controller.OnSceneGUI(currentEvent, controlId, terrain, editContext));
ConsumeEvents(terrain, editContext);
if (!isRecording && OnPaintOccurrence.history.Count != 0) {
SaveBrushData();
}
brushMaskFilterStackView.OnSceneGUI(editContext.sceneView);
if( editContext.hitValidTerrain && Event.current.keyCode == KeyCode.F && Event.current.type != EventType.Layout )
{
SceneView.currentDrawingSceneView.Frame( new Bounds() { center = raycastHitUnderCursor.point, size = new Vector3( brushSize, 1, brushSize ) }, false );
Event.current.Use();
}
filterContext.ReleaseRTHandles();
}
private void SaveBrushData() {
// Copy paintOccurrenceHistory to temp variable to prevent re-activating this condition
List<OnPaintOccurrence> tmpPaintOccurrenceHistory = new List<OnPaintOccurrence>(OnPaintOccurrence.history);
OnPaintOccurrence.history.Clear();
string fileName = EditorUtility.SaveFilePanelInProject("Save input playback", "PaintHistory", "txt", "");
if (fileName == "") {
return;
}
FileStream file;
if (File.Exists(fileName)) file = File.OpenWrite(fileName);
else file = File.Create(fileName);
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(file, tmpPaintOccurrenceHistory);
file.Close();
}
/// <summary>
/// Adds basic information to the selected brush.
/// </summary>
/// <param name="terrain">The Terrain in focus.</param>
/// <param name="editContext">The IOnSceneGUI to reference.</param>
/// <param name="builder">The StringBuilder containing the brush information.</param>
public virtual void AppendBrushInfo(Terrain terrain, IOnSceneGUI editContext, StringBuilder builder)
{
builder.AppendLine($"Brush: {m_Name}");
builder.AppendLine();
m_Controllers.ForEach((controller) => controller.AppendBrushInfo(terrain, editContext, builder));
builder.AppendLine();
builder.AppendLine(validationMessage);
}
/// <summary>
/// Scatters the location of the brush's stamp operation.
/// </summary>
/// <param name="terrain">The terrain in reference.</param>
/// <param name="uv">The UV location to scatter at.</param>
/// <returns>Returns false if there aren't any terrains to scatter the stamp on.</returns>
public bool ScatterBrushStamp(ref Terrain terrain, ref Vector2 uv)
{
if(m_BrushScatterController == null) {
bool invalidTerrain = terrain == null;
return !invalidTerrain;
}
else {
Vector2 scatteredUv = m_BrushScatterController.ScatterBrushStamp(uv, brushSize);
Terrain scatteredTerrain = terrain;
// Ensure that our UV is over a valid terrain AND in the range 0-1...
while((scatteredTerrain != null) && (scatteredUv.x < 0.0f)) {
scatteredTerrain = scatteredTerrain.leftNeighbor;
scatteredUv.x += 1.0f;
}
while((scatteredTerrain != null) && (scatteredUv.x > 1.0f)) {
scatteredTerrain = scatteredTerrain.rightNeighbor;
scatteredUv.x -= 1.0f;
}
while((scatteredTerrain != null) && scatteredUv.y < 0.0f) {
scatteredTerrain = scatteredTerrain.bottomNeighbor;
scatteredUv.y += 1.0f;
}
while((scatteredTerrain != null) && (scatteredUv.y > 1.0f)) {
scatteredTerrain = scatteredTerrain.topNeighbor;
scatteredUv.y -= 1.0f;
}
// Did we run out of terrains?
if(scatteredTerrain == null) {
return false;
}
else {
terrain = scatteredTerrain;
uv = scatteredUv;
return true;
}
}
}
/// <summary>
/// Activates a modifier key controller.
/// </summary>
/// <param name="k">The modifier key to activate.</param>
/// <returns>Returns false when the modifier key controller is null.</returns>
public bool ModifierActive(BrushModifierKey k)
{
return m_BrushModifierKeyController?.ModifierActive(k) ?? false;
}
private int m_TerrainUnderCursorLockCount = 0;
/// <summary>
/// Handles the locking of the terrain cursor in it's current position.
/// </summary>
/// <remarks>This method is commonly used when utilizing shortcuts.</remarks>
/// <param name="cursorVisible">Whether the cursor is visible within the scene. When the value is <c>true</c> the cursor is visible.</param>
/// <seealso cref="UnlockTerrainUnderCursor"/>
public void LockTerrainUnderCursor(bool cursorVisible)
{
if (m_TerrainUnderCursorLockCount == 0)
{
Cursor.visible = cursorVisible;
}
m_TerrainUnderCursorLockCount++;
}
/// <summary>
/// Handles unlocking of the terrain cursor.
/// </summary>
/// <seealso cref="LockTerrainUnderCursor(bool)"/>
public void UnlockTerrainUnderCursor()
{
if (m_TerrainUnderCursorLockCount > 0)
{
m_TerrainUnderCursorLockCount--;
}
else if (m_TerrainUnderCursorLockCount == 0)
{
// Last unlock enables the cursor...
Cursor.visible = true;
}
else if (m_TerrainUnderCursorLockCount < 0)
{
m_TerrainUnderCursorLockCount = 0;
throw new ArgumentOutOfRangeException(nameof(m_TerrainUnderCursorLockCount), "Cannot reduce m_TerrainUnderCursorLockCount below zero. Possible mismatch between lock/unlock calls.");
}
}
/// <summary>
/// Checks if the cursor is currently locked and can not be updated.
/// </summary>
public bool canUpdateTerrainUnderCursor => m_TerrainUnderCursorLockCount == 0;
/// <summary>
/// Gets and sets the terrain in focus.
/// </summary>
public Terrain terrainUnderCursor { get; protected set; }
/// <summary>
/// Gets and sets the value associated to whether there is a raycast hit detecting a terrain under the cursor.
/// </summary>
public bool isRaycastHitUnderCursorValid { get; private set; }
/// <summary>
/// Gets and sets the raycast hit that was under the cursor's position.
/// </summary>
public RaycastHit raycastHitUnderCursor { get; protected set; }
/// <summary>
/// Gets and sets the message for validating terrain parameters.
/// </summary>
public virtual string validationMessage { get; set; }
}
}