GLM Make Rotation Matrix from vec3 - c++

I am making a game and I need the projectile to face which direction its going. I know which direction its going and I need to make a transformation matrix that would allow me to align the projectile model direction (1, 0, 0) or the positive X axis, to any arbitrary vector. How could I do this in glm?

What are you using to represent the direction exactly?
You could do something like this:
glm::mat4 transform = glm::eulerAngleYXZ(euler.y, euler.x, euler.z);
Or via quaternions:
glm::quat rot = glm::angleAxis(glm::radians(angle_in_degrees), glm::vec3(x, y, z));
glm::mat4 rotMatrix = glm::mat4_cast(rot);
Unless you were looking for something as simple as glm::lookAt ?
detail::tmat4x4<T> glm::gtc::matrix_transform::lookAt
(
detail::tvec3< T > const & eye, // from
detail::tvec3< T > const & center, // to
detail::tvec3< T > const & up // up
)

Hey I figured the answer out, which was close to #Mr_Pouet but here it is:
const glm::vec3 a = ...;
const glm::vec3 b = ...; // in my case (1, 0, 0)
glm::vec3 v = glm::cross(b, a);
float angle = acos(glm::dot(b, a) / (glm::length(b) * glm::length(a)));
glm::mat4 rotmat = glm::rotate(angle, v);
You can replace a or b with anything you want, where a is the vector you want to translate to and b is where you are. We can optimize this if b is (1, 0, 0) or the x-axis, like in my case:
glm::vec3 v = glm::vec3(0, -a.z, a.y);
float angle = acos(a.x / glm::length(a));
glm::mat4 rotmat = glm::rotate(angle, v);
I hope this helps someone!

None of the other answers are really complete answers because they don't treat special cases like cross(.,.)=0 or up||at-eye.
Here is a rather complete solution that should work in every situation where you don't care about the orientation of the rotated object (ie. lines or cylinders etc.):
glm::vec3 from;
glm::vec3 to;
glm::vec3 v = glm::cross(to, from);
float angle = acos(glm::dot(to, from) / (glm::length(to) * glm::length(from)));
glm::mat4 rotmat = glm::rotate(angle, v);
// special cases lead to NaN values in the rotation matrix
if (glm::any(glm::isnan(rotmat * glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)))) {
if (angle < 0.1f) {
rotmat = glm::mat4(1.0f);
}
else if (angle > 3.1f) {
// rotate about any perpendicular vector
rotmat = glm::rotate(angle, glm::cross(from,
glm::vec3(from.y, from.z, from.x)));
}
else {
assert(false);
}
}
Another drawback of this solution is that it is very 'snappy', ie. the special case only triggers when the matrix blows up. This could lead to artifacts when processing cases close to the parallel case. Usually, one would want to transition between overlapping regions and apply continuous solutions in each. For that, a breakdown into Euler angles would be pereferable I guess. If someone steps over any such algorithm, please post. I mean that would also be a decent homework for students.

Related

Can multiply a rotation matrix to rotate the direction vector (vec 3)?

I adopted "Separating axis theorem (SAT)" to realize "OBB collision detection".
As shown below, SAT requires three elements.
The coordinates (x, y, z) of the midpoint
Length of each axis
Direction vector of each axis
// Initialized
SATOBB::SATOBB(glm::vec3 &pos, std::vector<glm::vec3> &dir, glm::vec3 &len)
{
i_Pos = pos;
i_Dir = dir;
i_Len = len;
m_Dir.push_back(glm::vec3(0,0,0)); // Yeah... I know this strange code.. Thanks for tkausel
}
// i_... is before change, m_... is after change
void SATOBB::update(
glm::mat4 &Rotate,
glm::mat4 &Trans,
glm::mat4 &Scale
)
{
glm::vec3 m_Pos = Trans * glm::vec4(i_Pos, 1.0f);
for (int i=0; i<i_Dir.size(); i++){
glm::vec3 m_Dir = Rotate * glm::vec4(i_Dir, 1.0f);
}
glm::vec3 m_Len = Scale * glm::vec4(i_Len, 1.0f);
}
I think the code for calculating "3." is wrong.
So, please let me know the correct calculation code.
For calculation, I wanted to use the mat4 function, so vec3 is used for "1. & 2.." (For reasons of expediency)
"3." was calculated using vec3.
Is it really enough to multiply the vector by the rotation matrix?
That is the problem.

