**0**

# Convert Vector to Euler (Cardan) Angles

Started by Kuroyume0161, Jun 22 2006 12:29 AM

11 replies to this topic

###
#1
Members - Reputation: **100**

Posted 22 June 2006 - 12:29 AM

Okay, I give up. I've scoured the internet and my texts. And I find the lack of information on this subject to be scandalous.
I have a vector - a 3D vector, ya know v:(x,y,z) which is from the origin. I need to get Euler (properly Cardan henceforth) angles that would rotate a point, say (1,0,0), to match that 3D vector. When I say Cardan angles, I mean explicitly one of these: XYZ, XZY, YXZ, YZX, ZXY, ZYX. The rotation order may be any one of these, so no locked into a particular rotation order methods.
No!! I cannot use quaternions or matrices or axis-angle or any other form. (PERIOD!) The deformation which I am forced to do *requires* (!!) Cardan angles since each axial rotation has a weight. So, no, don't bequest me to use quaternions or matrices or anything else. I need to convert a vector to Cardan Angles - I don't care if it involves quaternions, matrices, or axis-angles in between.
The start is a 3D vector. The end is Cardan angles that take an axially-aligned vector and rotate it so as to match that 3D vector.
And I know that there are two inevitable solutions (static frame and rotating frame). In this case, it should be safe to say that the frame is static - about the world coordinate system.
I've tried Shoemake's approach first converting the 3D vector into an axis-angle version (three-points - origin, axial vector, 3D vector), then to matrix. The results have not been correct. For this approach, definitely need more information to fill in the process.
This is a left-handed coordinate system with pre-concatenation of matrices, i.e.:
Matrix m = RotZ() * RotY() * RotX()
for an XYZ rotation matrix.
Anyone smart enough to figure that out?
Thanks,
Robert

###
#2
Crossbones+ - Reputation: **6997**

Posted 22 June 2006 - 12:36 AM

Well, the rotation to come from one vector to another could be expressed by a axis-angle pair. An axis-angle pair can be converted into a rotation matrix. And I know that methods exists to compute Euler angles from rotation matrices. Perhaps there is a more direct way, but I would wonder if the above doesn't work in principle.

EDIT: Don't know how good this is, but look e.g. at

http://www.euclideanspace.com/maths/geometry/rotations/axisAngle/index.htm

http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToMatrix/index.htm

http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToEuler/index.htm

EDIT: Don't know how good this is, but look e.g. at

http://www.euclideanspace.com/maths/geometry/rotations/axisAngle/index.htm

http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToMatrix/index.htm

http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToEuler/index.htm

###
#4
Crossbones+ - Reputation: **18617**

Posted 22 June 2006 - 03:56 AM

There is a one-parameter family of solutions. They can all be achieved like this:

#include <iostream>

#include <cmath>

using namespace std;

void rotate(double &x, double &y, double angle){

double c=cos(angle),s=sin(angle);

double x0 = x;

x = x*c - s*y;

y = x0*s + x0*y;

}

int main(void){

double target_x=1,target_y=2,target_z=3;

double x=1,y=0,z=0;

rotate(y,z,4); // Replace 4 by whatever you want. This is your free parameter

rotate(x,z,atan2(target_z,sqrt(target_x*target_x+target_y*target_y)));

rotate(x,y,atan2(target_y,target_x));

std::cout << x << ',' << y << ',' << z << std::endl;

}

###
#5
Members - Reputation: **769**

Posted 22 June 2006 - 07:13 AM

A direction vector can be turned into a pitch and yaw (two Euler angles) thusly:

An axis vector can be rotated into the direction by first rotating along the world Y axis by the pitch, then by the Z by the yaw. But you probably know that already.

I don't think it's possible to have angles about each (world) axis that don't depend on the order of rotations, because changing the order of rotations with the same angles would give you a different direction. I don't know what a Cardan angle is though.

double yaw = Math.Atan2(ds.X, ds.Y);

double pitch = Math.Atan2(ds.Z, Math.Sqrt((ds.X * ds.X) + (ds.Y * ds.Y)));

An axis vector can be rotated into the direction by first rotating along the world Y axis by the pitch, then by the Z by the yaw. But you probably know that already.

