using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; // ReSharper disable once CheckNamespace namespace AnimatorAsCode.V1 { public class AacFlClip { private readonly AacConfiguration _component; /// Exposes the underlying Unity Clip asset. [PublicAPI] public AnimationClip Clip { get; } internal AacFlClip(AacConfiguration component, AnimationClip clip) { _component = component; Clip = clip; } /// Set the clip to be looping. public AacFlClip Looping() { var settings = AnimationUtility.GetAnimationClipSettings(Clip); settings.loopTime = true; AnimationUtility.SetAnimationClipSettings(Clip, settings); return this; } /// Set the clip to be non-looping. public AacFlClip NonLooping() { var settings = AnimationUtility.GetAnimationClipSettings(Clip); settings.loopTime = false; AnimationUtility.SetAnimationClipSettings(Clip, settings); return this; } /// Start editing the clip with a lambda expression. public AacFlClip Animating(Action action) { action.Invoke(new AacFlEditClip(_component, Clip)); return this; } /// Enable or disable GameObjects. This lasts one frame. The array can safely contain null values. public AacFlClip Toggling(GameObject[] gameObjectsWithNulls, bool value) { var defensiveObjects = gameObjectsWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array foreach (var component in defensiveObjects) { var binding = AacInternals.Binding(_component, typeof(GameObject), component.transform, "m_IsActive"); AacInternals.SetCurve(Clip, binding, AacInternals.OneFrame(value ? 1f : 0f)); } return this; } /// Enable or disable a GameObject. This lasts one frame. public AacFlClip Toggling(GameObject gameObject, bool value) { var binding = AacInternals.Binding(_component, typeof(GameObject), gameObject.transform, "m_IsActive"); AacInternals.SetCurve(Clip, binding, AacInternals.OneFrame(value ? 1f : 0f)); return this; } /// Change a blendShape of a skinned mesh. This lasts one frame. public AacFlClip BlendShape(SkinnedMeshRenderer renderer, string blendShapeName, float value) { var binding = AacInternals.Binding(_component, typeof(SkinnedMeshRenderer), renderer.transform, $"blendShape.{blendShapeName}"); AacInternals.SetCurve(Clip, binding, AacInternals.OneFrame(value)); return this; } /// Change a blendShape of multiple skinned meshes. This lasts one frame. The array can safely contain null values. public AacFlClip BlendShape(SkinnedMeshRenderer[] rendererWithNulls, string blendShapeName, float value) { var defensiveObjects = rendererWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array foreach (var component in defensiveObjects) { var binding = AacInternals.Binding(_component, typeof(SkinnedMeshRenderer), component.transform, $"blendShape.{blendShapeName}"); AacInternals.SetCurve(Clip, binding, AacInternals.OneFrame(value)); } return this; } /// Change a blendShape of a skinned mesh, with an animation curve. public AacFlClip BlendShape(SkinnedMeshRenderer renderer, string blendShapeName, AnimationCurve animationCurve) { var binding = AacInternals.Binding(_component, typeof(SkinnedMeshRenderer), renderer.transform, $"blendShape.{blendShapeName}"); AacInternals.SetCurve(Clip, binding, animationCurve); return this; } /// Change a blendShape of multiple skinned meshes, with an animation curve. The array can safely contain null values. public AacFlClip BlendShape(SkinnedMeshRenderer[] rendererWithNulls, string blendShapeName, AnimationCurve animationCurve) { var defensiveObjects = rendererWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array foreach (var component in defensiveObjects) { var binding = AacInternals.Binding(_component, typeof(SkinnedMeshRenderer), component.transform, $"blendShape.{blendShapeName}"); AacInternals.SetCurve(Clip, binding, animationCurve); } return this; } public AacFlClip Positioning(Transform transform, Vector3 localPosition) { // Single-valued overloads must not tolerate null values if (transform == null) throw new NullReferenceException("Transform must not be null"); return Positioning(new GameObject[]{ transform.gameObject }, localPosition); } public AacFlClip RotatingUsingEulerInterpolation(Transform transform, Vector3 localEulerAngles) { // Single-valued overloads must not tolerate null values if (transform == null) throw new NullReferenceException("Transform must not be null"); return RotatingUsingEulerInterpolation(new GameObject[]{ transform.gameObject }, localEulerAngles); } public AacFlClip RotatingUsingQuaternionInterpolation(Transform transform, Quaternion localQuaternionAngles) { // Single-valued overloads must not tolerate null values if (transform == null) throw new NullReferenceException("Transform must not be null"); return RotatingUsingQuaternionInterpolation(new GameObject[]{ transform.gameObject }, localQuaternionAngles); } public AacFlClip Scaling(Transform transform, Vector3 scale) { // Single-valued overloads must not tolerate null values if (transform == null) throw new NullReferenceException("Transform must not be null"); return Scaling(new GameObject[]{ transform.gameObject }, scale); } public AacFlClip Positioning(GameObject gameObject, Vector3 localPosition) { // Single-valued overloads must not tolerate null values if (gameObject == null) throw new NullReferenceException("GameObject must not be null"); return Positioning(new GameObject[]{ gameObject }, localPosition); } public AacFlClip RotatingUsingEulerInterpolation(GameObject gameObject, Vector3 localEulerAngles) { // Single-valued overloads must not tolerate null values if (gameObject == null) throw new NullReferenceException("GameObject must not be null"); return RotatingUsingEulerInterpolation(new GameObject[]{ gameObject }, localEulerAngles); } public AacFlClip RotatingUsingQuaternionInterpolation(GameObject gameObject, Quaternion localQuaternionAngles) { // Single-valued overloads must not tolerate null values if (gameObject == null) throw new NullReferenceException("GameObject must not be null"); return RotatingUsingQuaternionInterpolation(new GameObject[]{ gameObject }, localQuaternionAngles); } public AacFlClip Scaling(GameObject gameObject, Vector3 scale) { // Single-valued overloads must not tolerate null values if (gameObject == null) throw new NullReferenceException("GameObject must not be null"); return Scaling(new GameObject[]{ gameObject }, scale); } public AacFlClip Positioning(Transform[] transformsWithNulls, Vector3 localPosition) { return Positioning(AsGameObjectsWithNulls(transformsWithNulls), localPosition); } public AacFlClip RotatingUsingEulerInterpolation(Transform[] transformsWithNulls, Vector3 localEulerAngles) { return RotatingUsingEulerInterpolation(AsGameObjectsWithNulls(transformsWithNulls), localEulerAngles); } public AacFlClip RotatingUsingQuaternionInterpolation(Transform[] transformsWithNulls, Quaternion localQuaternionAngles) { return RotatingUsingQuaternionInterpolation(AsGameObjectsWithNulls(transformsWithNulls), localQuaternionAngles); } public AacFlClip Scaling(Transform[] transformsWithNulls, Vector3 scale) { return Scaling(AsGameObjectsWithNulls(transformsWithNulls), scale); } private static GameObject[] AsGameObjectsWithNulls(Transform[] transformsWithNulls) { return transformsWithNulls.Select(o => o != null ? o.gameObject : null).ToArray(); } /// Change the position of a GameObject in local space. This lasts one frame. This lasts one frame. The array can safely contain null values. public AacFlClip Positioning(GameObject[] gameObjectsWithNulls, Vector3 localPosition) { var defensiveObjects = gameObjectsWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array foreach (var component in defensiveObjects) { AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalPosition.x"), AacInternals.OneFrame(localPosition.x)); AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalPosition.y"), AacInternals.OneFrame(localPosition.y)); AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalPosition.z"), AacInternals.OneFrame(localPosition.z)); } return this; } public AacFlClip RotatingUsingEulerInterpolation(GameObject[] gameObjectsWithNulls, Vector3 localEulerAngles) { var defensiveObjects = gameObjectsWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array foreach (var component in defensiveObjects) { // See https://forum.unity.com/threads/new-animationclip-property-names.367288/#post-2384172 // AacInternals.SetCurve internally uses AnimationClip.SetCurve instead of AnimationUtility.SetEditorCurve, starting from V1 AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalEuler.x"), AacInternals.OneFrame(localEulerAngles.x)); AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalEuler.y"), AacInternals.OneFrame(localEulerAngles.y)); AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalEuler.z"), AacInternals.OneFrame(localEulerAngles.z)); } return this; } public AacFlClip RotatingUsingQuaternionInterpolation(GameObject[] gameObjectsWithNulls, Quaternion localQuaternionAngles) { var defensiveObjects = gameObjectsWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array foreach (var component in defensiveObjects) { // See https://forum.unity.com/threads/new-animationclip-property-names.367288/#post-2384172 // AacInternals.SetCurve internally uses AnimationClip.SetCurve instead of AnimationUtility.SetEditorCurve, starting from V1 AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalRotation.x"), AacInternals.OneFrame(localQuaternionAngles.x)); AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalRotation.y"), AacInternals.OneFrame(localQuaternionAngles.y)); AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalRotation.z"), AacInternals.OneFrame(localQuaternionAngles.z)); AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalRotation.w"), AacInternals.OneFrame(localQuaternionAngles.w)); } return this; } public AacFlClip Scaling(GameObject[] gameObjectsWithNulls, Vector3 scale) { var defensiveObjects = gameObjectsWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array foreach (var component in defensiveObjects) { AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalScale.x"), AacInternals.OneFrame(scale.x)); AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalScale.y"), AacInternals.OneFrame(scale.y)); AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalScale.z"), AacInternals.OneFrame(scale.z)); } return this; } public AacFlClip TogglingComponent(Component[] componentsWithNulls, bool value) { var defensiveComponents = componentsWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array foreach (var component in defensiveComponents) { var binding = AacInternals.Binding(_component, component.GetType(), component.transform, "m_Enabled"); AacInternals.SetCurve(Clip, binding, AacInternals.OneFrame(value ? 1f : 0f)); } return this; } public AacFlClip TogglingComponent(Component component, bool value) { var binding = AacInternals.Binding(_component, component.GetType(), component.transform, "m_Enabled"); AacInternals.SetCurve(Clip, binding, AacInternals.OneFrame(value ? 1f : 0f)); return this; } /// Swap a material of a Renderer on the specified slot (indexed at 0). This lasts one frame. public AacFlClip SwappingMaterial(Renderer renderer, int slot, Material material) { var binding = AacInternals.Binding(_component, renderer.GetType(), renderer.transform, $"m_Materials.Array.data[{slot}]"); AnimationUtility.SetObjectReferenceCurve(Clip, binding, new[] { new ObjectReferenceKeyframe { time = 0f, value = material }, new ObjectReferenceKeyframe { time = 1/60f, value = material } }); return this; } /// Swap a material of a Particle System on the specified slot (indexed at 0). This lasts one frame.
/// In practice, this will animate the ParticleSystemRenderer of that particle system. public AacFlClip SwappingMaterial(ParticleSystem particleSystem, int slot, Material material) { var binding = AacInternals.Binding(_component, typeof(ParticleSystemRenderer), particleSystem.transform, $"m_Materials.Array.data[{slot}]"); AnimationUtility.SetObjectReferenceCurve(Clip, binding, new[] { new ObjectReferenceKeyframe { time = 0f, value = material }, new ObjectReferenceKeyframe { time = 1/60f, value = material } }); return this; } } public class AacFlEditClip { private readonly AacConfiguration _component; [PublicAPI] public AnimationClip Clip { get; } internal AacFlEditClip(AacConfiguration component, AnimationClip clip) { _component = component; Clip = clip; } /// Animates a path the traditional way. public AacFlSettingCurve Animates(string path, Type type, string propertyName) { var binding = new EditorCurveBinding { path = path, type = type, propertyName = propertyName }; return new AacFlSettingCurve(Clip, new[] {binding}); } /// Animates an object in the hierarchy relative to the animator root, the traditional way. public AacFlSettingCurve Animates(Transform transform, Type type, string propertyName) { var binding = AacInternals.Binding(_component, type, transform, propertyName); return new AacFlSettingCurve(Clip, new[] {binding}); } /// Animates the active property of a GameObject, toggling it. public AacFlSettingCurve Animates(GameObject gameObject) { var binding = AacInternals.Binding(_component, typeof(GameObject), gameObject.transform, "m_IsActive"); return new AacFlSettingCurve(Clip, new[] {binding}); } /// Animates the property of a component. The runtime type of the component will be used. public AacFlSettingCurve Animates(Component anyComponent, string property) { var binding = Internal_BindingFromComponent(anyComponent, property); return new AacFlSettingCurve(Clip, new[] {binding}); } /// Animates the property of several components. The runtime type of the component will be used. The array can safely contain null values. public AacFlSettingCurve Animates(Component[] anyComponentsWithNulls, string property) { var bindings = Internal_BindingsFromComponentsWithNulls(anyComponentsWithNulls, property); return new AacFlSettingCurve(Clip, bindings); } /// Animates a Float parameter of the animator (may sometimes be referred to as an Animated Animator Parameter, or AAP). public AacFlSettingCurve AnimatesAnimator(AacFlParameter floatParameter) { var binding = new EditorCurveBinding { path = "", type = typeof(Animator), propertyName = floatParameter.Name }; return new AacFlSettingCurve(Clip, new[] {binding}); } /// Animates a color property of a component. The runtime type of the component will be used. public AacFlSettingCurveColor AnimatesColor(Component anyComponent, string property) { var binding = Internal_BindingFromComponent(anyComponent, property); return new AacFlSettingCurveColor(Clip, new[] {binding}); } /// Animates a color property of several components. The runtime type of the component will be used. public AacFlSettingCurveColor AnimatesColor(Component[] anyComponentsWithNulls, string property) { var bindings = Internal_BindingsFromComponentsWithNulls(anyComponentsWithNulls, property); return new AacFlSettingCurveColor(Clip, bindings); } /// Animates an HDR color property of a component (uses XYZW instead of RGBA). The runtime type of the component will be used. public AacFlSettingCurveColor AnimatesHDRColor(Component anyComponent, string property) { var binding = Internal_BindingFromComponent(anyComponent, property); return new AacFlSettingCurveColor(Clip, new[] {binding}, true); } /// Animates an HDR color property of several components (uses XYZW instead of RGBA). The runtime type of the component will be used. public AacFlSettingCurveColor AnimatesHDRColor(Component[] anyComponentsWithNulls, string property) { var bindings = Internal_BindingsFromComponentsWithNulls(anyComponentsWithNulls, property); return new AacFlSettingCurveColor(Clip, bindings, true); } /// Animates an object reference of a component. The runtime type of the component will be used. public AacFlSettingCurveObjectReference AnimatesObjectReference(Component anyComponent, string property) { var binding = Internal_BindingFromComponent(anyComponent, property); return new AacFlSettingCurveObjectReference(Clip, new[] {binding}); } /// Animates an object reference of several components. The runtime type of the component will be used. public AacFlSettingCurveObjectReference AnimatesObjectReference(Component[] anyComponentsWithNulls, string property) { var bindings = Internal_BindingsFromComponentsWithNulls(anyComponentsWithNulls, property); return new AacFlSettingCurveObjectReference(Clip, bindings); } /// Returns an EditorCurveBinding of a component, relative to the animator root. The runtime type of the component will be used.
/// This is meant to be used in conjunction with traditional animation APIs. public EditorCurveBinding BindingFromComponent(Component anyComponent, string propertyName) { return Internal_BindingFromComponent(anyComponent, propertyName); } private EditorCurveBinding[] Internal_BindingsFromComponentsWithNulls(Component[] anyComponentsWithNulls, string property) { return anyComponentsWithNulls .Where(o => o != null) .Select(anyComponent => Internal_BindingFromComponent(anyComponent, property)) .ToArray(); } private EditorCurveBinding Internal_BindingFromComponent(Component anyComponent, string propertyName) { return AacInternals.Binding(_component, anyComponent.GetType(), anyComponent.transform, propertyName); } } public class AacFlSettingCurve { private readonly AnimationClip _clip; private readonly EditorCurveBinding[] _bindings; internal AacFlSettingCurve(AnimationClip clip, EditorCurveBinding[] bindings) { _clip = clip; _bindings = bindings; } /// Define the curve to be exactly one frame by defining two constant keyframes, usually lasting 1/60th of a second, with the desired value. public void WithOneFrame(float desiredValue) { foreach (var binding in _bindings) { AacInternals.SetCurve(_clip, binding, AacInternals.OneFrame(desiredValue)); } } /// Define the curve to last a specific amount of seconds by defining two constant keyframes, with the desired value. public void WithFixedSeconds(float seconds, float desiredValue) { foreach (var binding in _bindings) { AacInternals.SetCurve(_clip, binding, AacInternals.ConstantSeconds(seconds, desiredValue)); } } /// Start defining the keyframes with a lambda expression, expressing the unit to be in seconds. public void WithSecondsUnit(Action action) { InternalWithUnit(AacFlUnit.Seconds, action); } /// Start defining the keyframes with a lambda expression, expressing the unit in frames. public void WithFrameCountUnit(Action action) { InternalWithUnit(AacFlUnit.Frames, action); } /// Start defining the keyframes with a lambda expression, expressing the unit. public void WithUnit(AacFlUnit unit, Action action) { InternalWithUnit(unit, action); } private void InternalWithUnit(AacFlUnit unit, Action action) { var mutatedKeyframes = new List(); var builder = new AacFlSettingKeyframes(unit, mutatedKeyframes); action.Invoke(builder); foreach (var binding in _bindings) { AacInternals.SetCurve(_clip, binding, new AnimationCurve(mutatedKeyframes.ToArray())); } } /// Define the curve as the parameter. The duration is encoded inside the curve itself. public void WithAnimationCurve(AnimationCurve animationCurve) { foreach (var binding in _bindings) { AacInternals.SetCurve(_clip, binding, animationCurve); } } } public class AacFlSettingCurveObjectReference { private readonly AnimationClip _clip; private readonly EditorCurveBinding[] _bindings; internal AacFlSettingCurveObjectReference(AnimationClip clip, EditorCurveBinding[] bindings) { _clip = clip; _bindings = bindings; } /// Define the curve to be exactly one frame by defining two constant keyframes, usually lasting 1/60th of a second, with the desired object reference value. public void WithOneFrame(Object desiredValue) { foreach (var binding in _bindings) { AnimationUtility.SetObjectReferenceCurve(_clip, binding, new[] { new ObjectReferenceKeyframe { time = 0f, value = desiredValue }, new ObjectReferenceKeyframe { time = 1/60f, value = desiredValue } }); } } /// Obsolete. Use `WithUnit()` instead.
/// Start defining the keyframes with a lambda expression, expressing the unit. [Obsolete("This function was renamed to WithUnit(...)")] public void WithKeyframes(AacFlUnit unit, Action action) { WithUnit(unit, action); } /// Start defining the keyframes with a lambda expression, expressing the unit. public void WithUnit(AacFlUnit unit, Action action) { var mutatedObjectReferenceKeyframes = new List(); var builder = new AacFlSettingKeyframesObjectReference(unit, mutatedObjectReferenceKeyframes); action.Invoke(builder); foreach (var binding in _bindings) { AnimationUtility.SetObjectReferenceCurve(_clip, binding, mutatedObjectReferenceKeyframes.ToArray()); } } } public class AacFlSettingKeyframesObjectReference { private readonly AacFlUnit _unit; private readonly List _mutatedKeyframes; internal AacFlSettingKeyframesObjectReference(AacFlUnit unit, List mutatedKeyframes) { _unit = unit; _mutatedKeyframes = mutatedKeyframes; } /// Create a keyframe for an object reference. The unit is defined by the function that invokes this lambda expression. public AacFlSettingKeyframesObjectReference Setting(int timeInUnit, Object value) { _mutatedKeyframes.Add(new ObjectReferenceKeyframe { time = AsSeconds(timeInUnit), value = value }); return this; } private float AsSeconds(float timeInUnit) { switch (_unit) { case AacFlUnit.Frames: return timeInUnit / 60f; case AacFlUnit.Seconds: return timeInUnit; default: throw new ArgumentOutOfRangeException(); } } } public class AacFlSettingCurveColor { private readonly AnimationClip _clip; private readonly EditorCurveBinding[] _bindings; private readonly bool _hdr; internal AacFlSettingCurveColor(AnimationClip clip, EditorCurveBinding[] bindings, bool hdr = false) { _clip = clip; _bindings = bindings; _hdr = hdr; } public void WithOneFrame(Color desiredValue) { foreach (var binding in _bindings) { AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "x" : "r"), AacInternals.OneFrame(desiredValue.r)); AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "y" : "g"), AacInternals.OneFrame(desiredValue.g)); AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "z" : "b"), AacInternals.OneFrame(desiredValue.b)); AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "w" : "a"), AacInternals.OneFrame(desiredValue.a)); } } /// Obsolete. Use `WithUnit()` instead.
/// Start defining the keyframes with a lambda expression, expressing the unit. [Obsolete("This function was renamed to WithUnit(...)")] public void WithKeyframes(AacFlUnit unit, Action action) { WithUnit(unit, action); } /// Start defining the keyframes with a lambda expression, expressing the unit. public void WithUnit(AacFlUnit unit, Action action) { var mutatedKeyframesR = new List(); var mutatedKeyframesG = new List(); var mutatedKeyframesB = new List(); var mutatedKeyframesA = new List(); var builder = new AacFlSettingKeyframesColor(unit, mutatedKeyframesR, mutatedKeyframesG, mutatedKeyframesB, mutatedKeyframesA); action.Invoke(builder); foreach (var binding in _bindings) { AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "x" : "r"), new AnimationCurve(mutatedKeyframesR.ToArray())); AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "y" : "g"), new AnimationCurve(mutatedKeyframesG.ToArray())); AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "z" : "b"), new AnimationCurve(mutatedKeyframesB.ToArray())); AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "w" : "a"), new AnimationCurve(mutatedKeyframesA.ToArray())); } } } public class AacFlSettingKeyframes { private readonly AacFlUnit _unit; private readonly List _mutatedKeyframes; internal AacFlSettingKeyframes(AacFlUnit unit, List mutatedKeyframes) { _unit = unit; _mutatedKeyframes = mutatedKeyframes; } public AacFlSettingKeyframes Easing(float timeInUnit, float value) { _mutatedKeyframes.Add(new Keyframe(AsSeconds(timeInUnit), value, 0, 0)); return this; } public AacFlSettingKeyframes Constant(float timeInUnit, float value) { _mutatedKeyframes.Add(new Keyframe(AsSeconds(timeInUnit), value, 0, float.PositiveInfinity)); return this; } public AacFlSettingKeyframes Linear(float timeInUnit, float value) { float valueEnd = value; float valueStart = _mutatedKeyframes.Count == 0 ? value : _mutatedKeyframes.Last().value; float timeEnd = AsSeconds(timeInUnit); float timeStart = _mutatedKeyframes.Count == 0 ? value : _mutatedKeyframes.Last().time; float num = (float) (((double) valueEnd - (double) valueStart) / ((double) timeEnd - (double) timeStart)); // FIXME: This can cause NaN tangents which messes everything // return new AnimationCurve(new Keyframe[2] // { // new Keyframe(timeStart, valueStart, 0.0f, num), // new Keyframe(timeEnd, valueEnd, num, 0.0f) // }); if (_mutatedKeyframes.Count > 0) { var lastKeyframe = _mutatedKeyframes.Last(); lastKeyframe.outTangent = num; _mutatedKeyframes[_mutatedKeyframes.Count - 1] = lastKeyframe; } _mutatedKeyframes.Add(new Keyframe(AsSeconds(timeInUnit), value, num, 0.0f)); return this; } private float AsSeconds(float timeInUnit) { switch (_unit) { case AacFlUnit.Frames: return timeInUnit / 60f; case AacFlUnit.Seconds: return timeInUnit; default: throw new ArgumentOutOfRangeException(); } } } public class AacFlSettingKeyframesColor { private AacFlSettingKeyframes _r; private AacFlSettingKeyframes _g; private AacFlSettingKeyframes _b; private AacFlSettingKeyframes _a; internal AacFlSettingKeyframesColor(AacFlUnit unit, List mutatedKeyframesR, List mutatedKeyframesG, List mutatedKeyframesB, List mutatedKeyframesA) { _r = new AacFlSettingKeyframes(unit, mutatedKeyframesR); _g = new AacFlSettingKeyframes(unit, mutatedKeyframesG); _b = new AacFlSettingKeyframes(unit, mutatedKeyframesB); _a = new AacFlSettingKeyframes(unit, mutatedKeyframesA); } public AacFlSettingKeyframesColor Easing(int frame, Color value) { _r.Easing(frame, value.r); _g.Easing(frame, value.g); _b.Easing(frame, value.b); _a.Easing(frame, value.a); return this; } public AacFlSettingKeyframesColor Linear(float frame, Color value) { _r.Linear(frame, value.r); _g.Linear(frame, value.g); _b.Linear(frame, value.b); _a.Linear(frame, value.a); return this; } public AacFlSettingKeyframesColor Constant(int frame, Color value) { _r.Constant(frame, value.r); _g.Constant(frame, value.g); _b.Constant(frame, value.b); _a.Constant(frame, value.a); return this; } } public enum AacFlUnit { Frames, Seconds } }