532 lines
27 KiB
C#
532 lines
27 KiB
C#
|
using UnityEngine;
|
||
|
using UnityEngine.TerrainTools;
|
||
|
using UnityEditor.ShortcutManagement;
|
||
|
|
||
|
namespace UnityEditor.TerrainTools
|
||
|
{
|
||
|
internal class CloneBrushTool : TerrainPaintTool<CloneBrushTool>
|
||
|
{
|
||
|
#if UNITY_2019_1_OR_NEWER
|
||
|
[Shortcut("Terrain/Select Clone 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<CloneBrushTool>(); // set active tool
|
||
|
TerrainToolsAnalytics.OnShortcutKeyRelease("Select Clone Tool");
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
private bool m_ShowControls = true;
|
||
|
|
||
|
private enum ShaderPasses
|
||
|
{
|
||
|
CloneAlphamap = 0,
|
||
|
CloneHeightmap
|
||
|
}
|
||
|
|
||
|
internal enum MovementBehavior
|
||
|
{
|
||
|
FollowAlways = 0, // clone location will move with the brush always
|
||
|
Snap, // clone snaps back to set sample location on mouse up
|
||
|
FollowOnPaint, // clone location will move with the brush only when painting
|
||
|
Fixed, // clone wont move at all and will sample same location always
|
||
|
}
|
||
|
|
||
|
[System.Serializable]
|
||
|
struct BrushLocationData
|
||
|
{
|
||
|
public Terrain terrain;
|
||
|
public Vector3 pos;
|
||
|
|
||
|
public void Set(Terrain terrain, Vector3 pos)
|
||
|
{
|
||
|
this.terrain = terrain;
|
||
|
this.pos = pos;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[System.Serializable]
|
||
|
class CloneToolSerializedProperties
|
||
|
{
|
||
|
public MovementBehavior m_MovementBehavior;
|
||
|
public bool m_PaintHeightmap;
|
||
|
public bool m_PaintAlphamap;
|
||
|
public float m_StampingOffsetFromClone;
|
||
|
|
||
|
public void SetDefaults()
|
||
|
{
|
||
|
m_MovementBehavior = MovementBehavior.FollowAlways;
|
||
|
m_PaintHeightmap = true;
|
||
|
m_PaintAlphamap = true;
|
||
|
m_StampingOffsetFromClone = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CloneToolSerializedProperties cloneToolProperties = new CloneToolSerializedProperties();
|
||
|
|
||
|
|
||
|
[SerializeField]
|
||
|
IBrushUIGroup m_commonUI;
|
||
|
private IBrushUIGroup commonUI {
|
||
|
get
|
||
|
{
|
||
|
if (m_commonUI == null)
|
||
|
{
|
||
|
LoadSettings();
|
||
|
m_commonUI = new DefaultBrushUIGroup("CloneBrushTool", UpdateAnalyticParameters);
|
||
|
m_commonUI.OnEnterToolMode();
|
||
|
}
|
||
|
|
||
|
return m_commonUI;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// variables for keeping track of mouse and key presses and painting states
|
||
|
private bool m_lmb;
|
||
|
private bool m_ctrl;
|
||
|
private bool m_wasPainting;
|
||
|
private bool m_isPainting;
|
||
|
private bool m_HasDoneFirstPaint;
|
||
|
|
||
|
// The current brush location data we are sampling/cloning from
|
||
|
private BrushLocationData m_SampleLocation;
|
||
|
// Brush location defined when user ctrl-clicks. Where the sample location should
|
||
|
// "snap" back to when the user is not painting and clone behavior == Snap
|
||
|
[SerializeField]
|
||
|
private BrushLocationData m_SnapbackLocation;
|
||
|
// brush location data used for determining how much the user brush moved in a frame
|
||
|
private BrushLocationData m_PrevBrushLocation;
|
||
|
|
||
|
private static Material m_Material = null;
|
||
|
private static Material GetPaintMaterial()
|
||
|
{
|
||
|
if (m_Material == null)
|
||
|
{
|
||
|
m_Material = new Material(Shader.Find("Hidden/TerrainTools/CloneBrush"));
|
||
|
}
|
||
|
|
||
|
return m_Material;
|
||
|
}
|
||
|
|
||
|
public override string GetName()
|
||
|
{
|
||
|
return "Sculpt/Clone";
|
||
|
}
|
||
|
|
||
|
public override string GetDescription()
|
||
|
{
|
||
|
return Styles.descriptionString;
|
||
|
}
|
||
|
|
||
|
public override void OnEnterToolMode()
|
||
|
{
|
||
|
base.OnEnterToolMode();
|
||
|
commonUI.OnEnterToolMode();
|
||
|
}
|
||
|
|
||
|
public override void OnExitToolMode()
|
||
|
{
|
||
|
base.OnExitToolMode();
|
||
|
commonUI.OnExitToolMode();
|
||
|
}
|
||
|
|
||
|
public override void OnInspectorGUI(Terrain terrain, IOnInspectorGUI editContext)
|
||
|
{
|
||
|
EditorGUI.BeginChangeCheck();
|
||
|
|
||
|
commonUI.OnInspectorGUI(terrain, editContext);
|
||
|
|
||
|
m_ShowControls = TerrainToolGUIHelper.DrawHeaderFoldoutForBrush(Styles.controlHeader, m_ShowControls, cloneToolProperties.SetDefaults);
|
||
|
|
||
|
if (m_ShowControls)
|
||
|
{
|
||
|
EditorGUILayout.BeginVertical("GroupBox");
|
||
|
{
|
||
|
// draw button-like toggles for choosing which terrain textures to sample
|
||
|
EditorGUILayout.BeginHorizontal();
|
||
|
{
|
||
|
EditorGUILayout.PrefixLabel(Styles.cloneSourceContent);
|
||
|
|
||
|
cloneToolProperties.m_PaintAlphamap = GUILayout.Toggle(cloneToolProperties.m_PaintAlphamap, Styles.cloneTextureContent, GUI.skin.button);
|
||
|
cloneToolProperties.m_PaintHeightmap = GUILayout.Toggle(cloneToolProperties.m_PaintHeightmap, Styles.cloneHeightmapContent, GUI.skin.button);
|
||
|
}
|
||
|
EditorGUILayout.EndHorizontal();
|
||
|
|
||
|
cloneToolProperties.m_MovementBehavior = (MovementBehavior)EditorGUILayout.EnumPopup(Styles.cloneBehaviorContent, cloneToolProperties.m_MovementBehavior);
|
||
|
cloneToolProperties.m_StampingOffsetFromClone = EditorGUILayout.Slider(Styles.heightOffsetContent, cloneToolProperties.m_StampingOffsetFromClone,
|
||
|
-terrain.terrainData.size.y, terrain.terrainData.size.y);
|
||
|
}
|
||
|
EditorGUILayout.EndVertical();
|
||
|
}
|
||
|
|
||
|
if (EditorGUI.EndChangeCheck())
|
||
|
{
|
||
|
// intentionally do not reset HasDoneFirstPaint here because then changing the brush mask will corrupt the clone position
|
||
|
m_isPainting = false;
|
||
|
Save(true);
|
||
|
SaveSetting();
|
||
|
TerrainToolsAnalytics.OnParameterChange();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
|
||
|
ProcessInput(terrain, editContext);
|
||
|
|
||
|
if (!commonUI.isInUse)
|
||
|
{
|
||
|
UpdateBrushLocations(terrain, editContext);
|
||
|
}
|
||
|
|
||
|
// dont render preview if this isnt a repaint. losing performance if we do
|
||
|
if (Event.current.type != EventType.Repaint)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DrawBrushPreviews(terrain, editContext);
|
||
|
}
|
||
|
|
||
|
public override bool OnPaint(Terrain terrain, IOnPaint editContext)
|
||
|
{
|
||
|
commonUI.OnPaint(terrain, editContext);
|
||
|
|
||
|
if (!m_isPainting || m_SampleLocation.terrain == null)
|
||
|
return true;
|
||
|
|
||
|
if (commonUI.allowPaint)
|
||
|
{
|
||
|
Vector2 uv = editContext.uv;
|
||
|
|
||
|
if (commonUI.ScatterBrushStamp(ref terrain, ref uv))
|
||
|
{
|
||
|
// grab brush transforms for the sample location (where we are cloning from)
|
||
|
// and target location (where we are cloning to)
|
||
|
Vector2 sampleUV = TerrainUVFromBrushLocation(m_SampleLocation.terrain, m_SampleLocation.pos);
|
||
|
BrushTransform sampleBrushXform = TerrainPaintUtility.CalculateBrushTransform(m_SampleLocation.terrain, sampleUV, commonUI.brushSize, commonUI.brushRotation);
|
||
|
BrushTransform targetBrushXform = TerrainPaintUtility.CalculateBrushTransform(terrain, uv, commonUI.brushSize, commonUI.brushRotation);
|
||
|
|
||
|
// set material props that will be used for both heightmap and alphamap painting
|
||
|
Material mat = GetPaintMaterial();
|
||
|
Vector4 brushParams = new Vector4(commonUI.brushStrength, cloneToolProperties.m_StampingOffsetFromClone * 0.5f, terrain.terrainData.size.y, 0f);
|
||
|
mat.SetTexture("_BrushTex", editContext.brushTexture);
|
||
|
mat.SetVector("_BrushParams", brushParams);
|
||
|
|
||
|
// apply texture modifications to terrain
|
||
|
if (cloneToolProperties.m_PaintAlphamap)
|
||
|
PaintAlphamap(m_SampleLocation.terrain, terrain, sampleBrushXform, targetBrushXform, mat);
|
||
|
if (cloneToolProperties.m_PaintHeightmap)
|
||
|
PaintHeightmap(m_SampleLocation.terrain, terrain, sampleBrushXform, targetBrushXform, editContext, mat);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private void ProcessInput(Terrain terrain, IOnSceneGUI editContext)
|
||
|
{
|
||
|
// update Left Mouse Button state
|
||
|
if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && commonUI.isRaycastHitUnderCursorValid)
|
||
|
m_lmb = true;
|
||
|
else if (Event.current.type == EventType.MouseUp && Event.current.button == 0)
|
||
|
m_lmb = false;
|
||
|
|
||
|
if (!m_isPainting)
|
||
|
{
|
||
|
m_ctrl = Event.current.control;
|
||
|
}
|
||
|
|
||
|
m_wasPainting = m_isPainting;
|
||
|
|
||
|
// xxx(jcowles): this logic is no good becuse it makes assumptions about what modifier keys will enable/disable painting,
|
||
|
// however this cannot be known without querying other systems (e.g. orbiting the camera uses some combination of mouse
|
||
|
// buttons and modifier keys, but the exact configuration is a user setting). For now, assume when ALT is pressed, we are
|
||
|
// not painting.
|
||
|
m_isPainting = m_lmb && !m_ctrl && !Event.current.alt;
|
||
|
}
|
||
|
|
||
|
private void UpdateBrushLocations(Terrain terrain, IOnSceneGUI editContext)
|
||
|
{
|
||
|
if (!commonUI.isRaycastHitUnderCursorValid)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!m_isPainting)
|
||
|
{
|
||
|
// check to see if the user is selecting a new location for the clone sample
|
||
|
// and set the current sample location to that as well as the snap back location
|
||
|
if (m_lmb && m_ctrl)
|
||
|
{
|
||
|
m_HasDoneFirstPaint = false;
|
||
|
m_SampleLocation.Set(terrain, editContext.raycastHit.point);
|
||
|
m_SnapbackLocation.Set(terrain, editContext.raycastHit.point);
|
||
|
}
|
||
|
|
||
|
// snap the sample location back to the user-picked sample position
|
||
|
if (cloneToolProperties.m_MovementBehavior == MovementBehavior.Snap)
|
||
|
{
|
||
|
m_SampleLocation.Set(m_SnapbackLocation.terrain, m_SnapbackLocation.pos);
|
||
|
}
|
||
|
}
|
||
|
else if (!m_wasPainting && m_isPainting) // first frame of user painting
|
||
|
{
|
||
|
m_HasDoneFirstPaint = true;
|
||
|
// check if the user just started painting. do this so a delta pos
|
||
|
// isn't applied to the sample location on the first paint operation
|
||
|
m_PrevBrushLocation.Set(terrain, editContext.raycastHit.point);
|
||
|
}
|
||
|
|
||
|
bool updateClone = (m_isPainting && cloneToolProperties.m_MovementBehavior != MovementBehavior.Fixed) ||
|
||
|
(m_isPainting && cloneToolProperties.m_MovementBehavior == MovementBehavior.FollowOnPaint) ||
|
||
|
(m_HasDoneFirstPaint && cloneToolProperties.m_MovementBehavior == MovementBehavior.FollowAlways);
|
||
|
|
||
|
if (updateClone)
|
||
|
{
|
||
|
HandleBrushCrossingSeams(ref m_SampleLocation, editContext.raycastHit.point, m_PrevBrushLocation.pos);
|
||
|
}
|
||
|
|
||
|
// update the previous paint location for use in the next frame (if the user is painting)
|
||
|
m_PrevBrushLocation.Set(terrain, editContext.raycastHit.point);
|
||
|
}
|
||
|
|
||
|
// check to see if the sample brush is crossing any terrain seams/borders. have to do this manually
|
||
|
// since TerrainPaintUtility only immediate neighbors and not manually created PaintContexts
|
||
|
private void HandleBrushCrossingSeams(ref BrushLocationData brushLocation, Vector3 currBrushPos, Vector3 prevBrushPos)
|
||
|
{
|
||
|
if (brushLocation.terrain == null)
|
||
|
return;
|
||
|
|
||
|
Vector3 deltaPos = currBrushPos - prevBrushPos;
|
||
|
brushLocation.Set(brushLocation.terrain, brushLocation.pos + deltaPos);
|
||
|
|
||
|
Vector2 currUV = TerrainUVFromBrushLocation(brushLocation.terrain, brushLocation.pos);
|
||
|
|
||
|
if (currUV.x >= 1.0f && brushLocation.terrain.rightNeighbor != null)
|
||
|
brushLocation.terrain = brushLocation.terrain.rightNeighbor;
|
||
|
else if (currUV.x < 0.0f && brushLocation.terrain.leftNeighbor != null)
|
||
|
brushLocation.terrain = brushLocation.terrain.leftNeighbor;
|
||
|
|
||
|
if (currUV.y >= 1.0f && brushLocation.terrain.topNeighbor != null)
|
||
|
brushLocation.terrain = brushLocation.terrain.topNeighbor;
|
||
|
else if (currUV.y < 0.0f && brushLocation.terrain.bottomNeighbor != null)
|
||
|
brushLocation.terrain = brushLocation.terrain.bottomNeighbor;
|
||
|
}
|
||
|
|
||
|
private void DrawBrushPreviews(Terrain terrain, IOnSceneGUI editContext)
|
||
|
{
|
||
|
Vector2 sampleUV;
|
||
|
BrushTransform sampleXform;
|
||
|
PaintContext sampleContext = null;
|
||
|
// draw sample location brush and create context data to be used when drawing target brush previews
|
||
|
if (m_SampleLocation.terrain != null)
|
||
|
{
|
||
|
Material previewMat = Utility.GetDefaultPreviewMaterial();
|
||
|
sampleUV = TerrainUVFromBrushLocation(m_SampleLocation.terrain, m_SampleLocation.pos);
|
||
|
sampleXform = TerrainPaintUtility.CalculateBrushTransform(m_SampleLocation.terrain, sampleUV, commonUI.brushSize, commonUI.brushRotation);
|
||
|
sampleContext = TerrainPaintUtility.BeginPaintHeightmap(m_SampleLocation.terrain, sampleXform.GetBrushXYBounds());
|
||
|
var texelCtx = Utility.CollectTexelValidity(sampleContext.originTerrain, sampleXform.GetBrushXYBounds());
|
||
|
Utility.SetupMaterialForPaintingWithTexelValidityContext(sampleContext, texelCtx, sampleXform, previewMat);
|
||
|
TerrainPaintUtilityEditor.DrawBrushPreview(sampleContext, TerrainBrushPreviewMode.SourceRenderTexture,
|
||
|
editContext.brushTexture, sampleXform, previewMat, 0);
|
||
|
texelCtx.Cleanup();
|
||
|
}
|
||
|
|
||
|
// draw brush preview and mesh preview for current mouse position
|
||
|
if (commonUI.isRaycastHitUnderCursorValid)
|
||
|
{
|
||
|
Material previewMat = Utility.GetDefaultPreviewMaterial(commonUI.hasEnabledFilters);
|
||
|
BrushTransform brushXform = TerrainPaintUtility.CalculateBrushTransform(terrain, commonUI.raycastHitUnderCursor.textureCoord, commonUI.brushSize, commonUI.brushRotation);
|
||
|
PaintContext ctx = TerrainPaintUtility.BeginPaintHeightmap(terrain, brushXform.GetBrushXYBounds(), 1);
|
||
|
var texelCtx = Utility.CollectTexelValidity(ctx.originTerrain, brushXform.GetBrushXYBounds());
|
||
|
Utility.SetupMaterialForPaintingWithTexelValidityContext(ctx, texelCtx, brushXform, previewMat);
|
||
|
|
||
|
var filterRT = RTUtils.GetTempHandle(ctx.sourceRenderTexture.width, ctx.sourceRenderTexture.height, 0,
|
||
|
FilterUtility.defaultFormat);
|
||
|
Utility.GenerateAndSetFilterRT(commonUI, ctx.sourceRenderTexture, filterRT, previewMat);
|
||
|
|
||
|
TerrainPaintUtilityEditor.DrawBrushPreview(ctx, TerrainBrushPreviewMode.SourceRenderTexture,
|
||
|
editContext.brushTexture, brushXform, previewMat, 0);
|
||
|
|
||
|
if (sampleContext != null && cloneToolProperties.m_PaintHeightmap)
|
||
|
{
|
||
|
ApplyHeightmap(sampleContext, ctx, brushXform, terrain, editContext.brushTexture, commonUI.brushStrength);
|
||
|
RenderTexture.active = ctx.oldRenderTexture;
|
||
|
previewMat.SetTexture("_HeightmapOrig", ctx.sourceRenderTexture);
|
||
|
TerrainPaintUtilityEditor.DrawBrushPreview(ctx, TerrainBrushPreviewMode.DestinationRenderTexture,
|
||
|
editContext.brushTexture, brushXform, previewMat, 1);
|
||
|
}
|
||
|
|
||
|
// Restores RenderTexture.active
|
||
|
ctx.Cleanup();
|
||
|
texelCtx.Cleanup();
|
||
|
RTUtils.Release(filterRT);
|
||
|
}
|
||
|
|
||
|
// Restores RenderTexture.active
|
||
|
sampleContext?.Cleanup();
|
||
|
}
|
||
|
|
||
|
private Vector2 TerrainUVFromBrushLocation(Terrain terrain, Vector3 posWS)
|
||
|
{
|
||
|
// position relative to Terrain-space. doesnt handle rotations,
|
||
|
// since that's not really supported at the moment
|
||
|
Vector3 posTS = posWS - terrain.transform.position;
|
||
|
Vector3 size = terrain.terrainData.size;
|
||
|
|
||
|
return new Vector2(posTS.x / size.x, posTS.z / size.z);
|
||
|
}
|
||
|
|
||
|
private void ApplyHeightmap(PaintContext sampleContext, PaintContext targetContext, BrushTransform targetXform,
|
||
|
Terrain targetTerrain, Texture brushTexture, float brushStrength)
|
||
|
{
|
||
|
Material paintMat = GetPaintMaterial();
|
||
|
Vector4 brushParams = new Vector4(brushStrength, cloneToolProperties.m_StampingOffsetFromClone * 0.5f, targetTerrain.terrainData.size.y, 0f);
|
||
|
paintMat.SetTexture("_BrushTex", brushTexture);
|
||
|
paintMat.SetVector("_BrushParams", brushParams);
|
||
|
paintMat.SetTexture("_CloneTex", sampleContext.sourceRenderTexture);
|
||
|
paintMat.SetVector("_SampleUVScaleOffset", ComputeSampleUVScaleOffset(sampleContext, targetContext));
|
||
|
|
||
|
var brushMask = RTUtils.GetTempHandle(sampleContext.sourceRenderTexture.width, sampleContext.sourceRenderTexture.height, 0, FilterUtility.defaultFormat);
|
||
|
Utility.GenerateAndSetFilterRT(commonUI, sampleContext.sourceRenderTexture, brushMask, paintMat);
|
||
|
TerrainPaintUtility.SetupTerrainToolMaterialProperties(targetContext, targetXform, paintMat);
|
||
|
Graphics.Blit(targetContext.sourceRenderTexture, targetContext.destinationRenderTexture, paintMat, (int)ShaderPasses.CloneHeightmap);
|
||
|
RTUtils.Release(brushMask);
|
||
|
}
|
||
|
|
||
|
private Vector4 ComputeSampleUVScaleOffset(PaintContext sampleContext, PaintContext targetContext)
|
||
|
{
|
||
|
Vector4 sampleUVScaleOffset;
|
||
|
TerrainPaintUtility.BuildTransformPaintContextUVToPaintContextUV(targetContext, sampleContext, out sampleUVScaleOffset);
|
||
|
|
||
|
var sampleUV = TerrainUVFromBrushLocation(m_SampleLocation.terrain, m_SampleLocation.pos);
|
||
|
var paintContextUVOffset = sampleUV * (sampleContext.targetTextureHeight / (float)m_SampleLocation.terrain.terrainData.heightmapResolution);
|
||
|
|
||
|
float deltaUPixels = (sampleUV.x - commonUI.raycastHitUnderCursor.textureCoord.x) * targetContext.targetTextureWidth;
|
||
|
float deltaVPixels = (sampleUV.y - commonUI.raycastHitUnderCursor.textureCoord.y) * targetContext.targetTextureHeight;
|
||
|
sampleUVScaleOffset.z += ((int)deltaUPixels) / (float)sampleContext.pixelRect.width;
|
||
|
sampleUVScaleOffset.w += ((int)deltaVPixels) / (float)sampleContext.pixelRect.height;
|
||
|
|
||
|
return sampleUVScaleOffset;
|
||
|
}
|
||
|
|
||
|
private void PaintHeightmap(Terrain sampleTerrain, Terrain targetTerrain, BrushTransform sampleXform,
|
||
|
BrushTransform targetXform, IOnPaint editContext, Material mat)
|
||
|
{
|
||
|
PaintContext sampleContext = TerrainPaintUtility.BeginPaintHeightmap(sampleTerrain, sampleXform.GetBrushXYBounds());
|
||
|
PaintContext targetContext = TerrainPaintUtility.BeginPaintHeightmap(targetTerrain, targetXform.GetBrushXYBounds());
|
||
|
ApplyHeightmap(sampleContext, targetContext, targetXform, targetTerrain, editContext.brushTexture, commonUI.brushStrength);
|
||
|
TerrainPaintUtility.EndPaintHeightmap(targetContext, "Terrain Paint - Clone Brush (Heightmap)");
|
||
|
|
||
|
// Restores RenderTexture.active
|
||
|
sampleContext.Cleanup();
|
||
|
targetContext.Cleanup();
|
||
|
}
|
||
|
|
||
|
private void PaintAlphamap(Terrain sampleTerrain, Terrain targetTerrain, BrushTransform sampleXform, BrushTransform targetXform, Material mat)
|
||
|
{
|
||
|
Rect sampleRect = sampleXform.GetBrushXYBounds();
|
||
|
Rect targetRect = targetXform.GetBrushXYBounds();
|
||
|
int numSampleTerrainLayers = sampleTerrain.terrainData.terrainLayers.Length;
|
||
|
for (int i = 0; i < numSampleTerrainLayers; ++i)
|
||
|
{
|
||
|
TerrainLayer layer = sampleTerrain.terrainData.terrainLayers[i];
|
||
|
|
||
|
if (layer == null)
|
||
|
continue; // nothing to paint if the layer is NULL
|
||
|
|
||
|
PaintContext sampleContext = TerrainPaintUtility.BeginPaintTexture(sampleTerrain, sampleRect, layer);
|
||
|
|
||
|
int layerIndex = TerrainPaintUtility.FindTerrainLayerIndex(sampleTerrain, layer);
|
||
|
Texture2D layerTexture = TerrainPaintUtility.GetTerrainAlphaMapChecked(sampleTerrain, layerIndex >> 2);
|
||
|
PaintContext targetContext = PaintContext.CreateFromBounds(targetTerrain, targetRect, layerTexture.width, layerTexture.height);
|
||
|
targetContext.CreateRenderTargets(RenderTextureFormat.R8);
|
||
|
targetContext.GatherAlphamap(layer, true);
|
||
|
sampleContext.sourceRenderTexture.filterMode = FilterMode.Point;
|
||
|
mat.SetTexture("_CloneTex", sampleContext.sourceRenderTexture);
|
||
|
mat.SetVector("_SampleUVScaleOffset", ComputeSampleUVScaleOffset(sampleContext, targetContext));
|
||
|
|
||
|
var brushMask = RTUtils.GetTempHandle(targetContext.sourceRenderTexture.width, targetContext.sourceRenderTexture.height, 0, FilterUtility.defaultFormat);
|
||
|
Utility.GenerateAndSetFilterRT(commonUI, targetContext.sourceRenderTexture, brushMask, mat);
|
||
|
TerrainPaintUtility.SetupTerrainToolMaterialProperties(targetContext, targetXform, mat);
|
||
|
Graphics.Blit(targetContext.sourceRenderTexture, targetContext.destinationRenderTexture, mat, (int)ShaderPasses.CloneAlphamap);
|
||
|
// apply texture modifications and perform cleanup. same thing as calling TerrainPaintUtility.EndPaintTexture
|
||
|
targetContext.ScatterAlphamap("Terrain Paint - Clone Brush (Texture)");
|
||
|
|
||
|
// Restores RenderTexture.active
|
||
|
targetContext.Cleanup();
|
||
|
RTUtils.Release(brushMask);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static class Styles
|
||
|
{
|
||
|
public static readonly string descriptionString =
|
||
|
"Duplicates Terrain features from one region to another.\n\n" +
|
||
|
"Hold Ctrl + Click to set the area to sample from. Then Click to apply the cloned area.";
|
||
|
public static readonly GUIContent cloneSourceContent = EditorGUIUtility.TrTextContent("Terrain sources to clone:",
|
||
|
"Textures:\nBrush will gather and clone TerrainLayer data at Sample location\n\n" +
|
||
|
"Heightmap:\nBrush will gather and clone Heightmap data at Sample location");
|
||
|
public static readonly GUIContent cloneTextureContent = EditorGUIUtility.TrTextContent("Textures", "Brush will gather and clone TerrainLayer data from Sample location");
|
||
|
public static readonly GUIContent cloneHeightmapContent = EditorGUIUtility.TrTextContent("Heightmap", "Brush will gather and clone Heightmap data from Sample location");
|
||
|
public static readonly GUIContent cloneBehaviorContent = EditorGUIUtility.TrTextContent("Clone Movement Behavior",
|
||
|
"Snap:\nClone location will snap back to user-selected location on mouse-up\n\n" +
|
||
|
"Follow On Paint:\nClone location will move with mouse position (only when painting) and not snap back\n\n" +
|
||
|
"Follow Always:\nClone location will always move with mouse position (even when not painting) and not snap back\n\n" +
|
||
|
"Fixed:\nClone location will always stay at the user-selected location");
|
||
|
public static readonly GUIContent heightOffsetContent = EditorGUIUtility.TrTextContent("Height Offset",
|
||
|
"When stamping the heightmap, the cloned height will be added with this offset to raise or lower the cloned height at the stamp location.");
|
||
|
public static readonly GUIContent controlHeader = EditorGUIUtility.TrTextContent("Clone Brush Controls");
|
||
|
|
||
|
public static GUIStyle buttonActiveStyle = null;
|
||
|
public static GUIStyle buttonNormalStyle = null;
|
||
|
|
||
|
static Styles()
|
||
|
{
|
||
|
buttonNormalStyle = "Button";
|
||
|
buttonActiveStyle = new GUIStyle("Button");
|
||
|
buttonActiveStyle.normal.background = buttonNormalStyle.active.background;
|
||
|
}
|
||
|
|
||
|
public static GUIStyle GetButtonToggleStyle(bool isToggled)
|
||
|
{
|
||
|
return isToggled ? buttonActiveStyle : buttonNormalStyle;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void SaveSetting()
|
||
|
{
|
||
|
string cloneToolData = JsonUtility.ToJson(cloneToolProperties);
|
||
|
EditorPrefs.SetString("Unity.TerrainTools.Clone", cloneToolData);
|
||
|
}
|
||
|
|
||
|
private void LoadSettings()
|
||
|
{
|
||
|
string cloneToolData = EditorPrefs.GetString("Unity.TerrainTools.Clone");
|
||
|
cloneToolProperties.SetDefaults();
|
||
|
JsonUtility.FromJsonOverwrite(cloneToolData, cloneToolProperties);
|
||
|
}
|
||
|
|
||
|
//Analytics Setup
|
||
|
private TerrainToolsAnalytics.IBrushParameter[] UpdateAnalyticParameters() => new TerrainToolsAnalytics.IBrushParameter[]{
|
||
|
new TerrainToolsAnalytics.BrushParameter<bool>{Name = Styles.cloneTextureContent.text, Value = cloneToolProperties.m_PaintAlphamap},
|
||
|
new TerrainToolsAnalytics.BrushParameter<bool>{Name = Styles.cloneHeightmapContent.text, Value = cloneToolProperties.m_PaintHeightmap},
|
||
|
new TerrainToolsAnalytics.BrushParameter<string>{Name = Styles.cloneBehaviorContent.text, Value = cloneToolProperties.m_MovementBehavior.ToString()},
|
||
|
new TerrainToolsAnalytics.BrushParameter<float>{Name = Styles.heightOffsetContent.text, Value = cloneToolProperties.m_StampingOffsetFromClone},
|
||
|
};
|
||
|
}
|
||
|
}
|