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

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

Gravitational Attraction |Nature of Code


■Gravitational Attraction

※この章が何を伝えているかというと、結局のところ引力に変わる力が大きく働いている空間でのオブジェクトの動きをどう再現するか?だと思う。

質量を持つ全てのオブジェクトは互いに引力を与えあう。地球上に落ちるリンゴを例にすると、地球はリンゴを引っぱるが、リンゴも同様に地球を引っ張っている。

  • 質量m1, m2
  • オブジェクト間の距離r とるすと、F = Gm1m2*r^ / r2;となる。

※F|Gravitational Force。ベクターであり、applyForce()に渡す値に相当する
※G|universal gravitational constant。定数。プログラミングでは無視
※m1, m2|オブジェクトの質量。省く事も可能だが、大きさを用いる方がより面白い表現を与えられる。
※r^|オブジェクト1からオブジェクト2への方向ベクトル。Normalizeされているべき。 ※r2|オブジェクト間の距離

Force FはG,m1,m2に比例し、距離に反比例する。距離が離れればForceは弱まる。


ここでFを求める。

  1. オブジェクト1から2への方向ベクトルを求める。
  2. 次にFをm1,m2,距離から計算する。
ofVec3f dir, dirVec;
float distance;
ofVec3f F;

dirVec = location2 - location1;
dir = (location2 - location1).normalize();
distance = dirVec.length();
float m = G * m1 * m2 / distance * distance;

F = dir * m;

■Attractorオブジェクトの引力がMoverオブジェクトを引きつける

Attractorオブジェクトは位置、大きさを持った簡単なオブジェクトと定義します。 このAttractorに向かってMoverオブジェクトが引きつけられるようにするには、幾つかの方法がある。

  1. Attractor、Moverの両方のオブジェクトを受け取るメソッドを作る
  2. AttractorクラスにMoverオブジェクトを渡すメソッドを作る
  3. MoverクラスにAttractorオブジェクトを渡すメソッドを作る
  4. AttractorクラスにMoverオブジェクトを渡すメソッドを作り、ofVec3fのAttractionForceを返すメソッドを用意する。次に、 このAttractionForceをMoverオブジェクトのapplyForceに渡す。 等々。。

オブジェクト指向的には、両クラスが密な結びつきをもつような設計にはしない方が良いと思うのと、今までの記事でMoverクラスにapplyForceメソッドを使用していて、このメソッドが汎用的に作られている点から4)をチョイスしてみる。

ofVec3f force = a.attract(m);
m.applyForce(force);

次にAttractorクラスにattractメソッドを実装するわけだが、既に返却するForceは上記で論じたものになるので以下のようになる。 ここで注意している点として、考慮すべきは、mmの計算時にdistancedistanceでdivideしている箇所。まず0ではdevideできない。それともう一つ。このdistancedistance値が0に限りなく小さい場合、mmは凄く大きい数値になってしまうので、適当に抑制する必要がある。そのためofClampを使用しています。

ofVec3f attract( Mover m )
{
  ofVec3f dir, dirVec;
  float distance;
  ofVec3f F;

  dirVec = location - m.location;
  dir = dirVec.normalize();
  distance = dirVec.length();
  distance = ofClamp(5, 25);
  float mm = 0.4 * mass * m.mass / distance * distance;
  // G = 0.4

  F = dir * mm;
  return F;
}

ここまででMoverオブジェクト数を10個にし、Attractorの位置をマウス位置に反映したコードを掲載します。
※Mover::draw()内で、これまでcheckEdge()で画面の上下左右をチェックしていたが、外しています。

#include "testApp.h"

#define NUM_MOVERS 10


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

    float mass;
    ofVec3f acceleration;
    ofVec3f velocity;
    ofVec3f location;
    
    void applyForces(ofVec3f force)
    {
        ofVec3f f = force / mass;
        acceleration += f;
    }
    
    void checkEdige()
    {
        
        if (location.x >= ofGetWidth())
        {
            velocity.x *= -1;
            location.x = ofGetWidth();
        }

        if (location.x <= 0.0)
        {
            velocity.x *= -1;
            location.x = 0.0;
        }
        
        if (location.y <= 0)
        {
            velocity.y *= -1;
            location.y = 0.0;
        }

        if (location.y >= ofGetHeight())
        {
            velocity.y *= -1;
            location.y = ofGetHeight();
        }
    }
    
    void setup()
    {
        mass = ofRandom(2,30);
        velocity.set(0.0, 0.0, 0.0);
        location.set(ofRandom(0, ofGetWidth() - mass), 0.0, 0.0);
        acceleration.set(0.0, 0.0, 0.0);
    }
    
    void update()
    {
        velocity += acceleration;
        location += velocity;
        
        acceleration.set(0.0, 0.0, 0.0);
    }
    
    void draw()
    {
        ofSetColor(255);
        ofPushMatrix();
        ofTranslate(location);
        ofCircle(0.0, 0.0, 0.0, mass);
        ofPopMatrix();

//        checkEdige();
    }
};


class Attractor
{
public:
    Attractor(){
        location.set(512, 384, 0);
        mass = 20.0;
        G = 0.4;
    }
    virtual ~Attractor(){}
    
    
    float mass;
    ofVec3f location;
    float G;
    
    
    ofVec3f attract( Mover *m )
    {
        ofVec3f dir, dirVec;
        float distance;
        ofVec3f F;
        
        dirVec = location - m->location;
        dir = dirVec.normalize();
        distance = dirVec.length();
        distance = ofClamp(distance, 5, 25.0);
        
        float mm = G * mass * m->mass / distance * distance;
        
        F = dir * mm;
        
        return F;
    }
    
    
    void draw()
    {
        ofSetColor(100, 100, 100);
        ofRect(location.x, location.y, mass*2, mass*2);
    }
    
};


vector<Mover> movers;
Attractor a;



//--------------------------------------------------------------
void testApp::setup(){
    
    ofSetFrameRate(60);
    ofEnableSmoothing();
    ofSetVerticalSync(true);
    ofBackground(0);
    
    
    movers.resize(NUM_MOVERS);
    for (int i = 0; i < NUM_MOVERS; i++)
    {
        Mover *m = &movers[i];
        m->setup();
    }
}

//--------------------------------------------------------------
void testApp::update(){

    for (int i = 0; i < NUM_MOVERS; i++) {
        
        Mover* m = &movers[i];
        
        ofVec3f force_attract = a.attract(m);
        
        m->applyForces(force_attract);
        
        m->update();
    }
}

//--------------------------------------------------------------
void testApp::draw(){

    a.draw();
    
    for (int i = 0; i < NUM_MOVERS; i++) {
        Mover* m = &movers[i];
        m->draw();
    }
}

//--------------------------------------------------------------
void testApp::mouseMoved(int x, int y){
    
    a.location.x = x;
    a.location.y = y;
}