A game about forced loneliness, made by TACStudios
1using System; 2using System.Runtime.CompilerServices; 3using UnityEngine.Experimental.Rendering; 4 5namespace UnityEngine.Rendering 6{ 7 // Due to limitations in the builtin AnimationCurve we need this custom wrapper. 8 // Improvements: 9 // - Dirty state handling so we know when a curve has changed or not 10 // - Looping support (infinite curve) 11 // - Zero-value curve 12 // - Cheaper length property 13 14 /// <summary> 15 /// A wrapper around <c>AnimationCurve</c> to automatically bake it into a texture. 16 /// </summary> 17 [Serializable] 18 public class TextureCurve : IDisposable 19 { 20 const int k_Precision = 128; // Edit LutBuilder3D if you change this value 21 const float k_Step = 1f / k_Precision; 22 23 /// <summary> 24 /// The number of keys in the curve. 25 /// </summary> 26 [field: SerializeField] 27 public int length { get; private set; } // Calling AnimationCurve.length is very slow, let's cache it 28 29 [SerializeField] 30 bool m_Loop; 31 32 [SerializeField] 33 float m_ZeroValue; 34 35 [SerializeField] 36 float m_Range; 37 38 /// <summary> 39 /// Internal curve used to generate the Texture 40 /// </summary> 41 [SerializeField] 42 AnimationCurve m_Curve; 43 44 AnimationCurve m_LoopingCurve; 45 Texture2D m_Texture; 46 47 bool m_IsCurveDirty; 48 bool m_IsTextureDirty; 49 50 /// <summary> 51 /// Retrieves the key at index. 52 /// </summary> 53 /// <param name="index">The index to look for.</param> 54 /// <value>A key.</value> 55 public Keyframe this[int index] => m_Curve[index]; 56 57 /// <summary> 58 /// Creates a new <see cref="TextureCurve"/> from an existing <c>AnimationCurve</c>. 59 /// </summary> 60 /// <param name="baseCurve">The source <c>AnimationCurve</c>.</param> 61 /// <param name="zeroValue">The default value to use when the curve doesn't have any key.</param> 62 /// <param name="loop">Should the curve automatically loop in the given <paramref name="bounds"/>?</param> 63 /// <param name="bounds">The boundaries of the curve.</param> 64 public TextureCurve(AnimationCurve baseCurve, float zeroValue, bool loop, in Vector2 bounds) 65 : this(baseCurve.keys, zeroValue, loop, bounds) { } 66 67 /// <summary> 68 /// Creates a new <see cref="TextureCurve"/> from an arbitrary number of keyframes. 69 /// </summary> 70 /// <param name="keys">An array of Keyframes used to define the curve.</param> 71 /// <param name="zeroValue">The default value to use when the curve doesn't have any key.</param> 72 /// <param name="loop">Should the curve automatically loop in the given <paramref name="bounds"/>?</param> 73 /// <param name="bounds">The boundaries of the curve.</param> 74 public TextureCurve(Keyframe[] keys, float zeroValue, bool loop, in Vector2 bounds) 75 { 76 m_Curve = new AnimationCurve(keys); 77 m_ZeroValue = zeroValue; 78 m_Loop = loop; 79 m_Range = bounds.magnitude; 80 length = keys.Length; 81 SetDirty(); 82 } 83 84 /// <summary> 85 /// Cleans up the internal texture resource. 86 /// </summary> 87 public void Dispose() 88 { 89 Release(); 90 } 91 92 /// <summary> 93 /// Releases the internal texture resource. 94 /// </summary> 95 public void Release() 96 { 97 if (m_Texture != null) 98 CoreUtils.Destroy(m_Texture); 99 m_Texture = null; 100 } 101 102 /// <summary> 103 /// Marks the curve as dirty to trigger a redraw of the texture the next time <see cref="GetTexture"/> 104 /// is called. 105 /// </summary> 106 [MethodImpl(MethodImplOptions.AggressiveInlining)] 107 public void SetDirty() 108 { 109 m_IsCurveDirty = true; 110 m_IsTextureDirty = true; 111 } 112 113 static GraphicsFormat GetTextureFormat() 114 { 115 // UUM-41070: We require `Sample | SetPixels` but with the deprecated FormatUsage this was checking `SetPixels` 116 // For now, we keep checking for `SetPixels` until the performance hit of doing the correct checks is evaluated 117 if (SystemInfo.IsFormatSupported(GraphicsFormat.R16_SFloat, GraphicsFormatUsage.SetPixels)) 118 return GraphicsFormat.R16_SFloat; 119 if (SystemInfo.IsFormatSupported(GraphicsFormat.R8_UNorm, GraphicsFormatUsage.SetPixels)) 120 return GraphicsFormat.R8_UNorm; 121 122 return GraphicsFormat.R8G8B8A8_UNorm; 123 } 124 125 /// <summary> 126 /// Gets the texture representation of this curve. 127 /// </summary> 128 /// <returns>A 128x1 texture.</returns> 129 public Texture2D GetTexture() 130 { 131 if (m_Texture == null) 132 { 133 m_Texture = new Texture2D(k_Precision, 1, GetTextureFormat(), TextureCreationFlags.None); 134 m_Texture.name = "CurveTexture"; 135 m_Texture.hideFlags = HideFlags.HideAndDontSave; 136 m_Texture.filterMode = FilterMode.Bilinear; 137 m_Texture.wrapMode = TextureWrapMode.Clamp; 138 m_Texture.anisoLevel = 0; 139 m_IsTextureDirty = true; 140 } 141 142 if (m_IsTextureDirty) 143 { 144 var pixels = new Color[k_Precision]; 145 146 for (int i = 0; i < pixels.Length; i++) 147 pixels[i].r = Evaluate(i * k_Step); 148 149 m_Texture.SetPixels(pixels); 150 m_Texture.Apply(false, false); 151 m_IsTextureDirty = false; 152 } 153 154 return m_Texture; 155 } 156 157 /// <summary> 158 /// Evaluate a time value on the curve. 159 /// </summary> 160 /// <param name="time">The time within the curve you want to evaluate.</param> 161 /// <returns>The value of the curve, at the point in time specified.</returns> 162 public float Evaluate(float time) 163 { 164 if (m_IsCurveDirty) 165 length = m_Curve.length; 166 167 if (length == 0) 168 return m_ZeroValue; 169 170 if (!m_Loop || length == 1) 171 return m_Curve.Evaluate(time); 172 173 if (m_IsCurveDirty) 174 { 175 if (m_LoopingCurve == null) 176 m_LoopingCurve = new AnimationCurve(); 177 178 var prev = m_Curve[length - 1]; 179 prev.time -= m_Range; 180 var next = m_Curve[0]; 181 next.time += m_Range; 182 m_LoopingCurve.keys = m_Curve.keys; // GC pressure 183 m_LoopingCurve.AddKey(prev); 184 m_LoopingCurve.AddKey(next); 185 m_IsCurveDirty = false; 186 } 187 188 return m_LoopingCurve.Evaluate(time); 189 } 190 191 /// <summary> 192 /// Adds a new key to the curve. 193 /// </summary> 194 /// <param name="time">The time at which to add the key.</param> 195 /// <param name="value">The value for the key.</param> 196 /// <returns>The index of the added key, or -1 if the key could not be added.</returns> 197 [MethodImpl(MethodImplOptions.AggressiveInlining)] 198 public int AddKey(float time, float value) 199 { 200 int r = m_Curve.AddKey(time, value); 201 202 if (r > -1) 203 SetDirty(); 204 205 return r; 206 } 207 208 /// <summary> 209 /// Removes the keyframe at <paramref name="index"/> and inserts <paramref name="key"/>. 210 /// </summary> 211 /// <param name="index">The index of the keyframe to replace.</param> 212 /// <param name="key">The new keyframe to insert at the specified index.</param> 213 /// <returns>The index of the keyframe after moving it.</returns> 214 [MethodImpl(MethodImplOptions.AggressiveInlining)] 215 public int MoveKey(int index, in Keyframe key) 216 { 217 int r = m_Curve.MoveKey(index, key); 218 SetDirty(); 219 return r; 220 } 221 222 /// <summary> 223 /// Removes a key. 224 /// </summary> 225 /// <param name="index">The index of the key to remove.</param> 226 [MethodImpl(MethodImplOptions.AggressiveInlining)] 227 public void RemoveKey(int index) 228 { 229 m_Curve.RemoveKey(index); 230 SetDirty(); 231 } 232 233 /// <summary> 234 /// Smoothes the in and out tangents of the keyframe at <paramref name="index"/>. A <paramref name="weight"/> of 0 evens out tangents. 235 /// </summary> 236 /// <param name="index">The index of the keyframe to be smoothed.</param> 237 /// <param name="weight">The smoothing weight to apply to the keyframe's tangents.</param> 238 [MethodImpl(MethodImplOptions.AggressiveInlining)] 239 public void SmoothTangents(int index, float weight) 240 { 241 m_Curve.SmoothTangents(index, weight); 242 SetDirty(); 243 } 244 } 245 246 /// <summary> 247 /// A <see cref="VolumeParameter"/> that holds a <see cref="TextureCurve"/> value. 248 /// </summary> 249 [Serializable] 250 public class TextureCurveParameter : VolumeParameter<TextureCurve> 251 { 252 /// <summary> 253 /// Creates a new <see cref="TextureCurveParameter"/> instance. 254 /// </summary> 255 /// <param name="value">The initial value to store in the parameter.</param> 256 /// <param name="overrideState">The initial override state for the parameter.</param> 257 public TextureCurveParameter(TextureCurve value, bool overrideState = false) 258 : base(value, overrideState) { } 259 260 /// <summary> 261 /// Release implementation. 262 /// </summary> 263 public override void Release() => m_Value.Release(); 264 265 // TODO: TextureCurve interpolation 266 } 267}