Indyといっしょ Vol.5 プログラム編(2)

おとうさんのための1時間で作るウォークスループログラム講座
村崎 達哉 SoftwareDesign 1996年6月号(技術評論社)

Internet Explorerにオドロイタ

最近一番驚いたのは、マイクロソフトのInternet ExplorerにプラグインするVRMLブラウザです。なんと初期のIndyのWebSpaceより全然高速なのです。Pentium 133MHzで20Mのメモリしか乗せてないマシンで、曲がりなりにもグラフィックスエンジンを積んでるIndyより高速なのです。

これは一体どうなっているのでしょう。 おそらく計算の正確さを犠牲にしたインチキレンダリングをしているに違いありません。噂のDirect3DやDirectXといったものを使用しているのでしょう。

しかしこのインチキは現状のVRMLの精度に十分耐えうる、素晴らしいインチキです。アンチマイクロソフト派を自認する私でも、Bill Gatesのソフトウェアにかける、恐るべき執念には敬意を払わなければなりません。最近では何とブラウザやサーバーもただで配布するというのですから。

正直言って私は、VRMLは今のPCには負荷が大き過ぎて、もう数年しないとインターネット上では、普及しないのでないかと思っていましたが、ExplorerのVRMLの表示速度を見て、考えを変えました。このβ版が正式出荷になれば、どのサイトも先を競ってVRMLデータを採用するでしょう。

ところで、このようなVRMLブラウザを作ることは大変な工数と能力を必要とします。とても個人や数学的な素養のない人には、無理です。と言ってしまったら、今回は書くことが無くなってしまいます。WebSpaceやExploreのようなブラウザは無理でも、ちょっとした3Dビューワーやウォークスループログラムなら、実は誰にでも作れるのです。

そこで今回のテーマは「おとうさんのための1時間で作るウォークスループログラム講座」です。


RapidApp と WorkShop

今回の主要ツールはRapidApp(ラピッドアップ)とWorkShopです。RapidAppはSGIのいわゆるRAD(Rapid Application Development benefits)アプリケーションであり、PCのプログラムにはおなじみの、MSのVisualBasicやBlue Sky SoftwearのWinMaker、BorlandのDelphiのようなものです。UNIXプログラムならUIM/XやXDesignerをイメージすればよいでしょう。

図1 RapidAppのメインウィンドウ 図2 Controlsの画面

WorkShopは、UNIXではめずらしい本格的ソースレベルデバッカです。RapidAppを起動するとライセンスのチェックを試みるダイアログが表示された後に図1のメインウィンドウが表示されます。メインウィンドウは、今はやりのGUIのタブ形式になっており、タブは、

から構成されています。

これ以外に、ユーザ定義のクラスなどを読み込んだり作成したりするとUser Defined Classというタブも追加されます。ボタンやリストボックス、ダイヤルといったアイコンが表示されており、右半分には、現在設定中の要素のプロパティ(属性、最近やたらと耳にする用語で、VBあたりから多用されはじめました。)が表示されるようになっています。これを、リソースエディタと呼びます。

例えばコントロールタブでプロパティとしてactiveCallback、class Name、alignment、labelPixmap、labelString、labelType、recomputeSizeなどの項目が表示されます。この辺は、VisualBasicや、VC++などのビジュアルな開発ツールを使ったことのある人には、違和感なくなじめます。

ただしMotif独特の設定項目があり、細部を設定するようになってくると、XやMotif、ViewKitなどのウィジェットやカジェットの知識が必要になってくるのは仕方がないでしょう。

それではアプリケーション作成の手順を見てみましょう。

        開始
         ↓
    @ウィンドウのタイプを設定
         ↓
    Aコンテナ、コントロールを配置 ←−−−
         ↓                                |
    Bメニューを設定                       |
         ↓                                |
    C必要なクラス、コールバックを生成     |
         |  アプリ生成済みか?            |
         ↓NO                  |YES       |
    Dアプリケーションの生成   |          |
         ↓                    |          |
    Eコールバックの編集  ←−−           |
         ↓                  NO↑          |
    F実行、デバック      −−−−−−−−−
         |OK
         ↓
        完成

これだけではちょっと抽象的過ぎるので、具体例を示して説明しましょう。


