b5a87de2b9be10a9d6c9a72f608c2b9ab974e2b4
[xscreensaver] / hacks / intermomentary.c
1 /*
2  *  InterMomentary (dragorn@kismetwireless.net)
3  *  Directly ported code from complexification.net InterMomentary art
4  *  http://www.complexification.net/gallery/machines/interMomentary/applet_l/interMomentary_l.pde
5  *
6  * Intersecting Circles, Instantaneous
7  * J. Tarbell                              + complexification.net
8  * Albuquerque, New Mexico
9  * May, 2004
10  * 
11  * a REAS collaboration for the            + groupc.net
12  * Whitney Museum of American Art ARTPORT  + artport.whitney.org
13  * Robert Hodgin                           + flight404.com
14  * William Ngan                            + metaphorical.net
15  * 
16  *
17  * 1.0  Oct 10 2004  dragorn  Completed first port 
18  *
19  *
20  * Based, of course, on other hacks in:
21  *
22  * xscreensaver, Copyright (c) 1997, 1998, 2002 Jamie Zawinski <jwz@jwz.org>
23  *
24  * Permission to use, copy, modify, distribute, and sell this software and its
25  * documentation for any purpose is hereby granted without fee, provided that
26  * the above copyright notice appear in all copies and that both that
27  * copyright notice and this permission notice appear in supporting
28  * documentation.  No representations are made about the suitability of this
29  * software for any purpose.  It is provided "as is" without express or 
30  * implied warranty.
31  */
32
33 #include "screenhack.h"
34 #include <X11/Xutil.h>
35 #include <stdio.h>
36 #include <sys/time.h>
37
38 #ifndef MAX_WIDTH
39 #include <limits.h>
40 #define MAX_WIDTH SHRT_MAX
41 #endif
42
43 #ifdef TIME_ME
44 #include <time.h>
45 #endif
46
47 #include <math.h>
48
49 #include "hsv.h"
50
51 /* this program goes faster if some functions are inline.  The following is
52  * borrowed from ifs.c */
53 #if !defined( __GNUC__ ) && !defined(__cplusplus) && !defined(c_plusplus)
54 #undef inline
55 #define inline                  /* */
56 #endif
57
58 /* Pixel rider */
59 typedef struct {
60     float t;
61     float vt;
62     float mycharge;
63 } PxRider;
64
65 /* disc of light */
66 typedef struct {
67     /* index identifier */
68     int id;
69     /* position */
70     float x, y;
71     /* radius */
72     float r, dr;
73     /* velocity */
74     float vx, vy;
75
76     /* pixel riders */
77     int numr;
78     PxRider *pxRiders;
79 } Disc;
80
81 struct field {
82     unsigned int height;
83     unsigned int width;
84
85     int initial_discs;
86     Disc *discs;
87     
88     unsigned int num;
89
90     unsigned int maxrider;
91     unsigned int maxradius;
92
93     /* color parms */
94     unsigned long fgcolor;
95     unsigned long bgcolor;
96     int visdepth;
97
98     unsigned int cycles;
99
100     /* Offscreen image we draw to */
101     Pixmap off_map;
102     unsigned long int *off_alpha;
103 };
104
105 static void *xrealloc(void *p, size_t size) {
106     void *ret;
107     if ((ret = realloc(p, size)) == NULL) {
108         fprintf(stderr, "%s: out of memory\n", progname);
109         exit(1);
110     }
111     return ret;
112 }
113
114 struct field *init_field(void) {
115     struct field *f = xrealloc(NULL, sizeof(struct field));
116     f->height = 0;
117     f->width = 0;
118     f->initial_discs = 0;
119     f->discs = NULL;
120     f->num = 0;
121     f->maxrider = 0;
122     f->maxradius = 0;
123     f->cycles = 0;
124     f->fgcolor = 0;
125     f->bgcolor = 0;
126     f->off_alpha = NULL;
127     f->visdepth = 0;
128     return f;
129 }
130
131 /* Quick-ref to pixels in the alpha map */
132 #define ref_pixel(f, x, y)   ((f)->off_alpha[(y) * (f)->width + (x)])
133
134 inline void make_disc(struct field *f, float x, float y, float vx, float vy, float r) {
135     /* Synthesis of Disc::Disc and PxRider::PxRider */
136     Disc *nd;
137     int ix;
138
139     /* allocate a new disc */
140     f->discs = (Disc *) xrealloc(f->discs, sizeof(Disc) * (f->num + 1));
141
142     nd = &(f->discs[f->num]);
143
144     nd->id = f->num++;
145     nd->x = x;
146     nd->y = y;
147     nd->vx = vx;
148     nd->vy = vy;
149     nd->dr = r;
150     nd->r = frand(r) / 3;
151
152     nd->numr = (frand(r) / 2.62);
153     if (nd->numr > f->maxrider)
154         nd->numr = f->maxrider;
155
156     nd->pxRiders = NULL;
157     nd->pxRiders = (PxRider *) xrealloc(nd->pxRiders, sizeof(PxRider) * (f->maxrider));
158     for (ix = 0; ix < f->maxrider; ix++) {
159         nd->pxRiders[ix].vt = 0.0;
160         nd->pxRiders[ix].t = frand(M_PI * 2);
161         nd->pxRiders[ix].mycharge = 0;
162     }
163 }
164
165 inline void point2rgb(int depth, unsigned long c, unsigned short int *r, 
166                       unsigned short int *g, unsigned short int *b) {
167     switch(depth) {
168         case 32:
169         case 24:
170             *g = (c & 0xff00) >> 8; 
171             *r = (c & 0xff0000) >> 16; 
172             *b = c & 0xff; 
173             break;
174         case 16:
175             *g = ((c >> 5) & 0x3f) << 2;
176             *r = ((c >> 11) & 0x1f) << 3; 
177             *b = (c & 0x1f) << 3; 
178             break;
179         case 15:
180             *g = ((c >> 5) & 0x1f) << 3;
181             *r = ((c >> 10) & 0x1f) << 3;
182             *b = (c & 0x1f) << 3;
183             break;
184     }
185 }
186
187 inline unsigned long rgb2point(int depth, unsigned short int r, 
188                                unsigned short int g, unsigned short int b) {
189     unsigned long ret = 0;
190
191     switch(depth) {
192         case 32:
193             ret = 0xff000000;
194         case 24:
195             ret |= (r << 16) | (g << 8) | b;
196             break;
197         case 16:
198             ret = ((r>>3) << 11) | ((g>>2)<<5) | (b>>3);
199             break;
200         case 15:
201             ret = ((r>>3) << 10) | ((g>>3)<<5) | (b>>3);
202             break;
203     }
204
205     return ret;
206 }
207
208 /* alpha blended point drawing */
209 inline unsigned long trans_point(int x1, int y1, unsigned long myc, float a, struct field *f) {
210     if ((x1 >= 0) && (x1 < f->width) && (y1 >= 0) && (y1 < f->height)) {
211         if (a >= 1.0) {
212             ref_pixel(f, x1, y1) = myc;
213         } else {
214             unsigned short int or, og, ob;
215             unsigned short int r, g, b;
216             unsigned short int nr, ng, nb;
217             unsigned long c;
218
219             c = ref_pixel(f, x1, y1);
220             point2rgb(f->visdepth, c, &or, &og, &ob);
221             point2rgb(f->visdepth, myc, &r, &g, &b);
222
223             nr = or + (r - or) * a;
224             ng = og + (g - og) * a;
225             nb = ob + (b - ob) * a;
226
227             c = rgb2point(f->visdepth, nr, ng, nb);
228             ref_pixel(f, x1, y1) = c;
229
230             return c;
231         }
232     }
233
234     return 0;
235 }
236
237 inline void move_disc(struct field *f, int dnum) {
238     Disc *d = &(f->discs[dnum]);
239
240     /* add velocity to position */
241     d->x += d->vx;
242     d->y += d->vy;
243
244     /* bound check */
245     if (d->x + d->r < 0)
246         d->x += f->width + d->r + d->r;
247     if (d->x - d->r > f->width)
248         d->x -= f->width + d->r + d->r;
249     if (d->y + d->r < 0)
250         d->y += f->height + d->r + d->r;
251     if (d->y - d->r > f->height)
252         d->y -= f->height + d->r + d->r;
253
254     /* increase to destination radius */
255     if (d->r < d->dr)
256         d->r += 0.1;
257 }
258
259 inline void draw_glowpoint(Display *dpy, Window window, GC fgc, struct field *f, float px, float py) {
260     int i, j;
261     float a;
262     unsigned long c;
263
264     for (i =- 2; i < 3; i++) {
265         for (j =- 2; j < 3; j++) {
266             a = 0.8 - i * i * 0.1 - j * j * 0.1;
267
268             c = trans_point(px+i, py+j, f->fgcolor, a, f);
269             XSetForeground(dpy, fgc, c);
270             XDrawPoint(dpy, window, fgc, px + i, py + j);
271             XSetForeground(dpy, fgc, f->fgcolor);
272         }
273     }
274 }
275
276 inline void moverender_rider(Display *dpy, Window window, GC fgc, struct field *f, PxRider *rid, 
277                              float x, float y, float r) {
278     float px, py;
279     unsigned long int c;
280     unsigned short int cr, cg, cb;
281     int ch;
282     double cs, cv;
283
284     /* add velocity to theta */
285     rid->t = fmodf((rid->t + rid->vt + M_PI), (2 * M_PI)) - M_PI;
286     
287     rid->vt += frand(0.002) - 0.001;
288
289     /* apply friction brakes */
290     if (abs(rid->vt) > 0.02)
291         rid->vt *= 0.9;
292
293     /* draw */
294     px = x + r * cos(rid->t);
295     py = y + r * sin(rid->t);
296
297     if ((px < 0) || (px >= f->width) || (py < 0) || (py >= f->height))
298         return;
299
300     /* max brightness seems to be 0.003845 */
301
302     c = ref_pixel(f, (int) px, (int) py);
303     point2rgb(f->visdepth, c, &cr, &cg, &cb);
304     rgb_to_hsv(cr, cg, cb, &ch, &cs, &cv);
305
306     /* guestimated - 40 is 18% of 255, so scale this to 0.0 to 0.003845 */
307     if (cv > 0.0006921) {
308         draw_glowpoint(dpy, window, fgc, f, px, py); 
309
310         rid->mycharge = 0.003845;
311     } else {
312         rid->mycharge *= 0.98;
313
314         hsv_to_rgb(ch, cs, rid->mycharge, &cr, &cg, &cb);
315         c = rgb2point(f->visdepth, cr, cg, cb);
316
317         trans_point(px, py, c, 0.5, f);
318
319         XSetForeground(dpy, fgc, c);
320         XDrawPoint(dpy, window, fgc, px, py);
321         XSetForeground(dpy, fgc, f->fgcolor);
322     }
323 }
324
325 inline void render_disc(Display *dpy, Window window, GC fgc, struct field *f, int dnum) {
326     Disc *di = &(f->discs[dnum]);
327     int n, m;
328     float dx, dy, d;
329     float a, p2x, p2y, h, p3ax, p3ay, p3bx, p3by;
330     unsigned long c;
331
332     /* Find intersecting points with all ascending discs */
333     for (n = di->id + 1; n < f->num; n++) {
334         dx = f->discs[n].x - di->x;
335         dy = f->discs[n].y - di->y;
336         d = sqrt(dx * dx + dy * dy);
337
338         /* intersection test */
339         if (d < (f->discs[n].r + di->r)) {
340             /* complete containment test */
341             if (d > abs(f->discs[n].r - di->r)) {
342                 /* find solutions */
343                 a = (di->r * di->r - f->discs[n].r * f->discs[n].r + d * d) / (2 * d);
344                 p2x = di->x + a * (f->discs[n].x - di->x) / d;
345                 p2y = di->y + a * (f->discs[n].y - di->y) / d;
346
347                 h = sqrt(di->r * di->r - a * a);
348
349                 p3ax = p2x + h * (f->discs[n].y - di->y) / d;
350                 p3ay = p2y - h * (f->discs[n].x - di->x) / d;
351
352                 p3bx = p2x - h * (f->discs[n].y - di->y) / d;
353                 p3by = p2y + h * (f->discs[n].x - di->x) / d;
354
355                 /* bounds check */
356                 if ((p3ax < 0) || (p3ax >= f->width) || (p3ay < 0) || (p3ay >= f->height) ||
357                     (p3bx < 0) || (p3bx >= f->width) || (p3by < 0) || (p3by >= f->height))
358                     continue;
359                 
360                 /* p3a and p3b might be identical, ignore this case for now */
361                 /* XPutPixel(f->off_map, p3ax, p3ay, f->fgcolor); */
362                 c = trans_point(p3ax, p3ay, f->fgcolor, 0.75, f);
363                 XSetForeground(dpy, fgc, c);
364                 XDrawPoint(dpy, window, fgc, p3ax, p3ay);
365
366                 /* XPutPixel(f->off_map, p3bx, p3by, f->fgcolor); */
367                 c = trans_point(p3bx, p3by, f->fgcolor, 0.75, f);
368                 XSetForeground(dpy, fgc, c);
369                 XDrawPoint(dpy, window, fgc, p3bx, p3by);
370                 XSetForeground(dpy, fgc, f->fgcolor);
371             }
372         }
373
374     }
375
376     /* Render all the pixel riders */
377     for (m = 0; m < di->numr; m++) {
378         moverender_rider(dpy, window, fgc, f, &(di->pxRiders[m]), 
379                          di->x, di->y, di->r);
380     }
381 }
382
383 char *progclass = "InterMomentary";
384
385 char *defaults[] = {
386     ".background: black",
387     ".foreground: white",
388     "*drawDelay: 30000",
389     "*numDiscs: 85",
390     "*maxRiders: 40",
391     "*maxRadius: 100",
392     0
393 };
394
395 XrmOptionDescRec options[] = {
396     {"-background", ".background", XrmoptionSepArg, 0},
397     {"-foreground", ".foreground", XrmoptionSepArg, 0},
398     {"-draw-delay", ".drawDelay", XrmoptionSepArg, 0},
399     {"-num-discs", ".numDiscs", XrmoptionSepArg, 0},
400     {"-max-riders", ".maxRiders", XrmoptionSepArg, 0},
401     {"-max-radius", ".maxRadius", XrmoptionSepArg, 0},
402     {0, 0, 0, 0}
403 };
404
405 void build_img(Display *dpy, Window window, struct field *f) {
406     if (f->off_alpha) {
407         free(f->off_alpha);
408         f->off_alpha = NULL;
409
410         /* Assume theres also an off pixmap */
411         XFreePixmap(dpy, f->off_map);
412     }
413
414     f->off_alpha = (unsigned long *) xrealloc(f->off_alpha, sizeof(unsigned long) * 
415                                               f->width * f->height);
416
417     memset(f->off_alpha, f->bgcolor, sizeof(unsigned long) * f->width * f->height);
418
419     f->off_map = XCreatePixmap(dpy, window, f->width, f->height, f->visdepth);
420
421 }
422
423 inline void blank_img(Display *dpy, Window window, XWindowAttributes xgwa, GC fgc, struct field *f) {
424     memset(f->off_alpha, f->bgcolor, sizeof(unsigned long) * f->width * f->height);
425
426     XSetForeground(dpy, fgc, f->bgcolor);
427     XFillRectangle(dpy, window, fgc, 0, 0, xgwa.width, xgwa.height);
428     XSetForeground(dpy, fgc, f->fgcolor);
429 }
430
431 void screenhack(Display * dpy, Window window)
432 {
433     struct field *f = init_field();
434
435 #ifdef TIME_ME
436     time_t start_time = time(NULL);
437 #endif
438
439     int draw_delay = 0;
440     int tempx;
441
442     GC fgc, copygc;
443     XGCValues gcv;
444     XWindowAttributes xgwa;
445
446     draw_delay = (get_integer_resource("drawDelay", "Integer"));
447     f->maxrider = (get_integer_resource("maxRiders", "Integer"));
448     f->maxradius = (get_integer_resource("maxRadius", "Integer"));
449     f->initial_discs = (get_integer_resource("numDiscs", "Integer"));
450
451     if (f->initial_discs <= 10) {
452         fprintf(stderr, "%s: Initial discs must be greater than 10\n", progname);
453         return;
454     }
455
456     if (f->maxradius <= 30) {
457         fprintf(stderr, "%s: Max radius must be greater than 30\n", progname);
458         return;
459     }
460
461     if (f->maxrider <= 10) {
462         fprintf(stderr, "%s: Max riders must be greater than 10\n", progname);
463         return;
464     }
465     
466     XGetWindowAttributes(dpy, window, &xgwa);
467
468     f->height = xgwa.height;
469     f->width = xgwa.width;
470     f->visdepth = xgwa.depth;
471  
472     gcv.foreground = get_pixel_resource("foreground", "Foreground",
473                                         dpy, xgwa.colormap);
474     gcv.background = get_pixel_resource("background", "Background",
475                                         dpy, xgwa.colormap);
476     fgc = XCreateGC(dpy, window, GCForeground, &gcv);
477     copygc = XCreateGC(dpy, window, GCForeground, &gcv);
478
479     f->fgcolor = gcv.foreground;
480     f->bgcolor = gcv.background;
481
482     /* Initialize stuff */
483     build_img(dpy, window, f);
484
485     for (tempx = 0; tempx < f->initial_discs; tempx++) {
486         float fx, fy, x, y, r;
487         int bt;
488
489         /* Arrange in anti-collapsing circle */
490         fx = 0.4 * f->width * cos((2 * M_PI) * tempx / f->initial_discs);
491         fy = 0.4 * f->height * sin((2 * M_PI) * tempx / f->initial_discs);
492         x = frand(f->width / 2) + fx;
493         y = frand(f->height / 2) + fy;
494         r = 5 + frand(f->maxradius);
495         bt = 1;
496
497         if ((random() % 100) < 50)
498             bt = -1;
499
500         make_disc(f, x, y, bt * fx / 1000.0, bt * fy / 1000.0, r);
501         
502     }
503     
504     while (1) {
505         if ((f->cycles % 10) == 0) {
506             /* Restart if the window size changes */
507             XGetWindowAttributes(dpy, window, &xgwa);
508
509             if (f->height != xgwa.height || f->width != xgwa.width) {
510                 f->height = xgwa.height;
511                 f->width = xgwa.width;
512                 f->visdepth = xgwa.depth;
513
514                 build_img(dpy, window, f);
515             }
516         }
517
518         blank_img(dpy, f->off_map, xgwa, fgc, f);
519         for (tempx = 0; tempx < f->num; tempx++) {
520             move_disc(f, tempx);
521             render_disc(dpy, f->off_map, fgc, f, tempx);
522         }
523
524         XSetFillStyle(dpy, copygc, FillTiled);
525         XSetTile(dpy, copygc, f->off_map);
526         XFillRectangle(dpy, window, copygc, 0, 0, f->width, f->height);
527
528         f->cycles++;
529
530 /*        XSync(dpy, False); */
531
532         screenhack_handle_events(dpy);
533
534         if (draw_delay)
535             usleep(draw_delay);
536     }
537 }