I don't think it's possible to have angles about each (world) axis that don't depend on the order of rotations, because changing the order of rotations with the same angles would give you a different direction. I don't know what a Cardan angle is though.

###
#6
Members - Reputation: **2073**

Posted 22 June 2006 - 07:29 AM

This'll probably just be a restatement of what's already been said, but:

- As Bob said above, the third angle is redundant when aligning one vector with another.

- You could use trig to compute the first two angles about whichever axes you're interested in, and leave the third as zero...

- Or you could construct the axis-angle matrix that rotates the first vector to the second and extract the angles from it.

I take it from your post that although you need (non-repeating) Euler angles as the final form, you can use other intermediate representations. If you wish to go through an axis-angle matrix representation, Shoemake's article/code is really the way to go. If you tried this and it didn't work, then there may be more to the problem, either that or we're misunderstanding it somehow. (One potential pitfall is using axis-angle and Euler-extraction functions with non-matching basis vector orientations, so watch out for this. FYI Shoemake's examples use column vectors.)

- As Bob said above, the third angle is redundant when aligning one vector with another.

- You could use trig to compute the first two angles about whichever axes you're interested in, and leave the third as zero...

- Or you could construct the axis-angle matrix that rotates the first vector to the second and extract the angles from it.

I take it from your post that although you need (non-repeating) Euler angles as the final form, you can use other intermediate representations. If you wish to go through an axis-angle matrix representation, Shoemake's article/code is really the way to go. If you tried this and it didn't work, then there may be more to the problem, either that or we're misunderstanding it somehow. (One potential pitfall is using axis-angle and Euler-extraction functions with non-matching basis vector orientations, so watch out for this. FYI Shoemake's examples use column vectors.)

###
#7
Members - Reputation: **100**

Posted 22 June 2006 - 06:21 PM

Quote:

Original post by Code Fusion

k iam goin on a limb here , but if you have the two components of the vectors couldn't you

just calculate the the angle between the two by using Cos^-1(|a|.|b|/|a||b|) ?.

Yes, but this is the direct (dihedral) angle (non-orthogonal). Good for axis-angle representations along with a cross product calculation, but how does that get me orthogonal Euler angle rotations?

Robert

[Edited by - Kuroyume0161 on June 23, 2006 1:21:27 AM]

###
#8
Members - Reputation: **100**

Posted 22 June 2006 - 06:44 PM

Quote:

Original post by Bob Janova

A direction vector can be turned into a pitch and yaw (two Euler angles) thusly:double yaw = Math.Atan2(ds.X, ds.Y);

double pitch = Math.Atan2(ds.Z, Math.Sqrt((ds.X * ds.X) + (ds.Y * ds.Y)));

An axis vector can be rotated into the direction by first rotating along the world Y axis by the pitch, then by the Z by the yaw. But you probably know that already.

I don't think it's possible to have angles about each (world) axis that don't depend on the order of rotations, because changing the order of rotations with the same angles would give you a different direction. I don't know what a Cardan angle is though.

I've found that code and it sort of works, but because of the handedness of the system, modifications were necessary to get proper angles. But even this is not working in all cases. The problem is that the rotations are ordered, the first rotation is always a 'twist' (the roll) - but it is not always about the z-azis:

XYZ, XZY: roll = X

YXZ, YZX: roll = Y

ZXY, ZYX: roll = Z

Now, I may need to adjust for this. Haven't attempted it yet. These angles need to be ordered as they are being applied to geometry deformation that weights each axis rotation (e.g.):

Vector point *= (parentGlobal * trans * MatrixRotZ(wz*zrot) * MatrixRotY(wy*yrot) * MatrixRotX(wx*xrot) * scale);

where wx,wy,wz are weights between 0.0 and 1.0.

Arbitrary rolls (because X or Y is actually a twist) may cause improper deformations. Again, I haven't had a chance to test all of these scenarios.

***

