Sign in to follow this  
yezu666

Direction Vector to Rotation Matrix

Recommended Posts

yezu666    100
Hello!
I've been trying to solve a (rather simple?) problem lately. I'm surprised I haven't been able to find anything really useful on the net so far.

The thing is I have 3d direction vector. Use it to set the direction of an object in 3d space. To do that I need of course a transformation matrix.
I have a function that properly calculates the LookAt matrix. However it is correct for the view transformation. I had no success in using it to calculate the rotation of the object.

I have also tried using trigonometry using (assuming Y is up):
[code]
if(dir.getY() != 0.0f)
xAngle = (float)Math.toDegrees(Math.atan((dir.getZ()*dir.getZ()+dir.getX()*dir.getX())/dir.getY()));

if(dir.getZ() != 0.0f)
yAngle = (float)Math.toDegrees(Math.atan(dir.getX()/dir.getZ()));

TransformationMatrix.calculateRotationMatrix(xAngle, yAngle, 0.0f);
[/code]
However this does produce proper results only in certain situations.

Can anybody suggest a solution?

Thanks a lot :)

Share this post


Link to post
Share on other sites
haegarr    7372
[quote name='Yezu666' timestamp='1305888899' post='4813414']
...
The thing is I have 3d direction vector. Use it to set the direction of an object in 3d space. To do that I need of course a transformation matrix.
I have a function that properly calculates the LookAt matrix. However it is correct for the view transformation. I had no success in using it to calculate the rotation of the object.
...
[/quote]
The difference between the view transformation (a global-to-local transformation) and its camera transformation (a local-to-global transformation) is that the one is the inverse of the other. So, if your implementation actually computes a view transformation, then invert the resulting matrix and you're done.

Because the view transformation usually is composed of a translation and a rotation only, like so
[b]T[/b]( [b]p[/b] ) * [b]R[/b]
for column vectors, its inverse is probably computed simply as
( [b]T[/b]( [b]p[/b] ) * [b]R[/b] )[sup]-1[/sup] = [b]R[/b][sup]-1[/sup] * [b]T[/b]( [b]p[/b] )[sup]-1[/sup] = [b]R[/b][sup]t[/sup] * [b]T[/b]( -[b]p[/b] )
where the t denotes the transpose operation, so you may avoid a full-fledged matrix inversion implementation. (You have to reorder the matrices if you use row vectors, of course.)

Share this post


Link to post
Share on other sites
yezu666    100
[quote name='haegarr' timestamp='1305889342' post='4813416']
( [b]T[/b]( [b]p[/b] ) * [b]R[/b] )[sup]-1[/sup] = [b]R[/b][sup]-1[/sup] * [b]T[/b]( [b]p[/b] )[sup]-1[/sup] = [b]R[/b][sup]t[/sup] * [b]T[/b]( -[b]p[/b] )
[/quote]

Thanks for the answer :)
I'm trying to experiment with the transpose of the rotational part of the LookAt matrix, without much success unfortunately.
However the weird thing is that with the transposed matrix I get some scaling effects when applied to a mesh. Should that happen?

Share this post


Link to post
Share on other sites
yezu666    100
Ok. Solved. Sort of :)

I've managed to implement a function using hybrid method. It seems to work. However is not very elegant.
For anyone interested:

[code]
public static TransformationMatrix calculateDirectionalMatrix(Vector3d dir, Vector3d up)
{
float [] matrix = new float[16];

//Calculating as if a normal LookAt function:
Vector3d forward = new Vector3d(dir);

//Only with inverted direction and no height component.
forward.setX(-forward.getX());
forward.setY(0.0f);

forward.normalize();

Vector3d side = Vector3d.cross(forward , up);
side.normalize();

Vector3d plane_up = Vector3d.cross(side , forward);
plane_up.normalize();

matrix[0] = side.getX();
matrix[1] = plane_up.getX();
matrix[2] = -forward.getX();
matrix[3] = 0.0f;

matrix[4] = side.getY();
matrix[5] = plane_up.getY();
matrix[6] = -forward.getY();
matrix[7] = 0.0f;

matrix[8] = side.getZ();
matrix[9] = plane_up.getZ();
matrix[10] = -forward.getZ();
matrix[11] = 0.0f;

matrix[12] = 0.0f;
matrix[13] = 0.0f;
matrix[14] = 0.0f;
matrix[15] = 1.0f;

//Complete directional matrix for the XZ part of the direction.
TransformationMatrix ret = new TransformationMatrix(matrix);

//Now if relevant we calculate the height component of the direction using the trigonometry function.
if(dir.getY() != 0.0f)
{
float angle = (float)Math.toDegrees(Math.asin(dir.getY()));
TransformationMatrix Y = TransformationMatrix.calculateRotationMatrix(angle, 0.0f, 0.0f);

//We multiply matrices as D = XZ * Y;
ret.multiply(Y);
}

//TADA!
return ret;
}
[/code] Edited by Yezu666

