Menu

Arbitrary Type Interpolation

SourceForge Editorial Staff

Arbitrary Type Interpolation - Functional and Design Specification


Glossary


Interpolator: I use this term to refer to calculating an animated value v given an initial value v1, a final value v2, and an elapsed fraction f of the duration of the animation. A typical linear interpolation is usually represented by the parametric equation: v = v1 + f * (v2 - v1)
The use of interpolators in this feature is in deriving the value v for arbitrary types (colors, Numbers, Path segments, whatever). So where the actual interpolation equation may be the same for all types, the actual function may not be because we must access the data in a type-specific way (colors need to break up the numbers into RGB channels, etc.).
So think of interpolators, and the IInterpolator implementations in particular, as a way to translate from an elapsed fraction and the endpoints of an animation into a linearly-interpolated value of any given type.

Easing: The easing classes (both existing and proposed) also interpolate, but they serve a different purpose than the IInterpolator implementation classes. In the existing code, the functionality overlaps, because they define both an arbitrary easing function (linear or non-linear) upon the animation and the interpolation of values between the endpoints. In essence, they are performing both time interpolation (this is the 'easing' part) and value interpolation (where all values are of type Number). In the new system, we will use the IInterpolator implementing classes to perform the interpolation (because we are dealing with objects, not just numbers). The easing classes will provide the smaller case of just the timing behavior interpolation. Given an elapsed fraction of the animation, an easing function will return a potentially different (eased) fraction. This fraction is then passed into an IInterpolator to retrieve an appropriate value for that eased fraction.

Summary and Background


The current Tween class interpolates only between Number values. This is sufficient for the bulk of cases in our current components, where most animatable attributes are represented as numbers. But it is not sufficient for all objects and properties.

For example, a simple RGB interpolation between different colors requires splitting up each number into its component channels and interpolating separately between those values.

Similarly, interpolating between different objects in the new FXG elements, such as Path segments, requires custom work to extract the numbers of the different elements of each segment, interpolating on those values, and then recomposing the results.

What we need is a more general Tween system that can handle interpolating between arbitrary types.

Usage Scenarios


The most prevalent use case is color-interpolating effects that can supply start/end values for the colors and expect the system to correctly interpolate the inter-frame values of the colors.

Another example is interpolating between different states of FXG elements, such as a moving Path object.

A third example is a custom effect that a developer supplies which animates between states of an object of some unknown type. Ideally, our system would not only have interpolators for some standard known types, such as colors, but would also have the ability to plug in custom interpolators for types that the system does not know.

Detailed Description


This feature will supply a new Tween class, called Animation. This new class will share much of the functionality of the original Tween class (runs off a single Timer, is able to pause/stop/reverse, uses easing functions, dispatches events, interpolates values and sends them out to listeners), but will differ in some important ways: - new "interpolator" property: The user may supply an optional interpolator class which, for each frame, will be passed the start/end values supplied to the Animation constructor along with the elapsed fraction of the animation. A new value will be calculated by this interpolator and dispatched returned to the caller. By default, if the user does not supply an interpolator, the Animation will use an interpolator that operates on Numbers or an Array of Numbers, depending on the type of the input start/end values. - new constructor: The old Tween class has a constructor that takes an opaque Object as the event listener. Presumably this pre-dates AS3, as we should give the user more information about what this object is by having it be some Interface instead. But more importantly, we already have an event listening mechanism that we should use instead. Also, some of the other constructor parameters seem far less important at construction time, and we may want a constructor that does not require them. For example, minFps does not seem to be used at all in Tween, and the updateFunction and endFunction parameters are optional and redundant with the concept of event listeners. In essence, I would like to provide a simplified constructor for this new Animation class that just takes a duration, start and end values. - no auto-play: The current Tween class automatically starts upon construction. I think that it is desirable for developers to be able to construct objects separate from actually using them. I would like to add a play() function so that construction simply creates the data structures involved and play() actually starts the Animation. This will also necessitate adding a 'start' event that gets sent out, like the current update/end calls. - mx_internal API: There are some mx_internal properties and functions in Tween that I would like to get away from in the new class. - Easing functions: the current easing function API has useful functionality but a somewhat awkward API. I would like to provide a simpler API for easing functions that allows more customization, while still providing the ability to use these older easing functions for developers that are used to them. - Repeat behavior: Currently, there is repeat behavior specified in Effect, by way of the int value repeatCount. I would like to see this behavior moved to the Animation level, since repetition is not just a characteristic of effects, but is instead a timing-engine attribute. Also, the current repeat behavior is very minimal; I would like to see the behavior enhanced to provide more capabilities (and allow easier coding for developers because the engine does more for them). The Animation API below shows the new repeatBehavior functionality at work.

