Java程序  |  924行  |  29.45 KB

package aurelienribon.tweenengine;

import aurelienribon.tweenengine.equations.Quad;
import java.util.HashMap;
import java.util.Map;

/**
 * Core class of the Tween Engine. A Tween is basically an interpolation
 * between two values of an object attribute. However, the main interest of a
 * Tween is that you can apply an easing formula on this interpolation, in
 * order to smooth the transitions or to achieve cool effects like springs or
 * bounces.
 * <p/>
 *
 * The Universal Tween Engine is called "universal" because it is able to apply
 * interpolations on every attribute from every possible object. Therefore,
 * every object in your application can be animated with cool effects: it does
 * not matter if your application is a game, a desktop interface or even a
 * console program! If it makes sense to animate something, then it can be
 * animated through this engine.
 * <p/>
 *
 * This class contains many static factory methods to create and instantiate
 * new interpolations easily. The common way to create a Tween is by using one
 * of these factories:
 * <p/>
 *
 * - Tween.to(...)<br/>
 * - Tween.from(...)<br/>
 * - Tween.set(...)<br/>
 * - Tween.call(...)
 * <p/>
 *
 * <h2>Example - firing a Tween</h2>
 *
 * The following example will move the target horizontal position from its
 * current value to x=200 and y=300, during 500ms, but only after a delay of
 * 1000ms. The animation will also be repeated 2 times (the starting position
 * is registered at the end of the delay, so the animation will automatically
 * restart from this registered position).
 * <p/>
 *
 * <pre> {@code
 * Tween.to(myObject, POSITION_XY, 0.5f)
 *      .target(200, 300)
 *      .ease(Quad.INOUT)
 *      .delay(1.0f)
 *      .repeat(2, 0.2f)
 *      .start(myManager);
 * }</pre>
 *
 * Tween life-cycles can be automatically managed for you, thanks to the
 * {@link TweenManager} class. If you choose to manage your tween when you start
 * it, then you don't need to care about it anymore. <b>Tweens are
 * <i>fire-and-forget</i>: don't think about them anymore once you started
 * them (if they are managed of course).</b>
 * <p/>
 *
 * You need to periodicaly update the tween engine, in order to compute the new
 * values. If your tweens are managed, only update the manager; else you need
 * to call {@link #update()} on your tweens periodically.
 * <p/>
 *
 * <h2>Example - setting up the engine</h2>
 *
 * The engine cannot directly change your objects attributes, since it doesn't
 * know them. Therefore, you need to tell him how to get and set the different
 * attributes of your objects: <b>you need to implement the {@link
 * TweenAccessor} interface for each object class you will animate</b>. Once
 * done, don't forget to register these implementations, using the static method
 * {@link registerAccessor()}, when you start your application.
 *
 * @see TweenAccessor
 * @see TweenManager
 * @see TweenEquation
 * @see Timeline
 * @author Aurelien Ribon | http://www.aurelienribon.com/
 */
public final class Tween extends BaseTween<Tween> {
	// -------------------------------------------------------------------------
	// Static -- misc
	// -------------------------------------------------------------------------

	/**
	 * Used as parameter in {@link #repeat(int, float)} and
	 * {@link #repeatYoyo(int, float)} methods.
	 */
	public static final int INFINITY = -1;

	private static int combinedAttrsLimit = 3;
	private static int waypointsLimit = 0;

	/**
	 * Changes the limit for combined attributes. Defaults to 3 to reduce
	 * memory footprint.
	 */
	public static void setCombinedAttributesLimit(int limit) {
		Tween.combinedAttrsLimit = limit;
	}

	/**
	 * Changes the limit of allowed waypoints for each tween. Defaults to 0 to
	 * reduce memory footprint.
	 */
	public static void setWaypointsLimit(int limit) {
		Tween.waypointsLimit = limit;
	}

	/**
	 * Gets the version number of the library.
	 */
	public static String getVersion() {
		return "6.3.3";
	}

	// -------------------------------------------------------------------------
	// Static -- pool
	// -------------------------------------------------------------------------

	private static final Pool.Callback<Tween> poolCallback = new Pool.Callback<Tween>() {
		@Override public void onPool(Tween obj) {obj.reset();}
		@Override public void onUnPool(Tween obj) {obj.reset();}
	};

	private static final Pool<Tween> pool = new Pool<Tween>(20, poolCallback) {
		@Override protected Tween create() {return new Tween();}
	};

