1

そのため、四元数を使用して 3D 空間で 2 点のセグメントを作成し、後で同様の四元数を再計算しようとしています (空間を通る同じベクトルを表すもの。それ自体の周りのセグメントの回転は未定義であることを認識しています)。私はそのようにセグメントを作成しています:

sf::Vector3<float> Start(0, 0, 0);
sf::Vector3<float> End = Start;

//Create a vector from the start to the end
sf::Vector3<float> Translation = Orientation.MultVect(sf::Vector3<float>(0, 1, 0));

//Add that vector onto the start position
End.x += Translation.x * Length;
End.y += Translation.y * Length;
End.z += Translation.z * Length;

Orientation::MultVect() は次のようになります。

sf::Vector3<float> Quaternion::MultVect(sf::Vector3<float> Vector)
{
    //From http://www.idevgames.com/articles/quaternions
    Quaternion VectorQuat = Quaternion();
    VectorQuat.x = Vector.x;
    VectorQuat.y = Vector.y;
    VectorQuat.z = Vector.z;
    VectorQuat.w = 0.0;

    Quaternion Inverse = (*this);
    Inverse.Invert();

    Quaternion Result = Inverse * VectorQuat * (*this);

    sf::Vector3<float> ResultVector;
    ResultVector.x = Result.x;
    ResultVector.y = Result.y;
    ResultVector.z = Result.z;

    return ResultVector;
}

現在、この関数は他のコンテキストではかなりうまく機能しているように見えるので、ここに問題があるとは思いませんが、わかりません。また、フィードするクォータニオン if (オイラー角から構築し、他のクォータニオンとの乗算を使用することもあります) を考えると、ポイントは期待どおりの場所で終了することにも言及する必要があります。

Start私には、問題はとからクォータニオンを再計算することにあるように見えますEnd。そのために、この関数を使用します。これは、シーン内のオブジェクトを他のオブジェクトに向ける場合にうまく機能します (問題のオブジェクトがまったく同じ Y 軸に沿っている場合を除きます。この場合、NaN 値のクォータニオンを取得します)。これが私がそれを行う方法です:

Quaternion Quaternion::FromLookVector(sf::Vector3<float> FromPoint, sf::Vector3<float> ToPoint)
{
    ///Based on this post:
    ///http://stackoverflow.com/questions/13014973/quaternion-rotate-to
    //Get the normalized vector from origin position to ToPoint
    sf::Vector3<double> VectorTo(ToPoint.x - FromPoint.x,
                                 ToPoint.y - FromPoint.y,
                                 ToPoint.z - FromPoint.z);
    //Get the length of VectorTo
    double VectorLength = sqrt(VectorTo.x*VectorTo.x +
                               VectorTo.y*VectorTo.y +
                               VectorTo.z*VectorTo.z);
    //Normalize VectorTo
    VectorTo.x /= -VectorLength;
    VectorTo.y /= -VectorLength;
    VectorTo.z /= -VectorLength;

    //Define a unit up vector
    sf::Vector3<double> VectorUp(0, -1, 0);

    //The X axis is the cross product of both
    //Get the cross product as the axis of rotation
    sf::Vector3<double> AxisX(VectorTo.y*VectorUp.z - VectorTo.z*VectorUp.y,
                              VectorTo.z*VectorUp.x - VectorTo.x*VectorUp.z,
                              VectorTo.x*VectorUp.y - VectorTo.y*VectorUp.x);
    //Normalize the axis
    //Get the length of VectorTo
    double AxisXLength = sqrt(AxisX.x*AxisX.x +
                              AxisX.y*AxisX.y +
                              AxisX.z*AxisX.z);

    //Normalize VectorTo
    AxisX.x /= AxisXLength;
    AxisX.y /= AxisXLength;
    AxisX.z /= AxisXLength;

    //Get the adjusted Y vector
    //Get the cross product of the other two axes
    sf::Vector3<double> AxisY(VectorTo.y*AxisX.z - VectorTo.z*AxisX.y,
                              VectorTo.z*AxisX.x - VectorTo.x*AxisX.z,
                              VectorTo.x*AxisX.y - VectorTo.y*AxisX.x);
    //Normalize the axis
    //Get the length of VectorTo
    double AxisYLength = sqrt(AxisY.x*AxisY.x +
                              AxisY.y*AxisY.y +
                              AxisY.z*AxisY.z);
    //Normalize VectorTo
    AxisY.x /= AxisYLength;
    AxisY.y /= AxisYLength;
    AxisY.z /= AxisYLength;

    //A matrix representing the Thing's orientation
    GLfloat RotationMatrix[16] = {(float)AxisX.x,
                                  (float)AxisX.y,
                                  (float)AxisX.z,
                                  0,
                                  (float)AxisY.x,
                                  (float)AxisY.y,
                                  (float)AxisY.z,
                                  0,
                                  (float)VectorTo.x,
                                  (float)VectorTo.y,
                                  (float)VectorTo.z,
                                  0,
                                  0,
                                  0,
                                  0,
                                  1};

    Quaternion LookQuat = Quaternion::FromMatrix(RotationMatrix);

    //Reset the quaternion orientation
    return LookQuat;
}

したがって、セグメントを計算するときに、再構築された値が次のようになるかどうかも確認します。

sf::Vector3<float> Start(0, 0, 0);
sf::Vector3<float> End = Start;

//Create a vector from the start to the end
sf::Vector3<float> Translation = Orientation.MultVect(sf::Vector3<float>(0, 1, 0));
//Add that vector onto the start position
End.x += Translation.x * Length;
End.y += Translation.y * Length;
End.z += Translation.z * Length;

