099b0d1c8c265282c2162ab0bffeaba0374e419f
[xscreensaver] / hacks / ifs.c
1 /* Copyright © Chris Le Sueur and Robby Griffin, 2005-2006
2
3 Permission is hereby granted, free of charge, to any person obtaining
4 a copy of this software and associated documentation files (the
5 "Software"), to deal in the Software without restriction, including
6 without limitation the rights to use, copy, modify, merge, publish,
7 distribute, sublicense, and/or sell copies of the Software, and to
8 permit persons to whom the Software is furnished to do so, subject to
9 the following conditions:
10
11 The above copyright notice and this permission notice shall be included
12 in all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
18 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20 OTHER DEALINGS IN THE SOFTWARE.
21
22 Ultimate thanks go to Massimino Pascal, who created the original
23 xscreensaver hack, and inspired me with it's swirly goodness. This
24 version adds things like variable quality, number of functions and also
25 a groovier colouring mode.
26
27 This version by Chris Le Sueur <thefishface@gmail.com>, Feb 2005
28 Many improvements by Robby Griffin <rmg@terc.edu>, Mar 2006
29 Multi-coloured mode added by Jack Grahl <j.grahl@ucl.ac.uk>, Jan 2007
30 */
31
32 #include <assert.h>
33 #include <unistd.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <math.h>
37
38 #include "screenhack.h"
39
40 #undef countof
41 #define countof(x) (sizeof((x)) / sizeof(*(x)))
42
43 typedef struct {
44   float r, s, tx, ty;   /* Rotation, Scale, Translation X & Y */
45   float ro, rt, rc;     /* Old Rotation, Rotation Target, Rotation Counter */
46   float so, st, sc;     /* Old Scale, Scale Target, Scale Counter */
47   float sa, txa, tya;   /* Scale change, Translation change */
48
49   int ua, ub, utx;      /* Precomputed combined r,s,t values */
50   int uc, ud, uty;      /* Precomputed combined r,s,t values */
51
52 } Lens; 
53
54 struct state {
55   Display *dpy;
56   Window window;
57   GC gc;
58   Drawable backbuffer;
59   XColor *colours;
60   int ncolours;
61   int ccolour;
62   int blackColor, whiteColor;
63
64   int width, widthb, height;
65   int width8, height8;
66   unsigned int *board;
67   XPoint pointbuf[1000];
68   int npoints;
69   int xmin, xmax, ymin, ymax;
70   int x, y;
71
72   int delay;
73
74   int lensnum;
75   Lens *lenses;
76   int length;
77   int mode;
78   Bool recurse;
79   Bool multi;
80   Bool translate, scale, rotate;
81 };
82
83 #define getdot(x,y) (st->board[((y)*st->widthb)+((x)>>5)] &  (1<<((x) & 31)))
84 #define setdot(x,y) (st->board[((y)*st->widthb)+((x)>>5)] |= (1<<((x) & 31)))
85
86 static float
87 myrandom(float up)
88 {
89   return (((float)random() / RAND_MAX) * up);
90 }
91
92 static const char *ifs_defaults [] = {
93   ".background:         Black",
94   "*lensnum:            3",
95   "*fpsSolid:           true",
96   "*length:             9",
97   "*mode:               0",
98   "*colors:             200",
99   "*delay:              20000",
100   "*translate:          True",
101   "*scale:              True",
102   "*rotate:             True",
103   "*recurse:            False",
104   "*multi:              True",
105 # ifdef HAVE_COCOA      /* Don't second-guess Quartz's double-buffering */
106   "*doubleBuffer:       False",
107 #else
108   "*doubleBuffer:       True",
109 #endif
110   0
111 };
112
113 static XrmOptionDescRec ifs_options [] = {
114   { "-detail",          ".length",      XrmoptionSepArg, 0 },
115   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
116   { "-mode",            ".mode",        XrmoptionSepArg, 0 },
117   { "-colors",          ".colors",      XrmoptionSepArg, 0 },
118   { "-functions",       ".lensnum",     XrmoptionSepArg, 0 },
119   { "-no-translate",    ".translate",   XrmoptionNoArg, "False" },
120   { "-no-scale",        ".scale",       XrmoptionNoArg, "False" },
121   { "-no-rotate",       ".rotate",      XrmoptionNoArg, "False" },
122   { "-recurse",         ".recurse",     XrmoptionNoArg, "True" },
123   { "-iterate",         ".recurse",     XrmoptionNoArg, "False" },
124   { "-multi",           ".multi",       XrmoptionNoArg, "True" },
125   { "-no-multi",        ".multi",       XrmoptionNoArg, "False" },
126   { "-db",              ".doubleBuffer",XrmoptionNoArg, "True" },
127   { "-no-db",           ".doubleBuffer",XrmoptionNoArg, "False" },
128   { 0, 0, 0, 0 }
129 };
130
131
132 /* Draw all the queued points on the backbuffer */
133 static void
134 drawpoints(struct state *st)
135 {
136   XDrawPoints(st->dpy, st->backbuffer, st->gc, st->pointbuf, st->npoints,
137               CoordModeOrigin);
138   st->npoints = 0;
139 }
140
141 /* Set a point to be drawn, if it hasn't been already.
142  * Expects coordinates in 256ths of a pixel. */
143 static void
144 sp(struct state *st, int x, int y)
145 {
146   if (x < 0 || x >= st->width8 || y < 0 || y >= st->height8)
147     return;
148
149   x >>= 8;
150   y >>= 8;
151
152   if (getdot(x, y)) return;
153   setdot(x, y);
154
155   if (x < st->xmin) st->xmin = x;
156   if (x > st->xmax) st->xmax = x;
157   if (y < st->ymin) st->ymin = y;
158   if (y > st->ymax) st->ymax = y;
159
160   st->pointbuf[st->npoints].x = x;
161   st->pointbuf[st->npoints].y = y;
162   st->npoints++;
163
164   if (st->npoints >= countof(st->pointbuf)) {
165     drawpoints(st);
166   }
167 }
168
169
170 /* Precompute integer values for matrix multiplication and vector
171  * addition. The matrix multiplication will go like this (see iterate()):
172  *   |x2|     |ua ub|   |x|     |utx|
173  *   |  |  =  |     | * | |  +  |   |
174  *   |y2|     |uc ud|   |y|     |uty|
175  * 
176  * There is an extra factor of 2^10 in these values, and an extra factor of
177  * 2^8 in the coordinates, in order to implement fixed-point arithmetic.
178  */
179 static void
180 lensmatrix(struct state *st, Lens *l)
181 {
182   l->ua = 1024.0 * l->s * cos(l->r);
183   l->ub = -1024.0 * l->s * sin(l->r);
184   l->uc = -l->ub;
185   l->ud = l->ua;
186   l->utx = 131072.0 * st->width * (l->s * (sin(l->r) - cos(l->r))
187                                    + l->tx / 16 + 1);
188   l->uty = -131072.0 * st->height * (l->s * (sin(l->r) + cos(l->r))
189                                      + l->ty / 16 - 1);
190 }
191
192 static void
193 CreateLens(struct state *st,
194            float nr,
195            float ns,
196            float nx,
197            float ny,
198            Lens *newlens)
199 {
200   newlens->sa = newlens->txa = newlens->tya = 0;
201   if (st->rotate) {
202     newlens->r = newlens->ro = newlens->rt = nr;
203     newlens->rc = 1;
204   }
205   else newlens->r = 0;
206
207   if (st->scale) {
208     newlens->s = newlens->so = newlens->st = ns;
209     newlens->sc = 1;
210   }
211   else newlens->s = 0.5;
212
213   newlens->tx = nx;
214   newlens->ty = ny;
215
216   lensmatrix(st, newlens);
217 }
218         
219 static void
220 mutate(struct state *st, Lens *l)
221 {
222   if (st->rotate) {
223     float factor;
224     if(l->rc >= 1) {
225       l->rc = 0;
226       l->ro = l->rt;
227       l->rt = myrandom(4) - 2;
228     }
229     factor = (sin((-M_PI / 2.0) + M_PI * l->rc) + 1.0) / 2.0;
230     l->r = l->ro + (l->rt - l->ro) * factor;
231     l->rc += 0.01;
232   }
233   if (st->scale) {
234     float factor;
235     if (l->sc >= 1) {
236       /* Reset counter, obtain new target value */
237       l->sc = 0;
238       l->so = l->st;
239       l->st = myrandom(2) - 1;
240     }
241     factor = (sin((-M_PI / 2.0) + M_PI * l->sc) + 1.0) / 2.0;
242     /* Take average of old target and new target, using factor to *
243      * weight. It's computed sinusoidally, resulting in smooth,   *
244      * rhythmic transitions.                                      */
245     l->s = l->so + (l->st - l->so) * factor;
246     l->sc += 0.01;
247   }
248   if (st->translate) {
249     l->txa += myrandom(0.004) - 0.002;
250     l->tya += myrandom(0.004) - 0.002;
251     l->tx += l->txa;
252     l->ty += l->tya;
253     if (l->tx > 6) l->txa -= 0.004;
254     if (l->ty > 6) l->tya -= 0.004;
255     if (l->tx < -6) l->txa += 0.004;
256     if (l->ty < -6) l->tya += 0.004;
257     if (l->txa > 0.05 || l->txa < -0.05) l->txa /= 1.7;
258     if (l->tya > 0.05 || l->tya < -0.05) l->tya /= 1.7;
259   }
260   if (st->rotate || st->scale || st->translate) {
261     lensmatrix(st, l);
262   }
263 }
264
265
266 #define STEPX(l,x,y) (((l)->ua * (x) + (l)->ub * (y) + (l)->utx) >> 10)
267 #define STEPY(l,x,y) (((l)->uc * (x) + (l)->ud * (y) + (l)->uty) >> 10)
268 /*#define STEPY(l,x,y) (((l)->ua * (y) - (l)->ub * (x) + (l)->uty) >> 10)*/
269
270 /* Calls itself <lensnum> times - with results from each lens/function.  *
271  * After <length> calls to itself, it stops iterating and draws a point. */
272 static void
273 recurse(struct state *st, int x, int y, int length, int p)
274 {
275   int i;
276   Lens *l;
277
278   if (length == 0) {
279     if (p == 0) 
280       sp(st, x, y);
281     else {
282       l = &st->lenses[p];
283       sp(st, STEPX(l, x, y), STEPY(l, x, y));
284     }
285   }
286   else {
287     for (i = 0; i < st->lensnum; i++) {
288       l = &st->lenses[i];
289       recurse(st, STEPX(l, x, y), STEPY(l, x, y), length - 1, p);
290     }
291   }
292 }
293
294 /* Performs <count> random lens transformations, drawing a point at each
295  * iteration after the first 10.
296  */
297 static void
298 iterate(struct state *st, int count, int p)
299 {
300   int i;
301   Lens *l;
302   int x = st->x;
303   int y = st->y;
304   int tx;
305
306 # define STEP()                              \
307     l = &st->lenses[random() % st->lensnum]; \
308     tx = STEPX(l, x, y);                     \
309     y = STEPY(l, x, y);                      \
310     x = tx
311
312   for (i = 0; i < 10; i++) {
313     STEP();
314   }
315
316   for ( ; i < count; i++) {
317     STEP();
318     if (p == 0)
319       sp(st, x, y);
320     else
321       {
322         l = &st->lenses[p];
323         sp(st, STEPX(l, x, y), STEPY(l, x, y));
324       }
325   }
326
327 # undef STEP
328
329   st->x = x;
330   st->y = y;
331 }
332
333 /* Come on and iterate, iterate, iterate and sing... *
334  * Yeah, this function just calls iterate, mutate,   *
335  * and then draws everything.                        */
336 static unsigned long
337 ifs_draw (Display *dpy, Window window, void *closure)
338 {
339   struct state *st = (struct state *) closure;
340   int i;
341   int xmin = st->xmin, xmax = st->xmax, ymin = st->ymin, ymax = st->ymax;
342   int partcolor, x, y;
343   
344
345   /* erase whatever was drawn in the previous frame */
346   if (xmin <= xmax && ymin <= ymax) {
347     XSetForeground(st->dpy, st->gc, st->blackColor);
348     XFillRectangle(st->dpy, st->backbuffer, st->gc,
349                    xmin, ymin,
350                    xmax - xmin + 1, ymax - ymin + 1);
351     st->xmin = st->width + 1;
352     st->xmax = st->ymax = -1;
353     st->ymin = st->height + 1;
354   }
355
356   st->ccolour++;
357   st->ccolour %= st->ncolours;
358
359   /* calculate and draw points for this frame */
360   x = st->width << 7;
361   y = st->height << 7;
362   
363   if (st->multi) {
364     for (i = 0; i < st->lensnum; i++) {  
365       partcolor = st->ccolour * (i+1);
366       partcolor %= st->ncolours;
367       XSetForeground(st->dpy, st->gc, st->colours[partcolor].pixel);
368       memset(st->board, 0, st->widthb * st->height * sizeof(*st->board));
369       if (st->recurse)   
370         recurse(st, x, y, st->length - 1, i);
371       else
372         iterate(st, pow(st->lensnum, st->length - 1), i);
373       if (st->npoints) 
374         drawpoints(st);
375     }
376   } 
377   else {
378     
379     XSetForeground(st->dpy, st->gc, st->colours[st->ccolour].pixel);
380     memset(st->board, 0, st->widthb * st->height * sizeof(*st->board));
381     if (st->recurse)
382       recurse(st, x, y, st->length, 0);
383     else
384       iterate(st, pow(st->lensnum, st->length), 0);
385     if (st->npoints)
386       drawpoints(st);
387   }
388   
389   /* if we just drew into a buffer, copy the changed area (including
390    * erased area) to screen */
391   if (st->backbuffer != st->window
392       && ((st->xmin <= st->xmax && st->ymin <= st->ymax)
393           || (xmin <= xmax && ymin <= ymax))) {
394     if (st->xmin < xmin) xmin = st->xmin;
395     if (st->xmax > xmax) xmax = st->xmax;
396     if (st->ymin < ymin) ymin = st->ymin;
397     if (st->ymax > ymax) ymax = st->ymax;
398     XCopyArea(st->dpy, st->backbuffer, st->window, st->gc,
399               xmin, ymin,
400               xmax - xmin + 1, ymax - ymin + 1,
401               xmin, ymin);
402   }
403
404   for(i = 0; i < st->lensnum; i++) {
405     mutate(st, &st->lenses[i]);
406   }
407
408   return st->delay;
409 }
410
411 static void
412 ifs_reshape (Display *, Window, void *, unsigned int, unsigned int);
413
414 static void *
415 ifs_init (Display *d_arg, Window w_arg)
416 {
417   struct state *st = (struct state *) calloc (1, sizeof(*st));
418   int i;
419   XWindowAttributes xgwa;
420         
421   /* Initialise all this X shizzle */
422   st->dpy = d_arg;
423   st->window = w_arg;
424
425   st->blackColor = BlackPixel(st->dpy, DefaultScreen(st->dpy));
426   st->whiteColor = WhitePixel(st->dpy, DefaultScreen(st->dpy));
427   st->gc = XCreateGC(st->dpy, st->window, 0, NULL);
428
429   XGetWindowAttributes (st->dpy, st->window, &xgwa);
430   ifs_reshape(st->dpy, st->window, st, xgwa.width, xgwa.height);
431         
432   st->ncolours = get_integer_resource(st->dpy, "colors", "Colors");
433   if (st->ncolours < st->lensnum)
434     st->ncolours = st->lensnum;
435   if (st->colours) free(st->colours);
436   st->colours = (XColor *)calloc(st->ncolours, sizeof(XColor));
437   if (!st->colours) exit(1);
438   make_smooth_colormap (st->dpy, xgwa.visual, xgwa.colormap, 
439                         st->colours, &st->ncolours,
440                         True, 0, False);
441
442   /* Initialize IFS data */
443  
444   st->delay = get_integer_resource(st->dpy, "delay", "Delay");
445   st->length = get_integer_resource(st->dpy, "length", "Detail");
446   if (st->length < 0) st->length = 0;
447   st->mode = get_integer_resource(st->dpy, "mode", "Mode");
448
449   st->rotate    = get_boolean_resource(st->dpy, "rotate", "Boolean");
450   st->scale     = get_boolean_resource(st->dpy, "scale", "Boolean");
451   st->translate = get_boolean_resource(st->dpy, "translate", "Boolean");
452   st->recurse = get_boolean_resource(st->dpy, "recurse", "Boolean");
453   st->multi = get_boolean_resource(st->dpy, "multi", "Boolean");
454
455   st->lensnum = get_integer_resource(st->dpy, "lensnum", "Functions");
456   if (st->lenses) free (st->lenses);
457   st->lenses = (Lens *)calloc(st->lensnum, sizeof(Lens));
458   if (!st->lenses) exit(1);
459   for (i = 0; i < st->lensnum; i++) {
460     CreateLens(st,
461                myrandom(1)-0.5,
462                myrandom(1),
463                myrandom(4)-2,
464                myrandom(4)+2,
465                &st->lenses[i]);
466   }
467
468   return st;
469 }
470
471 static void
472 ifs_reshape (Display *dpy, Window window, void *closure, 
473              unsigned int w, unsigned int h)
474 {
475   struct state *st = (struct state *)closure;
476   XWindowAttributes xgwa;
477
478   /* oh well, we need the screen depth anyway */
479   XGetWindowAttributes (st->dpy, st->window, &xgwa);
480
481   st->width = xgwa.width;
482   st->widthb = ((xgwa.width + 31) >> 5);
483   st->height = xgwa.height;
484   st->width8 = xgwa.width << 8;
485   st->height8 = xgwa.height << 8;
486
487   if (!st->xmax && !st->ymax && !st->xmin && !st->ymin) {
488     st->xmin = xgwa.width + 1;
489     st->xmax = st->ymax = -1;
490     st->ymin = xgwa.height + 1;
491   }
492
493   if (st->backbuffer != None && st->backbuffer != st->window) {
494     XFreePixmap(st->dpy, st->backbuffer);
495     st->backbuffer = None;
496   }
497
498   if (get_boolean_resource (st->dpy, "doubleBuffer", "Boolean")) {
499     st->backbuffer = XCreatePixmap(st->dpy, st->window, st->width, st->height, xgwa.depth);
500     XSetForeground(st->dpy, st->gc, st->blackColor);
501     XFillRectangle(st->dpy, st->backbuffer, st->gc,
502                    0, 0, st->width, st->height);
503   } else {
504     st->backbuffer = st->window;
505     XClearWindow(st->dpy, st->window);
506   }
507
508   if (st->board) free(st->board);
509   st->board = (unsigned int *)calloc(st->widthb * st->height, sizeof(unsigned int));
510   if (!st->board) exit(1);
511 }
512
513 static Bool
514 ifs_event (Display *dpy, Window window, void *closure, XEvent *event)
515 {
516   return False;
517 }
518
519 static void
520 ifs_free (Display *dpy, Window window, void *closure)
521 {
522   struct state *st = (struct state *) closure;
523
524   if (st->board) free(st->board);
525   if (st->lenses) free(st->lenses);
526   if (st->colours) free(st->colours);
527   if (st->backbuffer != None && st->backbuffer != st->window)
528     XFreePixmap(st->dpy, st->backbuffer);
529   free(st);
530 }
531
532 XSCREENSAVER_MODULE ("IFS", ifs)