From 4a355deb54b48eee5432ee617d6d3916121b0816 Mon Sep 17 00:00:00 2001 From: Martin Michalec Date: Wed, 11 Feb 2026 06:47:31 +0300 Subject: add sources --- res/spotlight.frag | 17 ++++ src/bake.c | 53 +++++++++++ src/spotlight.c | 271 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 res/spotlight.frag create mode 100644 src/bake.c create mode 100644 src/spotlight.c diff --git a/res/spotlight.frag b/res/spotlight.frag new file mode 100644 index 0000000..91e4d0b --- /dev/null +++ b/res/spotlight.frag @@ -0,0 +1,17 @@ +#version 330 core + +in vec2 fragTexCoord; // from raylib + +uniform sampler2D texture0; // from raylib +uniform vec2 target; +uniform float dimness; +uniform float radius; + +out vec4 color; + +void main() +{ + color = mix (texture (texture0, fragTexCoord), vec4(0), + length (target - gl_FragCoord.xy) < radius + ? 0.0 : dimness); +} diff --git a/src/bake.c b/src/bake.c new file mode 100644 index 0000000..435d612 --- /dev/null +++ b/src/bake.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include +#include +#include + +#define FILENAME_INPUT "./res/spotlight.frag" +#define FILENAME_OUTPUT "./build/spotlight.frag.c" + +int +main (int argc, char *argv[]) +{ + remove (FILENAME_OUTPUT); + + int fd = open (FILENAME_INPUT, O_RDONLY); + if (fd == -1) { + perror ("open"); + return EXIT_FAILURE; + } + + struct stat sb; + if (fstat (fd, &sb) == -1) { + perror ("fstat"); + close (fd); + return EXIT_FAILURE; + } + + void *mapped = mmap (NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (mapped == MAP_FAILED) { + perror ("mmap"); + close (fd); + return EXIT_FAILURE; + } + + if (!ExportDataAsCode (mapped, sb.st_size, FILENAME_OUTPUT)) { + perror ("raylib"); + close (fd); + return EXIT_FAILURE; + } + + if (munmap (mapped, sb.st_size) == -1) + perror ("munmap"); + close (fd); + + if (chmod (FILENAME_OUTPUT, S_IRUSR | S_IRGRP | S_IROTH) == -1) { + perror ("chmod"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/src/spotlight.c b/src/spotlight.c new file mode 100644 index 0000000..ae45a93 --- /dev/null +++ b/src/spotlight.c @@ -0,0 +1,271 @@ +#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; +} -- cgit v1.3