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);
}
```