So I am using a quaternion to create a segment of two points in 3D space, and trying to recompute a similar quaternion later on (one representing the same vector through space; I am aware that the segment's rotation around itself is undefined). I am creating the segment as such:
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;
Where Orientation::MultVect() looks like this:
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;
}
Now this function seems to work rather well in other contexts, so I don't think the problem is here, but you never know. I should also mention that the point ends up where I expect it to, given the Quaternion I feed if (which I construct from Euler angles, sometimes with multiplication with other quaternions).
The problem appears, to me, to lie in recomputing the quaternion from Start
and End
. To do so, I use this function, which works well when orienting objects in the scene towards other objects (unless the objects in question are along the exact same Y axis, in which case I get quaternions with NaN values). Here is how I do that:
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;
}
So when I compute the segments, I also check what their reconstructed values would be, like this:
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";
And the two don't match up. For example, if the static end point is (0,14.3998,0.0558498), then the recomputed point is (0,8.05585,-6.39976). The two should be identical, though. The undefined part of the rotation shouldn't change the position of the end point, only the roll (or Z-rotation, or whatever you want to call it), which, since this is a segment, doesn't matter.
Note that when I end up using this for things other than simple segments, the roll will matter, which is why I use an up vector to make sure the objects I place along these segments will always have their tops facing upwards as much as possible (objects looking straight up or down can have a special arbitrary roll determined separately, if need be). Another goal is creating multiple segments strung together, each rotating relative to the orientation of the one that came before it rather than rotating relative to global space.
So what am I doing wrong here? Why can't I recompute a second quaternion that performs the same translation as the first one?