Cardan angles are the proper term for this subset of Euler angles - sometimes Euler angles are strictly applied to these rotation orders: XYX, XZX, YXY, YZY, ZXZ, ZYZ. Cardan angles do not repeat (check out Shoemake's "Euler Angle Conversion") as noted. Strictly speaking, they are not 'Euler angles', they are formally known as Cardan angles (sometimes Cardan-Bryant angles). But the term 'Euler angles' has become ubiquitous for all twelve of these rotation orders.

Here is the first test code:

// e- is the conformeE, r- is the conformeR

// - Make both endpoints local to world origin

Vector eVec = bc->GetVector(IPP_ENDPOINT) - bc->GetVector(IPP_ORIGIN);

BaseContainer* bbc = base->GetDataInstance();

Vector rVec = bbc->GetVector(IPP_ENDPOINT) - bbc->GetVector(IPP_ORIGIN);

// - If equivalent, no more processing required

if (VectorEqual(eVec, rVec, EPSILONL)) return;

// - Calculate angular offset between eVec/rVec Vectors in Rotation Order

Real r;

Vector eAngle;

Vector rAngle;

// -- Calculate Euler angles of Conformer

r = Sqrt(rVec.x*rVec.x + rVec.z*rVec.z);

rAngle.y = (r < EPSILONS) ? 0.0f : Support::Angle(rVec.z, rVec.x); // yaw

rAngle.x = -Support::Angle(rVec.y, r); // pitch

rAngle.z = 0.0f; // roll

// -- Concatenate Rotation-order specific matrix

Matrix mat;

LONG ro = bc->GetLong(IPP_RORDER);

if (ro == ROTORDER_XYZ) mat = MatrixMove(Vector(0.0)) * MatrixRotZ(rAngle.z) * MatrixRotY(rAngle.y) * MatrixRotX(rAngle.x) * MatrixScale(Vector(1.0));

else if (ro == ROTORDER_XZY) mat = MatrixMove(Vector(0.0)) * MatrixRotY(rAngle.y) * MatrixRotZ(rAngle.z) * MatrixRotX(rAngle.x) * MatrixScale(Vector(1.0));

else if (ro == ROTORDER_YXZ) mat = MatrixMove(Vector(0.0)) * MatrixRotZ(rAngle.z) * MatrixRotX(rAngle.x) * MatrixRotY(rAngle.y) * MatrixScale(Vector(1.0));

else if (ro == ROTORDER_YZX) mat = MatrixMove(Vector(0.0)) * MatrixRotX(rAngle.x) * MatrixRotZ(rAngle.z) * MatrixRotY(rAngle.y) * MatrixScale(Vector(1.0));

else if (ro == ROTORDER_ZXY) mat = MatrixMove(Vector(0.0)) * MatrixRotY(rAngle.y) * MatrixRotX(rAngle.x) * MatrixRotZ(rAngle.z) * MatrixScale(Vector(1.0));

else if (ro == ROTORDER_ZYX) mat = MatrixMove(Vector(0.0)) * MatrixRotX(rAngle.x) * MatrixRotY(rAngle.y) * MatrixRotZ(rAngle.z) * MatrixScale(Vector(1.0));

// -- Calculate Euler angles of Conformee

// Note that eVec is placed in the coordinate system where rVec is unrotated

eVec *= !mat;

r = Sqrt(eVec.x*eVec.x + eVec.z*eVec.z);

eAngle.y = (r < EPSILONS) ? 0.0f : Support::Angle(eVec.z, eVec.x); // yaw

eAngle.x = -Support::Angle(eVec.y, r); // pitch

eAngle.z = 0.0f; // roll

// These are the rotation angles from Conformee to Conformer Endpoint

Vector rotVec = Vector(-eAngle.x, -eAngle.y, 0.0);

Note the switch of Z and X in calculating the Yaw (rAngle.y and eAngle.y). This may be a result of the system handedness being left (I don't know - noone discusses important details like this). Note also that the matrix concatenation is applied to 'eVec' (the vector that needs to match the other vector 'rVec'). This makes the Euler angles absolute with respect to rVec pointing along the (0d,0d,0d) rotation axis and so that the anti-rotation (negative eAngle.x and eAngle.y) would point eVec along it as well (as best that I can determine).

ETA: It is possible that I don't want to concatenate in rotation order, but use strict XYZ. Or it could be that I need to consider rotation order in the 'direction vector to Euler angles' as well. These are unknowns since noone ever talks about it. Quaternions are great, but sometimes you need to do things that cannot be done with them. These problems have existed for centuries, yet finding maths, algorithms, and source for something like this is impossible. Someone could really make a killing just writing a book exclusively on 3D rotations - it is the bane of all 3D and there are enough problems to cover an encyclopedia. :)

Robert

[Edited by - Kuroyume0161 on June 23, 2006 1:44:56 AM]

###
#9
Members - Reputation: **100**

Posted 22 June 2006 - 07:00 PM

Quote:

Original post by jyk

I take it from your post that although you need (non-repeating) Euler angles as the final form, you can use other intermediate representations. If you wish to go through an axis-angle matrix representation, Shoemake's article/code is really the way to go. If you tried this and it didn't work, then there may be more to the problem, either that or we're misunderstanding it somehow. (One potential pitfall is using axis-angle and Euler-extraction functions with non-matching basis vector orientations, so watch out for this. FYI Shoemake's examples use column vectors.)

Yes.

Shoemake's code seems to return some incorrect results in my case - rotation angles are usually correct, but in the wrong 'slot' (e.g.: z instead of x). Part of this could well be the translation from right-left and column:major-row:major - I'm working in a left-handled coordinate system (he uses right). My matrices are row major (he uses column major). Don't know how the SDK applies vectors (column or row). My guess is as row vectors. I'd have to look at their vector * matrix operator code and figure it out. The SDK that I'm using doesn't use Matrix [4][4] or [3][3]. Instead it uses this:

Matrix

{

Vector off; // translation

Vector v1; // x-axis basis

Vector v2; // y-axis basis

Vector v3; // z-axis basis

}

The homogenous basis (0,0,0,1) is 'understood' and not present.

I could go the more strenuous route and keep the code as is and convert the matrix to column-major with a transpose and reflect.

###
#10
Members - Reputation: **100**

Posted 23 June 2006 - 01:15 PM

I'm still no closer to solving this. My gut feeling (which isn't as good as hard knowledge) is that in order to do this using the Z-axis as a standard Roll axis whereas my Roll axis varies, the two vectors will need to be such that the first (the target) or second (source) vector is more along the Z-axis than not (i.e.: rotate both vectors by 90d or 180d) so that any Roll that is not really a Roll in the ordered rotation is.