	/**
	 * Used for debug purpose. Gets the current number of objects that are
	 * waiting in the Tween pool.
	 */
	public static int getPoolSize() {
		return pool.size();
	}

	/**
	 * Increases the minimum capacity of the pool. Capacity defaults to 20.
	 */
	public static void ensurePoolCapacity(int minCapacity) {
		pool.ensureCapacity(minCapacity);
	}

	// -------------------------------------------------------------------------
	// Static -- tween accessors
	// -------------------------------------------------------------------------

	private static final Map<Class<?>, TweenAccessor<?>> registeredAccessors = new HashMap<Class<?>, TweenAccessor<?>>();

	/**
	 * Registers an accessor with the class of an object. This accessor will be
	 * used by tweens applied to every objects implementing the registered
	 * class, or inheriting from it.
	 *
	 * @param someClass An object class.
	 * @param defaultAccessor The accessor that will be used to tween any
	 * object of class "someClass".
	 */
	public static void registerAccessor(Class<?> someClass, TweenAccessor<?> defaultAccessor) {
		registeredAccessors.put(someClass, defaultAccessor);
	}

	/**
	 * Gets the registered TweenAccessor associated with the given object class.
	 *
	 * @param someClass An object class.
	 */
	public static TweenAccessor<?> getRegisteredAccessor(Class<?> someClass) {
		return registeredAccessors.get(someClass);
	}

	// -------------------------------------------------------------------------
	// Static -- factories
	// -------------------------------------------------------------------------

	/**
	 * Factory creating a new standard interpolation. This is the most common
	 * type of interpolation. The starting values are retrieved automatically
	 * after the delay (if any).
	 * <br/><br/>
	 *
	 * <b>You need to set the target values of the interpolation by using one
	 * of the target() methods</b>. The interpolation will run from the
	 * starting values to these target values.
	 * <br/><br/>
	 *
	 * The common use of Tweens is "fire-and-forget": you do not need to care
	 * for tweens once you added them to a TweenManager, they will be updated
	 * automatically, and cleaned once finished. Common call:
	 * <br/><br/>
	 *
	 * <pre> {@code
	 * Tween.to(myObject, POSITION, 1.0f)
	 *      .target(50, 70)
	 *      .ease(Quad.INOUT)
	 *      .start(myManager);
	 * }</pre>
	 *
	 * Several options such as delay, repetitions and callbacks can be added to
	 * the tween.
	 *
	 * @param target The target object of the interpolation.
	 * @param tweenType The desired type of interpolation.
	 * @param duration The duration of the interpolation, in milliseconds.
	 * @return The generated Tween.
	 */
	public static Tween to(Object target, int tweenType, float duration) {
		Tween tween = pool.get();
		tween.setup(target, tweenType, duration);
		tween.ease(Quad.INOUT);
		tween.path(TweenPaths.catmullRom);
		return tween;
	}

	/**
	 * Factory creating a new reversed interpolation. The ending values are
	 * retrieved automatically after the delay (if any).
	 * <br/><br/>
	 *
	 * <b>You need to set the starting values of the interpolation by using one
	 * of the target() methods</b>. The interpolation will run from the
	 * starting values to these target values.
	 * <br/><br/>
	 *
	 * The common use of Tweens is "fire-and-forget": you do not need to care
	 * for tweens once you added them to a TweenManager, they will be updated
	 * automatically, and cleaned once finished. Common call:
	 * <br/><br/>
	 *
	 * <pre> {@code
	 * Tween.from(myObject, POSITION, 1.0f)
	 *      .target(0, 0)
	 *      .ease(Quad.INOUT)
	 *      .start(myManager);
	 * }</pre>
	 *
	 * Several options such as delay, repetitions and callbacks can be added to
	 * the tween.
	 *
	 * @param target The target object of the interpolation.
	 * @param tweenType The desired type of interpolation.
	 * @param duration The duration of the interpolation, in milliseconds.
	 * @return The generated Tween.
	 */
	public static Tween from(Object target, int tweenType, float duration) {
		Tween tween = pool.get();
		tween.setup(target, tweenType, duration);
		tween.ease(Quad.INOUT);
		tween.path(TweenPaths.catmullRom);
		tween.isFrom = true;
		return tween;
	}

