高度な検索
Developer Connection
Member Login ログイン | ご入会 ADC連絡先

Technote 1110

Supporting Plug-in Renderers in QuickDraw3D 1.5.3 Applications


目次

プラグインレンダラのサポート

要約


ードパーティ製プラグインレンダラのサポートは QuickDraw3D 1.5 の新機能です。この TECHNOTE では、QuickDraw3D 1.5 のアプリケーションがプラグインを適切にサポートする方法を説明します。プラグインのサポートを追加することは非常に簡単で、それによってアプリケーションは現在利用可能なものはもちろん、将来のサードパーティ製プラグインをも利用できるようになります。

この TECHNOTE はアプリケーションでプラグインレンダラのサポートを提供することに興味のあるデベロッパの方のためのものです。『3D Graphics Programming With QuickDraw 3D』(Addison-Wesley 刊) にあるような QD3D のプログラミング知識が前提となっていることをご了承ください。


プラグインレンダラのサポート

アプリケーションを設計する際に大切なのは「インタラクディブ」な性能を持つレンダラ (例えば Apple のワイヤフレームレンダラおよびインタラクティブレンダラ) と、その他のレンダラ (LightWork Design のレイトレーサ) との違いを考慮に入れることです。LightWork Design の QD3D プラグインレンダラの詳細については、http://www.lightwork.com を参照してください。

インタラクティブな性能を持たないレンダラの場合、そのレンダラには出力専用の新しいウインドウを作成して与え、その間はインタラクティブな性能を持つ別のレンダラの管理下のウインドウで、ユーザがシーンのエレメントを操作できるようにするとよいでしょう。レンダラがインタラクティブな性能をサポートしているかどうかは、次のように Q3Renderer_IsInteractive API コールを使って調べることができます。

/*
* Q3Renderer_IsInteractive
* レンダラがインタラクティブに使用可能かどうかを調べる
*/
TQ3Boolean Q3Renderer_IsInteractive(TQ3RendererObject renderer);


自分ではレンダラを書かないにせよ、アプリケーションがプラグインレンダラをサポートすべきなのは明らかです。アプリケーションでプラグインレンダラを正しくサポートするためには、いくつかのことをしなければなりません。
  • 使用可能なレンダラを QD3D に問い合わせる。
  • 使用可能なレンダラのリストを構築し、ユーザに選択させる。
  • レンダラがインタラクティブかを調べる。
  • ユーザの選択に応じて、使用するレンダラを設定する。

また、アプリケーションの起動時に (それぞれの種類ごとに 1 個ずつ) レンダラのインスタンスを作成する必要があります。これをしなければならない理由は、レンダラのインスタンスを破壊してしまうと、設定情報も失われるからです。レンダラの設定変更のためのダイアログを用意して、随時変更できるようにするには、そのレンダラを存続させておく必要があります。

ここでお見せする方法は非常に単純なものであり、各レンダラにグローバルな設定情報を持たせているため、最もよい例とはいえません。本格的なアプリケーションでは、ウインドウごとにレンダラを管理すべきでしょう。それには 2 つの方法があります。1 つは、ウインドウごと (もっと正確には描画コンテキストごと) に別々のレンダラのリストを用意し、ウインドウが閉じるまで保持する方法です。もう 1 つは (このほうが効率がよいためこちらをおすすめしますが)、レンダラの設定情報を単純にキャッシュしておく方法です (Q3InteractiveRenderer_GetPreferences および Q3InteractiveRenderer_SetPreferences 関数を使用します)。

プラグインレンダラのリストの作成

使用可能なプラグインレンダラのリストを含むメニューの作成方法を見てみましょう。まず、システムにインストールされたレンダラのリストを取得しなければなりません。QD3D.h インタフェース・ファイルに次の部分があります (コメント部分は日本語で示します)。
/*
* TQ3SubClassData は、特定の親のタイプに属するサブクラスをオブジェクト
* システムに問い合わせる場合に使用する。
*/

