Break Down

Software Design, Creative Coding, MotionGraphics, 3DCG

Processingでキーフレームを実装する #002 イージング

f:id:shigoze:20200210180947g:plain

どういった内容?

前回、キーフレームを実装し、オブジェクトにアニメーションをつけることができました。今回はそれに加えてイージングも選択できるようにしていきます。

前回の続きのためこちらのブログも併せて参照してください。 shigoze.hatenablog.com


Processingの言語・特徴

「Processing」はjavaベースのプログラミングで静画表現、動画表現ができるフリーの統合開発環境(エディタ、コンパイラ、デバッガなどが一緒になったもの)です。豊富なアドオンが用意されており、OpenCVの導入、Arduinoとの連携し、Generative、Interactiveな表現ができます。

よく比較されるopenFrameworksとの違いは

  • 導入コストが低い
  • MacでもWindowsでも容易に動かせる
  • ただ処理は重たい

です。 具体的にopenFrameworksでやりたいことがない限りまずはProcessingから始めるのがおすすめです。


イージングとは

イージングとは、Adobe AfterEffectsにある、アニメーションの速度感を調整することで、現実の動きに近づけることができる機能です。前回はキーフレームとキーフレームの間のアニメーションは等速で変化するようになっていましたが、イージングを使用することでより自然に見えるようになります。 AfterEffectsではかなり細かい調整をすることができますが、今回は「Easing functions」というイージングのプリセットを使用していきます。

https://easings.net/

この「Easing functions」はもともとJava Scriptで書かれたものですが、Robert Penner氏がProcessingでも使えるようにしていただいています。 下記のURLよりダウンロードしてください。

https://gist.github.com/cocopon/1ec025bcffb3fd7995db/

また、こちらのブログも参考にさせていただきました。

https://cocopon.me/blog/2014/12/easing-pde/


設計

設計は前回より変更しておらず、内容はKeyPointクラスにイージングの指定データの追加、KeyFrameクラスではKeyPointクラスのイージングの指定データをもとにアニメーションを計算するように変更しました。


完成ソースコード

KeyFrameクラス、KeyPointクラス

// キーフレームを計算するクラス
class KeyFrame {
  // property
  private ArrayList<KeyPoint> keyPoints = new ArrayList<KeyPoint>();
  private int keyNum = 0;
  private boolean lastFlag = false;
  
  // method    
  public void addKey(float t, float k ,String e){
    
    KeyPoint tempKeyPoints = new KeyPoint();
    tempKeyPoints.timePoint = t;
    tempKeyPoints.keyPoint = k;
    tempKeyPoints.easing = e;
        
    keyPoints.add(tempKeyPoints);
  }
  
  public float updateKey(float t){
    float ret = 0;
    float t1 = keyPoints.get(keyNum).timePoint;
    float t2 = keyPoints.get(keyNum + 1).timePoint;
    float p1 = keyPoints.get(keyNum).keyPoint;
    float p2 = keyPoints.get(keyNum + 1).keyPoint;
    String e = keyPoints.get(keyNum).easing;
    
    // 最終キーに到達していない場合
    if (lastFlag == false){

      // 正規化した時のtの値を計算する
      float nt = (t - t1)/(t2 - t1);
      
      // 選択したイージングを適用する
      ret = calculateEase(e, nt);

      // 正規化を戻す
      ret = (ret * (p2 - p1)) + p1;

      // キーインクリメント判定
      if (t >= keyPoints.get(keyNum + 1).timePoint){
        keyNum++;
        
        // 最終キー判定
        if (keyNum >= (keyPoints.size() - 1)){
          lastFlag = true;
        }
      }
    } 
    
    // 最終キーに到達している場合
    else {
      ret = keyPoints.get(keyPoints.size() - 1).keyPoint;
    }
        
    return ret;
  }
  
  public void reset(){
    lastFlag = false;
    keyNum = 0;
  }
  
  private float calculateEase (String e, float nt) {
    float ret = nt;
    
    switch (e){
      case "easeInSine":
        ret = easeInSine(nt);
        break;
      case "easeOutSine":
        ret = easeOutSine(nt);  
        break;
      case "easeInOutSine":
        ret = easeInOutSine(nt);    
        break;
      case "easeInQuad":
        ret = easeInQuad(nt); 
        break;
      case "easeOutQuad":
        ret = easeOutQuad(nt);
        break;
      case "easeInOutQuad":
        ret = easeInOutQuad(nt);
        break;
      case "easeInCubic":
        ret = easeInCubic(nt);
        break;
      case "easeOutCubic":
        ret = easeOutCubic(nt);
        break;
      case "easeInOutCubic":
        ret = easeInOutCubic(nt);
        break;
      case "easeInQuart":
        ret = easeInQuart(nt);
        break;
      case "easeOutQuart":
        ret = easeOutQuart(nt);
        break;
      case "easeInOutQuart":
        ret = easeInOutQuart(nt);
        break;
      case "easeInQuint":
        ret = easeInQuint(nt);
        break;
      case "easeOutQuint":
        ret = easeOutQuint(nt);
        break;
      case "easeInOutQuint":
        ret = easeInOutQuint(nt);
        break;
      case "easeInExpo":
        ret = easeInExpo(nt);
        break;
      case "easeOutExpo":
        ret = easeOutExpo(nt);
        break;
      case "easeInOutExpo":
        ret = easeInOutExpo(nt);
        break;
      case "easeInCirc":
        ret = easeInCirc(nt);
        break;
      case "easeOutCirc":
        ret = easeOutCirc(nt);
        break;
      case "easeInOutCirc":
        ret = easeInOutCirc(nt);
        break;
      case "easeInBack":
        ret = easeInBack(nt);
        break;
      case "easeOutBack":
        ret = easeOutBack(nt);
        break;
      case "easeInOutBack":
        ret = easeInOutBack(nt);
        break;
      case "easeInElastic":
        ret = easeInElastic(nt);
        break;
      case "easeOutElastic":
        ret = easeOutElastic(nt);
        break;
      case "easeInOutElastic":
        ret = easeInOutElastic(nt);
        break;
      case "easeInBounce":
        ret = easeInBounce(nt);
        break;
      case "easeOutBounce":
        ret = easeOutBounce(nt);
        break;
      case "easeInOutBounce":
        ret = easeInOutBounce(nt);
        break;
      case "easeLinear":
        ret = nt;  
    }
    
    return ret;
  }
}

// キー情報を格納するデータクラス //
class KeyPoint{
  // プロパティ
  public float timePoint;
  public float keyPoint;
  public String easing;
}


Main

KeyFrame KF_y;
float t = 0;
float duration = 120;

void setup() {
  size(1280, 720);
  frameRate(60);
  
  // 位置xのキーフレーム
  KF_y = new KeyFrame();
  KF_y.addKey(new KeyPoint(0, 720/2+200, "easeOutExpo"));
  KF_y.addKey(new KeyPoint(60, 720/2-200, "easeInExpo"));
  KF_y.addKey(new KeyPoint(120, 720/2+200, ""));
}

void draw() {
  background(90, 177, 200);
  
  // キーフレーム現在値の取得
  float y = KF_y.updateKey(t);
  
  // 描画
  noStroke();
  fill(223,240,255,255);
  ellipse(1280/2, y ,100 ,100);
  t++;
  
  // デュレーションカウントで繰り返し
  if (t >= duration){
    KF_y.reset();
    t = 0;
  }
  
  // フレーム数を表示
  fill(255,255,255,255);
  text(frameCount, 10, 35);
}