Springhead
An open source physics engine for virtual reality, haptics and motion generation.
1. APIクラスの作り方・実装の仕方

1.1 APIクラスの作り方

ここでは,SpringheadのAPIクラスの宣言と実装の手順について説明します. APIクラスの概要,使い方については, APIのしくみ をご参照ください.

1.1.1 APIクラスの宣言

APIクラスのヘッダファイルは,Springhead2/include/Sdk名 に作ります. (Sdk名はSDKの名前 Graphics,Physicsなど)
ヘッダファイルの名前は,Sprオブジェクト名.h (SprPHSolid.h など)とします. APIクラスを宣言するには,

 struct SceneObjectIf: NamedObjectIf{
    SPR_IFDEF(SceneObject);
    /// 所属Sceneの取得
    virtual SceneIf* GetScene();
 };

のように,

  1. 基本のインタフェースクラスを継承する.
  2. SPR_IFDEF(クラス名)メンバを持つ.
  3. API関数を普通の関数として宣言する(virtual をつけてはいけない).

をします. SPR_IFDEF()は,いくつかのメンバの宣言をまとめたものです. SPR_IFDEF()に対応する実装は,Springhead2/bin/swig/swig.exe が自動生成します. Springhead2/src/Sdk名/Sdk名Stub.cpp に,

SPR_IFIMP?(クラス名, 基本クラス名);

という行ができます.これはSPR_IFDEF()に対応するメンバの実装になります. Cast()メンバ関数,DCAST()マクロが利用する型情報もここに入ります.

1.2 実装クラスの作り方

APIクラスを作ったら,それを実装するクラスを作ります.

1.2.1 実装クラスの宣言

実装クラスは,~

 class SceneObject:public NamedObject{
    SPR_OBJECTDEF(SceneObject);     ///<    クラス名の取得などの基本機能の実装
 public:
    virtual void SetScene(SceneIf* s);
    virtual SceneIf* GetScene();
    /// デバッグ用の表示
    virtual void Print(std::ostream& os) const {NamedObject::Print(os);}
 };

のように,宣言します.

1.2.1.1 SPR_OBJECTDEF()マクロ

クラスの宣言のなかのSPR_OBJECTDEF()は, 型情報のためのStatic関数(GetTypeInfo(), GetTypeInfoStatic())などを宣言します. 抽象クラスの場合は,実体化できないというエラーがでるので,代わりにSPR_OBJECTDEF_ABSTを使います. また,インタフェースを持たないクラスの場合は,代わりにSPR_OBJECTDEF_NOIFを使います.

基本クラスがテンプレートクラスの場合,基本クラスリストの取得が上手く行きません. この場合,SPR_OBJECTDEF1(cls, base); のように,マクロで直接基本クラスを指定します.

SPR_OBJECTDEF()の実装も,SPR_IFDEF()の場合と同様に, Springhead2/src/Sdk名/Sdk名Stub.cpp に,

SPR_OBJECTIMP?(クラス名, 基本クラス名);

という行ができます.これはSPR_OBJECTDEF()に対応するメンバの実装になります. Cast()メンバ関数,DCAST()マクロが利用する型情報もここに入ります.

1.2.1.2 APIの取得

インタフェースとオブジェクトを指すポインタは型が異なりますが,同じアドレスを指すポインタです. Object::GetObjectIf() は ObjectのthisポインタをObjectIf*にキャストして返します. APIクラスは thisポインタを

size_t ObjectIf::NChildObject() const {
    return ((Object*)this)->NChildObject();
}

のように,thisポインタの型を実装クラスに戻して関数を呼び出しています.

1.2.2 宣言したAPIの実装

まず,Springhead/src/Sdk名/SceneObject.h で,

class SceenObject: public NamedObject{
    SPR_OBJECTDEF(SceneObject);
    virutal void SetScene(SceneIf* s);  // 実装側の宣言
};

のように,関数を宣言し, まず,Springhead/src/Sdk名/SceneObject.cpp で,

 void SceneObject::SetScene(SceneIf* s){
    SetNameManager(s->GetObj<NameManager>());
    nameManager->GetNameMap();
 }

のように,宣言したAPIを実装します. APIクラスだけにあり,実装クラスにない関数があると, Springhead2/src/Sdk名/Sdk名Stub.cpp をコンパイルするときに, エラーになります.

1.3 ファイルからのロード・ファイルへのセーブ

FileIO SDK (FileIO) でロード・セーブができるようにするためには, APIクラスの定義に若干の細工をする必要があります. FileIO SDK の詳細は,5. ファイル入出力SDKの実装 を参照してください.

