目次


Processing によるデータ視覚化

第 3 回 2 次元グラフィックス、3 次元グラフィックス、物理的な動き、そしてネットワーク

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: Processing によるデータ視覚化

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:Processing によるデータ視覚化

このシリーズの続きに乞うご期待。

連載の第 1 回第 2 回では、基本的な 2 次元グラフィックスからテキスト、画像処理に至るまで、Processing 言語が持つ多くの機能について探りました。最終回となる今回の記事では、連載の締めくくりとして、Processing が 3 次元グラフィックス、照明、そしてネットワークをどのように扱うのかを見て行きます。ネットワークに関しては、視覚化に役立つデータを Processing アプリケーションに提供して処理させる方法を紹介します。

まずは、各種の変換と、変換を行うさまざまな Processing 機能について説明します。

変換

グラフィック・アプリケーションを開発した経験があれば、移動、拡大/縮小、回転などの操作を実装するために必要な線形代数および行列演算についてはご存知のことでしょう。Processing は、このような操作を単一の関数呼び出しで簡単に行えるようにするための関数を提供しており、内部で行われる複雑な行列演算を見えないようにしています。

座標系の移動

移動は単に、左上隅が新しいオフセット位置に来るように座標系を変更するにすぎません。ディスプレイ・ウィンドウ内で新しい演算のためのオフセットを定義するという点で、移動 (translate) は付加的な演算です。例えば、リスト 1 のコードは rect 関数を使って正方形を描画した後、translate を使って座標系の変更を指定します。translate 関数の後に描画される新しい形状は、いずれも定義されたオフセットを適用して表示されます。したがって、以下の例で rect 関数が作成する正方形は、実際には 100, 100, 150, 150 に位置することになります (図 1 の Cell A を参照)。

リスト 1. translate() を使用した座標系の変更
size(200, 200);

rect(0, 0, 50, 50);

translate(100, 100);

rect(0, 0, 50, 50);

拡大/縮小

scale 関数は、その名が示すように、描画演算の結果を拡大/縮小します。例えば、scale の引数に 2.0 を指定すると、オブジェクトは元のサイズより (各次元で) 200 パーセント大きくなります。リスト 2 に、この概念を説明します。このコードによる出力は、図 1 の Cell B に示されています。

リスト 2. 形状を描画する演算結果を scale() によって拡大する
size(200, 200);

rect(0, 0, 50, 50);

translate(75, 75);
scale(2.0);

rect(0, 0, 50, 50);

回転

rotate 関数は、左上隅 (0,0 座標) を中心に形状を回転させます。そのため、形状を回転させると言っても、実際には、形状はその位置で回転しません。リスト 3 は、正方形に回転演算を適用する例です。このコードは最初に正方形を描画し、続いてディスプレイ・ウィンドウに描画される以降の形状に適用する回転演算を指定します。その結果は、図 1 の Cell C に示すとおりです。

リスト 3. rotate() を使用したオブジェクトの回転
size(200, 200);

rect(100, 100, 50, 50);

rotate(PI/16);
rect(100, 100, 50, 50);

オブジェクトをその位置で回転させたい場合には、この目的を考慮して座標系を更新する必要があります。それには、リスト 4 に示すように、translate を使用して座標系を調整してから、回転後の正方形を (更新した x 座標と y 座標で) 再描画します。この場合の結果は、図 1 の Cell D のとおりです。

リスト 4. translate()rotate() の使用
size(200, 200);

rect(100, 100, 50, 50);

translate(100, 100);
rotate(PI/16);
rect(0, 0, 50, 50);

リスト 4 のコードは、オブジェクトの中心を軸としてオブジェクトをその位置で回転させているのではなく、オブジェクトの左上隅を軸に回転させています。一方、正方形を回転させるには、オブジェクトの描画方法を定義するという方法もあります。四角形のデフォルト描画モードは CORNERS です。これは、左上隅の位置が rect の x および y パラメーターによって定義されることを意味します。描画モードを CENTER (この場合、rect の x および y パラメーターが中心を定義) に変更すれば、正方形を描画して、その中心を軸に回転させることができます。この方法をリスト 5 に記載します。このコードによる結果を示しているのは、図 1 の Cell E です。

