package dan.dit.whatsthat.riddle.control;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.NonNull;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import dan.dit.whatsthat.util.general.MathFunction;
/**
* Created by daniel on 05.11.15.
*/
public class RiddleCanvasAnimation extends RiddleAnimation {
private List<CanvasAnimation> mAnimations = new ArrayList<>();
private RiddleAnimation mWrapped;
protected RiddleCanvasAnimation() {
super(RiddleAnimationController.LEVEL_GROUNDING);
}
protected RiddleCanvasAnimation(@NonNull RiddleAnimation animation) {
super(RiddleAnimationController.LEVEL_ON_TOP);
mWrapped = animation;
}
@Override
boolean isAlive() {
return mAnimations.size() > 0 && (mWrapped == null || mWrapped
.isAlive());
}
@Override
public void onMurdered() {
for (CanvasAnimation anim : mAnimations) {
anim.onMurdered();
}
}
protected void addAnimation(CanvasAnimation animation) {
mAnimations.add(animation);
}
@Override
protected void update(long updatePeriod) {
for (int i = 0; i < mAnimations.size(); i++) {
CanvasAnimation curr = mAnimations.get(i);
curr.update(updatePeriod);
if (!curr.isAlive()) {
mAnimations.remove(i);
i--;
}
}
if (mWrapped != null) {
mWrapped.update(updatePeriod);
}
}
@Override
public void draw(Canvas canvas, Paint paint) {
boolean saveAndRestore = mWrapped != null;
if (saveAndRestore) {
canvas.save();
}
for (int i = 0; i < mAnimations.size(); i++) {
CanvasAnimation curr = mAnimations.get(i);
curr.apply(canvas);
}
if (mWrapped != null) {
mWrapped.draw(canvas, paint);
}
if (saveAndRestore) {
canvas.restore();
}
}
@Override
public void onBorn() {
super.onBorn();
Log.d("Riddle", "Riddle view animation is born!");
}
@Override
public void onKilled(boolean murdered) {
super.onKilled(murdered);
Log.d("Riddle", "Riddle view animation is killed: " + murdered);
}
public static class Builder {
private RiddleCanvasAnimation mAnim;
private MathFunction mInterpolator;
private int mLives = 1;
private int mNextLifeMode = CanvasAnimation.DEFAULT_NEXT_LIFE_MODE;
public Builder() {
mAnim = new RiddleCanvasAnimation();
}
public Builder(@NonNull RiddleAnimation toWrap) {
mAnim = new RiddleCanvasAnimation(toWrap);
}
public Builder setInterpolator(MathFunction interpolator) {
mInterpolator = interpolator;
return this;
}
public Builder setLives(int lives) {
mLives = lives;
return this;
}
public Builder setNextLifeMode(int nextLifeMode) {
mNextLifeMode = nextLifeMode;
return this;
}
public Builder addRotate(float rotateDeltaDegrees, float rotateCenterX, float
rotateCenterY) {
mAnim.addAnimation(new Rotate(rotateDeltaDegrees, rotateCenterX, rotateCenterY,
0L).setLives(CanvasAnimation.LIVES_LIFE_FOREVER));
return this;
}
public Builder addRotate(float rotateDeltaDegrees, float rotateCenterX, float
rotateCenterY, long duration) {
mAnim.addAnimation(new Rotate(rotateDeltaDegrees, rotateCenterX, rotateCenterY,
duration).setInterpolator(mInterpolator).setLives(mLives).setNextLifeMode(mNextLifeMode));
return this;
}
public Builder addTranslate(float translateDeltaX, float translateDeltaY) {
mAnim.addAnimation(new Translate(translateDeltaX, translateDeltaY,
0L).setLives(CanvasAnimation.LIVES_LIFE_FOREVER));
return this;
}
public Builder addTranslate(float initialDeltaX, float initialDeltaY,
float translateDeltaX, float translateDeltaY, long duration) {
mAnim.addAnimation(new Translate(initialDeltaX, initialDeltaY,
translateDeltaX, translateDeltaY, duration)
.setInterpolator(mInterpolator).setLives(mLives).setNextLifeMode(mNextLifeMode));
return this;
}
public Builder addTranslate(float translateDeltaX, float translateDeltaY, long duration) {
mAnim.addAnimation(new Translate(translateDeltaX, translateDeltaY,
duration).setInterpolator(mInterpolator).setLives(mLives).setNextLifeMode(mNextLifeMode));
return this;
}
public Builder addScale(float scaleDeltaX, float scaleDeltaY,
float scaleCenterX, float scaleCenterY, long duration) {
mAnim.addAnimation(new Scale(scaleDeltaX, scaleDeltaY, scaleCenterX, scaleCenterY,
duration).setInterpolator(mInterpolator).setLives(mLives).setNextLifeMode(mNextLifeMode));
return this;
}
public RiddleCanvasAnimation build() {
return mAnim;
}
}
public static abstract class CanvasAnimation {
public static final MathFunction INTERPOLATOR_LINEAR = new MathFunction
.LinearInterpolation(0., 0., 1., 1.);
public static final MathFunction INTERPOLATOR_ACCELERATE = new MathFunction
.QuadraticInterpolation(0., 0., 1., 1.);
public static final MathFunction INTERPOLATOR_DECELERATE = new MathFunction
.QuadraticInterpolation(1., 1., 0., 0.);
/**
* If set to lives, the animation ill never die. Remember to remove animation at some time!
*/
public static final int LIVES_LIFE_FOREVER = -1;
/**
* Cycles from 0 to 1, then repeats. One cycle requires 1 life.
*/
public static final int NEXT_LIFE_MODE_REPEAT = 0;
/**
* Cycles from 0 to 1, 1 to 0, then repeats. One cycle requires 2 lives.
*/
public static final int NEXT_LIFE_MODE_REVERSE = 1;
/**
* Cycles from 0 to 1, 1 to 0, 0 to -1, -1 to 0, then repeats. One cycle requires 4 lives.
*/
public static final int NEXT_LIFE_MODE_REVERSE_INVERT = 2;
public static final int DEFAULT_NEXT_LIFE_MODE = NEXT_LIFE_MODE_REPEAT;
protected long mLifeTime;
private final long mTotalLifeTime;
private MathFunction mInterpolator; // evaluated from 0. to 1.
private int mLives; // dies if lives is zero
private int mNextLifeMode;
private int mCurrModeState; //handled by NextLifeMode
private boolean mLifeIsIncreasing;
protected CanvasAnimation(long totalLifeTime) {
mTotalLifeTime = Math.max(totalLifeTime, 0L);
mLifeTime = 0L;
mLifeIsIncreasing = true;
setLives(1);
setNextLifeMode(DEFAULT_NEXT_LIFE_MODE);
}
public CanvasAnimation setInterpolator(MathFunction interpolator) {
mInterpolator = interpolator;
return this;
}
public CanvasAnimation setLives(int lives) {
mLives = lives == 0 ? 1 : lives;
return this;
}
public CanvasAnimation setNextLifeMode(int mode) {
mNextLifeMode = mode;
mCurrModeState = 0;
return this;
}
protected double getInterpolatedLife(long lifeTime) {
if (mTotalLifeTime == 0L) {
return 1.;
}
final double lifeFraction = Math.abs(lifeTime) / (double) mTotalLifeTime;
if (mInterpolator == null) {
// default linear
return Math.signum(lifeTime) * lifeFraction;
}
return Math.signum(lifeTime) * mInterpolator.evaluate(lifeFraction);
}
protected abstract void apply(Canvas canvas);
public boolean isAlive() {
return mLives != 0;
}
public void onMurdered() {
mLives = 0;
mLifeTime = mLifeIsIncreasing ? Long.MAX_VALUE : Long.MIN_VALUE;
}
public void update(long updatePeriod) {
boolean die;
if (mLifeIsIncreasing) {
mLifeTime += updatePeriod;
switch (mNextLifeMode) {
case NEXT_LIFE_MODE_REVERSE_INVERT:
die = mLifeTime >= mTotalLifeTime
|| (mLifeTime >= 0L && mCurrModeState == 3);
break;
default:
die = mLifeTime >= mTotalLifeTime;
break;
}
} else {
mLifeTime -= updatePeriod;
switch (mNextLifeMode) {
case NEXT_LIFE_MODE_REVERSE_INVERT:
die = mLifeTime <= -mTotalLifeTime
|| (mLifeTime <= 0L && mCurrModeState == 1);
break;
default:
die = mLifeTime <= 0L;
break;
}
}
if (die) {
if (mLives > 0) {
mLives--;
}
switch (mNextLifeMode) {
case NEXT_LIFE_MODE_REVERSE_INVERT:
mCurrModeState++;
mCurrModeState %= 4;
mLifeIsIncreasing = mCurrModeState == 0 || mCurrModeState == 3;
break;
case NEXT_LIFE_MODE_REVERSE:
mCurrModeState++;
mCurrModeState %= 2;
mLifeIsIncreasing = mCurrModeState == 0;
break;
case NEXT_LIFE_MODE_REPEAT:
mLifeTime = 0L;
break;
default:
//do nothing
break;
}
}
}
}
private static class Rotate extends CanvasAnimation {
private float mCenterX;
private float mCenterY;
private float mRotateDeltaDegrees;
protected Rotate(float rotateDeltaDegrees, float centerX, float centerY, long
totalLifeTime) {
super(totalLifeTime);
mRotateDeltaDegrees = rotateDeltaDegrees;
mCenterX = centerX;
mCenterY = centerY;
}
@Override
protected void apply(Canvas canvas) {
float rotate = (float) (getInterpolatedLife(mLifeTime) *
mRotateDeltaDegrees);
canvas.rotate(rotate, mCenterX * canvas.getWidth(), mCenterY * canvas.getHeight());
}
}
private static class Translate extends CanvasAnimation {
private float mInitialDeltaX;
private float mInitialDeltaY;
private float mDeltaX;
private float mDeltaY;
protected Translate(float deltaX, float deltaY, long
totalLifeTime) {
this(0, 0, deltaX, deltaY, totalLifeTime);
}
protected Translate(float initialDeltaX, float initialDeltaY,
float deltaX, float deltaY, long totalLifeTime) {
super(totalLifeTime);
mInitialDeltaX = initialDeltaX;
mInitialDeltaY = initialDeltaY;
mDeltaX = deltaX;
mDeltaY = deltaY;
}
@Override
protected void apply(Canvas canvas) {
float interpolatedLife = (float) getInterpolatedLife(mLifeTime);
float dx = interpolatedLife * mDeltaX;
float dy = interpolatedLife * mDeltaY;
canvas.translate(mInitialDeltaX + dx, mInitialDeltaY + dy);
}
}
private static class Scale extends CanvasAnimation {
private float mDeltaX;
private float mDeltaY;
private float mCenterX;
private float mCenterY;
protected Scale(float deltaX, float deltaY, float centerX, float centerY, long
totalLifeTime) {
super(totalLifeTime);
mDeltaX = deltaX;
mDeltaY = deltaY;
mCenterX = centerX;
mCenterY = centerY;
}
@Override
protected void apply(Canvas canvas) {
float interpolatedLife = (float) getInterpolatedLife(mLifeTime);
float dx = interpolatedLife * mDeltaX;
float dy = interpolatedLife * mDeltaY;
canvas.scale(dx, dy, mCenterX * canvas.getWidth(), mCenterY * canvas.getHeight());
}
}
}