結果としてそれなりのものができましたが、何というかシミュレーションって計算精度とかめんどくさいなと改めて思いました・・・。
独楽の運動の再現です。
重心位置に重力が働いています。それによって独楽が歳差運動(さいさ~自分は最初読めなかったw)します。
数字は内部のデータの羅列なのですが、消すのが面d、というかそもそもわす、わざわざ消すこともないと思ったので特に気にしないでください
今回コアとなるコードはこれです
void RigidBody::AroundFixedPoint(){
Phy dt = Environment::Instance().GetTimeDelta();
//慣性テンソルを求める
Matrix3x3> I
(
CurrentIkk_.x, CurrentIkj_.x, -CurrentIkj_.z,
-CurrentIkj_.x, CurrentIkk_.y, CurrentIkj_.y,
CurrentIkj_.z, -CurrentIkj_.y, CurrentIkk_.z
);
//角運動量を算出
Vector> L = I*o_;
//オブジェクト空間における角速度の時間変化を算出
Vector> dotO = Linear::GetSolutions3ByLU(I, N_-(o_^L));
//角速度の更新
o_ += dotO * dt;
//角速度ベクトルから回転クォータニオンを導出
Vector o = GetValue(o_);
//角変位を求める
double omega = o.Length()*dt.GetValue();
if(omega (1.0,0.0,0.0);
omega = 0.0;
}else{
o.Normalize();
}
//角変位分回転するクォータニオン、更新される座標系からは変位は逆になることに注意
Quaternion dote = Linear::GetQuaternionByAxisAngle(-omega,o);
//回転の合成
e_ = dote ^ e_ ;
e_.Normalize();
MaterialEntity::Update();
N_ = Vector>();
}
#pragma once
#include "MaterialEntity.h"
#include "EulerAngles.h"
#include
class RigidBody :public MaterialEntity{
public:
RigidBody(
const Linear::Vector >& s, //重心位置
Physics::Phy m, //質量
const EulerAngles& e, //姿勢
const Linear::Vector>& Ikk,//重心周りの慣性モーメント
const Linear::Vector>& Ikj //重心周りの慣性乗積
)
:MaterialEntity(s,m),e_(Linear::GetQuaternionByEulerAngles(e).Reverse()),Ikk_(Ikk),Ikj_(Ikj),fixedPoint_(s),CurrentIkk_(Ikk),CurrentIkj_(Ikj)
{}
~RigidBody(){}
//ワールド空間基準で力を加える
virtual void AddForce(const Linear::Vector >&, const Linear::Vector >&);
//オブジェクト空間基準で力を加える
virtual void AddForceObject(const Linear::Vector >&, const Linear::Vector >&);
//固定点の指定
void SetFixedPoint(const Linear::Vector >&);
//固定軸の指定
void SetfixedAxis_(const Linear::Vector >&);
//オブジェクト空間への変換クォータニオンを得る
virtual Linear::Quaternion GetQuaternion(){return e_;}
virtual void Update();
virtual void Draw();
private:
void CulcInertiaTensor();
void AroundFixedPoint();
void AroundFixedAxis();
private:
Linear::Quaternion e_; //ワールド空間からオブジェクト空間への変換クォータニオン
Linear::Vector> o_; //オブジェクト空間から見た角速度ベクトル
Linear::Vector> Ikk_; //重心周りの慣性モーメント
Linear::Vector> Ikj_; //重心周りの慣性乗積
Linear::Vector> N_; //オブジェクト空間から見たオブジェクトにかかるトルク
//拘束回転用変数
Linear::Vector > fixedPoint_;//現在の固定点(オブジェクト空間。なければ重心)
Linear::Vector > fixedAxis_; //現在の固定軸(オブジェクト空間。なければ0)
Linear::Vector> CurrentIkk_;//現在の固定点周りの慣性モーメント
Linear::Vector> CurrentIkj_;//現在の固定点周りの慣性乗積
};
これだけのコードを書くのにどれだけ苦労したことやら・・・(泣)
まず、最初に書いたコードは
//角速度ベクトルから回転クォータニオンを導出
以下のコードが全く別のものでした。
当初姿勢制御用のメンバ変数e_はクォータニオンでなくオイラー角であらわしていました
//オイラー角の時間変化を算出
EulerAngles> dotRad(0.0,0.0,0.0);
double cosc = cos(e_.c);
double sinc = sin(e_.c);
double cosb = cos(e_.b);
double sinb = sin(e_.b);
dotRad.b = o_.x*cosc - o_.y*sinc;
if(abs(cosb) > VTOL){
dotRad.a = (cosc*o_.y+sinc*o_.x) / cosb;
}else{
dotRad.a = Phy(0.0);
}
dotRad.c = o_.z + dotRad.a*sinb;
EulerAngles temp(dotRad.a*dt,dotRad.b*dt,dotRad.c*dt);
//オイラー角の更新。
e_ += temp;
あるんですよね。落とし穴が
dotRad.a = (cosc*o_.y+sinc*o_.x) / cosb;
こいつです。cosbが小さくなるとdotRadの値の精度が話にならないくらい悪くなるんです。
おかげさまで回転はのろのろと動くのがやっと。角速度が大きくなれば途端にオイラー角の時間変化が発散してしまいます。
ここで登場してもらったのがクォータニオンさん。
最初に使うときは抵抗しかありませんでした。
というのも行列、ベクトル、オイラー角は(座標系や、掛け算の方向がDirectXでは一般的でないことによく悩まされるとはいえ)日頃使い慣れたものですが
クォータニオンは全くと言っていいほど(回転に使えるんだなぁジンバルロックないんだなぁくらいにしか)知らないわけです。
知識の信頼性でいえば
ベクトル: 100
行列: 88
オイラー角: 60
クォータニオン: 10
こんなものです。
ということでいろいろ調べました。
サイトの情報をあさったりもしましたが、取りあえず家にあった実例で学ぶゲーム3D数学(めんどくさくって読むの途中でやめてた)に紹介されてたクォータニオンの実装を参考にしてみることに
・・・・
・・・・
またですよ。
どうして外積の演算順序を変えるんだよ。
ちゃんと数学の世界で使われてる順序に演算しろよ。
変だと思ったから大学の図書館で調べなかったらまんまとだまされるとこでしたよ。
全く、行列といい、座標空間といい、クォータニオンといい、なんでもありかよ・・・
しかもサイトのほとんどが順番逆の方法採用してるし。
頭の中でいちいち変換公式を並べないといけないこっちの身にもなってくれよ。
鏡文字で文章書くようなもんなんですよ。
しかしそんなことはさておき、クォータニオンを導入したこと自体は正しかったようです。
ややこしいコードが
Quaternion dote = Linear::GetQuaternionByAxisAngle(-omega,o);
e_ = dote ^ e_ ;
これだけになり、なおかつ計算精度の問題ともおさらばできました。
もちろんクォータニオンは合成後正規化しておく必要がありますが、そんなことは些細な話。
いやいや、机の上でカリカリ計算してるときほどうまくいかないものですね。
高々独楽の運動ごときすぐに再現できるとたかをくくってましたよ
次は何をしようかな?