![]() |
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両方をオーバーライドしてくれます.