	/**
	 * Factory creating a new instantaneous interpolation (thus this is not
	 * really an interpolation).
	 * <br/><br/>
	 *
	 * <b>You need to set the target values of the interpolation by using one
	 * of the target() methods</b>. The interpolation will set the target
	 * attribute to these values after the delay (if any).
	 * <br/><br/>
	 *
	 * The common use of Tweens is "fire-and-forget": you do not need to care
	 * for tweens once you added them to a TweenManager, they will be updated
	 * automatically, and cleaned once finished. Common call:
	 * <br/><br/>
	 *
	 * <pre> {@code
	 * Tween.set(myObject, POSITION)
	 *      .target(50, 70)
	 *      .delay(1.0f)
	 *      .start(myManager);
	 * }</pre>
	 *
	 * Several options such as delay, repetitions and callbacks can be added to
	 * the tween.
	 *
	 * @param target The target object of the interpolation.
	 * @param tweenType The desired type of interpolation.
	 * @return The generated Tween.
	 */
	public static Tween set(Object target, int tweenType) {
		Tween tween = pool.get();
		tween.setup(target, tweenType, 0);
		tween.ease(Quad.INOUT);
		return tween;
	}

	/**
	 * Factory creating a new timer. The given callback will be triggered on
	 * each iteration start, after the delay.
	 * <br/><br/>
	 *
	 * The common use of Tweens is "fire-and-forget": you do not need to care
	 * for tweens once you added them to a TweenManager, they will be updated
	 * automatically, and cleaned once finished. Common call:
	 * <br/><br/>
	 *
	 * <pre> {@code
	 * Tween.call(myCallback)
	 *      .delay(1.0f)
	 *      .repeat(10, 1000)
	 *      .start(myManager);
	 * }</pre>
	 *
	 * @param callback The callback that will be triggered on each iteration
	 * start.
	 * @return The generated Tween.
	 * @see TweenCallback
	 */
	public static Tween call(TweenCallback callback) {
		Tween tween = pool.get();
		tween.setup(null, -1, 0);
		tween.setCallback(callback);
		tween.setCallbackTriggers(TweenCallback.START);
		return tween;
	}

	/**
	 * Convenience method to create an empty tween. Such object is only useful
	 * when placed inside animation sequences (see {@link Timeline}), in which
	 * it may act as a beacon, so you can set a callback on it in order to
	 * trigger some action at the right moment.
	 *
	 * @return The generated Tween.
	 * @see Timeline
	 */
	public static Tween mark() {
		Tween tween = pool.get();
		tween.setup(null, -1, 0);
		return tween;
	}

	// -------------------------------------------------------------------------
	// Attributes
	// -------------------------------------------------------------------------

	// Main
	private Object target;
	private Class<?> targetClass;
	private TweenAccessor<Object> accessor;
	private int type;
	private TweenEquation equation;
	private TweenPath path;

	// General
	private boolean isFrom;
	private boolean isRelative;
	private int combinedAttrsCnt;
	private int waypointsCnt;

	// Values
	private final float[] startValues = new float[combinedAttrsLimit];
	private final float[] targetValues = new float[combinedAttrsLimit];
	private final float[] waypoints = new float[waypointsLimit * combinedAttrsLimit];

	// Buffers
	private float[] accessorBuffer = new float[combinedAttrsLimit];
	private float[] pathBuffer = new float[(2+waypointsLimit)*combinedAttrsLimit];

	// -------------------------------------------------------------------------
	// Setup
	// -------------------------------------------------------------------------

	private Tween() {
		reset();
	}

	@Override
	protected void reset() {
		super.reset();

		target = null;
		targetClass = null;
		accessor = null;
		type = -1;
		equation = null;
		path = null;

		isFrom = isRelative = false;
		combinedAttrsCnt = waypointsCnt = 0;

		if (accessorBuffer.length != combinedAttrsLimit) {
			accessorBuffer = new float[combinedAttrsLimit];
		}

		if (pathBuffer.length != (2+waypointsLimit)*combinedAttrsLimit) {
			pathBuffer = new float[(2+waypointsLimit)*combinedAttrsLimit];
		}
	}

	private void setup(Object target, int tweenType, float duration) {
		if (duration < 0) throw new RuntimeException("Duration can't be negative");

		this.target = target;
		this.targetClass = target != null ? findTargetClass() : null;
		this.type = tweenType;
		this.duration = duration;
	}