リスト 5. translate() と、中心を軸とした rotate() の使用
size(200, 200);
rectMode(CENTER);

rect(100, 100, 50, 50);

translate(100, 100);
rotate(PI/16);
rect(0, 0, 50, 50);

ここで、以上の概念を適用して、スピログラフによる幾何学模様のような画像を作成してみます。この例では、正方形をその中心を軸に回転させますが、正方形を塗りつぶすことはしません。中心を軸にして 16 回、正方形を回転させると (リスト 6 に記載する単純なアプリケーションを参照)、図 1 の Cell F に示す結果となります。

リスト 6. 移動と回転の面白い例
size(200, 200);
rectMode(CENTER);
noFill();
translate(100, 100);

for (int i = 1 ; i < 16 ; i++) {
rotate( (PI/16)*i );
rect(0, 0, 100, 100);
}

Processing には、行列演算の複雑さを隠し、代わりに拡大/縮小や回転などの有用なグラフィック操作を行う単純な関数が揃っています。

図 1. リスト 1 からリスト 6 のコードによる画像
異なる演算の結果を示すコラージュ。各コード・リストによる移動 (translate)、拡大/縮小 (scale)、回転 (rotate)、移動/回転 (translate/rotate) の結果が示されています。
異なる演算の結果を示すコラージュ。各コード・リストによる移動 (translate)、拡大/縮小 (scale)、回転 (rotate)、移動/回転 (translate/rotate) の結果が示されています。

変換を管理する

基本的な変換について説明したところで、今度は複数の変換を適用してオブジェクトを描画するときに役立つもう 1 つの方法について詳しく説明します。変換関数が呼び出されると、以降の形状の描画には、その変換が適用されることを思い出してください。さらに変換を追加で実行することも可能で、こうした追加の変換は、その以前の演算で定義された現行の行列に適用されます。例えば、translate を別の translate のコンテキスト内で呼び出すと、そのコンテキスト内で呼び出された 2 番目の translate は、最初の座標系ではなく、1 番目の translate の座標系を基準にします。内部座標行列は、pushMatrix の呼び出しによって保存し、popMatrix を使用して復元することができます。これらの関数はスタック・セマンティクスを使用するため、形状に対して複数の変換を重ねて適用した後、これらの変換の適用を取りやめて元の行列を復元することができます。

一例として、リスト 7 に pushMatrixpopMatrix を使用したコードを記載します。このコードは、初期化の後、現行の座標行列をスタックにプッシュします。これに続き、translate を使用して、ディスプレイの中心を基準とした新しい座標系を作成し、この新規行列を保管します。次に、この行列を回転させてから保管し、translate で現行の座標系から新しいオフセット位置に移動させた後 (これは、新しい座標系を作成して回転させた後の行列からのオフセットです)、拡大して赤い楕円を描画します。今度は、描画前に行った拡大と移動を元に戻すためにスタックからポップした後で、緑の四角形を描画します。再びスタックからポップして、青い四角形を描画します (この場合、回転はもはや適用されていません)。最後に元の行列をポップして、グレーの正方形を描画します。この手法では、複数の変換を重ねて適用すると同時に、ディスプレイの他のオブジェクトには以前に使用した行列を復元することができます。リスト 7 で使用しているインデントは、読みやすくするためのもので、必要ではないことに注意してください。

リスト 7. 行列の保存と復元
size(200, 200);
rectMode(CENTER);
noFill();
smooth();
strokeWeight(2);
colorMode(RGB, 100);

pushMatrix();

translate(100, 100);
pushMatrix();

rotate(PI/4);
pushMatrix();

translate(20, 20);
scale(2.0);
stroke(255, 0, 0); // Red
ellipse(0, 0, 50, 10);

popMatrix();
stroke(0, 255, 0); // Green
rect(0, 0, 50, 25);

popMatrix();
stroke(0, 0, 255); // Blue
rect(0, 0, 75, 50);

popMatrix();
stroke(128, 128, 128); // Gray
rect(0, 0, 50, 50);

図 2 に、リスト 7 のコードによる結果を示します。

