Irrlichtにはスケルタルアニメーションの機能が実装されている。
IAnimatedMeshSceneNodeにより、スケルタルアニメーション(ボーン埋設によるメッシュの変形操作)を簡単に実現できる。
Irrlichtはウェイトマップの処理をソフトウェア(CPU)で行なっており、頂点シェーダ等を使っていない。このため、あまりにも頂点の多いメッシュをスケルタルアニメーションで動かすと、重たくなる可能性が高い。
基本的になるべくポリゴン数を増やさず、テクスチャで表現力を向上させる、いわゆるローポリモデリングが適している。
IAnimatedMeshSceneNodeにおいて、特定のボーンを直接制御したい場合、下記のようにする。この場合、"MyBone"の角度を(0,45,0)にする。
pNode->setJointMode(scene::EJUOR_CONTROL);
scene::IBoneSceneNode* pBone = pNode>getJointNode("MyBone");
pBone->setRotation(vector3df(0, 45, 0));
このような操作をしたい場合、setJointModeは必須である。Irrlichtにおいては、描画処理とアニメーション処理を独立して呼び出せないため、setJointModeをしないと描画処理の際にアニメーション処理によって自動的にボーンの位置が上書きされてしまう。
Xファイル等のメッシュには、スケルタルアニメーションの情報を埋め込むことができる。人物などのアニメーションは通常、このスケルタルアニメーションによって表現する。
しかし、スケルタルアニメーションは、原則として動かしたいメッシュ毎につけていく必要がある。また、一つのメッシュには一つの時系列しか埋め込むことが出来ないため、複数の動作を持たせたい場合には、異なる範囲のフレーム番号にそれぞれ埋め込む必要がある。
ここで、動作毎にファイルを分割できて、それを任意のメッシュに適用できれば、大幅な労力削減になるため、実際の開発では何らかの手段で各動作を対象メッシュから分離して、メッシュ間で流用できるようにすることが多い。
これをIrrlichtにおいて実現する方法を示す。
Irrlichtにおいては、ISkinnedMesh::useAnimationFromという関数があり、これを使って実現可能なように見えるが、メッシュ内から自分でデータを読み出して、他のメッシュに転写してしまったほうが柔軟性が高い。
これを実現するため、下記のSkinnedMeshAnimatorクラスを作成した。
class SkinnedMeshAnimator {
scene::ISkinnedMesh* m_pMesh;
public:
SkinnedMeshAnimator(void) : m_pMesh(NULL) {}
void apply(scene::IAnimatedMeshSceneNode* pTarget, irr::f32 nFrame);
bool init(scene::IAnimatedMesh* pMesh);
void release(void);
};
// LightWave3DからXファイルをエクスポートした場合、Frame1_MyBone などのように、ボーン名に勝手に接頭語を付けられてしまう。
// このため、getBoneNameBody/findBoneにより、接頭語を削った部分のみで名前を照合する。
// 他のツールを使っている場合は適したものに修正すること。
const char* getBoneNameBody(const char* szNodeName) {
const char* r = strchr(szNodeName, '_');
if(r) return r + 1;
return szNodeName;
}
scene::IBoneSceneNode* findBone(scene::IAnimatedMeshSceneNode* pNode, const char* szName) {
int n = pNode->getJointCount();
for(int i = 0; i < n; i ++) {
scene::IBoneSceneNode* pBone = pNode->getJointNode(i);
if(!strcmp(szName, getBoneNameBody(pBone->getName())))
return pBone;
}
return NULL;
}
bool SkinnedMeshAnimator::init(scene::IAnimatedMesh* pMesh) {
release();
m_pMesh = reinterpret_cast<scene::ISkinnedMesh*>(pMesh);
if(!m_pMesh) return false;
m_pMesh->grab();
return true;
}
void SkinnedMeshAnimator::release(void) {
if(m_pMesh)
m_pMesh->drop();
m_pMesh = NULL;
}
void SkinnedMeshAnimator::apply(scene::IAnimatedMeshSceneNode* pTarget, irr::f32 nFrame) {
m_pMesh->animateMesh(nFrame, 1.0f);
auto joints = m_pMesh->getAllJoints(); // autoが使えるのはVC2010以降
int n = m_pMesh->getJointCount();
for(int i = 0; i < n; i ++) {
scene::IBoneSceneNode* pbTarget = findBone(pTarget, getBoneNameBody(joints[i]->Name.c_str()));
if(pbTarget)
pbTarget->setRotation(joints[i]->LocalAnimatedMatrix.getRotationDegrees());
// 体格の異なるキャラクター間でも流用可能とするため、回転のみ反映する
}
}
使い方は下記の通り。
初期化
SkinnedMeshAnimator sma;
sma.init(smgr->getMesh("Animation.x”)); // Animation.xには、ボーンアニメーションが入っている
scene::IAnimatedMeshSceneNode* pChr =
pSceneManager->addAnimatedMeshSceneNode(smgr->getMesh(“Character.x”));
pChr->setJointMode(scene::EJUOR_CONTROL); // 勝手にアニメーションされないようにする
使用例
sma.apply(pChr, 10); // pChr(Character.x)にAnimation.xの10フレーム目のポーズをとらせる
sma.apply()を実行するとAnimation.x 内の指定したフレームにおける各ボーンのrotationをpTarget内の同名ボーンにコピーする。
同名ボーンが無いボーンについては何もしない。Animation.x 側を「上半身のみ」、「下半身のみ」で作って順次applyしていくことで、上半身と下半身に個別の動作をさせることができる。