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)」の法則だね。
そして、enumも無くすことができた。それに識別子が文字列だと、外部パラメーターで動作を決める実装が楽になる。達人プログラマに出てきた「DRY(Don't Repeat Youself)」の法則だね。
でもこれ、テンプレートのエイリアスを使っているのでVisualStudioは2013じゃないとビルドできない… さらばVisualStudio2012!!
関数ポインタの列挙、みなさんならどう実装しますか??
ではまた次回!! …ではなくて、次は中3女子さんにバトンタッチ!!(これは去年と同じ流れだw)