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.
Related
Using a glm::lookAt function to create the camera view matrix is good but does not help in storing the camera Euler angles. although the calculations seem correct, the view seems to bring the wrong pitch value.
in the following code, if the Y values of the current camera position and destination is the same then, there is no problem. however, the view seems to tilt further down or up if the Y values of the camera position and destination are not equal.
The question is: why does the camera not correctly points an object if the Y values of the camera and object positions are not equal.
float wrapAngle(float angle)
{
int break_after = 100;
constexpr float full_rotation = 2.0 * glm::pi<float>();
while (angle < 0.0f || angle >= full_rotation)
{
if (angle < 0.0f) angle = angle + full_rotation;
if (angle >= full_rotation) angle = angle - full_rotation;
if (--break_after == 0) break;
}
if (break_after == 0) angle = 0.0f;
return angle;
}
void getLookAtAngle(const glm::vec3& position, const glm::vec3& destination, glm::vec3 &angle)
{
//! Find vector of sight toward destination.
glm::vec3 sight = destination - position;
//! Find X, Y rotation against the axis (global).
double yAngle = wrapAngle(std::atan2(sight.x, sight.z));
glm::mat4 yModel(1);
yModel = glm::rotate(yModel, static_cast<float>(-yAngle), glm::vec3(0.0f, 1, 0));
sight = yModel * glm::vec4(sight, 1.0f);
double xAngle = wrapAngle(-std::atan2(sight.y, sight.z));
//! assign xAngle, yAngle to the parameter 'angle'
angle.x = glm::degrees(static_cast<float>(xAngle));
angle.y = glm::degrees(static_cast<float>(yAngle));
angle.z = 0.0f;
}
void updateCamera(const glm::vec3& position, const glm::vec3& destination)
{
glm::vec3 m_rotation(1);
getLookAtAngle(position, destination, m_rotation)
m_model = glm::mat4(1);
m_model = glm::rotate(m_model, glm::radians(m_rotation[0]), glm::vec3(1.0f, 0, 0));
m_model = glm::rotate(m_model, glm::radians(m_rotation[1]), glm::vec3(0.0f, 1, 0));
m_model = glm::rotate(m_model, glm::radians(m_rotation[2]), glm::vec3(0.0f, 0, 1));
m_front = glm::normalize(glm::vec3(m_model * glm::vec4(0, 0, 1.0f, 1.0f)));
m_right = glm::normalize(glm::cross(m_front, glm::vec3(0, 1.0f, 0)));
m_up = glm::normalize(glm::cross(m_right, m_front));
m_view = glm::lookAt(position, position+ m_front, m_up);
}
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.
I am struggling to understand how to change my algorithm to handle raycasting (utilized for MousePicking) using a Perspective projection and an Orthographic projection.
Currently I have a scene with 3D objects that have AxisAligned bounding boxes attached to them.
While rendering the scene using a perspective projection (created with glm::perspective) I can successfully use raycasting and my mouse to "pick" different objects in my scene. Here is a demonstration.
If I render the same scene, but using an Orthographic projection, and positioning the camera above the facing down (looking down the Y axis, Imagine like a level editor fora game) I am unable to correctly raycasting from the where the user clicks on the screen so I can get MousePicking working while rendering using an Orthographic projection. Here is a demonstration of it not working.
My algorithm at a high level:
auto const coords = mouse.coords();
glm::vec2 const mouse_pos{coords.x, coords.y};
glm::vec3 ray_dir, ray_start;
if (perspective) { // This "works"
auto const ar = aspect_rate;
auto const fov = field_of_view;
glm::mat4 const proj_matrix = glm::perspective(fov, ar, f.near, f.far);
auto const& target_pos = camera.target.get_position();
glm::mat4 const view_matrix = glm::lookAt(target_pos, target_pos, glm::vec3{0, -1, 0});
ray_dir = Raycast::calculate_ray_into_screen(mouse_pos, proj_matrix, view_matrix, view_rect);
ray_start = camera.world_position();
}
else if (orthographic) { // This "doesn't work"
glm::vec3 const POS = glm::vec3{50};
glm::vec3 const FORWARD = glm::vec3{0, -1, 0};
glm::vec3 const UP = glm::vec3{0, 0, -1};
// 1024, 768 with NEAR 0.001 and FAR 10000
//glm::mat4 proj_matrix = glm::ortho(0, 1024, 0, 768, 0.0001, 10000);
glm::mat4 proj_matrix = glm::ortho(0, 1024, 0, 768, 0.0001, 100);
// Look down at the scene from above
glm::mat4 view_matrix = glm::lookAt(POS, POS + FORWARD, UP);
// convert the mouse screen coordinates into world coordinates for the cube/ray test
auto const p0 = screen_to_world(mouse_pos, view_rect, proj_matrix, view_matrix, 0.0f);
auto const p1 = screen_to_world(mouse_pos, view_rect, proj_matrix, view_matrix, 1.0f);
ray_start = p0;
ray_dir = glm::normalize(p1 - p0);
}
bool const intersects = ray_intersects_cube(logger, ray_dir, ray_start,
eid, tr, cube, distances);
In perspective mode, we cast a ray into the scene and see if it intersects with the cube surrounding the object.
In orthographic mode, I'm casting two rays from the screen (one at z=0, the other at z=1) and creating a ray between those two points. I set the ray start point to where the mouse pointer is (with z=0) and use the ray direction just calculated as inputs into the same ray_cube_intersection algorithm.
My question is this
Since the MousePicking works using the Perspective projection, but not using an Orthographic projection:
Is it reasonable to assume the same ray_cube intersection algorithm can be used with a perspective/orthographic projection?
Is my thinking about setting the ray_start and ray_dir variables in the orthographic case correct?
Here is the source for the ray/cube collision algorithm in use.
glm::vec3
Raycast::calculate_ray_into_screen(glm::vec2 const& point, glm::mat4 const& proj,
glm::mat4 const& view, Rectangle const& view_rect)
{
// When doing mouse picking, we want our ray to be pointed "into" the screen
float constexpr Z = -1.0f;
return screen_to_world(point, view_rect, proj, view, Z);
}
bool
ray_cube_intersect(Ray const& r, Transform const& transform, Cube const& cube,
float& distance)
{
auto const& cubepos = transform.translation;
glm::vec3 const minpos = cube.min * transform.scale;
glm::vec3 const maxpos = cube.max * transform.scale;
std::array<glm::vec3, 2> const bounds{{minpos + cubepos, maxpos + cubepos}};
float txmin = (bounds[ r.sign[0]].x - r.orig.x) * r.invdir.x;
float txmax = (bounds[1 - r.sign[0]].x - r.orig.x) * r.invdir.x;
float tymin = (bounds[ r.sign[1]].y - r.orig.y) * r.invdir.y;
float tymax = (bounds[1 - r.sign[1]].y - r.orig.y) * r.invdir.y;
if ((txmin > tymax) || (tymin > txmax)) {
return false;
}
if (tymin > txmin) {
txmin = tymin;
}
if (tymax < txmax) {
txmax = tymax;
}
float tzmin = (bounds[ r.sign[2]].z - r.orig.z) * r.invdir.z;
float tzmax = (bounds[1 - r.sign[2]].z - r.orig.z) * r.invdir.z;
if ((txmin > tzmax) || (tzmin > txmax)) {
return false;
}
distance = tzmin;
return true;
}
edit: The math space conversions functions I'm using:
namespace boomhs::math::space_conversions
{
inline glm::vec4
clip_to_eye(glm::vec4 const& clip, glm::mat4 const& proj_matrix, float const z)
{
auto const inv_proj = glm::inverse(proj_matrix);
glm::vec4 const eye_coords = inv_proj * clip;
return glm::vec4{eye_coords.x, eye_coords.y, z, 0.0f};
}
inline glm::vec3
eye_to_world(glm::vec4 const& eye, glm::mat4 const& view_matrix)
{
glm::mat4 const inv_view = glm::inverse(view_matrix);
glm::vec4 const ray = inv_view * eye;
glm::vec3 const ray_world = glm::vec3{ray.x, ray.y, ray.z};
return glm::normalize(ray_world);
}
inline constexpr glm::vec2
screen_to_ndc(glm::vec2 const& scoords, Rectangle const& view_rect)
{
float const x = ((2.0f * scoords.x) / view_rect.right()) - 1.0f;
float const y = ((2.0f * scoords.y) / view_rect.bottom()) - 1.0f;
auto const assert_fn = [](float const v) {
assert(v <= 1.0f);
assert(v >= -1.0f);
};
assert_fn(x);
assert_fn(y);
return glm::vec2{x, -y};
}
inline glm::vec4
ndc_to_clip(glm::vec2 const& ndc, float const z)
{
return glm::vec4{ndc.x, ndc.y, z, 1.0f};
}
inline glm::vec3
screen_to_world(glm::vec2 const& scoords, Rectangle const& view_rect, glm::mat4 const& proj_matrix,
glm::mat4 const& view_matrix, float const z)
{
glm::vec2 const ndc = screen_to_ndc(scoords, view_rect);
glm::vec4 const clip = ndc_to_clip(ndc, z);
glm::vec4 const eye = clip_to_eye(clip, proj_matrix, z);
glm::vec3 const world = eye_to_world(eye, view_matrix);
return world;
}
} // namespace boomhs::math::space_conversions
I worked on this for several days because I ran into the same problem.
The unproject methods that we are used to work with are working 100% correctly here as well - even with orthographic projection. But with orthographic projection the direction vector going from the camera position into the screen is always the same. So, unprojecting the cursor in the same way dies not work as intended in this case.
What you want to do is getting the camera direction vector as it is but in order to get the ray origin you need to shift the camera position according to the current mouse position on screen.
My approach (C#, but you'll get the idea):
Vector3 worldUpDirection = new Vector3(0, 1, 0); // if your world is y-up
// Get mouse coordinates (2d) relative to window position:
Vector2 mousePosRelativeToWindow = GetMouseCoordsRelativeToWindow(); // (0,0) would be top left window corner
// get camera direction vector:
Vector3 camDirection = Vector3.Normalize(cameraTarget - cameraPosition);
// get x and y coordinates relative to frustum width and height.
// glOrthoWidth and glOrthoHeight are the sizeX and sizeY values
// you created your projection matrix with. If your frustum has a width of 100,
// x would become -50 when the mouse is left and +50 when the mouse is right.
float x = +(2.0f * mousePosRelativeToWindow .X / viewportWidth - 1) * (glOrthoWidth / 2);
float y = -(2.0f * mousePosRelativeToWindow .Y / viewPortHeight - 1) * (glOrthoHeight / 2);
// Now, you want to calculate the camera's local right and up vectors
// (depending on the camera's current view direction):
Vector3 cameraRight = Vector3.Normalize(Vector3.Cross(camDirection, worldUpDirection));
Vector3 cameraUp = Vector3.Normalize(Vector3.Cross(cameraRight, camDirection));
// Finally, calculate the ray origin:
Vector3 rayOrigin = cameraPosition + cameraRight * x + cameraUp * y;
Vector3 rayDirection = camDirection;
Now you have the ray origin and the ray direction for your orthographic projection.
With these you can run any ray-plane/volume-intersections as usual.
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.
I Am currently having alot of problems with the camera I am making. The problem occurs with my matrix rotation I am doing as this website says to avoid gimble lock..
One of the first problems you will note is that the order you apply
these rotations matter. As previously stated, a rotation matrix is an
orientation transform. Each transform defines a new coordinate system,
and the next transform is based on an object in the new space. For
example, if we apply the roll first, we have now changed what the axis
for the subsequent yaw is.
And when i perform this for example if I am wanted to pitch around the current x axis the x axis also changes in my axis to rotation method which is obviously wrong. Ive look around alot and cant find any solution. I have tried alot of differenet version of the axis angle rotation matrix..
void FrustumCamera::xAxisRotation(float angle)
{
Vector3<float> x = m_orientation.getXAxis();
Matrix4<float> matrix = m_orientation.axisAngleRotation(x,angle);
m_orientation = matrix*m_orientation;
normalise(m_orientation.getXAxis());
normalise(m_orientation.getYAxis());
normalise(m_orientation.getZAxis());
}
void FrustumCamera::yAxisRotation(float angle)
{
Vector3<float> y = m_orientation.getYAxis();
Matrix4<float> matrix = m_orientation.axisAngleRotation(y,angle);
m_orientation = matrix*m_orientation;
normalise(m_orientation.getXAxis());
normalise(m_orientation.getYAxis());
normalise(m_orientation.getZAxis());
}
Matrix4<Type> Matrix4<Type>::operator*(Matrix4& matrix)
{
Matrix4<Type> temp(m_matrix);
for(int i=0;i<4;i++)
{
for(int j=0;j<4;j++)
{
Type total = 0;
for(int k=0;k<4;k++)
{
total += m_matrix[i][k]*matrix.getAt(k,j);;
}
temp.setAt(i,j,total);
}
}
return temp;
}
template <class Type>
Matrix4<Type> Matrix4<Type>::axisAngleRotation(Vector3<Type> axis, const Type angle)
{
Type radians = angle * (double)degToRad;
Matrix4<Type> temp;
float c = cosf(radians);
float s = sinf(radians);
float t = 1.0f - c;
float x = axis.x;
float y = axis.y;
float z = axis.z;
temp.setAt(0,0, c+x*x*(t));
temp.setAt(0,1, x*y*(t)-z*s);
temp.setAt(0,2, x*z*(t)+y*s);
temp.setAt(0,3, 0.0f);
temp.setAt(1,0, y*x*(t)+z*s);
temp.setAt(1,1, c+y*y*(t));
temp.setAt(1,2, y*z*(t)-x*s);
temp.setAt(1,3, 0.0f);
temp.setAt(2,0, z*x*(t)-y*s);
temp.setAt(2,1, z*y*(1-c)+x*s);
temp.setAt(2,2, c+z*z*(t));
temp.setAt(2,3, 0.0f);
temp.setAt(3,0, 0.0f);
temp.setAt(3,1, 0.0f);
temp.setAt(3,2, 0.0f);
temp.setAt(3,3, 1.0f);
return temp;
}
void OpenGLRenderer::startDraw(unsigned long mask)
{
//sortBuffer(); // sort draw queue
clearBuffers(mask); // clear buffers
loadIdentity();
glTranslatef(-1*m_frustumCamera->getViewMatrix().getTranslationAxis().x,-1*m_frustumCamera->getViewMatrix().getTranslationAxis().y,-1*m_frustumCamera->getViewMatrix().getTranslationAxis().z);// load identity
glMultMatrixf(m_frustumCamera->getViewMatrix().getMatrix());
glTranslatef(m_frustumCamera->getViewMatrix().getTranslationAxis().x,m_frustumCamera->getViewMatrix().getTranslationAxis().y,m_frustumCamera->getViewMatrix().getTranslationAxis().z);
matrixStackPush();
}
I think order of multiplication can cause the problem, instead of
m_orientation = matrix*m_orientation;
try
m_orientation = m_orientation * matrix;