API Description


There are a few separate pieces to the new API. First, we need a new "IInterpolator" interface that standard and user-supplied interpolators will implement:

/**
 * The IInterpolator interface is used by classes that calculate
 * intermediate values for the Animation class. The Animation class
 * can handle parametric interpolation between Number values and
 * arrays of Number values, but it cannot handle different types
 * of interpolation, or interpolation between different types of
 * values. Implementors of this interface can provide arbitrary
 * interpolation capabilities so that Animations can be created between
 * arbitrary values.
 */
public interface IInterpolator
{
    /**
     * Returns the type that an implementor can handle
     */
    function get interpolatedType():Class;

    /**
     * Given an elapsed fraction of an animation between 0 and 1,
     * and start and end values, this function returns some value
     * based on whatever interpolation the implementor chooses to
     * provide.
     */
    function interpolate(fraction:Number,
        startValue:*, endValue:*):*;   
}

There are several standard implementations of IInterpolator that Flex will provide, including one that interpolates Numbers, one that interpolates Arrays of elements (where the caller supplies another interpolator for each array element), and one that interpolates Arrays of Numbers (just like the previous one, but preconfigured to deal with interpolating Number elements for this common case). Here, for example, are the NumberInterpolator and ArrayInterpolator classes:

/**
 * The NumberInterpolator class provides interpolation between
 * <code>Number</code> start and end values. 
 */
public class NumberInterpolator implements IInterpolator
{
    public function NumberInterpolator() {}

    /**
     * Returns the singleton of this class. Since all NumberInterpolators
     * have the same behavior, there is no need for more than one instance.
     */
    public static function getInstance():NumberInterpolator {}

    /**
     * Returns the <code>Number</code> type, which is the type of
     * object interpolated by NumberInterpolator
     */
    public function get interpolatedType():Class {}

    /**
     * @inheritDoc
     * 
     * <p>Interpolation for NumberInterpolator consists of a simple
     * parametric calculation between <code>startValue</code> and 
     * <code>endValue</code>, using <code>fraction</code> as the 
     * fraction elapsed from start to end, like this:</p>
     * 
     * <p><code>return startValue + fraction * (endValue - startValue);</code></p>
     */
    public function interpolate(fraction:Number, startValue:*, endValue:*):* {}

}

/**
 * ArrayInterpolator interpolates each element of start/end array
 * inputs separately, using another internal interpolator to do
 * the per-element interpolation. By default, the per-element
 * interpolation uses <code>NumberInterpolator</code>, but callers
 * can construct ArrayInterpolator with a different interpolator
 * instead.
 */
public class ArrayInterpolator implements IInterpolator
{

    /**
     * Constructor. An optional parameter provides a per-element
     * interpolator that will be used for every element of the arrays.
     * If no interpolator is supplied, <code>NumberInterpolator</code>
     * will be used by default.
     */
    public function ArrayInterpolator(value:IInterpolator = null) {}

    /**
     * The internal interpolator that ArrayInterpolator uses for
     * each element of the input arrays
     */
    public function get elementInterpolator():IInterpolator {}
    public function set elementInterpolator(value:IInterpolator):void {}

    /**
     * Returns the <code>Array</code> type, which is the type of
     * object interpolated by ArrayInterpolator
     */
    public function get interpolatedType():Class {}

    /**
     * @inheritDoc
     * 
     * Interpolation for ArrayInterpolator consists of running a separate
     * interpolation on each element of the startValue and endValue
     * arrays, returning a new Array that holds those interpolated values.
     */
    public function interpolate(fraction:Number, startValue:*, endValue:*):* {}

}

Note that NumberInterpolator provides a Singleton accessor because there is no need for more than one of these objects for any given system. It is expected, although not required, that other IInterpolator implementations would do similiarly.

One Additional IInterpolator implementation class that we should ship with Flex is one that interpolates colors. This is probably the most common case of interpolation that cannot be handled simply by our current Number interpolation.

/**
 * The ColorInterpolator class provides RGB-space interpolation between
 * <code>uint</code> start and end values. Interpolation is done by treating
 * the start and end values as integers with color channel information in
 * the least-significant 3 bytes, interpolating each of those channels
 * separately.
 */
public class ColorInterpolator implements IInterpolator
{   
    public function ColorInterpolator() {}

    /**
     * Returns the singleton of this class. Since all ColorInterpolators
     * have the same behavior, there is no need for more than one instance.
     */
    public static function getInstance():ColorInterpolator {}

