using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.TerrainTools;
using UnityEngine;
using UObject = UnityEngine.Object;
using UnityEngine.Experimental.Rendering;
namespace UnityEditor.TerrainTools
{
///
/// Provides methods for changing and restoring active s.
///
public struct ActiveRenderTextureScope : IDisposable
{
RenderTexture m_Prev;
///
/// Initializes and returns an instance of .
///
/// Call this constructor to swap the previous active with the RenderTexture that is passed in.
/// The RenderTexture to set as active.
public ActiveRenderTextureScope(RenderTexture rt)
{
m_Prev = RenderTexture.active;
RenderTexture.active = rt;
}
///
/// Restores the previous .
///
public void Dispose()
{
// restore prev active RT
RenderTexture.active = m_Prev;
}
}
///
/// Provides a utility class for safely managing the lifetime of a .
///
public class RTHandle
{
private RenderTexture m_RT;
private bool m_IsTemp;
///
/// The RenderTexture for this RTHandle.
///
public RenderTexture RT => m_RT;
///
/// The descriptor for the RTHandle and RenderTexture.
///
public RenderTextureDescriptor Desc => m_RT?.descriptor ?? default;
internal bool IsTemp => m_IsTemp;
///
/// The name for the RTHandle and RenderTexture.
///
public string Name {
get => m_RT?.name ?? default;
set => m_RT.name = value;
}
internal RTHandle()
{
}
///
/// Sets the name of the , and returns a reference to this .
///
/// The name of the underlying RenderTexture.
/// Returns a reference to this RTHandle.
public RTHandle WithName(string name)
{
Name = name;
return this;
}
///
/// Converts the to a type.
///
/// The RTHandle to convert.
/// Returns a RenderTexture handle.
public static implicit operator RenderTexture(RTHandle handle)
{
return handle.RT;
}
///
/// Converts the to a type.
///
/// The RTHandle to convert.
/// Returns a RenderTexture handle.
public static implicit operator Texture(RTHandle handle)
{
return handle.RT;
}
internal void SetRenderTexture(RenderTexture rt, bool isTemp)
{
m_RT = rt;
m_IsTemp = isTemp;
}
///
/// Represents a struct for handling the lifetime of an within a using block.
///
/// Releases the when this struct is disposed.
public struct RTHandleScope : System.IDisposable
{
RTHandle m_Handle;
internal RTHandleScope(RTHandle handle)
{
m_Handle = handle;
}
///
/// Releases the handled RenderTexture.
///
public void Dispose()
{
RTUtils.Release(m_Handle);
}
}
///
/// Gets a new disposable instance to use within using blocks.
///
/// Returns a new RTHandleScope instance.
public RTHandleScope Scoped() => new RTHandleScope(this);
}
///
/// Provides a utility class for getting and releasing s handles.
///
///
/// Tracks the lifetimes of these RenderTextures. Any RenderTextures that aren't released within several frames are regarded as leaked RenderTexture resources, which generate warnings in the Console.
///
public static class RTUtils
{
class Log
{
public int Frames;
public string StackTrace;
}
internal static bool s_EnableStackTrace = false;
private static Stack s_LogPool = new Stack();
private static Dictionary s_Logs = new Dictionary();
internal static int s_CreatedHandleCount;
internal static int s_TempHandleCount;
private static bool m_AgeCheckAdded;
private static void AgeCheck()
{
if (!m_AgeCheckAdded)
{
Debug.LogError("Checking lifetime of RenderTextures but m_AgeCheckAdded = false");
}
foreach (var kvp in s_Logs)
{
var log = kvp.Value;
if (log.Frames >= 4)
{
var trace = !s_EnableStackTrace ? string.Empty : "\n" + log.StackTrace;
Debug.LogWarning($"RTHandle \"{kvp.Key.Name}\" has existed for more than 4 frames. Possible memory leak.{trace}");
}
log.Frames++;
}
}
private static void CheckAgeCheck()
{
if (s_TempHandleCount != 0 || s_CreatedHandleCount != 0)
return;
Debug.Assert(s_Logs.Count == 0, "Internal RTHandle type counts for temporary and non-temporary RTHandles are both 0 but the containers for tracking leaked RTHandles have counts that are not 0");
if (!m_AgeCheckAdded)
{
EditorApplication.update += AgeCheck;
m_AgeCheckAdded = true;
}
else
{
EditorApplication.update -= AgeCheck;
m_AgeCheckAdded = false;
}
}
private static void AddLogForHandle(RTHandle handle)
{
var log = s_LogPool.Any() ? s_LogPool.Pop() : new Log();
if (s_EnableStackTrace)
log.StackTrace = System.Environment.StackTrace;
s_Logs.Add(handle, log);
}
///
/// Gets a RenderTextureDescriptor for operations on GPU.
///
/// The width of the RenderTexture.
/// The height of the RenderTexture.
/// The of the RenderTexture.
/// The depth of the RenderTexture.
/// The mip count of the RenderTexture. Default is 0.
/// The flag that determines whether RenderTextures created using this descriptor are in sRGB or Linear space.
/// Returns a RenderTextureDescriptor object.
///
public static RenderTextureDescriptor GetDescriptor(int width, int height, int depth, RenderTextureFormat format, int mipCount = 0, bool srgb = false)
{
return GetDescriptor(width, height, depth, GraphicsFormatUtility.GetGraphicsFormat(format, srgb), mipCount, srgb);
}
///
/// Gets a RenderTextureDescriptor for operations on GPU with the enableRandomWrite flag set to true.
///
/// The width of the RenderTexture.
/// The height of the RenderTexture.
/// The of the RenderTexture.
/// The depth of the RenderTexture.
/// The mip count of the RenderTexture. Default is 0.
/// The flag that determines whether RenderTextures created using this descriptor are in sRGB or Linear space.
/// Returns a RenderTextureDescriptor object.
///
public static RenderTextureDescriptor GetDescriptorRW(int width, int height, int depth, RenderTextureFormat format, int mipCount = 0, bool srgb = false)
{
return GetDescriptorRW(width, height, depth, GraphicsFormatUtility.GetGraphicsFormat(format, srgb), mipCount, srgb);
}
///
/// Gets a RenderTextureDescriptor for operations on GPU with the enableRandomWrite flag set to true.
///
/// The width of the RenderTexture.
/// The height of the RenderTexture.
/// The of the RenderTexture.
/// The depth of the RenderTexture.
/// The mip count of the RenderTexture. Default is 0.
/// The flag that determines whether RenderTextures created using this descriptor are in sRGB or Linear space.
/// Returns a RenderTextureDescriptor object.
///
public static RenderTextureDescriptor GetDescriptorRW(int width, int height, int depth, GraphicsFormat format, int mipCount = 0, bool srgb = false)
{
var desc = GetDescriptor(width, height, depth, format, mipCount, srgb);
desc.enableRandomWrite = true;
return desc;
}
/// Gets a RenderTextureDescriptor set up for operations on GPU.
/// The width of the RenderTexture.
/// The height of the RenderTexture.
/// The of the RenderTexture.
/// The depth of the RenderTexture.
/// The mip count of the RenderTexture. Default is 0.
/// The flag that determines whether RenderTextures created using this descriptor are in sRGB or Linear space.
/// Returns a RenderTextureDescriptor object.
public static RenderTextureDescriptor GetDescriptor(int width, int height, int depth, GraphicsFormat format, int mipCount = 0, bool srgb = false)
{
var desc = new RenderTextureDescriptor(width, height, format, depth)
{
sRGB = srgb,
mipCount = mipCount,
useMipMap = mipCount != 0,
};
return desc;
}
private static RTHandle GetHandle(RenderTextureDescriptor desc, bool isTemp)
{
CheckAgeCheck();
if (isTemp)
s_TempHandleCount++;
else
s_CreatedHandleCount++;
var handle = new RTHandle();
handle.SetRenderTexture(isTemp ? RenderTexture.GetTemporary(desc) : new RenderTexture(desc), isTemp);
AddLogForHandle(handle);
return handle;
}
///
/// Gets an for a acquired with .
///
/// Use to release the RTHandle.
/// RenderTextureDescriptor for the RenderTexture.
/// Returns a temporary RTHandle.
///
public static RTHandle GetTempHandle(RenderTextureDescriptor desc)
{
return GetHandle(desc, true);
}
///
/// Gets an for a acquired with .
///
/// Use to release the RTHandle.
/// The width of the RenderTexture.
/// The height of the RenderTexture.
/// The depth of the RenderTexture.
/// The format of the RenderTexture.
/// Returns a temporary RTHandle.
public static RTHandle GetTempHandle(int width, int height, int depth, GraphicsFormat format)
{
return GetHandle(GetDescriptor(width, height, depth, format), true);
}
///
/// Gets an for a acquired with new RenderTexture(desc).
///
/// The RenderTextureDescriptor for the RenderTexture.
/// Returns an RTHandle.
///
public static RTHandle GetNewHandle(RenderTextureDescriptor desc)
{
return GetHandle(desc, false);
}
///
/// Gets an for a acquired with new RenderTexture(desc).
///
/// Use to release the RTHandle.
/// The width of the RenderTexture.
/// The height of the RenderTexture.
/// The depth of the RenderTexture.
/// The format of the RenderTexture.
/// Returns an RTHandle.
public static RTHandle GetNewHandle(int width, int height, int depth, GraphicsFormat format)
{
return GetHandle(GetDescriptor(width, height, depth, format), false);
}
///
/// Releases the resource associated with the specified .
///
/// The RTHandle from which to release RenderTexture resources.
///
public static void Release(RTHandle handle)
{
if (handle.RT == null)
return;
if (!s_Logs.ContainsKey(handle))
throw new InvalidOperationException("Attemping to release a RTHandle that is not currently tracked by the system. This should never happen");
var log = s_Logs[handle];
s_Logs.Remove(handle);
log.Frames = 0;
log.StackTrace = null;
s_LogPool.Push(log);
if (handle.IsTemp)
{
--s_TempHandleCount;
RenderTexture.ReleaseTemporary(handle.RT);
}
else
{
--s_CreatedHandleCount;
handle.RT.Release();
Destroy(handle.RT);
}
CheckAgeCheck();
}
///
/// Destroys a created using new RenderTexture().
///
/// The RenderTexture to destroy.
///
///
public static void Destroy(RenderTexture rt)
{
if (rt == null)
return;
#if UNITY_EDITOR
if (Application.isPlaying)
UObject.Destroy(rt);
else
UObject.DestroyImmediate(rt);
#else
UObject.Destroy(rt);
#endif
}
///
/// Gets the number of s that have been requested and not released yet.
///
/// Returns the number of RTHandles that have been requested and not released.
public static int GetHandleCount() => s_Logs.Count;
}
///
/// Provides utility methods for Terrain.
///
public static class Utility
{
static Material m_DefaultPreviewMat = null;
///
/// Retrieves the default preview for painting on Terrains.
///
/// Whether the filter preview is enabled.
/// Returns Terrain painting's default preview .
public static Material GetDefaultPreviewMaterial(bool filtersPreviewEnabled = false)
{
if (m_DefaultPreviewMat == null)
{
m_DefaultPreviewMat = new Material(Shader.Find("Hidden/TerrainTools/BrushPreview"));
}
SetMaterialKeyword(m_DefaultPreviewMat, FilterUtility.filterPreviewKeyword, filtersPreviewEnabled);
//set defaults for uniforms in the shader
m_DefaultPreviewMat.SetFloat("_HoleStripeThreshold", 1.0f/255.0f);
m_DefaultPreviewMat.SetFloat("_UseAltColor", 0.0f);
m_DefaultPreviewMat.SetFloat("_IsPaintHolesTool", 0.0f);
return m_DefaultPreviewMat;
}
static Material m_PaintHeightMat;
///
/// Gets the paint height material to render builtin brush passes.
///
///
/// This material overrides the Builtin PaintHeight shader with Terrain Tools version of PaintHeight.
/// See for the available passes to choose from.
/// See "/com.unity.terrain-tools/Shaders/PaintHeight.shader" for the override PaintHeight shader.
///
/// Material with the Paint Height shader
public static Material GetPaintHeightMaterial()
{
if (m_PaintHeightMat == null)
{
m_PaintHeightMat = new Material(Shader.Find("Hidden/TerrainEngine/PaintHeightTool"));
}
return m_PaintHeightMat;
}
private static Material m_TexelValidityMaterial;
private static Material GetTexelValidityMaterial()
{
if (m_TexelValidityMaterial == null)
{
m_TexelValidityMaterial = new Material(Shader.Find("Hidden/TerrainTools/TexelValidityBlit"));
}
return m_TexelValidityMaterial;
}
///
/// Prepares the passed in for painting on Terrains.
///
/// The painting context data.
/// The brush's transformation data.
/// The material being used for painting on Terrains.
///
///
public static void SetupMaterialForPainting(PaintContext paintContext, BrushTransform brushTransform, Material material)
{
TerrainPaintUtility.SetupTerrainToolMaterialProperties(paintContext, brushTransform, material);
material.SetVector("_Heightmap_Tex",
new Vector4(
1f / paintContext.targetTextureWidth,
1f / paintContext.targetTextureHeight,
paintContext.targetTextureWidth,
paintContext.targetTextureHeight)
);
material.SetVector("_PcPixelRect",
new Vector4(paintContext.pixelRect.x,
paintContext.pixelRect.y,
paintContext.pixelRect.width,
paintContext.pixelRect.height)
);
material.SetVector("_PcUvVectors",
new Vector4(paintContext.pixelRect.x / (float)paintContext.targetTextureWidth,
paintContext.pixelRect.y / (float)paintContext.targetTextureHeight,
paintContext.pixelRect.width / (float)paintContext.targetTextureWidth,
paintContext.pixelRect.height / (float)paintContext.targetTextureHeight)
);
}
///
/// Prepares the passed in for painting on Terrains while checking for Texel Validity.
///
/// The painting context data.
/// The texel context data.
/// The brush's transformation data.
/// The material being used for painting on Terrains.
///
///
public static void SetupMaterialForPaintingWithTexelValidityContext(PaintContext paintContext, PaintContext texelCtx, BrushTransform brushTransform, Material material)
{
SetupMaterialForPainting(paintContext, brushTransform, material);
material.SetTexture("_PCValidityTex", texelCtx.sourceRenderTexture);
}
///
/// Retrieves texel context data used for checking texel validity.
///
///
///
///
/// Returns the object used for checking texel validity.
public static PaintContext CollectTexelValidity(Terrain terrain, Rect boundsInTerrainSpace, int extraBorderPixels = 0)
{
var res = terrain.terrainData.heightmapResolution;
// use holes format because we really only need to know if the texel value is 0 or 1
var ctx = PaintContext.CreateFromBounds(terrain, boundsInTerrainSpace, res, res, extraBorderPixels);
ctx.CreateRenderTargets(Terrain.holesRenderTextureFormat);
ctx.Gather(
t => t.terrain.terrainData.heightmapTexture, // just provide heightmap texture. no need to create a temp one
new Color(0, 0, 0, 0),
blitMaterial: GetTexelValidityMaterial()
);
return ctx;
}
///
/// Converts data from a into a
///
/// The to convert from.
/// The to convert data into.
/// Returns the range of the .
public static Vector2 AnimationCurveToRenderTexture(AnimationCurve curve, ref Texture2D tex)
{
//assume this a 1D texture that has already been created
tex.wrapMode = TextureWrapMode.Clamp;
tex.filterMode = FilterMode.Bilinear;
float val = curve.Evaluate(0.0f);
Vector2 range = new Vector2(val, val);
Color[] pixels = new Color[tex.width * tex.height];
pixels[0].r = val;
for (int i = 1; i < tex.width; i++)
{
float pct = (float)i / (float)tex.width;
pixels[i].r = curve.Evaluate(pct);
range[0] = Mathf.Min(range[0], pixels[i].r);
range[1] = Mathf.Max(range[1], pixels[i].r);
}
tex.SetPixels(pixels);
tex.Apply();
return range;
}
///
/// Sets and Generates the filter for transformation brushes and bind the texture to the provided Material.
/// **Note:** Using this method will enable the Terrain Layer filter
///
/// The brush's commonUI group.
/// The brushRender object used for acquiring the heightmap and splatmap texture to blit from.
/// The designated as the destination.
/// The to update.
///
///
public static void GenerateAndSetFilterRT(IBrushUIGroup commonUI, IBrushRenderUnderCursor brushRender, RenderTexture destinationRenderTexture, Material mat)
{
commonUI.GenerateBrushMask(brushRender, destinationRenderTexture);
mat.SetTexture("_FilterTex", destinationRenderTexture);
}
///
/// Generate the filter render texture for transformation brushes and bind the texture to the provided Material
/// Sets and Generates the filter for transformation brushes and bind the texture to the provided Material.
///
/// The brush's commonUI group.
/// The designated as the source.
/// The designated as the destination.
/// The to update.
///
///
public static void GenerateAndSetFilterRT(IBrushUIGroup commonUI, RenderTexture sourceRenderTexture, RenderTexture destinationRenderTexture, Material mat)
{
commonUI.GenerateBrushMask(sourceRenderTexture, destinationRenderTexture);
mat.SetTexture("_FilterTex", destinationRenderTexture);
}
///
/// Enable or disable the keyword for the provided Material instance
///
/// The material to set.
/// The keyword to enable and disable.
/// Whether to enable or disable the keyword of the material.
public static void SetMaterialKeyword(Material mat, string keyword, bool enabled)
{
if(enabled)
{
mat.EnableKeyword(keyword);
}
else
{
mat.DisableKeyword(keyword);
}
}
}
///
/// Provides mesh utility methods for Terrain.
///
public static class MeshUtils
{
///
/// Sets which shader pass to use when rendering the .
///
public enum ShaderPass
{
///
/// Height shader pass.
///
Height = 0,
///
/// Mask shader pass.
///
Mask = 1,
}
private static Material m_defaultProjectionMaterial;
///
/// Gets the default projection .
///
public static Material defaultProjectionMaterial {
get
{
if (m_defaultProjectionMaterial == null)
{
m_defaultProjectionMaterial = new Material(Shader.Find("Hidden/TerrainTools/MeshUtility"));
}
return m_defaultProjectionMaterial;
}
}
///
/// Converts a into a .
///
/// The to convert from.
/// Returns a converted .
public static Quaternion QuaternionFromMatrix(Matrix4x4 m)
{
// Adapted from: http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
Quaternion q = new Quaternion();
q.w = Mathf.Sqrt(Mathf.Max(0, 1 + m[0, 0] + m[1, 1] + m[2, 2])) / 2;
q.x = Mathf.Sqrt(Mathf.Max(0, 1 + m[0, 0] - m[1, 1] - m[2, 2])) / 2;
q.y = Mathf.Sqrt(Mathf.Max(0, 1 - m[0, 0] + m[1, 1] - m[2, 2])) / 2;
q.z = Mathf.Sqrt(Mathf.Max(0, 1 - m[0, 0] - m[1, 1] + m[2, 2])) / 2;
q.x *= Mathf.Sign(q.x * (m[2, 1] - m[1, 2]));
q.y *= Mathf.Sign(q.y * (m[0, 2] - m[2, 0]));
q.z *= Mathf.Sign(q.z * (m[1, 0] - m[0, 1]));
return q;
}
///
/// Transforms a .
///
/// The transformation .
/// The to transform.
/// Returns the transformed .
public static Bounds TransformBounds(Matrix4x4 m, Bounds bounds)
{
Vector3[] points = new Vector3[8];
// get points for each corner of the bounding box
points[0] = new Vector3(bounds.max.x, bounds.max.y, bounds.max.z);
points[1] = new Vector3(bounds.min.x, bounds.max.y, bounds.max.z);
points[2] = new Vector3(bounds.max.x, bounds.min.y, bounds.max.z);
points[3] = new Vector3(bounds.max.x, bounds.max.y, bounds.min.z);
points[4] = new Vector3(bounds.min.x, bounds.min.y, bounds.max.z);
points[5] = new Vector3(bounds.min.x, bounds.min.y, bounds.min.z);
points[6] = new Vector3(bounds.max.x, bounds.min.y, bounds.min.z);
points[7] = new Vector3(bounds.min.x, bounds.max.y, bounds.min.z);
Vector3 min = Vector3.one * float.PositiveInfinity;
Vector3 max = Vector3.one * float.NegativeInfinity;
for (int i = 0; i < points.Length; ++i)
{
Vector3 p = m.MultiplyPoint(points[i]);
// update min values
if (p.x < min.x)
{
min.x = p.x;
}
if (p.y < min.y)
{
min.y = p.y;
}
if (p.z < min.z)
{
min.z = p.z;
}
// update max values
if (p.x > max.x)
{
max.x = p.x;
}
if (p.y > max.y)
{
max.y = p.y;
}
if (p.z > max.z)
{
max.z = p.z;
}
}
return new Bounds() { max = max, min = min };
}
private static string GetPrettyVectorString(Vector3 v)
{
return string.Format("( {0}, {1}, {2} )", v.x, v.y, v.z);
}
///
/// Renders the top down projection of a into a .
///
/// The to render.
/// The transformation .
/// The designated as the destination.
/// The to update.
/// The to use.
public static void RenderTopdownProjection(Mesh mesh, Matrix4x4 model, RenderTexture destination, Material mat, ShaderPass pass)
{
RenderTexture prev = RenderTexture.active;
RenderTexture.active = destination;
Bounds modelBounds = TransformBounds(model, mesh.bounds);
float nearPlane = (modelBounds.max.y - modelBounds.center.y) * 4;
float farPlane = (modelBounds.min.y - modelBounds.center.y);
Vector3 viewFrom = new Vector3(modelBounds.center.x, modelBounds.center.z, -modelBounds.center.y);
Vector3 viewTo = viewFrom + Vector3.down;
Vector3 viewUp = Vector3.forward;
// Debug.Log(
// $@"Bounds =
// [
// center: { modelBounds.center }
// max: { modelBounds.max }
// extents: { modelBounds.extents }
// ]
// nearPlane: { nearPlane }
// farPlane: { farPlane }
// diff: { nearPlane - farPlane }
// view: [ from = { GetPrettyVectorString( viewFrom ) }, to = { GetPrettyVectorString( viewTo ) }, up = { GetPrettyVectorString( viewUp ) } ]"
// );
// reset the view to accomodate for the transformed bounds
Matrix4x4 view = Matrix4x4.LookAt(viewFrom, viewTo, viewUp);
Matrix4x4 proj = Matrix4x4.Ortho(-1, 1, -1, 1, nearPlane, farPlane);
Matrix4x4 mvp = proj * view * model;
GL.Clear(true, true, Color.black);
mat.SetMatrix("_Matrix_M", model);
mat.SetMatrix("_Matrix_MV", view * model);
mat.SetMatrix("_Matrix_MVP", mvp);
mat.SetPass((int)pass);
GL.PushMatrix();
{
Graphics.DrawMeshNow(mesh, Matrix4x4.identity);
}
GL.PopMatrix();
RenderTexture.active = prev;
}
}
}