#[Solved] Problem with camera axis flipping when rotating over some threshold

163 messages · Page 1 of 1 (latest)

gritty vale
#

Hello guys, I am moving my camera along surface of a sphere. Camera is always looking towards center of the sphere:
forward = center - pointOnSphere
I am having problem with right and up axes of camera flipping when phi is in range [k * PI, k+1 * PI], k element {1, 3, 5, ..., inf} as it can be seen in geogebra example.
I am trying to solve flipping issue by multiplying "right" and "up" vectors of camera by "-1" in case m_phi is in that range in which flipping occurs. This did not resolve the issue. Can anyone spot what I am missing? I've also recorded how it looks in my application (2nd video).
This is the code:

void Camera::calculatePointOnSphere(const double& startingX, const double& startingY, const double& currentX, const double& currentY)
{
    mXOffset += (currentX - startingX);
    mYOffset += currentY - startingY;
    m_theta = mXOffset / mWidth * glm::radians(360.0f) * mSensitivity;
    m_phi = mYOffset / mHeight * glm::radians(180.0f) * mSensitivity;
    mPointOnSphere.x = mSphereRadius * glm::cos(m_theta) * glm::sin(m_phi);
    mPointOnSphere.y = mSphereRadius * glm::cos(m_phi);
    mPointOnSphere.z = mSphereRadius * glm::sin(m_theta) * glm::sin(m_phi);
} 
void Camera::updateCameraAxis()
{
    float k = std::abs(m_phi / glm::pi<float>());
    float direction = ((int)std::floor(k) % 2 == 1 && (int)std::ceil(k) % 2 == 0) ? -1 : 1;
    mForward = glm::normalize(-mPointOnSphere);
    mRight = glm::normalize(glm::cross(mForward, mWorldUp)) * direction;
    mUp = glm::normalize(glm::cross(mRight, mForward)); // doesn't work even when multiplying with -1
tender frigate
#

It creates a tangent frame for a point on surface of sphere.

#

I suspect there is also analytical formula for bitangent, but I was lazy when last time fixed that code

summer zenith
#

While I'm not an expert in orientations I would assume it either has something to do with the angle representation or that you are using a fixed direction when for a source representation that has an infinite number of valid combinations (assuming the camera is looking at a sphere with a certain polar coordinate, all roll values are allowed).

While my poolar coordinate knowledge is limited at best, your interaction in the second video looks like you want a arcball rotation and for that there are multiple implementations and articles online, probably more or less based on shoemakes article.

gritty vale
gritty vale
# summer zenith While I'm not an expert in orientations I would assume it either has something t...

Thank you for your answer! Arcball rotation looks really cool and I am really glad you mentioned it because I didn't know what was this effect called but I've wanted to implement it 🙂 . I'll look it up more indepeth but at first look I can't quite see how I could be able to apply it to camera (it seems more like something that would be performed on object in a game - for example when you pick up object and inspect it, but I might be wrong).

summer zenith
#

Rotating an object and rotating the camera around the object is "the same" rotation(but revered). The only way you can tell the difference is if you have a reference point like a grid or ground plane. Similar to how a 3d camera is essentially translating and rotating the entire scene.

gritty vale
tender frigate
#

Not IIRC

#

hmm

#

I'm not 100% sure

gritty vale
#

Sry I am just not sure to what question are you refering to - maybe the last question (about there being a way to prevent it)?

tender frigate
#

I feel like this is X Y problem

#

You are describing issue with your solution, but I wonder what the actual goal this solution is attempting to meet

gritty vale
#

What about this: I can make quaternion that is used to rotate from WorldForward to my current forward (since there shouldn't be roll when rotating camera)

tender frigate
#

Do you have a camera?

#

Is the camera user controlled?

gritty vale
#

Yes

#

It is tracking my mouse x and y offset

tender frigate
#

Well so you want the camera to have state

#

more that just point on sphere

#

You need to track the current tangent frame / orientation for the camera

gritty vale
#

So do I just need to save my tangent frame at the beginning of rotation, and then it's tangent frame every "frame" and compare those vectors and deduce rotation from that information?

tender frigate
#

I mean you have class Camera { glm::vec3 position; glm::quat orientation; };

gritty vale
#

Because I have some bad experience with using quaternions directly to modify camera rotation - it was really weird and it didn't rotate in the manner that I expected it to

tender frigate
#

You need to keep track of the current position and orientation of the camera

#

well you need to know how to handle orientations - regardless of which representation you use

gritty vale
#

I didn't practically need to update rotation

#

I can send code if you would like to explain what I mean

tender frigate
#

no need

#

the root of your problem is that you throw away the orientation

#

you cannot do that

gritty vale
#

How do I compute it? And where should I use it (I don't see where can I apply it since lookAt figures out everything from those 2 points and vector)

#

Should I use rotation * (0,1,0) for up vector parameter in lookAt

tender frigate
#

I store this in my camera (node aka frame controller):

    glm::vec3 m_position;
    glm::mat4 m_orientation;
#

Then I have these routines:

auto Frame_controller::get_axis_x() const -> vec3
{
    return vec3{m_orientation[0]};
}

auto Frame_controller::get_axis_y() const -> vec3
{
    return vec3{m_orientation[1]};
}

auto Frame_controller::get_axis_z() const -> vec3
{
    return vec3{m_orientation[2]};
}
#
    const float speed = move_speed + speed_modifier.current_value();
    float tx = translate_x.current_value() + active_translate_x;
    if (tx != 0.0f) {
        m_position += get_axis_x() * tx * speed;
    }

    float ty = translate_y.current_value() + active_translate_y;
    if (ty != 0.0f) {
        m_position += get_axis_y() * ty * speed;
    }

    float tz = translate_z.current_value() + active_translate_z;
    if (tz != 0.0f) {
        m_position += get_axis_z() * tz * speed;
    }
#
    float rx = rotate_x.current_value() + active_rotate_x;
    float ry = rotate_y.current_value() + active_rotate_y;
    apply_rotation(rx, ry, 0.0f);
#
void Frame_controller::apply_rotation(float rx, float ry, float rz)
{
    glm::mat4 new_orientation = m_orientation;
    if (rx != 0.0f) {
        glm::mat4 rotate = erhe::math::create_rotation<float>(rx, get_axis_x());
        new_orientation = rotate * new_orientation;
    }
    if (ry != 0.0f) {
        glm::mat4 rotate = erhe::math::create_rotation<float>(ry, glm::vec3{0.0f, 1.0f, 0.0f}); //get_axis_y());
        new_orientation = rotate * new_orientation;
    }
    if (rz != 0.0f) {
        glm::mat4 rotate = erhe::math::create_rotation<float>(rz, get_axis_z());
        new_orientation = rotate * new_orientation;
    }
    m_orientation = new_orientation;
#

This is not exactly what you are after but maybe you get some idea how to use glm::vec3 m_position and glm::mat4 m_orientation

#

I cant recall why I have my own create_rotation(), I'm pretty sure glm has the same functionality nervous

#

the idea is that you apply rotation to previous rotation

gritty vale
gritty vale
#

I'll just try to find some old video to show you what I mean

#

Aside from it being really slow, once it got rotated weirdly, I couldn't turn it back to it's "leveled position". I don't remember what approach did I use back then, but I will surely go back to the new_rot = rot * old_rot
because it seems more stable and there aren't that many flips.
Thank you for all your answers 🙂

gritty vale
tender frigate
#

oh you want orbit camera.

#

I have that in my camera as well

#
void Frame_controller::apply_tumble(glm::vec3 pivot, float rx, float ry, float rz)
{
    glm::mat4 new_orientation = m_orientation;
    if (rx != 0.0f) {
        glm::mat4 rotate = erhe::math::create_rotation<float>(rx, get_axis_x());
        new_orientation = rotate * new_orientation;
    }
    if (ry != 0.0f) {
        glm::mat4 rotate = erhe::math::create_rotation<float>(ry, glm::vec3{0.0f, 1.0f, 0.0f}); //get_axis_y());
        new_orientation = rotate * new_orientation;
    }
    if (rz != 0.0f) {
        glm::mat4 rotate = erhe::math::create_rotation<float>(rz, get_axis_z());
        new_orientation = rotate * new_orientation;
    }
    {
        glm::mat4 old_world_from_view = m_orientation;
        glm::mat4 old_view_from_world = glm::transpose(old_world_from_view);
        glm::mat4 new_world_from_view = new_orientation;
        glm::vec3 direction_in_world  = m_position - pivot;
        glm::vec4 direction_in_view   = old_view_from_world * glm::vec4{direction_in_world, 0.0f};
        m_position = pivot + glm::vec3{new_world_from_view * direction_in_view};
    }
    m_orientation = new_orientation;
    update();
}
#

IDK if that matches with any of the articles

#

My method simply rotates camera but then applies translation (position offset) to keep the pivot point in the same place, in view space

#

this "normal" free camera and orbit camera are mostly identical

summer zenith
#

Pretty sure that camera code will suffer from the same issue since you are using a fixed global Y. edit: or maybe not 🤔

tender frigate
#

You mean this?

glm::mat4 rotate = erhe::math::create_rotation<float>(ry, glm::vec3{0.0f, 1.0f, 0.0f}); //get_axis_y());
#

using the commented get_axis_y() is a way to make truly free camera.

#

But that has other issues, and I have not found myself needing that

gritty vale
tender frigate
#

for rotation matrix, transpose is same as inverse

#

So world_from_view = inverse(view_from_world) = transpose(view_from_world)

gritty vale
#

Didn't know that, thank you

gritty vale
tender frigate
#

I wrote some comments for you:

// 1. Store the original camera orientation
glm::mat4 old_world_from_view = m_orientation;

// 2. Compute inverse of original camera orientation
glm::mat4 old_view_from_world = glm::transpose(old_world_from_view);

// 3. This is the new orientation after camera has turned
glm::mat4 new_world_from_view = new_orientation;

// 4. This is the direction vector from pivot point to camera position in world space
//    This is before camera is rotated
glm::vec3 direction_in_world  = m_position - pivot;

// 5. This is the direction vector from pivot point to camera position in view space
glm::vec4 direction_in_view   = old_view_from_world * glm::vec4{direction_in_world, 0.0f};

// 6. Compute new position for camera:
//  - Starting from pivot point
//  - apply the direction in view offset, transformed back to world space
//    using new orientation
m_position = pivot + glm::vec3{new_world_from_view * direction_in_view};
#

I suppose in step 4 I could use old_direction_in_world

#

and in step 6 I could first compute new_direction_in_world = glm::vec3{new_world_from_view * direction_in_view}; and then m_position = pivot + new_direction_in_world;

#

Key point is that direction_in_view remains constant no matter how camera is rotated, when you are oribiting around pivot point.

#

I suppose "direction" is not ideal name for the vector pivot to camera. Maybe I should call it PC (as in P to C vector). IDK. Or pivot_to_camera.

gritty vale
tender frigate
#

The idea of orbit camera is that the pivot point remains stationary, right?

#

Camera rotates around some given pivot point. In view space, the pivot point does not move, but everything else rotates around that point.

#

The bottom end point of the red ray is pivot point. That point remains stationary in view space. The vector from camera to that point remains fixed while orbiting (assuming camera is only rotated)

gritty vale
tender frigate
#

It is true in the world space as well, yes.

gritty vale
#

I was always looking from the world coordinate system

#

But if you look in view, then pivot is always in the center of the screen, and camera forward is also in the center of the screen and is pointing to pivot?

#

I've explained it poorly

#

Basically pivot is this green dot in view space?

#

Does that vector from camera to pivot in view space have any length?

tender frigate
#

Typically yes. Only special case if pivot point is camera location, then the pivot to camera vector would be 0.

#

In my implementation the pivot does not have to be in center of the view. It can be any point in the world

gritty vale
#

And you say that in view space the vector from camera to pivot is constant? It always has same parameter values for x, y and z no matter how camera is rotated?

tender frigate
#

That is the case when neither camera nor pivot point is moving.

#

When both points stay in same place, they stay the same in world and view space, and thus the vector from one to another point is constant

#

Of course if you move either, this is no longer true

#

But if you choose a pivot point, and don't move camera, the vector remains fixed.

gritty vale
#

Ah yeah that makes sense, I thought that even after rotating (which moves camera on sphere, because direction vector gets rotated around pivot), the vector stays the same and that confused me

tender frigate
#

Right so in world space the vector actually does not stay the same, only in view space

#

For a moment I was confused about this. But orbiting means that camera moves in world space.

#

However, in view space, we want the pivot to remain in exactly same position

gritty vale
#

And sorry about this question because it is really dumb, but is view space 2D - becasue it is what camera sees, and I thought of it as some sort of screen?

tender frigate
#

No view space is 3d

#

as seen from point of camera

#

If camera is not rotated and is at world origo (it has identity transform), view space is same as world space

#

if you then move camera up by 1 meter, you get from world to camera by moving up one meter, and from world to camera by moving down 1 meter

#

window / screen space is what is 2d - this is after projection and viewport transform

gritty vale
#

Oh got it! Please tell me if I am annoying you with these questions because all of these questions I am asking are suppoused to be trivial

tender frigate
#

When camera is orbiting, the pivot point remains same in view space, clip space, and window/screen space

#

No problem at all

gritty vale
#

Thank you!

tender frigate
#

These are not at all trivial, I figured out these not so long ago myself

#

But also these are not complicated - after you get it

gritty vale
#

That's nice to hear, I've started questioning if I will ever improve considering I am struggling at these topics

gritty vale
tender frigate
#

Graphics programming is constant learning, it never stops 🙂

#

You can do this for 20 years and then you figure out how to do this camera stuff like things.

gritty vale
# tender frigate When camera is orbiting, the pivot point remains same in view space, clip space,...

Got it. Although I am having hard time visualizing it since view space is 3D and not 2D (the reason it would make sense to me if it was 2D is because it would always be at the same point - like the pixel on the screen). So world space is like you are spectating some event, and view space is as if you've entered someone's body and are looking through his eyes? I don't know if this analogy makes sense

gritty vale
tender frigate
#

It is exactly like that

gritty vale
#

So there really is depth to it

gritty vale
#

So when you are in world space, you can see basically everything, and in view space, you can see just what is infront of you?

tender frigate
#

well world space has no view point, it is just.. coordinate system

#

same is true for view space

#

Only after projection, what is "visible" is a thing

#

Perspective projection defines a frustum, so things inside that frustum are visible

#

Orthographics projection defines a box in view space - you can also see things "behind" the camera position / view point

#

But view space itself does not define yet what is visible

gritty vale
#

Oooh that makes sense

tender frigate
#

After projection you are in clip space, which defines what is in and what is out

#

view space defines what direction is up, right, front, back (camera orientation), and what is the view point (camera position)

gritty vale
#

So is view space similar to world space (in sense that both are 3D coordinate systems), just positioned at the different origin?

tender frigate
#

typically, position and orientation is different, otherwise they are the same

#

In theory you can apply any transformation that can be represented by 4 by 4 to get from one space to another

gritty vale
#

So any time you multiply some point by 4x4 matrix, you are changing spaces?

#

I'll make sure to dig through internet to try to understand view space better not to waste your time anymore because I currently can't think of good question since I have little understanding of spaces (even though I thought I understood them). I'll come back when I'll be able to ask some smarter questions! Thank you for all answers, I'll be back sometime later. Is it ok if I ping you when I come back (I assume it won't be until Monday because I also got some errands to do)

tender frigate
#

Applying a transformation - by multiplying a matrix - always takes you from one space to another space

#

for most transformations, there is inverse transformation

#

IMHO it is best to always name your transformations so that it is clear what is the "source" space and what is the destination space

#

like world_from_camera and camera_from_world

gritty vale
gritty vale
#

About vector from pivot to camera being continuous in view space, does this explanation make sense:
So we've said that pivot stays at the same position (unless we move the camera, but if we rotate camera, it is fixed, and only camera position is getting changed since it is orbiting around pivot). When we go into view space, we are transforming every single object in the scene into this new coordinate system that has camera at the origin of it (camera might be at (5, 2, -1) in world space, but it will be at (0, 0, 0) in view space and every object from world space will be transformed by inverse of camera transform to fit in view space). So when we orbit the camera, pivot is being fixed in the world anyway, so it will also always be at the same position in view space, and since camera is at the origin of view space, no matter it's position in world space (that is changed while orbiting), it will be at the (0,0,0) of view space. Since we are always looking at the pivot, view space will be transformed accordingly to always place pivot at (0,0-d) => if camera is at (5,0,0) at the world, everything needs to get rotated and transformed so camera can be in (0,0,0). If camera is at (0,0,5), everything in world will need to get rotated and translated in some other manner to get camera at (0,0,0) and pointing in -Z, but since it is meant to be looking at the pivot (it is it's center parameter in lookAt function), it will get transformed in such a fashion that pivot is always positioned at the same position in view space?

tender frigate
#

So when we orbit the camera, pivot is being fixed in the world anyway, so it will also always be at the same position in view space,
There is no such causality. The pivot stays in the same location in view space only because a) that is what we want and b) we apply offset that it makes it so - after camera has been rotated

#

But yes, rest is about rought. In view space, objects are relative to view space, and we choose to keep the pivot point in constant location relative to the camera / in viewspace.

gritty vale
gritty vale
# tender frigate > So when we orbit the camera, pivot is being fixed in the world anyway, so it w...

That makes sense. Many other objects also don't change position in world space, but if camera moves or gets rotated, their position in view space will get changed aswell right? It is only because we want our camera to be oriented towards pivot, and because distance between pivot and camera position in world space is constant, that our vector going from camera to pivot is same in view space (no matter the camera position) - because it will always look towards it (the pivot, since it is "center" parameter of lookAt), and the distance between those 2 points (camera position and pivot) doesn't get changed in world space (therefore it doesn't get changed in view space either), so by applying inverse transformations of camera (inverse camera position and inverse camera rotation), pivot will always be infront of the camera at the same distance => we are producing same vector

tender frigate
#

You mentioned lookAt - my method does not use lookAt function (which constructs a new view matrix ftom eye, target and up vector). My method updates existing view transform by applying offset translation to maintain the desired constraint (fixing orbit pivot in view space).

gritty vale
#

What about rotation of camera?

#

Doesn't that also need to be accounted for

#

I've thought that by using lookAt and passing pivot to be the "target/center" argument, we can be sure that it will always be at the same position in view space

#

Since it will always be right infront of camera (in view space)

tender frigate
#

Applying offset translation is exactly the thing which compensates for camera being rotated, that is whole purpose of it.
Yes, you could also use lookAt(). Comparing the two orbit methods:

  • lookAt(): Somehow come up with new camera position after orbit, then use lookAt(), can probably use previous view up vector for lookAt. So, first update position, then compute updated orientation.
  • My method: Turn camera using same mechanic as for "free camera look turning", and apply offset translation to keep pivot in place. So, first update orientation, then apply offset translation.
#

Both methods are valid, I find my method simpler as I can share the camera rotation update logic with the "free look camera turning".

gritty vale
gritty vale
#

Do you get unwanted roll effect in your implementation? I am not sure how to get rid of roll

#

Because if you pitch camera around camera right, and then rotate around around global Y, and after that pitch around right again, there will be roll accumulated when looking from world perspective

tender frigate
#

I remember getting unwanted roll when using local instead of global up vector. Using fixed world up vector essentially fixes up vector so that there is never roll.

tender frigate
#

The original arcball paper calls unwanted roll hysteresis IIRC.

#

Moreover, closed loops of mouse motion may not produce the closed loops of rotation one expects. Rotate an object by +90° x, +90° y, -90° x , and then -90° y, and it is not back as it started, but off by 120°. This "hysteresis" effect is not inevitable, and would not be tolerated in a translation controller. The point is not that such behavior precludes undoing a complete drag sequence, but that it is unforg iving during dragging. Yet only Arcball avoids hysteresis, by a more careful mapping of mouse input to rotation.

gritty vale
gritty vale
gritty vale