    /**
     * Returns the <code>uint</code> type, which is the type of
     * object interpolated by ColorInterpolator
     */
    public function get interpolatedType():Class {}

    /**
     * @inheritDoc
     * 
     * The interpolation for ColorInterpolator takes the form of parametric
     * calculations on each of the bottom three bytes of 
     * <code>startValue</code> and <code>endValue</code>. This interpolates
     * each color channel separately if the start and end values represent
     * RGB colors.
     */
    public function interpolate(fraction:Number, startValue:*, endValue:*):* {}

}

We also plan to offer a new Animation class. It will have similar functionality to the existing Tween class, but will allow custom type interpolation and offers a simpler API for creating and playing Animations. It is the main class used for running time-based effects (apart from the old TweenEffect classes, which will still use Tween).

/**
 * Dispatched when the animation starts. The first 
 * <code>animationUpdate</code> event is dispatched at the 
 * same time.
 *
 * @eventType spark.events.AnimationEvent.ANIMATION_START
 */
<a href="Event%28name%3D%26quot%3BanimationStart%26quot%3B%2C%20type%3D%26quot%3Bspark.events.AnimationEvent%26quot%3B%29">Event(name="animationStart", type="spark.events.AnimationEvent")</a>

/**
 * Dispatched every time the animation updates the target.
 *
 * @eventType spark.events.AnimationEvent.ANIMATION_UPDATE
 */
<a href="Event%28name%3D%26quot%3BanimationUpdate%26quot%3B%2C%20type%3D%26quot%3Bspark.events.AnimationEvent%26quot%3B%29">Event(name="animationUpdate", type="spark.events.AnimationEvent")</a>

/**
 * Dispatched when the animation begins a new repetition, for
 * any effect that is repeated more than once.
 * An <code>animationUpdate</code> event is also dispatched 
 * at the same time.
 *
 * @eventType spark.events.AnimationEvent.ANIMATION_REPEAT
 */
<a href="Event%28name%3D%26quot%3BanimationRepeat%26quot%3B%2C%20type%3D%26quot%3Bspark.events.AnimationEvent%26quot%3B%29">Event(name="animationRepeat", type="spark.events.AnimationEvent")</a>

/**
 * Dispatched when the effect ends. An <code>animationUpdate</code> event 
 * is also dispatched at the same time. A repeating animation dispatches 
 * this event only after the final repetition.
 *
 * @eventType spark.events.AnimationEvent.ANIMATION_END
 */
<a href="Event%28name%3D%26quot%3BanimationEnd%26quot%3B%2C%20type%3D%26quot%3Bspark.events.AnimationEvent%26quot%3B%29">Event(name="animationEnd", type="spark.events.AnimationEvent")</a>

/**
 * The Animation class defines an animation that happens between 
 * start and end values over a specified period of time.
 * The animation can be a change in position, such as performed by
 * the Move effect; a change in size, as performed by the Resize effect;
 * a change in visibility, as performed by the Fade effect; or other 
 * types of animations used by effects or run directly with Animation.
 * This class defines the timing and value parts of the animation; other
 * code, either in effects or in application code, associate the animation
 * with objects and properties, such that the animated values produced by
 * Animation can then be applied to target objects and properties to actually
 * cause these objects to animate.
 *
 * <p>When defining animation effects, developers typically create an
 * instance of the Animate class, or some subclass thereof, which creates
 * an Animation in the <code>play()</code> method. The Animation instance
 * accepts start and end values, a duration, and optional parameters such as
 * easer and interpolator objects.</p>
 * 
 * <p>The Animation object calls listeners and the start and end of the animation,
 * as well as when the animation repeats and at regular update intervals during
 * the animation. These calls pass values which Animation calculated from
 * the start and end values and the easer and interpolator objects. These
 * values can then be used to set property values on target objects.</p>
 *
 *  @see spark.effects.Animate
 *  @see spark.effects.supportClasses.AnimateInstance
 */
public class Animation extends EventDispatcher
{
    /**
     * Constructs an Animation object.
     * 
     * @param startValue The initial value that the animation starts at
     * @param endValue The final value that the animation ends on
     * @param duration The length of time, in milliseconds, that the animation
     * will run
     */
    public function Animation(startValue:Object=null, endValue:Object=null, 
                                  duration:Number=-1) {}


    public static const LOOP:String = "loop";
    public static const REVERSE:String = "reverse";

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    /**
     * This variable indicates whether the animation is currently
     * running or not. The value is <code>false</code> unless the animation
     * has been played and not yet stopped (either programmatically or
     * automatically) or paused.
     */
    public function get isPlaying():Boolean
    {
        return _isPlaying;
    }