図 2. pushMatrixpopMatrix のデモンストレーション
中央に青い四角形、その内部にある回転した緑の四角形、そして緑の四角形と接触する回転した赤い楕円を示すスクリーン・ショット

3 次元グラフィックス

次は、Processing が 3 次元用に提供している API を探りながら、視覚化を 3 次元 (2 次元平面上) に拡張します。さらに詳しい内容について、必ず「参考文献」を参照してください。

リスト 8 に記載するサンプル・コードは、box 関数を使ってディスプレイ・ウィンドウにオブジェクトを作成する単純な 3 次元プログラムです。その名前が示唆するとおり、box 関数はディスプレイ内にボックスを作成します。サンプル・コードに示されているように、作成されるボックスは立方体ですが、いくつかのパラメーター (幅、高さ、奥行き) を追加するだけで、box 関数を使って直方体を作成することもできます。

この関数以外に、このコードで初めて登場した要素は、2 つの rotate 関数だけです。これらの関数では、特定の軸を中心にオブジェクトを回転させることができます。1 番目のボックスは、y 軸を中心に回転し、2 番目のボックスは x 軸を中心に回転します。回転のパラメーターは、特定の軸に対するマウスの位置から判断されます。マウスの値が読み取られると、-PI から PI までの範囲に (map 関数により) マッピングされます。

リスト 8. マウス・ベースで回転する単純な 3 次元の例
void setup() {
size(200, 200, P3D);
noFill();
smooth();
}

void draw() {
background(0);
  
translate(width/2, height/2, -(width/2));
rotateY(map(mouseX, 0, width, -PI, PI));
stroke(100);
box(150);
  
rotateX(map(mouseY, 0, height, -PI, PI));
stroke(150);
box(75);
}

図 3 に、リスト 8 による出力 (少なくとも、その静的な部分) を示します。

図 3. リスト 8 による静的な出力
立方体の 3 次元ワイヤフレームと、その中心を軸に回転するひと回り小さい立方体の 3 次元ワイヤフレーム示すスクリーン・ショット

さらに、照明を加えることも可能です。Processing には、この作業をいとも簡単にする一連の関数が用意されています。そのうちの 1 つの関数について、これから別の 3 次元オブジェクト作成関数を使って具体的に説明した後、他の関数について説明します。box 関数と同じように、sphere 関数を使用することで、球体をディスプレイ・ウィンドウに作成することができます。sphere の引数は、球体の半径を表します。

リスト 9 に、pointLight 関数を使用した例を示します。この関数は、pointLight の最後の 3 つの引数によって定義された空間内の位置に、光源を作成します。最初の 3 つの引数は、光の色を定義するために使用されます。

リスト 9. 球体と光
size(100, 100, P3D);
background(0);
noStroke();
pointLight(50, 100, 180, 80, 20, 40);
translate(20, 50, 0);
sphere(40);

図 4 に、リスト 9 の単純な Processing アプリケーションによる出力を示します。

図 4. pointLight のデモンストレーション
画像の左側から出現する、陰影のある、部分的に光が当てられた青い球体を示すスクリーン・ショット

pointLight 関数は、比較的単純な照明関数 (おそらく ambientLight 以外) の 1 つですが、Processing では照明をさらに詳細に制御することもできます。例えば、スポットライトを追加する spotLight 関数では大幅な制御が可能で、光の位置、方向、色だけでなく、スポットライト・コーンの角度や中央のバイアスも制御することができます。また、directionalLight 関数を使用すれば、特定の方向に光の焦点を合わせ、光の方向と角度に応じて変化する、より自然な照明を実現することができます。

Processing では、頂点を使用してオブジェクトを作成する、遥かに複雑な手段も使用することができます。この方法で形状を作成することで、オブジェクトに質感を与えられます。Processing のこの側面についての詳細は、「参考文献」を調べてください。

物理的な動き

前のセクションの 3 次元の例では静的なモデルを使用しましたが、発射されたような動作をオブジェクトに与えたいという場合には、このセクションで説明する例をリアルタイムの例に拡張することが可能です。リスト 10 に、大砲から発射されたボックスをシミュレートする単純な例を記載します。運動方程式についてはこの記事では説明しませんが、「参考文献」にその詳細についての参照先を記載してあります。