Share this post


Link to post
Share on other sites
gsamour    140
If you have a direction vector (I'm assuming this means your "forward" vector), then all you need to do to create the transformation matrix is create a basis using that vector.

Pseudocode:

vector3 tempUp = [0, 1, 0]
vector3 right = forward.cross(tempUp)
vector3 up = right.cross(forward)

matrix3x3 rotationMat =

[ right.x forward.x up.x ]
[ right.y forward.y up.y ]
[ right.z forward.z up.z ]

(haven't double checked but i'm almost sure this is correct)


EDIT: this all depends on whether you're using a right-handed or left-handed system (column-major or row-major matrices)

Share this post


Link to post
Share on other sites
haegarr    7372
[quote name='Yezu666' timestamp='1305893865' post='4813440']
I'm trying to experiment with the transpose of the rotational part of the LookAt matrix, without much success unfortunately.
However the weird thing is that with the transposed matrix I get some scaling effects when applied to a mesh. Should that happen?
[/quote]
Definitely not.

[quote name='Yezu666' timestamp='1305900613' post='4813467']
Ok. Solved. Sort of :)

I've managed to implement a function using hybrid method. It seems to work. However is not very elegant.
For anyone interested:

[code]
public static TransformationMatrix calculateDirectionalMatrix(Vector3d dir, Vector3d up)
{
float [] matrix = new float[16];

//Calculating as if a normal LookAt function:
Vector3d forward = new Vector3d(dir);

//Only with inverted direction and no height component.
forward.setX(-forward.getX());
forward.setY(0.0f); // <<< setY
forward.setY(-forward.getY()); // <<< setY

forward.normalize();

...[/code]
[/quote]
That code snippet is probably wrong: It negates the x component of the local forward vector variable, sets the y component to 0 and afterwards negates it, and does nothing with the z component. However, the method is ... abstruse. Why do you suppress the y component first and later consider it by rotation?

[quote name='gsamour' timestamp='1305901794' post='4813475']
If you have a direction vector (I'm assuming this means your "forward" vector), then all you need to do to create the transformation matrix is create a basis using that vector.

Pseudocode:

vector3 tempUp = [0, 1, 0]
vector3 right = forward.cross(tempUp)
vector3 up = right.cross(forward)

matrix3x3 rotationMat =

[ right.x forward.x up.x ]
[ right.y forward.y up.y ]
[ right.z forward.z up.z ]

(haven't double checked but i'm almost sure this is correct)


EDIT: this all depends on whether you're using a right-handed or left-handed system (column-major or row-major matrices)
[/quote]
Well, not to be offending, but ...

1. A rotation matrix must be orthonormal. I.e. the length of each column / row vector must be 1. The cross product gives you a vector with the length
| [b]a[/b] x [b]b[/b] | = | [b]a[/b] | * | [b]b[/b] | * sin( <[b]a[/b],[b]b[/b]> )
that is generally not 1 due to the sine, even if the participating vectors are of unit length. So some re-normalization is missed in your code!

E.g.

vector3 tempUp = [0, 1, 0]
forward.normalize();
vector3 right = forward.cross(tempUp)
right.normalize();
vector3 up = right.cross(forward)
up.normalize(); // (should not be necessary)

2. You've forgotten to mention that the matrix is for column vector use. "column-major" and "row-major" are terms that describe how the elements of a 2D matrix construct are arranged in linear (i.e. 1D) memory. Your code does no deal with this aspect in any way. Instead you meant "column vector use" vs. "row vector use".

3. How does "this all depends on whether you're using a right-handed or left-handed system" ?

4. Right-/left-handedness and matrix layout has absolutely nothing in common.

Share this post


Link to post
Share on other sites
gsamour    140
Not offended in any way :)


1. Oops... I forgot to normalize the vectors... Thanks!

2. The handedness affects the order in which you do the cross product

3. By row-major and column-major I meant the same thing as row-vectors and column-vectors (I've always heard of the term row-major and column-major referred to matrices)

Share this post


Link to post
Share on other sites
haegarr    7372
[quote name='gsamour' timestamp='1305903872' post='4813488']
Not offended in any way :)
[/quote]
Thanks :)