    /**
     * The value that the animation will produce at the beginning of the
     * animation. Values during the animation are calculated using the
     * <code>startValue</code> and <code>endValue</code>. This value
     * can be any arbitray object, but this type must be the same as
     * that for <code>endValue</code>, and types that are not Number
     * or Arrays of Number can only be handled if an <code>interpolator</code>
     * is supplied to the Animation to handle calculating intermediate
     * values.
     * 
     * @see #interpolator
     */
    public var startValue:Object;

    /**
     * The value that the animation will produce at the beginning of the
     * animation. Values during the animation are calculated using the
     * <code>startValue</code> and <code>endValue</code>. This value
     * can be any arbitray object, but this type must be the same as
     * that for <code>endValue</code>, and types that are not Number
     * or Arrays of Number can only be handled if an <code>interpolator</code>
     * is supplied to the Animation to handle calculating intermediate
     * values.
     * 
     * @see #interpolator
     */
    public var endValue:Object;

    /**
     * The length of time, in milliseconds, that this animation will run,
     * not counting any repetitions by use of <code>repeatCount</code>.
     */
    public var duration:Number;

    //----------------------------------
    //  repeatBehavior
    //----------------------------------
    /**
     * Sets the behavior of a repeating animation (an animation
     * with <code>repeatCount</code> equal to either 0 or >1). This
     * value should be either <code>LOOP</code>, where the animation
     * will repeat in the same order each time, or <code>REVERSE</code>,
     * where the animation will reverse direction each iteration.
     * 
     * @param value A String describing the behavior, either
     * Animation.LOOP or Animation.REVERSE
     * 
     * @default LOOP
     */
    public function get repeatBehavior():String {}
    public function set repeatBehavior(value:String):void {}

    //----------------------------------
    //  repeatCount
    //----------------------------------

    /**
     * Number of times that this animation will repeat. A value of
     * 0 means that it will repeat indefinitely. Only integer values are
     * supported, with fractional values rounded up to the next higher integer.
     * 
     * @param value Number of repetitions for this animation, with 0 being
     * an infinitely repeating animation. This value must be a positive 
     * number.
     * 
     * @default 1
     */
    public function set repeatCount(value:Number):void {}
    public function get repeatCount():Number {}

    //----------------------------------
    //  repeatDelay
    //----------------------------------

    /**
     * The amount of time spent waiting before each repetition cycle
     * begins. Setting this value to a non-zero number will have the
     * side effect that the previous animation cycle will end exactly at 
     * its end value, whereas non-delayed repetitions may skip over that 
     * value completely as the animation transitions smoothly from being
     * near the end of one cycle to being past the beginning of the next.
     *
     * @param value Amount of time, in milliseconds, to wait before beginning
     * each new cycle. This parameter is used starting with the first repetition,
     * not the first cycle. For a delay before the initial start, use
     * the <code>startDelay</code> property. Must be a value >= 0.
     * @see #startDelay
     * 
     * @default 0
     */
    public function set repeatDelay(value:Number):void {}
    public function get repeatDelay():Number {}

    /**
     * The amount of time spent waiting before the animation
     * begins.
     *
     * @param value Amount of time, in milliseconds, to wait before beginning
     * the animation. Must be a value >= 0.
     * 
     * @default 0
     */
    public function set startDelay(value:Number):void {}
    public function get startDelay():Number {}

    //----------------------------------
    //  intervalTime
    //----------------------------------

    /**
     * The time being used in the current frame calculations. This time is
     * shared by all active animations.
     */
    public static function get intervalTime():Number {}

    //----------------------------------
    //  interpolator
    //----------------------------------

    /**
     * The interpolator used by Animation to calculate values between
     * the start and end values. By default, interpolation is handled
     * by <code>NumberInterpolator</code> or, in the case of the start
     * and end values being arrays, by <code>NumberArrayInterpolator</code>.
     * Interpolation of other types, or of Numbers that should be interpolated
     * differently, such as <code>uint</code> values that hold color
     * channel information, can be handled by supplying a different
     * <code>interpolator</code>.
     */
    public var interpolator:IInterpolator = null;

    //----------------------------------
    //  resolution
    //----------------------------------

    /**
     * The maximum time between timing events, in milliseconds. It is
     * possible that the underlying timing mechanism may not be able to
     * achieve the rate requested by <code>resolution</code>. Since
     * all Animation objects run off the same underlying timer, 
     * they will all be serviced with the lowest <code>resolution</code>
     * requested.
     * 
     * @default 10
     */
    public function get resolution():Number {}
    public function set resolution(value:Number):void {}

    //----------------------------------
    //  elapsedTime
    //----------------------------------

