BNN CAMP vol.3 ...

Preview:

Citation preview

BNN CAMP vol.3 インタラクションデザインの現在プログラミング初心者のためのopenFrameworks入門 - 2: 構造をつくる

2013年8月3日田所 淳

このセクションの内容‣ 構造をもったより複雑なプログラムへ!

‣ 関数‣ クラス - オブジェクト指向プログラミング‣ くりかえしと配列‣ パーティクルシステムをつくる‣ ベクターフィールド

処理をまとめるサブルーチン ( = 関数)

サブルーチン (= 関数) とは‣ プログラム中で意味や内容がまとまっている作業をひとつの手続きとしたもの

‣ openFrameworks (つまり C++) では、サブルーチンのことを「関数 (function)」と呼ぶのが一般的

‣ 関数を使用する利点‣ 繰り返し現れる作業をまとめることができる‣ プログラムの可読性の向上‣ 保守性を高く保つ

関数:引数と返り値‣ 関数への入出力‣ 引数 (ひきすう, argument) - 関数に渡す値‣ 返り値 (return value) - 関数が返す値

関数

引数1 引数2 引数3

戻り値

C++ での関数の書きかた‣ C++での関数の書き方

‣ 例えば、int型の数の二乗を計算する関数

‣ もし戻り値がない関数の場合、戻り値の型は「void」にする

戻り値の型 名前空間::関数名(引数1, 引数2, 引数3...){ 関数の処理の内容}

int testApp::poweroftwo(int a){! return a * a;}

関数によるアニメーション:補完‣ 2つの地点を直線移動する座標を計算する関数をつくってみる‣ 現在の位置は、終了点を1とした割合(0.0~1.0)で表現

startPos(0.0)

currentPos(0.0~1.0の間)

endPos(1.0)

関数によるアニメーション:補完‣ 開始点(startPos)と終了点(endPos)をまず決める

startPos(0.0)

endPos(1.0)

関数によるアニメーション:補完‣ 経過した地点の割合 pct (0.0~1.0)を与えると、現在の地点の座標を計算して返す

startPos(0.0)

endPos(1.0)0.2

関数によるアニメーション:補完‣ 経過した地点の割合 pct (0.0~1.0)を与えると、現在の地点の座標を計算して返す

startPos(0.0)

endPos(1.0)

0.5

関数によるアニメーション:補完‣ 経過した地点の割合 pct (0.0~1.0)を与えると、現在の地点の座標を計算して返す

startPos(0.0)

endPos(1.0)

0.8

補足:ofPointで座標を指定する‣ これ以降、座標の点を表現する際には、X座標、Y座標ではなくそれをまとめた、ofPoint() を使用する

‣ X, Y座標をまとめて1つの変数で扱うことのできるもの(クラス)

posx y

補足:ofPointで座標を指定する‣ ofPointは、[変数名].x [変数名].y と指定しすることで複数の座標を一括して管理できる

‣ 例:(10.0, 20.0)の座標を表現する場合

float x;float y;

x = 10.0;y = 20.0

複数のfloat型

ofPoint pos;pos.x = 10.0;pos.y = 20.0;

ofPoint型

関数によるアニメーション:補完‣ 関数をつかって、定義してみる‣ 関数名:interpolateByPct

‣ 引数 :‣ float pct → 現在の二点間を補完する際の割合(0.0 ~ 1.0)

‣ 返り値 :‣ ofPoint pos → 割合から算出された座標

ofPoint interpolateByPct(float pct);

関数によるアニメーション:補完‣ 関数をtestAppに追加する場合‣ まずヘッダファイル(レシピ!)に、関数の概要を追加する‣ 料理の手順を工程表に追加するイメージ‣ 同時に必要となる変数 (材料) も全て追加しておく

ヘッダファイル = レシピ

interpolateByPct()

ofPoint startPos;ofPoint endPos;ofPoint currentPos;float pct;

関数によるアニメーション:補完‣ testApp.h に関数(手順)と変数(材料)を追加#pragma once#include "ofMain.h"

class testApp : public ofBaseApp{!public:! void setup();! void update();! void draw();

...《中略》...!! ofPoint interpolateByPct(float pct);!! ofPoint startPos;! ofPoint endPos;! ofPoint currentPos;! float pct;!};

追加

関数によるアニメーション:補完‣ 実装ファイルに手順を記述‣ testApp.cppに、testApp::interpolateByPct() { ... } というブロックを用意して処理内容を全て書いていく

interpolateByPct( )

位置を割合で指定すると現在の位置の座標を返す

関数によるアニメーション:補完‣ testApp.cpp に関数(手順)の内容を記述#include "testApp.h"

//--------------------------------------------------------------void testApp::setup(){! ofBackground(0, 0, 0);! ofSetFrameRate(60);! ofSetVerticalSync(true);}

《中略》...//--------------------------------------------------------------ofPoint testApp::interpolateByPct(float _pct){!! ofPoint pos;! pos.x = (1.0 - _pct) * startPos.x + (_pct) * endPos.x;! pos.y = (1.0 - _pct) * startPos.y + (_pct) * endPos.y;!! return pos;!}

追加

関数によるアニメーション:補完‣ 実際に座標を計算してみる‣ 2段階の手順が必要

‣ setup() でまず開始位置と終了位置を決定

‣ update() で割合(pct)を継続して入力すると、現在の位置を計算して返す

startPosendPos

interpolateByPct( )pct currentPos

interpolateByPct( )

関数によるアニメーション:補完‣ testApp.cpp に処理を追加#include "testApp.h"

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

! ofBackground(0, 0, 0);! ofSetFrameRate(60);! ofSetVerticalSync(true);

! startPos = ofPoint(10, 100);! endPos = ofPoint(1000, 600);! pct = 0;

}

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

! pct = pct + 0.01;! currentPos = interpolateByPct(pct);

}

追加

追加

関数によるアニメーション:補完‣ testApp.cpp に処理を追加//--------------------------------------------------------------void testApp::draw(){