[quote name='gsamour' timestamp='1305903872' post='4813488']
2. The handedness affects the order in which you do the cross product
[/quote]
Assuming you compute the cross-product of [b]e[/b][sub]x[/sub] and [b]e[/b][sub]y[/sub], i.e. the unit vectors in x and y direction.
[b]e[/b][sub]x[/sub] x [b]e[/b][sub]y[/sub] = [ 1 0 0 ][sup]t[/sup] x [ 0 1 0 ][sup]t[/sup] = [ 0 0 1 ][sup]t[/sup]
what give you the unit vector in z direction. No dependency of a handedness, because [ 0 0 1 ][sup]t[/sup] is correct in both systems!

If you would instead compute it in reverse order
[b]e[/b][sub]y[/sub] x [b]e[/b][sub]x[/sub] = [ 0 1 0 ][sup]t[/sup] x [ 1 0 0 ][sup]t[/sup] = [ 0 0 -1 ][sup]t[/sup]
you would get a wrong result!

[quote name='gsamour' timestamp='1305903872' post='4813488']
3. By row-major and column-major I meant the same thing as row-vectors and column-vectors (I've always heard of the term row-major and column-major referred to matrices)
[/quote]
Using column or else row vectors is important for the matrix product. The correspondence between 2 matrices is the transpose:
( [b]M[/b] * [b]v[/b] )[sup]t[/sup] = [b]v[/b][sup]t[/sup] * [b]M[/b][sup]t[/sup]
where the left side uses a column vector [b]v[/b] and the right side its row vector equivalent. Notice that the order is reverse, and notice that the vector / matrix is transposed compared to the original one.

On the other hand, row major layout means that a matrix
[ a b ]
[ c d ]
is stored number sequence
{ a b c d }
in memory, while the same matrix stored using column major layout looks like
{ a c b d }
This issue has nothing to do with matrix math but simply with the need for computer programs and/or file formats to serialize the 2D data of a matrix.

Share this post


Link to post
Share on other sites
yezu666    100
[quote name='haegarr' timestamp='1305903601' post='4813485']
That code snippet is probably wrong: It negates the x component of the local forward vector variable, sets the y component to 0 and afterwards negates it, and does nothing with the z component. However, the method is ... abstruse. Why do you suppress the y component first and later consider it by rotation?
[/quote]
Well Indeed I made a typo. The part:
[code]forward.setY(-forward.getY());[/code]is not needed. (Fixed it BTW :P)

I remove the Y component, because the upper method didn't provide proper rotation for it. So effectively I've split the operation into two rotations and then I multiply the resulting matrices. The method is not very straight forward but provides correct results. I'd gladly change it I if could find something simpler that calculates correctly.

Although I haven't noticed so far, I think there are errors in my LookAt function. AFAIK the resulting matrix should be orthonormal and the scaling effect after transposition suggests it is not. I must look into it. However I would be very happy if You could help me. :) Here is the code:

[code]
public static TransformationMatrix calculateLookAtMatrix(Vector3d eye, Vector3d center, Vector3d up)
{
float [] matrix = new float[16];

Vector3d forward = Vector3d.sub(center , eye);
forward.normalize();

Vector3d side = Vector3d.cross(forward , up);
side.normalize();

Vector3d plane_up = Vector3d.cross(side , forward);
plane_up.normalize();

matrix[0] = side.getX();
matrix[1] = plane_up.getX();
matrix[2] = -forward.getX();
matrix[3] = 0.0f;

matrix[4] = side.getY();
matrix[5] = plane_up.getY();
matrix[6] = -forward.getY();
matrix[7] = 0.0f;

matrix[8] = side.getZ();
matrix[9] = plane_up.getZ();
matrix[10] = -forward.getZ();
matrix[11] = 0.0f;

matrix[12] = 0.0f;
matrix[13] = 0.0f;
matrix[14] = 0.0f;
matrix[15] = 1.0f;

Vector3d neweye = new Vector3d(eye);
neweye.reverse();

TransformationMatrix ret = new TransformationMatrix(matrix);

ret.multiply(TransformationMatrix.calculateTranslationMatrix(neweye));

return ret;
}
[/code]
thanks a lot for the help :)

Share this post


Link to post
Share on other sites
yezu666    100
[quote name='gsamour' timestamp='1305901794' post='4813475']
vector3 tempUp = [0, 1, 0]
vector3 right = forward.cross(tempUp)
vector3 up = right.cross(forward)

matrix3x3 rotationMat =

[ right.x forward.x up.x ]
[ right.y forward.y up.y ]
[ right.z forward.z up.z ]
[/quote]
Thanks for the suggestion. I've tried this method but I don't seem to get a correct transformation from it. I'll keep trying, though :)

