![]()  | 
  
    Springhead
    
   An open source physics engine for virtual reality, haptics and motion generation. 
   | 
 
ここでは,SpringheadのAPIクラスの宣言と実装の手順について説明します. APIクラスの概要,使い方については, APIのしくみ をご参照ください.
APIクラスのヘッダファイルは,Springhead2/include/Sdk名 に作ります. (Sdk名はSDKの名前 Graphics,Physicsなど)
 ヘッダファイルの名前は,Sprオブジェクト名.h (SprPHSolid.h など)とします. APIクラスを宣言するには, 
 struct SceneObjectIf: NamedObjectIf{
    SPR_IFDEF(SceneObject);
    /// 所属Sceneの取得
    virtual SceneIf* GetScene();
 };
のように,
をします. SPR_IFDEF()は,いくつかのメンバの宣言をまとめたものです. SPR_IFDEF()に対応する実装は,Springhead2/bin/swig/swig.exe が自動生成します. Springhead2/src/Sdk名/Sdk名Stub.cpp に,
SPR_IFIMP?(クラス名, 基本クラス名);
という行ができます.これはSPR_IFDEF()に対応するメンバの実装になります. Cast()メンバ関数,DCAST()マクロが利用する型情報もここに入ります.
APIクラスを作ったら,それを実装するクラスを作ります.
実装クラスは,~
 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);}
 };
のように,宣言します.
クラスの宣言のなかの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()マクロが利用する型情報もここに入ります.
インタフェースとオブジェクトを指すポインタは型が異なりますが,同じアドレスを指すポインタです. Object::GetObjectIf() は ObjectのthisポインタをObjectIf*にキャストして返します. APIクラスは thisポインタを
size_t ObjectIf::NChildObject() const {
    return ((Object*)this)->NChildObject();
}
のように,thisポインタの型を実装クラスに戻して関数を呼び出しています.
まず,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 をコンパイルするときに, エラーになります.
FileIO SDK (FileIO) でロード・セーブができるようにするためには, APIクラスの定義に若干の細工をする必要があります. FileIO SDK の詳細は,5. ファイル入出力SDKの実装 を参照してください.
/   デスクリプタの読み出し(コピー版)
    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; }
     をオーバーロードするとコピーが減って効率がよくなります。 
    PHSceneIf::GetIfInfoStatic()->RegisterFactory(new FactoryImp(PHSolid));
       のようにファクトリを登録します. 登録は,ファイルのロードより前に行わなければなりません. SDKのコンストラクタで1度だけ呼び出すのが良いでしょう. 詳しくは 2.1.1 Factoryクラス を参照ください. 手順3の3つの関数のオーバーロードは、オブジェクト(例:PHSolid)が、 デスクリプタ(例:PHSolidDesc)を継承しているならば,
    ACCESS_DESC(実装クラス名);
  マクロを実装クラス(例:PHSolid)の宣言の中に置けば,オーバーライドしてくれます.
多重継承の都合でデスクリプタ(例:PHBallJointDesc)を継承できない場合があります。 実装クラスが別の実装クラス(=基本実装クラス)を継承する場合、基本実装クラスは既に 基本用のDescを継承しているからです。 DescはDescで基本用のDescを継承していますから、 基本Descと派生Descを同時に継承すると、基本部分のメンバーがダブってしまいます。
そのため、派生実装クラスではデスクリプタが継承できません。 その場合は、 SPR_DECLMEMBEROF_デスクリプタ名 マクロ(例:SPR_DECLMEMBEROF_PHBallJointDesc) を使えば自動化ができます。デスクリプタを定義すると、 それに対応するマクロSPR_DECLMEMBEROF_デスクリプタ名マクロ (例:SPR_DECLMEMBEROF_PHBallJointDesc)が自動的に定義されます。 オブジェクト(例:PHBallJoint)の定義の中で、
    SPR_DECLMEMBEROF_デスクリプタ名;
を定義すると、GetDesc()とGetDescSize()がオーバーライドされます。
ファイルへのロードセーブでは,何も無い状態からオブジェクトを生成して シーングラフを作ります.これに対して, シーンのオブジェクトの構造は変わらないけれども,シミュレーション中に 10ステップ前の状態に戻したいなどということも良くあるでしょう. そんな用途に使うのが状態の保存・再現です.
状態の保存・再現ができるクラスを作るには,
/   状態の読み出し(コピー版)
    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 {}
     をオーバーロードしてください.
    ACCESS_STATE(実装クラス名);
     マクロを実装クラスの宣言の中に置けば,オーバーライドしてくれます. ACCESS_DESC_STATE() マクロを置けば,DescとState両方をオーバーライドしてくれます.