! ofSetColor(31, 127, 255);! ofCircle(currentPos.x, currentPos.y, 20);

}

//--------------------------------------------------------------ofPoint testApp::interpolateByPct(float _pct){!! ofPoint pos;! pos.x = (1.0 - _pct) * startPos.x + (_pct) * endPos.x;! pos.y = (1.0 - _pct) * startPos.y + (_pct) * endPos.y;!! return pos;!}

追加

関数によるアニメーション:補完‣ 実行結果:設定した座標間を直線運動する

関数によるアニメーション:補完‣ もう少し工夫‣ 0.0から1.0に到達したら、また0.0に戻る(リセット)するように‣ 条件をつかえば簡単に実現可能‣ 「もし、pct の値が1.0を越えたら、0.0に戻れ」

‣ 「もし○○なら、××せよ」→ if分を用いる

関数によるアニメーション:補完‣ 選択 - 何らかの条件が成立したとき文の並びを実行する‣ 「もし○○なら××せよ、そうでなければ△△せよ」

処理 A 処理 B

条件?

Yes

No

関数によるアニメーション:補完‣ if~else文を使うif (【条件式】) { 【条件式が正しい時の処理 (真文)】} else { 【条件式が正しくない時の処理 (偽文)】}

関数によるアニメーション:補完‣ testApp.cpp - update() に処理を追加//--------------------------------------------------------------void testApp::update(){! pct = pct + 0.01;!! if (pct > 1.0) {! ! pct = 0.0;! }!! currentPos = interpolateByPct(pct);}

追加

関数によるアニメーション:補完‣ 実行結果:動きがループするようになったはず!

高度な補完:加速と減速‣ 補完の動きに、表情をつける‣ %による補完を、単純な加算ではなく、指数で指定してみる‣ 指数の値によって、時間と距離の曲線はどうなるか?

‣ OSX付属のGrapher.appをつかって、確かめてみる

高度な補完:加速と減速y = x

高度な補完:加速と減速y = x^2

高度な補完:加速と減速y = x^0.5

高度な補完:加速と減速‣ C++で指数計算をするには、powerf() 関数を使用する‣ 例:10の2乗

‣ interpolateByPct 関数に、引数 shaper を追加‣ pctを、shaperで指定した指数で乗算していく

powf(10.0, 2.0);

ofPoint testApp::interpolateByPct(float _pct, float _shaper){! ofPoint pos;! float shapedPct = powf(_pct, _shaper);

! pos.x = (1.0 - shapedPct) * startPos.x + shapedPct * endPos.x;! pos.y = (1.0 - shapedPct) * startPos.y + shapedPct * endPos.y;

! return pos;}

高度な補完:加速と減速‣ testApp.h :追加と修正#pragma once#include "ofMain.h"

class testApp : public ofBaseApp{!public:! void setup();! void update();! void draw();

...《中略》...

! ofPoint interpolateByPct(float pct, float shaper);!! ofPoint startPos;! ofPoint endPos;! ofPoint currentPos;! float pct;! float shaper;!};

修正

追加

高度な補完:加速と減速‣ testApp.cpp:追加と修正#include "testApp.h"

//--------------------------------------------------------------void testApp::setup(){! ofBackground(0, 0, 0);! ofSetFrameRate(60);! ofSetVerticalSync(true);

! startPos = ofPoint(10, 100);! endPos = ofPoint(1000, 600);! pct = 0;! shaper = 2.0;}

//--------------------------------------------------------------void testApp::update(){! pct = pct + 0.01;! if (pct > 1.0) {! ! pct = 0.0;! }! currentPos = interpolateByPct(pct, shaper);}

修正

追加

高度な補完:加速と減速‣ testApp.cpp:追加と修正//--------------------------------------------------------------ofPoint testApp::interpolateByPct(float _pct, float _shaper){!! ofPoint pos;! float shapedPct = powf(_pct, _shaper);!! pos.x = (1.0 - shapedPct) * startPos.x + shapedPct * endPos.x;! pos.y = (1.0 - shapedPct) * startPos.y + shapedPct * endPos.y;!! return pos;!}

修正

高度な補完:加速と減速‣ 実行結果

高度な補完:加速と減速‣ shaperの値をいろいろ変化させて、動きを観察してみる

shaper = 4.0 shaper = 0.5

オブジェクト指向プログラミング

‣ オブジェクト指向プログラミング

‣ Object Oriented Programming (OOP)‣ オブジェクト指向でProcessingのプログラムを作る‣ そもそもオブジェクト指向とは?

‣ 簡単なプログラムを、オブジェクト指向で書いてみる‣ クラスの定義‣ クラスの呼びだし

オブジェクト指向プログラミングとは?

‣ オブジェクト指向プログラミング言語のイメージ

プログラミング・パラダイムの変遷

オブジェクト

オブジェクト

オブジェクト

オブジェクト

‣ OOPの特徴

‣ 相互にメッセージを送り合う「オブジェクト」の集まりとしてプログラムを構成

‣ オブジェクトは、プロパティとメソッドから構成される‣ カプセル化 - 必要のない情報は隠す‣ インヘリタンス(継承) - あるオブジェクトが他のオブジェクトの特性を引き継ぐ

‣ ポリモーフィズム(多態性・多様性) - プログラミング言語の各要素が複数の型に属することを許す

オブジェクト指向プログラミングの概念

‣ オブジェクト‣ プロパティとメソッド‣ カプセル化‣ 継承 (インヘリタンス)‣ 多態性、多相性 (ポリモーフィズム)

OOP、5つのポイント

‣ オブジェクト指向プログラムのポイント:その1‣ オブジェクトの集まりとしてプログラムを構成‣ オブジェクト同士がメッセージを送りあう

OOP:ポイントその1

‣ オブジェクト指向プログラムのポイント:その2‣ オブジェクトは、プロパティ(性質、状態)と、メソッド(動作、ふるまい) から構成される

