330 lines
14 KiB
C#
330 lines
14 KiB
C#
|
using UnityEngine;
|
||
|
using UnityEngine.TerrainTools;
|
||
|
using UnityEditor.ShortcutManagement;
|
||
|
|
||
|
namespace UnityEditor.TerrainTools
|
||
|
{
|
||
|
//[FilePathAttribute("Library/TerrainTools/SetHeight", FilePathAttribute.Location.ProjectFolder)]
|
||
|
internal class SetHeightTool : TerrainPaintTool<SetHeightTool>
|
||
|
{
|
||
|
private static bool s_showToolControls = true;
|
||
|
|
||
|
#if UNITY_2019_1_OR_NEWER
|
||
|
[Shortcut("Terrain/Select Set Height Tool", typeof(TerrainToolShortcutContext))]
|
||
|
static void SelectShortcut(ShortcutArguments args)
|
||
|
{
|
||
|
TerrainToolShortcutContext context = (TerrainToolShortcutContext)args.context;
|
||
|
context.SelectPaintTool<SetHeightTool>();
|
||
|
TerrainToolsAnalytics.OnShortcutKeyRelease("Select Set Height Tool");
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
[SerializeField]
|
||
|
IBrushUIGroup m_commonUI;
|
||
|
private IBrushUIGroup commonUI {
|
||
|
get
|
||
|
{
|
||
|
if (m_commonUI == null)
|
||
|
{
|
||
|
LoadSettings();
|
||
|
m_commonUI = new DefaultBrushUIGroup("SetHeightTool", UpdateAnalyticParameters);
|
||
|
m_commonUI.OnEnterToolMode();
|
||
|
}
|
||
|
|
||
|
return m_commonUI;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Allows overriding for unit testing purposes
|
||
|
/// </summary>
|
||
|
/// <param name="uiGroup"></param>
|
||
|
internal void ChangeCommonUI(IBrushUIGroup uiGroup)
|
||
|
{
|
||
|
m_commonUI = uiGroup;
|
||
|
}
|
||
|
|
||
|
Material m_Material = null;
|
||
|
Material GetPaintMaterial()
|
||
|
{
|
||
|
if (m_Material == null)
|
||
|
m_Material = new Material(Shader.Find("Hidden/TerrainTools/SetExactHeight"));
|
||
|
return m_Material;
|
||
|
}
|
||
|
|
||
|
|
||
|
const string toolName = "Set Height";
|
||
|
[SerializeField]
|
||
|
float m_TargetHeight;
|
||
|
|
||
|
#if UNITY_2019_3_OR_NEWER
|
||
|
private enum HeightSpace
|
||
|
{
|
||
|
World,
|
||
|
Local
|
||
|
}
|
||
|
|
||
|
[SerializeField]
|
||
|
HeightSpace m_HeightSpace;
|
||
|
#endif
|
||
|
|
||
|
private static class Styles
|
||
|
{
|
||
|
public static readonly GUIContent controlHeader = EditorGUIUtility.TrTextContent("Set Height Controls");
|
||
|
public static readonly GUIContent description = EditorGUIUtility.TrTextContent("Adjusts the height of the Terrain to a specific value.\n\n" +
|
||
|
"Hold Ctrl + Click to sample the Terrain height at the cursor position.");
|
||
|
public static readonly GUIContent space = EditorGUIUtility.TrTextContent("Space", "The heightmap space in which the painting operates.");
|
||
|
public static readonly GUIContent flattenAll = EditorGUIUtility.TrTextContent("Flatten all", "If selected, it will traverse all neighbors and flatten them too");
|
||
|
public static readonly GUIContent height = EditorGUIUtility.TrTextContent("Height", "You can set the Height property manually or you can Ctrl-click on the terrain to sample the height at the mouse position (rather like the 'eyedropper' tool in an image editor).");
|
||
|
public static readonly GUIContent flatten = EditorGUIUtility.TrTextContent("Flatten Tile", "The Flatten button levels the whole terrain to the chosen height.");
|
||
|
}
|
||
|
|
||
|
public override string GetName()
|
||
|
{
|
||
|
return toolName;
|
||
|
}
|
||
|
|
||
|
public override string GetDescription()
|
||
|
{
|
||
|
return Styles.description.text;
|
||
|
}
|
||
|
|
||
|
public override void OnEnterToolMode()
|
||
|
{
|
||
|
base.OnEnterToolMode();
|
||
|
commonUI.OnEnterToolMode();
|
||
|
}
|
||
|
|
||
|
public override void OnExitToolMode()
|
||
|
{
|
||
|
base.OnExitToolMode();
|
||
|
commonUI.OnExitToolMode();
|
||
|
}
|
||
|
|
||
|
public override void OnSceneGUI(Terrain terrain, IOnSceneGUI editContext)
|
||
|
{
|
||
|
commonUI.OnSceneGUI2D(terrain, editContext);
|
||
|
|
||
|
// only do the rest if user mouse hits valid terrain or they are using the
|
||
|
// brush parameter hotkeys to resize, etc
|
||
|
if (!editContext.hitValidTerrain && !commonUI.isInUse)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// update brush UI group
|
||
|
commonUI.OnSceneGUI(terrain, editContext);
|
||
|
|
||
|
// dont render preview if this isnt a repaint. losing performance if we do
|
||
|
if (Event.current.type != EventType.Repaint)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Texture brushTexture = editContext.brushTexture;
|
||
|
|
||
|
using (IBrushRenderPreviewUnderCursor brushRender = new BrushRenderPreviewUIGroupUnderCursor(commonUI, "SetHeightTool", brushTexture))
|
||
|
{
|
||
|
if (brushRender.CalculateBrushTransform(out BrushTransform brushTransform))
|
||
|
{
|
||
|
Rect brushBounds = brushTransform.GetBrushXYBounds();
|
||
|
PaintContext paintContext = brushRender.AcquireHeightmap(false, brushBounds, 1);
|
||
|
Material previewMaterial = Utility.GetDefaultPreviewMaterial(commonUI.hasEnabledFilters);
|
||
|
|
||
|
var texelCtx = Utility.CollectTexelValidity(paintContext.originTerrain, brushTransform.GetBrushXYBounds());
|
||
|
Utility.SetupMaterialForPaintingWithTexelValidityContext(paintContext, texelCtx, brushTransform, previewMaterial);
|
||
|
var filterRT = RTUtils.GetTempHandle(paintContext.sourceRenderTexture.width,
|
||
|
paintContext.sourceRenderTexture.height, 0, FilterUtility.defaultFormat);
|
||
|
Utility.GenerateAndSetFilterRT(commonUI, paintContext.sourceRenderTexture, filterRT, previewMaterial);
|
||
|
TerrainPaintUtilityEditor.DrawBrushPreview(paintContext, TerrainBrushPreviewMode.SourceRenderTexture,
|
||
|
editContext.brushTexture, brushTransform, previewMaterial, 0);
|
||
|
|
||
|
// draw result preview
|
||
|
{
|
||
|
ApplyBrushInternal(paintContext, brushRender, commonUI.brushStrength, brushTexture, brushTransform, terrain);
|
||
|
|
||
|
// restore old render target
|
||
|
RenderTexture.active = paintContext.oldRenderTexture;
|
||
|
|
||
|
previewMaterial.SetTexture("_HeightmapOrig", paintContext.sourceRenderTexture);
|
||
|
TerrainPaintUtilityEditor.DrawBrushPreview(paintContext, TerrainBrushPreviewMode.DestinationRenderTexture,
|
||
|
editContext.brushTexture, brushTransform, previewMaterial, 1);
|
||
|
}
|
||
|
|
||
|
texelCtx.Cleanup();
|
||
|
RTUtils.Release(filterRT);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void ApplyBrushInternal(PaintContext paintContext, IBrushRenderUnderCursor brushRender, float brushStrength, Texture brushTexture, BrushTransform brushTransform, Terrain terrain)
|
||
|
{
|
||
|
Material mat = GetPaintMaterial();
|
||
|
#if UNITY_2019_3_OR_NEWER
|
||
|
float brushTargetHeight = Mathf.Clamp01((m_TargetHeight - paintContext.heightWorldSpaceMin) / paintContext.heightWorldSpaceSize);
|
||
|
Vector4 brushParams = new Vector4(brushStrength * 0.01f, PaintContext.kNormalizedHeightScale * brushTargetHeight, 0.0f, 0.0f);
|
||
|
#else
|
||
|
float terrainHeight = Mathf.Clamp01((m_TargetHeight - terrain.transform.position.y) / terrain.terrainData.size.y);
|
||
|
Vector4 brushParams = new Vector4(brushStrength * 0.01f, 0.5f * terrainHeight, 0.0f, 0.0f);
|
||
|
#endif
|
||
|
|
||
|
var brushMask = RTUtils.GetTempHandle(paintContext.sourceRenderTexture.width, paintContext.sourceRenderTexture.height, 0, FilterUtility.defaultFormat);
|
||
|
commonUI.GenerateBrushMask(paintContext.sourceRenderTexture, brushMask);
|
||
|
mat.SetTexture("_FilterTex", brushMask);
|
||
|
|
||
|
mat.SetTexture("_BrushTex", brushTexture);
|
||
|
mat.SetVector("_BrushParams", brushParams);
|
||
|
|
||
|
brushRender.SetupTerrainToolMaterialProperties(paintContext, brushTransform, mat);
|
||
|
brushRender.RenderBrush(paintContext, mat, 0);
|
||
|
RTUtils.Release(brushMask);
|
||
|
}
|
||
|
|
||
|
public override bool OnPaint(Terrain terrain, IOnPaint editContext)
|
||
|
{
|
||
|
commonUI.OnPaint(terrain, editContext);
|
||
|
|
||
|
if (commonUI.allowPaint)
|
||
|
{
|
||
|
if (Event.current != null && Event.current.control)
|
||
|
{
|
||
|
Terrain currentTerrain = commonUI.terrainUnderCursor;
|
||
|
m_TargetHeight = currentTerrain.terrainData.GetInterpolatedHeight(editContext.uv.x, editContext.uv.y) + currentTerrain.GetPosition().y;
|
||
|
editContext.Repaint();
|
||
|
SaveSetting();
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Texture brushTexture = editContext.brushTexture;
|
||
|
|
||
|
using (IBrushRenderUnderCursor brushRender = new BrushRenderUIGroupUnderCursor(commonUI, "SetHeightTool", brushTexture))
|
||
|
{
|
||
|
if (brushRender.CalculateBrushTransform(out BrushTransform brushTransform))
|
||
|
{
|
||
|
Rect brushBounds = brushTransform.GetBrushXYBounds();
|
||
|
PaintContext paintContext = brushRender.AcquireHeightmap(true, brushBounds);
|
||
|
|
||
|
ApplyBrushInternal(paintContext, brushRender, commonUI.brushStrength, brushTexture, brushTransform, terrain);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
internal void Flatten(Terrain terrain)
|
||
|
{
|
||
|
Undo.RegisterCompleteObjectUndo(terrain.terrainData, "Set Height - Flatten Tile");
|
||
|
|
||
|
var ctx = TerrainPaintUtility.BeginPaintHeightmap(terrain, new Rect(0, 0, terrain.terrainData.size.x, terrain.terrainData.size.z), 1);
|
||
|
|
||
|
Material mat = GetPaintMaterial();
|
||
|
|
||
|
float terrainHeight = Mathf.Clamp01((m_TargetHeight - terrain.transform.position.y) / terrain.terrainData.size.y);
|
||
|
|
||
|
Vector4 brushParams = new Vector4(0, 0.5f * terrainHeight, 0.0f, 0.0f);
|
||
|
mat.SetVector("_BrushParams", brushParams);
|
||
|
|
||
|
var size = terrain.terrainData.size;
|
||
|
size.y = 0;
|
||
|
var brushMask = RTUtils.GetTempHandle(ctx.sourceRenderTexture.width, ctx.sourceRenderTexture.height, 0, FilterUtility.defaultFormat);
|
||
|
commonUI.GenerateBrushMask(terrain, ctx.sourceRenderTexture, brushMask, terrain.transform.position, Mathf.Min(size.x, size.z), 0f); // TODO(wyatt): need to handle seams
|
||
|
mat.SetTexture("_FilterTex", brushMask);
|
||
|
mat.SetTexture("_MainTex", ctx.sourceRenderTexture);
|
||
|
Graphics.Blit(ctx.sourceRenderTexture, ctx.destinationRenderTexture, mat, 1);
|
||
|
TerrainPaintUtility.EndPaintHeightmap(ctx, "Set Height - Flatten Tile");
|
||
|
RTUtils.Release(brushMask);
|
||
|
PaintContext.ApplyDelayedActions();
|
||
|
}
|
||
|
|
||
|
void FlattenAll(Terrain terrain)
|
||
|
{
|
||
|
Terrain[] terrains = TerrainFillUtility.GetTerrainsInGroup(terrain);
|
||
|
|
||
|
for (int i = 0; i < terrains.Length; ++i)
|
||
|
{
|
||
|
Flatten(terrains[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void SetTargetHeight(float targetHeight)
|
||
|
{
|
||
|
m_TargetHeight = targetHeight;
|
||
|
}
|
||
|
public override void OnInspectorGUI(Terrain terrain, IOnInspectorGUI editContext)
|
||
|
{
|
||
|
EditorGUI.BeginChangeCheck();
|
||
|
|
||
|
commonUI.OnInspectorGUI(terrain, editContext);
|
||
|
|
||
|
s_showToolControls = TerrainToolGUIHelper.DrawHeaderFoldoutForBrush(Styles.controlHeader, s_showToolControls, () => { m_TargetHeight = 0; });
|
||
|
|
||
|
if (s_showToolControls)
|
||
|
{
|
||
|
EditorGUILayout.BeginVertical("GroupBox");
|
||
|
{
|
||
|
#if UNITY_2019_3_OR_NEWER
|
||
|
EditorGUI.BeginChangeCheck();
|
||
|
m_HeightSpace = (HeightSpace)EditorGUILayout.EnumPopup(Styles.space, m_HeightSpace);
|
||
|
if (EditorGUI.EndChangeCheck())
|
||
|
{
|
||
|
if (m_HeightSpace == HeightSpace.Local)
|
||
|
m_TargetHeight = Mathf.Clamp(m_TargetHeight, terrain.GetPosition().y, terrain.terrainData.size.y + terrain.GetPosition().y);
|
||
|
}
|
||
|
|
||
|
if (m_HeightSpace == HeightSpace.Local)
|
||
|
{
|
||
|
m_TargetHeight = EditorGUILayout.Slider(Styles.height, m_TargetHeight - terrain.GetPosition().y, 0, terrain.terrainData.size.y) + terrain.GetPosition().y;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_TargetHeight = EditorGUILayout.FloatField(Styles.height, m_TargetHeight);
|
||
|
}
|
||
|
#else
|
||
|
m_TargetHeight = EditorGUILayout.Slider(Styles.height, m_TargetHeight, 0, terrain.terrainData.size.y);
|
||
|
#endif
|
||
|
GUILayout.BeginHorizontal();
|
||
|
GUILayout.FlexibleSpace();
|
||
|
if (GUILayout.Button(Styles.flatten, GUILayout.ExpandWidth(false)))
|
||
|
{
|
||
|
Flatten(terrain);
|
||
|
}
|
||
|
if (GUILayout.Button(Styles.flattenAll, GUILayout.ExpandWidth(false)))
|
||
|
{
|
||
|
FlattenAll(terrain);
|
||
|
}
|
||
|
GUILayout.EndHorizontal();
|
||
|
|
||
|
if (EditorGUI.EndChangeCheck())
|
||
|
{
|
||
|
Save(true);
|
||
|
SaveSetting();
|
||
|
TerrainToolsAnalytics.OnParameterChange();
|
||
|
}
|
||
|
}
|
||
|
EditorGUILayout.EndVertical();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void SaveSetting()
|
||
|
{
|
||
|
EditorPrefs.SetFloat("Unity.TerrainTools.SetHeight.Height", m_TargetHeight);
|
||
|
}
|
||
|
|
||
|
private void LoadSettings()
|
||
|
{
|
||
|
m_TargetHeight = EditorPrefs.GetFloat("Unity.TerrainTools.SetHeight.Height", 0.0f);
|
||
|
}
|
||
|
|
||
|
//Analytics Setup
|
||
|
private TerrainToolsAnalytics.IBrushParameter[] UpdateAnalyticParameters() => new TerrainToolsAnalytics.IBrushParameter[]{
|
||
|
new TerrainToolsAnalytics.BrushParameter<float>{Name = Styles.height.text, Value = m_TargetHeight},
|
||
|
#if UNITY_2019_3_OR_NEWER
|
||
|
new TerrainToolsAnalytics.BrushParameter<string>{Name = Styles.space.text, Value = m_HeightSpace.ToString()},
|
||
|
#endif
|
||
|
};
|
||
|
}
|
||
|
}
|