1 /* xscreensaver, Copyright (c) 1999-2019 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  * Draws a grid of hexagons or other shapes and drops them out.
12  * Created 8-Jul-2013.
13  */
14 
15 #include <math.h>
16 #include "screenhack.h"
17 
18 #define countof(x) (sizeof(x)/sizeof(*(x)))
19 #define ABS(x) ((x)<0?-(x):(x))
20 
21 /* Avoid rounding errors by using a larger fixed-point grid.
22    Without this, we got little pointy errors at some corners. */
23 #define SCALE 10
24 
25 typedef struct {
26   int sides;
27   int cx, cy;
28   double th, radius, i, speed;
29   int colors[2];
30   Bool initted_p;
31 } cell;
32 
33 typedef struct {
34   Display *dpy;
35   Window window;
36   XWindowAttributes xgwa;
37 
38   int ncells, cells_size, gw, gh;
39   cell *cells;
40 
41   int delay;
42   double speed;
43   int sides;
44   Bool lockstep_p;
45   Bool uniform_p;
46   Bool initted_p;
47 
48   int ncolors;
49   XColor *colors;
50   GC gc;
51 
52 } state;
53 
54 
55 static void
make_cells(state * st)56 make_cells (state *st)
57 {
58   int grid_size = get_integer_resource (st->dpy, "size", "Size");
59   cell *cells2;
60   int size, r, gw, gh, x, y, i;
61   double th = 0;
62 
63   if (grid_size < 5) grid_size = 5;
64 
65   size = ((st->xgwa.width > st->xgwa.height
66            ? st->xgwa.width : st->xgwa.height)
67           / grid_size);
68   gw = st->xgwa.width  / size;
69   gh = st->xgwa.height / size;
70 
71   switch (st->sides) {
72   case 8:
73     r  = size * 0.75;
74     th = M_PI / st->sides;
75     gw *= 1.25;
76     gh *= 1.25;
77     break;
78   case 6:
79     r  = size / sqrt(3);
80     th = M_PI / st->sides;
81     gh *= 1.2;
82     break;
83   case 3:
84     size *= 2;
85     r  = size / sqrt(3);
86     th = M_PI / st->sides / 2;
87     break;
88   case 4:
89     size /= 2;
90     r  = size * sqrt (2);
91     th = M_PI / st->sides;
92     break;
93   default:
94     abort();
95     break;
96   }
97 
98   gw += 3;	/* leave a few extra columns off screen just in case */
99   gh += 3;
100 
101   st->ncells = gw * gh;
102 
103   if (st->initted_p && !st->cells) abort();
104   if (!st->initted_p && st->cells) abort();
105 
106   cells2 = (cell *) calloc (st->ncells, sizeof(*cells2));
107   if (! cells2) abort();
108 
109   if (st->cells)
110     {
111       for (y = 0; y < (st->gh < gh ? st->gh : gh); y++)
112         for (x = 0; x < (st->gw < gw ? st->gw : gw); x++)
113           cells2[y * gw + x] = st->cells [y * st->gw + x];
114       free (st->cells);
115       st->cells = 0;
116     }
117 
118   st->cells = cells2;
119   st->gw = gw;
120   st->gh = gh;
121 
122   i = 0;
123   for (y = 0; y < gh; y++)
124     for (x = 0; x < gw; x++)
125       {
126         cell *c = &st->cells[i];
127         c->sides = st->sides;
128         c->radius = SCALE * r;
129         c->th = th;
130 
131         switch (st->sides) {
132         case 8:
133           if (x & 1)
134             {
135               c->cx = SCALE * x * size;
136               c->radius /= 2;
137               c->th = M_PI / 4;
138               c->sides = 4;
139               c->radius *= 1.1;
140             }
141           else
142             {
143               c->cx = SCALE * x * size;
144               c->radius *= 1.02;
145               c->radius--;
146             }
147 
148           if (y & 1)
149             c->cx -= SCALE * size;
150 
151           c->cy = SCALE * y * size;
152 
153          break;
154         case 6:
155           c->cx = SCALE * x * size;
156           c->cy = SCALE * y * size * sqrt(3)/2;
157           if (y & 1)
158             c->cx -= SCALE * size * 0.5;
159           break;
160         case 4:
161           c->cx = SCALE * x * size * 2;
162           c->cy = SCALE * y * size * 2;
163           break;
164         case 3:
165           c->cx = SCALE * x * size * 0.5;
166           c->cy = SCALE * y * size * sqrt(3)/2;
167           if ((x & 1) ^ (y & 1))
168             {
169               c->th = th + M_PI;
170               c->cy -= SCALE * r * 0.5;
171             }
172           break;
173         default:
174           abort();
175         }
176 
177         if (! c->initted_p)
178           {
179             c->speed = st->speed * (st->uniform_p ? 1 : (0.1 + frand(0.9)));
180             c->i = st->lockstep_p ? 0 : random() % r;
181             c->colors[0] = (st->lockstep_p ? 0 : random() % st->ncolors);
182             c->colors[1] = 0;
183             c->initted_p = True;
184           }
185 
186         c->radius += SCALE;  /* Avoid single-pixel erase rounding errors */
187 
188         if (c->i > c->radius) c->i = c->radius;
189         if (c->colors[0] >= st->ncolors) c->colors[0] = st->ncolors-1;
190         if (c->colors[1] >= st->ncolors) c->colors[1] = st->ncolors-1;
191 
192         i++;
193       }
194 
195   st->initted_p = True;
196 }
197 
198 
199 static void
draw_cell(state * st,cell * c)200 draw_cell (state *st, cell *c)
201 {
202   XPoint points[20];
203   int i, j;
204   for (j = 0; j <= 1; j++)
205     {
206       int r = (j == 0 ? c->radius : c->i);
207       for (i = 0; i < c->sides; i++)
208         {
209           double th = i * M_PI * 2 / c->sides;
210           points[i].x = (c->cx + r * cos (th + c->th) + 0.5) / SCALE;
211           points[i].y = (c->cy + r * sin (th + c->th) + 0.5) / SCALE;
212         }
213       XSetForeground (st->dpy, st->gc, st->colors[c->colors[j]].pixel);
214       XFillPolygon (st->dpy, st->window, st->gc, points, c->sides,
215                     Convex, CoordModeOrigin);
216     }
217 
218   c->i -= SCALE * c->speed;
219   if (c->i < 0)
220     {
221       c->i = c->radius;
222       c->colors[1] = c->colors[0];
223       if (c != &st->cells[0])
224         c->colors[0] = st->cells[0].colors[0];
225       else
226         c->colors[0] = random() % st->ncolors;
227     }
228 }
229 
230 
231 static void
hexadrop_init_1(Display * dpy,Window window,state * st)232 hexadrop_init_1 (Display *dpy, Window window, state *st)
233 {
234   XGCValues gcv;
235   char *s1, *s2;
236 
237   st->dpy = dpy;
238   st->window = window;
239   st->delay = get_integer_resource (st->dpy, "delay", "Integer");
240   st->ncolors = get_integer_resource (st->dpy, "ncolors", "Integer");
241   st->speed = get_float_resource (st->dpy, "speed", "Speed");
242   if (st->speed < 0) st->speed = 0;
243 
244   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
245 
246   if (st->ncolors < 2) st->ncolors = 2;
247 
248   st->colors = (XColor *) calloc (sizeof(*st->colors), st->ncolors);
249 
250   if (st->ncolors < 10)
251     make_random_colormap (st->xgwa.screen, st->xgwa.visual, st->xgwa.colormap,
252                           st->colors, &st->ncolors, False, True, 0, True);
253   else
254     make_smooth_colormap (st->xgwa.screen, st->xgwa.visual, st->xgwa.colormap,
255                           st->colors, &st->ncolors, True, 0, True);
256   XSetWindowBackground (dpy, window, st->colors[0].pixel);
257 
258   s1 = get_string_resource (st->dpy, "uniform", "Uniform");
259   s2 = get_string_resource (st->dpy, "lockstep", "Lockstep");
260 
261   if ((!s1 || !*s1 || !strcasecmp(s1, "maybe")) &&
262       (!s2 || !*s2 || !strcasecmp(s2, "maybe")))
263     {
264       /* When being random, don't do both. */
265       st->uniform_p = random() & 1;
266       st->lockstep_p = st->uniform_p ? 0 : random() & 1;
267     }
268   else
269     {
270       if (!s1 || !*s1 || !strcasecmp(s1, "maybe"))
271         st->uniform_p = random() & 1;
272       else
273         st->uniform_p = get_boolean_resource (st->dpy, "uniform", "Uniform");
274 
275       if (!s2 || !*s2 || !strcasecmp(s2, "maybe"))
276         st->lockstep_p = random() & 1;
277       else
278         st->lockstep_p = get_boolean_resource (st->dpy, "lockstep","Lockstep");
279     }
280   if (s1) free (s1);
281   if (s2) free (s2);
282 
283   st->sides = get_integer_resource (st->dpy, "sides", "Sides");
284   if (! (st->sides == 0 || st->sides == 3 || st->sides == 4 ||
285          st->sides == 6 || st->sides == 8))
286     {
287       printf ("%s: invalid number of sides: %d\n", progname, st->sides);
288       st->sides = 0;
289     }
290 
291   if (! st->sides)
292     {
293       static int defs[] = { 3, 3, 3,
294                             4,
295                             6, 6, 6, 6,
296                             8, 8, 8 };
297       st->sides = defs[random() % countof(defs)];
298     }
299 
300   make_cells (st);
301   gcv.foreground = st->colors[0].pixel;
302   st->gc = XCreateGC (dpy, window, GCForeground, &gcv);
303 }
304 
305 
306 static void *
hexadrop_init(Display * dpy,Window window)307 hexadrop_init (Display *dpy, Window window)
308 {
309   state *st = (state *) calloc (1, sizeof(*st));
310   hexadrop_init_1 (dpy, window, st);
311   return st;
312 }
313 
314 
315 
316 static unsigned long
hexadrop_draw(Display * dpy,Window window,void * closure)317 hexadrop_draw (Display *dpy, Window window, void *closure)
318 {
319   state *st = (state *) closure;
320   int i;
321 
322   for (i = 0; i < st->ncells; i++)
323     draw_cell (st, &st->cells[i]);
324 
325   return st->delay;
326 }
327 
328 
329 static void
hexadrop_reshape(Display * dpy,Window window,void * closure,unsigned int w,unsigned int h)330 hexadrop_reshape (Display *dpy, Window window, void *closure,
331                  unsigned int w, unsigned int h)
332 {
333   state *st = (state *) closure;
334   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
335   make_cells (st);
336 }
337 
338 
339 static void
hexadrop_free_1(Display * dpy,Window window,void * closure)340 hexadrop_free_1 (Display *dpy, Window window, void *closure)
341 {
342   state *st = (state *) closure;
343   if (st->colors)
344     {
345       free_colors (st->xgwa.screen, st->xgwa.colormap, st->colors, st->ncolors);
346       free (st->colors);
347       st->colors = 0;
348     }
349   if (st->cells)
350     {
351       free (st->cells);
352       st->cells = 0;
353     }
354   if (st->gc)
355     {
356       XFreeGC (st->dpy, st->gc);
357       st->gc = 0;
358     }
359 
360   memset (st, 0, sizeof(*st));
361 }
362 
363 
364 static void
hexadrop_free(Display * dpy,Window window,void * closure)365 hexadrop_free (Display *dpy, Window window, void *closure)
366 {
367   hexadrop_free_1 (dpy, window, closure);
368   free (closure);
369 }
370 
371 
372 static Bool
hexadrop_event(Display * dpy,Window window,void * closure,XEvent * event)373 hexadrop_event (Display *dpy, Window window, void *closure, XEvent *event)
374 {
375   state *st = (state *) closure;
376 
377   if (screenhack_event_helper (dpy, window, event))
378     {
379       if (random() % 5)		/* Change everything */
380         {
381           hexadrop_free_1 (st->dpy, st->window, st);
382           hexadrop_init_1 (dpy, window, st);
383         }
384       else			/* Change colors only */
385         {
386           /* Save the old geometry */
387           cell *c = st->cells;
388           int n = st->ncells;
389           int s = st->sides;
390           int i;
391 
392           /* Protect it from being freed */
393           st->cells = 0;
394           hexadrop_free_1 (st->dpy, st->window, st);
395           hexadrop_init_1 (dpy, window, st);
396 
397           /* Reset the old cells */
398           for (i = 0; i < n; i++)
399             c[i].initted_p = False;
400 
401           /* Re-init, then put them back. */
402           free (st->cells);
403           st->cells = c;
404           st->ncells = n;
405           st->sides = s;
406         }
407 
408       return True;
409     }
410 
411   return False;
412 }
413 
414 
415 static const char *hexadrop_defaults [] = {
416   ".background:		black",
417   ".foreground:		white",
418   "*fpsSolid:		true",
419   "*delay:		30000",
420   "*sides:		0",
421   "*size:		15",
422   "*speed:		1.0",
423   "*ncolors:		128",
424   "*uniform:		Maybe",
425   "*lockstep:		Maybe",
426 #ifdef HAVE_MOBILE
427   "*ignoreRotation:     True",
428 #endif
429   0
430 };
431 
432 static XrmOptionDescRec hexadrop_options [] = {
433   { "-delay",		".delay",	XrmoptionSepArg, 0 },
434   { "-sides",		".sides",	XrmoptionSepArg, 0 },
435   { "-size",		".size",	XrmoptionSepArg, 0 },
436   { "-speed",		".speed",	XrmoptionSepArg, 0 },
437   { "-ncolors",		".ncolors",	XrmoptionSepArg, 0 },
438   { "-uniform-speed",	".uniform",	XrmoptionNoArg, "True"  },
439   { "-no-uniform-speed",".uniform",	XrmoptionNoArg, "False" },
440   { "-nonuniform-speed",".uniform",	XrmoptionNoArg, "False" },
441   { "-lockstep",	".lockstep",	XrmoptionNoArg, "True"  },
442   { "-no-lockstep",	".lockstep",	XrmoptionNoArg, "False" },
443   { 0, 0, 0, 0 }
444 };
445 
446 XSCREENSAVER_MODULE ("Hexadrop", hexadrop)
447