RapidAppチュートリアル

RapidAppには、MacromediaDirectorで作ったような、チュートリアルが付いています。このチュートリアルと同じような簡単なプログラムを作ることにします。このプログラムは、テキストフィールドに入力された文字列をボタンを押すことによって、リストボックスに追加していく、簡単なプログラムです。

A) メインウィンドウのウィンドウタブを開いて、SimpleWindowアイコンをクリックし て、ウィンドウを生成します。

B) コンテナタブに切り替えてBullutinBoardを選択すると、BullutinBoardの枠形エリアが現れますので、それをSimpleWindowの中に持っていってクリックします。

なおコンテナと言うのは最近オブジェクト指向やWindowsのOLEなどでよく使われる概念で、この場合はコントロールを含むもの(Container)という意味でしょう。

C) 次にコントロールタブを表示して、テキストフィールドとリストボックスとボタンをさっき生成したBullutinBoardの上に配置していきます。

そのとき、それぞれのコントロールのプロパティをメインウインドウの左側のプロパティエリアで設定していきます。
たとえばテキストの表示を中央よせにしたいときは、alignement(整列)をXmALIGNMENT_CENTERに設定します。(図3)

図3 画面

なおXmの付く変数はMotifの変数です。しかし、当然のことながらMotifで設定できる全てのプロパティをビジュアルに設定できるわけではなく、重要なものだけのようです。

ここまでは、まったくプログラムのことなど考えなくても作業は進められます。しかし、ここからは、クラスを生成したり、コールバック関数(テキストを入力したときや、ボタンを押したときなど、ユーザが何かのイベントを起こしたときに呼ばれる関数)を設定したりしなければなりません。
当然、ある程度のプログラマとしての基礎知識が必要になります。私も、いくらなんでもC++やMotifの知識のない人でも、どんなプログラムでも、RapidAppを使えば作れるなんていいません。
そんな夢のようなツールができてしまえば私も含めてプログラマという種族は地球上から途絶えてしまいます。
どんな便利なツールでも、ある定型の作業を簡単化することはできても、そのツールの想定していない作業を単純化することはできない(村崎)のです。これがプログラムの不完全性の定理です。

D) コントロールのレイアウトが終了したら、必要なクラスを生成しなければなりません。当然C++の基本的な考え方を踏まえた上で、必要に応じて新しいクラスを生成します。

それはどうゆうことかというと、ここでの場合、テキストフィールドやリストボックスはそのまま使えるでしょう。しかし、そのまま使えない場合も状況によってはあります、たとえば、日本語を入力するとその上にルビを振るテキストフィールドや数値入力をすると先頭に「\」マークを付け、3桁毎にコンマを挿入するとかいったものです。

このチュートリアルプログラムでクラスを派生する必要がありそうなのは、ブルティンボードでしょう。ボタンを押したらどうのこうのするという機能は、標準ではブルティンボードのクラスにはありません。

そこでボタンを押したときのコールバック関数を追加しなければなりませんが、それを実現するには、RapidApp標準のBulltinBoard VKComponentクラスから新しいMyWindowというクラスを派生しましょう。BullutinBoardとなっているところをMyWindowと書き換えます。(図4)

図4 新規クラスを派生させているところ

E) 次にボタンを押したときのコールバック関数を生成します。ウインドウのボタンをアクティブにしてします。するとボタンのプロパティがメインウインドウのリソースエディタに表示されますので、activeCallbackのところに関数名addText()を入力します。この例題の場合、他のコールバック関数は重要でないので、これだけにしましょう。

F) そしたら、いよいよアプリケーションの生成です。メニュー「Options/Application」でソース作成ディレクトリとアプリケーション名を設定します。プログラム名は「tutor」にしましょう。(図5)

図5 アプリケーションの生成

それから、メニュー「Project/Generate C++」でソースコードを自動生成します。次のようなソースが生成されます。


Makedepend                SimpleWindowMainWindow.h  ls1.txt
Makefile                  Tutor                     main.C
MyWindow.C                Tutor.cvdb                tutor.idb
MyWindow.h                Tutor.icon                tutor.spec
MyWindowUI.C              cvstatic.fileset          unimplemented.C
MyWindowUI.h              desktop.ftr
SimpleWindowMainWindow.C  icon.fti

