d436ebb4ecf4e689f01a3c5c53c45da2fda3d11f
[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 #ifdef HAVE_XDBE
54 # include <X11/extensions/Xdbe.h>
55 #endif /* HAVE_XDBE */
56
57
58 /* I haven't gotten the shm stuff to work yet... and I'm not sure it would
59    help much anyway.  -- jwz */
60 #undef HAVE_XSHM_EXTENSION
61
62 #ifdef HAVE_XSHM_EXTENSION
63 # include <sys/ipc.h>
64 # include <sys/shm.h>
65 # include <X11/extensions/XShm.h>
66 #endif /*  HAVE_XSHM_EXTENSION */
67
68
69 /* I thought it would be faster this way, but it turns out not to be... -jwz */
70 /* #define USE_XIMAGE */
71
72 char *progclass="Interference";
73
74 char *defaults [] = {
75   "*count:       3",     /* number of waves */
76   "*gridsize:    4",     /* pixel size, smaller values for better resolution */
77   "*ncolors:     128",   /* number of colours used */
78   "*speed:       30",    /* speed of wave origins moving around */
79   "*delay:       30000", /* or something */
80   "*color-shift: 60",    /* h in hsv space, smaller values for smaller
81                           * color gradients */
82   "*radius:      800",   /* wave extent */
83   "*gray:        false", /* color or grayscale */
84   "*mono:        false", /* monochrome, not very much fun */
85
86 #ifdef HAVE_XDBE
87   "*nodb:        true", /* don't use double buffering */
88 #endif /* HAVE_XDBE */
89
90 #ifdef HAVE_XSHM_EXTENSION
91   "*noshm:        false", /* don't use shared memory */
92 #endif /*  HAVE_XSHM_EXTENSION */
93   0
94 };
95
96 XrmOptionDescRec options [] = {
97   { "-count",       ".count",       XrmoptionSepArg, 0 }, 
98   { "-ncolors",     ".ncolors",     XrmoptionSepArg, 0 }, 
99   { "-gridsize",    ".gridsize",    XrmoptionSepArg, 0 }, 
100   { "-speed",       ".speed",       XrmoptionSepArg, 0 },
101   { "-delay",       ".delay",       XrmoptionSepArg, 0 },
102   { "-color-shift", ".color-shift", XrmoptionSepArg, 0 },
103   { "-radius",      ".radius",      XrmoptionSepArg, 0 },
104   { "-gray",        ".gray",        XrmoptionNoArg,  "True" },
105   { "-mono",        ".mono",        XrmoptionNoArg,  "True" },
106 #ifdef HAVE_XDBE
107   { "-nodb",        ".nodb",        XrmoptionNoArg,  "True" },
108 #endif /* HAVE_XDBE */
109 #ifdef HAVE_XSHM_EXTENSION
110   { "-noshm",        ".noshm",      XrmoptionNoArg,  "True" },
111 #endif /*  HAVE_XSHM_EXTENSION */
112   { 0, 0, 0, 0 }
113 };
114
115 int options_size = (sizeof (options) / sizeof (XrmOptionDescRec));
116
117 struct inter_source {
118   int x; 
119   int y;
120   double x_theta;
121   double y_theta;
122 };
123
124 struct inter_context {
125   /*
126    * Display-related entries 
127    */
128   Display* dpy;
129   Window   win;
130
131   Pixmap   dbuf;
132   GC       copy_gc;
133 #ifdef USE_XIMAGE
134   XImage  *ximage;
135 #endif /* USE_XIMAGE */
136
137 #ifdef HAVE_XDBE
138   Status   has_dbe;
139 #endif /* HAVE_XDBE */
140
141 #ifdef HAVE_XSHM_EXTENSION
142   int use_shm;
143   XShmSegmentInfo shm_info;
144 #endif /* HAVE_XSHM_EXTENSION */
145
146   /*
147    * Resources
148    */
149   int count;
150   int grid_size;
151   int colors;
152   int speed;
153   int delay;
154   int shift;
155   int radius;
156
157   /*
158    * Drawing-related entries
159    */
160   int w;
161   int h;
162   Colormap cmap;
163   XColor* pal;
164 #ifdef HAVE_XDBE
165   XdbeBackBuffer buf;
166 #endif /* HAVE_XDBE */
167   GC* gcs;
168
169   /*
170    * lookup tables
171    */
172   int* wave_height;
173     
174   /*
175    * Interference sources
176    */
177   struct inter_source* source;
178 };
179
180 #ifdef HAVE_XDBE
181 # define TARGET(c) ((c)->has_dbe ? (c)->buf : (c)->dbuf)
182 #else  /* HAVE_XDBE */
183 # define TARGET(c) ((c)->dbuf)
184 #endif /* !HAVE_XDBE */
185
186 void inter_init(Display* dpy, Window win, struct inter_context* c) 
187 {
188   XWindowAttributes xgwa;
189   double H[3], S[3], V[3];
190   int i;
191   int mono;
192   int gray;
193 #ifdef HAVE_XDBE
194   int major, minor;
195   int nodb;
196 #endif /* HAVE_XDBE */
197
198   XGCValues val;
199   unsigned long valmask = 0;
200
201   c->dpy = dpy;
202   c->win = win;
203
204   XGetWindowAttributes(c->dpy, c->win, &xgwa);
205   c->w = xgwa.width;
206   c->h = xgwa.height;
207   c->cmap = xgwa.colormap;
208
209 #ifdef HAVE_XDBE
210   nodb = get_boolean_resource("nodb", "Boolean");
211   if(nodb) {
212     c->has_dbe = False;
213   } else {
214     c->has_dbe = XdbeQueryExtension(dpy, &major, &minor);
215   }
216 #endif /* HAVE_XDBE */
217
218 #ifdef HAVE_XSHM_EXTENSION
219   c->use_shm = !get_boolean_resource("noshm", "Boolean");
220 #endif /*  HAVE_XSHM_EXTENSION */
221
222 #ifdef HAVE_XDBE
223   if (!c->has_dbe)
224 #endif /* HAVE_XDBE */
225     {
226       c->dbuf = XCreatePixmap(dpy, win, xgwa.width, xgwa.height, xgwa.depth);
227       val.function = GXcopy;
228     }
229
230   c->copy_gc = XCreateGC(c->dpy, c->dbuf, GCFunction, &val);
231
232   c->count = get_integer_resource("count", "Integer");
233   if(c->count < 1)
234     c->count = 1;
235   c->grid_size = get_integer_resource("gridsize", "Integer");
236   if(c->grid_size < 1)
237     c->grid_size = 1;
238   mono = get_boolean_resource("mono", "Boolean");
239   if(!mono) {
240     c->colors = get_integer_resource("ncolors", "Integer");
241     if(c->colors < 2)
242       c->colors = 2;
243   }
244   c->speed = get_integer_resource("speed", "Integer");
245   c->shift = get_float_resource("color-shift", "Float");
246   while(c->shift >= 360.0)
247     c->shift -= 360.0;
248   while(c->shift <= -360.0)
249     c->shift += 360.0;
250   c->radius = get_integer_resource("radius", "Integer");;
251   if(c->radius < 1)
252     c->radius = 1;
253
254 #ifdef USE_XIMAGE
255
256 # ifdef HAVE_XSHM_EXTENSION
257
258   if (c->use_shm)
259     {
260       c->ximage = XShmCreateImage(dpy, xgwa.visual, xgwa.depth,
261                                   ZPixmap, 0, &c->shm_info,
262                                   xgwa.width, c->grid_size);
263       c->shm_info.shmid = shmget(IPC_PRIVATE,
264                                  c->ximage->height * c->ximage->bytes_per_line,
265                                  IPC_CREAT | 0777);
266       if (c->shm_info.shmid == -1)
267         printf ("shmget failed!");
268       c->shm_info.readOnly = False;
269       c->ximage->data = shmat(c->shm_info.shmid, 0, 0);
270       printf("data=0x%X %d %d\n", c->ximage->data,
271              c->ximage->height, c->ximage->bytes_per_line);
272       XShmAttach(dpy, &c->shm_info);
273       XSync(dpy, False);
274     }
275   else
276 # endif /* HAVE_XSHM_EXTENSION */
277     {
278       c->ximage =
279         XCreateImage (dpy, xgwa.visual,
280                       xgwa.depth, ZPixmap, 0, 0, /* depth, fmt, offset, data */
281                       xgwa.width, c->grid_size,  /* width, height */
282                       8, 0);                     /* pad, bpl */
283       c->ximage->data = (unsigned char *)
284         calloc(c->ximage->height, c->ximage->bytes_per_line);
285     }
286 #endif /* USE_XIMAGE */
287
288   if(!mono) {
289     c->pal = calloc(c->colors, sizeof(XColor));
290
291     srand48(time(NULL));
292
293     gray = get_boolean_resource("gray", "Boolean");
294     if(!gray) {
295       H[0] = drand48()*360.0; 
296       H[1] = H[0] + c->shift < 360.0 ? H[0]+c->shift : H[0] + c->shift-360.0; 
297       H[2] = H[1] + c->shift < 360.0 ? H[1]+c->shift : H[1] + c->shift-360.0; 
298       S[0] = S[1] = S[2] = 1.0;
299       V[0] = V[1] = V[2] = 1.0;
300     } else {
301       H[0] = H[1] = H[2] = 0.0;
302       S[0] = S[1] = S[2] = 0.0;
303       V[0] = 1.0; V[1] = 0.5; V[2] = 0.0;
304     }
305
306     make_color_loop(c->dpy, c->cmap, 
307                     H[0], S[0], V[0], 
308                     H[1], S[1], V[1], 
309                     H[2], S[2], V[2], 
310                     c->pal, &(c->colors), 
311                     True, False);
312     if(c->colors < 2) { /* color allocation failure */
313       mono = 1;
314       free(c->pal);
315     }
316   }
317
318   if(mono) { /* DON'T else this with the previous if! */
319     c->colors = 2;
320     c->pal = calloc(2, sizeof(XColor));
321     c->pal[0].pixel = BlackPixel(c->dpy, DefaultScreen(c->dpy));
322     c->pal[1].pixel = WhitePixel(c->dpy, DefaultScreen(c->dpy));
323   }    
324
325 #ifdef HAVE_XDBE
326   if(c->has_dbe)
327     c->buf = XdbeAllocateBackBufferName(c->dpy, c->win, XdbeUndefined);
328 #endif /* HAVE_XDBE */
329
330   valmask = GCForeground;
331   c->gcs = calloc(c->colors, sizeof(GC));
332   for(i = 0; i < c->colors; i++) {
333     val.foreground = c->pal[i].pixel;    
334     c->gcs[i] = XCreateGC(c->dpy, TARGET(c), valmask, &val);
335   }
336
337   c->wave_height = calloc(c->radius, sizeof(int));
338   for(i = 0; i < c->radius; i++) {
339     float max = 
340       ((float)c->colors) * 
341       ((float)c->radius - (float)i) /
342       ((float)c->radius);
343     c->wave_height[i] = 
344       (int)
345       ((max + max*cos((double)i/50.0)) / 2.0);
346   }
347
348   c->source = calloc(c->count, sizeof(struct inter_source));
349   for(i = 0; i < c->count; i++) {
350     c->source[i].x_theta = drand48()*2.0*3.14159;
351     c->source[i].y_theta = drand48()*2.0*3.14159;
352   }
353
354 }
355
356 #define source_x(c, i) \
357   (c->w/2 + ((int)(cos(c->source[i].x_theta)*((float)c->w/2.0))))
358 #define source_y(c, i) \
359   (c->h/2 + ((int)(cos(c->source[i].y_theta)*((float)c->h/2.0))))
360
361 /*
362  * this is rather suboptimal. the sqrt() doesn't seem to be a big
363  * performance hit, but all those separate XFillRectangle()'s are.
364  * hell, it's just a quick hack anyway -- if someone wishes to tune
365  * it, go ahead! 
366  */
367
368 void do_inter(struct inter_context* c) 
369 {
370   int i, j, k;
371   int result;
372   int dist;
373 #ifdef HAVE_XDBE
374   XdbeSwapInfo info[1];
375 #endif /* HAVE_XDBE */
376   int g;
377
378   int dx, dy;
379
380   for(i = 0; i < c->count; i++) {
381     c->source[i].x_theta += (c->speed/1000.0);
382     if(c->source[i].x_theta > 2.0*3.14159)
383       c->source[i].x_theta -= 2.0*3.14159;
384     c->source[i].y_theta += (c->speed/1000.0);
385     if(c->source[i].y_theta > 2.0*3.14159)
386       c->source[i].y_theta -= 2.0*3.14159;
387     c->source[i].x = source_x(c, i);
388     c->source[i].y = source_y(c, i);
389   }
390
391   g = c->grid_size;
392
393   for(j = 0; j < c->h/g; j++) {
394     for(i = 0; i < c->w/g; i++) {
395       result = 0;
396       for(k = 0; k < c->count; k++) {
397         dx = i*g + g/2 - c->source[k].x;
398         dy = j*g + g/2 - c->source[k].y;
399         dist = sqrt(dx*dx + dy*dy); /* what's the performance penalty here? */
400         result += (dist > c->radius ? 0 : c->wave_height[dist]);
401       }
402       result %= c->colors;
403
404 #ifdef USE_XIMAGE
405       /* Fill in these `gridsize' horizontal bits in the scanline */
406       for(k = 0; k < g; k++)
407         XPutPixel(c->ximage, (g*i)+k, 0, c->pal[result].pixel);
408
409 #else  /* !USE_XIMAGE */
410       XFillRectangle(c->dpy, TARGET(c), c->gcs[result], g*i, g*j, g, g); 
411 #endif /* !USE_XIMAGE */
412     }
413
414 #ifdef USE_XIMAGE
415
416     /* Only the first scanline of the image has been filled in; clone that
417        scanline to the rest of the `gridsize' lines in the ximage */
418     for(k = 0; k < (g-1); k++)
419       memcpy(c->ximage->data + (c->ximage->bytes_per_line * (k + 1)),
420              c->ximage->data + (c->ximage->bytes_per_line * k),
421              c->ximage->bytes_per_line);
422
423     /* Move the bits for this horizontal stripe to the server. */
424 #ifdef HAVE_XSHM_EXTENSION
425     if (c->use_shm)
426       {
427         /* wtf? we get a badmatch unless ximage->data == 0 ? */
428         char *d = c->ximage->data;
429         c->ximage->data = 0;
430         XShmPutImage(c->dpy, TARGET(c), c->copy_gc, c->ximage,
431                      0, 0, 0, g*j, c->ximage->width, c->ximage->height,
432                      False);
433         c->ximage->data = d;
434       }
435     else
436 #endif /*  HAVE_XSHM_EXTENSION */
437       XPutImage(c->dpy, TARGET(c), c->copy_gc, c->ximage,
438                 0, 0, 0, g*j, c->ximage->width, c->ximage->height);
439
440 #endif /* USE_XIMAGE */
441   }
442
443 #ifdef HAVE_XDBE
444   if(c->has_dbe)
445     {
446       info[0].swap_window = c->win;
447       info[0].swap_action = XdbeUndefined;
448       XdbeSwapBuffers(c->dpy, info, 1);
449     }
450   else
451 #endif /* HAVE_XDBE */
452     {
453       XCopyArea (c->dpy, c->dbuf, c->win, c->copy_gc,
454                  0, 0, c->w, c->h, 0, 0);
455     }
456   XSync(c->dpy, False);
457 }
458
459 void screenhack(Display *dpy, Window win) 
460 {
461   struct inter_context c;
462   int delay;
463
464   delay = get_integer_resource("delay", "Integer");
465
466   inter_init(dpy, win, &c);
467   while(1) {
468     do_inter(&c); 
469     if(delay) 
470       usleep(delay);
471   }
472 }