Advertisement

Convert Vector to Euler (Cardan) Angles

Started by June 22, 2006 06:29 AM
10 comments, last by alvaro 18 years, 4 months ago
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
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
Advertisement
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|) ?.
"Any intelligent fool can make things bigger, more complex, and more violent. It takes a touch of genius -- and a lot of courage -- to move in the opposite direction."
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;}


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.
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.)
Advertisement
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]
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]
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.
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

This topic is closed to new replies.

Advertisement