G)「Project/Build Application」を選んで、コンパイルしてプログラムを作成します。コンパイルは難無く成功して、Project/Run Applicationを選ぶとプログラムが実行され、今作ったウインドウが見事に表示されます。

さて、実行してみて、肝心なことをわすれているのに気づきました。ボタンを押しても何にも実行されません。
そうです、コールバック関数は生成したけれども、肝心要の中身を編集していないのです。ところが、RapidAppの売りは、ここなのです。

実は再コンパイルすることなくコールバック関数を編集する機能があるのです。これをFix + Continue機能といいます。
これは、RapidAppの機能というよりもデバッガであるWorkShopが賢いのでこんなことができるのです。その編の説明が付属のチュートリアルによく説明されているので、その画面を参照しながら説明します。

H) メニュー「Project/Debug Application」を選んでWorkShopを起動します。デバッガの画面よりRunボタンを押して実行を開始します。

ここで、コールバックを実行すべき操作をしてやります。すなわち、Add Textボタンを押してやるのです。するとどうでしょう。デバッガは設定してもいないブレークポイントで停止します。その関数は

    void VkUnimplimented()
というグローバル関数です。実はここがうまくできていて、実装されていないコールバック関数には、すべてこの関数が定義されているのです。したがって、実装されていないコールバックを呼ぶと、必ずこの関数にひっかかるのです。(図6)

図6 VKUnimplimented( )で自動的にブレークする

I) デバッガのReturnボタンを押します。これは、その親の関数に戻るボタンですが、するとあのMyWindow::addText()のコールバック関数に戻るではないですか。グレート!!
そこで、::VkUnimplimented()はコメントアウトして、次のようにコーディングします。 (図7)

図7 ダイナミックにコールバックの関数を付加できる

コーディングする前に、メニューのFix+Continue/Editを選んでください。


void MyWindow::addText ( Widget w, XtPointer callData )
{
    XmPushButtonCallbackStruct *cbs = (XmPushButtonCallbackStruct*) callData;

    //--- Comment out the following line when MyWindow::addText is implemented:

    // ::VkUnimplemented ( w, "MyWindow::addText" );

    EZ(_scrolledText) << (char*)EZ(_textfield) << "\n";

    //--- Add application code for MyWindow::addText here:

}   // End MyWindow::addText()

ここで << の意味が分からない方は、C++を勉強してください。EZはモチーフの七面倒くさい関数を使わなくてよくするためのイージーな関数です。意味としてはテキストフィールドから取った文字列に改行文字を加えてリストボックスにセットするということです。
なお、コメントから、_scrolledText、_textfield が何かということは分かるでしょう。

さて、編集したら今度は「Fix+Continue/Parse and Reload...」を選んで変更を反映させます。これで完璧です。

J) 実行結果は図8のようになります。

図8 実行結果

それでは、ホンチャンのウォークスループログラムに移りましょう。


Open Inventorの復習

さて、ウォークスループログラム(といってもただの3Dビュアーですが)を作るにはInventorの復習をしなければなりません。なぜInventorかというと、InventorのタブにあるWalkThrough Viwerのクラスを使用するからです。それでは、先回の最後にチラッとだけ紹介した、魔法のプログラムを先回よりは詳しく見てみましょう。

図9 プログラムの結果 例:梨

// viewer.c++
//       シーングラフをファイルから読みだして、
//       シーングラフを構成するプログラム
//
#include <X11/Intrinsic.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/SoDB.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>
#include <Inventor/nodes/SoSeparator.h>

