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

330 lines
16 KiB
C#
Raw Normal View History

2023-03-28 13:24:16 -04:00
using UnityEngine;
using UnityEngine.TerrainTools;
using UnityEditor.ShortcutManagement;
namespace UnityEditor.TerrainTools
{
internal class BridgeTool : TerrainPaintTool<BridgeTool>
{
#if UNITY_2019_1_OR_NEWER
[Shortcut("Terrain/Select Bridge Tool", typeof(TerrainToolShortcutContext))] // tells shortcut manager what to call the shortcut and what to pass as args
static void SelectShortcut(ShortcutArguments args) {
TerrainToolShortcutContext context = (TerrainToolShortcutContext)args.context; // gets interface to modify state of TerrainTools
context.SelectPaintTool<BridgeTool>(); // set active tool
TerrainToolsAnalytics.OnShortcutKeyRelease("Select Bridge Tool");
}
#endif
[SerializeField]
IBrushUIGroup m_commonUI;
private IBrushUIGroup commonUI
{
get
{
if( m_commonUI == null )
{
LoadSettings();
m_commonUI = new DefaultBrushUIGroup( "BridgeTool", UpdateAnalyticParameters, DefaultBrushUIGroup.Feature.NoScatter );
m_commonUI.OnEnterToolMode();
}
return m_commonUI;
}
}
Terrain m_StartTerrain = null;
private Vector3 m_StartPoint;
Material m_Material = null;
Material GetPaintMaterial() {
if (m_Material == null)
m_Material = new Material(Shader.Find("Hidden/TerrainTools/SetExactHeight"));
return m_Material;
}
[System.Serializable]
class BridgeToolSerializedProperties
{
public AnimationCurve widthProfile;
public AnimationCurve heightProfile;
public AnimationCurve strengthProfile;
public AnimationCurve jitterProfile;
public void SetDefaults()
{
widthProfile = AnimationCurve.Linear(0, 1, 1, 1);
heightProfile = AnimationCurve.Linear(0, 0, 1, 0);
strengthProfile = AnimationCurve.Linear(0, 1, 1, 1);
jitterProfile = AnimationCurve.Linear(0, 0, 1, 0);
}
}
BridgeToolSerializedProperties bridgeToolProperties = new BridgeToolSerializedProperties();
public override string GetName()
{
return "Sculpt/Bridge";
}
public override string GetDescription()
{
return "Connects two points on a terrain creating a bridge.\n\n" +
"Hold Ctrl + Click to select the starting position.\n" +
"Release Ctrl + Click to set the end position.";
}
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);
if (editContext.hitValidTerrain || commonUI.isInUse)
{
commonUI.OnSceneGUI(terrain, editContext);
if (Event.current.type != EventType.Repaint)
{
return;
}
var previewMaterial = Utility.GetDefaultPreviewMaterial(commonUI.hasEnabledFilters);
float endWidth = Mathf.Abs(bridgeToolProperties.widthProfile.Evaluate(1.0f));
BrushTransform brushXform = TerrainPaintUtility.CalculateBrushTransform(terrain, commonUI.raycastHitUnderCursor.textureCoord, commonUI.brushSize * endWidth, commonUI.brushRotation);
PaintContext ctx = TerrainPaintUtility.BeginPaintHeightmap(terrain, brushXform.GetBrushXYBounds(), 1);
var brushMask = RTUtils.GetTempHandle(ctx.sourceRenderTexture.width, ctx.sourceRenderTexture.height, 0, FilterUtility.defaultFormat);
Utility.GenerateAndSetFilterRT(commonUI, ctx.sourceRenderTexture, brushMask, previewMaterial);
var texelCtx = Utility.CollectTexelValidity(ctx.originTerrain, brushXform.GetBrushXYBounds());
Utility.SetupMaterialForPaintingWithTexelValidityContext(ctx, texelCtx, brushXform, previewMaterial);
TerrainPaintUtilityEditor.DrawBrushPreview(ctx, TerrainBrushPreviewMode.SourceRenderTexture,
editContext.brushTexture, brushXform, previewMaterial, 0);
texelCtx.Cleanup();
ctx.Cleanup();
RTUtils.Release(brushMask);
}
if (Event.current.type != EventType.Repaint)
{
return;
}
//display a brush preview at the bridge starting location, using starting size from width profile
if (m_StartTerrain != null)
{
float startWidth = Mathf.Abs(bridgeToolProperties.widthProfile.Evaluate(0.0f));
float brushSize = commonUI.brushSize * startWidth;
Material previewMaterial = Utility.GetDefaultPreviewMaterial();
BrushTransform brushTransform = TerrainPaintUtility.CalculateBrushTransform(m_StartTerrain, m_StartPoint, brushSize, commonUI.brushRotation);
PaintContext sampleContext = TerrainPaintUtility.BeginPaintHeightmap(m_StartTerrain, brushTransform.GetBrushXYBounds());
var texelCtx = Utility.CollectTexelValidity(sampleContext.originTerrain, brushTransform.GetBrushXYBounds());
Utility.SetupMaterialForPaintingWithTexelValidityContext(sampleContext, texelCtx, brushTransform, previewMaterial);
TerrainPaintUtilityEditor.DrawBrushPreview(sampleContext, TerrainBrushPreviewMode.SourceRenderTexture,
editContext.brushTexture, brushTransform, previewMaterial, 0);
texelCtx.Cleanup();
sampleContext.Cleanup();
}
}
bool m_ShowBridgeControls = true;
public override void OnInspectorGUI(Terrain terrain, IOnInspectorGUI editContext)
{
EditorGUI.BeginChangeCheck();
commonUI.OnInspectorGUI(terrain, editContext);
m_ShowBridgeControls = TerrainToolGUIHelper.DrawHeaderFoldoutForBrush(Styles.controlHeader, m_ShowBridgeControls, bridgeToolProperties.SetDefaults);
if (m_ShowBridgeControls) {
//"Controls the width of the bridge over the length of the stroke"
bridgeToolProperties.widthProfile = EditorGUILayout.CurveField(Styles.widthProfileContent, bridgeToolProperties.widthProfile);
bridgeToolProperties.heightProfile = EditorGUILayout.CurveField(Styles.heightProfileContent, bridgeToolProperties.heightProfile);
bridgeToolProperties.strengthProfile = EditorGUILayout.CurveField(Styles.strengthProfileContent, bridgeToolProperties.strengthProfile);
bridgeToolProperties.jitterProfile = EditorGUILayout.CurveField(Styles.jitterProfileContent, bridgeToolProperties.jitterProfile);
}
if (EditorGUI.EndChangeCheck())
{
SaveSetting();
Save(true);
TerrainToolsAnalytics.OnParameterChange();
editContext.Repaint(RepaintFlags.Scene);
}
}
private Vector2 transformToWorld(Terrain t, Vector2 uvs)
{
Vector3 tilePos = t.GetPosition();
return new Vector2(tilePos.x, tilePos.z) + uvs * new Vector2(t.terrainData.size.x, t.terrainData.size.z);
}
private Vector2 transformToUVSpace(Terrain originTile, Vector2 worldPos) {
Vector3 originTilePos = originTile.GetPosition();
Vector2 uvPos = new Vector2((worldPos.x - originTilePos.x) / originTile.terrainData.size.x,
(worldPos.y - originTilePos.z) / originTile.terrainData.size.z);
return uvPos;
}
private void ApplyBrushInternal(Terrain terrain, Vector2 uv, Texture brushTexture, float brushSpacing)
{
//get the target position & height
float targetHeight = terrain.terrainData.GetInterpolatedHeight(uv.x, uv.y) / terrain.terrainData.size.y;
Vector3 targetPos = new Vector3(uv.x, uv.y, targetHeight);
if (terrain != m_StartTerrain) {
//figure out the stroke vector in uv,height space
Vector2 targetWorld = transformToWorld(terrain, uv);
Vector2 targetUVs = transformToUVSpace(m_StartTerrain, targetWorld);
targetPos.x = targetUVs.x;
targetPos.y = targetUVs.y;
}
Vector3 stroke = targetPos - m_StartPoint;
float strokeLength = stroke.magnitude;
int numSplats = (int)(strokeLength / (0.1f * Mathf.Max(brushSpacing, 0.01f)));
Terrain currTerrain = m_StartTerrain;
Material mat = GetPaintMaterial();
Vector2 posOffset = new Vector2(0.0f, 0.0f);
Vector2 currUV = new Vector2();
Vector4 brushParams = new Vector4();
Vector2 jitterVec = new Vector2(-stroke.z, stroke.x); //perpendicular to stroke direction
jitterVec.Normalize();
for (int i = 0; i < numSplats; i++)
{
float pct = (float)i / (float)numSplats;
float widthScale = bridgeToolProperties.widthProfile.Evaluate(pct);
float heightOffset = bridgeToolProperties.heightProfile.Evaluate(pct) / currTerrain.terrainData.size.y;
float strengthScale = bridgeToolProperties.strengthProfile.Evaluate(pct);
float jitterOffset = bridgeToolProperties.jitterProfile.Evaluate(pct) / Mathf.Max(currTerrain.terrainData.size.x, currTerrain.terrainData.size.z);
Vector3 currPos = m_StartPoint + pct * stroke;
//add in jitter offset (needs to happen before tile correction)
currPos.x += posOffset.x + jitterOffset * jitterVec.x;
currPos.y += posOffset.y + jitterOffset * jitterVec.y;
if (currPos.x >= 1.0f && (currTerrain.rightNeighbor != null)) {
currTerrain = currTerrain.rightNeighbor;
currPos.x -= 1.0f;
posOffset.x -= 1.0f;
}
if(currPos.x <= 0.0f && (currTerrain.leftNeighbor != null)) {
currTerrain = currTerrain.leftNeighbor;
currPos.x += 1.0f;
posOffset.x += 1.0f;
}
if(currPos.y >= 1.0f && (currTerrain.topNeighbor != null)) {
currTerrain = currTerrain.topNeighbor;
currPos.y -= 1.0f;
posOffset.y -= 1.0f;
}
if(currPos.y <= 0.0f && (currTerrain.bottomNeighbor != null)) {
currTerrain = currTerrain.bottomNeighbor;
currPos.y += 1.0f;
posOffset.y += 1.0f;
}
currUV.x = currPos.x;
currUV.y = currPos.y;
int finalBrushSize = (int)(widthScale * (float)commonUI.brushSize);
float finalHeight = (m_StartPoint + pct * stroke).z + heightOffset;
using(IBrushRenderWithTerrain brushRenderWithTerrain = new BrushRenderWithTerrainUIGroup(commonUI, "BridgeTool", brushTexture))
{
if(brushRenderWithTerrain.CalculateBrushTransform(currTerrain, currUV, finalBrushSize, out BrushTransform brushTransform))
{
Rect brushBounds = brushTransform.GetBrushXYBounds();
PaintContext paintContext = brushRenderWithTerrain.AcquireHeightmap(true, currTerrain, brushBounds);
mat.SetTexture("_BrushTex", brushTexture);
brushParams.x = commonUI.brushStrength * strengthScale;
brushParams.y = 0.5f * finalHeight;
mat.SetVector("_BrushParams", brushParams);
var brushMask = RTUtils.GetTempHandle(paintContext.sourceRenderTexture.width, paintContext.sourceRenderTexture.height, 0, FilterUtility.defaultFormat);
Utility.GenerateAndSetFilterRT(commonUI, paintContext.sourceRenderTexture, brushMask, mat);
brushRenderWithTerrain.SetupTerrainToolMaterialProperties(paintContext, brushTransform, mat);
brushRenderWithTerrain.RenderBrush(paintContext, mat, 0);
RTUtils.Release(brushMask);
}
}
}
}
public override bool OnPaint(Terrain terrain, IOnPaint editContext)
{
commonUI.OnPaint(terrain, editContext);
Vector2 uv = editContext.uv;
if(Event.current.shift) { return true; }
//grab the starting position & height
if (Event.current.control)
{
TerrainData terrainData = terrain.terrainData;
float height = terrainData.GetInterpolatedHeight(uv.x, uv.y) / terrainData.size.y;
m_StartPoint = new Vector3(uv.x, uv.y, height);
m_StartTerrain = terrain;
return true;
}
else if (!m_StartTerrain || (Event.current.type == EventType.MouseDrag)) {
return true;
}
else
{
ApplyBrushInternal(terrain, uv, editContext.brushTexture, commonUI.brushSpacing);
return false;
}
}
private static class Styles
{
public static readonly GUIContent controlHeader = EditorGUIUtility.TrTextContent("Bridge Tool Controls");
public static readonly GUIContent widthProfileContent = EditorGUIUtility.TrTextContent("Width Profile", "A multiplier that controls the width of the bridge over the length of the stroke");
public static readonly GUIContent heightProfileContent = EditorGUIUtility.TrTextContent("Height Offset Profile", "Adds a height offset to the bridge along the length of the stroke (World Units)");
public static readonly GUIContent strengthProfileContent = EditorGUIUtility.TrTextContent("Strength Profile", "A multiplier that controls influence of the bridge along the length of the stroke");
public static readonly GUIContent jitterProfileContent = EditorGUIUtility.TrTextContent("Horizontal Offset Profile", "Adds an offset perpendicular to the stroke direction (World Units)");
}
private void SaveSetting()
{
string bridgeToolData = JsonUtility.ToJson(bridgeToolProperties);
EditorPrefs.SetString("Unity.TerrainTools.Bridge", bridgeToolData);
}
private void LoadSettings()
{
string bridgeToolData = EditorPrefs.GetString("Unity.TerrainTools.Bridge");
bridgeToolProperties.SetDefaults();
JsonUtility.FromJsonOverwrite(bridgeToolData, bridgeToolProperties);
}
//Analytics Setup
private TerrainToolsAnalytics.IBrushParameter[] UpdateAnalyticParameters() => new TerrainToolsAnalytics.IBrushParameter[]{
new TerrainToolsAnalytics.BrushParameter<Keyframe[]>{Name = Styles.widthProfileContent.text, Value = bridgeToolProperties.widthProfile.keys},
new TerrainToolsAnalytics.BrushParameter<Keyframe[]>{Name = Styles.heightProfileContent.text, Value = bridgeToolProperties.heightProfile.keys},
new TerrainToolsAnalytics.BrushParameter<Keyframe[]>{Name = Styles.strengthProfileContent.text, Value = bridgeToolProperties.strengthProfile.keys},
new TerrainToolsAnalytics.BrushParameter<Keyframe[]>{Name = Styles.jitterProfileContent.text, Value = bridgeToolProperties.jitterProfile.keys},
};
}
}