Rotate Object around origin as it faces origin in OpenGL with GLM?

I'm trying to make a simple animation where an object rotates around the world origin in OpenGL using glm lib. My ideia is:
Send object to origin
Rotate it
Send back to original position
Make it look at what I want
Here's my implementation:
// Rotates object around point p
void rotate_about(float deltaTime, glm::vec3 p, bool ended) {
glm::vec3 axis = glm::vec3(0,1,0); //rotation axis
glm::mat4 scale_m = glm::scale(glm::mat4(1.0f), glm::vec3(scale, scale, scale)); //scale matrix
glm::mat4 rotation = getMatrix(Right, Up, Front, Position); //builds rotation matrix
rotation = glm::translate(rotation, p - Position );
rotation = glm::rotate(rotation, ROTATION_SPEED * deltaTime, axis);
rotation = glm::translate(rotation, Position - p );
Matrix = rotation * scale_m;
//look at point P
Front = glm::normalize(p - start_Position);
Right = glm::normalize(glm::cross(WorldUp, Front));
Up = glm::normalize(glm::cross(Right, Front));
if (ended == true) { //if last iteration of my animation: saves position
Position.x = Matrix[3][0];
Position.y = Matrix[3][1];
Position.z = Matrix[3][2];
}
}
getMatrix() simply returns a 4x4 matrix as:
| Right.x Right.y Right.z |
| Up.x Up.y Up.z |
| Front.x Front.y Front.z |
| Pos.x Pos.y Pos.z |
I'm using this image as reference:
As it is my model simply disappears when I start the animation. If I remove lines bellow "//look at point P" it rotates around the origin, but twitches every time my animation restarts. I'm guessing I'm losing or mixing informations I shouldn't somewhere.
How can I store my models Front/Right/Up information so I can rebuild its matrix from scratch?
First edit, this is the effect I'm having when I don't try to make my model look at the point P, in this case the origin. When I do try my model disappears. How can I make it look at where I want, and how can I get my models new Front/Right/Up vectors after I finish rotating it?
This is the code I ran in the gif above
Operations like glm::translate() or glm::roate() build a matrix by its parameters and multiply the input matrix by the new matrix
This means that
rotation = glm::translate(rotation, Position - p );
can be expressed as (pseudo code):
rotation = rotation * translation(Position - p);
Note, that the matrix multiplication has to be "read" from the left to the right. (See GLSL Programming/Vector and Matrix Operations)
The operation translate * rotate causes a rotation around the origin of the object:
The operation rotate * translate causes a rotation around the origin of the world:
The matrix glm::mat4 rotation (in the code of your question) is the current model matrix of your object.
It contains the position (translation) and the orientation of the object.
You want to rotate the object around the origin of the world.
To do so you have to create a matrix which contains the new rotation
glm::mat4 new_rot = glm::rotate(glm::mat4(1.0f), ROTATION_SPEED * deltaTime, axis);
Then you can calculate the final matrix as follows:
Matrix = new_rot * rotation * scale_m;
If you want to rotate an object around the a point p and the object should always face a point p, then all you need is the position of the object (start_position) and the rotation axis.
In your case the rotation axis is the up vector of the world.
glm::vec3 WorldUp( 0.0f, 1.0f, 0.0f );
glm::vec3 start_position = ...;
float scale = ...;
glm::vec3 p = ...;
Calculate the rotation matrix and the new (rotated) position
glm::mat4 rotate = glm::rotate(glm::mat4(1.0f), ROTATION_SPEED * deltaTime, WorldUp);
glm::vec4 pos_rot_h = rotate * glm::vec4( start_position - p, 1.0f );
glm::vec3 pos_rot = glm::vec3( pos_rot_h ) + p;
Calculate the direction in which the object should "look"
glm::vec3 Front = glm::normalize(p - pos_rot);
You can use your function getMatrix to setup the current orientation matrix of the object:
glm::vec3 Right = glm::normalize(glm::cross(WorldUp, Front));
glm::mat4 pos_look = getMatrix(Right, WorldUp, Front, pos_rot);
Calculate the model matrix:
glm::mat4 scale_m = glm::scale(glm::mat4(1.0f), glm::vec3(scale));
Matrix = pos_look * scale_m;
The final code may look like this:
glm::mat4 getMatrix(const glm::vec3 &X, const glm::vec3 &Y, const glm::vec3 &Z, const glm::vec3 &T)
{
return glm::mat4(
glm::vec4( X, 0.0f ),
glm::vec4( Y, 0.0f ),
glm::vec4( Z, 0.0f ),
glm::vec4( T, 1.0f ) );
}
void rotate_about(float deltaTime, glm::vec3 p, bool ended) {
glm::mat4 rotate = glm::rotate(glm::mat4(1.0f), ROTATION_SPEED * deltaTime, WorldUp);
glm::vec4 pos_rot_h = rotate * glm::vec4( start_position - p, 1.0f );
glm::vec3 pos_rot = glm::vec3( pos_rot_h ) + p;
glm::vec3 Front = glm::normalize(p - pos_rot);
glm::vec3 Right = glm::normalize(glm::cross(WorldUp, Front));
glm::mat4 pos_look = getMatrix(Right, WorldUp, Front, pos_rot);
glm::mat4 scale_m = glm::scale(glm::mat4(1.0f), glm::vec3(scale));
Matrix = pos_look * scale_m;
if ( ended == true )
Position = glm::vec3(Matrix[3]);
}
SOLUTION:
The problem was in this part:
rotation = glm::translate(rotation, p - Position );
rotation = glm::rotate(rotation, ROTATION_SPEED * deltaTime, axis);
rotation = glm::translate(rotation, Position - p );
if (ended == true) { //if last iteration of my animation: saves position
Position.x = Matrix[3][0];
Position.y = Matrix[3][1];
Position.z = Matrix[3][2];
}
Note that I was using the distance between world origin and model as the radius of the translation. However, after the animation ends I update the models Position, which changes the result of p - Position, i.e, the orbit radius. When this happen the model "twitches", because it lost rotation information.
I solved it by using a different variable for the orbit radius, and applying the translation on the z-axis of the model. When the translation is applied on the x-axis, the model - which faces the camera initially - will end up sideways to the origin. However, applying the translation on the z-axis will end up with the model either facing or backwards to the origin, depending on the signal.

