1
\$\begingroup\$

I'm writing a camera which needs to change a quaternion to Euler angles than change them back, it only works when the yaw is less than 90 degrees, I wrote a example to check:

glm::vec3 euler_angler = {0, 0, 0};
  for (int i = 0; i < 91; i++) {

    std::cout << "before: " << euler_angler.x << ", " << euler_angler.y << ", " << euler_angler.z << std::endl;

    euler_angler.y += 1;

    glm::quat quat({glm::radians(euler_angler.x), glm::radians(euler_angler.y), glm::radians(euler_angler.z)});

    glm::vec3 new_euler_angler = glm::eulerAngles(quat);

    std::cout << "after: " << glm::degrees(new_euler_angler.x) << ", " << glm::degrees(new_euler_angler.y) << ", "

              << glm::degrees(new_euler_angler.z) << std::endl;

  }

and when the Euler angles is {0, 89, 0}, then the Euler angles changed back to quat is the same, which is {0, 89, 0}, but when the origin changes to {0, 90, 0}, the new_euler_angler suddenly becomes to {180, 89.0001, 180}, and makes my camera rotation a mess.

Can someone help why this happened and what should I do?

the code of my camera:

glm::vec3 euler_angler = glm::eulerAngles(transform_data.rotation);
euler_angler.x = euler_angler.x + glm::radians(delta_y);
euler_angler.y += glm::radians(delta_x);
euler_angler.z = 0;
transform_data.rotation = glm::quat(euler_angler);

and here's my calculation of view matrix(which is a suspect because all other tutorials are using lookat matrix, I'm not sure my way is right or not):

// the position and rotation of the camera
auto transform_data = actor_.GetTransform().GetData();
glm::mat4 view = glm::mat4(1.0f);
view = glm::translate(view, -transform_data.position);
view = glm::toMat4(glm::quat(transform_data.rotation.w,
                               -transform_data.rotation.x,
                               -transform_data.rotation.y,
                               -transform_data.rotation.z)) * view;
render_camera_.SetViewMatrix(view);
glm::mat4 projection;
projection = glm::perspective(glm::radians(fov_), (float) aspect_, 0.1f, 100.0f);
render_camera_.SetProjectionMatrix(projection);
\$\endgroup\$
  • \$\begingroup\$ If you're converting back and forth between Euler angles and quaternions, then angle wrap-around is inevitable. Have you considered keeping your Euler angles stored as the ground truth that you can modify and express as a quaternion when needed, instead of trying to recover them from the quaternion? \$\endgroup\$ – DMGregory Jun 22 at 13:33
  • \$\begingroup\$ Hi @DMGregory, I'v read your link carefully but still get confused, if {0, 90, 0} and {180, 0, 180 } represent the same rotation, why my camera do not rotate correctly? Does this has anything to do with the quaternion conversion? Since my camera's rotation is represented by a quaternion inside? \$\endgroup\$ – ravenisadesk Jun 22 at 13:53
  • \$\begingroup\$ It's likely that some of your camera control code is making assumptions about the angles - like that z will always be zero - and so it's not correctly handling cases where it's handed back a converted angle that does not fit those assumptions. We'd need to see your camera control code to advise on where it goes awry. \$\endgroup\$ – DMGregory Jun 22 at 14:01
  • \$\begingroup\$ Hi @DMGregory, I paste my code in the question, I'm glad you can help, and you suggestion about a ground truth Euler angles works in my case, by the way. \$\endgroup\$ – ravenisadesk Jun 22 at 14:33
1
\$\begingroup\$

Your current code assumes it's always going to get Euler angles where x is between ±90° and z is close to 0.

Meanwhile, glm wants to return Euler angles that are standardized so that y is between ±90°, even if that means putting a large number in x & z to compensate.

So when you're in the vicinity of (0, 0-89, 0), your code behaves relatively intuitively. Adding +1 degree to the y makes your camera yaw 1 degree to the right, and adding +1 degree to the x makes your camera pitch 1 degree down (making some assumptions about your coordinate system).

When you cross 90 though, say to 91°, glm wants to keep y less than 90, so it picks an equivalent Euler angle triplet, (180, -89, 180). This is equivalent to yawing 180° opposite to the way you were facing, then pitching over backwards to reverse your heading, then rolling over to correct for the inversion. ie. It's still the same net orientation in the end.

But now, adding +1 degree to the x makes your camera pitch up, not down! (Because you've already pitched down 180 degrees, any more means wrapping around to the positive hemisphere now).

And the worst trouble comes from this line:

euler_angler.z = 0;

If z was close to zero as your code assumes, this would just neutralize any small bit of roll that had crept in. But we're 180 degrees away from that now, and we were counting on that 180° roll to turn us back upright after pitching ourselves upside-down. So zeroing this out actually has the effect of removing this correction and leaving the camera inverted.

Doing component-wise math on arbitrary Euler angles is fraught with problems, because the values on one axis can change the meaning of values on another axis.

A better solution for a pitch-yaw camera is to keep strict control over your Euler angles, so that you control when & where they wrap. You can store your desired pitch and yaw as member variables, so the only time their values change is when you deliberately add/subtract or wrap them. You can keep your z value always zero, so it's never roped into compensating for extreme pitch. And when you need a quaternion, you make one from your stored angles, keeping them as the sole source of truth describing the desired orientation.

As a bonus, this is faster - it's a lot quicker to read a couple floats from memory than it is to read a quaternion and then do all the trigonometry necessary to decode it into angles.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks very much. \$\endgroup\$ – ravenisadesk Jun 23 at 2:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.