GLM conversion from euler angles to quaternion and back does not hold - c++

I am trying to convert the orientation of an OpenVR controller that I have stored as a glm::vec3 of Euler angles into a glm::fquat and back, but I get wildly different results and the in-game behavior is just wrong (hard to explain, but the orientation of the object behaves normally for a small range of angles, then flips in weird axes).
This is my conversion code:
// get `orientation` from OpenVR controller sensor data
const glm::vec3 eulerAnglesInDegrees{orientation[PITCH], orientation[YAW], orientation[ROLL]};
debugPrint(eulerAnglesInDegrees);
const glm::fquat quaternion{glm::radians(eulerAnglesInDegrees)};
const glm::vec3 result{glm::degrees(glm::eulerAngles(quaternion))};
debugPrint(result);
// `result` should represent the same orientation as `eulerAnglesInDegrees`
I would expect eulerAnglesInDegrees and result to either be the same or equivalent representations of the same orientation, but that is apparently not the case. These are some example values I get printed out:
39.3851 5.17816 3.29104
39.3851 5.17816 3.29104
32.7636 144.849 44.3845
-147.236 35.1512 -135.616
39.3851 5.17816 3.29104
39.3851 5.17816 3.29104
32.0103 137.415 45.1592
-147.99 42.5846 -134.841
As you can see above, for some orientation ranges the conversion is correct, but for others it is completely different.
What am I doing wrong?
I've looked at existing questions and attempted a few things, including trying out every possible rotation order listed here, conjugating the quaternion, and other random things like flipping pitch/yaw/roll. Nothing gave me the expected result.
How can I convert euler angles to quaternions and back, representing the original orientation, using glm?
Some more examples of discrepancies:
original: 4; 175; 26;
computed: -175; 4; -153;
difference: 179; 171; 179;
original: -6; 173; 32;
computed: 173; 6; -147;
difference: -179; 167; 179;
original: 9; 268; -46;
computed: -170; -88; 133;
difference: 179; 356; -179;
original: -27; -73; 266;
computed: -27; -73; -93;
difference: 0; 0; 359;
original: -33; 111; 205;
computed: 146; 68; 25;
difference: -179; 43; 180;
I tried to find a pattern to fix the final computed results, but it doesn't seem like there's one easy to identify.
GIF + video of the behavior:
Full video on YouTube
Visual representation of my intuition/current understanding:
The above picture shows a sphere, and I'm in the center. When I aim the gun towards the green half of the sphere, the orientation is correct. When I aim the gun towards the red half of the sphere, it is incorrect - it seems like every axis is inverted, but I am not 100% sure that is the case.

32.7636 144.849 44.3845
-147.236 35.1512 -135.616
Those are the same. Left 33 or right 147. You are 180 from each other. Now look up 145 - that's past up that's 35 from horizon, your back is arched.
Now roll to get your back to the sky.
If you need to use Euler, try to keep pitch in -90 to +90, and roll in -180 to +180;
if (pitch > 90) {
pitch -= 90;
yaw += 180;
roll += 180;
}
if (roll > 180) {
roll = 360 - roll;
}
or something like that.

