package org.andengine.entity.modifier;
import org.andengine.entity.IEntity;
import org.andengine.util.adt.array.ArrayUtils;
import org.andengine.util.math.MathUtils;
import org.andengine.util.modifier.ease.EaseLinear;
import org.andengine.util.modifier.ease.IEaseFunction;
/**
* (c) Zynga 2012
*
* @author Nicolas Gramlich <ngramlich@zynga.com>
* @since 11:47:24 - 20.03.2012
* @see {@link http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Cardinal_spline}
* @see {@link http://algorithmist.wordpress.com/2009/10/06/cardinal-splines-part-4/}
*/
public class CardinalSplineMoveModifier extends DurationEntityModifier {
// ===========================================================
// Constants
// ===========================================================
// ===========================================================
// Fields
// ===========================================================
private final CardinalSplineMoveModifierConfig mCardinalSplineMoveModifierConfig;
private final IEaseFunction mEaseFunction;
private final int mControlSegmentCount;
private final float mControlSegmentCountInverse;
// ===========================================================
// Constructors
// ===========================================================
public CardinalSplineMoveModifier(final float pDuration, final CardinalSplineMoveModifierConfig pCardinalSplineMoveModifierConfig) {
this(pDuration, pCardinalSplineMoveModifierConfig, null, EaseLinear.getInstance());
}
public CardinalSplineMoveModifier(final float pDuration, final CardinalSplineMoveModifierConfig pCardinalSplineMoveModifierConfig, final IEaseFunction pEaseFunction) {
this(pDuration, pCardinalSplineMoveModifierConfig, null, pEaseFunction);
}
public CardinalSplineMoveModifier(final float pDuration, final CardinalSplineMoveModifierConfig pCardinalSplineMoveModifierConfig, final IEntityModifierListener pEntityModifierListener) {
this(pDuration, pCardinalSplineMoveModifierConfig, pEntityModifierListener, EaseLinear.getInstance());
}
public CardinalSplineMoveModifier(final float pDuration, final CardinalSplineMoveModifierConfig pCardinalSplineMoveModifierConfig, final IEntityModifierListener pEntityModifierListener, final IEaseFunction pEaseFunction) {
super(pDuration, pEntityModifierListener);
this.mCardinalSplineMoveModifierConfig = pCardinalSplineMoveModifierConfig;
this.mEaseFunction = pEaseFunction;
this.mControlSegmentCount = pCardinalSplineMoveModifierConfig.getControlPointCount() - 1;
this.mControlSegmentCountInverse = 1.0f / this.mControlSegmentCount;
}
@Override
public CardinalSplineMoveModifier deepCopy() {
return new CardinalSplineMoveModifier(this.mDuration, this.mCardinalSplineMoveModifierConfig.deepCopy(), this.mEaseFunction);
}
public CardinalSplineMoveModifier reverse() {
return new CardinalSplineMoveModifier(this.mDuration, this.mCardinalSplineMoveModifierConfig.deepCopyReverse(), this.mEaseFunction);
}
// ===========================================================
// Getter & Setter
// ===========================================================
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@Override
protected void onManagedInitialize(final IEntity pEntity) {
}
@Override
protected void onManagedUpdate(final float pSecondsElapsed, final IEntity pEntity) {
final float percentageDone = this.mEaseFunction.getPercentage(this.getSecondsElapsed(), this.mDuration);
/* Calculate active control point. */
final int p;
if(percentageDone == 1) {
p = this.mControlSegmentCount;
} else {
p = (int) (percentageDone / this.mControlSegmentCountInverse);
}
/* Retrieve control points. */
final int p0 = MathUtils.bringToBounds(0, this.mControlSegmentCount, p - 1);
final float pX0 = this.mCardinalSplineMoveModifierConfig.mControlPointXs[p0];
final float pY0 = this.mCardinalSplineMoveModifierConfig.mControlPointYs[p0];
final int p1 = MathUtils.bringToBounds(0, this.mControlSegmentCount, p);
final float pX1 = this.mCardinalSplineMoveModifierConfig.mControlPointXs[p1];
final float pY1 = this.mCardinalSplineMoveModifierConfig.mControlPointYs[p1];
final int p2 = MathUtils.bringToBounds(0, this.mControlSegmentCount, p + 1);
final float pX2 = this.mCardinalSplineMoveModifierConfig.mControlPointXs[p2];
final float pY2 = this.mCardinalSplineMoveModifierConfig.mControlPointYs[p2];
final int p3 = MathUtils.bringToBounds(0, this.mControlSegmentCount, p + 2);
final float pX3 = this.mCardinalSplineMoveModifierConfig.mControlPointXs[p3];
final float pY3 = this.mCardinalSplineMoveModifierConfig.mControlPointYs[p3];
/* Calculate new position. */
final float t = (percentageDone - (p * this.mControlSegmentCountInverse)) / this.mControlSegmentCountInverse;
final float tt = t * t;
final float ttt = tt * t;
/*
* Formula: s * (-ttt + 2tt – t) * P1 + s * (-ttt + tt) * P2 + (2ttt – 3tt + 1) * P2 + s(ttt – 2tt + t) * P3 + (-2ttt + 3tt) * P3 + s * (ttt – tt) * P4
*/
final float s = (1 - this.mCardinalSplineMoveModifierConfig.mTension) / 2;
final float b1 = s * ((-ttt + (2 * tt)) - t); // (s * (-ttt + 2tt – t)) * P1
final float b2 = (s * (-ttt + tt)) + (((2 * ttt) - (3 * tt)) + 1); // (s * (-ttt + tt) + (2ttt – 3tt + 1)) * P2
final float b3 = (s * ((ttt - (2 * tt)) + t)) + ((-2 * ttt) + (3 * tt)); // (s * (ttt – 2tt + t) + (-2ttt + 3tt)) * P3
final float b4 = s * (ttt - tt); // (s * (ttt – tt)) * P4
final float x = ((pX0 * b1) + (pX1 * b2) + (pX2 * b3) + (pX3 * b4));
final float y = ((pY0 * b1) + (pY1 * b2) + (pY2 * b3) + (pY3 * b4));
pEntity.setPosition(x, y);
}
// ===========================================================
// Methods
// ===========================================================
public static final float cardinalSplineX(final float pX0, final float pX1, final float pX2, final float pX3, final float pT, final float pTension) {
final float t = pT;
final float tt = t * t;
final float ttt = tt * t;
final float s = (1 - pTension) / 2;
final float b1 = s * ((-ttt + (2 * tt)) - t); // (s * (-ttt + 2tt – t)) * P1
final float b2 = (s * (-ttt + tt)) + (((2 * ttt) - (3 * tt)) + 1); // (s * (-ttt + tt) + (2ttt – 3tt + 1)) * P2
final float b3 = (s * ((ttt - (2 * tt)) + t)) + ((-2 * ttt) + (3 * tt)); // (s * (ttt – 2tt + t) + (-2ttt + 3tt)) * P3
final float b4 = s * (ttt - tt); // (s * (ttt – tt)) * P4
return ((pX0 * b1) + (pX1 * b2) + (pX2 * b3) + (pX3 * b4));
}
public static final float cardinalSplineY(final float pY0, final float pY1, final float pY2, final float pY3, final float pT, final float pTension) {
final float t = pT;
final float tt = t * t;
final float ttt = tt * t;
final float s = (1 - pTension) / 2;
final float b1 = s * ((-ttt + (2 * tt)) - t); // (s * (-ttt + 2tt – t)) * P1
final float b2 = (s * (-ttt + tt)) + (((2 * ttt) - (3 * tt)) + 1); // (s * (-ttt + tt) + (2ttt – 3tt + 1)) * P2
final float b3 = (s * ((ttt - (2 * tt)) + t)) + ((-2 * ttt) + (3 * tt)); // (s * (ttt – 2tt + t) + (-2ttt + 3tt)) * P3
final float b4 = s * (ttt - tt); // (s * (ttt – tt)) * P4
return ((pY0 * b1) + (pY1 * b2) + (pY2 * b3) + (pY3 * b4));
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
public static class CardinalSplineMoveModifierConfig {
// ===========================================================
// Constants
// ===========================================================
private static final int CONTROLPOINT_COUNT_MINIMUM = 4;
// ===========================================================
// Fields
// ===========================================================
/* package */ private final float[] mControlPointXs;
/* package */ private final float[] mControlPointYs;
/* package */ final float mTension;
// ===========================================================
// Constructors
// ===========================================================
/**
* @param pControlPointCount
* @param pTension [-1, 1]
*/
public CardinalSplineMoveModifierConfig(final int pControlPointCount, final float pTension) {
if(pControlPointCount < CardinalSplineMoveModifierConfig.CONTROLPOINT_COUNT_MINIMUM) {
throw new IllegalArgumentException("A " + CardinalSplineMoveModifierConfig.class.getSimpleName() + " needs at least " + CardinalSplineMoveModifierConfig.CONTROLPOINT_COUNT_MINIMUM + " control points.");
}
this.mTension = pTension;
this.mControlPointXs = new float[pControlPointCount];
this.mControlPointYs = new float[pControlPointCount];
}
public CardinalSplineMoveModifierConfig deepCopy() {
final int controlPointCount = this.getControlPointCount();
final CardinalSplineMoveModifierConfig copy = new CardinalSplineMoveModifierConfig(controlPointCount, this.mTension);
System.arraycopy(this.mControlPointXs, 0, copy.mControlPointXs, 0, controlPointCount);
System.arraycopy(this.mControlPointYs, 0, copy.mControlPointYs, 0, controlPointCount);
return copy;
}
public CardinalSplineMoveModifierConfig deepCopyReverse() {
final CardinalSplineMoveModifierConfig copy = this.deepCopy();
ArrayUtils.reverse(copy.mControlPointXs);
ArrayUtils.reverse(copy.mControlPointYs);
return copy;
}
// ===========================================================
// Getter & Setter
// ===========================================================
public int getControlPointCount() {
return this.mControlPointXs.length;
}
public void setControlPoint(final int pIndex, final float pX, final float pY) {
this.mControlPointXs[pIndex] = pX;
this.mControlPointYs[pIndex] = pY;
}
public float getControlPointX(final int pIndex) {
return this.mControlPointXs[pIndex];
}
public float getControlPointY(final int pIndex) {
return this.mControlPointYs[pIndex];
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
// ===========================================================
// Methods
// ===========================================================
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
}
}