1 /* voronoi, Copyright (c) 2007-2018 Jamie Zawinski <jwz@jwz.org>
2 *
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
9 * implied warranty.
10 */
11
12 #define DEFAULTS "*delay: 20000 \n" \
13 "*showFPS: False \n" \
14 "*suppressRotationAnimation: True\n" \
15
16 # define release_voronoi 0
17 #undef countof
18 #define countof(x) (sizeof((x))/sizeof((*x)))
19
20 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
21
22
23 #include "xlockmore.h"
24 #include <ctype.h>
25
26 #ifdef USE_GL /* whole file */
27
28 #define DEF_POINTS "25"
29 #define DEF_POINT_SIZE "9"
30 #define DEF_POINT_SPEED "1.0"
31 #define DEF_POINT_DELAY "0.05"
32 #define DEF_ZOOM_SPEED "1.0"
33 #define DEF_ZOOM_DELAY "15"
34
35 typedef struct node {
36 GLfloat x, y;
37 GLfloat dx, dy;
38 GLfloat ddx, ddy;
39 struct node *next;
40 GLfloat color[4], color2[4];
41 int rot;
42 } node;
43
44 typedef struct {
45 GLXContext *glx_context;
46 node *nodes;
47 int nnodes;
48 node *dragging;
49 int ncolors;
50 XColor *colors;
51 int point_size;
52
53 enum { MODE_WAITING, MODE_ADDING, MODE_ZOOMING } mode;
54 int adding;
55 double last_time;
56
57 GLfloat zooming; /* 1.0 starting zoom, 0.0 no longer zooming. */
58 GLfloat zoom_toward[2];
59
60 } voronoi_configuration;
61
62 static voronoi_configuration *vps = NULL;
63
64 /* command line arguments */
65 static int npoints;
66 static GLfloat point_size, point_speed, point_delay;
67 static GLfloat zoom_speed, zoom_delay;
68
69 static XrmOptionDescRec opts[] = {
70 { "-points", ".points", XrmoptionSepArg, 0 },
71 { "-point-size", ".pointSize", XrmoptionSepArg, 0 },
72 { "-point-speed", ".pointSpeed", XrmoptionSepArg, 0 },
73 { "-point-delay", ".pointDelay", XrmoptionSepArg, 0 },
74 { "-zoom-speed", ".zoomSpeed", XrmoptionSepArg, 0 },
75 { "-zoom-delay", ".zoomDelay", XrmoptionSepArg, 0 },
76 };
77
78 static argtype vars[] = {
79 {&npoints, "points", "Points", DEF_POINTS, t_Int},
80 {&point_size, "pointSize", "PointSize", DEF_POINT_SIZE, t_Float},
81 {&point_speed, "pointSpeed", "PointSpeed", DEF_POINT_SPEED, t_Float},
82 {&point_delay, "pointDelay", "PointDelay", DEF_POINT_DELAY, t_Float},
83 {&zoom_speed, "zoomSpeed", "ZoomSpeed", DEF_ZOOM_SPEED, t_Float},
84 {&zoom_delay, "zoomDelay", "ZoomDelay", DEF_ZOOM_DELAY, t_Float},
85 };
86
87 ENTRYPOINT ModeSpecOpt voronoi_opts =
88 {countof(opts), opts, countof(vars), vars, NULL};
89
90
91 /* Returns the current time in seconds as a double.
92 */
93 static double
double_time(void)94 double_time (void)
95 {
96 struct timeval now;
97 # ifdef GETTIMEOFDAY_TWO_ARGS
98 struct timezone tzp;
99 gettimeofday(&now, &tzp);
100 # else
101 gettimeofday(&now);
102 # endif
103
104 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
105 }
106
107
108 static node *
add_node(voronoi_configuration * vp,GLfloat x,GLfloat y)109 add_node (voronoi_configuration *vp, GLfloat x, GLfloat y)
110 {
111 node *nn = (node *) calloc (1, sizeof (*nn));
112 int i;
113 nn->x = x;
114 nn->y = y;
115
116 i = random() % vp->ncolors;
117 nn->color[0] = vp->colors[i].red / 65536.0;
118 nn->color[1] = vp->colors[i].green / 65536.0;
119 nn->color[2] = vp->colors[i].blue / 65536.0;
120 nn->color[3] = 1.0;
121
122 nn->color2[0] = nn->color[0] * 0.7;
123 nn->color2[1] = nn->color[1] * 0.7;
124 nn->color2[2] = nn->color[2] * 0.7;
125 nn->color2[3] = 1.0;
126
127 nn->ddx = frand (0.000001 * point_speed) * (random() & 1 ? 1 : -1);
128 nn->ddy = frand (0.000001 * point_speed) * (random() & 1 ? 1 : -1);
129
130 nn->rot = (random() % 360) * (random() & 1 ? 1 : -1);
131
132 nn->next = vp->nodes;
133 vp->nodes = nn;
134 vp->nnodes++;
135 return nn;
136 }
137
138
139 static int
cone(void)140 cone (void)
141 {
142 int i;
143 int faces = 64;
144 GLfloat step = M_PI * 2 / faces;
145 GLfloat th = 0;
146 GLfloat x = 1;
147 GLfloat y = 0;
148
149 glBegin(GL_TRIANGLE_FAN);
150 glVertex3f (0, 0, 1);
151 for (i = 0; i < faces; i++)
152 {
153 glVertex3f (x, y, 0);
154 th += step;
155 x = cos (th);
156 y = sin (th);
157 }
158 glVertex3f (1, 0, 0);
159 glEnd();
160 return faces;
161 }
162
163
164 static void
move_points(voronoi_configuration * vp)165 move_points (voronoi_configuration *vp)
166 {
167 node *nn;
168 for (nn = vp->nodes; nn; nn = nn->next)
169 {
170 if (nn == vp->dragging) continue;
171 nn->x += nn->dx;
172 nn->y += nn->dy;
173
174 if (vp->mode == MODE_WAITING)
175 {
176 nn->dx += nn->ddx;
177 nn->dy += nn->ddy;
178 }
179 }
180 }
181
182
183 static void
prune_points(voronoi_configuration * vp)184 prune_points (voronoi_configuration *vp)
185 {
186 node *nn;
187 node *prev = 0;
188 int lim = 5;
189
190 for (nn = vp->nodes; nn; prev = nn, nn = (nn ? nn->next : 0))
191 if (nn->x < -lim || nn->x > lim ||
192 nn->y < -lim || nn->y > lim)
193 {
194 if (prev)
195 prev->next = nn->next;
196 else
197 vp->nodes = nn->next;
198 free (nn);
199 vp->nnodes--;
200 nn = prev;
201 }
202 }
203
204
205 static void
zoom_points(voronoi_configuration * vp)206 zoom_points (voronoi_configuration *vp)
207 {
208 node *nn;
209
210 GLfloat tick = sin (vp->zooming * M_PI);
211 GLfloat scale = 1 + (tick * 0.02 * zoom_speed);
212
213 vp->zooming -= (0.01 * zoom_speed);
214 if (vp->zooming < 0) vp->zooming = 0;
215
216 if (vp->zooming <= 0) return;
217
218 if (scale < 1) scale = 1;
219
220 for (nn = vp->nodes; nn; nn = nn->next)
221 {
222 GLfloat x = nn->x - vp->zoom_toward[0];
223 GLfloat y = nn->y - vp->zoom_toward[1];
224 x *= scale;
225 y *= scale;
226 nn->x = x + vp->zoom_toward[0];
227 nn->y = y + vp->zoom_toward[1];
228 }
229 }
230
231
232
233 static void
draw_cells(ModeInfo * mi)234 draw_cells (ModeInfo *mi)
235 {
236 voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
237 node *nn;
238 int lim = 5;
239
240 for (nn = vp->nodes; nn; nn = nn->next)
241 {
242 if (nn->x < -lim || nn->x > lim ||
243 nn->y < -lim || nn->y > lim)
244 continue;
245
246 glPushMatrix();
247 glTranslatef (nn->x, nn->y, 0);
248 glScalef (lim*2, lim*2, 1);
249 glColor4fv (nn->color);
250 mi->polygon_count += cone ();
251 glPopMatrix();
252 }
253
254 glClear (GL_DEPTH_BUFFER_BIT);
255
256 if (vp->point_size <= 0)
257 ;
258 else if (vp->point_size < 3)
259 {
260 glPointSize (vp->point_size);
261 for (nn = vp->nodes; nn; nn = nn->next)
262 {
263 glBegin (GL_POINTS);
264 glColor4fv (nn->color2);
265 glVertex2f (nn->x, nn->y);
266 glEnd();
267 mi->polygon_count++;
268 }
269 }
270 else
271 {
272 for (nn = vp->nodes; nn; nn = nn->next)
273 {
274 int w = MI_WIDTH (mi);
275 int h = MI_HEIGHT (mi);
276 int s = vp->point_size;
277 int i;
278
279 glColor4fv (nn->color2);
280 glPushMatrix();
281 glTranslatef (nn->x, nn->y, 0);
282 glScalef (1.0 / w * s, 1.0 / h * s, 1);
283
284 glLineWidth (vp->point_size / 10);
285 nn->rot += (nn->rot < 0 ? -1 : 1);
286 glRotatef (nn->rot, 0, 0, 1);
287
288 glRotatef (180, 0, 0, 1);
289 for (i = 0; i < 5; i++)
290 {
291 glBegin (GL_TRIANGLES);
292 glVertex2f (0, 1);
293 glVertex2f (-0.2, 0);
294 glVertex2f ( 0.2, 0);
295 glEnd ();
296 glRotatef (360.0/5, 0, 0, 1);
297 mi->polygon_count++;
298 }
299 glPopMatrix();
300 }
301 }
302
303 #if 0
304 glPushMatrix();
305 glColor3f(1,1,1);
306 glBegin(GL_LINE_LOOP);
307 glVertex3f(0,0,0);
308 glVertex3f(1,0,0);
309 glVertex3f(1,1,0);
310 glVertex3f(0,1,0);
311 glEnd();
312 glScalef(0.25, 0.25, 1);
313 glBegin(GL_LINE_LOOP);
314 glVertex3f(0,0,0);
315 glVertex3f(1,0,0);
316 glVertex3f(1,1,0);
317 glVertex3f(0,1,0);
318 glEnd();
319 glPopMatrix();
320 #endif
321 }
322
323
324 /* Window management, etc
325 */
326 ENTRYPOINT void
reshape_voronoi(ModeInfo * mi,int width,int height)327 reshape_voronoi (ModeInfo *mi, int width, int height)
328 {
329 /* voronoi_configuration *vp = &vps[MI_SCREEN(mi)];*/
330
331 glViewport (0, 0, (GLint) width, (GLint) height);
332
333 glMatrixMode(GL_PROJECTION);
334 glLoadIdentity();
335 glOrtho (0, 1, 1, 0, -1, 1);
336
337 # ifdef HAVE_MOBILE /* So much WTF */
338 {
339 int rot = current_device_rotation();
340
341 glTranslatef (0.5, 0.5, 0);
342 // glScalef(0.19, 0.19, 0.19);
343
344 if (rot == 180 || rot == -180) {
345 glTranslatef (1, 1, 0);
346 } else if (rot == 90 || rot == -270) {
347 glRotatef (180, 0, 0, 1);
348 glTranslatef (0, 1, 0);
349 } else if (rot == -90 || rot == 270) {
350 glRotatef (180, 0, 0, 1);
351 glTranslatef (1, 0, 0);
352 }
353
354 glTranslatef(-0.5, -0.5, 0);
355 }
356 # endif
357
358 glMatrixMode(GL_MODELVIEW);
359 glLoadIdentity();
360
361 glClear(GL_COLOR_BUFFER_BIT);
362 }
363
364
365 static node *
find_node(ModeInfo * mi,GLfloat x,GLfloat y)366 find_node (ModeInfo *mi, GLfloat x, GLfloat y)
367 {
368 voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
369 int ps = (vp->point_size < 5 ? 5 : vp->point_size);
370 GLfloat hysteresis = (1.0 / MI_WIDTH (mi)) * ps;
371 node *nn;
372 for (nn = vp->nodes; nn; nn = nn->next)
373 if (nn->x > x - hysteresis && nn->x < x + hysteresis &&
374 nn->y > y - hysteresis && nn->y < y + hysteresis)
375 return nn;
376 return 0;
377 }
378
379
380 ENTRYPOINT Bool
voronoi_handle_event(ModeInfo * mi,XEvent * event)381 voronoi_handle_event (ModeInfo *mi, XEvent *event)
382 {
383 voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
384
385 if (event->xany.type == ButtonPress)
386 {
387 GLfloat x = (GLfloat) event->xbutton.x / MI_WIDTH (mi);
388 GLfloat y = (GLfloat) event->xbutton.y / MI_HEIGHT (mi);
389 node *nn = find_node (mi, x, y);
390 if (!nn)
391 nn = add_node (vp, x, y);
392 vp->dragging = nn;
393
394 return True;
395 }
396 else if (event->xany.type == ButtonRelease && vp->dragging)
397 {
398 vp->dragging = 0;
399 return True;
400 }
401 else if (event->xany.type == MotionNotify && vp->dragging)
402 {
403 vp->dragging->x = (GLfloat) event->xmotion.x / MI_WIDTH (mi);
404 vp->dragging->y = (GLfloat) event->xmotion.y / MI_HEIGHT (mi);
405 return True;
406 }
407
408 return False;
409 }
410
411 static void
state_change(ModeInfo * mi)412 state_change (ModeInfo *mi)
413 {
414 voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
415 double now = double_time();
416
417 if (vp->dragging)
418 {
419 vp->last_time = now;
420 vp->adding = 0;
421 vp->zooming = 0;
422 return;
423 }
424
425 switch (vp->mode)
426 {
427 case MODE_WAITING:
428 if (vp->last_time + zoom_delay <= now)
429 {
430 node *tn = vp->nodes;
431 vp->zoom_toward[0] = (tn ? tn->x : 0.5);
432 vp->zoom_toward[1] = (tn ? tn->y : 0.5);
433
434 vp->mode = MODE_ZOOMING;
435 vp->zooming = 1;
436
437 vp->last_time = now;
438 }
439 break;
440
441 case MODE_ADDING:
442 if (vp->last_time + point_delay <= now)
443 {
444 add_node (vp,
445 BELLRAND(0.5) + 0.25,
446 BELLRAND(0.5) + 0.25);
447 vp->last_time = now;
448 vp->adding--;
449 if (vp->adding <= 0)
450 {
451 vp->adding = 0;
452 vp->mode = MODE_WAITING;
453 vp->last_time = now;
454 }
455 }
456 break;
457
458 case MODE_ZOOMING:
459 {
460 zoom_points (vp);
461 if (vp->zooming <= 0)
462 {
463 vp->mode = MODE_ADDING;
464 vp->adding = npoints;
465 vp->last_time = now;
466 }
467 }
468 break;
469
470 default:
471 abort();
472 }
473 }
474
475
476 ENTRYPOINT void
init_voronoi(ModeInfo * mi)477 init_voronoi (ModeInfo *mi)
478 {
479 voronoi_configuration *vp;
480
481 MI_INIT (mi, vps);
482
483 vp = &vps[MI_SCREEN(mi)];
484
485 vp->glx_context = init_GL(mi);
486
487 vp->point_size = point_size;
488 if (vp->point_size < 0) vp->point_size = 10;
489
490 if (MI_WIDTH(mi) > 2560) vp->point_size *= 2; /* Retina displays */
491
492 vp->ncolors = 128;
493 vp->colors = (XColor *) calloc (vp->ncolors, sizeof(XColor));
494 make_smooth_colormap (0, 0, 0,
495 vp->colors, &vp->ncolors,
496 False, False, False);
497
498 reshape_voronoi (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
499
500 vp->mode = MODE_ADDING;
501 vp->adding = npoints * 2;
502 vp->last_time = 0;
503 }
504
505
506 ENTRYPOINT void
draw_voronoi(ModeInfo * mi)507 draw_voronoi (ModeInfo *mi)
508 {
509 voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
510 Display *dpy = MI_DISPLAY(mi);
511 Window window = MI_WINDOW(mi);
512
513 if (!vp->glx_context)
514 return;
515
516 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *vp->glx_context);
517
518 glShadeModel(GL_FLAT);
519 glEnable(GL_POINT_SMOOTH);
520 /* glEnable(GL_LINE_SMOOTH);*/
521 /* glEnable(GL_POLYGON_SMOOTH);*/
522
523 glEnable (GL_DEPTH_TEST);
524 glDepthFunc (GL_LEQUAL);
525
526 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
527
528 mi->polygon_count = 0;
529 draw_cells (mi);
530 move_points (vp);
531 prune_points (vp);
532 state_change (mi);
533
534 if (mi->fps_p) do_fps (mi);
535 glFinish();
536
537 glXSwapBuffers(dpy, window);
538 }
539
540
541 ENTRYPOINT void
free_voronoi(ModeInfo * mi)542 free_voronoi (ModeInfo *mi)
543 {
544 voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
545 node *n;
546 if (!vp->glx_context) return;
547 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *vp->glx_context);
548 if (vp->colors) free (vp->colors);
549 n = vp->nodes;
550 while (n) {
551 node *n2 = n->next;
552 free (n);
553 n = n2;
554 }
555 }
556
557 XSCREENSAVER_MODULE ("Voronoi", voronoi)
558
559 #endif /* USE_GL */
560