std::cout << "STATIC END     (";
std::cout << End.x << ",";
std::cout << End.y << ",";
std::cout << End.z << ")\n";

///TEST
Quaternion Reconstructed = Quaternion::FromLookVector(Start, End);
Translation = Reconstructed.MultVect(sf::Vector3<float>(0, 1, 0));
sf::Vector3<float> TestEnd = Start;
TestEnd.x += Translation.x * Length;
TestEnd.y += Translation.y * Length;
TestEnd.z += Translation.z * Length;

std::cout << "RECONSTRUCTED END (";
std::cout << TestEnd.x << ",";
std::cout << TestEnd.y << ",";
std::cout << TestEnd.z << ")\n";

そして、両者は一致しません。たとえば、静的エンド ポイントが (0,14.3998,0.0558498) の場合、再計算されたポイントは (0,8.05585,-6.39976) になります。ただし、2つは同一である必要があります。回転の未定義部分は終点の位置を変更するべきではなく、ロール (または Z 回転、または任意の呼び方) のみを変更する必要があります。これはセグメントであるため、問題ではありません。

これを単純なセグメント以外のものに使用する場合、ロールが重要になることに注意してください。そのため、これらのセグメントに沿って配置するオブジェクトの上部が常に可能な限り上向きになるようにアップ ベクトルを使用します (真上または真下に見えるオブジェクトは、必要に応じて個別に決定される特別な任意のロールを持つことができます)。もう 1 つの目標は、複数のセグメントをつなぎ合わせて作成することです。各セグメントは、グローバル スペースに対して回転するのではなく、その前のセグメントの方向に対して回転します。

それで、私はここで何が間違っていますか?最初のクォータニオンと同じ変換を実行する 2 番目のクォータニオンを再計算できないのはなぜですか?

4

1 に答える 1

2

2つのベクトル間の「回転」クォータニオンをどのように計算しているかは完全にはわかりませんが、非常に面倒だと確信しています。少なくとも、私があなたを正しく理解していれば、ある方向を指す「look」ベクトルがあり、オブジェクトは原点(0,0,0)からその方向に沿って「look」します。

上記の場合、それはそれほど難しいことではありません。しかし、私が非常に独特だと思うことの1つは、クォータニオン-ベクトルの乗算が逆の順序になっているように見えることです。私はクォータニオン*ベクトルを次のように定義しています:

quat qt = *this * quat(0, vec.x, vec.y, vec.z) * inverse();
return vec3(qt.x, qt.y, qt.z);

ここで、quatコンストラクターはquat(w、x、y、z)として定義され、inverse()メソッドはコピーを返します。逆は共役に等しく、(w、-x、-y、-z)として定義されます。しかし、これが真実であるためには、クォータニオンを正規化する必要があります。そうしないと、実際には方向を表しません(そして、その逆が共役に等しくなります)。次に、クォータニオン乗算を次のように定義します。

// This describes A * B (not communative!)
w = A.w * B.w - A.x * B.x - A.y * B.y - A.z * B.z;
x = A.w * B.x + A.x * B.w + A.y * B.z - A.z * B.y;
y = A.w * B.y + A.y * B.w + A.z * B.x - A.x * B.z;
z = A.w * B.z + A.z * B.w + A.x * B.y - A.y * B.x;

それが邪魔にならないように、「角度軸」からクォータニオンを構築できるようにする必要があります。つまり、回転軸と、その軸を中心に回転する角度(ラジアン)が必要です。直感的にはあまり意味がないので、そのアルゴリズムを紹介します。

// if axis is already unit length, remove the division
double halfAngle = angle * 0.5f; // In radians
double scale = sin(halfAngle) / axis.magnitude();

w = cos(halfAngle);
x = axis.x * scale;
y = axis.y * scale;
z = axis.z * scale;

したがって、ここで、回転する軸と、その周りを回転する量をラジアンで計算する必要があります。一見複雑に見えるかもしれませんが、これは何が起こっているのかを理解するための事例にすぎません。AとBの2つのベクトルがあります。AからBへの回転を表すクォータニオンを計算します。軸を回転させるには、両方に垂直な軸が必要です。明らかに、これは次のようになります。クロス積。右手の座標系を使用している場合は、次のようになります。

axis = A × B

左利きの座標系を使用している場合は、順序を逆にするだけでよいと思いますが、私の言葉を信じないでください。次に、2つのベクトル間の角度を取得します。これは、ドット積を使用することで非常に簡単に実行できます。唯一の問題は、両方のベクトルを正規化する必要があるため、長さが1であり、内積の結果が変更されないことです。このようにして、内積は角度の正弦を返すので、実際の角度を取得するには、次のようにします。

angle = acos(normalize(A) * normalize(B))

もちろん、乗算記号は内積を表します。ここで、上記のアルゴリズムに軸と角度を接続するだけで、ルックベクトルAからルックベクトルBへの「回転」を表すクォータニオンができます。ベクトルがまったく同じ方向を指している場合、それは賢明ではありません。軸が(0,0,0)になるため、アルゴリズムを適用します。アルゴリズムを見ると、ゼロで除算しようとするか、単にすべてゼロを出力することがわかると思います。したがって、そのアルゴリズムを適用するときは常に、軸がすべてゼロではないかどうかを最初に確認します。

あなたが現在使用している式は、私には非常に奇妙で非効率的なようです。なぜ最初に行列を計算するのか、私にはよくわかりません。行列からクォータニオンを計算するのは、かなりコストのかかる計算です。実際、反対の、クォータニオンからの行列の計算はさらに高速であると私は信じています。

とにかく、それが機能するように頑張ってください!

于 2013-02-19T13:49:39.010 に答える