Euler angles between two 3d vectors - math

How do you find the 3 euler angles between 2 3D vectors?
When I have one Vector and I want to get its rotation, this link can be usually used: Calculate rotations to look at a 3D point?
But how do I do it when calculating them according to one another?

As others have already pointed out, your question should be revised. Let's call your vectors a and b. I assume that length(a)==length(b) > 0 otherwise I cannot answer the question.
Calculate the cross product of your vectors v = a x b; v gives the axis of rotation. By computing the dot product, you can get the cosine of the angle you should rotate with cos(angle)=dot(a,b)/(length(a)length(b)), and with acos you can uniquely determine the angle (#Archie thanks for pointing out my earlier mistake). At this point you have the axis angle representation of your rotation.
The remaining work is to convert this representation to the representation you are looking for: Euler angles. Conversion Axis-Angle to Euler is a way to do it, as you have found it. You have to handle the degenerate case when v = [ 0, 0, 0], that is, when the angle is either 0 or 180 degrees.
I personally don't like Euler angles, they screw up the stability of your app and they are not appropriate for interpolation, see also
Strange behavior with android orientation sensor
Interpolating between rotation matrices

At first you would have to subtract vector one from vector two in order to get vector two relative to vector one. With these values you can calculate Euler angles.
To understand the calculation from vector to Euler intuitively, lets imagine a sphere with the radius of 1 and the origin at its center. A vector represents a point on its surface in 3D coordinates. This point can also be defined by spherical 2D coordinates: latitude and longitude, pitch and yaw respectively.
In order "roll <- pitch <- yaw" calculation can be done as follows:
To calculate the yaw you calculate the tangent of the two planar axes (x and z) considering the quadrant.
yaw = atan2(x, z) *180.0/PI;
Pitch is quite the same but as its plane is rotated along with yaw the 'adjacent' is on two axis. In order to find its length we will have to use the Pythagorean theorem.
float padj = sqrt(pow(x, 2) + pow(z, 2));
pitch = atan2(padj, y) *180.0/PI;
Notes:
Roll can not be calculated as a vector has no rotation around its own axis. I usually set it to 0.
The length of your vector is lost and can not be converted back.
In Euler the order of your axes matters, mix them up and you will get different results.

It took me a lot of time to find this answer so I would like to share it with you now.
first, you need to find the rotation matrix, and then with scipy you can easily find the angles you want.
There is no short way to do this.
so let's first declare some functions...
import numpy as np
from scipy.spatial.transform import Rotation
def normalize(v):
return v / np.linalg.norm(v)
def find_additional_vertical_vector(vector):
ez = np.array([0, 0, 1])
look_at_vector = normalize(vector)
up_vector = normalize(ez - np.dot(look_at_vector, ez) * look_at_vector)
return up_vector
def calc_rotation_matrix(v1_start, v2_start, v1_target, v2_target):
"""
calculating M the rotation matrix from base U to base V
M # U = V
M = V # U^-1
"""
def get_base_matrices():
u1_start = normalize(v1_start)
u2_start = normalize(v2_start)
u3_start = normalize(np.cross(u1_start, u2_start))
u1_target = normalize(v1_target)
u2_target = normalize(v2_target)
u3_target = normalize(np.cross(u1_target, u2_target))
U = np.hstack([u1_start.reshape(3, 1), u2_start.reshape(3, 1), u3_start.reshape(3, 1)])
V = np.hstack([u1_target.reshape(3, 1), u2_target.reshape(3, 1), u3_target.reshape(3, 1)])
return U, V
def calc_base_transition_matrix():
return np.dot(V, np.linalg.inv(U))
if not np.isclose(np.dot(v1_target, v2_target), 0, atol=1e-03):
raise ValueError("v1_target and v2_target must be vertical")
U, V = get_base_matrices()
return calc_base_transition_matrix()
def get_euler_rotation_angles(start_look_at_vector, target_look_at_vector, start_up_vector=None, target_up_vector=None):
if start_up_vector is None:
start_up_vector = find_additional_vertical_vector(start_look_at_vector)
if target_up_vector is None:
target_up_vector = find_additional_vertical_vector(target_look_at_vector)
rot_mat = calc_rotation_matrix(start_look_at_vector, start_up_vector, target_look_at_vector, target_up_vector)
is_equal = np.allclose(rot_mat # start_look_at_vector, target_look_at_vector, atol=1e-03)
print(f"rot_mat # start_look_at_vector1 == target_look_at_vector1 is {is_equal}")
rotation = Rotation.from_matrix(rot_mat)
return rotation.as_euler(seq="xyz", degrees=True)
Finding the XYZ Euler rotation angles from 1 vector to another might give you more than one answer.
Assuming what you are rotation is the look_at_vector of some kind of shape and you want this shape to stay not upside down and still look at the target_look_at_vector
if __name__ == "__main__":
# Example 1
start_look_at_vector = normalize(np.random.random(3))
target_look_at_vector = normalize(np.array([-0.70710688829422, 0.4156269133090973, -0.5720613598823547]))
phi, theta, psi = get_euler_rotation_angles(start_look_at_vector, target_look_at_vector)
print(f"phi_x_rotation={phi}, theta_y_rotation={theta}, psi_z_rotation={psi}")
Now if you want to have a specific role rotation to your shape, my code also supports that!
you just need to give the target_up_vector as a parameter as well.
just make sure it is vertical to the target_look_at_vector that you are giving.
if __name__ == "__main__":
# Example 2
# look and up must be vertical
start_look_at_vector = normalize(np.array([1, 2, 3]))
start_up_vector = normalize(np.array([1, -3, 2]))
target_look_at_vector = np.array([0.19283590755300162, 0.6597510192626469, -0.7263217228739983])
target_up_vector = np.array([-0.13225754322703182, 0.7509361508721898, 0.6469955018014842])
phi, theta, psi = get_euler_rotation_angles(
start_look_at_vector, target_look_at_vector, start_up_vector, target_up_vector
)
print(f"phi_x_rotation={phi}, theta_y_rotation={theta}, psi_z_rotation={psi}")

Getting Rotation Matrix in MATLAB is very easy
e.g.
A = [1.353553385, 0.200000003, 0.35]
B = [1 2 3]
[q] = vrrotvec(A,B)
Rot_mat = vrrotvec2mat(q)

Related

3D Vectors - Align a vector's coordinate system to a normal

I'm trying to align a coordinate system to a normal direction, in a way I only input X,Y,Z and the direction of the normal and the Y-rotation of the coordinate, to get the new X,Y,Z from the new aligned coordinate system.
A function that would look like this:
func transform(coordinate: XYZposition, to: XYZnormal, withY:Radians){
//missing mathematic
//rotate coordinates
return XYZpositionOfNewSystem
}
Here is an image representing visualize what I want to achieve. The axis are the representation of the coordinate system. First coordinate system is the original one, and the 2 on the right are the rotated ones.
Why 2 examples on the right?
As you can see, when aligning it to a face-normal, there is one free axis. The Y axis can take any rotation and still be aligned to the face.
That's why a rotation needs to be given to the function so it applies this Y-rotation as well.
I've found some maths on the Mathematics Stack Exchange site (Rotate 3D coordinate system such that z-axis is parallel to a given vector), but I'm lost if this is what I need, and I'm kind of lost on the maths applied and the symbols used.
Here is the answer you cited, implemented using python and numpy, and exchanging the roles of y and z to fit your needs:
from numpy import array, dot, cross, arccos, sin, cos
from scipy.spatial.distance import cosine
def angle(u,v):
return arccos(1 - cosine(u,v))
# assumes all four vectors are normalized
def get_transform_matrix(orig_system, n):
j = orig_system[:,1]
b = cross(j, n)
theta = angle(j, n)
q0 = cos(theta/2)
q1,q2,q3 = sin(theta/2) * b
Q = array([
[q0**2 + q1**2 - q2**2 - q3**2, 2*(q1*q3+q0*q2), 2*(q1*q2-q0*q3)],
[2*(q3*q1-q0*q2), q0**2 - q1**2 - q2**2 + q3**2, 2*(q3*q2+q0*q1)],
[2*(q2*q1+q0*q3), 2*(q2*q3-q0*q1), q0**2 - q1**2 + q2**2 - q3**2]
])
return Q
To transform a vector p into the new system, just compute Q # p:
from numpy import eye, sqrt
orig_system = eye(3)
n = array([0,1,1]) / sqrt(2)
Q = get_transform_matrix(orig_system, n)
print(Q)
# [[ 0.9267767 0. 0. ]
# [ 0. 0.78033009 0.5 ]
# [ 0. -0.5 0.78033009]]
p = array([10, 20, 30])
print(Q # p)
# [ 9.26776695 30.60660172 13.40990258]
Please do test it extensively!! I tested with a few different vectors and it looked okay, but I cannot guarantee that there are no mistakes.

Converting 3D rotation to 2D rotation

I've been trying to figure out the 2D rotation value as seen from orthographic "top" view for a 3D object with XYZ rotation values in Maya. Maybe another way to ask this could be: I want to figure out the 2D rotation of a 3D obj's direction.
Here is a simple image to illustrate my question:
I've tried methods like getting the twist value of an object using quaternion (script pasted below), to this post I've found: Component of a quaternion rotation around an axis.
If I set the quaternion's X and Z values to zero, this method works half way. I can get the correct 2D rotation even when obj is rotated in both X and Y axis, but when rotated in all 3 axis, the result is wrong.
I am pretty new to all the quaternion and vector calculations, so I've been having difficulty trying to wrap my head around it.
;)
def quaternionTwist(q, axisVec):
axisVec.normalize()
# Get the plane the axisVec is a normal of
orthonormal1, orthonormal2 = findOrthonormals(axisVec)
transformed = rotateByQuaternion(orthonormal1, q)
# Project transformed vector onto plane
flattened = transformed - ((transformed * axisVec) * axisVec)
flattened.normalize()
# Get angle between original vector and projected transform to get angle around normal
angle = math.acos(orthonormal1 * flattened)
return math.degrees(angle)
q = getMQuaternion(obj)
# Zero out X and Y since we are only interested in Y axis.
q.x = 0
q.z = 0
up = om2.MVector.kYaxisVector
angle = quaternionTwist(q, up)
Can you get the (x,y,z) coordinates of the rotated vector? Once you have them use the (x,y) values to find the angle with atan2(y,x).
I'm not familiar with the framework you're using, but if it does what it seems, I think you're almost there. Just don't zero out the X and Z components of the quaternion before calling quaternionTwist().
The quaternions q1 = (x,y,z,w) and q2 = (0, y, 0, w) don't represent the same rotation about the y-axis, especially since q2 written this way becomes unnormalized, so what you're really comparing is (x,y,z,w) with (0, y/|q2|, 0, w/|q2|) where |q2| = sqrt(y^2 + w^2).
Here is a working code for Maya using John Alexiou's answer:
matrix = dagPath.inclusiveMatrix() #OpenMaya dagPath for an object
axis = om2.MVector.kZaxisVector
v = (axis * matrix).normal()
angle = math.atan2(v.x, v.z) #2D angle on XZ plane

Scale 3D-Points in Plane

I have some points (3D) all on the same (known) plane. Now I want to scale these points within the plane as opposed to the whole 3D space.
Is there some quick solution for this e.g. a modified scaling matrix?
Can someone help me?
Thanks.
EDIT: I'm more looking for an idea/pseudocode how to do this. If you want use MatLab or some convenient language
Your plane can be known by three non-collinear points P0, P1, P2, or by its implicit equation,
A.x + B.y + C.z + D = 0
In the first case, consider the vector P0P1 and normalize it (U = P0P1/|P0P1|). Then compute a second vector orthogonal with the first, V = P0P2 - (P0P2.U).U and normalize it.
In the second case you can take the three intersection points with the axes, (-D/A, 0, 0), (0, -D/B, 0), (0, 0, -D/C) and you are back in the first case (but mind degenerate cases).
Use the two vectors to compute the desired 2D coordinates of any point P = (X, Y, Z) by the dot products
(x, y) = (P.U, P.V)
(This transform is a rotation that makes P0P1 parallel to the x axis and brings P0P1P2 in the plane xy.)

Drawing a plane

I want to draw a plane which is given by the equation: Ax+By+Cz+D=0.
I first tried to draw him by setting x,y and then get z from the equation. this did not work fine because there are some planes like 0x+0y+z + 0 = 0 and etc...
my current solution is this:
- draw the plane on ZY plane by giving 4 coordinates which goes to infinity.
- find out to rotation that should be done in order to bring the normal of the given plane(a,b,c) to lay
on z axis.
- find the translation that should be done in order for that plane be on x axis.
- make the exactly opposite transformation to those rotation and to this translation hence i will get the
plane in his place.
ok
this is a great thing but I can make the proper math calculations(tried a lot of times...) with the dot product and etc....
can someone help me in understanding the exact way it should be done OR give me some formula in which I will put ABCD and get the right transformation?
You will want the following transformation matrix:
[ x0_x y0_x z0_x o_x ]
M = [ x0_y y0_y z0_y o_y ]
[ x0_z y0_z z0_z o_z ]
[ 0 0 0 1 ]
Here, z0 is the normal of your plane, and o is the origin of your plane, and x0 and y0 are two vectors within your plane orthogonal to z0 that define the rotation and skew of your projection.
Then any point (x,y) on your XY plane can be projected to a point (p_x, p_y, p_z) your new plane with the following:
(p_x, p_y, p_z, w) = M * (x, y, 0, 1)
now, z0 in your transformation matrix is easy, that's the normal of your plane and that is simply n = normalize(a,b,c).
In choosing the rest however you have distinctly more freedom. For the origin you could take the point that the plane intersects the Z axis, unless of course the plane is parallel to the Z axis, in which case you need something else.
So e.g.
if (c != 0) { //plane intersects Z axis
o_x = 0;
o_y = 0;
o_z = -d/c;
}
else if (b != 0) { // plane intersects Y axis
o_x = 0;
o_y = -d/b;
o_z = 0;
}
else { // plane must intersect the X axis
o_x = -d/a;
o_y = 0;
o_z = 0;
}
In practice you may want to prefer a different test than (c != 0), because with that test it will succeed even is c is very very small but just different from zero, leading your origin to be at say, x=0, y=0, z=10e100 which would probably not be desirable. So some test like (abs(c) > threshold) is probably preferable. However you could of course take an entirely different point in the plane to put the origin, perhaps the point closest to the origin of your original coordinate system, which would be:
o = n * (d / sqrt(a^2 + b^2 + c^2))
Then finally we need to figure out an x0 and y0. Which could be any two linearly independent vectors that are orthogonal to z0.
So first, let's choose a vector in the XY plane for our x0 vector:
x0 = normalize(z0_y, -z0_x, 0)
Now, this fails if your z0 happens to be of the form (0, 0, z0_z) so we need a special case for that:
if (z0_x == 0 && z0_y == 0) {
x0 = (1, 0, 0)
}
else {
x0 = normalize(z0_y, -z0_x, 0)
}
Finally let's say we do not want skew and choose y0 to be orthogonal to both x0, and y0, then, using the crossproduct
y0 = normalize(x0_y*y0_z-x0_z*y0_y, x0_z*y0_x-x0_z*y0_z, x0_x*y0_y-x0_y*y0_x)
Now you have all to fill your transformation matrix.
Disclaimer: Appropriate care should be taken when using floating point representations for your numbers, simple (foo == 0) tests are not sufficient in those cases. Read up on floating point math before you start implementing stuff.
Edit: renamed some variables for clarity
Is this what you're asking?
Transforming a simple plane like the xy plane to your plane is fairly simple:
your plane is Ax+By+Cz+D=0
the xy plane is simply z=0. i.e. A=B=D=0, while C=whatever you want. We'll say 1 for simplicity's sake.
When you have a plane in this form, the normal of the plane is defined by the vector (A,B,C)
so you want a rotation that will take you from (0,0,1) to (A,B,C)*
*Note that this will only work if {A,B,C} is unitary. so you may have to divide A B and C each by sqrt(A^2+B^2+C^2).
rotating around just two of the axes can get your from any direction to any other direction, so we'll pick x and y;
here are the rotation matrices for rotations by a about the x axis, and b about the y axis.
Rx := {{1, 0, 0}, {0, Cos[a], Sin[a]}, {0, -Sin[a], Cos[a]}}
Ry := {{Cos[b], 0, -Sin[b]}, {0, 1, 0}, {Sin[b], 0, Cos[b]}}
if we do a rotation about x, followed by a rotation about y, of the vector normal to the xy plane, (0,0,1), we get:
Ry.Rx.{0,0,1} = {-Cos[a] Sin[b], Sin[a], Cos[a] Cos[b]}
which are your A B C values.
i.e.
A = -Cos[a]Sin[b]
B = Sin[a]
C = Cos[a]Cos[b]
From here, it's simple.
a = aSin[B]
so now A = -Cos[aSin[B]]Sin[b]
Cos[aSin[x]] = sqrt(1-x^2)
so:
A = -Sqrt[1-B^2] * Sin[b]
b = aSin[-A/sqrt[1-B^2]]
a = aSin[B] (rotation about x axis)
b = aSin[-A/sqrt[1-B^2]] (rotation about y axis)
So we now have the angles about the x and y axes we need to rotate by.
After this, you just need to shift your plane up or down until it matches the one you already have.
The plane you have at the moment, (after those two rotations) is going to be Ax+By+Cz=0.
the plane you want is Ax+Bx+Cz+D=0. To find out d, we will see where the z axis crosses your plane.
i.e. Cz+D=0 -> z = -D/C
So we transform your z in Ax+By+Cz=0 by -D/C to give:
Ax+By+C(z+D/C) = Ax+By+Cz+D=0. Oh would you look at that!
It turns out you don't have to do any extra maths once you have the angles to rotate by!
The two angles will give you A,B, and C. To get D you just copy it from what you had.
Hope that's of some help, I'm not entirely sure how you plan on actually drawing the plane though...
Edited to fix some horrible formatting. hopefully it's better now.

Perturb vector by some angle

I have a unit vector in 3D space whose direction I wish to perturb by some angle within the range 0 to theta, with the position of the vector remaining the same. What is a way I can accomplish this?
Thanks.
EDIT: After thinking about the way I posed the question, it seems to be a bit too general. I'll attempt to make it more specific: Assume that the vector originates from the surface of an object (i.e. sphere, circle, box, line, cylinder, cone). If there are different methods to finding the new direction for each of those objects, then providing help for the sphere one is fine.
EDIT 2: I was going to type this in a comment but it was too much.
So I have orig_vector, which I wish to perturb in some direction between 0 and theta. The theta can be thought of as forming a cone around my vector (with theta being the angle between the center and one side of the cone) and I wish to generate a new vector within that cone. I can generate a point lying on the plane that is tangent to my vector and thus creating a unit vector in the direction of the point, call it rand_vector. At this time, I orig_vector and trand_vector are two unit vectors perpendicular to each other.
I generate my first angle, angle1 between 0 and 2pi and I rotate rand_vector around orig_vector by angle1, forming rand_vector2. I looked up a resource online and it said that the second angle, angle2 should be between 0 and sin(theta) (where theta is the original "cone" angle). Then I rotate rand_vector2 by acos(angle2) around the vector defined by the cross product between rand_vector2 and orig_vector.
When I do this, I don't obtain the desired results. That is, when theta=0, I still get perturbed vectors, and I expect to get orig_vector. If anyone can explain the reason for the angles and why they are the way they are, I would greatly appreciate it.
EDIT 3: This is the final edit, I promise =). So I fixed my bug and everything that I described above works (it was an implementation bug, not a theory bug). However, my question about the angles (i.e. why is angle2 = sin(theta)*rand() and why is perturbed_vector = rand_vector2.Rotate(rand_vector2.Cross(orig_vector), acos(angle2)). Thanks so much!
Here's the algorithm that I've used for this kind of problem before. It was described in Ray Tracing News.
1) Make a third vector perpendicular to the other two to build an orthogonal basis:
cross_vector = unit( cross( orig_vector, rand_vector ) )
2) Pick two uniform random numbers in [0,1]:
s = rand( 0, 1 )
r = rand( 0, 1 )
3) Let h be the cosine of the cone's angle:
h = cos( theta )
4) Modify uniform sampling on a sphere to pick a random vector in the cone around +Z:
phi = 2 * pi * s
z = h + ( 1 - h ) * r
sinT = sqrt( 1 - z * z )
x = cos( phi ) * sinT
y = sin( phi ) * sinT
5) Change of basis to reorient it around the original angle:
perturbed = rand_vector * x + cross_vector * y + orig_vector * z
If you have another vector to represent an axis of rotation, there are libraries that will take the axis and the angle and give you a rotation matrix, which can then be multiplied by your starting vector to get the result you want.
However, the axis of rotation should be at right angles to your starting vector, to get the amount of rotation you expect. If the axis of rotation does not lie in the plane perpendicular to your vector, the result will be somewhat different than theta.
That being said, if you already have a vector at right angles to the one you want to perturb, and you're not picky about the direction of the perturbation, you can just as easily take a linear combination of your starting vector with the perpendicular one, adjust for magnitude as needed.
I.e., if P and Q are vectors having identical magnitude, and are perpendicular, and you want to rotate P in the direction of Q, then the vector R given by R = [Pcos(theta)+Qsin(theta)] will satisfy the constraints you've given. If P and Q have differing magnitude, then there will be some scaling involved.
You may be interested in 3D-coordinate transformations to change your vector angle.
I don't know how many directions you want to change your angle in, but transforming your Cartesian coordinates to spherical coordinates should allow you to make your angle change as you like.
Actually, it is very easy to do that. All you have to do is multiply your vector by the correct rotation matrix. The resulting vector will be your rotated vector. Now, how do you obtain such rotation matrix? That depends on the 3d framework/engine you are using. Any 3d framework must provide functions for obtaining rotation matrices, normally as static methods of the Matrix class.
Good luck.
Like said in other comments you can rotate your vector using a rotation matrix.
The rotation matrix has two angles you rotate your vector around. You can pick them with a random number generator, but just picking two from a flat generator is not correct. To ensure that your rotation vector is generated flat, you have to pick one random angle φ from a flat generator and the other one from a generator flat in cosθ ;this ensures that your solid angle element dcos(θ)dφ is defined correctly (φ and θ defined as usual for spherical coordinates).
Example: picking a random direction with no restriction on range, random() generates flat in [0,1]
angle1 = acos(random())
angle2 = 2*pi*random()
My code in unity - tested and working:
/*
* this is used to perturb given vector 'direction' by changing it by angle not more than 'angle' vector from
* base direction. Used to provide errors for player playing algorithms
*
*/
Vector3 perturbDirection( Vector3 direction, float angle ) {
// division by zero protection
if( Mathf.Approximately( direction.z, 0f )) {
direction.z = 0.0001f;
}
// 1 get some orthogonal vector to direction ( solve direction and orthogonal dot product = 0, assume x = 1, y = 1, then z = as below ))
Vector3 orthogonal = new Vector3( 1f, 1f, - ( direction.x + direction.y ) / direction.z );
// 2 get random vector from circle on flat orthogonal to direction vector. get full range to assume all cone space randomization (-180, 180 )
float orthoAngle = UnityEngine.Random.Range( -180f, 180f );
Quaternion rotateTowardsDirection = Quaternion.AngleAxis( orthoAngle, direction );
Vector3 randomOrtho = rotateTowardsDirection * orthogonal;
// 3 rotate direction towards random orthogonal vector by vector from our available range
float perturbAngle = UnityEngine.Random.Range( 0f, angle ); // range from (0, angle), full cone cover guarantees previous (-180,180) range
Quaternion rotateDirection = Quaternion.AngleAxis( perturbAngle, randomOrtho );
Vector3 perturbedDirection = rotateDirection * direction;
return perturbedDirection;
}

Resources