    /**
     *  @private
     *  The current millisecond position in the animation.
     *  This value is between 0 and <code>duration</code>.
     *  Use the seek() method to change the position of the animation.
     */
    public function get elapsedTime():Number {}

    //----------------------------------
    //  elapsedFraction
    //----------------------------------

    /**
     *  @private
     *  The current fraction elapsed in the animation, after easing
     *  has been applied. This value is between 0 and 1.
     */
    public function get elapsedFraction():Number {}

    //----------------------------------
    //  easer
    //----------------------------------

    /**
     * Sets the easing behavior for this Animation. This IEaser
     * object will be used to convert the elapsed fraction of 
     * the animation into an eased fraction, which will then be used to
     * calculate the value at that fraction.
     * 
     * @param value The IEaser object which will be used to calculate the
     * eased elapsed fraction every time a animation event occurs. A value
     * of <code>null</code> will be interpreted as meaning no easing is
     * desired, which is equivalent to using a Linear ease, or
     * <code>animation.easer = Linear.getInstance();</code>.
     * 
     * @default Sine(.5)
     */
    public function get easer():IEaser {}
    public function set easer(value:IEaser):void {}

    public function get playReversed():Boolean {}
    public function set playReversed(value:Boolean):void {}

    /**
     *  Interrupt the animation, jump immediately to the end of the animation, 
     *  and send out ending notifications
     */
    public function end():void {}

    /**
     * Start the animation
     */
    public function play():void {}

    /**
     *  Advances the animation effect to the specified position. 
     *
     *  @param playheadTime The position, in milliseconds, between 0
     *  and the value of the <code>duration</code> property.
     */ 
    public function seek(playheadTime:Number):void {}

    /**
     *  Plays the effect in reverse,
     *  starting from the current position of the effect.
     */
    public function reverse():void {}

    /**
     * Pauses the effect until the <code>resume()</code> method is called.
     * 
     * @see resume()
     */
    public function pause():void {}

    /**
     *  Stops the animation, ending it without dispatching an event or calling
     *  the Animation's <code>end()</code> function. 
     */
    public function stop():void {}

    /**
     *  Resumes the effect after it has been paused 
     *  by a call to the <code>pause()</code> method. 
     */
    public function resume():void {}

}

The new effects for Gumbo use Animation. Please refer to the New Effects for Components and Graphics spec for more information on these effects.

Finally, while we're changing the core Tween API, we should update the easing functions API as well. The current API is somewhat awkward and inflexible, and we should consider adding a more flexible and simple API instead. I would like to do this in a way where people could still use the existing easing functions, because their functionality is quite useful.

To be more specific, some of the problems I have with the current easing API include: - The easing functions all take 4 parameters, but they could really be simplified to just one: the elapsed duration of the animation. This single value encapsulates all of the information in the current parameters: elapsed time (t), duration (d), (start - end) value(C), and initial value (b). Given an elapsed fraction, the function could return an eased value which represents that fraction eased, which could then be applied by the Animation and its interpolator to the actual start/end values. - The easing functions are consistent … to a fault. It doesn't make sense, from an API standpoint, to have Linear.EaseIn, Linear.EaseOut, and Linear.EaseInOut all as different functions which do exactly the same thing: linearly interpolate. There is no concept of "easing" where linear interpolation is concerned, so having these functions on the Linear class seems confusing and wrong. I would like to see the easing classes more clearly represent what they do (and don't do). - The easing function is provided to Tween as an opaque "Function" object, which doesn't give much information to the API reader what they are supposed to do to handle calls to the function. I would like to see the API document more clearly what an easing function is supposed to do, by providing an interface or class type that will be checked at compile time to make sure that the easing call will be handled appropriately. - The current easing functions are very inflexible - they calculate specific easing functions … period. There is one for Sine, one for Cubic, one for Quadratic, etc. But there are none that take parameters that define the shape of a curve, or the amount of time spent easing in and out. It seems like useful functionality to define some classes that have flexible parameters that define the timing curve, and which allow developers to customize the easing function without having to define entirely new easing classes.

Most importantly, I'd like to have a simple interface that could be implemented easily to provide arbitrary easing functionality.

/**
 * IEaser is an interface implemented by classes that provide time-easing
 * functionality for Animation. Implementors are responsible for the single
 * function, <code>ease()</code>, which takes and returns a fraction according
 * to the easing behavior desired. As a simple example, LinearEase simply 
 * returns the same input fraction, since there is no easing performed by
 * that easer. As another example, a reversing easer could be written which
 * returned the inverse fraction, (1 - fraction).
 * 
 * <p>By easing the fractional values of the time elapsed in an animation, 
 * these classes are easing the resulting values of the animation, but they
 * only have to deal with the fractional value of time instead of any
 * specific object types.</p>
 * 
 * @see spark.effects.Animation
 */