状態1状態2状態3

オブジェクト

メソッド1 メソッド2

メソッド3メソッド4

OOP:ポイントその2

‣ 例:「りんご」をオブジェクトとして考える

赤5.0甘い

ふじ

実がなる

成長する

落ちる腐る

青4.0

すっぱい

青リンゴ実がなる

成長する

落ちる腐る

OOP:ポイントその2

‣ オブジェクト指向プログラムのポイント:その3‣ 必要のない情報は隠す (カプセル化)‣ プログラムの実装全てを知る必要はない‣ 必要なインターフェイス(接点)だけ見せて、あとは隠す

It’s this resemblance to real things that gives objects much of their power and appeal. They can notonly model components of real systems, but equally as well fulfill assigned roles as components insoftware systems.

Interface and Implementation

To invent programs, you need to be able to capture abstractions and express them in the programdesign. It’s the job of a programming language to help you do this. The language should facilitate theprocess of invention and design by letting you encode abstractions that reveal the way things work.It should let you make your ideas concrete in the code you write. Surface details shouldn’t obscurethe architecture of your program.

All programming languages provide devices that help express abstractions. In essence, these devicesare ways of grouping implementation details, hiding them, and giving them, at least to some extent,a common interface—much as a mechanical object separates its interface from its implementation, asillustrated in “Interface and Implementation” .

Figure 2-1 Inte rfa ce a nd Im ple m e nta tion

910

11

87 6

implementationinterface

Looking at such a unit from the inside, as the implementor, you’d be concerned with what it’scomposed of and how it works. Looking at it from the outside, as the user, you’re concerned onlywith what it is and what it does. You can look past the details and think solely in terms of the rolethat the unit plays at a higher level.

The principal units of abstraction in the C language are structures and functions . Both, in differentways, hide elements of the implementation:

� On the data side of the world, C structures group data elements into larger units which can thenbe handled as single entities. While some code must delve inside the structure and manipulatethe fields separately, much of the program can regard it as a single thing—not as a collection ofelements, but as what those elements taken together represent. One structure can include others,so a complex arrangement of information can be built from simpler layers.

14 Inte rfa ce a nd Im ple m e nta tion2007-12-11 | © 2007 Apple Inc. All Rights Reserved.

C H A P T E R 2

O bje ct-O rie nte d P rogra m m ing

インターフェイス 実装

OOP:ポイントその3

‣ オブジェクト指向プログラムのポイント:その4‣ インヘリタンス(継承)‣ オブジェクトから新たなオブジェクトを派生させる

植物

生物

動物

果物 穀物

りんご

ふじ 紅玉 デリシャス

バナナ マンゴー

OOP:ポイントその4

‣ オブジェクト指向プログラムのポイント:その5‣ ポリモーイズム(多態性、多様性)‣ オブジェクトはメッセージを受け取りそれに応じた処理を行う‣ メッセージの処理方法は、オブジェクト自身が知っていて、その処理はオブジェクトによって異なる

getName()

オブジェクトA:人間

「田所 淳」

getName()

オブジェクトB:車

「トヨタカローラ」

OOP:ポイントその5

‣ クラス‣ クラスとは:オブジェクトの「型紙」‣ クラスをインスタンス化 (実体化) することでインスタンス(オブジェクト)となる

色重さ(g)味

リンゴ(クラス)

実がなる

成長する

落ちる腐る

赤5.0甘い

ふじ(インスタンスオブジェクト)

実がなる

成長する

落ちる腐る

青4.0

すっぱい

青リンゴ(インスタンスオブジェクト)

実がなる

成長する

落ちる腐る

インスタンス化

クラス

‣ いままで扱ってきた、testApp も一つのクラス‣ メソッド - setup(), update(), draw() ...etc.‣ プロパティ - testApp全体で使用する変数

testAppもクラス

クラス変数

setup() update()

draw()exit()

testApp

‣ これまでのようにtestAppにどんどん機能を追加すると、様々な弊害が

‣ 可読性の低下、機能ごとに再利用できない、拡張が困難 ..etc.

testApp単体の限界

testApp testApp肥大化

‣ 機能ごとにオブジェクトを分けてプロジェクトを構成する‣ オブジェクトが相互に連携

testApp単体の限界

testApp

Rectangle

Particle

ControlPanel

OOP実践編クラスを作る

クラスの実装‣ 前半にやった、アニメーションする円をクラス化してみる‣ まずは円を表示するところまで

‣ クラス名:MoveCircle

‣ プロパティ (状態、変数):‣ ofPoint startPos : 円の初期位置

‣ メソッド (ふるまい、関数):‣ draw( ) : 円を描く

クラスの実装‣ こんな図にかく場合も‣ UMLクラス図

+ draw():void

+ currentPos:ofPoint

MoveCircle

クラスの実装‣ こんな図にかく場合も‣ UMLクラス図

+ draw():void

+ currentPos:ofPoint

MoveCircle

プロパティ (状態)メソッド(ふるまい)

XCodeプロジェクトにクラスを追加する‣ ファイルのリストの「src」フォルダを右クリック‣ リストから「New File (新規ファイル)」を選択

XCodeプロジェクトにクラスを追加する‣ Mac OS X > C and C++ > C++ File を選択

XCodeプロジェクトにクラスを追加する‣ 名前をつけて、「src」フォルダに保存

XCodeプロジェクトにクラスを追加する‣ 再度、リストから「New File (新規ファイル)」を選択

XCodeプロジェクトにクラスを追加する‣ Mac OS X > C and C++ > Header File を選択

XCodeプロジェクトにクラスを追加する‣ 名前をつけて、「src」フォルダに保存

XCodeプロジェクトにクラスを追加する‣ ファイルリストは以下のようになるはず‣ あとは、それぞれのファイルにコーディングしていく

クラスの記述‣ まずはヘッダーファイル (MoveCircle.h) から‣ レシピの材料と手順の一覧!

