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);
}
Related
I am trying to rotate an object around its own x-axis. It works exactly as expected when it is on the right side of the y-axis but showcases strange behavior when it on the left.
void Animation::rotate_x(float degree) {
// get the position of the object wrt to origin
glm::vec3 position = glm::vec3(m_model_mat[3]);
glm::vec3 position_rev = -position;
// get the angle of the obj wrt y-axis
float angleY = atan2(m_model_mat[0][2], sqrt((m_model_mat[0][0] * m_model_mat[0][0]) + (m_model_mat[0][1] * m_model_mat[0][1])));
angleY = angleY * 180 / M_PI;
std::cout << angleY << "\n";
if(position[0] < 0){
if(angleY > 0){
angleY = 90 + angleY;
} else if (angleY < 0)
{
angleY = -90 + angleY;
} else{
angleY = 180;
}
}
std::cout << angleY << "\n";
// rotate so angle with y is 0
glm::mat4 rot_y_mat = glm::mat4(1.0f);
rot_y_mat = glm::rotate(rot_y_mat, glm::radians(-angleY), glm::vec3(0.0f, 1.0f, 0.0f));
// rotate to bring back to the correct position wrt y
glm::mat4 rev_rot_y_mat = glm::mat4(1.0f);
rev_rot_y_mat = glm::rotate(rev_rot_y_mat, glm::radians(angleY), glm::vec3(0.0f, 1.0f, 0.0f));
// rotate on x
glm::mat4 rotation_mat = glm::mat4(1.0f);
rotation_mat = glm::rotate(rotation_mat, glm::radians(degree), glm::vec3(1.0f, 0.0f, 0.0f));
// translate back to the original position
glm::mat4 trans_to_position = glm::mat4(1.0f);
trans_to_position = glm::translate(trans_to_position, position);
// move obj to origin
glm::mat4 trans_to_origin = glm::mat4(1.0f);
trans_to_origin = glm::translate(trans_to_origin, position_rev);
m_model_mat = trans_to_origin * m_model_mat;
m_model_mat = rev_rot_y_mat * m_model_mat;
m_model_mat = rotation_mat * m_model_mat;
m_model_mat = rot_y_mat * m_model_mat;
m_model_mat = trans_to_position * m_model_mat;
}
Here the m_model_mat is my object matrix which I am transforming.
I'm currently working on my own small game engine (I am learning OpenGL). I made camera mechanism that can rotate vectors with Euler angles, and now I'm working on rotations with quaternions. Now I'm stuck because my quaternions rotations behave very strangely (flipping objects, not rotating camera as it should). Please, help me find out what is wrong with my algorithm. Could you suggest some fixes to my camera code? Below is my source code, and here is some marks to it: target I want to look at is at coordinates (0.0f, 0.0f, 0.0f). I want my position from which I am look at the target to be glm::vec3 position = glm::vec3(0.0f, 0.0f, 3.0f)
My camera class:
class Camera {
private:
bool eulerMode = false;
float m_mouseSensitivity;
float m_velocity;
glm::vec3 m_rightAxis{};
glm::vec3 m_upAxis;
glm::vec3 m_position;
glm::vec3 m_target{};
glm::vec3 m_r{};
glm::vec3 m_direction{};
static glm::vec3 rotateVector(float angle, glm::vec3 rotationAxis, glm::vec3 vectorToRotate);
static glm::quat quaternion(float angle, glm::vec3 vec);
public:
static Camera *s_context;
float m_yaw;
float m_pitch;
float m_mouseLastX;
float m_mouseLastY;
bool m_firstMouse;
glm::vec3 m_frontAxis;
Camera(float speed,
int width,
int height,
glm::vec3 position = glm::vec3(0.0f, 0.0f, 3.0f),
glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f),
glm::vec3 target = glm::vec3(0.0f, 0.0f, 0.0f)
);
glm::mat4 getLookAtMatrix();
static void quaternionRotate(GLFWwindow *window, double x, double y);
};
Camera::Camera(
float speed,
int width,
int height,
glm::vec3 position,
glm::vec3 up,
glm::vec3 target
)
: m_pitch(0.0f), m_yaw(-90.0f), m_mouseLastX((float) width / 2),
m_mouseLastY((float) height / 2), m_mouseSensitivity(0.1f), m_upAxis(up), m_position(position),
m_frontAxis(glm::vec3(0.0f, 0.0f, -1.0f)), m_firstMouse(true) {
m_velocity = speed;
m_direction = glm::normalize(position - target);
m_rightAxis = glm::normalize(glm::cross(up, m_direction));
m_upAxis = glm::cross(m_direction, m_rightAxis);
s_context = this;
glfwSetWindowUserPointer(g_Window->getOpenGLWindow(), this);
if (eulerMode) {
glfwSetCursorPosCallback(g_Window->getOpenGLWindow(), eulerRotate);
} else {
glfwSetCursorPosCallback(g_Window->getOpenGLWindow(), quaternionRotate);
}
}
glm::mat4 Camera::getLookAtMatrix() {
glm::mat4 view = glm::lookAt(m_position, m_r, m_upAxis);
return view;
}
// for the sake of brevity, I skipped some class methods that are unnecessary for quaternions rotations
void Camera::quaternionRotate(GLFWwindow *window, double x, double y) {
if (s_context->m_firstMouse) {
s_context->m_mouseLastX = (float) x;
s_context->m_mouseLastY = (float) y;
s_context->m_firstMouse = false;
}
auto xoffset = (float) (x - s_context->m_mouseLastX);
auto yoffset = (float) (s_context->m_mouseLastY - y);
s_context->m_mouseLastX = (float) x;
s_context->m_mouseLastY = (float) y;
float sensitivity = 0.1f;
xoffset *= sensitivity;
yoffset *= sensitivity;
s_context->m_yaw += xoffset;
s_context->m_pitch += yoffset;
glm::vec3 yAxis = glm::vec3(0, 1, 0);
// Rotate the view vector by the horizontal angle around the vertical axis
glm::vec3 view = s_context->m_direction;
view = glm::normalize(rotateVector(s_context->m_yaw, yAxis, view));
// Rotate the view vector by the vertical angle around the horizontal axis
glm::vec3 xAxis = glm::normalize(glm::cross(yAxis, view));
view = glm::normalize(rotateVector(s_context->m_pitch, xAxis, view));
s_context->m_r = view;
s_context->m_upAxis = glm::normalize(glm::cross(s_context->m_r, xAxis));
}
glm::vec3 Camera::rotateVector(float angle, const glm::vec3 rotationAxis, const glm::vec3 vectorToRotate) {
glm::quat rotationQ = quaternion(angle, rotationAxis);
glm::quat conjugateQ = glm::conjugate(rotationQ);
glm::quat result = rotationQ * vectorToRotate * conjugateQ;
return {result.x, result.y, result.z};
}
glm::quat Camera::quaternion(float angle, const glm::vec3 vec) {
float HalfAngleInRadians = glm::radians(angle / 2);
float SineHalfAngle = sinf(HalfAngleInRadians);
float CosHalfAngle = cosf(HalfAngleInRadians);
float xC = vec.x * SineHalfAngle;
float yC = vec.y * SineHalfAngle;
float zC = vec.z * SineHalfAngle;
float wC = CosHalfAngle;
return {wC, xC, yC, zC};
}
i need to implement arcball camera. I got something similar, but it works very crookedly (the angle changes sharply, when turning to the right / left, the camera raises up / down strongly).
Here is my source code, can you tell me where I went wrong:
bool get_arcball_vec(double x, double y, glm::vec3& a)
{
glm::vec3 vec = glm::vec3((2.0 * x) / window.getWidth() - 1.0, 1.0 - (2.0 * y) / window.getHeight(), 0.0);
if (glm::length(vec) >= 1.0)
{
vec = glm::normalize(vec);
}
else
{
vec.z = sqrt(1.0 - pow(vec.x, 2.0) - pow(vec.y, 2.0));
}
a = vec;
return true;
}
...
void onMouseMove(double x, double y) {
if (rightMouseButtonPressed) {
glm::vec3 a,b;
cur_mx = x;
cur_my = y;
if (cur_mx != last_mx || cur_my != last_my)
if (get_arcball_vec(last_mx, last_my, a) && get_arcball_vec(cur_mx, cur_my, b))
viewport.getCamera().orbit(a,b);
last_mx = cur_mx;
last_my = cur_my;
...
void Camera::orbit(glm::vec3 a, glm::vec3 b)
{
forward = calcForward();
right = calcRight();
double alpha = acos(glm::min(1.0f, glm::dot(b, a)));
glm::vec3 axis = glm::cross(a, b);
glm::mat4 rotationComponent = glm::mat4(1.0f);
rotationComponent[0] = glm::vec4(right, 0.0f);
rotationComponent[1] = glm::vec4(up, 0.0f);
rotationComponent[2] = glm::vec4(forward, 0.0f);
glm::mat4 toWorldCameraSpace = glm::transpose(rotationComponent);
axis = toWorldCameraSpace * glm::vec4(axis, 1.0);
glm::mat4 orbitMatrix = glm::rotate(glm::mat4(1.0f), (float)alpha, axis);
eye = glm::vec4(target, 1.0) + orbitMatrix * glm::vec4(eye - target, 1.0f);
up = orbitMatrix * glm::vec4(up, 1.0f);
}
I use this code to map 2D mouse position to the sphere:
Vector3 GetArcBallVector(const Vector2f & mousePos) {
float radiusSquared = 1.0; //squared radius of the sphere
//compute mouse position from the centre of screen to interval [-half, +half]
Vector3 pt = Vector3(
mousePos.x - halfScreenW,
halfScreenH - mousePos.y,
0.0f
);
//if length squared is smaller than sphere diameter
//point is inside
float lengthSqr = pt.x * pt.x + pt.y * pt.y;
if (lengthSqr < radiusSquared){
//inside
pt.z = std::sqrtf(radiusSquared - lengthSqr);
}
else {
pt.z = 0.0f;
}
pt.z *= -1;
return pt;
}
To calculate rotation, I use the last (startPt) and current (endPt) mapped position and do:
Quaternion actRot = Quaternion::Identity();
Vector3 axis = Vector3::Cross(endPt, startPt);
if (axis.LengthSquared() > MathUtils::EPSILON) {
float angleCos = Vector3::Dot(endPt, startPt);
actRot = Quaternion(axis.x, axis.y, axis.z, angleCos);
}
I prefer to use Quaternions over matrices since they are easy to multiply (for acumulated rotation) and interpolate (for some smooting).
I am trying to implement "planet" world. So in any point on it's surface with my old camera class I got result like this :
Camera class update code is:
void cCamera::ProcessMouseMovement(GLdouble dt, glm::vec2 curOffset)
{
curOffset.x *= static_cast<GLfloat>(dt * m_MouseSensitivity);
curOffset.y *= static_cast<GLfloat>(dt * m_MouseSensitivity);
m_fPitch += curOffset.x;
m_fYaw += curOffset.y;
if (m_fYaw > 89.0f)
m_fYaw = 89.0f;
if (m_fYaw < -89.0f)
m_fYaw = -89.0f;
UpdateCameraVectors();
}
void cCamera::UpdateCameraVectors(void)
{
float fYawRad = glm::radians(m_fYaw);
float fPitchRad = glm::radians(m_fPitch);
float fYawCos = cos(fYawRad);
glm::vec3 front { cos(fPitchRad) * fYawCos,
sin(fYawRad),
sin(fPitchRad) * fYawCos };
*m_pCameraFront = glm::normalize(front);
m_Right = glm::normalize(glm::cross(*m_pCameraFront, m_WorldUp));
m_Up = glm::normalize(glm::cross(m_Right, front));
*m_pViewMatrix = glm::lookAt(glm::vec3(0.f), glm::vec3(0.f) + *m_pCameraFront, m_Up);
}
As (0,0,0) is planet center, I suppose I should udate m_WorldUp vector this way:
m_WorldUp = glm::normalize(*m_pPos);
And indeed, the results are good: camera turned the right way, but it's rotation is broken. Yaw and pitch are still dependent on old world up. I think, I should update front vector with new m_WorldUp, but don't really know how.
Solution was found here: https://gamedev.stackexchange.com/questions/73588/how-do-i-fix-my-planet-facing-camera
In my case code is
void cCamera::UpdateCameraVectors(void)
{
m_WorldUp = glm::normalize(*m_pPos);
glm::quat world_axes_rotation = glm::angleAxis(glm::radians(m_fPitch), glm::vec3(0.0f, -1.0f, 0.0f));
world_axes_rotation = glm::normalize(world_axes_rotation);
world_axes_rotation = glm::rotate(world_axes_rotation, glm::radians(m_fYaw), glm::vec3(1.0f, 0.0f, 0.0f));
m_Pole = glm::normalize(m_Pole - glm::dot(m_WorldUp, m_Pole) * m_WorldUp);
glm::mat4 local_transform;
local_transform[0] = glm::vec4(m_Pole, 0.0f);
local_transform[1] = glm::vec4(m_WorldUp, 0.0f);
local_transform[2] = glm::vec4(glm::cross(m_Pole, m_WorldUp), 0.0f);
local_transform[3] = glm::vec4(0.f, 0.f, 0.f, 1.0f);
world_axes_rotation = glm::normalize(world_axes_rotation);
*m_pViewMatrix = local_transform * glm::mat4_cast(world_axes_rotation);
*m_pCameraFront = -1.0f * glm::vec3((*m_pViewMatrix)[2]);
m_Up = glm::vec3((*m_pViewMatrix)[1]);
m_Right = glm::vec3((*m_pViewMatrix)[0]);
*m_pViewMatrix = glm::inverse(*m_pViewMatrix);
}
I'm trying to move camera around the player.
Right now I'm using camera class like this:
Camera::Camera(glm::vec3 position, glm::vec3 up, GLfloat yaw, GLfloat pitch)
{
this->position = position;
this->m_WorldUp = up;
this->up = up;
this->m_Yaw = yaw;
this->m_Pitch = pitch;
this->UpdateCameraVectors();
}
glm::mat4 Camera::getViewMatrix()
{
return glm::lookAt(position, position + m_Front, up);
}
void Camera::ProcessKeyboard(Camera_Movement direction, GLfloat deltaTime)
{
float velocity = moveSpeed * deltaTime;
switch (direction) {
case FORWARD: position += m_Front * velocity; break;
case BACKWARD: position -= m_Front * velocity; break;
case LEFT: position -= m_Right * velocity; break;
case RIGHT: position += m_Right * velocity; break;
case UPWARDS: position += m_WorldUp * velocity; break;
case DOWNWARDS: position -= m_WorldUp * velocity; break;
}
}
void Camera::ProcessMouseMovement(GLfloat xOffset, GLfloat yOffset, GLboolean constrainPitch)
{
xOffset *= sensitivity;
yOffset *= sensitivity;
m_Yaw += xOffset;
m_Pitch += yOffset;
if (constrainPitch) {
if (m_Pitch > 89.0f) {
m_Pitch = 89.0f;
} else if (m_Pitch < -89.0f) {
m_Pitch = -89.0f;
}
}
UpdateCameraVectors();
}
void Camera::UpdateCameraVectors()
{
glm::vec3 front;
front.x = cos(glm::radians(m_Yaw)) * cos(glm::radians(m_Pitch));
front.y = -sin(glm::radians(m_Pitch));
front.z = sin(glm::radians(m_Yaw)) * cos(glm::radians(m_Pitch));
m_Front = glm::normalize(front);
m_Right = glm::normalize(glm::cross(m_Front, m_WorldUp));
up = glm::normalize(glm::cross(m_Right, m_Front));
}
It allows me to free look and move around the world.
Player's update method at the moment:
glm::mat4 projection = glm::mat4(1.0f);
projection = glm::perspective(glm::radians(45.0f), 16.0f / 9.0f, 0.1f, 1000.0f);
glm::mat4 view = glm::mat4(1.0f);
view = camera->getViewMatrix();
glm::mat4 model = glm::mat4(1.0f); {
glm::mat4 translate = glm::translate(model, position);
glm::mat4 rotate = glm::rotate(model, glm::radians(180.0f), glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 scale = glm::scale(model, glm::vec3(0.1f, 0.1f, 0.1f));
model = translate * rotate * scale;
}
glm::mat4 mvp = projection * view * model;
GLint u_mvp = shader.GetUniformLocation("u_mvp");
glUniformMatrix4fv(u_mvp, 1, GL_FALSE, glm::value_ptr(mvp));
I know that I have to change something with view matrix, but I have not got enough knowledge.
How can I upgrade my camera class that it can look, rotate, around a player, like in a circle, an MMO RPG style?
The camera class itself should not be receiving keyboard updates at all - that should be done in the player class. Every time the player moves, update the camera class with its new position. See comments in below code for more details.
Camera::Camera(glm::vec3 position, glm::vec3 up, GLfloat yaw, GLfloat pitch, GLfloat dist)
{
m_WorldUp = up;
// this->up = up; <- delete this variable; lookAt computes it for us
m_Pos = position; // this is the *player* position
m_Yaw = yaw;
m_Pitch = pitch;
m_Dist = dist; // distance from the player
UpdateViewMatrix(true);
}
// private method
void Camera::UpdateViewMatrix(bool computeDir = false)
{
// compute the new direction
if (computeDir)
{
glm::vec3 radial;
radial.x = cos(glm::radians(m_Yaw)) * cos(glm::radians(m_Pitch));
radial.y = sin(glm::radians(m_Pitch)); // there was a sign error here
radial.z = sin(glm::radians(m_Yaw)) * cos(glm::radians(m_Pitch));
m_Dir = -radial;
}
glm::vec3 pos = m_Pos - m_Dist * m_Dir; // *camera* position
// additional view matrix member variable
// you were using lookAt in the wrong way
m_View = glm::lookAt(m_Pos, pos, m_WorldUp);
}
// public method - call this everytime the player moves
void Camera::UpdateTargetPosition(glm::vec3 const & pos)
{
m_Pos = pos;
UpdateViewMatrix();
}
void Camera::UpdateAngles(GLfloat yaw, GLfloat pitch, GLboolean constrainPitch)
{
if (constrainPitch) {
if (pitch > 89.0f) {
pitch = 89.0f;
} else if (pitch < -89.0f) {
pitch = -89.0f;
}
}
// if yaw is outside the conventional range (-180.0, 180.0], shift it
if (yaw < -180.0f || yaw > 180.0f) {
yaw -= floor((yaw + 180.0f) / 360.0f) * 360.0f;
}
m_Yaw = yaw;
m_Pitch = pitch;
UpdateViewMatrix(true);
}
void Camera::ProcessMouseMovement(GLfloat xOffset, GLfloat yOffset, GLboolean constrainPitch)
{
UpdateAngles(m_Yaw + xOffset * sensitivity,
m_Pitch + yOffset * sensitivity,
constrainPitch);
}
for mouse follow you need:
camera = inverse(player * camera_view_and_offset)
where player is your player direct matrix, camera_view_and_offset is the view offset and turn around matrix relative to your player coordinate system and camera is your camera inverse matrix you should use as part of modelview ....
for more info see:
Understanding 4x4 homogenous transform matrices