using System;
using System.Runtime.CompilerServices;
using UnityEngine.Experimental.Rendering;
namespace UnityEngine.Rendering
{
// Due to limitations in the builtin AnimationCurve we need this custom wrapper.
// Improvements:
// - Dirty state handling so we know when a curve has changed or not
// - Looping support (infinite curve)
// - Zero-value curve
// - Cheaper length property
///
/// A wrapper around AnimationCurve to automatically bake it into a texture.
///
[Serializable]
public class TextureCurve : IDisposable
{
const int k_Precision = 128; // Edit LutBuilder3D if you change this value
const float k_Step = 1f / k_Precision;
///
/// The number of keys in the curve.
///
[field: SerializeField]
public int length { get; private set; } // Calling AnimationCurve.length is very slow, let's cache it
[SerializeField]
bool m_Loop;
[SerializeField]
float m_ZeroValue;
[SerializeField]
float m_Range;
///
/// Internal curve used to generate the Texture
///
[SerializeField]
AnimationCurve m_Curve;
AnimationCurve m_LoopingCurve;
Texture2D m_Texture;
bool m_IsCurveDirty;
bool m_IsTextureDirty;
///
/// Retrieves the key at index.
///
/// The index to look for.
/// A key.
public Keyframe this[int index] => m_Curve[index];
///
/// Creates a new from an existing AnimationCurve.
///
/// The source AnimationCurve.
/// The default value to use when the curve doesn't have any key.
/// Should the curve automatically loop in the given ?
/// The boundaries of the curve.
public TextureCurve(AnimationCurve baseCurve, float zeroValue, bool loop, in Vector2 bounds)
: this(baseCurve.keys, zeroValue, loop, bounds) { }
///
/// Creates a new from an arbitrary number of keyframes.
///
/// An array of Keyframes used to define the curve.
/// The default value to use when the curve doesn't have any key.
/// Should the curve automatically loop in the given ?
/// The boundaries of the curve.
public TextureCurve(Keyframe[] keys, float zeroValue, bool loop, in Vector2 bounds)
{
m_Curve = new AnimationCurve(keys);
m_ZeroValue = zeroValue;
m_Loop = loop;
m_Range = bounds.magnitude;
length = keys.Length;
SetDirty();
}
///
/// Finalizer.
///
~TextureCurve() { }
///
/// Cleans up the internal texture resource.
///
[Obsolete("Please use Release() instead.")]
public void Dispose() { }
///
/// Releases the internal texture resource.
///
public void Release()
{
CoreUtils.Destroy(m_Texture);
m_Texture = null;
}
///
/// Marks the curve as dirty to trigger a redraw of the texture the next time
/// is called.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetDirty()
{
m_IsCurveDirty = true;
m_IsTextureDirty = true;
}
static GraphicsFormat GetTextureFormat()
{
if (SystemInfo.IsFormatSupported(GraphicsFormat.R16_SFloat, FormatUsage.Sample | FormatUsage.SetPixels))
return GraphicsFormat.R16_SFloat;
if (SystemInfo.IsFormatSupported(GraphicsFormat.R8_UNorm, FormatUsage.Sample | FormatUsage.SetPixels))
return GraphicsFormat.R8_UNorm;
return GraphicsFormat.R8G8B8A8_UNorm;
}
///
/// Gets the texture representation of this curve.
///
/// A 128x1 texture.
public Texture2D GetTexture()
{
if (m_Texture == null)
{
m_Texture = new Texture2D(k_Precision, 1, GetTextureFormat(), TextureCreationFlags.None);
m_Texture.name = "CurveTexture";
m_Texture.hideFlags = HideFlags.HideAndDontSave;
m_Texture.filterMode = FilterMode.Bilinear;
m_Texture.wrapMode = TextureWrapMode.Clamp;
m_Texture.anisoLevel = 0;
m_IsTextureDirty = true;
}
if (m_IsTextureDirty)
{
var pixels = new Color[k_Precision];
for (int i = 0; i < pixels.Length; i++)
pixels[i].r = Evaluate(i * k_Step);
m_Texture.SetPixels(pixels);
m_Texture.Apply(false, false);
m_IsTextureDirty = false;
}
return m_Texture;
}
///
/// Evaluate a time value on the curve.
///
/// The time within the curve you want to evaluate.
/// The value of the curve, at the point in time specified.
public float Evaluate(float time)
{
if (m_IsCurveDirty)
length = m_Curve.length;
if (length == 0)
return m_ZeroValue;
if (!m_Loop || length == 1)
return m_Curve.Evaluate(time);
if (m_IsCurveDirty)
{
if (m_LoopingCurve == null)
m_LoopingCurve = new AnimationCurve();
var prev = m_Curve[length - 1];
prev.time -= m_Range;
var next = m_Curve[0];
next.time += m_Range;
m_LoopingCurve.keys = m_Curve.keys; // GC pressure
m_LoopingCurve.AddKey(prev);
m_LoopingCurve.AddKey(next);
m_IsCurveDirty = false;
}
return m_LoopingCurve.Evaluate(time);
}
///
/// Adds a new key to the curve.
///
/// The time at which to add the key.
/// The value for the key.
/// The index of the added key, or -1 if the key could not be added.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int AddKey(float time, float value)
{
int r = m_Curve.AddKey(time, value);
if (r > -1)
SetDirty();
return r;
}
///
/// Removes the keyframe at and inserts .
///
///
///
/// The index of the keyframe after moving it.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int MoveKey(int index, in Keyframe key)
{
int r = m_Curve.MoveKey(index, key);
SetDirty();
return r;
}
///
/// Removes a key.
///
/// The index of the key to remove.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveKey(int index)
{
m_Curve.RemoveKey(index);
SetDirty();
}
///
/// Smoothes the in and out tangents of the keyframe at . A of 0 evens out tangents.
///
/// The index of the keyframe to be smoothed.
/// The smoothing weight to apply to the keyframe's tangents.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SmoothTangents(int index, float weight)
{
m_Curve.SmoothTangents(index, weight);
SetDirty();
}
}
///
/// A that holds a value.
///
[Serializable]
public class TextureCurveParameter : VolumeParameter
{
///
/// Creates a new instance.
///
/// The initial value to store in the parameter.
/// The initial override state for the parameter.
public TextureCurveParameter(TextureCurve value, bool overrideState = false)
: base(value, overrideState) { }
///
/// Release implementation.
///
public override void Release() => m_Value.Release();
// TODO: TextureCurve interpolation
}
}