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}