‣ 材料 → 状態、性質 → つまり、プロパティ(変数)‣ 手順 → ふるまい、動作 → つまり、メソッド(関数)

‣ そのクラスのプロパティとメソッドを記述‣ 外部から参照するものは、public: 以下に書く

クラスの実装‣ MoveCircle.h - 円を描く際のレシピ!#pragma once

#include "ofMain.h"

class MoveCircle {!public:!! void draw();

! ofPoint currentPos;!};

クラスの実装‣ MoveCircle.h - 円を描く際のレシピ!#pragma once

#include "ofMain.h"

class MoveCircle {!public:!! void draw();

! ofPoint currentPos;!};

インクルードガードBuildの際に複数回読みこまれないためのしくみ

クラスの実装‣ MoveCircle.h - 円を描く際のレシピ!#pragma once

#include "ofMain.h"

class MoveCircle {!public:!! void draw();!! ofPoint currentPos;!};

oFの機能を使うためのライブラリを必ず読み込む

クラスの実装‣ MoveCircle.h - 円を描く際のレシピ!#pragma once

#include "ofMain.h"

class MoveCircle {!public:!! void draw();!! ofPoint currentPos;!};

クラス名

クラスの実装‣ MoveCircle.h - 円を描く際のレシピ!#pragma once

#include "ofMain.h"

class MoveCircle {!public:!! void draw();!! ofPoint currentPos;!};

public: 以下は外部に公開される

クラスの実装‣ MoveCircle.h - 円を描く際のレシピ!#pragma once

#include "ofMain.h"

class MoveCircle {!public:!! void draw();!! ofPoint currentPos;!};

メソッド:draw() - 円を描く

クラスの実装‣ MoveCircle.h - 円を描く際のレシピ!#pragma once

#include "ofMain.h"

class MoveCircle {!public:!! void draw();!! ofPoint currentPos;!};

プロパティ:currentPos - 初期位置

クラスの実装‣ MoveCircle.h - 円を描く際のレシピ!#pragma once

#include "ofMain.h"

class MoveCircle {!public:!! void draw();!! ofPoint currentPos;!}; 最後に必ずセミコロンをつける

クラスの記述‣ つぎに実装ファイル(MoveCircle.cpp)を書く‣ 円を描くための全ての手続きを記述していく

‣ 現在は、draw() 関数のみ

クラスの記述‣ MoveCircle.cpp - 円を実際に描く手順#include "MoveCircle.h"

void MoveCircle::draw() {!! ofFill();! ofSetColor(31,127,255);! ofCircle(currentPos.x, currentPos.y, 20,20);!}

必ずヘッダーファイルを読み込む

クラスの記述‣ MoveCircle.cpp - 円を実際に描く手順#include "MoveCircle.h"

void MoveCircle::draw() {!! ofFill();! ofSetColor(31,127,255);! ofCircle(currentPos.x, currentPos.y, 20,20);!}

戻り値 クラス名::関数名(引数)クラス名を名前空間として使用している他のクラスのdraw()関数との混同を避けている

クラスの記述‣ MoveCircle.cpp - 円を実際に描く手順#include "MoveCircle.h"

void MoveCircle::draw() {!! ofFill();! ofSetColor(31,127,255);! ofCircle(currentPos.x, currentPos.y, 20,20);!}

円を描画

クラスの記述‣ 最後に作成したクラスを、oFのメインクラスであるtestAppから呼び出します

‣ ヘッダーファイル testApp.h で MoveCircle を宣言

‣ これだけで、クラスが実体化(インスタンス化)される‣ クラス名:MoveCircle‣ インスタンス:myCircle

MoveCircle myCircle;

クラスの実装‣ testApp.h - MoveCircleをインスタンス化#pragma once

#include "ofMain.h"#include "MoveCircle.h"

class testApp : public ofBaseApp{!public:! void setup();! void update();! void draw();!...《中略》...!! MoveCircle myCircle;

};

クラスの実装‣ testApp.h - MoveCircleをインスタンス化#pragma once

#include "ofMain.h"#include "MoveCircle.h"

class testApp : public ofBaseApp{!public:! void setup();! void update();! void draw();!...《中略》...!! MoveCircle myCircle;

};

MovieCircleのヘッダを読み込む

クラスの実装‣ testApp.h - MoveCircleをインスタンス化#pragma once

#include "ofMain.h"#include "MoveCircle.h"

class testApp : public ofBaseApp{!public:! void setup();! void update();! void draw();!...《中略》...!! MoveCircle myCircle;

};

MovieCircleをインスタンス化

クラスの記述‣ メインの実装ファイル、testApp.cpp で作成したインスタンスを使用して円を描かせる

‣ testApp::setup( ) で円の初期位置を指定‣ testApp::draw( ) でMyCircleのdraw( )メソッドを呼びだし

クラスの実装‣ testApp.cpp - MoveCircleで円を描く#include "testApp.h"

//--------------------------------------------------------------void testApp::setup(){! ofSetFrameRate(60);! ofSetVerticalSync(true);! ofEnableAlphaBlending();! ofBackground(0, 0, 0);!! myCircle.currentPos = ofPoint(400, 300);}

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

//--------------------------------------------------------------void testApp::draw(){!! myCircle.draw();!}

クラスの実装‣ testApp.cpp - MoveCircleで円を描く#include "testApp.h"

//--------------------------------------------------------------void testApp::setup(){! ofSetFrameRate(60);! ofSetVerticalSync(true);! ofEnableAlphaBlending();! ofBackground(0, 0, 0);!! myCircle.currentPos = ofPoint(400, 300);}

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

//--------------------------------------------------------------void testApp::draw(){!! myCircle.draw();!}

初期位置の指定

クラスの実装‣ testApp.cpp - MoveCircleで円を描く#include "testApp.h"