typedef struct TQ3SubClassData {
   unsigned long numClasses;  /* 親クラスにあったサブクラスタイプの個数 */
   TQ3ObjectType *classTypes; /* そのクラスタイプを入れた配列 */
} TQ3SubClassData;


この構造体には、特定タイプのクラスの個数と、クラスタイプを表す 4 文字の識別子を含む TQ3ObjectType の配列が含まれます。このデータ構造体は、Q3ObjectHierarchy_GetSubClassData を呼び出して設定します。
/*
* 親タイプと、TQ3SubClassData 構造体のインスタンスが与えられると、
* その構造体に、クラス階層中、親クラスの直下のサブクラスの個数と
* クラスタイプを設定する。成功の場合は kQ3Success を、エラーが起こると
* kQ3Failure を返す。
*
* 注意: この関数は、classTypes 配列用にメモリを割り当てる。
* Q3ObjectClass_EmptySubClassData を呼び出して、必ずこのメモリを解放すること。
*/
TQ3Status Q3ObjectHierarchy_GetSubClassData(
   TQ3ObjectType objectClassType,
   TQ3SubClassData *subClassData
);


注意: この呼び出しを行うとメモリが割り当てられる場合がありますが、次のように割り当てられたメモリを解放する関数が用意されています。


/*
* TQ3SubClassData 構造体のインスタンスが与えられると、
* Q3ObjectClass_GetSubClassData を呼び出して割り当てたメモリをすべて解放する。
*
* 注意: この関数は、Q3ObjectClass_GetSubClassData の呼び出しの後には
* 必ず呼び出して、メモリリークを防ぐ必要がある。
*/
TQ3Status Q3ObjectHierarchy_EmptySubClassData(
   TQ3SubClassData *subClassData
);


レンダラタイプのサブクラスのリストを取得したら (上の Q3ObjectHierarchy_GetSubClassData 関数に kQ3SharedTypeRenderer を渡して)、配列中をループして、順番に配列内の各レンダラのサブクラスを処理することができます。

下のコードの断片は、レンダラのニックネームを取得しようとするものです。QD3D システムの各クラスは固有のタイプとクラス名を持っています。ニックネームという概念は新しいものです。QD3D チームがプラグインレンダラのシステムを開発中に、クラス名はローカライズできないため、アプリケーションのユーザインタフェースで使用できないことが明らかになりました。それで、呼び出しを行うアプリケーションに対してローカライズ済みの文字列を提供するオプションをレンダラに与えることを決定しました。QD3D 1.5.1 には、次のような新しい関数、Q3RendererClass_GetNickNameString があります。
/*
* Q3RendererClass_GetNickNameString
* アプリケーションがレンダラ名の文字列を取得するために使用する。
* レンダラはこの文字列を、例えばリソースとして、ローカライズした形式で
* 保存しておかなければならない。この文字列は、アプリケーションに
* 選択機能 (例えばメニューで) を提供する。
*
* 呼び出しの結果、返された文字列が nil の場合、アプリケーションは、
* レンダラのクラス名を使用してもよい。クラス名はローカライズできないので、
* まず必ず名前の文字列を取得するようにしなければならない。
*/
TQ3Status Q3RendererClass_GetNickNameString(
   TQ3ObjectType rendererClassType,
   TQ3ObjectClassNameString rendererClassString
);


この呼び出しでレンダラを記述する文字列を取得します。これをメニューや、その他のユーザインタフェースの要素に配置することができます。Q3ObjectHierarchy_GetSubClassData 関数はレンダラをソートせずに返すので、レンダラのリストは自分でソートする必要があります。

必要なコードはこれだけです。ひとつにまとめてみましょう。次のルーチンは、メニューハンドルを与えると、すべてのインストール済みのレンダラをメニューに挿入します。
#define kMaxRendererCount 10
static TQ3ObjectType pTypes[ kMaxRendererCount ] ;
#define mRendererMenu 5    /* レンダラメニューの定義 */

