読者です 読者をやめる 読者になる 読者になる

Processingでシーングラフを実現する(1)

花粉、絶好調ですね。遺伝子組み換え杉ってその後どうなったのでしょうか。

さて、今回はProcessingを使って簡単なシーングラフを作成してみたいと思います。

シーングラフについて

シーングラフ(Scene graph)は3Dグラフィックスなどでよく使われる、描画要素を管理するためのデータ構造です。各要素の位置関係をツリー構造で表現することにより、「Aが動いたらBも一緒に動く」のような要素間の位置の連動を簡単に実現することができます。

アニメーションなどを行う際に非常に便利な機能なのですが、残念ながらProcessingではシーングラフが提供されていないため、自前で簡単なものを作成してみることにします。

今回実現する機能

通常のシーングラフは、描画要素の位置だけでなく、光源、カメラ、エフェクト等、様々な属性を管理するのが一般的ですが、今回は実装をシンプルにするため、以下の機能にしぼって実現します。

  • 対象とする描画要素(ノード)は(x, y, z)の座標をもつ3次元要素とする
  • 親ノードを移動させると子供ノードも同様に移動する
  • 親ノードを回転させると子供ノードも同様に回転する
  • 親ノードを拡大/縮小すると子供ノードも同様に拡大/縮小する
  • 各ノードの描画処理はノードのdraw関数をオーバーライドして記述する

Processingには座標系の保存と復元を行うpushMarixpopMatrix命令がありますので、これらを使うことで比較的簡単にシーングラフが作成できます。

作成したシーングラフ用クラス

作成したシーングラフ用クラスはこちらです。

class Node {
  PVector pos = new PVector();
  PVector rot = new PVector();
  PVector scl = new PVector(1.0f, 1.0f, 1.0f);
  ArrayList<Node> childNodes = new ArrayList<Node>();

  void draw() {} // 派生クラスでオーバーライドして描画処理を記述する

  void doDraw() { // ノードに実際に描画を行わせるメソッド
    pushMatrix();

    translate(pos.x, pos.y, pos.z);
    rotateX(rot.x); rotateY(rot.y); rotateZ(rot.z);
    scale(scl.x, scl.y, scl.z);

    for (Node node: childNodes) {
      node.doDraw();
    }

    draw();

    popMatrix();
  }
};

このNodeクラスの派生クラスを作成し、drawメソッドをオーバーライドすることで各描画要素を作成します。各Nodeの親子化は少々乱暴ですがArrayList型のchildNodesを直接操作することで行います。

簡単なサンプルでの動作確認

では実際に簡単なサンプルを作成して動かしてみます。

class Plane extends Node { // 平面を描画するノード
  void draw() {
    fill(128, 255, 128);
    rect(-50, -50, 100, 100);
  }
}

Node root = new Node();
Plane plane = new Plane();

void setup() {
  size(300, 300, OPENGL);

  plane.pos.set(150.0f, 150.0f, 0.0f);
  root.childNodes.add(plane); // rootノードの子供としてplaneノードを登録
}

void draw() {
  background(0);

  plane.pos.x = 150.0f + sin(frameCount * 0.02f) * 100.0f;
  plane.rot.y += 0.05f;
  plane.scl.y = sin(frameCount * 0.03f) * 0.5f + 1.5f;

  root.doDraw();
}

無事動作が確認できました。でも、これだけではシーングラフのありがたみが伝わらないと思いますので、次回はもう少し複雑なサンプルを作成してみたいと思います。