cpp_akiraさんのブログからバトンタッチ!!


今日もコード書いとる??

「愛知出身、東京在住」なので、ふと「プログラムにも方言があったら…」と思ってすぐ考えるのを止めた。Embe…げふんげふん!!

とはいいつつも、今年は拙iPhoneアプリのプログラムも何もかもC++11で書き始めた年だったので、ちょっとでも古いコンパイルだともうビルド不可なんだよねー。この状態はある意味方言に近いのかしらん(違)

さて。拙作『ういろう』をリリースした後、次回作の構想を練りつつ、既存のコードをもっと良くしようと試行錯誤中。その中でも、イージングの実装に関して悩みに悩んでおりまして、まーちょいC++11っぽくスマートに書けないかなぁ…と日々試行錯誤を繰り返しております。

今回のお題: 関数ポインタの実装をC++11らしく書き換えてみるのに挑戦!!

「イージングって??」という方はこちらを参照の事。二点間を滑らかに補間する手法で、「グイーン」とか「ビューン」とか口で表現しがちな動きを30種類ほどの関数で定義しています。

んで、ういろうではこんな感じで実装してた。

#include <iostream>

enum class EasingType {
  LINEAR,

  BACK_IN,
  BACK_OUT,
  BACK_INOUT
};


template <typename T>
class Easing {
  typedef T (*EaseFunc)(float t, const T& b, const T& c, const float d);
  EaseFunc func_;

public:
  explicit Easing(const EasingType type) {
    switch (type) {
    case EasingType::LINEAR:
      func_ = Linear;
      break;

    case EasingType::BACK_IN:
      func_ = BackIn;
      break;

    case EasingType::BACK_OUT:
      func_ = BackOut;
      break;

    case EasingType::BACK_INOUT:
      func_ = BackInOut;
      break;
    }
  }

  // 経過時間、持続時間、始点、終点から、現在点を求める
  T operator()(float time, const float duration, T start, T end) {
    return func_(time, start, end - start, duration);
  }


private:
  // 以下、イージング関数の定義
  // t = 経過時間
  // b = 始点
  // c = 終点 - 始点
  // d = 持続時間
  static T Linear(float t, const T& b, const T& c, const float d) {
    return c*t/d + b;
  }
  
  static T BackIn(float t, const T& b, const T& c, const float d) {
    float s = 1.70158f;
    t/=d;
    return c*t*t*((s+1)*t - s) + b;
  }

  static T BackOut(float t, const T& b, const T& c, const float d) {
    float s = 1.70158f;
    t=t/d-1;
    return c*(t*t*((s+1)*t + s) + 1) + b;
  }

  static T BackInOut(float t, const T& b, const T& c, const float d) {
    float s = 1.70158f * 1.525f;
    if ((t/=d/2) < 1) return c/2*(t*t*((s+1)*t - s)) + b;
    t-=2;
    return c/2*(t*t*((s+1)*t + s) + 2) + b;
  }

};


int main() {
  Easing<float> easing(EasingType::BACK_IN);

  float start    = 10.0f;
  float end      = 100.0f;
  float duration = 5.0f;

  std::cout
    << "Start:" << easing(0.0f, duration, start, end)
    << " Middle:" << easing(2.5f, duration, start, end)
    << " End:" << easing(5.0f, duration, start, end)
    << std::endl;
}
 
もともとCで書いてあったのをテンプレートに直して、関数ポインタに代入して…微妙!! そしてenum!! ずっと昔から関数ポインタをenumで列挙するのが嫌いで…。 enumで形式を決めるのって、外部パラメーターから決める場合に「値→enum」「文字列→enum」変換する必要も出てきてなんかスマートじゃない。


というわけで、色々試行錯誤の結果、こんな感じの実装になった。

#include <iostream>
#include <unordered_map>
#include <functional>


template <typename T>
using Ease = std::function<T(float, const float, const T&, const T&)>;

template <typename T>
Ease<T> createEase(const std::string& ease_name) {
  // C++11で導入されたコンテナの初期化リスト
  static const std::unordered_map<std::string, Ease<T> > func_table = {
    { "LINEAR",
      [](float t, const float d, const T& b, const T& c) {
        return c*t/d + b;
      }
    },

    { "BACK_IN",
      [](float t, const float d, const T& b, const T& c) {
        float s = 1.70158f;
        t/=d;
        return c*t*t*((s+1)*t - s) + b;
      }
    },
    { "BACK_OUT",
      [](float t, const float d, const T& b, const T& c) {
        float s = 1.70158f;
        t=t/d-1;
        return c*(t*t*((s+1)*t + s) + 1) + b;
      }
    },
    { "BACK_INOUT",
      [](float t, const float d, const T& b, const T& c) {
        float s = 1.70158f * 1.525f;
        if ((t/=d/2) < 1) return c/2*(t*t*((s+1)*t - s)) + b;
        t-=2;
        return c/2*(t*t*((s+1)*t + s) + 2) + b;
      }
    }
  };
  return func_table.at(ease_name);
}


int main() {
  auto easing = createEase<float>("BACK_IN");

  float start    = 10.0f;
  float end      = 100.0f;
  float duration = 5.0f;

  std::cout
    << "Start:" << easing(0.0f, duration, start, end - start)
    << " Middle:" << easing(2.5f, duration, start, end - start)
    << " End:" << easing(5.0f, duration, start, end - start)
    << std::endl;
}

おおお!! C++11で導入されたコンテナの初期化リストって、ラムダ式もオッケーなのか….。

そして、enumも無くすことができた。それに識別子が文字列だと、外部パラメーターで動作を決める実装が楽になる。達人プログラマに出てきた「DRY(Don't Repeat Youself)」の法則だね。
 
でもこれ、テンプレートのエイリアスを使っているのでVisualStudioは2013じゃないとビルドできない… さらばVisualStudio2012!!

関数ポインタの列挙、みなさんならどう実装しますか??


ではまた次回!! …ではなくて、次は中3女子さんにバトンタッチ!!(これは去年と同じ流れだw)