setup 関数は、ディスプレイ・ウィンドウを作成し、ボックスの開始位置 (起点) を初期化します。draw 関数内では、background を使ってディスプレイをクリアしてから、大砲の向きに対する方向余弦を計算します。次に、時間変数を基に、3 次元のそれぞれに沿ったディスプレイ・ウィンドウ内でのボックスの位置を計算します。面白い効果を追加したいというだけの理由ですが、 x 軸と y 軸を中心に異なる速度でボックスを回転させます。

次に、pointLight 関数を使用して 2 つの光源を作成します。左側には青い光、右側には赤い光を配置します。その後、デバッグを行えるように、3 次元空間でのボックスの現在位置をコンソールに出力します。

ここでようやく、ボックスをディスプレイに配置します。そのために、まずは現行の行列のセットをスタックにプッシュし、それから translate を呼び出してボックスの変位を変更します。こうすることによって、x をウィンドウの右側へわずかにずらし、y 軸を反転させ (Processing の座標系では、y 軸の値は上に向かって小さくなるため)、z を外側に向かって投影することができます (同じく反転させます)。Processing の座標系では、x は右に向かって進み、y は下に向かって進み、-z は遠ざかっていくことに注意してください。最後に、x 軸と y 軸を中心とした回転を適用し、ボックスをディスプレイに配置してから、現行の行列のセットをポップします。

リスト 10. Processing での発射体シミュレーション
float x, y, z;          // Current Position
float velocity = 120.0; // Muzzle velocity;
float alpha = 30.0;     // Angle from y-axis
float gamma = 60.0;     // Angle from x-axis
float g = 9.8;          // Acceleration due to gravity (m/s^2)
float time = 0.0;
float dt = 0.1;
float rotX = 0.0, rotY = 0.0;

void setup() {
size(300, 400, P3D);
smooth();
  x = 0.0; y = 0.0; z = 0.0;
}


void draw() {
  float b, Lx, Ly, Lz;
  time += dt;
  
background(0);
  
  // Calculate cosines for the cannon orientation (static)
  b = cos((90.0-alpha) * 3.14/180.0);
  Lx = b * cos(gamma * 3.14/180.0);
  Ly = cos(alpha * 3.14/180.0);
  Lz = b * sin(gamma * 3.14/180.0);
 
  // Calculate the position of the box at the given time
  x = velocity * Lx * time;
  y = (cos(alpha*3.14/180.0)) + (velocity * Ly * time) - 
	(0.5 * g * time * time);
  z = velocity * Lz * time;

  // Rotate the box around the x- and y-axis.
  rotX += PI/256.0;
  rotY += PI/128.0;

  // Create two light sources (one blue and one red)
pointLight(0, 100, 255, 0, 0, 0);
pointLight(255, 0, 0, 400, 400, 0);

  println("x " + x + " y " + y + " z " + z );

  // Place the box in the display
pushMatrix();
translate(100, 400-y, -z);
rotateX(rotX); rotateY(rotY);
box(90);  
popMatrix();
}

このほんのわずかな量のコードで、回転する発射体の単純なシミュレーションを実装することができました。図 5 に、このシミュレーションの時間ステップを示しますが、リアルタイムの仮想化は、これよりも断然興味深いものになります。

図 5. リスト 10 による出力の一部
リスト 10 のコードでオブジェクトが操作される間、さまざまな時点で抜粋したフレームを示すスクリーン・ショット
リスト 10 のコードでオブジェクトが操作される間、さまざまな時点で抜粋したフレームを示すスクリーン・ショット

ネットワーク

Processing でネットワーク・アプリケーションを開発する方法は、C 言語の標準 API (バークレー・ソケット API) と比べると、Ruby と Python が採っている手法に似ています。ネットワーク関数はライブラリーに実装され、アプリケーション開発用に 2 つのクラスを提示します。1 つはサーバーを作成するときに使用する Server クラス、もう 1 つはクライアントを作成するための Client クラスです。さらに、アプリケーションの開発支援として使用できる一連のイベント・メソッドも用意されています。例えば、サーバー・イベントは、新しいクライアントがサーバーに接続すると呼び出されるコールバック・メソッドで、クライアント・イベントは、サーバーがクライアントにバイトを送信すると呼び出されるコールバック・メソッドです。