	private Class<?> findTargetClass() {
		if (registeredAccessors.containsKey(target.getClass())) return target.getClass();
		if (target instanceof TweenAccessor) return target.getClass();

		Class<?> parentClass = target.getClass().getSuperclass();
		while (parentClass != null && !registeredAccessors.containsKey(parentClass))
			parentClass = parentClass.getSuperclass();

		return parentClass;
	}

	// -------------------------------------------------------------------------
	// Public API
	// -------------------------------------------------------------------------

	/**
	 * Sets the easing equation of the tween. Existing equations are located in
	 * <i>aurelienribon.tweenengine.equations</i> package, but you can of course
	 * implement your owns, see {@link TweenEquation}. You can also use the
	 * {@link TweenEquations} static instances to quickly access all the
	 * equations. Default equation is Quad.INOUT.
	 * <p/>
	 *
	 * <b>Proposed equations are:</b><br/>
	 * - Linear.INOUT,<br/>
	 * - Quad.IN | OUT | INOUT,<br/>
	 * - Cubic.IN | OUT | INOUT,<br/>
	 * - Quart.IN | OUT | INOUT,<br/>
	 * - Quint.IN | OUT | INOUT,<br/>
	 * - Circ.IN | OUT | INOUT,<br/>
	 * - Sine.IN | OUT | INOUT,<br/>
	 * - Expo.IN | OUT | INOUT,<br/>
	 * - Back.IN | OUT | INOUT,<br/>
	 * - Bounce.IN | OUT | INOUT,<br/>
	 * - Elastic.IN | OUT | INOUT
	 *
	 * @return The current tween, for chaining instructions.
	 * @see TweenEquation
	 * @see TweenEquations
	 */
	public Tween ease(TweenEquation easeEquation) {
		this.equation = easeEquation;
		return this;
	}

	/**
	 * Forces the tween to use the TweenAccessor registered with the given
	 * target class. Useful if you want to use a specific accessor associated
	 * to an interface, for instance.
	 *
	 * @param targetClass A class registered with an accessor.
	 * @return The current tween, for chaining instructions.
	 */
	public Tween cast(Class<?> targetClass) {
		if (isStarted()) throw new RuntimeException("You can't cast the target of a tween once it is started");
		this.targetClass = targetClass;
		return this;
	}

	/**
	 * Sets the target value of the interpolation. The interpolation will run
	 * from the <b>value at start time (after the delay, if any)</b> to this
	 * target value.
	 * <p/>
	 *
	 * To sum-up:<br/>
	 * - start value: value at start time, after delay<br/>
	 * - end value: param
	 *
	 * @param targetValue The target value of the interpolation.
	 * @return The current tween, for chaining instructions.
	 */
	public Tween target(float targetValue) {
		targetValues[0] = targetValue;
		return this;
	}

	/**
	 * Sets the target values of the interpolation. The interpolation will run
	 * from the <b>values at start time (after the delay, if any)</b> to these
	 * target values.
	 * <p/>
	 *
	 * To sum-up:<br/>
	 * - start values: values at start time, after delay<br/>
	 * - end values: params
	 *
	 * @param targetValue1 The 1st target value of the interpolation.
	 * @param targetValue2 The 2nd target value of the interpolation.
	 * @return The current tween, for chaining instructions.
	 */
	public Tween target(float targetValue1, float targetValue2) {
		targetValues[0] = targetValue1;
		targetValues[1] = targetValue2;
		return this;
	}

	/**
	 * Sets the target values of the interpolation. The interpolation will run
	 * from the <b>values at start time (after the delay, if any)</b> to these
	 * target values.
	 * <p/>
	 *
	 * To sum-up:<br/>
	 * - start values: values at start time, after delay<br/>
	 * - end values: params
	 *
	 * @param targetValue1 The 1st target value of the interpolation.
	 * @param targetValue2 The 2nd target value of the interpolation.
	 * @param targetValue3 The 3rd target value of the interpolation.
	 * @return The current tween, for chaining instructions.
	 */
	public Tween target(float targetValue1, float targetValue2, float targetValue3) {
		targetValues[0] = targetValue1;
		targetValues[1] = targetValue2;
		targetValues[2] = targetValue3;
		return this;
	}