//--------------------------------------------------------------void testApp::setup(){! ofSetFrameRate(60);! ofSetVerticalSync(true);! ofEnableAlphaBlending();! ofBackground(0, 0, 0);!! myCircle.currentPos = ofPoint(400, 300);}

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

//--------------------------------------------------------------void testApp::draw(){!! myCircle.draw();!}

myCircleを使用して、円を描く

クラスの実装‣ ようやく円が描けた!!

クラスの実装‣ 次にMoveCircleクラスに、動きをつけてみる!‣ 関数のところで学んだ、補完する動きをクラスに組み込む

クラスの実装‣ クラス名:MoveCircle

‣ プロパティ:‣ ofPoint startPos - 開始位置‣ ofPoint endPos - 終了位置‣ ofPoint currentPos - 現在位置‣ float pct - 現在の位置の割合(%)‣ float shaper - 加速・減速の加減

‣ メソッド:‣ void draw(); - 四角形の描画‣ void update(); - 状態の更新‣ float interpolateByPct(float myPct); - 位置の計算

クラスの実装‣ クラス名:MoveCircle‣ UMLクラス図で表現すると、こんな感じ

‣ この設計図の通りに、ヘッダーファイル(MoveCircle.h)と実装ファイル(MoveCircle.cpp)を記述していく

+ draw():void+ update():void+ interpolateByPct():ofPoint

+ startPos:ofPoint+ endPos:ofPoint+ currentPos:ofPoint+ pct:float+ shaper:float

MoveCircle

クラスの実装‣ MoveCircle.h - 円を描く際のレシピ!#pragma once#include "ofMain.h"

class MoveCircle {!public:!! void draw();! void update();! ofPoint interpolateByPct(float pct);!! ofPoint currentPos;! ofPoint startPos;! ofPoint endPos;! float pct;! float shaper;};

クラスの実装‣ MoveCircle.h - 円を描く際のレシピ!#pragma once#include "ofMain.h"

class MoveCircle {!public:!! void draw();! void update();! ofPoint interpolateByPct(float pct);!! ofPoint currentPos;! ofPoint startPos;! ofPoint endPos;! float pct;! float shaper;};

メソッド

クラスの実装‣ MoveCircle.h - 円を描く際のレシピ!#pragma once#include "ofMain.h"

class MoveCircle {!public:!! void draw();! void update();! ofPoint interpolateByPct(float pct);!! ofPoint currentPos;! ofPoint startPos;! ofPoint endPos;! float pct;! float shaper;};

プロパティ

クラスの実装‣ 次に実装ファイル MoveCircle.cpp を改変

クラスの実装‣ MoveCircle.cpp - 円を動かす#include "MoveCircle.h"

void MoveCircle::draw() {

! ofFill();! ofSetColor(31,127,255); ofCircle(currentPos.x, currentPos.y, 20, 20);!}

void MoveCircle::update() {

! pct += 0.01f;! if (pct > 1) {! ! pct = 0;! }!! currentPos = interpolateByPct(pct);!}

クラスの実装‣ MoveCircle.cpp - 円を動かす

ofPoint MoveCircle::interpolateByPct(float _pct){!! float shapedPct = powf(_pct, shaper);!! ofPoint pos;! pos.x = (1 - shapedPct) * startPos.x + shapedPct * endPos.x;! pos.y = (1 - shapedPct) * startPos.y + shapedPct * endPos.y;!! return pos;!}

クラスの実装‣ メインクラスの実装ファイル testApp.cpp も変更が必要

クラスの実装‣ testApp.cpp - MoveCircleで円を動かす#include "testApp.h"

//--------------------------------------------------------------void testApp::setup(){! ofSetFrameRate(60);! ofBackground(0, 0, 0);! myCircle.startPos = ofPoint(10, 400);! myCircle.endPos = ofPoint(1000, 200);! myCircle.shaper = 2.0;}

//--------------------------------------------------------------void testApp::update(){! myCircle.update();}

//--------------------------------------------------------------void testApp::draw(){! myCircle.draw();}

...《後略》...

クラスの実装‣ 完成!!

OOP実践編パーティクルを動かす

OOP実践編:パーティクルを動かす‣ より実践的なOOPを、例を参照しながら理解する‣ パーティクル (Particles = 粒子) が空間内を飛びまわるプログラムを作成してみる

‣ 最初は1つの粒‣ 大量の粒を個別に動かすには?‣ オブジェクトの配列を利用すると便利

‣ 2種類の配列‣ 静的配列 (Array):固定長の配列‣ 動的配列 (Vector):可変長の配列

パーティクルのイメージ‣ 一定方向に向けて移動する力を持つ‣ ただし、常に反対方向の抵抗(摩擦など)を受けている

物体にかかる力 (force)F = ma (力 = 質量 * 加速度)ニュートンの運動方程式

抵抗力 (damping)

パーティクルクラスを設計する‣ パーティクルクラスに必要な要素

‣ クラス名:Particle

‣ プロパティ(状態)‣ 現在位置:pos (ofVec2f)‣ 速度:vel (ofVec2f)‣ 力:frc (ofVec2f)

パーティクルクラスを設計する‣ パーティクルクラスに必要な要素 (つづき)

‣ メソッド (動作)‣ コンストラクタ(後述):Particle()‣ デストラクタ(後述):~Particle()‣ 力のリセット:recetForce()‣ 力を加える:addForce()‣ 抵抗力(摩擦)を加える:addDampingForce()‣ 状態の初期化:setInitialCondition()‣ 位置更新:update()‣ 描画:draw()

パーティクルクラスを設計する‣ ofVec2f とは?

‣ ofPoint (x座標, y座標)に加えて、距離の算出や2点間の角度の計算など、ベクトル計算に特化したクラス

‣ oF v007からは、ofVectorMathとして標準で使用可能

パーティクルクラスを設計する‣ ParticleクラスのUMLクラス図

+ Particle()+ ~Particle()+ resetForce():void+ addForce(float x, float y):void+ addDumpingForce():void+ setInitialCondition(float px, float py, float vx, float vy):void+ update():void+ draw():void

