Go to Contents Go to Java Page
Project Looking Glass
 
 

はじめての LG3D プログラミング

- LG3D で Hello World! その 3 アニメーション編 -

 
 

今までの Hello, World は画像ファイルを使用して、それを表示させるという方法をとっていました。しかし、画像ファイルを用意するのはちと面倒です。

そこで、今回は動的に表示するイメージを作ってしまいましょう。ところで、動的にイメージが作れるのだったらということで、簡単なアニメーションにも挑戦してみました。

今回使用した LG3D のバージョンは Release-0.7.0 です。

  1. 動的にイメージを作る
  2. アニメーションに挑戦
  3. おまけ その 1
  4. おまけ その 2
  5. おわりに

 

 
 

動的にイメージを作る

 
 

LG3D には System.out.println("Hello, World!"); のように簡単に文字列を出力することはできません。出力できるのは、テクスチャすなわちイメージだけです。

それじゃというわけで、イメージを作ってしまいましょう。イメージを動的に作るにはいろいろ方法がありますが、一番基本的な BufferedImage クラスを使ってみます。

サンプルのソース HelloWorld6.java

イメージを作る部分を下に示します。

    private BufferedImage createImage() {
        // イメージの作成
        BufferedImage image = new BufferedImage(200, 25, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = (Graphics2D)image.getGraphics();
 
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 
                           RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g.setColor(new Color(192, 64, 64));
        g.setFont(new Font("Serif", Font.BOLD, 28));
        g.drawString("Hello, World!", 0, 20);
        g.dispose();
 
        return image;
    }

まずは BufferedImage オブジェクトを作成します。ここで作成しているのは 200 × 25 pixel で透明を使用できるイメージです。第 3 引数の TYPE_INT_ARGB は int の中にアルファを加えた ARGB の色情報を格納することを示しています。

生成するだけだと何もないイメージなので、そこに書き込んでいきます。そのためには Grphics オブジェクトを使用します。今までダブルバッファリングを自分で行われた方にはすぐ分かると思いますが、やり方はまったく同じです。

次の setRederinghHint メソッドは描画するときの設定を行うために使用します。ANTIALIASING というのは、輪郭の部分を濃淡で表すことによって見た目が滑らかになるような手法です。

そして、色を設定し、フォントを指定して、drawString メソッドを使用してイメージに文字を書き込みます。最後に使用した Graphics オブジェクトを廃棄しておきます。

これで、Hello, World! と書かれたイメージが作成できました。

さて、問題はここからです。イメージをどのように表示しましょうか。

今までは ImagePanel クラスというお手軽クラスを使用してきましたが、それを使うわけにはいきません。というのも、ImagePanel クラスは画像をファイルからしか読み込まないようになっているからなのです。

それでは、どうしましょう。

前回、Shape3D クラスなど表示できるクラスには Appearance クラスという色や材質などを保持したクラスを保持していることを説明しました。この Appearance クラスはその他にテクスチャも保持しています。

テクスチャというのは 3D 物体に貼りつけるイメージのことです。テクスチャをポリゴンに貼りつけることで、モデリングをすると複雑になってしまう形状も、擬似的に表すことができます。たとえば、デコボコを表すために下の図のようにデコボコの画像を貼りつけることで擬似的にデコボコを表してしまいます。

Texture Mapping
テクスチャの貼り付け (Texture Mapping)

このような技法をテクスチャマッピングといい、CG に欠かせない技術となっています。

先ほどのイメージもこのテクスチャとして物体に貼りつけてしまおうというわけです。

それでは、まずテクスチャを作ってみます。LG3D ではテクスチャを org.jdesktop.lg3d.sg.Texture クラスで表します。直接 new することもできるのですが、ここではユーティリティクラスの org.jdesktop.lg3d.sg.utils.image.TextureLoader クラスを使用してみましょう。

        BufferedImage image = createImage();
 
        TextureLoader loader = new TextureLoader(image);
        Texture texture =  loader.getTexture();
 
        Appearance appearance = new SimpleAppearance(1.0f, 1.0f, 1.0f, 1.0f,
                                                     SimpleAppearance.ENABLE_TEXTURE 
                                                     | SimpleAppearance.DISABLE_CULLING);
        appearance.setTexture(texture);
            
        float width = texture.getWidth() * UNIT_TRANS_FACTOR;
        float height = texture.getHeight() * UNIT_TRANS_FACTOR;
        
        FuzzyEdgePanel panel = new FuzzyEdgePanel(width, height, EDGE, appearance);

TexutreLoader オブジェクトを生成するにはコンストラクタでイメージを指定します。その他にも、いろいろな方法で生成できるのですが、それについては Javadoc をご覧ください。

TextureLoader オブジェクトが生成できたら、Texture オブジェクトを getTexture メソッドを使用して取得します。

さて、Texture オブジェクトは生成できました。次はこれを Appearance オブジェクトに設定します。

前回は Appearance オブジェクトを ImagePanel オブジェクトから取得させましたが、ここでは直接生成してみます。Appearance クラスを直接使用してもいいのですが、使い方が簡単な org.jdesktop.lg3d.utils.shape.SimpleAppearance クラスを使ってみました。

SimpleAppearance クラスのコンストラクタは第 1 から第 4 引数が RGBA の色情報で、次に SimpleAppearance オブジェクトのタイプを指定します。ENABLE_TETURE はテクスチャを使えるようにすることですが、DISABLE_CULLING はこういうものだと思っていてください。

次に、setTexture メソッドを使用して、生成した SimpleAppearance オブジェクトにテクスチャを持たせます。

前回は ImgePanel クラスを使用しましたが、今回はよりプリミティブな FuzzyEdgePanel クラスを使いました。FuzzyEdgePanel は Lg3dHelp で使われているクラスで、パネルの端の部分がぼやけたような感じになったパネルです。ぼやける量はコンストラクタの第 3 引数で指定できますが、ここでは EDGE を 0 にしているのでボケません。

後は前回と同じです。

FuzzyEdgePanel オブジェクトを Component3D オブジェクトに addChild して、それを Frame3D に addChild します。

実行すると下の絵のようになります。

HelloWorld6 の実行結果
HelloWorld6 の実行結果

事前にイメージを作らなくてもちゃんと表示することができました。

 

 
 

アニメーションに挑戦

 
 

せっかく動的にイメージを表示することができたので、やってみたいことがあります。

それはアニメーションなんです。アニメーションは異なるイメージを次々と表示させれば実現できます。

キャラクターが動くようなアニメーションでもいいのですが、まずは文字を変化させることをやってみます。

サンプルのソース HelloWorld7.java

アニメーションをするには定期的に繰り返し処理を行わなければならないのですが、それには java.util.Timer クラスを使用しました。java.util.TimerTask クラスを派生させたクラスに繰り返し行いたい処理を記述します。

    public void startAnimation() {
        image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
 
        TimerTask task = new TimerTask() {
                public void run() {
        	    // イメージを生成 & 描画
                    Graphics2D g = (Graphics2D)image.getGraphics();
 
                    g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 
                                       RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
                    g.setBackground(new Color(0, 0, 0, 0));
                    g.clearRect(0, 0, WIDTH, HEIGHT);
                    g.setColor(Color.WHITE);
                    g.setFont(new Font("Serif", Font.BOLD, 28));
                    
                    g.drawString(messages[index], 10, 25);
                    g.dispose();
  
                    // 次に描画する文字列のインデックスを処理しておく
                    index++;
                    if (index >= messages.length) {
                        index = 0;
                    }
 
                    // テクスチャの設定
                    TextureLoader loader = new TextureLoader(image);
                    appearance.setTexture(loader.getTexture());
                }
            };
 
        Timer timer = new Timer();
        timer.schedule(task, 0L, 5000L);
    }

BufferedImage オブジェクトを生成して描画するのは先ほどと同じです。しかし、BufferedImage オブジェクトは使い回しをするので、前回描いたものをクリアする必要があります。それを clearRect メソッドを使用して行っています。

描画する文字列はフィールドに定義してあります。

    private static final String[] messages = {"Hello, World!", "Hello, Tiger!",
                                              "Hello, Java3D!", "Hello, JAI!",
                                              "Hello. LG3D!!"};

できたイメージからテクスチャを作成して、Appearance オブジェクトに設定するのも HelloWorld6 クラスと同じです。

最後に Timger オブジェクトを生成して、5 秒ごとにタスクを繰り返すようにします。

これで実行すれば、Hello, World! から順々に文字列が表示されるはずです。

ところが、実行すると例外が発生してしまいました。

Exception in thread "Timer-0" javax.media.j3d.CapabilityNotSetException: Appeara
nce: no capability to set texture
        at javax.media.j3d.Appearance.setTexture(Unknown Source)
        at org.jdesktop.lg3d.sg.internal.j3d.j3dwrapper.Appearance.setTexture(Ap
pearance.java:484)
        at org.jdesktop.lg3d.sg.Appearance.setTexture(Appearance.java:491)
        at samples.HelloWorld7$1.run(HelloWorld7.java:104)
        at java.util.TimerThread.mainLoop(Timer.java:512)
        at java.util.TimerThread.run(Timer.java:462)

CapabilityNotSetException 例外のメッセージを見てみるとテクスチャを貼ることができないと書いてあります。

えー、でもさっきまでテクスチャ貼れてたじゃないですか。どうしてなんだろう。

SimpleAppearance クラスと Appearance クラスの Javadoc を見てみたら Appearance クラスの定数に ALLOW_TEXUTRE_WRITE なんてのがありました。これだ、と思ってソースに加えてみました。

        appearance = new SimpleAppearance(1.0f, 1.0f, 1.0f, 0.0f,
                                          SimpleAppearance.ENABLE_TEXTURE 
                                          | SimpleAppearance.DISABLE_CULLING
                                          | Appearance.ALLOW_TEXTURE_WRITE);

ところがこれでもだめです。

しかたがないということで、テクスチャ貼り替えをしていると思われる部分を lg3d-core のソースの中から探して見ました。そうしたら、コンストラクタで ALLOW_TEXTURE_WRITE を指定するのではなくて、Appearance#setCapability メソッドで指定するのだそうです。しかし、それは気づかないと思うんですが...

        appearance = new SimpleAppearance(1.0f, 1.0f, 1.0f, 0.0f,
                                          SimpleAppearance.ENABLE_TEXTURE 
                                          | SimpleAppearance.DISABLE_CULLING);
        appearance.setCapability(Appearance.ALLOW_TEXTURE_WRITE);

こうすることで、ちゃんと実行することができました。

HelloWorld7 の実行結果

 

 
 

おまけ その 1

 
 

せっかくアニメーションまでできたのですから、もう少しよくばって何か違うものを作ってみましょう。今までのソースを流用できて、簡単に作れるものがいいですね。

というわけで、ディジタル時計を作ってみます。

サンプルのソース LgClock.java

時計を作るといっても HelloWorld7 クラスとほとんど同じで、描画の部分が異なるぐらいです。

変更した部分は赤字で示してみました。

    public void startAnimation() {
        image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
        builder = new StringBuilder();
        formatter = new Formatter(builder);
 
        TimerTask task = new TimerTask() {
                public void run() {
 
                    Graphics2D g = (Graphics2D)image.getGraphics();
 
                    g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 
                                       RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
                    g.setBackground(new Color(0, 0, 0, 0));
                    g.clearRect(0, 0, WIDTH, HEIGHT);
                    g.setColor(Color.WHITE);
                    g.setFont(new Font("Serif", Font.BOLD, 20));
                     
                    builder.setLength(0);
                    formatter.format("%1$tY.%1$tb.%1$td %1$tH:%1$tM:%1$tS",
                                     System.currentTimeMillis());
                    g.drawString(formatter.toString(), 10, 18);
                    g.dispose();
 
                    TextureLoader loader = new TextureLoader(image);
                    appearance.setTexture(loader.getTexture());
                }
            };
 
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(task, 0L, 1000L);
    }

時間の表示のところに java.textDateFormat クラスを使わないで Tiger で取り入れられた java.util.Formatter クラスを使用しています。詳しくは虎の穴を参照ください。

LgClock
LgClock の実行結果

HelloWorld7 クラスと LgClock クラスで行ったアニメーションは効率がいい方法とはいえません。というのもアニメーションを行うために毎回 TextureLoader オブジェクトを生成し、新しい Texure オブジェクトを生成しているからです。

この方法で 1 秒間に何枚もテクスチャを描きかえるようなアニメーションはつらいのも事実です。

もう少し効率のいい方法もあるのですが、ここでは実現の容易さを優先させてこの方法をとりました。

 

 
 

おまけ その 2

 
 

今までは文字でアニメーションをしていましたが、やっぱりアニメーションといえば絵ですよね。そこで、こんなものを作ってみました。

サンプルのソース Jaggler.java

このサンプルは動的にテクスチャを生成するわけではなく、初期化時にまとめて画像ファイルを読み込んで、テクスチャを生成してしまいます。

    public void createTexture() throws IOException {
        textures = new ArrayList();
 
        for (int i = 0; i < 5; i++) {
            BufferedImage image = ImageIO.read(new File("Juggler" + i + ".png"));
            TextureLoader loader = new TextureLoader(image);
            Texture texture = loader.getTexture();
            textures.add(texture);
        }
    }

テクスチャを作ってしまえば後は定期的にそれを切り替えるだけです。

    public void startAnimation() {
        TimerTask task = new TimerTask() {
                public void run() {
                    appearance.setTexture(textures.get(index));
                     
                    index++;
                    if (index >= textures.size()) {
                        index = 0;
                    }
                }
            };
 
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(task, 0L, 100L);
    }

実行すれば、ジャグリングをしてくれるはず...

Juggler

えっ、誰かに似ている? たぶん、気のせいです ^^;;

 

 
 

おわりに

 
 

3D のプラットフォームで 2D のアニメーションをおこなうというのは本末転倒のような気がしないでもないですが。でも、これが重要なのです。

既存の 2D のアプリケーションは、X11 で描画される画面をキャプチャして、それをテクスチャとしてパネルにはっているのです。つまり、今回やったこととたいして違いがないということ (実際にはいろいろ違いがありますが ^^;; ) なのです。

Java の既存のアプリケーションもこのような技法を使って表示できるのではないかと考えています。専用の AWT の peer を作成すれば、こんなことも可能ではないかと。Sun の LG3D チームはまた別の手法 (Swing の Look and Feel を作成して UI クラスレベルでキャプチャを行う) を考えているようです。

3D ならではのアニメーションについては項をあらためて解説したいと思ってます。

 

今回使用したサンプルはここからダウンロードできます。

 

(Oct. 2004)
(改訂 Feb. 2005 Release-0.61 に対応)
(改訂 Aug. 2005 Release-0.7.0 に対応)

 
 
Go to Contents Go to Java Page