Share this post


Link to post
Share on other sites
gsamour    140
I definitely need to brush up on this stuff to have it clear in my head. Thanks haegarr, hopefully we've helped the OP

Share this post


Link to post
Share on other sites
haegarr    7372
[quote name='Yezu666' timestamp='1305905991' post='4813501']
Although I haven't noticed so far, I think there are errors in my LookAt function. AFAIK the resulting matrix should be orthonormal and the scaling effect after transposition suggests it is not. I must look into it. However I would be very happy if You could help me. :) Here is the code:
[/quote]
Please bear in mind that some aspects of the implementation are not clear yet. So I must guess some things...

[code]public static TransformationMatrix calculateLookAtMatrix(Vector3d eye, Vector3d center, Vector3d up)
{
float [] matrix = new float[16];

Vector3d forward = Vector3d.sub(center , eye);
forward.normalize();
[/code]
I assume that Vector3d.sub(a, b) computes a-b (in this order).

[code] Vector3d side = Vector3d.cross(forward , up);
side.normalize();

Vector3d plane_up = Vector3d.cross(side , forward); plane_up.normalize();
[/code]
Normalizing "plane_up" is not strictly necessary, because both arguments "side" and "forward" have unit length and "side" is guaranteed to be orthogonal to "forward".

[code] matrix[0] = side.getX();
matrix[1] = plane_up.getX();
matrix[2] = -forward.getX();
matrix[3] = 0.0f;

matrix[4] = side.getY();
matrix[5] = plane_up.getY();
matrix[6] = -forward.getY();
matrix[7] = 0.0f;

matrix[8] = side.getZ();
matrix[9] = plane_up.getZ();
matrix[10] = -forward.getZ();
matrix[11] = 0.0f;

matrix[12] = 0.0f;
matrix[13] = 0.0f;
matrix[14] = 0.0f;
matrix[15] = 1.0f;
[/code]
Now things become a bit complicated.

1. You're negating (solely) the forward direction, so your camera is defined to look along its local negative z direction, right?

2. Because you're about to compute the transposed matrix, from the indexing I conclude that you're using the column major layout, right? I mean that the matrix indices are arranged like so:
[ 0 4 8 12 ]
[ 1 5 9 13 ]
[ 2 6 10 14]
[ 3 7 11 15]

If that is right it would further mean that you are using column vectors.

[code]
Vector3d neweye = new Vector3d(eye);
neweye.reverse();

TransformationMatrix ret = new TransformationMatrix(matrix);
ret.multiply(TransformationMatrix.calculateTranslationMatrix(neweye));
[/code]
The ret.multiply computes probably [b]R[/b][sup]t[/sup] * [b]T[/b]( -eye ) here. This order would be correct if and only if you're using column vectors. Do you do so? Unfortunately, nothing in the shown code gives a clear criterion whether you do so.

[code]
return ret;
}
[/code]
Nothing to say here ;)

Share this post


Link to post
Share on other sites
jyk    2094
It looks like it's already been addressed, but:

[quote name='gsamour' timestamp='1305903872' post='4813488']
2. The handedness affects the order in which you do the cross product[/quote]
Actually, (spatial) handedness has no effect on how the cross product is computed or how the arguments should be ordered.

[quote]3. By row-major and column-major I meant the same thing as row-vectors and column-vectors (I've always heard of the term row-major and column-major referred to matrices)[/quote]
Although these terms are commonly conflated, they refer to two completely different and unrelated things (as haegarr explained above).

Share this post


Link to post
Share on other sites
gsamour    140
Thanks jyk and haegarr for clearing this up :)

EDIT: can you go into a little more detail please?

From Wolfram Mathworld... [url="http://mathworld.wolfram.com/CrossProduct.html"]http://mathworld.wolfram.com/CrossProduct.html[/url]

It says that the way the cross-product is computed there assumes the right-hand rule. Now I'm confused.

Share this post


Link to post
Share on other sites
jyk    2094
[quote name='gsamour' timestamp='1305926383' post='4813633']
EDIT: can you go into a little more detail please?

From Wolfram Mathworld... [url="http://mathworld.wolfram.com/CrossProduct.html"]http://mathworld.wol...ossProduct.html[/url]

It says that the way the cross-product is computed there assumes the right-hand rule. Now I'm confused.
[/quote]
I'm not sure why that specification is made (perhaps it's because the example in the diagram is with respect to a right-handed system).

In any case, the cross product is computed in the same way regardless of handedness; the 'right-hand rule' and 'left-hand rule' are just mnemonics to remember in which direction the cross product will point in a coordinate system with a given handedness.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this