http://www.jwz.org/xscreensaver/xscreensaver-5.07.tar.gz
[xscreensaver] / hacks / julia.c
1 /* -*- Mode: C; tab-width: 4 -*-
2  * julia --- continuously varying Julia set.
3  */
4 #if 0
5 static const char sccsid[] = "@(#)julia.c       4.03 97/04/10 xlockmore";
6 #endif
7
8 /* Copyright (c) 1995 Sean McCullough <bankshot@mailhost.nmt.edu>.
9  *
10  * Permission to use, copy, modify, and distribute this software and its
11  * documentation for any purpose and without fee is hereby granted,
12  * provided that the above copyright notice appear in all copies and that
13  * both that copyright notice and this permission notice appear in
14  * supporting documentation.
15  *
16  * This file is provided AS IS with no warranties of any kind.  The author
17  * shall have no liability with respect to the infringement of copyrights,
18  * trade secrets or any patents by this file or any part thereof.  In no
19  * event will the author be liable for any lost revenue or profits or
20  * other special, indirect and consequential damages.
21  *
22  * Revision History:
23  * 10-Jun-06: j.grahl@ucl.ac.uk: tweaked functions for parameter of Julia set
24  * 28-May-97: jwz@jwz.org: added interactive frobbing with the mouse.
25  * 10-May-97: jwz@jwz.org: turned into a standalone program.
26  * 02-Dec-95: snagged boilerplate from hop.c
27  *           used ifs {w0 = sqrt(x-c), w1 = -sqrt(x-c)} with random iteration 
28  *           to plot the julia set, and sinusoidially varied parameter for set 
29  *           and plotted parameter with a circle.
30  */
31
32 /*-
33  * One thing to note is that batchcount is the *depth* of the search tree,
34  * so the number of points computed is 2^batchcount - 1.  I use 8 or 9
35  * on a dx266 and it looks okay.  The sinusoidal variation of the parameter
36  * might not be as interesting as it could, but it still gives an idea of
37  * the effect of the parameter.
38  */
39
40 #ifdef STANDALONE
41 # define DEFAULTS       "*count:                1000  \n"                       \
42                                         "*cycles:               20    \n"                       \
43                                         "*delay:                10000 \n"                       \
44                                         "*ncolors:              200   \n" \
45                                         "*fpsSolid:             true   \n" \
46
47 # define UNIFORM_COLORS
48 # define reshape_julia 0
49 # define julia_handle_event 0
50 # include "xlockmore.h"                         /* in xscreensaver distribution */
51 #else  /* !STANDALONE */
52 # include "xlock.h"                                     /* in xlockmore distribution */
53 #endif /* !STANDALONE */
54
55
56 static Bool track_p;
57
58 #define DEF_MOUSE "False"
59
60 static XrmOptionDescRec opts[] =
61 {
62         {"-mouse", ".julia.mouse", XrmoptionNoArg, "on"},
63         {"+mouse", ".julia.mouse", XrmoptionNoArg, "off"},
64 };
65 static argtype vars[] =
66 {
67         {&track_p, "mouse", "Mouse", DEF_MOUSE, t_Bool},
68 };
69 static OptionStruct desc[] =
70 {
71         {"-/+mouse", "turn on/off mouse tracking"},
72 };
73
74 ENTRYPOINT ModeSpecOpt julia_opts = { 2, opts, 1, vars, desc };
75
76
77 #define numpoints ((0x2<<jp->depth)-1)
78
79 typedef struct {
80         int         centerx;
81         int         centery;    /* center of the screen */
82         double      cr;
83         double      ci;         /* julia params */
84         int         depth;
85         int         inc;
86         int         circsize;
87         int         erase;
88         int         pix;
89         long        itree;
90         int         buffer;
91         int         nbuffers;
92         int         redrawing, redrawpos;
93         Pixmap      pixmap;
94 #ifndef HAVE_COCOA
95         Cursor      cursor;
96 #endif
97         GC          stippledGC;
98         XPoint    **pointBuffer;        /* pointer for XDrawPoints */
99
100 } juliastruct;
101
102 static juliastruct *julias = NULL;
103
104 /* How many segments to draw per cycle when redrawing */
105 #define REDRAWSTEP 3
106
107 static void
108 apply(juliastruct * jp, register double xr, register double xi, int d)
109 {
110         double      theta, r;
111
112         jp->pointBuffer[jp->buffer][jp->itree].x =
113                 (int) (0.5 * xr * jp->centerx + jp->centerx);
114         jp->pointBuffer[jp->buffer][jp->itree].y =
115                 (int) (0.5 * xi * jp->centery + jp->centery);
116         jp->itree++;
117
118         if (d > 0) {
119                 xi -= jp->ci;
120                 xr -= jp->cr;
121
122 /* Avoid atan2: DOMAIN error message */
123                 if (xi == 0.0 && xr == 0.0)
124                         theta = 0.0;
125                 else
126                         theta = atan2(xi, xr) / 2.0;
127
128                 /*r = pow(xi * xi + xr * xr, 0.25); */
129                 r = sqrt(sqrt(xi * xi + xr * xr));      /* 3 times faster */
130
131                 xr = r * cos(theta);
132                 xi = r * sin(theta);
133
134                 d--;
135                 apply(jp, xr, xi, d);
136                 apply(jp, -xr, -xi, d);
137         }
138 }
139
140 static void
141 incr(ModeInfo * mi, juliastruct * jp)
142 {
143         int cx, cy;
144
145         if (track_p)
146           {
147                 Window r, c;
148                 int rx, ry;
149                 unsigned int m;
150                 XQueryPointer(MI_DISPLAY(mi), MI_WINDOW(mi),
151                                           &r, &c, &rx, &ry, &cx, &cy, &m);
152                 if (cx <= 0 || cy <= 0 ||
153                         cx >= MI_WIN_WIDTH(mi) || cy >= MI_WIN_HEIGHT(mi))
154                   goto NOTRACK;
155           }
156
157         if (track_p)
158           {
159                 jp->cr = ((double) (cx + 2 - jp->centerx)) * 2 / jp->centerx;
160                 jp->ci = ((double) (cy + 2 - jp->centery)) * 2 / jp->centery;
161           }
162         else
163           {
164           NOTRACK:
165 #if 0
166                 jp->cr = 1.5 * (sin(M_PI * (jp->inc / 300.0)) *
167                                                 sin(jp->inc * M_PI / 200.0));
168                 jp->ci = 1.5 * (cos(M_PI * (jp->inc / 300.0)) *
169                                                 cos(jp->inc * M_PI / 200.0));
170
171                 jp->cr += 0.5 * cos(M_PI * jp->inc / 400.0);
172                 jp->ci += 0.5 * sin(M_PI * jp->inc / 400.0);
173 #else
174         jp->cr = 1.5 * (sin(M_PI * (jp->inc / 290.0)) *
175                         sin(jp->inc * M_PI / 210.0));
176         jp->ci = 1.5 * (cos(M_PI * (jp->inc / 310.0)) *
177                         cos(jp->inc * M_PI / 190.0));
178
179         jp->cr += 0.5 * cos(M_PI * jp->inc / 395.0);
180         jp->ci += 0.5 * sin(M_PI * jp->inc / 410.0);
181 #endif
182           }
183 }
184
185 ENTRYPOINT void
186 init_julia(ModeInfo * mi)
187 {
188         Display    *display = MI_DISPLAY(mi);
189         Window      window = MI_WINDOW(mi);
190         juliastruct *jp;
191         XGCValues   gcv;
192         int         i;
193
194         if (julias == NULL) {
195                 if ((julias = (juliastruct *) calloc(MI_NUM_SCREENS(mi),
196                                               sizeof (juliastruct))) == NULL)
197                         return;
198         }
199         jp = &julias[MI_SCREEN(mi)];
200
201         jp->centerx = MI_WIN_WIDTH(mi) / 2;
202         jp->centery = MI_WIN_HEIGHT(mi) / 2;
203
204         jp->depth = MI_BATCHCOUNT(mi);
205         if (jp->depth > 10)
206                 jp->depth = 10;
207
208
209 #ifndef HAVE_COCOA
210         if (track_p && !jp->cursor)
211           {
212                 Pixmap bit;
213                 XColor black;
214                 black.red = black.green = black.blue = 0;
215                 black.flags = DoRed|DoGreen|DoBlue;
216                 bit = XCreatePixmapFromBitmapData (display, window, "\000", 1, 1,
217                                                                                    MI_WIN_BLACK_PIXEL(mi),
218                                                                                    MI_WIN_BLACK_PIXEL(mi), 1);
219                 jp->cursor = XCreatePixmapCursor (display, bit, bit, &black, &black,
220                                                                                   0, 0);
221                 XFreePixmap (display, bit);
222           }
223 #endif /* HAVE_COCOA */
224
225         if (jp->pixmap != None &&
226             jp->circsize != (MIN(jp->centerx, jp->centery) / 60) * 2 + 1) {
227                 XFreePixmap(display, jp->pixmap);
228                 jp->pixmap = None;
229         }
230         if (jp->pixmap == None) {
231                 GC          fg_gc = None, bg_gc = None;
232
233                 jp->circsize = (MIN(jp->centerx, jp->centery) / 96) * 2 + 1;
234                 jp->pixmap = XCreatePixmap(display, window, jp->circsize, jp->circsize, 1);
235                 gcv.foreground = 1;
236                 fg_gc = XCreateGC(display, jp->pixmap, GCForeground, &gcv);
237                 gcv.foreground = 0;
238                 bg_gc = XCreateGC(display, jp->pixmap, GCForeground, &gcv);
239                 XFillRectangle(display, jp->pixmap, bg_gc,
240                                0, 0, jp->circsize, jp->circsize);
241                 if (jp->circsize < 2)
242                         XDrawPoint(display, jp->pixmap, fg_gc, 0, 0);
243                 else
244                         XFillArc(display, jp->pixmap, fg_gc,
245                                  0, 0, jp->circsize, jp->circsize, 0, 23040);
246                 if (fg_gc != None)
247                         XFreeGC(display, fg_gc);
248                 if (bg_gc != None)
249                         XFreeGC(display, bg_gc);
250         }
251
252 #ifndef HAVE_COCOA
253         if (MI_WIN_IS_INROOT(mi))
254           ;
255         else if (jp->circsize > 0)
256           XDefineCursor (display, window, jp->cursor);
257         else
258           XUndefineCursor (display, window);
259 #endif /* HAVE_COCOA */
260
261         if (!jp->stippledGC) {
262                 gcv.foreground = MI_WIN_BLACK_PIXEL(mi);
263                 gcv.background = MI_WIN_BLACK_PIXEL(mi);
264                 if ((jp->stippledGC = XCreateGC(display, window,
265                                  GCForeground | GCBackground, &gcv)) == None)
266                         return;
267         }
268         if (MI_NPIXELS(mi) > 2)
269                 jp->pix = NRAND(MI_NPIXELS(mi));
270         jp->inc = ((LRAND() & 1) * 2 - 1) * NRAND(200);
271         jp->nbuffers = (MI_CYCLES(mi) + 1);
272         if (!jp->pointBuffer)
273                 jp->pointBuffer = (XPoint **) calloc(jp->nbuffers, sizeof (XPoint *));
274         for (i = 0; i < jp->nbuffers; ++i)
275                 if (jp->pointBuffer[i])
276                         (void) memset((char *) jp->pointBuffer[i], 0,
277                                       numpoints * sizeof (XPoint));
278                 else
279                         jp->pointBuffer[i] = (XPoint *) calloc(numpoints, sizeof (XPoint));
280         jp->buffer = 0;
281         jp->redrawing = 0;
282         jp->erase = 0;
283         XClearWindow(display, window);
284 }
285
286
287 /* hack: moved here by jwz. */
288 #define ERASE_IMAGE(d,w,g,x,y,xl,yl,xs,ys) \
289 if (yl<y) \
290 (y<yl+ys)?XFillRectangle(d,w,g,xl,yl,xs,y-yl): \
291 XFillRectangle(d,w,g,xl,yl,xs,ys); \
292 else if (yl>y) \
293 (y>yl-ys)?XFillRectangle(d,w,g,xl,y+ys,xs,yl-y): \
294 XFillRectangle(d,w,g,xl,yl,xs,ys); \
295 if (xl<x) \
296 (x<xl+xs)?XFillRectangle(d,w,g,xl,yl,x-xl,ys): \
297 XFillRectangle(d,w,g,xl,yl,xs,ys); \
298 else if (xl>x) \
299 (x>xl-xs)?XFillRectangle(d,w,g,x+xs,yl,xl-x,ys): \
300 XFillRectangle(d,w,g,xl,yl,xs,ys)
301
302
303 ENTRYPOINT void
304 draw_julia (ModeInfo * mi)
305 {
306         Display    *display = MI_DISPLAY(mi);
307         Window      window = MI_WINDOW(mi);
308         GC          gc = MI_GC(mi);
309         juliastruct *jp = &julias[MI_SCREEN(mi)];
310         double      r, theta;
311         register double xr = 0.0, xi = 0.0;
312         int         k = 64, rnd = 0, i, j;
313         XPoint     *xp = jp->pointBuffer[jp->buffer], old_circle, new_circle;
314
315         old_circle.x = (int) (jp->centerx * jp->cr / 2) + jp->centerx - 2;
316         old_circle.y = (int) (jp->centery * jp->ci / 2) + jp->centery - 2;
317         incr(mi, jp);
318         new_circle.x = (int) (jp->centerx * jp->cr / 2) + jp->centerx - 2;
319         new_circle.y = (int) (jp->centery * jp->ci / 2) + jp->centery - 2;
320         XSetForeground(display, gc, MI_WIN_BLACK_PIXEL(mi));
321         ERASE_IMAGE(display, window, gc, new_circle.x, new_circle.y,
322                     old_circle.x, old_circle.y, jp->circsize, jp->circsize);
323         /* draw a circle at the c-parameter so you can see it's effect on the
324            structure of the julia set */
325         XSetForeground(display, jp->stippledGC, MI_WIN_WHITE_PIXEL(mi));
326 #ifndef HAVE_COCOA
327         XSetTSOrigin(display, jp->stippledGC, new_circle.x, new_circle.y);
328         XSetStipple(display, jp->stippledGC, jp->pixmap);
329         XSetFillStyle(display, jp->stippledGC, FillOpaqueStippled);
330 #endif /* HAVE_COCOA */
331         XFillRectangle(display, window, jp->stippledGC, new_circle.x, new_circle.y,
332                        jp->circsize, jp->circsize);
333         if (jp->erase == 1) {
334                 XDrawPoints(display, window, gc,
335                     jp->pointBuffer[jp->buffer], numpoints, CoordModeOrigin);
336         }
337         jp->inc++;
338         if (MI_NPIXELS(mi) > 2) {
339                 XSetForeground(display, gc, MI_PIXEL(mi, jp->pix));
340                 if (++jp->pix >= MI_NPIXELS(mi))
341                         jp->pix = 0;
342         } else
343                 XSetForeground(display, gc, MI_WIN_WHITE_PIXEL(mi));
344         while (k--) {
345
346                 /* save calls to LRAND by using bit shifts over and over on the same
347                    int for 32 iterations, then get a new random int */
348                 if (!(k % 32))
349                         rnd = LRAND();
350
351                 /* complex sqrt: x^0.5 = radius^0.5*(cos(theta/2) + i*sin(theta/2)) */
352
353                 xi -= jp->ci;
354                 xr -= jp->cr;
355
356                 /* Avoid atan2: DOMAIN error message */
357                 if (xi == 0.0 && xr == 0.0)
358                         theta = 0.0;
359                 else
360                         theta = atan2(xi, xr) / 2.0;
361
362                 /*r = pow(xi * xi + xr * xr, 0.25); */
363                 r = sqrt(sqrt(xi * xi + xr * xr));      /* 3 times faster */
364
365                 xr = r * cos(theta);
366                 xi = r * sin(theta);
367
368                 if ((rnd >> (k % 32)) & 0x1) {
369                         xi = -xi;
370                         xr = -xr;
371                 }
372                 xp->x = jp->centerx + (int) ((jp->centerx >> 1) * xr);
373                 xp->y = jp->centery + (int) ((jp->centery >> 1) * xi);
374                 xp++;
375         }
376
377         jp->itree = 0;
378         apply(jp, xr, xi, jp->depth);
379
380         XDrawPoints(display, window, gc,
381                     jp->pointBuffer[jp->buffer], numpoints, CoordModeOrigin);
382
383         jp->buffer++;
384         if (jp->buffer > jp->nbuffers - 1) {
385                 jp->buffer -= jp->nbuffers;
386                 jp->erase = 1;
387         }
388         if (jp->redrawing) {
389                 for (i = 0; i < REDRAWSTEP; i++) {
390                         j = (jp->buffer - jp->redrawpos + jp->nbuffers) % jp->nbuffers;
391                         XDrawPoints(display, window, gc,
392                              jp->pointBuffer[j], numpoints, CoordModeOrigin);
393
394                         if (++(jp->redrawpos) >= jp->nbuffers) {
395                                 jp->redrawing = 0;
396                                 break;
397                         }
398                 }
399         }
400 }
401
402 ENTRYPOINT void
403 release_julia (ModeInfo * mi)
404 {
405         if (julias != NULL) {
406                 int         screen;
407
408                 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
409                         Display    *display = MI_DISPLAY(mi);
410                         juliastruct *jp = &julias[screen];
411                         int         buffer;
412
413                         if (jp->pointBuffer) {
414                                 for (buffer = 0; buffer < jp->nbuffers; buffer++)
415                                         if (jp->pointBuffer[buffer])
416                                                 (void) free((void *) jp->pointBuffer[buffer]);
417                                 (void) free((void *) jp->pointBuffer);
418                         }
419                         if (jp->stippledGC != None)
420                                 XFreeGC(display, jp->stippledGC);
421                         if (jp->pixmap != None)
422                                 XFreePixmap(display, jp->pixmap);
423 #ifndef HAVE_COCOA
424                         if (jp->cursor)
425                           XFreeCursor (display, jp->cursor);
426 #endif
427                 }
428                 (void) free((void *) julias);
429                 julias = NULL;
430         }
431 }
432
433 ENTRYPOINT void
434 refresh_julia (ModeInfo * mi)
435 {
436         juliastruct *jp = &julias[MI_SCREEN(mi)];
437
438         jp->redrawing = 1;
439         jp->redrawpos = 0;
440 }
441
442 XSCREENSAVER_MODULE ("Julia", julia)