yoppa.org


多摩美 – Media Lab. I 2016

openFrameworksアニメーションの基本

先週の復習

まず始めに先週作成したコードを復習してみましょう。ベクトル(ofVec2f)を使用したアニメーションのサンプルです。

このサンプルのポイントとなるのは、動きを2つのベクトルで表現している部分です。

  • ofVec2f location : 位置を記録するためのベクトル → 位置ベクトル
  • ofVec2f velocity : 速度を記録するためのベクトル → 速度ベクトル

ベクトルとは、大きさと向きをもった量のことです。下記の図のV(実際には上に→)がベクトルに相当します。ベクトルはx方向のベクトルとy方向ベクトルに分解することが可能です。下記の図ではVx, Vyがベクトルの成分に相当します。また、それぞれのベクトル成分の大きさが1のベクトルを「単位ベクトル」と呼んでいます。この図では、IJ が相当します。

ここまでのまとめ

  • ベクトル
  • ベクトルの成分
  • 単位ベクトル

物体の位置の座標は、原点からみたベクトルと捉えることも可能です。座標の原点から物体の位置の座標まで矢印を引いてベクトルを生成します。このベクトルを「位置ベクトル」と呼んでいます。位置ベクトルは2次元空間のみに適用されるわけではありません。1次元や3次元空間でもベクトルで位置を表現可能です。

速度もベクトルで表現可能です。位置ベクトルPがあったとして、そこにベクトルを加えると次の位置ベクトルQが求められます。この位置ベクトルに加えるベクトルを速度と考えます。このベクトルを「速度ベクトル」と呼びます。

サンプルプログラムに戻ると、まずsetup()で位置ベクトルと速度ベクトルの初期値を決めています。

void ofApp::setup(){
    (中略)    
    //位置と速度を初期化
    location = ofVec2f(ofGetWidth()/2, ofGetHeight()/2); //画面の中心に
    velocity = ofVec2f(ofRandom(-10, 10), ofRandom(-10, 10)); //ランダムな速度で
}

update()で、位置ベクトルに速度ベクトルを加えて、次のフレームの位置ベクトルを求めています。

void ofApp::update(){
    location += velocity; //速度から位置を更新
}

展開1 : 物体の数を増やしてみる – 配列とくりかえし

では、この運動する物体の数を増やすにはどうすれば良いでしょうか? まず単純に思いつくのは、物体の位置ベクトルと速度ベクトルを、物体の数だけ増やして定義する方法ではないでしょうか。

例:

しかしこの方法だと、10個くらいまでは対応できそうですが、100個、1000個、10000個と物体の数を増やそうとしたときに破綻することは確実です。

こういうときに便利な仕組みが「配列」という仕組みです。変数を値を1つだけ入れられる「箱」とすると配列は大量の値を番号をつけて管理する「ロッカー」のようなイメージで捉えると理解し易いでしょう。

先程のサンプルを改造して100個の位置と速度を記録する配列を利用してアニメーションしてみましょう。

配列は多くの場合、繰り返しの構文「for文」と一緒に使用されます。for文と配列をつかうと全ての配列の処理を一気に処理することが可能となるからです。for文と配列の関係の概略は以下のイメージになるでしょうか。

for(int i = 0; i < NUM; i++){
  配列[i]の処理を行う
}

これを踏まえてサンプルをみてみます。まずヘッダーファイルで配列を準備しています。このとき配列の上限の数を指定する必要があるのですが、すぐに変更できるように定数として定義しているところにも注目してください。

#pragma once
#include "ofMain.h"
    
class ofApp : public ofBaseApp{
public:
    (中略)
    //配列の最大数を定数として定義
    static const int NUM = 100;
    //配列を定義
    ofVec2f location[NUM]; //NUM個の位置ベクトル
    ofVec2f velocity[NUM]; //NUM個の速度ベクトル
};

次にこの配列をofApp::setup()で初期化しています。for文と配列の組み合わせがここで使用されています。

void ofApp::setup(){
    (中略)
    //NUM回くりかえし
    for (int i = 0; i < NUM; i++) {
        //位置と速度を初期化
        location[i] = ofVec2f(ofGetWidth()/2, ofGetHeight()/2);
        velocity[i] = ofVec2f(ofRandom(-10, 10), ofRandom(-10, 10));
    }
}

