From http://www.jwz.org/xscreensaver/xscreensaver-5.22.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                                         "*ignoreRotation: True   \n" \
47
48 # define UNIFORM_COLORS
49 # include "xlockmore.h"                         /* in xscreensaver distribution */
50 #else  /* !STANDALONE */
51 # include "xlock.h"                                     /* in xlockmore distribution */
52 #endif /* !STANDALONE */
53
54
55 #define DEF_MOUSE "False"
56
57 ENTRYPOINT ModeSpecOpt julia_opts = { 0, };
58
59
60 #define numpoints ((0x2<<jp->depth)-1)
61
62 typedef struct {
63         int         centerx;
64         int         centery;    /* center of the screen */
65         double      cr;
66         double      ci;         /* julia params */
67         int         depth;
68         int         inc;
69         int         circsize;
70         int         erase;
71         int         pix;
72         long        itree;
73         int         buffer;
74         int         nbuffers;
75         int         redrawing, redrawpos;
76         Pixmap      pixmap;
77 #ifndef HAVE_COCOA
78         Cursor      cursor;
79 #endif
80         GC          stippledGC;
81         XPoint    **pointBuffer;        /* pointer for XDrawPoints */
82     Bool        button_down_p;
83     int         mouse_x, mouse_y;
84
85 } juliastruct;
86
87 static juliastruct *julias = NULL;
88
89 /* How many segments to draw per cycle when redrawing */
90 #define REDRAWSTEP 3
91
92 static void
93 apply(juliastruct * jp, register double xr, register double xi, int d)
94 {
95         double      theta, r;
96
97         jp->pointBuffer[jp->buffer][jp->itree].x =
98                 (int) (0.5 * xr * jp->centerx + jp->centerx);
99         jp->pointBuffer[jp->buffer][jp->itree].y =
100                 (int) (0.5 * xi * jp->centery + jp->centery);
101         jp->itree++;
102
103         if (d > 0) {
104                 xi -= jp->ci;
105                 xr -= jp->cr;
106
107 /* Avoid atan2: DOMAIN error message */
108                 if (xi == 0.0 && xr == 0.0)
109                         theta = 0.0;
110                 else
111                         theta = atan2(xi, xr) / 2.0;
112
113                 /*r = pow(xi * xi + xr * xr, 0.25); */
114                 r = sqrt(sqrt(xi * xi + xr * xr));      /* 3 times faster */
115
116                 xr = r * cos(theta);
117                 xi = r * sin(theta);
118
119                 d--;
120                 apply(jp, xr, xi, d);
121                 apply(jp, -xr, -xi, d);
122         }
123 }
124
125 static void
126 incr(ModeInfo * mi, juliastruct * jp)
127 {
128         if (jp->button_down_p)
129           {
130                 jp->cr = ((double) (jp->mouse_x + 2 - jp->centerx)) * 2 / jp->centerx;
131                 jp->ci = ((double) (jp->mouse_y + 2 - jp->centery)) * 2 / jp->centery;
132           }
133         else
134           {
135 #if 0
136                 jp->cr = 1.5 * (sin(M_PI * (jp->inc / 300.0)) *
137                                                 sin(jp->inc * M_PI / 200.0));
138                 jp->ci = 1.5 * (cos(M_PI * (jp->inc / 300.0)) *
139                                                 cos(jp->inc * M_PI / 200.0));
140
141                 jp->cr += 0.5 * cos(M_PI * jp->inc / 400.0);
142                 jp->ci += 0.5 * sin(M_PI * jp->inc / 400.0);
143 #else
144         jp->cr = 1.5 * (sin(M_PI * (jp->inc / 290.0)) *
145                         sin(jp->inc * M_PI / 210.0));
146         jp->ci = 1.5 * (cos(M_PI * (jp->inc / 310.0)) *
147                         cos(jp->inc * M_PI / 190.0));
148
149         jp->cr += 0.5 * cos(M_PI * jp->inc / 395.0);
150         jp->ci += 0.5 * sin(M_PI * jp->inc / 410.0);
151 #endif
152           }
153 }
154
155 ENTRYPOINT void
156 init_julia(ModeInfo * mi)
157 {
158         Display    *display = MI_DISPLAY(mi);
159         Window      window = MI_WINDOW(mi);
160         juliastruct *jp;
161         XGCValues   gcv;
162         int         i;
163
164         if (julias == NULL) {
165                 if ((julias = (juliastruct *) calloc(MI_NUM_SCREENS(mi),
166                                               sizeof (juliastruct))) == NULL)
167                         return;
168         }
169         jp = &julias[MI_SCREEN(mi)];
170
171         jp->centerx = MI_WIN_WIDTH(mi) / 2;
172         jp->centery = MI_WIN_HEIGHT(mi) / 2;
173
174         jp->depth = MI_BATCHCOUNT(mi);
175         if (jp->depth > 10)
176                 jp->depth = 10;
177
178
179 #ifndef HAVE_COCOA
180         if (jp->button_down_p && !jp->cursor && !jp->cursor)
181           {
182                 Pixmap bit;
183                 XColor black;
184                 black.red = black.green = black.blue = 0;
185                 black.flags = DoRed|DoGreen|DoBlue;
186                 bit = XCreatePixmapFromBitmapData (display, window, "\000", 1, 1,
187                                                                                    MI_WIN_BLACK_PIXEL(mi),
188                                                                                    MI_WIN_BLACK_PIXEL(mi), 1);
189                 jp->cursor = XCreatePixmapCursor (display, bit, bit, &black, &black,
190                                                                                   0, 0);
191                 XFreePixmap (display, bit);
192           }
193 #endif /* HAVE_COCOA */
194
195         if (jp->pixmap != None &&
196             jp->circsize != (MIN(jp->centerx, jp->centery) / 60) * 2 + 1) {
197                 XFreePixmap(display, jp->pixmap);
198                 jp->pixmap = None;
199         }
200         if (jp->pixmap == None) {
201                 GC          fg_gc = None, bg_gc = None;
202
203                 jp->circsize = MAX(8, (MIN(jp->centerx, jp->centery) / 96) * 2 + 1);
204                 jp->pixmap = XCreatePixmap(display, window, jp->circsize, jp->circsize, 1);
205                 gcv.foreground = 1;
206                 fg_gc = XCreateGC(display, jp->pixmap, GCForeground, &gcv);
207                 gcv.foreground = 0;
208                 bg_gc = XCreateGC(display, jp->pixmap, GCForeground, &gcv);
209                 XFillRectangle(display, jp->pixmap, bg_gc,
210                                0, 0, jp->circsize, jp->circsize);
211                 if (jp->circsize < 2)
212                         XDrawPoint(display, jp->pixmap, fg_gc, 0, 0);
213                 else
214                         XFillArc(display, jp->pixmap, fg_gc,
215                                  0, 0, jp->circsize, jp->circsize, 0, 23040);
216                 if (fg_gc != None)
217                         XFreeGC(display, fg_gc);
218                 if (bg_gc != None)
219                         XFreeGC(display, bg_gc);
220         }
221
222 #ifndef HAVE_COCOA
223         if (MI_WIN_IS_INROOT(mi))
224           ;
225         else if (jp->circsize > 0)
226           XDefineCursor (display, window, jp->cursor);
227         else
228           XUndefineCursor (display, window);
229 #endif /* HAVE_COCOA */
230
231         if (!jp->stippledGC) {
232                 gcv.foreground = MI_WIN_BLACK_PIXEL(mi);
233                 gcv.background = MI_WIN_BLACK_PIXEL(mi);
234                 if ((jp->stippledGC = XCreateGC(display, window,
235                                  GCForeground | GCBackground, &gcv)) == None)
236                         return;
237         }
238         if (MI_NPIXELS(mi) > 2)
239                 jp->pix = NRAND(MI_NPIXELS(mi));
240         jp->inc = ((LRAND() & 1) * 2 - 1) * NRAND(200);
241         jp->nbuffers = (MI_CYCLES(mi) + 1);
242         if (!jp->pointBuffer)
243                 jp->pointBuffer = (XPoint **) calloc(jp->nbuffers, sizeof (XPoint *));
244         for (i = 0; i < jp->nbuffers; ++i)
245                 if (jp->pointBuffer[i])
246                         (void) memset((char *) jp->pointBuffer[i], 0,
247                                       numpoints * sizeof (XPoint));
248                 else
249                         jp->pointBuffer[i] = (XPoint *) calloc(numpoints, sizeof (XPoint));
250         jp->buffer = 0;
251         jp->redrawing = 0;
252         jp->erase = 0;
253         XClearWindow(display, window);
254 }
255
256
257 static void
258 reshape_julia (ModeInfo *mi, int w, int h)
259 {
260   init_julia (mi);
261 }
262
263
264 ENTRYPOINT Bool
265 julia_handle_event (ModeInfo *mi, XEvent *event)
266 {
267   juliastruct *jp = &julias[MI_SCREEN(mi)];
268
269   if (event->xany.type == ButtonPress &&
270       event->xbutton.button == Button1)
271     {
272       jp->button_down_p = True;
273       jp->mouse_x = event->xbutton.x;
274       jp->mouse_y = event->xbutton.y;
275       return True;
276     }
277   else if (event->xany.type == ButtonRelease &&
278            event->xbutton.button == Button1)
279     {
280       jp->button_down_p = False;
281       return True;
282     }
283   else if (event->xany.type == MotionNotify && jp->button_down_p)
284     {
285       jp->mouse_x = event->xmotion.x;
286       jp->mouse_y = event->xmotion.y;
287       return True;
288     }
289
290   return False;
291 }
292
293
294
295 /* hack: moved here by jwz. */
296 #define ERASE_IMAGE(d,w,g,x,y,xl,yl,xs,ys) \
297 if (yl<y) \
298 (y<yl+ys)?XFillRectangle(d,w,g,xl,yl,xs,y-yl): \
299 XFillRectangle(d,w,g,xl,yl,xs,ys); \
300 else if (yl>y) \
301 (y>yl-ys)?XFillRectangle(d,w,g,xl,y+ys,xs,yl-y): \
302 XFillRectangle(d,w,g,xl,yl,xs,ys); \
303 if (xl<x) \
304 (x<xl+xs)?XFillRectangle(d,w,g,xl,yl,x-xl,ys): \
305 XFillRectangle(d,w,g,xl,yl,xs,ys); \
306 else if (xl>x) \
307 (x>xl-xs)?XFillRectangle(d,w,g,x+xs,yl,xl-x,ys): \
308 XFillRectangle(d,w,g,xl,yl,xs,ys)
309
310
311 ENTRYPOINT void
312 draw_julia (ModeInfo * mi)
313 {
314         Display    *display = MI_DISPLAY(mi);
315         Window      window = MI_WINDOW(mi);
316         GC          gc = MI_GC(mi);
317         juliastruct *jp = &julias[MI_SCREEN(mi)];
318         double      r, theta;
319         register double xr = 0.0, xi = 0.0;
320         int         k = 64, rnd = 0, i, j;
321         XPoint     *xp = jp->pointBuffer[jp->buffer], old_circle, new_circle;
322
323         old_circle.x = (int) (jp->centerx * jp->cr / 2) + jp->centerx - 2;
324         old_circle.y = (int) (jp->centery * jp->ci / 2) + jp->centery - 2;
325         incr(mi, jp);
326         new_circle.x = (int) (jp->centerx * jp->cr / 2) + jp->centerx - 2;
327         new_circle.y = (int) (jp->centery * jp->ci / 2) + jp->centery - 2;
328         XSetForeground(display, gc, MI_WIN_BLACK_PIXEL(mi));
329         XFillArc(display, window, gc, 
330              old_circle.x-jp->circsize/2-2,
331              old_circle.y-jp->circsize/2-2,
332              jp->circsize+4, jp->circsize+4,
333              0, 360*64);
334         /* draw a circle at the c-parameter so you can see it's effect on the
335            structure of the julia set */
336         XSetForeground(display, jp->stippledGC, MI_WIN_WHITE_PIXEL(mi));
337 #ifndef HAVE_COCOA
338         XSetTSOrigin(display, jp->stippledGC, new_circle.x, new_circle.y);
339         XSetStipple(display, jp->stippledGC, jp->pixmap);
340         XSetFillStyle(display, jp->stippledGC, FillOpaqueStippled);
341 #endif /* HAVE_COCOA */
342         XDrawArc(display, window, jp->stippledGC, 
343              new_circle.x-jp->circsize/2,
344              new_circle.y-jp->circsize/2,
345              jp->circsize, jp->circsize,
346              0, 360*64);
347
348         if (jp->erase == 1) {
349                 XDrawPoints(display, window, gc,
350                     jp->pointBuffer[jp->buffer], numpoints, CoordModeOrigin);
351         }
352         jp->inc++;
353         if (MI_NPIXELS(mi) > 2) {
354                 XSetForeground(display, gc, MI_PIXEL(mi, jp->pix));
355                 if (++jp->pix >= MI_NPIXELS(mi))
356                         jp->pix = 0;
357         } else
358                 XSetForeground(display, gc, MI_WIN_WHITE_PIXEL(mi));
359         while (k--) {
360
361                 /* save calls to LRAND by using bit shifts over and over on the same
362                    int for 32 iterations, then get a new random int */
363                 if (!(k % 32))
364                         rnd = LRAND();
365
366                 /* complex sqrt: x^0.5 = radius^0.5*(cos(theta/2) + i*sin(theta/2)) */
367
368                 xi -= jp->ci;
369                 xr -= jp->cr;
370
371                 /* Avoid atan2: DOMAIN error message */
372                 if (xi == 0.0 && xr == 0.0)
373                         theta = 0.0;
374                 else
375                         theta = atan2(xi, xr) / 2.0;
376
377                 /*r = pow(xi * xi + xr * xr, 0.25); */
378                 r = sqrt(sqrt(xi * xi + xr * xr));      /* 3 times faster */
379
380                 xr = r * cos(theta);
381                 xi = r * sin(theta);
382
383                 if ((rnd >> (k % 32)) & 0x1) {
384                         xi = -xi;
385                         xr = -xr;
386                 }
387                 xp->x = jp->centerx + (int) ((jp->centerx >> 1) * xr);
388                 xp->y = jp->centery + (int) ((jp->centery >> 1) * xi);
389                 xp++;
390         }
391
392         jp->itree = 0;
393         apply(jp, xr, xi, jp->depth);
394
395         XDrawPoints(display, window, gc,
396                     jp->pointBuffer[jp->buffer], numpoints, CoordModeOrigin);
397
398         jp->buffer++;
399         if (jp->buffer > jp->nbuffers - 1) {
400                 jp->buffer -= jp->nbuffers;
401                 jp->erase = 1;
402         }
403         if (jp->redrawing) {
404                 for (i = 0; i < REDRAWSTEP; i++) {
405                         j = (jp->buffer - jp->redrawpos + jp->nbuffers) % jp->nbuffers;
406                         XDrawPoints(display, window, gc,
407                              jp->pointBuffer[j], numpoints, CoordModeOrigin);
408
409                         if (++(jp->redrawpos) >= jp->nbuffers) {
410                                 jp->redrawing = 0;
411                                 break;
412                         }
413                 }
414         }
415 }
416
417 ENTRYPOINT void
418 release_julia (ModeInfo * mi)
419 {
420         if (julias != NULL) {
421                 int         screen;
422
423                 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
424                         Display    *display = MI_DISPLAY(mi);
425                         juliastruct *jp = &julias[screen];
426                         int         buffer;
427
428                         if (jp->pointBuffer) {
429                                 for (buffer = 0; buffer < jp->nbuffers; buffer++)
430                                         if (jp->pointBuffer[buffer])
431                                                 (void) free((void *) jp->pointBuffer[buffer]);
432                                 (void) free((void *) jp->pointBuffer);
433                         }
434                         if (jp->stippledGC != None)
435                                 XFreeGC(display, jp->stippledGC);
436                         if (jp->pixmap != None)
437                                 XFreePixmap(display, jp->pixmap);
438 #ifndef HAVE_COCOA
439                         if (jp->cursor)
440                           XFreeCursor (display, jp->cursor);
441 #endif
442                 }
443                 (void) free((void *) julias);
444                 julias = NULL;
445         }
446 }
447
448 ENTRYPOINT void
449 refresh_julia (ModeInfo * mi)
450 {
451         juliastruct *jp = &julias[MI_SCREEN(mi)];
452
453         jp->redrawing = 1;
454         jp->redrawpos = 0;
455 }
456
457 XSCREENSAVER_MODULE ("Julia", julia)