+ pos:ofVec2f+ vel:ofVec2f+ frc:ofVec2f+ dumping:float

Particle

コンストラクタとデストラクタ‣ コンストラクタ、デストラクタ:特殊なメソッド

‣ コンストラクタ‣ クラスがインスタンス化された際に実行される‣ つまり初期化の関数‣ 関数名はかならずクラス名と同じにする

‣ デストラクタ‣ クラスが廃棄された際に実行される‣ 終了処理‣ 関数名は「~クラス名」にする

コンストラクタとデストラクタ‣ Particleクラスは完成したものをあらかじめ用意してきました‣ これを活用していきます!!

Particleクラスの実装‣ ヘッダーファイル:Particle.h#pragma once#include "ofMain.h"

class Particle {public:!! ofVec2f pos;! ofVec2f vel;! ofVec2f frc;! float damping;!! Particle();! ~Particle();! void resetForce();! void addForce(float x, float y);! void addDampingForce();! void setInitialCondition(float px, float py, float vx, float vy);! void update();! void draw();!};

Particleクラスの実装‣ 実装ファイル:Particle.cpp#include "Particle.h"

//コンストラクタ(初期化)Particle::Particle(){! setInitialCondition(0,0,0,0);! damping = 0.01f;}

//デストラクタ(終了処理)Particle::~Particle(){}

//力(加速度)をリセットvoid Particle::resetForce(){ frc.set(0,0);}

//力を加えるvoid Particle::addForce(float x, float y){ frc.x = frc.x + x; frc.y = frc.y + y;}

Particleクラスの実装‣ 実装ファイル:Particle.cpp//抵抗力の計算void Particle::addDampingForce(){ frc.x = frc.x - vel.x * damping; frc.y = frc.y - vel.y * damping;}

//初期状態を設定void Particle::setInitialCondition(float px, float py, float vx, float vy){ pos.set(px,py);! vel.set(vx,vy);}

//更新void Particle::update(){!! vel = vel + frc;! pos = pos + vel; }

//描画void Particle::draw(){ ofCircle(pos.x, pos.y, 3);}

testAppを設計‣ testAppから、Particleを呼びだしてみる‣ Particleクラスをインスタンス化、名前を「p」に

‣ testAppのsetup()内‣ Particleを初期設定:p.setInitialCondition()

‣ testAppのupdate()では…‣ Particleの力をリセット:p.restForce()‣ Particleの抵抗力を計算:p.addDampingForce()‣ Particleの位置を更新:p.update()

‣ testAppのdraw()で‣ Particleを描画:p.draw()

testAppクラスの実装‣ ヘッダーファイル:testApp.h#pragma once

#include "ofMain.h"#include "Particle.h"

