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

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

Force|Nature of Code.


■Force

ニュートンの質量保存の法則をプログラムに反映するにあたり、考えのまとめ。

A force is a vector that causes an object with mass to accelerate. 「力はベクトルであり、それは重量を持つオブジェクトに加速をもたらる」

An object at rest stays at rest and an object in motion stays in motion. 「動いているオブジェクトを動いたままにするには、何かしらのforceが必要(意訳)」

Forces always occur in pairs. The two forces are of equal strength, but in opposite directions. forceは常にペアで発生する。2つのforceは逆方向で同じ大きさ。(第3法則より)

If we calculate a PVector f that is a force of object A on object B, we must also apply the force—PVector.mult(f,-1);—that B exerts on object A.「forceをペアで考える」をプログラム的視点で考えるとこう。ただし必ずいつも逆方向のforceを考える必要はない。そして全ての自然現象をとらえる必要性も常にない。重要なのは、自然物理現象から単純にインスピレーションを持つという事を意識すること。


■Newton’s second low

Force equals mass times acceleration.

この第2法則が、おそらくプログラムするのに一番重要。F = MA なんで?かっていうと、この式は A = F / M記述出来る。ここから分かることは「加速度は力Fに比例し、大きさMに反比例する」 ちょっと具体的に書くと「めっちゃ押したら俺っち、すごく速く動くし、俺っち凄くデブだったら、すごく遅く動く」よ。

じゃぁMをプログラムで考えてみよう。まずM=1と考えると、A = F / 1;なので、A = F; こうなるとですよ、前回の記事でも書いたように、オブジェクトの動きに関してはAccelerationがキーになっていて〜velocityはaccelerationによって更新され、positionはvelocityによって決まる〜 M = 1の状態で考えるとA = Fなので、Fがオブジェクトの動きを統率していると言えます。例えばcode的にオブジェクトにFを反映するメソッドapplyForce(ofVec3f force)を作ると仮定したら、今のところ次のようになりますね。

void applyForce(ofVec3f force)
{
  acceleration = force; //Newton's Second Low as the simplest.
}

そーして!第2法則内では、 Net Force equals mass times acceleration. 正味の力(力の合計)は質量 x 加速度とイコールです。質量保存の法則(かな)。逆に?いうと「加速度は正味の力を質量で割ったものとイコール」

After all, as we saw in Newton’s first law, if all the forces add up to zero, an object experiences an equilibrium state (i.e. no acceleration). うん、ここもクリア。forcesが最終的に0になったらオブジェクトの状態は、そこで変化しない状態になる。ですね。で、上記のapplyForceメソッドは、質量保存の法則より、次のように書き変わります。

void applyForce(ofVec3f force)
{
  acceleration += force;
  // acceleration.add( force );
}

なんか、こういう手法をForce Accumulation というらしいです。update()で毎フレーム毎にForce Accumulationで行って行くにあたり、気をつけることは、オブジェクトの持っている加速度Accelerationを変更しているので、前回フレームのAccelerationをリセット(= 0)しておかないと、加速度がどんどん加算されていってしまうってことっす。

void update()
{
  acceleration = 0;
}

ここまでで、M = 1としてシンプルな質量保存の法則に基づいた事をざっくりと記述しました。次に質量Mを単純化しないで、もうちょっと見て行く事にしよう。


■Dealing with Mass

…質量保存の法則…から、上述しているように「加速度は正味の力を質量で割ったものとイコール」なので、再度applyForceメソッドを整理すると、

void applyForce(ofVec3f force)
{
  ofVec3f f = force;
  acceleration += ( f / m );
  // acceleration.add( f / m );
}

メソッド内部で ofVec3f f = force;としているのが何故か?というと、複数のオブジェクトが存在している場合に、外的forceを直接mで割ってしまうと、 2つ目のオブジェクトに対してapplyForceメソッドをコールする場合に渡すforceが1つ目のオブジェクトで既にmで割り算されてしまっているので、違う値を2つ目のオブジェクトのaccelerationに対して適応してしまう。具体的に言うと、風(1,-1,0.5)というforceが存在しているとすると、全てのオブジェクトに対して風(1,-1,0.5)のforceを反映すべきという概念ですね。


■Creating Forces

ここまでで分かった事は。。

  • forceはvectorである
  • forceは風や重力など外的要素全てを加算したもの
  • forceを質量mで割ったもの = 加速度(質量保存の法則)

では、実際にforceの初期化はどうするのか?みたいなところを掘り下げてみる。風forceと重力gravityが自然界のforceとして存在しているとし、オブジェクトmに対してForceを反映するには、こんなコード。

ofVec3f wind;
ofVec3f gravity;

wind.set(0.01, 0, 0);
gravity.set(0.0, 9.8, 0.0);
m.applyForce(wind);
m.applyForce(gravity);

ここまでの考え方で実装すると質量mが大きいほど、acceleration値は小さくなる。これは計算上とニュートンの第2法則からの観点では正しい。が、しかし、自然界で考えるとこれは正しくない。質量が大きいほど、加速度は大きくなるべきです。重いものほど速く落ちて行くよね。。ガリレオ・ガリレイの法則だと、どんなものでも大きさに関係なく同じ重力加速度が働くから、重力方向の速度は大きさによらないはずだけど。。

覚えておかないといけないのは、 Moverクラス内のapplyForce内ではF = maの式からa = F / mと、質量で割っているところと、重力加速度の調整としてgravityに質量mを掛けている意味の違い! あ〜スッキリした。

ということで、ここまでのコードをopenFrameWorksで書いてみました。

#include "testApp.h"


#define NUM_MOVERS 100


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), ofRandom(0, ofGetHeight() - mass));
        acceleration.set(0.0, 0.0, 0.0);
    }
    
    void update()
    {
        velocity += acceleration;
        location += velocity;
        
        acceleration.set(0.0, 0.0, 0.0);
    }
    
    void draw()
    {
        ofPushMatrix();
        ofTranslate(location);
        ofCircle(0.0, 0.0, 0.0, mass);
        ofPopMatrix();

        checkEdige();
    }
};


vector<Mover> movers;
ofVec3f wind;
ofVec3f gravity;


//--------------------------------------------------------------
void testApp::setup(){
    
    ofSetFrameRate(60);
    ofEnableSmoothing();
    ofSetVerticalSync(true);
    ofBackground(0);
    
    
    movers.resize(NUM_MOVERS);
    
    //自然界のforceをセット|重力を0.2にしている理由は画面上の動きが速すぎるため調整している。
    wind.set(0.2, 0.0, 0.0);
    gravity.set(0.0, 0.2, 0.0);
    
    for (int i = 0; i < NUM_MOVERS; i++)
    {
        Mover *m = &movers[i];
        m->setup();
    }
    
    ofSetColor(255);
    

}

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

    for (int i = 0; i < NUM_MOVERS; i++) {
        Mover* m = &movers[i];
        float mass = m->mass;
        
        //風フォース
        m->applyForces(wind);
        
        //重力フォース
        m->applyForces(gravity * mass);
        
        m->update();
    }
}

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

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