The definition of the any kind of 3 angles to represent a rotation is not only given by the order of the rotations, if they are extrinsic or extrinsic, but also which interval of angles you choose when you define the mapping of every element of the 3D Rotation Group to a tuple of 3 angles.
Unfortunately, it is common for software libraries to fail to explicit mention which subset of angles they support, so typically it is necessary to either test their behaviour or to direct inspect the source code. For a relevant issue regarding glm see https://github.com/g-truc/glm/issues/569, and see https://github.com/robotology/idyntree/pull/504 for a related discussion on another library on which I work.
In glm master, from a quick inspection of the code (https://github.com/g-truc/glm/blob/6543cc9ad1476dd62fbfbe3194fcf19412f0cbc0/glm/gtc/quaternion.inl#L10) and from the fact that in C++ asin image is roughly (-90.0, 90.0) and atan2 image is roughly (-180.0, 180.0), the assumed interval in glm seems to be roughly (-180.0, 180.0) x (-90.0, 90.0) x (-180.0, 180.0), so by limiting the second angle (the yaw, using the names that you are using) to (-90.0, 90.0). So, what you are seeing at the GLM level is basically a mapping from the provided angles to equivalent angles in the (-180.0, 180.0) x (-90.0, 90.0) x (-180.0, 180.0) range.
However, the fact that this angles are equivalent depends on how they are used, i.e. if you have a library that clamps the euler angles outside the used ranges, instead of converting it to equivalent angles, then you will obtain strange results. For this reason, I think it would be interesting to understand your problem to know how this angles are generated (the middle angles in particular seems to be part of the range (-90, 270) that is a strange, even if valid choice) and how they are interpreted to render the object in the visualization. Once you understand that, even if the rendering function works fine for angles in the original applications ranges, you can write a function to map "original application angles" to "GLM angles" and its inverse, that you can use for your original purpose.

Roughly following tony's advice and after some trial&error and pattern identification, I managed to figure out a way to restore the original values after the conversion.
ox, oy, and oz are the original pitch, yaw, and roll in degrees, before any conversion;
fx, fy, and fz are the new pitch, yaw, and roll in degrees, obtained after converting "Euler -> quaternion -> Euler" (via glm::degrees(glm::eulerAngles(glm::normalize(quaternion)))).
if (oy > 90.f)
{
fx -= 180.f;
fy -= 180.f;
fy *= -1.f;
fz += 180.f;
if (ox > 0.f)
{
fx += 360.f;
}
}
The above code seems to make the original angle values and the one after the conversion match exactly. While it answers the original question, it doesn't solve my actual issue... I was converting to a quaternion in order to smoothly interpolate to another angle. However, it seems that using glm::mix on the quaternion after the conversion results - again - in very unpredictable rotations.

Related

Rotation accuracy error builds up too fast?

When applying rotations one after another, precision errors accumulate.
But I am surprised of how fast the error builds up.
In this example I am comparing 2 transformations that are equivalent in theory.
In practice I get 0.02 degrees error by doing just 2 rotations instead of one.
I was expecting the error to be lower.
Is there a way to make the result of these 2 transformations closer? Other than using double precision variables.
#include <glm/gtx/rotate_vector.hpp>
double RadToDeg(double rad)
{
return rad * 180.0 / M_PI;
}
const glm::vec3 UP(0, 0, 1);
void CompareRotations()
{
glm::vec3 v0 = UP;
glm::vec3 v1 = glm::normalize((glm::vec3(0.0491, 0.0057, 0.9987)));
glm::vec3 v2 = glm::normalize((glm::vec3(0.0493, 0.0057, 0.9987)));
glm::vec3 axis_0_to_1 = glm::cross(v0, v1);
glm::vec3 axis_1_to_2 = glm::cross(v1, v2);
glm::vec3 axis_global = glm::cross(v0, v2);
float angle_0_to_1 = RadToDeg(acos(glm::dot(v0, v1)));
float angle_1_to_2 = RadToDeg(acos(glm::dot(v1, v2)));
float angle_global = RadToDeg(acos(glm::dot(v0, v2)));
glm::vec3 v_step = UP;
v_step = glm::rotate(v_step, angle_0_to_1, axis_0_to_1);
v_step = glm::rotate(v_step, angle_1_to_2, axis_1_to_2);
glm::vec3 v_glob = UP;
v_glob = glm::rotate(v_glob, angle_global, axis_global);
float angle = RadToDeg(acos(glm::dot(v_step, v_glob)));
if (angle > 0.01)
{
printf("error");
}
}
If you just want to continue rotating along the same axis, then it would probably be best to just increment the rotation angle around that axis and recompute a new matrix from that angle every time. Note that you can directly compute a matrix for rotation around an arbitrary axis. Building rotations from Euler Angles, for example, is generally neither necessary nor a great solution (singularities, numerically not ideal, behavior not very intuitive). There is an overload of glm::rotate() that takes an axis and an angle that you could use for that.
If you really have to concatenate many arbitrary rotations around arbitrary axes, then using Quaternions to represent your rotations would potentially be numerically more stable. Since you're already using GLM, you could just use the quaternions in there. You might find this tutorial useful.
Floating-point multiplication isn't as precise as you think, and every time you multiply two floating-point numbers you lose precision -- quite rapidly, as you have discovered.
Generally you want to store your transforms not as the result matrix, but as the steps required to get that matrix; for example, if you are doing only a single-axis transform, you store your transform as the angle and recompute the matrix each time. However, if multiple axes are involved, this gets very complicated very quickly.
Another approach is to use an underlying representation of the transform that can itself be transformed precisely. Quaternions are very popular for this (per Michael Kenzel's answer), but another approach that can be easier to visualize is to use a pair of vectors that represent the transform in a way that you can reconstitute a normalized matrix. For example, you can think of your rotation as a pair of vectors, forward and up. From this you can compute your transformation matrix with e.g.:
z_axis = normalize(forward);
x_axis = normalize(cross(up, forward));
y_axis = normalize(cross(forward, x_axis));
and then you build your transform matrix from these vectors; given those axes and a pos for your position the (column-major) OpenGL matrix will be:
{ x_axis.x, x_axis.y, x_axis.z, 0,
y_axis.x, y_axis.y, y_axis.z, 0,
z_axis.x, z_axis.y, z_axis.z, 0,
pos.x, pos.y, pos.z, 1 }
Similarly, you can renormalize a transform matrix by extracting the Z and Y vectors from your matrix as direction and up, respectively, and reconstructing a new matrix from them.
This does take a lot more computational complexity than using quaternions, but I find it much easier to wrap my head around.

Ellipse rotated not centered

I am trying to draw a rotated ellipse not centered at the origin (in c++).
so far my code "works":
for (double i = 0; i <= 360; i = i + 1) {
theta = i*pi / 180;
x = (polygonList[compt]->a_coeff / 2) * sin(theta) + polygonList[compt]->centroid->datapointx;
y = (polygonList[compt]->b_coeff / 2) * cos(theta) + polygonList[compt]->centroid->datapointy;
xTmp = (x - polygonList[compt]->centroid->datapointx)* cos(angle1) - (y - polygonList[compt]->centroid->datapointy)*sin(angle1) + polygonList[compt]->centroid->datapointx;
yTmp = (x - polygonList[compt]->centroid->datapointx)* sin(angle1) + (y - polygonList[compt]->centroid->datapointy)*cos(angle1) + polygonList[compt]->centroid->datapointy;
}
PolygonList is a list of "bloc" which will be replaced by an ellipse of same area.
My issue is that the angles are not quite exact, as if I had to put a protractor that'd fit the shape of my ellipse, the protractor would obviously get squeezed, and so would be the angles (is that clear ?)
Here is an example: I am trying to set a point on the top ellipse (E1) which would be lying on a line drawn between the centroid of E1, and any point on the second ellipse (E2).On this example, the point on E2 lies at an angle of ~220-230 degree. I am able to catch this angle, the angle seems ok.
The problem is that if I try to project this point on E1 by using this angle of ~225 degree, I end up on the second red circle on top. it looks like my angle is now ~265 degree, but in fact, if I shape the protractor to fit in my ellipse, I get the right angle (~225) ,cf img 2)
it is a bit hard to see the angle on that re-shaped protractor, but it does show ~225 degree.
My conclusion is that the ellipse is drawn like if I had to drew a circle and then I'd compress it, which changes the distance between the angles.
Could someone tell me how I could fix that ?
PS: to draw those ellipses I just use a for loop which plots a dot at every angle (from 0 to 360). we clearly see on the first picture that the distance between the dots are different whether we are at 0 or at 90 degree.
your parametrisation is exactly that, a circle is a case of ellipse with both axes are equal. It sounds like you need use rational representation of ellipse instead of standard: https://en.m.wikipedia.org/wiki/Ellipse
So, I've asked the question above so that I could find a possible overlap between 2 ellipses by checking the distance between any point on E2 and its projection on E1: if the distance between the centroid of E1 and the projected dot on E1 is larger than the distance between the centroid of E1 to a dot on E2 I'll assume an overlap. I reckon this solution has never been tried (or I haven't search enough) and should work fine. But before working I needed to get those angles right.
I have found a way to avoid using angles and projected dots, by checking the foci:
the sum of the distance between the focus A and B to any point around an axis is constant (let's call it DE1 for E1).
I then check the distance between my foci and any point on E2. If that distance becomes less than DE1, I'll assume a connection.
So far it seems to work fine :)
I'll put that here for anyone in need.
Flo

Euler angles to GlRotatef

I want to use euler angles with opengl. Some people suggest something like:
glRotatef(Rx,1,0,0);
glRotatef(Rx,0,1,0);
glRotatef(Rx,0,0,1);
Of course if you rotate in more than one direction at a time this method completely falls apart. The better approach as far as I know is to convert everything to the format OpenGL expects (angle and a vector)
Euler angles to Quaternion is something that is covered rather extensively in a lot of different places on the internet. Unfortunately it seems every post on the internet has a different suggestion, and for the life of me none of them seem to work. It is very possible there is a suggestion that works, but it is lost to me in the long list of not quite working suggestions.
Following this suggestion has gotten me close. The rotations work in individual directions, but when combined they seem to misbehave. Currently my code looks like this:
double c1 = cos((Rx/180.0f*3.141592)/2.0f);
double c2 = cos((Ry/180.0f*3.141592)/2.0f);
double c3 = cos((Rz/180.0f*3.141592)/2.0f);
double s1 = sin((Rx/180.0f*3.141592)/2.0f);
double s2 = sin((Ry/180.0f*3.141592)/2.0f);
double s3 = sin((Rz/180.0f*3.141592)/2.0f);
double x = (s1*c2*c3-c1*s2*s3)*180.0/3.141592;
double y = (c1*s2*c3+s1*c2*s3)*180.0/3.141592;
double z = (c1*c2*s3-s1*s2*c3)*180.0/3.141592;
double w = acos(c1*c2*c3+s1*s2*s3)*2.0*180.0/3.141592;
//normalize vector, maybe not necessary?
double mag = sqrt(x*x+y*y+z*z);
x /= mag;
y /= mag;
z /= mag;
glRotatef(w,x,y,z);
Can anyone offer any suggestions on where to go from here?
The reason the first suggestion doesn't work is because it is rotating relative to the previous rotations. For example, the second line (assuming you meant glRotatef(Rx,1,0,0); glRotatef(Ry,0,1,0); glRotatef(Rz,0,0,1);) will rotate relative to the new y axis which has already been rotated by the first line. You want to rotate relative to the original axis.
Wolfram Alpha (http://mathworld.wolfram.com/EulerAngles.html) has a comprehensive description of Euler Angles, whatever representation you want to use.
One possibility would be to use the OpenGL Mathematics Library and use the extension. You can then use axisAngle rotations around each axis successively.

Rotate a 3D- Point around another one

I have a function in my program which rotates a point (x_p, y_p, z_p) around another point (x_m, y_m, z_m) by the angles w_nx and w_ny.
The new coordinates are stored in global variables x_n, y_n, and z_n. Rotation around the y-axis (so changing value of w_nx - so that the y - values are not harmed) is working correctly, but as soon as I do a rotation around the x- or z- axis (changing the value of w_ny) the coordinates aren't accurate any more. I commented on the line I think my fault is in, but I can't figure out what's wrong with that code.
void rotate(float x_m, float y_m, float z_m, float x_p, float y_p, float z_p, float w_nx ,float w_ny)
{
float z_b = z_p - z_m;
float x_b = x_p - x_m;
float y_b = y_p - y_m;
float length_ = sqrt((z_b*z_b)+(x_b*x_b)+(y_b*y_b));
float w_bx = asin(z_b/sqrt((x_b*x_b)+(z_b*z_b))) + w_nx;
float w_by = asin(x_b/sqrt((x_b*x_b)+(y_b*y_b))) + w_ny; //<- there must be that fault
x_n = cos(w_bx)*sin(w_by)*length_+x_m;
z_n = sin(w_bx)*sin(w_by)*length_+z_m;
y_n = cos(w_by)*length_+y_m;
}
What the code almost does:
compute difference vector
convert vector into spherical coordinates
add w_nx and wn_y to the inclination and azimuth angle (see link for terminology)
convert modified spherical coordinates back into Cartesian coordinates
There are two problems:
the conversion is not correct, the computation you do is for two inclination vectors (one along the x axis, the other along the y axis)
even if computation were correct, transformation in spherical coordinates is not the same as rotating around two axis
Therefore in this case using matrix and vector math will help:
b = p - m
b = RotationMatrixAroundX(wn_x) * b
b = RotationMatrixAroundY(wn_y) * b
n = m + b
basic rotation matrices.
Try to use vector math. Decide in which order you rotate, first along x, then along y perhaps.
If you rotate along z-axis, [z' = z]
x' = x*cos a - y*sin a;
y' = x*sin a + y*cos a;
The same repeated for y-axis: [y'' = y']
x'' = x'*cos b - z' * sin b;
z'' = x'*sin b + z' * cos b;
Again rotating along x-axis: [x''' = x'']
y''' = y'' * cos c - z'' * sin c
z''' = y'' * sin c + z'' * cos c
And finally the question of rotating around some specific "point":
First, subtract the point from the coordinates, then apply the rotations and finally add the point back to the result.
The problem, as far as I see, is a close relative to "gimbal lock". The angle w_ny can't be measured relative to the fixed xyz -coordinate system, but to the coordinate system that is rotated by applying the angle w_nx.
As kakTuZ observed, your code converts point to spherical coordinates. There's nothing inherently wrong with that -- with longitude and latitude, one can reach all the places on Earth. And if one doesn't care about tilting the Earth's equatorial plane relative to its trajectory around the Sun, it's ok with me.
The result of not rotating the next reference axis along the first w_ny is that two points that are 1 km a part of each other at the equator, move closer to each other at the poles and at the latitude of 90 degrees, they touch. Even though the apparent purpose is to keep them 1 km apart where ever they are rotated.
if you want to transform coordinate systems rather than only points you need 3 angles. But you are right - for transforming points 2 angles are enough. For details ask Wikipedia ...
But when you work with opengl you really should use opengl functions like glRotatef. These functions will be calculated on the GPU - not on the CPU as your function. The doc is here.
Like many others have said, you should use glRotatef to rotate it for rendering. For collision handling, you can obtain its world-space position by multiplying its position vector by the OpenGL ModelView matrix on top of the stack at the point of its rendering. Obtain that matrix with glGetFloatv, and then multiply it with either your own vector-matrix multiplication function, or use one of the many ones you can obtain easily online.
But, that would be a pain! Instead, look into using the GL feedback buffer. This buffer will simply store the points where the primitive would have been drawn instead of actually drawing the primitive, and then you can access them from there.
This is a good starting point.

Direct3D & iPhone Accelerometer Matrix

I am using a WinSock connection to get the accelerometer info off and iPhone and into a Direct3D application. I have modified Apples GLGravity's sample code to get my helicopter moving in relation to gravity, however I need to "cap" the movement so the helicopter can't fly upside down! I have tried to limit the output of the accelerometer like so
if (y < -0.38f) {
y = -0.38f;
}
Except this doesn't seem to work!? The only thing I can think of is I need to modify the custom matrix, but I can't seem to get my head around what I need to be changing. The matrix is code is below.
_x = acceleration.x;
_y = acceleration.y;
_z = acceleration.z;
float length;
D3DXMATRIX matrix, t;
memset(matrix, '\0', sizeof(matrix));
D3DXMatrixIdentity(&matrix);
// Make sure acceleration value is big enough.
length = sqrtf(_x * _x + _y * _y + _z * _z);
if (length >= 0.1f && kInFlight == TRUE) { // We have a acceleration value good enough to work with.
matrix._44 = 1.0f; //
// First matrix column is a gravity vector.
matrix._11 = _x / length;
matrix._12 = _y / length;
matrix._13 = _z / length;
// Second matrix is arbitrary vector in the plane perpendicular to the gravity vector {Gx, Gy, Gz}.
// defined by the equation Gx * x + Gy * y + Gz * z = 0 in which we set x = 0 and y = 1.
matrix._21 = 0.0f;
matrix._22 = 1.0f;
matrix._23 = -_y / _z;
length = sqrtf(matrix._21 * matrix._21 + matrix._22 * matrix._22 + matrix._23 * matrix._23);
matrix._21 /= length;
matrix._22 /= length;
matrix._23 /= length;
// Set third matrix column as a cross product of the first two.
matrix._31 = matrix._12 * matrix._23 - matrix._13 * matrix._22;
matrix._32 = matrix._21 * matrix._13 - matrix._23 * matrix._11;
matrix._33 = matrix._11 * matrix._22 - matrix._12 * matrix._21;
}
If anyone can help it would be much appreciated!
I think double integration is probably over-complicating things. If I understand the problem correctly, the iPhone is giving you a vector of values from the accelerometers. Assuming the user isn't waving it around, that vector will be of roughly constant length, and pointing directly downwards with gravity.
There is one major problem with this, and that is that you can't tell when the user rotates the phone around the horizontal. Imagine you lie your phone on the table, with the bottom facing you as you're sitting in front of it; the gravity vector would be (0, -1, 0). Now rotate your phone around 90 degrees so the bottom is facing off to your left, but is still flat on the table. The gravity vector is still going to be (0, -1, 0). But you'd really want your helicopter to have turned with the phone. It's a basic limitation of the fact that the iPhone only has a 2D accelerometer, and it's extrapolating a 3D gravity vector from that.
So let's assume that you've told the user they're not allowed to rotate their phone like that, and they have to keep it with the bottom point to you. That's fine, you can still get a lot of control from that.
Next, you need to cap the input such that the helicopter never goes more than 90 degrees over on it's side. Imagine the vector that you're given as being a stick attached to your phone, and dangling with gravity. The vector you have is describing the direction of gravity, relative to the phone's flat surface. If it were (0, -1, 0) the stick is pointing directly downwards (-y). if it were (1, 0, 0), the stick is pointing to the right of the phone (+x), and implies that the phone has been twisted 90 degrees clockwise (looking away from you at the phone).
Assume in this metaphor that the stick has full rotational freedom. It can be pointing in any direction from the phone. So moving the stick around describes the surface of a sphere. But crucially, you only want the stick to be able to move around the lower half of that sphere. If the user twists the phone so that the stick would be in the upper half of the sphere, you want it to cap such that it's pointing somewhere around the equator of the sphere.
You can achieve this quite cleanly by using polar co-ordinates. 3D vectors and polar co-ordinates are interchangeable - you can convert to and from without losing any information.
Convert the vector you have (normalised of course) into a set of 3D polar co-ordinates (you should be able to find this logic on the web quite easily). This will give you an angle around the horizontal plane, and an angle for vertical plane (and a distance from the origin - for a normalised vector, this should be 1.0). If the vertical angle is positive, the vector is in the upper half of the sphere, negative it's in the lower half. Then, cap the vertical angle so that it is always zero or less (and so in the lower half of the sphere). Then you can take the horizontal and capped vertical angle, and convert it back into a vector.
This new vector, if plugged into the matrix code you already have, will give you the correct orientation, limited to the range of motion you need. It will also be stable if the user turns their phone slightly beyond the 90 degree mark - this logic will keep your directional vector as close to the user's current orientation as possible, without going beyond the limit you set.
Try normalizing the acceleration vector first. (edit: after you check the length) (edit edit: I guess I need to learn how to read... how do I delete my answer?)
So if I understand this correctly, the iPhone is feeding you accelerometer data, saying how hard you're moving the iPhone in 3 axes.
I'm not familiar with that apple sample, so I don't know what its doing. However, it sounds like you're mapping acceleration directly to orientation, but I think what you want to do is doubly integrate the acceleration in order to obtain a position and look at changes in position in order to orient the helicopter. Basically, this is more of a physics problem than a Direct3D problem.
It looks like you are using the acceleration vector from the phone to define one axis of a orthogonal frame of reference. And I suppose +Y is points towards the ground so you are concerned about the case when the vector points towards the sky.
Consider the case when the iphone reports {0, -6.0, 0}. You will change this vector to {0, -.38, 0}. But they both normalize to {0, -1.0, 0}. So, the effect of clamping y at -.38 is influenced by magnitude of the other two components of the vector.
What you really want is to limit the angle of the vector to the XZ plane when Y is negative.
Say you want to limit it to be no more than 30 degrees to the XZ plane when Y is negative. First normalize the vector then:
const float limitAngle = 30.f * PI/180.f; // angle in radians
const float sinLimitAngle = sinf(limitAngle);
const float XZLimitLength = sqrtf(1-sinLimitAngle*sinLimitAngle);
if (_y < -sinLimitAngle)
{
_y = -sinLimitAngle;
float XZlengthScale = XZLimitLength / sqrtf(_x*_x + _z*_z);
_x *= XZlengthScale;
_z *= XZlengthScale;
}