void SetUpRendererMenu( void )
{
  MenuHandle theMenu ;
  TQ3SubClassData subClassData;
  TQ3ObjectType *classPointer;
  short i, pRendererCount;
  TQ3ObjectClassNameString objectClassName;
  TQ3ObjectClassNameString objectClassString ;

      theMenu = GetMHandle(mRendererMenu);
      Q3ObjectHierarchy_GetSubClassData(kQ3SharedTypeRenderer,
                                        &subClassData);
      classPointer = subClassData.classTypes;
      pRendererCount = 0;
      i = subClassData.numClasses;
      while( i-- > 0 && pRendererCount <= kMaxRendererCount) {
          /*
          * " 汎用 " レンダラは内部で使用されるもので、描画はできないため、
          * ユーザインタフェース項目には表示しない。
          */
          if( *classPointer != kQ3RendererTypeGeneric )
          {
              Q3RendererClass_GetNickNameString(*classPointer,
                                                objectClassString );
              if( objectClassString[0] == '\0' )
              {
                  /* レンダラは名前を返さないので、クラス名を使用する。 */
                  Q3ObjectHierarchy_GetStringFromType(*classPointer,
                                                      objectClassName);
                  AppendMenu(theMenu,c2pstr((char *)objectClassName));
                  pTypes[pRendererCount++] = *classPointer ;
              }
              else
              {
                  AppendMenu(theMenu,c2pstr(objectClassString));
                  pTypes[pRendererCount++] = *classPointer ;
              }
          }
      classPointer++ ;
      }

      pTypes[pRendererCount] = NULL ;
      Q3ObjectHierarchy_EmptySubClassData( &subClassData ) ;
}


上の例で注意しなければならない重要な点が 3 つあります。まず、レンダラから名前が取得できない場合は、代わりにクラス名を使うことです。次に、kQ3RendererTypeGeneric タイプのレンダラを無視していることに注意してください。これはダミーのレンダラで、何もレンダリングしません。レンダリングに使用できないものをメニューに加えてはいけません。3 つめに、メニューのインデックスに合わせて、レンダラタイプを pTypes 配列に保管している点に注意してください。こうしておくと、あとでユーザの選択にしたがってレンダラを設定する際に非常に便利です。

最後に、Q3ObjectHierarchy_EmptySubClassData を呼び出して、システムにインストール済みのプラグインレンダラの問い合わせの際に割り当てたメモリを解放している点に注目してください。

レンダラの設定

インストール済みレンダラのリストを表示するメニューを構築してしまえば、ユーザの選択に基づいてレンダラの設定を行うのは簡単です。この例は必要以上に複雑ですが、それは QD3D ビューア・アプリケーションでレンダラを設定する場合を取り上げたためです。基本的な部分はすべての種類のアプリケーションで同じです。

#define kMaxRendererCount 10
static TQ3ObjectType pTypes[ kMaxRendererCount ] ;

typedef struct {
    TQ3ViewerObject fViewer; /* この文書のビューアへの参照を保存する */
    .
    .
    .
} ViewerDocument, *ViewerDocumentPtr, **ViewerDocumentHdl;

