summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLibravatar Martin Michalec <martin@michalec.dev>2026-02-11 03:47:31 +0000
committerLibravatar Martin Michalec <martin@michalec.dev>2026-02-11 03:47:31 +0000
commit4a355deb54b48eee5432ee617d6d3916121b0816 (patch)
tree84678b21428af2089e359bc95b28ebc8a7fe19f6 /src
parentadd .gitignore (diff)
downloadspotlight-4a355deb54b48eee5432ee617d6d3916121b0816.tar.gz
add sources
Diffstat (limited to 'src')
-rw-r--r--src/bake.c53
-rw-r--r--src/spotlight.c271
2 files changed, 324 insertions, 0 deletions
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 @@
1#include <stdlib.h>
2#include <stdio.h>
3#include <fcntl.h>
4#include <sys/mman.h>
5#include <sys/stat.h>
6#include <unistd.h>
7#include <raylib.h>
8
9#define FILENAME_INPUT "./res/spotlight.frag"
10#define FILENAME_OUTPUT "./build/spotlight.frag.c"
11
12int
13main (int argc, char *argv[])
14{
15 remove (FILENAME_OUTPUT);
16
17 int fd = open (FILENAME_INPUT, O_RDONLY);
18 if (fd == -1) {
19 perror ("open");
20 return EXIT_FAILURE;
21 }
22
23 struct stat sb;
24 if (fstat (fd, &sb) == -1) {
25 perror ("fstat");
26 close (fd);
27 return EXIT_FAILURE;
28 }
29
30 void *mapped = mmap (NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
31 if (mapped == MAP_FAILED) {
32 perror ("mmap");
33 close (fd);
34 return EXIT_FAILURE;
35 }
36
37 if (!ExportDataAsCode (mapped, sb.st_size, FILENAME_OUTPUT)) {
38 perror ("raylib");
39 close (fd);
40 return EXIT_FAILURE;
41 }
42
43 if (munmap (mapped, sb.st_size) == -1)
44 perror ("munmap");
45 close (fd);
46
47 if (chmod (FILENAME_OUTPUT, S_IRUSR | S_IRGRP | S_IROTH) == -1) {
48 perror ("chmod");
49 return EXIT_FAILURE;
50 }
51
52 return EXIT_SUCCESS;
53}
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 @@
1#include <stdlib.h>
2#include <stdio.h>
3#include <unistd.h>
4#include <sys/wait.h>
5#include <raylib.h>
6#include <raymath.h>
7#include "spotlight.frag.c"
8
9#define KEY_MOD_ROTATION KEY_LEFT_ALT
10#define KEY_MOD_RADIUS KEY_CAPS_LOCK
11
12// NOTE(cmmm): GetMousePosition returns actual value only after
13// certain amount of frames
14#define STARTUP_FRAME_COUNT 3
15
16#define SPOTLIGHT_SPEED_MAX 2000
17
18#define SPOTLIGHT_TARGET_INITIAL_ZOOM 1
19#define SPOTLIGHT_TARGET_INITIAL_ROTATION 0
20#define SPOTLIGHT_TARGET_INITIAL_FOCUSED false
21
22#define SPOTLIGHT_ROTATION_MULTIPLIER 45
23#define SPOTLIGHT_ROTATION_SPEED_MAX 180
24
25#define SPOTLIGHT_ZOOM_SPEED_MAX 4
26#define SPOTLIGHT_ZOOM_MIN 1
27#define SPOTLIGHT_ZOOM_MAX 64
28
29#define SPOTLIGHT_RADIUS_MULTIPLIER 50
30#define SPOTLIGHT_RADIUS_SPEED_MAX 10
31#define SPOTLIGHT_RADIUS_MIN 100
32
33#define SPOTLIGHT_DIMNESS_MIN .25
34#define SPOTLIGHT_DIMNESS_MAX .75
35
36#define SPOTLIGHT_FOCUS_SPEED_MAX 2000
37
38#define END_DURATION (.5)
39
40#define EXECLP(P, ...) execlp ((P), (P), __VA_ARGS__, NULL)
41
42static Texture2D screenshot;
43
44
45static Vector2 translation_target = {0};
46static Vector2 spotlight_pos;
47static Vector2 spotlight_pos_initial;
48static float spotlight_radius;
49static bool spotlight_focused = SPOTLIGHT_TARGET_INITIAL_FOCUSED;
50static float spotlight_target_zoom = SPOTLIGHT_TARGET_INITIAL_ZOOM;
51static float spotlight_target_rotation = SPOTLIGHT_TARGET_INITIAL_ROTATION;
52static float spotlight_target_radius;
53static Vector2 spotlight_target_pos;
54static float spotlight_radius_unfocused;
55static float spotlight_radius_initial_focused;
56
57static Shader shader;
58static int shader_location_target;
59static int shader_location_dimness;
60static int shader_location_radius;
61
62static Camera2D camera = {
63 .rotation = 0,
64 .zoom = 1,
65};
66
67static Vector2 screen_size = {0};
68static float frame_time;
69
70static void
71update_frame_info (void)
72{
73 frame_time = GetFrameTime ();
74 if (screen_size.x != GetScreenWidth () ||
75 screen_size.y != GetScreenHeight ()) {
76 screen_size.x = GetScreenWidth ();
77 screen_size.y = GetScreenHeight ();
78
79 spotlight_radius_unfocused = Vector2Length (screen_size);
80 spotlight_radius_initial_focused = fminf (screen_size.x, screen_size.y)/2;
81 }
82}
83
84static void
85handle_input (void)
86{
87 spotlight_target_pos = GetMousePosition ();
88
89 if (IsMouseButtonDown (MOUSE_BUTTON_RIGHT))
90 translation_target = Vector2Add (translation_target, GetMouseDelta ());
91
92 float wheel_move = GetMouseWheelMove ();
93 if (wheel_move) {
94 if (IsKeyDown (KEY_MOD_ROTATION)) {
95 spotlight_target_rotation += SPOTLIGHT_ROTATION_MULTIPLIER*wheel_move;
96 } else if (IsKeyDown (KEY_MOD_RADIUS)) {
97 if (!spotlight_focused) {
98 spotlight_focused = true;
99 spotlight_target_radius = spotlight_radius_initial_focused;
100 HideCursor ();
101 }
102
103 spotlight_target_radius += SPOTLIGHT_RADIUS_MULTIPLIER*wheel_move;
104 float spotlight_radius_max = spotlight_radius_unfocused;
105 spotlight_target_radius = Clamp (spotlight_target_radius,
106 SPOTLIGHT_RADIUS_MIN,
107 spotlight_radius_max);
108 } else {
109 float scale_factor = 1 + fabsf (wheel_move)/4;
110 if (!signbit (wheel_move)) scale_factor = 1/scale_factor;
111 spotlight_target_zoom = Clamp (spotlight_target_zoom*scale_factor,
112 SPOTLIGHT_ZOOM_MIN,
113 SPOTLIGHT_ZOOM_MAX);
114 }
115 }
116
117 if (IsKeyPressed (KEY_F)) {
118 spotlight_focused ^= 1;
119 if (spotlight_focused) {
120 spotlight_target_radius = spotlight_radius_initial_focused;
121 HideCursor ();
122 } else
123 ShowCursor ();
124 }
125}
126
127static void
128update (void)
129{
130 spotlight_pos = Vector2MoveTowards (spotlight_pos, spotlight_target_pos,
131 SPOTLIGHT_SPEED_MAX*frame_time);
132
133 Vector2 translation_delta = Vector2MoveTowards (translation_delta, translation_target,
134 SPOTLIGHT_SPEED_MAX*frame_time);
135 translation_target = Vector2Subtract (translation_target, translation_delta);
136 Vector2 delta_target = Vector2Rotate (Vector2Scale (translation_delta,
137 -1/camera.zoom),
138 -camera.rotation*DEG2RAD);
139 camera.target = Vector2Add (camera.target, delta_target);
140
141
142 if (!spotlight_focused) {
143 spotlight_target_radius = spotlight_radius_unfocused;
144 }
145
146 camera.rotation += Clamp (spotlight_target_rotation - camera.rotation,
147 -SPOTLIGHT_ROTATION_SPEED_MAX*frame_time,
148 +SPOTLIGHT_ROTATION_SPEED_MAX*frame_time);
149
150 camera.zoom += Clamp (spotlight_target_zoom - camera.zoom,
151 -SPOTLIGHT_ZOOM_SPEED_MAX*frame_time,
152 +SPOTLIGHT_ZOOM_SPEED_MAX*frame_time);
153 camera.target = GetScreenToWorld2D (spotlight_pos, camera);
154 camera.offset = spotlight_pos;
155
156 spotlight_radius += Clamp (spotlight_target_radius - spotlight_radius,
157 -SPOTLIGHT_FOCUS_SPEED_MAX*frame_time,
158 +SPOTLIGHT_FOCUS_SPEED_MAX*frame_time);
159}
160
161static void
162draw (void)
163{
164 Vector2 target = { spotlight_pos.x, screen_size.y - spotlight_pos.y, };
165 float dimness = Remap (spotlight_radius,
166 spotlight_radius_initial_focused,
167 SPOTLIGHT_RADIUS_MIN,
168 SPOTLIGHT_DIMNESS_MIN,
169 SPOTLIGHT_DIMNESS_MAX);
170 SetShaderValue (shader, shader_location_target, &target, SHADER_UNIFORM_VEC2);
171 SetShaderValue (shader, shader_location_dimness, &dimness, SHADER_UNIFORM_FLOAT);
172 SetShaderValue (shader, shader_location_radius, &spotlight_radius, SHADER_UNIFORM_FLOAT);
173
174 BeginDrawing (); {
175 ClearBackground (BLANK);
176 BeginMode2D (camera); {
177 BeginShaderMode (shader); {
178 DrawTextureV (screenshot, Vector2Zero (), WHITE);
179 } EndShaderMode ();
180 } EndMode2D ();
181 } EndDrawing ();
182}
183
184int
185main (int argc, char *argv[])
186{
187 char tmpname[] = "/tmp/spotlight.png";
188 // {
189 // int tmp_fd;
190 // do tmp_fd = mkstemp (tmpname);
191 // while (tmp_fd == -1);
192 // close (tmp_fd);
193 // }
194
195 {
196 pid_t grim_pid = fork ();
197 if (!grim_pid) EXECLP ("grim", tmpname);
198 int grim_exit_status;
199 waitpid (grim_pid, &grim_exit_status, 0);
200 if (grim_exit_status) {
201 TraceLog (LOG_ERROR, "GRIM: Unable to capture screenshot (%s)", tmpname);
202 exit (EXIT_FAILURE);
203 } TraceLog (LOG_INFO, "GRIM: Screenshot captured (%s)", tmpname);
204 }
205
206 SetConfigFlags (FLAG_FULLSCREEN_MODE |
207 FLAG_MSAA_4X_HINT |
208 FLAG_WINDOW_UNDECORATED |
209 FLAG_VSYNC_HINT);
210 InitWindow (0, 0, NULL);
211 SetMouseCursor (MOUSE_CURSOR_CROSSHAIR);
212 screenshot = LoadTexture (tmpname);
213
214 // shader = LoadShader (NULL, "./spotlight.frag");
215 shader = LoadShaderFromMemory (NULL, (void *) SPOTLIGHT_FRAG_DATA);
216 shader_location_target = GetShaderLocation (shader, "target");
217 shader_location_dimness = GetShaderLocation (shader, "dimness");
218 shader_location_radius = GetShaderLocation (shader, "radius");
219
220 spotlight_radius =
221 Vector2Length ((Vector2) { GetScreenWidth (), GetScreenHeight () });
222 for (int i = 0; i < STARTUP_FRAME_COUNT; ++i) draw ();
223 spotlight_pos = spotlight_pos_initial = GetMousePosition ();
224 camera.target = GetScreenToWorld2D (spotlight_pos, camera);
225 camera.offset = spotlight_pos;
226 while (!WindowShouldClose ()) {
227 update_frame_info ();
228 handle_input ();
229 update ();
230 draw ();
231 }
232
233 Vector2 camera_target = GetMousePosition ();
234 Camera2D start_camera = camera;
235 float start_spotlight_radius = spotlight_radius;
236 float distances[5] = {
237 Remap (Vector2Distance (start_camera.target, camera_target), 0, Vector2Length (screen_size), 0, 1),
238 Remap (Vector2Distance (start_camera.offset, camera_target), 0, Vector2Length (screen_size), 0, 1),
239 start_camera.rotation/SPOTLIGHT_ROTATION_MULTIPLIER,
240 Remap (start_camera.zoom, SPOTLIGHT_ZOOM_MIN, SPOTLIGHT_ZOOM_MAX, 0, 1),
241 Remap (start_spotlight_radius - spotlight_radius_unfocused, 0, spotlight_radius_unfocused - SPOTLIGHT_RADIUS_MIN, 0, 1),
242 };
243 float max_distance = 0;
244 for (int i = 0; i < 5; ++i) {
245 if (distances[i] > max_distance)
246 max_distance = distances[i];
247 }
248
249 if (max_distance) {
250 float end_speed = 4/(1 + fminf ( max_distance, 1));
251 for (float progress = end_speed*frame_time; progress < 1
252 ; progress += end_speed*frame_time) {
253 update_frame_info ();
254
255 Vector2 spotlight_pos_world = GetScreenToWorld2D (spotlight_pos, camera);
256 camera.target = Vector2Lerp (start_camera.target, camera_target, progress);
257 camera.offset = Vector2Lerp (start_camera.offset, camera_target, progress);
258 camera.rotation = Lerp (start_camera.rotation, 0, progress);
259 camera.zoom = Lerp (start_camera.zoom, 1, progress);
260 spotlight_radius = Lerp (start_spotlight_radius, spotlight_radius_unfocused, progress);
261 spotlight_pos = GetWorldToScreen2D (spotlight_pos_world, camera);
262
263 draw ();
264 }
265 }
266
267 UnloadTexture (screenshot);
268 CloseWindow ();
269
270 return EXIT_SUCCESS;
271}