How to construct rotation matrix when knowing only one vector ?? ( align one axis of matrix to this known vector ) ??

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Inces

I am desperately googling it for weeks, I feel I am somewhere close but nothing works…

What I want to do is aligning mesh of particle to its velocity, in a way, that particles front ( Z axis ) will become value of normalized velocity. How do I find X and Y axis of this rotation matrix ?? Are they composed of shuffled components of new Z axis ?? Or are they in some trigonometric relations ? Please don’t tell me I have to contruct pitch roll and yew matrix with arctangentented angle for every Axis locked… I also understand that I can find last Axis with cross product, but I have no idea what is the mathematical formula of 2nd axis being perpendicular to first…

Or perhaps there is no reason to use rotation matrixes for this purpose ??

:bust_in_silhouette: Reply From: DaddyMonster

I’m assuming 3d. By “rotation Matrix” I’m also assuming you mean Basis. Also, it’s not clear whether you’d like me to teach you the linear algebra or just point to the methods Godot offers to do this for you.

The basic Basis is the Identity which is a matrix made up of three unit vectors (length of 1) pointing right, up and forward (x, y, z)

Basis(Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1))

This is often used for rotation but in reality it’s more basic than that: it’s defining what right, up and forward mean. Want to flip a model upsidedown without changing every Vector3 in your mesh? Then definite the y component of the vector as being negative.

Basis(Vector3(1, 0, 0), Vector3(0, -1, 0), Vector3(0, 0, 1))

Like the identity Basis this Basis is still orthonormal: unit vectors at right angles so the model isn’t distorted or stretched. If I wanted to stretch twice on the y then:

Basis(Vector3(1, 0, 0), Vector3(0, 2, 0), Vector3(0, 0, 1))

Think of this as defining the axes on a graph, albeit a 3d one. It’s in reference to this that your mesh’s vertices vectors are plotted. (that an the “origin”, the starting point)

Let’s pick a random Vector3. Say Vector3(12, 5, 10) and let’s normalise it. In code that’s Vector3(12, 5, 10).normalized() in maths it’s this vector divided the length (which is just Pythagoras) sqrt(12*12+5*5+10*10) so Vector3(0.73, 0.3, 0.61). Let’s say this is forward; the y vector.

So now we need to work out the orthonormal vectors to get a useful Basis. Well, let’s use a placeholder vector for UP of Vector3(0, 1, 0) and do the cross product as you say. The cross product gives you a right angle vector. Honestly, this is already getting long and explaining the maths behind the cross product would take a while. Khan Academy has 144 videos going through it which I recommend.

