Поэтому я использую кватернион, чтобы создать сегмент из двух точек в трехмерном пространстве, и пытаюсь позже вычислить аналогичный кватернион (один, представляющий один и тот же вектор в пространстве; я знаю, что вращение сегмента вокруг себя не определено). Я создаю сегмент как таковой:
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;
}
Теперь эта функция, кажется, работает довольно хорошо в других контекстах, поэтому я не думаю, что проблема здесь, но вы никогда не знаете. Я должен также упомянуть, что точка заканчивается там, где я ожидаю, учитывая кватернион, который я кормлю, если (который я строю из углов Эйлера, иногда с умножением на другие кватернионы).
Мне кажется, что проблема заключается в пересчете кватерниона из 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). Оба должны быть идентичны, хотя. Неопределенная часть вращения не должна изменять положение конечной точки, только поворот (или Z-вращение, или как вы хотите это называть), что, поскольку это сегмент, не имеет значения.
Обратите внимание, что когда я в конечном итоге использую это для вещей, отличных от простых сегментов, бросок будут независимо от того, почему я использую вектор вверх, чтобы убедиться, что у объектов, которые я размещаю вдоль этих сегментов, вершины всегда будут как можно выше направлены вверх (объекты, смотрящие прямо вверх или вниз, могут иметь специальный произвольный бросок, определяемый отдельно, если это необходимо ). Другая цель — создать несколько сегментов, соединенных вместе, каждый из которых вращается относительно ориентации того, который был до него, а не вращается относительно глобального пространства.
Так что я тут делаю не так? Почему я не могу пересчитать второй кватернион, который выполняет тот же перевод, что и первый?
Я не совсем уверен, как вы вычисляете «вращающийся» кватернион между двумя векторами, но я уверен, что это очень громоздко. По крайней мере, если я вас правильно понимаю, у вас есть векторы «взгляда», которые указывают в каком-то направлении, и объект «смотрит» вдоль этого направления от начала координат (0,0,0), правильно ?.
Если вышесказанное имеет место, это не должно быть слишком сложно. Одна вещь, которую я нахожу довольно странной, заключается в том, что ваше кватернион-векторное умножение выглядит в обратном порядке. У меня есть кватернион * вектор, определяемый как:
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. Вы хотите вычислить кватернион, который описывает вращение ОТ A, B. Чтобы заставить ось вращаться вокруг, мы просто хотим перпендикулярную ось к обоим, очевидно, это было бы сделано, беря перекрестное произведение. Если вы используете правую систему координат, это будет:
axis = A × B
Если вы используете левую систему координат, я думаю, что вы должны просто изменить порядок, но не поверьте мне на слово. Теперь, чтобы получить угол между двумя векторами. Это очень просто сделать, взяв точечное произведение. Единственный улов в том, что вы должны нормализовать оба вектора, чтобы они имели длину 1 и не влияли на результат точечного произведения. Таким образом, скалярное произведение вернет косинус угла, поэтому, чтобы получить фактический угол, мы можем сделать:
angle = acos(normalize(A) * normalize(B))
Знак умножения, конечно же, обозначает скалярное произведение. Теперь мы просто включаем ось и угол в алгоритм, который я дал вам выше, и у нас есть кватернион, описывающий «вращение» от вектора взгляда A до вектора взгляда B. Теперь, если векторы указывают в одном и том же направлении, это было бы неразумно применить алгоритм, так как ось будет (0,0,0). Если вы посмотрите на алгоритм, я надеюсь, вы увидите, что либо попытаетесь разделить на ноль, либо просто вывести все нули. Поэтому всякий раз, когда я применяю этот алгоритм, я сначала проверяю, не являются ли все оси нулями.
Формула, которую вы сейчас используете, мне кажется очень странной и неэффективной. Я не очень понимаю, почему вы сначала вычисляете матрицу, вычисление кватерниона из матрицы является довольно дорогим вычислением. На самом деле я считаю, что вычисление противоположности, матрицы из кватерниона, еще быстрее.
В любом случае, удачи в работе!
Других решений пока нет …