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. /// /// 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; } } }