#include "cam.h" #include "reasings.h" #include static void cam_ctrl_editor(void); static void cam_ctrl_orbit(void); CameraInfo main_cam = {0}; void cam_init(void) { rlSetClipPlanes(0.3, 5000); main_cam.cam = (Camera3D) { .position = { 0, 10, 10 }, .target = { 0, 1, 0 }, .up = { 0, 1, 0}, .fovy = 70.0f, .projection = CAMERA_PERSPECTIVE, }; main_cam.update = false; main_cam.allow_fly = true; main_cam.state = CAM_MODE_EDIT_FLY; main_cam.perspective_fov = main_cam.cam.fovy; main_cam.view_rotation = QuaternionIdentity(); main_cam.mv_speed = 10; main_cam.mv_scale = 1; main_cam.lk_speed = 0.003f; } void cam_focus(Vec3 target, f32 distance, f32 view_height) { Camera *cam = &main_cam.cam; Vec3 backward = Vector3Normalize( Vector3Subtract(cam->position, cam->target)); Quaternion rotation = QuaternionNormalize( QuaternionFromMatrix(MatrixTranspose(MatrixLookAt( Vector3Zero(), Vector3Negate(backward), cam->up)))); main_cam.transition.start_rot = rotation; main_cam.transition.end_rot = rotation; main_cam.transition.start_pos = cam->position; main_cam.transition.start_target = cam->target; main_cam.transition.end_target = target; main_cam.transition.end_pos = Vector3Add(target, Vector3Scale(backward, fmaxf(distance, 0.5f))); main_cam.transition.start_fovy = cam->fovy; main_cam.transition.end_fovy = cam->fovy; if (cam->projection == CAMERA_ORTHOGRAPHIC) { main_cam.transition.end_fovy = fmaxf(view_height, 0.1f); } main_cam.transition.elapsed = 0; main_cam.transition.active = true; main_cam.state = CAM_MODE_EDIT_ORBIT; } static void cam_axis_view(int axis, bool negative) { Camera *cam = &main_cam.cam; f32 side = negative ? -1.0f : 1.0f; Vec3 backward = {0}; Vec3 up = {0, 1, 0}; switch (axis) { case 0: backward.x = side; break; case 1: backward.y = side; up = (Vec3){0, 0, -side}; break; case 2: backward.z = side; break; } if (main_cam.local_space) { backward = Vector3RotateByQuaternion( backward, main_cam.view_rotation); up = Vector3RotateByQuaternion( up, main_cam.view_rotation); } f32 distance = Vector3Distance(cam->position, cam->target); if (cam->projection == CAMERA_PERSPECTIVE) { main_cam.perspective_fov = cam->fovy; cam->fovy = 2.0f * distance * tanf(main_cam.perspective_fov * DEG2RAD * 0.5f); cam->projection = CAMERA_ORTHOGRAPHIC; } cam_focus(cam->target, distance, cam->fovy); main_cam.transition.end_pos = Vector3Add( cam->target, Vector3Scale(backward, distance)); Matrix view = MatrixLookAt( Vector3Zero(), Vector3Negate(backward), up); main_cam.transition.end_rot = QuaternionNormalize( QuaternionFromMatrix(MatrixTranspose(view))); main_cam.axis_view = true; } void cam_update(void) { if (main_cam.input) { bool fly = main_cam.update && main_cam.allow_fly && IsMouseButtonDown(MOUSE_BUTTON_RIGHT) && (IsKeyDown(KEY_W) || IsKeyDown(KEY_A) || IsKeyDown(KEY_S) || IsKeyDown(KEY_D) || IsKeyDown(KEY_Q) || IsKeyDown(KEY_E)); if (fly) { main_cam.transition.active = false; main_cam.state = CAM_MODE_EDIT_FLY; } Vec2 mouse = GetMouseDelta(); bool rotate = main_cam.update && IsMouseButtonDown(MOUSE_BUTTON_RIGHT) && !IsMouseButtonDown(MOUSE_BUTTON_MIDDLE) && (mouse.x != 0 || mouse.y != 0); bool toggle = !main_cam.update && !main_cam.transition.active && IsKeyPressed(KEY_P); bool leave_axis = main_cam.axis_view && !main_cam.transition.active && (fly || rotate); if (toggle || leave_axis) { main_cam.axis_view = false; Camera *cam = &main_cam.cam; f32 distance = Vector3Distance( cam->position, cam->target); if (cam->projection == CAMERA_PERSPECTIVE) { main_cam.perspective_fov = cam->fovy; cam->fovy = 2.0f * distance * tanf(main_cam.perspective_fov * DEG2RAD * 0.5f); cam->projection = CAMERA_ORTHOGRAPHIC; main_cam.state = CAM_MODE_EDIT_ORBIT; } else { Vec3 backward = Vector3Normalize( Vector3Subtract(cam->position, cam->target)); distance = cam->fovy / (2.0f * tanf(main_cam.perspective_fov * DEG2RAD * 0.5f)); cam->position = Vector3Add( cam->target, Vector3Scale(backward, distance) ); cam->fovy = main_cam.perspective_fov; cam->projection = CAMERA_PERSPECTIVE; } } if (!main_cam.update && !main_cam.transition.active && !IsKeyDown(KEY_LEFT_CONTROL)) { if (IsKeyPressed(KEY_L)) { main_cam.local_space = !main_cam.local_space; } bool negative = IsKeyDown(KEY_LEFT_SHIFT); if (IsKeyPressed(KEY_X)) { cam_axis_view(0, negative); } else if (IsKeyPressed(KEY_Y)) { cam_axis_view(1, negative); } else if (IsKeyPressed(KEY_Z)) { cam_axis_view(2, negative); } } bool navigate = main_cam.update || (main_cam.state == CAM_MODE_EDIT_ORBIT && GetMouseWheelMove() != 0); if (navigate && !main_cam.transition.active) { switch (main_cam.state) { case CAM_MODE_EDIT_FLY: cam_ctrl_editor(); break; case CAM_MODE_EDIT_ORBIT: cam_ctrl_orbit(); break; default: break; } } } if (!main_cam.transition.active) { return; } const f32 duration = 0.25f; main_cam.transition.elapsed = fminf( main_cam.transition.elapsed + GetFrameTime(), duration); f32 t = EaseSineInOut( main_cam.transition.elapsed, 0.0f, 1.0f, duration); Quaternion rotation = QuaternionSlerp( main_cam.transition.start_rot, main_cam.transition.end_rot, t); f32 start_distance = Vector3Distance( main_cam.transition.start_pos, main_cam.transition.start_target); f32 end_distance = Vector3Distance( main_cam.transition.end_pos, main_cam.transition.end_target); f32 distance = Lerp(start_distance, end_distance, t); main_cam.cam.target = Vector3Lerp( main_cam.transition.start_target, main_cam.transition.end_target, t); Vec3 backward = Vector3RotateByQuaternion( (Vec3){0, 0, 1}, rotation); main_cam.cam.position = Vector3Add( main_cam.cam.target, Vector3Scale(backward, distance)); main_cam.cam.up = Vector3RotateByQuaternion( (Vec3){0, 1, 0}, rotation); main_cam.cam.fovy = Lerp( main_cam.transition.start_fovy, main_cam.transition.end_fovy, t); if (main_cam.transition.elapsed >= duration) { main_cam.cam.position = main_cam.transition.end_pos; main_cam.cam.target = main_cam.transition.end_target; main_cam.cam.up = Vector3RotateByQuaternion( (Vec3){0, 1, 0}, main_cam.transition.end_rot); main_cam.cam.fovy = main_cam.transition.end_fovy; main_cam.transition.active = false; } } static void cam_rotate(bool orbit) { Camera *cam = &main_cam.cam; Vec2 delta = GetMouseDelta(); if (orbit && delta.x == 0 && delta.y == 0) { return; } Vec3 offset = Vector3Subtract(cam->position, cam->target); f32 distance = Vector3Length(offset); Vec3 backward = Vector3Scale(offset, 1.0f / distance); f32 yaw = atan2f(backward.x, backward.z); f32 pitch = asinf(Clamp(backward.y, -1.0f, 1.0f)); /* At a pole, use screen-up to recover the heading. */ if (backward.x * backward.x + backward.z * backward.z < 0.000001f) { f32 side = backward.y > 0 ? -1.0f : 1.0f; yaw = atan2f(cam->up.x * side, cam->up.z * side); } yaw -= delta.x * main_cam.lk_speed; pitch += delta.y * main_cam.lk_speed; pitch = Clamp(pitch, -PI * 0.5f + 0.001f, PI * 0.5f - 0.001f); backward = (Vec3){ sinf(yaw) * cosf(pitch), sinf(pitch), cosf(yaw) * cosf(pitch), }; offset = Vector3Scale(backward, distance); if (orbit) { cam->position = Vector3Add(cam->target, offset); } else { cam->target = Vector3Subtract(cam->position, offset); } cam->up = (Vec3){0, 1, 0}; } static void cam_ctrl_editor(void) { if (!main_cam.update) { return; } cam_rotate(false); Vec3 input_dir = {0}; f32 mv_speed = main_cam.mv_speed; if (IsKeyDown(KEY_W)) { input_dir.z += 1; } if (IsKeyDown(KEY_A)) { input_dir.x -= 1; } if (IsKeyDown(KEY_S)) { input_dir.z -= 1; } if (IsKeyDown(KEY_D)) { input_dir.x += 1; } if (IsKeyDown(KEY_Q)) { input_dir.y -= 1; } if (IsKeyDown(KEY_E)) { input_dir.y += 1; } input_dir = Vector3Normalize(input_dir); if (IsKeyDown(KEY_LEFT_SHIFT)) { mv_speed *= 3; } // makes it so you can stop faster than you can speed up f32 ms_amount = GetMouseWheelMove() < 0 ? GetMouseWheelMove() * 1.0 : GetMouseWheelMove() * 0.5; main_cam.mv_scale += ms_amount; main_cam.mv_scale = CLAMP(main_cam.mv_scale, 0.05, 80); mv_speed *= main_cam.mv_scale; mv_speed = CLAMP(mv_speed, 1, 1000); CameraMoveForward(&main_cam.cam, input_dir.z * mv_speed * GetFrameTime(), false); CameraMoveRight(&main_cam.cam, input_dir.x * mv_speed * GetFrameTime(), false); CameraMoveUp(&main_cam.cam, input_dir.y * mv_speed * GetFrameTime()); } static void cam_ctrl_orbit(void) { Camera *cam = &main_cam.cam; f32 wheel = GetMouseWheelMove(); f32 view_height; if (cam->projection == CAMERA_ORTHOGRAPHIC) { if (wheel != 0) { cam->fovy = Clamp( cam->fovy * powf(0.85f, wheel), 0.1f, 2000.0f); } view_height = cam->fovy; } else { f32 distance = Vector3Distance(cam->position, cam->target); if (wheel != 0) { f32 zoom = Clamp( distance * powf(0.85f, wheel), 0.5f, 1000.0f); CameraMoveToTarget(cam, zoom - distance); distance = zoom; } view_height = 2.0f * distance * tanf(cam->fovy * DEG2RAD * 0.5f); } if (!main_cam.update) { return; } Vec2 delta = GetMouseDelta(); if (IsMouseButtonDown(MOUSE_MIDDLE_BUTTON)) { Vec3 forward = GetCameraForward(cam); Vec3 right = GetCameraRight(cam); Vec3 up = Vector3Normalize( Vector3CrossProduct(right, forward)); f32 scale = view_height / GetScreenHeight(); Vec3 offset = Vector3Add( Vector3Scale(right, -delta.x * scale), Vector3Scale(up, delta.y * scale)); cam->position = Vector3Add(cam->position, offset); cam->target = Vector3Add(cam->target, offset); } else { cam_rotate(true); } }