public interface IEaser
{
    /**
     * This function takes the fraction elapsed of a current tween
     * (a value from 0 to 1) and returns a new elapsed value. This 
     * value will be used to calculate Animated property values. By 
     * changing the value of the elapsed fraction, we effectively change
     * the animation of the property.
     * 
     * @param fraction The elapsed fraction of an animation, from 0 to 1.
     * @return The eased value for the elapsed time. Typically, this value
     * should be constrained to lie between 0 and 1, although it is possible
     * to return values outside of this range. Note that the results of
     * returning such values are undefined, and depend on what kind of 
     * effects are using this eased value. For example, an object moving
     * in a linear fashion can have positions calculated outside of its start 
     * and end point without a problem, but other value types (such as color) 
     * may not result in desired effects if they use time values that cause
     * them to surpass their endpoint values.
     */
    function ease(fraction:Number):Number;
}

There will be simple subclasses that implement this interface in order to provide most expected easing functionality out of the box (in addition to an interface that is easy to implement for custom easing functionality that developers wish to provide). For example, here is a minimum set of easing classes that should be provided. These will take the place of the current Linear, Sine, Cubic, Quartic, Quadratic, and Quintic classes (note that the last four are replaced by the single Power class below). Constant is an additional class that provides functionality that does not exist now. And All of these, apart from Linear, provide parameterization of the easing in and out fractions, which give more flexibility than the previous easing functions. The EaseInOut class is a superclass of several of these classes, providing shared functionality. It is not intended to be used directly by callers.

/**
 * Provides the simplest easing functionality, which is none at all.
 * That is, given an input fraction f, a Linear ease will simply return
 * f. This is useful for cases where no easing behavior is desired.
 */
public class Linear implements IEaser
{
    /**
     * Returns the singleton instance of Linear.
     */
    public static function getInstance():Linear {}

    /**
     * Performs a linear ease on the fraction elapsed of an
     * animation, by simply returning the same fraction.
     * 
     * @param fraction The elapsed fraction of the animation
     * @return The eased fraction of the animation
     */
    public function ease(fraction:Number):Number {}
}

/**
 * The superclass for classes that provide easing capability where there
 * is an easing-in portion of the animation followed by an easing-out portion.
 * The default behavior of this class will simply return a linear
 * interpolation for both easing phases; developers should create a subclass
 * of EaseInOut to get more interestion behavior.
 */
public class EaseInOut implements IEaser
{
    /**
     * A utility constant which, when supplied as the 
     * <code>easeInFraction</code>, will create an easing instance
     * that spends the entire animation easing in. This is equivalent
     * to simply using the <code>easeInFraction = 1</code>.
     */
    public static const IN:Number = 1;

    /**
     * A utility constant which, when supplied as the 
     * <code>easeInFraction</code>, will create an easing instance
     * that spends the entire animation easing out. This is equivalent
     * to simply using the <code>easeInFraction = 0</code>.
     */
    public static const OUT:Number = 0;

    /**
     * A utility constant which, when supplied as the

     * <code>easeInFraction</code>, will create an easing instance
     * that eases in for the first half and eases out for the
     * remainder. This is equivalent
     * to simply using the <code>easeInFraction = .5</code>.
     */
    public static const IN_OUT:Number = .5;

    /**
     * Constructs an EaseInOut instance with an optional easeInFraction
     * parameter.
     * 
     * @param easeInFraction Optional parameter that sets the value of
     * the <code>easeInFraction</code> property.
     */
    public function EaseInOut(easeInFraction:Number = EaseInOut.IN_OUT) {}

    /**
     * The percentage of an animation that should be spent accelerating
     * according to the power formula. This factor sets an implicit
     * "easeOut" parameter, equal to (1 - easeIn), so that any time not
     * spent easing in is spent easing out. For example, to have an easing
     * equation that spends half the time easing in and half easing out,
     * set easeIn equal to .5.
     * 
     * @see IN
     * @see OUT
     * @see IN_OUT
     * 
     * @default .5
     */
    public function get easeInFraction():Number
    {
        return _easeInFraction;
    }
    public function set easeInFraction(value:Number):void
    {
        _easeInFraction = value;
    }