	/**
	 * Sets the target values of the interpolation. The interpolation will run
	 * from the <b>values at start time (after the delay, if any)</b> to these
	 * target values.
	 * <p/>
	 *
	 * To sum-up:<br/>
	 * - start values: values at start time, after delay<br/>
	 * - end values: params
	 *
	 * @param targetValues The target values of the interpolation.
	 * @return The current tween, for chaining instructions.
	 */
	public Tween target(float... targetValues) {
		if (targetValues.length > combinedAttrsLimit) throwCombinedAttrsLimitReached();
		System.arraycopy(targetValues, 0, this.targetValues, 0, targetValues.length);
		return this;
	}

	/**
	 * Sets the target value of the interpolation, relatively to the <b>value
	 * at start time (after the delay, if any)</b>.
	 * <p/>
	 *
	 * To sum-up:<br/>
	 * - start value: value at start time, after delay<br/>
	 * - end value: param + value at start time, after delay
	 *
	 * @param targetValue The relative target value of the interpolation.
	 * @return The current tween, for chaining instructions.
	 */
	public Tween targetRelative(float targetValue) {
		isRelative = true;
		targetValues[0] = isInitialized() ? targetValue + startValues[0] : targetValue;
		return this;
	}

	/**
	 * Sets the target values of the interpolation, relatively to the <b>values
	 * at start time (after the delay, if any)</b>.
	 * <p/>
	 *
	 * To sum-up:<br/>
	 * - start values: values at start time, after delay<br/>
	 * - end values: params + values at start time, after delay
	 *
	 * @param targetValue1 The 1st relative target value of the interpolation.
	 * @param targetValue2 The 2nd relative target value of the interpolation.
	 * @return The current tween, for chaining instructions.
	 */
	public Tween targetRelative(float targetValue1, float targetValue2) {
		isRelative = true;
		targetValues[0] = isInitialized() ? targetValue1 + startValues[0] : targetValue1;
		targetValues[1] = isInitialized() ? targetValue2 + startValues[1] : targetValue2;
		return this;
	}

	/**
	 * Sets the target values of the interpolation, relatively to the <b>values
	 * at start time (after the delay, if any)</b>.
	 * <p/>
	 *
	 * To sum-up:<br/>
	 * - start values: values at start time, after delay<br/>
	 * - end values: params + values at start time, after delay
	 *
	 * @param targetValue1 The 1st relative target value of the interpolation.
	 * @param targetValue2 The 2nd relative target value of the interpolation.
	 * @param targetValue3 The 3rd relative target value of the interpolation.
	 * @return The current tween, for chaining instructions.
	 */
	public Tween targetRelative(float targetValue1, float targetValue2, float targetValue3) {
		isRelative = true;
		targetValues[0] = isInitialized() ? targetValue1 + startValues[0] : targetValue1;
		targetValues[1] = isInitialized() ? targetValue2 + startValues[1] : targetValue2;
		targetValues[2] = isInitialized() ? targetValue3 + startValues[2] : targetValue3;
		return this;
	}

	/**
	 * Sets the target values of the interpolation, relatively to the <b>values
	 * at start time (after the delay, if any)</b>.
	 * <p/>
	 *
	 * To sum-up:<br/>
	 * - start values: values at start time, after delay<br/>
	 * - end values: params + values at start time, after delay
	 *
	 * @param targetValues The relative target values of the interpolation.
	 * @return The current tween, for chaining instructions.
	 */
	public Tween targetRelative(float... targetValues) {
		if (targetValues.length > combinedAttrsLimit) throwCombinedAttrsLimitReached();
		for (int i=0; i<targetValues.length; i++) {
			this.targetValues[i] = isInitialized() ? targetValues[i] + startValues[i] : targetValues[i];
		}

		isRelative = true;
		return this;
	}

	/**
	 * Adds a waypoint to the path. The default path runs from the start values
	 * to the end values linearly. If you add waypoints, the default path will
	 * use a smooth catmull-rom spline to navigate between the waypoints, but
	 * you can change this behavior by using the {@link #path(TweenPath)}
	 * method.
	 *
	 * @param targetValue The target of this waypoint.
	 * @return The current tween, for chaining instructions.
	 */
	public Tween waypoint(float targetValue) {
		if (waypointsCnt == waypointsLimit) throwWaypointsLimitReached();
		waypoints[waypointsCnt] = targetValue;
		waypointsCnt += 1;
		return this;
	}