update()とdraw()では、それぞれ配列の値をくりかえし参照しながら全ての物体の位置を更新して描画しています。

void ofApp::update(){
    //NUM回くりかえし
    for (int i = 0; i < NUM; i++) {
        location[i] += velocity[i];
    }
}
    
void ofApp::draw(){
    //NUM回くりかえし
    for (int i = 0; i < NUM; i++) {
        //計算した位置に円を描画
        ofSetColor(31, 12, 255); //円の色
        ofDrawCircle(location[i], 20); //半径40の円を描画
        ofDrawCircle(location[i], 20); //半径40の円を描画
        
        //画面の端でバウンドするように
        if (location[i].x < 0 || location[i].x > ofGetWidth()) {
            velocity[i].x *= -1; //横向きの速度を反転(バウンド)
        }
        if (location[i].y < 0 || location[i].y > ofGetHeight()) {
            velocity[i].y *= -1; //横向きの速度を反転(バウンド)
        }
    }
}

展開2 : 数の上限がわからない時には? – 可変長配列 vector を使う

ではこのプログラムの応用で、最初は0個だった物体が、時間の経過とともにどんどんと増えていくようなプログラムにしてみたいと思います。

ここで重大な問題が発生します。物体の数をどんどん増やしていきたい場合、最初に配列を定義する際の上限値をいくつにすれば良いのでしょうか? 例えば

ofVec2f location[999999999];
ofVec2f velocity[999999999];

というように、普通では超えそうもない大きな数で余裕をもって指定しておくという方法も可能ですが、あまり賢い方法ではありません。

こうしたときに便利な仕組みとして、可変長配列というものがあります。可変長配列とこれまで使用してきた固定長の配列(Array)との違いは、配列の長さを後から自由に変更可能であるという点です。

実際にサンプルをみながら、その使い方を理解しましょう。

まず、位置と速度、それぞれの可変長配列vectorの宣言をみてみましょう。vectorの宣言は少し複雑です。

vector< 配列の型> 配列名;

実際のサンプルではofApp.hで宣言されています。それぞれofVec2fで位置と速度の可変長配列を生成しています。

#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp{
public:
    
    (中略)
        
    //可変長配列(vector)を定義
    vector location; //位置ベクトル
    vector velocity; //速度ベクトル
};

配列へ値を追加、削除する方法はいくつか存在します。差し当って以下の命令を覚えましょう。

  • 配列名.push_back(追加する要素) : 配列の末尾に要素を追加
  • 配列名.pop_back(追加する要素) : 配列の末尾に要素を削除
  • 配列名.clear() : 配列を全消去

ofApp.cpp::uprdate() でフレームを更新するたびに、1つ要素を位置ベクトル、速度ベクトルそれぞれの配列の末尾に追加しています。

void ofApp::update(){
    
    (中略)
        
    //新規に位置ベクトルと速度ベクトルを生成し、配列に追加
    location.push_back(ofVec2f(ofGetWidth()/2, ofGetHeight()/2));
    velocity.push_back(ofVec2f(ofRandom(-10, 10), ofRandom(-10, 10)));    
}

展開3 : 一定の数に制限する

このままずっと増殖されることも可能ですが、ある一定の上限値を決めて、その数まで増えたら可変長配列vectorの先頭の要素を消していくようにしてみましょう。

まずofApp.hで上限値を決めています。

int max_num;

この値をもとに、update()で一定の数より増えたら先頭の要素を消すようにしています。

void ofApp::update(){
    
    (中略)
    
    //もし上限値を超えたら、先頭の要素を削除する
    if(location.size() > max_num){
        location.erase(location.begin());
        velocity.erase(velocity.begin());
    }
}

展開4 : マウスをドラッグした位置から物体を生成

応用例として、マウスをドラッグすると、そのドラッグした位置から物体が生成されるようにしてみましょう。

ポイントは1つだけです。update()で定義していた要素を追加する一連の命令を、mouseDragged()に移動しています。

void ofApp::mouseDragged(int x, int y, int button){
    //新規に位置ベクトルと速度ベクトルを生成し、配列に追加
    location.push_back(ofVec2f(x, y));
    velocity.push_back(ofVec2f(ofRandom(-2, 2), ofRandom(-2, 2)));
    
    //もし上限値を超えたら、先頭の要素を削除する
    if(location.size() > max_num){
        location.erase(location.begin());
        velocity.erase(velocity.begin());
    }
}