1
\$\begingroup\$

I am trying to build an arcball camera and it's mostly working, the problem is that as you rotate around the object you have selected (orbit_target_pos in the code), the camera starts to slowly roll (rotate around Z).

if I orbit around the object for a while, eventually the orientation becomes completely wrong. I also noticed that resetting the up vector to (0.f, 1.f, 0.f, 0.f) makes the camera orientation correct until I hold alt and start orbiting again, and the camera will slowly go back to be in the wrong orientation again.

This is the code to make the camera orbit around the selected object:

bool is_orbiting = false;
XMVECTOR orbit_target_pos = XMVectorSet(0.f, 0.f, 0.f, 1.f);

XMVECTOR camera_right = XMVectorSet(1.f, 0.f, 0.f, 0.f);
XMVECTOR camera_up = XMVectorSet(0.f, 1.f, 0.f, 0.f);
XMVECTOR camera_forward = XMVectorSet(0.f, 0.f, 1.f, 0.f);

void wndproc(UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (!GetAsyncKeyState(VK_MENU)) // ALT is not held down
    {
        is_orbiting = false;
    }

    switch (msg)
    {
    case WM_MOUSEMOVE:
        if (!ImGui::IsAnyWindowHovered() && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemFocused() && !ImGui::IsAnyWindowFocused())
        {
            if (ImGui::IsMouseDown(ImGuiMouseButton_Left))
            {
                float x = ImGui::GetMousePos().x;
                float y = ImGui::GetMousePos().y;

                float dx = XMConvertToRadians(0.5f * (x - last_mouse_pos.x));
                float dy = XMConvertToRadians(0.5f * (y - last_mouse_pos.y));

                camera_forward = XMVector3Normalize(camera_forward);
                camera_up = XMVector3Normalize(camera_up);
                camera_right = XMVector3Normalize(camera_right);

                camera_right = XMVector3Cross(camera_up, camera_forward);
                camera_up = XMVector3Normalize(XMVector3Cross(camera_forward, camera_right));

                if (GetAsyncKeyState(VK_MENU)) // ALT is held down
                {
                    is_orbiting = true;
                    XMVECTOR start_cam_pos = cam.position;

                    // Get the direction vector from the camera position to the target position (position - point = direction)
                    XMVECTOR cam_to_target_dir = orbit_target_pos - cam.position;

                    // Translate to target position (direction + point = point)
                    cam.position = cam_to_target_dir + cam.position;

                    // Rotate the camera forward and right vectors around the world up vector
                    XMMATRIX to_yaw = XMMatrixRotationY(dx);
                    XMVECTOR rot_cam_forward = XMVector3Normalize(XMVector3Transform(camera_forward, to_yaw));
                    XMVECTOR rot_cam_right = XMVector3Normalize(XMVector3Transform(camera_right, to_yaw));

                    // Rotate the camera forward and up vectors around the camera right vector
                    XMMATRIX to_pitch = XMMatrixRotationAxis(rot_cam_right, dy);
                    rot_cam_forward = XMVector3Normalize(XMVector3Transform(rot_cam_forward, to_pitch));
                    XMVECTOR rot_cam_up = XMVector3Normalize(XMVector3Transform(camera_up, to_pitch));

                    // Get the distance from the camera position to the object it orbits
                    XMVECTOR cam_to_target = XMVectorSubtract(orbit_target_pos, start_cam_pos);
                    float dist_to_target = XMVectorGetX(XMVector3Length(cam_to_target));

                    // Place the camera back
                    rot_cam_forward = XMVectorScale(rot_cam_forward, -dist_to_target);
                    cam.position = XMVectorAdd(cam.position, rot_cam_forward);

                    //look at the thing we're orbiting around
                    camera_forward = XMVector4Normalize(XMVectorSubtract(orbit_target_pos, cam.position));
                }
            }
        }
        last_mouse_pos.x = ImGui::GetMousePos().x;
        last_mouse_pos.y = ImGui::GetMousePos().y;
        break;
    }
}

This is the code to handle updating the camera view matrix. I don't think the problem is in this part of the code but I include it anyway just in case.

void update_camera()
{
    camera_up = XMVector3Normalize(camera_up);
    camera_forward = XMVector3Normalize(camera_forward);
    camera_right = XMVector3Cross(camera_up, camera_forward);
    camera_up = XMVector3Normalize(XMVector3Cross(camera_forward, camera_right));

    // Camera translation
    float x = -XMVectorGetX(XMVector3Dot(camera_right, cam.position));
    float y = -XMVectorGetX(XMVector3Dot(camera_up, cam.position));
    float z = -XMVectorGetX(XMVector3Dot(camera_forward, cam.position));

    XMMATRIX T;
    T.r[0] = camera_right;
    T.r[0].m128_f32[3] = x;

    T.r[1] = camera_up;
    T.r[1].m128_f32[3] = y;

    T.r[2] = camera_forward;
    T.r[2].m128_f32[3] = z;

    T.r[3] = XMVectorSet(0.f, 0.f, 0.f, 1.f);

    XMMATRIX view = T;
    XMStoreFloat4x4(&pass.view, view);

    // projection matrix
    float aspect_ratio = (float)g_hwnd_width / (float)g_hwnd_height;
    XMMATRIX proj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(0.25f * XM_PI, aspect_ratio, 1.0f, 1000.0f));
    XMStoreFloat4x4(&pass.proj, proj);
}
```
\$\endgroup\$
  • \$\begingroup\$ You may be interested in I'm rotating an object on two axes, so why does it keep twisting around the third axis? \$\endgroup\$ – DMGregory Jul 2 at 19:39
  • \$\begingroup\$ It does appear that there's a "roll" creeping in when I orbit around, especially the more I move the mouse up and down in addition to left and right. However changing to a world Y axis didn't help in my case. \$\endgroup\$ – mbl Jul 2 at 20:59
  • \$\begingroup\$ Resetting the up vector does correct the rotation though. By that I mean that there's a button I can press that will set the up vector to (0.f, 1.f, 0.f, 0.f). That makes the 2nd code snippet I showed above use that new up vector and fix the orientation since it's called every frame. I'm not sure what I'm supposed to do to make the orbiting code stop introducing rolls into the rotation though. It tried resetting the up vector every time before doing the orbit, but doing that makes the model squish onto itself when looking from below, but it does make the orbitting from left to right correct. \$\endgroup\$ – mbl Jul 2 at 21:07

Your Answer

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

Browse other questions tagged or ask your own question.