前回のぬいぐるみを作ってみるでは、モデル全体をSoftBodyで動かしていましたが、今回は部分的に動きを反映させてみます。ただし、通常のボーンとSoftBodyの動きを混ぜることはできません。完全に動きを分離できるものに限ります。その条件を満たしているオブジェクト(スカート、髪など)をSoftBodyで揺らし、それ以外はモーションデータで動かします。
部分的な反映はボーンによるスキン変形の仕組を利用しているので対応は簡単ですが、ボーンの動きにSoftBodyやコリジョン用剛体を追従させる処理が簡単ではないため、その方法を中心に解説します。

解説動画/ニコニコ動画
【ニコニコ動画】物理エンジン(Bullet)のSoftBodyでスカートを揺らしてみた
部分的なSoftBodyの動き反映
特別な処理は不要です。すでにあるボーン情報にSoftBody反映用のボーン情報を追加するだけです。
剛体をボーンに追従させる
物理計算前に剛体姿勢をボーン姿勢で上書きすることで可能です。
BulletのStep前に直接剛体姿勢の更新もできますが、サブステップが複数実行されたときにサブステップ前に剛体姿勢の更新を毎回行ったほうがコリジョンなどのずれが小さくなります。そのための機能がBulletに用意されているので、それを利用します。
方法は2つあります。
・サブステップ前に呼ばれるコールバック関数
・btMotionStateの派生クラスを作成、剛体に設定 (サンプルで採用)
コールバック関数
//サブステップ前に呼ばれる関数
void PreTickCallback(btDynamicsWorld *world, btScalar timeStep)
{
//ここに処理を
}
//サブステップで呼ばれる関数
void TickCallback(btDynamicsWorld *world, btScalar timeStep)
{
//ここに処理を
}
//初期化関数など
{
//サブステップ前に呼ばれるコールバック関数
World->setInternalTickCallback(PreTickCallback,this,true);
//サブステップで呼ばれる関数
World->setInternalTickCallback(TickCallback,this,false);
}
btMotionStateの派生クラス
btDefaultMotionStateの代わりにbtMotionStateの派生クラスを剛体に設定。
剛体のコリジョンフラグCF_KINEMATIC_OBJECTを有効。
ステップ毎に仮想関数getWorldTransformが呼ばれるので設定したい姿勢行列を渡す。
// btMotionStateの派生クラス
class MyMotionState : public btMotionState
{
public:
virtual void getWorldTransform (btTransform ¢erOfMassWorldTrans) const
{
centerOfMassWorldTrans = 設定したい姿勢行列;
}
};
{
body = new btRigidBody( MyMotionState*を );
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT | body->getCollisionFlags());
//これでStep毎にgetWorldTransformが呼ばれる
}
アンカー:SoftBodyと剛体の接続
btSoftBody::appendAnchor関数でアンカー追加、指定した剛体にノード(頂点)を接続できます。剛体が移動した場合、ノード(頂点)と剛体の位置関係(ローカル座標)を保った位置に移動する力を発生させます。
アンカーでの注意点
アンカー強度を最大(1.0)にしても完全には固定されず、剛体が速く移動するとずれが生じる。それと、剛体の重さ、体積などによってもずれ具合が変化。それを解決するために完全に固定するための処理を自作。
自作位置固定処理
アンカーでのずれは、今回の部分的なSoftBody反映にとっては致命的な問題です。激しく動くと髪やスカートがずれてしまいます。それを回避するために、完全に位置を固定する処理を自作しました。処理はbtSoftBodyクラスの派生クラスで行います。その自作クラスをWorldに追加。
ちなみに、派生クラスにする必要はないのですが、メモリやメンバ変数管理が楽になるので派生を採用しました。
処理内容
物理Step後に指定されたSoftBodyノード(頂点)を指定した位置に強制移動。
ただし、このままだと次の物理Stepで動きに不具合(物理量の矛盾)。
回避するために、強制移動前の位置を保存しておき、次の物理step前に元に戻す。
物理step後のずれがなくなり、さらに物理計算にも不具合なし。
強制移動の距離を小さくするため、アンカー処理と併用。
自作SoftBodyクラス
仮想関数は使いません。必要な情報を保存しておき、worldから取得したbtSoftBody*をアップキャストして使用します。
// 自作ソフトボディー
struct MySoftBody : public btSoftBody
{
//そのまま引数をbtSoftBodyコンストラクタへ
MySoftBody( btSoftBodyWorldInfo* worldInfo,int node_count, const btVector3* x, const btScalar* m);
MySoftBody( btSoftBodyWorldInfo* worldInfo);
// 位置固定 アンカーと同時に使用する
// 物理計算後固定位置にノード位置を設定
// 次の物理計算前にアンカーで設定された位置に戻す
// 物理後のボーン用に完全に固定し
// 次の物理計算前に元に戻すことで計算の破たんを防止
struct FixPoint
{
Node* pNode = nullptr;
btRigidBody* pRBody = nullptr;
bool bFix = false;//固定処理実行 NodePosの値が有効
btVector3 Local = btVector3(0,0,0); //固定位置 pRBodyローカル座標
btVector3 NodePos = btVector3(0,0,0); //Bulletが計算した本来の位置
};
typedef btAlignedObjectArray<FixPoint> FixPointArray;
FixPointArray aFixPoint;
void appendFixPoint(int node, btRigidBody* rb)
{
btVector3 local = rb->getWorldTransform().inverse()*m_nodes[node].m_x;
appendFixPoint(node,rb,local);
}
void appendFixPoint(int node, btRigidBody* rb, const btVector3& local)
{
FixPoint fp;
fp.pNode = &m_nodes[node];
fp.pRBody = rb;
fp.Local = local;
fp.bFix = false;
fp.pNode->m_battach = 1;
aFixPoint.push_back(fp);
}
static MySoftBody* upcast(btCollisionObject* colObj)
{
return dynamic_cast<MySoftBody*>(colObj);
}
};
位置固定処理
物理step前後に固定処理を実行。
// 物理Step前に実行
void PreFixPoint(btDynamicsWorld *world)
{
auto& objary = world->getCollisionObjectArray();
for(int o=0;o<objary.size();++o){
auto& obj = objary[o];
MySoftBody* msb = MySoftBody::upcast(obj);
if(!msb)continue;
auto& afp = msb->aFixPoint;
for(int f=0;f<afp.size();++f){
auto& fp = afp[f];
if(!fp.bFix || !fp.pNode || !fp.pRBody)continue;
// 強制的に固定した位置を本来の位置に戻す
fp.pNode->m_x = fp.NodePos;
fp.bFix = false;
}
}
}
// 物理step後に実行
void FixPoint(btDynamicsWorld *world)
{
auto& objary = world->getCollisionObjectArray();
for(int o=0;o<objary.size();++o){
auto& obj = objary[o];
MySoftBody* msb = MySoftBody::upcast(obj);
if(!msb)continue;
auto& afp = msb->aFixPoint;
for(int f=0;f<afp.size();++f){
auto& fp = afp[f];
if(!fp.pNode || !fp.pRBody)continue;
// 強制的に位置固定
fp.NodePos = fp.pNode->m_x;//本来の位置を保存
fp.bFix = true;
fp.pNode->m_x = fp.pRBody->getWorldTransform()*fp.Local;
}
}
}
モデルデータからSoftBody生成
前回からSoftBody生成用にメタセコイアのモデルデータを使っていますが、アンカー処理など頂点単位での設定が必要になります。専用ツールを作りたくないので、モデルデータの頂点カラーを利用します(表示とSoftBosyのモデルが分離しているので可能)。

