I've been trying to figure out this for hours now and I simply cannot come up with a solution for this: What I'm trying to achieve is transforming an orientation vector into 3 glm::rotation
calls.
I have a cylinder positioned at (0, 0, 0)
with a certain radius and height. With the Leap Motion SDK I'm performing tool tracking that gives me the tip position and a direction vector (that is expressed as a unit vector pointing in the same direction as the tip):
Source: https://developer.leapmotion.com/documentation/skeletal/java/api/Leap.Pointable.html
Furthermore, yaw
, pitch
and roll
can be extracted form that vector using the following SDK functions:
/// The yaw angle in radians.
///
/// Yaw is the angle between the negative z-axis and the projection of
/// the vector onto the x-z plane. In other words, yaw represents rotation
/// around the y-axis. If the vector points to the right of the negative z-axis,
/// then the returned angle is between 0 and pi radians (180 degrees);
/// if it points to the left, the angle is between 0 and -pi radians.
///
/// \image html images/Math_Yaw_Angle.png
///
/// @returns The angle of this vector to the right or left of the negative z-axis.
float yaw() const
{
return std::atan2(x, -z);
}
/// The pitch angle in radians.
///
/// Pitch is the angle between the negative z-axis and the projection of
/// the vector onto the y-z plane. In other words, pitch represents rotation
/// around the x-axis.
/// If the vector points upward, the returned angle is between 0 and pi radians
/// (180 degrees); if it points downward, the angle is between 0 and -pi radians.
///
/// \image html images/Math_Pitch_Angle.png
///
/// @returns The angle of this vector above or below the horizon (x-z plane).
float pitch() const {
return std::atan2(y, -z);
}
/// The roll angle in radians.
///
/// Roll is the angle between the negative y-axis and the projection of
/// the vector onto the x-y plane. In other words, roll represents rotation
/// around the z-axis. If the vector points to the left of the negative y-axis,
/// then the returned angle is between 0 and pi radians (180 degrees);
/// if it points to the right, the angle is between 0 and -pi radians.
///
/// \image html images/Math_Roll_Angle.png
///
/// Use this function to get roll angle of the plane to which this vector is a
/// normal. For example, if this vector represents the normal to the palm,
/// then this function returns the tilt or roll of the palm plane compared
/// to the horizontal (x-z) plane.
///
/// @returns The angle of this vector to the right or left of the y-axis.
float roll() const {
return std::atan2(x, -y);
}
In my OpenGL rendering loop, I'm extracting the tip position as well as the direction vector every frame. I'm passing the model matrix as a uniform variable to my shaders in the following way:
cylinder->ResetModelMatrix(); // Set model matrix to identity
glm::vec3 translation = glm::vec3(toolTipPosition.x, toolTipPosition.y, toolTipPosition.z);
cylinder->TranslateModel(translation);
cylinderProgram->SetUniform("modelMatrix", cylinder->GetModelMatrix());
With only the translation, this works nicely and looks something like this (with the cylinder always pointing up):
The problem occurs when I'm trying to rotate the cylinder according to the Leap's tool orientation. I have the following code:
yaw = toolDirection.yaw() * Leap::RAD_TO_DEG;
// similarly for pitch and roll
cylinder->Rotate(yaw, glm::vec3(0.0f, 1.0f, 0.0f));
// pitch around (1, 0, 0) and roll around (0, 0, 1)
However, I'm not getting the proper orientations and the cylinder is "flickering" and jumping between a lot of orientations when I try to apply this.
The important transformation functions are located in an interface from which my cylinder class is inheriting...
void Drawable::ResetModelMatrix()
{
this->modelMatrix = glm::mat4(1.0f);
}
void Drawable::TranslateModel(glm::vec3 translationVector)
{
this->modelMatrix = glm::translate(this->modelMatrix, translationVector);
}
void Drawable::RotateModel(float angle_in_degrees, glm::vec3 axisOfRotation)
{
this->modelMatrix = glm::rotate(this->modelMatrix, angle_in_degrees, axisOfRotation);
}
void Drawable::ScaleModel(glm::vec3 scalingVector)
{
this->modelMatrix = glm::scale(this->modelMatrix, scalingVector);
}
...with modelMatrix
beeing a member variable.
I've scanned a lot of similar questions on the topic of rotation in OpenGL and in general, but I'm not really sure what's exactly the problem with my code here.
I guess that there is some misunderstanding of what we have and what we want to get.
The case is that, the direction vector does not uniquely determines the orientation of the body in space. To make this statement clear, let's get a closer look at how can the rotations be represented in 3D space.
Rotations about the origin have three degrees of freedom. This means, that you need three independent parameters to uniquely specify the orientation of a body. Exactly three, no more no less. There is no matter what are they as far as none of them can't be expressed it terms of other. A 3D rotation can be specified in a number of ways. The most usual methods are:
Let's get a closer look to your problem. You have a direction vector and you want to use it to determine orientation of the cylinder. Essentially, you have only two independent parameters, because the length of the direction vector does not matter (let it be a unit vector for simplicity). So you can't just get the rotation from a direction vector, because there would be one arbitrary parameter.
It is just impossible to get three unknown independent parameters from the input that contain only two independent parameters. There is a need for some additional constraint. Usually it is an Up-vector. The additional constraint transforms the three unknown independent parameters into dependent parameters.
I'd advise you to use glm::orientation function that builds an orientation matrix from the input of a direction vector and an up-vector.
As for yaw, pitch and roll angles, that you get from the specified SDK functions. This functions doesn't not work in a way you are trying to use them. You should specify an up-vector. For example, let's consider, that the up-vector is the direction of the y axle. This means, that the roll angle would be always zero. You should calculate the yaw angle, just as you do it before. So:
float yaw = std::atan2(x, -z);
The pitch angle would be std::atan2(y, -z), but in rotated frame, not in the original. You should consider the pitch angle as an arc-tangent of the ratio of the direction vector projection length onto the x-z plane and its projection onto y axle. I mean:
float projectionLength = std::sqrt(x * x + z * z);
float pitch = std::atan2(y, projectionLength);
When you are constructing the transform matrix, you should first apply the rotation appropriate to the pitch angle, then the rotation appropriate to the yaw rotation and then the translation.
See this for more information about the Tait–Bryan angles
Also, you should be aware of the fact, that rotations are not commutative. This means that you should perform rotations in a strict sequence. There are six possibilities of choosing the rotation axes for Tait–Bryan angles, that are called as conventions. These conventions depend on the axes about which the rotations are carried out.