main(int argc, char **argv)
{
    if (argc != 2){
        fprintf(stderr, "Usage: %s datafile.iv\n", argv[0]);
        exit(0);
    }

    Widget mainWindow = SoXt::init(argv[0]);
    if (mainWindow == NULL) 
        exit( 1 );

    SoSeparator *root;

    // ファイルからシーングラフを読む
    SoInput in;
    if (in.openFile(argv[1])) {
        root = SoDB::readAll(&in);
        if (root == NULL) {
            fprintf(stderr, "Couldn't open file %s", argv[1]);
            exit(0);
        }
        in.closeFile();
    }
    else {
        fprintf(stderr, "Couldn't open file %s\n", argv[1]);
        exit(0);
    }

    //  ビュアーを生成する
    SoXtExaminerViewer *viewer = new SoXtExaminerViewer;

    //  シーングラフをビュアーに結合する
    viewer->setSceneGraph(root);

    // シーン全体がイメージに入るようにカメラを設定する
    viewer->viewAll();

    // レンダリング領域を表示に設定する
    viewer->show();

    // メインウインドウを表示し、イベントハンドリング
    // 状態にし、イメージを表示する
    SoXt::show(mainWindow);
    SoXt::mainLoop();
}

何度も言うようですが、これは、驚くべきプログラムです。Open Inventorの凄さを見せ付けてくれます。使い方は、プログラム名をviewerとすると
    viewer  モデルファイル名
です。ファイル名には、VRMLの元データとなったOpen Inventorのivファイルです。拡 張子には.ivを付けます。このファイルは、自由局面やユーザとのインタラクションを 定義できるなど、VRMLよりもさらに高度なファイルフォーマットです。簡単にプログラ ムを説明すると、
    root = SoDB::readAll(&in);
ここで、rootにファイル名で指定したモデルデータを読み込んでおり、
    SoXtExaminerViewer *viewer = new SoXtExaminerViewer;
の行で、様々なインターフェイスを備えた高度なビューワーをクリエイトしています。 実は、このビューワーがInventorの最大の魅力です。このクラスだけで、ウォークスル ーもできれば、座標変換の設定、属性の設定など、とりあえずやりたいことが色々でき てしまいます。C++、オブジェクト指向の偉大な実力の一端を伺い知ることができます。んーファンタスティック!!
    viewer->setSceneGraph(root);
この行でモデルをビューワにセットしています。たった50行足らずのこのプログラムの背後に何万ステップというクラスライブラリのコードがカプセル化されていることを忘れてはいけません。

ところで、なぜこのプログラムを再び持ち出したかというと、それは当然これからのウォークスループログラム作成編で利用するからです。


おとうさんのための1時間で作るウォークスループログラム講座

さて、それでは1時間でウォークスループログラムを作ってみましょう。

A) メインウインドウのWindowタブでVkWindowを、InventorタブでWalk Viewerを選んで乗っけます。(図10、11)[3分経過]

図10 WindowタブでVkWindowを選択したところ 図11 InventorタブでWalk Viewerを選択したところ

B) メニュー「Options/Application」でソース作成ディレクトリとアプリケーション名を設定します。プログラム名は「WalkView」にしました。[6分経過]

C) メニュー「Project/Generate C++」でソースコードを自動生成します。今回はWalk Viwer(SoXtWalkViewer)の派生クラスも自動生成して、SoXtWalkViewerDrivedクラスを作ります。以下に作成されたソースファイルを示します。[10分経過]


#uil.uil#                WalkView                 main.C
Makedepend               WalkView.cvdb            unimplemented.C
Makefile                 WalkView.icon            walkView
SoXtWalkViewerDerived.C  cvstatic.fileset         walkView.idb
SoXtWalkViewerDerived.h  desktop.ftr              walkView.spec
VkwindowMainWindow.C     icon.fti
VkwindowMainWindow.h     ls2.txt

D)「Project/Build Application」を選んで、コンパイルしてプログラムを作成、実行します。このコンパイルでエラーが出ることは想定していません。あたりまえですよね、自動生成したプログラムですから。しかし、IRIXのC++コンパイラはプリプロセッサだから意外と遅い、CPUの割にはかなり遅い![15分経過]

E) 今回は、チュートリアルのようにWorkShopデバッガのFix+Continueメニューを使ったダイナミックにコールバックを編集する手法は取らずに、直接ファイルを編集して、メニューのFile/Openに読み込みルーチンを追加してみます。
当然、それらのコールバックは、SoXtWalkViewerDerived.Cの中の

   void SoXtWalkViewerDerived::openFile(const char *filename)
だろうと、見当が付くので、メニュー「Project/Edit File...」でエディタを起動して、ファイルを編集します。(図12)
ここで、Open Inventorの復習が役に立ちます。