頂点カラーで頂点単位のパラメータ設定
頂点カラーの色(0~1)をアンカー接続強度。黒の場合アンカーなし。
頂点カラー透明度をSoftBodyノード(頂点)の動きやすさ。透明ほど自由に。
ノードの動きやすさ=ノードのlink強さ(SoftBodyマテリアルで設定)
ポリゴンのマテリアル名を「noweight」とするとそのポリゴンは、表示モデルへの対応付けから除外。
まだ実験中
上記の設定では、アンカー接続先の剛体は1つだけ(RGBで3つまでは可能だが未対応)。
複数設定できるように、特殊なポリゴンに特殊なマテリアルを割り当てておく。そこに接続された頂点は接続先剛体を別のものに変更。
マテリアル名「_joint_接続名」としておき、プログラム側で接続名を取得、接続先の剛体を選択。
サンプルプログラムでは、ツインテールの中央部分を背中の剛体にアンカー接続、それ以外は頭。
RGBの3つで十分かもしれない。
サンプルプログラム
主なソースコードは上記にあるものです。実験中のためソースコードがかなり分かり難いものになっています。前回SoftBodyのノード(頂点)の取得にm_qを使っていましたが、正しくはm_xです(m_qは1つ前の位置)。
サンプルに含まれている再配布モデル(ゴロペコ式ミク)とモーションデータ(WAVEFILE fullver.)は、サンプルプログラムの確認以外での使用、再々配布は禁止します。どちらも改造データの再配布可能かをそれぞれのreadme.txtで確認しています(2014/6/1現在)。
★改造データの問い合わせを作者様にしないよう、お願いします。改造データの利用は自己責任で。
“BulletのSoftBodyでスカートを揺らしてみる” をダウンロード BulletSoftBody2.zip – 1409 回のダウンロード – 955 KB
リンクなど
Bullet Physics Library
DirectXTex
DirectXTex texture processing library
ゴロペコ式ミク
【MMDモデル】ゴロペコ式ミク【配布】/ニコニコ静画
データの改造、再配布を行っています。
変更点
●独自の輪郭線処理用にポリゴンの削除、パラメータの変更
●SoftBody適用のため、マテリアルの追加、ポリゴンの切り分け
●スカート、ネクタイ、後髪の頂点を修正(重力の影響を受けた位置に)
●未対応データの削除(モーフなど)
モーションデータ WAVEFILE fullver.
【MMD】 WAVEFILE fullver. 【モーション完成】/ニコニコ動画
データの改造、再配布を行っています。
変更点
●最初の1000フレームのみ
始めまして
プログラマーのトクナガと申します
ミクのモデルを読み込み
モーションを適応さしていますが、それはDxライブラリー読み込み部分を適応してるのでしょうか?
それとも独自のライブラリーなどを使って使用しているのでしょうか?
ご解答お願いします
コメントありがとうございます。
ミクのモデル読み込みとモーション適応は、独自のプログラムを作成しています。サンプルプログラムにソースコードが入っています。とりあえず作ったものなので、あまり参考にはならないと思いますが……。