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