├── LICENSE ├── README.md └── arcball_camera.h /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # arcball_camera 2 | 3 | Single-header single-function C/C++ immediate-mode camera for your graphics demos 4 | 5 | Just call `arcball_camera_update` once per frame. 6 | 7 | # OpenGL Example 8 | 9 | ![openglexample](http://i.imgur.com/DOX9gBX.gif) 10 | 11 | Below is a fully-functional example program that works using OpenGL. 12 | 13 | Create a new Visual Studio project, drop this file in it, and it should just work. 14 | 15 | ``` 16 | #define ARCBALL_CAMERA_IMPLEMENTATION 17 | #include "arcball_camera.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #pragma comment(lib, "OpenGL32.lib") 25 | #pragma comment(lib, "glu32.lib") 26 | 27 | int g_wheel_delta; 28 | 29 | LRESULT CALLBACK MyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 30 | { 31 | switch (message) 32 | { 33 | case WM_CLOSE: 34 | ExitProcess(0); 35 | case WM_MOUSEWHEEL: 36 | g_wheel_delta += GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA; 37 | break; 38 | } 39 | 40 | return DefWindowProc(hWnd, message, wParam, lParam); 41 | } 42 | 43 | int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) 44 | { 45 | WNDCLASSEX wc; 46 | ZeroMemory(&wc, sizeof(wc)); 47 | wc.cbSize = sizeof(wc); 48 | wc.style = CS_OWNDC; 49 | wc.lpfnWndProc = MyWndProc; 50 | wc.hInstance = hInstance; 51 | wc.hCursor = LoadCursor(NULL, IDC_ARROW); 52 | wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND; 53 | wc.lpszClassName = TEXT("WindowClass"); 54 | RegisterClassEx(&wc); 55 | 56 | RECT wr = { 0, 0, 640, 480 }; 57 | AdjustWindowRect(&wr, 0, FALSE); 58 | HWND hWnd = CreateWindowEx( 59 | 0, TEXT("WindowClass"), 60 | TEXT("BasicGL"), 61 | WS_OVERLAPPEDWINDOW, 62 | 0, 0, wr.right - wr.left, wr.bottom - wr.top, 63 | 0, 0, hInstance, 0); 64 | 65 | PIXELFORMATDESCRIPTOR pfd; 66 | ZeroMemory(&pfd, sizeof(pfd)); 67 | pfd.nSize = sizeof(pfd); 68 | pfd.nVersion = 1; 69 | pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 70 | pfd.iPixelType = PFD_TYPE_RGBA; 71 | pfd.cColorBits = 32; 72 | pfd.cDepthBits = 24; 73 | pfd.cStencilBits = 8; 74 | pfd.iLayerType = PFD_MAIN_PLANE; 75 | 76 | HDC hDC = GetDC(hWnd); 77 | 78 | int chosenPixelFormat = ChoosePixelFormat(hDC, &pfd); 79 | SetPixelFormat(hDC, chosenPixelFormat, &pfd); 80 | 81 | HGLRC hGLRC = wglCreateContext(hDC); 82 | wglMakeCurrent(hDC, hGLRC); 83 | 84 | ShowWindow(hWnd, SW_SHOWNORMAL); 85 | 86 | float pos[3] = { 3.0f, 3.0f, 3.0f }; 87 | float target[3] = { 0.0f, 0.0f, 0.0f }; 88 | 89 | // initialize "up" to be tangent to the sphere! 90 | // up = cross(cross(look, world_up), look) 91 | float up[3]; 92 | { 93 | float look[3] = { target[0] - pos[0], target[1] - pos[1], target[2] - pos[2] }; 94 | float look_len = sqrtf(look[0] * look[0] + look[1] * look[1] + look[2] * look[2]); 95 | look[0] /= look_len; 96 | look[1] /= look_len; 97 | look[2] /= look_len; 98 | 99 | float world_up[3] = { 0.0f, 1.0f, 0.0f }; 100 | 101 | float across[3] = { 102 | look[1] * world_up[2] - look[2] * world_up[1], 103 | look[2] * world_up[0] - look[0] * world_up[2], 104 | look[0] * world_up[1] - look[1] * world_up[0], 105 | }; 106 | 107 | up[0] = across[1] * look[2] - across[2] * look[1]; 108 | up[1] = across[2] * look[0] - across[0] * look[2]; 109 | up[2] = across[0] * look[1] - across[1] * look[0]; 110 | 111 | float up_len = sqrtf(up[0] * up[0] + up[1] * up[1] + up[2] * up[2]); 112 | up[0] /= up_len; 113 | up[1] /= up_len; 114 | up[2] /= up_len; 115 | } 116 | 117 | LARGE_INTEGER then, now, freq; 118 | QueryPerformanceFrequency(&freq); 119 | QueryPerformanceCounter(&then); 120 | 121 | POINT oldcursor; 122 | GetCursorPos(&oldcursor); 123 | 124 | float oldview[16]; 125 | 126 | while (!GetAsyncKeyState(VK_ESCAPE)) 127 | { 128 | MSG msg; 129 | while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 130 | { 131 | TranslateMessage(&msg); 132 | DispatchMessage(&msg); 133 | } 134 | 135 | QueryPerformanceCounter(&now); 136 | float delta_time_sec = (float)(now.QuadPart - then.QuadPart) / freq.QuadPart; 137 | 138 | POINT cursor; 139 | GetCursorPos(&cursor); 140 | ScreenToClient(hWnd, &cursor); 141 | 142 | float view[16]; 143 | arcball_camera_update( 144 | pos, target, up, view, 145 | delta_time_sec, 146 | 0.1f, // zoom per tick 147 | 0.5f, // pan speed 148 | 3.0f, // rotation multiplier 149 | 640, 480, // screen (window) size 150 | oldcursor.x, cursor.x, 151 | oldcursor.y, cursor.y, 152 | GetAsyncKeyState(VK_MBUTTON), 153 | GetAsyncKeyState(VK_RBUTTON), 154 | g_wheel_delta, 155 | 0); 156 | 157 | if (memcmp(oldview, view, sizeof(float) * 16) != 0) 158 | { 159 | printf("\n"); 160 | printf("pos: %f, %f, %f\n", pos[0], pos[1], pos[2]); 161 | printf("target: %f, %f, %f\n", target[0], target[1], target[2]); 162 | printf("view: %f %f %f %f\n" 163 | " %f %f %f %f\n" 164 | " %f %f %f %f\n" 165 | " %f %f %f %f\n", 166 | view[0], view[1], view[2], view[3], 167 | view[4], view[5], view[6], view[7], 168 | view[8], view[9], view[10], view[11], 169 | view[12], view[13], view[14], view[15]); 170 | } 171 | 172 | memcpy(oldview, view, sizeof(float) * 16); 173 | glClear(GL_COLOR_BUFFER_BIT); 174 | 175 | glMatrixMode(GL_PROJECTION); 176 | glLoadIdentity(); 177 | gluPerspective(70.0, (double)(wr.right - wr.left) / (wr.bottom - wr.top), 0.001, 100.0); 178 | 179 | glMatrixMode(GL_MODELVIEW); 180 | glLoadMatrixf(view); 181 | 182 | glBegin(GL_TRIANGLES); 183 | glColor3ub(255, 0, 0); 184 | glVertex3f(-1.0f, -1.0f, 0.0f); 185 | glColor3ub(0, 255, 0); 186 | glVertex3f(1.0f, -1.0f, 0.0f); 187 | glColor3ub(0, 0, 255); 188 | glVertex3f(0.0f, 1.0f, 0.0f); 189 | glEnd(); 190 | 191 | SwapBuffers(hDC); 192 | 193 | then = now; 194 | oldcursor = cursor; 195 | 196 | g_wheel_delta = 0; 197 | } 198 | 199 | return 0; 200 | } 201 | ``` 202 | -------------------------------------------------------------------------------- /arcball_camera.h: -------------------------------------------------------------------------------- 1 | #ifndef ARCBALL_CAMERA_H 2 | #define ARCBALL_CAMERA_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif // __cplusplus 7 | 8 | // Flags for tweaking the view matrix 9 | #define ARCBALL_CAMERA_LEFT_HANDED_BIT 1 10 | 11 | // * eye: 12 | // * Current eye position. Will be updated to new eye position. 13 | // * target: 14 | // * Current look target position. Will be updated to new position. 15 | // * up: 16 | // * Camera's "up" direction. Will be updated to new up vector. 17 | // * view (optional): 18 | // * The matrix that will be updated with the new view transform. Previous contents don't matter. 19 | // * delta_time_seconds: 20 | // * Amount of seconds passed since last update. 21 | // * zoom_per_tick: 22 | // * How much the camera should zoom with every scroll wheel tick. 23 | // * pan_speed: 24 | // * How fast the camera should pan when holding middle click. 25 | // * rotation_multiplier: 26 | // * For amplifying the rotation speed. 1.0 means 1-1 mapping between arcball rotation and camera rotation. 27 | // * screen_width/screen_height: 28 | // * Dimensions of the screen the camera is being used in (the window size). 29 | // * x0, x1: 30 | // * Previous and current x coordinate of the mouse, respectively. 31 | // * y0, y1: 32 | // * Previous and current y coordinate of the mouse, respectively. 33 | // * midclick_held: 34 | // * Whether the middle click button is currently held or not. 35 | // * rclick_held: 36 | // * Whether the right click button is currently held or not. 37 | // * delta_scroll_ticks: 38 | // * How many scroll wheel ticks passed since the last update (signed number) 39 | // * flags: 40 | // * For producing a different view matrix depending on your conventions. 41 | void arcball_camera_update( 42 | float eye[3], 43 | float target[3], 44 | float up[3], 45 | float view[16], 46 | float delta_time_seconds, 47 | float zoom_per_tick, 48 | float pan_speed, 49 | float rotation_multiplier, 50 | int screen_width, int screen_height, 51 | int x0, int x1, 52 | int y0, int y1, 53 | int midclick_held, 54 | int rclick_held, 55 | int delta_scroll_ticks, 56 | unsigned int flags); 57 | 58 | // Utility for producing a look-to matrix without having to update a camera. 59 | void arcball_camera_look_to( 60 | const float eye[3], 61 | const float look[3], 62 | const float up[3], 63 | float view[16], 64 | unsigned int flags); 65 | 66 | #ifdef __cplusplus 67 | } 68 | #endif // __cplusplus 69 | 70 | #endif // ARCBALL_CAMERA_H 71 | 72 | #ifdef ARCBALL_CAMERA_IMPLEMENTATION 73 | 74 | #include 75 | #include 76 | 77 | #ifdef __cplusplus 78 | extern "C" { 79 | #endif // __cplusplus 80 | 81 | void arcball_camera_update( 82 | float eye[3], 83 | float target[3], 84 | float up[3], 85 | float view[16], 86 | float delta_time_seconds, 87 | float zoom_per_tick, 88 | float pan_speed, 89 | float rotation_multiplier, 90 | int screen_width, int screen_height, 91 | int px_x0, int px_x1, 92 | int px_y0, int px_y1, 93 | int midclick_held, 94 | int rclick_held, 95 | int delta_scroll_ticks, 96 | unsigned int flags) 97 | { 98 | // check preconditions 99 | { 100 | float up_len = sqrtf(up[0] * up[0] + up[1] * up[1] + up[2] * up[2]); 101 | assert(fabsf(up_len - 1.0f) < 0.000001f); 102 | 103 | float to_target[3] = { 104 | target[0] - eye[0], 105 | target[1] - eye[1], 106 | target[2] - eye[2], 107 | }; 108 | float to_target_len = sqrtf(to_target[0] * to_target[0] + to_target[1] * to_target[1] + to_target[2] * to_target[2]); 109 | assert(to_target_len > 0.0001f); 110 | } 111 | 112 | // right click is held, then mouse movements implement rotation. 113 | if (rclick_held) 114 | { 115 | float x0 = (float)(px_x0 - screen_width / 2); 116 | float x1 = (float)(px_x1 - screen_width / 2); 117 | float y0 = (float)((screen_height - px_y0 - 1) - screen_height / 2); 118 | float y1 = (float)((screen_height - px_y1 - 1) - screen_height / 2); 119 | float arcball_radius = (float)(screen_width > screen_height ? screen_width : screen_height); 120 | 121 | // distances to center of arcball 122 | float dist0 = sqrtf(x0 * x0 + y0 * y0); 123 | float dist1 = sqrtf(x1 * x1 + y1 * y1); 124 | 125 | float z0; 126 | if (dist0 > arcball_radius) 127 | { 128 | // initial click was not on the arcball, so just do nothing. 129 | goto end_rotate; 130 | } 131 | else 132 | { 133 | // compute depth of intersection using good old pythagoras 134 | z0 = sqrtf(arcball_radius * arcball_radius - x0 * x0 - y0 * y0); 135 | } 136 | 137 | float z1; 138 | if (dist1 > arcball_radius) 139 | { 140 | // started inside the ball but went outside, so clamp it. 141 | x1 = (x1 / dist1) * arcball_radius; 142 | y1 = (y1 / dist1) * arcball_radius; 143 | dist1 = arcball_radius; 144 | z1 = 0.0f; 145 | } 146 | else 147 | { 148 | // compute depth of intersection using good old pythagoras 149 | z1 = sqrtf(arcball_radius * arcball_radius - x1 * x1 - y1 * y1); 150 | } 151 | 152 | // rotate intersection points according to where the eye is 153 | { 154 | float to_eye_unorm[3] = { 155 | eye[0] - target[0], 156 | eye[1] - target[1], 157 | eye[2] - target[2] 158 | }; 159 | float to_eye_len = sqrtf(to_eye_unorm[0] * to_eye_unorm[0] + to_eye_unorm[1] * to_eye_unorm[1] + to_eye_unorm[2] * to_eye_unorm[2]); 160 | float to_eye[3] = { 161 | to_eye_unorm[0] / to_eye_len, 162 | to_eye_unorm[1] / to_eye_len, 163 | to_eye_unorm[2] / to_eye_len 164 | }; 165 | 166 | float across[3] = { 167 | -(to_eye[1] * up[2] - to_eye[2] * up[1]), 168 | -(to_eye[2] * up[0] - to_eye[0] * up[2]), 169 | -(to_eye[0] * up[1] - to_eye[1] * up[0]) 170 | }; 171 | 172 | // matrix that transforms standard coordinates to be relative to the eye 173 | float eye_m[9] = { 174 | across[0], across[1], across[2], 175 | up[0], up[1], up[2], 176 | to_eye[0], to_eye[1], to_eye[2] 177 | }; 178 | 179 | float new_p0[3] = { 180 | eye_m[0] * x0 + eye_m[3] * y0 + eye_m[6] * z0, 181 | eye_m[1] * x0 + eye_m[4] * y0 + eye_m[7] * z0, 182 | eye_m[2] * x0 + eye_m[5] * y0 + eye_m[8] * z0, 183 | }; 184 | 185 | float new_p1[3] = { 186 | eye_m[0] * x1 + eye_m[3] * y1 + eye_m[6] * z1, 187 | eye_m[1] * x1 + eye_m[4] * y1 + eye_m[7] * z1, 188 | eye_m[2] * x1 + eye_m[5] * y1 + eye_m[8] * z1, 189 | }; 190 | 191 | x0 = new_p0[0]; 192 | y0 = new_p0[1]; 193 | z0 = new_p0[2]; 194 | 195 | x1 = new_p1[0]; 196 | y1 = new_p1[1]; 197 | z1 = new_p1[2]; 198 | } 199 | 200 | // compute quaternion between the two vectors (http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final) 201 | float qw, qx, qy, qz; 202 | { 203 | float norm_u_norm_v = sqrtf((x0 * x0 + y0 * y0 + z0 * z0) * (x1 * x1 + y1 * y1 + z1 * z1)); 204 | qw = norm_u_norm_v + (x0 * x1 + y0 * y1 + z0 * z1); 205 | 206 | if (qw < 1.e-6f * norm_u_norm_v) 207 | { 208 | /* If u and v are exactly opposite, rotate 180 degrees 209 | * around an arbitrary orthogonal axis. Axis normalisation 210 | * can happen later, when we normalise the quaternion. */ 211 | qw = 0.0f; 212 | if (fabsf(x0) > fabsf(z0)) 213 | { 214 | qx = -y0; 215 | qy = x0; 216 | qz = 0.0f; 217 | } 218 | else 219 | { 220 | qx = 0.0f; 221 | qy = -z0; 222 | qz = y0; 223 | } 224 | } 225 | else 226 | { 227 | /* Otherwise, build quaternion the standard way. */ 228 | qx = y0 * z1 - z0 * y1; 229 | qy = z0 * x1 - x0 * z1; 230 | qz = x0 * y1 - y0 * x1; 231 | } 232 | 233 | float q_len = sqrtf(qx * qx + qy * qy + qz * qz + qw * qw); 234 | qx /= q_len; 235 | qy /= q_len; 236 | qz /= q_len; 237 | qw /= q_len; 238 | } 239 | 240 | // amplify the quaternion's rotation by the multiplier 241 | // this is done by slerp(Quaternion.identity, q, multiplier) 242 | // math from http://number-none.com/product/Understanding%20Slerp,%20Then%20Not%20Using%20It/ 243 | { 244 | // cos(angle) of the quaternion 245 | float c = qw; 246 | if (c > 0.9995f) 247 | { 248 | // if the angle is small, linearly interpolate and normalize. 249 | qx = rotation_multiplier * qx; 250 | qy = rotation_multiplier * qy; 251 | qz = rotation_multiplier * qz; 252 | qw = 1.0f + rotation_multiplier * (qw - 1.0f); 253 | float q_len = sqrtf(qx * qx + qy * qy + qz * qz + qw * qw); 254 | qx /= q_len; 255 | qy /= q_len; 256 | qz /= q_len; 257 | qw /= q_len; 258 | } 259 | else 260 | { 261 | // clamp to domain of acos for robustness 262 | if (c < -1.0f) 263 | c = -1.0f; 264 | else if (c > 1.0f) 265 | c = 1.0f; 266 | // angle of the initial rotation 267 | float theta_0 = acosf(c); 268 | // apply multiplier to rotation 269 | float theta = theta_0 * rotation_multiplier; 270 | 271 | // compute the quaternion normalized difference 272 | float qx2 = qx; 273 | float qy2 = qy; 274 | float qz2 = qz; 275 | float qw2 = qw - c; 276 | float q2_len = sqrtf(qx2 * qx2 + qy2 * qy2 + qz2 * qz2 + qw2 * qw2); 277 | qx2 /= q2_len; 278 | qy2 /= q2_len; 279 | qz2 /= q2_len; 280 | qw2 /= q2_len; 281 | 282 | // do the slerp 283 | qx = qx2 * sinf(theta); 284 | qy = qy2 * sinf(theta); 285 | qz = qz2 * sinf(theta); 286 | qw = cosf(theta) + qw2 * sinf(theta); 287 | } 288 | } 289 | 290 | // vector from the target to the eye, which will be rotated according to the arcball's arc. 291 | float to_eye[3] = { eye[0] - target[0], eye[1] - target[1], eye[2] - target[2] }; 292 | 293 | // convert quaternion to matrix (note: row major) 294 | float qmat[9] = { 295 | (1.0f - 2.0f * qy * qy - 2.0f * qz * qz), 2.0f * (qx * qy + qw * qz), 2.0f * (qx * qz - qw * qy), 296 | 2.0f * (qx * qy - qw * qz), (1.0f - 2.0f * qx * qx - 2.0f * qz * qz), 2.0f * (qy * qz + qw * qx), 297 | 2.0f * (qx * qz + qw * qy), 2.0f * (qy * qz - qw * qx), (1.0f - 2.0f * qx * qx - 2.0f * qy * qy) 298 | }; 299 | 300 | // compute rotated vector 301 | float to_eye2[3] = { 302 | to_eye[0] * qmat[0] + to_eye[1] * qmat[1] + to_eye[2] * qmat[2], 303 | to_eye[0] * qmat[3] + to_eye[1] * qmat[4] + to_eye[2] * qmat[5], 304 | to_eye[0] * qmat[6] + to_eye[1] * qmat[7] + to_eye[2] * qmat[8] 305 | }; 306 | 307 | // compute rotated up vector 308 | float up2[3] = { 309 | up[0] * qmat[0] + up[1] * qmat[1] + up[2] * qmat[2], 310 | up[0] * qmat[3] + up[1] * qmat[4] + up[2] * qmat[5], 311 | up[0] * qmat[6] + up[1] * qmat[7] + up[2] * qmat[8] 312 | }; 313 | 314 | float up2_len = sqrtf(up2[0] * up2[0] + up2[1] * up2[1] + up2[2] * up2[2]); 315 | up2[0] /= up2_len; 316 | up2[1] /= up2_len; 317 | up2[2] /= up2_len; 318 | 319 | // update eye position 320 | eye[0] = target[0] + to_eye2[0]; 321 | eye[1] = target[1] + to_eye2[1]; 322 | eye[2] = target[2] + to_eye2[2]; 323 | 324 | // update up vector 325 | up[0] = up2[0]; 326 | up[1] = up2[1]; 327 | up[2] = up2[2]; 328 | } 329 | end_rotate: 330 | 331 | // if midclick is held, then mouse movements implement translation 332 | if (midclick_held) 333 | { 334 | int dx = px_x0 - px_x1; 335 | int dy = -(px_y0 - px_y1); 336 | 337 | float to_eye_unorm[3] = { 338 | eye[0] - target[0], 339 | eye[1] - target[1], 340 | eye[2] - target[2] 341 | }; 342 | float to_eye_len = sqrtf(to_eye_unorm[0] * to_eye_unorm[0] + to_eye_unorm[1] * to_eye_unorm[1] + to_eye_unorm[2] * to_eye_unorm[2]); 343 | float to_eye[3] = { 344 | to_eye_unorm[0] / to_eye_len, 345 | to_eye_unorm[1] / to_eye_len, 346 | to_eye_unorm[2] / to_eye_len 347 | }; 348 | 349 | float across[3] = { 350 | -(to_eye[1] * up[2] - to_eye[2] * up[1]), 351 | -(to_eye[2] * up[0] - to_eye[0] * up[2]), 352 | -(to_eye[0] * up[1] - to_eye[1] * up[0]) 353 | }; 354 | 355 | float pan_delta[3] = { 356 | delta_time_seconds * pan_speed * (dx * across[0] + dy * up[0]), 357 | delta_time_seconds * pan_speed * (dx * across[1] + dy * up[1]), 358 | delta_time_seconds * pan_speed * (dx * across[2] + dy * up[2]), 359 | }; 360 | 361 | eye[0] += pan_delta[0]; 362 | eye[1] += pan_delta[1]; 363 | eye[2] += pan_delta[2]; 364 | 365 | target[0] += pan_delta[0]; 366 | target[1] += pan_delta[1]; 367 | target[2] += pan_delta[2]; 368 | } 369 | 370 | // compute how much scrolling happened 371 | float zoom_dist = zoom_per_tick * delta_scroll_ticks; 372 | 373 | // the direction that the eye will move when zoomed 374 | float to_target[3] = { 375 | target[0] - eye[0], 376 | target[1] - eye[1], 377 | target[2] - eye[2], 378 | }; 379 | 380 | float to_target_len = sqrtf(to_target[0] * to_target[0] + to_target[1] * to_target[1] + to_target[2] * to_target[2]); 381 | 382 | // if the zoom would get you too close, clamp it. 383 | if (!rclick_held) 384 | { 385 | if (zoom_dist >= to_target_len - 0.001f) 386 | { 387 | zoom_dist = to_target_len - 0.001f; 388 | } 389 | } 390 | 391 | // normalize the zoom direction 392 | float look[3] = { 393 | to_target[0] / to_target_len, 394 | to_target[1] / to_target_len, 395 | to_target[2] / to_target_len, 396 | }; 397 | 398 | float eye_zoom[3] = { 399 | look[0] * zoom_dist, 400 | look[1] * zoom_dist, 401 | look[2] * zoom_dist 402 | }; 403 | 404 | eye[0] += eye_zoom[0]; 405 | eye[1] += eye_zoom[1]; 406 | eye[2] += eye_zoom[2]; 407 | 408 | if (rclick_held) 409 | { 410 | // affect target too if right click is held 411 | // this allows you to move forward and backward (as opposed to zoom) 412 | target[0] += eye_zoom[0]; 413 | target[1] += eye_zoom[1]; 414 | target[2] += eye_zoom[2]; 415 | } 416 | 417 | arcball_camera_look_to(eye, look, up, view, flags); 418 | } 419 | 420 | void arcball_camera_look_to( 421 | const float eye[3], 422 | const float look[3], 423 | const float up[3], 424 | float view[16], 425 | unsigned int flags) 426 | { 427 | if (!view) 428 | return; 429 | 430 | float look_len = sqrtf(look[0] * look[0] + look[1] * look[1] + look[2] * look[2]); 431 | float up_len = sqrtf(up[0] * up[0] + up[1] * up[1] + up[2] * up[2]); 432 | 433 | assert(fabsf(look_len - 1.0f) < 0.000001f); 434 | assert(fabsf(up_len - 1.0f) < 0.000001f); 435 | 436 | // up'' = normalize(up) 437 | float up_norm[3] = { up[0] / up_len, up[1] / up_len, up[2] / up_len }; 438 | 439 | // f = normalize(look) 440 | float f[3] = { look[0] / look_len, look[1] / look_len, look[2] / look_len }; 441 | 442 | // s = normalize(cross(f, up2)) 443 | float s[3] = { 444 | f[1] * up_norm[2] - f[2] * up_norm[1], 445 | f[2] * up_norm[0] - f[0] * up_norm[2], 446 | f[0] * up_norm[1] - f[1] * up_norm[0] 447 | }; 448 | float s_len = sqrtf(s[0] * s[0] + s[1] * s[1] + s[2] * s[2]); 449 | s[0] /= s_len; 450 | s[1] /= s_len; 451 | s[2] /= s_len; 452 | 453 | // u = normalize(cross(normalize(s), f)) 454 | float u[3] = { 455 | s[1] * f[2] - s[2] * f[1], 456 | s[2] * f[0] - s[0] * f[2], 457 | s[0] * f[1] - s[1] * f[0] 458 | }; 459 | float u_len = sqrtf(u[0] * u[0] + u[1] * u[1] + u[2] * u[2]); 460 | u[0] /= u_len; 461 | u[1] /= u_len; 462 | u[2] /= u_len; 463 | 464 | if (!(flags & ARCBALL_CAMERA_LEFT_HANDED_BIT)) 465 | { 466 | // in a right-handed coordinate system, the camera's z looks away from the look direction. 467 | // this gets flipped again later when you multiply by a right-handed projection matrix 468 | // (notice the last row of gluPerspective, which makes it back into a left-handed system after perspective division) 469 | f[0] = -f[0]; 470 | f[1] = -f[1]; 471 | f[2] = -f[2]; 472 | } 473 | 474 | // t = [s;u;f] * -eye 475 | float t[3] = { 476 | s[0] * -eye[0] + s[1] * -eye[1] + s[2] * -eye[2], 477 | u[0] * -eye[0] + u[1] * -eye[1] + u[2] * -eye[2], 478 | f[0] * -eye[0] + f[1] * -eye[1] + f[2] * -eye[2] 479 | }; 480 | 481 | // m = [s,t[0]; u,t[1]; -f,t[2]]; 482 | view[0] = s[0]; 483 | view[1] = u[0]; 484 | view[2] = f[0]; 485 | view[3] = 0.0f; 486 | view[4] = s[1]; 487 | view[5] = u[1]; 488 | view[6] = f[1]; 489 | view[7] = 0.0f; 490 | view[8] = s[2]; 491 | view[9] = u[2]; 492 | view[10] = f[2]; 493 | view[11] = 0.0f; 494 | view[12] = t[0]; 495 | view[13] = t[1]; 496 | view[14] = t[2]; 497 | view[15] = 1.0f; 498 | } 499 | 500 | #ifdef __cplusplus 501 | } 502 | #endif // __cplusplus 503 | 504 | #endif // ARCBALL_CAMERA_IMPLEMENTATION 505 | --------------------------------------------------------------------------------