Firstborn/Library/PackageCache/com.unity.terrain-tools@4.0.5/Editor/TerrainTools/StampTool.cs

540 lines
26 KiB
C#
Raw Normal View History

2023-03-28 13:24:16 -04:00
using UnityEngine;
using UnityEngine.TerrainTools;
using UnityEditor.ShortcutManagement;
using UnityEngine.Experimental.Rendering;
namespace UnityEditor.TerrainTools
{
//[FilePathAttribute("Library/TerrainTools/Stamp", FilePathAttribute.Location.ProjectFolder)]
internal class StampTool : TerrainPaintTool<StampTool>
{
#if UNITY_2019_1_OR_NEWER
[Shortcut("Terrain/Select Stamp Tool", typeof(TerrainToolShortcutContext))]
static void SelectShortcut(ShortcutArguments args)
{
TerrainToolShortcutContext context = (TerrainToolShortcutContext)args.context;
context.SelectPaintTool<StampTool>();
TerrainToolsAnalytics.OnShortcutKeyRelease("Select Stamp Tool");
}
[ClutchShortcut("Terrain/Adjust Mesh Stamp Transform", typeof(TerrainToolShortcutContext), KeyCode.C)]
static void StrengthBrushShortcut(ShortcutArguments args)
{
if (args.stage == ShortcutStage.Begin && s_StampToolProperties.mode == StampToolMode.Mesh)
{
s_EditTransform = true;
}
else if (args.stage == ShortcutStage.End && s_StampToolProperties.mode == StampToolMode.Mesh)
{
s_EditTransform = false;
TerrainToolsAnalytics.OnShortcutKeyRelease("Adjust Mesh Stamp Transform");
}
}
#endif
static class Styles
{
public static readonly GUIContent controls = EditorGUIUtility.TrTextContent("Stamp Tool Controls");
public static readonly GUIContent description = EditorGUIUtility.TrTextContent("Left click to stamp the brush onto the terrain.\n\nHold control and mousewheel to adjust height.");
public static readonly GUIContent height = EditorGUIUtility.TrTextContent("Stamp Height", "You can set the Stamp Height manually or you can hold control and mouse wheel on the terrain to adjust it.");
public static readonly GUIContent blendAmount = EditorGUIUtility.TrTextContent("Blend Amount", "Blends between replacing and offsetting the existing heights under the stamp.");
public static readonly GUIContent stampToolBehavior = EditorGUIUtility.TrTextContent("Behavior", "Stamping behavior.");
public static readonly GUIContent minBehavior = EditorGUIUtility.TrTextContent("Min", "Stamp where the terrain's height is greater than the input height.");
public static readonly GUIContent maxBehavior = EditorGUIUtility.TrTextContent("Max", "Stamp where the terrain's height is less than the input height.");
public static readonly GUIContent setBehavior = EditorGUIUtility.TrTextContent("Set", "Stamp the terrain setting it's height to the input height");
public static readonly GUIContent stampToolMode = EditorGUIUtility.TrTextContent("Stamp Mode", "Select the mode used to stamp your terrain.");
public static readonly GUIContent brushMode = EditorGUIUtility.TrTextContent("Brush", "Stamp the terrain using a brush.");
public static readonly GUIContent meshMode = EditorGUIUtility.TrTextContent("Mesh", "Stamp the terrain using a mesh.");
public static readonly GUIContent stampHeightContent = EditorGUIUtility.TrTextContent("Height Offset", "The height to stamp the mesh into the terrain at.");
public static readonly GUIContent stampScaleContent = EditorGUIUtility.TrTextContent("Scale", "The scale of the mesh.");
public static readonly GUIContent stampRotationContent = EditorGUIUtility.TrTextContent("Rotation", "The rotation of the mesh.");
public static readonly GUIContent meshContent = EditorGUIUtility.TrTextContent("Mesh", "The mesh to stamp.");
public static readonly GUIContent settings = EditorGUIUtility.TrTextContent("Mesh Stamp Settings");
public static readonly GUIContent transformSettings = EditorGUIUtility.TrTextContent("Transform Settings:");
public static readonly GUIContent resetTransformContent = EditorGUIUtility.TrTextContent("Reset",
"Resets the mesh's rotation, scale, and height to their default state.");
public static readonly string nullMeshString = "Must assign a mesh to use with the Mesh Stamp Tool.";
}
enum StampToolBehavior
{
Min,
Max,
Set,
}
[System.Serializable]
public enum StampToolMode
{
Mesh,
Brush
}
private enum ShaderPasses
{
BrushPreviewFrontFaces = 0,
BrushPreviewBackFaces,
DepthPassFrontFaces,
DepthPassBackFaces,
StampToHeightmap
}
static class RenderTextureIDs
{
public static int cameraView = "cameraView".GetHashCode();
public static int meshStamp = "meshStamp".GetHashCode();
public static int meshStampPreview = "meshStampPreview".GetHashCode();
public static int meshStampMask = "meshStampMask".GetHashCode();
public static int sourceHeight = "sourceHeight".GetHashCode();
public static int combinedHeight = "combinedHeight".GetHashCode();
}
[System.Serializable]
class StampToolSerializedProperties
{
public StampToolBehavior behavior;
public StampToolMode mode;
public float stampHeight;
public float blendAmount;
//Mesh stamp
public Quaternion rotation;
public Vector3 scale;
public string meshAssetGUID;
public bool showToolSettings;
public void SetDefaults()
{
behavior = StampToolBehavior.Set;
mode = StampToolMode.Brush;
stampHeight = 100.0f;
blendAmount = 0.0f;
rotation = Quaternion.identity;
scale = Vector3.one;
meshAssetGUID = null;
showToolSettings = true;
}
}
private Mesh m_ActiveMesh;
public Mesh activeMesh
{
get
{
if (m_ActiveMesh == null && !string.IsNullOrEmpty(s_StampToolProperties.meshAssetGUID))
{
m_ActiveMesh = AssetDatabase.LoadAssetAtPath<Mesh>(AssetDatabase.GUIDToAssetPath(s_StampToolProperties.meshAssetGUID));
}
return m_ActiveMesh;
}
set
{
m_ActiveMesh = value;
}
}
static StampToolSerializedProperties s_StampToolProperties = new StampToolSerializedProperties();
static bool s_PrevEditTransform = false;
static bool s_EditTransform = false;
[System.NonSerialized]
bool m_Initialized = false;
bool m_DebugOrtho = true;
float m_HandleHeightOffsetWS;
RTHandleCollection m_RtCollection;
Vector3 m_BaseHandlePos;
[SerializeField]
IBrushUIGroup m_CommonUI;
private IBrushUIGroup commonUI {
get
{
if (m_CommonUI == null)
{
LoadSettings();
m_CommonUI = new DefaultBrushUIGroup("StampTool", UpdateAnalyticParameters, DefaultBrushUIGroup.Feature.NoSpacing);
m_CommonUI.OnEnterToolMode();
}
return m_CommonUI;
}
}
private Material m_Material = null;
private Material GetMaterial()
{
if (m_Material == null)
{
m_Material = new Material(Shader.Find("Hidden/TerrainEngine/PaintHeightTool"));
}
return m_Material;
}
private void Init()
{
if (!m_Initialized)
{
m_RtCollection = new RTHandleCollection();
m_RtCollection.AddRTHandle(RenderTextureIDs.cameraView, "cameraView", GraphicsFormat.R8G8B8A8_UNorm);
m_RtCollection.AddRTHandle(RenderTextureIDs.meshStamp, "meshStamp", GraphicsFormat.R16_SFloat);
m_RtCollection.AddRTHandle(RenderTextureIDs.meshStampPreview, "meshStampPreview", GraphicsFormat.R16_SFloat);
m_RtCollection.AddRTHandle(RenderTextureIDs.meshStampMask, "meshStampMask", GraphicsFormat.R16_UNorm);
m_RtCollection.AddRTHandle(RenderTextureIDs.sourceHeight, "sourceHeight", GraphicsFormat.R16_UNorm);
m_RtCollection.AddRTHandle(RenderTextureIDs.combinedHeight, "combinedHeight", GraphicsFormat.R16_UNorm);
m_Initialized = true;
}
}
/// <summary>
/// Allows overriding for unit testing purposes
/// </summary>
/// <param name="uiGroup"></param>
internal void ChangeCommonUI(IBrushUIGroup uiGroup)
{
m_CommonUI = uiGroup;
}
public override string GetName()
{
return "Stamp Terrain";
}
public override string GetDescription()
{
return "Projects the shape of a 3d mesh or brush mask onto the Terrain.\n\n" +
"Hold Ctrl + Scroll with the mouse wheel to change the Stamp Height.\n" +
"Hold Ctrl + Click to subtract the mesh.\n" +
"Hold C to rotate, scale, or set a height offset of the mesh using an interactive gizmo.";
}
internal void SetStampHeight(float height)
{
s_StampToolProperties.SetDefaults();
s_StampToolProperties.stampHeight = height;
}
private void ApplyBrushInternal(IPaintContextRender renderer, PaintContext paintContext, float brushStrength, Texture brushTexture, BrushTransform brushXform, Terrain terrain)
{
Material mat = GetMaterial();
float HeightUnderCursor = terrain.SampleHeight(commonUI.raycastHitUnderCursor.point) / (terrain.terrainData.size.y * 2.0f);
float height = s_StampToolProperties.stampHeight * brushStrength / (terrain.terrainData.size.y * 2f);
Vector4 brushParams = new Vector4((int) s_StampToolProperties.behavior, HeightUnderCursor, height, s_StampToolProperties.blendAmount);
mat.SetVector("_BrushParams", brushParams);
var brushMask = RTUtils.GetTempHandle(paintContext.sourceRenderTexture.width, paintContext.sourceRenderTexture.height, 0, FilterUtility.defaultFormat);
Utility.GenerateAndSetFilterRT(commonUI, paintContext.sourceRenderTexture, brushMask, mat);
if (s_StampToolProperties.mode == StampToolMode.Mesh)
{
Init();
m_RtCollection.ReleaseRTHandles();
m_RtCollection.GatherRTHandles(paintContext.sourceRenderTexture.width, paintContext.sourceRenderTexture.height, 16);
Graphics.Blit(paintContext.sourceRenderTexture, m_RtCollection[RenderTextureIDs.sourceHeight]);
Matrix4x4 toolMatrix = Matrix4x4.TRS(Vector3.zero, s_StampToolProperties.rotation, s_StampToolProperties.scale);
Bounds modelBounds = activeMesh.bounds;
float maxModelScale = Mathf.Max(Mathf.Max(modelBounds.size.x, modelBounds.size.y), modelBounds.size.z);
float x = .5f;
float y = .5f;
float xy = Mathf.Sqrt(x * x + y * y);
float z = .5f;
float xyz = Mathf.Sqrt(xy * xy + z * z);
maxModelScale *= xyz;
// build the model matrix to transform the mesh with. we want to scale it to fit in the brush bounds and also center it in the brush bounds
Matrix4x4 model = toolMatrix * Matrix4x4.Scale(Vector3.one / maxModelScale) * Matrix4x4.Translate(-modelBounds.center);
// actually render the mesh to texture to be used with the tool shader
MeshUtils.RenderTopdownProjection(activeMesh, model,
m_RtCollection[RenderTextureIDs.meshStamp],
MeshUtils.defaultProjectionMaterial,
MeshUtils.ShaderPass.Height);
// this doesn't actually apply any noise to the destination RT but will color the destination RT based on whether the fragment values are (+) or (-)
NoiseUtils.BlitPreview2D(m_RtCollection[RenderTextureIDs.meshStamp], m_RtCollection[RenderTextureIDs.meshStampPreview]);
Graphics.Blit(paintContext.destinationRenderTexture, m_RtCollection[RenderTextureIDs.combinedHeight]);
mat.SetTexture("_BrushTex", m_RtCollection[RenderTextureIDs.meshStamp]);
// restore old render target
RenderTexture.active = paintContext.oldRenderTexture;
}
else
{
mat.SetTexture("_BrushTex", brushTexture);
}
renderer.SetupTerrainToolMaterialProperties(paintContext, brushXform, mat);
renderer.RenderBrush(paintContext, mat, (int)TerrainBuiltinPaintMaterialPasses.StampHeight);
RTUtils.Release(brushMask);
}
public override void OnEnterToolMode()
{
Init();
base.OnEnterToolMode();
commonUI.OnEnterToolMode();
}
public override void OnExitToolMode()
{
base.OnExitToolMode();
commonUI.OnExitToolMode();
}
public override bool OnPaint(Terrain terrain, IOnPaint editContext)
{
if (s_StampToolProperties.mode == StampToolMode.Mesh && activeMesh == null || s_EditTransform)
{
return false;
}
commonUI.OnPaint(terrain, editContext);
// ignore mouse drags
if (commonUI.allowPaint && Event.current == null || Event.current.type != EventType.MouseDrag)
{
Texture brushTexture = editContext.brushTexture;
using (IBrushRenderUnderCursor brushRender = new BrushRenderUIGroupUnderCursor(commonUI, "Stamp", brushTexture))
{
if (brushRender.CalculateBrushTransform(out BrushTransform brushXform))
{
PaintContext paintContext = brushRender.AcquireHeightmap(true, brushXform.GetBrushXYBounds());
ApplyBrushInternal(brushRender, paintContext, commonUI.brushStrength, brushTexture, brushXform, terrain);
}
}
}
return true;
}
public override void OnSceneGUI(Terrain terrain, IOnSceneGUI editContext)
{
Init();
commonUI.OnSceneGUI2D(terrain, editContext);
if (!editContext.hitValidTerrain && !commonUI.isInUse && !s_EditTransform && !m_DebugOrtho)
{
return;
}
commonUI.OnSceneGUI(terrain, editContext);
bool justPressedEditKey = s_EditTransform && !s_PrevEditTransform;
bool justReleaseEditKey = s_PrevEditTransform && !s_EditTransform;
s_PrevEditTransform = s_EditTransform;
if (justPressedEditKey)
{
(commonUI as BaseBrushUIGroup).LockTerrainUnderCursor(true);
m_BaseHandlePos = commonUI.raycastHitUnderCursor.point;
m_HandleHeightOffsetWS = 0;
}
else if (justReleaseEditKey)
{
(commonUI as BaseBrushUIGroup).UnlockTerrainUnderCursor();
m_HandleHeightOffsetWS = 0;
}
// don't render mesh previews, etc. if the mesh field has not been populated yet
if (s_StampToolProperties.mode == StampToolMode.Mesh && activeMesh == null)
{
return;
}
Event evt = Event.current;
if (evt.control && (evt.type == EventType.ScrollWheel))
{
const float k_mouseWheelToHeightRatio = -0.004f;
s_StampToolProperties.stampHeight += Event.current.delta.y * k_mouseWheelToHeightRatio * editContext.raycastHit.distance;
evt.Use();
editContext.Repaint();
SaveSettings();
}
// We're only doing painting operations, early out if it's not a repaint
if (evt.type == EventType.Repaint && commonUI.isRaycastHitUnderCursorValid)
{
Texture brushTexture = editContext.brushTexture;
using (IBrushRenderPreviewUnderCursor brushRender = new BrushRenderPreviewUIGroupUnderCursor(commonUI, "Stamp", brushTexture))
{
if (brushRender.CalculateBrushTransform(out BrushTransform brushXform))
{
PaintContext paintContext = brushRender.AcquireHeightmap(false, brushXform.GetBrushXYBounds(), 1);
Material previewMaterial = Utility.GetDefaultPreviewMaterial(commonUI.hasEnabledFilters);
var texelCtx = Utility.CollectTexelValidity(paintContext.originTerrain, brushXform.GetBrushXYBounds());
Utility.SetupMaterialForPaintingWithTexelValidityContext(paintContext, texelCtx, brushXform, 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, brushXform, previewMaterial, 0);
// draw result preview
{
ApplyBrushInternal(brushRender, paintContext, commonUI.brushStrength, brushTexture, brushXform, terrain);
// restore old render target
RenderTexture.active = paintContext.oldRenderTexture;
previewMaterial.SetTexture("_HeightmapOrig", paintContext.sourceRenderTexture);
TerrainPaintUtilityEditor.DrawBrushPreview(paintContext, TerrainBrushPreviewMode.DestinationRenderTexture,
editContext.brushTexture, brushXform, previewMaterial, 1);
}
TerrainPaintUtility.ReleaseContextResources(paintContext);
m_RtCollection.ReleaseRTHandles();
texelCtx.Cleanup();
RTUtils.Release(filterRT);
}
}
}
if (s_EditTransform && s_StampToolProperties.mode == StampToolMode.Mesh)
{
EditorGUI.BeginChangeCheck();
{
Vector3 prevHandlePosWS = m_BaseHandlePos + Vector3.up * m_HandleHeightOffsetWS;
// draw transform handles
float handleSize = HandleUtility.GetHandleSize(prevHandlePosWS);
Quaternion brushRotation = Quaternion.AngleAxis(commonUI.brushRotation, Vector3.up);
Matrix4x4 brushRotMat = Matrix4x4.Rotate(brushRotation);
Matrix4x4 toolRotMat = Matrix4x4.Rotate(s_StampToolProperties.rotation);
Quaternion handleRot = MeshUtils.QuaternionFromMatrix(brushRotMat * toolRotMat);
Quaternion newRot = Handles.RotationHandle(handleRot, prevHandlePosWS);
s_StampToolProperties.rotation = MeshUtils.QuaternionFromMatrix(brushRotMat.inverse * Matrix4x4.Rotate(newRot));
s_StampToolProperties.scale = Handles.ScaleHandle(s_StampToolProperties.scale, prevHandlePosWS, handleRot, handleSize * 1.5f);
Vector3 currHandlePosWS = Handles.Slider(prevHandlePosWS, Vector3.up, handleSize, Handles.ArrowHandleCap, 1f);
float deltaHeight = (currHandlePosWS.y - prevHandlePosWS.y);
m_HandleHeightOffsetWS += deltaHeight;
s_StampToolProperties.stampHeight += deltaHeight;
}
if (EditorGUI.EndChangeCheck())
{
SaveSettings();
editContext.Repaint();
}
}
}
bool m_ShowControls = true;
public override void OnInspectorGUI(Terrain terrain, IOnInspectorGUI editContext)
{
EditorGUI.BeginChangeCheck();
commonUI.OnInspectorGUI(terrain, editContext);
m_ShowControls = TerrainToolGUIHelper.DrawHeaderFoldoutForBrush(Styles.controls, m_ShowControls, s_StampToolProperties.SetDefaults);
if (!m_ShowControls)
{
return;
}
EditorGUI.BeginChangeCheck();
{
EditorGUILayout.BeginVertical("GroupBox");
{
// brush coordinate space toolbar
EditorGUILayout.BeginHorizontal();
{
EditorGUILayout.PrefixLabel(Styles.stampToolMode);
if (GUILayout.Toggle(s_StampToolProperties.mode == StampToolMode.Brush, Styles.brushMode, GUI.skin.button))
{
s_StampToolProperties.mode = StampToolMode.Brush;
}
if (GUILayout.Toggle(s_StampToolProperties.mode == StampToolMode.Mesh, Styles.meshMode, GUI.skin.button))
{
s_StampToolProperties.mode = StampToolMode.Mesh;
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel(Styles.stampToolBehavior);
if (GUILayout.Toggle(s_StampToolProperties.behavior == StampToolBehavior.Min, Styles.minBehavior, GUI.skin.button))
{
s_StampToolProperties.behavior = StampToolBehavior.Min;
}
if (GUILayout.Toggle(s_StampToolProperties.behavior == StampToolBehavior.Set, Styles.setBehavior, GUI.skin.button))
{
s_StampToolProperties.behavior = StampToolBehavior.Set;
}
if (GUILayout.Toggle(s_StampToolProperties.behavior == StampToolBehavior.Max, Styles.maxBehavior, GUI.skin.button))
{
s_StampToolProperties.behavior = StampToolBehavior.Max;
}
EditorGUILayout.EndHorizontal();
s_StampToolProperties.stampHeight = EditorGUILayout.Slider(Styles.height, s_StampToolProperties.stampHeight, -terrain.terrainData.size.y, terrain.terrainData.size.y);
s_StampToolProperties.blendAmount = EditorGUILayout.Slider(Styles.blendAmount, s_StampToolProperties.blendAmount, 0.0f, 1.0f);
s_StampToolProperties.showToolSettings = TerrainToolGUIHelper.DrawDisableableLabelFoldout(Styles.settings, s_StampToolProperties.showToolSettings, s_StampToolProperties.mode != StampToolMode.Brush);
EditorGUI.BeginDisabledGroup(s_StampToolProperties.mode == StampToolMode.Brush);
{
EditorGUI.indentLevel++;
if (s_StampToolProperties.showToolSettings)
{
if (activeMesh == null)
{
EditorGUILayout.HelpBox(Styles.nullMeshString, MessageType.Warning);
}
activeMesh = EditorGUILayout.ObjectField(Styles.meshContent, activeMesh, typeof(Mesh), false) as Mesh;
s_StampToolProperties.scale = EditorGUILayout.Vector3Field(Styles.stampScaleContent, s_StampToolProperties.scale);
s_StampToolProperties.rotation = Quaternion.Euler(EditorGUILayout.Vector3Field(Styles.stampRotationContent, s_StampToolProperties.rotation.eulerAngles));
}
EditorGUI.indentLevel--;
}
EditorGUI.EndDisabledGroup();
}
EditorGUILayout.EndVertical();
}
if (EditorGUI.EndChangeCheck())
{
SaveSettings();
Save(true);
TerrainToolsAnalytics.OnParameterChange();
}
base.OnInspectorGUI(terrain, editContext);
}
private void SaveSettings()
{
s_StampToolProperties.meshAssetGUID = activeMesh == null ? "" : AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(activeMesh));
string stampToolData = JsonUtility.ToJson(s_StampToolProperties);
EditorPrefs.SetString("Unity.TerrainTools.Stamp", stampToolData);
}
private void LoadSettings()
{
string stampToolData = EditorPrefs.GetString("Unity.TerrainTools.Stamp");
s_StampToolProperties.SetDefaults();
JsonUtility.FromJsonOverwrite(stampToolData, s_StampToolProperties);
}
//Analytics Setup
private TerrainToolsAnalytics.IBrushParameter[] UpdateAnalyticParameters() => new TerrainToolsAnalytics.IBrushParameter[]{
new TerrainToolsAnalytics.BrushParameter<string>{Name = Styles.stampToolBehavior.text, Value = s_StampToolProperties.behavior.ToString()},
new TerrainToolsAnalytics.BrushParameter<float>{Name = Styles.height.text, Value = s_StampToolProperties.stampHeight},
new TerrainToolsAnalytics.BrushParameter<float>{Name = Styles.blendAmount.text, Value = s_StampToolProperties.blendAmount},
new TerrainToolsAnalytics.BrushParameter<Vector3>{Name = Styles.stampScaleContent.text, Value = s_StampToolProperties.scale},
new TerrainToolsAnalytics.BrushParameter<Vector3>{Name = Styles.stampRotationContent.text, Value = s_StampToolProperties.rotation.eulerAngles},
};
}
}