class testApp : public ofSimpleApp{!public: void setup(); void update(); void draw(); void keyPressed (int key); void keyReleased (int key); void mouseMoved(int x, int y ); void mouseDragged(int x, int y, int button); void mousePressed(int x, int y, int button); void mouseReleased(); //クラスParticleをインスタンス化 Particle p;};

testAppクラスの実装‣ 実装ファイル:testApp.cpp#include "testApp.h"

void testApp::setup(){!! ofSetVerticalSync(true);! ofSetFrameRate(60);! ofBackground(0, 0, 0);! p.setInitialCondition(ofGetWidth()/2, ofGetHeight()/2, ofRandom(-10,10), ofRandom(-10,10));}

void testApp::update(){! p.resetForce();! p.addDampingForce();! p.update(); }

void testApp::draw(){! ofSetColor(255, 255, 255);! p.draw();}

...《後略》...

testAppクラスの実装‣ 完成

インタラクションを追加‣ マウスをクリックすると、クリックした位置からパーティクルを放出するように改造

‣ 参考:testAppのマウスに関するインタラクション‣ void mouseMoved(int x, int y );‣ マウスを移動

‣ void mouseDragged(int x, int y, int button);‣ マウスをドラッグ

‣ void mousePressed(int x, int y, int button);‣ マウスのボタンを押した瞬間

‣ void mouseReleased();‣ マウスの押していたボタンを離した瞬間

testAppクラスの実装‣ testApp.cppに、下記の処理を追加するvoid testApp::mousePressed(int x, int y, int button){! p.setInitialCondition(x,y,ofRandom(-10,10), ofRandom(-10,10));}

testAppクラスの実装‣ 完成:マウスを画面上でクリックしてみる

クイズ:重力を追加する‣ testAppを少しだけ変更して、パーティクルに重力を適用してみる

‣ 重量とは、常にパーティクルに一定に係りつづける力‣ Particleには既に力を加える関数addForce()を実装済み

重力 (Velocity)

複数のパーティクルを同時に動かす静的配列(Array)と、動的配列(Vector)

複数のパーティクルを同時に動かす‣ 複数のパーティクルを同時に動かすようにする‣ パーティクルのオブジェクトを格納するやりかたに工夫が必要‣ 複数のオブジェクトを格納するには、配列を利用‣ 配列 = 値を格納するロッカーのようなもの

p[0]

p[1]

p[2]...

配列

NUM個

静的(固定長)配列と、動的(可変長)配列‣ 要素の数が固定された配列と、必要に応じて自由に長さをかえることのできる配列がある

‣ 静的配列:Array - 配列の数をあらかじめ指定する必要‣ 例:Particleクラスのオブジェクトpを100個格納

‣ 動的配列列:Vector - 配列の数を指定する必要はない‣ 例:Particleクラスのオブジェクトpを動的配列に

Particle p[100];

vector <Particle> p;

複数のパーティクルを同時に動かす(Array版)‣ まずは静的配列(Array)で実装してみる‣ Particleクラス(Particle.hとParticle.cpp)はそのまま変更なしに使用可能

p[0]

p[1]

p[2]...

Particle p[NUM]

NUM個

p[NUM]

複数のパーティクルを同時に動かす(Array版)‣ 配列(Array)に格納した、大量のパラメータを使用して、同時にパーティクルを動かすには?

‣ くりかえしの構文を使用する

‣ C++ では for文を使うと便利!!

‣ for文の書きかた

‣ 初期化式:初期化の際の条件式‣ 継続条件式:繰り返しを継続する条件式‣ 再初期化式:繰り返されるたびに実行される式

繰り返し

for (初期化式; 継続条件式; 再初期化式) { 文;}

/* 100回「+」の文字を出力する */

for (int i = 0; i < 100; i++) {

! cout << "+";

}

‣ for分を用いた、繰り返しの例 1

繰り返し

複数のパーティクルを同時に動かす(Array版)‣ パーティクルの位置の更新 (update) と 描画 (draw) を複数同時に動くように書き換える

‣ 配列と for文がポイント

void testApp::update(){! p.resetForce();! p.addForce(0, 0.1);! p.addDampingForce();! p.update();}

複数のパーティクルを同時に動かす(Array版)‣ update() の部分は、配列とfor文でこうなる

void testApp::update(){! for (int i = 0; i < NUM; i++) {! ! p[i].resetForce();! ! p[i].addForce(0, 0.1);! ! p[i].addDampingForce();! ! p[i].update();! }}

void testApp::draw(){! ofSetColor(255, 255, 255);! p.draw();}

複数のパーティクルを同時に動かす(Array版)‣ draw() も同様に

void testApp::draw(){! ofSetColor(255, 255, 255);! for (int i = 0; i < NUM; i++) {! ! p[i].draw();! }}

複数のパーティクルを同時に動かす(Array版)‣ ヘッダーファイル:testApp.h#pragma once

#include "ofMain.h"#include "Particle.h"

#define NUM 100

class testApp : public ofSimpleApp{!public: void setup(); void update(); void draw(); void keyPressed (int key); void keyReleased (int key); void mouseMoved(int x, int y ); void mouseDragged(int x, int y, int button); void mousePressed(int x, int y, int button); void mouseReleased(); //クラスParticleの配列 (NUM個) Particle p[NUM];};

複数のパーティクルを同時に動かす(Array版)‣ 実装ファイル:testApp.cpp#include "testApp.h"

void testApp::setup(){!! ofSetVerticalSync(true);! ofSetFrameRate(60);! ofBackground(0, 0, 0);}

void testApp::update(){ for (int i = 0; i < NUM; i++) { p[i].resetForce(); p[i].addForce(0, 0.1); p[i].addDampingForce(); p[i].update(); }}

void testApp::draw(){! ofSetColor(255, 255, 255); for (int i = 0; i < NUM; i++) { p[i].draw(); }}

複数のパーティクルを同時に動かす(Array版)‣ 実装ファイル:testApp.cpp

...《中略》...

void testApp::mousePressed(int x, int y, int button){ for (int i = 0; i < NUM; i++) { p[i].setInitialCondition(x, y, ofRandom(-10,10), ofRandom(-10,10)); }}

複数のパーティクルを同時に動かす(Array版)‣ 完成:画面上でマウスクリック!

複数のパーティクルを動かす(vector版)‣ 同じ動きを、動的配列(Vector)で置き換える

particles[0]

particles[1]

particles[2]...

Vector <Particle> particles

数は可変

複数のパーティクルを動かす(vector版)‣ Vectorの配列要素の操作

‣ 配列の末尾に要素を追加 → push_back()‣ 例:Particleのオブジェクトpを、particlesに追加

‣ 配列の末尾に要素を削除 → pop_back()

‣ 配列の全ての要素をクリア → clear()

particles.push_back(p);

particles.clear();

particles.pop_back();

複数のパーティクルを動かす(vector版)‣ ヘッダーファイル:testApp.h#pragma once#include "ofMain.h"#include "Particle.h"

#define NUM 100

class testApp : public ofSimpleApp{!public: void setup(); void update(); void draw(); void keyPressed (int key); void keyReleased (int key); void mouseMoved(int x, int y ); void mouseDragged(int x, int y, int button); void mousePressed(int x, int y, int button); void mouseReleased(); //クラスParticleの動的配列particles vector <Particle> particles;};

複数のパーティクルを動かす(vector版)‣ 実装ファイル:testApp.cpp#include "testApp.h"

void testApp::setup(){!! ofSetVerticalSync(true);! ofSetFrameRate(60);! ofBackground(0, 0, 0);}

void testApp::update(){ for (int i = 0; i < particles.size(); i++) { particles[i].resetForce(); particles[i].addForce(0, 0.1); particles[i].addDampingForce(); particles[i].update(); }}

void testApp::draw(){! ofSetColor(255, 255, 255); for (int i = 0; i < particles.size(); i++) { particles[i].draw(); }}

複数のパーティクルを動かす(vector版)‣ 実装ファイル:testApp.cpp...《中略》...

void testApp::mousePressed(int x, int y, int button){ particles.clear(); for (int i = 0; i < NUM; i++) { //Particleをインスタンス化 → myParticle Particle myParticle; //初期化 float vx = ofRandom(-10, 10); float vy = ofRandom(-10, 10); myParticle.setInitialCondition(x, y, vx, vy); //作成したオブジェクトを配列の末尾に追加 particles.push_back(myParticle); }}

複数のパーティクルを動かす(vector版)‣ 完成:画面上でマウスクリック!

応用編:パーティクルをどんどん追加‣ 動的配列の利点を活用‣ 最大数の制限なく、どんどんパーティクルを追加していく

‣ ユーザからのインタラクション:‣ マウスをドラッグすると、パーティクルを1つ追加‣ ドラッグしつづけると、どんどん増えていく‣ キーボードで「c」キーを入力すると、全部消去

‣ 今回もParticleクラスは一切変更の必要なし

応用編:パーティクルをどんどん追加‣ ヘッダーファイル:testApp.h#pragma once

#include "ofMain.h"#include "Particle.h"

class testApp : public ofSimpleApp{!public: void setup(); void update(); void draw(); void keyPressed (int key); void keyReleased (int key); void mouseMoved(int x, int y ); void mouseDragged(int x, int y, int button); void mousePressed(int x, int y, int button); void mouseReleased(); //クラスParticleの動的配列particles vector <Particle> particles;};

応用編:パーティクルをどんどん追加‣ 実装ファイル:testApp.cpp#include "testApp.h"

void testApp::setup(){!! ofSetVerticalSync(true);! ofSetFrameRate(60);! ofBackground(0, 0, 0);}

void testApp::update(){ for (int i = 0; i < particles.size(); i++) { particles[i].resetForce(); particles[i].addDampingForce(); particles[i].update(); }}

void testApp::draw(){! ofSetColor(255, 255, 255);! //画面左上にメッセージを表示! string message = "current particle num = " + ofToString(particles.size(),0);! ofDrawBitmapString(message, 20, 20);

for (int i = 0; i < particles.size(); i++) { particles[i].draw(); }}

応用編:パーティクルをどんどん追加‣ 実装ファイル:testApp.cpp (つづき)void testApp::keyPressed (int key){! //'c'キーでパーティクルを全部消去! if (key == 'c') {! ! particles.clear();! }! //'f'キーでフルスクリーン表示! if (key == 'f') {! ! ofToggleFullscreen();! }}

...《中略》...

void testApp::mouseDragged(int x, int y, int button){! //マウスをドラッグするとパーティクルが追加される! Particle myParticle;! float vx = ofRandom(-3, 3);! float vy = ofRandom(-3, 3);! myParticle.setInitialCondition(x, y, vx, vy);! particles.push_back(myParticle);}

...《後略》...

応用編:パーティクルをどんどん追加‣ 完成:画面上をドラッグしてパーティクルを追加

実習:パーティクルをつかって表現‣ Particleクラスと動的配列をつかって、表現してみる

‣ Patricleクラス自体を自由にカスタマイズしてもOK‣ Particleの位置情報を活用‣ 入力の方法を工夫する‣ Particleの動きを変更‣ 色や形を工夫‣ ...etc

実習:パーティクルをつかって表現‣ 応用例その1:

‣ Particleの位置に点を描くのではなく、それぞれの点を結ぶカーブを描く

‣ ofCarbeVertex() をつかう‣ testApp.cpp の draw()を書き換える

実習:パーティクルをつかって表現‣ 実装ファイル:testApp.cpp (drawのみ書き換え)...《中略》...

void testApp::draw(){! ofSetColor(255, 255, 255);! //画面左上にメッセージを表示! string message = "current particle num = " ! + ofToString(particles.size(),0);! ofDrawBitmapString(message, 20, 20);!! ofNoFill();! ofBeginShape();! for (int i = 0; i < particles.size(); i++){! ! ofCurveVertex(particles[i].pos.x, particles[i].pos.y);! }! ofEndShape();}

...《後略》...

実習:パーティクルをつかって表現‣ 完成:画面上をドラッグして線を描く

実習:パーティクルをつかって表現‣ 応用例その2:

‣ ビットマップ画像でパーティクルを描く‣ ofImageを使うと、外部画像ファイルを読みこんで表示可能‣ ofImageの詳細の解説は、また別途行う予定

‣ パーテイクルの位置に、ビットマップ画像を配置していく‣ X座標 → particles[i].pos.x;‣ Y座標 → particles[i].pos.y;

実習:パーティクルをつかって表現‣ ヘッダーファイル:testApp.h#pragma once

#include "ofMain.h"#include "Particle.h"

class testApp : public ofSimpleApp{!public: void setup(); void update(); void draw(); void keyPressed (int key); void keyReleased (int key); void mouseMoved(int x, int y ); void mouseDragged(int x, int y, int button); void mousePressed(int x, int y, int button); void mouseReleased(); //クラスParticleの動的配列particles vector <Particle> particles;! //ビットマップ画像! ofImage img;};

実習:パーティクルをつかって表現‣ 実装ファイル:testApp.cpp (1 / 3)#include "testApp.h"

void testApp::setup(){!! ofSetVerticalSync(true);! ofSetFrameRate(60);! ofBackground(0, 0, 0);! ofEnableBlendMode(OF_BLENDMODE_ADD);

! //イメージファイルを読込み! img.loadImage("particle32.png");}

void testApp::update(){ for (int i = 0; i < particles.size(); i++) { particles[i].resetForce(); particles[i].addDampingForce(); particles[i].update(); }}

実習:パーティクルをつかって表現‣ 実装ファイル:testApp.cpp (2 / 3)void testApp::draw(){! //画面左上にメッセージを表示! ofSetColor(255, 255, 255);! string message = "current particle num = " + ofToString(particles.size(),0);! ofDrawBitmapString(message, 20, 20);

! //パーティクルの位置に画像を表示! for (int i = 0; i < particles.size(); i++){! ! float posx = particles[i].pos.x - 16;! ! float posy = particles[i].pos.y - 16;! ! img.draw(posx, posy);! }}

void testApp::keyPressed (int key){! //'c'キーでパーティクルを全部消去! if (key == 'c') {! ! particles.clear();! }! //'f'キーでフルスクリーン表示! if (key == 'f') {! ! ofToggleFullscreen();! }}

実習:パーティクルをつかって表現‣ 実装ファイル:testApp.cpp (3 / 3)

【中略】

void testApp::mouseDragged(int x, int y, int button){! //マウスをドラッグするとパーティクルが追加される! Particle myParticle;! float vx = ofRandom(-1, 1);! float vy = ofRandom(-1, 1);! myParticle.setInitialCondition(x, y, vx, vy);! particles.push_back(myParticle);}

【後略】

実習:パーティクルをつかって表現‣ 完成:画面上をドラッグすると画像が表示される

休憩

Recommended