b486678290
Library -Artifacts
890 lines
37 KiB
C#
890 lines
37 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Runtime.Serialization.Formatters.Binary;
|
|
using System.Text;
|
|
using UnityEditor.ShortcutManagement;
|
|
using UnityEngine;
|
|
using UnityEngine.Experimental.Rendering;
|
|
|
|
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;
|
|
|
|
[ 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();
|
|
}
|
|
else
|
|
{
|
|
// 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;
|
|
private 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;
|
|
|
|
/// <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>
|
|
/// <param name="position">The brush's position.</param>
|
|
/// <param name="scale">The brush's scale.</param>
|
|
/// <param name="rotation">The brush's rotation.</param>
|
|
public void GenerateBrushMask(Terrain terrain, RenderTexture sourceRenderTexture,
|
|
RenderTexture destinationRenderTexture,
|
|
Vector3 position, float scale, float rotation)
|
|
{
|
|
filterContext.ReleaseRTHandles();
|
|
|
|
using(new ActiveRenderTextureScope(null))
|
|
{
|
|
// set the filter context properties
|
|
filterContext.brushPos = position;
|
|
filterContext.brushSize = scale;
|
|
filterContext.brushRotation = rotation;
|
|
|
|
// bind properties for filters to read/write to
|
|
var terrainData = terrain.terrainData;
|
|
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);
|
|
|
|
// 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>
|
|
/// Returns the brush name.
|
|
/// </summary>
|
|
public string brushName => m_Name;
|
|
|
|
/// <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>
|
|
/// Gets and sets the brush rotation.
|
|
/// </summary>
|
|
/// <remarks>Gets a value of 0 if the brush size controller isn't initialized.</remarks>
|
|
public float brushRotation
|
|
{
|
|
get { return m_BrushRotationController?.brushRotation ?? 0.0f; }
|
|
set { m_BrushRotationController.brushRotation = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets and sets the brush strength.
|
|
/// </summary>
|
|
/// <remarks>Gets a value of 1 if the brush size controller isn't initialized.</remarks>
|
|
public float brushStrength
|
|
{
|
|
get { return m_BrushStrengthController?.brushStrength ?? 1.0f; }
|
|
set { m_BrushStrengthController.brushStrength = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the brush spacing.
|
|
/// </summary>
|
|
/// <remarks>Returns a value of 0 if the brush size controller isn't initialized.</remarks>
|
|
public float brushSpacing => m_BrushSpacingController?.brushSpacing ?? 0.0f;
|
|
|
|
/// <summary>
|
|
/// Returns the brush scatter.
|
|
/// </summary>
|
|
/// <remarks>Returns a value of 0 if the brush size controller isn't initialized.</remarks>
|
|
public float brushScatter => m_BrushScatterController?.brushScatter ?? 0.0f;
|
|
|
|
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)
|
|
{
|
|
if (brushFlags != BrushGUIEditFlags.None)
|
|
{
|
|
editContext.ShowBrushesGUI(0, brushFlags);
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
m_ShowBrushMaskFilters = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.brushMask, m_ShowBrushMaskFilters);
|
|
if (m_ShowBrushMaskFilters)
|
|
{
|
|
brushMaskFilterStackView.OnGUI();
|
|
}
|
|
|
|
m_ShowModifierControls = TerrainToolGUIHelper.DrawHeaderFoldout(Styles.stroke, m_ShowModifierControls);
|
|
if (m_ShowModifierControls)
|
|
{
|
|
if(m_BrushStrengthController != null)
|
|
{
|
|
EditorGUILayout.BeginVertical(Styles.kGroupBox);
|
|
m_BrushStrengthController.OnInspectorGUI(terrain, editContext);
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
if(m_BrushSizeController != null)
|
|
{
|
|
EditorGUILayout.BeginVertical(Styles.kGroupBox);
|
|
m_BrushSizeController.OnInspectorGUI(terrain, editContext);
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
if(m_BrushRotationController != null)
|
|
{
|
|
EditorGUILayout.BeginVertical(Styles.kGroupBox);
|
|
m_BrushRotationController?.OnInspectorGUI(terrain, editContext);
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
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)"/>
|
|
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');
|
|
GUILayout.Box(trimmedText, Styles.Box, GUILayout.ExpandWidth(false));
|
|
Handles.EndGUI();
|
|
}
|
|
}
|
|
|
|
/// <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; }
|
|
}
|
|
}
|