How to rotate a vector by a given direction

I'm creating some random vectors/directions in a loop as a dome shape like this:
void generateDome(glm::vec3 direction)
{
for(int i=0;i<1000;++i)
{
float xDir = randomByRange(-1.0f, 1.0f);
float yDir = randomByRange(0.0f, 1.0f);
float zDir = randomByRange(-1.0f, 1.0f);
auto vec = glm::vec3(xDir, yDir, zDir);
vec = glm::normalize(vec);
...
//some transformation with direction-vector
}
...
}
This creates vectors as a dome-shape in +y direction (0,1,0):
Now I want to rotate the vec-Vector by a given direction-Vector like (1,0,0).
This should rotate the "dome" to the x-direction like this:
How can I achieve this? (preferably with glm)
A rotation is generally defined using some sort of offset (axis-angle, quaternion, euler angles, etc) from a starting position. What you are looking for would be more accurately described (in my opinion) as a re-orientation. Luckily this isn't too hard to do. What you need is a change-of-basis matrix.
First, lets just define what we're working with in code:
using glm::vec3;
using glm::mat3;
vec3 direction; // points in the direction of the new Y axis
vec3 vec; // This is a randomly generated point that we will
// eventually transform using our base-change matrix
To calculate the matrix, you need to create unit vectors for each of the new axes. From the example above it becomes apparent that you want the vector provided to become the new Y-axis:
vec3 new_y = glm::normalize(direction);
Now, calculating the X and Z axes will be a tad more complicated. We know that they must be orthogonal to each other and to the Y axis calculated above. The most logical way to construct the Z axis is to assume that the rotation is taking place in the plane defined by the old Y axis and the new Y axis. By using the cross-product we can calculate this plane's normal vector, and use that for the Z axis:
vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));
Technically the normalization isn't necessary here since both input vectors are already normalized, but for the sake of clarity, I've left it. Also note that there is a special case when the input vector is colinear with the Y-axis, in which case the cross product above is undefined. The easiest way to fix this is to treat it as a special case. Instead of what we have so far, we'd use:
if (direction.x == 0 && direction.z == 0)
{
if (direction.y < 0) // rotate 180 degrees
vec = vec3(-vec.x, -vec.y, vec.z);
// else if direction.y >= 0, leave `vec` as it is.
}
else
{
vec3 new_y = glm::normalize(direction);
vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));
// code below will go here.
}
For the X-axis, we can cross our new Y-axis with our new Z-axis. This yields a vector perpendicular to both of the others axes:
vec3 new_x = glm::normalize(glm::cross(new_y, new_z));
Again, the normalization in this case is not really necessary, but if y or z were not already unit vectors, it would be.
Finally, we combine the new axis vectors into a basis-change matrix:
mat3 transform = mat3(new_x, new_y, new_z);
Multiplying a point vector (vec3 vec) by this yields a new point at the same position, but relative to the new basis vectors (axes):
vec = transform * vec;
Do this last step for each of your randomly generated points and you're done! No need to calculate angles of rotation or anything like that.
As a side note, your method of generating random unit vectors will be biased towards directions away from the axes. This is because the probability of a particular direction being chosen is proportional to the distance to the furthest point possible in a given direction. For the axes, this is 1.0. For directions like eg. (1, 1, 1), this distance is sqrt(3). This can be fixed by discarding any vectors which lie outside the unit sphere:
glm::vec3 vec;
do
{
float xDir = randomByRange(-1.0f, 1.0f);
float yDir = randomByRange(0.0f, 1.0f);
float zDir = randomByRange(-1.0f, 1.0f);
vec = glm::vec3(xDir, yDir, zDir);
} while (glm::length(vec) > 1.0f); // you could also use glm::length2 instead, and avoid a costly sqrt().
vec = glm::normalize(vec);
This would ensure that all directions have equal probability, at the cost that if you're extremely unlucky, the points picked may lie outside the unit sphere over and over again, and it may take a long time to generate one that's inside. If that's a problem, it could be modified to limit the iterations: while (++i < 4 && ...) or by increasing the radius at which a point is accepted every iteration. When it is >= sqrt(3), all possible points would be considered valid, so the loop would end. Both of these methods would result in a slight biasing away from the axes, but in almost any real situation, it would not be detectable.
Putting all the code above together, combined with your code, we get:
void generateDome(glm::vec3 direction)
{
// Calculate change-of-basis matrix
glm::mat3 transform;
if (direction.x == 0 && direction.z == 0)
{
if (direction.y < 0) // rotate 180 degrees
transform = glm::mat3(glm::vec3(-1.0f, 0.0f 0.0f),
glm::vec3( 0.0f, -1.0f, 0.0f),
glm::vec3( 0.0f, 0.0f, 1.0f));
// else if direction.y >= 0, leave transform as the identity matrix.
}
else
{
vec3 new_y = glm::normalize(direction);
vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));
vec3 new_x = glm::normalize(glm::cross(new_y, new_z));
transform = mat3(new_x, new_y, new_z);
}
// Use the matrix to transform random direction vectors
vec3 point;
for(int i=0;i<1000;++i)
{
int k = 4; // maximum number of direction vectors to guess when looking for one inside the unit sphere.
do
{
point.x = randomByRange(-1.0f, 1.0f);
point.y = randomByRange(0.0f, 1.0f);
point.z = randomByRange(-1.0f, 1.0f);
} while (--k > 0 && glm::length2(point) > 1.0f);
point = glm::normalize(point);
point = transform * point;
// ...
}
// ...
}
You need to create a rotation matrix. Therefore you need a identity Matrix. Create it like this with
glm::mat4 rotationMat(1); // Creates a identity matrix
Now your can rotate the vectorspacec with
rotationMat = glm::rotate(rotationMat, 45.0f, glm::vec3(0.0, 0.0, 1.0));
This will rotate the vectorspace by 45.0 degrees around the z-axis (as shown in your screenshot). Now your almost done. To rotate your vec you can write
vec = glm::vec3(rotationMat * glm::vec4(vec, 1.0));
Note: Because you have a 4x4 matrix you need a vec4 to multiply it with the matrix. Generally it is a good idea always to use vec4 when working with OpenGL because vectors in smaller dimension will be converted to homogeneous vertex coordinates anyway.
EDIT: You can also try to use GTX Extensions (Experimental) by including <glm/gtx/rotate_vector.hpp>
EDIT 2: When you want to rotate the dome "towards" a given direction you can get your totation axis by using the cross-product between the direction and you "up" vector of the dome. Lets say you want to rotate the dome "toward" (1.0, 1.0, 1.0) and the "up" direction is (0.0, 1.0, 0.0) use:
glm::vec3 cross = glm::cross(up, direction);
glm::rotate(rotationMat, 45.0f, cross);
To get your rotation matrix. The cross product returns a vector that is orthogonal to "up" and "direction" and that's the one you want to rotate around. Hope this will help.