/*
* レンダラメニューのメニューコマンドを処理する
*/
void HandleRendererMenu( short menuItem )
{
  ViewerDocumentHdl theViewerDocumentHdl ;
  WindowPtr theWindow ;
  TQ3ViewerObject theViewer ;
  OSErr theError ;
  TQ3ViewObject myView ;
  TQ3Status myStatus ;
  TQ3RendererObject myRenderer ;

      theWindow = FrontWindow() ;
      if( theWindow != NULL )
      {
          /*
          * ウインドウの long の参照定数から、ビューアの文書のデータ構造体
          * への参照を取得して、それを適切なタイプにキャストする。
          * 参照が取得できない場合 (null の場合) はここで止める。
          */
          theViewerDocumentHdl =
             (ViewerDocumentHdl)GetWRefCon(theWindow) ;
          if(theViewerDocumentHdl == NULL)
              return ;

          /* データ構造体からビューアオブジェクトへの参照を取り出す */
          theViewer = (**theViewerDocumentHdl).fViewer ;
          if(theViewer == NULL)
              return ;
          myRenderer = Q3Renderer_NewFromType( pTypes[ menuItem - 1 ] );

          /*
          * ビューのレンダラを設定する
          */
          myView = Q3ViewerGetView( theViewer );
          if( myView != NULL && myRenderer != NULL )
          {
            /* レンダラを、上の switch 文で作成したものに設定する */
              myStatus = Q3View_SetRenderer(myView, myRenderer) ;
              /* レンダラの設定が終わったので、レンダラへの参照を
                 破棄することができる */
              myStatus = Q3Object_Dispose( myRenderer ) ;
              /* そして、ビューアの内容表示領域を描き直す */
              theError = Q3ViewerDrawContent( theViewer ) ;
          }
      }
}



レンダラの設定変更のサポート

プラグインレンダラをサポートする場合、レンダラの設定変更ダイアログにアクセスする必要があります。これは、レンダラの描画方法を設定するダイアログです。

QD3D の Q3Renderer_HasModalConfigure 関数を呼び出して、レンダラがモーダルな設定変更ダイアログを持っているかどうかを調べることができます。私たちは、QD3D 1.5.1 では、この関数をワイヤフレームレンダラにもインタラクティブレンダラにも実装しませんでしたが、QD3D のリリース 1.5.3 以降には実装する予定です。次のコードは、Q3Renderer_HasModalConfigure 関数の使用法を示したものです。

{
 TQ3RendererObject qd3dRenderer;
 TQ3ViewObject qd3dView;
 TQ3Status qd3dStatus;
 TQ3Boolean qd3dCanceled;
 TQ3DialogAnchor qd3dAnchor ;

    // レンダラを取得する
    qd3dView = Q3ViewerGetView(theViewer);
    qd3dStatus = Q3View_GetRenderer(qd3dView, &qd3dRenderer);

    // 設定変更ダイアログを構築する
    if (Q3Renderer_HasModalConfigure(qd3dRenderer))
    {
        #if 0
            /* モーダルにする */
             qd3dAnchor.clientEventHandler = NULL;
        #else
           /* イベントハンドラを渡すと、移動可能なモーダルになる */
           qd3dAnchor.clientEventHandler = HandleEvent ;
        #endif

        qd3dStatus = Q3Renderer_ModalConfigure(qd3dRenderer,
                                                qd3dAnchor,
                                                &qd3dCanceled);
    }

    // 不要なものを取り除く
    qd3dStatus = Q3Object_Dispose(qd3dRenderer);
};


上のコードで注目すべきなのは、clientEventHandler フィールドです。ダイアログがモーダルでよければ、アプリケーションはこのフィールドを nil に設定します。ダイアログを移動可能にしたければ、アプリケーションのイベントハンドラへの参照を渡す必要があります。

また、アップルメニューのうち「... について」メニューは選択不可にし、それ以外は選択可能にする必要があります。さらに、編集メニュー以外のメニューはすべて選択不可にし、編集メニューのうち、取り消し、カット、コピー、ペースト、消去は選択可能にし、それ以外は選択不可にします。これを次のコードの断片で示します。

