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

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

Autonomous Agents - your own desired velocity|Nature of Code

前回の「ターゲット」は恒久的に動かない的でした。ただし自然界における「環境」は様々であると同様に「ターゲット」も必ずしも動かないとは限りません。ここでは”動く”ターゲットと”環境そのものがターゲット”の例を考察してみます。結論から言うと今回の考察の焦点はdesired velocityの算出方法のコンセプトを自分で考える事になります。自分もそうなんのですが本などで勉強していると、その他の手段が想像出来なかったりで応用力が身に付かない。こういった観点でも、この課題はイイと思います。最初に前回の復習をコードで示しておきますので、参考にして思い出してください。

desired velocity = target - location;
desired velocity.normalize();
desired velocity *= maxSpeed;

ofVec3f steer = desired velocity - currnt velocity;
steer.limit(maxForce);

■ 動くターゲット

例として、ある半径rの円上を常に同じ角速度で動いているものをターゲットとしてみる。この場合は、これまでの「ターゲットを探す」行程は変わりません。唯一の違いはターゲットのみが動いている事です。なので、Vehicleクラスはなんら変化なし。変わるのはtargetオブジェクトを動かす必要があるので僕はtargetをクラス化しました。Targetクラスのみ参考にコードを掲載しますね。メインプログラムとしては今までと変わらずVehicleオブジェクトはメソッドseek(ofVec3f target)にターゲットの参照を渡してあげれば動くターゲットを追尾していきます。

class Target
{
public:
    Target(){}
    virtual ~Target(){}

    //円の中心
    ofVec3f origin;

    //ターゲット位置
    ofVec3f location;

    //ターゲット角速度
    ofVec3f acceleration;

    //ターゲットvelocity
    ofVec3f velocity;

    //スタート位置角度
    float deg;

    //円の半径
    float radius;

    void setup()
    {
        deg = 10.0;
        radius = 200;

        //円中心
        origin.set(ofGetWidth()*0.5, ofGetHeight()*0.5, 0.0);

        //ターゲットの位置|角度のみ
        location.set(radius*cos(deg*DEG_TO_RAD), radius*sin(deg*DEG_TO_RAD), 0);

        //ターゲットの位置|起点を足す
        location += origin;

        //角加速度はaccelerationのxとしている
        acceleration.set(0.3, 0, 0);

        //角速度
        velocity.set(0.0, 0.0, 0.0);
    }

    void update()
    {
        //角速度を一定にするためにacceleraionをリセットしない。
        deg += acceleration.x;
    }

    void draw()
    {
        location.set(radius*cos(deg*DEG_TO_RAD), radius*sin(deg*DEG_TO_RAD), 0.0);
        location += origin;

        ofSetColor(120);
        ofNoFill();
        ofCircle(location.x, location.y, 10);
    }
};

■ 環境をターゲット

次に今回の記事のメインになる”決められた環境内でしか動けない”という環境を考えてみます。具体的にはウィンドウ内に描画した四角形内で生きていられる(動く事が可能)環境をあげます。この場合、ターゲットは、どのように指定すれば良いでしょうか? 今までの例では「動かない位置」「動いている任意の位置」というように、必ず【位置】をパラメータとして持っているオブジェクトをターゲットとしていました。ターゲットによって生成されるVehecleオブジェクトのdesired velocityも、ターゲットの位置から生成する事が出来ました。しかし今回は”ある枠内”という条件になるために、これまでと同様に考えると、その枠内の全ての位置がターゲットとなってしまう。全ての考えられる位置をターゲットとして推測し、その中から一番近い位置をターゲットとする?等と考える人も居るかもしれないけれど、”ターゲット”の観点〜コンセプト〜が、そもそも違います。それと全ての枠内の位置をターゲットとしてしまうと、フレームレートも落ちてしまうし、何やら大変な事になってしまう。どうも「特定位置」をターゲットとして考える事をやめて別の視点でターゲットを設定しdesired velocityを求める必要がありそうですね。
まず”Vehicleクラス内でdesired velocityを求める”という点においては今までと同じです。desired velocityはsteerを求めるために必要なものですしVehicle外から与えられるものでもないのは、(おそらく)明白です。ではターゲットは何になりえるか?というと、今分かる情報から導くしかありません。枠内という条件。”環境条件”からdesired velocityを都度計算するようにするという事になります。そして、その生成の条件は環境によって自分で決める必要がありますが、今回の場合は次のような条件としました。

  1. Vehicleのスタート位置は四角形内からスタートする
  2. 枠はwindowサイズの幅と高さから、それぞれ25pixelずつ小さい領域とする
  3. Vehicle位置が上下左右に近づいたら枠外に行かないように跳ね返る

1.、2.は環境条件。3.がVehicleの動きを決めている条件になります。desired velocityを決めるのは3)の部分。枠の上下左右に到達した時に「跳ね返る」ようにするには、逆に言うと上下左右枠に到達していない時は現在のvelocityで移動していれば良い事になります。なので毎フレーム毎に現在の位置が上下左右に到達した時にVehicleの向きを変えるようにします。その為には最終的に算出されるsteer vector(次フレーム時のvelocityを決めるForceベクトル)を例えばVehicleが上にたどり着いたら、上下方向のvelocity成分が下向きになるようにdesired velocityを決めればいいですし、右端にたどり着いたら左右方向のvelocityが左方向になるようにdesired velocityを決めてあげれば良い事になります。

先に書いておきますが最終的な見栄えとしては、この類いの本やブログに掲載されている「画面内をオブジェクトが自由に動き回る」ものと何ら変わりはありません。ただし根本的に違う事は(何度も言いますが)操作・動かそうとしたいオブジェクトに命的なものを吹き込みたい場合に便利な考え方を考察する点にあります。それがReynoldsが言っているdesired velocity等の前回記事にも書かせて頂いている3つの基本コンセプトで、この記事でまとめようとしている事です。3)の部分をコードにしてみす。

fload d = 25.0;

ofVec3f desired = null;

/*
 *  左右方向
 */
//左端に来たら…
if (location.x < d)
{
  desired = new ofVec3f(maxSpeed, velocity.y);
}
//右端に来たら…
else if (location.x > ofGetWidth() -d)
{
  desired = new ofVec3f(-maxSpeed, velocity.y);
}

/*
 *  上下方向
 */
//上端に来たら
if (location.y < d)
{
  desired = new ofVec3f(velocity.x, maxSpeed);
}
//下端に来たら
else if (location.y > ofGetHeight()-d)
{
  desired = new ofVec3f(velocity.x, -maxSpeed);
}

if (desired != null)
{
  desired.normalize();
  desired.mult(maxSpeed);
  ofVec3f steer = ofVec3f.sub(desired, velocity);
  steer.limit(maxforce);
  applyForce(steer);
}

上述したように上下左右にキタ時にのみdesired velocityを作成し最終的にsteerをVehicleに反映しています。その為、上下左右の位置でない場合は、Vehicleのvelocityは変化しません。またコードを見てもわかるように上下端にキタ時のdesired velocityの左右成分は変化させていません。ここでは特に摩擦力は考慮していませんし、それ以外の環境条件もないという事にしています。

もう1つ!注意点というわけではないですが、VehicleのパラメータであるmaxForce値をあまりにも小さくしてしまうと、端に到達した時のsteer値が小さくなってしまうので、画面外に行ってしまいます。が、ちゃんと画面外で方向転換はしているので、じっくり待っていると、ちゃんと戻ってきますが、今回設定した環境条件の「枠外に出てはいけない」に反します。

Vehicle Example Within a Box from Kazuyoshi Ueno on Vimeo.