Calculating The Camera View Matrix

I have an issue where my camera seems to orbit the origin. It makes me think that I have my matrix multiplication backwards. However it seems correct to me, and if I reverse it it sorts out the orbit issue, but I get an other issue where I think my translation is backwards.
glm::mat4 Camera::updateDelta(const float *positionVec3, const float *rotationVec3)
{
// Rotation Axis
const glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
const glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
const glm::vec3 zAxis(0.0f, 0.0f, 1.0f); // Should this be -1?
// Accumulate Rotations
m_rotation.x += rotationVec3[0]; // pitch
m_rotation.y += rotationVec3[1]; // yaw
m_rotation.z += rotationVec3[2]; // roll
// Calculate Rotation
glm::mat4 rotViewMat;
rotViewMat = glm::rotate(rotViewMat, m_rotation.x, xAxis);
rotViewMat = glm::rotate(rotViewMat, m_rotation.y, yAxis);
rotViewMat = glm::rotate(rotViewMat, m_rotation.z, zAxis);
// Updated direction vectors
m_forward = glm::vec3(rotViewMat[0][2], rotViewMat[1][2], rotViewMat[2][2]);
m_up = glm::vec3(rotViewMat[0][1], rotViewMat[1][1], rotViewMat[2][1]);
m_right = glm::vec3(rotViewMat[0][0], rotViewMat[1][0], rotViewMat[2][0]);
m_forward = glm::normalize(m_forward);
m_up = glm::normalize(m_up);
m_right = glm::normalize(m_right);
// Calculate Position
m_position += (m_forward * positionVec3[2]);
m_position += (m_up * positionVec3[1]);
m_position += (m_right * positionVec3[0]);
m_position += glm::vec3(positionVec3[0], positionVec3[1], positionVec3[2]);
glm::mat4 translateViewMat;
translateViewMat = glm::translate(translateViewMat, m_position);
// Calculate view matrix.
//m_viewMat = rotViewMat * translateViewMat;
m_viewMat = translateViewMat * rotViewMat;
// Return View Proj
return m_projMat * m_viewMat;
}
Everywhere else I do the matrix multiplication in reverse which gives me the correct answer, but this function seems to want the reverse.
When calculating an objects position in 3D space I do this
m_worldMat = transMat * rotMat * scale;
Which works as expected.
There seem to be a few wrong things with the code.
One: rotViewMat is used in the first rotate call before it is initialized. What value does it get initially? Is it a unit matrix?
Two: Rotation does not have the mathematical properties you seem to assume. Rotating around x, then around y, then around z (each at a constant velocity) is not the same as rotating around any axis ("orbiting"), it is a weird wobble. Because of subsequent rotations, your accumulated x rotation ("pitch") for example may actually be causing a movement in an entirely different direction (consider what happens to x when the accumulated y rotation is close to 90 deg). Another way of saying that is rotation is non-commutative, see:
https://physics.stackexchange.com/questions/48345/non-commutative-property-of-rotation and
https://physics.stackexchange.com/questions/10362/how-does-non-commutativity-lead-to-uncertainty/10368#10368.
See also: http://en.wikipedia.org/wiki/Rotation_matrix#Sequential_angles and http://en.wikipedia.org/wiki/Euler_angles. Since Euler angles (roll-pitch-yaw) are not vectors, it doesn't make sense to add to them a velocity vector.
What you probably want is this:
glm::mat4 Camera::updateDelta(const float *positionVec3, const float *axisVec3, const float angularVelocity)
{
...
glm::mat4 rotViewMat; // initialize to unit matrix
rotViewMat = glm::rotate(rotViewMat, angularVelocity, axisVec3);
...

Glm Quaternion lookat function

I am trying to write a lookat function that uses glm::quat to represent rotations, based of off this answer. I am running into trouble getting a correct angle however. This is my lookat function:
void Camera::LookAt(float x, float y, float z) {
glm::vec3 lookVector = glm::vec3(x, y, z);
assert(lookVector != position);
glm::vec3 direction = glm::normalize(lookVector-position);
float dot = glm::dot(glm::vec3(0, 0, -1), direction);
if (fabs(dot - (-1.0f)) < 0.000001f)
rotation = glm::quat(RadiansToDegrees(M_PI), 0.0f, 1.0f, 0.0f);
if (fabs(dot - (1.0f)) < 0.000001f)
rotation = glm::quat();
float angle = RadiansToDegrees(acosf(dot));
glm::vec3 cross = (glm::cross(glm::vec3(0, 0, -1), direction));
rotation = glm::normalize(glm::angleAxis(angle, cross));
std::cout << glm::eulerAngles(rotation).x << " " << glm::eulerAngles(rotation).y << " " << glm::eulerAngles(rotation).z << "\n";
}
When I call LookAt(0.0f, 0.0f, 0.0f) when my camera is at (0.0f, 0.0f, -10.0f), this outputs a correct rotation of 0,0,0. However if I translate my camera to (0.0f, -0.01f, -10.0f) or more I get a rotation of about 124,0,0. This goes down if I continue to translate y by -0.01f. If I do not normalize the quaternion I do not get this problem. The rotation is still 124 about the x axis, but the appearance is fine. If however I normalize the quaternion later it once again appears to rotate to about 124. I can not normalize cross, because doing so throws an assert. What would cause me to get euler angles of 124 about x from my lookat function, and how can I fix it?
Since version 0.9.9.0 there is a function in <glm/gtc/quaternion.hpp> doing mostly what you want:
template<typename T, qualifier Q>
tquat<T, Q> quatLookAt(vec<3, T, Q> const& direction, vec<3, T, Q> const& up);
It was added by this pull request and has been merged into master July 24, 2017.
But:
direction has to be a normalized vector!
direction can't be parallel to up!
So you may want to write a safer wrapper around the function:
glm::quat safeQuatLookAt(
glm::vec3 const& lookFrom,
glm::vec3 const& lookTo,
glm::vec3 const& up,
glm::vec3 const& alternativeUp)
{
glm::vec3 direction = lookTo - lookFrom;
float directionLength = glm::length(direction);
// Check if the direction is valid; Also deals with NaN
if(!(directionLength > 0.0001))
return glm::quat(1, 0, 0, 0); // Just return identity
// Normalize direction
direction /= directionLength;
// Is the normal up (nearly) parallel to direction?
if(glm::abs(glm::dot(direction, up)) > .9999f) {
// Use alternative up
return glm::quatLookAt(direction, alternativeUp);
}
else {
return glm::quatLookAt(direction, up);
}
}
I have fixed the problem with the following code:
void Camera::LookAt(float x, float y, float z) {
glm::vec3 lookVector = glm::vec3(x, y, z);
assert(lookVector != position);
glm::vec3 direction = glm::normalize(lookVector-position);
float dot = glm::dot(glm::vec3(0, 0, 1), direction);
if (fabs(dot - (-1.0f)) < 0.000001f) {
rotation = glm::angleAxis(RadiansToDegrees(M_PI), glm::vec3(0, 1, 0));
return;
}
else if (fabs(dot - (1.0f)) < 0.000001f) {
rotation = glm::quat();
return;
}
float angle = -RadiansToDegrees(acosf(dot));
glm::vec3 cross = glm::normalize(glm::cross(glm::vec3(0, 0, 1), direction));
rotation = glm::normalize(glm::angleAxis(angle, cross));
}
I do not however understand the necessity of the negative on angle. It fixed the last of my problems, and an explanation of the math of why would be helpful.