summaryrefslogtreecommitdiff
path: root/src/spotlight.c
blob: ae45a93551920042ea38ae04ae7628ab651061ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <raylib.h>
#include <raymath.h>
#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;
}