bec8ee8f024449c79ead5d8dac89f01af6636c52
[xscreensaver] / hacks / substrate.c
1 /*
2  *  Substrate (dragorn@kismetwireless.net)
3  *  Directly ported code from complexification.net Substrate art
4  *  http://complexification.net/gallery/machines/substrate/applet_s/substrate_s.pde
5  *
6  *  Substrate code:
7  *  j.tarbell   June, 2004
8  *  Albuquerque, New Mexico
9  *  complexification.net
10  *
11  *  CHANGES
12  *
13  *  1.0  dragorn  Oct 10 2004    First port done
14  *
15  * Directly based the hacks of: 
16  * 
17  * xscreensaver, Copyright (c) 1997, 1998, 2002 Jamie Zawinski <jwz@jwz.org>
18  *
19  * Permission to use, copy, modify, distribute, and sell this software and its
20  * documentation for any purpose is hereby granted without fee, provided that
21  * the above copyright notice appear in all copies and that both that
22  * copyright notice and this permission notice appear in supporting
23  * documentation.  No representations are made about the suitability of this
24  * software for any purpose.  It is provided "as is" without express or 
25  * implied warranty.
26  */
27
28 #include "screenhack.h"
29 #include <X11/Xutil.h>
30 #include <stdio.h>
31 #include <sys/time.h>
32
33 #ifndef MAX_WIDTH
34 #include <limits.h>
35 #define MAX_WIDTH SHRT_MAX
36 #endif
37
38 #ifdef TIME_ME
39 #include <time.h>
40 #endif
41
42 #include <math.h>
43
44 /* this program goes faster if some functions are inline.  The following is
45  * borrowed from ifs.c */
46 #if !defined( __GNUC__ ) && !defined(__cplusplus) && !defined(c_plusplus)
47 #undef inline
48 #define inline                  /* */
49 #endif
50
51 /* Raw colormap extracted from pollockEFF.gif */
52 char *rgb_colormap[] = {
53     "rgb:20/1F/21", "rgb:26/2C/2E", "rgb:35/26/26", "rgb:37/2B/27",
54     "rgb:30/2C/2E", "rgb:39/2B/2D", "rgb:32/32/29", "rgb:3F/32/29",
55     "rgb:38/32/2E", "rgb:2E/33/3D", "rgb:33/3A/3D", "rgb:47/33/29",
56     "rgb:40/39/2C", "rgb:40/39/2E", "rgb:47/40/2C", "rgb:47/40/2E",
57     "rgb:4E/40/2C", "rgb:4F/40/2E", "rgb:4E/47/38", "rgb:58/40/37",
58     "rgb:65/47/2D", "rgb:6D/5D/3D", "rgb:74/55/30", "rgb:75/55/32",
59     "rgb:74/5D/32", "rgb:74/64/33", "rgb:7C/6C/36", "rgb:52/31/52",
60     "rgb:44/48/42", "rgb:4C/56/47", "rgb:65/5D/45", "rgb:6D/5D/44",
61     "rgb:6C/5D/4E", "rgb:74/6C/43", "rgb:7C/6C/42", "rgb:7C/6C/4B",
62     "rgb:6B/73/4B", "rgb:73/73/4B", "rgb:7B/7B/4A", "rgb:6B/6C/55",
63     "rgb:69/6D/5E", "rgb:7B/6C/5D", "rgb:6B/73/53", "rgb:6A/74/5D",
64     "rgb:72/7B/52", "rgb:7B/7B/52", "rgb:57/74/6E", "rgb:68/74/66",
65     "rgb:9C/54/2B", "rgb:9D/54/32", "rgb:9D/5B/35", "rgb:93/6B/36",
66     "rgb:AA/73/30", "rgb:C4/5A/27", "rgb:D9/52/23", "rgb:D8/5A/20",
67     "rgb:DB/5A/23", "rgb:E5/70/37", "rgb:83/6C/4B", "rgb:8C/6B/4B",
68     "rgb:82/73/5C", "rgb:93/73/52", "rgb:81/7B/63", "rgb:81/7B/6D",
69     "rgb:92/7B/63", "rgb:D9/89/3B", "rgb:E4/98/32", "rgb:DF/A1/33",
70     "rgb:E5/A0/37", "rgb:F0/AB/3B", "rgb:8A/8A/59", "rgb:B2/9A/58",
71     "rgb:89/82/6B", "rgb:9A/82/62", "rgb:88/8B/7C", "rgb:90/9A/7A",
72     "rgb:A2/82/62", "rgb:A1/8A/69", "rgb:A9/99/68", "rgb:99/A1/60",
73     "rgb:99/A1/68", "rgb:CA/81/48", "rgb:EB/8D/43", "rgb:C2/91/60",
74     "rgb:C2/91/68", "rgb:D1/A9/77", "rgb:C9/B9/7F", "rgb:F0/E2/7B",
75     "rgb:9F/92/8B", "rgb:C0/B9/99", "rgb:E6/B8/8F", "rgb:C8/C1/87",
76     "rgb:E0/C8/86", "rgb:F2/CC/85", "rgb:F5/DA/83", "rgb:EC/DE/9D",
77     "rgb:F5/D2/94", "rgb:F5/DA/94", "rgb:F4/E7/84", "rgb:F4/E1/8A",
78     "rgb:F4/E1/93", "rgb:E7/D8/A7", "rgb:F1/D4/A5", "rgb:F1/DC/A5",
79     "rgb:F4/DB/AD", "rgb:F1/DC/AE", "rgb:F4/DB/B5", "rgb:F5/DB/BD",
80     "rgb:F4/E2/AD", "rgb:F5/E9/AD", "rgb:F4/E3/BE", "rgb:F5/EA/BE",
81     "rgb:F7/F0/B6", "rgb:D9/D1/C1", "rgb:E0/D0/C0", "rgb:E7/D8/C0",
82     "rgb:F1/DD/C6", "rgb:E8/E1/C0", "rgb:F3/ED/C7", "rgb:F6/EC/CE",
83     "rgb:F8/F2/C7", "rgb:EF/EF/D0", 0
84 };
85
86 typedef struct {
87     /* Synthesis of data from Crack:: and SandPainter:: */
88     float x, y;
89     float t;
90
91     unsigned long sandcolor;
92     float sandp, sandg;
93 } crack;
94
95 struct field {
96     unsigned int height;
97     unsigned int width;
98
99     unsigned int initial_cracks;
100     
101     unsigned int num;
102     unsigned int max_num;
103
104     int grains; /* number of grains in the sand painting */
105
106     crack *cracks; /* grid of cracks */
107     int *cgrid; /* grid of actual crack placement */
108
109     /* Raw map of pixels we need to keep for alpha blending */
110     unsigned long int *off_img;
111    
112     /* color parms */
113     int numcolors;
114     unsigned long *parsedcolors;
115     unsigned long fgcolor;
116     unsigned long bgcolor;
117     int visdepth;
118
119     unsigned int cycles;
120
121     unsigned int wireframe;
122 };
123
124 static void 
125 *xrealloc(void *p, size_t size)
126 {
127     void *ret;
128     if ((ret = realloc(p, size)) == NULL) {
129         fprintf(stderr, "%s: out of memory\n", progname);
130         exit(1);
131     }
132     return ret;
133 }
134
135 struct field 
136 *init_field(void)
137 {
138     struct field *f = xrealloc(NULL, sizeof(struct field));
139     f->height = 0;
140     f->width = 0;
141     f->initial_cracks = 0;
142     f->num = 0;
143     f->max_num = 0;
144     f->cracks = NULL;
145     f->cgrid = NULL;
146     f->off_img = NULL;
147     f->numcolors = 0;
148     f->parsedcolors = NULL;
149     f->cycles = 0;
150     f->wireframe = 0;
151     f->fgcolor = 0;
152     f->bgcolor = 0;
153     f->visdepth = 0;
154     f->grains = 0;
155     return f;
156 }
157
158 /* Quick references to pixels in the offscreen map and in the crack grid */
159 #define ref_pixel(f, x, y)   ((f)->off_img[(y) * (f)->width + (x)])
160 #define ref_cgrid(f, x, y)   ((f)->cgrid[(y) * (f)->width + (x)])
161
162 inline void start_crack(struct field *f, crack *cr) {
163     /* synthesis of Crack::findStart() and crack::startCrack() */
164     int px = 0;
165     int py = 0;
166     int found = 0;
167     int timeout = 0;
168     float a;
169
170     /* shift until crack is found */
171     while ((!found) || (timeout++ > 10000)) {
172         px = (int) (random() % f->width);
173         py = (int) (random() % f->height);
174
175         if (ref_cgrid(f, px, py) < 10000)
176             found = 1;
177     }
178
179     if (found) {
180         /* start a crack */
181         a = ref_cgrid(f, px, py);
182
183         if ((random() % 100) < 50) {
184             /* conversion of the java int(random(-2, 2.1)) */
185             a -= 90 + (frand(4.1) - 2.0);
186         } else {
187             a += 90 + (frand(4.1) - 2.0);
188         }
189
190         /* Condensed from Crack::startCrack */
191         cr->x = px + ((float) 0.61 * cos(a * M_PI / 180));
192         cr->y = py + ((float) 0.61 * sin(a * M_PI / 180));
193         cr->t = a;
194     } else {
195         /* timeout */
196     }
197 }
198
199 inline void make_crack(struct field *f) {
200     crack *cr;
201
202     if (f->num < f->max_num) {
203         /* make a new crack */
204         f->cracks = (crack *) xrealloc(f->cracks, sizeof(crack) * (f->num + 1));
205
206         cr = &(f->cracks[f->num]);
207         /* assign colors */
208         cr->sandp = 0;
209         cr->sandg = (frand(0.2) - 0.01);
210         cr->sandcolor = f->parsedcolors[random() % f->numcolors];
211
212         /* start it */
213         start_crack(f, cr);
214
215         f->num++;
216     }
217 }
218
219 inline void point2rgb(int depth, unsigned long c, int *r, int *g, int *b) {
220     switch(depth) {
221         case 32:
222         case 24:
223             *g = (c & 0xff00) >> 8; 
224             *r = (c & 0xff0000) >> 16; 
225             *b = c & 0xff; 
226             break;
227         case 16:
228             *g = ((c >> 5) & 0x3f) << 2;
229             *r = ((c >> 11) & 0x1f) << 3; 
230             *b = (c & 0x1f) << 3; 
231             break;
232         case 15:
233             *g = ((c >> 5) & 0x1f) << 3;
234             *r = ((c >> 10) & 0x1f) << 3;
235             *b = (c & 0x1f) << 3;
236             break;
237     }
238 }
239
240 inline unsigned long rgb2point(int depth, int r, int g, int b) {
241     unsigned long ret = 0;
242
243     switch(depth) {
244         case 32:
245             ret = 0xff000000;
246         case 24:
247             ret |= (r << 16) | (g << 8) | b;
248             break;
249         case 16:
250             ret = ((r>>3) << 11) | ((g>>2)<<5) | (b>>3);
251             break;
252         case 15:
253             ret = ((r>>3) << 10) | ((g>>3)<<5) | (b>>3);
254             break;
255     }
256
257     return ret;
258 }
259
260 /* alpha blended point drawing */
261 inline unsigned long trans_point(int x1, int y1, unsigned long myc, float a, struct field *f) {
262     if ((x1 >= 0) && (x1 < f->width) && (y1 >= 0) && (y1 < f->height)) {
263         if (a >= 1.0) {
264             ref_pixel(f, x1, y1) = myc;
265         } else {
266             int or, og, ob;
267             int r, g, b;
268             int nr, ng, nb;
269             unsigned long c;
270
271             c = ref_pixel(f, x1, y1);
272
273             point2rgb(f->visdepth, c, &or, &og, &ob);
274             point2rgb(f->visdepth, myc, &r, &g, &b);
275
276             nr = or + (r - or) * a;
277             ng = og + (g - og) * a;
278             nb = ob + (b - ob) * a;
279
280             c = rgb2point(f->visdepth, nr, ng, nb);
281
282             ref_pixel(f, x1, y1) = c;
283
284             return c;
285         }
286     }
287
288     return 0;
289 }
290
291 inline void region_color(Display *dpy, Window window, GC fgc, struct field *f, crack *cr) {
292     /* synthesis of Crack::regionColor() and SandPainter::render() */
293
294     float rx = cr->x;
295     float ry = cr->y;
296     int openspace = 1;
297     int cx, cy;
298     float maxg;
299     int grains, i;
300     float w;
301     float drawx, drawy;
302     unsigned long c;
303
304     while (openspace) {
305         /* move perpendicular to crack */
306         rx += (0.81 * sin(cr->t * M_PI/180));
307         ry -= (0.81 * cos(cr->t * M_PI/180));
308
309         cx = (int) rx;
310         cy = (int) ry;
311
312         if ((cx >= 0) && (cx < f->width) && (cy >= 0) && (cy < f->height)) {
313             /* safe to check */
314             if (f->cgrid[cy * f->width + cx] > 10000) {
315                 /* space is open */
316             } else {
317                 openspace = 0;
318             }
319         } else {
320             openspace = 0;
321         }
322     }
323
324     /* SandPainter stuff here */
325
326     /* Modulate gain */
327     cr->sandg += (frand(0.1) - 0.050);
328     maxg = 1.0;
329
330     if (cr->sandg < 0)
331         cr->sandg = 0;
332
333     if (cr->sandg > maxg)
334         cr->sandg = maxg;
335
336     grains = f->grains;
337
338     /* Lay down grains of sand */
339     w = cr->sandg / (grains - 1);
340
341     for (i = 0; i < grains; i++) {
342         drawx = (cr->x + (rx - cr->x) * sin(cr->sandp + sin((float) i * w)));
343         drawy = (cr->y + (ry - cr->y) * sin(cr->sandp + sin((float) i * w)));
344
345         /* Draw sand bit */
346         c = trans_point(drawx, drawy, cr->sandcolor, (0.1 - i / (grains * 10.0)), f);
347
348         XSetForeground(dpy, fgc, c);
349         XDrawPoint(dpy, window, fgc, (int) drawx, (int) drawy);
350         XSetForeground(dpy, fgc, f->fgcolor);
351     }
352 }
353
354 void build_substrate(struct field *f) {
355     int tx, ty;
356
357     f->cycles = 0;
358
359     if (f->cgrid) {
360         free(f->cgrid);
361         f->cgrid = NULL;
362     }
363
364     if (f->cracks) {
365         free(f->cracks);
366         f->cracks = NULL;
367     }
368
369     f->num = 0;
370
371     /* erase the crack grid */
372     f->cgrid = (int *) xrealloc(f->cgrid, sizeof(int) * f->height * f->width);
373     memset(f->cgrid, 10001, f->height * f->width * sizeof(int));
374
375     /* make random crack seeds */
376     for (tx = 0; tx < 16; tx++) {
377         ty = (int) (random() % (f->width * f->height - 1));
378         f->cgrid[ty] = (int) random() % 360;
379     }
380     
381     /* make the initial cracks */
382     for (tx = 0; tx < f->initial_cracks; tx++)
383         make_crack(f);
384 }
385
386
387 inline void movedrawcrack(Display *dpy, Window window, GC fgc, struct field *f, int cracknum) {
388     /* Basically Crack::move() */
389
390     int cx, cy;
391     crack *cr = &(f->cracks[cracknum]);
392
393     /* continue cracking */
394     cr->x += ((float) 0.42 * cos(cr->t * M_PI/180));
395     cr->y += ((float) 0.42 * sin(cr->t * M_PI/180));
396
397     /* bounds check */
398     /* modification of random(-0.33,0.33) */
399     cx = (int) (cr->x + (frand(0.66) - 0.33));
400     cy = (int) (cr->y + (frand(0.66) - 0.33));
401
402     if ((cx >= 0) && (cx < f->width) && (cy >= 0) && (cy < f->height)) {
403         /* draw sand painter if we're not wireframe */
404         if (!f->wireframe)
405             region_color(dpy, window, fgc, f, cr);
406
407         /* draw fgcolor crack */
408         ref_pixel(f, cx, cy) = f->fgcolor;
409         XDrawPoint(dpy, window, fgc, cx, cy);
410
411         /* safe to check */
412         if ((f->cgrid[cy * f->width + cx] > 10000) ||
413             (abs(f->cgrid[cy * f->width + cx] - cr->t) < 5)) {
414             /* continue cracking */
415             f->cgrid[cy * f->width + cx] = (int) cr->t;
416         } else if (abs(f->cgrid[cy * f->width + cx] - cr->t) > 2) {
417             /* crack encountered (not self), stop cracking */
418             start_crack(f, cr); /* restart ourselves */
419             make_crack(f); /* generate a new crack */
420         }
421     } else {
422         /* out of bounds, top cracking */
423         start_crack(f, cr); /* restart ourselves */
424         make_crack(f); /* generate a new crack */
425     }
426
427 }
428
429 char *progclass = "Substrate";
430
431 char *defaults[] = {
432     ".background: white",
433     ".foreground: black",
434     "*wireFrame: false",
435     "*maxCycles: 10000",
436     "*growthDelay: 18000",
437     "*initialCracks: 3",
438     "*maxCracks: 100",
439     "*sandGrains: 64",
440     0
441 };
442
443 XrmOptionDescRec options[] = {
444     {"-background", ".background", XrmoptionSepArg, 0},
445     {"-foreground", ".foreground", XrmoptionSepArg, 0},
446     {"-wireframe", ".wireFrame", XrmoptionNoArg, "true"},
447     {"-max-cycles", ".maxCycles", XrmoptionSepArg, 0},
448     {"-growth-delay", ".growthDelay", XrmoptionSepArg, 0},
449     {"-initial-cracks", ".initialCracks", XrmoptionSepArg, 0},
450     {"-max-cracks", ".maxCracks", XrmoptionSepArg, 0},
451     {"-sand-grains", ".sandGrains", XrmoptionSepArg, 0},
452     {0, 0, 0, 0}
453 };
454
455 void build_img(Display *dpy, Window window, XWindowAttributes xgwa, GC fgc, struct field *f) {
456     if (f->off_img) {
457         free(f->off_img);
458         f->off_img = NULL;
459     }
460
461     f->off_img = (unsigned long *) xrealloc(f->off_img, sizeof(unsigned long) * 
462                                             f->width * f->height);
463
464     memset(f->off_img, f->bgcolor, sizeof(unsigned long) * f->width * f->height);
465 }
466
467 void screenhack(Display * dpy, Window window)
468 {
469     struct field *f = init_field();
470
471     unsigned int max_cycles = 0;
472     int growth_delay = 0;
473     int tempx;
474
475     GC fgc;
476     XGCValues gcv;
477     XWindowAttributes xgwa;
478     XColor tmpcolor;
479
480     growth_delay = (get_integer_resource("growthDelay", "Integer"));
481     max_cycles = (get_integer_resource("maxCycles", "Integer"));
482     f->initial_cracks = (get_integer_resource("initialCracks", "Integer"));
483     f->max_num = (get_integer_resource("maxCracks", "Integer"));
484     f->wireframe = (get_boolean_resource("wireFrame", "Boolean"));
485     f->grains = (get_integer_resource("sandGrains", "Integer"));
486
487     if (f->initial_cracks <= 2) {
488         fprintf(stderr, "%s: Initial cracks must be greater than 2\n", progname);
489         return;
490     }
491
492     if (f->max_num <= 10) {
493         fprintf(stderr, "%s: Maximum number of cracks must be greater than 2\n", progname);
494         return;
495     }
496     
497     XGetWindowAttributes(dpy, window, &xgwa);
498
499     f->height = xgwa.height;
500     f->width = xgwa.width;
501     f->visdepth = xgwa.depth;
502  
503     /* Count the colors in our map and assign them in a horrifically inefficient 
504      * manner but it only happens once */
505     while (rgb_colormap[f->numcolors] != NULL) {
506         f->parsedcolors = (unsigned long *) xrealloc(f->parsedcolors, 
507                                                      sizeof(unsigned long) * (f->numcolors + 1));
508         if (!XParseColor(dpy, xgwa.colormap, rgb_colormap[f->numcolors], &tmpcolor)) {
509             fprintf(stderr, "%s: couldn't parse color %s\n", progname,
510                     rgb_colormap[f->numcolors]);
511             exit(1);
512         }
513
514         if (!XAllocColor(dpy, xgwa.colormap, &tmpcolor)) {
515             fprintf(stderr, "%s: couldn't allocate color %s\n", progname,
516                     rgb_colormap[f->numcolors]);
517             exit(1);
518         }
519
520         f->parsedcolors[f->numcolors] = tmpcolor.pixel;
521
522         f->numcolors++;
523     }
524
525     gcv.foreground = get_pixel_resource("foreground", "Foreground",
526                                         dpy, xgwa.colormap);
527     gcv.background = get_pixel_resource("background", "Background",
528                                         dpy, xgwa.colormap);
529     fgc = XCreateGC(dpy, window, GCForeground, &gcv);
530
531     f->fgcolor = gcv.foreground;
532     f->bgcolor = gcv.background;
533
534     /* Initialize stuff */
535     build_img(dpy, window, xgwa, fgc, f);
536     build_substrate(f);
537     
538     while (1) {
539         if ((f->cycles % 10) == 0) {
540             /* Restart if the window size changes */
541             XGetWindowAttributes(dpy, window, &xgwa);
542
543             if (f->height != xgwa.height || f->width != xgwa.width) {
544                 f->height = xgwa.height;
545                 f->width = xgwa.width;
546                 f->visdepth = xgwa.depth;
547
548                 build_substrate(f);
549                 build_img(dpy, window, xgwa, fgc, f);
550                 XSetForeground(dpy, fgc, gcv.background);
551                 XFillRectangle(dpy, window, fgc, 0, 0, xgwa.width, xgwa.height);
552                 XSetForeground(dpy, fgc, gcv.foreground);
553             }
554         }
555
556         for (tempx = 0; tempx < f->num; tempx++) {
557             movedrawcrack(dpy, window, fgc, f, tempx);
558         }
559
560         f->cycles++;
561
562         XSync(dpy, False);
563
564         screenhack_handle_events(dpy);
565
566         if (f->cycles >= max_cycles && max_cycles != 0) {
567             build_substrate(f);
568             build_img(dpy, window, xgwa, fgc, f);
569             XSetForeground(dpy, fgc, gcv.background);
570             XFillRectangle(dpy, window, fgc, 0, 0, xgwa.width, xgwa.height);
571             XSetForeground(dpy, fgc, gcv.foreground);
572         }
573
574         if (growth_delay)
575             usleep(growth_delay);
576     }
577 }