Not sure about that, but the code above (and modified just to multiply in RotX*RotY*RotZ order) doesn't always work as expected. I'm getting nearly +/-90d rolls when the vectors shouldn't be rolling. It makes for some rather nasty bend deformations.

If it weren't for these darned deformation rotations, I'd settle for axis-angle to matrix and be done with it. Alas, I need the Euler/Cardan rotations, and they need to be proper (however one defines that in this situation). To relieve some of the confusion as to why I don't seem to have full control here - this is taking the data from one 3D application and using it in another through a plugin. So, no, I don't have full control. Nor do I have full knowledge of how the source application works internally - I have almost none (and they are not using much standard anything it seems).

Thanks,

Robert

Not sure about that, but the code above (and modified just to multiply in RotX*RotY*RotZ order) doesn't always work as expected. I'm getting nearly +/-90d rolls when the vectors shouldn't be rolling. It makes for some rather nasty bend deformations.

If it weren't for these darned deformation rotations, I'd settle for axis-angle to matrix and be done with it. Alas, I need the Euler/Cardan rotations, and they need to be proper (however one defines that in this situation). To relieve some of the confusion as to why I don't seem to have full control here - this is taking the data from one 3D application and using it in another through a plugin. So, no, I don't have full control. Nor do I have full knowledge of how the source application works internally - I have almost none (and they are not using much standard anything it seems).

Thanks,

Robert

###
#11
Members - Reputation: **100**

Posted 23 June 2006 - 11:58 PM

Think that I have it figured out. And my suspicions were correct.

Taking the vector rVec and determining its main axis direction (if two values are the same, any of the two main axes will probably work similarly) tells me which axis to consider as the 'Roll' axis. Both vectors, rVec and eVec, are rotated so that their main axes (X or Y) are now the Z-axis. With that, I can calculate using the standard DirectionVector-to-EulerAngles maths. Then I just assign the Euler vector angles to the appropriate x,y,z. Now the rotations are 'minimal' and the 'Roll' axis as determined for the vector is always 0.0.