	/**
	 * Adds a waypoint to the path. The default path runs from the start values
	 * to the end values linearly. If you add waypoints, the default path will
	 * use a smooth catmull-rom spline to navigate between the waypoints, but
	 * you can change this behavior by using the {@link #path(TweenPath)}
	 * method.
	 * <p/>
	 * Note that if you want waypoints relative to the start values, use one of
	 * the .targetRelative() methods to define your target.
	 *
	 * @param targetValue1 The 1st target of this waypoint.
	 * @param targetValue2 The 2nd target of this waypoint.
	 * @return The current tween, for chaining instructions.
	 */
	public Tween waypoint(float targetValue1, float targetValue2) {
		if (waypointsCnt == waypointsLimit) throwWaypointsLimitReached();
		waypoints[waypointsCnt*2] = targetValue1;
		waypoints[waypointsCnt*2+1] = targetValue2;
		waypointsCnt += 1;
		return this;
	}

	/**
	 * Adds a waypoint to the path. The default path runs from the start values
	 * to the end values linearly. If you add waypoints, the default path will
	 * use a smooth catmull-rom spline to navigate between the waypoints, but
	 * you can change this behavior by using the {@link #path(TweenPath)}
	 * method.
	 * <p/>
	 * Note that if you want waypoints relative to the start values, use one of
	 * the .targetRelative() methods to define your target.
	 *
	 * @param targetValue1 The 1st target of this waypoint.
	 * @param targetValue2 The 2nd target of this waypoint.
	 * @param targetValue3 The 3rd target of this waypoint.
	 * @return The current tween, for chaining instructions.
	 */
	public Tween waypoint(float targetValue1, float targetValue2, float targetValue3) {
		if (waypointsCnt == waypointsLimit) throwWaypointsLimitReached();
		waypoints[waypointsCnt*3] = targetValue1;
		waypoints[waypointsCnt*3+1] = targetValue2;
		waypoints[waypointsCnt*3+2] = targetValue3;
		waypointsCnt += 1;
		return this;
	}

	/**
	 * Adds a waypoint to the path. The default path runs from the start values
	 * to the end values linearly. If you add waypoints, the default path will
	 * use a smooth catmull-rom spline to navigate between the waypoints, but
	 * you can change this behavior by using the {@link #path(TweenPath)}
	 * method.
	 * <p/>
	 * Note that if you want waypoints relative to the start values, use one of
	 * the .targetRelative() methods to define your target.
	 *
	 * @param targetValues The targets of this waypoint.
	 * @return The current tween, for chaining instructions.
	 */
	public Tween waypoint(float... targetValues) {
		if (waypointsCnt == waypointsLimit) throwWaypointsLimitReached();
		System.arraycopy(targetValues, 0, waypoints, waypointsCnt*targetValues.length, targetValues.length);
		waypointsCnt += 1;
		return this;
	}

	/**
	 * Sets the algorithm that will be used to navigate through the waypoints,
	 * from the start values to the end values. Default is a catmull-rom spline,
	 * but you can find other paths in the {@link TweenPaths} class.
	 *
	 * @param path A TweenPath implementation.
	 * @return The current tween, for chaining instructions.
	 * @see TweenPath
	 * @see TweenPaths
	 */
	public Tween path(TweenPath path) {
		this.path = path;
		return this;
	}

	// -------------------------------------------------------------------------
	// Getters
	// -------------------------------------------------------------------------

	/**
	 * Gets the target object.
	 */
	public Object getTarget() {
		return target;
	}

	/**
	 * Gets the type of the tween.
	 */
	public int getType() {
		return type;
	}

	/**
	 * Gets the easing equation.
	 */
	public TweenEquation getEasing() {
		return equation;
	}

	/**
	 * Gets the target values. The returned buffer is as long as the maximum
	 * allowed combined values. Therefore, you're surely not interested in all
	 * its content. Use {@link #getCombinedTweenCount()} to get the number of
	 * interesting slots.
	 */
	public float[] getTargetValues() {
		return targetValues;
	}

	/**
	 * Gets the number of combined animations.
	 */
	public int getCombinedAttributesCount() {
		return combinedAttrsCnt;
	}

	/**
	 * Gets the TweenAccessor used with the target.
	 */
	public TweenAccessor<?> getAccessor() {
		return accessor;
	}

	/**
	 * Gets the class that was used to find the associated TweenAccessor.
	 */
	public Class<?> getTargetClass() {
		return targetClass;
	}

	// -------------------------------------------------------------------------
	// Overrides
	// -------------------------------------------------------------------------

