「がんばれない」けど「がんばりたい」

ITエンジニアの仕事のこと。AI、機械学習、ディープラーニング。地頭力。車のこと。

Autonomous Agents|Nature of Code

これまでは無機質な…言い換えれば「生きていない」ものに対してモデリングを行ってきましたが、もし「ある環境で生きている」ものをモデリングしたい場合は、どのようにコーディングすれば良いでしょうか。命・恐れ・夢や希望など。この記事からは「命」の存在するものをモデリングする事を考えてみようと思います。

まず基本となるコンセプトを確認しておきます。3つあります。

  1. An autonomous agent has a limited ability to perceive environment.
  2. An autonomous agent processes the information from its environment and calculates an action.
  3. An autonomous agent has no leader.

Vehicles and Steering

In his 1999 paper “Steering Behaviors for Autonomous Characters,” Reynolds uses the word “vehicle” to describe his autonomous agents, so we will follow suit.

という事で、Vehicleを最初のテーマとして考えてみる。ReynoldsはAutonomous Agentsを実現するにあたっては、シンプルに次の3つのレイヤーについてについて考えれば良いと言っています。

1. Action Selection
ゴールが存在し、それに向かって行動するagent。ただし任意瞬間での「状況や環境」は、変わって行くため、その瞬間の状況にから次の行動を選択する。

2. Steering
Actionが決まったら次の動きを計算する。これは今まで行ってきたForceにあたる。ここではSteeringForceと呼ぶことにします。Reynoldsは次のように定義しています。

steering force = desired velocity - current velocity.

3. Locomotion
ほとんどの事象で、この3つ目のレイヤーについては、無視する事になると書かれている。単純な移動のアニメーションになると書かれています。Vehicleでいうと、車輪をまわしたりするアニメーションを付け加える必要までは、このシミュレーションでは無いよね?という事のようです。

Steering Force

「Vehecleをゴールまでたどり着かせる」コードを考えてみる。これまでのapplyForceを単純に使用したコンセプトと違って「その瞬間の環境や状況」を下にVehecleに与えるforceを変化させ誘導させる。その為の式が上述した、

steering force = desired velocity - current velocity.

です。current velocityに対しdesired velocityに関しては未だ何もわかっていない値になるので、計算しないといけません。下図のようにVehecleの「目的」を「ゴールを探す」とした場合、desired velocityは”現在の位置からゴールの位置”へのベクトルとする事が出来る。

vehecle01

ofVec3f desired velocity = ofVec3f target - ofVec3f current;

だが、この式はマダマダrealisticではない。もしスクリーンが巨大な…例えば50万ピクセルx50万ピクセルだったとしたら、どのようにVehecleは動くだろう。Vehecleが出せるスピード(desired velocity)にリミッットがない場合を考えると、一瞬でtargetポジションにたどり着く。今回は、この様なアニメーションにはしたくはない。

The vehicle desires to move towards the target at maximum speed.

つまり「vehecleの持つdesired velocityはcurrent positionからtargetまでの方向に対して、vehecleの持つ最大速度を掛けたもの」になる。

ここまでをコードで表してみると、

class Vehecle
{
public:
  ofVec3f location;
  ofVec3f velocity;
  ofVec3f acceleration;
  float   maxSpeed;

  //seeking
  ofVec3f desired = target - location;
  desired.normalize();
  desired *= maxSpeed;
};
[/c]

上記コードのseeking|ゴールを探すの部分をseek()という名前のメソッドにして更にまとめてみると。

[c]
class Vehecle
{
public:
  ofVec3f location;
  ofVec3f velocity;
  ofVec3f acceleration;
  float   maxSpeed;

  void seek(ofVec3f target)
  {
    ofVec3f desired = target - location;
    desired.normalize();
    desired *= maxSpeed;

    //steering force = desired velocity - current velocity.
    ofVec3f steer = desired - velocity;

    applyForce(steer);
  }

};


ここで更に考察。「Vehecleは、どんな種類のもの?」なのか?。デッカいトラックなのか? 象なのかパンダなのか? いったい、どのくらいの力を持っているのか? これを考慮する事で、よりAutonomousなものをシミュレート出来るようになります。この「どのくらいの力を持っているのか?」をmaxforceとして定義し考慮したコードを書いてみます。

class Vehecle
{
public:
  ofVec3f location;
  ofVec3f velocity;
  ofVec3f acceleration;
  float   maxSpeed;
  float   maxForce;