Tests so far have all been positive. There is a likelyhood of finding an exception to this - a vector with equal non-zero values like v(10,10,10) would prove an interesting situation - but it should default to standard Z-axis Roll for better or worse.

Thanks,

Robert

Taking the vector rVec and determining its main axis direction (if two values are the same, any of the two main axes will probably work similarly) tells me which axis to consider as the 'Roll' axis. Both vectors, rVec and eVec, are rotated so that their main axes (X or Y) are now the Z-axis. With that, I can calculate using the standard DirectionVector-to-EulerAngles maths. Then I just assign the Euler vector angles to the appropriate x,y,z. Now the rotations are 'minimal' and the 'Roll' axis as determined for the vector is always 0.0.

// e- is the conformeE, r- is the conformeR

// - Make both endpoints local to world origin

Vector eVec = bc->GetVector(IPP_ENDPOINT) - bc->GetVector(IPP_ORIGIN);

BaseContainer* bbc = base->GetDataInstance();

Vector rVec = bbc->GetVector(IPP_ENDPOINT) - bbc->GetVector(IPP_ORIGIN);

// - If equivalent or either is zero vector (no direction), no more processing required

if (VectorEqual(eVec, rVec, EPSILONL)) return;

if (VectorEqual(eVec, zVec, EPSILONL)) return;

if (VectorEqual(rVec, zVec, EPSILONL)) return;

// - Determine main direction axis for vector

UCHAR axis;

Vector ar = Vector(Abs(rVec.x), Abs(rVec.y), Abs(rVec.z));

// -- X

if ((ar.x > ar.y) && (ar.x > ar.z))

{

Matrix align = MatrixRotX(RAD_90) * MatrixRotZ(RAD_90) * MatrixScale(Vector(1.0));

// new y is old x, new z is old y, new x is old z

rVec *= align;

eVec *= align;

axis = 0;

}

// -- Y

else if (ar.y > ar.z)

{

// new x is old y, new z is old x, new y is old z

Matrix align = MatrixRotY(-RAD_90) * MatrixRotZ(-RAD_90) * MatrixScale(Vector(1.0));

rVec *= align;

eVec *= align;

axis = 1;

}

// -- Z

else axis = 2;

// - Calculate angular offset between eVec/rVec Vectors in Rotation Order

// -- Calculate Euler angles of Conformer

Real r;

Vector eAngle;

Vector rAngle;

r = Sqrt(rVec.x*rVec.x + rVec.z*rVec.z);

rAngle.z = 0.0f; // roll

rAngle.y = (r < EPSILONS) ? 0.0f : Support::Angle(rVec.z, rVec.x); // yaw

rAngle.x = -Support::Angle(rVec.y, r); // pitch

// -- Concatenate Rotation-order specific matrix

Matrix mat = MatrixMove(zVec) * MatrixRotX(rAngle.x) * MatrixRotY(rAngle.y) * MatrixRotZ(rAngle.z) * MatrixScale(Vector(1.0));

// -- Calculate Euler angles of Conformee

// Note that eVec is placed in the coordinate system where rVec is unrotated

eVec *= !mat;

r = Sqrt(eVec.x*eVec.x + eVec.z*eVec.z);

// These are the rotation angles from Conformee to Conformer Endpoint

eAngle.z = 0.0f; // roll

eAngle.y = (r < EPSILONS) ? 0.0f : -Support::Angle(eVec.z, eVec.x); // yaw

eAngle.x = Support::Angle(eVec.y, r); // pitch

// Reorder rotations to avoid roll rotations

Vector rotVec;

if (axis == 0)

{

rotVec.x = eAngle.z;

rotVec.y = eAngle.x;

rotVec.z = eAngle.y;

}

else if (axis == 1)

{

rotVec.x = eAngle.y;

rotVec.y = eAngle.z;

rotVec.z = eAngle.x;

}

else

{

rotVec.x = eAngle.x;

rotVec.y = eAngle.y;

rotVec.z = eAngle.z;

}

Tests so far have all been positive. There is a likelyhood of finding an exception to this - a vector with equal non-zero values like v(10,10,10) would prove an interesting situation - but it should default to standard Z-axis Roll for better or worse.

Thanks,

Robert