エンジンで3Dポータルシステムを作成しています(ポータルゲームなど)。各ポータルには、クォータニオンに保存された独自の方向があります。ポータルの1つで仮想シーンをレンダリングするには、2つの四元数の差を計算する必要があります。その結果、仮想シーンを回転させます。
左側の壁に最初のポータルを作成し、右側の壁に2番目のポータルを作成する場合、1つの軸から別の軸への回転は1つの軸のみで行われますが、たとえば、最初のポータルが床に作成され、2番目のポータルが床に作成される場合、右の壁では、1つの軸から別の軸への回転が2軸になる可能性があります。回転がうまくいかないため、それが問題です。
たとえばX
軸とZ
軸の向きが1つの四元数に一緒に格納されており、手動でX
* Z
(またはZ
* X
)ですが、1つのクォータニオン(差分クォータニオン)でそれを行う方法は?または、シーンの回転を修正する他の方法はありますか?
編集:
この画像には、2つのポータルP1とP2があり、矢印はそれらがどのように回転するかを示しています。 P1を調べていると、P2がどのように見えるかがわかります。この画像の仮想シーンのようにメインシーンを回転する必要がある回転を見つけるには、次のようにします。
上記のこの方法は、差異が1つの軸でのみ発生する場合にのみ機能します。 1つのポータルが床または天井にある場合、差分クォータニオンは複数の軸に構築されるため、これは機能しません。提案されたように、P1のクォータニオンをP2のクォータニオンに掛けようとしましたが、逆にこれは機能しません。
編集2:
P2とP1の違いを見つけるには、次のようにします。
Quat q1 = P1->getOrientation();
Quat q2 = P2->getOrientation();
Quat diff = Quat::diff(q2, q1); // q2 * diff = q1 //
Quat :: diff関数は次のとおりです。
GE::Quat GE::Quat::diff(const Quat &a, const Quat &b)
{
Quat inv = a;
inv.inverse();
return inv * b;
}
逆:
void GE::Quat::inverse()
{
Quat q = (*this);
q.conjugate();
(*this) = q / Quat::dot((*this), (*this));
}
共役:
void GE::Quat::conjugate()
{
Quat q;
q.x = -this->x;
q.y = -this->y;
q.z = -this->z;
q.w = this->w;
(*this) = q;
}
ドット製品:
float GE::Quat::dot(const Quat &q1, const Quat &q2)
{
return q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w;
}
オペレーター*:
const GE::Quat GE::Quat::operator* ( const Quat &q) const
{
Quat qu;
qu.x = this->w*q.x + this->x*q.w + this->y*q.z - this->z*q.y;
qu.y = this->w*q.y + this->y*q.w + this->z*q.x - this->x*q.z;
qu.z = this->w*q.z + this->z*q.w + this->x*q.y - this->y*q.x;
qu.w = this->w*q.w - this->x*q.x - this->y*q.y - this->z*q.z;
return qu;
}
オペレーター/:
const GE::Quat GE::Quat::operator/ (float s) const
{
Quat q = (*this);
return Quat(q.x / s, q.y / s, q.z / s, q.w / s);
}
[〜#〜] glm [〜#〜] ライブラリでテストしたので、これらすべてが機能します
私は問題を解決しました。結局のところ、2つのローテーションの間に違いは必要ありません。 180度の回転で1つの回転を乗算し、次に(行列を使用して)2番目の回転の逆を乗算します。
Matrix m1 = p1->getOrientation().toMatrix();
Matrix m2 = p2->getOrientation().toMatrix();
Matrix model = m1 * Matrix::rotation(180, Vector3(0,1,0)) * Matrix::inverse(m2);
このように計算する翻訳:
Vector3 position = -p2->getPosition();
position = model * position + p1->getPosition();
model = Matrix::translation(position) * model;
diff * q1 == q2
のような四元数diff
を検索する場合は、乗法逆数を使用する必要があります。
diff * q1 = q2 ---> diff = q2 * inverse(q1)
where: inverse(q1) = conjugate(q1) / abs(q1)
and: conjugate( quaternion(re, i, j, k) ) = quaternion(re, -i, -j, -k)
四元数が回転四元数である場合、それらはすべて単位四元数でなければなりません。これにより、逆を簡単に見つけることができます。abs(q1) = 1
なので、i
、j
、およびk
を否定するだけで、inverse(q1) = conjugate(q1)
を見つけることができます。コンポーネント。
ただし、説明するシーンベースの幾何学的構成の種類では、変換を正しく計算する必要があるため、おそらく実際には上記を実行したくないでしょう。
すべてのことを正しく行う最も簡単な方法は、クォータニオンを4x4回転行列に変換し、それらを適切な順序で4x4変換行列と乗算することです。これは、ほとんどの紹介用コンピュータグラフィックテキストで説明されています。
ユークリッド変換を手動で作成し、回転をクォータニオン形式に保ちながら、クォータニオンを個別の移動ベクトルに段階的に適用することは確かに可能です。ただし、この方法は技術的にわかりにくく、コーディングエラーが発生しやすい傾向があります。4x4マトリックス形式が従来型である理由はいくつかあります。大きな方法の1つは、そのように正しく行う方が簡単であるように見えることです。
クォータニオンは次のように機能します。ローカル参照フレームは、架空のクォータニオン方向i、j、kとして表されます。例えば、ポータルドア1に立って矢印の方向を見る観察者の場合、方向iは矢印の方向を表すことができ、jは上であり、k = ijは観察者の右側を指す。クォータニオンq1で表されるグローバル座標では、3D座標の軸は
q1*(i,j,k)*q1^-1=q1*(i,j,k)*q1',
ここで、q 'は共役であり、単位四元数の場合、共役は逆です。
ここでのタスクは、グローバル座標で表されるローカルフレーム1の方向q *(i、j、k)* q 'がグローバル座標でフレーム2の回転方向と一致するように、単位四元数qを見つけることです。スケッチから、前方が後方になり、左が右になる、つまり
q1*q*(i,j,k)*q'*q1'=q2*(-i,j,-k)*q2'
=q2*j*(i,j,k)*j'*q2'
これは、
q1*q=q2*j or q=q1'*q2*j.
ただし、詳細は異なる場合があります。主に、別の軸がjではなく「上」方向を表す場合があるためです。
スケッチのグローバルシステムが下からであり、グローバルiが垂直方向の前方、グローバルjが上、グローバルkが右を指す場合、local1-(i、j、k)はグローバル-( -i、j、-k)、与える
q1=j.
local2-(i、j、k)はglobal-(-k、j、i)であり、
q2=sqrt(0.5)*(1+j),
以来
(1+j)*i*(1-j)=i*(1-j)^2=-2*i*j=-2*k and
(1+j)*k*(1-j)=(1+j)^2*k= 2*j*k= 2*i
これを実装の実際の値と比較すると、軸とクォータニオン方向の割り当てをどのように変更する必要があるかがわかります。
いいえ、希望する最終的な四元数を取得するには、2つの四元数を掛け合わせる必要があります。
最初のローテーションがq1
で、2番目のローテーションがq2
だとします。それらをこの順序で適用します。
結果の四元数はq2 * q1
になります。これは、複合回転を表します(四元数は左側の乗算を使用するため、q2
は、左から乗算することによりq1
に適用されます)
単一の四元数の計算に関する簡単なチュートリアルについては、私の 以前のスタックオーバーフローの回答 を参照してください
明確にするために、回転行列とオイラー角で同様の問題に直面するでしょう。 X、Y、およびZに関する変換を定義し、それらを乗算して、結果の変換行列( wiki )を取得します。ここでも同じ問題があります。回転行列と四元数は、回転を表すほとんどの方法で同等です。四元数は、表現するのが少し簡単であるため(そしてジンバルロックのアドレス指定が簡単であるため)、主に推奨されます。