リスト 11 に示すクライアントは、ピア Web サーバーと通信して、そのサーバーのソフトウェアを識別します。識別するための手段としては、単純に HTTP HEAD メソッドを使うだけです。このメソッドがクライアントのリクエストに関するメタ情報を返します。例えば、クライアントが Web サーバーからファイルを取得するには通常、HTTP GET リクエストを実行しますが、HEAD リクエストは HTTP メッセージ本体を返す代わりに、リクエストされたファイルのメタ情報 (サイズなど) を返します。興味のある情報を収集するには、これが最も簡単な方法です。

リスト 11 のコードはまず、新しい Client クラスを作成します。その際に定義するのは、接続先のサーバーとポートです (この例で定義しているのは、IBM の Web サーバー・ポートです)。Web サーバーに接続したら、write メソッドを使用して HTTP リクエストを送信します。draw メソッド内では、読み取り可能なバイトがあるかどうかをチェックし、ある場合にはバイトをトークン化して配列に組み込みます。次に、配列を検索して、サーバー・タイプに対応する HTTP レスポンス内のフィールドを探します (Server: Apache のようなフィールド)。サーバー・フィールドが見つかると、次のトークンを発行します。このトークンが、サーバー・ストリング自体となります (この例では、IBM_HTTP_Server)。

リスト 11. 単純なクライアント・ソケットの例
import processing.net.*;
Client myClient;
String inString;

void setup() {
  myClient = new Client( this, "www.ibm.com", 80);
  myClient.write("HEAD / HTTP/1.0\n\n");
}

void draw() {
  if (myClient.available() > 0) {
    inString = myClient.readString();
    String[] tokens = splitTokens(inString, ":\n ");
    for (int i = 0 ; i < tokens.length-1 ; i++) {
      if (tokens[i].equals("Server")) println(tokens[i+1]);
    } 
  }
}

Server クラスは Client クラスと同様のメソッド一式に加え、特定のクライアントとの接続を切断する disconnect メソッド、すべてのクライアントとの接続を切断してサーバーを停止する stop メソッドを提供します。この API についての詳細は、Processing リファレンス (「参考文献」にリンクを記載) を参照してください。

ネットワーク・マッシュアップの作成

もう 1 つのクライアント・ソケットの例として、インターネットからデータを取得して表示するクライアント・ソケットについて見て行きます。この例では、この最終回の他のセクションで学んだ概念を取り入れて、Web サービスからのデータを視覚化します。ソケット・クライアントを使用する目的は、Yahoo! Traffic REST API に接続し、特定の場所の交通情報を抽出するためです (リスト 12 を参照)。リモート・サーバーに接続した後 (Client クラスのインスタンス化の一環)、リクエストを作成します。作成するのは REST リクエストで、ここには通信したいサービス (Yahoo! の MapsService) と併せて、興味の対象である場所の情報 (カリフォルニア州サニーベール) を指定します。リクエストが完了したら、ディスプレイを作成してフォントをロードします。

draw 関数は、2 つのコード・セクションで構成されています。1 つは HTTP レスポンスを受信するセクション (最初に記載されているセクション) で、このセクションでは使用可能な文字を受信した後、現行の結果をトークン化して Title トークンを検索します (実際には <Title> ですが、トークン化の一環として括弧を削除しています)。Title トークンが見つかると、次のストリングが交通情報のタイトルとなります。

次のコード・セクションでは、交通情報ストリングを受信した場合 (受信したかどうかは、ストリング長がゼロでないかどうかで判断することができます)、そのストリングをディスプレイに出力します。前の 3 次元の例と同じように、出力には拡大/縮小および回転を適用して、見た目に面白い出力にしています。その結果、ストリングは無限にゆっくりと回転することになります。

リスト 12. 交通情報データを抽出する単純なクライアント
import processing.net.*;
Client myClient;
String input="";
String displayString="";
float myScale=1.0, myRatioX = 0.0, myRatioY = 0.0;