    /**
     * @inheritDoc
     * 
     * Calculates the eased fraction value based on the 
     * <code>easeInFraction</code> property. If the given
     * <code>fraction</code> is less than <code>easeInFraction</code>,
     * this will call the <code>easeIn()</code> function, otherwise it
     * will call the <code>easeOut()</code> function. It is expected
     * that these functions are overridden in a subclass.
     * 
     * @param fraction The elapsed fraction of the animation
     * @return The eased fraction of the animation
     */
    public function ease(fraction:Number):Number
    {
        var easeOutFraction:Number = 1 - easeInFraction;

        if (fraction <= easeInFraction && easeInFraction > 0)
            return easeInFraction * easeIn(fraction/easeInFraction);
        else
            return easeInFraction + easeOutFraction * 
                easeOut((fraction - easeInFraction)/easeOutFraction);
    }

    /**
     * Returns a value that represents the eased fraction during the 
     * ease-in part of the curve. The value returned by this class 
     * is simply the fraction itself, which represents a linear 
     * interpolation of the fraction. More interesting behavior is
     * implemented by subclasses of <code>EaseInOut</code>.
     * 
     * @param fraction The fraction elapsed of the easing-in portion
     * of the animation.
     * @return A value that represents the eased value for this
     * part of the animation.
     */
    protected function easeIn(fraction:Number):Number
    {
        return fraction;
    }

    /**
     * Returns a value that represents the eased fraction during the 
     * ease-out part of the curve. The value returned by this class 
     * is simply the fraction itself, which represents a linear 
     * interpolation of the fraction. More interesting behavior is
     * implemented by subclasses of <code>EaseInOut</code>.
     * 
     * @param fraction The fraction elapsed of the easing-out portion
     * of the animation.
     * @return A value that represents the eased value for this
     * part of the animation.
     */
    protected function easeOut(fraction:Number):Number
    {
        return fraction;
    }    
}

/**
 * Provides easing functionality using a Sine function and a
 * parameter that specifies how much time to spend easing in
 * and out.
 */
public class Sine extends EaseInOut
{    
    /**
     * Constructs a Sine instance with an optional 
     * <code>easeInFraction</code> parameter.
     */
    public function Sine(easeInFraction:Number = .5)
    {
        super(easeInFraction);
    }

    /**
     * @inheritDoc
     * 
     * The easeIn calculation for Sine is equal to 
     * <code>1 - cos(fraction*PI/2)</code>.
     */
    override protected function easeIn(fraction:Number):Number {}

    /**
     * @inheritDoc


     * 
     * The easeOut calculation for Sine is equal to 
     * <code>sin(fraction*PI/2)</code>.
     */
    override protected function easeOut(fraction:Number):Number {}
}

/**
 * Provides easing functionality using a polynomial expression, where the
 * instance is created with a <code>power</code> parameter describing the 
 * behavior of the expression.
 */
public class Power extends EaseInOut
{
    /**
     * The exponent that will be used in the easing calculation. For example,
     * to get quadratic behavior, set exponent equal to 2. To get cubic
     * behavior, set exponent equal to 3. A value of 1 represents linear
     * motion, while a value of 0 simply returns 1 from the ease
     * method.
     * 
     * @default 2
     */
    public function get exponent():int {}
    public function set exponent(value:int):void {}

    /**
     * Constructs a Power instance with optional easeInFraction and exponent
     * values
     */
    public function Power(easeInFraction:Number = .5, exponent:Number = 2) {}

    /**
     * @inheritDoc
     * 
     * The easeIn calculation for Power is equal to 
     * <code>fraction^^exponent</code>.
     */
    override protected function easeIn(fraction:Number):Number {}

    /**
     * @inheritDoc
     * 
     * The easeOut calculation for Power is equal to 
     * <code>1 - ((1-fraction)^^exponent)</code>.
     */
    override protected function easeOut(fraction:Number):Number {}
}

/**
 * Provides easing functionality with three phases during
 * the animation: acceleration, constant motion, and deceleration.
 * As the animation starts it will accelerate through the period
 * specified by the <code>acceleration</code> parameter, it will
 * then use constant (linear) motion through the next phase, and
 * will finally decelerate until the end during the period specified
 * by the <code>deceleration</code> parameter.
 * 
 * <p>The easing values for the three phases will be calculated
 * such that the behavior of constant acceleration, linear motion,
 * and constant deceleration will all occur within the specified 
 * duration of the animation.</p>
 * 
 * <p>Note that the linear motion phase in the middle will not
 * return the same values as a <code>Linear</code> unless both
 * acceleration and deceleration are 0. That phase consists of
 * constant motion, but the speed of that motion is determined by
 * the size of that phase.</p>
 */
