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