  void seek(ofVec3f target)
  {
    ofVec3f desired = target - location;
    desired.normalize();
    desired *= maxSpeed;

    //steering force = desired velocity - current velocity.
    ofVec3f steer = desired - velocity;

    //limit steer
    steer.limit(maxForce);

    applyForce(steer);
  }
};

steer.limitが増えただけです。

複数のVehecleがGoalにたどり着くまで向かって行くコードです。これまでのapplyForceと動きは変わらない。ただコードコンセプトが違う。そこが重要です。

#include "testApp.h"

#define NUM_VHECLES 100


class Vehecle
{
public:
    
    Vehecle()
    {
    }
    
    Vehecle(int x, int y)
    {
        acceleration.set(0.0, 0.0, 0.0);
        velocity.set(0.0, 0.0, 0.0);
        location.set(x, y, 0);
        
        maxSpeed = ofRandom(1, 3);
        maxForce = maxSpeed;
        
        mass = 1.0;
        
        r = 3.0;
        
        mesh.setMode(OF_PRIMITIVE_LINE_LOOP);
        mesh.addVertex(ofVec3f(0, -r*2, 0));
        mesh.addColor(ofFloatColor(0,0,0));
        mesh.addVertex(ofVec3f(-r, r*2, 0));
        mesh.addColor(ofFloatColor(0,0,0));
        mesh.addVertex(ofVec3f(r, r*2, 0));
        mesh.addColor(ofFloatColor(0,0,0));
        
        isDead = false;
    }
    
    virtual ~Vehecle()
    {
    }
    
    ofVec3f location;
    ofVec3f velocity;
    ofVec3f acceleration;
    ofVec3f finalTarget;
    
    bool isDead;
    float maxSpeed;
    float maxForce;
    float mass;
    float r;
    
    ofMesh mesh;
    
    
    /*-----------------
     * applyForce
     -----------------*/
    void applyForce(ofVec3f force)
    {
        ofVec3f f = force / mass;
        acceleration += f;
    }
    
    /*-----------------
     * seek my own target
     -----------------*/
    void seek(ofVec3f target)
    {
        finalTarget = target;
        ofVec3f desired = target - location;
        desired.normalize();
        desired *= maxSpeed;
        
        ofVec3f steer = desired - velocity;
        steer.limit(maxForce);
        
        applyForce(steer);
    }

    /*-----------------
     * update
     * velocity is limited.
     -----------------*/
    void update()
    {
        velocity += acceleration;
        
        //ここでもlimitする
        velocity.limit(maxSpeed);
        
        location += velocity;
        acceleration.set(0.0, 0.0, 0.0);
        
        if ( fabs((location - finalTarget).length()) < 2.0 && !isDead )
        {
            isDead = true;
        }
    }
    
    
    /*-----------------
     * draw
     -----------------*/
    void draw()
    {
        float ang = atan2(velocity.y, velocity.x)+PI/2;
        float deg = RAD_TO_DEG * ang;
        
        ofPushMatrix();
        ofTranslate(location.x, location.y);
        ofRotate(deg);
        
        mesh.draw();
        
        ofPopMatrix();
    }
};


vector<Vehecle> vs;
vector<ofVec3f> ts;


//--------------------------------------------------------------
void testApp::setup(){
    
    ofSetFrameRate(60);
    ofEnableSmoothing();
    ofSetVerticalSync(true);
    ofBackground(255);

    vs.reserve(NUM_VHECLES);
    for (int i = 0; i < NUM_VHECLES; i++)
    {
        int x = ofRandom(0, ofGetWidth());
        int y = ofRandom(0, ofGetHeight());
        Vehecle* v = new Vehecle(x, y);
        vs.push_back(*v);
        
        int tx = ofRandom(0, ofGetWidth());
        int ty = ofRandom(0, ofGetHeight());
        ts.push_back(ofVec3f(tx,ty,0));
    }
}

//--------------------------------------------------------------
void testApp::update(){
    
    int tslen = ts.size();
    int index = 0;
    vector<Vehecle>::iterator it = vs.begin();
    for( it = vs.begin(); it != vs.end(); ++it )
    {
        bool isDead = it->isDead;
        if (!isDead)
        {
            it -> seek(ts[index]);
            it -> update();
        }
        index++;
    }
}

//--------------------------------------------------------------
void testApp::draw(){
    
    int tIndex = 0;
    vector<Vehecle>::iterator it = vs.begin();
    for( it = vs.begin(); it != vs.end(); ++it )
    {
        if ((it -> isDead) == false)
        {
            it -> draw();
            
            ofVec3f t = ts[tIndex];
            ofFill();
            ofSetColor(200);
            ofCircle(t.x, t.y, 5);
        }
        tIndex++;
    }
}