void SpinDialog_ConfigureRenderer(void)
{
    TQ3ViewObject view;
    TQ3RendererObject renderer;
    TQ3DialogAnchor dialogAnchor;
    TQ3Boolean canceled;
    MenuHandle m;

        SpinView_GetView(&view);
        Q3View_GetRenderer(view, &renderer);
        if (Q3Renderer_HasModalConfigure(renderer)) {
            dialogAnchor.clientEventHandler = SpinEventHandlerWrapper;
            m = GetMHandle(kMenu_Apple);
            SpinMenu_EnableItem(m, kAppleMenu_About, false);
            m = GetMHandle(kMenu_File);
            SpinMenu_EnableItem(m, 0, false);
            m = GetMHandle(kMenu_Edit);
            SpinMenu_EnableItem(m, kEditMenu_Undo, true);
            SpinMenu_EnableItem(m, kEditMenu_Cut, true);
            SpinMenu_EnableItem(m, kEditMenu_Copy, true);
            SpinMenu_EnableItem(m, kEditMenu_Paste, true);
            SpinMenu_EnableItem(m, kEditMenu_Clear, true);
            SpinMenu_EnableItem(m, kEditMenu_Preferences, false);
            m = GetMHandle(kMenu_Commands);
            SpinMenu_EnableItem(m, 0, false);
            m = GetMHandle(kMenu_Dialog);
            SpinMenu_EnableItem(m, 0, false);
            m = GetMHandle(kMenu_View);
            SpinMenu_EnableItem(m, 0, false);
            m = GetMHandle(kMenu_Geometry);
            SpinMenu_EnableItem(m, 0, false);
            m = GetMHandle(kMenu_Complex);
            SpinMenu_EnableItem(m, 0, false);
            m = GetMHandle(kMenu_Demos);
            SpinMenu_EnableItem(m, 0, false);
            DrawMenuBar();
            Q3Renderer_ModalConfigure(renderer, dialogAnchor, &canceled);
            m = GetMHandle(kMenu_Apple);
            SpinMenu_EnableItem(m, kAppleMenu_About, true);
            m = GetMHandle(kMenu_File);
            SpinMenu_EnableItem(m, 0, true);
            m = GetMHandle(kMenu_Commands);
            SpinMenu_EnableItem(m, 0, true);
            m = GetMHandle(kMenu_Dialog);
            SpinMenu_EnableItem(m, 0, true);
            m = GetMHandle(kMenu_View);
            SpinMenu_EnableItem(m, 0, true);
            m = GetMHandle(kMenu_Geometry);
            SpinMenu_EnableItem(m, 0, true);
            m = GetMHandle(kMenu_Complex);
            SpinMenu_EnableItem(m, 0, true);
            m = GetMHandle(kMenu_Demos);
            SpinMenu_EnableItem(m, 0, true);
            SpinMenu_Update();
            DrawMenuBar();
        }
        else {
            Alert(kNoModalConfigureALRT, NULL);
        }
}


プラグインレンダラによるレンダラ名のサポート

レンダラのコードを書く人はほとんどいないでしょう。膨大な量の専門知識と時間を要するからです。しかし、次のようにサンプルレンダラのサンプルコードに (QD3D SDK から持ってきて) 修正を加えれば、バッファに含まれているレンダラ名の文字列を取り出すことができます。

extern AliasHandle SRgAliasHandle;
#define SR_NAME_RESOURCE  16211 /* コード中のレンダラ名の
                                   リソース文字列の ID */