	@Override
	public Tween build() {
		if (target == null) return this;

		accessor = (TweenAccessor<Object>) registeredAccessors.get(targetClass);
		if (accessor == null && target instanceof TweenAccessor) accessor = (TweenAccessor<Object>) target;
		if (accessor != null) combinedAttrsCnt = accessor.getValues(target, type, accessorBuffer);
		else throw new RuntimeException("No TweenAccessor was found for the target");

		if (combinedAttrsCnt > combinedAttrsLimit) throwCombinedAttrsLimitReached();
		return this;
	}

	@Override
	public void free() {
		pool.free(this);
	}

	@Override
	protected void initializeOverride() {
		if (target == null) return;

		accessor.getValues(target, type, startValues);

		for (int i=0; i<combinedAttrsCnt; i++) {
			targetValues[i] += isRelative ? startValues[i] : 0;

			for (int ii=0; ii<waypointsCnt; ii++) {
				waypoints[ii*combinedAttrsCnt+i] += isRelative ? startValues[i] : 0;
			}

			if (isFrom) {
				float tmp = startValues[i];
				startValues[i] = targetValues[i];
				targetValues[i] = tmp;
			}
		}
	}

	@Override
	protected void updateOverride(int step, int lastStep, boolean isIterationStep, float delta) {
		if (target == null || equation == null) return;

		// Case iteration end has been reached

		if (!isIterationStep && step > lastStep) {
			accessor.setValues(target, type, isReverse(lastStep) ? startValues : targetValues);
			return;
		}

		if (!isIterationStep && step < lastStep) {
			accessor.setValues(target, type, isReverse(lastStep) ? targetValues : startValues);
			return;
		}

		// Validation

		assert isIterationStep;
		assert getCurrentTime() >= 0;
		assert getCurrentTime() <= duration;

		// Case duration equals zero

		if (duration < 0.00000000001f && delta > -0.00000000001f) {
			accessor.setValues(target, type, isReverse(step) ? targetValues : startValues);
			return;
		}

		if (duration < 0.00000000001f && delta < 0.00000000001f) {
			accessor.setValues(target, type, isReverse(step) ? startValues : targetValues);
			return;
		}

		// Normal behavior

		float time = isReverse(step) ? duration - getCurrentTime() : getCurrentTime();
		float t = equation.compute(time/duration);

		if (waypointsCnt == 0 || path == null) {
			for (int i=0; i<combinedAttrsCnt; i++) {
				accessorBuffer[i] = startValues[i] + t * (targetValues[i] - startValues[i]);
			}

		} else {
			for (int i=0; i<combinedAttrsCnt; i++) {
				pathBuffer[0] = startValues[i];
				pathBuffer[1+waypointsCnt] = targetValues[i];
				for (int ii=0; ii<waypointsCnt; ii++) {
					pathBuffer[ii+1] = waypoints[ii*combinedAttrsCnt+i];
				}

				accessorBuffer[i] = path.compute(t, pathBuffer, waypointsCnt+2);
			}
		}

		accessor.setValues(target, type, accessorBuffer);
	}

	// -------------------------------------------------------------------------
	// BaseTween impl.
	// -------------------------------------------------------------------------

	@Override
	protected void forceStartValues() {
		if (target == null) return;
		accessor.setValues(target, type, startValues);
	}

	@Override
	protected void forceEndValues() {
		if (target == null) return;
		accessor.setValues(target, type, targetValues);
	}

	@Override
	protected boolean containsTarget(Object target) {
		return this.target == target;
	}

	@Override
	protected boolean containsTarget(Object target, int tweenType) {
		return this.target == target && this.type == tweenType;
	}

	// -------------------------------------------------------------------------
	// Helpers
	// -------------------------------------------------------------------------

	private void throwCombinedAttrsLimitReached() {
		String msg = "You cannot combine more than " + combinedAttrsLimit + " "
			+ "attributes in a tween. You can raise this limit with "
			+ "Tween.setCombinedAttributesLimit(), which should be called once "
			+ "in application initialization code.";
		throw new RuntimeException(msg);
	}

	private void throwWaypointsLimitReached() {
		String msg = "You cannot add more than " + waypointsLimit + " "
			+ "waypoints to a tween. You can raise this limit with "
			+ "Tween.setWaypointsLimit(), which should be called once in "
			+ "application initialization code.";
		throw new RuntimeException(msg);
	}
}