メ デ ィア ア ー ト の た め の openFrameworks プ ロ グ ラミン グ 入 門 田所 淳・比嘉了・久保田晃弘 Beyond Interaction - PDF edition 2012年12月12日作成 本ファイルは、2010年2月発行書籍『Beyond Interaction ―メディアアートのためのopenFrameworksプログラミン グ入門』をPDF化したものです。 2012年12月現在最新版であるopenFrameworks v0073に 対応した本書のサンプルコードは、下記URLからダウンロー ド可能です。 https://github.com/tado/BeyondInteraction Beyond Interaction―メディアアートのための openFrameworksプログラミング入門 仕様 B5正寸・並製本 サイズ: 182mm×257mm×21mm ページ数: 304p 重さ: 680g 用紙 カバー: アラベール ホワイト 菊 T 90.5kg 表紙: 地券紙 L T 18.5kg 見返し: 色上質山吹 四六 T 特厚口 本文: OKいしかり B T 71kg Beyond Interaction - PDF edition by Atsushi Tadokoro, Satoru Higa, Akihiro Kubota, BNN, Inc. is licensed under a Creative Commons 表示 - 非営利 - 継承 3.0 非移植 License. 撮影:水野聖二 PDF edition へのまえがき 『Beyond Interaction―メディアアートのための openFrameworksプログラミング入門』が発刊された のが2010年。当時v0062だったopenFrameworks は、2012年12月現在v0073となり、様々な機能 が追加、拡充されています。この約3年間で日本 でのopenFrameworksをとりまく状況も大きく変化 し、以前はほとんど知られていなかったこの開発環 境も、徐々に認知度を増してきています。メディア アートの作品はもちろん、映像作品や広告分野で も、openFrameworksを活用したプロジェクトをた びたび見かけるようになりました。 筆者自身もこの書籍の出版が縁となり、何度かの openFramworksのワークショップの講師を務めさ せ て い た だく機 会 に 恵 ま れ、 そこで 多 くの openFrameworksユーザーとふれあうことができ、 日本でのコミュニティの拡がりをひしひしと感じて います。 openFrameworksは現在も貪欲に様々な機能を取 り込んで、 「万能ナイフ」のような強力な開発フレー ムワークとして発展を続けています。しかし、機能 が拡充していっても、openFrameworksの簡便性 や開発の利便性は以前と変わることはありません。 今からopenFrameworksの世界に飛び込んでも、 すぐに最先端の技術や表現に到達することが可能 です。既にopenFrameworksを始めている方も、 まだこれからの方も、ぜひ一緒にこのムーブメント に加わり、さらに発展させていきましょう! 2012年12月7日 田所 淳 メ デ ィア ア ー ト の た め の openFrameworks プ ロ グ ラミン グ 入 門 田所 淳・比嘉了・久保田晃弘 Beyond Interaction – Introductory of openFrameworks Programming for Media Art by Atsushi Tadokoro, Satoru Higa, Akihiro Kubota Copyright © 2010 Atsushi Tadokoro, Satoru Higa, Akihiro Kubota Published in 2010 by BNN, Inc. All rights reserved. No part of this publication may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopy, recording or any information storage and retrieval system, without prior permission in writing from the publisher. 本書の一部または全部について、個人で使用するほかは、 (株)ビー・エヌ・エヌ新社および著作権者の承諾を得ずに 無断で複写・複製することは禁じられております。 はじめに 現在、コンピュータを用いて何か表現をしようとす います。制作の過程を楽しみながら、結果として る際、ドローイング、画像処理、アニメーション、 実践的な知識も身につけていくことを目標にしてい 映像編集などそれぞれの用途に特化したアプリケ ます。もちろん、他の言語や環境である程度のプ ーションを使用して制作するのが一般的でしょう。 ログラミングのスキルを既に身につけている方にと 様々な高機能のアプリケーションはすぐに誰にでも っても、openFrameworksのパワフルな環 境は、 入手可能で、そうした専用のツールを使うことでハ 新たな発見をもたらしてくれると確信しています。 イレベルなアウトプットを得ることが可能です。もち ろん、こうした専用のアプリケーションはとても便 本書の「Beyond Interaction」というタイトルには、 利で素晴しいものです。しかし、その一方で、思 既存のインタラクティブという概念を超えた新たな 考がアプリケーションの枠内に納まってしまい、表 表現の可能性を切り開いていきたいという願いが 現の足枷になっている場合もあるのではないでしょ 込められています。現在、私たちが使用しているパ うか。気がつくと、アプリケーションのメニューの ーソナルコンピュータの性能は、一昔前であればス 中だけで発想してしまってはいないでしょうか。 ーパーコンピュータと言っても差し支えないほどの コンピュータは既存の道具を超える可能性を秘め メタファーにしたGUIの概念や、マウスやキーボー ています。それは、コンピュータ自身が道具を生み ドでアクションを送ると一対一に対応したリアクシ 出すことができるという、メタな道具だというところ ョンが返されるという古典的なインタラクションの 性能を備えています。その一方で、デスクトップを です。コンピュータは表現のための仕組み自体を 考え方は、長い間変化のないまま使用され続けて 新たに創造することが可能なのです。そして、その います。もちろん、古典的なインタラクションは今 際に必要となるのがプログラミングのスキルです。 でも有用で必須のものでしょう。しかし、手元にあ る超高速のマシンの性能を活かしたより高次の新 本書『Beyond Interaction―メディアアートのた めのopenFrameworksプログラミング入門』は、 たな表現の可能性が、まだまだたくさん残されてい るのではないでしょうか。アルゴリズムを用いたジ 「openFrameworks」というプログラミング 言 語 ェネラティブ(generative, 生成的)な表現、映像 C++のフレームワークを使用して、プログラミング や音声をリアルタイムに解析し加工したものを再生 の初歩から高度な応用まで、具体的なサンプルを 成する分析−再合成といったアプローチ、セル・ 作成しながら実践的に学んでいくことを目的にして オートマトンやカオスなどプログラムの作者の予想 います。プログラミングの経験のない初心者から、 を超えて自律的に運動するアルゴリズム、マシンの メディアアートに興味を持ち自分で何かを作りたい 性能をフルに活用した新たな表現など、既存のイ と考えている方、ProcessingやFlashなど他の環 ンタラクションを超えた多くの表現の可能性が存在 境は経験しているがより強力なプログラミング環境 します。そうした未開の表現分野を切り開く強力な を探し求めている方まで、多くの方々にとって有益 武器がプログラミングの知識であり、そのための道 な情報を提供したいと考えています。 具がopenFrameworksなのです。 特にプログラム未経験の初心者の方は、プログラ 本書をきっかけに、より多くの方々がプログラムの ムというと高度な知識を要するものと考え、敬遠し 魅力を知り、既存のインタラクションを超えた新た てしまいがちです。しかし、何も恐れることはあり な次元の開拓者になっていただければと願っていま ません。本書は、初めてプログラミングをするとい す。ぜひ一緒にopenFrameworksのプログラミン う方を念頭に、プログラミングの初歩から順を追っ グを楽しみましょう。 て解説をしていきます。また、抽象的な概念をでき るだけ視覚的に具体的に理解できるよう工夫して 2010年1月 著者を代表して 田所 淳 welcome Zachary Lieberman openFrameworks開発者 openFrameworks is a toolkit for creative coding openFrameworksは、プログラミング言語C++に in C++, designed to help artists, hackers, and よるクリエイティブなコーディングのためのツールキ experimenters jump in and make software with ットです。アーティストやハッカー、実験者たちが、 minimal overhead. プログラミングの専門知識がなくても容易にソフト ウェアを開発できるように作られました。 It began in 2004, when I was teaching a group of graduate students at Parsons School of openFrameworksの始まりは、2004年に私がニ Design in New York City, and I wanted to give ューヨークにあるパーソンズの大学院で教えていた them a set of tools, similar to the kinds of tools I ときに ります。学生達に、彼等の作品を制作で was using as a practicing artist, to make their きるように、私自身がアーティストとして使っていた work with. I had just started teaching and other ものと同じようなツールを与えたいと思いました。 professors warned me, Art students aren t going 教え始めたばかりの頃は他の教員から「アート学生 to want to learn coding, and definitely not with はコードは学びたがらないよ。ましてやC++なんて」 C++. However, my students proved them wrong. と注意されていました。しかし学生達は間違いを Not only were they able to understand and 証明してくれました。誰も予知できなかったような jump in to the code in a way no one could never やり方で、学生達はコードを理解し順応しただけ have predicted, they were able to make great, でなく、非常にクリエイティブで魅力的な作品を作 creative, compelling works. り出しました。 Since those early days, the openFrameworks team has grown (we are now three core 4|5 それ以来openFrameworksのチームは大きくなり (今は主要デベロッパーが3人になりました)、またコミュニ developers) and the community has grown, but ティも成長してきました。しかし初心者でもすぐに the roots of openFrameworks - a tool designed 制作が始められるようにという当初の目的は同じで to help beginners get up and running has す。 always stayed the same. 教えることと作品を創ることに加え、自分は伝道者 In addition to teaching and making artwork, として働いていると感じることがあります。私が伝え I sometimes feel that I am also working as a たいことは、芸術活動は他の科学と同じように一 preacher. One thing that I often preach is that 種のリサーチであるということです。企業が未来の artistic practice is a form research, just like the プロダクトを作るためのR&D(リサーチ&開発)部門 other sciences. In the way that a company を持つように、芸術活動は人類のためのR&Dであ would have a R&D (research and development) り、新しい未来を創造するためのものです。 open department, to develop products of the future, Frameworksは、この「ラボラトリー(実験室)として artistic practice is a form of R&D for humanity, のアート制作」の考えを真剣に取り組む試みです。 imaging possible futures. openFrameworks is 私たちは研究結果をオープンソースツールとして発 an attempt to take this idea of art making as 表します。これによって、コードはユース・ドリブン laboratory seriously -- we publish our research (use-driven, 実践を目的としたもの)になります。作品の in the form of open source tools. In that way, the 制作過程で学んだことをツールキットに反映させ、 code is /use driven/: we take the things we 他のユーザーの作品制作に役立ててもらうのです。 learn as we make projects and put them back into the toolkit to help others make work. もう1 つ私が伝道している考えは、DIWO(Do It With Others, みんなと一緒に)です。最近、Make magazine Another idea I preach is DIWO (Do it with やInstructablesなどによってDIY(Do it Yourself)文化 others). There's been a big push recently for が大きく浸透しています。私は、DIYの次は DIWO DIY culture (Do it Yourself), things like Make だと考えています。私たちはopenFrameworksによ magazine and Instructables. The next step is るハッカーや実験者たちの親密なコミュニティを育 doing with others. With openFrameworks, we've てるように努力しています。私たちはオンラインやワ been trying to foster a tight community of ークショップ、インフォーマルな集会、実験室など hackers and experimenters, meeting both で交流しています。光栄なことに日本でもYCAM(山 online, but also in workshops, informal 口情報芸術センター)や九州大学などでいくつかのワ gatherings, and research labs. We've even had ークショップを開催する機会に恵まれました。これ the privilege of teaching several workshops in ら の ワ ー ク シ ョップ で は 毎 回 日 本 の open Japan, including YCAM (Yamaguchi Center for Frameworksコミュニティの多様さと熱心さに興奮 Arts and Media) and Kyushu University, each (エキサイト)させられます。 time we've been excited by how diverse and enthusiastic the OF community in Japan is. 最後に私が伝えたいことは、アートを作ることは困 The last thing I often preach is that making art is を書くことも(そんなには!)難しいことではないこと、 not hard, writing software, even in C++ is not (that!) そして制作過程をできるだけ楽しむことです。皆さん hard, and we should try to have as much fun as がこの本をエンジョイし、openFrameworksを楽し 難なことではないこと、ソフトウェアを作ること、C++ possible along the way. It's my sincere hope that んで頂けることを心から願っています。そしてまだ oF you have fun with this book, that you have fun with コミュニティに参加していない人は、ようこそ! openFrameworks, and that if you are not already a part of the community, *welcome*. [翻訳:吉田佐陽子] 目次 はじめに 3 welcome 4 制作環境について/ダウンロードデータについて 10 第1章 openFrameworks入門 11 – openFrameworksについて 12 –– openFrameworks って何? 12 –– openFrameworks開発の歴史 13 –– 「糊(glue)」としてのopenFrameworks 13 –– openFrameworksの特徴―既存のインタラクションを超えて 13 – 作例とコミュニティ 15 –– openFrameworksを使用した作品例 15 –– openFrameworks情報の入手 22 – プログラムの入手とインストール 24 –– openFrameworks開発のための環境設定 24 –– openFrameworksのダウンロード 26 –– Xcodeテンプレートの入手 27 –– openFrameworksのファイル構成 27 – サンプルプログラムの起動 29 –– サンプルプログラムを起動してみる 29 Interview 01:The Graffiti Research Lab(G.R.L.) 34 第2章 openFrameworksプログラミング初級編 39 6|7 – openFrameworksプログラミングを始めよう 40 –– この章の流れ 40 – 図形を描く 41 –– 座標系について 41 –– 新規プロジェクトを作成する 42 –– 関数―処理をまとめる 44 –– 直線を描く―ofLine 46 –– 四角形を描く―ofRect 47 –– 円、楕円を描く―ofCircle、ofEllipse 49 –– 三角形を描く―ofTriangle 50 – 色の設定 52 –– コンピュータ上での色の表現 52 –– 背景色と描画色 53 –– 色を塗り分ける 54 –– 透明度の付加 56 – 数値の記憶と計算 58 –– 数値の計算 58 –– 変数について 59 –– 変数を使って図形を描く 61 –– 画面の幅と高さを取得 64 – たくさんの図形を一気に描く 66 –– 繰り返し―for文 66 –– 規則的に配置する―カウンタ変数を利用した演算 70 –– 繰り返しを繰り返す 72 – たくさんの値を記憶する―配列 74 –– 配列の概念 74 –– 繰り返しと配列の組み合わせ 75 –– 乱数の使用 76 – 移動する―アニメーション 83 –– 図形を移動する 83 –– アニメーションのプログラムの構造 83 –– 物体を直線運動させる 84 – 「もし○○なら××せよ」 条件分岐― 86 –– もし画面からはみ出たら反対から出現させよ 86 –– 条件分岐の応用編―壁でバウンドさせる 90 – たくさんの図形を移動する 93 –– たくさんの円を同時に動かす 93 –– 配列を利用してたくさんの図形をまとめてコントロール 96 – 図形に触れる―インタラクション 103 –– インタラクションとは 103 –– マウスの位置の検知 103 –– マウスによるインタラクション 105 –– インタラクションとアニメーションを組み合わせる 107 – より高度な表現へ 112 –– 表現に現実味を持たせる 112 –– パーティクル―重力と摩擦力を導入 112 –– 動きの軌跡をフェード 116 Interview 02: 真鍋 大度 122 第3章 openFrameworksプログラミング中級編 129 8|9 – プロジェクトの構造をより深く理解する 130 –– openFrameworksのプロジェクト構造について 130 –– openFrameworksとC++の関係 130 –– オブジェクト指向プログラミングとは 130 –– オブジェクト=属性(プロパティ)+動作(メソッド) 131 –– クラス=オブジェクトの設計図 132 –– C++でのクラスの作成 133 –– ファイルを分割して管理する 139 –– UMLクラス図 141 –– クラスとしてtestAppを捉え直す 142 –– testAppはofBaseAppの子供 144 –– openFrameworksプロジェクト構造 145 – いろいろなメディアを扱う 148 –– openFrameworksで扱うことのできるメディア 148 –– 音を扱う―音とは何か 148 –– コンピュータで音を扱うには 149 –– 波形の生成―ofSoundStream 150 –– サウンドの入力―ofSoundStream 161 –– サウンドファイルの再生―ofSoundPlayer 163 –– 画像ファイルを扱う―ofImage 169 –– フォントデータを扱う―ofTrueTypeFont 174 –– 動画の再生―ofVideoPlayer 177 –– 動画のキャプチャ―ofVideoGrabber 181 – OOOF:オブジェクト指向oFプログラミング 184 –– openFrameworksでオブジェクト指向プログラミング 184 –– 新規クラスの生成 184 –– 画面の中心に円を表示する―メソッドの追加 1 185 –– 円の大きさと場所の初期値を指定する―コンストラクタの作成 188 –– 円の大きさと場所の情報を取得、設定する―セッターとゲッター 191 –– 円をアニメーションする―メソッドの追加 2 194 –– 円が拡大縮小する動きを加える―メソッドの追加 3 198 –– 画面をクリックすると、どんどん円が増殖する―複数のクラスの生成 1 202 –– 円をクリックすると、細かな円に分裂する―複数のクラスの生成 2 206 – アドオンの利用 211 –– アドオンとは 211 –– アドオンの使用 1 ―ofxBox2dを使う 211 –– アドオンの使用 2 ―ofxOpenCvを使用する 224 –– ofxBox2DとofxOpenCvを組み合せる 239 – 楽器を作る 247 –– ここまでの内容を統合する 247 –– 楽器の構想 247 –– 楽器の設計 248 –– プロジェクトの設計 249 –– 実装 251 –– プロジェクト完成 259 Interview 03: 市川 創太 262 第4章 openFrameworksプログラミング実践編 269 – プレイヤーの演奏にリアルタイムに反応するグラフィックス 270 – 被写体の軌跡をビジュアライズ 275 – 物理計算を使ったグラフィックス 283 – モーフィングのような鏡像 286 Text:計算によるデザイン(Design by Computations)―インタラクションを超えて 292 Appendix―Xcodeのテンプレートを使用せずにプロジェクトを作成する方法 298 Index 300 制作環境について 本 書 の 解 説 は、Mac OS X 10.6(Snow Leopard)+Xcode 3.2の 環 境 下 で 行っています。 openFrameworksはMac以外にもWindows、Linuxに対応しています。 ダウンロードデータについて 本書でとりあげられているサンプルプログラムは全て、Webサイトhttp://openframeworks.jp/ よりダウンロード可能です。全てのファイルはopenFrameworks ver.0.061で動作確認をしていま す。サンプルファイルは使用する環境別に分かれています。自分の環境に合わせてダウンロードし てください。 Mac OS X 10.6(Snow Leopard)+Xcode 3.2 Mac OS X 10.5(Leopard)+Xcode 3.1 Windows XP, Windows Vista, Windows 7+Visual Studio 2008 各章で解説しているプログラムのリスト番号と、プログラムのフォルダ名が対応しています。リスト 番号を確認してXcodeのプロジェクトファイル、もしくはVisual Studio 2008のプロジェクトファイ ルをダブルクリックして開いてください。 サンプルプログラムの構成は、今後のOSのアップデートやopenFrameworksのバージョンアップ に伴い変更されることがあります。詳細はダウンロードページに書かれた説明を参照するようにし てください。 ※本書に記載されたURL、バージョン等は、予告なく変更される場合があります。 ※サンプルデータを実行した結果については、著者や出版社などのいずれも一切の責任を負いかねます。 ご自身の責任においてご利用ください。 ※本書に記載されている商品名、会社名等は、それぞれの帰属者の所有物です。 10 | 11 1 openFrameworks入門 田所 淳 この章では、はじめてopenFrameworksに触れる方のための基本的な情報を扱います。 openFrameworksの成り立ちや特徴を解説し、素晴らしい作品をいくつか紹介しています。 また、openFrameworksを開発するための環境構築の方法を ステップバイステップで解説しています。 1 1–1–1 –1 openFrameworksについて openFrameworks って何? 本書では、全編にわたって「openFrameworks」について解説しています。具体的な内容に入る 前に、まずはじめにopenFrameworksとは何なのか、その概要と思想的背景について説明したい と思います。 openFrameworksという名称をそのまま直訳すると、 「開かれたフレームワーク」と解釈できるで しょう。まず、この「フレームワーク」という言葉について考えてみましょう。フレームワークとは、 一般的には「枠組み」や「仕組み」というように理解されます。しかし、ソフトウェアの分野でフレ ームワークという用語を使った場合には、アプリケーションを開発する際に頻繁に利用される機 能をまとめて、アプリケーション開発の土台として機能するようにしたソフトウェアという意味にな ります。つまりフレームワークとは、頻繁に使用する汎用的な機能は再利用して、独自に必要とな る部分だけに専念して開発することを可能とする枠組みです。 その用途に応じて、様々なフレームワークが存在します。データベースを操作するためのフレーム ワークや、Webアプリケーションを開発するためのフレームワークなど、様々な分野でフレームワ ークが開発され利用されています。 openFrameworksは、主にインタラクティブなコンテンツやメディアアートを制作することに特化し たフレームワークです。2次元や3次元の図形の描画、アニメーション、サウンドの録音と再生、 動画のキャプチャと再生、マウスやキーボードによるインタラクション、ネットワークの活用など、 マルチメディアコンテンツを制作するための様々な機能をすぐに利用できるフレームワークとして提 供されています。通常、これらの機能をプログラムするには、多くの煩雑な処理が伴います。自 分のプロジェクトで表現したいコアな部分に達するまでに、多くの労力を割かなくてはなりません。 しかし、openFrameworksを利用することで、そうした煩雑な作業の多くは簡単な命令を呼び出 すだけで実現され、より創造的な部分に専念することが可能となります。openFrameworksの力 を借りて、格段に容易に、メディアアート作品やインタラクティブデザインの制作を行うことが可 能となるのです。 openFrameworksは、Mac OS X、Windows、Linuxで動作させることが可能です。また、最新 のリリース(ver.0.061)では、Mac OS Xで開発したプロジェクトをiPhoneアプリとして書き出すこ とも可能となりました。 12 | 13 1–1–2 openFrameworks 開発の歴史 openFrameworksは、Zachary LiebermanとTheodore Watsonを中心として開発されました。 Zachary LiebermanがまだParsons School of Designのデザインとメディアアートのコースに在学 中に、フレームワークの開発が開始されています。彼は自分自身の作品の実現のためにC++を学 び始めたのですが、C++には膨大なライブラリ(頻繁に利用される複数のプログラムを、再利用 可能な形でひとまとまりにしたもの)が提供されているものの、それらを利用するための敷居は高 く、簡単に利用できる統合された環境がないことに気付きました。Ben FryとCasey Reasによっ て開発された「Processing」 (http://processing.org/)というJavaをベースにしたクリエイターのた めの創作環境を参考にして、openFrameworksが生まれました。 その後、同じParsons SchoolにいたTheodore Watsonや、Arturo Castro、Chirs O'Sheaなどと のコラボレーションによって、さらにはネットワーク上での様々なエンジニアやアーティストたちの 協力のもと、現在も開発の途上にあります。 1–1–3 「糊(glue)」としてのopenFrameworks openFrameworksの実体は、 「C++」というプログラミング言語のライブラリの集合体です。C++ は長い歴史をもつ汎用的な言語なので、様々なオープンソースのライブラリ資産があり、マルチメ ディアの用途に関しても多くのライブラリが公開され利用可能となっています。openFrameworks はそれらの既存のライブラリを利用しています。グラフィックの描画にはOpenGL、オーディオの 入出力にはRtAudio、フォントの表示にはFreeType、画像の入出力にはFreeImage、動画の再 生やキャプチャにはQuickTimeといった具合です。 openFrameworksは、それらの既存の高性能なライブラリを、整理されたかたちで、プログラミ ングの初心者にもわかりやすく利用できるように提供されている点に意義があると言えるでしょう。 Zachary Liebermanは、これを「糊(glue) 」に例えています。様々な機能を繋ぎ合わせ利用しや すくするための糊として、openFrameworksはデザインされているのです。 1–1–4 openFrameworksの特徴―既存のインタラクションを超えて C++をベースにしたopenFrameworksは、JavaをベースにしたProcessingに大きな影響を受けて います。しかしながら、その得 意とする範 囲はProcessingと比 較すると少し異なります。 openFrameworksの最大の利点は、コンピュータの性能を最大限に発揮できるという点にありま す。これは、Javaに比べてC++がより低レベル、つまりコンピュータのハードウェアに近い部分で の処理を記述できるということに起因します。例えば、大量の3Dのオブジェクトを一気に描画す る処理などは、openFrameworksが最も得意とするところです。また、画像認識などの処理を高 速に行いたい場合にもopenFrameworksは適しています。高度な物理演算や流体のシミュレーシ ョンなど、高速な計算が必要となる表現にも適した環境と言えるでしょう。 1–1 openFrameworks入門 こうした、openFrameworksに秘められたパワーは、既存のインタラクティブデザインの枠組みを 超える可能性をもっています。10個の物体をインタラクティブに操作する場合と、1,000個、 10,000個といった単位で物体を操作する場合では、その意味合いは変わってきます。ユーザー の操作に1対1で反応するという単純なインタラクションではなく、ユーザの操作をきっかけにして、 大量のオブジェクトが自律的に変化していくというような、これまでの直接操作によるインタラクシ ョンの概念を超えた表現が可能になるという点が、openFrameworksの最大の特徴であり魅力で あると言えるでしょう。 14 | 15 1 1–2–1 –2 作例とコミュニティ openFrameworksを使用した作品例 ここでは、実際にopneFrameworksを使用して制作された作品を紹介します。openFrameworks を利用することによって具体的にどのようなことができるのか、実際に制作された作品を見ながら イメージしていきましょう。 全身を使ってアクションペインティング:『Body Paint』―Mehmet Akten URL http://www.memo.tv/body_paint スクリーンの前で全身を動かすことで、画面上にインクが飛び散ったような模様が描かれます。 高度な流体のシミュレーションを行っているので、身振りのスピードや方向によって、まるで本当 にインクが壁にぶち撒けられたように、混ざり合い、飛び散り、流れ落ちる様子がリアルに再現 されています。 GPUのパワーで100 万個のパーティクル動かす: 『OpenCL in openFrameworks example』 ―Mehmet Akten URL 1–2 http://memo.tv/opencl_in_openframeworks_example_1_milion_particles openFrameworks入門 GPU(グラフィックス・プロセッシング・ユニット)とは、通常はパーソナルコンピュータやワーク ステーション等で画像処理を担当するユニットです。しかし、このデモではPCに搭載されたGPU を計算のための資源として利用して、これまでのCPU(中央演算処理装置)では不可能だった大 量の物体を高速にアニメーションさせることに成功しています。100万個のパーティクル(粒)が 秒間100フレーム以上の速度で滑らかにアニメーションしている様子がわかります。 巨大な手が現実世界に介入:『Hand from Above』―Chris O'Shea http://www.chrisoshea.org/projects/hand-from-above/ URL 街頭に設置された巨大なスクリーンには、その周囲で行き交う人々のライブ映像が映し出されて います。突然巨大な手が出現し、歩いている人々をくすぐったり持ち上げたりといったいたずらを 仕掛けてきます。まるで群集の上に見えない巨人がいて気紛れにちょっかいを出しているようで、 架空の存在との接触を通じて、公共空間の見知らぬ人同士に独特なコミュニケーションが生じて います。 大勢の鏡に監視される:『Audience』―Chris O'Shea URL http://www.chrisoshea.org/projects/audience/ ギャラリーには大量の鏡が設置されています。その中に人が入りこむと、一斉に全ての鏡が入りこ んで来た人間を注視します。鏡の中には、こちらを見つめ返す自分自身が映っています。その様 子は、鑑賞者が鏡を観ているのではなく、まるで大勢の観客に監視されているような気分に陥り ます。 16 | 17 巨大な眼との異種間交流:『Double-Taker (Snout)』― Golan Levin, Lawrence Hayhurst, Steven Benders, Fannie White URL http://www.flong.com/projects/snout/ 建物の入口の屋根の上に、目玉のついた筒状のロボットがとりつけられていて、訪問者を監視し ます。ロボットは、付近の人物の位置や動きに合わせて複雑で自律的な動きをし、アイコンタク トをとってきます。その複雑な動きは、まるで意思を持ってこちらを見つめているようで、そこには 言葉を越えたコミュニケーションが生まれているようです。 ボイスパフォーマンスと映像の融合:『Ursonography』―Golan Levin, Jaap Blonk URL http://www.flong.com/projects/ursonography/ ドイツのアーティストKurt Schwittersによる音響詩『ウルソナタ(Ursonate) 』を題材にしたオーデ ィオビジュアルパフォーマンス。ボイスパフォーマー Jaap Blonkによって朗読される詩は、コンピ ュータでリアルタイムに音声認識され、その声の音色やタイミングに同期して字幕としてスクリー ンに映し出されます。字幕は単純に画面下に表示されるのではなく、その音響詩の構造に応じて 画面上を様々な表情を見せながら動きまわります。 1–2 openFrameworks入門 手振りによるオーディオビジュアルパフォーマンス: 『The Manual Input Sessions』― Tmema (Golan Levin & Zachary Lieberman) URL http://www.tmema.org/mis/ 両手を使ったジェスチャーによる、オーディオビジュアルパフォーマンス。オーバーヘッドプロジェ クターの上に両手をかざすと、その手の形や動きがコンピュータで解析され、その場で様々な形 態の物体が生み出されます。生成された物体は画面上を動きまわり、その動きに応じて様々な音 が発生し、影と光によるリアルタイムなオーディオビジュアルパフォーマンスが展開していきます。 レーザー光線で巨大なビルにグラフィティを描く: 『L.A.S.E.R. Tag』―Graffiti Reserch Lab URL http://graffitiresearchlab.com/?page_id=76 レーザーポインタでビルの壁面や街角の看板をなぞると、まるでスプレーでグラフィティを描くよう に巨大な光の落書きをすることのできるシステム。彼らの活動の根本にはグラフィティ文化への深 い造詣があり、その行為はグラフィティで街に足跡(タギング)を残すように、テクノロジーやメデ ィアアートをハッキングしていくことに主眼が置かれています。LASER Tagをはじめとする多くのプ ロジェクトのプログラムは、オープンソースとして公式サイトからダウンロードすることができます。 18 | 19 視線でグラフィティを描く:『EyeWriter, Free Art and Technology (FAT), openFrameworks』―Graffiti Reserch Lab URL http://www.eyewriter.org/ 2003年に筋萎縮性側索硬化症(ALS)に罹患した、伝説的なグラフィティライターで活動家の Tony Quan(aka TEMPTONE)との共同プロジェクト。Tony Quanは、ALSの症状により眼球の 運動以外は自力で動かすことができなくなりました。世界中から集まったプロジェクトチームは、 彼が以前のようにまたグラフィティを描くことができるよう、眼球運動をトラッキングして、その軌 跡によって絵を描くことのできるオープンソースのシステムを構築しました。 大量のLEDで建築物をライトアップ:『Lights on』―Zachary Lieberman, Joel Gethin Lewis, Damian Stewart (YesYesNo), Daito Manabe URL http://openframeworks.info/project/lights-on/7 Lights onは、オーストリアのリンツにあるメディアアートの拠点アルスエレクトロニカセンターの建 物に取り付けた、1,085個のLEDライトを使用したオーディオビジュアルパフォーマンスです。音楽 に合わせてLEDのパターンや色がリアルタイムに変化してビルを照らし出します。このプロジェクト のために集まったメンバーは、たった3日間で全てのプログラムとサウンドを作りあげました。 1–2 openFrameworks入門 幾何学図形をジェネレート: 『Arcs 21』―Lia URL http://www.liaworks.com/theprojects/arcs-21-iphone-app/ Liaはオーストリア出身のソフトウェアアートやネットアートのパイオニアの一人です。Arcs 21はもと もとProcessingを利 用したオンライン作 品としてWebサイト上で 発 表されていたものを、 openFrameworksを利用してiPhoneアプリケーションに移植したものです。設定したパラメータ によって円弧を組み合せた美しいパターンが生成されます。 図形の位置と動きによる新しい楽器:『SynthPond』―Zach Gage URL 20 | 21 http://apps.stfj.net/synthPond/ SynthPondは、アプリとして実装された新しいミュージックシーケンサーです。平面上に配置した マーカーの位置によって、様々なパターンを演奏します。音の旋律だけでなく、その配置する位 置によって、演奏される音の空間定位やエフェクトを変化させることができます。さらには、Max/ MSPやPdなどの音響生成のためのアプリケーションと連携して、ライブパフォーマンスのコントロ ーラーとして使用することも可能となっています。 爪先大のディスプレイで生成するオブジェクト: 『F - void sample』―魚住 剛 URL http://plaza.bunka.go.jp/festival/2009/art/001217/ 暗闇の中で、設置されたわずか爪ほどの大きさの液晶ディスプレイを顕微鏡で覗きこむと、そこに は無数のオブジェクトが発生、消滅している様子を見ることができます。生成されるオブジェクトは、 無の状態から、…点、線、面、立体、超立体…と自律的に変化していきます。極端に小さなス ケールで展開される表現は、見る者の知覚を混乱させ、既存のリアリティを拡張します。 リアルタイムに収集され変容していくニュース:『全的に歪な行且 −第二犯−』―多田ひと美 URL http://tadahi.com/gw.html インターネットに接続されたコンピュータが、その日のその時間帯に配信されたニュースから1つ 記事を選び出します。選択されたニュースは構文解析され、文節単位に分解されます。分解され た文節ごとにイメージ検索を行い、元となったニュース記事を音声で読み上げるとともに、検索 された画像と画像に付随する言葉が投影されます。ニュースという多くの人が共有している情報 の基盤を変容させ文脈を破壊することで、公開された情報とプライベートな情報との関係、巨大 な情報ネットワークと個人の関係などが想起されます。 1–2 openFrameworks入門 1–2–2 openFrameworks 情報の入手 openFrameworksの最新の情報や、本書でカバーできる内容を越えた詳細な内容などは、 openFrameworksに関連するWebサイトを参照するとよいでしょう。 openFrameworks 公式サイト URL http://openframeworks.cc openFrameworksの公式Webサイトです。開発 コミュニティからのオフィシャルな情報はここから 発信されます。また、openFrameworksの最新 版や主なアドオンは、ここからダウンロードする ことができます。openFrameworksの最新の情 報は、まずこのWebサイトを調べるのがよいでし ょう。それぞれのメニューに有用な情報がたくさ ん掲載されています。 about:openFrameworksの概要です。openFrameworksの成り立ち、FAQ、関係する組 織のクレジット、ニュースなど基本的な情報がまとめられています。 setup:openFrameworksを利用するための開発環境の構築方法を、環境別に解説してい ます。 download:ここからopenFrameworksの最新版をダウンロードすることができます。 addons:「addon」とはopenFrameworksの機能拡張です。addonに関する情報がまとめら れています。 docs:openFrameworksの全ての機能についてまとめられたリファレンス(仕様についてまと められた文書)です。プログラミングをしていてわからない部分があれば、このページ を辞書的に用いることで全ての機能について調べることが可能です。 gallery:openFrameworksを使用した作品が大量に掲載されています。分野別、作家別に 最新の作品を閲覧できます。 community:openFrameworksに貢献しているアーティスト、開発者達の紹介ページです。 forum:openFrameworksに関して自由に情報交換のできる掲示板です。 wiki:wiki形式で、openFrameworksの最新の情報が公開されています。 openFrameworksフォーラム URL http://openframeworks.cc/forum/ openFrameworksのforumコーナーからリンクし ているopenFrameworksフォーラムは、open Frameworksのいちばんホットな話題を提供して い る サ イトと 言 って よ い で し ょう。open Frameworksの開発チームからの公式アナウンス 22 | 23 や初心者向けの情報、作例、イベント情報など様々な情報が掲示板形式で交換されています。 そのやりとりはとても活発で、大量の有意義な情報が世界中のopenFrameworksの開発者、ユ ーザーから発信されています。 openFrameworksの発展は、こうしたインターネットのコミュニティの力がとても大きな役割を果 たしています。様々な専門分野の知識に長けた人々の協力によって、現在もopenFrameworksは より良いものになるよう発展を続けているのです。こうしたコミュニティの協力体制によって様々な 成果が生まれる様子を、開発者の一人Zachary Liebermanは「Do It With Others」 、略して DIWO(みんなと一緒に)と呼んでいます。ありあわせの素材を利用して、一人で物を生み出すDIY (=Do It Yourself)ではなく、ネットワークを前提とした個人同士が結び付きあったコミュニティ の力で、様々な既存の技術を繋ぎ合わせて表現のための道具を生み出していくというのが、 openFrameworksの基本的な思想となっています。 こうしたDIWOの現場が、まさにこのopenFrameworksのフォーラムです。英語を使用する必要 がありますが、初心者でも勇気を出して積極的に質問すると、世界中のエキスパート達が親切に 質問に答えてくれるでしょう。 oF Unofficial URL http://openframeworks.info/ openFrameworksの公式サイトではとりあげられ ていないプロジェクトやアドオンなどが紹介され ています。また、開催されるワークショップの情 報やプログラミングのテクニックを紹介するコー ナーなど、プログラミングに有益な情報が掲載 されています。フロッキング(Flocking)という鳥 や魚の群が移動する際の動きをシミュレーション したアルゴリズムをopenFrameworksで実現する方法や、ofxBox2Dという物理法則を取り扱う ためのアドオオンの紹介など、実践的な情報に触れることが可能です。 openFrameworks.jp URL http://openframeworks.jp/ 本書の発売にあわせて、openFrameworksの日本語サイトも同時にオープンします。このサイト はopenFrameworksの日本の拠点になることを目指して活動を開始します。本家のサイトと同様 に、自由に議論のできるフォーラム、openFrameworksの最新情報を、日本のopenFrameworks コミュニティに向けて発信していく予定です。 1–2 openFrameworks入門 1 1–3–1 –3 プログラムの入手とインストール openFrameworks 開発のための環境設定 それでは、openFrameworksで開発をするための環境を構築していきましょう。openFrameworks を利用してプログラミングをするには、まず利用するオペレーティングシステムに合わせた開発環境 のインストールと設定が必要になります。ここでは、Mac OS XとWindowsでの開発環境の設定 方法について解説します。 前 節で説 明したように、openFrameworksはC++をベースにしています。そのため、open Frameworksでプログラミングをするには、C++のプログラムを実行可能な形式にするための「コ ンパイラ」というソフトウェアが必要となります。C++で書かれたプログラムはそのままでは実行す ることができません。コンパイラによって機械語のプログラムに翻訳(コンパイル)されることにより、 それぞれの環境に適した実行可能なプログラムとなります。 Mac OS XとWindowsどちらの環境でプログラムを開発をする場合でも、現在では統合された開 発環境で行うことが一般的です。統合開発環境(IDE)を利用することで、プログラムの入力や 編集のためのテキストエディタ、実行形式に変換するためのコンパイラ、作成したプログラムのエ ラーを発見および修正するためのデバッガといった従来ばらばらに利用していたものを、GUIを利 用した1つの操作体系でわかりやすく行うことが可能です。また、Mac OS X、Windowsのどちら を利用する場合でも、IDEは無料で提供されています。 まず、開発する環境に合わせて統合開発環境をインストールしましょう。 Mac OS Xの場合―Xcodeをインストールする Mac OS Xでは、Xcodeという統合開発環境がMac OS Xに付属するかたちで配布されています。 Xcodeのインストーラは、Macintoshを購入した際に付属してくるMac OS Xのインストールディス クに収録されてるので、それを利用してインストールしましょう。 Mac OS XのインストールディスクをMacのディスクドライブにセットします。ディスクが読みこまれ ると、インストーラを含むウィンドウが表示されます。 24 | 25 この中に表示されているOptional Installsフォル ダを開き、さらにその中にあるXcode Toolsフォ ルダを開きます。 そのフォルダ内にXcodeTools.mpkgというインス トーラが収録されています。ダブルクリックしてイ ンストーラを起動します。 ここからはインストーラの指示に従ってインストール を進めてください。インストールが完了すると、 Xcodeを用いた統合開発環境を利用することが可 能となります。Xcodeのアプリケーションは、 「Macintosh HD」→ 「Developer」→ 「Applications」 にインストールされます。この中にあるXcode.app をダブルクリックして起動します。Xcode.appの アイコンをDockに登録しておくと便利でしょう。 Windows の 場 合 ―Visual Studio 2008 Express Edition をインストールする Windows XP、Windows Vista、Windows 7のどれかを利用している場合は、マイクロソフトが 提供している統合開発環境であるVisual Studio 2008を利用することが可能です。Visual Studio 2008には、その機能や用途に応じて様々なエディションが存在しています。ここでは、アマチュ アや小規模なビジネス向けに無償で提供されている Visual Studio 2008 Express Editionを使用 することにします。 Visual Studio 2008 Express Editionをインストールするには、マイクロソフトのWebサイトからダ ウンロードする必要があります。まず、Visual Studio Express Editionのページにアクセスしてくだ さい。 URL 1–3 http://www.microsoft.com/japan/msdn/vstudio/express/ openFrameworks入門 この ペー ジを 見 ると、Visual Studio Express Editionは様々な製品から構成されていることが わかります。openFrameworksはC++をベースに しているので、この中のVisual C++が必要となり ます。Visual C++の説明の下にある「Webインス トール(ダウンロード)」というリンクをクリックし て、インストーラをダウンロードします。 ダウンロードが終了次第、インストールの準備 が始まります。あとはインストーラの指示に従っ て、Visual C++をインストールしてください。イン ストールが完了すると、システムのスタートメニュ ー の中に、「Visual Studio 2008 C++ Express Edition」が追加されているはずです。この中にあ る「Microsoft Visual C++ 2008 Express」を起 動すると、C++を使用した統合開発環境が起動 します。 1–3–2 openFrameworksのダウンロード 統合開発環境の準備ができたら、openFrame worksをダウンロードします。openFrameworks は、OSと開発環境に応じて様々なバージョンが 存在します。また、様々な拡張機能(アドオン) が最初からインストールされているかどうかで、 通常版(拡張機能なし) とFAT版(拡張機能あり) に分かれています。あらかじめ拡張機能がインス トールされているほうが便利ですので、ここでは FAT版をダウンロードすることにします。 URL 26 | 27 http://www.openframeworks.cc/download この中から、現在(2010年1月)の最新バージョンである、pre release v0.061をダウンロードし ます。それぞれの環境に応じたファイルをダウンロードしてください。 Mac OS X 10.5(Leopard)とXcodeで開発する場合: ) of_preRelease_v0061_osx_FAT.zip) x-code FAT (10.5( Mac OS X 10.6(Snow Leopard)とXcodeで開発する場合: ) of_preRelease_v0061_osxSL_FAT.zip) x-code FAT (10.6( Windows XP、Windows Vista、Windows 7で、Visual C++ 2008で開発する場合: visual studio 2008 FAT(of_preRelease_v0061_vs2008_FAT.zip) ダウンロードが終了したら、ZIPファイルを展開します。展開して生成されたフォルダを好きな場 所に移動します。例えば以下の場所に設定すると便利でしょう。 Mac OS Xの場合:「ホームディレクトリ」→「書類」 Windowsの場合:「マイドキュメント」 1–3–3 Xcodeテンプレートの入手 Mac OS XでXcodeを使用してopenFrameworksを開発する場合は、Xcodeのテンプレートをイ ンストールします。このテンプレートは、Xcodeを使用して新規にopenFrameworksのプロジェク トを作成する際に使用します。以下のサイトからXcodeのテンプレートをダウンロードしてください。 URL http://www.openframeworks.jp/download ZIPファイルを展開すると、Mac OS Xのバージ ョンに応じたインストーラが格納されています。 Mac OS X 10.5(Leopard)を利用の方は「open Frameworks Project Template Mac OS 10.5.pkg」 を、10.6(Snow Loeaprd)を利用の方は「open Frameworks Project Template Mac OS 10.6.pkg」 を起動してください。 1–3–4 openFrameworksのファイル構成 ダウンロードしたopenFrameworksの中身を見ていきましょう。openFrameworksのパッケージ には、様々なファイルがフォルダに分かれて格納されており、最初は少し戸惑うかもしれません。 しかし、その構成を整理して理解すれば、差し当たって必要となるものは少ないということがわか ります。openFrameworksのパッケージは、その機能ごとにフォルダに分けられています。まずは ファイルの構成を確認しましょう。 addons(アドオン):openFrameworksの拡張機能を収録。 1–3 openFrameworks入門 apps(アプリケーション):作成するアプリケーションはここに格納します。 addonsExamples:アドオン(拡張機能)を利用したサンプル。 examples:openFrameworksの各種サンプル。 libs(ライブラリ):openFrameworksの機能の核となる部分。openFrameworksのプログラ ムで使用される様々な機能のライブラリ群です。 other:その他のファイル。 of_preRelease_v0061 addons apps addonsExample libs examples other この分類の中で今後頻繁に使用するフォルダは、appsフォルダになります。これから作成していく openFrameworksのプロジェクトは、このappsフォルダ内に作成したフォルダ内に格納しなくて はなりません。examplesフォルダやaddonsExamplesフォルダはこのままの状態で残したいので あれば、自分で作成する専用の領域を用意したほうがよいでしょう。本書では、これから作成す る openFrameworksの全てのプロジェクトを「apps」→「myApps」というフォルダに格納するこ とにします。ですので、「apps」フォルダの中に「myApps」という新規フォルダを作成しておくよう にします。 of_preRelease_v0061 addons apps addonsExample examples libs other 28 | 29 myApps ※フォルダを作成し、 フォルダ名を「myApps」に 1 1–4–1 –4 サンプルプログラムの起動 サンプルプログラムを起動してみる openFrameworksのプログラムの作成を始める前に、まずはサンプルのプログラムを起動して、 その動作を確認してみましょう。 サンプルプログラムは 「1–3–4 openFrameworksのファイル構成」 (P.27)、で説明したように、appsフォルダに収録されています。この中からいくつか選んで実行し てみましょう。openFrameworksのプロジェクトを実行するには、Xcodeの場合はXcodeプロジ ェクトファイル(拡張子が「.xcodeproj」のファイル)、Visual C++の場合はVisual C++プロジェク トファイル(拡張子が「.vcproj」のファイル)をダブルクリックして起動します。 では、最初に画像の描画のサンプル「graphicsExample」を起動してみましょう。 「apps」→ 「examples」→「graphicsExample」フォルダ内にあるプロジェクトファイルをダブルクリックして 起 動 し ま す。Xcode の 場 合 は「openFrameworks.xcodeproj」、Visual C++ の 場 合 は 「graphicsExample.vcproj」になります。すると、それぞれの環境の統合開発環境が起動します。 このプロジェクトを実行するには、プロジェクトを「ビルド」する必要があります。Xcodeの場合は ウィンドウの上部にあるツールバーから「ビルドと実行」ボタンをクリックします。Visual C++では、 ツールバーから「デバッグ開始」ボタンをクリックします。 ビルドが完了すると、自動的にプログラムが実行されます。 1–4 openFrameworks入門 graphicsExampleは、openFrameworks の命令を使用して画像を描画するサンプ ルです。円、四角形、線など、基本的 な図形のアニメーションがカラーで描画 されている様子がわかります。メニューか ら「openFrameworksDebug」 →「Quit openFrameworksDebug」を選択すると、 プログラムが終了します。 examplesフォルダには、この他にも様々なサンプルが収録されています。主なサンプルをいくつか 実行してみましょう。 advancedGraphicsExample より複雑な図形の描画サンプル。色の加 算合成、図形の回転、sin関数によるア ニメーション、リサージュ図形の描画な ど、アルゴリズムを使用した表現のサンプ ルです。 eventsExample 現在の時刻の取得、実行開始してから の経過時間、マウスの位置、マウスの状 態(クリック、ドラッグなど)の取得をし ています。 audioInputExample マイクなどの音声入力デバイスから入力し たオーディオ信号の波形を視覚化してい ます。 30 | 31 audioOutputExample sin波を生成し、音として出力します。マ ウスの位置によって周波数や左右の定位 が変化します。 soundPlayerExample あらかじめ録音したサウンドファイルを再 生します。マウスボタンのクリックで再生、 マウスの位置で再生する音の種類や音の 再生スピードが変化します。 soundPlayerFFTExample 画面上に表示された円の動きに連動して、 サウンドファイルを再生します。また、再 生中の音声信号をリアルタイムに周波数 解析して、そのスペクトルを表示していま す。 imageLoaderExample 様々な形式の画像ファイル(GIF、Jpeg、 PNG)を読み込んで、画面に表示します。 1–4 openFrameworks入門 imageSaverExample キーボードで「X」キーを押すと、表示し ている画像をキャプチャして、画像ファイ ル(PNG形式)に書き出します。 fontsExample フォントファイル(TTFファイル)を読み込 んで画面に表示します。表示したフォント の回転、拡大縮小などのアニメーション も行っています。 fontShapesExample フォントファイル(TTFファイル)を読み込 み、そのフォントのデータから輪郭線の ベクトル情報を抽出しています。 textureExample 動的にビットマップのテクスチャを生成し て、描画しています。なめらかなグラデー ションの生成や透明度の変化、テクスチ ャのアニメーションなどを行っています。 32 | 33 textureScreengrabExample 画面上に表示された図形をキャプチャし、 そのデータをテクスチャとしてマッピング しています。リアルタイムに変化するアニ メーションがそのままテクスチャとしてマッ ピングされる様子がわかります。 movieGrabberExample コンピュータに接続したカメラ、または内 蔵のカメラの映像を画面に表示します。 moviePlayerExample ムービーファイル(MOVファイル)を読み 込み、画面上で再生します。また、再生 している映像のビットマップデータを解析 して、 ピクセレイト(モザイク化)しています。 このように、サンプルを一通り眺めるだけでも、図形の描画、アニメーション、フォントの利用、 音声の入出力、動画の利用など様々な機能を利用できることがわかります。様々な局面で利用し たい機能がすぐに使用できる状態で用意されているという点が、openFrameworksの最大の魅力 と言えるでしょう。 次章からは実際のプログラミングに入っていきます。この章で紹介した様々な作品と同じ環境で、 自分自身の表現が可能となります。openFrameworksの広大な世界を探検していきましょう。 1–4 openFrameworks入門 Interview 01 The Graffiti Research Lab (G.R.L.) アーティストにアイデアを与える プログラミング・ツール The Graffiti Research Lab Evan RothとJames Powderlyによって2006年に結 成される。近作にレーザーポインタで建物に光のグ ラフィティを描く 「L.A.S.E.R Tag(レーザータグ)」や LEDを 使 った「LED Throwies」 「Eyewriter」など。 グラフィティライターやアーティストに新たなコミュニ ケーションのためのオープンソース・テクノロジーを 提供することで、人々が広告や権力に取り囲まれた 環境をクリエイティブに変える力を獲得していくことを 目的に、フリー・テクノロジー/DIY精神に満ちあふ れた技術や道具の開発をおこなっている。インタビ ューには、G.R.L. UtahのMike Bacaも参加してくれた。 URL http://graffitiresearchlab.com/ 2009.10.31@CREAM ヨコハマ国際映像祭2009 聞き手:久保田 晃弘 34 | 35 ― 今日はopenFrameworksについていくつか聞かせ Evan Roth(E) 僕は Parsons The New School for Design で Zach(Zachary Lieberman)の下で勉強して てください。 いました。彼が最初にopenFrameworksを開発し Mike Baca(M) とにかく素晴らしいと思うのは、今 た頃ですから、2005年ですね。とても昔です。彼 ではTheo(Theodore Watson)のように、自分で全て のクラスでは、生徒が色々なライブラリを開発してい プログラミングをやっているアーティストがいること て、僕はタイポグラフィ・ライブラリを開発していま ですね。彼は自分でアイデアを考案して、自らコー した。昔はそのopenFrameworksのコードを持って ドを書き、最終的な段階まで作ることができる。そ いたけど、今は全部なくなってしまいました。とても してオープンソースにして公開し、他の人も利用で 良いものだったと思いますが… 僕の卒業制作は きるようにするよね。僕とEvanは、まだ彼の助け PrereleaseバージョンのopenFrameworksを書くこ を借りないとそこまではできないけど、自分たちで とでした。それ以後は、ずっとopenFrameworksを も少しは開発できますから。 使っています。 ― アーティストとプログラマーがアイデアを交換して、 ― Prerelease って0.04くらいの頃ですか? より良くすることは大事だと思いますね。 E もっと前のものです。リリースさえしていないもので M 僕はアーティストと技術者が別れているのが好きで はないです。アーティストはアイデアだけを考案し した。その当時のopenFrameworksのユーザーは、 Zachとあとは20人ほどの生徒だけでしたね。 て、そして作るのは技術者というようなね。絵描き にたとえると、ミケランジェロは彼の作品を作るた ― なるほど。 めにアシスタントがいたかもしれませんが、アシスタ ントが何をやっているかはわかっていますよね。同 M リリースするまで随分と時間がかかったんです。 様に、コードについて全く知識がないアーティスト が、プログラムによるアート作品を制作しても、彼 はアイデアをそれ以上、そのメディアで探求するこ ― openFrameworksを使った最初のプロジェクトは 何でしたか? とができません。実際には、アーティストではなく 技術者がそのアイデアを掘り下げているわけです。 M L.A.S.E.R Tag が い ちば ん 最 初 の G.R.L. の open それはバランス的におかしいですよね。その点、 Frameworksプロジェクトですね。最初のソフトウ openFrameworksは素晴らしい。なぜなら、自分 ェアはopenFrameworksのGenerative Graffitiだっ でアイデアを考案して自ら作ることができ、技術者 たから。Theoと関わるようになったのはEyebeam に聞かずとも自分のメディアやツールを知ることが で、僕らは別々のラボにいて、EvanとTheoは知り できますから。 合いだったんだけど、一度もプロジェクトアイデアに ついて深く喋ったことはなくて。でもある時期、あ ― つまり、openFrameworksのようなプログラミング・ れはTheoのアイデアだっけ? それとも君? ツールはアーティストにもアイデアを与えるというこ とですね? E 確か、ビルで何かをするっていう面白い招待を受 けたんだ。でもそのビルは僕らがやりたいことが気 M そうですね。アーティストの作品は、実際に次の に食わなかったんだよね。だからそのイベント自体 作品に絡んでいきますし、そしてさらに次へとまた をキャンセルしたんです。でも、どのみち後でやっ 続いていきます。 たけどね。そのビルは、グリッド状の窓があったん です。もしホテルに人が入って電気をつけると、自 ― EvanのopenFrameworksとの出会いを教えてくだ 動で反応する仕組みでした。 さい。第一印象はどのようなものでしたか? Interview 01 : The Graffiti Research Lab(G.R.L.) ― そのグリッドって大きなビットマップディスプレイみ にいるようになってから辞めました。 たいなイメージなのですか? (笑) E そうではなくて、絵を描くプログラムだよ。部屋の 電気がついたら、線がそこへ行き来するんです。 M 僕は2 ヶ月前にopenFrameworksを使い始めまし た。僕らのプロジェクトにはopenFrameworksを M ホテルの部屋にいる人が電気をつけたり消したりす 使ったものがたくさんあるけど、いつもTheoや ることで、窓の電気によるドローイングのようなこと ZachとかChris Sugrueに全部任せていて。でもあ をやりました。TheoはZachに師事していたから、 る時、僕もコードをさわれるようになるべきだって 彼はそのチームに所属していて、実際にそのフレー 思ったんです。そこで、コンピュータにインストール ムワークを動かしていました。それで、僕らはアイ して色々遊んでみました。僕はCプログラマーだっ デアを話し合ったり、彼がコードを書いて、フレー たし、以前はコードも書いていたんです。だからソ ムワークを使って言語をプロトタイピングしていまし フトフェアの開発については馴染みがありましたか たね。僕はその時はじめてopenFrameworksを知 ら、僕にとってはそんなに難しくなかったです。今、 りました。 僕らは色々なところを回っていて、ChrisとZachと Theoはコードを書いて、僕自身はプロジェクトを ― openFrameworksのいちばんの魅力はなんです 担当していますが、例えばこの2つの窓を一緒に見 たいと思ったときに、今では自分でコードを変えて か? 変更できます。もし、これがもっと難しいプログラ M 僕らの友達でも使えるところ(笑)。つまり、言語は ミング言語だったら大変ですけど、僕みたいにデザ 公開されているわけだから。たくさんのプログラマ インをやっている人間にとってもそこまで難しいもの ーが書いたり見たりできる。ビデオグラフィックス ではないですね。 をアウトプットしたり、あとシンプルなコンピュータ ビジョンですね。コンピュータビジョンのライブラリ は、僕らにとって最大の魅力です。 ― OpenCVライブラリですか? ― この 1LT/C (One Laser Tag Per Child) での、open Frameworksの使い方を教えていただけませんか? James Powderly(J) これは、5個のビルに同時にレ ーザータグを動かす方法を探していて、でもそうす M そう。CV系ですね。 るとラップトップを5個買わなくちゃいけなくて… そうしたらすごく高いでしょ? その時インテルから ― あなた自身もまた、プログラミングをやりますか? とても安いAtomプロセッサが出て、どんな風に動く か確かめるために買ってみたらとても良かったので、 E 前はプログラミングしていたけど、できる人と一緒 安くて誰でも使えるレーザータグ用の小さなボック スを一個作ろうと思いまして。僕は面倒くさがり屋 なので、まだプロトタイプ的な感じですが、でもこ れすごく良いんですよ。まさにopenFrameworksの ために作ったボックスですね。 ― 中見てもいいですか? J もちろん! ― これは、PCボックスですね。 36 | 37 J そう、PCが4つ入っていて、ここのボタンを押すと E あと、このプロジェクトにはとっても適しているんだ。 フルスクリーンのレーザータグができて、ここはシン フレームレートもずっと高くできますし。通常のコン グルスクリーンとか、色々なセッティングを簡単に ピュータで動かすのとは違って、これなら1秒間に 変更できます。色を変えたりだとかも。 20.5フレームも出ます。おまけに安くてとてもいいよ。 ― ランチボックスみたいですね(笑)。このプロジェク トにはどのようなコードを書いたんですか? J コードは一切書いてないですよ。他の人のコード ― つまりopenFrameworksは可能性を引き出してくれ るわけですね。 E そうですね。 を使って僕の装置で動くようにしただけです。僕は ハードウェアを作っただけです。他の人のプロジェ ― アートワークにとって速度は非常に重要ですからね。 クトを使って、他に使える何かを作るんです。 E そうですね。僕らは再生産可能なプロジェクトをや ― openFrameworksのいちばん重要なことを教えて りたいので、これだと安いコストでできます。実際、 いただけますか? この本で、若いアーティストを openFrameworksはとても速いので、システムに 勇気づけるようにしたいんです。 1,000ドルお金をかけなくても、僕の200ドルのシス テムでも十分動きます。 J 僕的には、openFrameworksはとても簡単に使え るというところがいちばんのポイントですね。予備 知識を必要としないし、とても簡単に始められるこ ― 操作もとてもシンプルですしね。簡単に使えること は重要です。このIR Rollerは? とができる。僕にとってはそこがいちばんの魅力です。 E このソフトウェアはTheoが書いたんですよ。Theo Interview 01 : The Graffiti Research Lab(G.R.L.) とZachがopenFrameworksのプログラムを作って 作るというコラボレーションがスタートし、15日間 くれました。Theoが本体のコードを書いて、Zachは 程、彼らとカリフォルニアのベニスビーチに集まりま ブラシの部分を書いたから、2つのopenFrameworks した。 を使っていることになりますね。 PLAYSTATION Eyeという、安いビデオゲーム(プレ ― Eyewriterについて伺いたいと思います。Zachが今 イステーション 3)用のUSBカメラを使ったんです。こ 年の6月に東京に来日して、Eyewriterのプロジェク れはクローズドアーキテクチャなのですが、でもた トを紹介してくれたので、その時からとても興味が くさんのハッカーがこれを使っていろいろやっている あります。 ので、APIをオープンにする方法を見つけました。 小さなブラケットみたいなサブシステムを安いサン M Eyewriterのプロジェクトは、Tony Quanという70 グラスに取り付けて、あとは全てプログラムでやっ 年後期から90年代までロサンゼルスで活動してい ています。ソフトウェアがユーザーの目とスクリーン たグラフィティライターにインスパイアされて作った を計算し、イラストレーターみたいなものをインタ んです。彼はある時、筋萎縮側索硬化症と診断さ ーフェイスにしています。Tonyは病院にいながら、 れました。その後、症状は徐々に進行していき、 そのシステムを使ってグラフィティを描くことができ 喋ることも食べることもできず、目以外すべてを動 ます。 かすことができなくなりました。彼は、10年前と変 わらず才能あるクリエイティブな人ですが、7年以 今は、他のデベロッパーにこれを使って色々試して 上体の自由がきかない状態です。 もらっています。openFrameworksはオープンアー キテクチャですので。 例えば、アーティストが きっかけは、Mick Ebelingという人とノースカロラ Eyewriterを使ってEmailをチェックするとかウェブ イナ で 会 ったことで す。Mick は、The Ebeling サーフしたりするとか。オープンなので、さらに色々 Groupというプロダクションの経営をしていて、色々 な人がデベロップして広がっていって欲しいと思っ なアーティストと仕事をしています。Tonyのことも ています。 知っていました。Mickは、体が動かなくなったグラ フィティライターのTonyと、グラフィティツールを作 る僕らとの間に関連を見つけたんです。Tonyが唯 ― Tonyさんがいちばんはじめに描いた文字は何だっ たのですか 一動かせる目と、僕らのレーザーを使って何かでき るのではないかと。 M Tです。Tonyのグラフィティ・ネームはTEMPTONE なので、その頭文字です。 僕らも可能だと思いました。方法はあるはずだと。 でも僕らだけでは無理でしたので、そこでZachと ― ありがとうございました。 ChrisとTheoに連絡して、Tonyが目を使って絵を 描けるようにできないかと相談したんです。そうして、 僕らがハードウェアを作り、彼らがソフトウェアを 38 | 39 [協力:山口崇洋 構成・翻訳:BNN] 2 openFrameworks プログラミング初級編 田所 淳 openFrameworksのプログラムを開発する環境が整いました。 早速プログラミングを開始して、openFrameworksの世界を探検していきましょう。 この章では、今までプログラミングをしたことのない初学者を対象に、 ステップバイステップでopenFrameworksを用いたプログラミングの基礎を解説していきます。 2 2–1–1 –1 openFrameworksプログラムを始めよう この章の流れ この章では、とても簡単なプログラムから始めて、徐々に要素を追加していくことでプログラミン グの初歩を学んでいきます。今までプログラミングを全く経験したことのない方でも理解できるよ うに進めていきます。また、一度何らかのプログラミング言語を学ぼうとして途中で挫折してして しまった経験のある方も心配ありません。openFrameworksを用いてプログラミングの概念を可 能なかぎり視覚的に表現することで、抽象的な概念を具体的なイメージで理解できるよう工夫し ています。 掲載されているサンプルプログラムは、本書のWebサイト(http://openframeworks.jp)から全て 入手可能です。必要に応じてダウンロードしてください。ただし、プログラミング言語はただソー スコードを眺めているだけではなかなか理解できないことも事実です。実際にコードをエディタ上 に打ち込んでいくことで、読んでいるだけでは気付かなかった箇所を発見することもあります。こ の章でとりあげるサンプルは短くシンプルなものがほとんどなので、可能なかぎり実際にソースコ ードを打ち込んでみて、動作を確認しながら進めていきましょう。 この章では以下のように、一連のプログラムの作成を通して、初歩的なプログラミングの知識を 説明していきます。 単純な図形を描く 位置を指定する 色を塗る たくさんの図形を一度に描く 図形を移動する 画面の端まできたら反対側から出現させる 画面の端でバウンドさせる たくさんの図形をアニメーションさせる 図形にマウスで触れる、動かす 最初は味気ないプログラムですが、動きやインタラクションを追加していくことで、徐々に複雑な サンプルへと変化していきます。生成される出力結果の変化を楽しみながら、1つずつプログラミ ングの基礎を学んでいきましょう。 40 | 41 2 2–2–1 –2 図形を描く 座標系について コンピュータ上で、ペイントソフトやドローソフトで図形を描く場合には、マウスやタブレットを用 いて実際に画面上に図形を描いていくことが可能です。しかしopenFrameworksで形を描く際に は、プログラムから数値を指定して図形を描くことになります。数値を指定して図形を描くために は、図形を描く場所とその大きさを指定する必要があります。 コンピュータの画面は、縦横にグリッド状に並んだたくさんの点の集まりから成りたっています。こ れは、無数の行と列から構成された巨大な表と考えるとわかりやすいかもしれません。このコンピ ュータ画面を構成する単位をピクセル(pixel)といいます。モニタ画面の解像度は、このピクセル が縦横にいくつ並んでいるかで表現されています。例えば、1024×768ピクセルのモニタ画面は、 1024列×768行の巨大な表と考えられます。この際、中に含まれるセルの数は786,432個になり ます。この巨大なピクセルで構成されたの表の中のセルの場所を指定することで、画面上の位置 を指定することが可能となるわけです。セルの場所を指定するには、そのセルが配置されている 列と行の数を指定すれば、特定の1つが定まります。 点の位置を明確に指定するための、数値の組のことを座標(coordinate) 、座標を表現するため のシステムのことを座標系と呼びます。コンピュータ画面の巨大なピクセルで構成された表は、縦 と横の2つの軸から構成される2次元の座標系であると考えられます。一般的に、2次元の座標 系で横の位置をx座標、縦の位置をy座標と呼び、このx座標とy座標の組み合わせ(x, y)によ って特定の点を指定します。 x openFrameworksでは、画面表示領域 の左上の点を(0, 0) 、つまりx座標0、y 座標0の点と考えます。この原点(0, 0) (0,0) から右方向にx座標は増加していき、下 方向にy座標は増加していきます。例えば y (300,200) (300, 200)という点は、画面表示領域 の左上から300ピクセル右に、200ピク セル下に移動した場所になります。 2–2 openFrameworksプログラミング初級編 2–2–2 新規プロジェクトを作成する まずはじめに、openFrameworksのプログラムを作成するためのプロジェクトを新規作成します。 Xcodeの「ファイル」→「新規プロジェクト」を選択し、新規プロジェクトのテンプレート選択画 面を開きます。次に、プロジェクトのテンプレートから「Mac OS X」→「openFrameworks」→ 「v0061 Application」を選択し、openFrameworksの新規プロジェクトを作成します。 プロジェクトを選択すると、ファイルの保存場所を指定するダイアログボックスが表示されますの で、1章の「openFrameworksのファイル構成」 (P.27)で作成した「apps」→「myApps」フォル ダ内に、「TestProject」という名前をつけて保存してください。 作成された新規プロジェクトの中には大量のファイルやフォルダが入っているので、最初は戸惑う かもしれません。しかし、この中で実際に触れるファイルはごくわずかです。プロジェクトの中の 「Testproject」→「src」フォルダに格納されている3つのファイルが、実際に編集するファイルとな っています。この章では、この3つのファイル以外には触れることはありません。 main.cpp:プロジェクト全体の起点となるファイルです。画面のサイズをここで指定します。 testApp.h:アプリケーションのヘッダファイルです。プログラムの材料一覧のような機能をは たします。 testApp.cpp:アプリケーションの処理の内容を記述する部分です。 42 | 43 main.cpp testApp.h testApp.cpp 編集するファイル openFrameworks の機能 基本的なファイル構成 では、実際にファイルの中身を見ていきましょう。 生成されたファイルの中から、プログラムの中心となるtestApp.cppファイルを表示します。ファイ ルの中身を表示するには、ウィンドウ左にあるグループとファイルのリストからtestApp.cppを選択 し、ツールバーにあるエディタボタンを押してください。 list 2–2_a #include "testApp.h" void testApp::setup(){ } void testApp::update(){ } void testApp::draw(){ } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ 2–2 openFrameworksプログラミング初級編 } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ } void testApp::mousePressed(int x, int y, int button){ } void testApp::mouseReleased(int x, int y, int button){ } void testApp::windowResized(int w, int h){ } ツールバーにある「ビルドと実行」ボタンを押すと、プロジェクトがビルドされ、プログラムが実行 されます。 LIST 2-2_A実行結果 2–2–3 関数―処理をまとめる LIST 2-2-Aの中身を見ると、 「void testApp::setup ( ) 」 「void testApp::update ( ) 」 「void testApp::draw ( ) 」 など、似たような記述が並んでいるのがわかります。これらの記述の後に中括弧「{ }」で囲まれた領域が あり、これによってプログラムは大きなブロックに分割されています。 これらの分割されたプログラム内のブロックを「関数(function) 」と呼びます。関数とは、プログ ラム中で意味や内容がまとまっている作業を、1つの手続きとしたものです。つまりこれらの一つ一 つが、処理のまとまりを表したブロックとなっています。繰り返し現れる処理を関数としてまとめる ことで、プログラムの可読性(読みやすさ)が高くなり、メンテナンスが容易になるというメリット があります。 44 | 45 関数は、一連の手続きを処理した後、最終結果としての値を出力します。この関数から返される 値を「戻り値」または「返り値」と呼びます。ただし、関数の種類によっては、何も値を戻さない ものもあります。 また、関数の種類によっては、処理に必要なパラメータを外部から入力することが可能です。こ のような関数に渡される値を「引数(ひきすう)」と呼びます。関数によっては、複数の引数を渡 す場合もあります。 関数の名前、戻り値、引数という3つの要素の定義が関数には必要となります。openFrameworks で関数を定義する際には、下記のような書式で定義します。 《戻り値の種類》《関数名》(《引数 1》,《引数 2》,《引数 3》...) { 《処理の内容》 } 戻り値が存在しな場合は「void」という記述を戻り値の種類に指定します。また、引数がない場 合には、空欄のままにしておきます。 こうした関数の基本を押さえた上で、最初に登場する関数に注目してください。 void testApp::setup(){ } この関数は、以下ののように定義されていることがわかります。 関数名:testApp::setup 戻り値:な し(void) 引数:なし つまりこの関数は、testApp::setupという名前の、関数に渡す入力も出力も存在しない関数だと いう意味になります。 testApp.cppに含まれる関数の中で、特に重要となるのは最初の3つの関数です。この3つは、そ れぞれopenFrameworksでプログラムを作成する際に重要な役割を果たしています。 testApp::setup() ― 初期化関数 setup ( ) 関数は、初期化関数、つまりプログラムを実行した際に一度だけ実行される関数になり ます。この中で、プログラム全体に関する基本設定を行います。画面書き換えの速度の設定(フ レームレート)、背景色、外部データのロードなどをここで行います。開始時に一度実行された 後は、プログラムが終了するまで再度実行されることはありません。 2–2 openFrameworksプログラミング初級編 testApp::update() ― メインループ関数 update ( ) 関数は、setup ( ) 関数が実行された後に、指定した画面書き換え速度の設定(フレー ムレート)の設定に従って繰り返し実行されます。プログラムが終了するまで、実行は繰り返し続 けられます。update ( ) 関数はdraw ( ) 関数の前に実行され、プログラムに記憶された値を更新す る際に使用されます。 testApp::draw() ― 描画関数 draw ( ) 関数は、update ( ) 関数の後に実行されます。update ( ) 関数と同様に、指定された画面 書き換えの間隔(フレームレート)でプログラムが終了するまで繰り返し実行されます。draw ( ) 関 数の中では、画面の描画に関する命令を実行することを想定されています。 メインループ 初期化 値の更新 描画 setup() update() draw() プログラム終了まで繰り返し 2–2–4 直線を描く ― ofLine まず手始めに簡単な形を描いてみましょう。いちばん単純な図形として、直線を描いてみます。直 線を描く命令は、画面の描画に関する命令になるので、testApp.cppファイルのdraw ( ) 関数の 中に書き加えます。 list 2–2_b #include "testApp.h" void testApp::setup(){ } void testApp::update(){ } void testApp::draw(){ ofLine(100, 300, 800, 500); //線を描く } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … 46 | 47 LIST 2-2_B実行結果 draw ( ) 関数の中に追加されたofLine ( ) という命令が直線を生成しています。このofLine ( ) という 命令も関数の一種になります。 openFrameworksで図形を描く際は、図形を生成する関数の後 に括弧「 ( ) 」を用いて、その関数に関するパラメータ、つまり引数を指定します。直線を描く ofLine ( ) という命令では、4つのパラメータを指定することで長方形の場所とサイズを設定してい ます。 それぞれの数値の意味は下記のようになっています。 ofLine(《始点のx 座標》,《始点のy 座標》,《終点のx 座標》,《終点のy 座標》); (x1,y1) (x2,y2) 座標や幅と高さの単位は全てピクセルです。つまりこの例の場合は、直線の始点の座標が(100, 300)ピクセルから、終点の座標(800, 500)ピクセルまで線を引くということを意味します。 また、全ての命令の終わりにはセミコロン「;」が必要です。セミコロンが入ることで、命令の終了 を意味します。文章の終わりに句読点「。」が入るのと同じ役割です。 2–2–5 四角形を描く ― ofRect 次に、この画面に長方形を追加してみましょう。長方形を描くにはofRect ( ) 関数を用います。 ofRect関数も4つの引数を持ちます。しかしその意味は直線を描くofLineとは異なります。 ofRect(《左上のx 座標》,《左上のy 座標》,《幅》,《高さ》); 2–2 openFrameworksプログラミング初級編 width height (x,y) では、左上の点の座標(200, 250)ピクセルの位置から、幅150ピクセル、高さ200ピクセルの 長方形を描いてみましょう。 list 2–2_c #include "testApp.h" void testApp::setup(){ } void testApp::update(){ } void testApp::draw(){ ofLine(100, 300, 800, 500); //線を描く ofRect(200, 250, 150, 200); //四角形を描く } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-2_C実行結果 48 | 49 2–2–6 円、楕円を描く ― ofCircle、ofEllipse 次に円を追加してみましょう。円を描く関数は2種類あります。ofCircle ( ) は縦横の半径が等しい 真円を描くことのできる関数です。引数は以下のようになっています。 ofCircle(《中心点のx 座標》,《中心点のy 座標》,《半径》); (x,y) 半径 真円ではなく、楕円を描く関数も存在します。楕円を描くにはofEllipse ( ) 関数を用います。中心 点と幅と高さを指定することで、楕円を描くことが可能となっています。ofEllipse ( ) の引数は以下 の構成となっています。 ofEllipse(《中心点のx 座標》,《中心点のy 座標》,《幅》,《高さ》); height width (x,y) では、ofCircle ( ) とofEllipse ( ) の2つの関数で、円を画面に加えてみましょう。この例では、真 円と楕円を1つずつ追加しています。 真円:中心点(450, 300)ピクセル、半径60ピクセル 楕円:中心点(550, 500)ピクセル、幅200ピクセル、高さ100ピクセル list 2–2_d #include "testApp.h" void testApp::setup(){ } 2–2 openFrameworksプログラミング初級編 void testApp::update(){ } void testApp::draw(){ ofLine(100, 300, 800, 500); //線を描く ofRect(200, 250, 150, 200); //四角形を描く ofCircle(450, 300, 60); //真円を描く ofEllipse(550, 500, 200, 100); //楕円を描く } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-2_D実行結果 2–2–7 三角形を描く ― ofTriangle 今度は三角形を追加しましょう。三角形は、 (x1,y1) 3つの点を指定して、それぞれの点を直線で 結んで描いています。3つの点を指定するので、 引数は6つになります。 (x2,y2) (x3,y3) 50 | 51 ofTriagnle(《点 1のx 座標》,《点 1のy 座標》,《点 2のx 座標》,《点 2のy 座標》, 《点 3のx 座標》,《点 3のy 座標》); では、(700, 200)、(550, 400)、(750, 380)の3点を結ぶ三角形を描いてみましょう。 list 2-2_e #include "testApp.h" void testApp::setup(){ } void testApp::update(){ } void testApp::draw(){ ofLine(100, 300, 800, 500); //線を描く ofRect(200, 250, 150, 200); //四角形を描く ofCircle(450, 300, 60); //真円を描く ofEllipse(550, 500, 200, 100); //楕円を描く ofTriangle(700, 200, 550, 400, 750, 380); //三角形を描く } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-2_E実行結果 2–2 openFrameworksプログラミング初級編 2 2-3-1 –3 色の設定 コンピュータ上での色の表現 ここまでのサンプルでは、グレーの背景に白で図形が描かれるという味気ない色彩になっていまし たが、色の設定を付加することで自由に色を塗ることが可能です。まず、コンピュータで色を表 現するというのはどういうことなのか考えたうえで、実際に設定していきます。 色について考える前提として、まず、コンピュータのスクリーン上ではどのようにして色を表現して いるのかを考えてみましょう。「2–2–1 座標系について」 (P.41)の中で、コンピュータの画面は ピクセルという構成単位が縦横に並んだ表のようなものだと説明しました。このコンピュータ画面 を構成する最小単位であるピクセルをさらに分解すると、赤(Red)、緑(Green) 、青(Blue)の 光から成りたっています。この赤緑青の3つの色は「光の三原色」と呼ばれ、この3色を混色する 、緑(Green)、 青(Blue) ことによって全ての色を表現することが可能です。光の三原色は赤(Red) のそれぞれの頭文字をとって「RGBカラーモデル」と呼ばれます。コンピュータのディスプレイは、 各ピクセルごとにRGBそれぞれの輝度を調整して色を表現しているのです。 RGBカラーモデルは、絵の具で色を混ぜ合せる場合と異なるという点に注意が必要です。光の3 原色は加法混色といって、混ぜ合わせると色が足し算されていきます。RGBを最大輝度で足し合 せると、混色の結果、白になります。それに対して、絵の具の混色は減法混色といって、元の光 を遮ることで色を混色していきます。そのため、合成の元になる三原色も光とは異なり、シアン (Cyan)、マゼンタ(Magenta)、イエロー(Yellow)によって構成されます。こうしたインクの三 原色を「色料の三原色」といい、色の頭文字をとって、CMYと略称されます。また実際の印刷の 場合には、より自然な色を表現するため黒のインクも加えられて、「CMYK」と呼ばれます。 RGB 52 | 53 CMYK 現在の典型的なディスプレイでは、1つのピクセルあたり24ビットの色の情報が表現可能です。こ の24ビットの情報を三等分して、RGBそれぞれに8ビットずつ色の階調をわりあてています。ビッ トというのは、0か1かという2進数で表現される数値です。8ビットの場合であれば、2進数で 00000000 ∼ 11111111の情報を扱えるということになります。これを私たちが普段馴染んでいる10 進数に変換すると、0 ∼ 255になり、0を含めて256段階の階調を表現できるという意味になり ます。RGBそれぞれが256階調表現できることにより、その混色の結果表現できる色数は、 16,777,216通り(2563、または224)になります。 赤(R) :8bit - 00000000 ∼ 11111111(2進数)=0 ∼ 255(10進数) 緑(G):8bit - 00000000 ∼ 11111111(2進数)=0 ∼ 255(10進数) 緑(B):8bit - 00000000 ∼ 11111111(2進数)=0 ∼ 255(10進数) 合計:24bit=224=2563=16,777,216 openFrameworksにおいても、色を表現する際は、RGBの輝度の値を0 ∼ 255の範囲で指定し ます。その結果、コンピュータで表現できる色を最大限に引き出すことが可能となっています。 2-3-2 背景色と描画色 まず全体の背景色を設定してみましょう。背景色はプログラムの初期化の際に指定したいので、 setup ( ) 関数の中で指定します。背景色を指定する関数は、ofBackground ( ) です。引数として RGB(Red, Green, Blue)を追加します。 ofBackGround(《Rの値》, 《Gの値》, 《Bの値》); 描画色は、ofSetColor ( ) 関数で指定します。ofSetColor ( ) 関数で色を指定すると、それ以降で 描いた図形が指定した色で塗り潰されます。背景色と同様に引数としてRGBで色を指定します。 ofSetColor(《Rの値》, 《Gの値》, 《Bの値》); では、前のセクションで図形を描いていったサンプルに、背景色と描画色の設定を追加してみま しょう。以下の色を指定してみます。 背景色:R = 31、G = 31、B = 31 描画色:R = 242、G = 204、B = 47 list 2-3_a #include "testApp.h" void testApp::setup(){ ofBackground(31, 31, 31); //背景色の設定 ofSetColor(242, 204, 47); //描画色の設定 2–3 openFrameworksプログラミング初級編 } void testApp::update(){ } void testApp::draw(){ ofLine(100, 300, 800, 500); //線を描く ofRect(200, 250, 150, 200); //四角形を描く ofCircle(450, 300, 60); //真円を描く ofEllipse(550, 500, 200, 100); //楕円を描く ofTriangle(700, 200, 550, 400, 750, 380); //三角形を描く } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-3_A実行結果 2-3-3 色を塗り分ける ofSetColor ( ) を初期化の際に一度だけ指定するのではなく、draw ( ) 関数内で図形を描画する 前に1つずつ指定することで、要素ごとに別々の色を指定して色を塗り分けることも可能です。直線、 四角形、円、楕円、三角形それぞれを、別々の色で塗り分けてみましょう。 54 | 55 list 2-3_b #include "testApp.h" void testApp::setup(){ ofBackground(47, 47, 47); //背景色の設定 } void testApp::update(){ } void testApp::draw(){ ofSetColor(242, 242, 242); //描画色の設定 ofLine(100, 300, 800, 500); //線を描く ofSetColor(242, 204, 47); //描画色の再設定 ofRect(200, 250, 150, 200); //四角形を描く ofSetColor(174, 221, 60); //描画色の再設定 ofCircle(450, 300, 60); //真円を描く ofSetColor(116, 193, 206); //描画色の再設定 ofEllipse(550, 500, 200, 100); //楕円を描く ofSetColor(211, 24, 24); //描画色の再設定 ofTriangle(700, 200, 550, 400, 750, 380); //三角形を描く } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-3_B実行結果 2–3 openFrameworksプログラミング初級編 2-3-4 透明度の付加 描画色のRGBの設定に加えて、透明度の設定を加えてみましょう。ofSetColor ( ) 関数の引数に4 つ目の値を追加すると、その値が透明度の設定になります。透明度を設定する4つめのチャンネ ルのことを「アルファチャンネル」と呼びます。アルファチャンネルもRGBと同様に、0 ∼ 255の範 囲で指定します。アルファチャンネルを0にすると完全に透明、255で完全な不透明な色となります。 ただし、openFrameworksでアルファチャンネルを指定するには、初期化の際にアルファチャンネルを使 用可能にする必要があります。アルファチャンネルを使用可能にするには、ofEnableAlphaBlending ( ) 関数を最初に呼び出す必要があります。一度ofEnableAlphaBlending ( ) 関数を呼び出すと、それ 以降の色の設定では全てアルファチャンネルが使用可能となります。 先程のサンプルにアルファチャンネルを設定してみましょう。次のサンプルでは、色の重なりを見 ることができるように、それぞれの図形の位置や大きさを変化させています。 list 2-3_c #include "testApp.h" void testApp::setup(){ ofBackground(47, 47, 47); //背景色の設定 ofEnableAlphaBlending(); //透明度(アルファチャンネル)を有効にする } void testApp::update(){ } void testApp::draw(){ ofSetColor(242, 242, 242, 127); //描画色の設定(アルファ付き) ofLine(100, 300, 800, 500); //線を描く ofSetColor(242, 204, 47, 127); //描画色の再設定(アルファ付き) ofRect(200, 250, 200, 300); //四角形を描く ofSetColor(174, 221, 60, 127); //描画色の再設定(アルファ付き) ofCircle(450, 300, 150); //真円を描く ofSetColor(116, 193, 206, 127); //描画色の再設定(アルファ付き) ofEllipse(550, 500, 400, 300); //楕円を描く ofSetColor(211, 24, 24, 127); //描画色の再設定(アルファ付き) ofTriangle(700, 150, 450, 400, 750, 400); //三角形を描く } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } 56 | 57 … … LIST 2-3_C実行結果 2–3 openFrameworksプログラミング初級編 2 2-4-1 –4 数値の記憶と計算 数値の計算 ここまでのサンプルでは、図形を描く際に全てのパラメータを具体的な数値で指定していました。 もちろん、この方法を用いて複雑な図形を描いていくことは可能です。しかし、描く図形が複雑 になるのに比例して、プログラムも複雑さが増大していきます。せっかくプログラムを用いて図形 を描くのであれば、より自動的な方法で複雑な図形を少ない労力で描きたいものです。 自動的に図形を描いていくためには、使用する数値を記憶して、その記憶した数値を必要に応じ 計算しながら繰り返し利用していく必要があります。この数値の記憶と計算によって、全ての値を 自分自身で入力しなくても、プログラム内で計算を行うことで自動的に値を変化させて図形を描く ことが可能となります。 このセクションでは、数値の計算を効果的に利用して、規則的に変化する図形を描いてみましょう。 ここでの例では、ちょうど画面(1024×768ピクセル)の中心に円の中心がくるように座標を計算 しています。さらに円の半径を徐々に変化させながら繰り返し円を描くことで、同心円状に円を配 置しています。 まずは簡単な四則演算を利用してみます。四則演算は一般的な数学記号で利用可能です。 +(足し算) -(引き算) *(掛け算) /(割り算) それでは、四則演算を用いて図形を規則的に描いてみましょう。 list 2-4_a #include "testApp.h" void testApp::setup(){ ofBackground(47, 47, 47); //背景色の設定 ofEnableAlphaBlending(); //透明度(アルファチャンネル)を有効にする ofSetCircleResolution(64); //円の解像度を設定 58 | 59 } void testApp::update(){ } void testApp::draw(){ ofSetColor(31, 127, 255, 127); //描画色の設定 ofCircle(1024/2, 768/2, 100); //円を描く ofCircle(1024/2, 768/2, 100+40); //半径を40増加させて円を描く ofCircle(1024/2, 768/2, 100+80); //半径を80増加させて円を描く ofCircle(1024/2, 768/2, 100+120); //半径を120増加させて円を描く ofCircle(1024/2, 768/2, 100+160); //半径を160増加させて円を描く } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-4_A実行結果 2-4-2 変数について プログラムで値を記憶して必要なときに利用するには、「変数(variable) 」を使います。変数を直 感的に理解するには、1つの値を格納することのできる箱をイメージするとよいでしょう。この箱を 利用するためには、 まず箱の名前と箱の中に入れるデータの種類を決めます。これを 「変数の宣言」 といいます。箱が用意できたら、この箱の中に実際のデータを格納し箱の名前と関連づけます。 2–4 openFrameworksプログラミング初級編 このデータを箱の中に入れる操作を「代入」といいます。一度箱の中に入れたら、あとは箱の名 前を指定することで箱の中身を見ることが可能となります。この箱の名前からデータの中身を見る ことを変数の「参照」といいます。 変数 値を格納する「箱」=変数 変数の宣言とデータ型 変数を宣言する際には、プログラムの中でどのような名前で変数を用いるのかという名前の宣言 と、どのような種類の変数を用いるのかという情報をあわせて宣言します。この変数の種類のこと を「データ型」と呼びます。openFrameworksでよく用いられるデータ型としては以下のようなも のがあります。 データ型の名前 型の意味 説明 例 bool 論理型 条件が、正しい(true)か、正しくないか(false)、とい true, false う真偽の判定に使用します。 char 文字型 アルファベットや数字などの半角英数文字に使用します。 a, b, 1, # int 整数型 小数点以下の値がない、整数値に使用します。負の値を とることも可能です。 1, 32, -120 float 単精度浮動 小数点型 小数点以下の値も含めた、実数値に使用します。 1.5, 3.14, -24.1542 変数の宣言の例 int x; //整数 float y; //実数 bool b; //論理値(ブール値) 変数の名前 変数の名称は、半角英数文字であれば、どのような名前をつけても構いません。ただし、変数 の名称は、プログラムの「読みやすさ」という観点から、できるだけその内容を類推しやすいもの にするように心がけましょう。 変数の代入 宣言した変数に値を代入するには、代入演算子「=」を用います。代入演算子は数学でいう等号 とは意味が異なるので注意が必要です。数学での等号は、イコール「=」を挟んで左辺と右辺は 等しいという意味になります。しかし、プログラミングにおいては、イコール「=」を挟んだ場合、 右辺の値を左辺の変数に代入する、という意味となります。 60 | 61 変数の代入の例 int x; // 整数 float y; // 実数 x = 10; // 整数の代入:変数xに10を代入 y = x + 0.5 // 式の代入:xに0.5加えた値をyに代入、結果として、y = 10.5 となる y = y + 2; // 自身を更新:変数yに2を加える、結果として y = 12.5 となる 2-4-3 変数を使って図形を描く 先程作成した同心円を描くプログラムを、変数を用いて書き直してみましょう。円の中心点のx座 標を「x」、y座標を「y」、円の半径を「radius」として、中心点は固定したままで半径を40ピクセ ルずつ規則的に増加させて円を描いていきます。 list 2-4_b #include "testApp.h" void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofEnableAlphaBlending(); //透明度を使用可能に ofSetColor(31, 127, 255, 127); //塗りの色を設定 ofSetCircleResolution(64); //円の解像度を設定 } void testApp::update(){ } void testApp::draw(){ float x; //円のx座標 float y; //円のy座標 float radius; //円の半径 x = 1024/2; //x座標を画面の中心に y = 768/2; //y座標を画面の中心に radius = 30; //半径の初期値を設定 radius = radius + 40; //半径を40増加させる ofCircle(x, y, radius); //1つ目の円を描く radius = radius + 40; //半径を40増加させる ofCircle(x, y, radius); //2つ目の円を描く radius = radius + 40; //半径を40増加させる ofCircle(x, y, radius); //3つ目の円を描く radius = radius + 40; //半径を40増加させる ofCircle(x, y, radius); //4つ目の円を描く 2–4 openFrameworksプログラミング初級編 radius = radius + 40; //半径を40増加させる ofCircle(x, y, radius); //5つ目の円を描く } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-4_B実行結果 さらに、円の中心点も規則的に移動させてみましょう。、図形の配置に動きが生まれてきます。 list 2-4_c #include "testApp.h" void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofEnableAlphaBlending(); //透明度を使用可能に ofSetColor(31, 127, 255, 127); //塗りの色を設定 ofSetCircleResolution(64); //円の解像度を設定 } void testApp::update(){ } void testApp::draw(){ float x; //円のx座標 float y; //円のy座標 62 | 63 float radius; //円の半径 x = 1024/2; //x座標を画面の中心に y = 768/2; //y座標を画面の中心に radius = 100; //半径を設定 ofCircle(x, y, radius); //1つ目の円を描く x = x + 20; //x座標を20増加させる y = y + 30; //y座標を20増加させる radius = radius + 40; //半径を40増加させる ofCircle(x, y, radius); //2つ目の円を描く x = x + 20; //x座標を20増加させる y = y + 30; //y座標を20増加させる radius = radius + 40; //半径を40増加させる ofCircle(x, y, radius); //3つ目の円を描く x = x + 20; //x座標を20増加させる y = y + 30; //y座標を20増加させる radius = radius + 40; //半径を40増加させる ofCircle(x, y, radius); //4つ目の円を描く x = x + 20; //x座標を20増加させる y = y + 30; //y座標を20増加させる radius = radius + 40; //半径を40増加させる ofCircle(x, y, radius); //5つ目の円を描く } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-4_C実行結果 2–4 openFrameworksプログラミング初級編 2-4-4 画面の幅と高さを取得 openFrameworksでは、現在描画している画面の幅と高さを取得する関数が用意されています。 この関数を用いることで、画面の実際の大きさを調べなくても、画面のサイズを利用して位置や 幅や高さなど図形の大きさを設定可能となります。画面の大きさを基準に位置と大きさを指定す ることで、後から全体のサイズを変更したい際にも、画面サイズの指定を変更するだけで全ての 図形要素がバランスを崩すことなく、そのまま拡大/縮小することが可能となります。 画面の幅と高さは、それぞれ次の関数で取得可能です。 ofGetWidth():画面の幅を返す ofGetHeight(): 画面の高さを返す 先程のサンプルを、画面のサイズを基準にして書き直してみましょう。 list 2-4_d #include "testApp.h" void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofEnableAlphaBlending(); //透明度を使用可能に ofSetColor(31, 127, 255, 127); //塗りの色を設定 ofSetCircleResolution(64); //円の解像度を設定 } void testApp::update(){ } void testApp::draw(){ float x; //円のx座標 float y; //円のy座標 float radius; //円の半径 x = ofGetWidth()/2; //x座標を画面の中心に y = ofGetHeight()/2; //y座標を画面の中心に radius = ofGetWidth()/40; //半径の初期値を設定 x = x + 20; //x座標を20ピクセル右へ y = y + 30; //y座標を30ピクセル下へ radius = radius + 40; //半径を画面の幅20分の1増加させる ofCircle(x, y, radius); //1つ目の円を描く x = x + 20; //x座標を20ピクセル右へ 64 | 65 y = y + 30; //y座標を30ピクセル下へ radius = radius + 40; //半径を画面の幅20分の1増加させる ofCircle(x, y, radius); //2つ目の円を描く x = x + 20; //x座標を20ピクセル右へ y = y + 30; //y座標を30ピクセル下へ radius = radius + 40; //半径を画面の幅20分の1増加させる ofCircle(x, y, radius); //3つ目の円を描く x = x + 20; //x座標を20ピクセル右へ y = y + 30; //y座標を30ピクセル下へ radius = radius + 40; //半径を画面の幅20分の1増加させる ofCircle(x, y, radius); //4つ目の円を描く x = x + 20; //x座標を20ピクセル右へ y = y + 30; //y座標を30ピクセル下へ radius = radius + 40; //半径を画面の幅20分の1増加させる ofCircle(x, y, radius); //5つ目の円を描く } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-4_D実行結果 2–4 openFrameworksプログラミング初級編 2 2-5-1 –5 たくさんの図形を一気に描く 繰り返し ― for 文 現状のサンプルプログラムを注意深く観察すると、似たような記述の繰り返しになっていることに 気がつきます。10回程度までの繰り返しであれば、ここまでのやり方のように繰り返しの数だけ似 たような命令を記述してもよいのですが、例えばこれが100回の繰り返し、1000回の繰り返しと いったように、繰り返しの数を増やしていった場合、現在のやり方では破綻してしまいます。 この似通った繰り返しの部分を、プログラミングの記述を工夫してすっきりとまとめることが可能 です。openFrameworksでは、「for文」という文を用いて繰り返しの構造を記述します。 for文の基本的な構文は以下のようになります。 for(《初期化》; 《ループの継続条件》; 《カウンタ変数の更新》){ 《文》 } 「カウンタ変数」とは、for文が繰り返されるたびにその繰り返し回数を記録するカウンタのような 役割を果たす変数です。 例えば、カウンタ変数「i」を用意して、iが0から99まで、合計100回繰り返して同じ処理をする 繰り返し文を書く場合は、以下のような構文となります。 int i; for(i = 0; i<100; i = i + 1){ 《繰り返す処理の内容》 } このとき変数iは、for文の中で繰り返し処理が1回行われるごとに、1つ増加していきます。この変 数iを効果的に用いることで、繰り返し回数に応じてパラメータを変化させることも可能となります。 また、カウンタ変数の更新の式「i = i + 1」は、「i++」と書き換えることが可能です。i++という式は、 「iに1を加える」ということを意味します。このように整数値に1を加える演算を「インクリメント」と 呼びます。以後、for文の中でカウンタ変数をインクリメントする場合は、省略した「i++」の書式 66 | 67 を使用します。 for文を用いて、先程の円を描いていくサンプルを書き換えてみましょう。このサンプルの繰り返し 処理の部分を箇条書きで書き出していくと、以下のようになります。 円の中心点の、x座標、y座標を画面の中心にする 円の半径を画面の幅の10分の1の大きさにする 指定した中心点の座標と半径で円を描く x座標を20ピクセル右へ y座標を30ピクセル下ヘ 半径を40ピクセル増加させる 指定した中心点の座標と半径で円を描く x座標を20ピクセル右へ y座標を30ピクセル下ヘ 半径を40ピクセル増加させる 指定した中心点の座標と半径で円を描く x座標を20ピクセル右へ y座標を30ピクセル下ヘ 半径を40ピクセル増加させる 指定した中心点の座標と半径で円を描く この処理の中で以下の部分が繰り返しになっていることに気がつきます。 x座標を20ピクセル右へ y座標を30ピクセル下ヘ 半径を40ピクセル増加させる 指定した中心点の座標と半径で円を描く この部分をfor文でまとめることで、プログラムは一気にシンプルなものとなります。 list 2-5_a #include "testApp.h" void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofEnableAlphaBlending(); //透明度を使用可能に ofSetColor(31, 127, 255, 127); //塗りの色を設定 ofSetCircleResolution(64); //円の解像度を設定 } 2–5 openFrameworksプログラミング初級編 void testApp::update(){ } void testApp::draw(){ float x; //画面のx座標の中心点 float y; //画面のy座標の中心点 float radius; //円の半径 int i; //for文のカウンタ変数 x = ofGetWidth()/2; //x座標を画面の中心に y = ofGetHeight()/2; //y座標を画面の中心に radius = 30; //半径を設定 //5回くりかえし for(i=0; i<5; i++){ x = x + 20; //x座標を20ピクセル右へ y = y + 30; //y座標を30ピクセル下ヘ radius = radius + 40; //半径を40ピクセル増加させる ofCircle(x, y, radius); //計算された中心点と半径で、円を描く } } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-5_A実行結果 68 | 69 一度for文を使用した構造を定義してしまえば、繰り返しの回数と変化率を調整して、同じ繰り返 しの構造を100回、1000回と一気に増やしていくことが可能となります。 試しに、プログラムの繰り返し回数と変化率を以下のように変化させてみましょう。 list 2-5_b 描画色の透明度: 1/10に。アルファ 120→12 繰り返し回数: 5回→50回 x座標の変化率: 20ピクセル→2ピクセル y座標の変化率: 30ピクセル→3ピクセル 半径の変化率: 40ピクセル→4ピクセル #include "testApp.h" void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofEnableAlphaBlending(); //透明度を使用可能に ofSetColor(31, 127, 255, 12); //塗りの色を設定 ofSetCircleResolution(64); //円の解像度を設定 } void testApp::update(){ } void testApp::draw(){ float x; //画面のx座標の中心点 float y; //画面のy座標の中心点 float radius; //円の半径 int i; //for文のカウンタ変数 x = ofGetWidth()/2; //x座標を画面の中心に y = ofGetHeight()/2; //y座標を画面の中心に radius = 30; //半径を設定 //50回くりかえし for(i=0; i<50; i++){ x = x + 2; //x座標を2ピクセル右へ y = y + 3; //y座標を3ピクセル下ヘ radius = radius + 4; //半径を4ピクセル増加させる ofCircle(x, y, radius); //計算された中心点と半径で、円を描く } } 2–5 openFrameworksプログラミング初級編 void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-5_B実行結果 2-5-2 規則的に配置する ― カウンタ変数を利用した演算 for文の繰り返し条件内で使用しているカウンタ変数を用いて、繰り返し処理の中に規則的な変 化をもたせてみましょう。例えば、カウンタ変数の値を、図形の位置だけでなく色の変化にも応 用することが可能です。 カウンタ変数を用いて、徐々に色が変化してグラデーションとなるサンプルを作成してみましょう。 list 2-5_c #include "testApp.h" void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofEnableAlphaBlending(); //透明度を使用可能に } void testApp::update(){ } void testApp::draw(){ 70 | 71 float x; //画面のx座標の中心点 float y; //画面のy座標の中心点 float w; //長方形の幅 float h; //長方形の高さ int i; //for文のカウンタ変数 w = ofGetWidth()/30.0 + 1; //長方形の幅を指定 h = ofGetHeight()/20.0 + 1; //長方形の高さを指定 x = 0; //x座標を0に y = ofGetHeight()/2 - h/2; //y座標を画面の上下の中央に //30回繰り返す for(i=0; i<30; i++){ ofSetColor(31, 127, 255/30 * i, 127); //塗りの色を変化させる x = ofGetWidth() / 30.0 * i; //x座標上に等間隔に配置 ofRect(x, y, w, h); //長方形を描く } } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-5_C実行結果 2–5 openFrameworksプログラミング初級編 2-5-3 繰り返しを繰り返す 繰り返しのfor 文の中に、さらにfor文を配置することも可能です。このようにfor 文を入れ子状に 配置すると、繰り返しの処理のユニットをさらに指定回数繰り返す、 という意味になります。例えば、 先程の色の横方向へのグラデーションのサンプルをさらに縦方向に繰り返すことで、縦横両方の 方向へグラデーションがかかった長方形で平面を埋めつくすことが可能となります。 list 2-5_d #include "testApp.h" void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofEnableAlphaBlending(); //透明度を使用可能に } void testApp::update(){ } void testApp::draw(){ float x; //画面のx座標の中心点 float y; //画面のy座標の中心点 float w; //長方形の幅 float h; //長方形の高さ int i, j; //for文のカウンタ変数 x = 0; //x座標を0に y = 0; //y座標を0に w = ofGetWidth()/30.0 + 1; //長方形の幅を指定 h = ofGetHeight()/20.0 + 1; //長方形の高さを指定 for(j=0; j<20; j++){ for(i=0; i<30; i++){ ofSetColor(255/20 * j, 127, 255/30 * i, 127); //塗りの色を変化させる x = ofGetWidth() / 30.0 * i; //x座標上に等間隔に配置 y = ofGetHeight() / 20.0 * j; //y座標上に等間隔に配置; ofRect(x, y, w, h); //長方形を描く } } } void testApp::keyPressed(int key){ 72 | 73 } void testApp::keyReleased(int key){ } … … LIST 2-5_D実行結果 2–5 openFrameworksプログラミング初級編 2 2-6-1 –6 たくさんの値を記憶する ― 配列 配列の概念 「2–4–2 変数について」 (P.59)で、変数とは箱のようなものだと解説しました。この箱には、1 つの値しか入れることができません。値が入っている変数に別の値を代入すると、最初に入って いた値は後から代入した値に置き換わり参照できなくなってしまいます。変数を使ってたくさんの 値を格納するには、その数の分の変数を用意しなくてはなりません。 例えば、4つの円の中心のx座標とy座標をそれぞれ変数に格納しようと考えたとします。必要な 数だけ変数を用意してみます。 float x0; float x1; float x2; float x3; float y0; float y1; float y2; float y3; 格納したい値の数が数個から10個くらいまでなら、このような方法で変数を用意することも可能 です。しかし、数が100個、1000個、10000個と増えていった場合、変数の宣言をするだけで 膨大な手間がかかってしまい、変数を管理、把握することも困難になっていくでしょう。 関係を持つ一連のデータを格納できる「配列」という仕組みが存在します。変数を箱とすると、 配列は箱をたくさん連結した「ロッカー」のようなものだとイメージするとわかりやすいかもしれま せん。同じ型の数値であれば、指定した数だけ一気に値を格納する領域を用意することが可能 です。先程の円の中心座標を例にすると、4つの円の中心座標を格納するには、以下の宣言をし ます。 float x[4]; float y[4]; 74 | 75 この宣言によって、以下の配列変数が作成されます。 x[0], x[1], x[2], x[3], y[0], y[1], y[2], y[3] 変数の名前の後に、大括弧「[ ]」 で囲われた連番の数字が割り振られています。この数字を添字(イ ンデックス)といい、この数字によって配列の何番目の値を参照するかを指定しています。ロッカ ーの番号をイメージするとよいでしょう。 float x[4]; x[0]; x[1]; float y[4]; x[2]; x[3]; y[0]; y[1]; y[2]; y[3]; 配列を宣言するには、配列の型と必要となる数を指定します。その構文と書式は以下のようにな ります。 《型の種類》《配列名》[《配列の最大個数》]; float x[100]; char message[20]; bool cheked[128]; 2-6-2 繰り返しと配列の組み合せ 配列は、繰り返しのための構文であるfor文の中で用いられることが多いです。for文のカウンタ変 数と配列を組み合わせることで、大量の配列の処理をシンプルなプログラムで実現可能になるか らです。例えば3の倍数を0から順番に100個生成し、配列に格納していくプログラムをfor文と配 列で書いてみます。 int multi_three[100]; for(int i = 0; i<100; i++){ multi_three[i] = i * 3; } このように、とても短くシンプルなコードで、大量の数値の処理を一気に行っていることがわかる でしょう。以降のサンプルでは、このようなfor文と配列の組み合わせが頻出します。この用法を しっかりと理解するようにしましょう。 2–6 openFrameworksプログラミング初級編 2-6-3 乱数の使用 乱数とは、一切の法則性、規則性がない数を表します。例えば、サイコロを振り続けると、1か ら6までの数の連なりを規則性なく得ることができます。このように、次の値が予測できない数列 を「乱数列」、数列の各要素を「乱数」といいます。 openFrameworksでは、ofRandom ( ) 関数を用いて、指定した値の範囲内で乱数を生成するこ とができます。ofRandom ( ) 関数の引数は下記の通りです。 ofRandom(《最小値》,《最大値》); この関数は、最小値から最大値の範囲で乱数を発生します。 乱数を用いることで、プログラミングに偶然の要素を盛り込むことが可能となります。例えば、大 まかな構造はあらかじめデザインしておき、細かなパラメータは一定の範囲内に収めた偶然に委 ねるといった方法が可能となります。こうした偶然性を利用することは、プログラミングならでは の表現方法と言えるでしょう。 乱数を効果的に取り入れた表現をいくつか試してみましょう。 乱数で位置を指定する 乱数を使用して、図形の位置を指定してみます。例として画面上にランダムに円を配置してみまし ょう。 円を描くには、x座標の中心点、y座標の中心点、半径という3つの情報が必要となります。この 3つの情報を、乱数で生成する数だけ配列として用意します。例えば、円を1000個生成するので あれば、以下の配列が必要となります。 float x[1000]; // x座標を記憶する配列1000個 float y[1000]; // y座標を記憶する配列1000個 float radius[1000]; // 半径を記憶する配列1000個 配列が用意できたら、setup ( ) 関数内で全ての円のパラメータをあらかじめ生成して記憶しておき ます。for文を用いて生成する円の個数分、繰り返しofRandom ( ) 関数で乱数を生成し、円のパ ラメータ(x座標、y座標、半径)の組み合わせを生成します。乱数で生成されたそれぞれの変 数は、配列に順番に格納されていきます。最終的に、用意された配列全てに乱数で生成された 値が代入されます。 draw ( ) 関数内では、あらかじめ生成して配列に格納した円の情報を順番にfor文を用いて読み出 し、指定された場所と半径で円を配置していきます。 以下の乱数の幅で、円を生成し配置してみましょう。 76 | 77 x座標: 0 ∼画面の幅(ofGetWidth) y座標: 0 ∼画面の高さ(ofGetHeight) 半径: 10 ∼ 40ピクセル list 2-6_a #include "testApp.h" float x[1000]; float y[1000]; float radius[1000]; void testApp::setup(){ int i; ofBackground(0, 0, 0); //背景色の設定 ofEnableAlphaBlending(); //透明度を使用可能に ofSetCircleResolution(64); //円の解像度を設定 for(i=0; i < 1000; i++){//1000個分の円の情報を生成し配列に記録していく x[i] = ofRandom(0, ofGetWidth()); //x座標 y[i] = ofRandom(0, ofGetHeight()); //y座標 radius[i] = ofRandom(10, 40); //半径 } } void testApp::update(){ } void testApp::draw(){ int i; ofSetColor(31, 63, 255, 63); //描画色の設定 for(i=0; i<1000; i++){ //あらかじめ個数分生成しておいた円情報を参照し、円を描く ofCircle(x[i], y[i], radius[i]); //円の描画 } } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … 2–6 openFrameworksプログラミング初級編 LIST 2-6_A実行結果 次の例では、画面いっぱいを使ってランダムにたくさんの直線を描いています。この場合は、初 期化の際に直線の個数分、線の始点の座標と終点の座標をランダムに生成して配列に格納し、 それを参照してdraw ( ) 関数内で、指定した個数分の直線の情報を参照し、線を描いています。 list 2-6_b #include "testApp.h" float start_x[1000]; //直線の始点 x座標 float start_y[1000]; //直線の始点 y座標 float end_x[1000]; //直線の終点 x座標 float end_y[1000]; //直線の終点 y座標 void testApp::setup(){ int i; ofBackground(0, 0, 0); //背景色の設定 ofEnableAlphaBlending(); //透明度を使用可能に ofSetLineWidth(2); //線の太さの設定 ofEnableSmoothing(); //線の描画をスムースに for(i=0; i < 1000; i++){ //線の始点と終点の座標をランダムに決定 start_x[i] = ofRandom(0, ofGetWidth()); start_y[i] = ofRandom(0, ofGetHeight()); end_x[i] = ofRandom(0, ofGetWidth()); end_y[i] = ofRandom(0, ofGetHeight()); } } void testApp::update(){ } void testApp::draw(){ 78 | 79 int i; ofSetColor(31, 127, 255, 63); //描画色の設定 for(i=0; i<1000; i++){ //あらかじめ個数分生成しておいた線の情報を参照 ofLine(start_x[i], start_y[i], end_x[i], end_y[i]); //直線の描画 } } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-6_B実行結果 乱数で色を指定する 乱数で生成した値を色に適用した場合も、面白い効果を得ることができます。次の例では、画 面の上から1ピクセルずつ横に直線を描いていき、画面を埋めつくすまで繰り返します。この直線 を描く際の色の設定をランダムにして、独特の効果を生み出しています。 list 2-6_c #include "testApp.h" int red[768]; int green[768]; int blue[768]; void testApp::setup(){ int i; 2–6 openFrameworksプログラミング初級編 ofBackground(0, 0, 0); //背景色の設定 for(i=0; i < 768; i++){ //色をランダムに生成 red[i] = ofRandom(0, 255); //レッド green[i] = ofRandom(0, 255); //グリーン blue[i] = ofRandom(0, 255); //ブルー } } void testApp::update(){ } void testApp::draw(){ int i; for(i=0; i < 768; i++){ //あらかじめ個数分生成しておいた線の情報を参照 ofSetColor(red[i],green[i],blue[i]); //描画色の設定 ofLine(0, i, ofGetWidth(), i); //横に直線を描く } } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-6_C実行結果 RGBの色の乱数の範囲を細かく規定することで、色の傾向をコントロールすることも可能です。 いくつか試してみましょう。 80 | 81 … … void testApp::setup(){ int i; ofBackground(0, 0, 0); for(i=0; i < 768; i++){ red[i] = ofRandom(0, 31); green[i] = ofRandom(63, 127); blue[i] = ofRandom(127, 255); } } … … R:0 ∼ 31の範囲/ G:63 ∼ 127の範囲/ B:127 ∼ 255の範囲に 設定した場合の実行結果 … … void testApp::setup(){ int i; ofBackground(0, 0, 0); //背景色の設定 for(i=0; i < 768; i++){ //色をランダムに生成 red[i] = ofRandom(191, 255); green[i] = ofRandom(127, 255); blue[i] = ofRandom(127, 255); } } … … 2–6 openFrameworksプログラミング初級編 R:191 ∼ 255の範囲/ G:127 ∼ 255の範囲/ B:127 ∼ 255の 範囲に設定した場合の実行結果 … … void testApp::setup(){ int i; ofBackground(0, 0, 0); //背景色の設定 for(i=0; i < 768; i++){ //色をランダムに生成 red[i] = ofRandom(0, 31); green[i] = ofRandom(31, 63); blue[i] = ofRandom(31, 63); } } … … R:0 ∼ 31の範囲/ G:31 ∼ 63の範囲/ B:31 ∼ 63の範囲に 設定した場合の実行結果 82 | 83 2 2-7-1 –7 移動する ― アニメーション 図形を移動する ここまでは、様々な形態の静止した図形を描いてきました。ここからはいよいよ動かしていきます。 複数の静止画を用意して、動きを表現する技術を「アニメーション」と呼びます。このセクション では、まずアニメーションの原理について考えていきます。そのうえで、openFrameworksでアニ メーションを作成する方法について学んでいきましょう。 多くの人は教科書のページの隅にパラパラマンガを描いたことがあるのではないでしょうか。ペー ジの端に少しずつずらした絵を描き、ページを素早くめくることで、描いた絵が動いて見えるとい うものです。これも、アニメーションの手法の1つと言えるでしょう。 パラパラマンガは、普通に本を読むようにゆっくりとページをめくっていては動いて見えません。ペ ージの端を指で押さえながら、高速でページをめくることによって、はじめて動きが知覚できます。 これは私たちの視覚の残像効果という特性を利用しています。私たちが視覚で光を見た際に、そ の光が消えた後もそれまで見ていた映像が残って見えるという現象です。この視覚の特性のため、 実際には高速で切り替わっている静止画が、なめらかに動いているように知覚されます。 コンピュータを用いてアニメーションを作成する原理も、基本的にはこれと全く同じ原理に基づい ています。複数の静止画を用意して、これらを高速で切り替えながら画面に表示することで、私 たちの視覚はそれが連続する映像と認識します。静止画がなめらかに変化するようにすることで、 図形が運動しているように知覚されます。 パラパラマンガや伝統的なセルアニメーションでは、全てのコマを1枚ずつ描いていく必要がありま す。しかし、プログラムを用いて運動の軌跡を計算することで、全てのコマを描くのではなく、動 きの原理を指定するだけで、なめらかなアニメーションを表現することが可能となります。ここでは、 簡単な図形の動きをopenFrameworksを用いてプログラムで定義することで、リアルなアニメーシ ョンを実現する方法について解説していきます。 2-7-2 アニメーションのプログラムの構造 「2–2–2 新規プロジェクトを作成する」 (P.42)で、openFrameworksを構成する3つの関数に 2–7 openFrameworksプログラミング初級編 ついて解説しました。復習すると以下のような役割をそれぞれが果しています。 setup()― 初期化関数:プログラムを実行した際に一度だけ実行される関数 update()― メインループ関数:指定した画面書き換え速度の設定(フレームレート)の設定 に従って、繰り返し実行される関数 draw()― 描画関数:画面の描画に関する命令を実行する関数 アニメーションを作成する際には、この3つの関数それぞれを活用していく必要があります。先程 のパラパラマンガの例でいうと、下記のように考えるとわかりやすいかもしれません。 setup() ― 準備:紙のサイズや色の設定、画材(色、線の太さなど)の選択、ページをめ くる速度を決定 update()― 更新:ページをめくる、ページをめくる際の変化を定義 draw()― 表示:イラストを表示する(イラストを見る) これをさらにプログラム 的 観 点 から下 記 のように書き換えてみます。これが 最 終 的 に openFrameworksでアニメーションでプログラムする際の枠組みとなります。 setup()― 画面サイズ、フレームレート、背景色の設定、描画設定、変数の初期化 update()― 変数の値の更新 draw() ― 描画 このように3つの関数の役割に注意しながら、まずは簡単なアニメーションのサンプルとして、1つ の図形が直線に沿って動いていくアニメーションを作成してみましょう。 2-7-3 物体を直線運動させる まずはじめに、円を右斜め下に直線運動させてみましょう。円を直線運動させるには、先程説明 した3つの関数で役割分担しています。具体的には、以下のように処理を振り分けます。 setup()― 初期設定:背景色の設定、フレームレート(1秒間に何回画面を書き換えるか) の設定、円のx座標とy座標を(0, 0)に初期化 update() ― 更新:円の座標を更新、x座標を3ピクセル右に、y座標を2ピクセル下に draw()― 描画:描画色を設定して、円を描く list 2-7_a #include "testApp.h" float loc_x; //円のx座標 float loc_y; //円のy座標 void testApp::setup(){ 84 | 85 ofBackground(0, 0, 0); //背景色の設定 ofSetFrameRate(60); //フレームレートを60fpsに ofSetCircleResolution(64); //円の解像度設定 loc_x = 0; //円のx座標初期位置 loc_y = 0; //円のy座標初期位置 } void testApp::update(){ loc_x = loc_x + 3; //円のx座標を3ピクセル移動 loc_y = loc_y + 2; //円のy座標を2ピクセル移動 } void testApp::draw(){ ofSetColor(31, 63, 255); //描画色の設定 ofCircle(loc_x, loc_y, 40); //円を描画 } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-7_A実行結果 プログラムを実行すると、円が右下に直線運動するはずです。ただし円は移動していき、最後に は画面の外に飛び出てしまいます。画面からはみ出た後も、円は表示されない画面の外をずっと 動き続けています。プログラムを止めるまでは、円は永遠に架空の空間を画面の外に向かって離 れていっているのです。 2–7 openFrameworksプログラミング初級編 2 2-8-1 –8 条件分岐 ―「もし○○なら××せよ」 もし画面からはみ出たら反対から出現させよ 「2–7–3 物体を直線運動させる」 (P.84)での動きを改良して、画面からはみ出ないように工夫 してみましょう。まず手始めに、上下左右の画面の端が、それぞれ反対側の端に空間がワープし て繋っていると考えてみましょう。ゲーム「パックマン」のような世界を想像するとわかりやすいか もしれません。上下左右それぞれの端は、以下のように繋っています。 画面の右端 → 画面の左端 画面の左端 → 画面の右端 画面の上端 → 画面の下端 画面の下端 → 画面の上端 この動きを、円の位置を中心に考えて、より論理的に書き下すと下記のようになるでしょう。 もし円の中心が画面右端からはみ出たら、画面の左端に移動しなさい もし円の中心が画面左端からはみ出たら、画面の右端に移動しなさい もし円の中心が画面上端からはみ出たら、画面の下端に移動しなさい もし円の中心が画面下端からはみ出たら、画面の上端に移動しなさい プログラミング言語で、このような「もし○○だったら、□□しなさい、 (そうでなければ、△△△ しなさい)」というような制御構造を表現するための仕組みとして、 「if文」というものがあります。if 文は、処理の条件となる条件式、条件に合致する倍に処理される真文、条件に合わない場合に 処理される偽文から構成されています。 openFrameworksで条件式を記述するには下記の書式を用います。 if(《条件式》){ 《真文》 } else { 《偽文》 } 86 | 87 偽文(そうでなければ、△△△しなさい)の部分が必要なければ、省略して if(《条件式》){ 《真文》 } と書くことも可能です。 このif文で処理するために、先程の条件4つを、座標を用いたより厳密な条件に整理し直してみ ましょう。 まず、円の中心座標をそれぞれ下記と定義します。 円の中心のx座標:loc_x 円の中心のy座標:loc_y また、画面の幅と高さは図形の描画の際に使用した、画面の幅と高さを取得する関数を使用し ます。 ofGetWidth():画面の幅を取得 ofGetHeight():画面の幅を取得 画面の上下左右の端の座標は下記になります。 画面の左端:x = 0; 画面の右端:x = ofGetWidth(); 画面の上端:y = 0; 画面の下端:y = ofGetHeight(); (0,0) (ofGetWidth(),ofGetHeight()) そのうえで条件を整理すると 条件 1:もし円の中心が画面右端からはみ出たら、画面の左端に移動しなさい 条件式:loc_x > ofGetWidth() 真文:loc_x = 0 2–8 openFrameworksプログラミング初級編 条件 2:もし円の中心が画面左端からはみ出たら、画面の右端に移動しなさい 条件式:loc_x < 0 真文:loc_x = ofGetWidth() 条件 3:もし円の中心が画面上端からはみ出たら、画面の下端に移動しなさい 条件式:loc_y < 0 真文:loc_y = ofGetHeight() 条件 4:もし円の中心が画面上端からはみ出たら、画面の下端に移動しなさい 条件式:loc_y > ofGetHeight() 真文:loc_y = 0 この4つの条件を、そのままupdate ( ) 関数の中に定義していきます。 list 2-8_a #include "testApp.h" float loc_x; //円のx座標 float loc_y; //円のy座標 void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofSetFrameRate(60); //フレームレートの設定 ofSetCircleResolution(64); //円の解像度設定 loc_x = 20; //円のx座標初期位置 loc_y = 20; //円のy座標初期位置 } void testApp::update(){ loc_x = loc_x + 3; //円のx座標を3ピクセル移動 loc_y = loc_y + 2; //円のy座標を2ピクセル移動 //条件1:左端→右端 if(loc_x < 0){ loc_x = ofGetWidth(); } //条件2:右端→左端 if(loc_x > ofGetWidth()){ loc_x = 0; } //条件3:上端→下端 if(loc_y < 0){ 88 | 89 loc_y = ofGetHeight(); } //条件4:上端→下端 if(loc_y > ofGetHeight()){ loc_y = 0; } } void testApp::draw(){ ofSetColor(31, 63, 255); //描画色の設定 ofCircle(loc_x, loc_y, 40); //円を描画 } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-8_A実行結果 円が画面の端に到達すると、反対側から出現して、常に画面からはみ出ることなく表示されるで しょう。 2–8 openFrameworksプログラミング初級編 2-8-2 条件分岐の応用編 ― 壁でバウンドさせる 次に、同じ動きを基本としながら、画面の端に逹した際の動きに変化を加えてみましょう。先程は、 画面の端は反対側につながっていましたが、今度は物体を跳ね返す壁のような役割に変化させ てみます。イメージは「ブロック崩し」ゲームのような効果です。この動きを実現するには、先程 のプログラムをどう変更すればよいでしょうか。 壁に当たって跳ね返るという運動を、図に描いて考 えてみるとわかりやすいかもしれません。例えば、 画面の下端に向って右斜め下方向に物体が衝突 し、跳ね返る動きについて考えてみます。このとき、 摩擦も重力もない空間だとすると、x軸方向の運動 はそのまま右方向に持続され、y軸方向の動きだ けが、下方向から上方向に反転していることがわ かります。方向は反転しますがスピードはそのまま 保存されます。 横の壁に衝突した際には、x軸とy軸の関係が逆に なります。例えば、左下方向に動く物体が左の壁 に衝突した際には、y軸方向の運動はそのまま下 方向に持続され、x軸方向の運動が左から右方向 へと反転します。 この反転する動きをより厳密に定義してみましょう。 例えば、3のスピードで壁に衝突した際には、壁で 運動の方向が反転し、-3の力となって運動を持続 します。-2のスピードで衝突した際には、2のスピ ードに変化します。つまり、ちょうど-1を掛け算した スピードになるわけです。 これらを整理すると、壁に衝突した際の条件式は以下のようにまとめられるでしょう。 まず、円の中心座標をそれぞれ下記と定義します。 円の中心のx座標:loc_x 円の中心のy座標:loc_y これに加えて、円の移動スピードを記憶するための変数を新規に定義します。 x軸方向のスピード:speed_x y軸方向のスピード:speed_y この際、上下左右の壁に衝突した際の動きは以下のように表現されます。 90 | 91 条件 1: もし円の中心が画面右端からはみ出たら、左方向に跳ね返る 条件式:loc_x > ofGetWidth(); 真文:speed_x = speed_x * -1; 条件 2:もし円の中心が画面左端からはみ出たら、右方向に跳ね返る 条件式:loc_x < 0; 真文:speed_x = speed_x * -1; 条件 3:もし円の中心が画面下端からはみ出たら、上方向に跳ね返る 条件式:loc_y > ofGetHeight(); 真文:speed_y = speed_y * -1; 条件 4:もし円の中心が画面上端からはみ出たら、下方向に跳ね返る 条件式:loc_y < 0; 真文:speed_y = speed_y * -1; list 2-8_b #include "testApp.h" float loc_x; //円のx座標 float loc_y; //円のy座標 float speed_x; //x軸方向のスピード float speed_y; //x軸方向のスピード void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofSetFrameRate(60); //フレームレートの設定 ofSetCircleResolution(64); //円の解像度設定 loc_x = 20; //円のx座標初期位置 loc_y = 20; //円のy座標初期位置 speed_x = 4; //x軸方向スピード初期値 speed_y = 7; //y軸方向スピード初期値 } void testApp::update(){ loc_x = loc_x + speed_x; //円のx座標を更新 loc_y = loc_y + speed_y; //円のy座標を更新 //条件1:左端で跳ね返る if(loc_x < 0){ speed_x = speed_x * -1; } //条件2:右端で跳ね返る 2–8 openFrameworksプログラミング初級編 if(loc_x > ofGetWidth()){ speed_x = speed_x * -1; } //条件3:上端で跳ね返る if(loc_y < 0){ speed_y = speed_y * -1; } //条件4:下端で跳ね返る if(loc_y > ofGetHeight()){ speed_y = speed_y * -1; } } void testApp::draw(){ ofSetColor(31, 63, 255); //描画色の設定 ofCircle(loc_x, loc_y, 40); //円を描画 } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-8_A実行結果 92 | 93 2 2-9-1 –9 たくさんの図形を移動する たくさんの円を同時に動かす 壁に衝突して跳ね返る動きを使って、一度にたくさんの図形を同時にアニメーションする方法につ いて考えてみます。先程作成したプログラムでは、円1つに対して、壁でバウンドする動きを実現 するために必要な変数は下記の4つです。 円の中心のx座標 円の中心のy座標 x軸方向のスピード y軸方向のスピード これらの変数をアニメーションした円の数だけ用意すれば、複数のアニメーションを同時に行うこ とが可能となります。例えば、3つの円を同時にアニメーションするプログラムを必要な数だけ変 数を用意して、座標の計算も円の数だけ行ってみましょう。 初期化の際の円の中心位置と円のスピードは、ofRandom ( ) 関数を用いてランダムに生成してい ます。 list 2-9_a #include "testApp.h" float loc_x1; //円1のx座標 float loc_y1; //円1のy座標 float speed_x1; //x軸方向のスピード1 float speed_y1; //x軸方向のスピード1 float loc_x2; //円2のx座標 float loc_y2; //円2のy座標 float speed_x2; //x軸方向のスピード2 float speed_y2; //x軸方向のスピード2 float loc_x3; //円3のx座標 float loc_y3; //円3のy座標 2–9 openFrameworksプログラミング初級編 float speed_x3; //x軸方向のスピード3 float speed_y3; //x軸方向のスピード3 void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofSetFrameRate(60); //フレームレートの設定 ofSetCircleResolution(64); //円の解像度設定 ofEnableAlphaBlending(); //アルファチャンネルを有効に loc_x1 = ofRandom(0, ofGetWidth()); //円1のx座標初期位置 loc_y1 = ofRandom(0, ofGetHeight()); //円1のy座標初期位置 speed_x1 = ofRandom(-10, 10); //x軸方向スピード初期値1 speed_y1 = ofRandom(-10, 10); //y軸方向スピード初期値1 loc_x2 = ofRandom(0, ofGetWidth()); //円2のx座標初期位置 loc_y2 = ofRandom(0, ofGetHeight()); //円2のy座標初期位置 speed_x2 = ofRandom(-10, 10); //x軸方向スピード初期値2 speed_y2 = ofRandom(-10, 10); //y軸方向スピード初期値2 loc_x3 = ofRandom(0, ofGetWidth()); //円3のx座標初期位置 loc_y3 = ofRandom(0, ofGetHeight()); //円3のy座標初期位置 speed_x3 = ofRandom(-10, 10); //x軸方向スピード初期値3 speed_y3 = ofRandom(-10, 10); //y軸方向スピード初期値3 } void testApp::update(){ loc_x1 = loc_x1 + speed_x1; //円1のx座標を更新 loc_y1 = loc_y1 + speed_y1; //円1のy座標を更新 //円1の跳ね返り条件 if(loc_x1 < 0){ speed_x1 = speed_x1 * -1; } if(loc_x1 > ofGetWidth()){ speed_x1 = speed_x1 * -1; } if(loc_y1 < 0){ speed_y1 = speed_y1 * -1; } if(loc_y1 > ofGetHeight()){ speed_y1 = speed_y1 * -1; } loc_x2 = loc_x2 + speed_x2; //円2のx座標を更新 94 | 95 loc_y2 = loc_y2 + speed_y2; //円2のy座標を更新 //円2の跳ね返り条件 if(loc_x2 < 0){ speed_x2 = speed_x2 * -1; } if(loc_x2 > ofGetWidth()){ speed_x2 = speed_x2 * -1; } if(loc_y2 < 0){ speed_y2 = speed_y2 * -1; } if(loc_y2 > ofGetHeight()){ speed_y2 = speed_y2 * -1; } loc_x3 = loc_x3 + speed_x3; //円3のx座標を更新 loc_y3 = loc_y3 + speed_y3; //円3のy座標を更新 //円3の跳ね返り条件 if(loc_x3 < 0){ speed_x3 = speed_x3 * -1; } if(loc_x3 > ofGetWidth()){ speed_x3 = speed_x3 * -1; } if(loc_y3 < 0){ speed_y3 = speed_y3 * -1; } if(loc_y3 > ofGetHeight()){ speed_y3 = speed_y3 * -1; } } void testApp::draw(){ ofSetColor(31, 63, 255, 127); //描画色の設定 ofCircle(loc_x1, loc_y1, 40); //円1を描画 ofCircle(loc_x2, loc_y2, 40); //円2を描画 ofCircle(loc_x3, loc_y3, 40); //円3を描画 } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ 2–9 openFrameworksプログラミング初級編 } … … LIST 2-9_A実行結果 この方法を用いれば、論理的にはいくらでもアニメーションさせる物体を増やしていくことは可能 です。しかし、上記の円が3つのサンプルでも、プログラムが冗長になっているのが見てとれます。 この方法でアニメーションする物体を100個、1000個、10000個と増やしていった場合、すぐに 破綻することは明らかでしょう。 2-9-2 配列を利用してたくさんの図形をまとめてコントロール 先程のサンプルに見られた同じ機能を持った大量の変数の処理は、 「2–5 たくさんの図形を一 気に描く」 (P.66)で紹介した、配列とfor文によってプログラムが大幅に整理される典型的なケー スです。アニメーションする物体に必要な変数を全て配列にしてしまい、それらをfor文による繰 り返しで一気に処理することで、プログラムが大幅に簡略化します。 アニメーションに必要な4つの変数を全て配列にしてみましょう。 円の中心のx座標:float loc_x[]; 円の中心のy座標:float loc_y[]; x軸方向のスピード:float speed_x[]; y軸方向のスピード:float speed_y[]; また配列の要素の数をあらかじめ「定数」として定義します。定数とはプログラムの中で繰り返し 用いられる既に決定されている固定の数値を、名前を付けて定義する機能です。プログラムの先 頭で、 「#define」という文を利用して定義します。例えば、NUMという定数を指定した場合には 常に100という数値を表すように定義する場合であれば、 96 | 97 #define NUM 100 となります。#define文の末尾にはセミコロン「;」が入らないことに注意してください。 円の数を表す定数NUMを100として、100個の円に必要となる変数を全て配列として定義して、 先程のプログラムを整理してみましょう。 list 2-9_b #include "testApp.h" #define NUM 100 //円の数を表す定数NUMを100と定義 float loc_x[NUM]; //円のx座標 float loc_y[NUM]; //円のy座標 float speed_x[NUM]; //x軸方向のスピード float speed_y[NUM]; //y軸方向のスピード2 void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofSetFrameRate(60); //フレームレートの設定 ofSetCircleResolution(64); //円の解像度設定 ofEnableAlphaBlending(); //アルファチャンネルを有効に //NUMの数だけ初期値の生成を繰り返す for(int i = 0; i < NUM; i++){ loc_x[i] = ofRandom(0, ofGetWidth()); //円のx座標初期位置 loc_y[i] = ofRandom(0, ofGetHeight()); //円のy座標初期位置 speed_x[i] = ofRandom(-10, 10); //x軸方向スピード初期値 speed_y[i] = ofRandom(-10, 10); //y軸方向スピード初期値 } } void testApp::update(){ //NUMの数だけ座標の更新を繰り返す for(int i = 0; i < NUM; i++){ loc_x[i] = loc_x[i] + speed_x[i]; //円のx座標を更新 loc_y[i] = loc_y[i] + speed_y[i]; //円のy座標を更新 //円の跳ね返り条件 if(loc_x[i] < 0){ speed_x[i] = speed_x[i] * -1; } if(loc_x[i] > ofGetWidth()){ 2–9 openFrameworksプログラミング初級編 speed_x[i] = speed_x[i] * -1; } if(loc_y[i] < 0){ speed_y[i] = speed_y[i] * -1; } if(loc_y[i] > ofGetHeight()){ speed_y[i] = speed_y[i] * -1; } } } void testApp::draw(){ ofSetColor(31, 63, 255, 127); //描画色の設定 //NUMの数だけ円を描画する for(int i = 0; i < NUM; i++){ ofCircle(loc_x[i], loc_y[i], 40); //円を描画 } } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-9_B実行結果 98 | 99 こうして一度配列としてアニメーションのパラメータを定義してしまえば、アニメーションする物体 の数の増減や、変数の追加によってそれぞれの物体で変化させる属性を付加することが、とても 容易になります。例えば、先程のサンプルでは円の大きさが一定でしたが、円の半径を表す配列 を追加して、円の大きさもランダムに変化させてみましょう。また、円の数も100個から1000個に 増やしてみます。 list 2-9_c #include "testApp.h" #define NUM 1000 //円の数を表す定数NUMを1000と定義 float loc_x[NUM]; //円のx座標 float loc_y[NUM]; //円のy座標 float speed_x[NUM]; //x軸方向のスピード float speed_y[NUM]; //y軸方向のスピード float radius[NUM]; //円の半径 void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofSetFrameRate(60); //フレームレートの設定 ofSetCircleResolution(64); //円の解像度設定 ofEnableAlphaBlending(); //アルファチャンネルを有効に //NUMの数だけ初期値の生成を繰り返す for(int i = 0; i < NUM; i++){ loc_x[i] = ofRandom(0, ofGetWidth()); //円のx座標初期位置 loc_y[i] = ofRandom(0, ofGetHeight()); //円のy座標初期位置 speed_x[i] = ofRandom(-5, 5); //x軸方向スピード初期値 speed_y[i] = ofRandom(-5, 5); //y軸方向スピード初期値 radius[i] = ofRandom(4, 40); //円の半径の初期値 } } void testApp::update(){ //NUMの数だけ座標の更新を繰り返す for(int i = 0; i < NUM; i++){ loc_x[i] = loc_x[i] + speed_x[i]; //円のx座標を更新 loc_y[i] = loc_y[i] + speed_y[i]; //円のy座標を更新 //円の跳ね返り条件 if(loc_x[i] < 0){ speed_x[i] = speed_x[i] * -1; } if(loc_x[i] > ofGetWidth()){ 2–9 openFrameworksプログラミング初級編 speed_x[i] = speed_x[i] * -1; } if(loc_y[i] < 0){ speed_y[i] = speed_y[i] * -1; } if(loc_y[i] > ofGetHeight()){ speed_y[i] = speed_y[i] * -1; } } } void testApp::draw(){ ofSetColor(31, 63, 255, 127); //描画色の設定 //NUMの数だけ円を描画する for(int i = 0; i < NUM; i++){ ofCircle(loc_x[i], loc_y[i], radius[i]); //円を描画 } } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-9_C実行結果 100 | 101 さらにそれぞれの円の色を変化させてみましょう。色の成分R、G、Bをそれぞれ配列として用意 して、ランダムに色を塗っていきましょう。 list 2-9_d #include "testApp.h" #define NUM 1000 //円の数を表す定数NUMを1000と定義 float loc_x[NUM]; //円のx座標 float loc_y[NUM]; //円のy座標 float speed_x[NUM]; //x軸方向のスピード float speed_y[NUM]; //y軸方向のスピード float radius[NUM]; //円の半径 int red[NUM]; //Red成分 int green[NUM]; //Green成分 int blue[NUM]; //Blue成分 void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofSetFrameRate(60); //フレームレートの設定 ofSetCircleResolution(64); //円の解像度設定 ofEnableAlphaBlending(); //アルファチャンネルを有効に //NUMの数だけ初期値の生成を繰り返す for(int i = 0; i < NUM; i++){ loc_x[i] = ofRandom(0, ofGetWidth()); //円のx座標初期位置 loc_y[i] = ofRandom(0, ofGetHeight()); //円のy座標初期位置 speed_x[i] = ofRandom(-5, 5); //x軸方向スピード初期値 speed_y[i] = ofRandom(-5, 5); //y軸方向スピード初期値 radius[i] = ofRandom(4, 40); //円の半径を設定 red[i] = ofRandom(0, 255); //Red成分を設定 green[i] = ofRandom(0, 255); //Green成分を設定 blue[i] = ofRandom(0, 255); //Blue成分を設定 } } void testApp::update(){ //NUMの数だけ座標の更新を繰り返す for(int i = 0; i < NUM; i++){ loc_x[i] = loc_x[i] + speed_x[i]; //円のx座標を更新 loc_y[i] = loc_y[i] + speed_y[i]; //円のy座標を更新 //円の跳ね返り条件 if(loc_x[i] < 0){ speed_x[i] = speed_x[i] * -1; 2–9 openFrameworksプログラミング初級編 } if(loc_x[i] > ofGetWidth()){ speed_x[i] = speed_x[i] * -1; } if(loc_y[i] < 0){ speed_y[i] = speed_y[i] * -1; } if(loc_y[i] > ofGetHeight()){ speed_y[i] = speed_y[i] * -1; } } } void testApp::draw(){ //NUMの数だけ円を描画する for(int i = 0; i < NUM; i++){ ofSetColor(red[i], green[i], blue[i], 127); //描画色の設定 ofCircle(loc_x[i], loc_y[i], radius[i]); //円を描画 } } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-9_D実行結果 102 | 103 2 2-10-1 –10 図形に触れる ― インタラクション インタラクションとは これまでのサンプルは、プログラムを実行すると、静止画であってもアニメーションであっても、ユ ーザーがプログラムを終了するまではプログラムされた通りに自動的に再生されていました。言い 換えると、このプログラムにユーザーが介入する要素は、プログラムの起動と終了のみと言えるで しょう。 openFrameworksでは、様々な手段でプログラムを実行している最中にも、ユーザーがプログラ ムに対して介入することが可能です。ユーザーがアクションを起こし、それに反応してプログラム は何らかのリアクションをする。ユーザーはさらにその反応を見て、別のアクションを起こす。そう した、2つ以上の要素(この場合は、人間とコンピュータ上で動いているプログラム)の相互作用 のことを、インタラクション(interaction)と呼びます。 プログラムにインタラクションを付加することで、より意外性に富んだ豊かな表現が可能となりま す。また、インタラクションをデザインしていくことは、これまでの伝統的なデザインの枠を超えた 新たな領域と言えるでしょう。どのようにしたら効果的なインタラクションを実現できるのか、どの ようにデザインすれば自然なインタラクションが生まれるのか、といったようにまだまだ未知の領 域が残された刺激的な世界が拡がっています。 openFrameworksでは、あらかじめ様々なインタラクションの仕組みが用意されています。このセ クションでは、まずマウスとキーボードを用いて簡単なインタラクションのサンプルを作成してみま しょう。 2-10-2 マウスの位置の検知 プログラムにインタラクションを加える最も簡単な方法は、マウスの現在位置を利用する方法でし ょう。マウスの現在位置は、とても簡単に取得することが可能です。mouseX、mouseYという特 殊な変数を使用します。それぞれ現在のマウスのx座標とy座標が常に最新の状態で代入されて います。 mouseX:現在のマウスのx座標 mouseY:現在のマウスのy座標 2–10 openFrameworksプログラミング初級編 マウスの現在位置を利用した簡単なサンプルを作成してみます。マウスの現在位置に円を描くプ ログラムを作成してみましょう。 list 2-10_a #include "testApp.h" void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofSetFrameRate(60); //フレームレートの設定 ofSetCircleResolution(64); //円の解像度設定 } void testApp::update(){ } void testApp::draw(){ ofSetColor(31, 63, 255); //描画色の設定 ofCircle(mouseX, mouseY, 40); //マウスの現在位置を中心に円を描く } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … LIST 2-10_A実行結果。マウスの動きにともなって円が移動する 104 | 105 2-10-3 マウスによるインタラクション マウスポインタの位置と、ユーザーがマウスやキーボードを用いて行う動作を組み合わせることで、 より複雑なインタラクションが実現可能です。 openFrameworksでは、ユーザーがマウスで行った動作を検知し、その状態に応じて処理をす るための関数が豊富に用意されています。今までのサンプルで、draw ( ) 関数以下に空白のまま 未使用であった関数がたくさん並んでいました(本書中では省略表記していましたが) 。実はそれ らはユーザーからのマウスとキーボードの様々な入力を受け取るための機能だったのです。 まずはマウスに関する関数から見ていきます。マウスのボタン、マウスの動き、現在のマウスの位 置を組み合わせることで、様々なインタラクションが実現可能となっています。openFrameworks で用意されているマウスの状態を検知する関数には、以下のものがあります。 関数名 引数 関数の起動する瞬間 mouseMoved(int x, int y) x:現在のマウスのx 座標、y:現在のマウスの y 座標 マウスを移動したとき mouseDrugged(int x, inty) x:現在のマウスのx 座標、y:現在のマウスの y 座標 マウスをドラッグ(マウス ポインタを押したままで 移動)したとき mousePressed(int x, int y, x:現在のマウスのx 座標、y:現在のマウスの y 座標、button:ボタンの種類(0: 左ボタン、 int button) マウスのボタンを押した とき x:現在のマウスのx 座標、y:現在のマウスの y 座標、button:ボタンの種類(0: 左ボタン、 1: 中ボタン、2: 右ボタン) 押されていたマウスボタ ンを離したとき 1: 中ボタン、2: 右ボタン) mouseReleased(int x, int y, int button) では、マウスを移動する、ドラッグする、押す、離すという動作でそれぞれ状態が変化するサン プルを作成してみましょう。それぞれの動作で以下のように状態を変化させてみます。 マウスを動かす:円の色をグレー(127, 127, 127)に マウスをドラッグ:円の中心位置をマウスポインタの場所へ マウスボタンを押す:円の色を赤(255, 63, 31)に、円の中心位置をマウスポインタの場所へ マウスボタンを離す:円の色を青(31, 63, 255)に list 2-10_b #include "testApp.h" float loc_x, loc_y; int red, green, blue; void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofSetFrameRate(60); //フレームレートの設定 ofSetCircleResolution(64); //円の解像度設定 2–10 openFrameworksプログラミング初級編 //円の初期位置 loc_x = ofGetWidth() / 2; loc_y = ofGetHeight() / 2; //円の色、初期値 red = 31; green = 63; blue = 255; } void testApp::update(){ } void testApp::draw(){ ofSetColor(red, green, blue); //描画色の設定 ofCircle(loc_x, loc_y, 40); //画面の中心に円を描く } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ //円の色をグレーに red = 127; green = 127; blue = 127; } void testApp::mouseDragged(int x, int y, int button){ //円の中心位置をマウスの位置に loc_x = x; loc_y = y; } void testApp::mousePressed(int x, int y, int button){ //円の中心位置をマウスの位置に loc_x = x; loc_y = y; //円の色を赤に red = 255; 106 | 107 green = 63; blue = 31; } void testApp::mouseReleased(int x, int y, int button){ //円の色を青に red = 31; green = 63; blue = 255; } void testApp::windowResized(int w, int h){ } LIST 2-10_B実行結果 2-10-4 インタラクションとアニメーションを組み合わせる では、「2–9–2 配列を利用してたくさんの図形をまとめてコントロール」 (P.96)で作成した、大 量の円が動きまわるプログラムにインタラクションを付加してみましょう。 マウスボタンが押されているか、いないかによって、円の動きを変化させてみましょう。 マウスボタンが押されていないとき:壁で跳ね返りながら自由に運動を続ける(通常状態) マウスボタンが押されているとき:マウスポインタに円が集ってくる マウスポインタに円が集ってくるような動きにするためにはどうすればよいでしょうか。マウスボタン をクリックした瞬間に全ての円がマウスポインタの座標まで移動してしまうと、円は瞬間移動した ようになってしまい、集合してきたように見えません。マウスポインタの座標へ円がなめらかに移 動してきたように見せるために、その間の動きを補完する必要があります。 2–10 openFrameworksプログラミング初級編 ある座標から、もう1つの座標まで物体をなめらかに移動するテクニックには様々な手法があり、 その計算方法によって実現される動きにもそれぞれ個性があります。このサンプルでは最もシンプ ルな補完の方法を試してみましょう。 ある座標から別の座標まで移動するにあたって、まず両方の座標のx軸、y軸双方の差分を計算 します。言い換えると、2点間の距離を測っているとも考えられます。そこで算出された距離を、 常に一定の値で割り算をして、その1単位分だけ移動します。移動が完了したら、改めてまた距 離を再測定し、一定の数で割り算します。このような計算を続けていくと、近づくスピードを徐々 に減速しながら、なめらかに移動するようなアニメーションが作成されます。 B A 1 2 3 4 5... 補完のイメージ図 このサンプルでは、マウスのボタンが押されているかどうかを記憶するブール型(P.60)の変数 「mouse_pressed」を新規に定義して、マウスボタンを押したら「mouse_pressed = true」、マウ スボタンを離したら「mouse_pressed = false」となるようにしています。この判定は、それぞれ mousePressed ( ) 関数と、mouseReleased ( ) 関数で行っています。 そしてupdate ( ) 関数では、まずマウスボタンが現在押されているのかを判定し、もし押されてい たのであれば(mouse_pressed = true)前述の補完の計算をして円のスピードを再計算し、押さ れていなければ(mouse_pressed = false)そのままスピードを変化させずに自由運動を続けると いう処理をしています。また、マウスポインタが離れた瞬間には、円のスピードを再初期化してい ます。 list 2-10_c #include "testApp.h" #define NUM 1000 //円の数を表す定数NUMを1000と定義 float loc_x[NUM]; //円のx座標 float loc_y[NUM]; //円のy座標 float speed_x[NUM]; //x軸方向のスピード float speed_y[NUM]; //y軸方向のスピード float radius[NUM]; //円の半径 int red[NUM]; //Red成分 int green[NUM]; //Green成分 int blue[NUM]; //Blue成分 bool mouse_pressed; //マウスはクリックされているか? void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 108 | 109 ofSetFrameRate(60); //フレームレートの設定 ofSetCircleResolution(64); //円の解像度設定 ofEnableAlphaBlending(); //アルファチャンネルを有効に mouse_pressed = false; //マウス状態を「クリックされていない」に //NUMの数だけ初期値の生成を繰り返す for(int i = 0; i < NUM; i++){ loc_x[i] = ofRandom(0, ofGetWidth()); //円のx座標初期位置 loc_y[i] = ofRandom(0, ofGetHeight()); //円のy座標初期位置 speed_x[i] = ofRandom(-5, 5); //x軸方向スピード初期値 speed_y[i] = ofRandom(-5, 5); //y軸方向スピード初期値 radius[i] = ofRandom(4, 40); //円の半径を設定 red[i] = ofRandom(0, 255); //Red成分を設定 green[i] = ofRandom(0, 255); //Green成分を設定 blue[i] = ofRandom(0, 255); //Blue成分を設定 } } void testApp::update(){ //NUMの数だけ座標の更新を繰り返す for(int i = 0; i < NUM; i++){ //もしマウスがクリックされていたらマウスに集まってくる if(mouse_pressed){ //マウスの現在位置から円のスピードを再計算 speed_x[i] = (mouseX - loc_x[i]) / 8.0; //マウスのx座標と円のx 座標の距離の1/8だけ接近 speed_y[i] = (mouseY - loc_y[i]) / 8.0; //マウスのy座標と円のy 座標の距離の1/8だけ接近 } loc_x[i] = loc_x[i] + speed_x[i]; //円のx座標を更新 loc_y[i] = loc_y[i] + speed_y[i]; //円のy座標を更新 //円の跳ね返り条件 if(loc_x[i] < 0){ speed_x[i] = speed_x[i] * -1; } if(loc_x[i] > ofGetWidth()){ speed_x[i] = speed_x[i] * -1; } if(loc_y[i] < 0){ 2–10 openFrameworksプログラミング初級編 speed_y[i] = speed_y[i] * -1; } if(loc_y[i] > ofGetHeight()){ speed_y[i] = speed_y[i] * -1; } } } void testApp::draw(){ //NUMの数だけ円を描画する for(int i = 0; i < NUM; i++){ ofSetColor(red[i], green[i], blue[i], 127); //描画色の設定 ofCircle(loc_x[i], loc_y[i], radius[i]); //円を描画 } } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ } void testApp::mousePressed(int x, int y, int button){ mouse_pressed = true; //マウスが押されている状態に } void testApp::mouseReleased(int x, int y, int button){ mouse_pressed = false; //マウスが押されていない状態に //円のスピードを再初期化 for(int i = 0; i < NUM; i++){ speed_x[i] = ofRandom(-5, 5); //x軸方向スピード初期値 speed_y[i] = ofRandom(-5, 5); //y軸方向スピード初期値 } } 110 | 111 void testApp::windowResized(int w, int h){ } LIST 2-10_C実行結果 2–10 openFrameworksプログラミング初級編 2 –11 より高度な表現へ 表現に現実味を持たせる 2-11-1 ここまでの動きをさらに現実味のある表現へと改良していきたいと思います。では、どのようにした ら現状のプログラムにリアリティを持たせることができるでしょうか。 ここまで作成してきたプログラムでは、一度動き出した物体はプログラムが終了するまで同じスピ ードで動き続けていました。しかし現実の世界では、このようなことはありえないことを私たちは 経験的に知っています。結果として、この動きはコンピュータの中だけで成立するバーチャルなも のとして認識してしまうのです。 私たちの暮らす日常の世界では、物体が外部からのエネルギーの補充なしに永遠に動き続けるこ とはまずありません。物体が動く際には、常に重力や摩擦力の影響を受けているからです。最初、 勢い良く動いていた物体も、時間とともに運動する力が削がれていき、いずれ静止します。 運動に対して物理的な制約を適用することで、より馴染みのある動きとなり、結果としてより現実 感のある表現となります。そのために、重力や摩擦力といった物理的な法則を動きの中に適用し ていきます。重力や摩擦力といった現実世界を支配する物理法則は、簡単な数式に還元するこ とが可能です。 2-11-2 パーティクル ― 重力と摩擦力を導入 重力と摩擦力を、プログラムに適用してみましょう。 まず、重力について考えます。このプログラムでは、常に画面の下に向って重力が働いていると考 えます。つまり、地面が下にあり、物体の動きを横から見ている状態になります。運動している 物体と地球との間に、万有引力が発生します。ただし、地球の質量に対して運動している物体の 質量は無視できるほと小さなものなので、ここでは運動する物体が一方的に地球の引力の影響を 受けると考えて差し支えないでしょう。つまり、物体は常に下方向への力がかかる状態となります。 この状態をプログラムで表現するには、まず引力の強さを設定し、変数「gravity」に代入します。 そして物体の次の座標を計算する際に、常にy軸の正方向に設定した重力の値を加算します。こ れにより、全ての物体に一定の重力が働いている様子を再現することが可能です。 112 | 113 次に、摩擦力について考えてみましょう。 摩擦力とは、物体が移動する際に進行方向とは逆向きに働く力です。例えば、物体との接触面 に発生する抵抗や、物体の周囲の空気による抵抗などが考えられます。摩擦力は移動する物体 の材質や、接触する面の材質、空気の状態などによって変化します。 摩擦力をプログラムで表現するには、まず、0.0 ∼ 1.0の間で摩擦係数を設定します。摩擦係数 1.0の場合がまったく摩擦力のない環境、摩擦係数0.0の場合は、摩擦力が強すぎて一切の運動 が起こらない状況を表しています。摩擦係数を決定したら、物体が運動する際に、毎フレーム毎 に常にそのスピードに摩擦係数を乗算します。摩擦力はx軸方向とy軸方向の運動両方に同じよ うに作用するので、x軸方向のスピードとy軸方向のスピードに同じ係数をかけ算します。このこと により、運動の勢いが徐々に摩擦力によって削がれていき、最後には静止するという動きを再現 することが可能となるのです。 list 2-11_a #include "testApp.h" #define NUM 1000 //円の数を表す定数NUMを1000と定義 float loc_x[NUM]; //円のx座標 float loc_y[NUM]; //円のy座標 float speed_x[NUM]; //x軸方向のスピード float speed_y[NUM]; //y軸方向のスピード float radius[NUM]; //円の半径 int red[NUM]; //Red成分 int green[NUM]; //Green成分 int blue[NUM]; //Blue成分 bool mouse_pressed; //マウスはクリックされているか? float gravity; //重力 float friction; //摩擦 void testApp::setup(){ ofBackground(0, 0, 0); //背景色の設定 ofSetFrameRate(60); //フレームレートの設定 ofSetCircleResolution(64); //円の解像度設定 ofEnableAlphaBlending(); //アルファチャンネルを有効に mouse_pressed = false; //マウス状態を「クリックされていない」に gravity = 0.1; //重力の強さを設定 friction = 0.999; //摩擦力の強さを設定 //NUMの数だけ初期値の生成を繰り返す for(int i = 0; i < NUM; i++){ loc_x[i] = ofGetWidth() / 2; //円のx座標初期位置 loc_y[i] = ofGetHeight() / 2; //円のy座標初期位置 2–11 openFrameworksプログラミング初級編 speed_x[i] = ofRandom(-10, 10); //x軸方向スピード初期値 speed_y[i] = ofRandom(-10, 10); //y軸方向スピード初期値 radius[i] = ofRandom(1, 10); //円の半径を設定 red[i] = ofRandom(0, 255); //Red成分を設定 green[i] = ofRandom(0, 255); //Green成分を設定 blue[i] = ofRandom(0, 255); //Blue成分を設定 } } void testApp::update(){ //NUMの数だけ座標の更新を繰り返す for(int i = 0; i < NUM; i++){ //もしマウスがクリックされていたらマウスに集ってくる if(mouse_pressed){ //マウスの現在位置から円のスピードを再計算 speed_x[i] = (mouseX - loc_x[i]) / 8.0; //マウスのx座標と円のx座標の距離の1/8だけ接近 speed_y[i] = (mouseY - loc_y[i]) / 8.0; } speed_x[i] = speed_x[i] * friction; //x軸方向の摩擦力の影響を計算 speed_y[i] = speed_y[i] * friction; //y軸方向の摩擦力の影響を計算 speed_y[i] = speed_y[i] + gravity; //重力の影響を計算 loc_x[i] = loc_x[i] + speed_x[i]; //円のx座標を更新 loc_y[i] = loc_y[i] + speed_y[i]; //円のy座標を更新 //円の跳ね返り条件 if(loc_x[i] < 0){ loc_x[i] = 0; speed_x[i] = speed_x[i] * -1.0; } if(loc_x[i] > ofGetWidth()){ loc_x[i] = ofGetWidth(); speed_x[i] = speed_x[i] * -1.0; } if(loc_y[i] < 0){ loc_y[i] = 0; speed_y[i] = speed_y[i] * -1.0; } if(loc_y[i] > ofGetHeight()){ loc_y[i] = ofGetHeight(); 114 | 115 speed_y[i] = speed_y[i] * -1.0; } } } void testApp::draw(){ //NUMの数だけ円を描画する for(int i = 0; i < NUM; i++){ ofSetColor(red[i], green[i], blue[i], 127); //描画色の設定 ofCircle(loc_x[i], loc_y[i], radius[i]); //円1を描画 } } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ } void testApp::mousePressed(int x, int y, int button){ mouse_pressed = true; //マウスが押されている状態に } void testApp::mouseReleased(int x, int y, int button){ mouse_pressed = false; //マウスが押されていない状態に //円のスピードを再初期化 for(int i = 0; i < NUM; i++){ speed_x[i] = ofRandom(-10, 10); //x軸方向スピード初期値 speed_y[i] = ofRandom(-10, 10); //y軸方向スピード初期値 } } void testApp::windowResized(int w, int h){ } 2–11 openFrameworksプログラミング初級編 LIST 2-11_A実行結果 2-11-3 動きの軌跡をフェード さらに工夫を加えてみましょう。物体の動きをよりわかりやすくするために、その動いた軌跡を表 示するようにしてみます。 動きの軌跡の表示をプログラムで実現するための簡単な方法があります。それは、毎フレームの 画面の書き換えの部分に操作を加えることです。openFrameworksでは、フレームが更新する際 に自動的に前のフレームの画像を全て消去しています。これは、新たなフレームを描く際に、一 度背景色で全ての画面を塗り潰すことで実現されています。この毎フレームで行われている背景 色での全画面の塗り潰しの処理を省いてしまうと、どうなるでしょう。前のフレームで描画した画 像がそのまま画面に残ります。その結果として、物体の動きの軌跡が全て画面上に残ることにな ります。 openFrameworksでは、このフレームを更 新する際の背 景 色の塗り潰しの処 理の有 無を、 ofSetBackgroundAuto ( ) 関数で設定することが可能です。この関数の引数にブール型の変数を 設定することで、塗り潰しの有無を設定します。 フレーム更新の際の背景色での塗り潰しあり:ofSetBackgroundAuto(true) フレーム更新の際の背景色での塗り潰しなし:ofSetBackgroundAuto(false) 何も設定しない際には、塗りつぶしあり、つまりofSetBackgroundAuto(true)が設定されています。 ただし、塗り潰しをなしにするだけでは軌跡が全て蓄積してしまい、すぐに画面が軌跡に埋めつく されてしまいます。ここでもう少し工夫して、毎回フレームを更新する際に、全画面に半透明の四 角形を描くようにしてみます。画面を更新する前に、全部の画面を埋めつくす半透明のセロファン を被せて、その上から新たな図形を描くというイメージです。この処理により、まるで物体の運動 の軌跡がフェードしているような効果を得ることが可能となります。 116 | 117 list 2-11_b #include "testApp.h" #define NUM 1000 //円の数を表す定数NUMを1000と定義 float loc_x[NUM]; //円のx座標 float loc_y[NUM]; //円のy座標 float speed_x[NUM]; //x軸方向のスピード float speed_y[NUM]; //y軸方向のスピード float radius[NUM]; //円の半径 int red[NUM]; //Red成分 int green[NUM]; //Green成分 int blue[NUM]; //Blue成分 bool mouse_pressed; //マウスはクリックされているか? float gravity; float friction; void testApp::setup(){ ofSetBackgroundAuto(false); ofBackground(0, 0, 0); //背景色の設定 ofSetFrameRate(60); //フレームレートの設定 ofSetCircleResolution(64); //円の解像度設定 ofEnableAlphaBlending(); //アルファチャンネルを有効に mouse_pressed = false; //マウス状態を「クリックされていない」に gravity = 0.1; //重力 friction = 0.999; //NUMの数だけ初期値の生成を繰り返す for(int i = 0; i < NUM; i++){ loc_x[i] = ofGetWidth() / 2; //円のx座標初期位置 loc_y[i] = ofGetHeight() / 2; //円のy座標初期位置 speed_x[i] = ofRandom(-10, 10); //x軸方向スピード初期値 speed_y[i] = ofRandom(-10, 10); //y軸方向スピード初期値 radius[i] = ofRandom(1, 10); //円の半径を設定 red[i] = ofRandom(0, 255); //Red成分を設定 green[i] = ofRandom(0, 255); //Green成分を設定 blue[i] = ofRandom(0, 255); //Blue成分を設定 } } void testApp::update(){ //NUMの数だけ座標の更新を繰り返す for(int i = 0; i < NUM; i++){ //もしマウスがクリックされていたらマウスに集ってくる if(mouse_pressed){ 2–11 openFrameworksプログラミング初級編 //マウスの現在位置から円のスピードを再計算 speed_x[i] = (mouseX - loc_x[i]) / 8.0; //マウスのx座標と円の x座標の距離の1/8だけ接近 speed_y[i] = (mouseY - loc_y[i]) / 8.0; } speed_x[i] = speed_x[i] * friction; speed_y[i] = speed_y[i] * friction; speed_y[i] = speed_y[i] + gravity; loc_x[i] = loc_x[i] + speed_x[i]; //円のx座標を更新 loc_y[i] = loc_y[i] + speed_y[i]; //円のy座標を更新 //円の跳ね返り条件 if(loc_x[i] < 0){ loc_x[i] = 0; speed_x[i] = speed_x[i] * -1.0; } if(loc_x[i] > ofGetWidth()){ loc_x[i] = ofGetWidth(); speed_x[i] = speed_x[i] * -1.0; } if(loc_y[i] < 0){ loc_y[i] = 0; speed_y[i] = speed_y[i] * -1.0; } if(loc_y[i] > ofGetHeight()){ loc_y[i] = ofGetHeight(); speed_y[i] = speed_y[i] * -1.0; } } } void testApp::draw(){ //全画面を半透明の黒でフェード ofSetColor(0, 0, 0, 23); ofRect(0, 0, ofGetWidth(), ofGetHeight()); //NUMの数だけ円を描画する for(int i = 0; i < NUM; i++){ ofSetColor(red[i], green[i], blue[i], 127); //描画色の設定 ofCircle(loc_x[i], loc_y[i], radius[i]); //円1を描画 } } 118 | 119 void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ } void testApp::mousePressed(int x, int y, int button){ mouse_pressed = true; //マウスが押されている状態に } void testApp::mouseReleased(int x, int y, int button){ mouse_pressed = false; //マウスが押されていない状態に //円のスピードを再初期化 for(int i = 0; i < NUM; i++){ speed_x[i] = ofRandom(-10, 10); //x軸方向スピード初期値 speed_y[i] = ofRandom(-10, 10); //y軸方向スピード初期値 } } void testApp::windowResized(int w, int h){ } LIST 2-11_B実行結果 2–11 openFrameworksプログラミング初級編 openFrameworksプログラミング初級編はこれで終わります。ここまで到達した方は、プログラミ ングの初歩から、重力と摩擦力をもったリアルな動きにインタラクションを付加するという、高度 なインタラクティブなアニメーションを作成するスキルを身につけました。この技術を基本にして、 個別のアニメーションのアルゴリズムを変更していくことで、様々な応用が可能となります。ぜひ 自分自身のアイデアを投入して独自の作品の制作にチャレンジしてみてください。 しかし、openFrameworksにはまだまだ紹介していない様々な魅力的な機能や、より高度なプロ グラミング手法がたくさんあります。次の章では、さらにステップアップするために、これまでやっ てきたことを整理しながら、より高次元の表現に挑戦していきます。引き続き、楽しくプログラミ ングしていきましょう。 120 | 121 2–11 openFrameworksプログラミング初級編 Interview 02 Daito Manabe トライアンドエラーの積み重ねが、 次につながっていく 真鍋 大度(まなべ だいと) Artist, Coder, Hacker, Sound designer, DJ, VJ 確固たるプログラミング技術と徹底的なリサーチ、 柔軟なスタンスで、国内外を問わず数多くのプロジ ェクトに参加している。2009年1月に行われたArs Electronica Center OpeningイベントではZachary Lieberman率いるYesYesNoとのコラボレート作品 を発表。自身の筋電センサー、低周波発生器を用 いたパフォーマンス、DJingも行った。顔面を音楽 のヴィジュアライザーとして扱ったYouTubeの実験 映像がギーク系ブログで話題になり一ヶ月足らず で100 万ビューを達 成。2008 年 3月、4nchor5 la6を設立、石橋素と共同主宰。Rhizomatiks取 締役。 URL http://www.daito.ws/ 2009.11.19@4nchor5 la6 聞き手:久保田 晃弘 122 | 123 ― まずは真鍋さんの経歴から聞かせていただけます か。特に、プログラミングとの出会いはいつ頃だっ り、周りが使い始めてからです。去年ぐらいからみ んな使うようになってきましたね。 たのですか? ― oFを多くの人が使うようになった理由はどこにある 真鍋大度(M) 大学が理系で、数学科だったので と思いますか? すが、幾何学の研究室に入って曲面の視覚化をす る際にJavaを使っていました。それが本格的にプ M まずはやはりProcessingに比べて速いっていうのが ログラムと付き合い始めたきっかけですね。もっと あるんじゃないでしょうか。あとは画像解析のライ 前の話で言うと、小学校のときにPC88とMSXを ブラリOpenCVのサンプルがすぐに使えるということ 持っていたのでBASICでゲームを作ったことはあり も大きかったと思います。より多くのライブラリを使 ます。大学卒業後はメーカー系のSE職に就きまし え ると い う の も あ りま す ね。 ま た、 こ れ は た。そこでは高速道路の監視システムなどの大規 Processingも同じですけど、自分でアドオンにして 模なシステムを作っていて、50台のカメラをサーバ 公開することでコミュニティに参加・貢献できると ルームから制御するというようなことをやっていまし いうチャンスもありますよね。 た。 ― 2008年11月に開催されたYCAM(山口情報芸術セ ― 現在のようなメディアアートのフィールドで活動する ンター)5周年記念シンポジウムで、Zachが「DIY ようになっていったのは、どのような流れだったの (Do It Yourself)からDIWO(Do It With Others, み でしょう? んなと一緒に)へ」 、ということを言っていました。 oFのコミュニティもここ最近活発化してますよね。 M 会社を辞めてIAMASに入って、サウンドアート寄り 現在oFを使ったデザインワークはありますか? のことをやりたかったのでMax/MSPを使い始めまし た。卒業して食い扶持がなかったので芸大の先端 (東京藝術大学 先端芸術表現科)で助手をやった M ビーコン、ルートと一緒にやったNikeの仕事で、 洋服の色を解析して、そこから靴のデザインを作り のですが、そこで石橋素氏と出会ったんです。彼 出す、ということをやりました。最初は全部oFでや は既にインタラクティブなことをデザインのフィール っていたのですが、フルHDのビデオの再生がoFで ドでやっていたので、一緒にやろうということになり はうまくできなかったので、ビデオの再生とパーテ ま し た。 そ の 後、 大 学 時 代 の 友 人 達 と ィクルの描画はMax/MSPでやって解析はoFでやる Rhizomatiksという会社を設立して、制作場所が というふうに、描画の部分と解析の部分を分けて 必要になったのでこのラボを立ち上げて、今に至る 制作するということをやっていました。 という感じですかね。最近はデザインワーク(クラ イアントワーク)とアートワークを半々ぐらいの割合 で活動しています。 ―この間のアルス・エレクトロニカでの作品「Lights on」について教えてもらえますか? どのようにoF を使ったのか、またMax/MSPと組み合わせたのか。 ― はじめてoF(openFrameworks)を知ったのはい つですか? ―あのときいちばんネックというかキモだったのはスピ ードだったんです。3日間という非常にタイトな制 M Mixiのコミュニティで知ったのが最初です。一昨年 作期間でした。事前にメールで「どんな表現がで ぐらいだったかな。だから使い始めたのはそんなに きそうか」といった話だけざっくりとしてイメージを 早くはないです。その頃は今みたいにリソースもな 共有しておいて、現場に着いたら最初にZachたち かったですしね。僕は自分の周りの人から情報をも が作っていたコードをもらい、僕はoFを書くという らうということが多いので、oFを本格的に使い始め よりは、oFのコードやシミュレータを見ながら「こ たのも、Zach(Zachary Lieberman)と知り合った のへんはこういうOSCのデータを送ってくれたら使 Interview 02 : Daito Manabe えるかもしれない」 「この値は音に変換すると使える ― ARとかiPhoneアプリとか、最近の新しいテクノロ かもしれない」というようなやりとりをしました。ま ジーについてはどのように見てますか。既に何か作 た逆に僕の方でも、 「今こういう音を作ってるんだ ってますか? けど、このエフェクトの値を送ったらどこかのパラメ ータに当てはめることできない?」みたいな感じで M 2年前にNikeの仕事でARを使ったことがあります 進めていました。基本的にはOSCで向こうのプロ が、今はあまりやってないんです。もちろん、ライ ジェクトとやりとりをするというかたちなんですが、 ゾマではARもiPhoneアプリもやっています。僕が そこでどういうデータが必要かということはその都 関わった仕事で面白かったのは、まだiPod touch 度その都度実験しながらやっていて。彼らは本当 が出る前にやっていたR&Dの仕事ですね。iPodに にスピードが速いので、トライ&エラーの回数がも カメラ、GPSを搭載して、ネットワークにも繋がっ のすごく稼げるんです。だから、oFを使う利点って たらどんなサービスができるかを考えて、実際にプ やっぱりそのへんが大きいと思います。すぐにどうい ロトタイプを作るというようなプロジェクトでした。 う見栄えになるのかというのを試せますからね。 いざ実際にiPhoneが出てみると、その頃振り絞っ ― しかし3日間であれをやるというのはものすごいスピ いましたね。みんな同じサービスを考えるという良 て考えたアイデアはほとんどがすぐにアプリで出て い教訓になりました。現時点では実現不可能な夢 ードですね。 を見ることの方が面白いですね。 M この間もニュージーランドでZachたちとやったので すが、そのときは現場で僕に与えられた期間は2日 間しかなかったんです。こちらも同じくリモートでイ ― oFを使ってないものも含めて、現在取り組んでいる プロジェクトを教えてください。 メージを共有して事前にモジュールを作って臨むの ですが、本番直前まで皆アップデートをしていて、 M バイオエンジニア照岡正樹氏の指導の元、原田克 そのチャレンジ精神に脱帽しました。Maxであれば 彦氏、石橋素氏、ライゾマでBodyhackというワー 僕もできますが、oFで直前にコードをゴリゴリ書き クショップを、TEIというカンファレンスをはじめ国 換えるのは恐くてできません。 内外でやっています。生体データをブロードキャス トしたり、tweetして、それを二次利用できないか ― そういえば、YCAMの「ミニマム・インターフェイス」 展の時も、ZachはSkypeで通話したり、コーラや 考えるところを目標にしています。こちらはoF、p5、 Maxでエグザンプルを制作しています。まだまだ集 ポテトチップスをつまみながら、ほとんど泊り込み 合データにしてもサービスに展開するのは難しいで でやっていました。机の下でそのまま寝てたことも すが、医療だけではなく環境問題や広告にも展開 ありました(笑) 。 できる可能性があるのではと考えて色々と試してい ます。アイドルの生体情報をサービスにするような M 彼らは使えるクラスの数もものすごくたくさん持って こともやりたいと思って色々プレゼン中ですが全滅 ますからね。今までやってきたことを、そのプロジ です。後は「pa++ern」というマイクロコードを使っ ェクトのために組み合わせて作るというスタイルで た作品の発展版を石橋素氏と作っています。コー やっていると思います。だから速い。 ドからグラフィックを生成し、ミシンが読み込める バイナリファイルを生成して、それをサウンドファイ ― oFを使った最近の作品で何か気になるものはあり ルに変換して再生したものを音響解析して、グラフ ィック、刺繍のパターンに戻す、というものです。 ますか? Ruby、C++、Max/MSPでやっています。面白いのは、 M MemoがやっているOpenCLを使った実験や、Kyle (McDonald)がやっている3Dスキャンでしょうか。 後はPythonのラッパーが気になります。 124 | 125 サウンドファイルを再生してそこにリバーブやディレ イをかけると刺繍のパターンも変えられるというとこ ろですね。デジタルデータならではの遊びですが、 誤差問題で相当苦しみました。あと、今年やろう ― CBCNETの記事(http://www.cbc-net.com/dots/daito_ と思っているのは空中で何かをやるネタ、マルチカ manabe/manabe_01/)で、真鍋さんは次のようなこと メラやハイスピードカメラを使ったダンス作品、あ をおっしゃられていました。 「ジェネレート系の映像 とは陶芸。3Dプリンタになる可能性もあります。 や 音 楽 がProce55ing、openFrameworks、Max/ MSPのおかげで簡単に作れるようになって、作品 ― なるほど。oFでやりやすいのはどういった処理です (らしきもの)が世の中に氾濫してしまったので、そ か? また、弱点があるとするとすればどの辺りで ろそろ新しいアプローチを探さなくてはならない」 しょう? と。このことについてもう少し詳しく聞かせていただ けますか? M やはりC++だけあってかなり処理速度が速いですね。 なので、画像解析やパーティクルの生成はやりや M 音と映像のシンクを考えるというのは古くからある すいです。Flash等をやっている人はスピードにびっ テーマですよね。ヴィジュアライズはoFならMemo、 くりするのではないでしょうか。弱点というか難しい p5ならFlight404ぐらいすごければ、FFTした結果 ところは、C++がある程度わからないとちょっと難 から映像を生成するだけでも感動を与えることはで しいのかな、というのがありますよね、やっぱり。 きると思います。でも、そこまで到達するのは至難 ポインタとか、誰もが躓くところなのでしょうが。そ の業ですよね。そこの土俵に上がるのも良いと思う こを通らないで済むために作られたのがoFだと思 のですが、到達できない場合は他の視点から音と いますが、やはり基礎は必要になってきますね。将 映像の関係にアプローチするのもありかなと思いま 来は、もっと直感的にコーディングできるようにな す。例えばAlvaro Cassinelli君とやっているレーザ るんじゃないでしょうか。 ーのプロジェクトでは、二次元の平面ではなく立 体物のスキャニング結果から音を生成しているので Interview 02 : Daito Manabe すが、レーザーのトラッキング情報を500フレーム ほど音楽的な活動ではなかったですね。Hiphop 以上で音アプリに送信しているので30fpsの映像を JazzバンドをやってCDを数枚出したりもしましたが、 使った場合とは異なる実験ができます。先ほどの 今やっている活動は数学やIAMASからの影響の方 pa++ernのサウンド版も刺繍バイナリデータから作 が大きいと思います。ただ、音楽も数学もどちらも ったサウンドを再生、解析して刺繍データに戻すと 途中で断念してるわけです。ピアノは面白くないし ころに面白さがあるんです。正直言えば、彼らがや 数学は難しいし(笑) っているようなことに憧れて作ってみることもありま すが、ワナビー(Wanna be)系表現になって面白 ― なるほど。でも、今の時代は、何か1つのことを極 いものにならないですね。ヴィジュアライズもとっか める方向よりもむしろ、複数のものを組み合せてい かりとしては良いと思うのですが、どこかで独自の くハイブリッド的なアプローチが大事なんじゃない 視点を見つけられるとチャレンジできることが増え かなとよく思います。 て良いかもしれないです。 M いろいろ中途半端にやってきたおかげで、割と器用 ― なるほど。続いてopenFrameworks以外のもっと に対応できるようになったのかなというのはありま 一般的な話を聞かせてもらってもいいでしょうか。 すね。数学をやってよかったなと思うことはよくあり まず、真鍋さんはもともと数学への志向が強かった ます。数学は1つの言語でもあるので、理解できれ のですか? ばプログラム言語に書き換えることができますし、 相手が理解できれば数式でやりとりすることも可能 M うーん… 数学しかできなかったというか。 ― ずっと得意でした? ですし。便利なことは多いと思います。 ― 知っておくと、ちょっと見ただけである程度はわか るので、数式とかに臆さない方がいいですよね。 M 中学高校では代ゼミの模試で全国1位とか、偏差 値が80超えるとかそういうのは結構ありましたよ。 M そういえば、最近ある中学生からよく質問を受けて 学校のテストでは多分90点以下は取ったことがな いるんです。Processingを使っているらしいのです いですね。でも大学に入ってからは証明の問題ば が、 「こういう作品はProcessingで作れるかどう かりになってだんだん嫌になって。そこからの数学 か?」とか。 は辛かったですね。あっという間に落ちこぼれまし た。 「もう数学やめろ」と教授に怒られて退学を考 ― もしかして小林くん? えたこともありましたね。 M そうですそうです! 久保田さんのところにも質問 ― ゲームはやりましたか? ファミコンとか。 が来てるんですね(笑)。 M:小学校の初めの頃アメリカに住んでいたので、 ― 今やメルトモですよ(笑) 。最近彼はTwitterも始め ATARIをかなりやってましたね。その後はMSX、 ています。けっこう突っ込んだこと聞いてくる。 「こ PC88、X68000ですね。 ういったメディアアート作品を作りたいんだけど、こ ういう仕組みで実現できますか?」とかね。 ― 音楽への志向も強かったのですか? M 僕は「とりあえず数学の授業をまずちゃんと受けな M んー、そうですね… 両親がミュージシャンだった さい」とアドバイスした(笑) 。 ので子どもの頃は音楽をやらされてましたが、中学 に入る頃やめています。大学時代は週に6回バイト でDJをやっていた時期もありましたが、今思えばさ 126 | 127 (笑) M 早まるなと(笑) 。 すよね。いざ日々のアプリ使用やWebサイト閲覧の ログを取ってみたら一日にトータルで1時間近く ― でも実際、真鍋さんはこういった分野のヒーローと いうか、憧れている若い子たちは多いと思います。 M そうでしょうか… どうなんだろうな。プロジェクト Twitterをやっていたりしますからね。石橋氏はそれ でTwitterを退会しました(笑)。 よく、アイデアが先か技術が先かを聞かれるんです。 で言うと僕らは手をひたすら動かす末端というか、 海外、特にヨーロッパで。僕のやっていることって、 いちばん最後に作業する人間ですからね。 床をこすって音が出るとか、口の中が光るとか顔が ぴくぴく動くとか(笑) 、けっこうわかりやすいじゃ ― でもこれからは、実際に手を動かして作れる人が、 クライアント企業と対等に渡り合うということは重 ないですか。だから、何を目標にしているの?とい う話になるんです。でも、どっちが先かということは 要でしょうね。技術的なことに対するアドバイスや 考えないで作っているので、いつもうまく答えられ コンサルタントなどにもきちんとお金をいただいて、 ないんですよね。どっちが先、というのはないんです。 いいクライアントを育てていくことで、より良い仕事 が生まれるような土壌にしていきたいですね。 ―確かに、考えてみれば人間というのは複数のことを 同時に思考しているわけで、どっちが先か?という M そうですね。クライアントさんや代理店さんと一緒 に成長できるといいなと思って、海外のアーティス 質問には、同時です、というのがいちばん適当な 答えかもしれませんね。 トやデザイナーを呼んで「Flying Tokyo」というイベ ントをやったり、海外のフェスに行った後は現地の M トライアンドエラーをしているうちに面白いことを見 情報をシェアするための報告会をしています。 つける、ということが多いですよね。作品として成 ― 最後の質問です。テクノロジーの進歩と、人間の とやってみようって始めて。それで、そのまま終わる 表現や認知の拡がりの関係をどのように考えていま ものもたくさんあります。やってみたんだけど特に新 すか? しい発見もないし面白くもならなかったね、って。 立するかどうかわかってないけど、とりあえずちょっ ただそういうのを増やしていくと、なにかの時にそ M テクノロジーが進歩すればごくごく普通に表現は変 れが引っかかって、以前にやったあれとあれを組み わって行くでしょうし、認知も違ったものになると 合わせると面白くなりそうって感じでうまくいくこと 思います。逆に、これだけ情報に対する環境が変 も多いです。トライアンドエラーの積み重ねが、次 わっているのに影響を受けない方がすごいと思いま につながっていく。 すし。しかし、気をつけないといけない点も多いで [構成:BNN] Interview 02 : Daito Manabe 128 | 129 3 openFrameworks プログラミング中級編 田所 淳 この章では、さらに突っ込んでopenFrameworksのプログラミングを学んでいきます。 まず、openFrameworksの構造についてもう一度しっかりと理解したうえで、 単純な図形だけでなく、画像や動画、音声など様々なメディアを扱ったり、 オブジェクト指向プログラミングというプログラミングの手法について学んでいきます。 また、openFramewroksに機能を付加するアドオンの使用についてもとりあげます。 3 3–1–1 –1 プロジェクトの構造をより深く理解する openFrameworksのプロジェクト構造について 2章では、まずはプログラムに触れながら慣れていくということを目的にしていました。そのため、 厳密な定義よりも、まずは実行してみるということを重視してきました。しかし、これから高度なプ ロジェクトを作成していくにあたって、よりしっかりとopenFrameworksのプロジェクトの仕組みに ついて理解していく必要があります。この章ではまず、openFrameworksの基本構造について見 直していきたいと思います。 3–1–2 openFrameworksとC++の関係 ここまでの章では、openFrameworksは、C++というプログラミング言語をベースにした開発環 境だと解説してきました。しかし、具体的にC++とopenFrameworksがどのように関連しているの かということについて、解説を省いたままプログラムの作成を進めてきました。そこで、ここでしっ かりとopenFrameworksとC++の関連について、構造的な理解を深めていきたいと思います。 C++は汎用的なプログラミング言語で、1990年代以降、最もよく利用されているプログラミング 言語のひとつと言われています。C++はある特定の目的のためにあるのではなく、できるだけ広範 な用途に使用できるように設計されています。このC++は、C言語を拡張した言語として誕生しま した。C言語とC++の大きな違いは「クラス」を利用できるという点です。また、この「クラス」と いう概念をとり入れたことにより、C++は「オブジェクト指向プログラミング」と呼ばれます。つまり、 C++を理解するには、オブジェクト指向プログラミングという概念が理解への となります。 openFrameworksはC++をベースにしているので、やはりオブジェクト指向プログラミングという 概念が重要となります。 C++とopenFrameworksの関係を理解するにあたって、まず、オブジェクト指向プログラミングと いう概念について解説します。その知識を前提としたうえで、両者の関係について理解を深めて いきたいと思います。 3–1–3 オブジェクト指向プログラミングとは オブジェクト指向プログラミングとは、「オブジェクト」というプログラム機能の部品の集合が、相 130 | 131 互にメッセージを送り合いながらプログラムを構成していくというプログラミング技法です。それぞ れのオブジェクトは独立していて、自分自身で値や処理手順を保持しています。オブジェクトの一 つ一つが、小さなプログラムのモジュールであると考えられます。そうした小さなモジュール同士 が相互にメッセージを送り合うことで大きなプログラムを構成するというのが、オブジェクト指向 の基本的な考え方となります。 メッセージを送り合うオブジェクト オブジェクト = 属性(プロパティ)+ 動作(メソッド) オブジェクト指向の考え方を、より日常的で具体的な例で考えてみましょう。オブジェクトは日本 語にすると「物」です。オブジェクト指向プログラミングの構成単位であるこのオブジェクトは、私 たちの周囲にある物で例えてみることができます。オブジェクト指向プログラミングでは、物を2つ の観点から整理します。1つはその物固有の「属性」 、もう1つはその物自身に対する「動作」です。 属性のことを「プロパティ」、動作のことを「メソッド」と言い換えることもできます。この複数のプ ロパティとメソッドから構成されるオブジェクトの概念を図示すると、次のようになるでしょう。 2 ド ッ ソ メ メ ソ ッ ド 1 プロパティ 1 プロパティ 2 プロパティ 3 プロパティ 4 メ ソ ッ ド 3 4 ド ッ ソ メ 3–1–4 プロパティとメソッドから 構成されるオブジェクト この考え方は、実際に身の回りのものに当てはめてみるとより容易に理解可能になるでしょう。 オブジェクト―犬 属性(プロパティ):犬種、年齢、性別 動作(メソッド):歩く、吠える、 を食べる、寝る 3–1 openFrameworksプログラミング中級編 オブジェクト―テレビ 属性(プロパティ):画面サイズ、チャンネル、ボリューム 動作(メソッド):点ける、消す、チャンネル変更する、音量変更する オブジェクト―リンゴ 属性(プロパティ):色、重量、味 落 歩 く 長 す る 成 下 更 変 ル ネ ン ャ チ 更 る べ 食 変 腐 る 量 寝 る 犬 色 重量 味 音 画面サイズ チャンネル ボリューム す 熟 す 消 る え 吠 犬種 年齢 性別 点 け る 動作(メソッド):成長する、熟す、落下、腐る テレビ リンゴ 身のまわりのオブジェクト もちろん、現実の犬やテレビやリンゴはこんなに単純ではありません。もっとたくさんの属性や動 作を持っています。しかし重要なことは、物の全ての性質を写しとるのではなく、プログラムで必 要となる属性と動作を抽出するという操作です。プログラムの中のオブジェクトは、あくまでも物 そのものではなく、現実世界の中からそのプログラムの機能で必要なものだけを取り出して抽象 化された「物=オブジェクト」なのです。 3–1–5 クラス = オブジェクトの設計図 では、どのようにしたらオブジェクトをプログラム内に生成することができるのでしょうか。オブジ ェクト指向プログラミングでは、オブジェクトを生成するにはまずその設計図を作成しなければな りません。この設計図のことを「クラス」と呼びます。クラスに属性と動作の詳細を記述することで、 オブジェクトのふるまいを決定します。 クラスは設計図なので、そのままではプロブラムのモジュールとして動作することはできません。ク ラスを実際に使用するには、クラスをオブジェクトとして生成する必要があります。この操作を「イ ンスタンス化」といいます。 クラスからインスタンス化されるオブジェクトは1つとは限りません。1つのクラスから、必要に応じ て複数のオブジェクトを生成することも可能です。例えば、車の設計図を1つ作成すれば、それ を元にして何台でも車を製造することが可能になるということと同じです。このようにして、共通の 属性を持ち、同じ動作が可能なオブジェクトを、一度にたくさん生成することが可能となります。 132 | 133 インスタンス3 メ ソ 4 ッ ド ドッ ソ1 メ プロパティ1 プロパティ2 プロパティ3 プロパティ4 メ ソ ッ ド 3 クラスとインスタンス 3–1–6 インスタンス2 メ ソ 4 ッ ド ドッ ソ1 メ イン スタ ンス 化 プロパティ1 プロパティ2 プロパティ3 プロパティ4 4 ド ッ ソ メ メ ソ ッ ド 3 4 ド ッ ソ メ インスタンス化 メ 2 ソ ッ ド ドッ ソ メ3 プロパティ1 プロパティ2 プロパティ3 プロパティ4 クラス インスタンス1 メ 2 ソ ッ ド ドッ ソ メ3 メ ソ ッ ド 1 メ ソ ッ ド 1 2 ド ッ ソ メ 2 ド ッ ソ メ 化 ンス スタ イン プロパティ1 プロパティ2 プロパティ3 プロパティ4 C++ でのクラスの作成 では、実際にC++でクラスを作成してみましょう。身近な例として、犬を例にしてクラスを作成して みましょう。ただし、ここではとても単純化した犬を扱います。 クラス名―Dog 属性 name(犬の名前) 動作 bark(犬が吠える) まずは、XcodeでC++のコマンドライン用プロジェクトを作成します。Xcodeを起動して、 「ファイル」 →「新規プロジェクト」を選択し、新規プロジェクトのテンプレート選択画面を表示します。テン プレートの中から「Mac OS X」→「Application」→「Command Line Tool」を選択し、下部に 表示される「Type」のプルダウンメニューから「C++ stdc++」を選択してください(Xcode 3.1の場 合は、「Mac OS X」→「Command Line Utility」→「C++ Tool」を選択)。これは、C++でコマン ドラインで実行するツールを作成するテンプレートです。 Xcode 3.2の場合 3–1 openFrameworksプログラミング中級編 Xcode 3.1の場合 新規プロジェクトに、 「dog」という名前をつけて保存してください。すると、 「dog」 「dog.1」 「main. cpp」という3つのファイルが生成されます。この中からmain.cppを選択し、 「エディタ」ボタンを 押してファイルの内容を表示します。 main.cpp #include <iostream> int main (int argc, char * const argv[]) { // insert code here... std::cout << "Hello, World!\n"; return 0; } まず、このプログラムのメインとなるmain関数の中身を空にして、必要となる設定を追加します。 #include <iostream> using namespace std; int main (int argc, char * const argv[]) { //メイン関数 } ここにDogオブジェクトの設計図となるクラスの定義を追加します。クラスの定義は、main関数 の手前で、下記のように行います。 #include <iostream> using namespace std; class Dog { // Dogクラスの定義 }; //Dogクラスの定義終了 (最後に";"が入るのに注意) int main (int argc, char * const argv[]) { //メイン関数 } 134 | 135 カプセル化 ― privateとpublicの区別 クラスの内部は、privateとpublicという2つの領域に分けられています。これは、外部からその属 性や動作を参照することができるかできないか、という区分をしています。privateの領域はクラス の内部からしか参照できません。反対に、publicの領域は外部の他のクラスから参照して利用す ることが可能です。オブジェクト指向プログラミングでは、外部から参照する必要のない部分は 極力隠すことによって、プログラムのパーツの再利用を容易にして開発効率を高めようと考えてい ます。この特徴を「カプセル化」と呼びます。 カプセル化は、時計について考えると理解しやすいかもしれません。普段、私たちが時計を使用 する際には、時計の針(デジタル時計では文字表示)の状態や、時刻を合わせるダイアル、目覚 まし機能など、実際に利用する部分の機能について理解さえすれば時計の機能をフルに活用する ことができます。しかし、ほとんどの人は時計の機械の内部の機構について理解していません。 時計の内部構造は、使用する人には必要ないので、目にふれないところに隠されています。これ がカプセル化です。時計で実際に使用する針や文字盤、時刻合わせの機能、目覚まし機能などは、 public領域に属します。一方、時計内部の複雑な機構はprivate領域に属すると考えられます。 今回作成するDogクラスは、全ての属性、動作は外部へ公開することにします。つまり全てを public領域に配置することにします。 属性(プロパティ)は変数、動作(メソッド)は関数 では実際にC++のクラスの中で、属性(プロパティ)と動作(メソッド)はどのように定義すればよ いのでしょうか。属性はそのオブジェクトの状態を表すことができればよいので、変数を使えばそ の役割を果たすことが可能です。 オブジェクトの動作(メソッド)は、複数の処理のまとまりとして定義すれば良いと考えます。関数 を使うことでこの役割を記述することが可能となります。 メソッドの定義 では、実際にDogクラスの定義の中に、犬の名前(name)と、犬が吠える動作(bark)を定義 してみましょう。 #include <iostream> using namespace std; class Dog { // Dogクラスの定義 public: string name; //犬の名前 (状態) void bark(); //犬が吠える (動作) }; //Dogクラスの定義終了 (最後に";"が入るのに注意) int main (int argc, char * const argv[]) { //メイン関数 } 3–1 openFrameworksプログラミング中級編 次にbark ( ) 関数の処理の内容を記述します。クラスのメソッドの定義は、下記の書式で行います。 《戻り値の型》《クラス名》::《関数名》(《引数の型》 《変数名》){ //《関数の内容》 } この書式に沿って、Dogクラスのメソッドbark ( ) を書いてみましょう。 void Dog::bark() { //Dogのメソッド bark() の定義 } bark ( ) 関数の処理の内容を記述します。下記のコードは、bark ( ) 関数が実行されると、犬の名 前(name)と鳴き声( 「ワンワン!」 )を表示するように記述しています。 void Dog::bark() { //Dogのメソッド bark() の定義 //プログラムのデータ出力に、名前と鳴き声を表示 cout << name << "「ワンワン!」" << endl; } ここまでで、プログラムの全体は以下のようになりました。次にmain ( ) 関数でプログラム全体の 処理を記述していきます。 #include <iostream> using namespace std; class Dog { // Dogクラスの定義 public: string name; //犬の名前 (状態) void bark(); //犬が吠える (動作) }; //Dogクラスの定義終了 (最後に";"が入るのに注意) void Dog::bark() { //Dogのメソッド bark() の定義 //プログラムのデータ出力に、名前と鳴き声を表示 cout << name << "「ワンワン!」" << endl; } int main (int argc, char * const argv[]) { //メイン関数 } 136 | 137 犬を生成 ― クラスのインスタンス化 3–1–4で述べたように、クラスはそのままではプログラムのモジュールとして使用することはできま せん。クラスはあくまで設計図であり、実際のプログラムではないからです。クラスを使用するには、 「インスタンス化」という処理をする必要があります。インスタンス化の手順は、変数の定義に似 ています。クラス名とそのクラスを格納するインスタンス名を記述することで、クラスがインスタン ス化されます。 main ( ) 関数の中で、Dogクラスをインスタンス化した「hachi」を生成してみましょう。 名前をつける ― プロパティに値を代入 クラス内部の属性(プロパティ)に、外部から値を設定してみましょう。クラスのプロパティの中で、 publicに宣言されている変数であれば、下記の書式でクラスの外部からも値の参照や代入が可 能となります。 《インスタンス名》.《変数名》 この書式をDogクラスのインスタンス「hachi」の犬の名前「name」にあてはめると、下記の指定 で参照できるようになります。 hachi.name この書式を用いて、hachiの名前をハチにしてみましょう。 #include <iostream> using namespace std; class Dog { // Dogクラスの定義 public: string name; //犬の名前 (状態) void bark(); //犬が吠える (動作) }; //Dogクラスの定義終了 (最後に";"が入るのに注意) void Dog::bark() { //Dogのメソッド bark() の定義 //プログラムのデータ出力に、名前と鳴き声を表示 cout << name << "「ワンワン!」" << endl; } int main (int argc, char * const argv[]) { //メイン関数 Dog hachi; //Dogクラスをインスタンス化して、hachiを生成 hachi.name = "ハチ"; //hachiのプロパティ nameに"ハチ"を代入 } 3–1 openFrameworksプログラミング中級編 犬が吠える ― メソッドの実行 最後にメソッドbark ( ) を実行させてみましょう。メソッドの実行も変数の際とやり方は変わりませ ん。インスタンス名に関数名を指定して、クラスの外部からメソッドを実行します。引数がある場 合は ( ) の中に引数を指定します。 《インスタンス名》.《変数名》(《引数》); では、実際にプログラムの中でbark ( ) 関数を実行させてみます。 list 3–1_a #include <iostream> using namespace std; class Dog { // Dogクラスの定義 public: string name; //犬の名前 (状態) void bark(); //犬が吠える (動作) }; //Dogクラスの定義終了 (最後に";"が入るのに注意) void Dog::bark() { //Dogのメソッド bark() の定義 //プログラムのデータ出力に、名前と鳴き声を表示 cout << name << "「ワンワン!」" << endl; } int main (int argc, char * const argv[]) { //メイン関数 Dog hachi; //Dogクラスをインスタンス化して、hachiを生成 hachi.name = "ハチ"; //hachiのプロパティ nameに"ハチ"を代入 hachi.bark(); //hachiのメソッドbark()を実行 } これで、プロジェクトの完成です。 プログラムの実行 早速、作成したプログラムを実行してみましょう。実行したプログラムからの出力を確認するため に、 「実行」→「コンソール」を選択して、コンソール画面を表示します。ここには、プログラムの 実行過程で出力されたメッセージが表示されます。bark ( ) 関数で指定した、cout命令からの出 力もこのコンソールに出力されます。 コンソールを表示したら、 「ビルドと実行(ビルドして進行) 」をクリックしてプログラムをビルドして 実行します。正しくプログラムが実行されると、出力されるメッセージの中に下記のような表示が あるはずです。 138 | 139 run [Switching to process xxxx] ハチ「ワンワン!」 実行中... Debugger stopped. Program exited with status value:0. 無事、ハチが「ワンワン!」と吠えてくれました。 3–1–7 ファイルを分割して管理する 現状では、Dogクラスの定義やメソッドの処理の内容など、全てのプログラムが1つのファイル 「main.cpp」に格納されています。現状のクラスの定義はとてもシンプルなものなので、本来であ ればこのままで十分なのですが、今後のopenFrameworksの理解のために、クラスを独立したフ ァイルとして書き出してみましょう。 C++のクラスファイルは、独立したファイルとして記述することが可能です。また、C++ではプログ ラムが大きくなるにつれて、クラスの定義やクラス全体に適用される変数の宣言などの管理を容易 にするため、共通する宣言の部分を「ヘッダファイル」にまとめておくと便利です。 先程のDogクラスを例に、クラスの宣言をヘッダファイルとして独立させ、またそれに対応するメ ソッドの定義もmain.cppから独立したファイルに分割してみましょう。 プロジェクトに新規にソースファイルを追加するには、Xcodeの「グループとファイル」にあるファ イルのリストから、 「dog」→「Source」のフォルダを[control]+クリック(もしくは右クリック)し て、表示されるメニューから「追加」→「新規ファイル」を選択します。 3–1 openFrameworksプログラミング中級編 すると新規ファイルのテンプレートを選択する画面が表示されます。まず、 「C and C++」→「C++ File」を選択し、ファイル名を「Dog.cpp」にします。その下にある「同時に"Dog.h"も作成」のチ ェックボックスをチェックします。保存場所やその他の設定はそのままで、 「完了ボタン」を押します。 この操作で、Sourceフォルダの中にDog.hとDog.cppの2つのファイルが追加されます。 Xcode 3.1の場合 Xcode 3.2の場合 この操作によって、Sourceフォルダの中には「main.cpp」に 加えて「Dog.h」 「Dog.cpp」の3つファイルが格納されている はずです。 それぞれのファイルに、main.cppから機能を分割していきます。以下のように、main.cppから Dog.h、Dog.cppへコピー&ペーストしてください。 list 3–1_b #include "Dog.h" main.cpp int main (int argc, char * const argv[]) { //メイン関数 Dog hachi; //Dogクラスをインスタンス化して、hachiを生成 hachi.name = "ハチ"; //hachiのプロパティ nameに"ハチ"を代入 hachi.bark(); //hachiのメソッドbark()を実行 } 140 | 141 Dog.h #include <iostream> using namespace std; class Dog { // Dogクラスの定義 public: string name; //犬の名前 (状態) void bark(); //犬が吠える (動作) }; //Dogクラスの定義終了 (最後に";"が入るのに注意) Dog.cpp #include "Dog.h" void Dog::bark() { //Dogのメソッド bark() の定義 //プログラムのデータ出力に、名前と鳴き声を表示 cout << name << "「ワンワン!」" << endl;} } main.cppの冒頭にある「#include "Dog.h"」という表記は「インクルード文」といい、main.cppの 中でヘッダファイルDog.hの内容を読み込むための指定です。このインクルード文によって、ファ イルが分割されていても、main.cppはDog.hの内容を参照することができるようになります。Dog. hを読み込むインクルード文は、Dog.cppの冒頭にも記述されています。これも、main.cppと同様 に、Dog.cppからDog.hのクラス定義を参照するための仕組みです。 インクルード文のように、先頭に「#」の付いた命令をプリプロセッサ命令といいます。プリプロセ ッサ命令とは、ソースプログラムをビルド(コンパイル)する前にソースプログラムに対して行われ る前処理に関する指定です。C++のコンパイラは、プログラムをビルドする前にあらかじめそれぞ れのファイルの関連をインクルード文を見て整理したうえで、コンパイルしているわけです。 プリプロセッサ命令の末尾には、セミコロン「;」が付かないことに注意してください。 3–1–8 UMLクラス図 ソフトウェア工学の分野では、オブジェクトの構造を記述するための標準化された規格が存在し ます。現在広く使用されているオブジェクトのモデリング記述言語の規格としては、UML(Unified Modeling Language、統一モデリング言語)という仕様記述言語があげられます。UMLには用 途に応じて様々な構造の記述のための図があるのですが、その中で、クラスの概念とその関係を 記述するための記述モデルとして、「UMLクラス図」というものがあります。 では、今回作成したDogクラスをUMLクラス図で表現してみましょう。クラスは長方形で記述され、 3つの行に分割されています。いちばん上の行にはクラスの名称を記述します。その下の行には、 クラスのプロパティを記述します。プロパティは「プロパティの名称:データ型」という書式で記入 3–1 openFrameworksプログラミング中級編 します。さらにその下の行には、 クラスのメソッドを記入します。メソッドは 「メソッドの名称:戻り値」 という書式になります。プロパティとメソッド共に、複数存在する場合には、複数行に分けて列挙 していきます。 また、それぞれのプロパティとメソッドの先頭には、その可視性(publicかprivateか)に関する目 印を付けることができます。先頭につける記号によって以下のように定義されます。 「+」はpublic 「-」はprivate これらの約束事を踏まえて、DogクラスをUMLクラス図で表現してみます。クラスの内容を簡素で わかりやすく表現することが可能です。 Dog + name:string + bark():void 3–1–9 クラスとしてtestAppを捉え直す ここまでは、純粋なC++のクラスの構造について解説してきました。なぜここまで延々とC++のク ラスの構 造を説 明したきたのかというと、このクラスの構 造を理 解できるようになると、 openFrameworksのプロジェクトの構造がとてもすっきりと捉えることが可能となるからです。 先程のDogクラスの例で最終的に作成した3つのファイル、 「main.cpp」 「Dog.h」 「Dog.cpp」と、 新規に生成されたopenFrameworksのプロジェクトで生成されるファイル 「main.cpp」 「testApp.h」 「testApp.cpp」を見比べてみましょう。その構造はとてもよく似ていることがわかると思います。 main.cpp #include "ofMain.h" #include "testApp.h" #include "ofAppGlutWindow.h" int main(){ ofAppGlutWindow window; ofSetupOpenGL(&window, 1024,768, OF_WINDOW); ofRunApp(new testApp()); } testApp.h #ifndef _TEST_APP #define _TEST_APP 142 | 143 #include "ofMain.h" class testApp : public ofBaseApp { 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(int x, int y, int button); void windowResized(int w, int h); }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ } void testApp::update(){ } void testApp::draw(){ } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ } void testApp::mousePressed(int x, int y, int button){ 3–1 openFrameworksプログラミング中級編 } void testApp::mouseReleased(int x, int y, int button){ } void testApp::windowResized(int w, int h){ } C++のクラスについて理解したうえで、この空白の「testApp.h」 「testApp.cpp」を眺めてみると、 実は今までのopenFrameworksのプログラムを作成するという作業は、testAppというクラスにつ いて記述していたことに気づくのではないかと思います。setup ( ) やupdate ( ) 、draw ( ) など、今 まで使用してきた関数内での処理は全て、testAppクラスの動作(=メソッド)について定義して いたことになります。 このtestAppクラスを、先程のDogクラスと同様にUMLクラス図に書き出すと、下記のようになり ます。 testApp + + + + + + + + + + testApp::setup():void testApp::update():void testApp::draw():void testApp::keyPressed(int key):void testApp::keyReleased(int key):void testApp::mouseMoved(int x, int y):void testApp::mouseDragged(int x, int y, int button):void testApp::mousePressed(int x, int y, int button):void testApp::mouseReleased(int x, int y, int button):void testApp::windowResized(int w, int h):void 2章では、testAppクラス全体に適用される変数をtestApp.cppの冒頭に記述していました。しかし、 クラスの構造を明確にするという観点から考えると、本来はtestApp.cppのpublic領域に記述す るほうが、testAppクラスの状態(=プロパティ)であるという意味付けがより明快となるでしょう。 今後のサンプルでは、クラス全体に適用される変数、つまりクラスのプロパティはtestApp.hに記 述するようにします。 3–1–10 testAppはofBaseAppの子供 このように、testAppクラスの構造はC++のクラスとして捉えることが可能です。しかし、testApp クラスは普 通のC++クラスとは大きく異なる点もあります。testAppクラスのsetup ( ) 関 数や update ( ) 関数など、あらかじめ用意されているメソッドには、画面表示の詳細やウィンドウの表 示のやり方など細かな指定をすることなく、最低限の記述でアプリケーションが生成されていまし た。実際にC++で一からこれと同じことをやろうとすると、この段階に到達するまでに膨大なプロ グラミングをする必要があります。なぜopenFrameworksの環境ではこの手間を省くことができる 144 | 145 のでしょうか。 は、testAppクラスのクラス定義の最初の部分にあります。testApp.hの6行目に注目してくださ い。 class testApp : public ofBaseApp { クラス指定の際に、testAppの後ろに「: public ofBaseApp」という記述があります。これは、 testAppクラスは「ofBaseApp」というクラスの状態や動作を全て引き継いでいる、ということを 意味しています。つまり、testAppクラスでは最初からofBaseAppクラスで定義された状態や動 作を何もしなくても受け継いでいるのです。このことを、オブジェクト指向プログラミングでは「継 承」と呼びます。継承はオブジェクト指向プログラミングの重要な概念の1つです。 車で例えるなら、 「車」というクラスを継承すれば、そこから「スポーツカー」 「救急車」 「トラック」 など様々な派生するクラスを生成できるというようなイメージです。 testAppクラスは、ofBaseAppクラスを継承したクラスです。そして、ofBaseAppクラスは、画面 の 表 示 の 処 理 や、 ウィンドウの 処 理、 アニメー ション の 際 の 画 面 の 書 き 換 えなど、 openFrameworksでアプリケーションを構築する際のエンジンとなっているわけです。この ofBaseAppクラスのお陰で、openFrameworksの環境では難しいことは考えることなく、創造的 な部分にのみ集中してプログラミングをすることが可能となっています。 3–1–11 openFrameworksプロジェクト構造 main.cppとtestApp.hの冒頭で、「ofMain.h」というヘッダファイルがインクルードされています。 このofMain.hは、openFrameworksの様々な機能を定義したヘッダファイルをまとめて読み込ん でいます。このofMaih.hをインクルードすることで、openFrameworksの多岐にわたる機能が一 括して使用できるようになっているのです。実際にエディタでofMain.hを開いてみると、下記のよ うに定義されています。様々なヘッダファイルのインクルード文から構成されていることがわかります。 ofMain.h #ifndef OF_MAIN #define OF_MAIN //-------------------------// utils #include "ofConstants.h" #include "ofMath.h" #include "ofUtils.h" #include "ofTypes.h" //-------------------------- 3–1 openFrameworksプログラミング中級編 // communication #ifndef TARGET_OF_IPHONE #include "ofSerial.h" #include "ofStandardFirmata.h" #include "ofArduino.h" #endif //-------------------------// graphics #include "ofTexture.h" #include "ofTrueTypeFont.h" #include "ofGraphics.h" #include "ofImage.h" //-------------------------// app #include "ofBaseApp.h" #include "ofAppRunner.h" //-------------------------// audio #include "ofSoundStream.h" #include "ofSoundPlayer.h" //-------------------------// video #ifndef TARGET_OF_IPHONE #include "ofVideoGrabber.h" #include "ofVideoPlayer.h" #endif //-------------------------// events #include "ofEvents.h" #endif 実は、openFrameworksとは独自に開発された1つの大きな存在ではありません。その実体は、 画像、音声、動画など、様々な機能をもったクラスの集合から成り立っています。その多くは openFrameworks独自のものではなく、単体の機能として開発された様々なオープンソースのプ ロジェクトです。openFrameworksは、そうしたバラバラに存在していた様々な既存の機能を1つ 146 | 147 に繋ぎあわせてすぐに使用できるようにした、いわばプログラミングのための「糊」のようなものと 言えます。この便利な糊によって、数多くの便利な機能をすぐに簡単に使用できるようになってい るのです。 こうしたopenFrameworksの構造を図式化すると右記のようになります。 プログラムの開始 main.cpp testApp.h コードを書くところ testApp.cpp openFreameworks の機能 ofSimpleApp,ofGraphics,ofImage,ofTruTypeFont, ofVideplayer,ofVideoGrabber,ofTexture,ofSoundplayer, ofSoundStream,ofSerial,ofMath,ofUtils ベースとなるライブラリ群 openGL GLUT freeimage freetype fmod rtaudio quicktime openCV これまでのサンプルの中で出てきた、ofRect ( ) 、ofBackground ( ) 、ofGetScreenWidth ( ) などの 「of」という接頭子がついた関数は、全てopenFrameworksのクラス群で定義された関数です。 これらの「of 〜」という関数を効果的に用いることが、openFrameworksを使いこなすコツです。 openFrameworksで独自に拡張された機能は、全てopenFrameworksのドキュメントページから 参照できます。このページを辞書代わりに使用するとよいでしょう。 URL 3–1 http://www.openframeworks.cc/documentation openFrameworksプログラミング中級編 3 3–2–1 –2 いろいろなメディアを扱う openFrameworksで扱うことのできるメディア 前のセクションでは、openFrameworksはプログラミングのための様々な機能を統合する「糊」 のようなものであるという説明をしました。openFrameworksに用意された様々なライブラリを使 うことで、ここまでのサンプルで使用してきた単純な図形の描画だけでなく、様々なメディアを使 用して表現したり、ユーザーとコミュニケーションをすることが可能となります。 では、実際にどのようなメディアを扱うことができるのでしょうか。ここでは、openFrameworks で利用可能な様々なメディアを概観していきたいと思います。音、画像、フォント、動画というよ うに、取り扱うメディアごとにその入出力の方法、加工のやり方などを学んでいきます。 音 音響生成 サウンドの入力 サウンドファイルの再生 画像 画像ファイルの読込み 画面のキャプチャー 画像ファイルの書き出し フォント フォントファイルの読込み 動画 動画ファイルの再生 カメラから動画をキャプチャ 3–2–2 音を扱う ―音とは何か まずはじめに、音を扱ってみたいと思います。openFrameworksを利用して音響を生成したり再 生する前に、まずはそもそも音とは何か、どうしたら音をコンピュータで扱うことができるのかとい 148 | 149 った基本的な部分から考えてみましょう。 音とは「振動」です。物を叩いたり、弦を弾いたりして、振動している場所から音は発生します。 この振動は「音波」という言葉からも連想されるように空気中を伝わる「波」として耳に届きます。 ただし波といっても、海岸に押し寄せる波のように、空気が上下に振動しながら空間を動いてい るわけではありません。音波は「疎密波」といって、振動によって生まれた空気の密度が濃い部 分と薄い部分の繰り返しが空気中を伝わってくるのです。海の波を「横波」とすると、音は振動 が波の進行方向と同じ方向である「縦波」と考えられます。 音の伝わる方向 音源 この空気の密度を縦軸に時間を横軸にとって、その変化をプロットしたものが「波形」と呼ばれる ものです。波形はその形状の違いによって音色の違いを生み出します。また、波形の周期が短い ほど高い音になり、波形の振幅が多いほど大きな音量になります。1秒間に音が何回周期を持つ かという単位を周波数といい、Hz(ヘルツ)という単位で表します。例えば、440Hzは1秒間に 440回の周期がある音の高さを表します。人間の耳は大体20Hzから20,000Hzまでを知覚できる と言われています。 密 空気の密度 疎 1 周期 3–2–3 コンピュータで音を扱うには コンピュータは、波形の形状を記録して音として再生しています。ただし、コンピュータは連続す るデータ(アナログデータ)をそのまま扱うことはできません。何らかの手段で数値の集合として 変換しない限り、コンピュータはデータとして扱うことはできないからです。 コンピュータで音を扱う際には、音の波形を一定の時間間隔で区切って、その区切った時間ごと の波形の位置を記録して数値の集合に変換しています。この処理のことを「サンプリング」と呼び ます。また、サンプリングを1秒間に何回行うのかという単位を、サンプリング周波数といいます。 例えば、CDの場合は1秒間に44,100回サンプリングしています。また、DVDでは48,000回サン 3–2 openFrameworksプログラミング中級編 プリングしています。サンプリング周波数の数値が大きくなるほど、高周波の成分の音を記録する ことが可能となり、より高品質の音になると言えます。 コンピュータで音を再生する方法には、大きく分けて2つアプローチがあります。1つは全く何もな い状態から波形の形状を計算して生成し、音として再生する方法です。この方法を「音響合成」 といいます。もう1つの方向性は、現実世界の音をサンプリングして波形として記録し、その波形 に編集や加工をしたうえで音として再生成するという方法です。この方法は「サンプリング&プレ イバック」と呼ばれます。もちろん、この2つの方法を組み合せることも可能です。 openFrameworksでは、この「音響合成」と「サンプリング&プレイバック」両方の方法が可能で す。実際に試していきましょう。 3–2–4 波形の生成 ― ofSoundStream まず最初は「音響合成」、 つまり一から音を生成する方法について試してみたいと思います。まずは、 いちばん簡単な例としてsin波を生成するサンプルを作成してみます。sin波というのは、その名前 の通り、三角関数のsinの値の変化をそのまま波形として使用したものです。数学的には、あらゆ る周期的な波形は様々なsin波の集合として表現できることが証明されています。つまりsin波は全 ての波形の中で、最も濁りのない純粋な波形と考えられ、 「純音」と呼ばれることもあります。 sin波は円周上を周回する点の高さをプロットしていったものとも考えられます。円周上を1周すると、 ちょうどsin波の1周期になります。三角関数を計算する際には角度の単位は1周が1度∼ 360度の 度数法ではなく、円の弧の半径の長さに対する比率で角度を測る弧度法(ラジアン)という単位 で計算します。ラジアンでは円周上での周回は0 ∼ 2πで表現されます。 0,2π π/4 7π/4 π/2 3π/2 5π/4 3π/4 π 150 | 151 では、sin波を生成し、音として出力するプログラムを作成してみましょう。Xcodeから「ファイル」 →「新規プロジェクト」を選択し、「SineWave1」という新規プロジェクトを作成してください。以 下のようにプロジェクト内のソースコードを編集します。 list 3–2_a testApp.h #ifndef _TEST_APP #define _TEST_APP #include "ofMain.h" class testApp : public ofBaseApp { 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(int x, int y, int button); void windowResized(int w, int h); void audioRequested(float * input, int bufferSize, int nChannels); int sampleRate; //サンプリング周波数 float pan; //定位 float amp; //音量 float phase; //位相 float frequency; //周波数 }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ sampleRate = 44100; //サンプリング周波数 amp = 0.5; //音量 pan = 0.5; //左右の定位 phase = 0; //位相 frequency = 440; //周波数 3–2 openFrameworksプログラミング中級編 ofSoundStreamSetup(2, 0, this); //サウンドストリームの準備、左右2ch } void testApp::update(){ } void testApp::draw(){ } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ } void testApp::mousePressed(int x, int y, int button){ } void testApp::mouseReleased(int x, int y, int button){ } void testApp::windowResized(int w, int h){ } void testApp::audioRequested (float * output, int bufferSize, int nChannels){ float sample; //出力する音のサンプル float phaseDiff; //位相の変化 //1サンプルあたりの位相の変化を計算 phaseDiff = TWO_PI * frequency / sampleRate; //バッファのサイズ分の波形を生成 for (int i = 0; i < bufferSize; i++){ //位相を更新 phase += phaseDiff; while (phase > TWO_PI){ 152 | 153 phase -= TWO_PI; } //Sin波を生成 sample = sin(phase); // オーディオアウト、左右2ch output[i * nChannels] = sample * pan * amp; output[i * nChannels + 1] = sample * pan * amp; } } プログラムを実行すると、「ピー」という音が聞こえるはずです。聞こえない場合はコンピュータの 音声出力がONになっているか確認してください。 音を出力するためにaudioRequested ( ) という関数を追加しています。これは、設定したチャンネ ル 数 で、 指 定 し た サ イズ の 波 形 を サ ウンドとして 出 力 す る た め の 機 能 で す。 この audioRequested ( ) を使用するためには、まずsetup ( ) 中でofSoundStreamSetup ( ) という関数 で、あらかじめ基本的なサウンド出力の設定を指定しておく必要があります。それぞれの引数に下 記のように指定します。 ofSoundStreamSetup(《出力チャンネル数》, 《入力サンプル数》, 《ofSimpleApp へのポインタ》) 今回はサウンド入力はなし、ステレオ2chで出力したいので、 ofSoundStreamSetup(2, 0, this); となります。 audioRequested ( ) の中では、実際にsin波の波形を計算して出力しています。波形は下記の手 順で計算しています。 出力するsin波の周波数とサンプリング周波数から、1 サンプルあたりの位相(時間)の変化を計算 バッファーサイズだけ以下の計算を繰り返し 位相(時間)を更新 位相(時間)とsin関数で、現在の1サンプルを計算 オーディオ出力にサンプルを書き出し では次に、出力した音の波形を視覚的に確認できるようにしてみましょう。 まず、波形の出力用に配列を2つ用意します。lAudioは左チャンネルの波形の表示用、rAudioは 右チャンネルの波形の表示用とします。audioRequested ( ) で波形を計算した際に、音を出力と 同時にlAudio、rAudioにサンプルの値を配列に記録します。そこで記録した配列の値をもとに、 draw ( ) 内で波形を描画しています。 3–2 openFrameworksプログラミング中級編 list 3–2_b #ifndef _TEST_APP testApp.h #define _TEST_APP #include "ofMain.h" class testApp : public ofBaseApp { 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(int x, int y, int button); void windowResized(int w, int h); void audioRequested(float * input, int bufferSize, int nChannels); int sampleRate; //サンプリング周波数 int bufSize; //バッファの大きさ float pan; //定位 float amp; //音量 float phase; //位相 float frequency; //周波数 float lAudio[256]; //左チャンネル波形 float rAudio[256]; //右チャンネル波形 }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ sampleRate = 44100; //サンプリング周波数 bufSize = 256; amp = 0.5; //音量 pan = 0.5; //左右の定位 phase = 0; //位相 frequency = 440; //周波数 154 | 155 ofSetFrameRate(30); ofBackground(32, 32, 32); ofSoundStreamSetup(2, 0, this); //サウンドストリームの準備、左右2ch } void testApp::update(){ } void testApp::draw(){ float audioHeight = ofGetHeight()/2.0f; float phaseDiff = ofGetWidth()/float(bufSize); ofSetColor(0,0,255); //左チャンネル波形を描画 for (int i = 0; i < bufSize; i++){ ofLine(i*phaseDiff, audioHeight/2, i*phaseDiff, audioHeight/2+lAudio[i]*audioHeight); } //右チャンネル波形を描画 for (int i = 0; i < bufSize; i++){ ofLine(i*phaseDiff, audioHeight/2*3, i*phaseDiff, audioHeight/2*3+rAudio[i]*audioHeight); } } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ } void testApp::mousePressed(int x, int y, int button){ } void testApp::mouseReleased(int x, int y, int button){ 3–2 openFrameworksプログラミング中級編 } void testApp::windowResized(int w, int h){ } void testApp::audioRequested(float * output, int bufferSize, int nChannels){ float sample; //出力する音のサンプル float phaseDiff; //位相の変化 //1サンプルあたりの位相の変化を計算 phaseDiff = TWO_PI * frequency / sampleRate; //バッファのサイズ分の波形を生成 for (int i = 0; i < bufferSize; i++){ //位相を更新 phase += phaseDiff; while (phase > TWO_PI){ phase -= TWO_PI; } //Sin波を生成 sample = sin(phase); // オーディオアウト、左右2ch lAudio[i] = output[i * nChannels] = sample * pan * amp; rAudio[i] = output[i * nChannels + 1] = sample * pan * amp; } } 実行結果 最後に、 より色々な波形を出力できるようにしてみましょう。キーボードの入力で、 [1]はsin波、 [2] はノコギリ波、[3]は矩形波、[4]は三角波、 [5]はノイズというように、様々な波形を切り替 156 | 157 えられるようにします。さらに[+]で音量を上げ、[-]で音量を下げています。またマウスのx軸 上の位置によって左右のバランスを変化させて、y軸上の位置で周波数を変化できるようにしてみ ましょう。 list 3–2_c #ifndef _TEST_APP testApp.h #define _TEST_APP #include "ofMain.h" class testApp : public ofBaseApp { 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(int x, int y, int button); void windowResized(int w, int h); void audioRequested(float * input, int bufferSize, int nChannels); int sampleRate; //サンプリング周波数 int bufSize; //バッファの大きさ float pan; //定位 float amp; //音量 float phase; //位相 float frequency; //周波数 int waveShape; //波形 float lAudio[256]; //左チャンネル波形 float rAudio[256]; //右チャンネル波形 }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ 3–2 openFrameworksプログラミング中級編 sampleRate = 44100; //サンプリング周波数 bufSize = 256; amp = 0.5; //音量 pan = 0.5; //左右の定位 phase = 0; //位相 frequency = 440; //周波数 waveShape = 1; //波形 ofSetFrameRate(30); ofBackground(32, 32, 32); ofSoundStreamSetup(2, 0, this); //サウンドストリームの準備、左右2ch } void testApp::update(){ } void testApp::draw(){ Float audioHeight = ofGetHeight()/2.0f; float phaseDiff = ofGetWidth()/float(bufSize); ofSetColor(0,0,255); //左チャンネル波形を描画 for (int i = 0; i < bufSize; i++){ ofLine(i*phaseDiff, audioHeight/2, i*phaseDiff, audioHeight/2+lAudio[i]*audioHeight); } //右チャンネル波形を描画 for (int i = 0; i < bufSize; i++){ ofLine(i*phaseDiff, audioHeight/2*3, i*phaseDiff, audioHeight/2*3+rAudio[i]*audioHeight); } } void testApp::keyPressed (int key){ //キー入力によって波形選択と音量調整 switch(key){ case '-': //音量下げる amp -= 0.05; amp = MAX(amp, 0); break; case '+': //音量上げる amp += 0.05; 158 | 159 amp = MIN(amp, 1); break; case '1': //sin波 waveShape = 1; break; case '2': //ノコギリ波 waveShape = 2; break; case '3': //矩形波 waveShape = 3; break; case '4': //三角波 waveShape = 4; break; case '5': //ホワイトノイズ waveShape = 5; break; } } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y ){ pan = (float)x / (float)ofGetWidth(); float heightPct = (float(ofGetHeight()-y) / float(ofGetHeight())); frequency = 4000.0f * heightPct; if(frequency < 20){ frequency = 20; } } void testApp::mouseDragged(int x, int y, int button){ } void testApp::mousePressed(int x, int y, int button){ } void testApp::mouseReleased(int x, int y, int button){ } void testApp::windowResized(int w, int h){ } 3–2 openFrameworksプログラミング中級編 void testApp::audioRequested(float * output, int bufferSize, int nChannels){ float sample; //出力する音のサンプル float phaseDiff; //位相の変化 //1サンプルあたりの位相の変化を計算 phaseDiff = TWO_PI * frequency / sampleRate; //バッファのサイズ分の波形を生成 for (int i = 0; i < bufferSize; i++){ //位相を更新 phase += phaseDiff; while (phase > TWO_PI){ phase -= TWO_PI; } // 波形を選択 switch (waveShape) { case 1: // Sin波 sample = sin(phase); break; case 2: // ノコギリ波 sample = - phase / PI + 1; break; case 3: // 矩形波 sample = (phase < PI) ? -1 : 1; break; case 4: // 三角波 sample = (phase < PI) ? -2 / PI * phase + 1 : 2 / PI * phase - 3; break; case 5: // ホワイトノイズ sample = ofRandom(-1, 1); } // オーディオアウト、左右2ch lAudio[i] = output[i * nChannels] = sample * pan * amp; rAudio[i] = output[i * nChannels + 1] = sample * (1.0 - pan) * amp; } } 160 | 161 実行結果 3–2–5 サウンドの入力 ― ofSoundStream では次に、サウンドの出力ではなく、逆に外部のサウンドを入力して波形を表示してみましょう。 サウンドの入力にもofSoundStreamクラスを使用します。 #ifndef _TEST_APP list 3–2_d testApp.h #define _TEST_APP #include "ofMain.h" class testApp : public ofBaseApp{ 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(int x, int y, int button); void windowResized(int w, int h); void audioReceived (float * input, int bufferSize, int nChannels); int bufSize; //バッファーサイズ float * left; //左チャンネルのサンプル float * right; //右チャンネルのサンプル 3–2 openFrameworksプログラミング中級編 }; #endif testApp.cpp #include "testApp.h" #include "stdio.h" void testApp::setup(){ //バッファーのサイズを指定 bufSize = 256; //画面の基本設 ofBackground(0,0,0); ofSetColor(0,0,255); //サウンドストリームの初期化 ofSoundStreamSetup(0,2,this, 44100, bufSize, 4); //左右チャンネル音声の波形を格納する配列 left = new float[bufSize]; right = new float[bufSize]; } void testApp::draw(){ float audioHeight = ofGetHeight()/2.0f; float phaseDiff = ofGetWidth()/float(bufSize); //左チャンネル波形を描画 for (int i = 0; i < bufSize; i++){ ofLine(i*phaseDiff, audioHeight/2, i*phaseDiff, audioHeight/2+left[i]*audioHeight); } //右チャンネル波形を描画 for (int i = 0; i < bufSize; i++){ ofLine(i*phaseDiff, audioHeight/2*3, i*phaseDiff, audioHeight/2*3+right[i]*audioHeight); } } void testApp::audioReceived(float * input, int bufferSize, int nChannels){ //音声入力を配列に格納 for (int i = 0; i < bufferSize; i++){ left[i] = input[i*2]; right[i] = input[i*2+1]; 162 | 163 } } void testApp::keyPressed (int key){ } void testApp::keyReleased(int key){ } … … 実行結果 testApp.hでは、クラスのメソッドとしてaudioReceived ( ) を追加しています。このメソッドはオー ディオの入力を設定した際に、オーディオが入力されたときに呼び出されるイベントです。メソッド の引数を経由して、オーディオ入力された信号の配列、バッファーのサイズ、チャンネル数といっ たオーディオ入力に関する情報を取得することが可能となります。testApp.hでは同時に入力する 音声のバッファーのサイズと、左右チャンネルの信号を格納する配列をそれぞれ用意しています。 testApp.cppで実際に音声を入力しその波形を表示しています。audioReceivedメソッドを経由し て取得した左右のチャンネルの信号を記録した配列をもとに、波形を描画しています。 3–2–6 サウンドファイルの再生 ― ofSoundPlayer 次に、音を再生するためのもう1つのアプローチ、サンプリング&プレイバックを試してみましょう。 サンプリング&プレイバックを最も簡単に実現する方法は、既存のサウンドファイルのデータを読 み込んで再生するやり方です。openFrameworksでは、サウンドファイルを読み込んで再生する Waveファイル(.wav) ためのofSoundPlayerというクラスが用意されています。ofSoundPlyaerは、 、 Aiffファイル(.aif)、MP3ファイル(.mp3)、RAWファイル(.raw)など様々なファイル形式のサウ ンドファイルを読み込んで再生することが可能です。 3–2 openFrameworksプログラミング中級編 まずはじめに、サウンドファイルを再生する簡単なサンプルを作成してみましょう。 #ifndef _TEST_APP list 3–2_e testApp.h #define _TEST_APP #include "ofMain.h" class testApp : public ofBaseApp { 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(int x, int y, int button); void windowResized(int w, int h); ofSoundPlayer mySound; //ofSoundクラスをインスタンス化 }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ mySound.loadSound("glitch_loop.wav"); //サウンドファイルの読込み mySound.setLoop(true); //ループ再生をONに mySound.play(); //サウンド再生開始 } void testApp::update(){ } void testApp::draw(){ } … … 164 | 165 ofSoundPlayerはクラスなので、使用するためにはまずインスンタンス化する必要があります。こ のサンプルでは、インスタンス化はtestApp.hの中で行っています。次に、ofSoundPlayerのイン スタンスのメソッドを利用して、サウンドファイルの読み込みをします。再生するサウンドファイルは、 アプリケーションの実行ファイルと同じ場所にある「data」フォルダの中に配置します。 openFrameworksのプロジェクト内では、サウンドファイルの配置場所は《プロジェクトフォルダ》 /bin/data/になります。 ofSoundPlayerを利用すると、サウンドファイルの読み込み、再生の他にも様々な指定が可能です。 先程のサンプルでは、再生の際にループするように設定していました。 ( サウンドファイル名》); 《ofSoundPlayerのインスタンス》.loadSound《 :サウンドファイルの読み込み 《ofSoundPlayerのインスタンス》.play() :サウンドの再生開始 《ofSoundPlayerのインスタンス》.stop() :サウンドの再生終了 《ofSoundPlayerのインスタンス》.setVolume() :サウンドの音量を設定 《ofSoundPlayerのインスタンス》.setPan() :サウンドの定位 ( 左右の音量のバランス)を設定 《ofSoundPlayerのインスタンス》 :サウンドの再生のスピードを設定、再生スピードを上げるとピッチも上がる 《ofSoundPlayerのインスタンス》.setLoop() :ループ再生をするかどうか設定 《ofSoundPlayerのインスタンス》.setMultiPlay() :同じサウンドを一度に重ねて再生するかどうか設定 もう少しサウンドファイルを操作できるように改良してみましょう。画面上でマウスボタンを押すと 再生開始、マウスボタンを離すと再生終了にしてみます。また、マウスのy軸上の位置で再生スピ ードを変更、マウスのx軸上の位置で左右の音量バランスを変更できるようにもしてみましょう。 #ifndef _TEST_APP list 3–2_f testApp.h #define _TEST_APP #include "ofMain.h" class testApp : public ofBaseApp { public: void setup(); void update(); 3–2 openFrameworksプログラミング中級編 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(int x, int y, int button); void windowResized(int w, int h); ofSoundPlayer mySound; //ofSoundクラスをインスタンス化 }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ ofBackground(0, 0, 0); mySound.loadSound("drum_loop.aif"); //サウンドファイルの読込み mySound.setLoop(true); //ループ再生をONに } void testApp::update(){ } void testApp::draw(){ } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ //パンの設定 mySound.setPan(x / (float)ofGetWidth() * 2 - 1.0f); //再生スピード変更 166 | 167 mySound.setSpeed( 0.5f + ((float)(ofGetHeight() - y) / (float) ofGetHeight())*1.0f); } void testApp::mousePressed(int x, int y, int button){ //パンの設定 mySound.setPan(x / (float)ofGetWidth() * 2 - 1.0f); //再生スピード設定 mySound.setSpeed( 0.5f + ((float)(ofGetHeight() - y) / (float) ofGetHeight())*1.0f); //サウンド再生開始 mySound.play(); } void testApp::mouseReleased(int x, int y, int button){ mySound.stop(); //サウンド再生終了 } void testApp::windowResized(int w, int h){ } さらに工夫を加えてみます。現在再生しているサウンドの音量を取得して、その音量にあわせて円 の半径を変化させてみましょう。音を再生する様子を視覚的に捉えることができるようになります。 #ifndef _TEST_APP list 3–2_g testApp.h #define _TEST_APP #include "ofMain.h" class testApp : public ofBaseApp { 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); 3–2 openFrameworksプログラミング中級編 void mouseReleased(int x, int y, int button); void windowResized(int w, int h); ofSoundPlayer mySound; //ofSoundクラスをインスタンス化 float radius; //円の半径 }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ ofBackground(0, 0, 0); ofSetVerticalSync(true); ofSetCircleResolution(64); ofEnableAlphaBlending(); radius = 0; //円の半径 mySound.loadSound("drum_loop.aif"); //サウンドファイルの読込み mySound.setLoop(true); //ループ再生をONに } void testApp::update(){ float * val = ofSoundGetSpectrum(1); //再生中のサウンドの音量を取得 radius = val[0] * 800.0; //円の半径に適用 } void testApp::draw(){ ofSetColor(0, 63, 255, 180); ofCircle(mouseX, mouseY, radius); } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ 168 | 169 //パンの設定 mySound.setPan(x / (float)ofGetWidth() * 2 - 1.0f); //再生スピード変更 mySound.setSpeed( 0.5f + ((float)(ofGetHeight() - y) / (float) ofGetHeight())*1.0f); } void testApp::mousePressed(int x, int y, int button){ //パンの設定 mySound.setPan(x / (float)ofGetWidth() * 2 - 1.0f); //再生スピード設定 mySound.setSpeed( 0.5f + ((float)(ofGetHeight() - y) / (float) ofGetHeight())*1.0f); //サウンド再生開始 mySound.play(); } void testApp::mouseReleased(int x, int y, int button){ mySound.stop(); //サウンド再生終了 } void testApp::windowResized(int w, int h){ } 実行結果 3–2–7 画像ファイルを扱う ― ofImage 音に続いて、画像データを扱ってみましょう。静止したデジタル画像データは、コンピュータ画面 上に図形を描画する際と同様に、x軸方向とy軸方向にグリッド状に整列したピクセルの集合体 です。それぞれのピクセルの色の濃度を数値として記録しています。現在のほとんどのコンピュー 3–2 openFrameworksプログラミング中級編 タは、32bitのカラーを表示できる性能があります。32bitカラーの場合、RGBA(Red、Green、 Blue、Alpha)それぞれの色の値で8bitずつ、つまり256段階で色の階調を記録しています。 A:8bit B:8bit G:8bit R:8bit 画像データをハードディスクなどの記憶装置に保存する際には、ピクセルの数値データをそのま ま保存するとファイルの容量が巨大になってしまうため、計算処理によりデータを圧縮し、容量 を削減して保存する場合がほとんどです。情報の圧縮の方法、保存の形式などによって、様々な 画像ファイルの形式が存在します。そのため、画像ファイルを読み書きするためには、こうした画 像フォーマットの規格に沿ってデータを取り扱う必要があります。 openFrameworksでは、画像ファイルの読み込みと書き出しに「freeImage」という既存のライブ ラリをopenFrameworksから利用できるようにしたofImageクラスを利用します。ofImageクラス の機能を介して、PNG、BMP、Jpeg、TIFFなどの主要な形式の画像をデータとして読み込んだり、 生成した画像データをファイルとして書き出すことが可能となります。 では実際にofImageを利用して、画像ファイルを読み込んで画像データとして利用したり、画面 をキャプチャしたデータを画像ファイルとして保存してみましょう。新規プロジェクト「Show Image」を作成します。 #ifndef _TEST_APP list 3–2_h testApp.h #define _TEST_APP #include "ofMain.h" class testApp : public ofBaseApp { public: void setup(); void update(); void draw(); void keyPressed(int key); void keyReleased(int key); void mouseMoved(int x, int y ); 170 | 171 void mouseDragged(int x, int y, int button); void mousePressed(int x, int y, int button); void mouseReleased(int x, int y, int button); void windowResized(int w, int h); ofImage myImage; //画像ファイルより読みこまれたイメージデータ ofImage grabbedImage; //画面をキャプチャーしたイメージデータ }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ //画面の基本設定 ofBackground(0,0,0); ofEnableSmoothing(); //画面の混色の設定を加算合成にする glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); //画像データの読込み myImage.loadImage("MonaLisa.jpg"); } void testApp::update(){ } void testApp::draw(){ //色の設定 ofSetColor(255, 255, 255); //読み込んだ画像データを画面に描画 myImage.draw(20,20); //画像データのビットマップ情報を配列に格納 unsigned char * pixels = myImage.getPixels(); //画像の幅と高さを所得 int w = myImage.width; int h = myImage.height; //画像を8ピクセル間隔でスキャン for (int i = 0; i < w; i+=8){ for (int j = 0; j < h; j+=8){ //ピクセルのRGBの値を取得 int valueR = pixels[j*3 * w + i*3]; 3–2 openFrameworksプログラミング中級編 int valueG = pixels[j*3 * w + i*3+1]; int valueB = pixels[j*3 * w + i*3+2]; //取得したRGB値をもとに、円を描画 //取得したピクセルの明るさを、円の半径に対応させている ofSetColor(255, 0, 0, 63); ofCircle(440+i, 20+j, 10*valueR/255.0); ofSetColor(0, 255, 0, 63); ofCircle(440+i, 20+j, 10*valueG/255.0); ofSetColor(0, 0, 255, 63); ofCircle(440+i, 20+j, 10*valueB/255.0); } } } void testApp::keyPressed(int key){ //「x」キーを押すと、画面をキャプチャーする if(key == 'x'){ //位置とサイズを指定して、画面をキャプチャー grabbedImage.grabScreen(430,10,420,642); //キャプチャーした画像データを「grabbedImage.png」で保存 grabbedImage.saveImage("grabbedImage.png"); } } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } … … 実行結果 172 | 173 まず、testApp.hでofImageのインスタンスを2つ生成しています。myImageは既存の画像ファイ ル を 読 み 込 んで、 デ ー タとして 利 用 す る た め の も の で す。 ま た、grabbedImage は openFrameworksで生成した画面の領域を指定して、その枠内の画像データをキャプチャし、画 像データとして保存するため使用します。 testApp.cppでは、ofImageを利用した画像ファイルの読み込み、画像ファイルの解析、画像フ ァイルの書き出しという一連の処理を行っています。 ofImageのインスタンスに対して、「loadImage("ファイル名")」というメソッドを実行して、指定し た場所にある画像ファイルを画像データとして読み込んでいます。ここで読み込む画像ファイルは、 音声データと同様にopenFrameworksのプロジェクトフォルダ内にある「bin」→「data」フォルダ 内に配置します。読み込むファイルの画像形式は、そのファイルの拡張子から類推して自動的に 判別しています。 ofImageにloadImageを使用して読み込んだ画像ファイルのデータは、ピクセル単位でその値を 取り出して解析することが可能です。ofImageのインスタンスに「getPixels ( ) 」というメソッドを実 行すると、その画像データのピクセルごとの色の階調の情報を配列として取り出すことができます。 画像ファイルのカラーモードが透明度の情報を持ったRGBAカラーモデルの場合には、画像の1 ピクセルに対して4つの値(RGBA)、透明度の情報のないRGBカラーモデルの画像の場合には、 ピクセルあたり3つの値(RGB)の値が、順番に並んで配列に格納されています。 R G B このサンプルでは、RGBカラーモードの画像を読み込んで、RGBそれぞれの色の濃度の情報を8 ピクセル間隔でスキャンし、それぞれ別々の配列に格納しています。解析したピクセル情報をもと に、それぞれの色ごとに濃度に応じた半透明の円を描画して、読み込んだ画像を点描のような効 果で再現しています。 こうして生成した画像を今度は画像ファイルとして書き出してみましょう。openFrameworksで生 成した画面を画像ファイルとして書き出すには、実行している画面の中から画像ファイルとして書 き出す領域を指定します。ofImageのインスタンスに対して、「grabScreen( 左端のx座標, 上端の y座標, 幅, 高さ)」というメソッドを実行することで、領域の指定枠内をキャプチャして、メソッドを 実行したofImageのインスタンスにその画像データを格納します。 画像データをファイルとして保存するには、ofImageのインスタンスに「savaImage("ファイル名")」 というメソッドを実行します。そうすると、指定したファイル名で、画像ファイルを読み込んだ場合 3–2 openFrameworksプログラミング中級編 と同様に、openFrameworksのプロジェクトフォ ルダ内にある「bin」→「data」フォルダ内に画像 ファイルを書き出します。画像ファイルのフォー マットは、指定した拡張子から類推して自動的 に決定されます。このサンプルでは、[x]キーを 入力すると点描風に画像を再現した部分のみ抽 出して、PNG形式で保存しています。 実行結果 フォントデータを扱う ― ofTrueTypeFont 3–2–8 次に、フォントデータを読み込んで表示してみましょう。音声ファイルや画像ファイルと同様に、フ ォントデータを格納したファイルにも様々なフォーマットが存在します。openFrameworksでは TrueTypeフォントという形式を読み込むことが可能です。TrueTypeフォントを取り扱うには、 ofTrueTypeFontクラスを使用します。読み込んだフォントは、アンチエイリアスと呼ばれる、フォ ントの輪郭が画面上でなめらかに見えるような処理がされた状態で画面上に表示することが可能 です。それでは、新規プロジェクト「LoadFont」を作成しましょう。 #ifndef _TEST_APP list 3–2_i testApp.h #define _TEST_APP #include "ofMain.h" class testApp : public ofBaseApp { 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); 174 | 175 void mousePressed(int x, int y, int button); void mouseReleased(int x, int y, int button); void windowResized(int w, int h); ofTrueTypeFont georgia; //TrueTypeフォントデータ int fontX; //フォントのx軸上の位置 }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ //画面の基本設定 ofBackground(255, 255, 255); ofSetVerticalSync(true); ofSetFrameRate(60); //フォントデータを読みこみ georgia.loadFont("Georgia.ttf", 60); //フォントのx軸上の表示位置の初期化 fontX = ofGetWidth(); } void testApp::update(){ //フォントを左に移動 fontX -= 1; //フォントを取り囲む長方形を計算 ofRectangle rect = georgia.getStringBoundingBox("Hello! I am openFrameworks.", 0,0); //フォントの右端が画面左端からはみ出たら、位置を右端に戻す if(fontX < -rect.width){ fontX = ofGetWidth(); } } void testApp::draw(){ ofSetColor(0, 127, 255); georgia.drawString("Hello! I am openFrameworks.", fontX, ofGetHeight()/2); } void testApp::keyPressed(int key){ 3–2 openFrameworksプログラミング中級編 } void testApp::keyReleased(int key){ } … … 実行結果 ofTrueTypeFontを利用したフォントデータの読み込みは、ofImageで画像ファイルを読み込む方 法に似ています。まず、読み込みたいTrueType形式のフォントファイル(拡張子は.ttf)をプロジェ クトフォルダ内にある「bin」→「data」フォルダに格納しておきます。 testApp.hで、フォントを取り扱うためのクラスofTrueTypeFontのインスタンスを用意します。この サンプルでは、Georgiaフォントを読み込むためのインスタンスとして、「georgia」という名前で ofTrueTypeFontクラスのインスタンスを生成しています。 また、testApp.cppでは、フォントの読み込みと表示、表示したフォントのアニメーションを行って います。まずofTrueTypeFontのインスタンスに対して「loadFont("フォントファイル名", フォントサ イズ )」というメソッドを実行します。フォントの形状を記録したデータがofTrueTypeクラスのイン スタンスに読み込まれます。読み込んだフォントデータを画面に表示するには、ofTrueTypeFont クラスのインスタンスに対して「drawString("表示する文字列", 表示位置のx座標, 表示位置のy 座標 )」というメソッドを実行します。表示する文字列の部分には、表示したいテキストをそのまま 記入してもよいですし、char型の配列として指定して、表示する文字を動的に変更することも可 能です。 フォントを表示する位置を変化させることで、文字をアニメーションさせることもできます。このサ ンプルでは、x座標を変化させてフォントをスクロールしています。フォントが画面から完全にはみ 出したかどうか 判 定するためには、フォントの 文 字 列 の 領 域 の 情 報 が 必 要となります。 ofTryeTypeFontのインスタンスに対して「getStringBoundingBox("表示する文字列", 表示位置 のx座標, 表示位置のy座標 )」というメソッドを実行すると、その表示する領域を取り囲む四角形 176 | 177 の領域を取得することが可能です。このサンプルでは、この領域の情報を利用して、スクロール した文字が画面から完全に消えたらまた位置をリセットして画面の反対側からスクロールを再開 するようにしています。 3–2–9 動画の再生 ― ofVideoPlayer 次に、動画ファイルを読み込んで再生してみましょう。openFrameworksでは、QuickTimeのライ ブラリを利用して動画の読み込み、再生を行っています。openFrameworksからQuickTimeを利 用するためには、ofVidePlayerクラスを使用します。ofMoviePlayerクラスを介して、QuickTime 形式のファイルを読み込み、画面上で再生したり解析することが可能です。openFrameworksで 扱う動画は、パラパラマンガのように、変化する画像データを指定した間隔で次々に表示してい ると考えるとよいでしょう。新規プロジェクト「MoviePlayer」を作成してください。 #ifndef _TEST_APP list 3–2_j testApp.h #define _TEST_APP #include "ofMain.h" class testApp : public ofBaseApp{ 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(int x, int y, int button); void windowResized(int w, int h); ofVideoPlayer fingersMovie; }; #endif testApp.cpp #include "testApp.h" #include "stdio.h" 3–2 openFrameworksプログラミング中級編 void testApp::setup(){ //画面の基本設定 ofBackground(0,0,0); ofEnableSmoothing(); //画面の混色の設定を加算合成にする glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); //ムービーデータを読込む fingersMovie.loadMovie("fingers.mov"); //ムービーの再生開始 fingersMovie.play(); } void testApp::update(){ //ムービー再生を待機状態に fingersMovie.idleMovie(); } void testApp::draw(){ //色の設定 ofSetColor(0xFFFFFF); //ムービーデータを画面に表示 fingersMovie.draw(20,20); //ムービーのビットマップデータを解析し、配列に格納 unsigned char * pixels = fingersMovie.getPixels(); //画像を8ピクセルごとにスキャン for (int i = 0; i < fingersMovie.width; i+=8){ for (int j = 0; j < fingersMovie.height; j+=8){ //RGBそれぞれのピクセルの明度を取得 unsigned char r = pixels[(j * 320 + i)*3]; unsigned char g = pixels[(j * 320 + i)*3+1]; unsigned char b = pixels[(j * 320 + i)*3+2]; //取得したRGB値をもとに、円を描画 //取得したピクセルの明るさを、円の半径に対応させている ofSetColor(255, 0, 0, 100); ofCircle(360 + i,20+j,10.0*(float)r/255.0); ofSetColor(0, 255, 0, 100); ofCircle(360 + i,20+j,10.0*(float)g/255.0); ofSetColor(0, 0, 255, 100); ofCircle(360 + i,20+j,10.0*(float)b/255.0); } } } 178 | 179 void testApp::keyPressed (int key){ switch(key){ case '0': //「0」キーを押すと、ムービーを最初のフレームに巻き戻し fingersMovie.firstFrame(); break; } } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y ){ } void testApp::mouseDragged(int x, int y, int button){ //マウスをドラッグすると、ムービーのタイムラインを操作できる fingersMovie.setPosition((float)x / (float)ofGetWidth()); } void testApp::mousePressed(int x, int y, int button){ //マウスのプレスで、ムービーを一時停止 fingersMovie.setPaused(true); } void testApp::mouseReleased(int x, int y, int button){ //マウスのプレスで、ムービーの再生を再開 fingersMovie.setPaused(false); } void testApp::windowResized(int w, int h){ } 実行結果 3–2 openFrameworksプログラミング中級編 testApp.hでは、動画を再生するためのofVideoPlayerクラスのインスタンスfingersMovieを生 成しています。 testApp.cppで、実際に動画ファイルを読み込んで再生します。ムービーファイルを読み込むには、 ofMoviePlayerクラスのインスタンスに対して「loadMovie("ムービーファイル名")」というメソッドを 実行します。ムービーファイルのデータは画像やフォントと同様に、「bin」→「data」フォルダに格 納します。 読み込んだムービーを再生するには、いくつかの手続きが必要です。まず、読み込んだムービー を再生状態にします。ムービーの再生には「play ( ) 」メソッドを使用します。この命令によって、 読み込んだムービーの再生を開始します。再生状態になったムービーに対して、testAppの update ( ) メソッド内では「idleMovie ( ) 」メソッドを使用して待機状態にしておく必要があります。 画面上に再生しているムービーを表示するために、draw ( ) 関数の中でofVideoPlayerのインスタ ンスに対して「draw( 表示位置のx座標, 表示位置のy座標 )」というメソッドを使用します。この命 令によって、ムービーファイルの内容が画面に表示されます。 動画データが画像やフォントと大きく異なる点は、時間軸を持ったデータだというところでしょう。 ofVideoPlayerクラスには、ムービーの時間軸を操作するためのメソッドがいくつか用意されてい ます。 《ofVideoPalyerのインスタンス》.play() :動画の再生 《ofVideoPalyerのインスタンス》.stop() :動画の停止 《ofVideoPalyerのインスタンス》.idleModvie() :動画を待機状態にする 《ofVideoPalyerのインスタンス》.firstFrame() :動画の最初のページに戻る 《ofVideoPalyerのインスタンス》.setPosition() :動画の指定した時間へ移動 このサンプルでは、キーボードから[0]キーを押すと最初のフレームに戻り、マウスをドラッグす るマウスポインタのx軸上の位置に応じてムービーの再生位置を変化させることで、動画をタイム ラインで操作できるようにしています。 ofVideoPlayerで取得したムービーデータは、画像ファイルと同様に、ピクセル単位で解析するこ とが可能です。そのためには、ofVideoPlayerのインスタンスに対して「getPixels ( ) 」というメソッ ドを実行することで、RGBに分解された状態でそれぞれのピクセルの濃度を取得することができま す。サンプルではその値を利用して、映像を点描風にピクセレイトしています。 180 | 181 3–2– 10 動画のキャプチャ ― ofVideoGrabber 次に、コンピュータに接続したカメラから動画をリアルタイムに読み込んで利用してみましょう。動 画をカメラから読み込むには、ofVideoGrabberクラスを利用します。コンピュータにUSBや FireWire(IEEE 1394)で接続したライブカメラや、コンピュータに内蔵されたカメラを利用するこ とが可能です。新規プロジェクト「VideoGrabber」を作成してください。 #ifndef _TEST_APP list 3–2_k testApp.h #define _TEST_APP #include "ofMain.h" class testApp : public ofBaseApp{ 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(int x, int y, int button); void windowResized(int w, int h); ofVideoGrabber vidGrabber; //ofVideoGrabberのインスタンス int camWidth; //カメラから取り込む画像の幅 int camHeight; //カメラから取り込む画像の高さ }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ //画面の基本設定 ofBackground(0,0,0); ofEnableSmoothing(); 3–2 openFrameworksプログラミング中級編 //画面の混色の設定を加算合成にする glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); //キャプチャするムービーのサイズを指定 camWidth = 480; camHeight = 320; vidGrabber.setVerbose(true); vidGrabber.initGrabber(camWidth,camHeight); } void testApp::update(){ //ムービーをカメラからキャプチャする vidGrabber.grabFrame(); } void testApp::draw(){ ofSetColor(0xffffff); vidGrabber.draw(20,20); //ムービーのビットマップデータを解析し、配列に格納 unsigned char * pixels = vidGrabber.getPixels(); //画像を10ピクセルごとにスキャン for (int i = 0; i < camWidth; i+=10){ for (int j = 0; j < camHeight; j+=10){ //RGBそれぞれのピクセルの明度を取得 unsigned char r = pixels[(j * camWidth + i)*3]; unsigned char g = pixels[(j * camWidth + i)*3+1]; unsigned char b = pixels[(j * camWidth + i)*3+2]; //取得したRGB値をもとに、円を描画 //取得したピクセルの明るさを、円の半径に対応させている ofSetColor(255, 0, 0, 100); ofCircle(camWidth+40 + i,20+j,20.0*(float)r/255.0); ofSetColor(0, 255, 0, 100); ofCircle(camWidth+40 + i,20+j,20.0*(float)g/255.0); ofSetColor(0, 0, 255, 100); ofCircle(camWidth+40 + i,20+j,20.0*(float)b/255.0); } } } void testApp::keyPressed (int key){ //「s」キーを押すと、ビデオ取り込みの設定画面を表示 if (key == 's' || key == 'S'){ vidGrabber.videoSettings(); 182 | 183 } } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y ){ } … … 実行結果 testApp.hで、ofVideoGrabberクラスのインスタンス、vidGrabberを生成しています。また同時 にカメラから画像をとりこむ際の幅と高さを記憶するために、camWidthとcamHeightという変数 を用意しています。 testApp.cppでは、まずカメラから取り込む画像の幅と高さを指定しています。そのうえでカメラ 取り込 みの 初 期 化 を行 います。 初 期 化 をするには、ofVideoGrabberのインスタンスに 「initGrabber( 幅、高さ)」というメソッドを実行します。 実際にデータを取り込むには、ofVideoGrabberクラスのインスタンスに「grabFrame ( ) 」メソッ ドを実行します。このメソッドを実行するたびに、新規にカメラから1フレーム画像を読み込まれ、 インスタンス内にデータとして格納されます。読み込みの際には、動画としてカメラから画像を取 得するために、grabFrame ( ) メソッドをtestAppのupdate ( ) 内で繰り返し実行するようにしていま す。読み込まれた画像を画面に表示するには、ofVideoGrabberのインスタンスに「draw( 表示位 置のx座標, 表示位置のy座標 )」メソッドを実行します。 カメラから読み込まれた画像データは、ofImageと同じ方法でピクセル単位で解析することが可 能です。ofVideoGrabberのインスタンスに「getPixels ( ) 」メソッドを実行して、配列に画像のピ クセルごとのデータを読み込みます。これを利用することで、カメラからの映像をリアルタイムに解 析し、加工することが可能となります。サンプルでは、カメラからの映像を利用して点描風に画 像処理しています。 3–2 openFrameworksプログラミング中級編 3 3–3– 1 –3 OOOF: オブジェクト指向oFプログラミング openFrameworksでオブジェクト指向プログラミング ここまでで学んできたC++のクラス生成の知識と様々なメディアの取り扱い方法をふまえて、より 本格的な構造をもったプロジェクトを作成していきましょう。 まずは、ここまでのC++でのクラス生成の知識をopenFrameworksにも適用してみましょう。 openFrameworksで複数のクラスを生成して、役割を分割しながらプログラムができるようになる と、これまでのtestApp単体でのプログラムでは複雑になりがちだった記述も、すっきりシンプル に作成していくことが可能となります。単純なサンプルから徐々に複雑なサンプルへ、段階を追っ て作成していきましょう。 まずはじめに「ofBlob」というクラスを定義してみます。このクラスを少しずつ発展させていくことで、 徐々に複雑なプログラムになっていきます。このセクションを通して、次のように徐々にプロジェク トを発展させていきます。 新規クラスの生成 画面の中心に円を表示する ― メソッドの追加1 円の大きさと場所の初期値を指定する ― コンストラクタ 円の大きさと場所の情報を取得、設定する ― セッターとゲッター 円をアニメーションする ― メソッドの追加2 円が拡大縮小する動きを加える ― メソッドの追加3 画面をクリックすると、どんどん円が増殖する ― 複製のクラスの生成 1 円をクリックすると、細かな円に分裂する ― 複製のクラスの生成 2 3–3– 2 新規クラスの生成 まずはじめにopenFrameworksの新規プロジェクトを作成します。いつも通りXcodeの 「ファイル」 →「新規プロジェクト」を選択して、プロジェクトのタイプはopenFrameworks Applicationで作 成します。名前は「ofBlob」とします。 openFrameworksに新規のクラスを追加する方法は、純粋なC++のプロジェクトに新規クラスを 追加する方法とほぼ同じです。Xcodeの左側のコラム「グループとファイル」のプロジェクト名の 184 | 185 アイコンを開き、その直下にある「src」フォルダを[コ ントール]キー+クリック、もしくは右クリックします。 メニューが表示されるので、 「追加」→「新規ファイル」 を選択します。 選択すると「新規ファイルのテンプレートを選択:」 という画面が表示されるので、「Mac OS X」→「C and C++」 を選択し、 表示されるアイコンの中から 「C++ File」を選択します。 追加するC++ Fileの詳細な設定をする画面が表示さ れます。今回は、ofBlobというクラスを作成してみよ うと思っているのでファイル名は「ofBlob.cpp」にして、 「同時に"ofBlob.h"を作成する」というチェックボック スをチェックします。「保存場所」は編集せずそのまま で。「プロジェクトに追加」と「ターゲット」の設定も そのままにして、「完了」を選びます。 最 終 的に「src」フォルダの中には、 「ofBlob.h」と 「ofBlob.cpp」という2つのファイルが追加されている はずです。 3–3– 3 画面の中心に円を表示する ― メソッドの追加 1 ofBlobクラスを使用して、まずは画面の中心に円を表示してみようと思います。クラスに1つだけ draw ( ) メソッドを追加します。このメソッドは画面の中心に円を描くというものです。 UMLクラス図で表現すると以下のようになります。 ofBlob + draw():void 3–3 openFrameworksプログラミング中級編 この設計図に沿ってクラスを作成していきます。クラスの記述方法は一般的なC++と同じです。ヘ ッダファイル(クラス名.h)で、クラスの骨格を宣言し、それぞれのメソッドの内容はソースファイ ル(クラス名.cpp)に記述していきます。 list 3–3_a ofBlob.h #ifndef _OF_BLOB //インクルードガード #define _OF_BLOB #include "ofMain.h" //ofMain.hをインクルード class ofBlob { public: // メソッドを定義 void draw(); //円を描く }; #endif クラスのヘッダファイルの冒頭にある#ifndef…、#define…、いちばん末尾にある#endifという記 述は、プリプロセッサ命令です。openFrameworksのクラスではほとんどの場合、このようなプリ プロセッサ命令に上下を囲まれています。この記述によって、クラスの定義が重複して出現するこ とを防止しています。この仕組みを「インクルードガード」と呼びます。#ifndefや#defineの後ろに は「識別子」というクラスを特定するための名前を記述します。慣習的に大文字で付けることが多 いです。例えば、クラス名が「ofBlob」の場合には「_OF_BLOB」などとするとよいでしょう。 #ifndef 《識別子》 #define 《識別子》 ...《クラスの定義》... #endif ofBlob.cpp #include "ofBlob.h" //メソッド:円を描く void ofBlob::draw(){ ofSetColor(31, 63, 255); //描画色を設定 ofCircle(ofGetWidth()/2, ofGetHeight()/2, 100); //画面の中心に円を描く } 186 | 187 作成したクラスofBlobは、testAppから呼び出すことで使用することが可能となります。testApp からofBlobクラスで定義した内容を使用可能にするには、testApp.hの冒頭で、ofBlobのヘッダ ーファイルofBlob.hを読み込む必要があります。インクルード文を用いて、ofBlob.hを読み込んで います。 これでクラスofBlobをtestAppから参照することができました。しかし、まだこれだけでは画面上 に円を表示することはできません。クラスはあくまで設計図に過ぎません。クラスをプログラムの 一 部として機 能させるには、まずインスタンス化する必 要があります。このサンプルでは、 testApp.hでofBlobクラスをインスタンス化して、インスタンスに「blob」という名前を付けています。 testApp.h #ifndef _TEST_APP //インクルードガード #define _TEST_APP #include "ofMain.h" #include "ofBlob.h" //Blob.hをインクルード class testApp : public ofBaseApp { 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(int x, int y, int button); void windowResized(int w, int h); ofBlob blob; //ofBlobクラスをインスタンスblobを生成 }; #endif 最後にtestApp.cppで画面表示のための具体的な処理について記述していきます。といっても、 testApp.cppでやることはほとんどありません。まず画面の背景色などの基本的な設定をsetup ( ) 関数内でしたうえで、draw ( ) 関数でofBlobのインスタンスblobのdraw ( ) メソッドを呼び出してい ます。 3–3 openFrameworksプログラミング中級編 testApp.cpp #include "testApp.h" void testApp::setup(){ //画面の設定 ofBackground(0, 0, 0); ofSetCircleResolution(32); ofEnableAlphaBlending(); ofSetFrameRate(30); } void testApp::update(){ } void testApp::draw(){ blob.draw(); //blobのdraw()メソッドを実行 } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … 実行してみましょう。画面の中央に円が表示されます。 実行結果 3–3– 4 円の大きさと場所の初期値を指定する ― コントラスタの作成 次に、円の位置と大きさの具体的な数値をdraw ( ) メソッドに直接書き込むのではなく、ofBlob 188 | 189 クラスがインスタンス化される際に初期値として指定して、その値を繰り返し使用できるようにして みます。 クラスがインスタンス化される際に、クラスの初期設定をしたい場合があります。クラスの初期化 の際に呼び出されて、内容の初期化などを行うには「コンストラクタ」という特殊なメソッドを使用 します。コンストラクタは、他のメソッドとは違ういくつかの特徴があります。まずそのメソッドの名 前なのですが、必ずクラス名と同じ名前にしなければなりません。例えば、ofBlobクラスのメソッ ドは、ofBlob ( ) という名前になります。また、コンストラクタは戻り値を返すことがありません。で すので宣言の際にも戻り値の型を書く必要はありません。 では、ofBlobクラスが実行された際に、円の位置と大きさを設定するようにしましょう。設定した 値はプライベートなプロパティとして保存することにします。今回、円の位置を記録するのに 「ofPoint」という型を利用してみます。これは、openFrameworksで用意されている型で、1つの 変数にx座標とy座標、両方の値を代入することが可能です。それぞれの値は、 《ofPointの変数》 .x、《ofPointの変数》.yとすると個別に参照することが可能です。 ofPoint pos; //ofPoint型の宣言 pos = ofPoint(10, 20); //x座標に10、y座標に20を代入 ofCircle(pos.x, pos.y, 100); //x座標10、y座標20の位置に円を描く ofPointの使用例 ここでは、円の位置をofPoint pos、円の半径をfloat dimというプライベートな変数に記録するこ とにします。コンストラクタの指定とあわせて、作成するクラスのUML図は下記のようになります。 ofBlob - pos:ofPoint - dim:float + ofBlob() + draw():void では、さっそくこの設計図に従って、クラスを修正していきましょう。 list 3–3_b #ifndef _OF_BLOB ofBlob.h #define _OF_BLOB #include "ofMain.h" class ofBlob { private: ofPoint pos; //円の位置 float dim; //円の半径 public: ofBlob(); //コンストラクタ 3–3 openFrameworksプログラミング中級編 void draw(); //円を描く }; #endif このクラスのコンストラクタとdraw ( ) メソッドの具体的な処理をofBlob.cppに記述していきます。 まずコンストラクタでは、円の位置を(400, 300)の座標にして、半径を80ピクセルに指定して います。draw ( ) メソッドではこの値を利用して2つの円を描いています。まず半透明の青で外周 の円を描き、その中心点に小さい赤い円を重ねて、円の中の核のような見た目にしています。 testApp.hとtestApp.cppの変更はありません。 ofBlob.cpp #include "ofBlob.h" //コンストラクタ:位置と半径を指定 ofBlob::ofBlob(){ pos = ofPoint(ofGetWidth()/2, ofGetHeight()/2); //位置の指定 dim = 100.0; //半径の指定 } //メソッド:円を描く void ofBlob::draw(){ //円1を描く - 外周 ofSetColor(31, 63, 255, 100); ofCircle(pos.x, pos.y, dim); //円2を描く - 核 ofSetColor(255, 0, 0, 200); ofCircle(pos.x, pos.y, dim/10.0); ofSetColor(31, 63, 255); } 実行結果 190 | 191 3–3– 5 円の大きさと場所の情報を取得、設定する ― セッターとゲッター 次に、コンストラクタで指定された円の場所と大きさを、testApp側から参照したり値を変更でき るようにしてみたいと思います。現在円の場所posと大きさdimは、プライベートな変数として定義 されています。ですので、そのまま外部から参照することはできません。こうした場合、セッター (setter)とゲッター(getter)と呼ばれるメソッドを定義して値の参照と変更をできるようにする手 法がよく用いられます。それぞれの変数に対して、次のようにセッターとゲッターを用意します。 pos―円の位置 setPos:posの値を変更(セッター) getPos:posの値を取得(ゲッター) dim―円の半径 setDim:dimの値を変更(セッター) getDim:dimの値を取得(ゲッター) setterとgetterはそれぞれ以下のような構造になっています。ゲッターにあるreturnという命令は、 メソッドの参照元に処理の結果として値を返します。この参照元に戻す値のことを戻り値と言いま す。 //セッター void 《クラス名》::《セッター名》(《引数の型》《引数》){ 《プライベート変数》 = 《引数》; } //ゲッター 《戻り値の型》《クラス名》::《ゲッター名》(){ return 《プライベート変数》; } セッターとゲッターの値を加えたUMLクラス図は下記のようになります。 ofBlob - pos:ofPoint - dim:float + + + + + + 3–3 ofBlob() draw():void setPos(pos:ofPoint):void getPos():ofPoint setDim(dim:float):void getDim():float openFrameworksプログラミング中級編 では、ofBlobの定義にセッターとゲッターを加えてみましょう。 list 3–3_c #ifndef _OF_BLOB ofBlob.h #define _OF_BLOB #include "ofMain.h" class ofBlob { private: ofPoint pos; //円の位置 float dim; //円の半径 public: ofBlob(); //コンストラクタ void draw(); //円を描く void setDim(float dim); //dimセッター float getDim(); //dimゲッター void setPos(ofPoint pos); //posセッター ofPoint getPos(); //posゲッター }; #endif ofBlob.cpp #include "ofBlob.h" //コンストラクタ:位置と半径を指定 ofBlob::ofBlob() { pos = ofPoint(400, 300); //位置の指定 dim = 80.0; //半径の指定 } //メソッド:円を描く void ofBlob::draw(){ //円1を描く - 外周 ofSetColor(31, 63, 255, 100); ofCircle(pos.x, pos.y, dim); //円2を描く - 核 ofSetColor(255, 0, 0, 200); ofCircle(pos.x, pos.y, dim/10.0); ofSetColor(31, 63, 255); } 192 | 193 //posセッター void ofBlob::setPos(ofPoint _pos) { pos = _pos; } //posゲッター ofPoint ofBlob::getPos() { return pos; } //dimセッター void ofBlob::setDim(float _dim) { dim = _dim; } //dimゲッター float ofBlob::getDim( { return dim; } セッターを利用して円の位置と場所を変更してみましょう。testApp.cppからセッターを呼び出し ます。セッターを利用して位置と大きさの初期値を上書きすることで、円の位置と大きさが変化し ている様子がわかるでしょう。 testApp.cpp #include "testApp.h" void testApp::setup(){ //画面の設定 ofBackground(0, 0, 0); ofSetCircleResolution(32); ofEnableAlphaBlending(); ofSetFrameRate(30); //円の位置と場所を変更 blob.setPos(ofPoint(300,300)); blob.setDim(200); } 3–3 openFrameworksプログラミング中級編 void testApp::update(){ } void testApp::draw(){ blob.draw(); //blobクラスのdraw()を実行 } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … 実行結果 3–3– 6 円をアニメーションする ― メソッドの追加 2 では次に、円をアニメーションさせてみましょう。円の動きに関する処理もtestAppではなく ofBlob側に記述します。こうすることで、testAppとは独立して円の動きを定義することが可能と なります。円の移動、つまり座標の更新に関する処理は、ofBlobクラスのupdate ( ) メソッドとし て定義することにします。testApp.cpp内のupdate ( ) メソッドから、ofBlobのupdate ( ) メソッド を呼び出すことで、表示された円がアニメーションします。 まずは簡単な動きを試してみます。円が一定のスピードで移動するようにしてみましょう。さらに画 面の端に来た際には跳ね返って画面の外に出ないようにする処理も加えます。円のスピードを 「speed」というプライベート変数にします。円の移動スピードもx座標とy座標両方を1つにできる と便利なので、ofPoint型を使います。また、speedに関してもセッターとゲッターで値の参照と 変更をできるようにしておきます。 194 | 195 UMLクラス図もあわせて更新します。 ofBlob - pos:ofPoint - dim:float - speed:ofPoint + + + + + + + + ofBlob() draw():void setPos(pos:ofPoint):void getPos():ofPoint setDim(dim:float):void getDim():float setSpeed(speed:ofPoint):void getSpeed():ofPoint list 3–3_d #ifndef _OF_BLOB ofBlob.h #define _OF_BLOB #include "ofMain.h" class ofBlob { private: ofPoint pos; //円の位置 float dim; //円の半径 ofPoint speed; //円の移動スピード public: ofBlob(); //コンストラクタ void draw(); //円を描く void update(); //円の移動 void setDim(float dim); //dimセッター float getDim(); //dimゲッター void setPos(ofPoint pos); //posセッター ofPoint getPos(); //posゲッター void setSpeed(ofPoint speed); //speedセッター ofPoint getSpeed(); //speedゲッター }; #endif ofBlob.cpp #include "ofBlob.h" //コンストラクタ:位置と半径を指定 ofBlob::ofBlob(){ pos = ofPoint(ofGetWidth()/2, ofGetHeight()/2); //位置の初期値 3–3 openFrameworksプログラミング中級編 dim = 100.0; //半径の初期値 speed = ofPoint(0, 0); //移動スピードの初期値 } //メソッド:円を描く void ofBlob::draw(){ //円1を描く - 外周 ofSetColor(31, 63, 255, 100); ofCircle(pos.x, pos.y, dim); //円2を描く - 核 ofSetColor(255, 0, 0, 200); ofCircle(pos.x, pos.y, dim/10.0); ofSetColor(31, 63, 255); } //円のアニメーションを定義 void ofBlob::update(){ pos += speed; //座標を更新 //画面の端に来たら跳ね返る if(pos.x < dim || pos.x > ofGetWidth()-dim){ speed.x *= -1; } if(pos.y < dim || pos.y > ofGetHeight()-dim){ speed.y *= -1; } } //posセッター void ofBlob::setPos(ofPoint _pos){ pos = _pos; } //posゲッター ofPoint ofBlob::getPos(){ return pos; } //dimセッター void ofBlob::setDim(float _dim){ dim = _dim; } //dimゲッター float ofBlob::getDim(){ return dim; } 196 | 197 //speedセッター void ofBlob::setSpeed(ofPoint _speed){ speed = _speed; } //speedゲッター ofPoint ofBlob::getSpeed(){ return speed; } testApp.h #ifndef _TEST_APP #define _TEST_APP #include "ofMain.h" #include "ofBlob.h" class testApp : public ofBaseApp { 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(int x, int y, int button); void windowResized(int w, int h); ofBlob blob; }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ //画面の設定 ofBackground(0, 0, 0); ofSetCircleResolution(32); ofEnableAlphaBlending(); 3–3 openFrameworksプログラミング中級編 ofSetFrameRate(30); //円の位置と場所を変更 blob.setPos(ofPoint(300,300)); blob.setDim(100); blob.setSpeed(ofPoint(3, 5)); } void testApp::update(){ blob.update(); //blobクラスのupdate()を実行 } void testApp::draw(){ blob.draw(); //blobクラスのdraw()を実行 } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } … … 実行結果 3–3– 7 円が拡大縮小する動きを加える ― メソッドの追加 3 円の座標移動だけでなく、円自身にも面白い動きを加えてみましょう。円の大きさが定期的に拡 大と縮小を繰り返すような動きをつけてみようと思います。円の伸縮運動には、sin関数を利用し ます。この運動のための変数をさらに3つ追加します。伸縮運動のスピードは、コンストラクタで 198 | 199 の初期化の際にランダムに設定されるようにします。伸縮運動の定義も座標移動のアニメーショ ンと同様にupdate ( ) メソッドの中で定義します。 float phase:伸縮運動のためのsin関数の位相 float phaseSpeed:伸縮運動のスピード float movedDim:伸縮した結果の半径 これらの伸縮運動に関する変数も全てプライベート変数として定義します。UMLクラス図は下記 のようになります。 ofBlob - pos:ofPoint dim:float speed:ofPoint phaseSpeed:float movedDim:float + + + + + + + + ofBlob() draw():void setPos(pos:ofPoint):void getPos():ofPoint setDim(dim:float):void getDim():float setSpeed(speed:ofPoint):void getSpeed():ofPoint では、この動きを実際にプログラムしてみましょう。 list 3–3_e #ifndef _OF_BLOB ofBlob.h #define _OF_BLOB #include "ofMain.h" class ofBlob { private: ofPoint pos; //円の位置 float dim; //円の半径 ofPoint speed; //円の移動スピード float phase; //円の伸縮運動の位相 float phaseSpeed; //円の伸縮スピード float movedDim; //伸縮した結果の半径 public: ofBlob(); //コンストラクタ void draw(); //円を描く void update(); //円の移動 void setDim(float dim); //dimセッター float getDim(); //dimゲッター 3–3 openFrameworksプログラミング中級編 void setPos(ofPoint pos); //posセッター ofPoint getPos(); //posゲッター void setSpeed(ofPoint speed); //speedセッター ofPoint getSpeed(); //speedゲッター }; #endif ofBlob.cpp #include "ofBlob.h" //コンストラクタ:位置と半径を指定 ofBlob::ofBlob() { pos = ofPoint(ofGetWidth()/2, ofGetHeight()/2); //位置の初期値 dim = 100.0; //半径の初期値 speed = ofPoint(0, 0); //移動スピードの初期値 phaseSpeed = ofRandom(0.1, 0.5); //伸縮スピード phase = 0; } //メソッド:円を描く void ofBlob::draw() { //円1を描く - 外周 ofSetColor(31, 63, 255, 100); ofCircle(pos.x, pos.y, movedDim); //伸縮運動した半径を適用する //円2を描く - 核 ofSetColor(255, 0, 0, 200); ofCircle(pos.x, pos.y, dim/10.0); ofSetColor(31, 63, 255); } //円のアニメーションを定義 void ofBlob::update() { //円の伸縮運動 movedDim = dim + sin(phase)*dim/4; phase += phaseSpeed; if(phase > TWO_PI){ phase -= TWO_PI; } //座標を更新 pos += speed; //画面の端に来たら跳ね返る 200 | 201 if(pos.x < dim || pos.x > ofGetWidth()-dim){ speed.x *= -1; } if(pos.y < dim || pos.y > ofGetHeight()-dim){ speed.y *= -1; } } //posセッター void ofBlob::setPos(ofPoint _pos) { pos = _pos; } //posゲッター ofPoint ofBlob::getPos() { return pos; } //dimセッター void ofBlob::setDim(float _dim) { dim = _dim; } //dimゲッター float ofBlob::getDim() { return dim; } //speedセッター void ofBlob::setSpeed(ofPoint _speed) { speed = _speed; } //speedゲッター ofPoint ofBlob::getSpeed() { return speed; } 3–3 openFrameworksプログラミング中級編 実行結果 3–3– 8 画面をクリックすると、どんどん円が増殖する ― 複数のクラスの生成 1 クラスは設計図なので、1つの定義から複数のインスタンスを生成することが可能です。車の設計 図があれば工場で大量生産できるようなイメージです。ofBlob単体の動きは完成したので、今度 はこのクラスを大量に複製してみたいと思います。 まずはじめに、画面をクリックすると、クリックした場所にofBlobのインスタンスが生成されるとい うサンプルを作成します。何度もクリックするとクリックした数だけ複製されていきます。また、生 成される際には、大きさや移動スピードはランダムな値を設定されるようにしてみましょう。 ofBlob.hとofBlob.cppは変更の必要はありません。 list 3–3_f #ifndef _TEST_APP testApp.h #define _TEST_APP #include "ofMain.h" #include "ofBlob.h" class testApp : public ofBaseApp { 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(int x, int y, int button); 202 | 203 void windowResized(int w, int h); //ofBlobのインスタンスを格納するvector(動的配列)blobs vector <ofBlob> blobs; }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ //画面の設定 ofBackground(0, 0, 0); ofSetCircleResolution(32); ofEnableAlphaBlending(); ofSetFrameRate(30); } void testApp::update(){ //blobsに格納されたofBlobのインスタンスの数だけupdate()をくりかえす for (int i=0; i<blobs.size(); i++) { blobs[i].update(); } } void testApp::draw(){ //blobsに格納されたofBlobのインスタンスの数だけdraw()をくりかえす for (int i=0; i<;blobs.size(); i++) { blobs[i].draw(); } } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ 3–3 openFrameworksプログラミング中級編 } void testApp::mousePressed(int x, int y, int button){ } void testApp::mouseReleased(int x, int y, int button){ //マウスをクリックした位置に、新規にofBlobsのインスタンスを生成 ofBlob b; //ofBlobsをインスタンス化してbを生成 b.setPos(ofPoint(mouseX,mouseY)); //位置をマウスの場所に b.setDim(ofRandom(10,40)); //大きさをランダムに設定 b.setSpeed(ofPoint(ofRandom(-3,3),ofRandom(-3,3))); //スピードをランダムに blobs.push_back(b); //生成されたインスタンスbを動的配列blobsに追加 } void testApp::windowResized(int w, int h){ } 実行結果。クリックするごとに円が増殖する このサンプルでは、「動的配列(vector)」という新たな概念が導入されています。 たくさんの要素を一度に扱う際には、今までは配列(Array)を使用してきました。例えば、100 個のofBlobのインスタンスを扱いたいのであれば、今までのやり方ですと配列を使用して次のよう に定義していたでしょう。 ofBlobs blobs[100]; ところが今回のサンプルでは、ユーザーがクリックした回数だけ制限なくofBlobを複製していきた いと考えています。ところが、配列はまず生成する際に確保する要素の数を指定しなければなり ません。先程の配列の例では、100個以上は複製できません。これを避けるために、例えば、最 初から大量の数を確保する(例えば10,000個の領域を確保する)というのも1つの方法です。し かし、確保した10,000という数字には根拠がなく、さらにメモリを無駄に消費してしまうという問 題もあり、あまり賢い方法ではありません。 204 | 205 そこでこのサンプルでは、vector(動的配列)というものを使用しています。vectorは通常の配列 と違って、最初に要素数を宣言する必要がありません。要素数は必要な際に後から追加していく ことが可能となっています。vectorを使うことで、今回のように要素の数があらかじめわからない ような場合でも、効率良くプログラムすることが可能です。 vectorの宣言は、下記のような書式になっています。 vector <《要素の型》> 《vector 名》 この書式に従って、testApp.hでは、ofBlob型のインスタンスを要素にするvectorを宣言しています。 vector <ofBlob> blobs; 動的配列であるblobsは、通常の配列と同じように使用することが可能です。例えば、特定の要 素の内容を参照したければ、下記のように通常の配列と同じ書式で値を取り出すことができます。 a = blobs[0]; //blobsの先頭の要素の内容を参照 通常の配列としての機能のほか、vectorには様々な関数が用意されています。 at() :指定した位置の要素を返す back():最終要素を返す clear() :全ての要素を削除する front() :先頭要素を返す insert():要素をベクタに挿入する pop_back() :最終要素を削除する push_back() :ベクタの末尾に要素を追加する size() :ベクタ中の要素数を返す testApp.cppではこのvectorの関数を利用して、様々な処理を行っています。 まずはじめに、マウスをクリックすると新規にofBlobのインスタンスを生成する部分では、生成し たofBlobのインスタンスにパラメータを設定した後、vectorのpush_back ( ) 関数を利用して要素 の末尾に追加しています。 //マウスをクリックした位置に、新規にofBlobsのインスタンスを生成 ofBlob b; //ofBlobsをインスタンス化してbを生成 ... blobs.push_back(b); //生成されたインスタンスbを動的配列blobsに追加 また、update ( ) 関数とdraw ( ) 関数では、要素の数だけofBlobsのメソッドを実行するため、現 3–3 openFrameworksプログラミング中級編 在の要素の数を知る必要があります。この際にvectorのsize ( ) 関数を使用してfor文を作成し、 要素の数だけ繰り返し処理をしています。 void testApp::update(){ //blobsに格納されたofBlobのインスタンスの数だけupdate()をくりかえす for (int i=0; i<blobs.size(); i++) { blobs[i].update(); } } void testApp::draw(){ //blobsに格納されたofBlobのインスタンスの数だけdraw()をくりかえす for (int i=0; i<blobs.size(); i++) { blobs[i].draw(); } } 3–3– 9 円をクリックすると、細かな円に分裂する ― 複数のクラスの生成 2 最後に少し面白いインタラクションを追加してみましょう。現在の伸縮しながら移動する円をクリ ックすると、細胞が分裂するように4つの細かな円に分裂します。分裂した細かな円をさらにクリ ックすると、より細かな円4つに分裂します。これを繰り返すと、次々とフラクタル状に円が増殖し ていくという仕組みです。 変更を加えるファイルは、testApp.cppのみです。keyReleased ( ) 関数とmouseReleased ( ) 関 数を更新しています。 #include "testApp.h" list 3–3_g testApp.cpp void testApp::setup(){ //画面の設定 ofBackground(0, 0, 0); ofSetCircleResolution(32); ofEnableAlphaBlending(); ofSetFrameRate(30); } void testApp::update(){ //blobsに格納されたofBlobのインスタンスの数だけupdate()をくりかえす for (int i=0; i<;blobs.size(); i++) { 206 | 207 blobs[i].update(); } } void testApp::draw(){ //blobsに格納されたofBlobのインスタンスの数だけdraw()をくりかえす for (int i=0; i<blobs.size(); i++) { blobs[i].draw(); } } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ switch (key) { //タイプしたキーを判定 case 'f': //入力したキーが「f」なら、フルスクリーン表示に切り替える ofToggleFullscreen(); break; case 'r': //入力したキーが「r」なら、全ての円をクリア blobs.clear(); } } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ } void testApp::mousePressed(int x, int y, int button){ } void testApp::mouseReleased(int x, int y, int button){ int bSize = blobs.size(); //blobsの要素数をbSizeに格納 if (bSize < 1) { //もしまだ一つもなければ、新規にofBlobを追加 ofBlob b = ofBlob(); //インスタンス化 float dim = ofGetHeight()/3; //大きさを画面サイズの1/3に b.setDim(dim); //大きさを設定 b.setSpeed(ofPoint(0,0)); //スピードは0で設定 b.setPos(ofPoint(mouseX, mouseY)); //位置は画面の中心に設定 blobs.push_back(b); //動的配列blobsにofBlobのインスタンスbを追加 } 3–3 openFrameworksプログラミング中級編 //もし画面に1つでも円が表示されていたら、クリックした円を4つに分裂させる for (int i=0; i<bSize; i++) { //要素の数だけくりかえし ofPoint pos = blobs[i].getPos(); //円の位置を取得してposに代入 float dim = blobs[i].getDim(); //円の大きさを取得してdimに代入 float dist = ofDist(pos.x, pos.y, mouseX, mouseY); //マウスの位置 と円の位置の距離を測定 if(dist < dim){ //もしクリックした場所が円の内部だったら、分裂させる //円1:自分自身を縮小し、左に移動 blobs[i].setDim(dim*0.6); blobs[i].setPos(ofPoint(pos.x-dim*0.7,pos.y)); blobs[i].setSpeed(ofPoint(ofRandom(-dim/150,dim/150), ofRandom(-dim/150,dim/150))); /円2:新規にofBlobを生成し、右に配置 ofBlob b1 = ofBlob(); b1.setDim(dim*0.6); b1.setPos(ofPoint(pos.x+dim*0.7,pos.y)); b1.setSpeed(ofPoint(ofRandom(-dim/150,dim/150), ofRandom(-dim/150,dim/150))); blobs.push_back(b1); //円3:新規にofBlobを生成し、上に配置 ofBlob b2 = ofBlob(); b2.setDim(dim*0.6); b2.setPos(ofPoint(pos.x,pos.y-dim*0.7)); b2.setSpeed(ofPoint(ofRandom(-dim/150,dim/150), ofRandom(-dim/150,dim/150))); blobs.push_back(b2); //円4:新規にofBlobを生成し、下に配置 ofBlob b3 = ofBlob(); b3.setDim(dim*0.6); b3.setPos(ofPoint(pos.x,pos.y+dim*0.7)); b3.setSpeed(ofPoint(ofRandom(-dim/150,dim/150), ofRandom(-dim/150,dim/150))); blobs.push_back(b3); } } } void testApp::windowResized(int w, int h){ } 208 | 209 実行結果 mouseReleased ( ) での処理が主な変更部分です。マウスをクリックすると、もし画面にまだ1つ も円が生成されていなければ新規に1つ追加、もし画面に1つでも円があれば、円の内部をクリッ クした時のみ円を4つに分割するという処理をしています。円の内部をクリックしたか判定するため に、画面上にある全ての円に対してマウスポインタとの距離を測定し、その距離が円の半径より 小さい場合のみ分裂の処理を行います。 マウスポインタと円の中心との距離の測定には、ofDist ( ) 関数を用いています。この関数は、二 組のx座標とy座標の値を引数に指定すると、二点間の距離を返します。 int bSize = blobs.size(); //blobsの要素数をbSizeに格納 if (bSize < 1) { //もしまだ一つもなければ、新規にofBlobを追加 《円の新規作成》 } //もし画面に1つでも円が表示されていたら、クリックした円を4つに分裂させる for (int i=0; i<bSize; i++) { //要素の数だけくりかえし ofPoint pos = blobs[i].getPos(); //円の位置を取得してposに代入 float dim = blobs[i].getDim(); //円の大きさを取得してdimに代入 float dist = ofDist(pos.x, pos.y, mouseX, mouseY); //マウスの位置と円の 位置の距離を測定 if(dist < dim){ //もしクリックした場所が円の内部だったら、分裂させる 《円の新規作成》 } } もしクリックした場所が円の内側なら、円を分裂させます。4つに分割させたいので、自分自身を 縮小した円に加えて新規に3つの円を生成して全部で4つにします。4つの円が生成されたら、そ れぞれの円の座標を上下左右にずらしています。 3–3 openFrameworksプログラミング中級編 //円1:自分自身を縮小し、左に移動 blobs[i].setDim(dim*0.6); blobs[i].setPos(ofPoint(pos.x-dim*0.7,pos.y)); blobs[i].setSpeed(ofPoint(ofRandom(-dim/150,dim/150), ofRandom(-dim/150,dim/150))); //円2:新規にofBlobを生成し、右に配置 ofBlob b1 = ofBlob(); b1.setDim(dim*0.6); b1.setPos(ofPoint(pos.x+dim*0.7,pos.y)); b1.setSpeed(ofPoint(ofRandom(-dim/150,dim/150), ofRandom(-dim/150,dim/150))); blobs.push_back(b1); //円3:新規にofBlobを生成し、上に配置 ofBlob b2 = ofBlob(); b2.setDim(dim*0.6); b2.setPos(ofPoint(pos.x,pos.y-dim*0.7)); b2.setSpeed(ofPoint(ofRandom(-dim/150,dim/150), ofRandom(-dim/150,dim/150))); blobs.push_back(b2); //円4:新規にofBlobを生成し、下に配置 ofBlob b3 = ofBlob(); b3.setDim(dim*0.6); b3.setPos(ofPoint(pos.x,pos.y+dim*0.7)); b3.setSpeed(ofPoint(ofRandom(-dim/150,dim/150), ofRandom(-dim/150,dim/150))); blobs.push_back(b3); 210 | 211 3 3–4– 1 –4 アドオンの利用 アドオンとは この セクションで は、 アドオン の 利 用 につ いて 解 説します。 アドオン(addon)とは、 openFrameworksに機 能を拡 張するためのライブラリです。アドオンを追 加することで、 openFrameworks 単 体ではできなかった様々な機 能を実 現しています。また、アドオンは openFrameworksの開発者以外でも独自に開発して追加することが可能となっており、様々な開 発者がopenFrameworks向けのアドオンを公開しています。 openFrameworksのFAT版をインストールすると、既にいくつかのアドオンが付属してきます。ネ ットワーク通信から画像解析、3次元モデルの読み込みなど、様々な領域をカバーしています。 アドオンの名称 説明 ofxDirList ディレクトリの項目の一覧を生成 ofxXmlSettings アプリケーションの設定をXML形式で保存、読込み ofxOsc Open Sound Control (アプリケーション間で主にサウンド情報をやりとりするプロトコル) をopenFrameworksで使用する ofxOpenCv 画像処理・画像認識用のC言語ライブラリOpenCVを使用できるようにする ofxNetwork ネットワーク通信のプロトコル、TCPとUDPを使用可能にする、マルチキャストにも対応 ofxThred クロスプラットフォームでスレッドの管理を実現 ofxVectorMath ベクトルや行列の計算をする ofxVectorGraphics openFrameworksからPostscriptを生成し出力する ofx3dModelLoader 3ds形式の3DモデルをopenFrameworksに読み込む openFrameworks pre release v0.061 FAT版に収録されているアドオン一覧 3–4– 2 アドオンの使用 1 ― ofxBox2dを使う では実際にアドオンを使用してみましょう。ここでは、ofxBox2dというアドオンを試してみます。 ofxBox2dは、Box2DというライブラリをopenFrameworksで使用できるよう移植したものです。 Box2Dは「物理エンジン」と呼ばれるライブラリの1つです。物理エンジンは、重力や衝突、摩擦 といった物理計算を、複雑な計算をすることなく利用できるようにしたライブラリです。Box2Dは もともとはC++で書かれたライブラリですが、その便利さからActionScript 3やJava、C#など様々 な言語に移植されています。 3–4 openFrameworksプログラミング中級編 アドオンの入手 ofxBox2dはopenFrameworksのFAT版に付属していません。ですので、まず最初にアドオンをダ ウンロードする必要があります。最新版のプログラムをダウンロードするには、svn(Subversion) というバージョン管理用のコマンドを利用してネットワーク経由で入手します。 「アプリケーション」 →「ユーティリティ」→「ターミナル」を起動してください。 ターミナルが起動したら、下記のコマンドを入力します。 $ cd ~/Desktop/ $ svn checkout http://vanderlin.googlecode.com/svn/trunk/openframeworks/ addons/ofxBox2d/ しばらくメッセージが表示された後、DesktopにofxBox2dというフォルダが作成されています。こ れがofxBox2dのファイル一式になります。 作成されたofxBox2dフォルダを、《openFrameworksをインストールしたフォルダ》/addons/ ofxBox2dに移動します。これでアドオンofxBox2dの準備は完了です。 アドオンをopenFrameworksのプロジェクトに追加する 次にアドオンをプロジェクトに追加します。Xcodeで「ファイル」→「新規プロジェクト」を選択し、 プロジェクトタイプをopenFramework Applicationで新 規のプロジェクトをプロジェクト名 「ofxBox2dTest」で作成します。ここにofxBox2dを追加してみましょう。 まず、Xcode画面の左側のコラム、「グループとファイル」の欄のいちばん先頭にあるプロジェクト 名「ofxBox2dTest」の表示されたアイコンを[control]キー+クリック、もしくは右クリックします。 表示されるメニューから「追加」→「新規グループ」を選択します。すると「ofxBox2dTest」の下 にフォルダが追加され、フォルダ名を入力するモードになります。ここでフォルダ名を「addons」 に設定します。 次に作成したaddonsフォルダを[control]キー+クリック、もしくは右クリックします。先程と同 様にメニューが表示されます。今度は「追加」→「既存のファイル」を選択します。するとファイ ル選択のダイアログが表示されますので、先程addonsフォルダにインストールした「ofxBox2d」 フォルダを選択してください。 212 | 213 設定は右下の画面のとおりにして、「追加」ボタンを押します。 最後に、グループとファイルの中のofxBox2dフォルダの中 にある「example」フォルダを選択し、 [delete]キーを押 します。すると 「参照を削除:この参照のみを削除しますか? オリジナルファイルもゴミ箱に入れますか?」という選択画 面が表示されます。ここで「参照を削除」を選んでください。 以上の作業を完了すると、グループとファイルの中身は、 下記の画面のようになるはずです。これでプロジェクトに ofxBox2dのアドオンが追加されました。 ofxBox2dを使用するには、もう1つofxVectorMathをアド オンとして追加する必要があります。ofxVectorMathは最 初からopenFrameworksのFAT版に収録されているので ダウンロードする必要はありません。プロジェクトに追加 すれば使用可能です。先程と同様の手順で、「addons」 フォルダを[control]キー+クリック、もしくは右クリックし て、「 追 加 」→「 既 存ファイルの追 加 」を選 択します。 ofxBox2dと同じ手順で、ofxVectorMathをフォルダごと選 択してaddonsの中に追加します。ofxVectorMathフォルダ の中にある「example」フォルダと「install.xml」ファイルも [delete]キーで消去し「参照を削除」を選択します。最 終的にグループとファイル内のaddonsフォルダの中身は下 記のようになるでしょう。これで必要なアドオンの準備は 完了です。 Box2Dサンプル1:落下する円形の物体 まずはじめに簡単なサンプルから始めましょう。画面をクリックするとランダムに半径を設定した円 が落下してくるというプログラムを作成してみます。 3–4 openFrameworksプログラミング中級編 list 3–4_a #ifndef _TEST_APP testApp.h #define _TEST_APP #include "ofMain.h" #include "ofxVectorMath.h" //ofxVectorMathのライブラリを読込み #include "ofxBox2d.h" //ofxBox2dのライブラリを読込み class testApp : public ofBaseApp { 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(int x, int y, int button); void windowResized(int w, int h); ofxBox2d box2d; //Box2Dのインスタンス vector <ofxBox2dCircle> circles; //円を格納するvector }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ //画面設定 ofSetVerticalSync(true); ofBackground(0, 0, 0); //Box2D初期設定 box2d.init(); //Box2Dの世界を初期化 box2d.setGravity(0,5); //重力を設定、下に5の力 box2d.createBounds(0, 0, ofGetWidth(), ofGetHeight()); //画面を壁で囲む box2d.setFPS(30); //30fpsで表示 } 214 | 215 void testApp::update(){ box2d.update(); //Box2Dを更新 } void testApp::draw(){ //circlesに格納された全ての円を描画 for(int i=0; i<circles.size(); i++) { circles[i].draw(); } //Box2Dで生成された図形を描画 box2d.draw(); } void testApp::keyPressed(int key){ } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ } void testApp::mousePressed(int x, int y, int button){ //画面をクリックすると、円を追加 float r = ofRandom(10, 40); //半径を設定 ofxBox2dCircle circle; //ofxBox2dCircle(円)クラスをインスタンス化 circle.setPhysics(1.0, 0.8, 0.5); //物理パラメータを設定(重さ、反発力、摩擦力) circle.setup(box2d.getWorld(), mouseX, mouseY, r); //マウスの位置に円を設定 circles.push_back(circle); //生成した円をcirclesに追加 } void testApp::mouseReleased(int x, int y, int button){ } void testApp::windowResized(int w, int h){ } 3–4 openFrameworksプログラミング中級編 実行結果 testApp.hの冒頭で、#includeを使って、 「ofxVectorMath.h」と「ofxBox2d.h」を読み込んでいる ところに注目してください。このことで、ofxVectorMathとofxBox2dのアドオンの機能を利用でき るようになります。 #include "ofxVectorMath.h" //ofxVectorMathのライブラリを読込み #include "ofxBox2d.h" //ofxBox2dのライブラリを読込み パブリック変数の定義として、ofxBox2dをインスタンス化してbox2dを生成しています。この ofxBox2dが様々な演算をする物理エンジン本体です。それに加えて、生成された円を格納する ためのVecor「circles」を作成します。 ofxBox2d box2d; //Box2Dのインスタンス vector <ofxBox2dCircle> circles; //円を格納するvector testApp.cppでは、setup ( ) 関数の中で、Box2Dの世界の様々な初期設定を行っています。 「box2d. init ( ) 」でBox2Dの世界の初期化、「box2d.setGrabity(...)」で重力の設定(下向きに5の強さ) 、 「box2d.createBounds(...)」では、 画 面の上 下 左 右を壁で囲んでいます。 最 後に、「box2d. setFPS(...)」で1秒間に描画するコマ数を設定しています。 box2d.init(); //Box2Dの世界を初期化 box2d.setGravity(0,5); //重力を設定、下に5の力 box2d.createBounds(0, 0, ofGetWidth(), ofGetHeight()); //四隅に壁を追加 box2d.setFPS(30); //30fpsで表示 次に、mousePressed ( ) 関数の処理を見てみましょう。ここでは、マウスがクリックされる度に新 規の円の生成をしています。まず半径をランダムに決定しています。次に「ofxBox2dCircle」クラ スをインスタンス化して「circle」を生成しています。このofBox2dCircleは、Box2Dで定義された 円形の物体です。この物体をBox2Dの世界に追加すると、重力や物体同士の衝突などが自動的 に適用され計算をしてくれます。次にこの生成された円に基本的な物理パラメータを追加します。 「circle.setPhysics(...)」で、重さ、反発力、摩擦力を設定しています。基本設定が完了したところ で「circle.setup(...)」が円をBox2dの世界に追加しています。生成された円は、vectorである circlesにpush_back ( ) して追加しています。 216 | 217 float r = ofRandom(10, 40); //半径を設定 ofxBox2dCircle circle; //ofxBox2dCircle(円)クラスをインスタンス化 circle.setPhysics(1.0, 0.8, 0.5); //物理パラメータを設定(重さ、反発力、摩擦力) circle.setup(box2d.getWorld(), mouseX, mouseY, r); //マウスの位置に円を設定 circles.push_back(circle); //生成した円をcirclesに追加 update ( ) 関数とdraw ( ) 関数での処理はとてもシンプルです。update ( ) 関数内では、ofxBox2d クラスのインスタンスbox2dに対して、update ( ) メソッドを呼び出すだけで全ての座標計算を自 動的に行います。こうした面倒な計算を全て任せてしまうことができる部分が、物理エンジンを使 う最大のメリットと言えるでしょう。 box2d.update(); //Box2Dを更新 座標の計算が終了したら、draw ( ) 関数で作成した円を全て描画します。circlesに格納された全 ての円をfor文を利用して描画しています。最後にofxBox2dのインスタンスbox2dに対して、 draw ( ) メソッドを呼び出しています。これは、box2d側で生成されたオブジェクト(このサンプル の場合は上下左右の壁やマウスで円をドラッグした際に表示されるラインなど)を描画しています。 //circlesに格納された全ての円を描画 for(int i=0; i<circles.size(); i++) { circles[i].draw(); } //Box2Dで生成された図形を描画 box2d.draw(); カスタムの図形を追加する ofxBox2dCircleは、 ワイヤーフレ ームだけ の 円 で 少し 味 気 ない 感じもします。 そこで、 ofxBox2dCircleを継承したオリジナルのクラスを作成して、より表現に幅を持たせてみたいと思い ます。この章の「3–3 OOOF:オブジェクト指向oFプログラミング」でやった手順通りに新規に クラスを追加します。Xcodeの「グループとファイル」内のsrcフォルダを[control]キー+クリック、 もしくは右クリックして、「追加」→「新規ファイル」を選択し、新規ファイルのテンプレート選択 画面から「Mac OS X」→「C and C++」→「C++ File」を選択し、ファイル名を「CustomCircle. cpp」に設定します。 testAppからは、ofxBox2dCircleを使用するのと全く同じやり方で、CustomCircleを使用するこ とが可能です。これは、CustomCircleがofxBox2dCircleを継承したクラスなので、その性質を そのまま受け継いでいるからです。 今回のサンプルでは、マウスのクリックではなく、キーボードから[c]をタイプすると、マウスの 位置にカスタムにデザインした円を配置するようにしています。 3–4 openFrameworksプログラミング中級編 list 3–4_b #include "ofxVectorMath.h" CustomCircle.h #include "ofxBox2d.h" //ofxBox2dCircleを継承したクラスCustomCircleを定義 class CustomCircle : public ofxBox2dCircle { public: void draw(); //円を描画する }; CustomCircle.cpp #include "CustomCircle.h" void CustomCircle::draw(){ float radius = getRadius(); //半径を取得 glPushMatrix(); //座標を変更 glTranslatef(getPosition().x, getPosition().y, 0); //物体の位置に座標を移動 ofFill(); //色を塗り潰す ofSetColor(31,127,255,100); //円1の色を設定 ofCircle(0, 0, radius); //円1を描画 ofSetColor(31,127,255,200); //円2の色を設定 ofCircle(0, 0, radius*0.7); //円2を描画 glPopMatrix(); //座標を元に戻す } testApp.h #ifndef _TEST_APP #define _TEST_APP #include "ofMain.h" #include "ofxVectorMath.h" //ofxVectorMathのライブラリを読込み #include "ofxBox2d.h" //ofxBox2dのライブラリを読込み #include "CustomCircle.h" //CustomCircleで作成したクラスを使用 class testApp : public ofBaseApp { 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); 218 | 219 void mousePressed(int x, int y, int button); void mouseReleased(int x, int y, int button); void windowResized(int w, int h); ofxBox2d box2d; //Box2Dのインスタンス vector <CustomCircle> circles; //CustomCircleのインスタンスを格納するvector }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ //画面設定 ofSetVerticalSync(true); ofEnableAlphaBlending(); ofSetCircleResolution(64); ofBackground(0, 0, 0); //Box2D初期設定 box2d.init(); //Box2Dの世界を初期化 box2d.setGravity(0,5); //重力を設定、下に5の力 box2d.createBounds(0, 0, ofGetWidth(), ofGetHeight()); //四隅に壁を追加 box2d.setFPS(30); //30fpsで表示 } void testApp::update(){ box2d.update(); //Box2Dを更新 } void testApp::draw(){ //circlesに格納された全ての図形を描画 for(int i=0; i<circles.size(); i++) { circles[i].draw(); } //Box2Dで生成された図形を描画 box2d.draw(); } void testApp::keyPressed(int key){ //「c」をタイプすると、マウスの位置に円を追加 3–4 openFrameworksプログラミング中級編 if (key == 'c') { float r = ofRandom(5, 20); //半径を設定 CustomCircle c; //CustomCircleクラスをインスタンス化 c.setPhysics(1.0, 0.8, 0.5); //物理パラメータを設定 c.setup(box2d.getWorld(), mouseX, mouseY, r); //マウスの位置に円を設定 circles.push_back(c); //生成した円をcirclesに追加 } } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } … … 実行結果 画面に障害物を配置する Box2Dの世界に追加する物体は、全て動いている必要はありません。画面に追加する際の設定 で固定した物体を配置することも可能です。試しに、画面上に固定された長方形の障害物を配 置してみましょう。 カスタム の 円 を 描くクラス、CustomCircleクラス(CustomCircle.hと CustomParticle.cpp)に関しては変更はありません。 list 3–4_c #ifndef _TEST_APP testApp.h #define _TEST_APP #include "ofMain.h" 220 | 221 #include "ofxVectorMath.h" //ofxVectorMathのライブラリを読込み #include "ofxBox2d.h" //ofxBox2dのライブラリを読込み #include "CustomCircle.h" //CustomCircleで作成したクラスを使用 class testApp : public ofBaseApp { 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(int x, int y, int button); void windowResized(int w, int h); ofxBox2d box2d; //Box2Dのインスタンス vector <CustomCircle> circles; //CustomCircleのインスタンスを格納するvector vector <ofxBox2dRect> rects; //障害物用の長方形ofxBox2dRectを格納する vector }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ //画面設定 ofSetVerticalSync(true); ofEnableAlphaBlending(); ofSetCircleResolution(64); ofBackground(0, 0, 0); //Box2D初期設定 box2d.init(); //Box2Dの世界を初期化 box2d.setGravity(0,5); //重力を設定、下に5の力 box2d.createFloor(); box2d.checkBounds(false); box2d.setFPS(30); //30fpsで表示 //障害物を追加 3–4 openFrameworksプログラミング中級編 for (int i=0; i<100; i++) { //100個の配置する ofxBox2dRect r; //ofxBox2dRect(長方形)の物体のインスタンスrを生成 float w = 2; //幅2 float h = 2; //高さ2 float x = ofRandom(50, ofGetWidth()-50); //ランダムにx座標の位置を指定 float y = ofRandom(50, ofGetHeight()-50); //ランダムにy座標の位置を指定 r.setPhysics(1.0, 1.2, 0.5); //物理パラメータを設定、重さ1、反発力1.2、摩擦力:0.5 r.setup(box2d.getWorld(), x, y, w, h, true); //固定した状態で画面に追加 rects.push_back(r); //rectsに生成した長方形を追加 } } void testApp::update(){ box2d.update(); //Box2Dを更新 } void testApp::draw(){ //circlesに格納された全ての図形を描画 for(int i=0; i<circles.size(); i++) { circles[i].draw(); } //rectsに格納された全ての図形を描画 for(int i=0; i<rects.size(); i++) { rects[i].draw(); } //Box2Dで生成された図形を描画 box2d.draw(); } void testApp::keyPressed(int key){ //「c」をタイプすると、マウスの位置に円を追加 if (key == 'c') { float r = ofRandom(5, 20); //半径を設定 CustomCircle c; //CustomCircleクラスをインスタンス化 c.setPhysics(1.0, 0.8, 0.5); //物理パラメータを設定 c.setup(box2d.getWorld(), mouseX, mouseY, r); //マウスの位置に円を設定 circles.push_back(c); //生成した円をcirclesvectorに追加 } // 「r」をタイプすると、全ての円を消去 if (key == 'r') { for(int i=0; i<circles.size(); i++) { 222 | 223 circles[i].destroyShape(); } circles.clear(); } } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } … … 実行結果 まず、testApp.hの中で複数の障害物の長方形を格納するためのvectorであるrectsに格納してい ます。それぞれの長 方 形は、「ofxBox2dRect」というクラスのインスタンスとなっています。 ofxBox2dRectは、長方形の形の物体をBox2Dの世界に追加するクラスです。 vector <ofxBox2dRect> rects; //障害物用の長方形ofxBox2dRectを格納するvector 実際に複数の障害物を追加する処理は、setup ( ) 関数の中で行っています。このサンプルでは、 縦2ピクセル、横2ピクセルの長方形100個をランダムな場所に配置しています。 for (int i=0; i<100; i++) { //100個の配置する ofxBox2dRect r; //ofxBox2dRect(長方形)の物体のインスタンスrを生成 float w = 2; //幅2 float h = 2; //高さ2 float x = ofRandom(50, ofGetWidth()-50); //ランダムにx座標の位置を指定 3–4 openFrameworksプログラミング中級編 float y = ofRandom(50, ofGetHeight()-50); //ランダムにy座標の位置を指定 r.setPhysics(1.0, 1.2, 0.5); //物理パラメータを設定、重さ1、反発力1.2、摩擦力:0.5 r.setup(box2d.getWorld(), x, y, w, h, true); //固定した状態で画面に追加 rects.push_back(r); //rectsに生成した長方形を追加 } 配置した100個の障害物は、それ自身は画面上で固定されていますがBox2Dの物理演算の世界 に配置された物体ですので、上から円との衝突を計算してパチンコのように複雑に跳ね返します。 3–4–3 アドオンの使用 2 ― ofxOpenCvを使用する 次に、別の種類のアドオンを使用してみましょう。ここではofxOpenCvというアドオンをとりあげ てみます。 ofxOpenCvを使用して動体検知を行う ofxOpenCvは、OpenCVというライブラリの一部をopenFrameworksから使用できるようにした アドオンです。OpenCVとは、Intel Open Source Computer Vision Libraryの略で、アメリカ Intel社で開発された画像処理、画像認識のためのライブラリです。オープンソースで公開されて おり、商用・非商用を問わずフリーで使用することが許可されています。ロボット工学や生体認 証などの分野の研究に使用されています。 このOpenCVを利用した画像認識の技術は、メディアアートの世界でも広く利用されています。1 章の作 品 例で紹 介した、Chris O'SheaのHand from Aboveや、Graffiti Reserch Labによる L.A.S.E.R. Tagなど、現実世界のリアルタイムな視覚情報から移動する物体を検知して、その情 報を元に仮想世界を合成するような系統の作品の多くは、その画像解析のベースにofxOpenCv を用いています。こうしたメディアアート作品にとってOpenCVの技術は欠かすことのできない中核 技術となっているといっても過言ではないでしょう。 ここでは、ofxOpenCvを利用して動体検知、つまり動いている物体を検出し、検出された物体 の輪郭を抽出するサンプルを作成してみます。以下のような手順で、輪郭抽出まで順を追って解 説していきます。 1 ビデオカメラから画像を取り込む 2 画像をグレースケールに変換 3 背景差分処理をする 4 輪郭抽出を行う ofxOpenCvをプロジェクトに追加する まず、新規にプロジェクト「OpenCvTest」を作成してください。 まずはじめにofxOpenCvをプロジェクトに追加します。ofxOpenCvはopenFrameworksのFAT版 224 | 225 にあらかじめ収録されているので、ダウンロードする必要はありません。 これまでの他のアドオンと同様の手順で、 「グループとファイル」の欄のいちばん先頭にあるプロジ ェクト名「ofxOpenCvTest」の表示されたアイコンを[control]キー+クリック、もしくは右クリッ クして、表示されるメニューから「追加」→「新規グループ」を選択します。追加されたフォルダ 名を「addons」に設定します。次に、 「addons」フォルダを[control]キー+クリック、もしくは 右クリックして、「 追 加 」→「 既 存ファイルの追 加 」を選 択し、addonsフォルダ内にある ofxOepnCvをフォルダごと選択してaddonsの中に追加します。ofxOpenCvフォルダの中にある 「example」フォルダと「install.xml」ファイルも[delete]キーで消去し「参照を削除」を選択しま す。これでofxOpenCvの準備は完了です。 <screen4-4-10.jpeg> <screen4-4-11.jpeg> <screen4-4-12.jpeg> <screen4-4-13.jpeg> ビデオカメラから画像を取り込む 次に、testApp.hとtestApp.cppを編集し、カメラからの映像をofxOpenCvで分析できるように 取り込んでみます。 testApp.hでは、コンピュータに接続したカメラから映像を取り込むために、ofVideoGrabberク ラスのインスタンスvidGrabberを用意しています。ビデオから取り込まれた最新の画像はこの vidGrabberに格納されます。このvidGrabberから取り込まれた映像をofxOpenCvで解析するた めに、ofxOpenCvでカラー画像を扱うためのクラス、ofxCvColorImageクラスのインスタンス colorImgを生成しています。ofVideoGrabberから取り込まれた映像の最新のフレームのビットマ ップ情報を常にこのcolorImgにコピーすることで、映像をofxOpenCvで解析する準備をしています。 次にtestApp.cppでの処理を見ていきましょう。setup ( ) 関数内では、ofVideoGrabberのインス タンスvidGrabberを幅320pixel、高さ240pixelのサイズで映像を取り込むように設定し、初期 化しています。また、同時にofxColorImageのインスタンスcolorImgで画像を解析するための領 域を確保します。ofxOpenCvの画像を扱うクラスでは、使用する前にまずその画像の大きさを指 3–4 openFrameworksプログラミング中級編 定して、その分の領域を確保する必要があります。ofxColorImageで指定した幅と高さで解析す る画像の領域を確保するには、下記のように記述します。 《ofxColorImageのインスタンス》.allocate(《幅》,《高さ》); このサンプルでは、colorImgのサイズは、カメラから取り込む画像に合わせて幅360pixel、高さ 240pixelで領域を確保しています。 update ( ) 関数では、常にカメラから読みこまれた最新の映像を1フレーム分格納するようにしてい ます。その最新フレームの画像をofxColoImageのインスタンスcolorImgに格納しています。 ofxColorImageに画像データを読みこみは、下記のように行います。 《ofxColorImage のインスタンス 》.setFromPixels(《ofVideoGrabber のインスタンス 》 .getPixels(), 《幅》,《高さ》); この操作により、カメラから読み込まれた最新の映像1フレーム分の画像データを、ofxOpenCv を用いて解析する準備ができました。 draw ( ) 関数内で、現状の画像の状態をプレビューします。現在解析のために保持している colorImgの画像を画面に描画します。ofxColorImageで扱っている画像データを画面に描画する には下記の命令を使用します。 《ofxColorImageのインスタンス》.draw(《左端のx 座標》,《上端のy 座標》); このサンプルでは、(10, 10)の場所に画像を描画しています。 list 3–4_d #ifndef _TEST_APP testApp.h #define _TEST_APP #include "ofMain.h" #include "ofxOpenCv.h" class testApp : public ofBaseApp { public: void setup(); void update(); void draw(); void keyPressed (int key); void keyReleased(int key); void mouseMoved(int x, int y); 226 | 227 void mouseDragged(int x, int y, int button); void mousePressed(int x, int y, int button); void mouseReleased(int x, int y, int button); void windowResized(int w, int h); ofVideoGrabber vidGrabber; //ビデオ入力 ofxCvColorImage colorImg; //OpenCVで扱うカラーイメージ }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ ofBackground(0,0,0); //幅320pixel、高さ240pixelでビデオ取り込み初期化 vidGrabber.initGrabber(320,240); //OpenCVで解析する320pixel x 240pixelのカラー画像の領域を確保 colorImg.allocate(320,240); } void testApp::update(){ //新規にフレームを取り込んだかを判定する変数 bool bNewFrame = false; //1フレーム映像を取り込み vidGrabber.grabFrame(); //最後に取り込んだフレームから変化があったかを判定 bNewFrame = vidGrabber.isFrameNew(); //新規のフレームの場合とりこみ実行 if (bNewFrame){ //OpenCVで解析するカラー画像領域に取得した映像を格納 colorImg.setFromPixels(vidGrabber.getPixels(), 320,240); } } void testApp::draw(){ //取り込んだ画像を表示 colorImg.draw(10,10); } … … 3–4 openFrameworksプログラミング中級編 実行結果 画像をグレースケールに変換 今回のプロジェクトでは、動いている物体を検知するために映像を使用するので、色の情報は必 要ありません。解析する映像のデータ量は少ないほうが効率が良くなります。そこで、カメラから 取り込んだカラー映像をグレースケールに変換します。 まず、testApp.hにグレースケールの画像を扱うためのofxOpenCvのクラス、ofxCvGraysacaleImage のインスタンス、grayImageを追加しています。 testApp.cppでは、まずsetup ( ) 関数内で、カラー画像colorImgと同様に、グレースケールの画 像grayImgのための領域をcolorImgと同じサイズ360pixel×240pixelで確保しています。 update ( ) 関数では、カラー画像のイメージをグレースケールに変換しています。といっても処理 はとても簡単です。カラーのイメージを格納したofxCvColorImgのインスタンスcolorImgを、グレ ースケールofxGrayscaleImageのイメージを格納するためのインスタンスgrayImageに代入するだ けで、画像はグレースケールに変換されます。 draw ( ) 関数で、グレースケールに変換された画像をプレビューしています。カラー画像の40pixel 右 (360, 20)の点にグレースケールに変換した画像を描画しています。 list 3–4_e #ifndef _TEST_APP testApp.h #define _TEST_APP #include "ofMain.h" #include "ofxOpenCv.h" class testApp : public ofBaseApp { public: void setup(); void update(); 228 | 229 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(int x, int y, int button); void windowResized(int w, int h); ofVideoGrabber vidGrabber; //ビデオ入力 ofxCvColorImage colorImg; //OpenCVで扱うカラーイメージ ofxCvGrayscaleImage grayImage; //OpenCVで扱うグレースケールイメージ }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ ofBackground(0,0,0); //幅320pixel、高さ240pixelでビデオ取り込み初期化 vidGrabber.initGrabber(320,240); //OpenCVで解析する320pixel x 240pixelのカラー画像の領域を確保 colorImg.allocate(320,240); //OpenCVで解析する320pixel x 240pixelのグレースケール画像の領域を確保 grayImage.allocate(320,240); } void testApp::update(){ //新規にフレームを取り込んだかを判定する変数 bool bNewFrame = false; //1フレーム映像を取り込み vidGrabber.grabFrame(); //最後に取り込んだフレームから変化があったかを判定 bNewFrame = vidGrabber.isFrameNew(); //新規のフレームの場合とりこみ実行 if (bNewFrame){ //OpenCVで解析するカラー画像領域に取得した映像を格納 colorImg.setFromPixels(vidGrabber.getPixels(), 320,240); //取り込んだカラー映像をグレースケールに変換 3–4 openFrameworksプログラミング中級編 grayImage = colorImg; } } void testApp::draw(){ //取り込んだ画像を(10,10)の位置に表示 colorImg.draw(10,10); //グレースケールに変換した画像を(340,10)の位置に表示 grayImage.draw(340,10); } … … <小見出し> 実行結果 背景差分処理をする 物体の動きを検出するプログラムを作成するにあたって、まず考えなければならないのは、取り込 まれた画像の背景と物体を分離することです。背景が単色のスクリーンになっているような環境で あれば、動いている物体の輪郭はそのまま検出できますが、ギャラリーや公共の空間など、様々 な色や形が背景に映り込んでしまうような環境でプログラムを使用しなければならない状況だとす ると、いかにして背景と物体とを分離して認識するかということが重要な課題となります。 ofxOpenCvでは、背景差分法という手法を用いて、背景から移動する物体を分離することが可 能です。背景差分法とは以下の手順で背景から物体を分離して検出する方法です。 1 あらかじめ、検出する物体が無い状態で、背景画像を記録しておく 2 検出する物体を含めた現在の画像を入力 3 記録しておいた背景画像と、現在の画像との差分をとった画像を生成 4 差分をとった画像を背景から分離するため、設定した値を閾値として、白黒に2値化する では実際に、背景差分法によって入力画像を処理してみましょう。背景差分法を実装するにあた 230 | 231 って、まず背景の画像を記録する必要があります。このサンプルでは、スペースキーを押すと背景 画像を撮影するようにしようと思います。また[+]キーを押すと差分画像を2値化する際の閾値の レベルを上げ、反対に[-]キーを押すと閾値のレベルを下げています。 testApp.hでは、2つのofxGrayscaleImageクラスをインスタンス化した、グレースケールの画像2 つを追加しています。1つは、背景画像のイメージを格納するためのインスタンスgrayBgです。そ してもう1つは、背景画像と現在の画像との差分をとり、一定の閾値を境に2値化した画像 grayDiffです。さらに、背景画像の登録を何度もやり直しできるように、背景画像を学習済みか どうかを判断するためのブール型の変数bLearnBackgroundと、差分画像を2値化する際の閾値 の値を保存するための変数thresholdを新規に作成しています。 testApp.cppでは、まずsetup ( ) でgrayBgとgrayDiffそれぞれに320pixel×240pixelで画像の領 域の確保を追加しています。update ( ) 関数で背景差分法の処理をしています。背景画像を新規 に取り込むモードになっているかどうかを判定し、もし背景画像を取り込むモードになっていたら、 現在のグレースケールのイメージを背景画像として登録します。一度背景画像を登録したら、背 景画像の取り込みは次にスペースキーを押すまでは行わないようにしています。 背景差分法は具体的には、記録した背景画像と現在の最新のフレームの画像を、それぞれのピ クセルごとに値の差分をとることによって実現しています。ofxOpenCvを使用することにより、2つ の画像の差分の演算は簡単に行うことが可能です。下記のような書式で行います。 《ofxGrayscaleImageのインスタンス》.absDiff(《ofxImageのインスタンス1》, 《ofxImage のインスタンス2》); 差分を計算したら、画像のピクセルごとの明るさを一定の値を境目にして、それより上のものと下 のもので白か黒かという2つの値に分離します。つまり、濃淡のある画像を白黒画像に変換するこ とになります。この処理を「2値化」と呼びます。物体の輪郭を検出するにあたり、差分の画像を 2値化する必要があります。下記の記述で2値化を行うことが可能です。2値化する際の境目とな る値を「閾値」と呼びます。 《ofxGrayscaleImageのインスタンス》.threshold(《閾値》); このサンプルでは、[+]キーの入力があると閾値の値を上げ、 [-]キーの入力があると閾値の値 を下げるようにしています。閾値の値をうまく調整することにより、物体の輪郭をきれいに抽出す ることが可能となります。 draw ( ) 関数では、今までの画像に加えて、登録した背景画像と、背景との差分をとって2値化 した差分画像を描画しています。 3–4 openFrameworksプログラミング中級編 list 3–4_f #ifndef _TEST_APP testApp.h #define _TEST_APP #include "ofMain.h" #include "ofxOpenCv.h" class testApp : public ofBaseApp { 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(int x, int y, int button); void windowResized(int w, int h); ofVideoGrabber vidGrabber; //ビデオ入力 ofxCvColorImage colorImg; //OpenCVで扱うカラーイメージ ofxCvGrayscaleImage grayImage; //OpenCVで扱うグレースケールイメージ ofxCvGrayscaleImage grayBg; //背景のグレースケールイメージ ofxCvGrayscaleImage grayDiff; //背景との差分のグレースケールイメージ bool bLearnBakground; //背景を記録したかどうか int threshold; //背景の差分の閾値 }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ ofBackground(0,0,0); //幅320pixel、高さ240pixelでビデオ取り込み初期化 vidGrabber.initGrabber(320,240); //OpenCVで解析する320pixel x 240pixelのカラー画像の領域を確保 232 | 233 colorImg.allocate(320,240); //OpenCVで解析する320pixel x 240pixelのグレースケール画像の領域を確保 grayImage.allocate(320,240); //背景画像として320pixel x 240pixelのグレースケール画像の領域を確保 grayBg.allocate(320,240); //背景との差分画像として320pixel x 240pixelのグレースケール画像の領域を確保 grayDiff.allocate(320,240); //背景の学習モードを真に bLearnBakground = true; //閾値を100に threshold = 100; } void testApp::update(){ //新規にフレームを取り込んだかを判定する変数 bool bNewFrame = false; //1フレーム映像を取り込み vidGrabber.grabFrame(); //最後に取り込んだフレームから変化があったかを判定 bNewFrame = vidGrabber.isFrameNew(); //新規のフレームの場合とりこみ実行 if (bNewFrame){ //OpenCVで解析するカラー画像領域に取得した映像を格納 colorImg.setFromPixels(vidGrabber.getPixels(), 320,240); //取り込んだカラー映像をグレースケールに変換 grayImage = colorImg; //新規に背景を記録する場合 if (bLearnBakground == true){ //現在の取り込んだグレースケールイメージを記憶 grayBg = grayImage; //背景の記録をしないモードに戻す bLearnBakground = false; } //背景画像と現在の画像の差分の絶対値を取得 grayDiff.absDiff(grayBg, grayImage); //差分画像を設定した閾値を境に二値化 grayDiff.threshold(threshold); } } void testApp::draw(){ //取り込んだ画像を(10,10)の位置に表示 colorImg.draw(10,10); 3–4 openFrameworksプログラミング中級編 //グレースケールに変換した画像を(340,10)の位置に表示 grayImage.draw(340,10); //学習した背景画像を(10,260)の位置に表示 grayBg.draw(10,260); //背景画像と現在の画像との差分を(340, 260)の位置に表示 grayDiff.draw(340,260); } void testApp::keyPressed(int key){ switch (key){ case ' ': //スペースキーで背景を学習モードに bLearnBakground = true; break; case '+': //[+]キーで背景の閾値のレベルを上げる(最大255) threshold ++; if (threshold > 255) threshold = 255; break; case '-': //[-]キーで背景の閾値のレベルを下げる(最小0) threshold --; if (threshold < 0) threshold = 0; break; } } … … 実行結果 輪郭抽出を行う ようやく、背景から物体を分離した状態で2値化した画像を生成することができるようになりまし た。この2値化した画像をもとに、物体の輪郭の情報を検出したいと思います。ofxOpenCvでは、 234 | 235 2値化した画像から輪郭を抽出するクラスofxCvContourFinderが用意されています。このクラス を使用することで、すぐに画像から輪郭を抽出することが可能です。 まず、testApp.hで輪郭抽出のためのクラスofxCvContourFinderのインスタンスcontourFinderを 新規に追加しています。 次に、testApp.cppを見ていきましょう。update ( ) 関数の最後で、ofxCvContourFinderを利用 して2値化差分画像から輪郭を抽出しています。ofxCvContourFinderを利用して輪郭の情報を 取得するには以下のように行います。 《ofxCvContourFinder のインスタンス》.findContours(《分析する画像 ofxGraysacleImage》 , 《最小エリア》, 《最大エリア》, 《認識する物体の数》, 《穴を検出するか》); この処理によって、contourFinderには検出された輪郭に関する情報が格納されます。 最後に、draw ( ) 関数で抽出した輪郭の情報を取り出して、描画しています。ofxCvContourFinder クラスには、あらかじめ抽出した輪郭をopenFrameworksの命令で描画する機能が組み込まれて います。この機能を利用することで、簡単に輪郭の情報をプレビューすることが可能です。まず、 抽出された物体の数を取得します。検出された物体の数は、 「 《ofxCvContourFinderのインスタン ス》.nBlobs」というプロパティに格 納されています。また、 検出されたそれぞれの物 体は、 「《ofxCvContourFinderのインスタンス》.blobs[ ]」という配列に格納されています。この検出され た物体の数と、物体の配列を利用して、以下のような書式で検出した物体の輪郭線を描いてい ます。 for (int i = 0; i < 《ofxCvContourFinderのインスタンス》.nBlobs; i++){ 《ofxCvContourFinderのインスタンス》.blobs[i].draw(《x 座標》,《y 座標》); } list 3–4_g #ifndef _TEST_APP testApp.h #define _TEST_APP #include "ofMain.h" #include "ofxOpenCv.h" class testApp : public ofBaseApp { public: void setup(); void update(); void draw(); 3–4 openFrameworksプログラミング中級編 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(int x, int y, int button); void windowResized(int w, int h); ofVideoGrabber vidGrabber; //ビデオ入力 ofxCvColorImage colorImg; //OpenCVで扱うカラーイメージ ofxCvGrayscaleImage grayImage; //OpenCVで扱うグレースケールイメージ ofxCvGrayscaleImage grayBg; //背景のグレースケールイメージ ofxCvGrayscaleImage grayDiff; //背景との差分のグレースケールイメージ ofxCvContourFinder contourFinder; //輪郭抽出のためのインスタンス bool bLearnBakground; //背景を記録したかどうか int threshold; //背景の差分の閾値 }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ ofBackground(0,0,0); //幅320pixel、高さ240pixelでビデオ取り込み初期化 vidGrabber.initGrabber(320,240); //OpenCVで解析する320pixel x 240pixelのカラー画像の領域を確保 colorImg.allocate(320,240); //OpenCVで解析する320pixel x 240pixelのグレースケール画像の領域を確保 grayImage.allocate(320,240); //背景画像として320pixel x 240pixelのグレースケール画像の領域を確保 grayBg.allocate(320,240); //背景との差分画像として320pixel x 240pixelのグレースケール画像の領域を確保 grayDiff.allocate(320,240); //背景の学習モードを真に bLearnBakground = true; //閾値を100に threshold = 100; } 236 | 237 void testApp::update(){ //新規にフレームを取り込んだかを判定する変数 bool bNewFrame = false; //1フレーム映像を取り込み vidGrabber.grabFrame(); //最後に取り込んだフレームから変化があったかを判定 bNewFrame = vidGrabber.isFrameNew(); //新規のフレームの場合とりこみ実行 if (bNewFrame){ //OpenCVで解析するカラー画像領域に取得した映像を格納 colorImg.setFromPixels(vidGrabber.getPixels(), 320,240); //取り込んだカラー映像をグレースケールに変換 grayImage = colorImg; //新規に背景を記録する場合 if (bLearnBakground == true){ //現在の取り込んだグレースケールイメージを記憶 grayBg = grayImage; //背景の記録をしないモードに戻す bLearnBakground = false; } //背景画像と現在の画像の差分の絶対値を取得 grayDiff.absDiff(grayBg, grayImage); //差分画像を設定した閾値を境に二値化 grayDiff.threshold(threshold); //二値化した差分画像から、輪郭を抽出する contourFinder.findContours(grayDiff, 20, (340*240)/3, 10, true); } } void testApp::draw(){ ofNoFill(); ofSetColor(0xffffff); //取り込んだ画像を(10,10)の位置に表示 colorImg.draw(10,10); //グレースケールに変換した画像を(340,10)の位置に表示 grayImage.draw(340,10); //記録した背景画像を(10,260)の位置に表示 grayBg.draw(10,260); //背景画像と現在の画像との差分を(340,260)の位置に表示 grayDiff.draw(340,260); //抽出した輪郭線の情報を(340,500)の位置に描画する for (int i = 0; i < contourFinder.nBlobs; i++){ 3–4 openFrameworksプログラミング中級編 contourFinder.blobs[i].draw(340,500); } } void testApp::keyPressed(int key){ switch (key){ case ' ': //スペースキーで背景を学習モードに bLearnBakground = true; break; case '+': //[+]キーで背景の閾値のレベルを上げる(最大255) threshold ++; if (threshold > 255) threshold = 255; break; case '-': //[-]キーで背景の閾値のレベルを下げる(最小0) threshold --; if (threshold < 0) threshold = 0; break; } } … … 実行結果 238 | 239 3–4–4 ofxBox2DとofxOpenCvを組み合せる アドオンのまとめとして、本章でとりあげた2つのアドオン、ofxBox2DとofxOpenCvの両方を用い たプログラムを作成してみましょう。ofxOpenCvでカメラからの映像の動体検知を行い輪郭抽出 し、そこで得られた輪郭線の情報をパーティクルの障害物として使用して、ofxBox2Dを利用した 物理演算を行うというサンプルを作成してみます。 まず、新規プロジェクト「ofxOpenCvBox2d」を作成してください。 ここに、アドオンを追加していきます。まず「3–4–2 アドオンの 使用 1―ofxBox2dを使う」の「アドオンをopenFrameworks のプロジェクトに追加する」 (P.212)で解説したやり方に沿って、 ofxBox2dとofxVectorMathをアドオンに追加します。さらに「3– 4–3 アドオンの使用 2―ofxOpenCvを使用する」の「ofx OpenCvをプロジェクトに追加する」 (P.224)の手順を参照しな がら、ofxOpenCvを追加してください。最終的には、addonsフ ォルダの中は右記のようになっているはずです。 プログラムの構造は、ここまでで解説してきたアドオンの使用法をそのまま活用しています。まず、 ofxOpenCvを利用して、カメラから取り込んだ映像から動体検知をしてその物体の輪郭を検出し ます。検出した輪郭線の座標情報は、連続する線分としてofxBox2dの物理演算の処理に加えて いきます。ただし、検出した輪郭線の情報をそのままofxBox2dの線分として利用すると、とても 複雑な形状をした障害物となるため、演算量が膨大になり処理がスムーズに行われない恐れがあ ります。そのため、このサンプルではofxContourAnalysisというアドオンを使用して、輪郭線を分 析し、より単純な線分に変換しています。ofxContourAnalysisは、サンプルファイルのsrcフォル ダ内に内包されています。 プログラムのパラメータの調整や処理の切り替えは、キーボードとマウスを使用します。使用可能 なキーとその処理の内容は下記のように定義されています。 スペースキー:背景画像の取り込み [+]キー:2値化の閾値を上げる [-]キー:2値化の閾値を下げる 1 キー:輪郭線を複雑に 2キー:輪郭線を単純に [r]キー:線分のつながる順番を逆転。これにより輪郭の内と外が反転する マウスをクリック:画面上のクリックした場所にパーティクルを追加する ofxBox2dCircleを継承した、カスタムにデザインしたパーティクルCustomParticleクラスは、この 章の「カスタムの図形を追加する」 (P.217)で使用したクラスをそのまま使用しています。クラスの 追加のやりかたは、この説明を参照してください。 3–4 openFrameworksプログラミング中級編 list 3–4_h #include "ofxVectorMath.h" CustomCircle.h #include "ofxBox2d.h" //ofxBox2dCircleを継承したクラスCustomCircleを定義 class CustomCircle : public ofxBox2dCircle { public: void draw(); //円を描画する }; CustomCircle.cpp #include "CustomCircle.h" void CustomCircle::draw() { float radius = getRadius(); //半径を取得 glPushMatrix(); //座標を変更 glTranslatef(getPosition().x, getPosition().y, 0); //物体の位置に座標を移動 ofFill(); //色を塗り潰す ofSetColor(31,127,255,100); //円1の色を設定 ofCircle(0, 0, radius); //円1を描画 ofSetColor(31,127,255,200); //円2の色を設定 ofCircle(0, 0, radius*0.7); //円2を描画 glPopMatrix(); //座標を元に戻す } testApp.hとtestApp.cppで、ofxOpenCvとofxBox2dを統合しています。以下のような流れで処 理を行っています。 ofVideoGrabberでカメラからの映像を取り込み 最新のフレームをofxOpenCvで解析 グレースケールに変換 記録した背景画像との差分を計算 差分画像を2値化 輪郭抽出 抽出した輪郭の座標をofxContourAnalysisで単純な形態に変換 変換した輪郭線に沿って、ofxBox2dLineで連続する直線としてトレースする 最新のカメラからの映像を描画 パーティクルと輪郭線をトレースした直線を描画 パーティクルと生成した直線とで物理計算を行う 環境に応じて、2値化の閾値レベル[- / +]と、輪郭線の単純さ[1 / 2]を調整し、映像に映った 物体の輪郭と分析された輪郭線がちょうど重なるように設定すると、まるで物体がパーティクルを 240 | 241 押し退けているように、現実世界と仮想世界が統合する様子を確認できます。この方法は、イン タラクティブなメディアアート作品のベースとなるシステムとして様々な作品に応用可能です。 list 3–4_i #ifndef _TEST_APP testApp.h #define _TEST_APP #include "ofMain.h" #include "ofxOpenCv.h" #include "ofxBox2d.h" #include "ofxVectorMath.h" #include "CustomCircle.h" #include "ofxContourAnalysis.h" class testApp : public ofBaseApp { 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(int x, int y, int button); void windowResized(int w, int h); ofVideoGrabber vidGrabber; //ビデオ入力 ofxCvColorImage colorImg; //OpenCVで扱うカラーイメージ ofxCvGrayscaleImage grayImage; //OpenCVで扱うグレースケールイメージ ofxCvGrayscaleImage grayBg; //背景のグレースケールイメージ ofxCvGrayscaleImage grayDiff; //背景との差分のグレースケールイメージ ofxCvContourFinder contourFinder; //輪郭抽出のためのインスタンス ofxContourAnalysis contourAnalysis; //輪郭線を解析するクラスのインスタンス vector <ofPoint> simpleCountour; //単純化された輪郭線の座標の配列 bool bLearnBakground; //背景を学習したかどうか int threshold; //背景の差分の閾値 float simpleAmount; //輪郭線の単純さ bool bReversePoints; //輪郭線を繋ぐ方向 3–4 openFrameworksプログラミング中級編 ofxBox2d box2d; //box2dのインスタンス ofxBox2dLine lineStrip; //直線の連なり vector <CustomCircle> circles; //独自に作成した円形のパーティクル }; #endif testApp.cpp #include "testApp.h" void testApp::setup(){ ofBackground(0, 0, 0); ofEnableAlphaBlending(); ofSetFrameRate(30); //ビデオ取り込み初期化 vidGrabber.initGrabber(320,240); //解析用の画像の領域確保 colorImg.allocate(320,240); grayImage.allocate(320,240); grayBg.allocate(320,240); grayDiff.allocate(320,240); //背景画像を記憶しているかどうか bLearnBakground = true; //閾値 threshold = 20; //輪郭線の単純さ simpleAmount = 1.0; //輪郭線を繋ぐ順序 bReversePoints = false; //Box2D初期設定 box2d.init(); box2d.setGravity(0,20); box2d.createBounds(); box2d.setFPS(30.0); } void testApp::update(){ //Box2Dを更新 box2d.update(); //新規にフレームを取り込んだかを判定する変数 bool bNewFrame = false; 242 | 243 //1フレーム映像を取り込み vidGrabber.grabFrame(); //最後に取り込んだフレームから変化があったかを判定 bNewFrame = vidGrabber.isFrameNew(); //新規のフレームの場合とりこみ実行 if (bNewFrame){ //OpenCVで解析するカラー画像領域に、カメラから取得した映像を格納 colorImg.setFromPixels(vidGrabber.getPixels(), 320,240); //取り込んだカラー映像をグレースケールに変換 grayImage = colorImg; //新規に背景を学習する場合 if (bLearnBakground == true){ //現在の取り込んだグレースケールイメージを記憶 grayBg = grayImage; //背景の学習をしないモードに戻す bLearnBakground = false; } //背景画像と現在の画像の差分の絶対値を取得 grayDiff.absDiff(grayBg, grayImage); //差分画像を設定した閾値を境に二値化 grayDiff.threshold(threshold); //二値化した差分画像から、輪郭を抽出する contourFinder.findContours(grayDiff, 40, (340*240), 40, true); //もし物体が1つ以上検出されたら処理を実行 if(contourFinder.nBlobs > 0){ //輪郭線を単純化する contourAnalysis.simplify(contourFinder.blobs[0].pts, simpleCountour, simpleAmount); //線分の頂点座標 ofPoint p; //線の連なりをBox2Dの世界に配置 lineStrip.setWorld(box2d.getWorld()); //前回使用した線を破棄 lineStrip.clear(); //指定した方向に、輪郭線をなぞる if(bReversePoints) { //逆回り for(int i=simpleCountour.size()-1; i>0; i--) { //単純化した輪郭線の頂点の座標を取得 p.x = simpleCountour[i].x*ofGetWidth()/320; p.y = simpleCountour[i].y*ofGetHeight()/240; //線分の連なりに、頂点の座標を追加 lineStrip.addPoint(p.x, p.y); 3–4 openFrameworksプログラミング中級編 } } else { //通常回り for(int i=0; i<simpleCountour.size(); i++) { //単純化した輪郭線の頂点の座標を取得 p.x = simpleCountour[i].x*ofGetWidth()/320; p.y = simpleCountour[i].y*ofGetHeight()/240; //線分の連なりに、頂点の座標を追加 lineStrip.addPoint(p.x, p.y); } } //線分の連なった形態の生成 lineStrip.createShape(); } } } void testApp::draw(){ //カメラから入力された映像を描画 colorImg.draw(0, 0, ofGetWidth(), ofGetHeight()); //輪郭線を描く lineStrip.draw(); //circlesに格納された全ての図形を描画 for(int i=0; i<circles.size(); i++) { circles[i].draw(); } //ログを表示 ofSetColor(255, 255, 255); string info = "FPS: "+ofToString(ofGetFrameRate()); info += "¥nThreshold: "+ofToString(threshold); info += "¥nNumber of Blobs: "+ofToString(contourFinder.nBlobs); info += "¥nSimple Amount: "+ofToString(simpleAmount); info += "¥npress [r] to toggle points direction." + ofToString(bReversePoints); info += "¥nPress [space] to capture background."; info += "¥nPress [+/-] to change threshold"; info += "¥nPress [1/2] to change simple amount"; ofDrawBitmapString(info, 20, 20); } void testApp::keyPressed 244 | 245 (int key){ switch (key){ case ' ': //スペースキーで背景をとりこみ bLearnBakground = true; break; case '+': //[+]キーで背景の閾値のレベルを上げる(最大255) threshold ++; if (threshold > 255) threshold = 255; break; case '-': //[-]キーで背景の閾値のレベルを下げる(最小0) threshold --; if (threshold < 0) threshold = 0; break; case '1': //[1]キーで輪郭線をより複雑に simpleAmount -= 0.01; break; case '2': //[2]キーで輪郭線をより単純に simpleAmount += 0.01; break; case 'r': //[r]キーで輪郭線を繋ぐ向きを逆転 bReversePoints = !bReversePoints; break; } } void testApp::keyReleased(int key){ } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ } void testApp::mousePressed(int x, int y, int button){ //マウスをクリックすると新規にパーティクルを追加 float r = ofRandom(10, 20); //半径を設定 CustomCircle c; //CustomCircleクラスをインスタンス化 c.setPhysics(0.00001, 0.9, 0.001); //物理パラメータを設定 c.setup(box2d.getWorld(), mouseX, mouseY, r); //マウスの位置に円を設定 circles.push_back(c); //生成した円をcirclesベクターに追加 } void testApp::mouseReleased(int x, int y, int button){ 3–4 openFrameworksプログラミング中級編 } void testApp::windowResized(int w, int h){ } 実行結果 246 | 247 3 3–5– 1 –5 楽器を作る ここまでの内容を統合する この章のまとめとして、今までとりあげてきた様々な内容を統合して、1つのプロジェクトを作成し てみたいと思います。まず、ここまでのトピックスをまとめてみましょう。 オブジェクト指向 openFrameworksの構造の理解 ファイルの分割 クラスの作成 複数のクラスの使用 音を扱う 波形の生成 サンプリング&プレイバック アドオンの使用 物理エンジンBox2Dの使用 これらの内容をできるだけ網羅したものを作成していきたいものです。そこで、このセクションでは 「楽器」をテーマにしてみたいと思います。楽器といってもギターやピアノといった既存の楽器の真 似をするということではありません。既存の楽器の概念の枠を超えた、全く新しい楽器を創造し ていきたいと考えています。 3–5– 2 楽器の構想 現在使用されている多くの楽器は、演奏者が息を吹き込んだり、弦を弾いたり、キーボードを押 したりといった何らかの動作を楽器に対して行うと、それに呼応して音が鳴るという仕組みのもの がほとんどです。演奏者のアクションと楽器の反応が一対一で対応している状態と言えるでしょう。 しかし、せっかくプログラムを用いて楽器を作成するのであれば、ユーザーの操作に対する反応 が常に決まっているのではなく、その入力をきっかけとして、自動的に演奏が持続していくような、 コンピュータと人間の相互作用により自律的に発展していくような、 「半自動」的な楽器にできる とより面白いものになりそうです。これは、既存のインタラクションの概念を超える可能性をも秘 めているのではないでしょうか。 3–5 openFrameworksプログラミング中級編 今回作成する楽器は、こうしたユーザーの意図を超えた動きを発生させるために、物理エンジン を活用してみたいと思います。物理法則をうまく適用することで、おおまかなコントロールはしなが ら、細かな動きは偶然に任せることが可能です。時には意図を超えたふるまいをすることもあるか もしれませんが、それもこの楽器の特性と考えていきましょう。この物理エンジンの部分には、 3–4でとりあげたofxBox2dのアドオンを利用します。音の生成には、ofSoundPlayerを用いたサ ウンドファイルのプレイバック機能を使用します。ofSoundStreamの機能を使用して、音の波形 からデザインしていくことも可能なのですが、複雑な音響を生成するには、波形の変調やフィルタ リングなど、さらに高度な信号処理の技術について学んでいく必要があります。今回に関しては、 より簡易な方法として、あらかじめ作成した音を再生するという方向で作成していこうと思います。 プログラムは、この章でとりあげた新規のクラス生成を利用して、オブジェクト指向プログラミン グで構造化していきます。音を発生する1つの単位を1つのオブジェクト(=パーティクル)として扱 うようにしたいと思います。これらのパーティクルを大量に生成することで、音が複雑に折り重なり、 豊かな音響を生成するような仕組みにできればと思います。この一つ一つのパーティクルは動く 物体として画面上に表示され、その動きが音のパラメータと同期することで、映像と音響の融合 を目指します。 3–5– 3 楽器の設計 今回、音響を変化させる基本機能として、「ばね」の動きをベースにしていきたいと思います。伸 び縮みするばねの動きは、ofxBox2dのofxBox2dJointクラスを使用することで容易に実現可能で す。ofxBox2dJointを利用するには、Box2Dの世界に追加された物体を2つ用意して、その物体 間を接続するようにします。ばねの弾力や摩擦といった物理的なパラメータは細かく設定ができま す。ばねは画面上に追加した物体同士を全て結ぶようにします。例えば5つの物体を接続する場 合は、30本のばねが必要ということになります。 このばねで相互に接続された物体一つ一つから音を生成するようにしてみます。一つ一つのパー ティクルにスピーカーが付いていて、そこからそれぞれ別々の音が再生されているというイメージで す。また、物体の位置によって、再生するサウンドの音量と定位(左右の音量のバランス)を変 化させてみます。それぞれのパーティクルは、サウンドを再生している際には半径が伸び縮みして 音を再生していることを表現します。 最終的にミックスされた音は画面の中心から再生されるとイメージしてください。画面の中心には、 固定された特別なパーティクルが1つ固定された状態で配置されていて、自分以外の全てのパー ティクルから再生された音を集めてきてミックスします。この中心のパーティクルは、全てをミック スした音の音量によって半径が変化します。 248 | 249 パーティクル:音を発生 中心のパーティクル 全ての音をミックスする 全てのパーティクルは 「ばね」で相互に接続 3–5– 4 プロジェクトの設計 では、実際にopenFrameworksのプロジェクトとしてプログラムを開始できるよう、より詳細にそ れぞれの機能を設計していこうと思います。まず、全体の構造について考えてみましょう。 今回、testAppの他に、音を再生しながら動きまわるパーティクルと、そのパーティクル同士を接 続 する「 ば ね 」の2 種 類 のクラスを 作 成していこうと思 います。まず、 パー ティクルは ofxBox2dCircleを継承したクラスとしてCustomCircleクラスを作成します。クラスのベースとして ofxBox2dCircleを利用することで、その細かな動きをBox2Dの物理演算に任せることが可能とな るからです。CustomCircleクラスには、その元となっているofxBox2dCircleの機能に加えて、サ ウンドファイルを読み込んで再生する機能を付加します。また、サウンドの再生状態によって半径 を変化させるように、draw ( ) メソッドを上書きします。 このパーティクル同士を接続する「ばね」に関しては、ofxBox2dJointというクラスを拡張します。 このクラスはBox2Dの世界の物体同士を繋ぐことのできるロープのようなものです。パラメータを 変化させることで、両者を「ばね」のように伸び縮みする線で接続することも可能です。今回は、 接続する物体の距離に応じて、線の濃度を変化させようと思います。ですので、ofBox2dJointを そのまま使用するのではなく、クラスを継承したCustomJointというクラスを定義して、draw ( ) メ ソッドのみを上書きするようにします。 プログラム全体のおおまかな構造が決まったところで、それぞれのクラスで必要となる処理の内容 を、プロパティとメソッドに分けて整理してみましょう。 testApp プロパティ box2D:ofBox2d 物理エンジンBox2Dのインスタンス circles:vector 音を発生するパーティクルCustomCirclesを格納するベクター joints:vector 全てのパーティクル同士を接続する「ばね」CustomJointsを格納するベクター 3–5 openFrameworksプログラミング中級編 メソッド setup():void openFrameworksの画面初期設定 ofxBox2dの初期設定(初期化、重力の設定、フレームレートの設定) 最初の1つめの画面中心に配置するパーティクルを生成、画面に追加 生成したパーティクルを、ベクターに追加 update():void 全てのパーティクルの座標を更新 Box2D 全体の更新 draw():void 全ての「ばね」を描画 全てのパーティクルを描画 keyPressed(key:int):void もし入力されたキーが「f」だったらフルスクリーンにする mousePressed(x:int, y:int, button:int):void 新規にパーティクルを追加 自分以外の全てのパーティクルと「ばね」で接続 作成したパーティクルの位置を、マウスの座標へ移動 windowReized(w:int, h:int):void もし画面のサイズが変化したら、1つ目のパーティクルを画面中央に移動 CustomCircle プロパティ num:int パーティクル番号 mySound:ofSoundPlayer サウンドファイル再生のためのクラスofSoundPlayerのインスタンス radius:float パーティクルの半径 soundSpeed:float 再生する音声のスピード(スピードが速くなると、ピッチも高くなる) メソッド CustomCircle(num:int):void コンストラクタ 引数には自分自身のパーティクル番号を受け取る update():void 最初のパーティクルだったら (num == 1) 画面の中央で固定 半径は、全体の音量によって変化させる それ以外のパーティクルだったら (num > 1) 位置はBox2Dで計算 定期的に伸び縮みする、スピードは音程から決定 画面中心からの距離を測り、音量を決定 250 | 251 左右の位置から、定位 ( 左右の音量のバランス)を決定 draw():void 最初のパーティクルだったら (num == 1) 赤い色で、円を描画 それ以外のパーティクルだったら (num > 1) 赤い色で、円を描画 CustomJoint プロパティ なし メソッド draw():void 接続する2点間の距離を測る 距離に応じて透明度を変化させる(近いほど濃く) 「ばね」の線を描く 3–5– 5 実装 では最後に、設計した通りにコーディングしていきましょう。先程定義した機能の通りに、順番 に実装していきます。 list 3–5_a #ifndef _TEST_APP testApp.h #define _TEST_APP #include "ofMain.h" #include "ofxBox2d.h" #include "ofxVectorMath.h" #include "CustomCircle.h" #include "CustomJoint.h" class testApp : public ofBaseApp { 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); 3–5 openFrameworksプログラミング中級編 void mousePressed(int x, int y, int button); void mouseReleased(int x, int y, int button); void windowResized(int w, int h); //物理エンジンofxBox2dのインスタンス化 ofxBox2d box2d; //ofxBox2dCircleを拡張して円形のパーティクルを生成、ベクター circlesに格納 vector < CustomCircle * > circles; //物体同士を結ぶ「ばね」のクラスofxBox2dJointを拡張、ベクター jointsに格納 vector < CustomJoint * > joints; }; #endif testApp.hでは、まずofxBox2dをインスタンス化し、box2dを生成しています。また、全てのパー ティクルを格納するためのベクター circlesと、全ての「ばね」を格納するベクター jointsを用意し ています。 testApp.cpp #include "testApp.h" void testApp::setup(){ //画面の書き換えタイミングと同期 ofSetVerticalSync(true); //背景色 ofBackground(0, 0, 0); //円の解像度 ofSetCircleResolution(64); //フレームレートの設定 ofSetFrameRate(30); //毎回異なる乱数を発生させる ofSeedRandom(); //ofxBox2dの初期設定 //物理エンジンを初期化 box2d.init(); //重力は使用しない box2d.setGravity(0,0); //Box2D側のフレームレートを設定 box2d.setFPS(30.0); 252 | 253 //画面の中心に1つめのパーティクルを生成 //CustomCircleクラスをインスタンス化 CustomCircle *c =new CustomCircle(circles.size()); //物理パラメータを設定 c->setPhysics(1.0, 0, 0); //ワールドに追加 c->setup(box2d.getWorld(), ofGetWidth()/2, ofGetHeight()/2, 10, true); //衝突判定はしない c->disableCollistion(); //ベクター circlesに追加 circles.push_back(c); } void testApp::update(){ //全てのパーティクルの座標を更新 for(int i=0; i<circles.size(); i++){ circles[i]->update(); } //Box2Dの座標を更新 box2d.update(); } void testApp::draw(){ //全ての「ばね」を描画 for(int i=0; i<joints.size(); i++){ joints[i]->draw(); } //全てのパーティクルを描画 for(int i=0; i<circles.size(); i++){ circles[i]->draw(); } } void testApp::keyReleased(int key){ } void testApp::keyPressed(int key){ //もし「f」キーを入力したら、フルクスリーン化 switch (key) { case 'f': 3–5 openFrameworksプログラミング中級編 ofToggleFullscreen(); break; } } void testApp::mouseMoved(int x, int y){ } void testApp::mouseDragged(int x, int y, int button){ } //マウスをクリックすると新規にパーティクルを追加 void testApp::mousePressed(int x, int y, int button){ //CustomCircleクラスをインスタンス化、引数は現在のパーティクルの数 CustomCircle *c = new CustomCircle(circles.size()); //物理パラメータを設定 c->setPhysics(1.0, 0, 0);(1.0, 0, 0); //ワールドにパーティクル追加 c->setup(box2d.getWorld(), ofGetWidth()/2+200, ofGetHeight()/2, 10); //衝突判定はしない c->disableCollistion(); //ベクター circlesに追加 circles.push_back(c); //自分以外の全てのパーティクルと「ばね」で接続 for (int i = 0; i<circles.size(); i++) { //CustomJointクラスをインスタンス化 CustomJoint *joint = new CustomJoint(); //生成した 「ばね」 をワールドに追加 joint->setWorld(box2d.getWorld()); //2つの物体を接続 joint->addJoint(circles[i]->body, circles[circles.size()-1]->body, 0.2, -0.02);size()-1]->body, 0.2, -0.02); //ベクター jointsに追加 joints.push_back(joint); } //パーティクルの位置をマウスの座標に移動 circles[circles.size()-1]->setPosition(mouseX, mouseY); } void testApp::mouseReleased(int x, int y, int button){ } 254 | 255 void testApp::windowResized(int w, int h){ //ウィンドウをリサイズした際に、最初のパーティクルを画面の中心に移動する if (circles.size() > 0) { circles[0]->setPosition(ofGetWidth()/2, ofGetHeight()/2); } } testApp.cppで、Box2Dの基本設定や、パーティクルとばねの追加など全体の統合を行っています。 setup ( ) 関 数 内では、まず 背 景 色 やフレームレート、 円を描く際 の 解 像 度 など、open Frameworks全体の画面設定をしています。ofSetVerticalSync(true)という指定は、画面を更新 する際にモニターやプロジェクターの書き換え速度に合わせて画面の更新タイミングを調整する 機能です。この機能によって、素早い動きをした際に画面が途中までしか更新されずちらついてし まう現象を防いでいます。その次に、ofxBox2dの基本設定、物理エンジンの初期化、重力の設 定、Box2D側のフレームレートの設定などをしています。 setup ( ) 関数では、さらに1つ目のパーティクルを画面に追加しています。このパーティクルは常に 画面の中央に表示され、全体の音量に応じて大きさが変化します。まずCustomCircleクラスをイ ンスタンス化し、物理パラメータの設定、Box2Dのワールドへの追加などをして、最終的に全て のパーティクルを管理しているベクター circlesに追加します。また、今回生成するパーティクル全 てに共通する設定として、パーティクル同士の衝突の判定はしないようにしています。これは、 disableCollision ( ) というメソッドにより設定されています。 次にmousePressed ( ) 関数の中身について見ていきましょう。ここでは、マウスがクリックされる たびに新規のパーティクルを追加しています。マウスがクリックされると、新規にCustomCircleの インスタンスを生成し、物理パラメータの設定、Box2Dのワールドへの追加、衝突判定を無効 化したうえで、ベクター circlesに追加します。パーティクルを追加した後で、自分自身以外の全 てのパーティクルに対して「ばね」となるCustomJointを追加して、2つのパーティクル同士を接続 しています。接続が完了したら、追加したCustomJointのインスタンスをベクター jointsに追加し ています。 update ( ) 関数では、全てのパーティクルの座標を更新しています。またBox2Dの情報も更新しま す。update ( ) 関数で更新された情報を元に、draw ( ) 関数は、全てのパーティクルと「ばね」を 描いています。 CustomCircle.h #include "ofxVectorMath.h" #include "ofxBox2d.h" //ofxBox2dCircleを継承したクラスCustomCircleを定義 class CustomCircle : public ofxBox2dCircle { 3–5 openFrameworksプログラミング中級編 public: CustomCircle(int num); void update(); //設定の更新 void draw(); //パラメータを描画する int num; //パーティクルの番号 ofSoundPlayer mySound; //音の再生クラスofSoundPlayerをインスタンス化 float radius; //半径 float soundSpeed; //音の再生スピード(ピッチ) }; CustomCircleクラスは、ofxBox2dCircleを継承したクラスとして作成しています。基本的な性質 はofxBox2dCircleのものを受け継ぎつつ、そこにサウンドを再生する機能を付加しています。ま た描画される円のデザインにも変更を加え、さらに半径を変化できるように更新しています。クラ スがインスタンス化される際に自分自身のパーティクルの番号、つまり今何番目かのパーティクル かという情報を受け取ります。これによって、最初のパーティクルの際だけ違う動きと色を指定す るようにしています。 CustomCircle.hでは、コンストラクタCustomCircle ( ) に加えて、update ( ) とdraw ( ) メソッドを それぞれ上書きするため、メソッドとして定義しています。またプロパティとして、サウンドファイル を再生するためのofSoundPlayerのインスンタンス、円の半径と音の再生スピードのためのプロパ ティを用意しています。 CustomCircle.cpp #include "CustomCircle.h" //コンストラクタ、引数にパーティクル番号を受け取る CustomCircle::CustomCircle(int _num){ num = _num; //引数の_numをクラスのプロパティに代入 radius = 0; //半径初期化 //最初のパーティクルでなければ、サウンドを再生させる if (num > 0) { //音程の配列を生成 float notes[] = {0.5, 0.75, 1, 1.5, 2.0, 3.0}; //音程を決める soundSpeed = notes[((int)(ofRandom(0, 5)))]; //サウンドファイルの読みこみ mySound.loadSound("ambientSound.wav"); //ループをONに mySound.setLoop(true); //設定したスピード(音程)で再生 256 | 257 mySound.setSpeed(soundSpeed); //画面の中心位置からの距離を計測 float dist = ofDist(getPosition().x, getPosition().y, ofGetWidth()/2, ofGetHeight()/2); //再生音量を計算する float volume = (1.0 - dist/300.0)*0.2; //音量が0以下の場合は、0で固定 if (volume < 0) { volume = 0; } //音量を適用する mySound.setVolume(volume); //サウンド再生開始 mySound.play(); } } void CustomCircle::update(){ //最初のパーティクルの時だけ、半径を全体の音量で変化させる if (num == 0) { float * vol = ofSoundGetSpectrum(1); radius = vol[0] * 2000; } else { //それ以外のパーティクルは音程によって伸縮せる //半径を算出 radius = sin(mySound.getPosition()*TWO_PI*20)*20+5; //画面の中心位置からの距離を計測 float dist = ofDist(getPosition().x, getPosition().y, ofGetWidth()/2, ofGetHeight()/2); //音量を計算する float volume = (1.0 - dist/300.0)*0.2; //定位(左右のバランス)を計算する float pan = getPosition().x / (float)ofGetWidth(); /音量が0以下の場合は、0で固定 if (volume < 0) { volume = 0; } //音量を適用する mySound.setVolume(volume); //定位を適用する mySound.setPan(pan); } } 3–5 openFrameworksプログラミング中級編 void CustomCircle::draw(){ //伸縮する円の外周 ofFill(); if (num == 0) { //最初のパーティクルは赤 ofSetColor(255, 0, 0, 100); } else { //それ以外は青 ofSetColor(0, 63, 255, 100); } //円を描画 ofCircle(getPosition().x, getPosition().y, radius); //円の「核」 if (num == 0) { //最初のパーティクルは赤 ofSetColor(255, 0, 0); } else { //それ以外は青 ofSetColor(0, 63, 255); } //円を描画 ofCircle(getPosition().x, getPosition().y, 3); } CustomCircleのsetup ( ) 関数では、サウンド再生のための設定しています。1番目のパーティクル は中心でサウンドをミックスする役割なので再生しませんが、それ以外であればサウンドファイル の音程を決めて、サウンドファイルを読み込み、中心からの距離から音量を決定し、再生をして います。 update ( ) 関数では円の半径を変化させています。半径の変化は、1つめのパーティクルとそれ以 外で別の動きをしています。最初のパーティクルは全体の音量を計測し、その値によって半径を 変化させています。それ以外のパーティクルは、音程に応じてスピードを決めて、sin関数を用い て円を伸縮運動させています。また、update ( ) 関数が実行されるたびに中心からの距離を計測 して、その値によって中心に近いほど大きな音量になるようにしています。また左右に位置によっ て音量の左右のバランスを調整するようにしています。 CustomJoint.h #include "ofxVectorMath.h" #include "ofxBox2d.h" class CustomJoint : public ofxBox2dJoint { public: //「ばね」を描画 void draw(); }; 258 | 259 #include "CustomJoint.h" CustomJoint.cpp void CustomJoint::draw(){ //バネがアクティブではなければ、なにもしない if(!alive) return; //接続する2つの物体の位置を取得 b2Vec2 p1 = joint->GetAnchor1(); b2Vec2 p2 = joint->GetAnchor2(); p1 *= OFX_BOX2D_SCALE; p2 *= OFX_BOX2D_SCALE; //接続する距離に応じて透明度を調整して、線を描く //2つの物体の距離を算出 float dist = ofDist(p1.x, p1.y, p2.x, p2.y); //スムーズに描画 ofEnableSmoothing(); /ぬり潰しなし ofNoFill(); //線の太さ設定 ofSetLineWidth(2); //透明度を距離に応じて変化させる。距離が近いほど濃くなる ofSetColor(255, 255, 255, 100-(dist/3.0)); //線を描画 ofLine(p1.x, p1.y, p2.x, p2.y); //スムース描画をoffに ofDisableSmoothing(); } CustomJointクラスは、通常のofxBox2dJointを継承してdraw ( ) メソッドだけを変更しています。 変更点は距離に応じて線の透明度を変化させている部分です。接続する2点間の距離を計測し て、その距離によって透明度を変化させています。距離が近いほど濃い色に設定されています。 3–5– 6 プロジェクト完成 これで「楽器」が完成しました。画面をクリックすると、 音を発生するパーティクルが追加され、 「ば ね」の動きによってミックスされていきます。動きまわるパーティクルの左右の位置が音の左右の 定位と同期し、中心のパーティクルとの距離が音のボリュームと同期しています。音程は協和的 な和音になるような音程の中からランダムに選ばれ、徐々に音のレイヤーが重なっていきます。 紙面の都合もあり、まだまだこの「楽器」の構造はシンプルで、その音や動きも原始的なものです。 実際に演奏するには不完全なものかもしれません。ですが、この仕組みをベースにして、例えば 複数のサウンドファイルをパーティクルの色や形で分類してミックスできるようにしたり、音のパー 3–5 openFrameworksプログラミング中級編 ティクル自体に音量やエフェクトなどの操作をするための機能を設けたりといったように、いろい ろなアイデアが生まれてきます。このサンプルでは、それぞれの役割ごとにクラスを作成している ので、既に作成したクラスを拡張していくことで、ここまで作成したプログラムを無駄にすることな く新たな機能を付加していくことが可能です。 ボタンをクリックすると常に決まった反応をする、というような単純なインタラクションではなく、 乱数や物理法則などの不確定な要素をうまく取り込むことで、時として予想を超える結果が生み 出される瞬間があります。作成したプログラムが作者自身の想像の範囲を超えるという体験は、 プログラムを用いて創作する醍醐味かもしれません。そこで得られた発見がさらに次の創作への ヒントとなり、結果としてプログラムとの間に創造のためのフィードバックを感じることがあります。 プログラムをすることは、発想を表現する手段であると同時に、アイデアを生み出すためのエンジ ンともなるのです。 次章では、プログラムのサンプルではなく、実際に「作品」として鑑賞できるような、より実践的 なプログラミングを紹介しています。どのように発想から表現が生まれてくるのか、また実際に作 成したプログラムからのフィードバックを受けてどのように改良していくのかといった、より現場レ ベルでの技術に触れていきましょう。 260 | 261 3–5 openFrameworksプログラミング中級編 Interview 03 Sota Ichikawa 今やコンピューティングって 当たり前のこと。 それを操作できるスキルというのは、 ごく普通にあった方がいい 市川 創太(いちかわ そうた) 建築家。一級建築士。 doubleNegatives Architecture開設者。 に山口情報芸術センターで《Corpora in Si(gh)te》 (市川創太、Max Rhiner、Ákos Maróy、小旗か 建築における空間表記方法を拡張・考察し、設 おる、比嘉了、鳴川肇)として大規模インスタレー 計の手法・プロセス自体を開発実践しつつ、アー ションへ再構成、長期展示された後、第11回ベネ ティストとの高次でのコラボーレションを遂げてい チアビエンナーレ国際建築展でハンガリー国代表 る。1997年ドイツのメディア・アーティストグルー に選ばれる。この展示が話題を呼び、ドイツ ベル プKnowbotic Researchの都市プロジェクト《10_ リン、オーストリア リンツにて招待展示され、2010 DENCIES》 (キヤノン・アートラボ)にコラボレータ 年にはメキシコでの展示が予定されている。2008 ーとして参加。2003年アーティスト三上晴子との 年よりアーティスト中谷芙二子とdNAのコラボレー コラボレ ー ション 作 品《gravicells》 (http://g--r. ション《MU: Mercurial Unfolding》がスタートして com)を発表。この作品は国内、ヨーロッパ、北 いる。2001年から2004年まで東京造形大学メデ アメリカ14都市で招待展示されている。2007年か ィアテクノロジーコースにて、2001年から2008年 ら 久 保 田 晃 弘とThe Cellular Automaton Band まで多摩美術大学情報デザイン学科にて非常勤 (http://cellautoband.net/)を展開。 講師として教 をとる。 1998年に建築設計、インスタレーションなど横断 的でユニークな活 動を展 開する建 築グループ URL http://doublenegatives.jp/ doubleNegatives Architecture(dNA, ダブルネガ ティヴス アーキテクチャー)を開設。グループの手 がける一連の《Corpora》プロジェクトは、2007年 262 | 263 2009.12.08@doubleNegatives Architecture 聞き手:久保田 晃弘 ― 最初にまず、市川さんはもともと建築家でいらっし いましたが、バイトの身分だしあまり突撃していっ ゃるわけですが、そういった市川さんがなぜプログ て学ぶといった状況でもなくて。 ラミングというものに入っていったのかについてお 聞かせいただけますか。 市川創太(I) 僕は、ちょうど建築事務所に広く ― 最初は何を使ったのですか? I エクセルです。表計算ソフトの(笑)。でもあれで CADシステムが入ってくるぐらいの時期に学生を終 変換の計算はできた。3次元の点の情報を入力す えたんです。ですから手で図面も引くしコンピュー ると変換結果が表になって出力されるというような タのCADシステムも使う、そういう過渡期の頃を通 ものを作って、それを手で方眼紙にプロットしてい 過しました。当時建築学生の間ではform Zという きました。 モデリングソフトがよく使われていましたね。CAD はMiniCAD(後のVectorWorks) 。設計事務所で ― エクセルってところが泣かせますね。 はAutoCAD。でも、個人的にはそういったCADソ フトのテイストは好きではなくて、3Dモデリングや I 今思うと、エクセルでもVisual Basicとかを使って レンダリングにのめりこんでいたわけではなかった。 自動的に描画するようにプログラムすることもでき ある程度スキルはあったので、設計事務所での仕 たんですけど、当時はわからなくて。ものすごくた 事ではそれを発揮していましたけど。修士課程の いへんだったのですが、そのときはとにかく自分の 研究室では、空間の表記方法自体を作り直すとい アイデアがどんなふうに見えるか知りたかったんで うことに無謀にも取り組んでいたんです。 すね。それが、まあプログラミングと言えるか微妙 ですが、自分がプログラミングのようなものに触れ ―「空間の表記方法」ですか。 I はい。 「空間の表記方法」は広く図面と呼ばれてい た最初のきっかけです。 ― なるほど。 るものを指します。これは建築家やIDデザイナーが 駆使する空間の言語のようなもので、それは必然 I その後はいくつかの建築事務所をアルバイトで渡り 歩きつつ、CADソフトのファイルフォーマットの 的に発想に大きく関わる。もし別の言語で思考す ることができたなら、異なる空間概念によって、新 DXFファイルを読んで変換して、別のDXFファイル たな次元に行けるのではないかという期待がありま に書き出す、というようなものを作ったりしてました。 した。具体的にはいわゆるxyzではない書き方で それをやるのにはどんな言語でもよかったのですが、 空間の表記方法を変える、ということを目指してい なるべくそのスキルを上げていけばコアなものにた ました。ある基点から全方向を捉える極座標をベ どりつける可能性のある言語を使いたくて、それで ースにした空間の捉え方です。それをやろうとする C++を選びました。Borland Turbo C++。本を読ん ときに、xyzという3次元情報を角度と距離に置き での独学でしたね。 換えるためのコンバート作業が必要だったんです。 ― 他にはどんなことをやってらしたのですが? ― そこでプログラミングの必要性が出てきたわけです ね。 I そのころdoubleNegatives Architecutureという建 築グループ(以下dNA)をなんとなく始めていて、 I ええ。ただ、当時の僕にはプログラミングのスキル そこがずっと活動の中心です。僕が通っていた大学 はまったくなかった。美大の建築科出身ですからそ の教授で六角鬼丈さんという建築家がいらっしゃる ういった授業も受けてこなかったし、まわりにでき のですが、彼が手掛けた宮城県にある「感覚ミュ る人もいませんでした。かろうじて、当時出入して ージアム」に常設のインスタレーションを作らせて いた磯崎新アトリエの菊池誠さんや佐藤健司さんと もらいました。このプロジェクトには建築家の結城 いった方たちがプログラムを書いたりしていらっしゃ Interview 03 : Sota Ichikawa 光正、音楽家のYAMP KOLT(a.k.a.藤乃家舞)が かは1つのフレームしか使わず、そのフレームの中 参加しています。空間表記法プロジェクトの成果を、 にLingoというスクリプトを書き込むというやり方を 3D音響を使った空間インスタレーションに適用し してたんです。フレームを出るタイミングでフレーム たものです。他にはインターネットを使ったプロジ に戻るという。特殊な使い方ではあるんですが、そ ェクトで、いろいろなWebサイトを勝手にミックス れができれば描画ルーティンを中心にしたプログラ してしまうようなものをJavaで作ってみたり、dextro ミングの世界にどんどん入っていける。なので、そ (http://dextro.org)というオーストリアのグラフィッ ういったやり方を理解できる人はそのやり方をして クデザイナーが参加してMacromedia(当時)の もらって、どうしてもできない人は、普通にフレー Directorを使って、複数のエージェントが自律して ムを使ったやり方をしてもらう、という分かれ道を Webサイトのリンクをたどり、その軌跡を残してい 用意していました。 く作品、接続しているクライアントからコンテンツ の置いてあるサーバまでをtracerouteして、そのIP ― あの頃はDirectorの存 在は 大きかったですね。 アドレスから3次元表現する作品、などを作ったり ProcessingもなければFlashもなかったし、Max/ していました。 MSPは音だけだったし、Directorがなければ美大 でアルゴリズミックなことやインタラクティブなこと ― その頃、多摩美に非常勤として来ていただくように はできなかった。 なったわけですよね。もともとはどういった経緯で したっけ? I そうですね。xtraという拡張機能を使って、カメラ トラッキングやシリアル通信もできましたので、実 I 三上(晴子)先生に声を掛けてもらって、単発の「デ 空間とのつながりとして入出力に拡張性があったの ジタル・フラー」というバックミンスター・フラーを は大きいです。当時はけっこうDirectorを使って作 題材にしたワークショップをやらせてもらいました。 品を作っているアーティストは多かったと思います。 このワークショップのテーマなどは三上先生が既に dumb typeの『Voyage』のインスタレーションの 設定してあったものでしたが、 「情報デザイン」とい 初期のバージョンもDirectorで作られていたと聞い うすごい名前のついた所でやるということで、最初 ています。 はかなりプレッシャーがありましたね。自分自身、 ちゃんとプログラミングを授業で学んできたわけで それで、最初はDirectorを使っていたわけですが、 はなく独学でやってきたし、とにかく動けばいいみ そのうち学生の方からProcessingへのプッシュがあ たいなソフトばかりを作ってましたから。 って。 「もう企業が作ったソフトを使っている時代で はない!」と(笑)。それは後にprocessing.jpを立 ― 美術大学の学生にプログラミングを教えるというこ ち上げ、 『Built with Processing』の著者の一人で とで、いろいろ難しさもあったと思います。当時の もある前 川( 峻 志 )くんなんですけどね。でも 授業はどのような内容だったのですか? Processingに一気に切り替えるのは不安だったん です。フレーム方式への逃げ道もないですし。また I 「デジタル・フラー」の後、僕が受け持ったのは2・ 一方で、Flashコンテンツで素晴らしいものがたく 3年生を対象にした授業で、空間的でコンピューテ さん出てきた頃でしたから、就職のことを考えたら ィングを使ったインスタレーションを目指すようなワ Flashを覚えた方がいいんだろうなという思いもあっ ークショップだったのですが、簡単にCADなどを使 た。さらに、できる人はすごくできて、かなり高度 いつつ、プログラミングに関してはDirectorをベー なことをやろうしている子もいたりして。そのうちの スにしていました。レベル設定が難しかったですね。 一人が平川(紀道)くんなんですが。そこで、思い Director ってFlash同様タイムシーケンスがあって 切って講座を2つに分けたんです。卒業制作や高 ムービーを作るというのが基本的な使い方じゃない 度な作品制作のためのコースを用意してもらって、 ですか。でも当時、さきほど話に出たdextroなん そちらでは自分がこれまでやってきたことから得た 264 | 265 スキルを、細々とでも伝えられるような内容にした いなと考えていました。4、5人ぐらいしかいない、 まあほとんど平川くん用のコースだったわけですが。 ― 結局最後まで残って、いちばん伸びたのが平川く んでしたね。市川さんが教えていらっしゃったから こそ、 『GLOBAL BEARING』は生まれたんだと思い ます。 I でも平川くんは本当に優秀だったから。4月にワー クショップを始 めて、その 秋 の 発 表 には 概 ね 当時のカリキュラム 『GLOBAL BEARING』ができてしまっていた! ― 当時の多摩美では、三上さんと市川さんがメディア という見解になってきた。これは比嘉君の提案だっ アートのワークショップをやって、僕と田所さんが たと思うのだけど、インスタレーション作品のため 並行してサウンドアートのワークショップをやって、 であれば、ウィンドウ周りのウィジェットは極力省い それが4年生になってゼミで一体化するというかた たもので十分なので、ゲーム開発などに使われて ちで授業を進めるというスタイルでしたね。空間・ いるSDL(Simple DirectMedia Layer)というライ ビジュアル系とサウンド系がそこで出会って、一緒 ブラリを使ってみようと。この頃からWindowsとの に卒制をやるというのは、僕らにとってもけっこうエ クロスプラットフォームも視野に入れるようになりま キサイティングな場だったと思います。 した。 文 字の表 示にはftgl+free-type、その他 portaudio、OSC、OpenCV、ARToolKitなどは自 それで、openFrameworksへと至った経緯という のはどのような流れだったのですか? 分たちでライブラリをパッケージにして、ソースから 相対パスでリンクするようなセットをワークショップ 用に作っていました。まさにoFのスタイルです。 I そもそもは、ハードコードしてパフォーマンスの良 dNAの 仕 事 の 経 験 やスイス人メンバー のMax い作品表現のためのソフトを作れるようになること Rheinerから 教 わったスキル から、SDLよりも が目標でした。美大ということもあり機材もMacが GLFWを使うようになったり、Luaスクリプトエンジ 主流だったため、Cocoaを使ってMacネイティヴな ンなど、少しずつ機能を改良・追加したりいろいろ ところから始めました。まだPowerPCの頃、OSは 苦労して、Xcodeでやりやすいものを作ることを試 Puma、Jaguarあたり、Xcode 1.0, 1.5だったと思 みていたんです。 います。Objective-Cは実はあまり経験がなかった のですが、言語思想や仕様ははっきりしていて、 そんなところで、アンテナ感度の高い比嘉くんが見 C++をメインに使っている自分からはそれほど不便 つけてきて、oFと出会った。便利なものあるじゃん! は感じませんでした。「変わっているー」と思ったと と。正直感心しましたね。すぐに使えるし、作品 ころは多々ありますけど(笑)。2Dの描画というよ 表現をしている人が、アーティストのために作って りは、最初から3Dができるようにしたかったので、 いる雰囲気が多分にありました。リンクの仕方やコ OpenGLを使える環境を作ることが前提でした。こ ンパイルの早さ、全体のファイルサイズの小ささな の体制のとき平川くんがワークショップにいました。 ど、一般のプログラマーがプライオリティを高く設 その後、僕のTA(Teaching Assistant)を務めてく 定しそうな部分をほとんど犠牲にして、使いやすさ れていた、本書の著者でもある比嘉(了)くんとい やパッケージのモビリティ、「すぐ動く」ということ ろいろ相談して、ネット上のリソースも多いことだ に重点が置かれていることはoFの大きな特徴だし、 し、C++の方がポテンシャルが高いのではないか? 美大生にフレンドリーだと思います。2007年の後 Interview 03 : Sota Ichikawa 半から早速採用しました。おかげで、毎度ワーク ショップの前半で時間のとられていた開発環境の 整備という部分が非常に楽になりましたね。 ― 結局最初の、そしてひょっとしたらいちばんの敷居 は、プログラムを書いてコンパイルできて動くように する、というプログラミング環境の構築ですからね。 美大では、まずそこまでが難しい(笑)。そういう 意 味 で Processingは 素 晴 らしい わ け で す が、 openFrameworksはそれをもっとネイティブなかた ちで実現したというところに価値があると思います ね。 I そうですね。Processingは敷居が低く入りやすい ですが、スキルが上がってやればやるほどパフォー マンスに不満が出てきてしまう。あとは、コンパイ ルという作業が入るので、ハードコードの入門にも いいかなと思います。ProcessingだとRun(再生ボ タン)で動くというイメージがちょっと強すぎるから。 ― 学生からは、市川さんの授業で使う例題がすごく 実践的だということで評判でした。いきなり3Dか ら入りますからね。空間から入る。 I 授業自体はけっこう乱暴で、勝手に作れ!という感 じでしたね。その後1年生のProcessingの授業を 受け持つことになって、ちょっと考えさせられまし た。入門の手ほどきをするのはもっと適任がいるん じゃないかなと(笑)。本書の著者の田所さんの丁 寧で完璧な授業ノートを見て、さらに「自分は向い ていないなぁ」と思いましたね。とはいえ基本的に プログラミングは独学するものだと思っています。 だから、そこに入っていくうえでの壁を乗り越えら れる精神的な強さとか、躓いたときに聞ける人がい るかとかが重要ですね。プログラミングって、見え ないものを想像して、考えながら書かないといけな いという部分があるわけですが、そういうことは美 大の学生だと特に辛いと思うんですね。でも、やっ ぱ り 勉 強 し な いとい け な い んで すよ。open FrameworksはC++のためのフレームワークなので、 C++は勉強しなければ駄目なんです。 ― 例えばMax/MSPのようなグラフィカルな環境と比 266 | 267 dNAが開発した建築デザインツール。このソフトウェアはQt等で開発されている Interview 03 : Sota Ichikawa 較して、openFrameworksはどうですか? にビジュアルから入っていくと尚更です。変数という のは初期の段階で覚えなければいけない概念です I そうですね、僕自身はほとんどMax/MSPを使えな が、変数というのがアドレスなのか実際の値なのか いので、そのうえでの回答になってしまいますが… という違いがわかるかわからないかで、だいぶ違う 理想としては両方できた方がいいと思います。 んですよね。そういうことは、本当は最初に勉強す Max/MSPは便利な環境だと思いますし、ぱっと思 ると思うんですけど、美大系は逆でいいんじゃない いついたアイデアをさっと実験するといったことに かなと。自分もそういう順序で学んでいったので。 非常に適していると思います。オブジェクトをつな げて、作りたい事象をフローにしたりダイアグラム ― 確かにそうですね。やりたいことがまずあって、そ れを突き進めていきながら覚えればいいんですよね。 にしたりする思考はすごく育つでしょう。でも、プロ グラミングってそれだけじゃないと思うんです。 Max/MSPは自由度がある環境だと思うのですが、 最後に、美術でも建築でも、そうした作品を作る テキストベースのプログラミングはもっと自由です。 人たちにとって、プログラミングを学ぶことは新し だから両方やっていった方が確実にプログラミング い視点を切り開いてくれるものだと思いますか? の可能性を拡げる。dNAの空間表記方法のプロジ ェクトに関するコンセプトと同様ですが、言語や道 具が発想に与える影響は確実にあります。一方で、 I 一概には言えませんが、いまやコンピューティング って特別なことではなくて当たり前のことです。そう プログラミング言語であればどんなものにも共通し いう特別でないことをある程度操作できるスキルと た考え方がありますよね。基本的にはOSの管理す いうのは、ごく普通にあった方がいい。頭では発 るメモリの上に展開して、そのメモリに数値が入っ 想できないことを生み出すポテンシャルがあります ていてアドレスがあって…云々、ということを別の見 から。建築家であっても、プログラミングぐらいは え方にしているというだけですから。だから両方行 できるようになるべきと自分では思っています。そう き来できたほうがいい。 でなければ、建築家のような枯れた職種で新たな 展開はないでしょう。 openFrameworksがいくら便利であっても、やはり ポインタとかアドレスで引っ掛かるんですよ。最初 268 | 269 [構成:BNN] 4 openFrameworks プログラミング実践編 比嘉 了 この章では、openFrameworksを実際に用いたインスタレーションの紹介と、 自分の中にあるアイデアを素早く形にするための openFrameworksの利用法にフォーカスを当てて、 openFrameworksを使った小品をいくつか紹介します。 4 –1 プレイヤーの演奏に リアルタイムに反応する グラフィックス 2009 年 8月3日に六本木 SuperDeluxで行われたイベントで、鈴木絵理子さんとのパフォーマ ンス時に使用したプロジェクトを、openFrameworksを使ったプログラムの実践的な例として 紹介したいと思います。 パフォーマンス中ではいくつかのプログラムを曲のように切り替えながら進行していったのですが、 その中から音声に反応するビジュアルを主体とした作品「Cosmos」をとりあげます。 サンプルデータ:Cosmos_simple、Cosmos_full 僕は基本的には、前もって何を作るかのイメージはなしの状態で始めて、コードを書きながらど のようなものにしようかなと試行錯誤しながら作っていくというパターンで進めることが多いのです が、このプログラムを書いているときはぼんやりながら、プレイヤーの演奏にカッチリと同期してい るものがいいかなというイメージがあったので、安直に音声を入力してビジュアルが反応するもの を作ってみるか…といった感じでスタートを切りました。 ライブパフォーマンス用のプログラムを書く場合は特にそうなのですが、制作途中に急にひらめき があったり、必要のなさそうな要素を削ったりすることが多いので、openFrameworksのお手軽 さはだいぶありがたいところです。 ビジュアルを音に同期させるにあたって、音声を何らかの方法で解析する必要があります。そうい ったときに比較的簡単なのは、入力された音量を見る方法と、入力された音声にFFT変換をかけ て周波数ごとに分離された音量を見る方法の2種類で、今回は後者のFFT変換の方法をとること にしました。 FFTとは FFTについての詳しい説明は省きますが、今回の用途だと、「入力された音声に低い音がどれぐら 270 | 271 い、そして高い音がどれぐらい入っているのか、というデータがとれるもの」程度の認識でOKだと 思います。 そのような処理を利用したビジュアルはカーステレオやiTunesのビジュアライザなどにもついている ので、みなさん目にしたことも多いのではないでしょうか。 アドオンの入手方法 さて、FFT変換を行うことにしたわけですが、具体的な実装方法を知らなかったのとパフォーマン スの期日が迫っていたので、横着してopenFrameworksのアドオンを利用することにしました。 openFrameworksはユーザーコミュニティがとても活発で、 「○○をするにはどうしたらいいか」、 「これが動かないんだけどどうして?」といった情報がopenFrameworks forum(http://www. openframeworks.cc/forum/)を中心に熱く議論されています。他にも何かやりたいことに躓いて しまった場合、forumを眺めているとひらめきがあるかもしれません。 今回使っているFFT処理のコードは、forum内を「fft」というキーワードで検索した結果の audio input FFT example というスレッド(http://www.openframeworks.cc/forum/viewtopic.php?t= 186)に投稿されていたサンプルのFFTクラスを利用しました(ありがとう!)。 forumは基本的に英語でのやりとりになるのですが、めげずに読み進めているといいことがあるの ではないかという気がします。 [ソースコード解説] 作るにあたって気にしていたのは、入力するデータソースがFFT解析したスペクトルの場合、やっ ぱりどうしてもカーステレオのグラフィックアナライザの表示や、オーディオビジュアライザのような アウトプットになってしまいがちなのではないか、という点でした。 しばらく考えているうちに、スペクトルをXY平面的に使うのではなくて、極座標的な表示にしたら いいのではないかというアイデアが浮かび、そうすると入力された音声をFFTでサイン波の集合に 分解する技術的な部分とのリンクもできてカッコイイんじゃないか、さらに各周波数帯の振幅によ って回転させれば楽しいな、という所まで考えた(妄想した)ところで実装してみることにしました。 オーディオの初期化とfftをかける部分 音声の入力はsetup() などでofSoundStreamSetup関数の第二引数を1以上にしてコールすると audioReceivedが呼ばれるようになります。forumでみつけたfftクラスはpowerSpectrumメソッド にもろもろデータを渡すと計算してくれる仕組みのようだったので適当にコールして、計算された magnitude配列の中の値の大小が激しい感じだったので平方根をとってならしてみました。 void testApp::setup(){ // … // オーディオインプットを初期化 ofSoundStreamSetup(0, 1, this, 44100, buffer_size, 4); } // … void testApp::audioReceived(float* input, int bufferSize, int nChannels) 4–1 openFrameworksプログラミング実践編 { // 入力された音声シグナルをaudio_inputにコピー memcpy(audio_input, input, sizeof(float) * bufferSize); float avg_power = 0.0f; // アドオンのFFTクラスを使ってスペクトルを解析、 magnitudeが欲しかったFFT振幅の値になる myfft.powerSpectrum(0, (int)fft_size, audio_input, buffer_size, magnitude, phase, power, &avg_power); // 結果の値の大小が激しかったので平方根をとった for (int i = 0; i < fft_size; i++) { magnitude[i] = powf(magnitude[i], 0.5); } } ここまでで、外部から入ってきた音声にFFT解析をかけてmagnitude配列に保存、という処理に なります。 背景ノイズの消去 音にシビアに反応するプログラムの場合、空調や配線のちょっとしたノイズを拾ってしまってプロ グラムがずっと反応している状態になってしまうことがあるので、それを回避するために背景ノイズ を取り除く処理を入れました。スペースキーを押したときにmagnitude[]の平均値をノイズ成分と してメモリしておいて、実際に使うときにノイズ成分を引けば、なんとなくいい感じの値になるんじ ゃないかな…という期待を込めて。 void testApp::keyPressed(int key){ // スペースキーを押したときにFFT振幅平均値のスナップショットをコピー if (key == ' ') { memcpy(magnitude_average_snapshot, magnitude_average, sizeof(float) * fft_size); } } // ... void testApp::update() { // FFT振幅の平均値を計算 for (int i = 0; i < fft_size; i++) { float x = 0.025; magnitude_average[i] = (magnitude[i] * x) + (magnitude_ average[i] * (1 - x)); 272 | 273 } for (int i = 0; i < fft_size; i++) { // 環境ノイズをキャンセル float v = fabs(magnitude_average[i] - magnitude_average_ snapshot[i]); // ... } } 円を回転させる さて、いよいよ環境ノイズをキャンセルした結果を使って絵を動かす部分です。testApp::update() メソッド内で、ノイズをキャンセルした値をcircle_phase配列に0.0 ∼ 1.0の範囲に収まるようにし つつ蓄積していきます。ちなみにif文ではなくてwhile文を使っているのは、例えばvの値が2以上 になってしまったときでも0.0 ∼ 1.0の中に収まることを保証するためです。sin、cos関数を使って 円の動きを表現するやり方は、色々な所で応用が効くので覚えておくといいと思います。 その後、testApp::draw()メソッド内でcircle_phase配列の値を使って円の角度を計算しつつ、配 列のインデックスを表す変数iの値を半径とした座標に円を描画していきます。 void testApp::update() { // ... for (int i = 0; i < fft_size; i++) { // 環境ノイズをキャンセル float v = fabs(magnitude_average[i] - magnitude_average_ snapshot[i]); v *= 0.1; // 円の回転角(0.0 ~ 1.0)を更新 circle_phase[i] += v; while (circle_phase[i] > 1) circle_phase[i] -= 1; } } void testApp::draw() { // ... for (int i = 0; i < fft_size; i++) { // 円の回転角からXY座標を算出、iの値を半径の長さに反映させる float xx = cos(TWO_PI * circle_phase[i]) * i; float yy = sin(TWO_PI * circle_phase[i]) * i; 4–1 openFrameworksプログラミング実践編 // 円を描画 ofCircle(xx, yy, 2); } } ひとまず完成 ここまでで、マイクから拾った音を解析してグラフィックスに適応するプログラムが一通りできまし た。 「Cosmos_simple」フォルダにソースコード一式があります。実際にパフォーマンスで使った ものはもう少しビジュアルエフェクトや視点の変更などを組み込んだバージョンになるのですが、 ノリで書いてしまった部分もあり、ここで解説するには付随する記述が多くなってしまいそうなの で省略します。その代わりに、どういった過程で制作を進めていたかについて書きたいと思います。 実は「Cosmos_simple」でも、本稿の前半にあったアイデアの大半は実現できている状態になっ ているのですが、なんとなく物足りない部分が多いと感じるのではないでしょうか。実際のところ 現状だとちょっとカッコいい計測機器のような段階にあって、その次の段階に移行するまでは、 作品としても楽器としてもビジュアルとしても、満足がいくレベルに到達するまでイテレーション(終 了条件に逹するまで一定の処理を繰り返すこと)する必要があります。 例えば要素の数を増やしてみたり、サイズや色や視点を変更してみたり、細部のスピードにこだ わってみたり、制作途中で出てきたバグがかっこよければそれを採用してみたり、新しいアイデア を取り入れてみたり、全然ダメだったらコンセプトを見直してみたり等々の変更を加えて、プログ ラムを実行してみて触ってみて、気になる所があればまた変更を入れる。そういう過程の中では、 とにかくたくさんの可能性を試してみるスピード、それが面白いかどうかを見極める判断力、プロ グラムを画材のように扱う基礎体力、可能性のアイデアを出すための自分なりのバックグラウンド の知識の蓄積がとても重要になってきます。 そのあたりのことをふまえて、現状のちょっとカッコいい計測機器を自分なりにどうやったらいい感 じになるか、いじりたおしてみるといいかもしれません。可能性のひとつとして、 「Cosmos_full」フ ォルダにソースコード一式を入れておきました。全部で240行弱のソースコードなので、読みとい たり改変したりしていく途中に、みなさんの中に発見があれば幸いです。 Cosmos_simple実行結果 274 | 275 4 –2 被写体の軌跡をビジュアライズ 本書の編集側から「画像解析のサンプルが欲しい」と依頼があったので、openFrameworksと OpenCVを組み合わせたスケッチを作ってみました。 openFrameworksは Processingを踏襲したAPIを備えていて、もともとスケッチ的にプログラ ムを書きやすい環境ではあるのですが、それに加えてC/C++ の膨大なライブラリ群や高速な実 行速度も相まって、 「ちょろっと書いただけなのにすごい楽しいものができた」という部分が魅力的 な環境であると思います。ここからは、そのあたりを伝えられればいいなと思っています。 サンプルデータ:OpticalFlowSketch1 OpenCVはC/C++プラットフォームをベースに開発されているオープンソースの画像解析ライブラ リです。画像にマスクやエフェクトをかけたり、さまざまなアルゴリズムを用いて画像解析した結 果を利用することができ、とても高速に動作します。 OpenCVとopenFrameworksを連携させることで、openFrameworksで描画するグラフィックス をカメラからの動画にリアルタイムに合成することができ、ちょっと不思議な効果を出すことができ ます。このプログラムは、動画中の動いた部分を検出する「オプティカルフロー」と呼ばれるテク ニックを使って、動きのあった部分をトラッキングして煙のような軌跡をリアルタイムオーバーレイ するようなものが作りたいなと思って作りました。 OpenCVを使ったプログラムを書くときは、addonsExamples/opencvExampleからプロジェクト をコピーしてきて、それをベースにしてプログラムを書いていくと楽です。opencvExampleの testApp.cppを見ると、 #define _USE_LIVE_VIDEO 4–2 openFrameworksプログラミング実践編 と書かれた行があります。この行をコメントアウトしたりコメントを外したりすると、カメラからのリ アルタイムの画像を使うかdataフォルダに保存してある動画ファイルを使うかのスイッチができるの で、トライ&エラーを繰り返すであろう開発時にいちいち頭を振ったり手を振ったりしなくてよくな り大変便利です。是非活用してください。 [ソースコード解説] オプティカルフローは時間的に連続した2枚の画像を比較して、画像中の移動したと思われる部 分を検出してくれるナイスな処理です。これも難しいところはあまりよく理解していないのですが、 OpenCVの機能として実装されているのと、Webを検索すればサンプルコードが出てくるので、そ れを参考にしながら進めていきました。 testApp.cppのupdateメソッド内でオプティカルフローを計算する部分が以下です。 void testApp::update(){ bool bNewFrame = false; #ifdef _USE_LIVE_VIDEO vidGrabber.grabFrame(); bNewFrame = vidGrabber.isFrameNew(); #else vidPlayer.idleMovie(); bNewFrame = vidPlayer.isFrameNew(); #endif if (bNewFrame){ #ifdef _USE_LIVE_VIDEO src_image.setFromPixels(vidGrabber.getPixels(), VIDEO_ WIDTH, VIDEO_HEIGHT); #else src_image.setFromPixels(vidPlayer.getPixels(), VIDEO_ WIDTH, VIDEO_HEIGHT); #endif // オプティカルフロー処理 // ほぼ http://opencv.jp/sample/optical_flow.html からコピペ int count = 150; // 検出するポイントの最大数 static IplImage *eig = cvCreateImage(VIDEO_SIZE, IPL_DEPTH_32F, 1); static IplImage *temp = cvCreateImage(VIDEO_SIZE, IPL_DEPTH_32F, 1); static CvPoint2D32f *corners1 = (CvPoint2D32f*)cvAlloc(count * sizeof(CvPoint2D32f)); static CvPoint2D32f *corners2 = (CvPoint2D32f*)cvAlloc(count * sizeof(CvPoint2D32f)); 276 | 277 static IplImage *prev_pyramid = cvCreateImage(cvSize(VIDEO_ WIDTH+8, VIDEO_HEIGHT/3), IPL_DEPTH_8U, 1); static IplImage *curr_pyramid = cvCreateImage(cvSize(VIDEO_ WIDTH+8, VIDEO_HEIGHT/3), IPL_DEPTH_8U, 1); static char *status = (char*)cvAlloc(count); static IplImage *curr_image = cvCreateImage(VIDEO_SIZE, IPL_DEPTH_8U, 1); static IplImage *prev_image = cvCreateImage(VIDEO_SIZE, IPL_DEPTH_8U, 1); // src_imageの中身をグレイスケールに変換しつつ curr_image にコピー cvCvtColor(src_image.getCvImage(), curr_image, CV_RGB2GRAY); // 特徴点を抽出 float block_size = 10; // 検出するポイント間の最小距離 cvGoodFeaturesToTrack(curr_image, eig, temp, corners1, &count, 0.001, block_size, NULL); // curr_image と prev_image についてオプティカルフローを計算 cvCalcOpticalFlowPyrLK(curr_image, prev_image, curr_pyramid, prev_pyramid, corners1, corners2, count, cvSize(10,10), 4, status, NULL, cvTermCriteria(CV_TERMCRIT_EPS|CV_TERMCRIT_ITER, 64, 0.01), 0); // curr_image を prev_image にコピー cvCopyImage(curr_image, prev_image); // 検出できたフローに対するループ for (int i = 0; i < count; i++) { if (status[i]) { // 始点 ofxVec2f to = ofxVec2f(corners1[i].x, corners1[i].y); // 終点 ofxVec2f from = ofxVec2f(corners2[i].x, corners2[i].y); // 取得したフローに対して何かしらの処理をする updateFlowPoint(to, from); } } // ... } } 関数内で変数をstatic付きで宣言すると「=以降が初回のみ実行されて、次回からのアクセス時に も内容が保持されている」という挙動になります。このコードではそれを使ってテンポラリに使う IplImageの初期化をしています。メモリの確保/開放の処理をいちいち書かなくていいのと、グ 4–2 openFrameworksプログラミング実践編 ローバルスコープに変数を書いて「あの変数名何だっけな…?」とか言ってソースコードを上に下 に見にいったりしなくていいので、OpenCVを使ったプログラムを書くときはけっこう便利です。マ ルチスレッドにしないといけなくなったときなどにバグを生む可能性があってあまりお行儀が良くな い書き方ではありますが、 「1つのスレッドからしかアクセスしない、呼び出し元がいつも固定である」 という条件で使う分には問題ないんじゃないかなと思って使っています。 オプティカルフローの結果は、上記コードの下のほうにあるforループからupdateFlowPoint関数 を呼び出すかたちで利用しています。 軌跡の描画とアップデート オプティカルフローを用いて動いた部分のベクトルの計算ができました。それを元に軌跡のグラフ ィックを書いていきます。おおまかな流れとしては、 1. フローの始点に近い軌跡オブジェクトを検索&更新、なければ新規に追加 2. 長い間更新がなかった軌跡オブジェクトは消去 3. 軌跡オブジェクトを描画 というかたちになっています。 まずは軌跡を表すクラス、FlowLineに注目します。 FlowLineは線を描画するためのFlowPointの配列のpointsと、それとは別に現在のコントロール ポイントを表すposと現在の色を表すcolorを持つ構造にしました。クラスの外からposとcolorの 値を変更しつつupdateメソッドをコールし続けるとpointsにFlowPointオブジェクトが追加されて いって、drawメソッドをコールすると軌跡が描画される、という仕掛けになっています。また、 updateメソッドの中で軌跡のy軸をいじってだんだん上がっていくことで煙のようなエフェクトを与 えているのと、線がだんだんなめらかになるようなフィルタをかけています。 こういった感じで、testAppクラスのようなupdateやdrawメソッドを自分で定義するクラスにも実 装して、testAppクラスのそれぞれのメソッド内でコールするようにするやり方は、プログラムの構 造がわかりやすくなるのでなにげによく使います。 // 位置と色の状態を持った構造体 struct FlowPoint { ofxVec3f pos; ofxVec3f color; }; // 軌跡を描画するためのクラス class FlowLine { public: ofxVec3f pos; // 軌跡の先頭の位置 ofxVec3f color; // 軌跡の先頭の色 deque<FlowPoint> points; // 軌跡を構成する点列 float alpha; // アルファ値 278 | 279 float rise_speed; // 上昇スピード FlowLine() { // 軌跡が上っていくスピードをランダムでちらす rise_speed = ofRandom(0.5, 5); } void update() { // 軌跡の先頭に現在のポイントを追加 FlowPoint p; p.pos = (points.front().pos*0.2 + pos*0.8); p.color = (points.front().color*0.2 + color*0.8); points.push_front(p); if (points.size() > 2) { for (int i = 1; i < points.size(); i++) { // 軌跡がだんだん上っていくように points[i].pos.y -= rise_speed; // 線をなめらかに points[i-1].pos = points[i].pos*0.6 + points[i-1]. pos*0.4; } } // //軌跡の長さを制限する if (points.size() > 100) points.pop_back(); // アルファ値をだんだん小さくする alpha += -alpha * 0.05; } void draw() { // 軌跡の描画 glBegin(GL_LINE_STRIP); for (int i = 0; i < points.size(); i++) { FlowPoint &p = points[i]; float a = 1 - (float)i / (float)points.size(); //だんだん透明に glColor4f(p.color.x,p.color.y,p.color.z,a*alpha); glVertex2fv(p.pos.v); } glEnd(); } // アルファ値が十分に小さいときに消去するためのフラグ用の関数 4–2 openFrameworksプログラミング実践編 bool alive() { return alpha > 0.05; } }; ちなみにpointsで使っているdequeは、機能としてはvectorとだいたい一緒なのですが、先頭と 末尾に対する要素の追加/削除が高速なC++標準のコンテナクラスです。軌跡の実装のように、 先頭に要素を追加しつつ数が れそうなときは末尾を削除といった場合には、vectorよりもパフ ォーマンス的にいいのかなと思って使ってみました。 トラッキング 今度は「1. フローの始点に近い軌跡オブジェクトを検索&更新、なければ新規に追加」の部分で す。updateメソッドから呼ばれるupdateFlowPoint関数内でトラッキングの実装をしています。 さきほど定義したFlowLineクラスの配列、flow_linesを定義して、その中にオブジェクトを追加し たり削除したりする方向で進めていきます。 ソートの部分が多少わかりにくいかもしれませんが、flow_lines配列内のオブジェクトについて始 点からいちばん近いposメンバを持ったオブジェクトを探しています。そのいちばん近いオブジェク トが一定距離以内にある場合は更新、なかった場合は新しい軌跡オブジェクトを追加するという 挙動です。このようにして、近い点を追いかけていくことでトラッキングを実現しています。 一定の距離内にある場合、 コントロールポイントの位置を更新 一定の距離内にない場合、 コントロールポイントを新規に追加 FlowLineオブジェクトのコントロールポイント 新しく検出されたフローの始点 // 軌跡オブジェクトの配列 vector<FlowLine*> flow_lines; // 一番近い点を持ったオブジェクトを探すためにソートをかける関数オブジェクト struct sort_by_distance { sort_by_distance(ofxVec2f pos) { this->pos = pos; 280 | 281 } bool operator()(const FlowLine* a, const FlowLine* b) { float len_a = (a->pos - pos).squareLength(); float len_b = (b->pos - pos).squareLength(); return len_a < len_b; } ofxVec2f pos; }; // 検出したフローに対する処理 void updateFlowPoint(ofxVec2f to, ofxVec2f from) { // 始点と終点のベクトル距離(=移動量)を求める float len = (from - to).length(); if (len > 1 && len < 50) // 距離がいい感じだったら... { // 終点周辺のピクセルの色を取得 CvScalar c = cvGet2D(src_image.getCvImage(), (int)to.y, (int) to.x); ofxVec4f color = ofxVec4f(c.val[0] / 255.0f, c.val[1] / 255.0f,c.val[2] / 255.0f); // temp_linesの中のオブジェクトを始点に近い順にソート sort(flow_lines.begin(), flow_lines.end(), sort_by_ distance(from)); if (flow_lines.empty() || (flow_lines[0]->pos - from).length() > 30) { // temp_linesが空か、始点の近くにオブジェクトがない場合新しく追加 FlowLine *line = new FlowLine(); line->pos = to; line->alpha = 0; line->color = color; FlowPoint point; point.pos = to; point.color = color; line->points.push_back(point); flow_lines.push_back(line); } else { // 近いオブジェクトがみつかったので色とか位置とかを更新 FlowLine *line = flow_lines[0]; line->color = color; 4–2 openFrameworksプログラミング実践編 line->pos = to; line->alpha += (1 - line->alpha) * 0.1; } } } 軌跡の更新と削除 最後にflow_lines配列中のオブジェクトのupdateメソッドをコールしつつ、長い間コントロールポ イントの位置の更新のなかったオブジェクトを消去する部分です。オブジェクトの寿命はアルファ 値と結びつけてあって、十分に薄くなったら消去する挙動にしました。aliveメソッドを呼んで、 falseが帰ってきた場合にはflow_lines配列から削除して、オブジェクトを開放します。 void testApp::update(){ // ... if (bNewFrame){ // ... // FlowLineの更新、削除 vector<FlowLine*>::iterator it = flow_lines.begin(); while (it != flow_lines.end()) { FlowLine *line = (*it); // 更新 line->update(); // オブジェクトの生存フラグをチェックして、falseだった場合は消去する if (line->alive() == false) { it = flow_lines.erase(it); delete line; } else { it++; } } } } 282 | 283 4 –3 物理計算を使ったグラフィックス 前項のオプティカルフローで軌跡を描画するプログラムを組んでいる際に、一通りアイデア通りに 組んでみたものの、思っていたよりもしっくりこないものができてしまい、途方に暮れているときが ありました。そんな状態のときに心機一転、軌跡を描画する部分に着目して違ったアプローチで 思考錯誤して書いたスケッチもせっかくなので紹介したいと思います。 結局こちらの案は「OpticalFlowSketch1」にはマージしなかったのですが、単純に連続した点 をプロットしていくのではなくて、物理計算を使って重さを持ったパーティクルが引力を持った点 に引き込まれる過程を、軌跡の集合としてビジュアライズしていきました。 サンプルデータ:LineSketch [ソースコード解説] まずはParticleクラスを見ていきます。 軌跡を描画するクラス 軌跡を描画する部分の基本的な考え方は「OpticalFlowSketch1」のFlowLineクラスとほとんど一 緒なのですが、物理計算のための変数がいくつか追加されています。このプログラムではParticle クラスには描画まわりのメソッドのみを実装するに留めておいて、重要な処理はupdateメソッドで 実行しています。 4–3 openFrameworksプログラミング実践編 class Particle { public: float mass; // 質量 ofxVec3f pos; // 位置 ofxVec3f velocity; // 加速度 ofxVec3f factor; // 因子 deque<ofxVec3f> line_strip; // 軌跡の配列 Particle() { mass = 1; } void update() { // 軌跡を追加 if ((line_strip.front() - pos).squareLength() > 60) { line_strip.push_front(pos); } // 長くなりすぎた軌跡を削除 if (line_strip.size() > 300) line_strip.pop_back(); } void draw() { // 軌跡を描画 glBegin(GL_LINE_STRIP); for (int i = 0; i < line_strip.size(); i++) { float a = 1 - (float)i / (float)line_strip.size(); glColor4f(1, 1, 1, a * 0.3); glVertex3fv(line_strip[i].v); } glEnd(); } }; vector<Particle> particles; // Particleの配列 運動を計算する Particleクラスには現在の位置を表すpos、パーティクルの質量を表すmass、加速度を表す velocityを定義しました。1つめのforループの中で物体にかかる空気抵抗や引力などの力をfactor に次々と加算していき、2つめのforループ内では、factorの内容を元にして加速度を表すvelocity を更新して、それによって位置を算出しています。「質量が重い物体は同じ加速度を受けた軽い 284 | 285 物体に対して動きにくい」という慣性の法則を取り入れるために、factorとvelocityの2つの変数 が必要になっています。 実際に進む距離 加速度 軽い物体 重い物体 同じ加速度を受けていても重い物体は影響を受けにくい もちろん、現実の物理法則をシミュレーションするだけではなく、パラメータによっては現実では ありえない反重力のような運動のルールを記述してもおもしろいかもしれません。運動ルールの記 述は1つめのforループの中で行っているので、色々と試してみると意外な動きの発見があります。 void testApp::update(){ for (int i = 0; i < particles.size(); i++) { Particle &p = particles[i]; // 空気抵抗? p.factor = -p.velocity * 0.1; // 中心点に集まるように p.factor += (center - p.pos) * 0.1; // 万有引力めいたものの計算 for (int k = 0; k < particles.size(); k++) { if (i == k) continue; Particle &pp = particles[k]; p.factor += (pp.pos - p.pos) / pp.mass * 0.001; } } for (int i = 0; i < particles.size(); i++) { Particle &p = particles[i]; // 加速度の計算 p.velocity += p.factor/p.mass * 0.1; // 位置の更新 p.pos += p.velocity * 0.1; p.update(); } } 4–3 openFrameworksプログラミング実践編 4 –4 モーフィングのような鏡像 さらにもう1つ、オプティカルフローを使ったスケッチを紹介したいと思います。4-2で紹介したオ プティカルフローを使ったスケッチは、検出された特徴点をトラッキングして、動いた物体につい ていくオーバーレイ的な表現のものでしたが、今回は移動した物体のベクトルを使って画像を変 形させていく不思議な鏡のようなものにしたいなと思って作り始めました。 サンプルデータ:OpticalFlowSketch2 [ソースコード解説] まず、ofxVec2fクラスの2次元配列のvector_matrixを定義することにしました。 頂点ベクトルの初期化 vector_matrixには、BLOCK_SIZEで指定したサイズで領域を正方形に分割していった頂点の変 移ベクトルを保存します。 static const int BLOCK_SIZE = 40; static ofxVec2f vector_matrix[VIDEO_HEIGHT / BLOCK_SIZE + 1][VIDEO_ WIDTH / BLOCK_SIZE + 1]; void testApp::setup(){ // ... 286 | 287 // 配列を初期化 for (int y = 0; y < VIDEO_HEIGHT / BLOCK_SIZE + 1; y++) { for (int x = 0; x < VIDEO_WIDTH / BLOCK_SIZE + 1; x++) { ofxVec2f &p = vector_matrix[y][x]; p.x = 0; p.y = 0; } } } <キャプション> 画像をブロックに分割するイメージ 移動量を使ってグリッドを変形させる 次にオプティカルフローで検出されたフローを処理する部分です。オプティカルフローの処理の部 分は「OpticalFlowSketch1」とほぼ同じなので省略します。検出されたフローの始点をBLOCK_ SIZEで割るとvector_matrix配列内のどこに該当するかがわかるので、該当するブロックに加算し ていくことでグリッドを変形させます。 v1 v1+v2+v3 v2 v3 グリッド領域内のベクトルを使ってグリッドを変形させる 4–4 openFrameworksプログラミング実践編 void testApp::update(){ // ... if (bNewFrame){ // ... // オプティカルフロー検出部分はOpticalFlowSketch1とほぼ同様 for (int i = 0; i < corner_count; i++) { if (status[i]) { ofxVec2f from = ofxVec2f(corners1[i].x, corners1[i].y); ofxVec2f to = ofxVec2f(corners2[i].x, corners2[i].y); float len = (to - from).length(); if (len < 40) { int xx = from.x / BLOCK_SIZE; int yy = from.y / BLOCK_SIZE; // グリッド領域内のベクトルを加算 vector_matrix[yy][xx] += (to - from) * 0.3; } } } // ... } グリッドを平滑化 変形させっぱなしだとグリッドがトゲトゲしくなってしまって大変なことになるので、平滑化フィル タをかけてじんわりとなじませるようにします。このフィルタは簡単なデジタルフィルタの応用で、 配列をループで回していって前の値、現在の値、次の値の平均の値を次々に現在の値に代入し ていく感じで適応します。smooth変数の値を変えていくことで平滑化の強さを変更できます。 n-1 元の配列 288 | 289 n+1 …… …… ×0.25 フィルタ処理後の配列 n …… ×0.5 ×0.25 …… 今回はofxVec2f型に対して適応していますが、こういった平滑化の処理は足し算とかけ算ができ る型であれば何にでも適応可能なので、ふわっとした感じを出したいときはよく使うイディオムです。 void testApp::update(){ // ... // 平滑化の度合い float smooth = 0.3; // ベクトルをX軸方向に平滑化 for (int y = 0; y < VIDEO_HEIGHT / BLOCK_SIZE + 1; y++) { for (int x = 1; x < VIDEO_WIDTH / BLOCK_SIZE; x++) { ofxVec2f &pp = vector_matrix[y][x]; ofxVec2f &p1 = vector_matrix[y][x - 1]; ofxVec2f &p2 = vector_matrix[y][x + 1]; pp = (p1 + p2) * smooth * 0.5 + pp * (1 - smooth); } } // ベクトルをY軸方向に平滑化 for (int x = 0; x < VIDEO_WIDTH / BLOCK_SIZE + 1; x++) { for (int y = 1; y < VIDEO_HEIGHT / BLOCK_SIZE; y++) { ofxVec2f &pp = vector_matrix[y][x]; ofxVec2f &p1 = vector_matrix[y - 1][x]; ofxVec2f &p2 = vector_matrix[y + 1][x]; pp = (p1 + p2) * smooth * 0.5 + pp * (1 - smooth); } } // ... } 変形したテクスチャの描画 いよいよ描画の部分なのですが、openFrameworksには画像の中の一定領域のみを描画するよ うな機能がないので、今回のようなブロックに分割した画像の領域を描画しようとした場合には OpenGLの機能を直に使う必要があります。openFrameworksの描画システムはGLUT(GLUTと は、OpenGL Utility Toolkitの略称で、OSなどの環境の違いに依存することなくOpenGLのプロ グラミングを可能にするライブラリです)をベースにした作りになっているので、OpenGLやGLUT の関数群を直接呼び出すことができます。 OpenGLでテクスチャを貼ったポリゴンを描画するには、テクスチャをバインドした後に、glBegin 関数で描画スタイルを選択した後に頂点の分だけglTexCoord関数でテクスチャ座標を指定して 4–4 openFrameworksプログラミング実践編 対応する頂点座標をglVertex関数で指定し、最後にglEnd関数を呼び出します。glBegin関数 に渡す引数によってどのようなモードで描画するかを指定するのですが、このプログラムではGL_ QUAD_STRIPを渡して連続した四角形を描画しつつテクスチャ座標を指定して、グリッド状にテ クスチャを貼っていきました。 v0 v2 v3 v1 v4 v6 v5 v7 GL_QUAD_STRIPの描画順 GL_QUAD_STRIPでポリゴンを描画するには上の図のような順序で座標を指定していきます。な のでグリッドのy座標を縦にずらしながら2頂点ずつ描画していくと1行分のポリゴンが描画される ことになります。その処理を全ての列に対して行なうと画像がきちんと描画されるという流れです。 void testApp::draw(){ // ... // 移動平均の画像をアルファ値0でカラ描画。 // 後にテクスチャをバインドする時にこれがないとアップデートされなかったので glColor4f(1, 1, 1, 0); acc_image.draw(0, 0); // アルファ値を元に戻す glColor4f(1, 1, 1, 1); // テクスチャをバインド acc_image.getTextureReference().bind(); for (int y = 0; y < VIDEO_HEIGHT / BLOCK_SIZE; y++) { // ポリゴンの描画 glBegin(GL_QUAD_STRIP); for (int x = 0; x < VIDEO_WIDTH / BLOCK_SIZE + 1; x++) { ofxVec2f t1 = ofxVec2f(x * BLOCK_SIZE, y * BLOCK_SIZE); ofxVec2f t2 = ofxVec2f(x * BLOCK_SIZE, (y + 1) * BLOCK_ SIZE); ofxVec2f v1 = t1 + vector_matrix[y][x]; ofxVec2f v2 = t2 + vector_matrix[y + 1][x]; 290 | 291 // テクスチャ座標と頂点座標を指定 glTexCoord2fv(t1.v); glVertex2fv(v1.v); glTexCoord2fv(t2.v); glVertex2fv(v2.v); } glEnd(); } // テクスチャをアンバインド acc_image.getTextureReference().unbind(); // ... } 本章の参考URL C++入門 http://wisdom.sakura.ne.jp/programming/cpp/index.html C++ Reference http://www.cppll.jp/cppreference/ http://www.cppreference.com/wiki/(英語) プログラミング言語C++ http://www.amazon.co.jp/dp/475611895X ロベールのC++入門講座 http://www.amazon.co.jp/dp/4839926050 OpenGL入門 http://wisdom.sakura.ne.jp/system/opengl/ OpenGL Reference http://www.opengl.org/sdk/docs/man/(英語) OpenGLプログラミングガイド http://www.amazon.co.jp/dp/4894717239 opencv.jp http://opencv.jp/ 詳解 OpenCV http://www.amazon.co.jp/dp/4873114136 実例で学ぶゲーム3D数学 http://www.amazon.co.jp/dp/4873113776 ディジタルフィルタとz変換 http://laputa.cs.shinshu-u.ac.jp/~yizawa/InfSys1/basic/chap10/ 4–4 openFrameworksプログラミング実践編 計算によるデザイン (Design by Computations) ―インタラクションを超えて 久保田晃弘 多摩美術大学 情報デザイン学科 情報芸術コース 教授 プログラミング教育の再発見 (モジュール)化されていなければならない」 「プログ ラムは効率良くなければならない」 「プログラムは簡 日本の国際メディア研究財団で、さまざまなリアク 潔でなければならない」といった、ある種の完璧主 ティブ・デザインの実験を行なっていたジョン前田 義に根差した価値観がベースにあり、それをいかに が、1996年に米国MITのメディアラボ準教授に着 エレガントに実装するかがプログラミングにおける 任して開 始したプロジェクトが「DBN(Design by 美学、すなわち「アート・オブ・プログラミング」 Numbers, 数によるデザイン) 」1 であった。100×100ド の実践であった。こうした文化を反映して、従来の ットというミニマルなビットマップスクリーン上に、 プログラミングの教科書の多くは、数の並べ替え 基本的な描画や反復、あるいは条件分岐といった、 や文字列処理、数値計算やデータ構造といった数 最小限の命令を持ったプログラム言語で視覚デザ 理的な例題から始まり、そこから具体的な、数学 インを行う。これまでは主に、コンピュータ科学者 的、工学的な事例に入っていく2 。しかし視覚的、 や工学者、技術者などの、いわゆる理工系のため 空間的にものごとを考え、直感的、身体的にコミ のものだったプログラミングやアルゴリズミックな思 ュニケートするアーティストやデザイナーに、そうし 考法を、アートやデザインという表現の世界、さら た抽象的・論理的な例題は向いていない。 にはそこにある美意識や美学と結びつける。それが この「数によるデザイン」プロジェクトの出発点であ アーティストやデザイナーがまず最初に興味を持つ った。 のは、抽象的な概念や構造ではなく、身体や知覚 と直接結びついた具体的な表現である。それは視 コンピュータ科学者や工学者、技術者の文化に根 覚表現においては、目の前にあるディスプレイ上に 差したプログラミングの世界には、「プログラムは 点を打ち、線を引き、面を塗ることであり、音響 正確に動かなければならない」 「プログラムは構造 表現においては、モニタスピーカからクリック、サ 292 | 293 スケッチからコミュニティへ イン波、ノイズなどの音を出し、それを聴くことだ。 だからジョン前田のテキストブック『Design By Numbers̶デジタル・メディアのデザイン技法』3 DBNのアプローチはその後、ジョン前田の「美学 では、まず画面上に線を引くことやスピーカで音を と計算」グループの学生であったベン・フライとケ 出すことから出発する。鉛筆で1つ1つ点を打ったり、 イシー・リースによる「Processing」4 5 というオー 1本1本線を引くように、すべての点や線の座標を入 プンなプログラミング環境の開発にバトンタッチさ 力することで描画する。色もRGBの値を1つ1つ指 れ、それはインターネットを介して世界各地のアー 定する。そこにはまだ抽象や構造といった概念は ティストやデ ザイン 教 育 の 現 場 に 拡 がった。 ない。まずはとことん具体的で直接的であることが Processingでは個々のプログラムのことを「スケッ 重要であり、抽象や構造以上に、プログラミング チ」と呼んでいるが、これはProcessingが前述の の結果が視覚や聴覚に即座にフィードバックされ、 実行・確認・修正の繰り返しによって行われる構 それを受けて即座に(つまりインタラクティブに)プログ 成的なプログラミング開発を、伝統的な美術作品 ラムを修正できるようになっていることの方が重要 の制作やデザイン・プロセスに必要不可欠なドロ なのだ *。 ーイングやスケッチと同等のものと見做そうとする 姿勢の現れである。 *だから最初のうちはプログラムをシンプルにしたりエレガン トにすることを考える必要はない。すべての点や線、色の 座標を入力するような愚直で非効率的な書き方でも、とに かくプログラムが動けばよい。少なくとも、それだけでコン ピュータ・グラフィックスは描けるのだ。 しかしながらProcessingによってもたらされたもの は、スケッチブックに例えられるようなアートやデザ インのためのパーソナルなプログラミング環境だけ ではなかった。むしろそれ以上に重要だったのが、 こうしてプログラムが走り、コンピュータで何かが Processingがフリーでオープンなプロジェクトであ 描けるようになれば、まずは最初の、そしてひょっ ることで、その周囲にProcessingのユーザーやディ としたら最大の関所はクリアしたようなものだ。あ ベロッパを核とした、アクティブなオンライン・コミ とはそれを少しづつ修正したり要素を追加していき ュニティが形成されたことだ。Processing.orgのサ ながら、アニメーションやインタラクションへとステ イトには、単なるアプリケーションの配布のみなら ップ・バイ・ステップで、いわゆるヴィジュアル・シ ず、Processingの機能を拡張するライブラリや、チ ンキングのような実行・確認・修正の繰り返しに ュートリアルやリファレンスなどのドキュメント、困 よる現代的なデザイン・メソッドと同じように進め ったときに質問できるディスカッション・ボードなど、 て行けばよい。アーティストのためのプログラミン さまざまな知識や文化が広く保管、共有されている。 (イラストレーション)」 「動かす(ア グは、 点や線を「描く ニメーション)」 「反応する(インタラクション)」という3 インターネットがもたらした、独学を容易にする環 つのステップによる、発見的かつ構成的なプロセス 境は、物事をクローズドにすることで偽りの権威を に根差している 。 * * こうした考え方のおおもとになっているのは、人工知能の 父、マーヴィン・ミンスキーから「生きている内で最も偉大 な数学教育者」と呼ばれた、シーモア・パパートの構成 主義(Constructivism)的なプログラミング教育である。 パパートは、インタラクティヴなタートルグラフィックをベー スにしたプログラミング言語「LOGO」を開発し、子供に 対して先駆的なプログラミング教育を行った。この試みは 現在、MITのニコラス・ネグロポンテらによるOLPC(One Laptopper Child)プロジェクトへと引き継がれ、さらなる 普及と発展を続けている。 保持しようとしてきた学会や論文集などのアカデミ ズムを崩壊させたが、それは同時に、パーソナル な世界に留まっていた独学によるDIY(Do It Yourself) の世界を解放し、後にザッカリー・リーバーマンが 「DIWO(Do It With Others)」と呼ぶことになる、接続 された個人の集合による新たな創造的環境を生み 出した。 かつてプログラムはテキストエディタを用いて1から 書いていくものであったが、今日の接続された環境 においては、プログラム・コードも画像ファイルや 計算によるデザイン―インタラクションを超えて サウンドファイル同様に、既にある例題やサンプル、 っては命ともいえる、ラインプリンターやプロッタな あるいはネットワークで公開されているさまざまな ど出力装置のクオリティも低かった。それでも当時 事例を出発点に、それらを参照したり解読しなが のコンピュータ・アートの先駆者たちは、自ら新し らサンプリング/リミックスしていくことで制作する い造形のためのアルゴリズムを探求し、それを実 のが常である。もちろんそこには、ある種の無駄や 行するためのプログラムを書き、さらには独自の出 偶然が入り込まざるを得ないが、そうした一見冗 力装置まで工夫して、新しい芸術の道を切り開い 長なものの中にこそ新たな表現の可能性が潜在し てきた。 ている。プログラミングやソフトウェアの世界にも、 DJやVJ同様のリミックス・カルチャーが深く浸透し しかしながら、そうした未だ見ぬ新たな造形のため ていった。 のコンピュータ・アートは、その後コンピュータを 仕事場や家庭に1台でも多く普及させようという市 場の力学によって、誰もが使える汎用アプリケーシ コンピュータ・アートのリメディエーション ョンの開発が中心となり、既存の造形の再現に回 理工系のためのものではないプログラミング教育 やデザイナーは、独自のアルゴリズムの探求やプロ 収されていった。多くのコンピュータ・アーティスト を体系化し、さらにそれをコミュニティの中に根付 グラムの開発ではなく、企業が提供する汎用アプ かせたという点で、DBNやProcessingがもたらし リケーションの機能の枠内で、管理された独自性 たものは確かに画期的なものであったが、それを を(それも微細な差異を)競うことになった。 結果としての造形という観点から見れば、プログラ ミングによる造形は何も新しいものではなく、その 今振り替えれば、DBNはそうした市場ベースのコ 元祖は1950 ∼ 60年代に始まった初期のコンピュ ンピュータ・アートのデッドエンドに対して、あえて ータ・アート6 にある。 60年代の、技術的には稚拙な、しかし思索を尽く し熱気に れたコンピュータ・アートをリメディエイ 米ジョージア工科大学でコンピュータ文化を研究し トすることで、もう一度コンピュータによる表現の ているジェイ・デイヴィッド・ボルターらは、1999年に 原点と可能性を再考し、もう1つの歴史を生み出そ 出版した『Remediation: Understanding NewMedia うとする試みであった。それは別の言い方をすれば、 7 で、メディアは (再メディア化:ニューメディアの理解)』 DBNとはアーティストやデザイナーのための「コン 技術によってその形態や表現がかたちづくられてし ピュータ・アートの模写ツール」であるということだ。 まうのではなく、まずは既存のさまざまなメディアの フランス留学中の黒田清輝がルーブル美術館で名 表現形式を模倣しつつ取り入れ、そこから変容、 画の模写に勤しんだように、模写は単なる表面上 発展していくという論を展開した。例えば、コンピ の色や形を真似ることではなく、先人の創造のプ ュータ・グラフィックス、バーチャル・リアリティ、 ロセスを自分なりの方法でトレースすることで、技 ウェブといったデジタルメディアによる表現は、まず 法の背後に隠された創造の原点を探り、そこから は、それ以前の絵画や写真、本や雑誌、テレビや 新たな創造の可能性を発見するプロセスであり、 映画の形態を借りるところから出発した。コンピュ 創作のためには必要不可欠かつ最も基本的なエク ータのGUIは既存のデスクトップをメタファーとし、 ササイズだといえるだろう。 電子テキストや電子ブックは既存の紙の本のフォー マットを踏襲している。 DBNやProcessingのチュートリアルのサンプルは、 いわば今日までのコンピュータ・アートを生み出し 「コンピュータの処理能力は18 ∼ 24か月ごとに倍 になる」というムーアの法則を持ち出すまでもなく、 てきたアルゴリズムの断片の集合体であり、前述の ように多数のサンプルをリミックスすることによるプ 60年代当時のコンピュータの性能は今とは比べも ログラミングの習得は、分散共同化されたオンライ のにならないほど貧弱であり、さらに美術作品にと ン環境における今日的な模写システムによる、表現 294 | 295 スキルの獲得方法である。コンピュータ・アートの 百万倍であるため、チーラの文明は地球時間でわ ルーブル美術館は、ネットワーク上に遍在している。 ずか1 ヶ月あまりで進化する。コンピュータの計算 速度が速くなるということは、つまりコンピュータが 中性子星上のチーラのようになるということだ。何 デレゲーションによる抽象操作 せ10GFLOPSの計算速度とは、CPUが1回計算す る間に、光ですらたった3cmしか進むことができな では、こうした初期コンピュータ・アートのリメディ い程なのだ。では、そんな全く異なる速度の時間 エイションの先にあるものは一体何なのだろうか。 を持つ者同士は、一体どのようにインタラクトしコ ミュニケートすればよいのだろうか? わずか100×100ドットの空間だけを用いるDBNに よって再提示された「数によるデザイン」は、その GUIの代名詞でもあった「直接操作」あるいは「直 後継者であるProcessingによって解像度(精度)が 感的な操作性」が謳われたのは、コンピュータの 向上し、オブジェクト指向プログラミングや3Dグラ 計算速度が人間の知覚や思考に比して遅く、しか フィックスなど、さまざまな拡張がされていったが、 もコンピュータがまだネットワークに繋がれていな その根幹にある基本的な考え方は、DBNの「数に い、80年代のパーソナル・コンピュータ時代であ よるデザイン」を踏襲している。 った。インターネット時代になり、コンピュータは 高速化すると同時に、単体のマシンとしてではなく、 しかしながら、今日のコンピュータのいちばんの特 後にクラウド・コンピュータと呼ばれるように、群 徴は、60年代のコンピュータや初期のパーソナル・ れで使用されるようになった。その時にユーザーが コンピュータとは比べものにならないくらいの計算 行うのはコンピュータを直感的に直接操作すること 速度や、メモリ空間の増大にある。90年代には、 ではなく、検索や書き込みのようなさまざまなメッ そうした計算リソースの向上はアルゴリズムの計算 セージをそれらに投げかけることである。すると、 ではなく、インターフェイスやリアリティ(現実世界の コンピュータ群はその計算能力とネットワークをフ シミュレーション)のために用いるべきだと考えられて ルに活用して、メッセージに対する何らかの反応を いた。 返してくれる。こうしたやりとりは、コンピュータに 操作を任せる「デレゲーション(委譲)」と呼ぶこと コンピュータを用いた表現を特徴づけるものとして、 ができる9 。デレゲーションがインタラクションと異 よく「インタラクション」があげられる。実際、最 なるのは、 イベントの発生(人間)と処理(コンピュータ) 初に述べたように、表現のためのプログラミング教 を分離し、高速な計算やアルゴリズムなどの処理 育は、点や線を描き、動かし、そしてインタラクト のフィードバックを隠 することで、コンピュータに する、という3つのステップに根差している。インタ 対する操作そのものを抽象することにある。操作を ラクションはコンピュータと人間が一体となって、 抽象することでコンピュータ群は空間や環境になり、 あるいはコンピュータが人間をサポートしながら表 デスクトップや物理世界のメタファーも不要になる。 現を作りあげていくためのものである。しかしコン Google公開当時の検索画面が入力フォームのみ ピュータの速度が人間の知覚や反応の速度よりも のシンプルなもので十分だったのは、そこにメタフ 格段に速くなってくると、コンピュータにとって人間 ァーが存在していないからである。そのことを、デ とインタラクションすることが、むしろ足枷になって レゲーションによるポストGUI時代の「抽象操作」 くる。 あるいは「間接的な操作性」と呼ぶことができるだ ろう。もはやコンピュータの計算に人間が直接介 ロバート・フォワードが1980年に発表した 『竜の卵』 入することは不可能であり、コンピュータと人間が 8 というSF小説には、人間と中性子星上の知的生 一体になることも不要になる。 命チーラとのファースト・コンタクトが描かれてい る。中性子星上では時間の進み方が地球上の 計算によるデザイン―インタラクションを超えて 計算が拓く新しいデザイン るシステム(計算知識エンジン)も公開されている。 Googleの検索はウェブ上にあるデータを選択し表 マイクロソフト社を作りあげたビル・ゲイツの業績 示するだけだが、Wolfram|Alphaは、たとえウェブ に対しては賛否両論さまざまな意見があるが、それ 上に答えが存在しない場合でも、計算によって新 でも彼がコンピュータの本質を突いていたといわざ たな答えを生成することができる。インタラクショ るを得ないのは、彼が常に「ユーザーにとっては速 ンを超えるのは、高速な計算による「ジェネレーシ 度が最も重要だ」と考えていたことにある。遅いプ ョン(生成)」なのだ *。 ログラムを書く人の言い訳の常套句は「すぐにコン ピュータが速くなる」であるが、重要なことは、遅 いプログラムが速くなる頃には速いプログラムはさ * 最近はGoogleも、Google InsightsやGoogle Squaredの ように、検索に生成的な機能を持たせることを実験している。 らに速くなっていることだ。どんなにハードウェアの 2008年11月から2009年2月に山口情報芸術セン 性能が向上しても、今遅いプログラムが今速いプ ター(YCAM)で開催され、僕も参加した「ミニマム・ ログラムよりも速くなることはない。本書で取り上 インターフェイス」展 では、マルティン・カルテン げ たopenFrameworksは、 その 構 文 やsetup()、 ブルネルらによる「reacTable」 、ザッカリー・リー draw()モデルなど、Processingに強い影響を受け バーマンの「Card Play」やクリス・ サグリュの ており、関数名などもProcessingとほぼ同じ構成 「Delicate Boundaries」 、 高 尾 俊 介の「Depth of になっている。しかしながら、こうした表面的な類 the Field」 、リーディング・エッジ・デザインによ 似とは別に、openFrameworksのいちばんの特徴 る会場ナビゲーションのための「on the Fly」、そし は、Processingに比較して5倍を超える計算速度 て僕の「純粋Φ」など、まさにミニマム・インターフ にある。 ェイスならぬ、「カメラ・トラッキング展」と呼ぶべ き内容であった。 通常、コンピュータはその計算速度が2倍になった ら買い換えよ、といわれているが、5倍もの速度の こうしたカメラ・トラッキングを用いたインターフェ 差があれば単なる量の差ではなく、そこから新たな イスの特徴は、GUIのようにボタンやスライダーと 質が生まれてくる。コンピュータが遅かった頃は、 いったディスプレイ上の具体的なオブジェクトをイ 人々は速度の本当の意味を知ることなく、コンピュ ンターフェイス部品とするのではなく、拡張現実 ータのアルゴリズムを複雑にすることで、その処理 (Augmented Reality)技術がそうであるように、イン を高度にしようとしてきたが、コンピュータの計算 ターフェイスを非オブジェクト化し、現実の物理空 能力が向上することで、人々は速度が持つ本当の 間内に溶け込ますことにある。しかしそのためには、 意味を経験できるようになった。そして、複雑なこ コンピュータの側にコンピュータ・ビジョンやフィー とをゆっくりやるよりも、単純なことを高速に行う方 ドバックのための物理エンジンなどの多大な計算処 が、人間にとって遥かに有益な結果を生み出すこ 理が必要とされる。物理空間に埋め込まれたユビ とに気づき始める。セル・オートマトンやカオス系 キュタスなミニマム・インターフェイスとは、実は「マ のプログラムが、高速な計算によってそのプログラ キシマム・コンピュテーション」のことなのだ。 ムサイズからは想像もできない程の多様な結果を openFrameworksでアドオンとして提供されている 生み出すように、グラフィックスやゲームだけでなく、 OpenCVやBox2dは、このマキシマム・コンピュテ 理解や知能にとっても、速度が最も重要だったのだ。 ーションによるミニマム・インターフェイスのための 基本的かつ強力なツールである。 実際、Googleの多くのプログラムは、この「単純 なことを高速に行う」という考えに基づいてデザイ 2001年に『ニューメディアの言語』 を出版して一躍 ンされており、さらに今日は、スティーブン・ウルフ 有名になった、UCSD視覚芸術学科教授のレフ・マ ラムのWolfram|Alpha のように、アルゴリズミック ノヴィッチが現在率いる 「Software Studies Initiative」 な計算によってユーザーからのメッセージに反応す には、18台のクアッドコアPCでドライブされる、2 296 | 297 億8672万ピクセル、9.7 m×2.3 mの超高速高解 こうした大量かつ複雑なデータを、コンピュータの 像度大画面ディスプレイがあり、そこで彼らは生態 能力をフルに生かすことで簡略化せずに視覚化し、 学や博物学の手法とコンピュータによる計算を結 複雑なものを複雑なまま人間の理解やコミュニケ びつけながら、映画やアニメーション、マンガなど ーションに繋げていくための、豊富な事例(実験) の映像文化のカルチュラル・アナリシスを行なって が掲載されている。これらの表現の中核にあるの いる。例えば、既に50,000タイトル、120万ペー が、高速な「計算」による量やスケールのダイナミ ジのマンガがデジタイズされ、そのデータをスーパ ックな生成と、そこから生まれる新たな「美学」で ーコンピュータを用いて計算的に分析することで、 ある。それこそが、従来の人間中心主義のデザイ 世界最大の大規模ディスプレイでリアルタイムに表 ンとは逆に、コンピュータの側から人間の知覚や 示する。計算の可能性は、科学技術のみならず、 理解可能性の限界に挑戦し、わかりやすさや使い 人文学の世界でも広く着目され始めてきた。 やすさそのものの概念を変革しようとする、21世紀 の「 計 算 情 報 デ ザイン(ComputationalInformation 30億塩基対のヒトゲノム、400億のウェブページ、 Design) 」の本質なのだ。複製と検索の時代は終焉 2000億の銀河系の星の数など、身の回りには、 を告げ、今まさに「DBC(Design by Computations, 計 膨大というしかない大量のデータが 算によるデザイン)」による生成の時代が花開こうとし れている。 「視覚的複雑性」 や「情報美学」 のサイトには、 ている。 参考文献 1 Design by Numbers, http://dbn.media.mit.edu/ 2 アラン・ビアマン『やさしいコンピュータ科学』 アスキー,1993. 3 ジョン前田『Design By Numbers̶デジタル・メディア のデザイン技法』ソフトバンク,2001. 4 Processing 1.0, http://processing.org/ 5 Casey Reas, Ben Fry「Processing: A Programming Handbook for Visual Designers and Artists」 The MIT Press, 2007. 6『20世紀コンピュータ・アートの軌跡と展望 ̶現代アルゴ リズム・アートの先駆者・現代作家の作品・思想̶』 多摩美術大学, 2006. 7 J. David Bolter, Richard Grusin「Remediation: Understanding New Media」The MIT Press, 1999. 8 ロバート L.フォワード『竜の卵』ハヤカワ文庫468, 早川書房,1982. 9 久保田晃弘他『GUIを再発明する:ポスト・ウィンドウ・ インターフェイス序論』多摩美術大学研究紀要 第23号, 2008. http://www.idd.tamabi.ac.jp/~kubotaa/research/ post-window-final.pdf Wolfram|Alpha, http://www.wolframalpha.com/ minimum interface, http://minimum.ycam.jp/ Lev Manovich「The Language of New Media」 The MIT Press, 2001. Software Studies, http://lab.softwarestudies.com/ visual complexity, http://www.visualcomplexity.com/vc/ information aesthetics, http://infosthetics.com/a 計算によるデザイン―インタラクションを超えて Appendix Xcodeのテンプレートを使用せずにプロジェクトを作成する方法 本書の解説では、openFrameworksのプロジェクトをXcodeを利用して新規に作成する際には、 本書の公式サイトhttp://openframeworks.jpからプロジェクトのテンプレートをダウンロードし、 新規プロジェクトを作成するという方法を紹介しています。このXcodeのテンプレートを用いる方 法が最も簡便な方法なのですが、将来openFrameworksがバージョンアップした際などには、そ のまま使用することができなくなる可能性もあります。 そこでこのAppendeixでは、Xcodeのテンプレートを使用することなく空のプロジェクトを利用し て、新規に自分用のプロジェクトを生成するという方法を紹介します。この方法は、将来 openFrameworksがバージョンアップしたとしてもそのまま継続して使用可能です。 以下、既存の空プロジェクトを利用して、「myNewApp」という名称で新規にプロジェクトを生成 する手順について解説します。 新規プロジェクトの作成手順 1. 空プロジェクト(emptyProject)をコピーする まずはじめに、あらかじめ用意されている空のプロ ジェクトテンプレート「emptyExample」をフォルダ ごとコピーします。空のプロジェクトフォルダは、右 図の場所に格納されています。 [openFrameworksのフォルダ]/apps/examples/ emptyExample このコピーしたフォルダを、myAppsフォルダ以下 にペーストします。 [openFrameworksのフォルダ]/apps/myApps/ emptyExample 2. フォルダ名を変更する コ ピー し たemptyExampleフォル ダ の 名 称 を 「myNewApp」に変更します。 3. プロジェクトファイル名を変更する プロジェクトのフォルダ内にある、Xcodeのプロジ ェクトファイルの名称を「myNewApp.Xcodeproj」 に変更します。 298 | 299 4. ターゲットの名称変更 Xcodeのプロジェクトファイルをダブルクリックして、 Xcodeを起動します。Xcodeの左コラムにある「グ ループとファイル」の一覧の中の「ターゲット」の項 目を拡げます。中には「emptyExample」という名 称でターゲットファイルが入っています。この名称 を変更します。ターゲットファイルのアイコンを右ク リックして、「名称変更」を選択します。するとファ イル名を変更できるようになるので、 「myNewApp」 に名称を変更します。 5. 実行ファイル名のリセット まだこの状態では、実行するアプリケーションのフ ァイル 名(.app)が「emptyExampleDebug.app」 になってしまっています。これを解消するには、ツー ルバーの「概要」のプルダウンメニューを操作しま す。プルダウンの項目の「アクティブな構成」を一度 「Release」にした後で、もう一度「Debug」に戻し ます。この操作によって、アプリケーションの名称が 「myNewAppDebug.app」に変更されます。 6. ビルド これで全ての設定は完了です。ツールバーから「ビ ルドと実行」を選択してきちんとプログラムがコンパ イルされるか確認してください。ビルドが完了する と、myNewAppDebugという名称でプログラムが 実行されるはずです。この空プロジェクトのsrcフォ ルダ内の「testApp.h」や「testApp.cpp」をエディ タで編集してプログラムを作成していきます。 Appendix Index 人名 draw Ben Fry 13, 293 Casey Reas 13, 293 drawString 46, 84 176 Chris O'Shea 16 E Damian Stewart(YesYesNo) 19 eventsExample Fannie White 17 Golan Levin 17 F Graffiti Reserch Lab 18 FFT 270 Jaap Blonk 17 float 60 Joel Gethin Lewis 19 fontsExample 32 Lawrence Hayhurst 17 fontShapesExample 32 Lia 20 for文 Mehmet Akten 15 FreeImage Steven Benders 17 FreeType Theodore Watson 13 Tmema 18 G Zach Gage 20 getPixels Zachary Lieberman 13 getStringBoundingBox 魚住 剛 21 GLUT ジョン前田 292, 293 多田ひと美 21 真鍋大度 19 30 66 13, 170 13 173, 183 176 147, 289 grabFrame 183 grabScreen 173 I 数字・記号 #define 96, 186 2値化 231 if文 86 imageLoaderExample 31 imageSaverExample initGrabber A 32 183 int 60 iPhone 12 addons 27 advancedGraphicsExample 30 apps 28 L audioInputExample 30 libs audioOutputExample 31 loadFont 176 28 audioReceived 163 loadImage 173 audioRequested 153 loadMovie 180 B M bool 60 main.cpp 42, 139 Max/MSP 21, 123, 266 MITメディアラボ 292 mouseDrugged 105 60 mouseMoved 105 CMYK 52 mousePressed 105 C言語 130 mouseReleased 105 mouseX 103 mouseY 105 C C++ 13, 130 char D DBN(Design by Numbers) DIWO 292 5, 23, 123, 293 300 | 301 movieGrabberExample 33 moviePlayerExample 33 O setup 45, 84 sin波 31, 150 ofBackground 53 soundPlayerExample 31 ofCircle 49 soundPlayerFFTExample 31 ofEllipse 49 ofEnableAlphaBlending 56 T ofGetHeight 64 testApp.cpp 42, 142 ofGetWidth 64 testApp.h 42, 142 ofImage 169 textureExample ofLine 46 textureScreengrabExample ofRandom 76 TrueTypeフォント ofRect ofSetBackgroundAuto ofSetColor ofSoundPlayer ofSoundStream ofSoundStreamSetup ofTriangle ofTrueTypeFont oF Unofficial 53 163 150, 161 U UML UMLクラス図 update 50 174 23 ofVideoGrabber 181 177 vector(動的配列) 25 Visual Studio 2008 25 X Xcode ofxOpenCv 211, 224 Xcodeテンプレート OpenCL 15, 124 OpenCV 224, 275 22 アドオン openFrameworks公式サイト 22 アニメーション 28 アルファチャンネル アンチエイリアス 色 インクルードガード P Processing public 56 174 52 186 141 インスタンス化 132, 137, 187 135 インタラクション 13, 177 52 75 49 音 148 オブジェクト指向 170 103 円 オブジェクト RtAudio 83 インクルード文 R RGBA 211 135 Q RGB 27 13, 293 インデックス QuickTime 24, 29, 131, 298 あ openFrameworksフォーラム 13, 289 204 Visual C++ 211, 239 private 141 46, 84 V ofxBox2d other 141 153 213 OpenGL 33 174 47 116 ofxVectoreMath ofVideoPlayer 32 130 130, 184 オプティカルフロー 275 音響合成 150 13 か S saveImage 173 カウンタ変数 66, 72 画像ファイル 169 カプセル化 135 index 関数(function) 44 ピクセル(pixel) 41 偽文 84 描画関数 46 クラス 132 描画色 53 継承 145 ビルド ゲッター 191 フォントデータ 174 コンストラクタ 188 物理エンジン 211 コンパイラ 24 プライベート変数 プリプロセッサ命令 さ 29 191 141, 186 フレームレート 84 サウンドの入力 160 フレームワーク 12, 13 サウンドファイルの再生 163 プロジェクト構造 130, 145 座標(coordinate) 41 プロパティ 座標系 41 変数(variable) 59 三角形 50 宣言 60 三原色 52 代入 60 サンプリング 149 サンプリング&プレイバック 150 131 ま 四角形 47 マウスの位置 103 四則演算 58 摩擦係数 113 摩擦力 112 重力 112 条件式 86 メインループ関数 条件分岐 86 メソッド 45 メソッドの追加 初期化関数 新規クラスの作成 新規プロジェクト 真文 184 191 セミコロン 47, 97, 141 添字 75 た 楕円 49 代入演算子 60 直線 46 直線運動 84 データ型 60 定数 96 統合開発環境 24 動画のキャプチャ 181 動画の再生 177 透明度 56 は 背景色 53 配列(Array) 74 波形の生成 150 パラメータ 47 引数(Argument) 45 302 | 303 185, 194, 198 45 42, 133, 184, 298 84 セッター 戻り値 46 131 ら ライブラリ ラジアン 乱数 13 150 76 著者略歴 田所 淳[たどころ あつし] 1972年、千葉県生まれ。慶應義塾大学政策メディア研 究科修了。多摩美術大学情報デザイン学科情報芸術 コース非常勤講師。千葉商科大学政策情報学部、政 策情報研究科非常勤講師。アルゴリズムを用いた音響 合成による音楽作品の創作、ラップトップコンピュータ を用いたインプロビゼーション(即興演奏)などを行う。 講義では、サウンド・アート、ソフトウェア・アートなど の制作のためのワークショップを担当。使用するソフト は、openFrameworks、Processing、Flash、Max/ MSP、SuperColliderなど。講義資料は全てWebサイ トhttp://yoppa.org/で公開している。また受託業務とし て、Flashサイトを中心としたWebサイトのデザインやプ ログラミングを行う。 比嘉 了[ひが さとる] 1983年生まれ。多摩美術大学大学院デザイン専攻情 報デザイン領域修了。プログラマー/アーティスト。自 作のソフトウェアを用いたサウンド・パフォーマンスやソ フトウエア・アートの制作を行なう。主な展覧会に 「OPEN SPACE 2008」 (2008年, NTTインターコミュニ ケーション・センター)、 「scopic measure #08」 (2008 年, 山口情報芸術センター)などがある。 http://structor.jp/ 久保田晃弘[くぼた あきひろ] 1960年生まれ。東京大学大学院工学系研究科博士課 程修了/工学博士。多摩美術大学教授。ハイブリッド な自作楽器を用いた音響映像作品の制作と演奏を通じ て、デジタル、ノイズ、アルゴリズム、即興、計算、生 成などに関する考察を続ける。近年、細胞や生体を素 材としたバイオメディア・アートに関するプロジェクトを開 始。近作に 『マテリアルAV―共鳴するインターフェイス』 (ntt/icc, 2003) 、 『コードの技法:弦楽四重奏曲第2番 Reflective Intervals 』 (ZAIM Yokohama, 2008) 、 『純 粋φ―Abstract Painterly Interface』 (YCAM, 2008) 、 著書に『消えゆくコンピュータ』 (1999) 『 、ポスト・テクノ (ロ ジー) ミュージック』 (2001) 『 、デザイン言語』 (2002) 『 、改 訂新版 200ジャズ語事典』 (2007) 、 『創造性の宇宙̶ 創世記から情報空間へ』 (2008)などがある(共著含) 。 http://homepage2.nifty.com/~bota/ Beyond Interaction ―メディアアートのためのopenFrameworksプログラミング入門 2010年2月24日 初版第1刷発行 著者 田所 淳・比嘉 了・久保田晃弘 発行人 籔内康一 発行所 株式会社ビー・エヌ・エヌ新社 〒104-0042 東京都中央区入船3-7-2 35山京ビル URL: www.bnn.co.jp E-mail: [email protected] Fax: 03-5543-3108 印刷・製本 シナノ印刷株式会社 デザイン sign デザイン協力 赤波江春奈 翻訳協力 岡本圭加 編集 村田純一 ※本書の内容に関するお問い合わせは、E-mailまたはFaxにてご連絡ください。 ※乱丁本・落丁本はお取り替えいたします。 © 2010 Atsushi Tadokoro, Satoru Higa, Akihiro Kubota Printed in Japan ISBN978-4-86100-670-8
© Copyright 2024 Paperzz