TQ3Status SR_GetNameString(
    unsigned char *dataBuffer,
    unsigned long bufferSize,
    unsigned long *actualDataSize)
{
 TQ3Status status = kQ3Success ;
 Boolean wasChanged;
 FSSpec fileSpec;
 OSErr macErr;
 Str255 tempBuffer ;
 short SRgOldResFile, SRgResFile;

     /*
     * このレンダラのリソースファイルを見つけ、リソースファイルを開き、
     * レンダラ名の文字列を見つけ、リソースファイルを閉じ、渡された
     * バッファに文字列を返す。
     */
     SRgOldResFile = CurResFile();
     if ((SRgAliasHandle != NULL) || (*SRgAliasHandle != NULL)) {
         macErr = ResolveAlias(NULL, SRgAliasHandle, &fileSpec,
                               &wasChanged);
         if (macErr == noErr) {
             SRgResFile = FSpOpenResFile(&fileSpec, fsRdPerm);
             if (SRgResFile != -1) {
                 *actualDataSize = 0L;
                 GetIndString( tempBuffer, SR_NAME_RESOURCE, 1 ) ;
                 /* バッファ末尾の不要部分を切り捨てる */
                 *actualDataSize = (tempBuffer[0] bufferSize) ?
                                    bufferSize : tempBuffer[0] ;
                 if (dataBuffer != NULL) {
                   /* リソースマネージャから返されたパスカル文字列から
                      コピーする */
                     memcpy((char *)dataBuffer,
                     (char *) &tempBuffer[1],
                     *actualDataSize );
                     /* そして、文字列の終端を設定する */
                     dataBuffer[*actualDataSize] = '\0' ;
                 }
                     CloseResFile(SRgResFile);
                     UseResFile(SRgOldResFile);
             }
             else {
                 Q3XMacintoshError_Post(ResError());
                 status = kQ3Failure;
             }
         }
         else {
             Q3XMacintoshError_Post(macErr);
             status = kQ3Failure;
         }
     }
     else {
         status = kQ3Failure;  /* null エイリアス */
     }

     if( status == kQ3Failure )
     {
     *actualDataSize = 0L ;
     dataBuffer = NULL ;
     }

     return (status);
}


上記のコードで重要なのは、レンダラファイルへの参照を (SRgAliasHandle に) 保存して、リソースフォークを開けるようにすることです。この例では、リソースを文字列リストの'STR#'タイプリソースとしてファイルに入れることにしました。こうすれば、1 バイト文字や 2 バイト文字の複数のローカライズ文字列を保存して、レンダラがそれらを切り替えることができます。もちろん、文字列は 1 つだけ持たせ、レンダラのマーケットに合わせてローカライズすることもできます。

上記のコードはレンダラのロードの際に必ず呼び出されるようにしなければなりません。そのためには、レンダラのメタハンドラを変更しなければなりません。変更は非常に簡単で、次のように、名前を取得するルーチンを参照する case 文を追加するだけです。

/*
* レンダラ名の文字列
*/
case kQ3XMethodTypeRendererGetNickNameString: {
   return (TQ3XFunctionPointer) SR_GetNameString;
break;
}



Renderer.h の変更点を見るとわかります。

/*
* kQ3XMethodTypeRendererGetNickNameString
*
* メニューなどのユーザインタフェース項目に表示するため、
* アプリケーションがレンダラの名前を取得できるようにする。
*
* dataBuffer が NULL の場合、actualDataSize は名前を保存するのに必要な
* データバッファのサイズをバイト数で返す
*
* bufferSize は、dataBuffer が指すメモリブロックの実際のサイズ
*
* actualDataSize - 関数の戻りでバッファに書き込まれた実際のバイト数。
* dataBuffer が NULL の場合は必要な dataBuffer のサイズ
*
* オプション
*/

#define kQ3XMethodTypeRendererGetNickNameString \
   Q3_METHOD_TYPE('r','d','n','s')
typedef TQ3Status (QD3D_CALLBACK *TQ3XRendererGetNickNameStringMethod)(
   unsigned char *dataBuffer,
   unsigned long bufferSize,
   unsigned long *actualDataSize);



要約

デベロッパは、QD3D アプリケーションで必ずプラグインのサポートを提供したいと思うでしょう。プラグインを適切にサポートするアプリケーションは、現在あるレンダラまたは将来登場するサードパーティのレンダラを利用でき、プラグインをサポートしないアプリケーションより圧倒的に有利になるからです。

QD3D アプリケーションでのプラグインのサポートは非常に簡単です。プラグインをサポートするためにアプリケーションが行う必要のある作業は次の通りです。

  • 使用可能なレンダラを QD3D に問い合わせる。
  • 使用可能なレンダラのリストを構築し、ユーザに選択させる。
  • レンダラがインタラクティブかを調べる。
  • ユーザの選択に応じて、使用するレンダラを設定する。


参考文献

『3D Graphics Programming With QuickDraw 3D』(Addison-Wesley 刊)

更新日: 1997 年 10 月 31 日