#include #include #include #include #include #include #include "spotlight.frag.c" #define KEY_MOD_ROTATION KEY_LEFT_ALT #define KEY_MOD_RADIUS KEY_CAPS_LOCK // NOTE(cmmm): GetMousePosition returns actual value only after // certain amount of frames #define STARTUP_FRAME_COUNT 3 #define SPOTLIGHT_SPEED_MAX 2000 #define SPOTLIGHT_TARGET_INITIAL_ZOOM 1 #define SPOTLIGHT_TARGET_INITIAL_ROTATION 0 #define SPOTLIGHT_TARGET_INITIAL_FOCUSED false #define SPOTLIGHT_ROTATION_MULTIPLIER 45 #define SPOTLIGHT_ROTATION_SPEED_MAX 180 #define SPOTLIGHT_ZOOM_SPEED_MAX 4 #define SPOTLIGHT_ZOOM_MIN 1 #define SPOTLIGHT_ZOOM_MAX 64 #define SPOTLIGHT_RADIUS_MULTIPLIER 50 #define SPOTLIGHT_RADIUS_SPEED_MAX 10 #define SPOTLIGHT_RADIUS_MIN 100 #define SPOTLIGHT_DIMNESS_MIN .25 #define SPOTLIGHT_DIMNESS_MAX .75 #define SPOTLIGHT_FOCUS_SPEED_MAX 2000 #define END_DURATION (.5) #define EXECLP(P, ...) execlp ((P), (P), __VA_ARGS__, NULL) static Texture2D screenshot; static Vector2 translation_target = {0}; static Vector2 spotlight_pos; static Vector2 spotlight_pos_initial; static float spotlight_radius; static bool spotlight_focused = SPOTLIGHT_TARGET_INITIAL_FOCUSED; static float spotlight_target_zoom = SPOTLIGHT_TARGET_INITIAL_ZOOM; static float spotlight_target_rotation = SPOTLIGHT_TARGET_INITIAL_ROTATION; static float spotlight_target_radius; static Vector2 spotlight_target_pos; static float spotlight_radius_unfocused; static float spotlight_radius_initial_focused; static Shader shader; static int shader_location_target; static int shader_location_dimness; static int shader_location_radius; static Camera2D camera = { .rotation = 0, .zoom = 1, }; static Vector2 screen_size = {0}; static float frame_time; static void update_frame_info (void) { frame_time = GetFrameTime (); if (screen_size.x != GetScreenWidth () || screen_size.y != GetScreenHeight ()) { screen_size.x = GetScreenWidth (); screen_size.y = GetScreenHeight (); spotlight_radius_unfocused = Vector2Length (screen_size); spotlight_radius_initial_focused = fminf (screen_size.x, screen_size.y)/2; } } static void handle_input (void) { spotlight_target_pos = GetMousePosition (); if (IsMouseButtonDown (MOUSE_BUTTON_RIGHT)) translation_target = Vector2Add (translation_target, GetMouseDelta ()); float wheel_move = GetMouseWheelMove (); if (wheel_move) { if (IsKeyDown (KEY_MOD_ROTATION)) { spotlight_target_rotation += SPOTLIGHT_ROTATION_MULTIPLIER*wheel_move; } else if (IsKeyDown (KEY_MOD_RADIUS)) { if (!spotlight_focused) { spotlight_focused = true; spotlight_target_radius = spotlight_radius_initial_focused; HideCursor (); } spotlight_target_radius += SPOTLIGHT_RADIUS_MULTIPLIER*wheel_move; float spotlight_radius_max = spotlight_radius_unfocused; spotlight_target_radius = Clamp (spotlight_target_radius, SPOTLIGHT_RADIUS_MIN, spotlight_radius_max); } else { float scale_factor = 1 + fabsf (wheel_move)/4; if (!signbit (wheel_move)) scale_factor = 1/scale_factor; spotlight_target_zoom = Clamp (spotlight_target_zoom*scale_factor, SPOTLIGHT_ZOOM_MIN, SPOTLIGHT_ZOOM_MAX); } } if (IsKeyPressed (KEY_F)) { spotlight_focused ^= 1; if (spotlight_focused) { spotlight_target_radius = spotlight_radius_initial_focused; HideCursor (); } else ShowCursor (); } } static void update (void) { spotlight_pos = Vector2MoveTowards (spotlight_pos, spotlight_target_pos, SPOTLIGHT_SPEED_MAX*frame_time); Vector2 translation_delta = Vector2MoveTowards (translation_delta, translation_target, SPOTLIGHT_SPEED_MAX*frame_time); translation_target = Vector2Subtract (translation_target, translation_delta); Vector2 delta_target = Vector2Rotate (Vector2Scale (translation_delta, -1/camera.zoom), -camera.rotation*DEG2RAD); camera.target = Vector2Add (camera.target, delta_target); if (!spotlight_focused) { spotlight_target_radius = spotlight_radius_unfocused; } camera.rotation += Clamp (spotlight_target_rotation - camera.rotation, -SPOTLIGHT_ROTATION_SPEED_MAX*frame_time, +SPOTLIGHT_ROTATION_SPEED_MAX*frame_time); camera.zoom += Clamp (spotlight_target_zoom - camera.zoom, -SPOTLIGHT_ZOOM_SPEED_MAX*frame_time, +SPOTLIGHT_ZOOM_SPEED_MAX*frame_time); camera.target = GetScreenToWorld2D (spotlight_pos, camera); camera.offset = spotlight_pos; spotlight_radius += Clamp (spotlight_target_radius - spotlight_radius, -SPOTLIGHT_FOCUS_SPEED_MAX*frame_time, +SPOTLIGHT_FOCUS_SPEED_MAX*frame_time); } static void draw (void) { Vector2 target = { spotlight_pos.x, screen_size.y - spotlight_pos.y, }; float dimness = Remap (spotlight_radius, spotlight_radius_initial_focused, SPOTLIGHT_RADIUS_MIN, SPOTLIGHT_DIMNESS_MIN, SPOTLIGHT_DIMNESS_MAX); SetShaderValue (shader, shader_location_target, &target, SHADER_UNIFORM_VEC2); SetShaderValue (shader, shader_location_dimness, &dimness, SHADER_UNIFORM_FLOAT); SetShaderValue (shader, shader_location_radius, &spotlight_radius, SHADER_UNIFORM_FLOAT); BeginDrawing (); { ClearBackground (BLANK); BeginMode2D (camera); { BeginShaderMode (shader); { DrawTextureV (screenshot, Vector2Zero (), WHITE); } EndShaderMode (); } EndMode2D (); } EndDrawing (); } int main (int argc, char *argv[]) { char tmpname[] = "/tmp/spotlight.png"; // { // int tmp_fd; // do tmp_fd = mkstemp (tmpname); // while (tmp_fd == -1); // close (tmp_fd); // } { pid_t grim_pid = fork (); if (!grim_pid) EXECLP ("grim", tmpname); int grim_exit_status; waitpid (grim_pid, &grim_exit_status, 0); if (grim_exit_status) { TraceLog (LOG_ERROR, "GRIM: Unable to capture screenshot (%s)", tmpname); exit (EXIT_FAILURE); } TraceLog (LOG_INFO, "GRIM: Screenshot captured (%s)", tmpname); } SetConfigFlags (FLAG_FULLSCREEN_MODE | FLAG_MSAA_4X_HINT | FLAG_WINDOW_UNDECORATED | FLAG_VSYNC_HINT); InitWindow (0, 0, NULL); SetMouseCursor (MOUSE_CURSOR_CROSSHAIR); screenshot = LoadTexture (tmpname); // shader = LoadShader (NULL, "./spotlight.frag"); shader = LoadShaderFromMemory (NULL, (void *) SPOTLIGHT_FRAG_DATA); shader_location_target = GetShaderLocation (shader, "target"); shader_location_dimness = GetShaderLocation (shader, "dimness"); shader_location_radius = GetShaderLocation (shader, "radius"); spotlight_radius = Vector2Length ((Vector2) { GetScreenWidth (), GetScreenHeight () }); for (int i = 0; i < STARTUP_FRAME_COUNT; ++i) draw (); spotlight_pos = spotlight_pos_initial = GetMousePosition (); camera.target = GetScreenToWorld2D (spotlight_pos, camera); camera.offset = spotlight_pos; while (!WindowShouldClose ()) { update_frame_info (); handle_input (); update (); draw (); } Vector2 camera_target = GetMousePosition (); Camera2D start_camera = camera; float start_spotlight_radius = spotlight_radius; float distances[5] = { Remap (Vector2Distance (start_camera.target, camera_target), 0, Vector2Length (screen_size), 0, 1), Remap (Vector2Distance (start_camera.offset, camera_target), 0, Vector2Length (screen_size), 0, 1), start_camera.rotation/SPOTLIGHT_ROTATION_MULTIPLIER, Remap (start_camera.zoom, SPOTLIGHT_ZOOM_MIN, SPOTLIGHT_ZOOM_MAX, 0, 1), Remap (start_spotlight_radius - spotlight_radius_unfocused, 0, spotlight_radius_unfocused - SPOTLIGHT_RADIUS_MIN, 0, 1), }; float max_distance = 0; for (int i = 0; i < 5; ++i) { if (distances[i] > max_distance) max_distance = distances[i]; } if (max_distance) { float end_speed = 4/(1 + fminf ( max_distance, 1)); for (float progress = end_speed*frame_time; progress < 1 ; progress += end_speed*frame_time) { update_frame_info (); Vector2 spotlight_pos_world = GetScreenToWorld2D (spotlight_pos, camera); camera.target = Vector2Lerp (start_camera.target, camera_target, progress); camera.offset = Vector2Lerp (start_camera.offset, camera_target, progress); camera.rotation = Lerp (start_camera.rotation, 0, progress); camera.zoom = Lerp (start_camera.zoom, 1, progress); spotlight_radius = Lerp (start_spotlight_radius, spotlight_radius_unfocused, progress); spotlight_pos = GetWorldToScreen2D (spotlight_pos_world, camera); draw (); } } UnloadTexture (screenshot); CloseWindow (); return EXIT_SUCCESS; }