7f66b01b0c99b01bafbce687878b79eb3cabd789
[xscreensaver] / hacks / interference.c
1 /* interference.c --- colored fields via decaying sinusoidal waves.
2  * An entry for the RHAD Labs Screensaver Contest.
3  *
4  * Author: Hannu Mallat <hmallat@cs.hut.fi>
5  *
6  * Copyright (C) 1998 Hannu Mallat.
7  *
8  * Permission to use, copy, modify, distribute, and sell this software and its
9  * documentation for any purpose is hereby granted without fee, provided that
10  * the above copyright notice appear in all copies and that both that
11  * copyright notice and this permission notice appear in supporting
12  * documentation.  No representations are made about the suitability of this
13  * software for any purpose.  It is provided "as is" without express or 
14  * implied warranty.
15  *
16  * decaying sinusoidal waves, which extend spherically from their
17  * respective origins, move around the plane. a sort of interference
18  * between them is calculated and the resulting twodimensional wave
19  * height map is plotted in a grid, using softly changing colours.
20  *
21  * not physically (or in any sense) accurate, but fun to look at for 
22  * a while. you may tune the speed/resolution/interestingness tradeoff 
23  * with X resources, see below.
24  *
25  * Created      : Wed Apr 22 09:30:30 1998, hmallat
26  * Last modified: Wed Apr 22 09:30:30 1998, hmallat
27  *
28  * TODO:
29  *
30  *    This really needs to be sped up.
31  *
32  *    I've tried making it use XPutPixel/XPutImage instead of XFillRectangle,
33  *    but that doesn't seem to help (it's the same speed at gridsize=1, and
34  *    it actually makes it slower at larger sizes.)
35  *
36  *    I played around with shared memory, but clearly I still don't know how
37  *    to use the XSHM extension properly, because it doesn't work yet.
38  *
39  *    Hannu had put in code to use the double-buffering extension, but that
40  *    code didn't work for me on Irix.  I don't think it would help anyway,
41  *    since it's not the swapping of frames that is the bottleneck (or a source
42  *    of visible flicker.)
43  *
44  *     -- jwz, 4-Jun-98
45  */
46
47 #include <math.h>
48
49 #include "screenhack.h"
50
51 # include <X11/Xutil.h>
52
53 /* I thought it would be faster this way, but it turns out not to be... -jwz */
54 #undef USE_XIMAGE
55
56 #ifndef USE_XIMAGE
57 # undef HAVE_XSHM_EXTENSION  /* only applicable when using XImages */
58 #endif /* USE_XIMAGE */
59
60
61 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
62 # include "xdbe.h"
63 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
64
65 #ifdef HAVE_XSHM_EXTENSION
66 # include "xshm.h"
67 #endif /* HAVE_XSHM_EXTENSION */
68
69 char *progclass="Interference";
70
71 char *defaults [] = {
72   "*count:       3",     /* number of waves */
73   "*gridsize:    4",     /* pixel size, smaller values for better resolution */
74   "*ncolors:     128",   /* number of colours used */
75   "*speed:       30",    /* speed of wave origins moving around */
76   "*delay:       30000", /* or something */
77   "*color-shift: 60",    /* h in hsv space, smaller values for smaller
78                           * color gradients */
79   "*radius:      800",   /* wave extent */
80   "*gray:        false", /* color or grayscale */
81   "*mono:        false", /* monochrome, not very much fun */
82
83   "*doubleBuffer: True",
84 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
85   "*useDBE:      True", /* use double buffering extension */
86 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
87
88 #ifdef HAVE_XSHM_EXTENSION
89   "*useSHM:      True", /* use shared memory extension */
90 #endif /*  HAVE_XSHM_EXTENSION */
91   0
92 };
93
94 XrmOptionDescRec options [] = {
95   { "-count",       ".count",       XrmoptionSepArg, 0 }, 
96   { "-ncolors",     ".ncolors",     XrmoptionSepArg, 0 }, 
97   { "-gridsize",    ".gridsize",    XrmoptionSepArg, 0 }, 
98   { "-speed",       ".speed",       XrmoptionSepArg, 0 },
99   { "-delay",       ".delay",       XrmoptionSepArg, 0 },
100   { "-color-shift", ".color-shift", XrmoptionSepArg, 0 },
101   { "-radius",      ".radius",      XrmoptionSepArg, 0 },
102   { "-gray",        ".gray",        XrmoptionNoArg,  "True" },
103   { "-mono",        ".mono",        XrmoptionNoArg,  "True" },
104   { "-db",          ".doubleBuffer", XrmoptionNoArg,  "True" },
105   { "-no-db",       ".doubleBuffer", XrmoptionNoArg,  "False" },
106 #ifdef HAVE_XSHM_EXTENSION
107   { "-shm",     ".useSHM",      XrmoptionNoArg, "True" },
108   { "-no-shm",  ".useSHM",      XrmoptionNoArg, "False" },
109 #endif /*  HAVE_XSHM_EXTENSION */
110   { 0, 0, 0, 0 }
111 };
112
113 int options_size = (sizeof (options) / sizeof (XrmOptionDescRec));
114
115 struct inter_source {
116   int x; 
117   int y;
118   double x_theta;
119   double y_theta;
120 };
121
122 struct inter_context {
123   /*
124    * Display-related entries 
125    */
126   Display* dpy;
127   Window   win;
128
129 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
130   XdbeBackBuffer back_buf;
131 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
132   Pixmap   pix_buf;
133
134   GC       copy_gc;
135 #ifdef USE_XIMAGE
136   XImage  *ximage;
137 #endif /* USE_XIMAGE */
138
139 #ifdef HAVE_XSHM_EXTENSION
140   Bool use_shm;
141   XShmSegmentInfo shm_info;
142 #endif /* HAVE_XSHM_EXTENSION */
143
144   /*
145    * Resources
146    */
147   int count;
148   int grid_size;
149   int colors;
150   int speed;
151   int delay;
152   int shift;
153   int radius;
154
155   /*
156    * Drawing-related entries
157    */
158   int w;
159   int h;
160   Colormap cmap;
161   XColor* pal;
162   GC* gcs;
163
164   /*
165    * lookup tables
166    */
167   int* wave_height;
168     
169   /*
170    * Interference sources
171    */
172   struct inter_source* source;
173 };
174
175 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
176 # define TARGET(c) ((c)->back_buf ? (c)->back_buf : \
177                     (c)->pix_buf ? (c)->pix_buf : (c)->win)
178 #else  /* HAVE_DOUBLE_BUFFER_EXTENSION */
179 # define TARGET(c) ((c)->pix_buf ? (c)->pix_buf : (c)->win)
180 #endif /* !HAVE_DOUBLE_BUFFER_EXTENSION */
181
182 void inter_init(Display* dpy, Window win, struct inter_context* c) 
183 {
184   XWindowAttributes xgwa;
185   double H[3], S[3], V[3];
186   int i;
187   int mono;
188   int gray;
189   XGCValues val;
190   unsigned long valmask = 0;
191   Bool dbuf = get_boolean_resource ("doubleBuffer", "Boolean");
192
193   memset (c, 0, sizeof(*c));
194
195   c->dpy = dpy;
196   c->win = win;
197
198   XGetWindowAttributes(c->dpy, c->win, &xgwa);
199   c->w = xgwa.width;
200   c->h = xgwa.height;
201   c->cmap = xgwa.colormap;
202
203 #ifdef HAVE_XSHM_EXTENSION
204   c->use_shm = get_boolean_resource("useSHM", "Boolean");
205 #endif /*  HAVE_XSHM_EXTENSION */
206
207   if (dbuf)
208     {
209 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
210       c->back_buf = xdbe_get_backbuffer (c->dpy, c->win, XdbeUndefined);
211 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
212
213 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
214       if (!c->back_buf)
215 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
216         c->pix_buf = XCreatePixmap (dpy, win, xgwa.width, xgwa.height,
217                                     xgwa.depth);
218     }
219
220   val.function = GXcopy;
221   c->copy_gc = XCreateGC(c->dpy, TARGET(c), GCFunction, &val);
222
223   c->count = get_integer_resource("count", "Integer");
224   if(c->count < 1)
225     c->count = 1;
226   c->grid_size = get_integer_resource("gridsize", "Integer");
227   if(c->grid_size < 1)
228     c->grid_size = 1;
229   mono = get_boolean_resource("mono", "Boolean");
230   if(!mono) {
231     c->colors = get_integer_resource("ncolors", "Integer");
232     if(c->colors < 2)
233       c->colors = 2;
234   }
235   c->speed = get_integer_resource("speed", "Integer");
236   c->shift = get_float_resource("color-shift", "Float");
237   while(c->shift >= 360.0)
238     c->shift -= 360.0;
239   while(c->shift <= -360.0)
240     c->shift += 360.0;
241   c->radius = get_integer_resource("radius", "Integer");;
242   if(c->radius < 1)
243     c->radius = 1;
244
245 #ifdef USE_XIMAGE
246
247   c->ximage = 0;
248
249 # ifdef HAVE_XSHM_EXTENSION
250   if (c->use_shm)
251     {
252       c->ximage = create_xshm_image(dpy, xgwa.visual, xgwa.depth,
253                                     ZPixmap, 0, &c->shm_info,
254                                     xgwa.width, c->grid_size);
255       if (!c->ximage)
256         c->use_shm = False;
257     }
258 # endif /* HAVE_XSHM_EXTENSION */
259
260   if (!c->ximage)
261     {
262       c->ximage =
263         XCreateImage (dpy, xgwa.visual,
264                       xgwa.depth, ZPixmap, 0, 0, /* depth, fmt, offset, data */
265                       xgwa.width, c->grid_size,  /* width, height */
266                       8, 0);                     /* pad, bpl */
267       c->ximage->data = (unsigned char *)
268         calloc(c->ximage->height, c->ximage->bytes_per_line);
269     }
270 #endif /* USE_XIMAGE */
271
272   if(!mono) {
273     c->pal = calloc(c->colors, sizeof(XColor));
274
275     gray = get_boolean_resource("gray", "Boolean");
276     if(!gray) {
277       H[0] = frand(360.0); 
278       H[1] = H[0] + c->shift < 360.0 ? H[0]+c->shift : H[0] + c->shift-360.0; 
279       H[2] = H[1] + c->shift < 360.0 ? H[1]+c->shift : H[1] + c->shift-360.0; 
280       S[0] = S[1] = S[2] = 1.0;
281       V[0] = V[1] = V[2] = 1.0;
282     } else {
283       H[0] = H[1] = H[2] = 0.0;
284       S[0] = S[1] = S[2] = 0.0;
285       V[0] = 1.0; V[1] = 0.5; V[2] = 0.0;
286     }
287
288     make_color_loop(c->dpy, c->cmap, 
289                     H[0], S[0], V[0], 
290                     H[1], S[1], V[1], 
291                     H[2], S[2], V[2], 
292                     c->pal, &(c->colors), 
293                     True, False);
294     if(c->colors < 2) { /* color allocation failure */
295       mono = 1;
296       free(c->pal);
297     }
298   }
299
300   if(mono) { /* DON'T else this with the previous if! */
301     c->colors = 2;
302     c->pal = calloc(2, sizeof(XColor));
303     c->pal[0].pixel = BlackPixel(c->dpy, DefaultScreen(c->dpy));
304     c->pal[1].pixel = WhitePixel(c->dpy, DefaultScreen(c->dpy));
305   }    
306
307   valmask = GCForeground;
308   c->gcs = calloc(c->colors, sizeof(GC));
309   for(i = 0; i < c->colors; i++) {
310     val.foreground = c->pal[i].pixel;    
311     c->gcs[i] = XCreateGC(c->dpy, TARGET(c), valmask, &val);
312   }
313
314   c->wave_height = calloc(c->radius, sizeof(int));
315   for(i = 0; i < c->radius; i++) {
316     float max = 
317       ((float)c->colors) * 
318       ((float)c->radius - (float)i) /
319       ((float)c->radius);
320     c->wave_height[i] = 
321       (int)
322       ((max + max*cos((double)i/50.0)) / 2.0);
323   }
324
325   c->source = calloc(c->count, sizeof(struct inter_source));
326   for(i = 0; i < c->count; i++) {
327     c->source[i].x_theta = frand(2.0)*3.14159;
328     c->source[i].y_theta = frand(2.0)*3.14159;
329   }
330
331 }
332
333 #define source_x(c, i) \
334   (c->w/2 + ((int)(cos(c->source[i].x_theta)*((float)c->w/2.0))))
335 #define source_y(c, i) \
336   (c->h/2 + ((int)(cos(c->source[i].y_theta)*((float)c->h/2.0))))
337
338 /*
339  * this is rather suboptimal. the sqrt() doesn't seem to be a big
340  * performance hit, but all those separate XFillRectangle()'s are.
341  * hell, it's just a quick hack anyway -- if someone wishes to tune
342  * it, go ahead! 
343  */
344
345 void do_inter(struct inter_context* c) 
346 {
347   int i, j, k;
348   int result;
349   int dist;
350   int g;
351
352   int dx, dy;
353
354   for(i = 0; i < c->count; i++) {
355     c->source[i].x_theta += (c->speed/1000.0);
356     if(c->source[i].x_theta > 2.0*3.14159)
357       c->source[i].x_theta -= 2.0*3.14159;
358     c->source[i].y_theta += (c->speed/1000.0);
359     if(c->source[i].y_theta > 2.0*3.14159)
360       c->source[i].y_theta -= 2.0*3.14159;
361     c->source[i].x = source_x(c, i);
362     c->source[i].y = source_y(c, i);
363   }
364
365   g = c->grid_size;
366
367   for(j = 0; j < c->h/g; j++) {
368     for(i = 0; i < c->w/g; i++) {
369       result = 0;
370       for(k = 0; k < c->count; k++) {
371         dx = i*g + g/2 - c->source[k].x;
372         dy = j*g + g/2 - c->source[k].y;
373         dist = sqrt(dx*dx + dy*dy); /* what's the performance penalty here? */
374         result += (dist > c->radius ? 0 : c->wave_height[dist]);
375       }
376       result %= c->colors;
377
378 #ifdef USE_XIMAGE
379       /* Fill in these `gridsize' horizontal bits in the scanline */
380       for(k = 0; k < g; k++)
381         XPutPixel(c->ximage, (g*i)+k, 0, c->pal[result].pixel);
382
383 #else  /* !USE_XIMAGE */
384       XFillRectangle(c->dpy, TARGET(c), c->gcs[result], g*i, g*j, g, g); 
385 #endif /* !USE_XIMAGE */
386     }
387
388 #ifdef USE_XIMAGE
389
390     /* Only the first scanline of the image has been filled in; clone that
391        scanline to the rest of the `gridsize' lines in the ximage */
392     for(k = 0; k < (g-1); k++)
393       memcpy(c->ximage->data + (c->ximage->bytes_per_line * (k + 1)),
394              c->ximage->data + (c->ximage->bytes_per_line * k),
395              c->ximage->bytes_per_line);
396
397     /* Move the bits for this horizontal stripe to the server. */
398 # ifdef HAVE_XSHM_EXTENSION
399     if (c->use_shm)
400       XShmPutImage(c->dpy, TARGET(c), c->copy_gc, c->ximage,
401                    0, 0, 0, g*j, c->ximage->width, c->ximage->height,
402                    False);
403     else
404 # endif /*  HAVE_XSHM_EXTENSION */
405       XPutImage(c->dpy, TARGET(c), c->copy_gc, c->ximage,
406                 0, 0, 0, g*j, c->ximage->width, c->ximage->height);
407
408 #endif /* USE_XIMAGE */
409   }
410
411 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
412   if (c->back_buf)
413     {
414       XdbeSwapInfo info[1];
415       info[0].swap_window = c->win;
416       info[0].swap_action = XdbeUndefined;
417       XdbeSwapBuffers(c->dpy, info, 1);
418     }
419   else
420 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
421     if (c->pix_buf)
422       {
423         XCopyArea (c->dpy, c->pix_buf, c->win, c->copy_gc,
424                    0, 0, c->w, c->h, 0, 0);
425       }
426
427   XSync(c->dpy, False);
428 }
429
430 void screenhack(Display *dpy, Window win) 
431 {
432   struct inter_context c;
433   int delay;
434
435   delay = get_integer_resource("delay", "Integer");
436
437   inter_init(dpy, win, &c);
438   while(1) {
439     do_inter(&c); 
440     screenhack_handle_events (dpy);
441     if(delay) usleep(delay);
442   }
443 }