1.3.1 ロード・セーブ可能なオブジェクトの作り方

  1. ロードしたいデータを含んだディスクリプタ(例:PHSolidDesc)を作ります.
  2. インタフェースクラス(例:PHSolidIf)を作ります.
  3. 実装クラス(例:PHSolid)を作ります.このとき実装クラスで,
    /   デスクリプタの読み出し(コピー版)
        virtual bool GetDesc(void* desc) const { return false; }
    /   デスクリプタのサイズ
        virtual size_t GetDescSize() const { return 0; };
        
    をオーバーロードしてください.
    /   ディスクリプタの読み出し(コピー版)
        virtual bool GetDesc(void* d) const;
        
    の代わりに
    /   ディスクリプタの読み出し(参照版)
        virtual const void* GetDescAddress() const { return NULL; }
        
    をオーバーロードするとコピーが減って効率がよくなります。
  4. シーングラフ上で,実装クラス(例:PHSolid)の先祖になるクラスのなかから, 実装クラス(例:PHSolid)を生成するクラス(例:PHScene)を決めます.
  5. 生成クラス(例:PHScene)のCreateObject()が実装クラス(PHSolid)を作れるように,
        PHSceneIf::GetIfInfoStatic()->RegisterFactory(new FactoryImp(PHSolid));
          
    のようにファクトリを登録します. 登録は,ファイルのロードより前に行わなければなりません. SDKのコンストラクタで1度だけ呼び出すのが良いでしょう. 詳しくは 2.1.1 Factoryクラス を参照ください.

手順3の3つの関数のオーバーロードは、オブジェクト(例:PHSolid)が、 デスクリプタ(例:PHSolidDesc)を継承しているならば,

    ACCESS_DESC(実装クラス名);
  

マクロを実装クラス(例:PHSolid)の宣言の中に置けば,オーバーライドしてくれます.

1.3.2 非継承時のメンバ宣言の自動化

多重継承の都合でデスクリプタ(例:PHBallJointDesc)を継承できない場合があります。 実装クラスが別の実装クラス(=基本実装クラス)を継承する場合、基本実装クラスは既に 基本用のDescを継承しているからです。 DescはDescで基本用のDescを継承していますから、 基本Descと派生Descを同時に継承すると、基本部分のメンバーがダブってしまいます。

そのため、派生実装クラスではデスクリプタが継承できません。 その場合は、 SPR_DECLMEMBEROF_デスクリプタ名 マクロ(例:SPR_DECLMEMBEROF_PHBallJointDesc) を使えば自動化ができます。デスクリプタを定義すると、 それに対応するマクロSPR_DECLMEMBEROF_デスクリプタ名マクロ (例:SPR_DECLMEMBEROF_PHBallJointDesc)が自動的に定義されます。 オブジェクト(例:PHBallJoint)の定義の中で、

    SPR_DECLMEMBEROF_デスクリプタ名;

を定義すると、GetDesc()とGetDescSize()がオーバーライドされます。

1.4 状態の保存・再現

ファイルへのロードセーブでは,何も無い状態からオブジェクトを生成して シーングラフを作ります.これに対して, シーンのオブジェクトの構造は変わらないけれども,シミュレーション中に 10ステップ前の状態に戻したいなどということも良くあるでしょう. そんな用途に使うのが状態の保存・再現です.

状態の保存・再現ができるクラスを作るには,

  1. 保存したい状態を保持する構造体(例:PHSolidState)を作ります.
  2. 実装クラス (例:PHSolid)を作ります. このとき実装クラスで,
    /   状態の読み出し(コピー版)
        virtual bool GetState(void* state) const { return false; }
    /   状態の読み出し(参照版)
        virtual const void* GetStateAddress() const { return NULL; }
    /   状態の設定
        virtual void SetState(const void* state){}
    /   状態のサイズ
        virtual size_t GetStateSize() const { return 0; };
    /   メモリブロックを状態型に初期化
        virtual void ConstructState(void* m) const {}
    /   状態型をメモリブロックに戻す
        virtual void DestructState(void* m) const {}
        
    をオーバーロードしてください.
    実装クラス(例:PHSolid)が状態構造体(例:PHSolidState)を継承しているなら,
        ACCESS_STATE(実装クラス名);
        
    マクロを実装クラスの宣言の中に置けば,オーバーライドしてくれます. ACCESS_DESC_STATE() マクロを置けば,DescとState両方をオーバーライドしてくれます.