図12 ファイルを編集しているところ


    SoSeparator *root;

    SoInput in;
    if (in.openFile(argv[1])) {
        root = SoDB::readAll(&in);
        if (root == NULL) {
            fprintf(stderr, "Couldn't open file %s", argv[1]);
            exit(0);
        }
        in.closeFile();
    }
    else {
        fprintf(stderr, "Couldn't open file %s\n", argv[1]);
        exit(0);
    }

    viewer->setSceneGraph(root);

の部分をopenFile()に追加してやるのです。[25分経過]

F) コンパイルしてデバッグします。以外と時間がかかって、ちゃんと動くようになるまでには、15分ぐらいかかってしまいました。(図13)また、いろいろなデータで試さなければなりません。結局これも15分かかってしまいました。[55分経過]

図13 プログラムの結果 例:ジェット機

このプログラムには、ファイルの読み込みしか、ユーザの定義関数は入れていません。しかし、これだけで十分な機能を持ったビューアーができます。ワイヤーフレーム表示やテクスチャの表示などもできます。例えばFile/Open...メニューから家屋のモデルデータを読み込めば、ウォークスルーができてしまいます。私は嘘はつきません。


エピローグ

このRapidAppも残念なことに、IRIXでしか使えません。Open Inventorに関しては、数社のセカンドライセンスを取得した会社によって、Windows NTや他のOSでも利用できます。

SGIもソフトウェア部門を別会社にするという話しがあるのですから、ここは、けち臭いことは言わないで、純正のWindows NT版RapidAppなどを作ってくれれば、爆発的に売れるに違いありません。ハードよりソフトウェアで儲ける時代なのですから。私も仕事ですから、もしバーチャルリアリティのようなプログラムを作るのであれば、IRIXは勿論のこと、インストールべースが膨大なWindows版も販売したいと思うのが人情でしょう。だから、RapidAppもマルチプラットフォームならどんなに素晴らしいでしょうか。

予断になりますが、私もこんな連載をやってると「Indy狂」と思われても仕方ありません。いえいえ、1部のMacユーザのようなそんな偏狭な了見は持ち合わせておりません。コンピュータはコンピュータ、いいものがあれば東奔西走、どこへでも足を運ぶつもりです。パーソナルなコンピュータを初めて買ったのは、X68Kでした。あのときはこの歴史的な名機の6万色という発色数(当時NECの98は16色)と68000というCPUに惹かれました。そして現在は、仕事でSolarisやIndy、そしてDOS/Vなどをメインに使っています。ここ数年で一番惹かれたのはNeXTでしたが、買うお金がありませんでした。私はいつも毛色の違ったコンピュータに惹かれてきました。しかし最近は、X68KやNeNTやAmigaなどのような悪臭を放つ個性的なコンピュータが少なくなってきているのがちょっと残念です。

私にとって以前までは、Indyも明らかに匂いをぷんぷんさせてるマシンの一つでした。数年前までは(今も)SUNやHP、IBMのワークステーションが本流であり、IRISは亜流でした。この、CGというアングラでマイナー世界でしか生きていけないIndyという危険なマシーンが私を魅了しました。しかし、何だか最近は、マルチメディア、インターネットで急に注目されて、恋人がメジャーデビューしたような、ちょっとだけさみしいような気がします。とはいえ、連載が終了して、Indyがどこかに行ってしまうと思うと、猛烈にIndyを個人的に購入したい欲求に駆られている今日この頃です。妻よ、買ってもいいですか?

さて、次回は何について書きましょうか。当社もようやくWWW(www.cobalt.co.jp)を設けました。他人のWebの構築を手掛けているのに、自分の会社にはWebがないということで、紺屋の白袴というそしりを浴びていたので、ほっとしています。しかし、心無いヤカラからは、ダサイとかセンスが無いとか言いたい放題。そこでCosmoを使って、誰にも真似できないエレガントなWebを作ってその体験記でも載せようと思ったら、アメリカでようやくCosmo Codeが出荷されたのみで、4部作全部が出荷されるのは年末ということです。ソフトの出荷予定で年末のアナウンスは当然来春以降を意味しますので、どうすればいいのでしょう、うちのWebはいつリニューアルされるのでしょうか。


前にもどる