public class Constant implements IEaser
{
    /**
     * The percentage at the beginning of an animation that should be 
     * spent accelerating. Acceleration must be a value from 0 (meaning
     * no acceleration phase) to 1 (meaning the entire animation will
     * be spent accelerating). Additionally, the acceleration and
     * deceleration factors together must not be greater than 1.
     * 
     * @default .2
     */
    public function get acceleration():Number {}
    public function set acceleration(value:Number):void {}

    /**
     * The percentage at the end of an animation that should be 
     * spent decelerating. Deceleration must be a value from 0 (meaning
     * no deceleration phase) to 1 (meaning the entire animation will
     * be spent decelerating). Additionally, the acceleration and
     * deceleration factors together must not be greater than 1.
     * 
     * @default .2
     */
    public function get deceleration():Number {}
    public function set deceleration(value:Number):void {}

    /**
     * Constructs a Constant instance with optional acceleration and
     * deceleration parameters.
     */
    public function Constant(acceleration:Number = .2, deceleration:Number = .2) {}

    /**
     * @inheritDoc
     * 
     * Calculates the eased fraction value based on the
     * acceleration and deceleration factors. If <code>fraction</code>
     * is less than <code>acceleration</code>, it calculates a value
     * based on accelerating up to the constant phase. If <code>fraction</code>
     * is greater than <code>acceleration</code> and less than 
     * <code>(1-deceleration)</code>, it calculates a value based
     * on linear motion between the acceleration and deceleration phases.
     * Otherwise, it calculates a value based on constant deceleration
     * between the constant motion phase and zero.
     * 
     * @param fraction The elapsed fraction of the animation
     * @return The eased fraction of the animation
     */
    public function ease(fraction:Number):Number {}
}

B Features


Flash Easing Functions

The Flash "Motion Objects" work in Flash 10 provides several canonical easing functions, all of which use the same method of interpolating a single fractional (0-1) value. We should consider using some or all of their functions (as appropriate for open source Flex) to enable sharing of code and approaches across the products. Rather than invent new easing functions specific to Flex, let's use functions that already exist.

Examples and Usage


The real use of Animation from MXML would be indirectly, through the use of effects. This usage is shown in the New Effects for Components and Graphics spec. When Animation is used directly, it will usually be done from ActionScript code, as shown below. In this example, we set up an Animation to run between 0 and 1 for 1000 milliseconds. The animation will run twice, playing in reverse at the end of the first cycle. It will be set up with the specified 'easer' object and will call our 'updateHandler' listener.

        timerTween = new Animation(0, 1, 1000);
        timerTween.addEventListener(AnimationEvent.ANIMATION_UPDATE, updateHandler);
        timerTween.easer = easer;
        timerTween.repeatCount = 2;
        timerTween.repeatBehavior = Animation.REVERSE;
        timerTween.play();

Additional Implementation Details


All of the items above except those in the B Features section have already been implemented and checked in. These classes are currently used by the new effects that are specified in the New Effects for Components and Graphics spec.

Prototype Work


Most of the above classes, other than the items in 'B features', have working prototypes.

Compiler Work


None anticipated.

Web Tier Compiler Impact


N/A

Flex Feature Dependencies


The effects documented in New Effects for Components and Graphics depend on the classes specified here.

Backwards Compatibility


Syntax changes

As specified above, there are no compatibility issues. We might consider integrating some of the functionality above into existing classes instead of new classes. For example, maybe TweenEffectInstance should spawn an Animation object instead of a Tween object; this would affect some API in this existing class if we choose to go this route.

Behavior

none anticipated

Warnings/Deprecation

None anticipated, although if we choose to move all Effects to use Animation instead of the old Tween class, we might think about deprecating classes such as Tween and the Easing functions.

Accessibility


None anticipated

Performance


As with any animation system, timely performance is key to usefulness. We will need to ensure that the functionality we add can be accomplished in a performance-sensitive manner.

Globalization


None anticipated

Localization


Compiler Features

None anticipated

Framework Features

None anticipated

Issues and Recommendations


We need to decide whether to reinvent or revise existing classes, where applicable. For example, should we leave the old Tween and add the new Animation class? Or should we change the implementation of Tween in an incompatible way? Should we add new effect base classes that use the new Animation class? Or should we change existing effect classes to use the new Animation class instead?

When we change the Easing API, how much functionality should we ship out of the box? Is it good enough to offer the existing easing functions via the new API? Or some subset of these functions? Should we provide a wrapper layer for the old functions ('Classic') that provide exactly the old easing functions through the new interface? Should we try to re-use the Flash authoring functions (directly or indirectly through their easing algorithms)?

Documentation


None anticipated, other than typical API documentation

QA


Yes


Related

Wiki: Flex 4
Wiki: New Effects for Components and Graphics

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.