void setup() {
  myClient = new Client(this, "local.yahooapis.com", 80);
  myClient.write("GET /MapsService/V1/trafficData"+
      "?appid=YdnDemo&city=Sunnyvale&state=CA HTTP/1.1\n");
  myClient.write("Accept: text/html, text/xml\n");
  myClient.write("Host: mtjones.com\n\n");

size(900, 300, P3D);
  PFont font = loadFont("AbadiMT-Condensed-48.vlw");
textFont(font, 48);
frameRate(20);
smooth();
}

void draw()
{
background(100);

  if (myClient.available() > 0) {
    input += myClient.readString();
    
    String tokens[] = splitTokens(input, "<>\n");
    
    for (int i = 0 ; i < tokens.length ; i++) {
      if (tokens[i].equals("Title")) {
        displayString = tokens[i+1];
      }
    }
    
  }
  
  if ((displayString.length() > 0) && (myScale > 0)) {

    myRatioX += (PI/300);
    myRatioY += (PI/460);
    myScale -= 0.005;
 
scale(myScale);
pushMatrix();
translate( 50, height/2, 0);
rotateX(myRatioX);
rotateY(myRatioY);
rotateZ(myRatioY/4);
text(displayString, 0, 0);
popMatrix();
  }
  
}

図 6 に、リスト 12 による結果を示します。この画像は 23 フレーム目で撮ったもので、その動作が始まったばかりの状態です。

図 6. リスト 12 による出力
「Road construction, on I-280 at SARATOGA AVE」というテキストが回転して後ろに傾いているスクリーン・ショット
「Road construction, on I-280 at SARATOGA AVE」というテキストが回転して後ろに傾いているスクリーン・ショット

さらに詳しく調べてください

Processing は可視化のための優れた言語と環境というだけでなく、これは、オープンソース技術での可能性を示す素晴らしい例です。Processing は、エンジニアや科学者がデータの視覚化に利用しているだけではなく、アーティスト、そしてプログラミングとビジュアル・デザインに興味を持つ人々も使用しています。


ダウンロード可能なリソース


関連トピック

  • Processing の詳細、最新バージョンのダウンロード、そして興味深いサンプルとチュートリアルを見つけるには、Processing.org にアクセスしてください。独自のアプリケーションを開発したら、ぜひ OpenProcessing サイトでそのアプリケーションを共有してください。
  • Processing は、2 次元および 3 次元グラフィックスを処理する際の面倒な詳細、特に線形代数を、変換行列の背後に隠してくれます。Processing API リファレンスおよび 3-D チュートリアルでは、3 次元について詳しく学ぶことができます。
  • Processing の最も優れたマニュアルの 1 つとして挙げられるのは、『Processing: A Programming Handbook for Visual Designers and Artists』(Casey Reas、Ben Fry 共著、MIT Press、2004年) です。この本は興味をそそるだけでなく、Processing 言語、ライブラリー、そして機能の大部分をわかりやすく図解して取り上げています。さらに、『Getting Started with Processing』(Casey Reas、Ben Fry 共著、O'Reilly Media、2010 年) もリリースされています。
  • ゲーム開発のための物理シミュレーション入門』(David M. Bourgis 著、オーム社、2001年) では、ゲーム開発の舞台裏で活躍する数学および物理学について、興味深い見方を提供しています。
  • developerWorks podcasts ではソフトウェア開発者のための興味深いインタビューや議論を聞くことができます。
  • Twitter で developerWorks をフォローしてください。
  • オープンソース技術を使用して開発し、IBM の製品と併用するときに役立つ広範囲のハウツー情報、ツール、およびプロジェクト・アップデートについては、developerWorks Open source ゾーンを参照してください。
  • IBM 製品の評価版をダウンロードするか、あるいは IBM SOA Sandbox のオンライン試用版で、DB2®、Lotus®、Rational®、Tivoli®、および WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。
static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=642254
ArticleTitle=Processing によるデータ視覚化: 第 3 回 2 次元グラフィックス、3 次元グラフィックス、物理的な動き、そしてネットワーク
publish-date=02222011