openFrameworks C++11 で乱数を工夫する

#導入
###>概要
openFrameworks 0.9.~から`C++11`がサポートされました。
この`C++11`の機能を用いることで乱数の生成に工夫が出来ます、今回はその方法を紹介します。
みなさん、こんな経験ありませんか？パラメータを初期化する際、`ofRandom()`を使うと、のっぺり、すべて同じ確率で乱数が生成されてしまう、、ある箇所に集中させたいときどうすればいいんだろう？
そんな時、`C++11`の`std::normal_distribution`を用いれば簡単に解決できます。
ここで解説するサンプルプロジェクトはgithubで公開しています。
https://github.com/Hiroki11x/openFrameworksSample/tree/master/oF_NormalDistributionSample



###>正規分布に従う擬似乱数を生成
この画像も[wikipedia](https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E5%88%86%E5%B8%83)から取ってきたものですが、正規分布とは大雑把に言うと、正規分布はある平均値に集中して集まるような確率分布です。(詳しくは[wikipedia](https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E5%88%86%E5%B8%83)を)
![imgres.png](https://qiita-image-store.s3.amazonaws.com/0/56510/4ae9e71e-2f2f-a1e5-1921-640869203ee0.png)

この分布を使うことで、例えば中心に集中して円を以下のように配置したい場合、

![スクリーンショット 2016-05-12 3.58.56.png](https://qiita-image-store.s3.amazonaws.com/0/56510/0e82586a-78cf-85e6-4e0f-008e1f59dfe7.png)

このプログラムの実装方法について以下で説明します。


#実装(2次元)
###>ヘッダファイルの定義
1. 正規分布に従う擬似乱数を生成するために`C++11`から追加された`normal_distribution`を用いる必要があります。このクラスが定義されている`<random>`をincludeする必要があります。
2. 生成した擬似乱数を座標として保持するために`ofVec2f`型の`vector`をメンバ変数として宣言しておきます。

```ofApp.h
#pragma once

#include "ofMain.h"
#include <random>　// <- ここ

class ofApp : public ofBaseApp{
private:
    vector<ofVec2f> position_vector_2d; // <- ここ
public:
    void setup();
    void update();
    void draw();
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void mouseEntered(int x, int y);
    void mouseExited(int x, int y);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
};
```

###>実装ファイルについて

まず正規分布の擬似乱数生成に関して以下のStepで行っている
1. `engine`として~~`default_random_engine`~~`mt19937`を選択
2. `distribution`として`normal_distribution`を選択
3. デフォルトの`engine`を宣言
4. 釣鐘型の正規分布を平均値、標準偏差を与えて宣言
5. 擬似乱数生成器を作る
6. 生成器から生成した乱数を`vector`に`push_back`

ただし

|用語|説明|
|:-----------|:------------|
|エンジン(`engine`)|乱数または擬似乱数を生成する|
|分布(`distribution`)|生成した値を一定範囲の数学的分布へとマップする|

あとは、その座標をもとに描画を行っているだけである。
`bind()`に関しては最後に補足を書いておきました。
`normal_distribution`のリファレンスは[こちら](http://cpprefjp.github.io/reference/random/normal_distribution.html)を参考にしてください。

```ofApp.cpp
#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){

    //Normal Distribution
    using my_engine = mt19937; // <-1
    using my_distribution = normal_distribution<>; // <-2
    my_engine mEngine {}; // <-3
    my_distribution mDistribution {0.0,10.0};// <-4 (平均0.0、標準偏差10.0で分布させる)

    auto generate_random = bind(mDistribution,mEngine);// <-5
    for(int i = 0 ; i<10000 ; i++){ //<-以下6
        float  x = round(generate_random());
        float y = round(generate_random());
        position_vector_2d.push_back(ofVec2f(x,y));
    }

    ofBackground(0);
}

//--------------------------------------------------------------
void ofApp::update(){
}

//--------------------------------------------------------------
void ofApp::draw(){
    ofSetColor(0,200,255,20);
    ofPushMatrix();
    ofTranslate(ofGetWidth()/2,ofGetHeight()/2);
    for(auto v :position_vector_2d){
        ofDrawCircle(v.x*10 ,v.y*10 ,5);
    }
    ofPopMatrix();
}
```

#実装(3次元)
2次元のものを3次元に拡張しただけです。カメラの追加や視点の移動などを行っていますが、それ以外ほぼ変わりません。以下にソースコードと実行結果を載せておきます。

```ofApp.h
#pragma once

#include "ofMain.h"
#include <random>

class ofApp : public ofBaseApp{
private:
    vector<ofVec3f> position_vector_3d;
    ofEasyCam cam;
    float cam_dist;

public:
    void setup();
    void update();
    void draw();

    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void mouseEntered(int x, int y);
    void mouseExited(int x, int y);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);

};
```

```ofApp.cpp
#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){

    //Normal Distribution
    using my_engine = mt19937;
    using my_distribution = normal_distribution<>;
    my_engine mEngine {};
    my_distribution mDistribution {0.0,10.0};// 平均0.0、標準偏差10.0で分布させる

    auto generate_random = bind(mDistribution,mEngine);
    for(int i = 0 ; i<10000 ; i++){
        float x = round(generate_random());
        float y = round(generate_random());
        float z = round(generate_random());
        position_vector_3d.push_back(ofVec3f(x,y,z));
    }

    ofBackground(0);
    cam_dist = 600;
}

//--------------------------------------------------------------
void ofApp::update(){
    cam.setPosition(0, 0, cam_dist);
}

//--------------------------------------------------------------
void ofApp::draw(){
    ofSetColor(0,200,255,20);
    ofPushMatrix();
    ofTranslate(ofGetWidth()/2,ofGetHeight()/2);

    cam.begin();

    for(auto v :position_vector_3d){
        ofDrawBox(v.x*10 ,v.y*10 ,v.z*10 ,5);
    }

    cam.end();

    ofPopMatrix();
    ofDrawBitmapStringHighlight("cam_dist: "+ofToString(cam_dist), 20,20);
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    if(key == OF_KEY_UP){
        cam_dist += 10;
    }else if(key ==OF_KEY_DOWN){
        cam_dist-= 10;
    }
}
```

実行するとこのようになります、キー操作によってカメラの位置を変更できます。

![gif animation](https://github.com/Hiroki11x/openFrameworksSample/raw/master/oF_NormalDistributionSample/movie.gif)

#補足(`bind()`について)
標準ライブラリの`bind()`は例えば今回の場合、以下のようになっていました。

```cpp
auto generate_random = bind(mDistribution,mEngine);
```

この時、第二引数の`mEngine`を受け取り、第一引数の`mDistribution`を実行する関数オブジェクトを作成します。
つまり`generate_random()`の呼び出し自体は、`mDistribution(mEngine)`の呼び出しと等価です。

#訂正版
yumetodoさんからご指摘をいただきました`push_back`重そうなので、`std::reserve()`,`std::generate()`を使うのがいいのではないかという点、`std::bind()`を最適化できないコンパイラ(VS)もあるということで、三次元の実装に関して修正を加えたいと思います。ヘッダファイルは変わらず、実装ファイル(`ofApp.cpp::setup()`)について以下に載せておきます。


###1. `std::generate()`を用いる場合

```cpp
//--------------------------------------------------------------
void ofApp::setup(){
//Normal Distribution /std::generate:32868msec
    using my_engine = mt19937;
    using my_distribution = normal_distribution<>;
    my_engine mEngine {};
    my_distribution mDistribution {0.0,10.0};// 平均0.0、標準偏差10.0で分布させる

    const auto t1 = std::chrono::high_resolution_clock::now();
    const auto size = position_vector_3d.size();
    position_vector_3d.resize(size + 100000);
    std::generate(position_vector_3d.begin()+ size, position_vector_3d.end(),[&mDistribution, &mEngine](){ return ofVec3f(mDistribution(mEngine),mDistribution(mEngine),mDistribution(mEngine));});
    const auto t2 = std::chrono::high_resolution_clock::now();
    std::cout << std::chrono::duration_cast<std::chrono::microseconds>(t2-t1).count() << "msec" << std::endl;
    ofBackground(0);
    cam_dist = 600;
}
```

###2. `std::reserve()`を用いる場合

```cpp
//--------------------------------------------------------------
void ofApp::setup(){
//Normal Distribution /std::reserve: 30977msec /without reserve -> 32646msec
    using my_engine = mt19937;
    using my_distribution = normal_distribution<>;
    my_engine mEngine {};
    my_distribution mDistribution {0.0,10.0};// 平均0.0、標準偏差10.0で分布させる

    const auto t1 = std::chrono::high_resolution_clock::now();
    position_vector_3d.reserve(100000);
    for(int i = 0 ; i<100000 ; i++){//338663microsec/344389microsec
        float  x = round(mDistribution(mEngine));
        float y = round(mDistribution(mEngine));
        float z = round(mDistribution(mEngine));
        position_vector_3d.push_back(ofVec3f(x,y,z));
    }

    const auto t2 = std::chrono::high_resolution_clock::now();
    std::cout << std::chrono::duration_cast<std::chrono::microseconds>(t2-t1).count() << "msec" << std::endl;
    ofBackground(0);
    cam_dist = 600;
}
```

また、エンジン型に関して`default_random_engine`より、`mt19937`を採用した方がいいみたいです。

###3. エンジン型について
> 擬似乱数エンジンは、基本的には32ビット版メルセンヌ・ツイスターの実装である`mt19937`、もしくはその64ビット版の実装である`mt19937_64`のどちらかを使用することを推奨する。
非専門用途のための`default_random_engine`というエンジン型も定義されているが、この型は環境によって乱雑度が低く、周期も短い擬似乱数エンジンの別名として定義される場合がある。
`mt19937`は、状態の大きさが`sizeof(std::uint_fast32_t) * (624 + 1)`だけあり、少々サイズが大きいが、それを受け入れられる状況であれば、デフォルトで`mt19937`を採用しよう。このエンジンでは、乱雑度や周期の長さが問題になることは少ない。
[cpprefjp \<random\>ヘッダ](http://cpprefjp.github.io/reference/random.html)からの引用です。