Suffice to say, it’s the linear algebra way of taking the sine, whereas the dot product is the same as the cosine. In Godot it’s just Vector3(0.73, 0.3, 0.61).cross(Vector3(0, 1, 0). It output the right angle vector, in this case the x.

But we need to now work out the y - because we just had a placeholder. So, let’s cross the Vectors we know. Vector3(0.73, 0.3, 0.61).cross(Vector3(-0.61, 0, 0.73)).

So our orthonormal basis is pointing to y is:

Basis(Vector3(0.73, 0.3, 0.61), Vector3(0.219, -0.9, 0.18), Vector3(-0.61, 0, 0.73))

Oops, that’s upside down, reverse one of the crosses to put it right way up.

Or… Just put look_at(Vector3(12, 5, 10), Vector3.UP)

To rotate it with angles then you can just use a matrix with sine and cosine. In 3d you need 3 sets of 3x3 matrices. Sounds complicated but it’s just really basic trig and a bit of matrix multiplication. This is for Euler rotations (like a gimbal).

If gimbal locking is an issue then you can use quaternions but this is more advanced so ignore the maths and just use the inbuilt functions.

Honestly, if all this maths is too much then you have loads of inbuilt methods where you can ignore the maths. It’s good to know what the Basis means fundamentally so I hoped this helped a little.

Thank you and sorry for confusing question !

I am working with shaders with it, so it is less about basis, than it is about VERTEX and there are no helper functions like look_at().
And I wanted to construct rotation matrix, so I could multiply VERTEX by it, and expected to transform my mesh in a way that its Z axis is facing given movement direction.

I tried to construct it as your example on contructing basis, but multiplying VERTEX by it results in Z axis facing velocity but mesh being flattened like in 2D.
Does this mean I still have to construct 3 rotation matrixes, using angles of all Axis and multiply VERTEX by all of them ? It is like million lines of code for such a simple thing like facing movement direction…

Also thank You for all those math explaining, I will have to dig into it, every information appreciated :slight_smile: I did not understand placeholder thing thou, can I just cross with any vector and than cross with its result to get any orthonormal basis ?

Inces | 2021-10-06 07:43

Oh that’s fine, everything I said above applies equally to shaders.

To start at the end, the placeholder is because the cross product gives you a right angle vector. It takes two vectors and outputs a 3rd at right angles. You need two vectors to do this vec_a.cross(vec_b)but you’ve only got one. So you make a placeholder vector b and then fix it later:

In the example below vec_y is a known unit vector and vec_z is a placeholder for FORWARD.

vec_x = vec_y.cross(vec_z)
vec_z = vec_y.cross(vec_x)

We worked out x using z and then we corrected z on the two correct vectors. Best thing, put a pencil on your desk. Then put another one pointing in any direction. Now realise that the cross gives you a right angle and you’ll end up with it nicely orthonormal facing the original pencil.

The way of performing Euler rotations on 3d matrices is very easy. You know orthographic view? Well, let’s imagine our object is a cross with a line on each axis. We can just use the cosine and sine to work out where each vertex should be moved. The values we don’t need to move we just zero.

Let’s look at an x rotation matrix. Let’s imagine we have a vector pointing right, well, the x is identity (doesn’t change) and y and z are just sine/cosine of whatever angle you’re rotating. Google “3d rotation Khan academy” and “matrix-vector multiplication Khan Academy”.

Here’s the matrix. Looks complicated, it’s really not:

mat3 x = mat3(vec3(1, 0, 0), vec3(0, cos(a), sin(a)), vec3(0, -sin(a), cos(a))

So, in the shader let’s make three functions which we can call to rotate. It’s literally nothing more than matrix-vector multiplication:

vec3 rotate_x(vec, a){
    mat3 x = mat3(vec3(1, 0, 0), vec3(0, cos(a), sin(a)), vec3(0, -sin(a), cos(a))
    return vec3(vec.x*x[0, 0]+vec.y*x[0, 1]+vec.z*x[0, 2], vec.x*x[1, 0]+vec.y*x[1, 1]+vec.z*x[1, 2], vec.x*x[2, 0]+vec.y*x[2, 1]+vec.z*x[2, 2])
}

I’ve done this quickly, off the top of my head and without checking a thing so there’s certain to be a careless mistake but hopefully you get the idea well enough to do the matrices for y and z.

DaddyMonster | 2021-10-06 09:58

I think I get it and I am soo gratefull, but I can’t believe it is this annoying :slight_smile:

However I was experimenting with your ideas and it looks like it is possible with one simple matrix in the end ! I am not sure why it suddenly worked :

with known given vector velocity ( future Z ), instead of placeholder, I used rotation formula to create X vector, by rotating velocity 90 degrees about Y :

    dirz = velocity
mat3 rot90 = mat3(vec3(0.0,0.0,1.0),vec3(0.0,1.0,0.0),vec3(-1.0,0.0,0.0));
	vec3 dirx = dirz * rot90;

. After that, I used cross of X and Z to get Y and combined for final rotation matrix. Vertex multiplied by this matrix works well, result is correct, mesh facing velocity, not streched, not flattened.

Inces | 2021-10-06 11:59

That’s it, that’s lovely, you’re getting it.

So this:

mat3 rot90 = mat3(vec3(0.0,0.0,1.0),vec3(0.0,1.0,0.0),vec3(-1.0,0.0,0.0));

In layman’s terms you’re saying that the x axis locally is pointing forward globally(100% on the global y). Up locally is up globally (no change). Forward is pointing to the left.

Say we have the example vector vec3(0.0, 1.0, 0.0) - let’s multiply it by the matrix. Well, the x is zero so times it and you’ll get zero. The y is 0*0+1*1+0*0 = 1.0. The z is like x, zero. So vec3(0.0, 1.0, 0.0) - unchanged.

But “y” was identity, that’s the same as 1* in normal maths (which is a 1d vector btw). So we expect that! So you’re performing a y axis rotation.

Let’s do vec3(1.0, 0.0, 0.0) as well to really drive the concept home. That’s:

x = 1*0 + 0*0 + 0*1 = 0
y = 1*0 + 0*1 + 0*0 = 0
z = 1*-1 + 0*0 + 0*0 = -1
 = vec3(0, 0, -1)

As you did you can just literally just do mat*vec and get the right vec3 but I wanted to show you what’s going on under the hood. You literally just times the x component of your vector with each component in the x vector, sum them and voila, you have the x value. Rinse and repeat for y and z.

As for the matrix, when you’re doing an orthographic Euler rotation, a 2d problem! Like on graph paper. So imagine an arrow pointing up and an arrow pointing right. If you want to rotate the right one that then the new x is the cosine of the angle. So if the angle is zero then the cosine is 1 so x is 1. If the angle is PI/2 then the x is 0. same principle for the y and z but sometimes it’s cosine, sometimes sine or their negative.

It really is as simple as that.

I can’t encourage you enough to watch the two Khan Academy videos I mentioned. Maybe even get six pencils - each pencil representing a vec3, make a global orthonormal basis and a local orthonormal basis and label them if you’re feeling keen.

Then rotate the local one until it describes what’s in your matrix.

Do this irl and you’ll grasp it forever. Obviously it’s not possible to imagine 4d matrices but the maths holds regardless. Believe it or not, rotating a 4d hypercube is trivial but there’s no human way to picture it so don’t try.

DaddyMonster | 2021-10-06 13:18

Oof I have been in the topic for a few days and I get it more and more.

My shortened all-angles rotation matrix example from above is not working well in most cases. Sometimes it looks right, but there are ocasions that I see it is unnaturally skewing mesh.

So in the end I really ended up with 3 rotation matrices, one for each axis locked. I take VELOCITY vector and get each of its angles between axis and ground with atan functions, and it works fine. You helped me understand most of this process, but my question actually remains. Can You tthink of any shortened way to this, some shortcut formula ? I am just thinking, all those arctangents as arguments of sinus and cosinus, it just feels like I am overenegeenering this.

Inces | 2021-10-08 18:02

Good to hear that you’re getting there. It look me a long time and lots of sitting down with a pen and paper completing exercises to get it intuitively - there are no real short cuts here but it is surmountable and my god is it useful.

Let me have a crack at it for you:

shader_type spatial;

vec3 rotate_x(vec3 v, float a){
    return mat3(vec3(1, 0, 0), vec3(0, cos(a), sin(a)), vec3(0, -sin(a), cos(a))) * v;
}

vec3 rotate_y(vec3 v, float a){
    return mat3(vec3(cos(a), 0, -sin(a)), vec3(0, 1, 0),  vec3(sin(a),0, cos(a))) * v;
}

vec3 rotate_z(vec3 v, float a){
    return mat3(vec3(cos(a), sin(a), 0), vec3(-sin(a), cos(a), 0), vec3(0, 0, 1)) * v;
}

void vertex(){
    const float PI = 3.14159;
    VERTEX = rotate_x(VERTEX, sin(TIME)*PI);
    VERTEX = rotate_y(VERTEX, cos(TIME*0.5)*PI);
    VERTEX = rotate_z(VERTEX, sin(TIME*0.25)*PI);
}

As usual, this is off the top of my head and written in within 2 mins, nothing tested, nothing checked, expect silly mistakes galore. (Isn’t it weird that PI isn’t defined…)

Note that with Euler rotations order matters. If it suddenly stops working that’ll be because of gimbal locking - with x and z on the same plane - fundamental geometric issue with Euler. Also, rotating from one position to another when you’re not exactly on an axis will not be closest path. To solve that you need a different approach expanding the problem into a 4d matrix; that’s advanced, stay away from it until you’re ready.

DaddyMonster | 2021-10-09 12:21

Yeah I already constructed these 3 matrices like in this example. As argument angle ( your float a ) I feed angle of VELOCITY vector towards origin, found with arctangent. I noticed, that there are some meshes, that when they have their VERTEX rotated are behaving weirdly, I felt it is interpolation issue. However VERTEX is Vector3, and when instead of VERTEX I construct matrix4 and rotate whole TRANSFORM ( which is Vector4), than they work properly.

Inces | 2021-10-09 15:53

This just rotates by an angle. If you want it to point in the direction of its velocity then that’s just the cross product that I went over in gdscript.

Are you basically looking for a “look_at” function?

shader_type spatial;

uniform vec3 velocity = vec3(12, 324, 1);

vec3 rotate_x(vec3 v, float a){
    return mat3(vec3(1, 0, 0), vec3(0, cos(a), sin(a)), vec3(0, -sin(a), cos(a))) * v;
}

vec3 look_at(vec3 z, vec3 v){
	vec3 x = cross(rotate_x(z, -3.14159/2.0), z);
	vec3 y = cross(z, x);
	return mat3(x, y, z) * v;
}

void vertex(){
    VERTEX = look_at(normalize(velocity), VERTEX);
}

Rather than use the placeholder vector, I rotated the z by 90 degrees on the x and used that as y - otherwise you get issues with up velocities.

DaddyMonster | 2021-10-09 17:05

You know, the above is a bit hacky and ugly. So’s this swishing tbh:

vec3 look_at(vec3 z, vec3 v){
	vec3 x = cross(vec3(-z.z, 0, z.x), z);
	vec3 y = cross(z, x);
	return mat3(normalize(x), normalize(y), normalize(z)) * v;
}

Problem is at precisely “up” you get issues taking the cross product… You need the vectors to be apart and you can’t take the cross with just one vector.

Yeah, you might need quaternions / Gram-Schmidt / Gaussian Reduction to properly solve this. The perfect solution is advanced I suspect.

The gdscript() look_at rotates on a plane so it doesn’t hit this issue, you provide the orthogonal vector.

Sorry I couldn’t be more help.

DaddyMonster | 2021-10-09 18:24

It looks awesome, smart and short !

I feel like I am almost there. You used some confusing syntax in this code, and I think it is the reason my shader doesn’t get it ( although it shows no error)

Your function look_at is said to return vec3, but it returns mat3. How do I work with this ? Do i multiply by resulting vector ? Or should it be mat3 in the end ?

Inces | 2021-10-10 10:53

Meh, I wasn’t very happy with it. There’s a more elegant solution out there that I’m missing. Still, it works more or less.

No, no, it doesn’t return a mat3, it returns a vec3. When you’re multiplying a matrix with a matrix (or a matrix with a vector like in this case) it is only “defined” (possible) if the rows on the first are equal to the columns of the 2nd. Then the result will be equal to the columns on the first and the rows on the second. So in the case of 3x3 * 3x1, that is defined because 3=3 and the output will be 3x1; a 3d vector.

DaddyMonster | 2021-10-10 11:31

Is this not working for you? I just tried it with a cube and works perfectly apart from when the velocity is exactly up or zero. Solving that optimally is above my pay grade, I was in the thicko maths group at school.

Honestly, I’d just replace these cases with something not quite zero and not quite up and send that to the shader instead. Elegant optimal solutions are all well and good but botching something passable together is what gets projects done.

DaddyMonster | 2021-10-10 11:55

I have no idea why it does not work :frowning:

When in particle shader where TRANSFORM mat4 is used mesh becomes frozen, like position would be set to 0, and direction is wrong.

When in spatial shader, where VERTEX vec3 is used, my cube mesh turn into bench shape or other weird shapes, depending on what axis I input as Z argument :frowning:

It is OK, thanks to You I learned a lot about vector maths, I guess it will hit me some day, what am I doing wrong, thank You a lot for all your time

Inces | 2021-10-10 13:30

Let’s leave this topic for now :slight_smile:

You are great at those transformations so I prefer to ask You here rather than post new question :

How can I rotate VERTEX in shader around a pivot ?? There is no TRANSFORM available there and no quaternions. Every mesh rotates around its origin by default. Is there any way I can input different position for pivot ? And use rotation matrices to rotate mesh about that pivot ??

Inces | 2021-10-11 18:08

An easy one. :slight_smile: Just add an offset vector, eg vec3(1, 2, 3) + VERTEX * rot_matrix and it’ll revolve around that. You can use the look_atfunction we wrote too if you like.

DaddyMonster | 2021-10-11 18:40

Thanks! Now with all those info I menaged to construct awesome mesh shattering shader :slight_smile:

Inces | 2021-10-12 10:34

Awesome! I’ve quite enjoyed this chat. Vectors/matrices baffled me for ages until one day (after a lot of studying obvs) it just clicked. Hopefully, you’ve got over that tipping point.

Good luck!

DaddyMonster | 2021-10-12 11:06

So I noticed, that your look_at function works perfectly when TRANSFORM is multiplied by it once, problems only appear when rotation matrix is applied every frame. Mesh looks like it is rotating madly about the V vector, but its Z axis behaves relatively fine. Can it be the issue that each of cross products is significantly different every frame ? Can You think of a way around this ? ( It is not about the speed, multiplying by DELTA doesn’t help )

Inces | 2021-10-14 21:21

Sorry for bothering You, I get this now :slight_smile: I can’t input raw velocity every frame, I need to use difference between current direction and new velocity. I solved it by resetting TRANSFROM to identity matrix before look_at is applied :slight_smile:

Inces | 2021-10-15 18:43